Skip to content

Commit e0b1bb3

Browse files
jelbournpkozlowski-opensource
authored andcommitted
feat(compiler): extract doc info for JsDoc (#51733)
Based on top of #51713 This commit adds docs extraction for information provided in JsDoc comments, including descriptions and Jsdoc tags. PR Close #51733
1 parent a24ae99 commit e0b1bb3

File tree

7 files changed

+335
-8
lines changed

7 files changed

+335
-8
lines changed

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import {FunctionExtractor} from '@angular/compiler-cli/src/ngtsc/docs/src/function_extractor';
10+
import {extractJsDocDescription, extractJsDocTags, extractRawJsDoc} from '@angular/compiler-cli/src/ngtsc/docs/src/jsdoc_extractor';
1011
import ts from 'typescript';
1112

1213
import {Reference} from '../../imports';
@@ -22,7 +23,7 @@ type PropertyDeclarationLike = ts.PropertyDeclaration|ts.AccessorDeclaration;
2223
/** Extractor to pull info for API reference documentation for a TypeScript class. */
2324
class ClassExtractor {
2425
constructor(
25-
protected declaration: ClassDeclaration,
26+
protected declaration: ClassDeclaration&ts.ClassDeclaration,
2627
protected reference: Reference,
2728
protected typeChecker: ts.TypeChecker,
2829
) {}
@@ -32,7 +33,10 @@ class ClassExtractor {
3233
return {
3334
name: this.declaration.name!.text,
3435
entryType: EntryType.UndecoratedClass,
35-
members: this.extractAllClassMembers(this.declaration as ts.ClassDeclaration),
36+
members: this.extractAllClassMembers(this.declaration),
37+
description: extractJsDocDescription(this.declaration),
38+
jsdocTags: extractJsDocTags(this.declaration),
39+
rawComment: extractRawJsDoc(this.declaration),
3640
};
3741
}
3842

@@ -84,6 +88,8 @@ class ClassExtractor {
8488
type: extractResolvedTypeString(propertyDeclaration, this.typeChecker),
8589
memberType: MemberType.Property,
8690
memberTags: this.getMemberTags(propertyDeclaration),
91+
description: extractJsDocDescription(propertyDeclaration),
92+
jsdocTags: extractJsDocTags(propertyDeclaration),
8793
};
8894
}
8995

@@ -154,7 +160,7 @@ class ClassExtractor {
154160
/** Extractor to pull info for API reference documentation for an Angular directive. */
155161
class DirectiveExtractor extends ClassExtractor {
156162
constructor(
157-
declaration: ClassDeclaration,
163+
declaration: ClassDeclaration&ts.ClassDeclaration,
158164
reference: Reference,
159165
protected metadata: DirectiveMeta,
160166
checker: ts.TypeChecker,
@@ -207,7 +213,7 @@ class DirectiveExtractor extends ClassExtractor {
207213

208214
/** Extracts documentation info for a class, potentially including Angular-specific info. */
209215
export function extractClass(
210-
classDeclaration: ClassDeclaration, metadataReader: MetadataReader,
216+
classDeclaration: ClassDeclaration&ts.ClassDeclaration, metadataReader: MetadataReader,
211217
typeChecker: ts.TypeChecker): ClassEntry {
212218
const ref = new Reference(classDeclaration);
213219
const metadata = metadataReader.getDirectiveMetadata(ref);

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,33 @@
99
import ts from 'typescript';
1010

1111
import {ConstantEntry, EntryType} from './entities';
12+
import {extractJsDocDescription, extractJsDocTags, extractRawJsDoc,} from './jsdoc_extractor';
1213

1314
/** Extracts documentation entry for a constant. */
1415
export function extractConstant(
1516
declaration: ts.VariableDeclaration, typeChecker: ts.TypeChecker): ConstantEntry {
1617
// For constants specifically, we want to get the base type for any literal types.
17-
// For example, TypeScript by default extacts `const PI = 3.14` as PI having a type of the
18+
// For example, TypeScript by default extracts `const PI = 3.14` as PI having a type of the
1819
// literal `3.14`. We don't want this behavior for constants, since generally one wants the
1920
// _value_ of the constant to be able to change between releases without changing the type.
20-
// `VERSION` is a good example here- the version is always a `string`, but the actual value of
21+
// `VERSION` is a good example here; the version is always a `string`, but the actual value of
2122
// the version string shouldn't matter to the type system.
2223
const resolvedType =
2324
typeChecker.getBaseTypeOfLiteralType(typeChecker.getTypeAtLocation(declaration));
2425

26+
// In the TS AST, the leading comment for a variable declaration is actually
27+
// on the ancestor `ts.VariableStatement` (since a single variable statement may
28+
// contain multiple variable declarations).
29+
const variableStatement = declaration.parent.parent;
30+
const rawComment = extractRawJsDoc(declaration.parent.parent);
31+
2532
return {
2633
name: declaration.name.getText(),
2734
type: typeChecker.typeToString(resolvedType),
2835
entryType: EntryType.Constant,
36+
rawComment,
37+
description: extractJsDocDescription(declaration),
38+
jsdocTags: extractJsDocTags(declaration),
2939
};
3040
}
3141

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,18 @@ export enum MemberTags {
4040
Output = 'output',
4141
}
4242

43+
export interface JsDocTagEntry {
44+
name: string;
45+
comment: string;
46+
}
47+
4348
/** Base type for all documentation entities. */
4449
export interface DocEntry {
4550
entryType: EntryType;
4651
name: string;
52+
description: string;
53+
rawComment: string;
54+
jsdocTags: JsDocTagEntry[];
4755
}
4856

4957
/** Documentation entity for a constant. */
@@ -73,6 +81,8 @@ export interface MemberEntry {
7381
name: string;
7482
memberType: MemberType;
7583
memberTags: MemberTags[];
84+
description: string;
85+
jsdocTags: JsDocTagEntry[];
7686
}
7787

7888
/** Sub-entry for a class property. */

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import {EntryType, FunctionEntry, ParameterEntry} from '@angular/compiler-cli/src/ngtsc/docs/src/entities';
10+
import {extractJsDocDescription, extractJsDocTags, extractRawJsDoc} from '@angular/compiler-cli/src/ngtsc/docs/src/jsdoc_extractor';
1011
import ts from 'typescript';
1112

1213
import {extractResolvedTypeString} from './type_extractor';
@@ -32,13 +33,16 @@ export class FunctionExtractor {
3233
name: this.declaration.name!.getText(),
3334
returnType,
3435
entryType: EntryType.Function,
36+
description: extractJsDocDescription(this.declaration),
37+
jsdocTags: extractJsDocTags(this.declaration),
38+
rawComment: extractRawJsDoc(this.declaration),
3539
};
3640
}
3741

3842
private extractAllParams(params: ts.NodeArray<ts.ParameterDeclaration>): ParameterEntry[] {
3943
return params.map(param => ({
4044
name: param.name.getText(),
41-
description: 'TODO',
45+
description: extractJsDocDescription(param),
4246
type: extractResolvedTypeString(param, this.typeChecker),
4347
isOptional: !!(param.questionToken || param.initializer),
4448
isRestParam: !!param.dotDotDotToken,
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import ts from 'typescript';
10+
11+
import {JsDocTagEntry} from './entities';
12+
13+
14+
/** Gets the set of JsDoc tags applied to a node. */
15+
export function extractJsDocTags(node: ts.HasJSDoc): JsDocTagEntry[] {
16+
return ts.getJSDocTags(node).map(t => ({
17+
name: t.tagName.getText(),
18+
comment: ts.getTextOfJSDocComment(t.comment) ?? '',
19+
}));
20+
}
21+
22+
/**
23+
* Gets the JsDoc description for a node. If the node does not have
24+
* a description, returns the empty string.
25+
*/
26+
export function extractJsDocDescription(node: ts.HasJSDoc): string {
27+
// If the node is a top-level statement (const, class, function, etc.), we will get
28+
// a `ts.JSDoc` here. If the node is a `ts.ParameterDeclaration`, we will get
29+
// a `ts.JSDocParameterTag`.
30+
const commentOrTag = ts.getJSDocCommentsAndTags(node).find(d => {
31+
return ts.isJSDoc(d) || ts.isJSDocParameterTag(d);
32+
});
33+
34+
const comment = commentOrTag?.comment ?? '';
35+
return typeof comment === 'string' ? comment : ts.getTextOfJSDocComment(comment) ?? '';
36+
}
37+
38+
/**
39+
* Gets the raw JsDoc applied to a node. If the node does not have a JsDoc block,
40+
* returns the empty string.
41+
*/
42+
export function extractRawJsDoc(node: ts.HasJSDoc): string {
43+
// Assume that any node has at most one JsDoc block.
44+
return ts.getJSDocCommentsAndTags(node).find(ts.isJSDoc)?.getFullText() ?? '';
45+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ runInEachFileSystem(os => {
3838
expect(constantEntry.type).toBe('string');
3939
});
4040

41-
it('should extract mutliple constant declarations in a single statement', () => {
41+
it('should extract multiple constant declarations in a single statement', () => {
4242
env.write('test.ts', `
4343
export const PI = 3.14, VERSION = '16.0.0';
4444
`);

0 commit comments

Comments
 (0)