From 1dc7ab04b2aeb4a42220e13e8ee49a1e29e61223 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Tue, 16 Sep 2014 10:54:54 -0700 Subject: [PATCH 1/3] Add implementation of getNavigateToItems based on the new compiler --- src/compiler/types.ts | 3 +- src/services/services.ts | 195 ++++++++++++++++-- .../navigationItemsExactMatch.ts | 8 +- .../navigationItemsExactMatch2.ts | 2 +- ...navigationItemsInConstructorsExactMatch.ts | 0 .../navigationItemsPrefixMatch.ts | 6 +- .../navigationItemsPrefixMatch2.ts | 2 +- .../navigationItemsSubStringMatch.ts | 4 +- .../navigationItemsSubStringMatch2.ts | 2 +- 9 files changed, 193 insertions(+), 29 deletions(-) rename tests/cases/{fourslash_old => fourslash}/navigationItemsExactMatch.ts (76%) rename tests/cases/{fourslash_old => fourslash}/navigationItemsExactMatch2.ts (93%) rename tests/cases/{fourslash_old => fourslash}/navigationItemsInConstructorsExactMatch.ts (100%) rename tests/cases/{fourslash_old => fourslash}/navigationItemsPrefixMatch.ts (80%) rename tests/cases/{fourslash_old => fourslash}/navigationItemsPrefixMatch2.ts (94%) rename tests/cases/{fourslash_old => fourslash}/navigationItemsSubStringMatch.ts (83%) rename tests/cases/{fourslash_old => fourslash}/navigationItemsSubStringMatch2.ts (97%) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index bb8a106a81..011e28ed9a 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -237,7 +237,8 @@ module ts { Synthetic = 0x00000100, // Synthetic node (for full fidelity) DeclarationFile = 0x00000200, // Node is a .d.ts file - Modifier = Export | Ambient | Public | Private | Static + Modifier = Export | Ambient | Public | Private | Static, + AccessibilityModifier = Public | Private } export interface Node extends TextRange { diff --git a/src/services/services.ts b/src/services/services.ts index d51eeee97c..6049052a9c 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -70,6 +70,7 @@ module ts { getSourceUnit(): TypeScript.SourceUnitSyntax; getSyntaxTree(): TypeScript.SyntaxTree; getScriptSnapshot(): TypeScript.IScriptSnapshot; + getNamedDeclarations(): Declaration[]; update(scriptSnapshot: TypeScript.IScriptSnapshot, version: string, isOpen: boolean, textChangeRange: TypeScript.TextChangeRange): SourceFile; } @@ -326,6 +327,7 @@ module ts { private syntaxTree: TypeScript.SyntaxTree; private scriptSnapshot: TypeScript.IScriptSnapshot; + private namedDeclarations: Declaration[]; public getSourceUnit(): TypeScript.SourceUnitSyntax { // If we don't have a script, create one from our parse tree. @@ -340,6 +342,59 @@ module ts { return this.getSyntaxTree().lineMap(); } + public getNamedDeclarations() { + if (!this.namedDeclarations) { + var sourceFile = this; + var namedDeclarations: Declaration[] = []; + var isExternalModule = ts.isExternalModule(sourceFile); + + forEachChild(sourceFile, function visit(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.ImportDeclaration: + case SyntaxKind.Method: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.Constructor: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.TypeLiteral: + if ((node).name) { + namedDeclarations.push(node); + } + forEachChild(node, visit); + break; + + case SyntaxKind.VariableStatement: + case SyntaxKind.ModuleBlock: + case SyntaxKind.FunctionBlock: + forEachChild(node, visit); + break; + + case SyntaxKind.Parameter: + if (!(node.flags & NodeFlags.AccessibilityModifier)) { + // Only consider properties defined as constructor parameters + break; + } + case SyntaxKind.VariableDeclaration: + case SyntaxKind.EnumMember: + case SyntaxKind.Property: + namedDeclarations.push(node); + break; + } + + // do not go any deeper + return undefined; + }); + + this.namedDeclarations = namedDeclarations; + } + + return this.namedDeclarations; + } + public getSyntaxTree(): TypeScript.SyntaxTree { if (!this.syntaxTree) { var start = new Date().getTime(); @@ -820,11 +875,11 @@ module ts { static staticModifier = "static"; } - export class MatchKind { - static none: string = null; - static exact = "exact"; - static subString = "substring"; - static prefix = "prefix"; + enum MatchKind { + none = 0, + exact = 1, + substring = 2, + prefix = 3 } interface IncrementalParse { @@ -1983,6 +2038,29 @@ module ts { return ScriptElementKind.unknown; } + function getNodeKind(node: Node): string { + switch (node.kind) { + case SyntaxKind.ModuleDeclaration: return ScriptElementKind.moduleElement; + case SyntaxKind.ClassDeclaration: return ScriptElementKind.classElement; + case SyntaxKind.InterfaceDeclaration: return ScriptElementKind.interfaceElement; + case SyntaxKind.EnumDeclaration: return ScriptElementKind.enumElement; + case SyntaxKind.VariableDeclaration: return ScriptElementKind.variableElement; + case SyntaxKind.FunctionDeclaration: return ScriptElementKind.functionElement; + case SyntaxKind.GetAccessor: return ScriptElementKind.memberGetAccessorElement; + case SyntaxKind.SetAccessor: return ScriptElementKind.memberSetAccessorElement; + case SyntaxKind.Method: return ScriptElementKind.memberFunctionElement; + case SyntaxKind.Property: return ScriptElementKind.memberVariableElement; + case SyntaxKind.IndexSignature: return ScriptElementKind.indexSignatureElement; + case SyntaxKind.ConstructSignature: return ScriptElementKind.constructSignatureElement; + case SyntaxKind.CallSignature: return ScriptElementKind.callSignatureElement; + case SyntaxKind.Constructor: return ScriptElementKind.constructorImplementationElement; + case SyntaxKind.TypeParameter: return ScriptElementKind.typeParameterElement; + case SyntaxKind.EnumMember: return ScriptElementKind.variableElement; + case SyntaxKind.Parameter: return (node.flags & NodeFlags.AccessibilityModifier) ? ScriptElementKind.memberVariableElement : ScriptElementKind.parameterElement; + return ScriptElementKind.unknown; + } + } + function getNodeModifiers(node: Node): string { var flags = node.flags; var result: string[] = []; @@ -2134,6 +2212,7 @@ module ts { return result; } + /// References and Occurances function getOccurrencesAtPosition(filename: string, position: number): ReferenceEntry[] { synchronizeHostData(); @@ -2612,9 +2691,10 @@ module ts { return false; } - /// Search within node "container" for references for a search value, where the search value is defined as a - /// tuple of(searchSymbol, searchText, searchLocation, and searchMeaning). - /// searchLocation: a node where the search value + /** Search within node "container" for references for a search value, where the search value is defined as a + * tuple of(searchSymbol, searchText, searchLocation, and searchMeaning). + * searchLocation: a node where the search value + */ function getReferencesInNode(container: Node, searchSymbol: Symbol, searchText: string, searchLocation: Node, searchMeaning: SearchMeaning, result: ReferenceEntry[]): void { var sourceFile = container.getSourceFile(); @@ -2980,12 +3060,13 @@ module ts { } } - /// Given an initial searchMeaning, extracted from a location, widen the search scope based on the declarations - /// of the corresponding symbol. e.g. if we are searching for "Foo" in value position, but "Foo" references a class - /// then we need to widen the search to include type positions as well. - /// On the contrary, if we are searching for "Bar" in type position and we trace bar to an interface, and an uninstantiated - /// module, we want to keep the search limited to only types, as the two declarations (interface and uninstantiated module) - /// do not intersect in any of the three spaces. + /** Given an initial searchMeaning, extracted from a location, widen the search scope based on the declarations + * of the corresponding symbol. e.g. if we are searching for "Foo" in value position, but "Foo" references a class + * then we need to widen the search to include type positions as well. + * On the contrary, if we are searching for "Bar" in type position and we trace bar to an interface, and an uninstantiated + * module, we want to keep the search limited to only types, as the two declarations (interface and uninstantiated module) + * do not intersect in any of the three spaces. + */ function getIntersectingMeaningFromDeclarations(meaning: SearchMeaning, declarations: Declaration[]): SearchMeaning { if (declarations) { do { @@ -3022,7 +3103,7 @@ module ts { return new ReferenceEntry(node.getSourceFile().filename, TypeScript.TextSpan.fromBounds(start, end), isWriteAccess(node)); } - /// A node is considedered a writeAccess iff it is a name of a declaration or a target of an assignment + /** A node is considedered a writeAccess iff it is a name of a declaration or a target of an assignment */ function isWriteAccess(node: Node): boolean { if (node.kind === SyntaxKind.Identifier && isDeclarationOrFunctionExpressionOrCatchVariableName(node)) { return true; @@ -3042,6 +3123,88 @@ module ts { return false; } + /// NavigateTo + function getNavigateToItems(searchValue: string): NavigateToItem[] { + synchronizeHostData(); + + // Split search value in terms array + var terms = searchValue.split(" "); + + // default NavigateTo approach: if search term contains only lower-case chars - use case-insensitive search, otherwise switch to case-sensitive version + var searchTerms = map(terms, t => ({ caseSensitive: hasAnyUpperCaseCharacter(t), term: t })); + + var items: NavigateToItem[] = []; + + // Search the declarations in all files and output matched NavigateToItem into array of NavigateToItem[] + forEach(program.getSourceFiles(), sourceFile => { + cancellationToken.throwIfCancellationRequested(); + + var filename = sourceFile.filename; + var declarations = sourceFile.getNamedDeclarations(); + for (var i = 0, n = declarations.length; i < n; i++) { + var declaration = declarations[i]; + var name = declaration.name.text; + var matchKind = getMatchKind(searchTerms, name); + if (matchKind !== MatchKind.none) { + var container = getContainerNode(declaration); + items.push({ + name: name, + kind: getNodeKind(declaration), + kindModifiers: getNodeModifiers(declaration), + matchKind: MatchKind[matchKind], + fileName: filename, + textSpan: TypeScript.TextSpan.fromBounds(declaration.getStart(), declaration.getEnd()), + containerName: container.name ? container.name.text : "", + containerKind: container.name ? getNodeKind(container) : "" + }); + } + } + }); + + return items; + + function hasAnyUpperCaseCharacter(s: string): boolean { + for (var i = 0; i < s.length; ++i) { + if (s.charAt(i).toLocaleLowerCase() !== s.charAt(i)) { + return true; + } + } + + return false; + } + + function getMatchKind(searchTerms: { caseSensitive: boolean; term: string }[], name: string): MatchKind { + var matchKind = MatchKind.none; + + if (name) { + for (var j = 0, n = searchTerms.length; j < n; j++) { + var searchTerm = searchTerms[j]; + var nameToSearch = searchTerm.caseSensitive ? name : name.toLocaleLowerCase(); + // in case of case-insensitive search searchTerm.term will already be lower-cased + var index = nameToSearch.indexOf(searchTerm.term); + if (index < 0) { + // Didn't match. + return MatchKind.none; + } + + var termKind = MatchKind.substring; + if (index === 0) { + // here we know that match occur at the beginning of the string. + // if search term and declName has the same length - we have an exact match, otherwise declName have longer length and this will be prefix match + termKind = name.length === searchTerm.term.length ? MatchKind.exact : MatchKind.prefix; + } + + // Update our match kind if we don't have one, or if this match is better. + if (matchKind === MatchKind.none || termKind < matchKind) { + matchKind = termKind; + } + } + } + + return matchKind; + } + } + /// Syntactic features function getSyntaxTree(filename: string): TypeScript.SyntaxTree { filename = TypeScript.switchToForwardSlashes(filename); @@ -3382,7 +3545,7 @@ module ts { getImplementorsAtPosition: (filename, position) => [], getNameOrDottedNameSpan: getNameOrDottedNameSpan, getBreakpointStatementAtPosition: getBreakpointStatementAtPosition, - getNavigateToItems: (searchValue) => [], + getNavigateToItems: getNavigateToItems, getRenameInfo: (fileName, position): RenameInfo => RenameInfo.CreateError(getLocaleSpecificMessage(Diagnostics.You_cannot_rename_this_element.key)), getNavigationBarItems: getNavigationBarItems, getOutliningSpans: getOutliningSpans, diff --git a/tests/cases/fourslash_old/navigationItemsExactMatch.ts b/tests/cases/fourslash/navigationItemsExactMatch.ts similarity index 76% rename from tests/cases/fourslash_old/navigationItemsExactMatch.ts rename to tests/cases/fourslash/navigationItemsExactMatch.ts index e388be7f7b..9658c52324 100644 --- a/tests/cases/fourslash_old/navigationItemsExactMatch.ts +++ b/tests/cases/fourslash/navigationItemsExactMatch.ts @@ -6,17 +6,17 @@ //// // Class //// {| "itemName": "Point", "kind": "class", "parentName": "Shapes" |}export class Point { //// // Instance member -//// {| "itemName": "origin", "kind": "property", "parentName": "Shapes.Point", "matchKind": "exact"|}private origin = 0.0; +//// {| "itemName": "origin", "kind": "property", "parentName": "Point", "matchKind": "exact"|}private origin = 0.0; //// -//// {| "itemName": "distFromZero", "kind": "property", "parentName": "Shapes.Point", "matchKind": "exact"|}private distFromZero = 0.0; +//// {| "itemName": "distFromZero", "kind": "property", "parentName": "Point", "matchKind": "exact"|}private distFromZero = 0.0; //// //// // Getter -//// {| "itemName": "distance", "kind": "getter", "parentName": "Shapes.Point", "matchKind": "exact" |}get distance(): number { return 0; } +//// {| "itemName": "distance", "kind": "getter", "parentName": "Point", "matchKind": "exact" |}get distance(): number { return 0; } //// } ////} //// ////// Local variables -////{| "itemName": "point", "kind": "var", "parentName": "", "matchKind": "exact" |}var point = new Shapes.Point(); +////{| "itemName": "point", "kind": "var", "parentName": "", "matchKind": "exact"|}var point = new Shapes.Point(); //// Testing for exact matching of navigationItems diff --git a/tests/cases/fourslash_old/navigationItemsExactMatch2.ts b/tests/cases/fourslash/navigationItemsExactMatch2.ts similarity index 93% rename from tests/cases/fourslash_old/navigationItemsExactMatch2.ts rename to tests/cases/fourslash/navigationItemsExactMatch2.ts index 5bdf8d87e8..8f934dcc3c 100644 --- a/tests/cases/fourslash_old/navigationItemsExactMatch2.ts +++ b/tests/cases/fourslash/navigationItemsExactMatch2.ts @@ -20,7 +20,7 @@ goTo.marker("file1"); verify.navigationItemsListCount(2, "point", "exact"); -verify.navigationItemsListCount(3, "distance", "prefix"); +verify.navigationItemsListCount(5, "distance", "prefix"); verify.navigationItemsListCount(1, "origin", "substring"); verify.navigationItemsListCount(0, "square", "exact"); diff --git a/tests/cases/fourslash_old/navigationItemsInConstructorsExactMatch.ts b/tests/cases/fourslash/navigationItemsInConstructorsExactMatch.ts similarity index 100% rename from tests/cases/fourslash_old/navigationItemsInConstructorsExactMatch.ts rename to tests/cases/fourslash/navigationItemsInConstructorsExactMatch.ts diff --git a/tests/cases/fourslash_old/navigationItemsPrefixMatch.ts b/tests/cases/fourslash/navigationItemsPrefixMatch.ts similarity index 80% rename from tests/cases/fourslash_old/navigationItemsPrefixMatch.ts rename to tests/cases/fourslash/navigationItemsPrefixMatch.ts index d528ca70a8..b6ae4af0f3 100644 --- a/tests/cases/fourslash_old/navigationItemsPrefixMatch.ts +++ b/tests/cases/fourslash/navigationItemsPrefixMatch.ts @@ -6,12 +6,12 @@ //// // Class //// {| "itemName": "Point", "kind": "class", "parentName": "Shapes" |}export class Point { //// // Instance member -//// {| "itemName": "originality", "kind": "property", "parentName": "Shapes.Point", "matchKind": "prefix"|}private originality = 0.0; +//// {| "itemName": "originality", "kind": "property", "parentName": "Point", "matchKind": "prefix"|}private originality = 0.0; //// -//// {| "itemName": "distanceFromOrig", "kind": "property", "parentName": "Shapes.Point", "matchKind": "prefix"|}private distanceFromOrig = 0.0; +//// {| "itemName": "distanceFromOrig", "kind": "property", "parentName": "Point", "matchKind": "prefix"|}private distanceFromOrig = 0.0; //// //// // Getter -//// {| "itemName": "distanceFarFarAway", "kind": "getter", "parentName": "Shapes.Point", "matchKind": "prefix" |}get distanceFarFarAway(): number { return 0; } +//// {| "itemName": "distanceFarFarAway", "kind": "getter", "parentName": "Point", "matchKind": "prefix" |}get distanceFarFarAway(): number { return 0; } //// } ////} //// diff --git a/tests/cases/fourslash_old/navigationItemsPrefixMatch2.ts b/tests/cases/fourslash/navigationItemsPrefixMatch2.ts similarity index 94% rename from tests/cases/fourslash_old/navigationItemsPrefixMatch2.ts rename to tests/cases/fourslash/navigationItemsPrefixMatch2.ts index aa6ce2814b..282d0e2414 100644 --- a/tests/cases/fourslash_old/navigationItemsPrefixMatch2.ts +++ b/tests/cases/fourslash/navigationItemsPrefixMatch2.ts @@ -25,7 +25,7 @@ var notFoundSearchValue = "mPointThatIJustInitiated wrongKeyWord"; goTo.marker("file1"); verify.navigationItemsListCount(3, "origin", "prefix"); -verify.navigationItemsListCount(2, "distance", "prefix"); +verify.navigationItemsListCount(3, "distance", "prefix"); verify.navigationItemsListCount(0, notFoundSearchValue, "exact"); verify.navigationItemsListCount(0, notFoundSearchValue, "prefix"); diff --git a/tests/cases/fourslash_old/navigationItemsSubStringMatch.ts b/tests/cases/fourslash/navigationItemsSubStringMatch.ts similarity index 83% rename from tests/cases/fourslash_old/navigationItemsSubStringMatch.ts rename to tests/cases/fourslash/navigationItemsSubStringMatch.ts index d5e359e1d8..8dc42a2365 100644 --- a/tests/cases/fourslash_old/navigationItemsSubStringMatch.ts +++ b/tests/cases/fourslash/navigationItemsSubStringMatch.ts @@ -6,10 +6,10 @@ //// // Class //// {| "itemName": "Point", "kind": "class", "parentName": "Shapes", "matchKind": "substring" |}export class Point { //// // Instance member -//// {| "itemName": "originPointAttheHorizon", "kind": "property", "parentName": "Shapes.Point", "matchKind": "substring"|}private originPointAttheHorizon = 0.0; +//// {| "itemName": "originPointAttheHorizon", "kind": "property", "parentName": "Point", "matchKind": "substring"|}private originPointAttheHorizon = 0.0; //// //// // Getter -//// {| "itemName": "distanceFromOrigin", "kind": "getter", "parentName": "Shapes.Point", "matchKind": "substring" |}get distanceFromOrigin(): number { return 0; } +//// {| "itemName": "distanceFromOrigin", "kind": "getter", "parentName": "Point", "matchKind": "substring" |}get distanceFromOrigin(): number { return 0; } //// //// } ////} diff --git a/tests/cases/fourslash_old/navigationItemsSubStringMatch2.ts b/tests/cases/fourslash/navigationItemsSubStringMatch2.ts similarity index 97% rename from tests/cases/fourslash_old/navigationItemsSubStringMatch2.ts rename to tests/cases/fourslash/navigationItemsSubStringMatch2.ts index d6dc9fe5e0..c1afd7326c 100644 --- a/tests/cases/fourslash_old/navigationItemsSubStringMatch2.ts +++ b/tests/cases/fourslash/navigationItemsSubStringMatch2.ts @@ -14,7 +14,7 @@ //// ////var myPointThatIJustInitiated = new Shapes.Point(); ////interface IDistance{ -//// var INITIATED123; +//// INITIATED123; //// public horizon(): void; ////} From 706ca8eae9a85194cc3029d03f9b2162be62923a Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Tue, 16 Sep 2014 10:55:47 -0700 Subject: [PATCH 2/3] use NodeFlags.AccessibilityModifier in places where we need NodeFlags.Private | NodeFlags.Public --- src/compiler/checker.ts | 3 +-- src/compiler/emitter.ts | 4 ++-- src/compiler/parser.ts | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b99d6d27d2..252f31a498 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5211,8 +5211,7 @@ module ts { var otherKind = node.kind === SyntaxKind.GetAccessor ? SyntaxKind.SetAccessor : SyntaxKind.GetAccessor; var otherAccessor = getDeclarationOfKind(node.symbol, otherKind); if (otherAccessor) { - var visibilityFlags = NodeFlags.Private | NodeFlags.Public; - if (((node.flags & visibilityFlags) !== (otherAccessor.flags & visibilityFlags))) { + if (((node.flags & NodeFlags.AccessibilityModifier) !== (otherAccessor.flags & NodeFlags.AccessibilityModifier))) { error(node.name, Diagnostics.Getter_and_setter_accessors_do_not_agree_in_visibility); } diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 317db201f3..a2de8c19c4 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1431,7 +1431,7 @@ module ts { function emitParameterPropertyAssignments(node: ConstructorDeclaration) { forEach(node.parameters, param => { - if (param.flags & (NodeFlags.Public | NodeFlags.Private)) { + if (param.flags & NodeFlags.AccessibilityModifier) { writeLine(); emitStart(param); emitStart(param.name); @@ -2630,7 +2630,7 @@ module ts { function emitParameterProperties(constructorDeclaration: ConstructorDeclaration) { if (constructorDeclaration) { forEach(constructorDeclaration.parameters, param => { - if (param.flags & (NodeFlags.Public | NodeFlags.Private)) { + if (param.flags & NodeFlags.AccessibilityModifier) { emitPropertyDeclaration(param); } }); diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 25ca24ae61..06c215412e 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -3213,7 +3213,7 @@ module ts { switch (modifierToken) { case SyntaxKind.PublicKeyword: - if (flags & NodeFlags.Private || flags & NodeFlags.Public) { + if (flags & NodeFlags.AccessibilityModifier) { grammarErrorAtPos(modifierStart, modifierLength, Diagnostics.Accessibility_modifier_already_seen); } else if (flags & NodeFlags.Static) { @@ -3226,7 +3226,7 @@ module ts { break; case SyntaxKind.PrivateKeyword: - if (flags & NodeFlags.Private || flags & NodeFlags.Public) { + if (flags & NodeFlags.AccessibilityModifier) { grammarErrorAtPos(modifierStart, modifierLength, Diagnostics.Accessibility_modifier_already_seen); } else if (flags & NodeFlags.Static) { From d8d1ed399c8a8138bd9f3f4d8b1133148834f214 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Tue, 16 Sep 2014 14:27:05 -0700 Subject: [PATCH 3/3] Respond to code review comments --- src/services/services.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 6049052a9c..5aa155d603 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -3164,8 +3164,10 @@ module ts { return items; function hasAnyUpperCaseCharacter(s: string): boolean { - for (var i = 0; i < s.length; ++i) { - if (s.charAt(i).toLocaleLowerCase() !== s.charAt(i)) { + for (var i = 0, n = s.length; i < n; i++) { + var c = s.charCodeAt(i); + if ((CharacterCodes.A <= c && c <= CharacterCodes.Z) || + (c >= CharacterCodes.maxAsciiCharacter && s.charAt(i).toLocaleLowerCase() !== s.charAt(i))) { return true; } }