diff --git a/packages/compiler-cli/src/ngtsc/docs/src/class_extractor.ts b/packages/compiler-cli/src/ngtsc/docs/src/class_extractor.ts index b926f28a1a81d..04e6878734e36 100644 --- a/packages/compiler-cli/src/ngtsc/docs/src/class_extractor.ts +++ b/packages/compiler-cli/src/ngtsc/docs/src/class_extractor.ts @@ -6,8 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -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'; @@ -16,6 +14,9 @@ import {ClassDeclaration} from '../../reflection'; import {ClassEntry, DirectiveEntry, EntryType, InterfaceEntry, MemberEntry, MemberTags, MemberType, MethodEntry, PipeEntry, PropertyEntry} from './entities'; import {isAngularPrivateName} from './filters'; +import {FunctionExtractor} from './function_extractor'; +import {extractGenerics} from './generics_extractor'; +import {extractJsDocDescription, extractJsDocTags, extractRawJsDoc} from './jsdoc_extractor'; import {extractResolvedTypeString} from './type_extractor'; // For the purpose of extraction, we can largely treat properties and accessors the same. @@ -53,6 +54,7 @@ class ClassExtractor { entryType: ts.isInterfaceDeclaration(this.declaration) ? EntryType.Interface : EntryType.UndecoratedClass, members: this.extractAllClassMembers(this.declaration), + generics: extractGenerics(this.declaration), description: extractJsDocDescription(this.declaration), jsdocTags: extractJsDocTags(this.declaration), rawComment: extractRawJsDoc(this.declaration), diff --git a/packages/compiler-cli/src/ngtsc/docs/src/entities.ts b/packages/compiler-cli/src/ngtsc/docs/src/entities.ts index efc072c8e1ccc..83e755921f38e 100644 --- a/packages/compiler-cli/src/ngtsc/docs/src/entities.ts +++ b/packages/compiler-cli/src/ngtsc/docs/src/entities.ts @@ -43,11 +43,19 @@ export enum MemberTags { Output = 'output', } +/** Documentation entity for single JsDoc tag. */ export interface JsDocTagEntry { name: string; comment: string; } +/** Documentation entity for single generic parameter. */ +export interface GenericEntry { + name: string; + constraint: string|undefined; + default: string|undefined; +} + /** Base type for all documentation entities. */ export interface DocEntry { entryType: EntryType; @@ -69,6 +77,7 @@ export type TypeAliasEntry = ConstantEntry; export interface ClassEntry extends DocEntry { isAbstract: boolean; members: MemberEntry[]; + generics: GenericEntry[]; } // From an API doc perspective, class and interfaces are identical. @@ -97,6 +106,7 @@ export interface PipeEntry extends ClassEntry { export interface FunctionEntry extends DocEntry { params: ParameterEntry[]; returnType: string; + generics: GenericEntry[]; } /** Sub-entry for a single class or enum member. */ diff --git a/packages/compiler-cli/src/ngtsc/docs/src/function_extractor.ts b/packages/compiler-cli/src/ngtsc/docs/src/function_extractor.ts index 50bfe5e74e0fe..818b12368d5ad 100644 --- a/packages/compiler-cli/src/ngtsc/docs/src/function_extractor.ts +++ b/packages/compiler-cli/src/ngtsc/docs/src/function_extractor.ts @@ -6,17 +6,17 @@ * found in the LICENSE file at https://angular.io/license */ -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 {EntryType, FunctionEntry, ParameterEntry} from './entities'; +import {extractGenerics} from './generics_extractor'; +import {extractJsDocDescription, extractJsDocTags, extractRawJsDoc} from './jsdoc_extractor'; import {extractResolvedTypeString} from './type_extractor'; +export type FunctionLike = ts.FunctionDeclaration|ts.MethodDeclaration|ts.MethodSignature; + export class FunctionExtractor { - constructor( - private declaration: ts.FunctionDeclaration|ts.MethodDeclaration|ts.MethodSignature, - private typeChecker: ts.TypeChecker, - ) {} + constructor(private declaration: FunctionLike, private typeChecker: ts.TypeChecker) {} extract(): FunctionEntry { // TODO: is there any real situation in which the signature would not be available here? @@ -33,6 +33,7 @@ export class FunctionExtractor { name: this.declaration.name!.getText(), returnType, entryType: EntryType.Function, + generics: extractGenerics(this.declaration), description: extractJsDocDescription(this.declaration), jsdocTags: extractJsDocTags(this.declaration), rawComment: extractRawJsDoc(this.declaration), diff --git a/packages/compiler-cli/src/ngtsc/docs/src/generics_extractor.ts b/packages/compiler-cli/src/ngtsc/docs/src/generics_extractor.ts new file mode 100644 index 0000000000000..cda905b382663 --- /dev/null +++ b/packages/compiler-cli/src/ngtsc/docs/src/generics_extractor.ts @@ -0,0 +1,25 @@ +/** + * @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 {GenericEntry} from './entities'; + +type DeclarationWithTypeParams = { + typeParameters?: ts.NodeArray|undefined +}; + +/** Gets a list of all the generic type parameters for a declaration. */ +export function extractGenerics(declaration: DeclarationWithTypeParams): GenericEntry[] { + return declaration.typeParameters?.map(typeParam => ({ + name: typeParam.name.getText(), + constraint: typeParam.constraint?.getText(), + default: typeParam.default?.getText(), + })) ?? + []; +} diff --git a/packages/compiler-cli/test/ngtsc/doc_extraction/class_doc_extraction_spec.ts b/packages/compiler-cli/test/ngtsc/doc_extraction/class_doc_extraction_spec.ts index 9c2140cea3413..463bf0b666a3b 100644 --- a/packages/compiler-cli/test/ngtsc/doc_extraction/class_doc_extraction_spec.ts +++ b/packages/compiler-cli/test/ngtsc/doc_extraction/class_doc_extraction_spec.ts @@ -15,7 +15,7 @@ import {NgtscTestEnvironment} from '../env'; const testFiles = loadStandardTestFiles({fakeCore: true, fakeCommon: true}); -runInEachFileSystem(os => { +runInEachFileSystem(() => { let env!: NgtscTestEnvironment; describe('ngtsc class docs extraction', () => { @@ -296,5 +296,90 @@ runInEachFileSystem(os => { expect(resetEntry.name).toBe('reset'); expect(resetEntry.memberTags).toContain(MemberTags.Abstract); }); + + it('should extract class generic parameters', () => { + env.write('index.ts', ` + export class UserProfile { + constructor(public name: T) { } + } + + export class TwinProfile { + constructor(public name: U, age: V) { } + } + + export class AdminProfile { + constructor(public name: X) { } + } + + export class BotProfile { + constructor(public name: Q) { } + } + + export class ExecProfile { + constructor(public name: W) { } + }`); + + const docs: DocEntry[] = env.driveDocsExtraction('index.ts'); + expect(docs.length).toBe(5); + + const [ + userProfileEntry, + twinProfileEntry, + adminProfileEntry, + botProfileEntry, + execProfileEntry, + ] = docs as ClassEntry[]; + + expect(userProfileEntry.generics.length).toBe(1); + expect(twinProfileEntry.generics.length).toBe(2); + expect(adminProfileEntry.generics.length).toBe(1); + expect(botProfileEntry.generics.length).toBe(1); + expect(execProfileEntry.generics.length).toBe(1); + + const [userProfileGenericEntry] = userProfileEntry.generics; + expect(userProfileGenericEntry.name).toBe('T'); + expect(userProfileGenericEntry.constraint).toBeUndefined(); + expect(userProfileGenericEntry.default).toBeUndefined(); + + const [nameGenericEntry, ageGenericEntry] = twinProfileEntry.generics; + expect(nameGenericEntry.name).toBe('U'); + expect(nameGenericEntry.constraint).toBeUndefined(); + expect(nameGenericEntry.default).toBeUndefined(); + expect(ageGenericEntry.name).toBe('V'); + expect(ageGenericEntry.constraint).toBeUndefined(); + expect(ageGenericEntry.default).toBeUndefined(); + + const [adminProfileGenericEntry] = adminProfileEntry.generics; + expect(adminProfileGenericEntry.name).toBe('X'); + expect(adminProfileGenericEntry.constraint).toBe('String'); + expect(adminProfileGenericEntry.default).toBeUndefined(); + + const [botProfileGenericEntry] = botProfileEntry.generics; + expect(botProfileGenericEntry.name).toBe('Q'); + expect(botProfileGenericEntry.constraint).toBeUndefined(); + expect(botProfileGenericEntry.default).toBe('string'); + + const [execProfileGenericEntry] = execProfileEntry.generics; + expect(execProfileGenericEntry.name).toBe('W'); + expect(execProfileGenericEntry.constraint).toBe('String'); + expect(execProfileGenericEntry.default).toBe('string'); + }); + + it('should extract method generic parameters', () => { + env.write('index.ts', ` + export class UserProfile { + save(data: T): void { } + }`); + + const docs: DocEntry[] = env.driveDocsExtraction('index.ts'); + expect(docs.length).toBe(1); + + const classEntry = docs[0] as ClassEntry; + const [genericEntry] = (classEntry.members[0] as MethodEntry).generics; + + expect(genericEntry.name).toBe('T'); + expect(genericEntry.constraint).toBeUndefined(); + expect(genericEntry.default).toBeUndefined(); + }); }); }); diff --git a/packages/compiler-cli/test/ngtsc/doc_extraction/common_doc_extraction_spec.ts b/packages/compiler-cli/test/ngtsc/doc_extraction/common_doc_extraction_spec.ts index e61ea5624470f..2a087a368d806 100644 --- a/packages/compiler-cli/test/ngtsc/doc_extraction/common_doc_extraction_spec.ts +++ b/packages/compiler-cli/test/ngtsc/doc_extraction/common_doc_extraction_spec.ts @@ -14,7 +14,7 @@ import {NgtscTestEnvironment} from '../env'; const testFiles = loadStandardTestFiles({fakeCore: true, fakeCommon: true}); -runInEachFileSystem(os => { +runInEachFileSystem(() => { let env!: NgtscTestEnvironment; describe('ngtsc common docs extraction', () => { diff --git a/packages/compiler-cli/test/ngtsc/doc_extraction/constant_doc_extraction_spec.ts b/packages/compiler-cli/test/ngtsc/doc_extraction/constant_doc_extraction_spec.ts index ee12cd21df721..826b96da99cee 100644 --- a/packages/compiler-cli/test/ngtsc/doc_extraction/constant_doc_extraction_spec.ts +++ b/packages/compiler-cli/test/ngtsc/doc_extraction/constant_doc_extraction_spec.ts @@ -15,7 +15,7 @@ import {NgtscTestEnvironment} from '../env'; const testFiles = loadStandardTestFiles({fakeCore: true, fakeCommon: true}); -runInEachFileSystem(os => { +runInEachFileSystem(() => { let env!: NgtscTestEnvironment; describe('ngtsc constant docs extraction', () => { diff --git a/packages/compiler-cli/test/ngtsc/doc_extraction/directive_doc_extraction_spec.ts b/packages/compiler-cli/test/ngtsc/doc_extraction/directive_doc_extraction_spec.ts index c21f19e0accca..dce740e266522 100644 --- a/packages/compiler-cli/test/ngtsc/doc_extraction/directive_doc_extraction_spec.ts +++ b/packages/compiler-cli/test/ngtsc/doc_extraction/directive_doc_extraction_spec.ts @@ -15,7 +15,7 @@ import {NgtscTestEnvironment} from '../env'; const testFiles = loadStandardTestFiles({fakeCore: true, fakeCommon: true}); -runInEachFileSystem(os => { +runInEachFileSystem(() => { let env!: NgtscTestEnvironment; describe('ngtsc directive docs extraction', () => { diff --git a/packages/compiler-cli/test/ngtsc/doc_extraction/doc_extraction_filtering_spec.ts b/packages/compiler-cli/test/ngtsc/doc_extraction/doc_extraction_filtering_spec.ts index 0a65a885550b0..4c4b0f7f0b825 100644 --- a/packages/compiler-cli/test/ngtsc/doc_extraction/doc_extraction_filtering_spec.ts +++ b/packages/compiler-cli/test/ngtsc/doc_extraction/doc_extraction_filtering_spec.ts @@ -14,7 +14,7 @@ import {NgtscTestEnvironment} from '../env'; const testFiles = loadStandardTestFiles({fakeCore: true, fakeCommon: true}); -runInEachFileSystem(os => { +runInEachFileSystem(() => { let env!: NgtscTestEnvironment; describe('ngtsc docs extraction filtering', () => { diff --git a/packages/compiler-cli/test/ngtsc/doc_extraction/enum_doc_extraction_spec.ts b/packages/compiler-cli/test/ngtsc/doc_extraction/enum_doc_extraction_spec.ts index 2765bb96e2f80..6a9920b66f536 100644 --- a/packages/compiler-cli/test/ngtsc/doc_extraction/enum_doc_extraction_spec.ts +++ b/packages/compiler-cli/test/ngtsc/doc_extraction/enum_doc_extraction_spec.ts @@ -15,7 +15,7 @@ import {NgtscTestEnvironment} from '../env'; const testFiles = loadStandardTestFiles({fakeCore: true, fakeCommon: true}); -runInEachFileSystem(os => { +runInEachFileSystem(() => { let env!: NgtscTestEnvironment; describe('ngtsc enum docs extraction', () => { diff --git a/packages/compiler-cli/test/ngtsc/doc_extraction/function_doc_extraction_spec.ts b/packages/compiler-cli/test/ngtsc/doc_extraction/function_doc_extraction_spec.ts index 5f83a2f8ffc64..c9f9b3c5053a9 100644 --- a/packages/compiler-cli/test/ngtsc/doc_extraction/function_doc_extraction_spec.ts +++ b/packages/compiler-cli/test/ngtsc/doc_extraction/function_doc_extraction_spec.ts @@ -15,7 +15,7 @@ import {NgtscTestEnvironment} from '../env'; const testFiles = loadStandardTestFiles({fakeCore: true, fakeCommon: true}); -runInEachFileSystem(os => { +runInEachFileSystem(() => { let env!: NgtscTestEnvironment; describe('ngtsc function docs extraction', () => { @@ -113,5 +113,22 @@ runInEachFileSystem(os => { expect(numberOverloadEntry.params[0].type).toBe('number'); expect(numberOverloadEntry.returnType).toBe('number'); }); + + it('should extract function generics', () => { + env.write('index.ts', ` + export function save(data: T) { } + `); + + const docs: DocEntry[] = env.driveDocsExtraction('index.ts'); + expect(docs.length).toBe(1); + + const [functionEntry] = docs as FunctionEntry[]; + expect(functionEntry.generics.length).toBe(1); + + const [genericEntry] = functionEntry.generics; + expect(genericEntry.name).toBe('T'); + expect(genericEntry.constraint).toBeUndefined(); + expect(genericEntry.default).toBeUndefined(); + }); }); }); diff --git a/packages/compiler-cli/test/ngtsc/doc_extraction/interface_doc_extraction_spec.ts b/packages/compiler-cli/test/ngtsc/doc_extraction/interface_doc_extraction_spec.ts index b87990ef160e7..4abb3e63b5880 100644 --- a/packages/compiler-cli/test/ngtsc/doc_extraction/interface_doc_extraction_spec.ts +++ b/packages/compiler-cli/test/ngtsc/doc_extraction/interface_doc_extraction_spec.ts @@ -15,7 +15,7 @@ import {NgtscTestEnvironment} from '../env'; const testFiles = loadStandardTestFiles({fakeCore: true, fakeCommon: true}); -runInEachFileSystem(os => { +runInEachFileSystem(() => { let env!: NgtscTestEnvironment; describe('ngtsc interface docs extraction', () => { 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 b69f0931a9b24..41583a2345604 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 @@ -15,7 +15,7 @@ import {NgtscTestEnvironment} from '../env'; const testFiles = loadStandardTestFiles({fakeCore: true, fakeCommon: true}); -runInEachFileSystem(os => { +runInEachFileSystem(() => { let env!: NgtscTestEnvironment; describe('ngtsc jsdoc extraction', () => { diff --git a/packages/compiler-cli/test/ngtsc/doc_extraction/ng_module_doc_extraction_spec.ts b/packages/compiler-cli/test/ngtsc/doc_extraction/ng_module_doc_extraction_spec.ts index 36cacbf4fab09..c8e0647b6ae9a 100644 --- a/packages/compiler-cli/test/ngtsc/doc_extraction/ng_module_doc_extraction_spec.ts +++ b/packages/compiler-cli/test/ngtsc/doc_extraction/ng_module_doc_extraction_spec.ts @@ -15,7 +15,7 @@ import {NgtscTestEnvironment} from '../env'; const testFiles = loadStandardTestFiles({fakeCore: true, fakeCommon: true}); -runInEachFileSystem(os => { +runInEachFileSystem(() => { let env!: NgtscTestEnvironment; describe('ngtsc NgModule docs extraction', () => { diff --git a/packages/compiler-cli/test/ngtsc/doc_extraction/pipe_doc_extraction_spec.ts b/packages/compiler-cli/test/ngtsc/doc_extraction/pipe_doc_extraction_spec.ts index adc4a3c187767..180ee9fcbfde6 100644 --- a/packages/compiler-cli/test/ngtsc/doc_extraction/pipe_doc_extraction_spec.ts +++ b/packages/compiler-cli/test/ngtsc/doc_extraction/pipe_doc_extraction_spec.ts @@ -15,7 +15,7 @@ import {NgtscTestEnvironment} from '../env'; const testFiles = loadStandardTestFiles({fakeCore: true, fakeCommon: true}); -runInEachFileSystem(os => { +runInEachFileSystem(() => { let env!: NgtscTestEnvironment; describe('ngtsc pipe docs extraction', () => { diff --git a/packages/compiler-cli/test/ngtsc/doc_extraction/reexport_docs_extraction_spec.ts b/packages/compiler-cli/test/ngtsc/doc_extraction/reexport_docs_extraction_spec.ts index d0e0b2fb278c7..0d2ecd84eb79a 100644 --- a/packages/compiler-cli/test/ngtsc/doc_extraction/reexport_docs_extraction_spec.ts +++ b/packages/compiler-cli/test/ngtsc/doc_extraction/reexport_docs_extraction_spec.ts @@ -15,7 +15,7 @@ import {NgtscTestEnvironment} from '../env'; const testFiles = loadStandardTestFiles({fakeCore: true, fakeCommon: true}); -runInEachFileSystem(os => { +runInEachFileSystem(() => { let env!: NgtscTestEnvironment; describe('ngtsc re-export docs extraction', () => { diff --git a/packages/compiler-cli/test/ngtsc/doc_extraction/type_alias_doc_extraction_spec.ts b/packages/compiler-cli/test/ngtsc/doc_extraction/type_alias_doc_extraction_spec.ts index aa60c999ea3ba..771ecf299a4b7 100644 --- a/packages/compiler-cli/test/ngtsc/doc_extraction/type_alias_doc_extraction_spec.ts +++ b/packages/compiler-cli/test/ngtsc/doc_extraction/type_alias_doc_extraction_spec.ts @@ -15,7 +15,7 @@ import {NgtscTestEnvironment} from '../env'; const testFiles = loadStandardTestFiles({fakeCore: true, fakeCommon: true}); -runInEachFileSystem(os => { +runInEachFileSystem(() => { let env!: NgtscTestEnvironment; describe('ngtsc type alias docs extraction', () => {