diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index fb0a1503214..7a91fcdcad2 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -56,7 +56,7 @@ namespace ts.NavigationBar { curCancellationToken = cancellationToken; curSourceFile = sourceFile; try { - return map(topLevelItems(rootNavigationBarNode(sourceFile)), convertToTopLevelItem); + return map(primaryNavBarMenuItems(rootNavigationBarNode(sourceFile)), convertToPrimaryNavBarMenuItem); } finally { reset(); @@ -111,8 +111,8 @@ namespace ts.NavigationBar { return root; } - function addLeafNode(node: Node): void { - pushChild(parent, emptyNavigationBarNode(node)); + function addLeafNode(node: Node, name?: DeclarationName): void { + pushChild(parent, emptyNavigationBarNode(node, name)); } function emptyNavigationBarNode(node: Node, name?: DeclarationName): NavigationBarNode { @@ -243,23 +243,26 @@ namespace ts.NavigationBar { } break; + case SyntaxKind.ShorthandPropertyAssignment: + addNodeWithRecursiveChild(node, (node).name); + break; + case SyntaxKind.SpreadAssignment: + const { expression } = node; + // Use the expression as the name of the SpreadAssignment, otherwise show as . + isIdentifier(expression) ? addLeafNode(node, expression) : addLeafNode(node); + break; case SyntaxKind.BindingElement: + case SyntaxKind.PropertyAssignment: case SyntaxKind.VariableDeclaration: - const { name, initializer } = node; + const { name, initializer } = node; if (isBindingPattern(name)) { addChildrenRecursively(name); } else if (initializer && isFunctionOrClassExpression(initializer)) { - if (initializer.name) { - // Don't add a node for the VariableDeclaration, just for the initializer. - addChildrenRecursively(initializer); - } - else { - // Add a node for the VariableDeclaration, but not for the initializer. - startNode(node); - forEachChild(initializer, addChildrenRecursively); - endNode(); - } + // Add a node for the VariableDeclaration, but not for the initializer. + startNode(node); + forEachChild(initializer, addChildrenRecursively); + endNode(); } else { addNodeWithRecursiveChild(node, initializer); @@ -699,12 +702,15 @@ namespace ts.NavigationBar { } } - /** Flattens the NavNode tree to a list, keeping only the top-level items. */ - function topLevelItems(root: NavigationBarNode): NavigationBarNode[] { - const topLevel: NavigationBarNode[] = []; + /** Flattens the NavNode tree to a list of items to appear in the primary navbar menu. */ + function primaryNavBarMenuItems(root: NavigationBarNode): NavigationBarNode[] { + // The primary (middle) navbar menu displays the general code navigation hierarchy, similar to the navtree. + // The secondary (right) navbar menu displays the child items of whichever primary item is selected. + // Some less interesting items without their own child navigation items (e.g. a local variable declaration) only show up in the secondary menu. + const primaryNavBarMenuItems: NavigationBarNode[] = []; function recur(item: NavigationBarNode) { - if (isTopLevel(item)) { - topLevel.push(item); + if (shouldAppearInPrimaryNavBarMenu(item)) { + primaryNavBarMenuItems.push(item); if (item.children) { for (const child of item.children) { recur(child); @@ -713,9 +719,16 @@ namespace ts.NavigationBar { } } recur(root); - return topLevel; + return primaryNavBarMenuItems; - function isTopLevel(item: NavigationBarNode): boolean { + /** Determines if a node should appear in the primary navbar menu. */ + function shouldAppearInPrimaryNavBarMenu(item: NavigationBarNode): boolean { + // Items with children should always appear in the primary navbar menu. + if (item.children) { + return true; + } + + // Some nodes are otherwise important enough to always include in the primary navigation menu. switch (navigationBarNodeKind(item)) { case SyntaxKind.ClassDeclaration: case SyntaxKind.ClassExpression: @@ -728,13 +741,6 @@ namespace ts.NavigationBar { case SyntaxKind.JSDocCallbackTag: return true; - case SyntaxKind.Constructor: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - case SyntaxKind.VariableDeclaration: - return hasSomeImportantChild(item); - case SyntaxKind.ArrowFunction: case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: @@ -755,15 +761,9 @@ namespace ts.NavigationBar { case SyntaxKind.Constructor: return true; default: - return hasSomeImportantChild(item); + return false; } } - function hasSomeImportantChild(item: NavigationBarNode): boolean { - return some(item.children, child => { - const childKind = navigationBarNodeKind(child); - return childKind !== SyntaxKind.VariableDeclaration && childKind !== SyntaxKind.BindingElement; - }); - } } } @@ -778,19 +778,19 @@ namespace ts.NavigationBar { }; } - function convertToTopLevelItem(n: NavigationBarNode): NavigationBarItem { + function convertToPrimaryNavBarMenuItem(n: NavigationBarNode): NavigationBarItem { return { text: getItemName(n.node, n.name), kind: getNodeKind(n.node), kindModifiers: getModifiers(n.node), spans: getSpans(n), - childItems: map(n.children, convertToChildItem) || emptyChildItemArray, + childItems: map(n.children, convertToSecondaryNavBarMenuItem) || emptyChildItemArray, indent: n.indent, bolded: false, grayed: false }; - function convertToChildItem(n: NavigationBarNode): NavigationBarItem { + function convertToSecondaryNavBarMenuItem(n: NavigationBarNode): NavigationBarItem { return { text: getItemName(n.node, n.name), kind: getNodeKind(n.node), diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 95d501de110..91b0a7a1991 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -353,8 +353,13 @@ namespace ts { case SyntaxKind.MethodDeclaration: case SyntaxKind.MethodSignature: return ScriptElementKind.memberFunctionElement; + case SyntaxKind.PropertyAssignment: + const {initializer} = node as PropertyAssignment; + return isFunctionLike(initializer) ? ScriptElementKind.memberFunctionElement : ScriptElementKind.memberVariableElement; case SyntaxKind.PropertyDeclaration: case SyntaxKind.PropertySignature: + case SyntaxKind.ShorthandPropertyAssignment: + case SyntaxKind.SpreadAssignment: return ScriptElementKind.memberVariableElement; case SyntaxKind.IndexSignature: return ScriptElementKind.indexSignatureElement; case SyntaxKind.ConstructSignature: return ScriptElementKind.constructSignatureElement; diff --git a/tests/cases/fourslash/navbarNestedCommonJsExports.ts b/tests/cases/fourslash/navbarNestedCommonJsExports.ts index 3c2d4499172..14c289a3bc2 100644 --- a/tests/cases/fourslash/navbarNestedCommonJsExports.ts +++ b/tests/cases/fourslash/navbarNestedCommonJsExports.ts @@ -29,7 +29,23 @@ verify.navigationBar([ kind: "script", childItems: [ { text: "a", kind: "const" }, - ], + ] }, + { + text: "a", + kind: "const", + childItems: [ + { text: "b", kind: "const"}, + ], + indent: 1, + }, + { + text: "b", + kind: "const", + childItems: [ + { text: "c", kind: "const" }, + ], + indent: 2, + } ]); diff --git a/tests/cases/fourslash/navigationBarAnonymousClassAndFunctionExpressions.ts b/tests/cases/fourslash/navigationBarAnonymousClassAndFunctionExpressions.ts index ad1ba1e72f9..d35d051eda6 100644 --- a/tests/cases/fourslash/navigationBarAnonymousClassAndFunctionExpressions.ts +++ b/tests/cases/fourslash/navigationBarAnonymousClassAndFunctionExpressions.ts @@ -62,7 +62,7 @@ verify.navigationTree({ "childItems": [ { "text": "foo", - "kind": "function" + "kind": "method" } ] } @@ -185,7 +185,7 @@ verify.navigationBar([ "childItems": [ { "text": "foo", - "kind": "function" + "kind": "method" } ], "indent": 2 diff --git a/tests/cases/fourslash/navigationBarAssignmentTypes.ts b/tests/cases/fourslash/navigationBarAssignmentTypes.ts new file mode 100644 index 00000000000..d3ee66f9b08 --- /dev/null +++ b/tests/cases/fourslash/navigationBarAssignmentTypes.ts @@ -0,0 +1,64 @@ +/// +////'use strict' +////const a = { +//// ...b, +//// c, +//// d: 0 +////}; + +verify.navigationTree({ + text: "", + kind: "script", + childItems: [ + { + text: "a", + kind: "const", + childItems: [ + { + text: "b", + kind: "property", + }, + { + text: "c", + kind: "property" + }, + { + text: "d", + kind: "property" + } + ] + } + ] +}); + +verify.navigationBar([ + { + text: "", + kind: "script", + childItems: [ + { + text: "a", + kind: "const" + } + ] + }, + { + text: "a", + kind: "const", + childItems: [ + { + text: "b", + kind: "property", + }, + { + text: "c", + kind: "property" + }, + { + text: "d", + kind: "property" + } + ], + indent: 1 + } +]); diff --git a/tests/cases/fourslash/navigationBarFunctionIndirectlyInVariableDeclaration.ts b/tests/cases/fourslash/navigationBarFunctionIndirectlyInVariableDeclaration.ts index a0ed5174f56..a9b588041dd 100644 --- a/tests/cases/fourslash/navigationBarFunctionIndirectlyInVariableDeclaration.ts +++ b/tests/cases/fourslash/navigationBarFunctionIndirectlyInVariableDeclaration.ts @@ -1,11 +1,16 @@ /// ////var a = { -//// propA: function() {} +//// propA: function() { +//// var c; +//// } ////}; ////var b; ////b = { -//// propB: function() {} +//// propB: function() { +//// // function must not have an empty body to appear top level +//// var d; +//// } ////}; verify.navigationTree({ @@ -18,7 +23,13 @@ verify.navigationTree({ "childItems": [ { "text": "propA", - "kind": "function" + "kind": "method", + "childItems": [ + { + "text": "c", + "kind": "var" + } + ] } ] }, @@ -28,7 +39,13 @@ verify.navigationTree({ }, { "text": "propB", - "kind": "function" + "kind": "method", + "childItems": [ + { + "text": "d", + "kind": "var" + } + ] } ] }); @@ -48,7 +65,7 @@ verify.navigationBar([ }, { "text": "propB", - "kind": "function" + "kind": "method" } ] }, @@ -58,14 +75,31 @@ verify.navigationBar([ "childItems": [ { "text": "propA", - "kind": "function" + "kind": "method" } ], "indent": 1 }, + { + "text": "propA", + "kind": "method", + "childItems": [ + { + "text": "c", + "kind": "var" + } + ], + "indent": 2 + }, { "text": "propB", - "kind": "function", + "kind": "method", + "childItems": [ + { + "text": "d", + "kind": "var" + } + ], "indent": 1 } ]); diff --git a/tests/cases/fourslash/navigationBarFunctionLikePropertyAssignments.ts b/tests/cases/fourslash/navigationBarFunctionLikePropertyAssignments.ts new file mode 100644 index 00000000000..39bc0e1893e --- /dev/null +++ b/tests/cases/fourslash/navigationBarFunctionLikePropertyAssignments.ts @@ -0,0 +1,91 @@ +/// + +////var functions = { +//// a: 0, +//// b: function () { }, +//// c: function x() { }, +//// d: () => { }, +//// e: y(), +//// f() { } +////}; + +verify.navigationTree({ + text: "", + kind: "script", + childItems: [ + { + text: "functions", + kind: "var", + childItems: [ + { + text: "a", + kind: "property" + }, + { + text: "b", + kind: "method" + }, + { + text: "c", + kind: "method" + }, + { + text: "d", + kind: "method" + }, + { + text: "e", + kind: "property" + }, + { + text: "f", + kind: "method" + } + ] + } + ] +}); + +verify.navigationBar([ + { + "text": "", + "kind": "script", + "childItems": [ + { + "text": "functions", + "kind": "var" + } + ] + }, + { + "text": "functions", + "kind": "var", + "childItems": [ + { + "text": "a", + "kind": "property" + }, + { + "text": "b", + "kind": "method" + }, + { + "text": "c", + "kind": "method" + }, + { + "text": "d", + "kind": "method" + }, + { + "text": "e", + "kind": "property" + }, + { + "text": "f", + "kind": "method" + } + ], + "indent": 1 + } +]); \ No newline at end of file diff --git a/tests/cases/fourslash/navigationBarFunctionPrototype.ts b/tests/cases/fourslash/navigationBarFunctionPrototype.ts index fbd3ba2c16f..2806f36dae9 100644 --- a/tests/cases/fourslash/navigationBarFunctionPrototype.ts +++ b/tests/cases/fourslash/navigationBarFunctionPrototype.ts @@ -44,11 +44,11 @@ verify.navigationTree({ "childItems": [ { "text": "get", - "kind": "function" + "kind": "method" }, { "text": "set", - "kind": "function" + "kind": "method" } ] }, @@ -57,11 +57,11 @@ verify.navigationTree({ "childItems": [ { "text": "get", - "kind": "function" + "kind": "method" }, { "text": "set", - "kind": "function" + "kind": "method" } ] } @@ -108,5 +108,33 @@ verify.navigationBar([ } ], "indent": 1 - } + }, + { + "text": "staticProp", + "childItems": [ + { + "text": "get", + "kind": "method" + }, + { + "text": "set", + "kind": "method" + } + ], + "indent": 2 + }, + { + "text": "name", + "childItems": [ + { + "text": "get", + "kind": "method" + }, + { + "text": "set", + "kind": "method" + } + ], + "indent": 2 + } ]); diff --git a/tests/cases/fourslash/navigationBarFunctionPrototypeBroken.ts b/tests/cases/fourslash/navigationBarFunctionPrototypeBroken.ts index 48270dc3aa1..01d70c61170 100644 --- a/tests/cases/fourslash/navigationBarFunctionPrototypeBroken.ts +++ b/tests/cases/fourslash/navigationBarFunctionPrototypeBroken.ts @@ -138,6 +138,28 @@ verify.navigationBar([ } ] }, + { + "text": "G", + "kind": "method", + "childItems": [ + { + "text": "A", + "kind": "method" + } + ], + "indent": 1 + }, + { + "text": "A", + "kind": "method", + "childItems": [ + { + "text": "a", + "kind": "function" + } + ], + "indent": 2 + }, { "text": "A", "kind": "class", @@ -152,5 +174,16 @@ verify.navigationBar([ } ], "indent": 1 - } + }, + { + "text": "A", + "kind": "method", + "childItems": [ + { + "text": "a", + "kind": "function" + } + ], + "indent": 2 + }, ]); diff --git a/tests/cases/fourslash/navigationBarFunctionPrototypeNested.ts b/tests/cases/fourslash/navigationBarFunctionPrototypeNested.ts index dea8b872061..97f9a5a5805 100644 --- a/tests/cases/fourslash/navigationBarFunctionPrototypeNested.ts +++ b/tests/cases/fourslash/navigationBarFunctionPrototypeNested.ts @@ -208,6 +208,16 @@ verify.navigationBar([ ], "indent": 2 }, + { + "text": "x", + "childItems": [ + { + "text": "get", + "kind": "method" + } + ], + "indent": 3 + }, { "text": "D", "kind": "class", diff --git a/tests/cases/fourslash/navigationBarInitializerSpans.ts b/tests/cases/fourslash/navigationBarInitializerSpans.ts index 7b044db9c4c..036b83cf318 100644 --- a/tests/cases/fourslash/navigationBarInitializerSpans.ts +++ b/tests/cases/fourslash/navigationBarInitializerSpans.ts @@ -1,10 +1,25 @@ /// -////const [|[|x|] = () => 0|]; -////const f = [|function [|f|]() {}|]; +////// get the name for the navbar from the variable name rather than the function name +////const [|[|x|] = () => { var [|a|]; }|]; +////const [|[|f|] = function f() { var [|b|]; }|]; +////const [|[|y|] = { [|[|z|]: function z() { var [|c|]; }|] }|]; -const [s0, s0Name, s1, s1Name] = test.spans(); -const sGlobal = { start: 0, length: 45 }; +const [ + s0, + s0Name, + s0Child, + s1, + s1Name, + s1Child, + s2, + s2Name, + s2Child, + s2ChildName, + s2GrandChildName +] = test.spans(); + +const sGlobal = { start: 0, length: 188 }; verify.navigationTree({ text: "", @@ -13,16 +28,54 @@ verify.navigationTree({ childItems: [ { text: "f", - kind: "function", + kind: "const", spans: [s1], nameSpan: s1Name, + childItems: [ + { + text: "b", + kind: "var", + spans: [s1Child], + nameSpan: s1Child, + }, + ], }, { text: "x", kind: "const", spans: [s0], nameSpan: s0Name, + childItems: [ + { + text: "a", + kind: "var", + spans: [s0Child], + nameSpan: s0Child, + }, + ], }, + { + text: "y", + kind: "const", + spans: [s2], + nameSpan: s2Name, + childItems:[ + { + text: "z", + kind: "method", + spans: [s2Child], + nameSpan: s2ChildName, + childItems: [ + { + text: "c", + kind: "var", + spans: [s2GrandChildName], + nameSpan: s2GrandChildName, + }, + ], + }, + ], + } ] }, { checkSpans: true }); @@ -34,7 +87,7 @@ verify.navigationBar([ childItems: [ { text: "f", - kind: "function", + kind: "const", spans: [s1], }, { @@ -42,12 +95,63 @@ verify.navigationBar([ kind: "const", spans: [s0], }, + { + text: "y", + kind: "const", + spans: [s2], + } ], }, { text: "f", - kind: "function", + kind: "const", spans: [s1], + childItems: [ + { + text: "b", + kind: "var", + spans: [s1Child], + }, + ], indent: 1, }, + { + text: "x", + kind: "const", + spans: [s0], + childItems: [ + { + text: "a", + kind: "var", + spans: [s0Child], + }, + ], + indent: 1, + }, + { + text: "y", + kind: "const", + spans: [s2], + childItems: [ + { + text: "z", + kind: "method", + spans: [s2Child], + }, + ], + indent: 1, + }, + { + text: "z", + kind: "method", + spans: [s2Child], + childItems: [ + { + text: "c", + kind: "var", + spans: [s2GrandChildName], + }, + ], + indent: 2, + }, ], { checkSpans: true }); diff --git a/tests/cases/fourslash/navigationBarItemsBindingPatterns.ts b/tests/cases/fourslash/navigationBarItemsBindingPatterns.ts index d2b8b24bcbf..7e64257604c 100644 --- a/tests/cases/fourslash/navigationBarItemsBindingPatterns.ts +++ b/tests/cases/fourslash/navigationBarItemsBindingPatterns.ts @@ -5,6 +5,7 @@ ////let foo1, {a, b} ////const bar1, [c, d] ////var {e, x: [f, g]} = {a:1, x:[]}; +////var { h: i = function j() {} } = obj; verify.navigationTree({ "text": "", @@ -53,6 +54,10 @@ verify.navigationTree({ { "text": "g", "kind": "var" + }, + { + "text": "i", + "kind": "var" } ] }); @@ -105,6 +110,10 @@ verify.navigationBar([ { "text": "g", "kind": "var" + }, + { + "text": "i", + "kind": "var" } ] } diff --git a/tests/cases/fourslash/navigationBarItemsFunctions.ts b/tests/cases/fourslash/navigationBarItemsFunctions.ts index f43cd3effaf..5978a1b940d 100644 --- a/tests/cases/fourslash/navigationBarItemsFunctions.ts +++ b/tests/cases/fourslash/navigationBarItemsFunctions.ts @@ -7,6 +7,9 @@ //// function biz() { //// var z = 10; //// } +//// function qux() { +//// // A function with an empty body should not be top level +//// } //// } ////} //// @@ -46,6 +49,10 @@ verify.navigationTree({ } ] }, + { + "text": "qux", + "kind": "function" + }, { "text": "y", "kind": "var" @@ -110,11 +117,27 @@ verify.navigationBar([ "text": "biz", "kind": "function" }, + { + "text": "qux", + "kind": "function" + }, { "text": "y", "kind": "var" } ], "indent": 2 - } + }, + { + "text": "biz", + "kind": "function", + "childItems": [ + { + "text": "z", + "kind": "var" + } + ], + "indent": 3 + }, + ]); diff --git a/tests/cases/fourslash/navigationBarItemsPropertiesDefinedInConstructors.ts b/tests/cases/fourslash/navigationBarItemsPropertiesDefinedInConstructors.ts index 6e8e0ea350c..d880c6e3844 100644 --- a/tests/cases/fourslash/navigationBarItemsPropertiesDefinedInConstructors.ts +++ b/tests/cases/fourslash/navigationBarItemsPropertiesDefinedInConstructors.ts @@ -78,5 +78,17 @@ verify.navigationBar([ } ], "indent": 1 + }, + { + "text": "constructor", + "kind": "constructor", + "childItems": [ + { + "text": "local", + "kind": "var" + } + ], + "indent": 2 } + ]); diff --git a/tests/cases/fourslash/navigationBarMerging_grandchildren.ts b/tests/cases/fourslash/navigationBarMerging_grandchildren.ts index 732b1794b68..87b70558548 100644 --- a/tests/cases/fourslash/navigationBarMerging_grandchildren.ts +++ b/tests/cases/fourslash/navigationBarMerging_grandchildren.ts @@ -1,5 +1,6 @@ /// +////// Should not merge grandchildren with property assignments ////const o = { //// a: { //// m() {}, @@ -17,8 +18,20 @@ verify.navigationTree({ text: "o", kind: "const", childItems: [ - { text: "m", kind: "method" }, - { text: "m", kind: "method" }, + { + text: "a", + kind: "property", + childItems: [ + { text: "m", kind: "method" } + ] + }, + { + text: "b", + kind: "property", + childItems: [ + { text: "m", kind: "method" } + ] + }, ], }, ] @@ -36,9 +49,26 @@ verify.navigationBar([ text: "o", kind: "const", childItems: [ - { text: "m", kind: "method" }, - { text: "m", kind: "method" }, + { text: "a", kind: "property" }, + { text: "b", kind: "property" }, ], indent: 1, }, + { + text: "a", + kind: "property", + childItems: [ + { text: "m", kind: "method" }, + ], + indent: 2, + }, + { + text: "b", + kind: "property", + childItems: [ + { text: "m", kind: "method" }, + ], + indent: 2, + }, + ]); diff --git a/tests/cases/fourslash/navigationBarNestedObjectLiterals.ts b/tests/cases/fourslash/navigationBarNestedObjectLiterals.ts new file mode 100644 index 00000000000..5c62a7e26fb --- /dev/null +++ b/tests/cases/fourslash/navigationBarNestedObjectLiterals.ts @@ -0,0 +1,139 @@ +/// + +////var a = { +//// b: 0, +//// c: {}, +//// d: { +//// e: 1, +//// }, +//// f: { +//// g: 2, +//// h: { +//// i: 3, +//// }, +//// }, +////} + +verify.navigationTree({ + "text": "", + "kind": "script", + "childItems": [ + { + "text": "a", + "kind": "var", + "childItems": [ + { + "text": "b", + "kind": "property" + }, + { + "text": "c", + "kind": "property" + }, + { + "text": "d", + "kind": "property", + "childItems": [ + { + "text": "e", + "kind": "property" + } + ] + }, + { + "text": "f", + "kind": "property", + "childItems": [ + { + "text": "g", + "kind": "property" + }, + { + "text": "h", + "kind": "property", + "childItems": [ + { + "text": "i", + "kind": "property" + } + ] + } + ] + } + ] + } + ] + }); + +verify.navigationBar([ + { + "text": "", + "kind": "script", + "childItems": [ + { + "text": "a", + "kind": "var" + } + ] + }, + { + "text": "a", + "kind": "var", + "childItems": [ + { + "text": "b", + "kind": "property" + }, + { + "text": "c", + "kind": "property" + }, + { + "text": "d", + "kind": "property" + }, + { + "text": "f", + "kind": "property" + } + ], + "indent": 1 + }, + { + "text": "d", + "kind": "property", + "childItems": [ + { + "text": "e", + "kind": "property" + } + ], + "indent": 2 + }, + { + "text": "f", + "kind": "property", + "childItems": [ + { + "text": "g", + "kind": "property" + }, + { + "text": "h", + "kind": "property" + } + ], + "indent": 2 + }, + { + "text": "h", + "kind": "property", + "childItems": [ + { + "text": "i", + "kind": "property" + } + ], + "indent": 3 + } + ]); \ No newline at end of file