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