diff --git a/packages/compiler-cli/BUILD.bazel b/packages/compiler-cli/BUILD.bazel index ed9ce710be5..19263e3b469 100644 --- a/packages/compiler-cli/BUILD.bazel +++ b/packages/compiler-cli/BUILD.bazel @@ -75,6 +75,7 @@ ts_library( "//packages/compiler-cli/src/ngtsc/core", "//packages/compiler-cli/src/ngtsc/core:api", "//packages/compiler-cli/src/ngtsc/diagnostics", + "//packages/compiler-cli/src/ngtsc/docs", "//packages/compiler-cli/src/ngtsc/file_system", "//packages/compiler-cli/src/ngtsc/incremental", "//packages/compiler-cli/src/ngtsc/indexer", diff --git a/packages/compiler-cli/src/ngtsc/core/BUILD.bazel b/packages/compiler-cli/src/ngtsc/core/BUILD.bazel index e0894f405f4..0f12a1af41b 100644 --- a/packages/compiler-cli/src/ngtsc/core/BUILD.bazel +++ b/packages/compiler-cli/src/ngtsc/core/BUILD.bazel @@ -16,6 +16,7 @@ ts_library( "//packages/compiler-cli/src/ngtsc/annotations/common", "//packages/compiler-cli/src/ngtsc/cycles", "//packages/compiler-cli/src/ngtsc/diagnostics", + "//packages/compiler-cli/src/ngtsc/docs", "//packages/compiler-cli/src/ngtsc/entry_point", "//packages/compiler-cli/src/ngtsc/file_system", "//packages/compiler-cli/src/ngtsc/imports", diff --git a/packages/compiler-cli/src/ngtsc/core/src/compiler.ts b/packages/compiler-cli/src/ngtsc/core/src/compiler.ts index 612767c62ce..32f75a17bc2 100644 --- a/packages/compiler-cli/src/ngtsc/core/src/compiler.ts +++ b/packages/compiler-cli/src/ngtsc/core/src/compiler.ts @@ -12,6 +12,7 @@ import {ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecorato import {InjectableClassRegistry} from '../../annotations/common'; import {CycleAnalyzer, CycleHandlingStrategy, ImportGraph} from '../../cycles'; import {COMPILER_ERRORS_WITH_GUIDES, ERROR_DETAILS_PAGE_BASE_URL, ErrorCode, FatalDiagnosticError, ngErrorCode} from '../../diagnostics'; +import {DocEntry, DocsExtractor} from '../../docs'; import {checkForPrivateExports, ReferenceGraph} from '../../entry_point'; import {absoluteFromSourceFile, AbsoluteFsPath, LogicalFileSystem, resolve} from '../../file_system'; import {AbsoluteModuleStrategy, AliasingHost, AliasStrategy, DefaultImportTracker, DeferredSymbolTracker, ImportRewriter, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, NoopImportRewriter, PrivateExportAliasingHost, R3SymbolsImportRewriter, Reference, ReferenceEmitStrategy, ReferenceEmitter, RelativePathStrategy, UnifiedModulesAliasingHost, UnifiedModulesStrategy} from '../../imports'; @@ -656,6 +657,26 @@ export class NgCompiler { return generateAnalysis(context); } + /** + * Gets information for the current program that may be used to generate API + * reference documentation. This includes Angular-specific information, such + * as component inputs and outputs. + */ + getApiDocumentation(): DocEntry[] { + const compilation = this.ensureAnalyzed(); + const checker = this.inputProgram.getTypeChecker(); + const docsExtractor = new DocsExtractor(checker, compilation.metaReader); + + let entries: DocEntry[] = []; + for (const sourceFile of this.inputProgram.getSourceFiles()) { + // We don't want to generate docs for `.d.ts` files. + if (sourceFile.isDeclarationFile) continue; + + entries.push(...docsExtractor.extract(sourceFile)); + } + return entries; + } + /** * Collect i18n messages into the `Xi18nContext`. */ diff --git a/packages/compiler-cli/src/ngtsc/docs/BUILD.bazel b/packages/compiler-cli/src/ngtsc/docs/BUILD.bazel new file mode 100644 index 00000000000..ca5838f6719 --- /dev/null +++ b/packages/compiler-cli/src/ngtsc/docs/BUILD.bazel @@ -0,0 +1,18 @@ +load("//tools:defaults.bzl", "ts_library") + +package(default_visibility = ["//visibility:public"]) + +# Compiler code pertaining to extracting data for generating API reference documentation. +ts_library( + name = "docs", + srcs = ["index.ts"] + glob([ + "src/**/*.ts", + ]), + module_name = "@angular/compiler-cli/src/ngtsc/docs", + deps = [ + "//packages/compiler-cli/src/ngtsc/metadata", + "//packages/compiler-cli/src/ngtsc/util", + "@npm//@types/node", + "@npm//typescript", + ], +) diff --git a/packages/compiler-cli/src/ngtsc/docs/index.ts b/packages/compiler-cli/src/ngtsc/docs/index.ts new file mode 100644 index 00000000000..f2a94f9aa89 --- /dev/null +++ b/packages/compiler-cli/src/ngtsc/docs/index.ts @@ -0,0 +1,10 @@ +/** + * @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 + */ + +export {DocEntry} from './src/entities'; +export {DocsExtractor} from './src/extractor'; diff --git a/packages/compiler-cli/src/ngtsc/docs/src/entities.ts b/packages/compiler-cli/src/ngtsc/docs/src/entities.ts new file mode 100644 index 00000000000..ebc89324bb6 --- /dev/null +++ b/packages/compiler-cli/src/ngtsc/docs/src/entities.ts @@ -0,0 +1,12 @@ +/** + * @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 + */ + +/** Base type for all documentation entities. */ +export interface DocEntry { + name: string; +} diff --git a/packages/compiler-cli/src/ngtsc/docs/src/extractor.ts b/packages/compiler-cli/src/ngtsc/docs/src/extractor.ts new file mode 100644 index 00000000000..e1550ac4538 --- /dev/null +++ b/packages/compiler-cli/src/ngtsc/docs/src/extractor.ts @@ -0,0 +1,48 @@ +/** + * @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 {MetadataReader} from '../../metadata'; + +import {DocEntry} from './entities'; + + +/** + * Extracts all information from a source file that may be relevant for generating + * public API documentation. + */ +export class DocsExtractor { + constructor(private checker: ts.TypeChecker, private reader: MetadataReader) {} + + /** + * Gets the set of all documentable entries from a source file. + * @param sourceFile The file from which to extract documentable entries. + */ + extract(sourceFile: ts.SourceFile): DocEntry[] { + let entries: DocEntry[] = []; + + for (const statement of sourceFile.statements) { + // TODO(jelbourn): get all of rest of the docs + if (ts.isClassDeclaration(statement)) { + // Assume that anonymous classes should not be part of public documentation. + if (!statement.name) continue; + + entries = entries.concat(this.extractClassDocs(statement)); + } + } + + return entries; + } + + /** Extract docs info specific to classes. */ + private extractClassDocs(statement: ts.ClassDeclaration): DocEntry { + // TODO(jelbourn): get all of the rest of the docs + return {name: statement.name!.text}; + } +} diff --git a/packages/compiler-cli/src/ngtsc/program.ts b/packages/compiler-cli/src/ngtsc/program.ts index ba8d070d3bf..b98aa64c274 100644 --- a/packages/compiler-cli/src/ngtsc/program.ts +++ b/packages/compiler-cli/src/ngtsc/program.ts @@ -15,6 +15,7 @@ import {verifySupportedTypeScriptVersion} from '../typescript_support'; import {CompilationTicket, freshCompilationTicket, incrementalFromCompilerTicket, NgCompiler, NgCompilerHost} from './core'; import {NgCompilerOptions} from './core/api'; +import {DocEntry} from './docs'; import {absoluteFrom, AbsoluteFsPath, getFileSystem, resolve} from './file_system'; import {TrackedIncrementalBuildStrategy} from './incremental'; import {IndexedComponent} from './indexer'; @@ -347,6 +348,15 @@ export class NgtscProgram implements api.Program { return this.compiler.getIndexedComponents(); } + /** + * Gets information for the current program that may be used to generate API + * reference documentation. This includes Angular-specific information, such + * as component inputs and outputs. + */ + getApiDocumentation(): DocEntry[] { + return this.compiler.getApiDocumentation(); + } + getEmittedSourceFiles(): Map { throw new Error('Method not implemented.'); } diff --git a/packages/compiler-cli/test/ngtsc/BUILD.bazel b/packages/compiler-cli/test/ngtsc/BUILD.bazel index e9b55352512..f393ec9eb27 100644 --- a/packages/compiler-cli/test/ngtsc/BUILD.bazel +++ b/packages/compiler-cli/test/ngtsc/BUILD.bazel @@ -9,6 +9,7 @@ ts_library( "//packages/compiler-cli", "//packages/compiler-cli/src/ngtsc/core:api", "//packages/compiler-cli/src/ngtsc/diagnostics", + "//packages/compiler-cli/src/ngtsc/docs", "//packages/compiler-cli/src/ngtsc/file_system", "//packages/compiler-cli/src/ngtsc/file_system/testing", "//packages/compiler-cli/src/ngtsc/indexer", diff --git a/packages/compiler-cli/test/ngtsc/docs_spec.ts b/packages/compiler-cli/test/ngtsc/docs_spec.ts new file mode 100644 index 00000000000..4b937724aca --- /dev/null +++ b/packages/compiler-cli/test/ngtsc/docs_spec.ts @@ -0,0 +1,39 @@ +/** + * @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 {DocEntry} from '@angular/compiler-cli/src/ngtsc/docs'; +import {runInEachFileSystem} from '@angular/compiler-cli/src/ngtsc/file_system/testing'; +import {loadStandardTestFiles} from '@angular/compiler-cli/src/ngtsc/testing'; + +import {NgtscTestEnvironment} from './env'; + +const testFiles = loadStandardTestFiles({fakeCore: true, fakeCommon: true}); + +runInEachFileSystem(os => { + let env!: NgtscTestEnvironment; + + describe('ngtsc docs extraction', () => { + beforeEach(() => { + env = NgtscTestEnvironment.setup(testFiles); + env.tsconfig(); + }); + + it('should extract classes', () => { + env.write('test.ts', ` + class UserProfile {} + + class CustomSlider {} + `); + + const docs: DocEntry[] = env.driveDocsExtraction(); + expect(docs.length).toBe(2); + expect(docs[0].name).toBe('UserProfile'); + expect(docs[1].name).toBe('CustomSlider'); + }); + }); +}); diff --git a/packages/compiler-cli/test/ngtsc/env.ts b/packages/compiler-cli/test/ngtsc/env.ts index f2e2aec4bb8..d736a8f5fed 100644 --- a/packages/compiler-cli/test/ngtsc/env.ts +++ b/packages/compiler-cli/test/ngtsc/env.ts @@ -7,6 +7,7 @@ */ import {CustomTransformers, defaultGatherDiagnostics, Program} from '@angular/compiler-cli'; +import {DocEntry} from '@angular/compiler-cli/src/ngtsc/docs'; import * as api from '@angular/compiler-cli/src/transformers/api'; import ts from 'typescript'; @@ -286,6 +287,13 @@ export class NgtscTestEnvironment { return (program as NgtscProgram).getIndexedComponents(); } + driveDocsExtraction(): DocEntry[] { + const {rootNames, options} = readNgcCommandLineAndConfiguration(this.commandLineArgs); + const host = createCompilerHost({options}); + const program = createProgram({rootNames, host, options}); + return (program as NgtscProgram).getApiDocumentation(); + } + driveXi18n(format: string, outputFileName: string, locale: string|null = null): void { const errorSpy = jasmine.createSpy('consoleError').and.callFake(console.error); const args = [