From cae42fde4f7c8b651920129d69218b9c46ee526b Mon Sep 17 00:00:00 2001 From: Bill Ticehurst Date: Fri, 11 Mar 2016 08:48:00 -0800 Subject: [PATCH 1/9] Basic flat list of items --- .../fourslash/navbarFunctionExpressions.ts | 80 ++++++ tests/cases/fourslash/navbarTsNamespace.ts | 261 ++++++++++++++++++ 2 files changed, 341 insertions(+) create mode 100644 tests/cases/fourslash/navbarFunctionExpressions.ts create mode 100644 tests/cases/fourslash/navbarTsNamespace.ts diff --git a/tests/cases/fourslash/navbarFunctionExpressions.ts b/tests/cases/fourslash/navbarFunctionExpressions.ts new file mode 100644 index 0000000000000..cb7e258f25149 --- /dev/null +++ b/tests/cases/fourslash/navbarFunctionExpressions.ts @@ -0,0 +1,80 @@ +/// + +// @allowJs: true +// @Filename: test.js +//// // Should return NavigateTo items for foo & bar functions +//// var bar = (function foo(a){ +//// let bar = function (){ +//// return true; +//// } +//// return bar; +//// })(this); +//// function dummy(){} + +var expected = [ + { + "text": "", + "kind": "module", + "kindModifiers": "", + "spans": [ + { + "start": 0, + "length": 184 + } + ], + "childItems": [ + { + "text": "bar", + "kind": "var", + "kindModifiers": "", + "spans": [ + { + "start": 62, + "length": 102 + } + ], + "childItems": [], + "indent": 0, + "bolded": false, + "grayed": false + }, + { + "text": "dummy", + "kind": "function", + "kindModifiers": "", + "spans": [ + { + "start": 166, + "length": 18 + } + ], + "childItems": [], + "indent": 0, + "bolded": false, + "grayed": false + } + ], + "indent": 0, + "bolded": false, + "grayed": false + }, + { + "text": "dummy", + "kind": "function", + "kindModifiers": "", + "spans": [ + { + "start": 166, + "length": 18 + } + ], + "childItems": [], + "indent": 1, + "bolded": false, + "grayed": false + } +]; + +debug.printScriptLexicalStructureItems(); +debug.printNavigationItems(); +verify.getScriptLexicalStructureListContains("bar", "function"); diff --git a/tests/cases/fourslash/navbarTsNamespace.ts b/tests/cases/fourslash/navbarTsNamespace.ts new file mode 100644 index 0000000000000..3a3ae6d73993d --- /dev/null +++ b/tests/cases/fourslash/navbarTsNamespace.ts @@ -0,0 +1,261 @@ +/// + +// @Filename: test.ts +//// let v1 = true; +//// var v2 = false; +//// const c1 = true; +//// +//// namespace N1 { +//// var v3 = 10; +//// export var v4 = true; +//// +//// export function f1() { +//// const v5 = 42; +//// function inner1(){} +//// } +//// +//// function f2() { +//// function inner2(){ +//// function inner3(){} +//// } +//// } +//// } + + + +debug.printScriptLexicalStructureItems(); +debug.printNavigationItems(); +verify.getScriptLexicalStructureListContains("N", "module"); + +var priorResult = [ + { + "text": "", + "kind": "module", + "kindModifiers": "", + "spans": [ + { + "start": 0, + "length": 297 + } + ], + "childItems": [ + { + "text": "c1", + "kind": "const", + "kindModifiers": "", + "spans": [ + { + "start": 37, + "length": 9 + } + ], + "childItems": [], + "indent": 0, + "bolded": false, + "grayed": false + }, + { + "text": "v1", + "kind": "let", + "kindModifiers": "", + "spans": [ + { + "start": 4, + "length": 9 + } + ], + "childItems": [], + "indent": 0, + "bolded": false, + "grayed": false + }, + { + "text": "v2", + "kind": "var", + "kindModifiers": "", + "spans": [ + { + "start": 19, + "length": 10 + } + ], + "childItems": [], + "indent": 0, + "bolded": false, + "grayed": false + } + ], + "indent": 0, + "bolded": false, + "grayed": false + }, + { + "text": "N1", + "kind": "module", + "kindModifiers": "", + "spans": [ + { + "start": 49, + "length": 248 + } + ], + "childItems": [ + { + "text": "f1", + "kind": "function", + "kindModifiers": "export", + "spans": [ + { + "start": 116, + "length": 79 + } + ], + "childItems": [], + "indent": 0, + "bolded": false, + "grayed": false + }, + { + "text": "f2", + "kind": "function", + "kindModifiers": "", + "spans": [ + { + "start": 205, + "length": 90 + } + ], + "childItems": [], + "indent": 0, + "bolded": false, + "grayed": false + }, + { + "text": "v3", + "kind": "var", + "kindModifiers": "", + "spans": [ + { + "start": 72, + "length": 7 + } + ], + "childItems": [], + "indent": 0, + "bolded": false, + "grayed": false + }, + { + "text": "v4", + "kind": "var", + "kindModifiers": "export", + "spans": [ + { + "start": 96, + "length": 9 + } + ], + "childItems": [], + "indent": 0, + "bolded": false, + "grayed": false + } + ], + "indent": 1, + "bolded": false, + "grayed": false + }, + { + "text": "f1", + "kind": "function", + "kindModifiers": "export", + "spans": [ + { + "start": 116, + "length": 79 + } + ], + "childItems": [ + { + "text": "inner1", + "kind": "function", + "kindModifiers": "", + "spans": [ + { + "start": 170, + "length": 19 + } + ], + "childItems": [], + "indent": 0, + "bolded": false, + "grayed": false + } + ], + "indent": 2, + "bolded": false, + "grayed": false + }, + { + "text": "f2", + "kind": "function", + "kindModifiers": "", + "spans": [ + { + "start": 205, + "length": 90 + } + ], + "childItems": [ + { + "text": "inner2", + "kind": "function", + "kindModifiers": "", + "spans": [ + { + "start": 229, + "length": 60 + } + ], + "childItems": [], + "indent": 0, + "bolded": false, + "grayed": false + } + ], + "indent": 2, + "bolded": false, + "grayed": false + }, + { + "text": "inner2", + "kind": "function", + "kindModifiers": "", + "spans": [ + { + "start": 229, + "length": 60 + } + ], + "childItems": [ + { + "text": "inner3", + "kind": "function", + "kindModifiers": "", + "spans": [ + { + "start": 260, + "length": 19 + } + ], + "childItems": [], + "indent": 0, + "bolded": false, + "grayed": false + } + ], + "indent": 3, + "bolded": false, + "grayed": false + } +]; \ No newline at end of file From 080f639ff826be55c0381a9547dce07d2f846ce3 Mon Sep 17 00:00:00 2001 From: Bill Ticehurst Date: Sun, 13 Mar 2016 13:00:59 -0700 Subject: [PATCH 2/9] Support for AMD modules --- src/services/navigationBar.ts | 179 ++++++++++++++++++++++++++++++++++ 1 file changed, 179 insertions(+) diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index f62c6cb1700dd..51cebcb66360f 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -1,8 +1,187 @@ +/// /// /* @internal */ namespace ts.NavigationBar { + export function getJsNavigationBarItems(sourceFile: SourceFile, compilerOptions: CompilerOptions): NavigationBarItem[] { + const anonFnText = ""; + let indent = 0; + + let rootName = isExternalModule(sourceFile) ? + "\"" + escapeString(getBaseFileName(removeFileExtension(normalizePath(sourceFile.fileName)))) + "\"" + : ""; + + let 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); + + function visitNode(node: Node) { + const newItem = createNavBarItem(node); + + if (newItem) { + topItem.childItems.push(newItem); + } + + // Add a level if traversing into a container + if (isNavBarContainer(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 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 )) { + return undefined; + } + // Fall through + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.Constructor: + // TODO: Check behavior with names that are symbols, indexers, etc. + const name = node.kind === SyntaxKind.Constructor ? + "constructor" : declarationNameToString((node as (Declaration)).name); + + const elementKind = + node.kind === SyntaxKind.VariableDeclaration ? ScriptElementKind.variableElement : + node.kind === SyntaxKind.FunctionDeclaration ? ScriptElementKind.functionElement : + node.kind === SyntaxKind.ClassDeclaration ? ScriptElementKind.classElement : "constructor"; + + return getNavBarItem(name, elementKind, [getNodeSpan(node)]); + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + return getDefineModuleItem(node) || getFunctionExpressionItem(node); + case SyntaxKind.MethodDeclaration: + const methodDecl = node as MethodDeclaration; + return getNavBarItem(declarationNameToString(methodDecl.name), + ScriptElementKind.memberFunctionElement, + [getNodeSpan(node)]); + // TODO: Class properties/getters/setters, export defaults, import/export identifiers + default: + return undefined; + } + } + + function isNavBarContainer(node: Node) { + // We only want to add a new level when going into a function or a class + switch (node.kind) { + case SyntaxKind.ClassDeclaration: + case SyntaxKind.Constructor: + case SyntaxKind.MethodDeclaration: + case SyntaxKind.FunctionDeclaration: + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + return true; + default: + return false; + } + } + + function getNavBarItem(text: string, kind: string, spans: TextSpan[], kindModifiers = ScriptElementKindModifier.none): NavigationBarItem { + return { + text, kind, kindModifiers, spans, childItems: [], indent, bolded: false, grayed: false + } + } + + 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)]); + } + + function getFunctionExpressionItem(node: Node): NavigationBarItem { + if (node.kind !== SyntaxKind.FunctionExpression && node.kind !== SyntaxKind.ArrowFunction) { + return undefined; + } + + const fnExpr = node as FunctionExpression | ArrowFunction; + let fnName: string; + if (fnExpr.name && getFullWidth(fnExpr.name) > 0) { + // The function 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.FirstAssignment) { + fnName = (fnExpr.parent as BinaryExpression).left.getText(); + if (fnName.length > 20) { + fnName = fnName.substring(0,20) + "..."; + } + } + // 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 = anonFnText; + } + } + return getNavBarItem(fnName, ScriptElementKind.functionElement, [getNodeSpan(node)]); + } + + function getNodeSpan(node: Node) { + return node.kind === SyntaxKind.SourceFile + ? createTextSpanFromBounds(node.getFullStart(), node.getEnd()) + : createTextSpanFromBounds(node.getStart(), node.getEnd()); + } + + return sourceFileItem.childItems; + } + 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); + } + // If the source file has any child items, then it included in the tree // and takes lexical ownership of all other top-level items. let hasGlobalNode = false; From 9a5cb29a0aca84b09bb65227547b84334ce599e1 Mon Sep 17 00:00:00 2001 From: Bill Ticehurst Date: Sun, 13 Mar 2016 13:04:01 -0700 Subject: [PATCH 3/9] Fixed whitespace --- src/services/navigationBar.ts | 52 +++++++++++++++++------------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index 51cebcb66360f..d6cc473ef6328 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -33,7 +33,7 @@ namespace ts.NavigationBar { 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(); @@ -55,7 +55,7 @@ namespace ts.NavigationBar { } // 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 || + if (varDecl.initializer && (varDecl.initializer.kind === SyntaxKind.FunctionExpression || varDecl.initializer.kind === SyntaxKind.ArrowFunction )) { return undefined; } @@ -64,11 +64,11 @@ namespace ts.NavigationBar { case SyntaxKind.ClassDeclaration: case SyntaxKind.Constructor: // TODO: Check behavior with names that are symbols, indexers, etc. - const name = node.kind === SyntaxKind.Constructor ? + const name = node.kind === SyntaxKind.Constructor ? "constructor" : declarationNameToString((node as (Declaration)).name); - const elementKind = - node.kind === SyntaxKind.VariableDeclaration ? ScriptElementKind.variableElement : + const elementKind = + node.kind === SyntaxKind.VariableDeclaration ? ScriptElementKind.variableElement : node.kind === SyntaxKind.FunctionDeclaration ? ScriptElementKind.functionElement : node.kind === SyntaxKind.ClassDeclaration ? ScriptElementKind.classElement : "constructor"; @@ -78,8 +78,8 @@ namespace ts.NavigationBar { return getDefineModuleItem(node) || getFunctionExpressionItem(node); case SyntaxKind.MethodDeclaration: const methodDecl = node as MethodDeclaration; - return getNavBarItem(declarationNameToString(methodDecl.name), - ScriptElementKind.memberFunctionElement, + return getNavBarItem(declarationNameToString(methodDecl.name), + ScriptElementKind.memberFunctionElement, [getNodeSpan(node)]); // TODO: Class properties/getters/setters, export defaults, import/export identifiers default: @@ -107,7 +107,7 @@ namespace ts.NavigationBar { text, kind, kindModifiers, spans, childItems: [], indent, bolded: false, grayed: false } } - + function getDefineModuleItem(node: Node): NavigationBarItem { if (node.kind !== SyntaxKind.FunctionExpression && node.kind !== SyntaxKind.ArrowFunction) { return undefined; @@ -116,12 +116,12 @@ namespace ts.NavigationBar { // 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) { @@ -129,14 +129,14 @@ namespace ts.NavigationBar { } return getNavBarItem(defaultName, ScriptElementKind.moduleElement, [getNodeSpan(node.parent)]); } - + function getFunctionExpressionItem(node: Node): NavigationBarItem { if (node.kind !== SyntaxKind.FunctionExpression && node.kind !== SyntaxKind.ArrowFunction) { return undefined; } const fnExpr = node as FunctionExpression | ArrowFunction; - let fnName: string; + let fnName: string; if (fnExpr.name && getFullWidth(fnExpr.name) > 0) { // The function expression has an identifier, so use that as the name fnName = declarationNameToString(fnExpr.name); @@ -147,7 +147,7 @@ namespace ts.NavigationBar { 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 && + else if (fnExpr.parent.kind === SyntaxKind.BinaryExpression && (fnExpr.parent as BinaryExpression).operatorToken.kind === SyntaxKind.FirstAssignment) { fnName = (fnExpr.parent as BinaryExpression).left.getText(); if (fnName.length > 20) { @@ -155,7 +155,7 @@ namespace ts.NavigationBar { } } // See if it is a property assignment, and if so use the property name - else if (fnExpr.parent.kind === SyntaxKind.PropertyAssignment && + else if (fnExpr.parent.kind === SyntaxKind.PropertyAssignment && (fnExpr.parent as PropertyAssignment).name) { fnName = (fnExpr.parent as PropertyAssignment).name.getText(); } @@ -163,7 +163,7 @@ namespace ts.NavigationBar { fnName = anonFnText; } } - return getNavBarItem(fnName, ScriptElementKind.functionElement, [getNodeSpan(node)]); + return getNavBarItem(fnName, ScriptElementKind.functionElement, [getNodeSpan(node)]); } function getNodeSpan(node: Node) { @@ -174,14 +174,14 @@ namespace ts.NavigationBar { return sourceFileItem.childItems; } - + export function getNavigationBarItems(sourceFile: SourceFile, compilerOptions: CompilerOptions): ts.NavigationBarItem[] { - // TODO: Handle JS files differently in 'navbar' calls for now, but ideally we should unify + // 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); } - + // If the source file has any child items, then it included in the tree // and takes lexical ownership of all other top-level items. let hasGlobalNode = false; @@ -309,7 +309,7 @@ namespace ts.NavigationBar { return topLevelNodes; } - + function sortNodes(nodes: Node[]): Node[] { return nodes.slice(0).sort((n1: Declaration, n2: Declaration) => { if (n1.name && n2.name) { @@ -326,7 +326,7 @@ namespace ts.NavigationBar { } }); } - + function addTopLevelNodes(nodes: Node[], topLevelNodes: Node[]): void { nodes = sortNodes(nodes); @@ -357,8 +357,8 @@ namespace ts.NavigationBar { function isTopLevelFunctionDeclaration(functionDeclaration: FunctionLikeDeclaration) { if (functionDeclaration.kind === SyntaxKind.FunctionDeclaration) { - // A function declaration is 'top level' if it contains any function declarations - // within it. + // 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, @@ -377,7 +377,7 @@ namespace ts.NavigationBar { return false; } - + function getItemsWorker(nodes: Node[], createItem: (n: Node) => ts.NavigationBarItem): ts.NavigationBarItem[] { let items: ts.NavigationBarItem[] = []; @@ -574,19 +574,19 @@ namespace ts.NavigationBar { let 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 createModuleItem(node: ModuleDeclaration): NavigationBarItem { let moduleName = getModuleName(node); - + let childItems = getItemsWorker(getChildNodes((getInnermostModule(node).body).statements), createChildItem); return getNavigationBarItem(moduleName, From b3a35c5d3299f6e60b183815dcfbcbefda45df74 Mon Sep 17 00:00:00 2001 From: Bill Ticehurst Date: Sun, 13 Mar 2016 13:10:14 -0700 Subject: [PATCH 4/9] Remove temporary test --- .../fourslash/navbarFunctionExpressions.ts | 80 ------ tests/cases/fourslash/navbarTsNamespace.ts | 261 ------------------ 2 files changed, 341 deletions(-) delete mode 100644 tests/cases/fourslash/navbarFunctionExpressions.ts delete mode 100644 tests/cases/fourslash/navbarTsNamespace.ts diff --git a/tests/cases/fourslash/navbarFunctionExpressions.ts b/tests/cases/fourslash/navbarFunctionExpressions.ts deleted file mode 100644 index cb7e258f25149..0000000000000 --- a/tests/cases/fourslash/navbarFunctionExpressions.ts +++ /dev/null @@ -1,80 +0,0 @@ -/// - -// @allowJs: true -// @Filename: test.js -//// // Should return NavigateTo items for foo & bar functions -//// var bar = (function foo(a){ -//// let bar = function (){ -//// return true; -//// } -//// return bar; -//// })(this); -//// function dummy(){} - -var expected = [ - { - "text": "", - "kind": "module", - "kindModifiers": "", - "spans": [ - { - "start": 0, - "length": 184 - } - ], - "childItems": [ - { - "text": "bar", - "kind": "var", - "kindModifiers": "", - "spans": [ - { - "start": 62, - "length": 102 - } - ], - "childItems": [], - "indent": 0, - "bolded": false, - "grayed": false - }, - { - "text": "dummy", - "kind": "function", - "kindModifiers": "", - "spans": [ - { - "start": 166, - "length": 18 - } - ], - "childItems": [], - "indent": 0, - "bolded": false, - "grayed": false - } - ], - "indent": 0, - "bolded": false, - "grayed": false - }, - { - "text": "dummy", - "kind": "function", - "kindModifiers": "", - "spans": [ - { - "start": 166, - "length": 18 - } - ], - "childItems": [], - "indent": 1, - "bolded": false, - "grayed": false - } -]; - -debug.printScriptLexicalStructureItems(); -debug.printNavigationItems(); -verify.getScriptLexicalStructureListContains("bar", "function"); diff --git a/tests/cases/fourslash/navbarTsNamespace.ts b/tests/cases/fourslash/navbarTsNamespace.ts deleted file mode 100644 index 3a3ae6d73993d..0000000000000 --- a/tests/cases/fourslash/navbarTsNamespace.ts +++ /dev/null @@ -1,261 +0,0 @@ -/// - -// @Filename: test.ts -//// let v1 = true; -//// var v2 = false; -//// const c1 = true; -//// -//// namespace N1 { -//// var v3 = 10; -//// export var v4 = true; -//// -//// export function f1() { -//// const v5 = 42; -//// function inner1(){} -//// } -//// -//// function f2() { -//// function inner2(){ -//// function inner3(){} -//// } -//// } -//// } - - - -debug.printScriptLexicalStructureItems(); -debug.printNavigationItems(); -verify.getScriptLexicalStructureListContains("N", "module"); - -var priorResult = [ - { - "text": "", - "kind": "module", - "kindModifiers": "", - "spans": [ - { - "start": 0, - "length": 297 - } - ], - "childItems": [ - { - "text": "c1", - "kind": "const", - "kindModifiers": "", - "spans": [ - { - "start": 37, - "length": 9 - } - ], - "childItems": [], - "indent": 0, - "bolded": false, - "grayed": false - }, - { - "text": "v1", - "kind": "let", - "kindModifiers": "", - "spans": [ - { - "start": 4, - "length": 9 - } - ], - "childItems": [], - "indent": 0, - "bolded": false, - "grayed": false - }, - { - "text": "v2", - "kind": "var", - "kindModifiers": "", - "spans": [ - { - "start": 19, - "length": 10 - } - ], - "childItems": [], - "indent": 0, - "bolded": false, - "grayed": false - } - ], - "indent": 0, - "bolded": false, - "grayed": false - }, - { - "text": "N1", - "kind": "module", - "kindModifiers": "", - "spans": [ - { - "start": 49, - "length": 248 - } - ], - "childItems": [ - { - "text": "f1", - "kind": "function", - "kindModifiers": "export", - "spans": [ - { - "start": 116, - "length": 79 - } - ], - "childItems": [], - "indent": 0, - "bolded": false, - "grayed": false - }, - { - "text": "f2", - "kind": "function", - "kindModifiers": "", - "spans": [ - { - "start": 205, - "length": 90 - } - ], - "childItems": [], - "indent": 0, - "bolded": false, - "grayed": false - }, - { - "text": "v3", - "kind": "var", - "kindModifiers": "", - "spans": [ - { - "start": 72, - "length": 7 - } - ], - "childItems": [], - "indent": 0, - "bolded": false, - "grayed": false - }, - { - "text": "v4", - "kind": "var", - "kindModifiers": "export", - "spans": [ - { - "start": 96, - "length": 9 - } - ], - "childItems": [], - "indent": 0, - "bolded": false, - "grayed": false - } - ], - "indent": 1, - "bolded": false, - "grayed": false - }, - { - "text": "f1", - "kind": "function", - "kindModifiers": "export", - "spans": [ - { - "start": 116, - "length": 79 - } - ], - "childItems": [ - { - "text": "inner1", - "kind": "function", - "kindModifiers": "", - "spans": [ - { - "start": 170, - "length": 19 - } - ], - "childItems": [], - "indent": 0, - "bolded": false, - "grayed": false - } - ], - "indent": 2, - "bolded": false, - "grayed": false - }, - { - "text": "f2", - "kind": "function", - "kindModifiers": "", - "spans": [ - { - "start": 205, - "length": 90 - } - ], - "childItems": [ - { - "text": "inner2", - "kind": "function", - "kindModifiers": "", - "spans": [ - { - "start": 229, - "length": 60 - } - ], - "childItems": [], - "indent": 0, - "bolded": false, - "grayed": false - } - ], - "indent": 2, - "bolded": false, - "grayed": false - }, - { - "text": "inner2", - "kind": "function", - "kindModifiers": "", - "spans": [ - { - "start": 229, - "length": 60 - } - ], - "childItems": [ - { - "text": "inner3", - "kind": "function", - "kindModifiers": "", - "spans": [ - { - "start": 260, - "length": 19 - } - ], - "childItems": [], - "indent": 0, - "bolded": false, - "grayed": false - } - ], - "indent": 3, - "bolded": false, - "grayed": false - } -]; \ No newline at end of file From 71f604a8b22ad6047ed253b59a0e4c3d324d7e19 Mon Sep 17 00:00:00 2001 From: Bill Ticehurst Date: Sun, 13 Mar 2016 17:51:34 -0700 Subject: [PATCH 5/9] Added accessors, imports, and default export --- src/services/navigationBar.ts | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index d6cc473ef6328..6ca0cb084c6ea 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -81,7 +81,32 @@ namespace ts.NavigationBar { return getNavBarItem(declarationNameToString(methodDecl.name), ScriptElementKind.memberFunctionElement, [getNodeSpan(node)]); - // TODO: Class properties/getters/setters, export defaults, import/export identifiers + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + const accessor = node as GetAccessorDeclaration | SetAccessorDeclaration; + let accessorName: string; + if (accessor.name && getFullWidth(accessor.name) > 0) { + accessorName = declarationNameToString(accessor.name); + } + else { + accessorName = ""; + } + return getNavBarItem(accessorName, ScriptElementKind.memberGetAccessorElement, [getNodeSpan(node)]); + case SyntaxKind.ExportAssignment: + 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; + } + 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) + // TODO: Should export specifiers add a navbar item? + // They are often just references to items defined elsewhere in the file (unless renamed, or re-exported) + // case SyntaxKind.ExportSpecifier: // e.g. 'a' or 'b' in: export {a, foo as b} + const decl = node as (ImportSpecifier | ImportClause | NamespaceImport | ExportSpecifier); + const declName = declarationNameToString(decl.name); + return getNavBarItem(declName, ScriptElementKind.constElement, [getNodeSpan(node)]); default: return undefined; } @@ -96,6 +121,8 @@ namespace ts.NavigationBar { case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: case SyntaxKind.ArrowFunction: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: return true; default: return false; From 5c10301be5d764c1a8fe5f1fdaa33bc4ae1093d4 Mon Sep 17 00:00:00 2001 From: Bill Ticehurst Date: Mon, 14 Mar 2016 09:50:53 -0700 Subject: [PATCH 6/9] Hardened code and added export specifiers --- src/services/navigationBar.ts | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index 6ca0cb084c6ea..a8783d69f6e7f 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -63,14 +63,18 @@ namespace ts.NavigationBar { case SyntaxKind.FunctionDeclaration: case SyntaxKind.ClassDeclaration: case SyntaxKind.Constructor: - // TODO: Check behavior with names that are symbols, indexers, etc. + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: const name = node.kind === SyntaxKind.Constructor ? "constructor" : declarationNameToString((node as (Declaration)).name); const elementKind = node.kind === SyntaxKind.VariableDeclaration ? ScriptElementKind.variableElement : node.kind === SyntaxKind.FunctionDeclaration ? ScriptElementKind.functionElement : - node.kind === SyntaxKind.ClassDeclaration ? ScriptElementKind.classElement : "constructor"; + node.kind === SyntaxKind.ClassDeclaration ? ScriptElementKind.classElement : + node.kind === SyntaxKind.GetAccessor ? ScriptElementKind.memberGetAccessorElement : + node.kind === SyntaxKind.SetAccessor ? ScriptElementKind.memberSetAccessorElement : + "constructor"; return getNavBarItem(name, elementKind, [getNodeSpan(node)]); case SyntaxKind.FunctionExpression: @@ -78,20 +82,12 @@ namespace ts.NavigationBar { return getDefineModuleItem(node) || getFunctionExpressionItem(node); case SyntaxKind.MethodDeclaration: const methodDecl = node as MethodDeclaration; + if (!methodDecl.name) { + return undefined; + } return getNavBarItem(declarationNameToString(methodDecl.name), ScriptElementKind.memberFunctionElement, [getNodeSpan(node)]); - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - const accessor = node as GetAccessorDeclaration | SetAccessorDeclaration; - let accessorName: string; - if (accessor.name && getFullWidth(accessor.name) > 0) { - accessorName = declarationNameToString(accessor.name); - } - else { - accessorName = ""; - } - return getNavBarItem(accessorName, ScriptElementKind.memberGetAccessorElement, [getNodeSpan(node)]); case SyntaxKind.ExportAssignment: return getNavBarItem("default", ScriptElementKind.variableElement, [getNodeSpan(node)]); case SyntaxKind.ImportClause: // e.g. 'def' in: import def from 'mod' (in ImportDeclaration) @@ -101,10 +97,17 @@ namespace ts.NavigationBar { } 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) - // TODO: Should export specifiers add a navbar item? - // They are often just references to items defined elsewhere in the file (unless renamed, or re-exported) - // case SyntaxKind.ExportSpecifier: // e.g. 'a' or 'b' in: export {a, foo as b} + 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: From f85911dbc6e069817697824bd2c74970c53d6f83 Mon Sep 17 00:00:00 2001 From: Bill Ticehurst Date: Mon, 14 Mar 2016 12:46:11 -0700 Subject: [PATCH 7/9] Refactoring and class expressions --- src/services/navigationBar.ts | 398 +++++++++++++++++----------------- 1 file changed, 195 insertions(+), 203 deletions(-) diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index a8783d69f6e7f..1061cc99d6b63 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -3,208 +3,6 @@ /* @internal */ namespace ts.NavigationBar { - export function getJsNavigationBarItems(sourceFile: SourceFile, compilerOptions: CompilerOptions): NavigationBarItem[] { - const anonFnText = ""; - let indent = 0; - - let rootName = isExternalModule(sourceFile) ? - "\"" + escapeString(getBaseFileName(removeFileExtension(normalizePath(sourceFile.fileName)))) + "\"" - : ""; - - let 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); - - function visitNode(node: Node) { - const newItem = createNavBarItem(node); - - if (newItem) { - topItem.childItems.push(newItem); - } - - // Add a level if traversing into a container - if (isNavBarContainer(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 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 )) { - return undefined; - } - // Fall through - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.Constructor: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - const name = node.kind === SyntaxKind.Constructor ? - "constructor" : declarationNameToString((node as (Declaration)).name); - - const elementKind = - node.kind === SyntaxKind.VariableDeclaration ? ScriptElementKind.variableElement : - node.kind === SyntaxKind.FunctionDeclaration ? ScriptElementKind.functionElement : - node.kind === SyntaxKind.ClassDeclaration ? ScriptElementKind.classElement : - node.kind === SyntaxKind.GetAccessor ? ScriptElementKind.memberGetAccessorElement : - node.kind === SyntaxKind.SetAccessor ? ScriptElementKind.memberSetAccessorElement : - "constructor"; - - return getNavBarItem(name, elementKind, [getNodeSpan(node)]); - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - return getDefineModuleItem(node) || getFunctionExpressionItem(node); - case SyntaxKind.MethodDeclaration: - const methodDecl = node as MethodDeclaration; - if (!methodDecl.name) { - return undefined; - } - return getNavBarItem(declarationNameToString(methodDecl.name), - ScriptElementKind.memberFunctionElement, - [getNodeSpan(node)]); - case SyntaxKind.ExportAssignment: - 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; - } - 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; - } - } - - function isNavBarContainer(node: Node) { - // We only want to add a new level when going into a function or a class - switch (node.kind) { - case SyntaxKind.ClassDeclaration: - case SyntaxKind.Constructor: - case SyntaxKind.MethodDeclaration: - case SyntaxKind.FunctionDeclaration: - case SyntaxKind.FunctionExpression: - case SyntaxKind.ArrowFunction: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - return true; - default: - return false; - } - } - - function getNavBarItem(text: string, kind: string, spans: TextSpan[], kindModifiers = ScriptElementKindModifier.none): NavigationBarItem { - return { - text, kind, kindModifiers, spans, childItems: [], indent, bolded: false, grayed: false - } - } - - 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)]); - } - - function getFunctionExpressionItem(node: Node): NavigationBarItem { - if (node.kind !== SyntaxKind.FunctionExpression && node.kind !== SyntaxKind.ArrowFunction) { - return undefined; - } - - const fnExpr = node as FunctionExpression | ArrowFunction; - let fnName: string; - if (fnExpr.name && getFullWidth(fnExpr.name) > 0) { - // The function 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.FirstAssignment) { - fnName = (fnExpr.parent as BinaryExpression).left.getText(); - if (fnName.length > 20) { - fnName = fnName.substring(0,20) + "..."; - } - } - // 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 = anonFnText; - } - } - return getNavBarItem(fnName, ScriptElementKind.functionElement, [getNodeSpan(node)]); - } - - function getNodeSpan(node: Node) { - return node.kind === SyntaxKind.SourceFile - ? createTextSpanFromBounds(node.getFullStart(), node.getEnd()) - : createTextSpanFromBounds(node.getStart(), node.getEnd()); - } - - return sourceFileItem.childItems; - } - 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. @@ -743,4 +541,198 @@ namespace ts.NavigationBar { return getTextOfNodeFromSourceText(sourceFile.text, node); } } -} \ No newline at end of file + + export function getJsNavigationBarItems(sourceFile: SourceFile, compilerOptions: CompilerOptions): NavigationBarItem[] { + const anonFnText = ""; + const anonClassText = ""; + let indent = 0; + + let rootName = isExternalModule(sourceFile) ? + "\"" + escapeString(getBaseFileName(removeFileExtension(normalizePath(sourceFile.fileName)))) + "\"" + : ""; + + let 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); + + function visitNode(node: Node) { + const newItem = createNavBarItem(node); + + if (newItem) { + topItem.childItems.push(newItem); + } + + // Add a level if traversing into a container + if (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 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: + const name = node.flags && (node.flags & NodeFlags.Default) ? "default" : + node.kind === SyntaxKind.Constructor ? "constructor" : + declarationNameToString((node as (Declaration)).name); + + const elementKind = + node.kind === SyntaxKind.VariableDeclaration ? ScriptElementKind.variableElement : + node.kind === SyntaxKind.FunctionDeclaration ? ScriptElementKind.functionElement : + node.kind === SyntaxKind.ClassDeclaration ? ScriptElementKind.classElement : + node.kind === SyntaxKind.Constructor ? ScriptElementKind.constructorImplementationElement : + node.kind === SyntaxKind.GetAccessor ? ScriptElementKind.memberGetAccessorElement : + node.kind === SyntaxKind.SetAccessor ? ScriptElementKind.memberSetAccessorElement : + "unknown"; + + return getNavBarItem(name, elementKind, [getNodeSpan(node)]); + case SyntaxKind.FunctionExpression: + case SyntaxKind.ArrowFunction: + case SyntaxKind.ClassExpression: + return getDefineModuleItem(node) || getFunctionOrClassExpressionItem(node); + case SyntaxKind.MethodDeclaration: + const methodDecl = node as MethodDeclaration; + if (!methodDecl.name) { + return undefined; + } + return getNavBarItem(declarationNameToString(methodDecl.name), + ScriptElementKind.memberFunctionElement, + [getNodeSpan(node)]); + case SyntaxKind.ExportAssignment: + 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; + } + } + + function getNavBarItem(text: string, kind: string, spans: TextSpan[], kindModifiers = ScriptElementKindModifier.none): NavigationBarItem { + return { + text, kind, kindModifiers, spans, childItems: [], indent, bolded: false, grayed: false + } + } + + 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)]); + } + + 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.FirstAssignment) { + fnName = (fnExpr.parent as BinaryExpression).left.getText(); + if (fnName.length > 20) { + fnName = fnName.substring(0,20) + "..."; + } + } + // 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)]); + } + + function getNodeSpan(node: Node) { + return node.kind === SyntaxKind.SourceFile + ? createTextSpanFromBounds(node.getFullStart(), node.getEnd()) + : createTextSpanFromBounds(node.getStart(), node.getEnd()); + } + + return sourceFileItem.childItems; + } +} From fe4efe8cdf8314e59f8f6d00547c06924a33c835 Mon Sep 17 00:00:00 2001 From: Bill Ticehurst Date: Mon, 14 Mar 2016 12:56:48 -0700 Subject: [PATCH 8/9] Safety guard and couple of comments --- src/services/navigationBar.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index 1061cc99d6b63..05dfe1045c72d 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -566,7 +566,7 @@ namespace ts.NavigationBar { } // Add a level if traversing into a container - if (isFunctionLike(node) || isClassLike(node)) { + if (newItem && (isFunctionLike(node) || isClassLike(node))) { const lastTop = topItem; indent++; topItem = newItem; @@ -606,6 +606,7 @@ namespace ts.NavigationBar { 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 set const name = node.flags && (node.flags & NodeFlags.Default) ? "default" : node.kind === SyntaxKind.Constructor ? "constructor" : declarationNameToString((node as (Declaration)).name); @@ -633,6 +634,7 @@ namespace ts.NavigationBar { 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) { From 6a8fb3ed3d0cc831bfbe4cb47b7bc0a14cca8b7e Mon Sep 17 00:00:00 2001 From: Bill Ticehurst Date: Mon, 14 Mar 2016 16:54:20 -0700 Subject: [PATCH 9/9] Code review feedback --- src/services/navigationBar.ts | 42 ++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index 05dfe1045c72d..7d104f8681c6d 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -606,30 +606,17 @@ namespace ts.NavigationBar { 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 set - const name = node.flags && (node.flags & NodeFlags.Default) ? "default" : + // "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); - - const elementKind = - node.kind === SyntaxKind.VariableDeclaration ? ScriptElementKind.variableElement : - node.kind === SyntaxKind.FunctionDeclaration ? ScriptElementKind.functionElement : - node.kind === SyntaxKind.ClassDeclaration ? ScriptElementKind.classElement : - node.kind === SyntaxKind.Constructor ? ScriptElementKind.constructorImplementationElement : - node.kind === SyntaxKind.GetAccessor ? ScriptElementKind.memberGetAccessorElement : - node.kind === SyntaxKind.SetAccessor ? ScriptElementKind.memberSetAccessorElement : - "unknown"; - - return getNavBarItem(name, elementKind, [getNodeSpan(node)]); + 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; - if (!methodDecl.name) { - return undefined; - } return getNavBarItem(declarationNameToString(methodDecl.name), ScriptElementKind.memberFunctionElement, [getNodeSpan(node)]); @@ -710,10 +697,10 @@ namespace ts.NavigationBar { } // 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.FirstAssignment) { + (fnExpr.parent as BinaryExpression).operatorToken.kind === SyntaxKind.EqualsToken) { fnName = (fnExpr.parent as BinaryExpression).left.getText(); if (fnName.length > 20) { - fnName = fnName.substring(0,20) + "..."; + fnName = fnName.substring(0, 17) + "..."; } } // See if it is a property assignment, and if so use the property name @@ -735,6 +722,25 @@ namespace ts.NavigationBar { : createTextSpanFromBounds(node.getStart(), node.getEnd()); } + 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"; + } + } + return sourceFileItem.childItems; } }