diff --git a/snapshots/input/syntax/src/overload.d.ts b/snapshots/input/syntax/src/overload.d.ts index f263ed41..901135ed 100644 --- a/snapshots/input/syntax/src/overload.d.ts +++ b/snapshots/input/syntax/src/overload.d.ts @@ -1,3 +1,5 @@ +// format-options: showDocs + export interface Overloader { onLiteral(param: 'a'): void onLiteral(param: 'b'): void diff --git a/snapshots/output/syntax/src/overload.d.ts b/snapshots/output/syntax/src/overload.d.ts index a9749c98..bd0bc9c6 100644 --- a/snapshots/output/syntax/src/overload.d.ts +++ b/snapshots/output/syntax/src/overload.d.ts @@ -1,11 +1,31 @@ // language TypeScript // < definition syntax 1.0.0 src/`overload.d.ts`/ +//documentation ```ts +// > module "overload.d.ts" +// > ``` + +// format-options: showDocs export interface Overloader { // ^^^^^^^^^^ definition syntax 1.0.0 src/`overload.d.ts`/Overloader# +// documentation ```ts +// > interface Overloader +// > ``` onLiteral(param: 'a'): void //^^^^^^^^^ definition syntax 1.0.0 src/`overload.d.ts`/Overloader#onLiteral(). +//documentation ```ts +// > (method) onLiteral(param: "a"): void +// > ``` +//documentation ```ts +// > (method) onLiteral(param: "b"): void +// > ``` // ^^^^^ definition syntax 1.0.0 src/`overload.d.ts`/Overloader#onLiteral().(param) +// documentation ```ts +// > (parameter) param: "a" +// > ``` +// documentation ```ts +// > (parameter) param: "b" +// > ``` onLiteral(param: 'b'): void //^^^^^^^^^ definition syntax 1.0.0 src/`overload.d.ts`/Overloader#onLiteral(). // ^^^^^ definition syntax 1.0.0 src/`overload.d.ts`/Overloader#onLiteral().(param) diff --git a/src/FileIndexer.ts b/src/FileIndexer.ts index 9b9f200c..9a7eac1b 100644 --- a/src/FileIndexer.ts +++ b/src/FileIndexer.ts @@ -25,6 +25,8 @@ export class FileIndexer { private localCounter = new Counter() private propertyCounters: Map = new Map() private localSymbolTable: Map = new Map() + private symbolInformation: Map = + new Map() private workingDirectoryRegExp: RegExp constructor( public readonly checker: ts.TypeChecker, @@ -78,7 +80,7 @@ export class FileIndexer { ) const moduleName = this.sourceFile.moduleName || path.basename(this.sourceFile.fileName) - this.document.symbols.push( + this.pushSymbolInformation( new scip.scip.SymbolInformation({ symbol: symbol.value, documentation: ['```ts\nmodule "' + moduleName + '"\n```'], @@ -344,7 +346,9 @@ export class FileIndexer { ): void { const documentation = [ '```ts\n' + - this.hideWorkingDirectory(this.signatureForDocumentation(node, sym)) + + this.hideWorkingDirectory( + this.signatureForDocumentation(node, sym, declaration) + ) + '\n```', ] const docstring = sym.getDocumentationComment(this.checker) @@ -352,7 +356,7 @@ export class FileIndexer { documentation.push(ts.displayPartsToString(docstring)) } - this.document.symbols.push( + this.pushSymbolInformation( new scip.scip.SymbolInformation({ symbol: symbol.value, documentation, @@ -362,6 +366,37 @@ export class FileIndexer { ) } + private pushSymbolInformation(info: scip.scip.SymbolInformation): void { + const existing = this.symbolInformation.get(info.symbol) + if (!existing) { + this.symbolInformation.set(info.symbol, info) + this.document.symbols.push(info) + return + } + + // Overload declarations share one SCIP symbol but can contribute distinct + // signatures and relationships. SCIP permits only one SymbolInformation + // entry per symbol, so merge that metadata into the first entry. + for (const documentation of info.documentation) { + if (!existing.documentation.includes(documentation)) { + existing.documentation.push(documentation) + } + } + for (const relationship of info.relationships) { + const previous = existing.relationships.find( + candidate => candidate.symbol === relationship.symbol + ) + if (previous) { + previous.is_definition ||= relationship.is_definition + previous.is_reference ||= relationship.is_reference + previous.is_implementation ||= relationship.is_implementation + previous.is_type_definition ||= relationship.is_type_definition + } else { + existing.relationships.push(relationship) + } + } + } + private pushOccurrence(occurrence: scip.scip.Occurrence): void { const lastOccurrence = this.document.occurrences.at(-1) if (lastOccurrence) { @@ -634,28 +669,28 @@ export class FileIndexer { return undefined } - private signatureForDocumentation(node: ts.Node, sym: ts.Symbol): string { + private signatureForDocumentation( + node: ts.Node, + sym: ts.Symbol, + declaration: ts.Node + ): string { const kind = scriptElementKind(node, sym) const type = (): string => this.checker.typeToString(this.checker.getTypeAtLocation(node)) const asSignatureDeclaration = ( node: ts.Node, - sym: ts.Symbol + declaration: ts.Node ): ts.SignatureDeclaration | undefined => { - const declaration = sym.declarations?.[0] - if (!declaration) { - return undefined - } return ts.isConstructorDeclaration(node) ? node - : ts.isFunctionDeclaration(declaration) + : ts.isFunctionDeclaration(declaration) || + ts.isMethodDeclaration(declaration) || + ts.isMethodSignature(declaration) ? declaration - : ts.isMethodDeclaration(declaration) - ? declaration - : undefined + : undefined } const signature = (): string | undefined => { - const signatureDeclaration = asSignatureDeclaration(node, sym) + const signatureDeclaration = asSignatureDeclaration(node, declaration) if (!signatureDeclaration) { return undefined } diff --git a/src/main.test.ts b/src/main.test.ts index d0d9a887..6114e291 100644 --- a/src/main.test.ts +++ b/src/main.test.ts @@ -75,6 +75,19 @@ for (const snapshotDirectory of snapshotDirectories) { throw new Error('empty LSIF index') } for (const document of index.documents) { + const symbols = new Set() + const duplicateSymbols: string[] = [] + for (const symbol of document.symbols) { + if (symbols.has(symbol.symbol)) { + duplicateSymbols.push(symbol.symbol) + } + symbols.add(symbol.symbol) + } + assert.equal( + duplicateSymbols, + [], + `${document.relative_path} should not contain duplicate SymbolInformation` + ) assert.ok( document.language, `${document.relative_path} should have a SCIP document language`