From b174311e9862d61b45247c5552413ab15ce6d7ef Mon Sep 17 00:00:00 2001 From: Christoph Hegemann Date: Thu, 12 Nov 2026 09:26:45 +0000 Subject: [PATCH] feat: populate SymbolInformation.signature_documentation Signatures used to be rendered as a ```ts code block at the start of SymbolInformation.documentation. They now go in the structured signature_documentation field (language "typescript"), and documentation holds only docstrings, as scip.proto asks new indexers to do. Sourcegraph's hover renders signature_documentation as a code block before documentation, so keeping both would show the signature twice. Consumers that read only documentation will no longer see signatures. Amp-Thread-ID: https://ampcode.com/threads/T-01a1cefb-43ef-72b1-9eea-3323b0b1f550 Co-authored-by: Amp --- .../multi-project/packages/a/src/index.ts | 6 +-- snapshots/output/syntax/src/overload.d.ts | 21 ++++------ snapshots/output/syntax/src/surrogates.ts | 42 +++++++------------ src/FileIndexer.test.ts | 14 ++++--- src/FileIndexer.ts | 42 ++++++++++++------- src/SnapshotTesting.ts | 23 +++++++--- src/main.test.ts | 6 ++- 7 files changed, 80 insertions(+), 74 deletions(-) diff --git a/snapshots/output/multi-project/packages/a/src/index.ts b/snapshots/output/multi-project/packages/a/src/index.ts index f5ace936..68b2cf4d 100644 --- a/snapshots/output/multi-project/packages/a/src/index.ts +++ b/snapshots/output/multi-project/packages/a/src/index.ts @@ -1,16 +1,14 @@ // language TypeScript // < definition @example/a 1.0.0 src/`index.ts`/ -//documentation ```ts +//signature_documentation typescript // > module "index.ts" -// > ``` // format-options: showDocs export function a(): string { // ^ definition @example/a 1.0.0 src/`index.ts`/a(). -// documentation ```ts +// signature_documentation typescript // > function a(): string -// > ``` return '' } diff --git a/snapshots/output/syntax/src/overload.d.ts b/snapshots/output/syntax/src/overload.d.ts index 4f3b5adc..fb3bf1e9 100644 --- a/snapshots/output/syntax/src/overload.d.ts +++ b/snapshots/output/syntax/src/overload.d.ts @@ -1,25 +1,21 @@ // language TypeScript // < definition syntax 1.0.0 src/`overload.d.ts`/ -//documentation ```ts +//signature_documentation typescript // > module "overload.d.ts" -// > ``` // format-options: showDocs export interface Overloader { // ^^^^^^^^^^ definition syntax 1.0.0 src/`overload.d.ts`/Overloader# -// documentation ```ts +// signature_documentation typescript // > interface Overloader -// > ``` onLiteral(param: 'a'): void //^^^^^^^^^ definition syntax 1.0.0 src/`overload.d.ts`/Overloader#onLiteral(). -//documentation ```ts +//signature_documentation typescript // > (method) onLiteral(param: "a"): void -// > ``` // ^^^^^ definition syntax 1.0.0 src/`overload.d.ts`/Overloader#onLiteral().(param) -// documentation ```ts +// signature_documentation typescript // > (parameter) param: "a" -// > ``` 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) @@ -28,17 +24,14 @@ export interface Overloader { // Ordinary TypeScript names must not be rewritten as generated Svelte names. export type $$ComponentProps = string // ^^^^^^^^^^^^^^^^ definition syntax 1.0.0 src/`overload.d.ts`/$$ComponentProps# -// documentation ```ts +// signature_documentation typescript // > type $$ComponentProps -// > ``` export type Demo__SvelteComponent_ = number // ^^^^^^^^^^^^^^^^^^^^^^ definition syntax 1.0.0 src/`overload.d.ts`/Demo__SvelteComponent_# -// documentation ```ts +// signature_documentation typescript // > type Demo__SvelteComponent_ -// > ``` export type __sveltets_2_IsomorphicComponent = boolean // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ definition syntax 1.0.0 src/`overload.d.ts`/__sveltets_2_IsomorphicComponent# -// documentation ```ts +// signature_documentation typescript // > type __sveltets_2_IsomorphicComponent -// > ``` diff --git a/snapshots/output/syntax/src/surrogates.ts b/snapshots/output/syntax/src/surrogates.ts index dbd79488..74dbd4c0 100644 --- a/snapshots/output/syntax/src/surrogates.ts +++ b/snapshots/output/syntax/src/surrogates.ts @@ -1,80 +1,66 @@ // language TypeScript // < definition syntax 1.0.0 src/`surrogates.ts`/ -//documentation ```ts +//signature_documentation typescript // > module "surrogates.ts" -// > ``` // format-options: showDocs export const high = '\ud800' // ^^^^ definition syntax 1.0.0 src/`surrogates.ts`/high. -// documentation ```ts +// signature_documentation typescript // > var high: "�" -// > ``` export const low = '\udfff' // ^^^ definition syntax 1.0.0 src/`surrogates.ts`/low. -// documentation ```ts +// signature_documentation typescript // > var low: "�" -// > ``` export const pair = '\ud83d\ude00' // ^^^^ definition syntax 1.0.0 src/`surrogates.ts`/pair. -// documentation ```ts +// signature_documentation typescript // > var pair: "😀" -// > ``` export const mixed = '\ud800\ud83d\ude00\udfff' // ^^^^^ definition syntax 1.0.0 src/`surrogates.ts`/mixed. -// documentation ```ts +// signature_documentation typescript // > var mixed: "�😀�" -// > ``` export const adjacent = '\ud800\ud800\udc00\udc00' // ^^^^^^^^ definition syntax 1.0.0 src/`surrogates.ts`/adjacent. -// documentation ```ts +// signature_documentation typescript // > var adjacent: "�𐀀�" -// > ``` export const unicode = 'ASCII café 中文 😀' // ^^^^^^^ definition syntax 1.0.0 src/`surrogates.ts`/unicode. -// documentation ```ts +// signature_documentation typescript // > var unicode: "ASCII café 中文 😀" -// > ``` export const escaped = '\\ud800' // ^^^^^^^ definition syntax 1.0.0 src/`surrogates.ts`/escaped. -// documentation ```ts +// signature_documentation typescript // > var escaped: "\\ud800" -// > ``` export function C() {} // ^ definition syntax 1.0.0 src/`surrogates.ts`/C(). -// documentation ```ts +// signature_documentation typescript // > function C(): void -// > ``` const prototype = (C.prototype = { // ^^^^^^^^^ definition syntax 1.0.0 src/`surrogates.ts`/prototype. -// documentation ```ts +// signature_documentation typescript // > var prototype: { '\uD800': number; 'low\... -// > ``` // ^ reference syntax 1.0.0 src/`surrogates.ts`/C(). // ^^^^^^^^^ reference typescript 6.0.3 lib/`lib.es5.d.ts`/Function#prototype. '\ud800': 1, //^^^^^^^^ definition syntax 1.0.0 src/`surrogates.ts`/C().`�`. -//documentation ```ts +//signature_documentation typescript // > (property) '\ud800': number -// > ``` 'low\udfff': 2, //^^^^^^^^^^^ definition syntax 1.0.0 src/`surrogates.ts`/C().`low�`. -//documentation ```ts +//signature_documentation typescript // > (property) 'low\udfff': number -// > ``` '\ud83d\ude00': 3, //^^^^^^^^^^^^^^ definition syntax 1.0.0 src/`surrogates.ts`/C().`😀`. -//documentation ```ts +//signature_documentation typescript // > (property) '\ud83d\ude00': number -// > ``` '\\ud800': 4, //^^^^^^^^^ definition syntax 1.0.0 src/`surrogates.ts`/C().`\ud800`. -//documentation ```ts +//signature_documentation typescript // > (property) '\\ud800': number -// > ``` }) prototype['\ud800'] //^^^^^^^^ reference syntax 1.0.0 src/`surrogates.ts`/prototype. diff --git a/src/FileIndexer.test.ts b/src/FileIndexer.test.ts index 9da10829..ea8c7312 100644 --- a/src/FileIndexer.test.ts +++ b/src/FileIndexer.test.ts @@ -106,9 +106,11 @@ test('lone surrogates in documentation and symbols survive serialization', () => const decoded = fromBinary(IndexSchema, toBinary(IndexSchema, index)) .documents[0] assert.ok(equals(DocumentSchema, decoded, document)) - assert.equal(decoded.symbols[0].documentation, [ - '```ts\nmodule "module-�"\n```', - ]) + assert.equal(decoded.symbols[0].documentation, []) + assert.equal( + decoded.symbols[0].signatureDocumentation?.text, + 'module "module-�"' + ) const signatures = [ 'var high: "�"', @@ -121,8 +123,10 @@ test('lone surrogates in documentation and symbols survive serialization', () => ] for (const signature of signatures) { assert.ok( - decoded.symbols.some(info => - info.documentation.includes('```ts\n' + signature + '\n```') + decoded.symbols.some( + info => + info.signatureDocumentation?.language === 'typescript' && + info.signatureDocumentation.text === signature ), `missing signature: ${signature}` ) diff --git a/src/FileIndexer.ts b/src/FileIndexer.ts index 9491459d..9ef1b262 100644 --- a/src/FileIndexer.ts +++ b/src/FileIndexer.ts @@ -12,6 +12,8 @@ import { type Relationship, RelationshipSchema, Severity, + type Signature, + SignatureSchema, type SymbolInformation, SymbolInformation_Kind, SymbolInformationSchema, @@ -108,13 +110,12 @@ export class FileIndexer { ) const moduleName = this.sourceFile.moduleName || path.basename(this.sourceInfo.fileName) - const language = this.sourceInfo.language ?? 'ts' this.pushSymbolInformation( create(SymbolInformationSchema, { symbol: symbol.value, - documentation: [ - `\`\`\`${language}\nmodule "${moduleName}"\n\`\`\``.toWellFormed(), - ], + signatureDocumentation: signatureDocumentation( + `module "${moduleName}"` + ), kind: SymbolInformation_Kind.File, }) ) @@ -423,22 +424,19 @@ export class FileIndexer { declaration: ts.Node, symbol: ScipSymbol ): void { - const documentation = [ - '```ts\n' + - this.sanitizeDocumentation( - this.signatureForDocumentation(node, sym, declaration) - ) + - '\n```', - ] const docstring = sym.getDocumentationComment(this.checker) - if (docstring.length > 0) { - documentation.push(ts.displayPartsToString(docstring)) - } - this.pushSymbolInformation( create(SymbolInformationSchema, { symbol: symbol.value, - documentation: documentation.map(text => text.toWellFormed()), + documentation: + docstring.length > 0 + ? [ts.displayPartsToString(docstring).toWellFormed()] + : [], + signatureDocumentation: signatureDocumentation( + this.sanitizeDocumentation( + this.signatureForDocumentation(node, sym, declaration) + ) + ), relationships: this.relationships(declaration, symbol), kind: symbolKind(declaration, sym), }) @@ -459,6 +457,7 @@ export class FileIndexer { if (existing.documentation.length === 0) { existing.documentation = info.documentation } + existing.signatureDocumentation ??= info.signatureDocumentation for (const relationship of info.relationships) { const previous = existing.relationships.find( candidate => candidate.symbol === relationship.symbol @@ -1129,6 +1128,17 @@ function scriptElementKind( return ts.ScriptElementKind.unknown } +// Signatures use TypeScript syntax even for JavaScript and Svelte sources. +function signatureDocumentation(text: string): Signature | undefined { + if (text === '') { + return undefined + } + return create(SignatureSchema, { + language: 'typescript', + text: text.toWellFormed(), + }) +} + function symbolKind( declaration: ts.Node, sym: ts.Symbol diff --git a/src/SnapshotTesting.ts b/src/SnapshotTesting.ts index 20b496f5..d76fd145 100644 --- a/src/SnapshotTesting.ts +++ b/src/SnapshotTesting.ts @@ -135,19 +135,19 @@ export function formatSnapshot( prefix += ' '.repeat(range.start.character - 2) } - const pushOneDoc = (docs: string[], external: boolean): void => { + const pushOneDoc = (info: SymbolInformation, external: boolean): void => { if (!formatOptions.showDocs) { return } - for (const documentation of docs) { - for (const [idx, line] of documentation.split('\n').entries()) { + const pushLines = (label: string, lines: string[]): void => { + for (const [idx, line] of lines.entries()) { out.push(prefix) if (idx === 0) { if (external) { out.push('external ') } - out.push('documentation ') + out.push(label + ' ') } else { out.push(' > ') } @@ -157,6 +157,17 @@ export function formatSnapshot( } } } + + for (const documentation of info.documentation) { + pushLines('documentation', documentation.split('\n')) + } + const signature = info.signatureDocumentation + if (signature) { + pushLines('signature_documentation', [ + signature.language, + ...signature.text.split('\n'), + ]) + } } const pushOneRelationship = (relationships: Relationship[]): void => { @@ -189,13 +200,13 @@ export function formatSnapshot( const externalSymbol = externalSymbolTable.get(symbol) if (externalSymbol) { pushKind(externalSymbol.kind) - pushOneDoc(externalSymbol.documentation, true) + pushOneDoc(externalSymbol, true) pushOneRelationship(externalSymbol.relationships) } else { const info = symbolTable.get(symbol) if (info) { pushKind(info.kind) - pushOneDoc(info.documentation, false) + pushOneDoc(info, false) pushOneRelationship(info.relationships) } } diff --git a/src/main.test.ts b/src/main.test.ts index 9dec18f0..4a0076ee 100644 --- a/src/main.test.ts +++ b/src/main.test.ts @@ -57,7 +57,11 @@ function assertStableSvelteSymbols(index: Index): void { } } for (const symbol of document.symbols) { - const text = [symbol.symbol, ...symbol.documentation].join('\n') + const text = [ + symbol.symbol, + ...symbol.documentation, + symbol.signatureDocumentation?.text ?? '', + ].join('\n') if (generatedSvelteIdentifier.test(text)) { throw new Error(`generated Svelte documentation leaked: ${text}`) }