diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 64e25e0eb9a..f0d335fda3b 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -2571,20 +2571,29 @@ namespace FourSlash { } } - public verifyNavigationBar(json: any) { - const items = this.languageService.getNavigationBarItems(this.activeFile.fileName); - if (JSON.stringify(items, replacer) !== JSON.stringify(json)) { - this.raiseError(`verifyNavigationBar failed - expected: ${stringify(json)}, got: ${stringify(items, replacer)}`); + public verifyNavigationBar(json: any, options: { checkSpans?: boolean } | undefined) { + this.verifyNavigationTreeOrBar(json, this.languageService.getNavigationBarItems(this.activeFile.fileName), "Bar", options); + } + + public verifyNavigationTree(json: any, options: { checkSpans?: boolean } | undefined) { + this.verifyNavigationTreeOrBar(json, this.languageService.getNavigationTree(this.activeFile.fileName), "Tree", options); + } + + private verifyNavigationTreeOrBar(json: any, tree: any, name: "Tree" | "Bar", options: { checkSpans?: boolean } | undefined) { + if (JSON.stringify(tree, replacer) !== JSON.stringify(json)) { + this.raiseError(`verifyNavigation${name} failed - expected: ${stringify(json)}, got: ${stringify(tree, replacer)}`); } - // Make the data easier to read. function replacer(key: string, value: any) { switch (key) { case "spans": - // We won't ever check this. - return undefined; + return options && options.checkSpans ? value : undefined; + case "start": + case "length": + // Never omit the values in a span, even if they are 0. + return value; case "childItems": - return value.length === 0 ? undefined : value; + return !value || value.length === 0 ? undefined : value; default: // Omit falsy values, those are presumed to be the default. return value || undefined; @@ -2592,18 +2601,6 @@ namespace FourSlash { } } - public verifyNavigationTree(json: any) { - const tree = this.languageService.getNavigationTree(this.activeFile.fileName); - if (JSON.stringify(tree, replacer) !== JSON.stringify(json)) { - this.raiseError(`verifyNavigationTree failed - expected: ${stringify(json)}, got: ${stringify(tree, replacer)}`); - } - - function replacer(key: string, value: any) { - // Don't check "spans", and omit falsy values. - return key === "spans" ? undefined : (value || undefined); - } - } - public printNavigationItems(searchValue: string) { const items = this.languageService.getNavigateToItems(searchValue); Harness.IO.log(`NavigationItems list (${items.length} items)`); @@ -3533,6 +3530,10 @@ namespace FourSlashInterface { return this.state.getRanges(); } + public spans(): ts.TextSpan[] { + return this.ranges().map(r => ts.createTextSpan(r.start, r.end - r.start)); + } + public rangesByText(): ts.Map { return this.state.rangesByText(); } @@ -3966,12 +3967,12 @@ namespace FourSlashInterface { this.state.verifyImportFixAtPosition(expectedTextArray, errorCode); } - public navigationBar(json: any) { - this.state.verifyNavigationBar(json); + public navigationBar(json: any, options?: { checkSpans?: boolean }) { + this.state.verifyNavigationBar(json, options); } - public navigationTree(json: any) { - this.state.verifyNavigationTree(json); + public navigationTree(json: any, options?: { checkSpans?: boolean }) { + this.state.verifyNavigationTree(json, options); } public navigationItemsListCount(count: number, searchValue: string, matchKind?: string, fileName?: string) { diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index f7ed515a18f..bf34f28fed6 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -209,17 +209,24 @@ namespace ts.NavigationBar { case SyntaxKind.BindingElement: case SyntaxKind.VariableDeclaration: - const decl = node; - const name = decl.name; + const { name, initializer } = node; 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 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(); + } } else { - addNodeWithRecursiveChild(decl, decl.initializer); + addNodeWithRecursiveChild(node, initializer); } break; @@ -644,7 +651,14 @@ namespace ts.NavigationBar { } } - function isFunctionOrClassExpression(node: Node): boolean { - return node.kind === SyntaxKind.FunctionExpression || node.kind === SyntaxKind.ArrowFunction || node.kind === SyntaxKind.ClassExpression; + function isFunctionOrClassExpression(node: Node): node is ArrowFunction | FunctionExpression | ClassExpression { + switch (node.kind) { + case SyntaxKind.ArrowFunction: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ClassExpression: + return true; + default: + return false; + } } } diff --git a/tests/cases/fourslash/fourslash.ts b/tests/cases/fourslash/fourslash.ts index 7901d9550cb..b3d29456569 100644 --- a/tests/cases/fourslash/fourslash.ts +++ b/tests/cases/fourslash/fourslash.ts @@ -114,6 +114,7 @@ declare namespace FourSlashInterface { markerNames(): string[]; marker(name?: string): Marker; ranges(): Range[]; + spans(): Array<{ start: number, length: number }>; rangesByText(): ts.Map; markerByName(s: string): Marker; symbolsInScope(range: Range): any[]; @@ -250,8 +251,8 @@ declare namespace FourSlashInterface { fileAfterApplyingRefactorAtMarker(markerName: string, expectedContent: string, refactorNameToApply: string, formattingOptions?: FormatCodeOptions): void; importFixAtPosition(expectedTextArray: string[], errorCode?: number): void; - navigationBar(json: any): void; - navigationTree(json: any): void; + navigationBar(json: any, options?: { checkSpans?: boolean }): void; + navigationTree(json: any, options?: { checkSpans?: boolean }): void; navigationItemsListCount(count: number, searchValue: string, matchKind?: string, fileName?: string): void; navigationItemsListContains(name: string, kind: string, searchValue: string, matchKind: string, fileName?: string, parentName?: string): void; occurrencesAtPositionContains(range: Range, isWriteAccess?: boolean): void; diff --git a/tests/cases/fourslash/navigationBarAnonymousClassAndFunctionExpressions.ts b/tests/cases/fourslash/navigationBarAnonymousClassAndFunctionExpressions.ts index 69fa697f7bc..9a2228c1eaf 100644 --- a/tests/cases/fourslash/navigationBarAnonymousClassAndFunctionExpressions.ts +++ b/tests/cases/fourslash/navigationBarAnonymousClassAndFunctionExpressions.ts @@ -46,7 +46,7 @@ verify.navigationTree({ }, { "text": "x", - "kind": "function", + "kind": "const", "childItems": [ { "text": "xx", @@ -90,7 +90,7 @@ verify.navigationTree({ }, { "text": "cls2", - "kind": "class" + "kind": "const" }, { "text": "cls3", @@ -138,7 +138,7 @@ verify.navigationBar([ }, { "text": "x", - "kind": "function" + "kind": "const" }, { "text": "y", @@ -160,7 +160,7 @@ verify.navigationBar([ }, { "text": "x", - "kind": "function", + "kind": "const", "childItems": [ { "text": "xx", @@ -205,7 +205,7 @@ verify.navigationBar([ }, { "text": "cls2", - "kind": "class" + "kind": "const" }, { "text": "cls3", @@ -219,11 +219,6 @@ verify.navigationBar([ "kind": "class", "indent": 2 }, - { - "text": "cls2", - "kind": "class", - "indent": 2 - }, { "text": "cls3", "kind": "class", diff --git a/tests/cases/fourslash/navigationBarInitializerSpans.ts b/tests/cases/fourslash/navigationBarInitializerSpans.ts new file mode 100644 index 00000000000..67752c85577 --- /dev/null +++ b/tests/cases/fourslash/navigationBarInitializerSpans.ts @@ -0,0 +1,51 @@ +/// + +////const [|x = () => 0|]; +////const f = [|function f() {}|]; + +const [s0, s1] = test.spans(); +const sGlobal = { start: 0, length: 45 }; + +verify.navigationTree({ + text: "", + kind: "script", + spans: [sGlobal], + childItems: [ + { + text: "f", + kind: "function", + spans: [s1], + }, + { + text: "x", + kind: "const", + spans: [s0], + }, + ] +}, { checkSpans: true }); + +verify.navigationBar([ + { + text: "", + kind: "script", + spans: [sGlobal], + childItems: [ + { + text: "f", + kind: "function", + spans: [s1], + }, + { + text: "x", + kind: "const", + spans: [s0], + }, + ], + }, + { + text: "f", + kind: "function", + spans: [s1], + indent: 1, + }, +], { checkSpans: true }); diff --git a/tests/cases/fourslash/navigationBarItemsNamedArrowFunctions.ts b/tests/cases/fourslash/navigationBarItemsNamedArrowFunctions.ts index 32abf4092c7..328f205b990 100644 --- a/tests/cases/fourslash/navigationBarItemsNamedArrowFunctions.ts +++ b/tests/cases/fourslash/navigationBarItemsNamedArrowFunctions.ts @@ -17,11 +17,13 @@ verify.navigationBar([ }, { "text": "func", - "kind": "function" + "kind": "const", + "kindModifiers": "export", }, { "text": "func2", - "kind": "function" + "kind": "const", + "kindModifiers": "export", }, { "text": "value", @@ -35,18 +37,6 @@ verify.navigationBar([ "kind": "function", "kindModifiers": "export", "indent": 1 - }, - { - "text": "func", - "kind": "function", - "kindModifiers": "export", - "indent": 1 - }, - { - "text": "func2", - "kind": "function", - "kindModifiers": "export", - "indent": 1 } ]); @@ -61,12 +51,12 @@ verify.navigationTree({ }, { "text": "func", - "kind": "function", + "kind": "const", "kindModifiers": "export" }, { "text": "func2", - "kind": "function", + "kind": "const", "kindModifiers": "export" }, {