Skip to content

Commit

Permalink
feat(compiler): extract docs for accessors, rest params, and types (#…
Browse files Browse the repository at this point in the history
…51733)

Based on top of #51697

Adds extraction for accessors (getters/setters), rest params, and resolved type info for everything so far. This also refactors function extraction into a new class and splits tests for common class info and directive info into separate files.

PR Close #51733
  • Loading branch information
jelbourn authored and pkozlowski-opensource committed Sep 18, 2023
1 parent c7daf7e commit b9c7015
Show file tree
Hide file tree
Showing 8 changed files with 362 additions and 187 deletions.
Expand Up @@ -6,21 +6,25 @@
* found in the LICENSE file at https://angular.io/license
*/

import {FunctionExtractor} from '@angular/compiler-cli/src/ngtsc/docs/src/function_extractor';
import ts from 'typescript';

import {Reference} from '../../imports';
import {DirectiveMeta, InputMapping, InputOrOutput, MetadataReader} from '../../metadata';
import {ClassDeclaration} from '../../reflection';

import {ClassEntry, DirectiveEntry, EntryType, MemberEntry, MemberTags, MemberType, MethodEntry, PropertyEntry} from './entities';
import {extractFunction} from './function-extractor';
import {extractResolvedTypeString} from './type_extractor';

/** A class member declaration that is *like* a property (including accessors) */
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 reference: Reference,
protected checker: ts.TypeChecker,
protected typeChecker: ts.TypeChecker,
) {}

/** Extract docs info specific to classes. */
Expand Down Expand Up @@ -54,35 +58,46 @@ class ClassExtractor {
return this.extractMethod(memberDeclaration);
} else if (ts.isPropertyDeclaration(memberDeclaration)) {
return this.extractClassProperty(memberDeclaration);
} else if (ts.isAccessor(memberDeclaration)) {
return this.extractGetterSetter(memberDeclaration);
}

// We only expect methods and properties. If we encounter something else,
// We only expect methods, properties, and accessors. If we encounter something else,
// return undefined and let the rest of the program filter it out.
return undefined;
}

/** Extracts docs for a class method. */
protected extractMethod(methodDeclaration: ts.MethodDeclaration): MethodEntry {
const functionExtractor = new FunctionExtractor(methodDeclaration, this.typeChecker);
return {
...extractFunction(methodDeclaration),
...functionExtractor.extract(),
memberType: MemberType.method,
memberTags: this.getMemberTags(methodDeclaration),
};
}

/** Extracts doc info for a property declaration. */
protected extractClassProperty(propertyDeclaration: ts.PropertyDeclaration): PropertyEntry {
protected extractClassProperty(propertyDeclaration: PropertyDeclarationLike): PropertyEntry {
return {
name: propertyDeclaration.name.getText(),
getType: 'TODO',
setType: 'TODO',
type: extractResolvedTypeString(propertyDeclaration, this.typeChecker),
memberType: MemberType.property,
memberTags: this.getMemberTags(propertyDeclaration),
};
}

/** Extracts doc info for an accessor member (getter/setter). */
protected extractGetterSetter(accessor: ts.AccessorDeclaration): PropertyEntry {
return {
...this.extractClassProperty(accessor),
memberType: ts.isGetAccessor(accessor) ? MemberType.getter : MemberType.setter,
};
}

/** Gets the tags for a member (protected, readonly, static, etc.) */
protected getMemberTags(member: ts.MethodDeclaration|ts.PropertyDeclaration): MemberTags[] {
protected getMemberTags(member: ts.MethodDeclaration|ts.PropertyDeclaration|
ts.AccessorDeclaration): MemberTags[] {
const tags: MemberTags[] = this.getMemberTagsFromModifiers(member.modifiers ?? []);

if (member.questionToken) {
Expand Down Expand Up @@ -124,14 +139,15 @@ class ClassExtractor {
* - The member is protected
*/
private isMemberExcluded(member: ts.ClassElement): boolean {
return !member.name || !this.isMethodOrProperty(member) ||
return !member.name || !this.isDocumentableMember(member) ||
!!member.modifiers?.some(mod => mod.kind === ts.SyntaxKind.PrivateKeyword);
}

/** Gets whether a class member is either a member or a property. */
private isMethodOrProperty(member: ts.ClassElement): member is ts.MethodDeclaration
/** Gets whether a class member is a method, property, or accessor. */
private isDocumentableMember(member: ts.ClassElement): member is ts.MethodDeclaration
|ts.PropertyDeclaration {
return ts.isMethodDeclaration(member) || ts.isPropertyDeclaration(member);
return ts.isMethodDeclaration(member) || ts.isPropertyDeclaration(member) ||
ts.isAccessor(member);
}
}

Expand All @@ -157,6 +173,7 @@ class DirectiveExtractor extends ClassExtractor {
};
}

/** Extracts docs info for a directive property, including input/output metadata. */
override extractClassProperty(propertyDeclaration: ts.PropertyDeclaration): PropertyEntry {
const entry = super.extractClassProperty(propertyDeclaration);

Expand Down
6 changes: 4 additions & 2 deletions packages/compiler-cli/src/ngtsc/docs/src/entities.ts
Expand Up @@ -25,6 +25,8 @@ export enum EntryType {
export enum MemberType {
property = 'property',
method = 'method',
getter = 'getter',
setter = 'setter',
}

/** Informational tags applicable to class members. */
Expand Down Expand Up @@ -69,8 +71,7 @@ export interface MemberEntry {

/** Sub-entry for a class property. */
export interface PropertyEntry extends MemberEntry {
getType: string;
setType: string;
type: string;
inputAlias?: string;
outputAlias?: string;
}
Expand All @@ -84,4 +85,5 @@ export interface ParameterEntry {
description: string;
type: string;
isOptional: boolean;
isRestParam: boolean;
}
9 changes: 4 additions & 5 deletions packages/compiler-cli/src/ngtsc/docs/src/extractor.ts
Expand Up @@ -6,14 +6,13 @@
* found in the LICENSE file at https://angular.io/license
*/

import {Reference} from '@angular/compiler-cli/src/ngtsc/imports';
import ts from 'typescript';

import {DirectiveMeta, InputMapping, InputOrOutput, MetadataReader} from '../../metadata';
import {ClassDeclaration, isNamedClassDeclaration} from '../../reflection';
import {MetadataReader} from '../../metadata';
import {isNamedClassDeclaration} from '../../reflection';

import {extractClass} from './class-extractor';
import {ClassEntry, DirectiveEntry, DocEntry, EntryType, FunctionEntry, MemberEntry, MemberTags, MemberType, MethodEntry, ParameterEntry, PropertyEntry} from './entities';
import {extractClass} from './class_extractor';
import {DocEntry} from './entities';


/**
Expand Down
33 changes: 0 additions & 33 deletions packages/compiler-cli/src/ngtsc/docs/src/function-extractor.ts

This file was deleted.

47 changes: 47 additions & 0 deletions packages/compiler-cli/src/ngtsc/docs/src/function_extractor.ts
@@ -0,0 +1,47 @@
/**
* @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 {EntryType, FunctionEntry, ParameterEntry} from '@angular/compiler-cli/src/ngtsc/docs/src/entities';
import ts from 'typescript';

import {extractResolvedTypeString} from './type_extractor';

export class FunctionExtractor {
constructor(
private declaration: ts.FunctionDeclaration|ts.MethodDeclaration,
private typeChecker: ts.TypeChecker,
) {}

extract(): FunctionEntry {
// TODO: is there any real situation in which the signature would not be available here?
// Is void a better type?
const signature = this.typeChecker.getSignatureFromDeclaration(this.declaration);
const returnType = signature ?
this.typeChecker.typeToString(this.typeChecker.getReturnTypeOfSignature(signature)) :
'unknown';

return {
params: this.extractAllParams(this.declaration.parameters),
// We know that the function has a name here because we would have skipped it
// already before getting to this point if it was anonymous.
name: this.declaration.name!.getText(),
returnType,
entryType: EntryType.function,
};
}

private extractAllParams(params: ts.NodeArray<ts.ParameterDeclaration>): ParameterEntry[] {
return params.map(param => ({
name: param.name.getText(),
description: 'TODO',
type: extractResolvedTypeString(param, this.typeChecker),
isOptional: !!(param.questionToken || param.initializer),
isRestParam: !!param.dotDotDotToken,
}));
}
}
14 changes: 14 additions & 0 deletions packages/compiler-cli/src/ngtsc/docs/src/type_extractor.ts
@@ -0,0 +1,14 @@
/**
* @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';

/** Gets the string representation of a node's resolved type. */
export function extractResolvedTypeString(node: ts.Node, checker: ts.TypeChecker): string {
return checker.typeToString(checker.getTypeAtLocation(node));
}

0 comments on commit b9c7015

Please sign in to comment.