3
3
/* @internal */
4
4
namespace ts . NavigationBar {
5
5
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
+
6
12
// If the source file has any child items, then it included in the tree
7
13
// and takes lexical ownership of all other top-level items.
8
14
let hasGlobalNode = false ;
@@ -130,7 +136,7 @@ namespace ts.NavigationBar {
130
136
131
137
return topLevelNodes ;
132
138
}
133
-
139
+
134
140
function sortNodes ( nodes : Node [ ] ) : Node [ ] {
135
141
return nodes . slice ( 0 ) . sort ( ( n1 : Declaration , n2 : Declaration ) => {
136
142
if ( n1 . name && n2 . name ) {
@@ -147,7 +153,7 @@ namespace ts.NavigationBar {
147
153
}
148
154
} ) ;
149
155
}
150
-
156
+
151
157
function addTopLevelNodes ( nodes : Node [ ] , topLevelNodes : Node [ ] ) : void {
152
158
nodes = sortNodes ( nodes ) ;
153
159
@@ -178,8 +184,8 @@ namespace ts.NavigationBar {
178
184
179
185
function isTopLevelFunctionDeclaration ( functionDeclaration : FunctionLikeDeclaration ) {
180
186
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.
183
189
if ( functionDeclaration . body && functionDeclaration . body . kind === SyntaxKind . Block ) {
184
190
// Proper function declarations can only have identifier names
185
191
if ( forEach ( ( < Block > functionDeclaration . body ) . statements ,
@@ -198,7 +204,7 @@ namespace ts.NavigationBar {
198
204
199
205
return false ;
200
206
}
201
-
207
+
202
208
function getItemsWorker ( nodes : Node [ ] , createItem : ( n : Node ) => ts . NavigationBarItem ) : ts . NavigationBarItem [ ] {
203
209
let items : ts . NavigationBarItem [ ] = [ ] ;
204
210
@@ -395,19 +401,19 @@ namespace ts.NavigationBar {
395
401
let result : string [ ] = [ ] ;
396
402
397
403
result . push ( moduleDeclaration . name . text ) ;
398
-
404
+
399
405
while ( moduleDeclaration . body && moduleDeclaration . body . kind === SyntaxKind . ModuleDeclaration ) {
400
406
moduleDeclaration = < ModuleDeclaration > moduleDeclaration . body ;
401
407
402
408
result . push ( moduleDeclaration . name . text ) ;
403
- }
409
+ }
404
410
405
411
return result . join ( "." ) ;
406
412
}
407
413
408
414
function createModuleItem ( node : ModuleDeclaration ) : NavigationBarItem {
409
415
let moduleName = getModuleName ( node ) ;
410
-
416
+
411
417
let childItems = getItemsWorker ( getChildNodes ( ( < Block > getInnermostModule ( node ) . body ) . statements ) , createChildItem ) ;
412
418
413
419
return getNavigationBarItem ( moduleName ,
@@ -534,4 +540,206 @@ namespace ts.NavigationBar {
534
540
return getTextOfNodeFromSourceText ( sourceFile . text , node ) ;
535
541
}
536
542
}
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