Skip to content

Commit 8d3a89a

Browse files
AntonChesnokovkirjs
authored andcommitted
fix(compiler-cli): escape angular control flow in jsdoc
Escape @-prefixed template control flow constructs during doc extraction so JSDoc parsing keeps description text intact. Add regression coverage for @for snippets. (cherry picked from commit 5bfa027)
1 parent 4634b46 commit 8d3a89a

File tree

2 files changed

+47
-7
lines changed

2 files changed

+47
-7
lines changed

packages/compiler-cli/src/ngtsc/docs/src/jsdoc_extractor.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ import ts from 'typescript';
1111
import {JsDocTagEntry} from './entities';
1212

1313
/**
14-
* RegExp to match the `@` character follow by any Angular decorator, used to escape Angular
15-
* decorators in JsDoc blocks so that they're not parsed as JsDoc tags.
14+
* RegExp to match the `@` character follow by any Angular decorator and control flow syntax, used to escape Angular
15+
* decorators and control flow syntax in JsDoc blocks so that they're not parsed as JsDoc tags.
1616
*/
17-
const decoratorExpression =
18-
/@(?=(Injectable|Component|Directive|Pipe|NgModule|Input|Output|HostBinding|HostListener|Inject|Optional|Self|Host|SkipSelf|ViewChild|ViewChildren|ContentChild|ContentChildren))/g;
17+
const angularAtExpression =
18+
/@(?=(Injectable|Component|Directive|Pipe|NgModule|Input|Output|HostBinding|HostListener|Inject|Optional|Self|Host|SkipSelf|ViewChild|ViewChildren|ContentChild|ContentChildren|if|else|for|switch|case|default|empty|defer|placeholder|loading|error|let)\b)/g;
1919

2020
/** Gets the set of JsDoc tags applied to a node. */
2121
export function extractJsDocTags(node: ts.HasJSDoc): JsDocTagEntry[] {
@@ -79,12 +79,12 @@ function getEscapedNode(node: ts.HasJSDoc): ts.HasJSDoc {
7979
return file.statements.find((s) => ts.isClassDeclaration(s)) as ts.ClassDeclaration;
8080
}
8181

82-
/** Escape the `@` character for Angular decorators. */
82+
/** Escape the `@` character for Angular decorators and template control flow syntax. */
8383
function escapeAngularDecorators(comment: string): string {
84-
return comment.replace(decoratorExpression, '_NG_AT_');
84+
return comment.replace(angularAtExpression, '_NG_AT_');
8585
}
8686

87-
/** Unescapes the `@` character for Angular decorators. */
87+
/** Unescapes the `@` character for Angular decorators and template control flow syntax. */
8888
function unescapeAngularDecorators(comment: string): string {
8989
return comment.replace(/_NG_AT_/g, '@');
9090
}

packages/compiler-cli/test/ngtsc/doc_extraction/jsdoc_extraction_spec.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,5 +350,45 @@ runInEachFileSystem(() => {
350350
expect(entry.jsdocTags.length).toBe(1);
351351
expect(entry.jsdocTags[0].name).toBe('deprecated');
352352
});
353+
354+
it('should escape Angular control flow syntax', () => {
355+
env.write(
356+
'index.ts',
357+
`
358+
/**
359+
* Renders a list using Angular control flow.
360+
*
361+
* \`\`\`html
362+
* @for (item of items; track item) {
363+
* @if (item.visible) {
364+
* <div>{{item.label}}</div>
365+
* } @else {
366+
* <span>Hidden</span>
367+
* }
368+
* @empty {
369+
* <div>No items</div>
370+
* }
371+
* }
372+
* \`\`\`
373+
*
374+
* @deprecated Use something else.
375+
*/
376+
export class Example {}
377+
`,
378+
);
379+
380+
const docs: DocEntry[] = env.driveDocsExtraction('index.ts');
381+
expect(docs.length).toBe(1);
382+
383+
const entry = docs[0] as ClassEntry;
384+
expect(entry.description).toContain('@for (item of items; track item)');
385+
expect(entry.description).toContain('@if (item.visible)');
386+
expect(entry.description).toContain('@empty {');
387+
expect(entry.jsdocTags.length).toBe(1);
388+
expect(entry.jsdocTags[0]).toEqual({
389+
name: 'deprecated',
390+
comment: 'Use something else.',
391+
});
392+
});
353393
});
354394
});

0 commit comments

Comments
 (0)