diff --git a/adev/shared-docs/pipeline/api-gen/rendering/transforms/jsdoc-transforms.mts b/adev/shared-docs/pipeline/api-gen/rendering/transforms/jsdoc-transforms.mts index 98f7822a2d1..38fbe90b85d 100644 --- a/adev/shared-docs/pipeline/api-gen/rendering/transforms/jsdoc-transforms.mts +++ b/adev/shared-docs/pipeline/api-gen/rendering/transforms/jsdoc-transforms.mts @@ -213,10 +213,6 @@ function convertLinks(text: string) { } function parseAtLink(link: string): {label: string; url: string} | undefined { - // Because of microsoft/TypeScript/issues/59679 - // getTextOfJSDocComment introduces an extra space between the symbol and a trailing () - link = link.replace(/ \(\)$/, ''); - let [rawSymbol, description] = link.split(/\s(.+)/); if (rawSymbol.startsWith('#')) { rawSymbol = rawSymbol.substring(1); diff --git a/packages/compiler-cli/src/ngtsc/docs/src/jsdoc_extractor.ts b/packages/compiler-cli/src/ngtsc/docs/src/jsdoc_extractor.ts index 96f39e1d1c7..285f29a2a2f 100644 --- a/packages/compiler-cli/src/ngtsc/docs/src/jsdoc_extractor.ts +++ b/packages/compiler-cli/src/ngtsc/docs/src/jsdoc_extractor.ts @@ -24,13 +24,42 @@ export function extractJsDocTags(node: ts.HasJSDoc): JsDocTagEntry[] { return ts.getJSDocTags(escapedNode).map((t) => { return { name: t.tagName.getText(), - // In TS 5.9, ts.getTextOfJSDocComment still strips "http" from comments breaking any absolute links in @see blocks. - // eg: @see https://angular.dev - comment: unescapeAngularDecorators(ts.getTextOfJSDocComment(t.comment) ?? ''), + comment: unescapeAngularDecorators(getJSDocTagComment(t) ?? ''), }; }); } +/** + * Gets the comment text from a JSDoc tag + * This wrapper attemps to work around some TS bugs (microsoft/TypeScript#59679, microsoft/TypeScript#63027 etc). + */ +function getJSDocTagComment(tag: ts.JSDocTag): string | undefined { + const comment = tag.comment; + if (comment === undefined) return undefined; + + // If the comment is a string, it might be the result of the TS 5.9 bug where "http" is stripped. + if (typeof comment === 'string') { + if (comment.startsWith('://')) { + // Try to verify if we can recover "https" or "http" from the raw tag text. + const rawText = tag.getText(); + if (rawText.includes('https' + comment)) { + return 'https' + comment; + } + if (rawText.includes('http' + comment)) { + return 'http' + comment; + } + } + return comment; + } + + let text = ts.getTextOfJSDocComment(comment); + // getTextOfJSDocComment introduces an extra space between the symbol and a trailing () + if (text) { + text = text.replace(/ \(\)\}$/, '()}'); + } + return text; +} + /** * Gets the JsDoc description for a node. If the node does not have * a description, returns the empty string. diff --git a/packages/compiler-cli/test/ngtsc/doc_extraction/jsdoc_extraction_spec.ts b/packages/compiler-cli/test/ngtsc/doc_extraction/jsdoc_extraction_spec.ts index 28dca9b7bee..43a9c49a25e 100644 --- a/packages/compiler-cli/test/ngtsc/doc_extraction/jsdoc_extraction_spec.ts +++ b/packages/compiler-cli/test/ngtsc/doc_extraction/jsdoc_extraction_spec.ts @@ -7,12 +7,7 @@ */ import {DocEntry} from '../../../src/ngtsc/docs'; -import { - ClassEntry, - FunctionEntry, - FunctionSignatureMetadata, - MethodEntry, -} from '../../../src/ngtsc/docs/src/entities'; +import {ClassEntry, FunctionEntry, MethodEntry} from '../../../src/ngtsc/docs/src/entities'; import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing'; import {loadStandardTestFiles} from '../../../src/ngtsc/testing'; @@ -390,5 +385,50 @@ runInEachFileSystem(() => { comment: 'Use something else.', }); }); + + it('should handle a jsdoc with a url', () => { + env.write( + 'index.ts', + ` + /** + * + * @see https://www.youtube.com/watch?v=dQw4w9WgXcQ + */ + export class Foo {}; + `, + ); + + const docs: DocEntry[] = env.driveDocsExtraction('index.ts'); + expect(docs.length).toBe(1); + + const entry = docs[0] as ClassEntry; + expect(entry.jsdocTags.length).toBe(1); + expect(entry.jsdocTags[0]).toEqual({ + name: 'see', + comment: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ', + }); + }); + + it('should handle a jsdoc with a trailing ()', () => { + env.write( + 'index.ts', + ` + /** + * + * @see {@link entry()} + */ + export class Foo {}; + `, + ); + const docs: DocEntry[] = env.driveDocsExtraction('index.ts'); + expect(docs.length).toBe(1); + + const entry = docs[0] as ClassEntry; + expect(entry.jsdocTags.length).toBe(1); + expect(entry.jsdocTags[0]).toEqual({ + name: 'see', + comment: '{@link entry()}', + }); + }); }); });