diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index effaa53bc10b6..1d7e968444ec2 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -160,6 +160,20 @@ namespace ts.NavigationBar { for (let node of nodes) { switch (node.kind) { case SyntaxKind.ClassDeclaration: + topLevelNodes.push(node); + for (const member of (node).members) { + if (member.kind === SyntaxKind.MethodDeclaration || member.kind === SyntaxKind.Constructor) { + type FunctionLikeMember = MethodDeclaration | ConstructorDeclaration; + if ((member).body) { + // We do not include methods that does not have child functions in it, because of duplications. + if (hasNamedFunctionDeclarations(((member).body).statements)) { + topLevelNodes.push(member); + } + addTopLevelNodes(((member).body).statements, topLevelNodes); + } + } + } + break; case SyntaxKind.EnumDeclaration: case SyntaxKind.InterfaceDeclaration: topLevelNodes.push(node); @@ -182,23 +196,40 @@ namespace ts.NavigationBar { } } - function isTopLevelFunctionDeclaration(functionDeclaration: FunctionLikeDeclaration) { + function hasNamedFunctionDeclarations(nodes: NodeArray): boolean { + for (let s of nodes) { + if (s.kind === SyntaxKind.FunctionDeclaration && !isEmpty((s).name.text)) { + return true; + } + } + return false; + } + + function isTopLevelFunctionDeclaration(functionDeclaration: FunctionLikeDeclaration): boolean { if (functionDeclaration.kind === SyntaxKind.FunctionDeclaration) { // A function declaration is 'top level' if it contains any function declarations // within it. if (functionDeclaration.body && functionDeclaration.body.kind === SyntaxKind.Block) { // Proper function declarations can only have identifier names - if (forEach((functionDeclaration.body).statements, - s => s.kind === SyntaxKind.FunctionDeclaration && !isEmpty((s).name.text))) { - + if (hasNamedFunctionDeclarations((functionDeclaration.body).statements)) { return true; } - // Or if it is not parented by another function. i.e all functions - // at module scope are 'top level'. + // 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; } + + // 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) { + + return true; + } + } } } @@ -376,6 +407,10 @@ namespace ts.NavigationBar { case SyntaxKind.ClassDeclaration: return createClassItem(node); + case SyntaxKind.MethodDeclaration: + case SyntaxKind.Constructor: + return createMemberFunctionLikeItem(node); + case SyntaxKind.EnumDeclaration: return createEnumItem(node); @@ -424,11 +459,11 @@ namespace ts.NavigationBar { getIndent(node)); } - function createFunctionItem(node: FunctionDeclaration) { + function createFunctionItem(node: FunctionDeclaration): ts.NavigationBarItem { if (node.body && node.body.kind === SyntaxKind.Block) { let childItems = getItemsWorker(sortNodes((node.body).statements), createChildItem); - return getNavigationBarItem(!node.name ? "default": node.name.text , + return getNavigationBarItem(!node.name ? "default": node.name.text, ts.ScriptElementKind.functionElement, getNodeModifiers(node), [getNodeSpan(node)], @@ -439,6 +474,31 @@ namespace ts.NavigationBar { return undefined; } + function createMemberFunctionLikeItem(node: MethodDeclaration | ConstructorDeclaration): ts.NavigationBarItem { + if (node.body && node.body.kind === SyntaxKind.Block) { + let 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; + } + + return getNavigationBarItem(memberFunctionName, + scriptElementKind, + getNodeModifiers(node), + [getNodeSpan(node)], + childItems, + getIndent(node)); + } + + return undefined; + } + function createSourceFileItem(node: SourceFile): ts.NavigationBarItem { let childItems = getItemsWorker(getChildNodes(node.statements), createChildItem); diff --git a/tests/cases/fourslash/navigationBarItemsInsideMethodsAndConstructors.ts b/tests/cases/fourslash/navigationBarItemsInsideMethodsAndConstructors.ts new file mode 100644 index 0000000000000..4dcca43af356d --- /dev/null +++ b/tests/cases/fourslash/navigationBarItemsInsideMethodsAndConstructors.ts @@ -0,0 +1,42 @@ +/// + +////class Class { +//// constructor() { +//// {| "itemName": "LocalFunctionInConstructor", "kind": "function", "parentName": "Class"|}function LocalFunctionInConstructor() { +//// +//// } +//// +//// {| "itemName": "LocalInterfaceInConstrcutor", "kind": "interface", "parentName": "foo"|}interface LocalInterfaceInConstrcutor { +//// } +//// +//// enum LocalEnumInConstructor { +//// {| "itemName": "LocalEnumMemberInConstructor", "kind": "property", "parentName": "LocalEnumInConstructor"|}LocalEnumMemberInConstructor, +//// } +//// } +//// +//// method() { +//// {| "itemName": "LocalFunctionInMethod", "kind": "function", "parentName": "foo"|}function LocalFunctionInMethod() { +//// {| "itemName": "LocalFunctionInLocalFunctionInMethod", "kind": "function", "parentName": "bar"|}function LocalFunctionInLocalFunctionInMethod() { +//// +//// } +//// } +//// +//// {| "itemName": "LocalInterfaceInMethod", "kind": "interface", "parentName": "foo"|}interface LocalInterfaceInMethod { +//// } +//// +//// enum LocalEnumInMethod { +//// {| "itemName": "LocalEnumMemberInMethod", "kind": "property", "parentName": "foo"|}LocalEnumMemberInMethod, +//// } +//// } +//// +//// emptyMethod() { // Non child functions method should not be duplicated +//// +//// } +////} + +test.markers().forEach((marker) => { + verify.getScriptLexicalStructureListContains(marker.data.itemName, marker.data.kind, marker.fileName, marker.data.parentName); +}); + +// no other items +verify.getScriptLexicalStructureListCount(17);