Skip to content

Commit

Permalink
feat(compiler): extract doc info for JsDoc (#51733)
Browse files Browse the repository at this point in the history
Based on top of #51713

This commit adds docs extraction for information provided in JsDoc comments, including descriptions and Jsdoc tags.

PR Close #51733
  • Loading branch information
jelbourn authored and pkozlowski-opensource committed Sep 18, 2023
1 parent a24ae99 commit e0b1bb3
Show file tree
Hide file tree
Showing 7 changed files with 335 additions and 8 deletions.
14 changes: 10 additions & 4 deletions packages/compiler-cli/src/ngtsc/docs/src/class_extractor.ts
Expand Up @@ -7,6 +7,7 @@
*/

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

import {Reference} from '../../imports';
Expand All @@ -22,7 +23,7 @@ type PropertyDeclarationLike = ts.PropertyDeclaration|ts.AccessorDeclaration;
/** Extractor to pull info for API reference documentation for a TypeScript class. */
class ClassExtractor {
constructor(
protected declaration: ClassDeclaration,
protected declaration: ClassDeclaration&ts.ClassDeclaration,
protected reference: Reference,
protected typeChecker: ts.TypeChecker,
) {}
Expand All @@ -32,7 +33,10 @@ class ClassExtractor {
return {
name: this.declaration.name!.text,
entryType: EntryType.UndecoratedClass,
members: this.extractAllClassMembers(this.declaration as ts.ClassDeclaration),
members: this.extractAllClassMembers(this.declaration),
description: extractJsDocDescription(this.declaration),
jsdocTags: extractJsDocTags(this.declaration),
rawComment: extractRawJsDoc(this.declaration),
};
}

Expand Down Expand Up @@ -84,6 +88,8 @@ class ClassExtractor {
type: extractResolvedTypeString(propertyDeclaration, this.typeChecker),
memberType: MemberType.Property,
memberTags: this.getMemberTags(propertyDeclaration),
description: extractJsDocDescription(propertyDeclaration),
jsdocTags: extractJsDocTags(propertyDeclaration),
};
}

Expand Down Expand Up @@ -154,7 +160,7 @@ class ClassExtractor {
/** Extractor to pull info for API reference documentation for an Angular directive. */
class DirectiveExtractor extends ClassExtractor {
constructor(
declaration: ClassDeclaration,
declaration: ClassDeclaration&ts.ClassDeclaration,
reference: Reference,
protected metadata: DirectiveMeta,
checker: ts.TypeChecker,
Expand Down Expand Up @@ -207,7 +213,7 @@ class DirectiveExtractor extends ClassExtractor {

/** Extracts documentation info for a class, potentially including Angular-specific info. */
export function extractClass(
classDeclaration: ClassDeclaration, metadataReader: MetadataReader,
classDeclaration: ClassDeclaration&ts.ClassDeclaration, metadataReader: MetadataReader,
typeChecker: ts.TypeChecker): ClassEntry {
const ref = new Reference(classDeclaration);
const metadata = metadataReader.getDirectiveMetadata(ref);
Expand Down
14 changes: 12 additions & 2 deletions packages/compiler-cli/src/ngtsc/docs/src/constant_extractor.ts
Expand Up @@ -9,23 +9,33 @@
import ts from 'typescript';

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

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

// In the TS AST, the leading comment for a variable declaration is actually
// on the ancestor `ts.VariableStatement` (since a single variable statement may
// contain multiple variable declarations).
const variableStatement = declaration.parent.parent;
const rawComment = extractRawJsDoc(declaration.parent.parent);

return {
name: declaration.name.getText(),
type: typeChecker.typeToString(resolvedType),
entryType: EntryType.Constant,
rawComment,
description: extractJsDocDescription(declaration),
jsdocTags: extractJsDocTags(declaration),
};
}

Expand Down
10 changes: 10 additions & 0 deletions packages/compiler-cli/src/ngtsc/docs/src/entities.ts
Expand Up @@ -40,10 +40,18 @@ export enum MemberTags {
Output = 'output',
}

export interface JsDocTagEntry {
name: string;
comment: string;
}

/** Base type for all documentation entities. */
export interface DocEntry {
entryType: EntryType;
name: string;
description: string;
rawComment: string;
jsdocTags: JsDocTagEntry[];
}

/** Documentation entity for a constant. */
Expand Down Expand Up @@ -73,6 +81,8 @@ export interface MemberEntry {
name: string;
memberType: MemberType;
memberTags: MemberTags[];
description: string;
jsdocTags: JsDocTagEntry[];
}

/** Sub-entry for a class property. */
Expand Down
Expand Up @@ -7,6 +7,7 @@
*/

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

import {extractResolvedTypeString} from './type_extractor';
Expand All @@ -32,13 +33,16 @@ export class FunctionExtractor {
name: this.declaration.name!.getText(),
returnType,
entryType: EntryType.Function,
description: extractJsDocDescription(this.declaration),
jsdocTags: extractJsDocTags(this.declaration),
rawComment: extractRawJsDoc(this.declaration),
};
}

private extractAllParams(params: ts.NodeArray<ts.ParameterDeclaration>): ParameterEntry[] {
return params.map(param => ({
name: param.name.getText(),
description: 'TODO',
description: extractJsDocDescription(param),
type: extractResolvedTypeString(param, this.typeChecker),
isOptional: !!(param.questionToken || param.initializer),
isRestParam: !!param.dotDotDotToken,
Expand Down
45 changes: 45 additions & 0 deletions packages/compiler-cli/src/ngtsc/docs/src/jsdoc_extractor.ts
@@ -0,0 +1,45 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import ts from 'typescript';

import {JsDocTagEntry} from './entities';


/** Gets the set of JsDoc tags applied to a node. */
export function extractJsDocTags(node: ts.HasJSDoc): JsDocTagEntry[] {
return ts.getJSDocTags(node).map(t => ({
name: t.tagName.getText(),
comment: ts.getTextOfJSDocComment(t.comment) ?? '',
}));
}

/**
* Gets the JsDoc description for a node. If the node does not have
* a description, returns the empty string.
*/
export function extractJsDocDescription(node: ts.HasJSDoc): string {
// If the node is a top-level statement (const, class, function, etc.), we will get
// a `ts.JSDoc` here. If the node is a `ts.ParameterDeclaration`, we will get
// a `ts.JSDocParameterTag`.
const commentOrTag = ts.getJSDocCommentsAndTags(node).find(d => {
return ts.isJSDoc(d) || ts.isJSDocParameterTag(d);
});

const comment = commentOrTag?.comment ?? '';
return typeof comment === 'string' ? comment : ts.getTextOfJSDocComment(comment) ?? '';
}

/**
* Gets the raw JsDoc applied to a node. If the node does not have a JsDoc block,
* returns the empty string.
*/
export function extractRawJsDoc(node: ts.HasJSDoc): string {
// Assume that any node has at most one JsDoc block.
return ts.getJSDocCommentsAndTags(node).find(ts.isJSDoc)?.getFullText() ?? '';
}
Expand Up @@ -38,7 +38,7 @@ runInEachFileSystem(os => {
expect(constantEntry.type).toBe('string');
});

it('should extract mutliple constant declarations in a single statement', () => {
it('should extract multiple constant declarations in a single statement', () => {
env.write('test.ts', `
export const PI = 3.14, VERSION = '16.0.0';
`);
Expand Down

0 comments on commit e0b1bb3

Please sign in to comment.