diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 79b6249539dbc..ffd10c37b5d29 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -138,6 +138,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[]; if (array) { diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index 57726a45cb023..02c575d3d28a5 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -2,829 +2,503 @@ /* @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; - // 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); - } + 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: - 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; - } - } - - // 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; + default: + createNavNode(parent, node); } - }); - - // 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) { + else { 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; - - case SyntaxKind.ModuleDeclaration: - let moduleDeclaration = node; - topLevelNodes.push(node); - addTopLevelNodes((getInnermostModule(moduleDeclaration).body).statements, topLevelNodes); - break; - - case SyntaxKind.FunctionDeclaration: - let functionDeclaration = node; - if (isTopLevelFunctionDeclaration(functionDeclaration)) { - topLevelNodes.push(node); - addTopLevelNodes((functionDeclaration.body).statements, topLevelNodes); - } + // TODO: These should probably be added to isDeclaration + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.IndexSignature: + createNavNode(parent, node); break; + default: + forEachChild(node, recur); } } } - function hasNamedFunctionDeclarations(nodes: NodeArray): boolean { - for (const s of nodes) { - if (s.kind === SyntaxKind.FunctionDeclaration && !isEmpty((s).name.text)) { - return true; - } - } - return false; + let parentNode = parent.node; + if (parentNode.kind === SyntaxKind.ModuleDeclaration) { + parentNode = getInteriorModule(parentNode); } + forEachChild(parentNode, recur); - 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; - } + mergeChildren(parent.children); + sortChildren(parent.children); + } - // 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 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; + }); - // 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) { + /** 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)); - return true; - } - } + // 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 getItemsWorker(nodes: Node[], createItem: (n: Node) => ts.NavigationBarItem): ts.NavigationBarItem[] { - const items: ts.NavigationBarItem[] = []; + /** 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); + } - const keyToItem: Map = {}; + target.children.push(...source.children); + mergeChildren(target.children); + sortChildren(target.children); + } + } - 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; + /** 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); + } + if (name1) { + return 1; + } + if (name2) { + return -1; + } + else { + return navKind(child1) - navKind(child2); + } - 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); - } - } - } + // 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): number { + 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; } + }); + } - 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 "[]"; + 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; - } - } - - // Didn't find a match, just add this child to the list. - target.childItems.push(sourceChild); + /** 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 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); - + 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); + 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; - } - else { - memberFunctionName = "constructor"; - scriptElementKind = ts.ScriptElementKind.constructorImplementationElement; + // TODO: file issue: 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; } - - 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)); - } + if (isConst(variableDeclarationNode)) { + return ts.ScriptElementKind.constElement; + } + else if (isLet(variableDeclarationNode)) { + return ts.ScriptElementKind.letElement; + } + else { + return ts.ScriptElementKind.variableElement; + } - 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)); - } + case SyntaxKind.ArrowFunction: + return ts.ScriptElementKind.functionElement; - 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)); - } + default: + return getNodeKind(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); - - while (moduleDeclaration.body && moduleDeclaration.body.kind === SyntaxKind.ModuleDeclaration) { - moduleDeclaration = moduleDeclaration.body; - - result.push(moduleDeclaration.name.text); - } - - return result.join("."); + function getModuleName(moduleDeclaration: ModuleDeclaration): string { + // We want to maintain quotation marks. + if (isAmbientModule(moduleDeclaration)) { + return getTextOfNode(moduleDeclaration.name); } - function removeComputedProperties(node: EnumDeclaration): Declaration[] { - return filter(node.members, member => member.name === undefined || member.name.kind !== SyntaxKind.ComputedPropertyName); - } + // Otherwise, we need to aggregate each identifier to build up the qualified name. + const result: string[] = []; - /** - * Like removeComputedProperties, but retains the properties with well known symbol names - */ - function removeDynamicallyNamedProperties(node: ClassDeclaration | InterfaceDeclaration): Declaration[] { - return filter(node.members, member => !hasDynamicName(member)); - } + result.push(moduleDeclaration.name.text); - function getInnermostModule(node: ModuleDeclaration): ModuleDeclaration { - while (node.body.kind === SyntaxKind.ModuleDeclaration) { - node = node.body; - } - - 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); + // 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; + } - function visitNode(node: Node) { - const newItem = createNavBarItem(node); + function isComputedProperty(member: EnumMember): boolean { + return member.name === undefined || member.name.kind === SyntaxKind.ComputedPropertyName; + } - if (newItem) { - topItem.childItems.push(newItem); - } + function getNodeSpan(node: Node) { + return node.kind === SyntaxKind.SourceFile + ? createTextSpanFromBounds(node.getFullStart(), node.getEnd()) + : createTextSpanFromBounds(node.getStart(), node.getEnd()); + } - // 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 tryGetName(node: Node): string { + const decl = node; + if (decl.name) { + return getPropertyNameForPropertyNameNode(decl.name); } - - 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)]); - default: - return undefined; - } + switch (node.kind) { + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.ClassExpression: + return getFunctionOrClassName(node); + default: + return undefined; } + } - function getNavBarItem(text: string, kind: string, spans: TextSpan[], kindModifiers = ScriptElementKindModifier.none): NavigationBarItem { - return { - text, kind, kindModifiers, spans, childItems: [], indent, bolded: false, grayed: false - }; + function getFunctionOrClassName(node: FunctionExpression | FunctionDeclaration | ArrowFunction | ClassLikeDeclaration): string { + if (node.name && getFullWidth(node.name) > 0) { + return declarationNameToString(node.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 a var initializer. If so, use the var name. + else if (node.parent.kind === SyntaxKind.VariableDeclaration) { + return declarationNameToString((node.parent as VariableDeclaration).name); } - - 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 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 getNodeSpan(node: Node) { - return node.kind === SyntaxKind.SourceFile - ? createTextSpanFromBounds(node.getFullStart(), node.getEnd()) - : createTextSpanFromBounds(node.getStart(), node.getEnd()); + // 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 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"; - } + // Default exports are named "default" + else if (node.flags & NodeFlags.Default) { + return "default"; + } + else { + return isClassLike(node) ? anonymousClassText : anonymousFunctionText; } + } - return sourceFileItem.childItems; + function isFunctionOrClassExpression(node: Node): boolean { + return node.kind === SyntaxKind.FunctionExpression || node.kind === SyntaxKind.ArrowFunction || node.kind === SyntaxKind.ClassExpression; } + + const anonymousFunctionText = ""; + const anonymousClassText = ""; } diff --git a/src/services/services.ts b/src/services/services.ts index 35fb0887b3b59..8c28c174f3519 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -6867,7 +6867,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/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 8a0d2d36364c2..b86d1b493d2fb 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -235,7 +235,7 @@ declare namespace FourSlashInterface { printBreakpointAtCurrentLocation(): void; printNameOrDottedNameSpans(pos: number): void; printErrorList(): void; - printNavigationBar(): void; + printNavigationBar(showChildItems?: boolean): void; printNavigationItems(searchValue?: string): void; printScriptLexicalStructureItems(): void; printReferences(): void; 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/navigationBarAnonymousDeclarationExpressions.ts b/tests/cases/fourslash/navigationBarAnonymousDeclarationExpressions.ts new file mode 100644 index 0000000000000..f15959812fc06 --- /dev/null +++ b/tests/cases/fourslash/navigationBarAnonymousDeclarationExpressions.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..4d037e42fa78e --- /dev/null +++ b/tests/cases/fourslash/navigationBarDeclarationExpressions.ts @@ -0,0 +1,76 @@ +/// + +////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 + } +]); + +/* +verify.navigationBarCount(6); +verify.navigationBarIndex("A", 0); +verify.navigationBarIndex("B", 1); +verify.navigationBarIndex("Cls", 2); +verify.navigationBarIndex("X", 3); +verify.navigationBarIndex("Y", 4); + +verify.navigationBarContains("Cls", "class"); +verify.navigationBarChildItem("Cls", "meth", "method"); +*/ \ No newline at end of file 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..391b71aac1014 100644 --- a/tests/cases/fourslash/navigationBarItemsFunctionsBroken.ts +++ b/tests/cases/fourslash/navigationBarItemsFunctionsBroken.ts @@ -1,7 +1,7 @@ /// ////function f() { -//// function; +//// function; // This is not included as a navigation item, but it causes 'f' to be considered top-level. ////} verify.navigationBar([ @@ -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..2608100ba61b7 100644 --- a/tests/cases/fourslash/navigationBarItemsMissingName1.ts +++ b/tests/cases/fourslash/navigationBarItemsMissingName1.ts @@ -4,25 +4,30 @@ ////} verify.navigationBar([ - { - "text": "\"navigationBarItemsMissingName1\"", - "kind": "module", - "childItems": [ - { - "text": "C", - "kind": "class" - } - ] - }, - { + { + "text": "\"navigationBarItemsMissingName1\"", + "kind": "module", + "childItems": [ + { + "text": "", + "kind": "function", + "kindModifiers": "export" + }, + { "text": "C", - "kind": "class", - "childItems": [ - { - "text": "foo", - "kind": "method" - } - ], - "indent": 1 - } + "kind": "class" + } + ] + }, + { + "text": "C", + "kind": "class", + "childItems": [ + { + "text": "foo", + "kind": "method" + } + ], + "indent": 1 + } ]); 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..0548e9838c22a 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": "", + "kind": "module", + "childItems": [ + { + "text": "A.B.C", + "kind": "module" + }, + { + "text": "A.B", + "kind": "module" + }, + { + "text": "A", + "kind": "module" + }, + { + "text": "\"X.Y.Z\"", + "kind": "module", + "kindModifiers": "declare" + }, + { + "text": "'X2.Y2.Z2'", + "kind": "module", + "kindModifiers": "declare" + } + ] + }, + { "text": "A.B.C", - "kind": "module" - }, - { + "kind": "module", + "childItems": [ + { + "text": "x", + "kind": "var", + "kindModifiers": "export" + } + ], + "indent": 1 + }, + { "text": "A.B", - "kind": "module" - }, - { + "kind": "module", + "childItems": [ + { + "text": "y", + "kind": "var", + "kindModifiers": "export" + } + ], + "indent": 1 + }, + { "text": "A", - "kind": "module" - }, - { + "kind": "module", + "childItems": [ + { + "text": "B", + "kind": "module" + }, + { + "text": "z", + "kind": "var", + "kindModifiers": "export" + } + ], + "indent": 1 + }, + { + "text": "B", + "kind": "module", + "childItems": [ + { + "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" - }, - { + "kindModifiers": "declare", + "indent": 1 + }, + { "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" - }, - { - "text": "B", - "kind": "module" - } - ], - "indent": 1 - }, - { - "text": "B", - "kind": "module", - "childItems": [ - { - "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 - } + "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" + } + ] + } +]);