From fa9e0fa8e80e369b39b4bdd3095d3cdca1f3de6b Mon Sep 17 00:00:00 2001 From: Titian Cernicova-Dragomir Date: Thu, 29 Aug 2019 19:19:04 +0300 Subject: [PATCH] Better outlining spans for prototype methods (#32782) * Changed outlining to better outline ES5 classes (functions assigned to prototype) * Changed outlining to better outline ES5 classes (properties assigned to functions) * Fixed some small bugs when merging es5 class nodes. Added tests for new es5 class outline. * Added support for interlaced ES5 classes (where an ES5 class's members are mixed with other declarations). * Fixed crash in outline when assigning {} to the prototype. * Added support for nested es5 declarations. * Added support for prototype assignment for es5 classes. --- src/services/navigationBar.ts | 254 ++++++++++++++++-- .../navigationBarFunctionPrototype.ts | 92 ++++++- .../navigationBarFunctionPrototype2.ts | 64 +++++ .../navigationBarFunctionPrototype3.ts | 64 +++++ .../navigationBarFunctionPrototype4.ts | 74 +++++ .../navigationBarFunctionPrototypeBroken.ts | 156 +++++++++++ ...avigationBarFunctionPrototypeInterlaced.ts | 188 +++++++++++++ .../navigationBarFunctionPrototypeNested.ts | 226 ++++++++++++++++ 8 files changed, 1089 insertions(+), 29 deletions(-) create mode 100644 tests/cases/fourslash/navigationBarFunctionPrototype2.ts create mode 100644 tests/cases/fourslash/navigationBarFunctionPrototype3.ts create mode 100644 tests/cases/fourslash/navigationBarFunctionPrototype4.ts create mode 100644 tests/cases/fourslash/navigationBarFunctionPrototypeBroken.ts create mode 100644 tests/cases/fourslash/navigationBarFunctionPrototypeInterlaced.ts create mode 100644 tests/cases/fourslash/navigationBarFunctionPrototypeNested.ts diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index 57bf910efdafb..fb0a1503214b2 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -33,6 +33,9 @@ namespace ts.NavigationBar { let parentsStack: NavigationBarNode[] = []; let parent: NavigationBarNode; + const trackedEs5ClassesStack: (Map | undefined)[] = []; + let trackedEs5Classes: Map | undefined; + // NavigationBarItem requires an array, but will not mutate it, so just give it this for performance. let emptyChildItemArray: NavigationBarItem[] = []; @@ -112,10 +115,10 @@ namespace ts.NavigationBar { pushChild(parent, emptyNavigationBarNode(node)); } - function emptyNavigationBarNode(node: Node): NavigationBarNode { + function emptyNavigationBarNode(node: Node, name?: DeclarationName): NavigationBarNode { return { node, - name: isDeclaration(node) || isExpression(node) ? getNameOfDeclaration(node) : undefined, + name: name || (isDeclaration(node) || isExpression(node) ? getNameOfDeclaration(node) : undefined), additionalNodes: undefined, parent, children: undefined, @@ -123,16 +126,42 @@ namespace ts.NavigationBar { }; } + function addTrackedEs5Class(name: string) { + if (!trackedEs5Classes) { + trackedEs5Classes = createMap(); + } + trackedEs5Classes.set(name, true); + } + function endNestedNodes(depth: number): void { + for (let i = 0; i < depth; i++) endNode(); + } + function startNestedNodes(targetNode: Node, entityName: EntityNameExpression) { + const names: Identifier[] = []; + while (!isIdentifier(entityName)) { + const name = entityName.name; + entityName = entityName.expression; + if (name.escapedText === "prototype") continue; + names.push(name); + } + names.push(entityName); + for (let i = names.length - 1; i > 0; i--) { + const name = names[i]; + startNode(targetNode, name); + } + return [names.length - 1, names[0]] as const; + } + /** * Add a new level of NavigationBarNodes. * This pushes to the stack, so you must call `endNode` when you are done adding to this node. */ - function startNode(node: Node): void { - const navNode: NavigationBarNode = emptyNavigationBarNode(node); + function startNode(node: Node, name?: DeclarationName): void { + const navNode: NavigationBarNode = emptyNavigationBarNode(node, name); pushChild(parent, navNode); // Save the old parent parentsStack.push(parent); + trackedEs5ClassesStack.push(trackedEs5Classes); parent = navNode; } @@ -143,10 +172,11 @@ namespace ts.NavigationBar { sortChildren(parent.children); } parent = parentsStack.pop()!; + trackedEs5Classes = trackedEs5ClassesStack.pop(); } - function addNodeWithRecursiveChild(node: Node, child: Node | undefined): void { - startNode(node); + function addNodeWithRecursiveChild(node: Node, child: Node | undefined, name?: DeclarationName): void { + startNode(node, name); addChildrenRecursively(child); endNode(); } @@ -236,8 +266,15 @@ namespace ts.NavigationBar { } break; - case SyntaxKind.ArrowFunction: case SyntaxKind.FunctionDeclaration: + const nameNode = (node).name; + // If we see a function declaration track as a possible ES5 class + if (nameNode && isIdentifier(nameNode)) { + addTrackedEs5Class(nameNode.text); + } + addNodeWithRecursiveChild(node, (node).body); + break; + case SyntaxKind.ArrowFunction: case SyntaxKind.FunctionExpression: addNodeWithRecursiveChild(node, (node).body); break; @@ -275,21 +312,94 @@ namespace ts.NavigationBar { addLeafNode(node); break; + case SyntaxKind.CallExpression: case SyntaxKind.BinaryExpression: { const special = getAssignmentDeclarationKind(node as BinaryExpression); switch (special) { case AssignmentDeclarationKind.ExportsProperty: case AssignmentDeclarationKind.ModuleExports: - case AssignmentDeclarationKind.PrototypeProperty: - case AssignmentDeclarationKind.Prototype: addNodeWithRecursiveChild(node, (node as BinaryExpression).right); return; + case AssignmentDeclarationKind.Prototype: + case AssignmentDeclarationKind.PrototypeProperty: { + const binaryExpression = (node as BinaryExpression); + const assignmentTarget = binaryExpression.left as PropertyAccessExpression; + + const prototypeAccess = special === AssignmentDeclarationKind.PrototypeProperty ? + assignmentTarget.expression as PropertyAccessExpression : + assignmentTarget; + + let depth = 0; + let className: Identifier; + // If we see a prototype assignment, start tracking the target as a class + // This is only done for simple classes not nested assignments. + if (isIdentifier(prototypeAccess.expression)) { + addTrackedEs5Class(prototypeAccess.expression.text); + className = prototypeAccess.expression; + } + else { + [depth, className] = startNestedNodes(binaryExpression, prototypeAccess.expression as EntityNameExpression); + } + if (special === AssignmentDeclarationKind.Prototype) { + if (isObjectLiteralExpression(binaryExpression.right)) { + if (binaryExpression.right.properties.length > 0) { + startNode(binaryExpression, className); + forEachChild(binaryExpression.right, addChildrenRecursively); + endNode(); + } + } + } + else if (isFunctionExpression(binaryExpression.right) || isArrowFunction(binaryExpression.right)) { + addNodeWithRecursiveChild(node, + binaryExpression.right, + className); + } + else { + startNode(binaryExpression, className); + addNodeWithRecursiveChild(node, binaryExpression.right, assignmentTarget.name); + endNode(); + } + endNestedNodes(depth); + return; + } + case AssignmentDeclarationKind.ObjectDefinePropertyValue: + case AssignmentDeclarationKind.ObjectDefinePrototypeProperty: { + const defineCall = node as BindableObjectDefinePropertyCall; + const className = special === AssignmentDeclarationKind.ObjectDefinePropertyValue ? + defineCall.arguments[0] : + (defineCall.arguments[0] as PropertyAccessExpression).expression as EntityNameExpression; + + const memberName = defineCall.arguments[1]; + const [depth, classNameIdentifier] = startNestedNodes(node, className); + startNode(node, classNameIdentifier); + startNode(node, setTextRange(createIdentifier(memberName.text), memberName)); + addChildrenRecursively((node as CallExpression).arguments[2]); + endNode(); + endNode(); + endNestedNodes(depth); + return; + } + case AssignmentDeclarationKind.Property: { + const binaryExpression = (node as BinaryExpression); + const assignmentTarget = binaryExpression.left as PropertyAccessExpression; + const targetFunction = assignmentTarget.expression; + if (isIdentifier(targetFunction) && assignmentTarget.name.escapedText !== "prototype" && + trackedEs5Classes && trackedEs5Classes.has(targetFunction.text)) { + if (isFunctionExpression(binaryExpression.right) || isArrowFunction(binaryExpression.right)) { + addNodeWithRecursiveChild(node, binaryExpression.right, targetFunction); + } + else { + startNode(binaryExpression, targetFunction); + addNodeWithRecursiveChild(binaryExpression.left, binaryExpression.right, assignmentTarget.name); + endNode(); + } + return; + } + break; + } case AssignmentDeclarationKind.ThisProperty: - case AssignmentDeclarationKind.Property: case AssignmentDeclarationKind.None: - case AssignmentDeclarationKind.ObjectDefinePropertyValue: case AssignmentDeclarationKind.ObjectDefinePropertyExports: - case AssignmentDeclarationKind.ObjectDefinePrototypeProperty: break; default: Debug.assertNever(special); @@ -315,8 +425,8 @@ namespace ts.NavigationBar { /** Merge declarations of the same kind. */ function mergeChildren(children: NavigationBarNode[], node: NavigationBarNode): void { const nameToItems = createMap(); - filterMutate(children, child => { - const declName = getNameOfDeclaration(child.node); + filterMutate(children, (child, index) => { + const declName = child.name || getNameOfDeclaration(child.node); const name = declName && nodeText(declName); if (!name) { // Anonymous items are never merged. @@ -331,7 +441,7 @@ namespace ts.NavigationBar { if (itemsWithSameName instanceof Array) { for (const itemWithSameName of itemsWithSameName) { - if (tryMerge(itemWithSameName, child, node)) { + if (tryMerge(itemWithSameName, child, index, node)) { return false; } } @@ -340,7 +450,7 @@ namespace ts.NavigationBar { } else { const itemWithSameName = itemsWithSameName; - if (tryMerge(itemWithSameName, child, node)) { + if (tryMerge(itemWithSameName, child, index, node)) { return false; } nameToItems.set(name, [itemWithSameName, child]); @@ -348,8 +458,116 @@ namespace ts.NavigationBar { } }); } + const isEs5ClassMember: Record = { + [AssignmentDeclarationKind.Property]: true, + [AssignmentDeclarationKind.PrototypeProperty]: true, + [AssignmentDeclarationKind.ObjectDefinePropertyValue]: true, + [AssignmentDeclarationKind.ObjectDefinePrototypeProperty]: true, + [AssignmentDeclarationKind.None]: false, + [AssignmentDeclarationKind.ExportsProperty]: false, + [AssignmentDeclarationKind.ModuleExports]: false, + [AssignmentDeclarationKind.ObjectDefinePropertyExports]: false, + [AssignmentDeclarationKind.Prototype]: true, + [AssignmentDeclarationKind.ThisProperty]: false, + }; + function tryMergeEs5Class(a: NavigationBarNode, b: NavigationBarNode, bIndex: number, parent: NavigationBarNode): boolean | undefined { + + function isPossibleConstructor(node: Node) { + return isFunctionExpression(node) || isFunctionDeclaration(node) || isVariableDeclaration(node); + } + const bAssignmentDeclarationKind = isBinaryExpression(b.node) || isCallExpression(b.node) ? + getAssignmentDeclarationKind(b.node) : + AssignmentDeclarationKind.None; + + const aAssignmentDeclarationKind = isBinaryExpression(a.node) || isCallExpression(a.node) ? + getAssignmentDeclarationKind(a.node) : + AssignmentDeclarationKind.None; + + // We treat this as an es5 class and merge the nodes in in one of several cases + if ((isEs5ClassMember[bAssignmentDeclarationKind] && isEs5ClassMember[aAssignmentDeclarationKind]) // merge two class elements + || (isPossibleConstructor(a.node) && isEs5ClassMember[bAssignmentDeclarationKind]) // ctor function & member + || (isPossibleConstructor(b.node) && isEs5ClassMember[aAssignmentDeclarationKind]) // member & ctor function + || (isClassDeclaration(a.node) && isEs5ClassMember[bAssignmentDeclarationKind]) // class (generated) & member + || (isClassDeclaration(b.node) && isEs5ClassMember[aAssignmentDeclarationKind]) // member & class (generated) + || (isClassDeclaration(a.node) && isPossibleConstructor(b.node)) // class (generated) & ctor + || (isClassDeclaration(b.node) && isPossibleConstructor(a.node)) // ctor & class (generated) + ) { + + let lastANode = a.additionalNodes && lastOrUndefined(a.additionalNodes) || a.node; + + if ((!isClassDeclaration(a.node) && !isClassDeclaration(b.node)) // If neither outline node is a class + || isPossibleConstructor(a.node) || isPossibleConstructor(b.node) // If either function is a constructor function + ) { + const ctorFunction = isPossibleConstructor(a.node) ? a.node : + isPossibleConstructor(b.node) ? b.node : + undefined; + + if (ctorFunction !== undefined) { + const ctorNode = setTextRange( + createConstructor(/* decorators */ undefined, /* modifiers */ undefined, [], /* body */ undefined), + ctorFunction); + const ctor = emptyNavigationBarNode(ctorNode); + ctor.indent = a.indent + 1; + ctor.children = a.node === ctorFunction ? a.children : b.children; + a.children = a.node === ctorFunction ? concatenate([ctor], b.children || [b]) : concatenate(a.children || [a], [ctor]); + } + else { + if (a.children || b.children) { + a.children = concatenate(a.children || [a], b.children || [b]); + if (a.children) { + mergeChildren(a.children, a); + sortChildren(a.children); + } + } + } - function tryMerge(a: NavigationBarNode, b: NavigationBarNode, parent: NavigationBarNode): boolean { + lastANode = a.node = setTextRange(createClassDeclaration( + /* decorators */ undefined, + /* modifiers */ undefined, + a.name as Identifier || createIdentifier("__class__"), + /* typeParameters */ undefined, + /* heritageClauses */ undefined, + [] + ), a.node); + } + else { + a.children = concatenate(a.children, b.children); + if (a.children) { + mergeChildren(a.children, a); + } + } + + const bNode = b.node; + // We merge if the outline node previous to b (bIndex - 1) is already part of the current class + // We do this so that statements between class members that do not generate outline nodes do not split up the class outline: + // Ex This should produce one outline node C: + // function C() {}; a = 1; C.prototype.m = function () {} + // Ex This will produce 3 outline nodes: C, a, C + // function C() {}; let a = 1; C.prototype.m = function () {} + if (parent.children![bIndex - 1].node.end === lastANode.end) { + setTextRange(lastANode, { pos: lastANode.pos, end: bNode.end }); + } + else { + if (!a.additionalNodes) a.additionalNodes = []; + a.additionalNodes.push(setTextRange(createClassDeclaration( + /* decorators */ undefined, + /* modifiers */ undefined, + a.name as Identifier || createIdentifier("__class__"), + /* typeParameters */ undefined, + /* heritageClauses */ undefined, + [] + ), b.node)); + } + return true; + } + return bAssignmentDeclarationKind === AssignmentDeclarationKind.None ? false : true; + } + + function tryMerge(a: NavigationBarNode, b: NavigationBarNode, bIndex: number, parent: NavigationBarNode): boolean { + // const v = false as boolean; + if (tryMergeEs5Class(a, b, bIndex, parent)) { + return true; + } if (shouldReallyMerge(a.node, b.node, parent)) { merge(a, b); return true; @@ -444,7 +662,7 @@ namespace ts.NavigationBar { } if (name) { - const text = nodeText(name); + const text = isIdentifier(name) ? name.text : nodeText(name); if (text.length > 0) { return cleanText(text); } diff --git a/tests/cases/fourslash/navigationBarFunctionPrototype.ts b/tests/cases/fourslash/navigationBarFunctionPrototype.ts index 5494dab9f05e1..fbd3ba2c16f6d 100644 --- a/tests/cases/fourslash/navigationBarFunctionPrototype.ts +++ b/tests/cases/fourslash/navigationBarFunctionPrototype.ts @@ -3,6 +3,18 @@ // @Filename: foo.js ////function f() {} ////f.prototype.x = 0; +////f.y = 0; +////f.prototype.method = function () {}; +////Object.defineProperty(f, 'staticProp', { +//// set: function() {}, +//// get: function(){ +//// } +////}); +////Object.defineProperty(f.prototype, 'name', { +//// set: function() {}, +//// get: function(){ +//// } +////}); verify.navigationTree({ "text": "", @@ -10,11 +22,50 @@ verify.navigationTree({ "childItems": [ { "text": "f", - "kind": "function" - }, - { - "text": "x", - "kind": "property" + "kind": "class", + "childItems": [ + { + "text": "constructor", + "kind": "constructor" + }, + { + "text": "x", + "kind": "property" + }, + { + "text": "y" + }, + { + "text": "method", + "kind": "function" + }, + { + "text": "staticProp", + "childItems": [ + { + "text": "get", + "kind": "function" + }, + { + "text": "set", + "kind": "function" + } + ] + }, + { + "text": "name", + "childItems": [ + { + "text": "get", + "kind": "function" + }, + { + "text": "set", + "kind": "function" + } + ] + } + ] } ] }); @@ -26,17 +77,36 @@ verify.navigationBar([ "childItems": [ { "text": "f", - "kind": "function" - }, - { - "text": "x", - "kind": "property" + "kind": "class" } ] }, { "text": "f", - "kind": "function", + "kind": "class", + "childItems": [ + { + "text": "constructor", + "kind": "constructor" + }, + { + "text": "x", + "kind": "property" + }, + { + "text": "y" + }, + { + "text": "method", + "kind": "function" + }, + { + "text": "staticProp" + }, + { + "text": "name" + } + ], "indent": 1 } ]); diff --git a/tests/cases/fourslash/navigationBarFunctionPrototype2.ts b/tests/cases/fourslash/navigationBarFunctionPrototype2.ts new file mode 100644 index 0000000000000..547ba92c833bb --- /dev/null +++ b/tests/cases/fourslash/navigationBarFunctionPrototype2.ts @@ -0,0 +1,64 @@ +/// + +// @Filename: foo.js + +////A.prototype.a = function() { }; +////A.prototype.b = function() { }; +////function A() {} + +verify.navigationTree({ + "text": "", + "kind": "script", + "childItems": [ + { + "text": "A", + "kind": "class", + "childItems": [ + { + "text": "a", + "kind": "function" + }, + { + "text": "b", + "kind": "function" + }, + { + "text": "constructor", + "kind": "constructor" + } + ] + } + ] +}); + +verify.navigationBar([ + { + "text": "", + "kind": "script", + "childItems": [ + { + "text": "A", + "kind": "class" + } + ] + }, + { + "text": "A", + "kind": "class", + "childItems": [ + { + "text": "a", + "kind": "function" + }, + { + "text": "b", + "kind": "function" + }, + { + "text": "constructor", + "kind": "constructor" + } + ], + "indent": 1 + } +]); diff --git a/tests/cases/fourslash/navigationBarFunctionPrototype3.ts b/tests/cases/fourslash/navigationBarFunctionPrototype3.ts new file mode 100644 index 0000000000000..4314e942cea26 --- /dev/null +++ b/tests/cases/fourslash/navigationBarFunctionPrototype3.ts @@ -0,0 +1,64 @@ +/// + +// @Filename: foo.js + +////var A; +////A.prototype.a = function() { }; +////A.b = function() { }; + +verify.navigationTree({ + "text": "", + "kind": "script", + "childItems": [ + { + "text": "A", + "kind": "class", + "childItems": [ + { + "text": "constructor", + "kind": "constructor" + }, + { + "text": "a", + "kind": "function" + }, + { + "text": "b", + "kind": "function" + } + ] + } + ] +}); + +verify.navigationBar([ + { + "text": "", + "kind": "script", + "childItems": [ + { + "text": "A", + "kind": "class" + } + ] + }, + { + "text": "A", + "kind": "class", + "childItems": [ + { + "text": "constructor", + "kind": "constructor" + }, + { + "text": "a", + "kind": "function" + }, + { + "text": "b", + "kind": "function" + } + ], + "indent": 1 + } +]); diff --git a/tests/cases/fourslash/navigationBarFunctionPrototype4.ts b/tests/cases/fourslash/navigationBarFunctionPrototype4.ts new file mode 100644 index 0000000000000..c24261ee8230d --- /dev/null +++ b/tests/cases/fourslash/navigationBarFunctionPrototype4.ts @@ -0,0 +1,74 @@ +/// + +// @Filename: foo.js + +////var A; +////A.prototype = { }; +////A.prototype = { m() {} }; +////A.prototype.a = function() { }; +////A.b = function() { }; + +verify.navigationTree({ + "text": "", + "kind": "script", + "childItems": [ + { + "text": "A", + "kind": "class", + "childItems": [ + { + "text": "constructor", + "kind": "constructor" + }, + { + "text": "m", + "kind": "method" + }, + { + "text": "a", + "kind": "function" + }, + { + "text": "b", + "kind": "function" + } + ] + } + ] +}); + +verify.navigationBar([ + { + "text": "", + "kind": "script", + "childItems": [ + { + "text": "A", + "kind": "class" + } + ] + }, + { + "text": "A", + "kind": "class", + "childItems": [ + { + "text": "constructor", + "kind": "constructor" + }, + { + "text": "m", + "kind": "method" + }, + { + "text": "a", + "kind": "function" + }, + { + "text": "b", + "kind": "function" + } + ], + "indent": 1 + } +]); diff --git a/tests/cases/fourslash/navigationBarFunctionPrototypeBroken.ts b/tests/cases/fourslash/navigationBarFunctionPrototypeBroken.ts new file mode 100644 index 0000000000000..48270dc3aa167 --- /dev/null +++ b/tests/cases/fourslash/navigationBarFunctionPrototypeBroken.ts @@ -0,0 +1,156 @@ +/// + +// @Filename: foo.js + +////function A() {} +////A. // Started typing something here +////A.prototype.a = function() { }; +////G. // Started typing something here +////A.prototype.a = function() { }; + +verify.navigationTree({ + "text": "", + "kind": "script", + "spans": [ + { + "start": 0, + "length": 151 + } + ], + "childItems": [ + { + "text": "G", + "kind": "method", + "spans": [ + { + "start": 84, + "length": 66 + } + ], + "nameSpan": { + "start": 84, + "length": 1 + }, + "childItems": [ + { + "text": "A", + "kind": "method", + "spans": [ + { + "start": 84, + "length": 66 + } + ], + "nameSpan": { + "start": 120, + "length": 1 + }, + "childItems": [ + { + "text": "a", + "kind": "function", + "spans": [ + { + "start": 136, + "length": 14 + } + ], + "nameSpan": { + "start": 132, + "length": 1 + } + } + ] + } + ] + }, + { + "text": "A", + "kind": "class", + "spans": [ + { + "start": 0, + "length": 82 + } + ], + "nameSpan": { + "start": 9, + "length": 1 + }, + "childItems": [ + { + "text": "constructor", + "kind": "constructor", + "spans": [ + { + "start": 0, + "length": 15 + } + ] + }, + { + "text": "A", + "kind": "method", + "spans": [ + { + "start": 16, + "length": 66 + } + ], + "nameSpan": { + "start": 52, + "length": 1 + }, + "childItems": [ + { + "text": "a", + "kind": "function", + "spans": [ + { + "start": 68, + "length": 14 + } + ], + "nameSpan": { + "start": 64, + "length": 1 + } + } + ] + } + ] + } + ] +}, { checkSpans: true }); + +verify.navigationBar([ + { + "text": "", + "kind": "script", + "childItems": [ + { + "text": "G", + "kind": "method" + }, + { + "text": "A", + "kind": "class" + } + ] + }, + { + "text": "A", + "kind": "class", + "childItems": [ + { + "text": "constructor", + "kind": "constructor" + }, + { + "text": "A", + "kind": "method" + } + ], + "indent": 1 + } +]); diff --git a/tests/cases/fourslash/navigationBarFunctionPrototypeInterlaced.ts b/tests/cases/fourslash/navigationBarFunctionPrototypeInterlaced.ts new file mode 100644 index 0000000000000..083212164c099 --- /dev/null +++ b/tests/cases/fourslash/navigationBarFunctionPrototypeInterlaced.ts @@ -0,0 +1,188 @@ +/// + +// @Filename: foo.js + +////var b = 1; +////function A() {}; +////A.prototype.a = function() { }; +////A.b = function() { }; +////b = 2 +/////* Comment */ +////A.prototype.c = function() { } +////var b = 2 +////A.prototype.d = function() { } + +verify.navigationTree({ + "text": "", + "kind": "script", + "spans": [ + { + "start": 0, + "length": 174 + } + ], + "childItems": [ + { + "text": "A", + "kind": "class", + "spans": [ + { + "start": 11, + "length": 122 + }, + { + "start": 144, + "length": 30 + } + ], + "nameSpan": { + "start": 20, + "length": 1 + }, + "childItems": [ + { + "text": "constructor", + "kind": "constructor", + "spans": [ + { + "start": 11, + "length": 15 + } + ] + }, + { + "text": "a", + "kind": "function", + "spans": [ + { + "start": 45, + "length": 14 + } + ], + "nameSpan": { + "start": 41, + "length": 1 + } + }, + { + "text": "b", + "kind": "function", + "spans": [ + { + "start": 67, + "length": 14 + } + ], + "nameSpan": { + "start": 63, + "length": 1 + } + }, + { + "text": "c", + "kind": "function", + "spans": [ + { + "start": 119, + "length": 14 + } + ], + "nameSpan": { + "start": 115, + "length": 1 + } + }, + { + "text": "d", + "kind": "function", + "spans": [ + { + "start": 160, + "length": 14 + } + ], + "nameSpan": { + "start": 156, + "length": 1 + } + } + ] + }, + { + "text": "b", + "kind": "var", + "spans": [ + { + "start": 4, + "length": 5 + } + ], + "nameSpan": { + "start": 4, + "length": 1 + } + }, + { + "text": "b", + "kind": "var", + "spans": [ + { + "start": 138, + "length": 5 + } + ], + "nameSpan": { + "start": 138, + "length": 1 + } + } + ] +}, { checkSpans: true }); + +verify.navigationBar([ + { + "text": "", + "kind": "script", + "childItems": [ + { + "text": "A", + "kind": "class" + }, + { + "text": "b", + "kind": "var" + }, + { + "text": "b", + "kind": "var" + } + ] + }, + { + "text": "A", + "kind": "class", + "childItems": [ + { + "text": "constructor", + "kind": "constructor" + }, + { + "text": "a", + "kind": "function" + }, + { + "text": "b", + "kind": "function" + }, + { + "text": "c", + "kind": "function" + }, + { + "text": "d", + "kind": "function" + } + ], + "indent": 1 + } +]); diff --git a/tests/cases/fourslash/navigationBarFunctionPrototypeNested.ts b/tests/cases/fourslash/navigationBarFunctionPrototypeNested.ts new file mode 100644 index 0000000000000..dea8b872061eb --- /dev/null +++ b/tests/cases/fourslash/navigationBarFunctionPrototypeNested.ts @@ -0,0 +1,226 @@ +/// + +// @Filename: foo.js + +////function A() {} +////A.B = function () { } +////A.B.prototype.d = function () { } +////Object.defineProperty(A.B.prototype, "x", { +//// get() {} +////}) +////A.prototype.D = function () { } +////A.prototype.D.prototype.d = function () { } + + +verify.navigationTree({ + "text": "", + "kind": "script", + "spans": [ + { + "start": 0, + "length": 216 + } + ], + "childItems": [ + { + "text": "A", + "kind": "class", + "spans": [ + { + "start": 0, + "length": 215 + } + ], + "nameSpan": { + "start": 9, + "length": 1 + }, + "childItems": [ + { + "text": "constructor", + "kind": "constructor", + "spans": [ + { + "start": 0, + "length": 15 + } + ] + }, + { + "text": "B", + "kind": "class", + "spans": [ + { + "start": 22, + "length": 114 + } + ], + "nameSpan": { + "start": 18, + "length": 1 + }, + "childItems": [ + { + "text": "constructor", + "kind": "constructor", + "spans": [ + { + "start": 22, + "length": 16 + } + ] + }, + { + "text": "d", + "kind": "function", + "spans": [ + { + "start": 58, + "length": 16 + } + ], + "nameSpan": { + "start": 54, + "length": 1 + } + }, + { + "text": "x", + "spans": [ + { + "start": 77, + "length": 59 + } + ], + "nameSpan": { + "start": 114, + "length": 3 + }, + "childItems": [ + { + "text": "get", + "kind": "method", + "spans": [ + { + "start": 125, + "length": 8 + } + ], + "nameSpan": { + "start": 125, + "length": 3 + } + } + ] + } + ] + }, + { + "text": "D", + "kind": "class", + "spans": [ + { + "start": 153, + "length": 62 + } + ], + "nameSpan": { + "start": 149, + "length": 1 + }, + "childItems": [ + { + "text": "constructor", + "kind": "constructor", + "spans": [ + { + "start": 153, + "length": 16 + } + ] + }, + { + "text": "d", + "kind": "function", + "spans": [ + { + "start": 199, + "length": 16 + } + ], + "nameSpan": { + "start": 195, + "length": 1 + } + } + ] + } + ] + } + ] +}, { checkSpans: true }); + +verify.navigationBar([ + { + "text": "", + "kind": "script", + "childItems": [ + { + "text": "A", + "kind": "class" + } + ] + }, + { + "text": "A", + "kind": "class", + "childItems": [ + { + "text": "constructor", + "kind": "constructor" + }, + { + "text": "B", + "kind": "class" + }, + { + "text": "D", + "kind": "class" + } + ], + "indent": 1 + }, + { + "text": "B", + "kind": "class", + "childItems": [ + { + "text": "constructor", + "kind": "constructor" + }, + { + "text": "d", + "kind": "function" + }, + { + "text": "x" + } + ], + "indent": 2 + }, + { + "text": "D", + "kind": "class", + "childItems": [ + { + "text": "constructor", + "kind": "constructor" + }, + { + "text": "d", + "kind": "function" + } + ], + "indent": 2 + } +]);