33/* @internal */
44namespace ts . NavigationBar {
55 export function getNavigationBarItems ( sourceFile : SourceFile , compilerOptions : CompilerOptions ) : ts . NavigationBarItem [ ] {
6+ // TODO: Handle JS files differently in 'navbar' calls for now, but ideally we should unify
7+ // the 'navbar' and 'navto' logic for TypeScript and JavaScript.
8+ if ( isSourceFileJavaScript ( sourceFile ) ) {
9+ return getJsNavigationBarItems ( sourceFile , compilerOptions ) ;
10+ }
11+
612 // If the source file has any child items, then it included in the tree
713 // and takes lexical ownership of all other top-level items.
814 let hasGlobalNode = false ;
@@ -130,7 +136,7 @@ namespace ts.NavigationBar {
130136
131137 return topLevelNodes ;
132138 }
133-
139+
134140 function sortNodes ( nodes : Node [ ] ) : Node [ ] {
135141 return nodes . slice ( 0 ) . sort ( ( n1 : Declaration , n2 : Declaration ) => {
136142 if ( n1 . name && n2 . name ) {
@@ -147,7 +153,7 @@ namespace ts.NavigationBar {
147153 }
148154 } ) ;
149155 }
150-
156+
151157 function addTopLevelNodes ( nodes : Node [ ] , topLevelNodes : Node [ ] ) : void {
152158 nodes = sortNodes ( nodes ) ;
153159
@@ -178,8 +184,8 @@ namespace ts.NavigationBar {
178184
179185 function isTopLevelFunctionDeclaration ( functionDeclaration : FunctionLikeDeclaration ) {
180186 if ( functionDeclaration . kind === SyntaxKind . FunctionDeclaration ) {
181- // A function declaration is 'top level' if it contains any function declarations
182- // within it.
187+ // A function declaration is 'top level' if it contains any function declarations
188+ // within it.
183189 if ( functionDeclaration . body && functionDeclaration . body . kind === SyntaxKind . Block ) {
184190 // Proper function declarations can only have identifier names
185191 if ( forEach ( ( < Block > functionDeclaration . body ) . statements ,
@@ -198,7 +204,7 @@ namespace ts.NavigationBar {
198204
199205 return false ;
200206 }
201-
207+
202208 function getItemsWorker ( nodes : Node [ ] , createItem : ( n : Node ) => ts . NavigationBarItem ) : ts . NavigationBarItem [ ] {
203209 let items : ts . NavigationBarItem [ ] = [ ] ;
204210
@@ -395,19 +401,19 @@ namespace ts.NavigationBar {
395401 let result : string [ ] = [ ] ;
396402
397403 result . push ( moduleDeclaration . name . text ) ;
398-
404+
399405 while ( moduleDeclaration . body && moduleDeclaration . body . kind === SyntaxKind . ModuleDeclaration ) {
400406 moduleDeclaration = < ModuleDeclaration > moduleDeclaration . body ;
401407
402408 result . push ( moduleDeclaration . name . text ) ;
403- }
409+ }
404410
405411 return result . join ( "." ) ;
406412 }
407413
408414 function createModuleItem ( node : ModuleDeclaration ) : NavigationBarItem {
409415 let moduleName = getModuleName ( node ) ;
410-
416+
411417 let childItems = getItemsWorker ( getChildNodes ( ( < Block > getInnermostModule ( node ) . body ) . statements ) , createChildItem ) ;
412418
413419 return getNavigationBarItem ( moduleName ,
@@ -534,4 +540,206 @@ namespace ts.NavigationBar {
534540 return getTextOfNodeFromSourceText ( sourceFile . text , node ) ;
535541 }
536542 }
537- }
543+
544+ export function getJsNavigationBarItems ( sourceFile : SourceFile , compilerOptions : CompilerOptions ) : NavigationBarItem [ ] {
545+ const anonFnText = "<function>" ;
546+ const anonClassText = "<class>" ;
547+ let indent = 0 ;
548+
549+ let rootName = isExternalModule ( sourceFile ) ?
550+ "\"" + escapeString ( getBaseFileName ( removeFileExtension ( normalizePath ( sourceFile . fileName ) ) ) ) + "\""
551+ : "<global>" ;
552+
553+ let sourceFileItem = getNavBarItem ( rootName , ScriptElementKind . moduleElement , [ getNodeSpan ( sourceFile ) ] ) ;
554+ let topItem = sourceFileItem ;
555+
556+ // Walk the whole file, because we want to also find function expressions - which may be in variable initializer,
557+ // call arguments, expressions, etc...
558+ forEachChild ( sourceFile , visitNode ) ;
559+
560+ function visitNode ( node : Node ) {
561+ const newItem = createNavBarItem ( node ) ;
562+
563+ if ( newItem ) {
564+ topItem . childItems . push ( newItem ) ;
565+ }
566+
567+ // Add a level if traversing into a container
568+ if ( newItem && ( isFunctionLike ( node ) || isClassLike ( node ) ) ) {
569+ const lastTop = topItem ;
570+ indent ++ ;
571+ topItem = newItem ;
572+ forEachChild ( node , visitNode ) ;
573+ topItem = lastTop ;
574+ indent -- ;
575+
576+ // If the last item added was an anonymous function expression, and it had no children, discard it.
577+ if ( newItem && newItem . text === anonFnText && newItem . childItems . length === 0 ) {
578+ topItem . childItems . pop ( ) ;
579+ }
580+ }
581+ else {
582+ forEachChild ( node , visitNode ) ;
583+ }
584+ }
585+
586+ function createNavBarItem ( node : Node ) : NavigationBarItem {
587+ switch ( node . kind ) {
588+ case SyntaxKind . VariableDeclaration :
589+ // Only add to the navbar if at the top-level of the file
590+ // Note: "const" and "let" are also SyntaxKind.VariableDeclarations
591+ if ( node . parent /*VariableDeclarationList*/ . parent /*VariableStatement*/
592+ . parent /*SourceFile*/ . kind !== SyntaxKind . SourceFile ) {
593+ return undefined ;
594+ }
595+ // If it is initialized with a function expression, handle it when we reach the function expression node
596+ const varDecl = node as VariableDeclaration ;
597+ if ( varDecl . initializer && ( varDecl . initializer . kind === SyntaxKind . FunctionExpression ||
598+ varDecl . initializer . kind === SyntaxKind . ArrowFunction ||
599+ varDecl . initializer . kind === SyntaxKind . ClassExpression ) ) {
600+ return undefined ;
601+ }
602+ // Fall through
603+ case SyntaxKind . FunctionDeclaration :
604+ case SyntaxKind . ClassDeclaration :
605+ case SyntaxKind . Constructor :
606+ case SyntaxKind . GetAccessor :
607+ case SyntaxKind . SetAccessor :
608+ // "export default function().." looks just like a regular function/class declaration, except with the 'default' flag
609+ const name = node . flags && ( node . flags & NodeFlags . Default ) && ! ( node as ( Declaration ) ) . name ? "default" :
610+ node . kind === SyntaxKind . Constructor ? "constructor" :
611+ declarationNameToString ( ( node as ( Declaration ) ) . name ) ;
612+ return getNavBarItem ( name , getScriptKindForElementKind ( node . kind ) , [ getNodeSpan ( node ) ] ) ;
613+ case SyntaxKind . FunctionExpression :
614+ case SyntaxKind . ArrowFunction :
615+ case SyntaxKind . ClassExpression :
616+ return getDefineModuleItem ( node ) || getFunctionOrClassExpressionItem ( node ) ;
617+ case SyntaxKind . MethodDeclaration :
618+ const methodDecl = node as MethodDeclaration ;
619+ return getNavBarItem ( declarationNameToString ( methodDecl . name ) ,
620+ ScriptElementKind . memberFunctionElement ,
621+ [ getNodeSpan ( node ) ] ) ;
622+ case SyntaxKind . ExportAssignment :
623+ // e.g. "export default <expr>"
624+ return getNavBarItem ( "default" , ScriptElementKind . variableElement , [ getNodeSpan ( node ) ] ) ;
625+ case SyntaxKind . ImportClause : // e.g. 'def' in: import def from 'mod' (in ImportDeclaration)
626+ if ( ! ( node as ImportClause ) . name ) {
627+ // No default import (this node is still a parent of named & namespace imports, which are handled below)
628+ return undefined ;
629+ }
630+ // fall through
631+ case SyntaxKind . ImportSpecifier : // e.g. 'id' in: import {id} from 'mod' (in NamedImports, in ImportClause)
632+ case SyntaxKind . NamespaceImport : // e.g. '* as ns' in: import * as ns from 'mod' (in ImportClause)
633+ case SyntaxKind . ExportSpecifier : // e.g. 'a' or 'b' in: export {a, foo as b} from 'mod'
634+ // Export specifiers are only interesting if they are reexports from another module, or renamed, else they are already globals
635+ if ( node . kind === SyntaxKind . ExportSpecifier ) {
636+ if ( ! ( node . parent . parent as ExportDeclaration ) . moduleSpecifier && ! ( node as ExportSpecifier ) . propertyName ) {
637+ return undefined ;
638+ }
639+ }
640+ const decl = node as ( ImportSpecifier | ImportClause | NamespaceImport | ExportSpecifier ) ;
641+ if ( ! decl . name ) {
642+ return undefined ;
643+ }
644+ const declName = declarationNameToString ( decl . name ) ;
645+ return getNavBarItem ( declName , ScriptElementKind . constElement , [ getNodeSpan ( node ) ] ) ;
646+ default :
647+ return undefined ;
648+ }
649+ }
650+
651+ function getNavBarItem ( text : string , kind : string , spans : TextSpan [ ] , kindModifiers = ScriptElementKindModifier . none ) : NavigationBarItem {
652+ return {
653+ text, kind, kindModifiers, spans, childItems : [ ] , indent, bolded : false , grayed : false
654+ }
655+ }
656+
657+ function getDefineModuleItem ( node : Node ) : NavigationBarItem {
658+ if ( node . kind !== SyntaxKind . FunctionExpression && node . kind !== SyntaxKind . ArrowFunction ) {
659+ return undefined ;
660+ }
661+
662+ // No match if this is not a call expression to an identifier named 'define'
663+ if ( node . parent . kind !== SyntaxKind . CallExpression ) {
664+ return undefined ;
665+ }
666+ const callExpr = node . parent as CallExpression ;
667+ if ( callExpr . expression . kind !== SyntaxKind . Identifier || callExpr . expression . getText ( ) !== 'define' ) {
668+ return undefined ;
669+ }
670+
671+ // Return a module of either the given text in the first argument, or of the source file path
672+ let defaultName = node . getSourceFile ( ) . fileName ;
673+ if ( callExpr . arguments [ 0 ] . kind === SyntaxKind . StringLiteral ) {
674+ defaultName = ( ( callExpr . arguments [ 0 ] ) as StringLiteral ) . text ;
675+ }
676+ return getNavBarItem ( defaultName , ScriptElementKind . moduleElement , [ getNodeSpan ( node . parent ) ] ) ;
677+ }
678+
679+ function getFunctionOrClassExpressionItem ( node : Node ) : NavigationBarItem {
680+ if ( node . kind !== SyntaxKind . FunctionExpression &&
681+ node . kind !== SyntaxKind . ArrowFunction &&
682+ node . kind !== SyntaxKind . ClassExpression ) {
683+ return undefined ;
684+ }
685+
686+ const fnExpr = node as FunctionExpression | ArrowFunction | ClassExpression ;
687+ let fnName : string ;
688+ if ( fnExpr . name && getFullWidth ( fnExpr . name ) > 0 ) {
689+ // The expression has an identifier, so use that as the name
690+ fnName = declarationNameToString ( fnExpr . name ) ;
691+ }
692+ else {
693+ // See if it is a var initializer. If so, use the var name.
694+ if ( fnExpr . parent . kind === SyntaxKind . VariableDeclaration ) {
695+ fnName = declarationNameToString ( ( fnExpr . parent as VariableDeclaration ) . name ) ;
696+ }
697+ // See if it is of the form "<expr> = function(){...}". If so, use the text from the left-hand side.
698+ else if ( fnExpr . parent . kind === SyntaxKind . BinaryExpression &&
699+ ( fnExpr . parent as BinaryExpression ) . operatorToken . kind === SyntaxKind . EqualsToken ) {
700+ fnName = ( fnExpr . parent as BinaryExpression ) . left . getText ( ) ;
701+ if ( fnName . length > 20 ) {
702+ fnName = fnName . substring ( 0 , 17 ) + "..." ;
703+ }
704+ }
705+ // See if it is a property assignment, and if so use the property name
706+ else if ( fnExpr . parent . kind === SyntaxKind . PropertyAssignment &&
707+ ( fnExpr . parent as PropertyAssignment ) . name ) {
708+ fnName = ( fnExpr . parent as PropertyAssignment ) . name . getText ( ) ;
709+ }
710+ else {
711+ fnName = node . kind === SyntaxKind . ClassExpression ? anonClassText : anonFnText ;
712+ }
713+ }
714+ const scriptKind = node . kind === SyntaxKind . ClassExpression ? ScriptElementKind . classElement : ScriptElementKind . functionElement ;
715+ return getNavBarItem ( fnName , scriptKind , [ getNodeSpan ( node ) ] ) ;
716+ }
717+
718+ function getNodeSpan ( node : Node ) {
719+ return node . kind === SyntaxKind . SourceFile
720+ ? createTextSpanFromBounds ( node . getFullStart ( ) , node . getEnd ( ) )
721+ : createTextSpanFromBounds ( node . getStart ( ) , node . getEnd ( ) ) ;
722+ }
723+
724+ function getScriptKindForElementKind ( kind : SyntaxKind ) {
725+ switch ( kind ) {
726+ case SyntaxKind . VariableDeclaration :
727+ return ScriptElementKind . variableElement ;
728+ case SyntaxKind . FunctionDeclaration :
729+ return ScriptElementKind . functionElement ;
730+ case SyntaxKind . ClassDeclaration :
731+ return ScriptElementKind . classElement ;
732+ case SyntaxKind . Constructor :
733+ return ScriptElementKind . constructorImplementationElement ;
734+ case SyntaxKind . GetAccessor :
735+ return ScriptElementKind . memberGetAccessorElement ;
736+ case SyntaxKind . SetAccessor :
737+ return ScriptElementKind . memberSetAccessorElement ;
738+ default :
739+ return "unknown" ;
740+ }
741+ }
742+
743+ return sourceFileItem . childItems ;
744+ }
745+ }
0 commit comments