@@ -31,14 +31,15 @@ export class FileIndexer {
3131 public readonly input : Input ,
3232 public readonly document : scip . scip . Document ,
3333 public readonly globalSymbolTable : Map < ts . Node , ScipSymbol > ,
34+ public readonly globalConstructorTable : Map < ts . ClassDeclaration , boolean > ,
3435 public readonly packages : Packages ,
3536 public readonly sourceFile : ts . SourceFile
3637 ) {
3738 this . workingDirectoryRegExp = new RegExp ( options . cwd , 'g' )
3839 }
3940 public index ( ) : void {
4041 // 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 ')) {
4243 // return
4344 // }
4445 this . emitSourceFileOccurrence ( )
@@ -67,6 +68,7 @@ export class FileIndexer {
6768 }
6869 private visit ( node : ts . Node ) : void {
6970 if (
71+ ts . isConstructorDeclaration ( node ) ||
7072 ts . isIdentifier ( node ) ||
7173 ts . isPrivateIdentifier ( node ) ||
7274 ts . isStringLiteralLike ( node )
@@ -76,6 +78,7 @@ export class FileIndexer {
7678 this . visitSymbolOccurrence ( node , sym )
7779 }
7880 }
81+
7982 ts . forEachChild ( node , node => this . visit ( node ) )
8083 }
8184
@@ -84,7 +87,10 @@ export class FileIndexer {
8487 //
8588 // This code is directly based off src/services/goToDefinition.ts.
8689 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 )
8894
8995 // If this is an alias, and the request came at the declaration location
9096 // get the aliased symbol instead. This allows for goto def on an import e.g.
@@ -105,14 +111,33 @@ export class FileIndexer {
105111 return symbol
106112 }
107113
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+
108131 private visitSymbolOccurrence ( node : ts . Node , sym : ts . Symbol ) : void {
109132 const range = Range . fromNode ( node ) . toLsif ( )
110133 let role = 0
111134 const isDefinitionNode = isDefinition ( node )
112135 if ( isDefinitionNode ) {
113136 role |= scip . scip . SymbolRole . Definition
114137 }
115- const declarations = isDefinitionNode
138+ const declarations = ts . isConstructorDeclaration ( node )
139+ ? [ node ]
140+ : isDefinitionNode
116141 ? // Don't emit ambiguous definition at definition-site. You can reproduce
117142 // ambiguous results by triggering "Go to definition" in VS Code on `Conflict`
118143 // in the example below:
@@ -123,7 +148,20 @@ export class FileIndexer {
123148 [ node . parent ]
124149 : sym ?. declarations || [ ]
125150 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+ }
127165
128166 if ( scipSymbol . isEmpty ( ) ) {
129167 // Skip empty symbols
@@ -474,17 +512,24 @@ export class FileIndexer {
474512 const kind = scriptElementKind ( node , sym )
475513 const type = ( ) : string =>
476514 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 => {
478519 const declaration = sym . declarations ?. [ 0 ]
479520 if ( ! declaration ) {
480521 return undefined
481522 }
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 )
488533 if ( ! signatureDeclaration ) {
489534 return undefined
490535 }
@@ -508,6 +553,9 @@ export class FileIndexer {
508553 return 'type ' + node . getText ( )
509554 case ts . ScriptElementKind . classElement :
510555 case ts . ScriptElementKind . localClassElement :
556+ if ( ts . isConstructorDeclaration ( node ) ) {
557+ return 'constructor' + ( signature ( ) || '' )
558+ }
511559 return 'class ' + node . getText ( )
512560 case ts . ScriptElementKind . interfaceElement :
513561 return 'interface ' + node . getText ( )
@@ -769,5 +817,7 @@ function declarationName(node: ts.Node): ts.Node | undefined {
769817 * ^^^^^^^^^^^^^^^^^^^^^ node.parent
770818 */
771819function isDefinition ( node : ts . Node ) : boolean {
772- return declarationName ( node . parent ) === node
820+ return (
821+ declarationName ( node . parent ) === node || ts . isConstructorDeclaration ( node )
822+ )
773823}
0 commit comments