From 11b72913932acee42b2c20a64c62763640b128eb Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Fri, 3 Jun 2016 08:23:52 -0700 Subject: [PATCH 01/11] Refactor navigation bar --- src/compiler/core.ts | 11 + src/services/navigationBar.ts | 1178 ++++++----------- src/services/services.ts | 2 +- .../navbar_contains-no-duplicates.ts | 8 +- tests/cases/fourslash/navbar_exportDefault.ts | 16 +- ...BarAnonymousClassAndFunctionExpressions.ts | 147 ++ .../navigationBarDeclarationExpressions.ts | 64 + .../fourslash/navigationBarGetterAndSetter.ts | 48 + .../fourslash/navigationBarItemsFunctions.ts | 14 + .../navigationBarItemsFunctionsBroken.ts | 6 + .../navigationBarItemsFunctionsBroken2.ts | 10 + ...ionBarItemsInsideMethodsAndConstructors.ts | 12 +- .../fourslash/navigationBarItemsItems2.ts | 7 +- ...rItemsItemsContainsNoAnonymousFunctions.ts | 80 -- .../navigationBarItemsMissingName1.ts | 5 + .../navigationBarItemsMissingName2.ts | 10 +- .../fourslash/navigationBarItemsModules.ts | 196 +-- ...ationBarItemsMultilineStringIdentifiers.ts | 56 +- tests/cases/fourslash/navigationBarMerging.ts | 189 +++ .../cases/fourslash/navigationBarVariables.ts | 26 + .../server/jsdocTypedefTagNavigateTo.ts | 46 +- 21 files changed, 1147 insertions(+), 984 deletions(-) create mode 100644 tests/cases/fourslash/navigationBarAnonymousClassAndFunctionExpressions.ts create mode 100644 tests/cases/fourslash/navigationBarDeclarationExpressions.ts create mode 100644 tests/cases/fourslash/navigationBarGetterAndSetter.ts delete mode 100644 tests/cases/fourslash/navigationBarItemsItemsContainsNoAnonymousFunctions.ts create mode 100644 tests/cases/fourslash/navigationBarMerging.ts create mode 100644 tests/cases/fourslash/navigationBarVariables.ts diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 79b6249539dbc..d9924cf9e5e54 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -137,6 +137,17 @@ namespace ts { } return result; } + + export function filterMutate(array: T[], f: (x: T) => boolean): void { + let outIndex = 0; + for (const item of array) { + if (f(item)) { + array[outIndex] = item; + outIndex++; + } + } + array.length = outIndex; + } export function map(array: T[], f: (x: T) => U): U[] { let result: U[]; diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index 92cf64eac947d..de70015d95b10 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -2,856 +2,540 @@ /* @internal */ namespace ts.NavigationBar { - export function getNavigationBarItems(sourceFile: SourceFile, compilerOptions: CompilerOptions): ts.NavigationBarItem[] { - // TODO: Handle JS files differently in 'navbar' calls for now, but ideally we should unify - // the 'navbar' and 'navto' logic for TypeScript and JavaScript. - if (isSourceFileJavaScript(sourceFile)) { - return getJsNavigationBarItems(sourceFile, compilerOptions); - } - - return getItemsWorker(getTopLevelNodes(sourceFile), createTopLevelItem); - - function getIndent(node: Node): number { - let indent = 1; // Global node is the only one with indent 0. - - let current = node.parent; - while (current) { - switch (current.kind) { - case SyntaxKind.ModuleDeclaration: - // If we have a module declared as A.B.C, it is more "intuitive" - // to say it only has a single layer of depth - do { - current = current.parent; - } - while (current.kind === SyntaxKind.ModuleDeclaration); - - // fall through - case SyntaxKind.ClassDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.FunctionDeclaration: - indent++; - } + export function getNavigationBarItems(sourceFile: SourceFile): NavigationBarItem[] { + const root = createNavNode(undefined, sourceFile); + return map(topLevelItems(root), convertToTopLevelItem); + } - current = current.parent; - } + /** + * Represents a navBar item and its children. + * The returned NavigationBarItem is more complicated and doesn't include 'parent', so we use these to do work before converting. + */ + interface NavNode { + node: Node; + additionalNodes: Node[]; // May be missing + parent: NavNode; // Missing for root decl + children: NavNode[]; + indent: number; // # of parents + } + function navKind(n: NavNode): SyntaxKind { + return n.node.kind; + } + function navModifiers(n: NavNode): string { + return getNodeModifiers(n.node); + } - return indent; + /** Creates a child node and adds it to parent. */ + function createNavNode(parent: NavNode, node: Node): NavNode { + const navNode: NavNode = { + node, + additionalNodes: undefined, + parent, + children: [], + indent: + parent ? parent.indent + 1 : 0 + }; + if (parent) { + parent.children.push(navNode); } + addChildren(navNode); + return navNode; + } - function getChildNodes(nodes: Node[]): Node[] { - const childNodes: Node[] = []; - - function visit(node: Node) { + /** Traverse through parent.node's descendants and find declarations to add as parent's children. */ + function addChildren(parent: NavNode): void { + function recur(node: Node) { + if (isDeclaration(node)) { switch (node.kind) { - case SyntaxKind.VariableStatement: - forEach((node).declarationList.declarations, visit); + case SyntaxKind.Parameter: // Parameter properties handled by SyntaxKind.Constructor case + case SyntaxKind.TypeParameter: + case SyntaxKind.PropertyAssignment: + // Don't treat this as a declaration. + forEachChild(node, recur); break; - case SyntaxKind.ObjectBindingPattern: - case SyntaxKind.ArrayBindingPattern: - forEach((node).elements, visit); + + case SyntaxKind.Constructor: + // Get parameter properties, and treat them as being on the *same* level as the constructor, not under it. + const ctr = node; + createNavNode(parent, ctr); + for (const param of ctr.parameters) { + if (isParameterPropertyDeclaration(param)) { + createNavNode(parent, param); + } + } break; - case SyntaxKind.ExportDeclaration: - // Handle named exports case e.g.: - // export {a, b as B} from "mod"; - if ((node).exportClause) { - forEach((node).exportClause.elements, visit); + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + if (!hasDynamicName(( node))) { + createNavNode(parent, node); } break; - case SyntaxKind.ImportDeclaration: - let importClause = (node).importClause; - if (importClause) { - // Handle default import case e.g.: - // import d from "mod"; - if (importClause.name) { - childNodes.push(importClause); - } + case SyntaxKind.EnumMember: + if (!isComputedProperty(node)) { + createNavNode(parent, node); + } + break; + + case SyntaxKind.ImportClause: + let importClause = node; + // Handle default import case e.g.: + // import d from "mod"; + if (importClause.name) { + createNavNode(parent, importClause); + } - // Handle named bindings in imports e.g.: - // import * as NS from "mod"; - // import {a, b as B} from "mod"; - if (importClause.namedBindings) { - if (importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { - childNodes.push(importClause.namedBindings); - } - else { - forEach((importClause.namedBindings).elements, visit); - } + // Handle named bindings in imports e.g.: + // import * as NS from "mod"; + // import {a, b as B} from "mod"; + if (importClause.namedBindings) { + if (importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { + createNavNode(parent, importClause.namedBindings); + } + else { + forEach((importClause.namedBindings).elements, recur); } } break; case SyntaxKind.BindingElement: case SyntaxKind.VariableDeclaration: - if (isBindingPattern((node).name)) { - visit((node).name); - break; + const decl = node; + const name = decl.name; + if (isBindingPattern(name)) { + recur(name); + } + else if (decl.initializer && isFunctionOrClassExpression(decl.initializer)) { + // For `const x = function() {}`, just use the function node, not the const. + recur(decl.initializer); + } + else { + createNavNode(parent, node); } - // Fall through - case SyntaxKind.ClassDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ExportSpecifier: - case SyntaxKind.TypeAliasDeclaration: - childNodes.push(node); break; + + default: + createNavNode(parent, node); } } + else { + switch (node.kind) { + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.IndexSignature: + createNavNode(parent, node); + break; + default: + if (node.jsDocComments) { + for (const jsDocComment of node.jsDocComments) { + recur(jsDocComment); + } + } - // for (let i = 0, n = nodes.length; i < n; i++) { - // let node = nodes[i]; - - // if (node.kind === SyntaxKind.ClassDeclaration || - // node.kind === SyntaxKind.EnumDeclaration || - // node.kind === SyntaxKind.InterfaceDeclaration || - // node.kind === SyntaxKind.ModuleDeclaration || - // node.kind === SyntaxKind.FunctionDeclaration) { - - // childNodes.push(node); - // } - // else if (node.kind === SyntaxKind.VariableStatement) { - // childNodes.push.apply(childNodes, (node).declarations); - // } - // } - forEach(nodes, visit); - return sortNodes(childNodes); - } - - function getTopLevelNodes(node: SourceFile): Node[] { - const topLevelNodes: Node[] = []; - topLevelNodes.push(node); - - addTopLevelNodes(node.statements, topLevelNodes); - - return topLevelNodes; - } - - function sortNodes(nodes: Node[]): Node[] { - return nodes.slice(0).sort((n1: Declaration, n2: Declaration) => { - if (n1.name && n2.name) { - return localeCompareFix(getPropertyNameForPropertyNameNode(n1.name), getPropertyNameForPropertyNameNode(n2.name)); - } - else if (n1.name) { - return 1; - } - else if (n2.name) { - return -1; - } - else { - return n1.kind - n2.kind; + forEachChild(node, recur); } - }); - - // node 0.10 treats "a" as greater than "B". - // For consistency, sort alphabetically, falling back to which is lower-case. - function localeCompareFix(a: string, b: string) { - const cmp = a.toLowerCase().localeCompare(b.toLowerCase()); - if (cmp !== 0) - return cmp; - // Return the *opposite* of the `<` operator, which works the same in node 0.10 and 6.0. - return a < b ? 1 : a > b ? -1 : 0; } } - function addTopLevelNodes(nodes: Node[], topLevelNodes: Node[]): void { - nodes = sortNodes(nodes); - - for (const node of nodes) { - switch (node.kind) { - case SyntaxKind.ClassDeclaration: - topLevelNodes.push(node); - for (const member of (node).members) { - if (member.kind === SyntaxKind.MethodDeclaration || member.kind === SyntaxKind.Constructor) { - type FunctionLikeMember = MethodDeclaration | ConstructorDeclaration; - if ((member).body) { - // We do not include methods that does not have child functions in it, because of duplications. - if (hasNamedFunctionDeclarations(((member).body).statements)) { - topLevelNodes.push(member); - } - addTopLevelNodes(((member).body).statements, topLevelNodes); - } - } - } - break; - case SyntaxKind.EnumDeclaration: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.TypeAliasDeclaration: - topLevelNodes.push(node); - break; + let parentNode = parent.node; + if (parentNode.kind === SyntaxKind.ModuleDeclaration) { + parentNode = getInteriorModule(parentNode); + } + forEachChild(parentNode, recur); - case SyntaxKind.ModuleDeclaration: - let moduleDeclaration = node; - topLevelNodes.push(node); - addTopLevelNodes((getInnermostModule(moduleDeclaration).body).statements, topLevelNodes); - break; + mergeChildren(parent.children); + sortChildren(parent.children); + } - case SyntaxKind.FunctionDeclaration: - let functionDeclaration = node; - if (isTopLevelFunctionDeclaration(functionDeclaration)) { - topLevelNodes.push(node); - addTopLevelNodes((functionDeclaration.body).statements, topLevelNodes); - } - break; + /** Merge declarations of the same kind. */ + function mergeChildren(children: NavNode[]): void { + const nameToNavNodes: Map = {}; + filterMutate(children, child => { + const decl = child.node; + const name = decl.name && decl.name.getText(); + if (!name) + // Anonymous items are never merged. + return true; + + const itemsWithSameName = nameToNavNodes[name]; + if (!itemsWithSameName) { + nameToNavNodes[name] = [child]; + return true; + } + + for (const s of itemsWithSameName) { + if (shouldReallyMerge(s.node, child.node)) { + merge(s, child); + return false; } } - } + itemsWithSameName.push(child); + return true; + }); + + /** a and b have the same name, but they may not be mergeable. */ + function shouldReallyMerge(a: Node, b: Node): boolean { + return a.kind === b.kind && (a.kind !== SyntaxKind.ModuleDeclaration || areSameModule(a, b)); - function hasNamedFunctionDeclarations(nodes: NodeArray): boolean { - for (const s of nodes) { - if (s.kind === SyntaxKind.FunctionDeclaration && !isEmpty((s).name.text)) { + // We use 1 NavNode to represent 'A.B.C', but there are multiple source nodes. + // Only merge module nodes that have the same chain. Don't merge 'A.B.C' with 'A'! + function areSameModule(a: ModuleDeclaration, b: ModuleDeclaration): boolean { + if (a.body.kind !== b.body.kind) { + return false; + } + if (a.body.kind !== SyntaxKind.ModuleDeclaration) { return true; } + return areSameModule(a.body, b.body); } - return false; } - function isTopLevelFunctionDeclaration(functionDeclaration: FunctionLikeDeclaration): boolean { - if (functionDeclaration.kind === SyntaxKind.FunctionDeclaration) { - // A function declaration is 'top level' if it contains any function declarations - // within it. - if (functionDeclaration.body && functionDeclaration.body.kind === SyntaxKind.Block) { - // Proper function declarations can only have identifier names - if (hasNamedFunctionDeclarations((functionDeclaration.body).statements)) { - return true; - } - - // Or if it is not parented by another function. I.e all functions at module scope are 'top level'. - if (!isFunctionBlock(functionDeclaration.parent)) { - return true; - } + /** Merge source into target. Source should be thrown away after this is called. */ + function merge(target: NavNode, source: NavNode): void { + target.additionalNodes = target.additionalNodes || []; + target.additionalNodes.push(source.node); + if (source.additionalNodes) { + target.additionalNodes.push(...source.additionalNodes); + } - // Or if it is nested inside class methods and constructors. - else { - // We have made sure that a grand parent node exists with 'isFunctionBlock()' above. - const grandParentKind = functionDeclaration.parent.parent.kind; - if (grandParentKind === SyntaxKind.MethodDeclaration || - grandParentKind === SyntaxKind.Constructor) { + target.children.push(...source.children); + mergeChildren(target.children); + sortChildren(target.children); + } + } - return true; - } - } - } + /** Recursively ensure that each NavNode's children are in sorted order. */ + function sortChildren(children: NavNode[]): void { + children.sort((child1, child2) => { + const name1 = tryGetName(child1.node), name2 = tryGetName(child2.node); + if (name1 && name2) { + const cmp = localeCompareFix(name1, name2); + return cmp !== 0 ? cmp : navKind(child1) - navKind(child2); + } + else { + return name1 ? 1 : name2 ? -1 : navKind(child1) - navKind(child2); } + }); + } - return false; + // node 0.10 treats "a" as greater than "B". + const localeCompareIsCorrect = "a".localeCompare("B") < 0; + function localeCompareFix(a: string, b: string): number { + if (localeCompareIsCorrect) { + return a.localeCompare(b); } - function getItemsWorker(nodes: Node[], createItem: (n: Node) => ts.NavigationBarItem): ts.NavigationBarItem[] { - const items: ts.NavigationBarItem[] = []; - - const keyToItem: Map = {}; + const cmp = a.toLowerCase().localeCompare(b.toLowerCase()); + if (cmp !== 0) + return cmp; + // Return the *opposite* of the `<` operator + return a < b ? 1 : a > b ? -1 : 0; + } - for (const child of nodes) { - const item = createItem(child); - if (item !== undefined) { - if (item.text.length > 0) { - const key = item.text + "-" + item.kind + "-" + item.indent; + /** + * This differs from getItemName because this is just used for sorting. + * We only sort nodes by name that have a more-or-less "direct" name, as opposed to `new()` and the like. + * So `new()` can still come before an `aardvark` method. + */ + function tryGetName(node: Node): string | undefined { + if (node.kind === SyntaxKind.ModuleDeclaration) { + return getModuleName(node); + } - const itemWithSameName = keyToItem[key]; - if (itemWithSameName) { - // We had an item with the same name. Merge these items together. - merge(itemWithSameName, item); - } - else { - keyToItem[key] = item; - items.push(item); - } - } - } - } + const decl = node; + if (decl.name) { + return getPropertyNameForPropertyNameNode(decl.name); + } + switch (node.kind) { + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.ClassExpression: + return getFunctionOrClassName(node); + case SyntaxKind.JSDocTypedefTag: + return getJSDocTypedefTagName(node); + default: + return undefined; + } + } - return items; + function getItemName(node: Node): string { + if (node.kind === SyntaxKind.ModuleDeclaration) { + return getModuleName(node); } - function merge(target: ts.NavigationBarItem, source: ts.NavigationBarItem) { - // First, add any spans in the source to the target. - addRange(target.spans, source.spans); + const name = (node).name; + if (name) { + const text = name.getText(); + if (text.length > 0) + return text; + } - if (source.childItems) { - if (!target.childItems) { - target.childItems = []; + switch (node.kind) { + case SyntaxKind.SourceFile: + const sourceFile = node; + return isExternalModule(sourceFile) + ? `"${escapeString(getBaseFileName(removeFileExtension(normalizePath(sourceFile.fileName))))}"` + : ""; + case SyntaxKind.ArrowFunction: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + if (node.flags & NodeFlags.Default) { + return "default"; } + return getFunctionOrClassName(node); + case SyntaxKind.Constructor: + return "constructor"; + case SyntaxKind.ConstructSignature: + return "new()"; + case SyntaxKind.CallSignature: + return "()"; + case SyntaxKind.IndexSignature: + return "[]"; + case SyntaxKind.JSDocTypedefTag: + return getJSDocTypedefTagName(node); + default: + Debug.fail(); + return ""; + } + } - // Next, recursively merge or add any children in the source as appropriate. - outer: - for (const sourceChild of source.childItems) { - for (const targetChild of target.childItems) { - if (targetChild.text === sourceChild.text && targetChild.kind === sourceChild.kind) { - // Found a match. merge them. - merge(targetChild, sourceChild); - continue outer; - } + function getJSDocTypedefTagName(node: JSDocTypedefTag): string { + if (node.name) { + return node.name.text; + } + else { + const parentNode = node.parent && node.parent.parent; + if (parentNode && parentNode.kind === SyntaxKind.VariableStatement) { + if ((parentNode).declarationList.declarations.length > 0) { + const nameIdentifier = (parentNode).declarationList.declarations[0].name; + if (nameIdentifier.kind === SyntaxKind.Identifier) { + return (nameIdentifier).text; } - - // Didn't find a match, just add this child to the list. - target.childItems.push(sourceChild); } } + return ""; } + } - function createChildItem(node: Node): ts.NavigationBarItem { - switch (node.kind) { - case SyntaxKind.Parameter: - if (isBindingPattern((node).name)) { - break; - } - if ((node.flags & NodeFlags.Modifier) === 0) { - return undefined; - } - return createItem(node, getTextOfNode((node).name), ts.ScriptElementKind.memberVariableElement); - - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - return createItem(node, getTextOfNode((node).name), ts.ScriptElementKind.memberFunctionElement); - - case SyntaxKind.GetAccessor: - return createItem(node, getTextOfNode((node).name), ts.ScriptElementKind.memberGetAccessorElement); - - case SyntaxKind.SetAccessor: - return createItem(node, getTextOfNode((node).name), ts.ScriptElementKind.memberSetAccessorElement); - - case SyntaxKind.IndexSignature: - return createItem(node, "[]", ts.ScriptElementKind.indexSignatureElement); + /** Flattens the NavNode tree to a list, keeping only the top-level items. */ + function topLevelItems(root: NavNode): NavNode[] { + const topLevel: NavNode[] = []; + function recur(item: NavNode) { + if (isTopLevel(item)) { + topLevel.push(item); + for (const child of item.children) { + recur(child); + } + } + } + recur(root); + return topLevel; + function isTopLevel(item: NavNode): boolean { + switch (navKind(item)) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: case SyntaxKind.EnumDeclaration: - return createItem(node, getTextOfNode((node).name), ts.ScriptElementKind.enumElement); - - case SyntaxKind.EnumMember: - return createItem(node, getTextOfNode((node).name), ts.ScriptElementKind.memberVariableElement); - - case SyntaxKind.ModuleDeclaration: - return createItem(node, getModuleName(node), ts.ScriptElementKind.moduleElement); - case SyntaxKind.InterfaceDeclaration: - return createItem(node, getTextOfNode((node).name), ts.ScriptElementKind.interfaceElement); - + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.SourceFile: case SyntaxKind.TypeAliasDeclaration: - return createItem(node, getTextOfNode((node).name), ts.ScriptElementKind.typeElement); - - case SyntaxKind.CallSignature: - return createItem(node, "()", ts.ScriptElementKind.callSignatureElement); - - case SyntaxKind.ConstructSignature: - return createItem(node, "new()", ts.ScriptElementKind.constructSignatureElement); - - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - return createItem(node, getTextOfNode((node).name), ts.ScriptElementKind.memberVariableElement); + case SyntaxKind.JSDocTypedefTag: + return true; - case SyntaxKind.ClassDeclaration: - return createItem(node, getTextOfNode((node).name), ts.ScriptElementKind.classElement); + case SyntaxKind.Constructor: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return hasSomeImportantChild(item); + case SyntaxKind.ArrowFunction: case SyntaxKind.FunctionDeclaration: - return createItem(node, getTextOfNode((node).name), ts.ScriptElementKind.functionElement); - - case SyntaxKind.VariableDeclaration: - case SyntaxKind.BindingElement: - let variableDeclarationNode: Node; - let name: Node; - - if (node.kind === SyntaxKind.BindingElement) { - name = (node).name; - variableDeclarationNode = node; - // binding elements are added only for variable declarations - // bubble up to the containing variable declaration - while (variableDeclarationNode && variableDeclarationNode.kind !== SyntaxKind.VariableDeclaration) { - variableDeclarationNode = variableDeclarationNode.parent; - } - Debug.assert(variableDeclarationNode !== undefined); - } - else { - Debug.assert(!isBindingPattern((node).name)); - variableDeclarationNode = node; - name = (node).name; - } - - if (isConst(variableDeclarationNode)) { - return createItem(node, getTextOfNode(name), ts.ScriptElementKind.constElement); - } - else if (isLet(variableDeclarationNode)) { - return createItem(node, getTextOfNode(name), ts.ScriptElementKind.letElement); - } - else { - return createItem(node, getTextOfNode(name), ts.ScriptElementKind.variableElement); - } + case SyntaxKind.FunctionExpression: + return isTopLevelFunctionDeclaration(item); - case SyntaxKind.Constructor: - return createItem(node, "constructor", ts.ScriptElementKind.constructorImplementationElement); - - case SyntaxKind.ExportSpecifier: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.ImportClause: - case SyntaxKind.NamespaceImport: - return createItem(node, getTextOfNode((node).name), ts.ScriptElementKind.alias); + default: + return false; } + function isTopLevelFunctionDeclaration(item: NavNode): boolean { + if (!(item.node).body) { + return false; + } - return undefined; - - function createItem(node: Node, name: string, scriptElementKind: string): NavigationBarItem { - return getNavigationBarItem(name, scriptElementKind, getNodeModifiers(node), [getNodeSpan(node)]); + switch (navKind(item.parent)) { + case SyntaxKind.ModuleBlock: + case SyntaxKind.SourceFile: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.Constructor: + return true; + default: + return hasSomeImportantChild(item); + } } - } - - function isEmpty(text: string) { - return !text || text.trim() === ""; - } - - function getNavigationBarItem(text: string, kind: string, kindModifiers: string, spans: TextSpan[], childItems: NavigationBarItem[] = [], indent = 0): NavigationBarItem { - if (isEmpty(text)) { - return undefined; + function hasSomeImportantChild(item: NavNode) { + return forEach(item.children, child => { + const childKind = navKind(child); + return childKind !== SyntaxKind.VariableDeclaration && childKind !== SyntaxKind.BindingElement; + }); } + } + } + function convertToTopLevelItem(n: NavNode): NavigationBarItem { + const spans = [getNodeSpan(n.node)]; + return { + text: getItemName(n.node), + kind: nodeKind(n.node), + kindModifiers: navModifiers(n), + spans, + childItems: map(n.children, convertToChildItem), + indent: n.indent, + bolded: false, + grayed: false + }; + + function convertToChildItem(n: NavNode): NavigationBarItem { + const nodes = [n.node]; + if (n.additionalNodes) { + nodes.push(...n.additionalNodes); + } + const spans = map(nodes, getNodeSpan); return { - text, - kind, - kindModifiers, + text: getItemName(n.node), + kind: nodeKind(n.node), + kindModifiers: navModifiers(n), spans, - childItems, - indent, + childItems: [], + indent: 0, bolded: false, grayed: false }; } + } - function createTopLevelItem(node: Node): ts.NavigationBarItem { - switch (node.kind) { - case SyntaxKind.SourceFile: - return createSourceFileItem(node); - - case SyntaxKind.ClassDeclaration: - return createClassItem(node); - - case SyntaxKind.MethodDeclaration: - case SyntaxKind.Constructor: - return createMemberFunctionLikeItem(node); - - case SyntaxKind.EnumDeclaration: - return createEnumItem(node); - - case SyntaxKind.InterfaceDeclaration: - return createInterfaceItem(node); - - case SyntaxKind.ModuleDeclaration: - return createModuleItem(node); - - case SyntaxKind.FunctionDeclaration: - return createFunctionItem(node); - - case SyntaxKind.TypeAliasDeclaration: - return createTypeAliasItem(node); - } - - return undefined; - - function createModuleItem(node: ModuleDeclaration): NavigationBarItem { - const moduleName = getModuleName(node); - - const childItems = getItemsWorker(getChildNodes((getInnermostModule(node).body).statements), createChildItem); - - return getNavigationBarItem(moduleName, - ts.ScriptElementKind.moduleElement, - getNodeModifiers(node), - [getNodeSpan(node)], - childItems, - getIndent(node)); - } - - function createFunctionItem(node: FunctionDeclaration): ts.NavigationBarItem { - if (node.body && node.body.kind === SyntaxKind.Block) { - const childItems = getItemsWorker(sortNodes((node.body).statements), createChildItem); - - return getNavigationBarItem(!node.name ? "default" : node.name.text, - ts.ScriptElementKind.functionElement, - getNodeModifiers(node), - [getNodeSpan(node)], - childItems, - getIndent(node)); - } - - return undefined; - } - - function createTypeAliasItem(node: TypeAliasDeclaration): ts.NavigationBarItem { - return getNavigationBarItem(node.name.text, - ts.ScriptElementKind.typeElement, - getNodeModifiers(node), - [getNodeSpan(node)], - [], - getIndent(node)); - } - - function createMemberFunctionLikeItem(node: MethodDeclaration | ConstructorDeclaration): ts.NavigationBarItem { - if (node.body && node.body.kind === SyntaxKind.Block) { - const childItems = getItemsWorker(sortNodes((node.body).statements), createChildItem); - let scriptElementKind: string; - let memberFunctionName: string; - if (node.kind === SyntaxKind.MethodDeclaration) { - memberFunctionName = getPropertyNameForPropertyNameNode(node.name); - scriptElementKind = ts.ScriptElementKind.memberFunctionElement; + // TODO: We should just use getNodeKind. No reason why navigationBar and navigateTo should have different behaviors. + function nodeKind(node: Node): string { + switch (node.kind) { + case SyntaxKind.SourceFile: + return ScriptElementKind.moduleElement; + + case SyntaxKind.EnumMember: + return ScriptElementKind.memberVariableElement; + + case SyntaxKind.VariableDeclaration: + case SyntaxKind.BindingElement: + let variableDeclarationNode: Node; + let name: Node; + + if (node.kind === SyntaxKind.BindingElement) { + name = (node).name; + variableDeclarationNode = node; + // binding elements are added only for variable declarations + // bubble up to the containing variable declaration + while (variableDeclarationNode && variableDeclarationNode.kind !== SyntaxKind.VariableDeclaration) { + variableDeclarationNode = variableDeclarationNode.parent; } - else { - memberFunctionName = "constructor"; - scriptElementKind = ts.ScriptElementKind.constructorImplementationElement; - } - - return getNavigationBarItem(memberFunctionName, - scriptElementKind, - getNodeModifiers(node), - [getNodeSpan(node)], - childItems, - getIndent(node)); + Debug.assert(variableDeclarationNode !== undefined); } - - return undefined; - } - - function createSourceFileItem(node: SourceFile): ts.NavigationBarItem { - const childItems = getItemsWorker(getChildNodes(node.statements), createChildItem); - - const rootName = isExternalModule(node) - ? "\"" + escapeString(getBaseFileName(removeFileExtension(normalizePath(node.fileName)))) + "\"" - : ""; - - return getNavigationBarItem(rootName, - ts.ScriptElementKind.moduleElement, - ts.ScriptElementKindModifier.none, - [getNodeSpan(node)], - childItems); - } - - function createClassItem(node: ClassDeclaration): ts.NavigationBarItem { - let childItems: NavigationBarItem[]; - - if (node.members) { - const constructor = forEach(node.members, member => { - return member.kind === SyntaxKind.Constructor && member; - }); - - // Add the constructor parameters in as children of the class (for property parameters). - // Note that *all non-binding pattern named* parameters will be added to the nodes array, but parameters that - // are not properties will be filtered out later by createChildItem. - const nodes: Node[] = removeDynamicallyNamedProperties(node); - if (constructor) { - addRange(nodes, filter(constructor.parameters, p => !isBindingPattern(p.name))); - } - - childItems = getItemsWorker(sortNodes(nodes), createChildItem); + else { + Debug.assert(!isBindingPattern((node).name)); + variableDeclarationNode = node; + name = (node).name; } - const nodeName = !node.name ? "default" : node.name.text; - - return getNavigationBarItem( - nodeName, - ts.ScriptElementKind.classElement, - getNodeModifiers(node), - [getNodeSpan(node)], - childItems, - getIndent(node)); - } - - function createEnumItem(node: EnumDeclaration): ts.NavigationBarItem { - const childItems = getItemsWorker(sortNodes(removeComputedProperties(node)), createChildItem); - return getNavigationBarItem( - node.name.text, - ts.ScriptElementKind.enumElement, - getNodeModifiers(node), - [getNodeSpan(node)], - childItems, - getIndent(node)); - } - - function createInterfaceItem(node: InterfaceDeclaration): ts.NavigationBarItem { - const childItems = getItemsWorker(sortNodes(removeDynamicallyNamedProperties(node)), createChildItem); - return getNavigationBarItem( - node.name.text, - ts.ScriptElementKind.interfaceElement, - getNodeModifiers(node), - [getNodeSpan(node)], - childItems, - getIndent(node)); - } - } - - function getModuleName(moduleDeclaration: ModuleDeclaration): string { - // We want to maintain quotation marks. - if (isAmbientModule(moduleDeclaration)) { - return getTextOfNode(moduleDeclaration.name); - } - - // Otherwise, we need to aggregate each identifier to build up the qualified name. - const result: string[] = []; - - result.push(moduleDeclaration.name.text); + if (isConst(variableDeclarationNode)) { + return ts.ScriptElementKind.constElement; + } + else if (isLet(variableDeclarationNode)) { + return ts.ScriptElementKind.letElement; + } + else { + return ts.ScriptElementKind.variableElement; + } - while (moduleDeclaration.body && moduleDeclaration.body.kind === SyntaxKind.ModuleDeclaration) { - moduleDeclaration = moduleDeclaration.body; + case SyntaxKind.ArrowFunction: + return ts.ScriptElementKind.functionElement; - result.push(moduleDeclaration.name.text); - } + case SyntaxKind.JSDocTypedefTag: + return ScriptElementKind.typeElement; - return result.join("."); + default: + return getNodeKind(node); } + } - function removeComputedProperties(node: EnumDeclaration): Declaration[] { - return filter(node.members, member => member.name === undefined || member.name.kind !== SyntaxKind.ComputedPropertyName); + function getModuleName(moduleDeclaration: ModuleDeclaration): string { + // We want to maintain quotation marks. + if (isAmbientModule(moduleDeclaration)) { + return getTextOfNode(moduleDeclaration.name); } - /** - * Like removeComputedProperties, but retains the properties with well known symbol names - */ - function removeDynamicallyNamedProperties(node: ClassDeclaration | InterfaceDeclaration): Declaration[] { - return filter(node.members, member => !hasDynamicName(member)); - } + // Otherwise, we need to aggregate each identifier to build up the qualified name. + const result: string[] = []; - function getInnermostModule(node: ModuleDeclaration): ModuleDeclaration { - while (node.body.kind === SyntaxKind.ModuleDeclaration) { - node = node.body; - } + result.push(moduleDeclaration.name.text); - return node; - } + while (moduleDeclaration.body && moduleDeclaration.body.kind === SyntaxKind.ModuleDeclaration) { + moduleDeclaration = moduleDeclaration.body; - function getNodeSpan(node: Node) { - return node.kind === SyntaxKind.SourceFile - ? createTextSpanFromBounds(node.getFullStart(), node.getEnd()) - : createTextSpanFromBounds(node.getStart(), node.getEnd()); + result.push(moduleDeclaration.name.text); } - function getTextOfNode(node: Node): string { - return getTextOfNodeFromSourceText(sourceFile.text, node); - } + return result.join("."); } - export function getJsNavigationBarItems(sourceFile: SourceFile, compilerOptions: CompilerOptions): NavigationBarItem[] { - const anonFnText = ""; - const anonClassText = ""; - let indent = 0; - - const rootName = isExternalModule(sourceFile) ? - "\"" + escapeString(getBaseFileName(removeFileExtension(normalizePath(sourceFile.fileName)))) + "\"" - : ""; - - const sourceFileItem = getNavBarItem(rootName, ScriptElementKind.moduleElement, [getNodeSpan(sourceFile)]); - let topItem = sourceFileItem; - - // Walk the whole file, because we want to also find function expressions - which may be in variable initializer, - // call arguments, expressions, etc... - forEachChild(sourceFile, visitNode); - - function visitNode(node: Node) { - const newItem = createNavBarItem(node); - - if (newItem) { - topItem.childItems.push(newItem); - } + /** + * For 'module A.B.C', we want to get the node for 'C'. + * We store 'A' as associated with a NavNode, and use getModuleName to traverse down again. + */ + function getInteriorModule(decl: ModuleDeclaration): ModuleDeclaration { + return decl.body.kind === SyntaxKind.ModuleDeclaration ? getInteriorModule(decl.body) : decl; + } - if (node.jsDocComments && node.jsDocComments.length > 0) { - for (const jsDocComment of node.jsDocComments) { - visitNode(jsDocComment); - } - } + function isComputedProperty(member: EnumMember): boolean { + return member.name === undefined || member.name.kind === SyntaxKind.ComputedPropertyName; + } - // Add a level if traversing into a container - if (newItem && (isFunctionLike(node) || isClassLike(node))) { - const lastTop = topItem; - indent++; - topItem = newItem; - forEachChild(node, visitNode); - topItem = lastTop; - indent--; - - // If the last item added was an anonymous function expression, and it had no children, discard it. - if (newItem && newItem.text === anonFnText && newItem.childItems.length === 0) { - topItem.childItems.pop(); - } - } - else { - forEachChild(node, visitNode); - } - } + function getNodeSpan(node: Node) { + return node.kind === SyntaxKind.SourceFile + ? createTextSpanFromBounds(node.getFullStart(), node.getEnd()) + : createTextSpanFromBounds(node.getStart(), node.getEnd()); + } - function createNavBarItem(node: Node): NavigationBarItem { - switch (node.kind) { - case SyntaxKind.VariableDeclaration: - // Only add to the navbar if at the top-level of the file - // Note: "const" and "let" are also SyntaxKind.VariableDeclarations - if (node.parent/*VariableDeclarationList*/.parent/*VariableStatement*/ - .parent/*SourceFile*/.kind !== SyntaxKind.SourceFile) { - return undefined; - } - // If it is initialized with a function expression, handle it when we reach the function expression node - const varDecl = node as VariableDeclaration; - if (varDecl.initializer && (varDecl.initializer.kind === SyntaxKind.FunctionExpression || - varDecl.initializer.kind === SyntaxKind.ArrowFunction || - varDecl.initializer.kind === SyntaxKind.ClassExpression)) { - return undefined; - } - // Fall through - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - // "export default function().." looks just like a regular function/class declaration, except with the 'default' flag - const name = node.flags && (node.flags & NodeFlags.Default) && !(node as (Declaration)).name ? "default" : - node.kind === SyntaxKind.Constructor ? "constructor" : - declarationNameToString((node as (Declaration)).name); - return getNavBarItem(name, getScriptKindForElementKind(node.kind), [getNodeSpan(node)]); - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.ClassExpression: - return getDefineModuleItem(node) || getFunctionOrClassExpressionItem(node); - case SyntaxKind.MethodDeclaration: - const methodDecl = node as MethodDeclaration; - return getNavBarItem(declarationNameToString(methodDecl.name), - ScriptElementKind.memberFunctionElement, - [getNodeSpan(node)]); - case SyntaxKind.ExportAssignment: - // e.g. "export default " - return getNavBarItem("default", ScriptElementKind.variableElement, [getNodeSpan(node)]); - case SyntaxKind.ImportClause: // e.g. 'def' in: import def from 'mod' (in ImportDeclaration) - if (!(node as ImportClause).name) { - // No default import (this node is still a parent of named & namespace imports, which are handled below) - return undefined; - } - // fall through - case SyntaxKind.ImportSpecifier: // e.g. 'id' in: import {id} from 'mod' (in NamedImports, in ImportClause) - case SyntaxKind.NamespaceImport: // e.g. '* as ns' in: import * as ns from 'mod' (in ImportClause) - case SyntaxKind.ExportSpecifier: // e.g. 'a' or 'b' in: export {a, foo as b} from 'mod' - // Export specifiers are only interesting if they are reexports from another module, or renamed, else they are already globals - if (node.kind === SyntaxKind.ExportSpecifier) { - if (!(node.parent.parent as ExportDeclaration).moduleSpecifier && !(node as ExportSpecifier).propertyName) { - return undefined; - } - } - const decl = node as (ImportSpecifier | ImportClause | NamespaceImport | ExportSpecifier); - if (!decl.name) { - return undefined; - } - const declName = declarationNameToString(decl.name); - return getNavBarItem(declName, ScriptElementKind.constElement, [getNodeSpan(node)]); - case SyntaxKind.JSDocTypedefTag: - if ((node).name) { - return getNavBarItem( - (node).name.text, - ScriptElementKind.typeElement, - [getNodeSpan(node)]); - } - else { - const parentNode = node.parent && node.parent.parent; - if (parentNode && parentNode.kind === SyntaxKind.VariableStatement) { - if ((parentNode).declarationList.declarations.length > 0) { - const nameIdentifier = (parentNode).declarationList.declarations[0].name; - if (nameIdentifier.kind === SyntaxKind.Identifier) { - return getNavBarItem( - (nameIdentifier).text, - ScriptElementKind.typeElement, - [getNodeSpan(node)]); - } - } - } - } - default: - return undefined; - } + function getFunctionOrClassName(node: FunctionExpression | FunctionDeclaration | ArrowFunction | ClassLikeDeclaration): string { + if (node.name && getFullWidth(node.name) > 0) { + return declarationNameToString(node.name); } - - function getNavBarItem(text: string, kind: string, spans: TextSpan[], kindModifiers = ScriptElementKindModifier.none): NavigationBarItem { - return { - text, kind, kindModifiers, spans, childItems: [], indent, bolded: false, grayed: false - }; + // See if it is a var initializer. If so, use the var name. + else if (node.parent.kind === SyntaxKind.VariableDeclaration) { + return declarationNameToString((node.parent as VariableDeclaration).name); } - - function getDefineModuleItem(node: Node): NavigationBarItem { - if (node.kind !== SyntaxKind.FunctionExpression && node.kind !== SyntaxKind.ArrowFunction) { - return undefined; - } - - // No match if this is not a call expression to an identifier named 'define' - if (node.parent.kind !== SyntaxKind.CallExpression) { - return undefined; - } - const callExpr = node.parent as CallExpression; - if (callExpr.expression.kind !== SyntaxKind.Identifier || callExpr.expression.getText() !== "define") { - return undefined; - } - - // Return a module of either the given text in the first argument, or of the source file path - let defaultName = node.getSourceFile().fileName; - if (callExpr.arguments[0].kind === SyntaxKind.StringLiteral) { - defaultName = ((callExpr.arguments[0]) as StringLiteral).text; - } - return getNavBarItem(defaultName, ScriptElementKind.moduleElement, [getNodeSpan(node.parent)]); + // See if it is of the form " = function(){...}". If so, use the text from the left-hand side. + else if (node.parent.kind === SyntaxKind.BinaryExpression && + (node.parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken) { + return (node.parent as BinaryExpression).left.getText(); } - - function getFunctionOrClassExpressionItem(node: Node): NavigationBarItem { - if (node.kind !== SyntaxKind.FunctionExpression && - node.kind !== SyntaxKind.ArrowFunction && - node.kind !== SyntaxKind.ClassExpression) { - return undefined; - } - - const fnExpr = node as FunctionExpression | ArrowFunction | ClassExpression; - let fnName: string; - if (fnExpr.name && getFullWidth(fnExpr.name) > 0) { - // The expression has an identifier, so use that as the name - fnName = declarationNameToString(fnExpr.name); - } - else { - // See if it is a var initializer. If so, use the var name. - if (fnExpr.parent.kind === SyntaxKind.VariableDeclaration) { - fnName = declarationNameToString((fnExpr.parent as VariableDeclaration).name); - } - // See if it is of the form " = function(){...}". If so, use the text from the left-hand side. - else if (fnExpr.parent.kind === SyntaxKind.BinaryExpression && - (fnExpr.parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken) { - fnName = (fnExpr.parent as BinaryExpression).left.getText(); - } - // See if it is a property assignment, and if so use the property name - else if (fnExpr.parent.kind === SyntaxKind.PropertyAssignment && - (fnExpr.parent as PropertyAssignment).name) { - fnName = (fnExpr.parent as PropertyAssignment).name.getText(); - } - else { - fnName = node.kind === SyntaxKind.ClassExpression ? anonClassText : anonFnText; - } - } - const scriptKind = node.kind === SyntaxKind.ClassExpression ? ScriptElementKind.classElement : ScriptElementKind.functionElement; - return getNavBarItem(fnName, scriptKind, [getNodeSpan(node)]); + // See if it is a property assignment, and if so use the property name + else if (node.parent.kind === SyntaxKind.PropertyAssignment && (node.parent as PropertyAssignment).name) { + return (node.parent as PropertyAssignment).name.getText(); } - - function getNodeSpan(node: Node) { - return node.kind === SyntaxKind.SourceFile - ? createTextSpanFromBounds(node.getFullStart(), node.getEnd()) - : createTextSpanFromBounds(node.getStart(), node.getEnd()); + // Default exports are named "default" + else if (node.flags & NodeFlags.Default) { + return "default"; } - - function getScriptKindForElementKind(kind: SyntaxKind) { - switch (kind) { - case SyntaxKind.VariableDeclaration: - return ScriptElementKind.variableElement; - case SyntaxKind.FunctionDeclaration: - return ScriptElementKind.functionElement; - case SyntaxKind.ClassDeclaration: - return ScriptElementKind.classElement; - case SyntaxKind.Constructor: - return ScriptElementKind.constructorImplementationElement; - case SyntaxKind.GetAccessor: - return ScriptElementKind.memberGetAccessorElement; - case SyntaxKind.SetAccessor: - return ScriptElementKind.memberSetAccessorElement; - default: - return "unknown"; - } + else { + return isClassLike(node) ? "" : ""; } + } - return sourceFileItem.childItems; + function isFunctionOrClassExpression(node: Node): boolean { + return node.kind === SyntaxKind.FunctionExpression || node.kind === SyntaxKind.ArrowFunction || node.kind === SyntaxKind.ClassExpression; } } diff --git a/src/services/services.ts b/src/services/services.ts index 90740b257c6b5..79de33423e013 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -6881,7 +6881,7 @@ namespace ts { function getNavigationBarItems(fileName: string): NavigationBarItem[] { const sourceFile = syntaxTreeCache.getCurrentSourceFile(fileName); - return NavigationBar.getNavigationBarItems(sourceFile, host.getCompilationSettings()); + return NavigationBar.getNavigationBarItems(sourceFile); } function getSemanticClassifications(fileName: string, span: TextSpan): ClassifiedSpan[] { diff --git a/tests/cases/fourslash/navbar_contains-no-duplicates.ts b/tests/cases/fourslash/navbar_contains-no-duplicates.ts index e259a7bef901f..6bae780a9996a 100644 --- a/tests/cases/fourslash/navbar_contains-no-duplicates.ts +++ b/tests/cases/fourslash/navbar_contains-no-duplicates.ts @@ -95,13 +95,13 @@ verify.navigationBar([ "kindModifiers": "export,declare" }, { - "text": "Test", - "kind": "class", + "text": "B", + "kind": "var", "kindModifiers": "export,declare" }, { - "text": "B", - "kind": "var", + "text": "Test", + "kind": "class", "kindModifiers": "export,declare" }, { diff --git a/tests/cases/fourslash/navbar_exportDefault.ts b/tests/cases/fourslash/navbar_exportDefault.ts index dc99cff7d649c..e547c9129a69d 100644 --- a/tests/cases/fourslash/navbar_exportDefault.ts +++ b/tests/cases/fourslash/navbar_exportDefault.ts @@ -16,7 +16,14 @@ goTo.file("a.ts"); verify.navigationBar([ { "text": "\"a\"", - "kind": "module" + "kind": "module", + "childItems": [ + { + "text": "default", + "kind": "class", + "kindModifiers": "export" + } + ] }, { "text": "default", @@ -52,6 +59,13 @@ verify.navigationBar([ { "text": "\"c\"", "kind": "module", + "childItems": [ + { + "text": "default", + "kind": "function", + "kindModifiers": "export" + } + ] }, { "text": "default", diff --git a/tests/cases/fourslash/navigationBarAnonymousClassAndFunctionExpressions.ts b/tests/cases/fourslash/navigationBarAnonymousClassAndFunctionExpressions.ts new file mode 100644 index 0000000000000..f15959812fc06 --- /dev/null +++ b/tests/cases/fourslash/navigationBarAnonymousClassAndFunctionExpressions.ts @@ -0,0 +1,147 @@ +/// + +////global.cls = class { }; +////(function() { +//// const x = () => { +//// // Presence of inner function causes x to be a top-level function. +//// function xx() {} +//// }; +//// const y = { +//// // This is not a top-level function (contains nothing, but shows up in childItems of its parent.) +//// foo: function() {} +//// }; +//// (function nest() { +//// function moreNest() {} +//// })(); +////})(); +////(function() { // Different anonymous functions are not merged +//// // These will only show up as childItems. +//// function z() {} +//// console.log(function() {}) +////}) +////(function classes() { +//// // Classes show up in top-level regardless of whether they have names or inner declarations. +//// const cls2 = class { }; +//// console.log(class cls3 {}); +//// (class { }); +////}) + +verify.navigationBar([ + { + "text": "", + "kind": "module", + "childItems": [ + { + "text": "", + "kind": "function" + }, + { + "text": "", + "kind": "function" + }, + { + "text": "classes", + "kind": "function" + }, + { + "text": "global.cls", + "kind": "class" + } + ] + }, + { + "text": "", + "kind": "function", + "childItems": [ + { + "text": "nest", + "kind": "function" + }, + { + "text": "x", + "kind": "function" + }, + { + "text": "y", + "kind": "const" + } + ], + "indent": 1 + }, + { + "text": "nest", + "kind": "function", + "childItems": [ + { + "text": "moreNest", + "kind": "function" + } + ], + "indent": 2 + }, + { + "text": "x", + "kind": "function", + "childItems": [ + { + "text": "xx", + "kind": "function" + } + ], + "indent": 2 + }, + { + "text": "", + "kind": "function", + "childItems": [ + { + "text": "", + "kind": "function" + }, + { + "text": "z", + "kind": "function" + } + ], + "indent": 1 + }, + { + "text": "classes", + "kind": "function", + "childItems": [ + { + "text": "", + "kind": "class" + }, + { + "text": "cls2", + "kind": "class" + }, + { + "text": "cls3", + "kind": "class" + } + ], + "indent": 1 + }, + { + "text": "", + "kind": "class", + "indent": 2 + }, + { + "text": "cls2", + "kind": "class", + "indent": 2 + }, + { + "text": "cls3", + "kind": "class", + "indent": 2 + }, + { + "text": "global.cls", + "kind": "class", + "indent": 1 + } +]); diff --git a/tests/cases/fourslash/navigationBarDeclarationExpressions.ts b/tests/cases/fourslash/navigationBarDeclarationExpressions.ts new file mode 100644 index 0000000000000..214a14949ac5e --- /dev/null +++ b/tests/cases/fourslash/navigationBarDeclarationExpressions.ts @@ -0,0 +1,64 @@ +/// + +////console.log(console.log(class Y {}, class X {}), console.log(class B {}, class A {})); +////console.log(class Cls { meth() {} }); + +verify.navigationBar([ + { + "text": "", + "kind": "module", + "childItems": [ + { + "text": "A", + "kind": "class" + }, + { + "text": "B", + "kind": "class" + }, + { + "text": "Cls", + "kind": "class" + }, + { + "text": "X", + "kind": "class" + }, + { + "text": "Y", + "kind": "class" + } + ] + }, + { + "text": "A", + "kind": "class", + "indent": 1 + }, + { + "text": "B", + "kind": "class", + "indent": 1 + }, + { + "text": "Cls", + "kind": "class", + "childItems": [ + { + "text": "meth", + "kind": "method" + } + ], + "indent": 1 + }, + { + "text": "X", + "kind": "class", + "indent": 1 + }, + { + "text": "Y", + "kind": "class", + "indent": 1 + } +]); diff --git a/tests/cases/fourslash/navigationBarGetterAndSetter.ts b/tests/cases/fourslash/navigationBarGetterAndSetter.ts new file mode 100644 index 0000000000000..48105e4e9af3a --- /dev/null +++ b/tests/cases/fourslash/navigationBarGetterAndSetter.ts @@ -0,0 +1,48 @@ +/// + +////class X { +//// get x() {} +//// set x(value) { +//// // Inner declaration should make the setter top-level. +//// function f() {} +//// } +////} + +verify.navigationBar([ + { + "text": "", + "kind": "module", + "childItems": [ + { + "text": "X", + "kind": "class" + } + ] + }, + { + "text": "X", + "kind": "class", + "childItems": [ + { + "text": "x", + "kind": "getter" + }, + { + "text": "x", + "kind": "setter" + } + ], + "indent": 1 + }, + { + "text": "x", + "kind": "setter", + "childItems": [ + { + "text": "f", + "kind": "function" + } + ], + "indent": 2 + } +]); diff --git a/tests/cases/fourslash/navigationBarItemsFunctions.ts b/tests/cases/fourslash/navigationBarItemsFunctions.ts index 91546462a2572..f5aa3fb681dd5 100644 --- a/tests/cases/fourslash/navigationBarItemsFunctions.ts +++ b/tests/cases/fourslash/navigationBarItemsFunctions.ts @@ -32,6 +32,12 @@ verify.navigationBar([ { "text": "baz", "kind": "function", + "childItems": [ + { + "text": "v", + "kind": "var" + } + ], "indent": 1 }, { @@ -41,6 +47,10 @@ verify.navigationBar([ { "text": "bar", "kind": "function" + }, + { + "text": "x", + "kind": "var" } ], "indent": 1 @@ -52,6 +62,10 @@ verify.navigationBar([ { "text": "biz", "kind": "function" + }, + { + "text": "y", + "kind": "var" } ], "indent": 2 diff --git a/tests/cases/fourslash/navigationBarItemsFunctionsBroken.ts b/tests/cases/fourslash/navigationBarItemsFunctionsBroken.ts index 96dcbb944ae07..2f17a5006ff5f 100644 --- a/tests/cases/fourslash/navigationBarItemsFunctionsBroken.ts +++ b/tests/cases/fourslash/navigationBarItemsFunctionsBroken.ts @@ -18,6 +18,12 @@ verify.navigationBar([ { "text": "f", "kind": "function", + "childItems": [ + { + "text": "", + "kind": "function" + } + ], "indent": 1 } ]); diff --git a/tests/cases/fourslash/navigationBarItemsFunctionsBroken2.ts b/tests/cases/fourslash/navigationBarItemsFunctionsBroken2.ts index 127bde8c9e71d..f907bdad06719 100644 --- a/tests/cases/fourslash/navigationBarItemsFunctionsBroken2.ts +++ b/tests/cases/fourslash/navigationBarItemsFunctionsBroken2.ts @@ -10,6 +10,10 @@ verify.navigationBar([ "text": "", "kind": "module", "childItems": [ + { + "text": "", + "kind": "function" + }, { "text": "f", "kind": "function" @@ -19,6 +23,12 @@ verify.navigationBar([ { "text": "f", "kind": "function", + "childItems": [ + { + "text": "", + "kind": "function" + } + ], "indent": 1 } ]); diff --git a/tests/cases/fourslash/navigationBarItemsInsideMethodsAndConstructors.ts b/tests/cases/fourslash/navigationBarItemsInsideMethodsAndConstructors.ts index 28ec25c6f1dd2..3af3b351114b1 100644 --- a/tests/cases/fourslash/navigationBarItemsInsideMethodsAndConstructors.ts +++ b/tests/cases/fourslash/navigationBarItemsInsideMethodsAndConstructors.ts @@ -76,17 +76,17 @@ verify.navigationBar([ "kind": "property" } ], - "indent": 2 + "indent": 3 }, { "text": "LocalFunctionInConstructor", "kind": "function", - "indent": 2 + "indent": 3 }, { "text": "LocalInterfaceInConstrcutor", "kind": "interface", - "indent": 2 + "indent": 3 }, { "text": "method", @@ -116,7 +116,7 @@ verify.navigationBar([ "kind": "property" } ], - "indent": 2 + "indent": 3 }, { "text": "LocalFunctionInMethod", @@ -127,11 +127,11 @@ verify.navigationBar([ "kind": "function" } ], - "indent": 2 + "indent": 3 }, { "text": "LocalInterfaceInMethod", "kind": "interface", - "indent": 2 + "indent": 3 } ]); diff --git a/tests/cases/fourslash/navigationBarItemsItems2.ts b/tests/cases/fourslash/navigationBarItemsItems2.ts index 762531d8778b5..6ab0827e8f959 100644 --- a/tests/cases/fourslash/navigationBarItemsItems2.ts +++ b/tests/cases/fourslash/navigationBarItemsItems2.ts @@ -12,6 +12,11 @@ verify.navigationBar([ "text": "\"navigationBarItemsItems2\"", "kind": "module", "childItems": [ + { + "text": "", + "kind": "class", + "kindModifiers": "export" + }, { "text": "A", "kind": "module" @@ -19,7 +24,7 @@ verify.navigationBar([ ] }, { - "text": "default", + "text": "", "kind": "class", "kindModifiers": "export", "indent": 1 diff --git a/tests/cases/fourslash/navigationBarItemsItemsContainsNoAnonymousFunctions.ts b/tests/cases/fourslash/navigationBarItemsItemsContainsNoAnonymousFunctions.ts deleted file mode 100644 index 276e19624c92a..0000000000000 --- a/tests/cases/fourslash/navigationBarItemsItemsContainsNoAnonymousFunctions.ts +++ /dev/null @@ -1,80 +0,0 @@ -/// -// @Filename: scriptLexicalStructureItemsContainsNoAnonymouseFunctions_0.ts -/////*file1*/ -////(function() { -//// // this should not be included -//// var x = 0; -//// -//// // this should not be included either -//// function foo() { -//// -//// } -////})(); -//// -// @Filename: scriptLexicalStructureItemsContainsNoAnonymouseFunctions_1.ts -/////*file2*/ -////var x = function() { -//// // this should not be included -//// var x = 0; -//// -//// // this should not be included either -//// function foo() { -////}; -//// -// @Filename: scriptLexicalStructureItemsContainsNoAnonymouseFunctions_2.ts -////// Named functions should still show up -/////*file3*/ -////function foo() { -////} -////function bar() { -////} - -goTo.marker("file1"); -verify.navigationBar([ - { - "text": "", - "kind": "module" - } -]); - -goTo.marker("file2"); -verify.navigationBar([ - { - "text": "", - "kind": "module", - "childItems": [ - { - "text": "x", - "kind": "var" - } - ] - } -]); - -goTo.marker("file3"); -verify.navigationBar([ - { - "text": "", - "kind": "module", - "childItems": [ - { - "text": "bar", - "kind": "function" - }, - { - "text": "foo", - "kind": "function" - } - ] - }, - { - "text": "bar", - "kind": "function", - "indent": 1 - }, - { - "text": "foo", - "kind": "function", - "indent": 1 - } -]); diff --git a/tests/cases/fourslash/navigationBarItemsMissingName1.ts b/tests/cases/fourslash/navigationBarItemsMissingName1.ts index af0e89683bcfe..2a445a5e1ed61 100644 --- a/tests/cases/fourslash/navigationBarItemsMissingName1.ts +++ b/tests/cases/fourslash/navigationBarItemsMissingName1.ts @@ -8,6 +8,11 @@ verify.navigationBar([ "text": "\"navigationBarItemsMissingName1\"", "kind": "module", "childItems": [ + { + "text": "", + "kind": "function", + "kindModifiers": "export" + }, { "text": "C", "kind": "class" diff --git a/tests/cases/fourslash/navigationBarItemsMissingName2.ts b/tests/cases/fourslash/navigationBarItemsMissingName2.ts index 2eda3b07855c7..a878c5be8455b 100644 --- a/tests/cases/fourslash/navigationBarItemsMissingName2.ts +++ b/tests/cases/fourslash/navigationBarItemsMissingName2.ts @@ -9,10 +9,16 @@ verify.navigationBar([ { "text": "", - "kind": "module" + "kind": "module", + "childItems": [ + { + "text": "", + "kind": "class" + } + ] }, { - "text": "default", + "text": "", "kind": "class", "childItems": [ { diff --git a/tests/cases/fourslash/navigationBarItemsModules.ts b/tests/cases/fourslash/navigationBarItemsModules.ts index 979f22084e587..ec11cd52b942c 100644 --- a/tests/cases/fourslash/navigationBarItemsModules.ts +++ b/tests/cases/fourslash/navigationBarItemsModules.ts @@ -28,107 +28,107 @@ //The declarations of A.B.C.x do not get merged, so the 4 vars are independent. //The two 'A' modules, however, do get merged, so in reality we have 7 modules. verify.navigationBar([ - { - "text": "", - "kind": "module", - "childItems": [ - { - "text": "A.B.C", - "kind": "module" - }, - { - "text": "A.B", - "kind": "module" - }, - { - "text": "A", - "kind": "module" - }, - { - "text": "\"X.Y.Z\"", + { + "text": "", "kind": "module", - "kindModifiers": "declare" - }, - { + "childItems": [ + { + "text": "'X2.Y2.Z2'", + "kind": "module", + "kindModifiers": "declare" + }, + { + "text": "\"X.Y.Z\"", + "kind": "module", + "kindModifiers": "declare" + }, + { + "text": "A", + "kind": "module" + }, + { + "text": "A.B", + "kind": "module" + }, + { + "text": "A.B.C", + "kind": "module" + } + ] + }, + { "text": "'X2.Y2.Z2'", "kind": "module", - "kindModifiers": "declare" - } - ] - }, - { - "text": "A.B.C", - "kind": "module", - "childItems": [ - { - "text": "x", - "kind": "var", - "kindModifiers": "export" - } - ], - "indent": 1 - }, - { - "text": "A.B", - "kind": "module", - "childItems": [ - { - "text": "y", - "kind": "var", - "kindModifiers": "export" - } - ], - "indent": 1 - }, - { - "text": "A", - "kind": "module", - "childItems": [ - { - "text": "z", - "kind": "var", - "kindModifiers": "export" - }, - { + "kindModifiers": "declare", + "indent": 1 + }, + { + "text": "\"X.Y.Z\"", + "kind": "module", + "kindModifiers": "declare", + "indent": 1 + }, + { + "text": "A", + "kind": "module", + "childItems": [ + { + "text": "B", + "kind": "module" + }, + { + "text": "z", + "kind": "var", + "kindModifiers": "export" + } + ], + "indent": 1 + }, + { "text": "B", - "kind": "module" - } - ], - "indent": 1 - }, - { - "text": "B", - "kind": "module", - "childItems": [ - { + "kind": "module", + "childItems": [ + { + "text": "C", + "kind": "module" + } + ], + "indent": 2 + }, + { "text": "C", - "kind": "module" - } - ], - "indent": 2 - }, - { - "text": "C", - "kind": "module", - "childItems": [ - { - "text": "x", - "kind": "var", - "kindModifiers": "declare" - } - ], - "indent": 3 - }, - { - "text": "\"X.Y.Z\"", - "kind": "module", - "kindModifiers": "declare", - "indent": 1 - }, - { - "text": "'X2.Y2.Z2'", - "kind": "module", - "kindModifiers": "declare", - "indent": 1 - } + "kind": "module", + "childItems": [ + { + "text": "x", + "kind": "var", + "kindModifiers": "declare" + } + ], + "indent": 3 + }, + { + "text": "A.B", + "kind": "module", + "childItems": [ + { + "text": "y", + "kind": "var", + "kindModifiers": "export" + } + ], + "indent": 1 + }, + { + "text": "A.B.C", + "kind": "module", + "childItems": [ + { + "text": "x", + "kind": "var", + "kindModifiers": "export" + } + ], + "indent": 1 + } ]); diff --git a/tests/cases/fourslash/navigationBarItemsMultilineStringIdentifiers.ts b/tests/cases/fourslash/navigationBarItemsMultilineStringIdentifiers.ts index 2ee4c93a2f1fb..4a29d718f2e0a 100644 --- a/tests/cases/fourslash/navigationBarItemsMultilineStringIdentifiers.ts +++ b/tests/cases/fourslash/navigationBarItemsMultilineStringIdentifiers.ts @@ -30,20 +30,12 @@ verify.navigationBar([ "kind": "module", "childItems": [ { - "text": "Bar", - "kind": "class" - }, - { - "text": "Foo", - "kind": "interface" - }, - { - "text": "\"Multiline\\r\\nMadness\"", + "text": "\"Multiline\\\nMadness\"", "kind": "module", "kindModifiers": "declare" }, { - "text": "\"Multiline\\\nMadness\"", + "text": "\"Multiline\\r\\nMadness\"", "kind": "module", "kindModifiers": "declare" }, @@ -51,9 +43,35 @@ verify.navigationBar([ "text": "\"MultilineMadness\"", "kind": "module", "kindModifiers": "declare" + }, + { + "text": "Bar", + "kind": "class" + }, + { + "text": "Foo", + "kind": "interface" } ] }, + { + "text": "\"Multiline\\\nMadness\"", + "kind": "module", + "kindModifiers": "declare", + "indent": 1 + }, + { + "text": "\"Multiline\\r\\nMadness\"", + "kind": "module", + "kindModifiers": "declare", + "indent": 1 + }, + { + "text": "\"MultilineMadness\"", + "kind": "module", + "kindModifiers": "declare", + "indent": 1 + }, { "text": "Bar", "kind": "class", @@ -83,23 +101,5 @@ verify.navigationBar([ } ], "indent": 1 - }, - { - "text": "\"Multiline\\r\\nMadness\"", - "kind": "module", - "kindModifiers": "declare", - "indent": 1 - }, - { - "text": "\"Multiline\\\nMadness\"", - "kind": "module", - "kindModifiers": "declare", - "indent": 1 - }, - { - "text": "\"MultilineMadness\"", - "kind": "module", - "kindModifiers": "declare", - "indent": 1 } ]); diff --git a/tests/cases/fourslash/navigationBarMerging.ts b/tests/cases/fourslash/navigationBarMerging.ts new file mode 100644 index 0000000000000..192efe5db44b5 --- /dev/null +++ b/tests/cases/fourslash/navigationBarMerging.ts @@ -0,0 +1,189 @@ +/// + +// @Filename: file1.ts +////module a { +//// function foo() {} +////} +////module b { +//// function foo() {} +////} +////module a { +//// function bar() {} +////} + +verify.navigationBar([ + { + "text": "", + "kind": "module", + "childItems": [ + { + "text": "a", + "kind": "module" + }, + { + "text": "b", + "kind": "module" + } + ] + }, + { + "text": "a", + "kind": "module", + "childItems": [ + { + "text": "bar", + "kind": "function" + }, + { + "text": "foo", + "kind": "function" + } + ], + "indent": 1 + }, + { + "text": "b", + "kind": "module", + "childItems": [ + { + "text": "foo", + "kind": "function" + } + ], + "indent": 1 + } +]); + +// Does not merge unlike declarations. +// @Filename: file2.ts +////module a {} +////function a() {} + +goTo.file("file2.ts"); +verify.navigationBar([ + { + "text": "", + "kind": "module", + "childItems": [ + { + "text": "a", + "kind": "function" + }, + { + "text": "a", + "kind": "module" + } + ] + }, + { + "text": "a", + "kind": "function", + "indent": 1 + }, + { + "text": "a", + "kind": "module", + "indent": 1 + } +]); + +// Merges recursively +// @Filename: file3.ts +////module a { +//// interface A { +//// foo: number; +//// } +////} +////module a { +//// interface A { +//// bar: number; +//// } +////} + +goTo.file("file3.ts"); +verify.navigationBar([ + { + "text": "", + "kind": "module", + "childItems": [ + { + "text": "a", + "kind": "module" + } + ] + }, + { + "text": "a", + "kind": "module", + "childItems": [ + { + "text": "A", + "kind": "interface" + } + ], + "indent": 1 + }, + { + "text": "A", + "kind": "interface", + "childItems": [ + { + "text": "bar", + "kind": "property" + }, + { + "text": "foo", + "kind": "property" + } + ], + "indent": 2 + } +]); + +// Does not merge 'module A' with 'module A.B' + +// @Filename: file4.ts +////module A { export var x; } +////module A.B { export var y; } + +goTo.file("file4.ts"); +verify.navigationBar([ + { + "text": "", + "kind": "module", + "childItems": [ + { + "text": "A", + "kind": "module" + }, + { + "text": "A.B", + "kind": "module" + } + ] + }, + { + "text": "A", + "kind": "module", + "childItems": [ + { + "text": "x", + "kind": "var", + "kindModifiers": "export" + } + ], + "indent": 1 + }, + { + "text": "A.B", + "kind": "module", + "childItems": [ + { + "text": "y", + "kind": "var", + "kindModifiers": "export" + } + ], + "indent": 1 + } +]); diff --git a/tests/cases/fourslash/navigationBarVariables.ts b/tests/cases/fourslash/navigationBarVariables.ts new file mode 100644 index 0000000000000..e501d50d4c6f4 --- /dev/null +++ b/tests/cases/fourslash/navigationBarVariables.ts @@ -0,0 +1,26 @@ +/// + +////var x = 0; +////let y = 1; +////const z = 2; + +verify.navigationBar([ + { + "text": "", + "kind": "module", + "childItems": [ + { + "text": "x", + "kind": "var" + }, + { + "text": "y", + "kind": "let" + }, + { + "text": "z", + "kind": "const" + } + ] + } +]); diff --git a/tests/cases/fourslash/server/jsdocTypedefTagNavigateTo.ts b/tests/cases/fourslash/server/jsdocTypedefTagNavigateTo.ts index 77cd75aa44c77..3f8d66d7abd1a 100644 --- a/tests/cases/fourslash/server/jsdocTypedefTagNavigateTo.ts +++ b/tests/cases/fourslash/server/jsdocTypedefTagNavigateTo.ts @@ -11,20 +11,34 @@ //// var numberLike; verify.navigationBar([ - { - "text": "NumberLike", - "kind": "type" - }, - { - "text": "NumberLike2", - "kind": "type" - }, - { - "text": "NumberLike2", - "kind": "var" - }, - { - "text": "numberLike", - "kind": "var" - } + { + "text": "", + "kind": "module", + "childItems": [ + { + "text": "numberLike", + "kind": "var" + }, + { + "text": "NumberLike", + "kind": "type" + }, + { + "text": "NumberLike2", + "kind": "var" + }, + { + "text": "NumberLike2", + "kind": "type" + } + ] + }, + { + "text": "NumberLike", + "kind": "type" + }, + { + "text": "NumberLike2", + "kind": "type" + } ]); \ No newline at end of file From 077489269a4ae661b6e22e6628e6bbfd0c3503f4 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Fri, 3 Jun 2016 09:07:24 -0700 Subject: [PATCH 02/11] Fix locale compare for node 0.10 --- src/services/navigationBar.ts | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index de70015d95b10..d01805b13b36c 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -229,12 +229,23 @@ namespace ts.NavigationBar { if (localeCompareIsCorrect) { return a.localeCompare(b); } - - const cmp = a.toLowerCase().localeCompare(b.toLowerCase()); - if (cmp !== 0) - return cmp; - // Return the *opposite* of the `<` operator - return a < b ? 1 : a > b ? -1 : 0; + else { + // This isn't perfect, but it passes all of our tests. + for (let i = 0; i < Math.min(a.length, b.length); i++) { + const chA = a.charAt(i), chB = b.charAt(i); + if (chA === "\"" && chB === "'") { + return 1; + } + if (chA === "'" && chB === "\"") { + return -1; + } + const cmp = chA.toLowerCase().localeCompare(chB.toLowerCase()); + if (cmp !== 0) { + return cmp; + } + } + return a.length - b.length; + } } /** From eae7ca787f4195c1f2447c0670f12a6e78f41a3a Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Fri, 3 Jun 2016 09:32:20 -0700 Subject: [PATCH 03/11] Fix lint error --- src/compiler/core.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index d9924cf9e5e54..ffd10c37b5d29 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -137,7 +137,7 @@ namespace ts { } return result; } - + export function filterMutate(array: T[], f: (x: T) => boolean): void { let outIndex = 0; for (const item of array) { From 19ce6f712616c987f503debf40cb83d84db93a76 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Fri, 3 Jun 2016 10:55:06 -0700 Subject: [PATCH 04/11] Remove call to `isDeclaration`; since we use a different idea of a declaration, just use a single switch statement. --- src/services/navigationBar.ts | 173 +++++++++++++++++----------------- 1 file changed, 87 insertions(+), 86 deletions(-) diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index d01805b13b36c..c772c3ea89113 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -44,101 +44,102 @@ namespace ts.NavigationBar { /** Traverse through parent.node's descendants and find declarations to add as parent's children. */ function addChildren(parent: NavNode): void { - function recur(node: Node) { - if (isDeclaration(node)) { - switch (node.kind) { - case SyntaxKind.Parameter: // Parameter properties handled by SyntaxKind.Constructor case - case SyntaxKind.TypeParameter: - case SyntaxKind.PropertyAssignment: - // Don't treat this as a declaration. - forEachChild(node, recur); - break; - - case SyntaxKind.Constructor: - // Get parameter properties, and treat them as being on the *same* level as the constructor, not under it. - const ctr = node; - createNavNode(parent, ctr); - for (const param of ctr.parameters) { - if (isParameterPropertyDeclaration(param)) { - createNavNode(parent, param); - } + function recur(node: Node): void { + switch (node.kind) { + case SyntaxKind.Constructor: + // Get parameter properties, and treat them as being on the *same* level as the constructor, not under it. + const ctr = node; + createNavNode(parent, ctr); + for (const param of ctr.parameters) { + if (isParameterPropertyDeclaration(param)) { + createNavNode(parent, param); } - break; + } + break; - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - if (!hasDynamicName(( node))) { - createNavNode(parent, node); - } - break; + case SyntaxKind.MethodDeclaration: + case SyntaxKind.MethodSignature: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + if (!hasDynamicName(( node))) { + createNavNode(parent, node); + } + break; - case SyntaxKind.EnumMember: - if (!isComputedProperty(node)) { - createNavNode(parent, node); - } - break; - - case SyntaxKind.ImportClause: - let importClause = node; - // Handle default import case e.g.: - // import d from "mod"; - if (importClause.name) { - createNavNode(parent, importClause); - } + case SyntaxKind.EnumMember: + if (!isComputedProperty(node)) { + createNavNode(parent, node); + } + break; + + case SyntaxKind.ImportClause: + let importClause = node; + // Handle default import case e.g.: + // import d from "mod"; + if (importClause.name) { + createNavNode(parent, importClause); + } - // Handle named bindings in imports e.g.: - // import * as NS from "mod"; - // import {a, b as B} from "mod"; - if (importClause.namedBindings) { - if (importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { - createNavNode(parent, importClause.namedBindings); - } - else { - forEach((importClause.namedBindings).elements, recur); - } - } - break; - - case SyntaxKind.BindingElement: - case SyntaxKind.VariableDeclaration: - const decl = node; - const name = decl.name; - if (isBindingPattern(name)) { - recur(name); - } - else if (decl.initializer && isFunctionOrClassExpression(decl.initializer)) { - // For `const x = function() {}`, just use the function node, not the const. - recur(decl.initializer); + // Handle named bindings in imports e.g.: + // import * as NS from "mod"; + // import {a, b as B} from "mod"; + if (importClause.namedBindings) { + if (importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { + createNavNode(parent, importClause.namedBindings); } else { - createNavNode(parent, node); + forEach((importClause.namedBindings).elements, recur); } - break; - - default: - createNavNode(parent, node); - } - } - else { - switch (node.kind) { - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.IndexSignature: + } + break; + + case SyntaxKind.BindingElement: + case SyntaxKind.VariableDeclaration: + const decl = node; + const name = decl.name; + if (isBindingPattern(name)) { + recur(name); + } + else if (decl.initializer && isFunctionOrClassExpression(decl.initializer)) { + // For `const x = function() {}`, just use the function node, not the const. + recur(decl.initializer); + } + else { createNavNode(parent, node); - break; - default: - if (node.jsDocComments) { - for (const jsDocComment of node.jsDocComments) { - recur(jsDocComment); - } + } + break; + + case SyntaxKind.ArrowFunction: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.ExportSpecifier: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.ImportSpecifier: + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.ModuleDeclaration: + case SyntaxKind.NamespaceImport: + case SyntaxKind.ShorthandPropertyAssignment: + case SyntaxKind.TypeAliasDeclaration: + case SyntaxKind.JSDocTypedefTag: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.IndexSignature: + createNavNode(parent, node); + break; + + default: + if (node.jsDocComments) { + for (const jsDocComment of node.jsDocComments) { + recur(jsDocComment); } + } - forEachChild(node, recur); - } + forEachChild(node, recur); } } @@ -514,7 +515,7 @@ namespace ts.NavigationBar { return member.name === undefined || member.name.kind === SyntaxKind.ComputedPropertyName; } - function getNodeSpan(node: Node) { + function getNodeSpan(node: Node): TextSpan { return node.kind === SyntaxKind.SourceFile ? createTextSpanFromBounds(node.getFullStart(), node.getEnd()) : createTextSpanFromBounds(node.getStart(), node.getEnd()); From 981a9836ecd0c822b2524ebbe7aca508993f307e Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Fri, 3 Jun 2016 11:09:03 -0700 Subject: [PATCH 05/11] Add more tests --- tests/cases/fourslash/navigationBarImports.ts | 30 +++++++++++++++++++ .../cases/fourslash/navigationBarVariables.ts | 27 +++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 tests/cases/fourslash/navigationBarImports.ts diff --git a/tests/cases/fourslash/navigationBarImports.ts b/tests/cases/fourslash/navigationBarImports.ts new file mode 100644 index 0000000000000..d6dab49cfce3e --- /dev/null +++ b/tests/cases/fourslash/navigationBarImports.ts @@ -0,0 +1,30 @@ +/// + +////import a, {b} from "m"; +////import c = require("m"); +////import * as d from "m"; + +verify.navigationBar([ + { + "text": "\"navigationBarImportEquals\"", + "kind": "module", + "childItems": [ + { + "text": "a", + "kind": "alias" + }, + { + "text": "b", + "kind": "alias" + }, + { + "text": "c", + "kind": "alias" + }, + { + "text": "d", + "kind": "alias" + } + ] + } +]); diff --git a/tests/cases/fourslash/navigationBarVariables.ts b/tests/cases/fourslash/navigationBarVariables.ts index e501d50d4c6f4..42344c96f73df 100644 --- a/tests/cases/fourslash/navigationBarVariables.ts +++ b/tests/cases/fourslash/navigationBarVariables.ts @@ -24,3 +24,30 @@ verify.navigationBar([ ] } ]); + +// @Filename: file2.ts +////var {a} = 0; +////let {a: b} = 0; +////const [c] = 0; + +goTo.file("file2.ts"); +verify.navigationBar([ + { + "text": "", + "kind": "module", + "childItems": [ + { + "text": "a", + "kind": "var" + }, + { + "text": "b", + "kind": "let" + }, + { + "text": "c", + "kind": "const" + } + ] + } +]); From 56b91fdbd422b1c8d0d498d0733d1c45a99cb921 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Fri, 3 Jun 2016 11:13:00 -0700 Subject: [PATCH 06/11] Fix name --- tests/cases/fourslash/navigationBarImports.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cases/fourslash/navigationBarImports.ts b/tests/cases/fourslash/navigationBarImports.ts index d6dab49cfce3e..43cb99c556a7e 100644 --- a/tests/cases/fourslash/navigationBarImports.ts +++ b/tests/cases/fourslash/navigationBarImports.ts @@ -6,7 +6,7 @@ verify.navigationBar([ { - "text": "\"navigationBarImportEquals\"", + "text": "\"navigationBarImports\"", "kind": "module", "childItems": [ { From 902a667097e2cad04b3bdea433c575f87929e472 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Mon, 6 Jun 2016 06:02:48 -0700 Subject: [PATCH 07/11] Fix test --- tests/cases/fourslash/server/jsdocTypedefTagNavigateTo.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/cases/fourslash/server/jsdocTypedefTagNavigateTo.ts b/tests/cases/fourslash/server/jsdocTypedefTagNavigateTo.ts index 3f8d66d7abd1a..1c617091f4c27 100644 --- a/tests/cases/fourslash/server/jsdocTypedefTagNavigateTo.ts +++ b/tests/cases/fourslash/server/jsdocTypedefTagNavigateTo.ts @@ -35,10 +35,12 @@ verify.navigationBar([ }, { "text": "NumberLike", - "kind": "type" + "kind": "type", + "indent": 1, }, { "text": "NumberLike2", - "kind": "type" + "kind": "type", + "indent": 1 } ]); \ No newline at end of file From 1c9e64b0f2ce20517ea82047e9f82c011ca268e1 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Thu, 9 Jun 2016 13:06:14 -0700 Subject: [PATCH 08/11] Respond to PR comments --- src/services/navigationBar.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index c772c3ea89113..39ea5206450c6 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -13,8 +13,8 @@ namespace ts.NavigationBar { */ interface NavNode { node: Node; - additionalNodes: Node[]; // May be missing - parent: NavNode; // Missing for root decl + additionalNodes?: Node[]; + parent?: NavNode; // Missing for root decl children: NavNode[]; indent: number; // # of parents } @@ -32,8 +32,7 @@ namespace ts.NavigationBar { additionalNodes: undefined, parent, children: [], - indent: - parent ? parent.indent + 1 : 0 + indent: parent ? parent.indent + 1 : 0 }; if (parent) { parent.children.push(navNode); From 2f5f7e46d3396109954e80cd4cc732257fd1e037 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Mon, 13 Jun 2016 06:10:50 -0700 Subject: [PATCH 09/11] Respond to some PR comments --- src/services/navigationBar.ts | 68 +++++++++++++++++------------------ 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index 39ea5206450c6..c6487a72ff2eb 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -2,32 +2,32 @@ /* @internal */ namespace ts.NavigationBar { - export function getNavigationBarItems(sourceFile: SourceFile): NavigationBarItem[] { - const root = createNavNode(undefined, sourceFile); - return map(topLevelItems(root), convertToTopLevelItem); - } - /** * Represents a navBar item and its children. * The returned NavigationBarItem is more complicated and doesn't include 'parent', so we use these to do work before converting. */ - interface NavNode { + interface NavigationBarNode { node: Node; additionalNodes?: Node[]; - parent?: NavNode; // Missing for root decl - children: NavNode[]; + parent?: NavigationBarNode; // Missing for root + children: NavigationBarNode[]; indent: number; // # of parents } - function navKind(n: NavNode): SyntaxKind { + function navKind(n: NavigationBarNode): SyntaxKind { return n.node.kind; } - function navModifiers(n: NavNode): string { + function navModifiers(n: NavigationBarNode): string { return getNodeModifiers(n.node); } + export function getNavigationBarItems(sourceFile: SourceFile): NavigationBarItem[] { + const root = createNavigationBarNode(/*parent*/ undefined, sourceFile); + return map(topLevelItems(root), convertToTopLevelItem); + } + /** Creates a child node and adds it to parent. */ - function createNavNode(parent: NavNode, node: Node): NavNode { - const navNode: NavNode = { + function createNavigationBarNode(parent: NavigationBarNode, node: Node): NavigationBarNode { + const navNode: NavigationBarNode = { node, additionalNodes: undefined, parent, @@ -42,16 +42,16 @@ namespace ts.NavigationBar { } /** Traverse through parent.node's descendants and find declarations to add as parent's children. */ - function addChildren(parent: NavNode): void { + function addChildren(parent: NavigationBarNode): void { function recur(node: Node): void { switch (node.kind) { case SyntaxKind.Constructor: // Get parameter properties, and treat them as being on the *same* level as the constructor, not under it. const ctr = node; - createNavNode(parent, ctr); + createNavigationBarNode(parent, ctr); for (const param of ctr.parameters) { if (isParameterPropertyDeclaration(param)) { - createNavNode(parent, param); + createNavigationBarNode(parent, param); } } break; @@ -63,13 +63,13 @@ namespace ts.NavigationBar { case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: if (!hasDynamicName(( node))) { - createNavNode(parent, node); + createNavigationBarNode(parent, node); } break; case SyntaxKind.EnumMember: if (!isComputedProperty(node)) { - createNavNode(parent, node); + createNavigationBarNode(parent, node); } break; @@ -78,7 +78,7 @@ namespace ts.NavigationBar { // Handle default import case e.g.: // import d from "mod"; if (importClause.name) { - createNavNode(parent, importClause); + createNavigationBarNode(parent, importClause); } // Handle named bindings in imports e.g.: @@ -86,7 +86,7 @@ namespace ts.NavigationBar { // import {a, b as B} from "mod"; if (importClause.namedBindings) { if (importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { - createNavNode(parent, importClause.namedBindings); + createNavigationBarNode(parent, importClause.namedBindings); } else { forEach((importClause.namedBindings).elements, recur); @@ -106,7 +106,7 @@ namespace ts.NavigationBar { recur(decl.initializer); } else { - createNavNode(parent, node); + createNavigationBarNode(parent, node); } break; @@ -128,7 +128,7 @@ namespace ts.NavigationBar { case SyntaxKind.CallSignature: case SyntaxKind.ConstructSignature: case SyntaxKind.IndexSignature: - createNavNode(parent, node); + createNavigationBarNode(parent, node); break; default: @@ -153,8 +153,8 @@ namespace ts.NavigationBar { } /** Merge declarations of the same kind. */ - function mergeChildren(children: NavNode[]): void { - const nameToNavNodes: Map = {}; + function mergeChildren(children: NavigationBarNode[]): void { + const nameToNavNodes: Map = {}; filterMutate(children, child => { const decl = child.node; const name = decl.name && decl.name.getText(); @@ -196,7 +196,7 @@ namespace ts.NavigationBar { } /** Merge source into target. Source should be thrown away after this is called. */ - function merge(target: NavNode, source: NavNode): void { + function merge(target: NavigationBarNode, source: NavigationBarNode): void { target.additionalNodes = target.additionalNodes || []; target.additionalNodes.push(source.node); if (source.additionalNodes) { @@ -210,7 +210,7 @@ namespace ts.NavigationBar { } /** Recursively ensure that each NavNode's children are in sorted order. */ - function sortChildren(children: NavNode[]): void { + function sortChildren(children: NavigationBarNode[]): void { children.sort((child1, child2) => { const name1 = tryGetName(child1.node), name2 = tryGetName(child2.node); if (name1 && name2) { @@ -239,7 +239,7 @@ namespace ts.NavigationBar { if (chA === "'" && chB === "\"") { return -1; } - const cmp = chA.toLowerCase().localeCompare(chB.toLowerCase()); + const cmp = chA.toLocaleLowerCase().localeCompare(chB.toLocaleLowerCase()); if (cmp !== 0) { return cmp; } @@ -336,9 +336,9 @@ namespace ts.NavigationBar { } /** Flattens the NavNode tree to a list, keeping only the top-level items. */ - function topLevelItems(root: NavNode): NavNode[] { - const topLevel: NavNode[] = []; - function recur(item: NavNode) { + function topLevelItems(root: NavigationBarNode): NavigationBarNode[] { + const topLevel: NavigationBarNode[] = []; + function recur(item: NavigationBarNode) { if (isTopLevel(item)) { topLevel.push(item); for (const child of item.children) { @@ -349,7 +349,7 @@ namespace ts.NavigationBar { recur(root); return topLevel; - function isTopLevel(item: NavNode): boolean { + function isTopLevel(item: NavigationBarNode): boolean { switch (navKind(item)) { case SyntaxKind.ClassDeclaration: case SyntaxKind.ClassExpression: @@ -375,7 +375,7 @@ namespace ts.NavigationBar { default: return false; } - function isTopLevelFunctionDeclaration(item: NavNode): boolean { + function isTopLevelFunctionDeclaration(item: NavigationBarNode): boolean { if (!(item.node).body) { return false; } @@ -390,7 +390,7 @@ namespace ts.NavigationBar { return hasSomeImportantChild(item); } } - function hasSomeImportantChild(item: NavNode) { + function hasSomeImportantChild(item: NavigationBarNode) { return forEach(item.children, child => { const childKind = navKind(child); return childKind !== SyntaxKind.VariableDeclaration && childKind !== SyntaxKind.BindingElement; @@ -399,7 +399,7 @@ namespace ts.NavigationBar { } } - function convertToTopLevelItem(n: NavNode): NavigationBarItem { + function convertToTopLevelItem(n: NavigationBarNode): NavigationBarItem { const spans = [getNodeSpan(n.node)]; return { text: getItemName(n.node), @@ -412,7 +412,7 @@ namespace ts.NavigationBar { grayed: false }; - function convertToChildItem(n: NavNode): NavigationBarItem { + function convertToChildItem(n: NavigationBarNode): NavigationBarItem { const nodes = [n.node]; if (n.additionalNodes) { nodes.push(...n.additionalNodes); From 7eb3ce6ac86ba295d4172b8bb3d4d21fa1d69ed3 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Mon, 13 Jun 2016 11:20:26 -0700 Subject: [PATCH 10/11] Improve performance --- src/services/navigationBar.ts | 358 +++++++++++++++++++++------------- 1 file changed, 221 insertions(+), 137 deletions(-) diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index c6487a72ff2eb..53bf7cc7d99cc 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -3,153 +3,226 @@ /* @internal */ namespace ts.NavigationBar { /** - * Represents a navBar item and its children. + * Represents a navigation bar item and its children. * The returned NavigationBarItem is more complicated and doesn't include 'parent', so we use these to do work before converting. */ interface NavigationBarNode { node: Node; additionalNodes?: Node[]; - parent?: NavigationBarNode; // Missing for root - children: NavigationBarNode[]; + parent?: NavigationBarNode; // Present for all but root node + children?: NavigationBarNode[]; indent: number; // # of parents } - function navKind(n: NavigationBarNode): SyntaxKind { + + export function getNavigationBarItems(sourceFile: SourceFile): NavigationBarItem[] { + return map(topLevelItems(rootNavigationBarNode(sourceFile)), convertToTopLevelItem); + } + + function navigationBarNodeKind(n: NavigationBarNode): SyntaxKind { return n.node.kind; } - function navModifiers(n: NavigationBarNode): string { - return getNodeModifiers(n.node); + + function pushChild(parent: NavigationBarNode, child: NavigationBarNode): void { + if (parent.children) { + parent.children.push(child); + } + else { + parent.children = [child]; + } + } + + /* + For performance, we keep navigation bar parents on a stack rather than passing them through each recursion. + `parent` is the current parent and is *not* stored in parentsStack. + `startNode` sets a new parent and `endNode` returns to the previous parent. + */ + const parentsStack: NavigationBarNode[] = []; + let parent: NavigationBarNode; + + function rootNavigationBarNode(sourceFile: SourceFile): NavigationBarNode { + Debug.assert(!parentsStack.length); + const root: NavigationBarNode = { node: sourceFile, additionalNodes: undefined, parent: undefined, children: undefined, indent: 0 }; + parent = root; + for (const statement of sourceFile.statements) { + addChildrenRecursively(statement); + } + endNode(); + Debug.assert(parent === undefined && !parentsStack.length); + return root; } - export function getNavigationBarItems(sourceFile: SourceFile): NavigationBarItem[] { - const root = createNavigationBarNode(/*parent*/ undefined, sourceFile); - return map(topLevelItems(root), convertToTopLevelItem); + function addLeafNode(node: Node): void { + pushChild(parent, emptyNavigationBarNode(node)); } - /** Creates a child node and adds it to parent. */ - function createNavigationBarNode(parent: NavigationBarNode, node: Node): NavigationBarNode { - const navNode: NavigationBarNode = { + function emptyNavigationBarNode(node: Node): NavigationBarNode { + return { node, additionalNodes: undefined, parent, - children: [], - indent: parent ? parent.indent + 1 : 0 + children: undefined, + indent: parent.indent + 1 }; - if (parent) { - parent.children.push(navNode); + } + + /** + * Add a new level of NavigationBarNodes. + * This pushes to the stack, so you must call `endNode` when you are done adding to this node. + */ + function startNode(node: Node): void { + const navNode: NavigationBarNode = emptyNavigationBarNode(node); + pushChild(parent, navNode); + + // Save the old parent + parentsStack.push(parent); + parent = navNode; + } + + /** + * Call after calling `startNode` and adding children to it. + */ + function endNode(): void { + if (parent.children) { + mergeChildren(parent.children); + sortChildren(parent.children); } - addChildren(navNode); - return navNode; + parent = parentsStack.pop(); } - /** Traverse through parent.node's descendants and find declarations to add as parent's children. */ - function addChildren(parent: NavigationBarNode): void { - function recur(node: Node): void { - switch (node.kind) { - case SyntaxKind.Constructor: - // Get parameter properties, and treat them as being on the *same* level as the constructor, not under it. - const ctr = node; - createNavigationBarNode(parent, ctr); - for (const param of ctr.parameters) { - if (isParameterPropertyDeclaration(param)) { - createNavigationBarNode(parent, param); - } - } - break; + function addNodeWithRecursiveChild(node: Node, child: Node): void { + startNode(node); + addChildrenRecursively(child); + endNode(); + } - case SyntaxKind.MethodDeclaration: - case SyntaxKind.MethodSignature: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.PropertyDeclaration: - case SyntaxKind.PropertySignature: - if (!hasDynamicName(( node))) { - createNavigationBarNode(parent, node); - } - break; + /** Look for navigation bar items in node's subtree, adding them to the current `parent`. */ + function addChildrenRecursively(node: Node): void { + if (!node || isToken(node)) { + return; + } - case SyntaxKind.EnumMember: - if (!isComputedProperty(node)) { - createNavigationBarNode(parent, node); - } - break; - - case SyntaxKind.ImportClause: - let importClause = node; - // Handle default import case e.g.: - // import d from "mod"; - if (importClause.name) { - createNavigationBarNode(parent, importClause); + switch (node.kind) { + case SyntaxKind.Constructor: + // Get parameter properties, and treat them as being on the *same* level as the constructor, not under it. + const ctr = node; + addNodeWithRecursiveChild(ctr, ctr.body); + + // Parameter properties are children of the class, not the constructor. + for (const param of ctr.parameters) { + if (isParameterPropertyDeclaration(param)) { + addLeafNode(param); } + } + break; + + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + case SyntaxKind.MethodSignature: + if (!hasDynamicName((node))) { + addNodeWithRecursiveChild(node, (node).body); + } + break; - // Handle named bindings in imports e.g.: - // import * as NS from "mod"; - // import {a, b as B} from "mod"; - if (importClause.namedBindings) { - if (importClause.namedBindings.kind === SyntaxKind.NamespaceImport) { - createNavigationBarNode(parent, importClause.namedBindings); - } - else { - forEach((importClause.namedBindings).elements, recur); - } - } - break; - - case SyntaxKind.BindingElement: - case SyntaxKind.VariableDeclaration: - const decl = node; - const name = decl.name; - if (isBindingPattern(name)) { - recur(name); - } - else if (decl.initializer && isFunctionOrClassExpression(decl.initializer)) { - // For `const x = function() {}`, just use the function node, not the const. - recur(decl.initializer); + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertySignature: + if (!hasDynamicName((node))) { + addLeafNode(node); + } + break; + + case SyntaxKind.ImportClause: + let importClause = node; + // Handle default import case e.g.: + // import d from "mod"; + if (importClause.name) { + addLeafNode(importClause); + } + + // Handle named bindings in imports e.g.: + // import * as NS from "mod"; + // import {a, b as B} from "mod"; + const {namedBindings} = importClause; + if (namedBindings) { + if (namedBindings.kind === SyntaxKind.NamespaceImport) { + addLeafNode(namedBindings); } else { - createNavigationBarNode(parent, node); + forEach((namedBindings).elements, addLeafNode); } - break; + } + break; - case SyntaxKind.ArrowFunction: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.ClassExpression: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.ExportSpecifier: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ImportEqualsDeclaration: - case SyntaxKind.ImportSpecifier: - case SyntaxKind.InterfaceDeclaration: - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.NamespaceImport: - case SyntaxKind.ShorthandPropertyAssignment: - case SyntaxKind.TypeAliasDeclaration: - case SyntaxKind.JSDocTypedefTag: - case SyntaxKind.CallSignature: - case SyntaxKind.ConstructSignature: - case SyntaxKind.IndexSignature: - createNavigationBarNode(parent, node); - break; + case SyntaxKind.BindingElement: + case SyntaxKind.VariableDeclaration: + const decl = node; + const name = decl.name; + if (isBindingPattern(name)) { + addChildrenRecursively(name); + } + else if (decl.initializer && isFunctionOrClassExpression(decl.initializer)) { + // For `const x = function() {}`, just use the function node, not the const. + addChildrenRecursively(decl.initializer); + } + else { + addNodeWithRecursiveChild(decl, decl.initializer); + } + break; - default: - if (node.jsDocComments) { - for (const jsDocComment of node.jsDocComments) { - recur(jsDocComment); - } + case SyntaxKind.ArrowFunction: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + addNodeWithRecursiveChild(node, (node).body); + break; + + case SyntaxKind.EnumDeclaration: + startNode(node); + for (const member of (node).members) { + if (!isComputedProperty(member)) { + addLeafNode(member); } + } + endNode(); + break; - forEachChild(node, recur); - } - } + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.InterfaceDeclaration: + startNode(node); + for (const member of (node).members) { + addChildrenRecursively(member); + } + endNode(); + break; - let parentNode = parent.node; - if (parentNode.kind === SyntaxKind.ModuleDeclaration) { - parentNode = getInteriorModule(parentNode); - } - forEachChild(parentNode, recur); + case SyntaxKind.ModuleDeclaration: + startNode(node); + forEachChild(getInteriorModule(node), addChildrenRecursively); + endNode(); + break; + + case SyntaxKind.ExportSpecifier: + case SyntaxKind.ImportEqualsDeclaration: + case SyntaxKind.IndexSignature: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.TypeAliasDeclaration: + addLeafNode(node); + break; + + default: + if (node.jsDocComments) { + for (const jsDocComment of node.jsDocComments) { + for (const tag of jsDocComment.tags) { + if (tag.kind === SyntaxKind.JSDocTypedefTag) { + addLeafNode(tag); + } + } + } + } - mergeChildren(parent.children); - sortChildren(parent.children); + forEachChild(node, addChildrenRecursively); + } } /** Merge declarations of the same kind. */ @@ -162,7 +235,7 @@ namespace ts.NavigationBar { // Anonymous items are never merged. return true; - const itemsWithSameName = nameToNavNodes[name]; + const itemsWithSameName = getProperty(nameToNavNodes, name); if (!itemsWithSameName) { nameToNavNodes[name] = [child]; return true; @@ -203,9 +276,11 @@ namespace ts.NavigationBar { target.additionalNodes.push(...source.additionalNodes); } - target.children.push(...source.children); - mergeChildren(target.children); - sortChildren(target.children); + target.children = concatenate(target.children, source.children); + if (target.children) { + mergeChildren(target.children); + sortChildren(target.children); + } } } @@ -215,10 +290,10 @@ namespace ts.NavigationBar { const name1 = tryGetName(child1.node), name2 = tryGetName(child2.node); if (name1 && name2) { const cmp = localeCompareFix(name1, name2); - return cmp !== 0 ? cmp : navKind(child1) - navKind(child2); + return cmp !== 0 ? cmp : navigationBarNodeKind(child1) - navigationBarNodeKind(child2); } else { - return name1 ? 1 : name2 ? -1 : navKind(child1) - navKind(child2); + return name1 ? 1 : name2 ? -1 : navigationBarNodeKind(child1) - navigationBarNodeKind(child2); } }); } @@ -341,8 +416,10 @@ namespace ts.NavigationBar { function recur(item: NavigationBarNode) { if (isTopLevel(item)) { topLevel.push(item); - for (const child of item.children) { - recur(child); + if (item.children) { + for (const child of item.children) { + recur(child); + } } } } @@ -350,7 +427,7 @@ namespace ts.NavigationBar { return topLevel; function isTopLevel(item: NavigationBarNode): boolean { - switch (navKind(item)) { + switch (navigationBarNodeKind(item)) { case SyntaxKind.ClassDeclaration: case SyntaxKind.ClassExpression: case SyntaxKind.EnumDeclaration: @@ -380,7 +457,7 @@ namespace ts.NavigationBar { return false; } - switch (navKind(item.parent)) { + switch (navigationBarNodeKind(item.parent)) { case SyntaxKind.ModuleBlock: case SyntaxKind.SourceFile: case SyntaxKind.MethodDeclaration: @@ -392,43 +469,50 @@ namespace ts.NavigationBar { } function hasSomeImportantChild(item: NavigationBarNode) { return forEach(item.children, child => { - const childKind = navKind(child); + const childKind = navigationBarNodeKind(child); return childKind !== SyntaxKind.VariableDeclaration && childKind !== SyntaxKind.BindingElement; }); } } } + // NavigationBarItem requires an array, but will not mutate it, so just give it this for performance. + const emptyChildItemArray: NavigationBarItem[] = []; + function convertToTopLevelItem(n: NavigationBarNode): NavigationBarItem { - const spans = [getNodeSpan(n.node)]; return { text: getItemName(n.node), kind: nodeKind(n.node), - kindModifiers: navModifiers(n), - spans, - childItems: map(n.children, convertToChildItem), + kindModifiers: getNodeModifiers(n.node), + spans: getSpans(n), + childItems: map(n.children, convertToChildItem) || emptyChildItemArray, indent: n.indent, bolded: false, grayed: false }; function convertToChildItem(n: NavigationBarNode): NavigationBarItem { - const nodes = [n.node]; - if (n.additionalNodes) { - nodes.push(...n.additionalNodes); - } - const spans = map(nodes, getNodeSpan); return { text: getItemName(n.node), kind: nodeKind(n.node), - kindModifiers: navModifiers(n), - spans, - childItems: [], + kindModifiers: getNodeModifiers(n.node), + spans: getSpans(n), + childItems: emptyChildItemArray, indent: 0, bolded: false, grayed: false }; } + + function getSpans(n: NavigationBarNode): TextSpan[] { + const spans = [getNodeSpan(n.node)]; + if (n.additionalNodes) { + for (const node of n.additionalNodes) { + spans.push(getNodeSpan(node)); + } + } + return spans; + } } // TODO: We should just use getNodeKind. No reason why navigationBar and navigateTo should have different behaviors. From a7fe7b586834c5eabe190657b438f656aefc489d Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Thu, 16 Jun 2016 10:02:09 -0700 Subject: [PATCH 11/11] More performance improvement --- src/compiler/parser.ts | 6 +- src/compiler/utilities.ts | 4 +- src/server/client.ts | 17 ++-- src/server/editorServices.ts | 12 ++- src/server/session.ts | 10 +-- src/services/navigationBar.ts | 153 ++++++++++++++++++++-------------- 6 files changed, 118 insertions(+), 84 deletions(-) diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index b24dd85f2f0dd..9ead0cb8b0b35 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -17,19 +17,19 @@ namespace ts { } function visitNode(cbNode: (node: Node) => T, node: Node): T { - if (node) { + if (node !== void 0) { return cbNode(node); } } function visitNodeArray(cbNodes: (nodes: Node[]) => T, nodes: Node[]) { - if (nodes) { + if (nodes !== void 0) { return cbNodes(nodes); } } function visitEachNode(cbNode: (node: Node) => T, nodes: Node[]) { - if (nodes) { + if (nodes !== void 0) { for (const node of nodes) { const result = cbNode(node); if (result) { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 74ea3459b2359..d246b55556c8b 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -302,8 +302,8 @@ namespace ts { return getTokenPosOfNode(node.jsDocComments[0]); } - // For a syntax list, it is possible that one of its children has JSDocComment nodes, while - // the syntax list itself considers them as normal trivia. Therefore if we simply skip + // For a syntax list, it is possible that one of its children has JSDocComment nodes, while + // the syntax list itself considers them as normal trivia. Therefore if we simply skip // trivia for the list, we may have skipped the JSDocComment as well. So we should process its // first child to determine the actual position of its first token. if (node.kind === SyntaxKind.SyntaxList && (node)._children.length > 0) { diff --git a/src/server/client.ts b/src/server/client.ts index b0a1a8ec9e913..459f723d25381 100644 --- a/src/server/client.ts +++ b/src/server/client.ts @@ -45,8 +45,9 @@ namespace ts.server { return lineMap; } - private lineOffsetToPosition(fileName: string, lineOffset: protocol.Location): number { - return ts.computePositionOfLineAndCharacter(this.getLineMap(fileName), lineOffset.line - 1, lineOffset.offset - 1); + private lineOffsetToPosition(fileName: string, lineOffset: protocol.Location, lineMap?: number[]): number { + lineMap = lineMap || this.getLineMap(fileName); + return ts.computePositionOfLineAndCharacter(lineMap, lineOffset.line - 1, lineOffset.offset - 1); } private positionToOneBasedLineOffset(fileName: string, position: number): protocol.Location { @@ -448,7 +449,7 @@ namespace ts.server { return this.lastRenameEntry.locations; } - decodeNavigationBarItems(items: protocol.NavigationBarItem[], fileName: string): NavigationBarItem[] { + decodeNavigationBarItems(items: protocol.NavigationBarItem[], fileName: string, lineMap: number[]): NavigationBarItem[] { if (!items) { return []; } @@ -457,8 +458,11 @@ namespace ts.server { text: item.text, kind: item.kind, kindModifiers: item.kindModifiers || "", - spans: item.spans.map(span => createTextSpanFromBounds(this.lineOffsetToPosition(fileName, span.start), this.lineOffsetToPosition(fileName, span.end))), - childItems: this.decodeNavigationBarItems(item.childItems, fileName), + spans: item.spans.map(span => + createTextSpanFromBounds( + this.lineOffsetToPosition(fileName, span.start, lineMap), + this.lineOffsetToPosition(fileName, span.end, lineMap))), + childItems: this.decodeNavigationBarItems(item.childItems, fileName, lineMap), indent: item.indent, bolded: false, grayed: false @@ -473,7 +477,8 @@ namespace ts.server { const request = this.processRequest(CommandNames.NavBar, args); const response = this.processResponse(request); - return this.decodeNavigationBarItems(response.body, fileName); + const lineMap = this.getLineMap(fileName); + return this.decodeNavigationBarItems(response.body, fileName, lineMap); } getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): TextSpan { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 8eae43b503d3f..0ed277d448956 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -353,12 +353,16 @@ namespace ts.server { * @param line 1-based index * @param offset 1-based index */ - positionToLineOffset(filename: string, position: number): ILineInfo { + positionToLineOffset(filename: string, position: number, lineIndex?: LineIndex): ILineInfo { + lineIndex = lineIndex || this.getLineIndex(filename); + const lineOffset = lineIndex.charOffsetToLineNumberAndPos(position); + return { line: lineOffset.line, offset: lineOffset.offset + 1 }; + } + + getLineIndex(filename: string): LineIndex { const path = toPath(filename, this.host.getCurrentDirectory(), this.getCanonicalFileName); const script: ScriptInfo = this.filenameToScript.get(path); - const index = script.snap().index; - const lineOffset = index.charOffsetToLineNumberAndPos(position); - return { line: lineOffset.line, offset: lineOffset.offset + 1 }; + return script.snap().index; } } diff --git a/src/server/session.ts b/src/server/session.ts index 7f3ba105999a4..18e382183822a 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -857,7 +857,7 @@ namespace ts.server { this.projectService.closeClientFile(file); } - private decorateNavigationBarItem(project: Project, fileName: string, items: ts.NavigationBarItem[]): protocol.NavigationBarItem[] { + private decorateNavigationBarItem(project: Project, fileName: string, items: ts.NavigationBarItem[], lineIndex: LineIndex): protocol.NavigationBarItem[] { if (!items) { return undefined; } @@ -869,10 +869,10 @@ namespace ts.server { kind: item.kind, kindModifiers: item.kindModifiers, spans: item.spans.map(span => ({ - start: compilerService.host.positionToLineOffset(fileName, span.start), - end: compilerService.host.positionToLineOffset(fileName, ts.textSpanEnd(span)) + start: compilerService.host.positionToLineOffset(fileName, span.start, lineIndex), + end: compilerService.host.positionToLineOffset(fileName, ts.textSpanEnd(span), lineIndex) })), - childItems: this.decorateNavigationBarItem(project, fileName, item.childItems), + childItems: this.decorateNavigationBarItem(project, fileName, item.childItems, lineIndex), indent: item.indent })); } @@ -890,7 +890,7 @@ namespace ts.server { return undefined; } - return this.decorateNavigationBarItem(project, fileName, items); + return this.decorateNavigationBarItem(project, fileName, items, compilerService.host.getLineIndex(fileName)); } private getNavigateToItems(searchValue: string, fileName: string, maxResultCount?: number): protocol.NavtoItem[] { diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index 53bf7cc7d99cc..afe74baf78fd2 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -14,8 +14,17 @@ namespace ts.NavigationBar { indent: number; // # of parents } - export function getNavigationBarItems(sourceFile: SourceFile): NavigationBarItem[] { - return map(topLevelItems(rootNavigationBarNode(sourceFile)), convertToTopLevelItem); + export function getNavigationBarItems(sourceFile_: SourceFile): NavigationBarItem[] { + sourceFile = sourceFile_; + const result = map(topLevelItems(rootNavigationBarNode(sourceFile)), convertToTopLevelItem); + sourceFile = void 0; + return result; + } + + // Keep sourceFile handy so we don't have to search for it every time we need to call `getText`. + let sourceFile: SourceFile; + function nodeText(node: Node): string { + return node.getText(sourceFile); } function navigationBarNodeKind(n: NavigationBarNode): SyntaxKind { @@ -23,7 +32,7 @@ namespace ts.NavigationBar { } function pushChild(parent: NavigationBarNode, child: NavigationBarNode): void { - if (parent.children) { + if (parent.children !== void 0) { parent.children.push(child); } else { @@ -41,13 +50,13 @@ namespace ts.NavigationBar { function rootNavigationBarNode(sourceFile: SourceFile): NavigationBarNode { Debug.assert(!parentsStack.length); - const root: NavigationBarNode = { node: sourceFile, additionalNodes: undefined, parent: undefined, children: undefined, indent: 0 }; + const root: NavigationBarNode = { node: sourceFile, additionalNodes: void 0, parent: void 0, children: void 0, indent: 0 }; parent = root; for (const statement of sourceFile.statements) { addChildrenRecursively(statement); } endNode(); - Debug.assert(parent === undefined && !parentsStack.length); + Debug.assert(parent === void 0 && !parentsStack.length); return root; } @@ -58,9 +67,9 @@ namespace ts.NavigationBar { function emptyNavigationBarNode(node: Node): NavigationBarNode { return { node, - additionalNodes: undefined, + additionalNodes: void 0, parent, - children: undefined, + children: void 0, indent: parent.indent + 1 }; } @@ -82,7 +91,7 @@ namespace ts.NavigationBar { * Call after calling `startNode` and adding children to it. */ function endNode(): void { - if (parent.children) { + if (parent.children !== void 0) { mergeChildren(parent.children); sortChildren(parent.children); } @@ -97,7 +106,7 @@ namespace ts.NavigationBar { /** Look for navigation bar items in node's subtree, adding them to the current `parent`. */ function addChildrenRecursively(node: Node): void { - if (!node || isToken(node)) { + if (node === void 0 || isToken(node)) { return; } @@ -135,7 +144,7 @@ namespace ts.NavigationBar { let importClause = node; // Handle default import case e.g.: // import d from "mod"; - if (importClause.name) { + if (importClause.name !== void 0) { addLeafNode(importClause); } @@ -143,7 +152,7 @@ namespace ts.NavigationBar { // import * as NS from "mod"; // import {a, b as B} from "mod"; const {namedBindings} = importClause; - if (namedBindings) { + if (namedBindings !== void 0) { if (namedBindings.kind === SyntaxKind.NamespaceImport) { addLeafNode(namedBindings); } @@ -160,7 +169,7 @@ namespace ts.NavigationBar { if (isBindingPattern(name)) { addChildrenRecursively(name); } - else if (decl.initializer && isFunctionOrClassExpression(decl.initializer)) { + else if (decl.initializer !== void 0 && isFunctionOrClassExpression(decl.initializer)) { // For `const x = function() {}`, just use the function node, not the const. addChildrenRecursively(decl.initializer); } @@ -211,7 +220,7 @@ namespace ts.NavigationBar { break; default: - if (node.jsDocComments) { + if (node.jsDocComments !== void 0) { for (const jsDocComment of node.jsDocComments) { for (const tag of jsDocComment.tags) { if (tag.kind === SyntaxKind.JSDocTypedefTag) { @@ -227,28 +236,46 @@ namespace ts.NavigationBar { /** Merge declarations of the same kind. */ function mergeChildren(children: NavigationBarNode[]): void { - const nameToNavNodes: Map = {}; + const nameToItems: Map = {}; filterMutate(children, child => { const decl = child.node; - const name = decl.name && decl.name.getText(); - if (!name) + const name = decl.name && nodeText(decl.name); + if (name === void 0) { // Anonymous items are never merged. return true; + } - const itemsWithSameName = getProperty(nameToNavNodes, name); - if (!itemsWithSameName) { - nameToNavNodes[name] = [child]; + const itemsWithSameName = getProperty(nameToItems, name); + if (itemsWithSameName === void 0) { + nameToItems[name] = child; return true; } - for (const s of itemsWithSameName) { - if (shouldReallyMerge(s.node, child.node)) { - merge(s, child); + if (itemsWithSameName instanceof Array) { + for (const itemWithSameName of itemsWithSameName) { + if (tryMerge(itemWithSameName, child)) { + return false; + } + } + itemsWithSameName.push(child); + return true; + } + else { + const itemWithSameName = itemsWithSameName; + if (tryMerge(itemWithSameName, child)) { return false; } + nameToItems[name] = [itemWithSameName, child]; + return true; + } + + function tryMerge(a: NavigationBarNode, b: NavigationBarNode): boolean { + if (shouldReallyMerge(a.node, b.node)) { + merge(a, b); + return true; + } + return false; } - itemsWithSameName.push(child); - return true; }); /** a and b have the same name, but they may not be mergeable. */ @@ -272,12 +299,12 @@ namespace ts.NavigationBar { function merge(target: NavigationBarNode, source: NavigationBarNode): void { target.additionalNodes = target.additionalNodes || []; target.additionalNodes.push(source.node); - if (source.additionalNodes) { + if (source.additionalNodes !== void 0) { target.additionalNodes.push(...source.additionalNodes); } target.children = concatenate(target.children, source.children); - if (target.children) { + if (target.children !== void 0) { mergeChildren(target.children); sortChildren(target.children); } @@ -288,7 +315,7 @@ namespace ts.NavigationBar { function sortChildren(children: NavigationBarNode[]): void { children.sort((child1, child2) => { const name1 = tryGetName(child1.node), name2 = tryGetName(child2.node); - if (name1 && name2) { + if (name1 !== void 0 && name2 !== void 0) { const cmp = localeCompareFix(name1, name2); return cmp !== 0 ? cmp : navigationBarNodeKind(child1) - navigationBarNodeKind(child2); } @@ -298,30 +325,27 @@ namespace ts.NavigationBar { }); } - // node 0.10 treats "a" as greater than "B". - const localeCompareIsCorrect = "a".localeCompare("B") < 0; - function localeCompareFix(a: string, b: string): number { - if (localeCompareIsCorrect) { - return a.localeCompare(b); - } - else { - // This isn't perfect, but it passes all of our tests. - for (let i = 0; i < Math.min(a.length, b.length); i++) { - const chA = a.charAt(i), chB = b.charAt(i); - if (chA === "\"" && chB === "'") { - return 1; - } - if (chA === "'" && chB === "\"") { - return -1; - } - const cmp = chA.toLocaleLowerCase().localeCompare(chB.toLocaleLowerCase()); - if (cmp !== 0) { - return cmp; - } + // More efficient to create a collator once and use its `compare` than to call `a.localeCompare(b)` many times. + const collator: { compare(a: string, b: string): number } = typeof Intl === "undefined" ? void 0 : new Intl.Collator(); + // Intl is missing in Safari, and node 0.10 treats "a" as greater than "B". + const localeCompareIsCorrect = collator && collator.compare("a", "B") < 0; + const localeCompareFix: (a: string, b: string) => number = localeCompareIsCorrect ? collator.compare : function(a, b) { + // This isn't perfect, but it passes all of our tests. + for (let i = 0; i < Math.min(a.length, b.length); i++) { + const chA = a.charAt(i), chB = b.charAt(i); + if (chA === "\"" && chB === "'") { + return 1; + } + if (chA === "'" && chB === "\"") { + return -1; + } + const cmp = chA.toLocaleLowerCase().localeCompare(chB.toLocaleLowerCase()); + if (cmp !== 0) { + return cmp; } - return a.length - b.length; } - } + return a.length - b.length; + }; /** * This differs from getItemName because this is just used for sorting. @@ -334,7 +358,7 @@ namespace ts.NavigationBar { } const decl = node; - if (decl.name) { + if (decl.name !== void 0) { return getPropertyNameForPropertyNameNode(decl.name); } switch (node.kind) { @@ -345,7 +369,7 @@ namespace ts.NavigationBar { case SyntaxKind.JSDocTypedefTag: return getJSDocTypedefTagName(node); default: - return undefined; + return void 0; } } @@ -355,10 +379,11 @@ namespace ts.NavigationBar { } const name = (node).name; - if (name) { - const text = name.getText(); - if (text.length > 0) + if (name !== void 0) { + const text = nodeText(name); + if (text.length > 0) { return text; + } } switch (node.kind) { @@ -393,7 +418,7 @@ namespace ts.NavigationBar { } function getJSDocTypedefTagName(node: JSDocTypedefTag): string { - if (node.name) { + if (node.name !== void 0) { return node.name.text; } else { @@ -416,7 +441,7 @@ namespace ts.NavigationBar { function recur(item: NavigationBarNode) { if (isTopLevel(item)) { topLevel.push(item); - if (item.children) { + if (item.children !== void 0) { for (const child of item.children) { recur(child); } @@ -453,7 +478,7 @@ namespace ts.NavigationBar { return false; } function isTopLevelFunctionDeclaration(item: NavigationBarNode): boolean { - if (!(item.node).body) { + if ((item.node).body === void 0) { return false; } @@ -506,7 +531,7 @@ namespace ts.NavigationBar { function getSpans(n: NavigationBarNode): TextSpan[] { const spans = [getNodeSpan(n.node)]; - if (n.additionalNodes) { + if (n.additionalNodes !== void 0) { for (const node of n.additionalNodes) { spans.push(getNodeSpan(node)); } @@ -537,7 +562,7 @@ namespace ts.NavigationBar { while (variableDeclarationNode && variableDeclarationNode.kind !== SyntaxKind.VariableDeclaration) { variableDeclarationNode = variableDeclarationNode.parent; } - Debug.assert(variableDeclarationNode !== undefined); + Debug.assert(variableDeclarationNode !== void 0); } else { Debug.assert(!isBindingPattern((node).name)); @@ -595,17 +620,17 @@ namespace ts.NavigationBar { } function isComputedProperty(member: EnumMember): boolean { - return member.name === undefined || member.name.kind === SyntaxKind.ComputedPropertyName; + return member.name === void 0 || member.name.kind === SyntaxKind.ComputedPropertyName; } function getNodeSpan(node: Node): TextSpan { return node.kind === SyntaxKind.SourceFile ? createTextSpanFromBounds(node.getFullStart(), node.getEnd()) - : createTextSpanFromBounds(node.getStart(), node.getEnd()); + : createTextSpanFromBounds(node.getStart(sourceFile), node.getEnd()); } function getFunctionOrClassName(node: FunctionExpression | FunctionDeclaration | ArrowFunction | ClassLikeDeclaration): string { - if (node.name && getFullWidth(node.name) > 0) { + if (node.name !== void 0 && getFullWidth(node.name) > 0) { return declarationNameToString(node.name); } // See if it is a var initializer. If so, use the var name. @@ -615,11 +640,11 @@ namespace ts.NavigationBar { // See if it is of the form " = function(){...}". If so, use the text from the left-hand side. else if (node.parent.kind === SyntaxKind.BinaryExpression && (node.parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken) { - return (node.parent as BinaryExpression).left.getText(); + return nodeText((node.parent as BinaryExpression).left); } // See if it is a property assignment, and if so use the property name else if (node.parent.kind === SyntaxKind.PropertyAssignment && (node.parent as PropertyAssignment).name) { - return (node.parent as PropertyAssignment).name.getText(); + return nodeText((node.parent as PropertyAssignment).name); } // Default exports are named "default" else if (node.flags & NodeFlags.Default) {