From 4b271d2d2301225d318991a0153b2d842aeb2d91 Mon Sep 17 00:00:00 2001 From: Cyrus Najmabadi Date: Fri, 20 Feb 2015 16:28:33 -0800 Subject: [PATCH] Address code review feedback. --- Jakefile | 1 + src/services/navigateTo.ts | 105 ++++++++++++++++++ src/services/services.ts | 211 ++++++++++--------------------------- 3 files changed, 164 insertions(+), 153 deletions(-) create mode 100644 src/services/navigateTo.ts diff --git a/Jakefile b/Jakefile index 7b17a83a579..acbc29b808e 100644 --- a/Jakefile +++ b/Jakefile @@ -65,6 +65,7 @@ var servicesSources = [ return path.join(compilerDirectory, f); }).concat([ "breakpoints.ts", + "navigateTo.ts", "navigationBar.ts", "outliningElementsCollector.ts", "services.ts", diff --git a/src/services/navigateTo.ts b/src/services/navigateTo.ts new file mode 100644 index 00000000000..059259eec9d --- /dev/null +++ b/src/services/navigateTo.ts @@ -0,0 +1,105 @@ +module ts.NavigateTo { + type RawNavigateToItem = { name: string; fileName: string; matchKind: MatchKind; declaration: Declaration }; + + enum MatchKind { + none = 0, + exact = 1, + substring = 2, + prefix = 3 + } + + export function getNavigateToItems(program: Program, cancellationToken: CancellationTokenObject, searchValue: string, maxResultCount: number): NavigateToItem[]{ + // 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 rawItems: RawNavigateToItem[] = []; + + // 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]; + // TODO(jfreeman): Skip this declaration if it has a computed name + var name = (declaration.name).text; + var matchKind = getMatchKind(searchTerms, name); + if (matchKind !== MatchKind.none) { + rawItems.push({ name, fileName, matchKind, declaration }); + } + } + }); + + rawItems.sort((i1, i2) => i1.matchKind - i2.matchKind); + if (maxResultCount !== undefined) { + rawItems = rawItems.slice(0, maxResultCount); + } + + var items = map(rawItems, createNavigateToItem); + + return items; + + function createNavigateToItem(rawItem: RawNavigateToItem): NavigateToItem { + var declaration = rawItem.declaration; + var container = getContainerNode(declaration); + return { + name: rawItem.name, + kind: getNodeKind(declaration), + kindModifiers: getNodeModifiers(declaration), + matchKind: MatchKind[rawItem.matchKind], + fileName: rawItem.fileName, + textSpan: createTextSpanFromBounds(declaration.getStart(), declaration.getEnd()), + // TODO(jfreeman): What should be the containerName when the container has a computed name? + containerName: container && container.name ? (container.name).text : "", + containerKind: container && container.name ? getNodeKind(container) : "" + }; + } + + function hasAnyUpperCaseCharacter(s: string): boolean { + 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; + } + } + + 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; + } + } +} \ No newline at end of file diff --git a/src/services/services.ts b/src/services/services.ts index 76c2bd80984..7c77b8c5cd0 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2,6 +2,7 @@ /// /// +/// /// /// /// @@ -1378,13 +1379,6 @@ module ts { public static typeAlias = "type alias name"; } - enum MatchKind { - none = 0, - exact = 1, - prefix = 2, - substring = 3, - } - /// Language Service interface CompletionSession { @@ -1992,6 +1986,62 @@ module ts { }); } + /* @internal */ export function getContainerNode(node: Node): Node { + while (true) { + node = node.parent; + if (!node) { + return undefined; + } + switch (node.kind) { + case SyntaxKind.SourceFile: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.ModuleDeclaration: + return node; + } + } + } + + /* @internal */ export 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.TypeAliasDeclaration: return ScriptElementKind.typeElement; + case SyntaxKind.EnumDeclaration: return ScriptElementKind.enumElement; + case SyntaxKind.VariableDeclaration: + return isConst(node) + ? ScriptElementKind.constElement + : isLet(node) + ? ScriptElementKind.letElement + : ScriptElementKind.variableElement; + case SyntaxKind.FunctionDeclaration: return ScriptElementKind.functionElement; + case SyntaxKind.GetAccessor: return ScriptElementKind.memberGetAccessorElement; + case SyntaxKind.SetAccessor: return ScriptElementKind.memberSetAccessorElement; + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + return ScriptElementKind.memberFunctionElement; + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + 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; + } + export function createLanguageService(host: LanguageServiceHost, documentRegistry: DocumentRegistry = createDocumentRegistry()): LanguageService { var syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host); var ruleProvider: formatting.RulesProvider; @@ -2722,29 +2772,6 @@ module ts { } } - function getContainerNode(node: Node): Node { - while (true) { - node = node.parent; - if (!node) { - return undefined; - } - switch (node.kind) { - case SyntaxKind.SourceFile: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.ModuleDeclaration: - return node; - } - } - } - // TODO(drosen): use contextual SemanticMeaning. function getSymbolKind(symbol: Symbol, typeResolver: TypeChecker, location: Node): string { var flags = symbol.getFlags(); @@ -2831,39 +2858,6 @@ 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.TypeAliasDeclaration: return ScriptElementKind.typeElement; - case SyntaxKind.EnumDeclaration: return ScriptElementKind.enumElement; - case SyntaxKind.VariableDeclaration: - return isConst(node) - ? ScriptElementKind.constElement - : isLet(node) - ? ScriptElementKind.letElement - : ScriptElementKind.variableElement; - case SyntaxKind.FunctionDeclaration: return ScriptElementKind.functionElement; - case SyntaxKind.GetAccessor: return ScriptElementKind.memberGetAccessorElement; - case SyntaxKind.SetAccessor: return ScriptElementKind.memberSetAccessorElement; - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - return ScriptElementKind.memberFunctionElement; - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - 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 getSymbolModifiers(symbol: Symbol): string { return symbol && symbol.declarations && symbol.declarations.length > 0 ? getNodeModifiers(symbol.declarations[0]) @@ -4666,96 +4660,7 @@ module ts { function getNavigateToItems(searchValue: string, maxResultCount?: number): 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 rawItems: { name: string; fileName: string; matchKind: MatchKind; declaration: Declaration }[] = []; - - // 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]; - // TODO(jfreeman): Skip this declaration if it has a computed name - var name = (declaration.name).text; - var matchKind = getMatchKind(searchTerms, name); - if (matchKind !== MatchKind.none) { - rawItems.push({ name, fileName, matchKind, declaration }); - } - } - }); - - rawItems.sort((i1, i2) => i1.matchKind - i2.matchKind); - if (maxResultCount !== undefined) { - rawItems = rawItems.slice(0, maxResultCount); - } - - var items = map(rawItems, i => { - var declaration = i.declaration; - var container = getContainerNode(declaration); - return { - name: i.name, - kind: getNodeKind(declaration), - kindModifiers: getNodeModifiers(declaration), - matchKind: MatchKind[i.matchKind], - fileName: i.fileName, - textSpan: createTextSpanFromBounds(declaration.getStart(), declaration.getEnd()), - // TODO(jfreeman): What should be the containerName when the container has a computed name? - containerName: container && container.name ? (container.name).text : "", - containerKind: container && container.name ? getNodeKind(container) : "" - }; - }); - - return items; - - function hasAnyUpperCaseCharacter(s: string): boolean { - 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; - } - } - - 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; - } + return ts.NavigateTo.getNavigateToItems(program, cancellationToken, searchValue, maxResultCount); } function containErrors(diagnostics: Diagnostic[]): boolean {