@@ -31,14 +31,15 @@ export class FileIndexer {
31
31
public readonly input : Input ,
32
32
public readonly document : scip . scip . Document ,
33
33
public readonly globalSymbolTable : Map < ts . Node , ScipSymbol > ,
34
+ public readonly globalConstructorTable : Map < ts . ClassDeclaration , boolean > ,
34
35
public readonly packages : Packages ,
35
36
public readonly sourceFile : ts . SourceFile
36
37
) {
37
38
this . workingDirectoryRegExp = new RegExp ( options . cwd , 'g' )
38
39
}
39
40
public index ( ) : void {
40
41
// Uncomment below if you want to skip certain files for local development.
41
- // if (!this.sourceFile.fileName.includes('infer-relationship ')) {
42
+ // if (!this.sourceFile.fileName.includes('constructor ')) {
42
43
// return
43
44
// }
44
45
this . emitSourceFileOccurrence ( )
@@ -67,6 +68,7 @@ export class FileIndexer {
67
68
}
68
69
private visit ( node : ts . Node ) : void {
69
70
if (
71
+ ts . isConstructorDeclaration ( node ) ||
70
72
ts . isIdentifier ( node ) ||
71
73
ts . isPrivateIdentifier ( node ) ||
72
74
ts . isStringLiteralLike ( node )
@@ -76,6 +78,7 @@ export class FileIndexer {
76
78
this . visitSymbolOccurrence ( node , sym )
77
79
}
78
80
}
81
+
79
82
ts . forEachChild ( node , node => this . visit ( node ) )
80
83
}
81
84
@@ -84,7 +87,10 @@ export class FileIndexer {
84
87
//
85
88
// This code is directly based off src/services/goToDefinition.ts.
86
89
private getTSSymbolAtLocation ( node : ts . Node ) : ts . Symbol | undefined {
87
- const symbol = this . checker . getSymbolAtLocation ( node )
90
+ const rangeNode : ts . Node = ts . isConstructorDeclaration ( node )
91
+ ? node . getFirstToken ( ) ?? node
92
+ : node
93
+ const symbol = this . checker . getSymbolAtLocation ( rangeNode )
88
94
89
95
// If this is an alias, and the request came at the declaration location
90
96
// get the aliased symbol instead. This allows for goto def on an import e.g.
@@ -105,14 +111,33 @@ export class FileIndexer {
105
111
return symbol
106
112
}
107
113
114
+ private hasConstructor ( classDeclaration : ts . ClassDeclaration ) : boolean {
115
+ const cached = this . globalConstructorTable . get ( classDeclaration )
116
+ if ( cached !== undefined ) {
117
+ return cached
118
+ }
119
+
120
+ for ( const member of classDeclaration . members ) {
121
+ if ( ts . isConstructorDeclaration ( member ) ) {
122
+ this . globalConstructorTable . set ( classDeclaration , true )
123
+ return true
124
+ }
125
+ }
126
+
127
+ this . globalConstructorTable . set ( classDeclaration , false )
128
+ return false
129
+ }
130
+
108
131
private visitSymbolOccurrence ( node : ts . Node , sym : ts . Symbol ) : void {
109
132
const range = Range . fromNode ( node ) . toLsif ( )
110
133
let role = 0
111
134
const isDefinitionNode = isDefinition ( node )
112
135
if ( isDefinitionNode ) {
113
136
role |= scip . scip . SymbolRole . Definition
114
137
}
115
- const declarations = isDefinitionNode
138
+ const declarations = ts . isConstructorDeclaration ( node )
139
+ ? [ node ]
140
+ : isDefinitionNode
116
141
? // Don't emit ambiguous definition at definition-site. You can reproduce
117
142
// ambiguous results by triggering "Go to definition" in VS Code on `Conflict`
118
143
// in the example below:
@@ -123,7 +148,20 @@ export class FileIndexer {
123
148
[ node . parent ]
124
149
: sym ?. declarations || [ ]
125
150
for ( const declaration of declarations ) {
126
- const scipSymbol = this . scipSymbol ( declaration )
151
+ let scipSymbol = this . scipSymbol ( declaration )
152
+
153
+ if (
154
+ ( ( ts . isIdentifier ( node ) && ts . isNewExpression ( node . parent ) ) ||
155
+ ( ts . isPropertyAccessExpression ( node . parent ) &&
156
+ ts . isNewExpression ( node . parent . parent ) ) ) &&
157
+ ts . isClassDeclaration ( declaration ) &&
158
+ this . hasConstructor ( declaration )
159
+ ) {
160
+ scipSymbol = ScipSymbol . global (
161
+ scipSymbol ,
162
+ methodDescriptor ( '<constructor>' )
163
+ )
164
+ }
127
165
128
166
if ( scipSymbol . isEmpty ( ) ) {
129
167
// Skip empty symbols
@@ -474,17 +512,24 @@ export class FileIndexer {
474
512
const kind = scriptElementKind ( node , sym )
475
513
const type = ( ) : string =>
476
514
this . checker . typeToString ( this . checker . getTypeAtLocation ( node ) )
477
- const signature = ( ) : string | undefined => {
515
+ const asSignatureDeclaration = (
516
+ node : ts . Node ,
517
+ sym : ts . Symbol
518
+ ) : ts . SignatureDeclaration | undefined => {
478
519
const declaration = sym . declarations ?. [ 0 ]
479
520
if ( ! declaration ) {
480
521
return undefined
481
522
}
482
- const signatureDeclaration : ts . SignatureDeclaration | undefined =
483
- ts . isFunctionDeclaration ( declaration )
484
- ? declaration
485
- : ts . isMethodDeclaration ( declaration )
486
- ? declaration
487
- : undefined
523
+ return ts . isConstructorDeclaration ( node )
524
+ ? node
525
+ : ts . isFunctionDeclaration ( declaration )
526
+ ? declaration
527
+ : ts . isMethodDeclaration ( declaration )
528
+ ? declaration
529
+ : undefined
530
+ }
531
+ const signature = ( ) : string | undefined => {
532
+ const signatureDeclaration = asSignatureDeclaration ( node , sym )
488
533
if ( ! signatureDeclaration ) {
489
534
return undefined
490
535
}
@@ -508,6 +553,9 @@ export class FileIndexer {
508
553
return 'type ' + node . getText ( )
509
554
case ts . ScriptElementKind . classElement :
510
555
case ts . ScriptElementKind . localClassElement :
556
+ if ( ts . isConstructorDeclaration ( node ) ) {
557
+ return 'constructor' + ( signature ( ) || '' )
558
+ }
511
559
return 'class ' + node . getText ( )
512
560
case ts . ScriptElementKind . interfaceElement :
513
561
return 'interface ' + node . getText ( )
@@ -769,5 +817,7 @@ function declarationName(node: ts.Node): ts.Node | undefined {
769
817
* ^^^^^^^^^^^^^^^^^^^^^ node.parent
770
818
*/
771
819
function isDefinition ( node : ts . Node ) : boolean {
772
- return declarationName ( node . parent ) === node
820
+ return (
821
+ declarationName ( node . parent ) === node || ts . isConstructorDeclaration ( node )
822
+ )
773
823
}
0 commit comments