mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
refactor(compiler-cli): Add a map of every symbols used inside a package (#57346)
This commit changes the structure of the API extraction files to include all symbols used inside a package. The structure is a `Map`, Symbol => package eg: 'ApplicationRef' => '@angular/core' PR Close #57346
This commit is contained in:
committed by
Andrew Kushnir
parent
93bdbbc812
commit
9814767d34
@@ -902,7 +902,7 @@ export class NgCompiler {
|
||||
* @param entryPoint Path to the entry point for the package for which API
|
||||
* docs should be extracted.
|
||||
*/
|
||||
getApiDocumentation(entryPoint: string): DocEntry[] {
|
||||
getApiDocumentation(entryPoint: string): {entries: DocEntry[]; symbols: Map<string, string>} {
|
||||
const compilation = this.ensureAnalyzed();
|
||||
const checker = this.inputProgram.getTypeChecker();
|
||||
const docsExtractor = new DocsExtractor(checker, compilation.metaReader);
|
||||
@@ -918,7 +918,7 @@ export class NgCompiler {
|
||||
}
|
||||
|
||||
// TODO: Technically the current directory is not the root dir.
|
||||
// Should probably be derived from the config.
|
||||
// Should probably be derived from the config.
|
||||
const rootDir = this.inputProgram.getCurrentDirectory();
|
||||
return docsExtractor.extractAll(entryPointSourceFile, rootDir);
|
||||
}
|
||||
|
||||
@@ -27,9 +27,13 @@ import {
|
||||
isInitializerApiFunction,
|
||||
} from './initializer_api_function_extractor';
|
||||
import {extractTypeAlias} from './type_alias_extractor';
|
||||
import {getImportedSymbols} from './import_extractor';
|
||||
|
||||
type DeclarationWithExportName = readonly [string, ts.Declaration];
|
||||
|
||||
/** The modules here doesn't expose symbols publicly as they are considered private */
|
||||
const privateModules = new Set(['core/primitives/signals', 'core/primitives/event-dispatch']);
|
||||
|
||||
/**
|
||||
* Extracts all information from a source file that may be relevant for generating
|
||||
* public API documentation.
|
||||
@@ -46,8 +50,12 @@ export class DocsExtractor {
|
||||
*
|
||||
* @param sourceFile The file from which to extract documentable entries.
|
||||
*/
|
||||
extractAll(sourceFile: ts.SourceFile, rootDir: string): DocEntry[] {
|
||||
extractAll(
|
||||
sourceFile: ts.SourceFile,
|
||||
rootDir: string,
|
||||
): {entries: DocEntry[]; symbols: Map<string, string>} {
|
||||
const entries: DocEntry[] = [];
|
||||
const symbols = new Map<string, string>();
|
||||
|
||||
const exportedDeclarations = this.getExportedDeclarations(sourceFile);
|
||||
for (const [exportName, node] of exportedDeclarations) {
|
||||
@@ -62,6 +70,20 @@ export class DocsExtractor {
|
||||
// We want the real source file of the declaration.
|
||||
const realSourceFile = node.getSourceFile();
|
||||
|
||||
/**
|
||||
* `sourceFile` is generaly a `export * from './public_api';` with no imports.
|
||||
* It is necessary to pick-up every import from the real source files.
|
||||
* This allows to include symbols from other packages (like @angular/core)
|
||||
* By doing this, the generation remains independant from other packages
|
||||
*/
|
||||
const importedSymbols = getImportedSymbols(realSourceFile);
|
||||
importedSymbols.forEach((moduleName, symbolName) => {
|
||||
if (privateModules.has(moduleName) || symbolName.startsWith('ɵ')) {
|
||||
return;
|
||||
}
|
||||
symbols.set(symbolName, moduleName);
|
||||
});
|
||||
|
||||
// Set the source code references for the extracted entry.
|
||||
(entry as DocEntryWithSourceInfo).source = {
|
||||
filePath: getRelativeFilePath(realSourceFile, rootDir),
|
||||
@@ -77,7 +99,7 @@ export class DocsExtractor {
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
return {entries, symbols};
|
||||
}
|
||||
|
||||
/** Extract the doc entry for a single declaration. */
|
||||
|
||||
@@ -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';
|
||||
|
||||
/**
|
||||
* For a given SourceFile, it extracts all imported symbols from other Angular packages.
|
||||
*
|
||||
* @returns a map Symbol => Package, eg: ApplicationRef => @angular/core
|
||||
*/
|
||||
export function getImportedSymbols(sourceFile: ts.SourceFile): Map<string, string> {
|
||||
const importSpecifiers = new Map<string, string>();
|
||||
|
||||
function visit(node: ts.Node) {
|
||||
if (ts.isImportDeclaration(node)) {
|
||||
let moduleSpecifier = node.moduleSpecifier.getText(sourceFile).replace(/['"]/g, '');
|
||||
|
||||
if (moduleSpecifier.startsWith('@angular')) {
|
||||
const namedBindings = node.importClause?.namedBindings;
|
||||
|
||||
if (namedBindings && ts.isNamedImports(namedBindings)) {
|
||||
namedBindings.elements.forEach((importSpecifier) => {
|
||||
const importName = importSpecifier.name.text;
|
||||
const importAlias = importSpecifier.propertyName
|
||||
? importSpecifier.propertyName.text
|
||||
: undefined;
|
||||
|
||||
importSpecifiers.set(importAlias ?? importName, moduleSpecifier);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ts.forEachChild(node, visit);
|
||||
}
|
||||
|
||||
visit(sourceFile);
|
||||
|
||||
return importSpecifiers;
|
||||
}
|
||||
@@ -399,7 +399,7 @@ export class NgtscProgram implements api.Program {
|
||||
* @param entryPoint Path to the entry point for the package for which API
|
||||
* docs should be extracted.
|
||||
*/
|
||||
getApiDocumentation(entryPoint: string): DocEntry[] {
|
||||
getApiDocumentation(entryPoint: string): {entries: DocEntry[]; symbols: Map<string, string>} {
|
||||
return this.compiler.getApiDocumentation(entryPoint);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* @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 {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({fakeCommon: true});
|
||||
|
||||
runInEachFileSystem(() => {
|
||||
let env!: NgtscTestEnvironment;
|
||||
|
||||
describe('ngtsc function docs extraction', () => {
|
||||
beforeEach(() => {
|
||||
env = NgtscTestEnvironment.setup(testFiles);
|
||||
env.tsconfig();
|
||||
});
|
||||
|
||||
it('should extract imported symbols from other angular packages', () => {
|
||||
env.write(
|
||||
'index.ts',
|
||||
`
|
||||
import {ApplicationRef} from '@angular/core';
|
||||
import {FormGroup} from '@angular/forms';
|
||||
|
||||
export function getApp(): ApplicationRef {
|
||||
}
|
||||
|
||||
export function getForm(): FormGroup {
|
||||
}
|
||||
`,
|
||||
);
|
||||
|
||||
const symbols = env.driveDocsExtractionForSymbols('index.ts');
|
||||
|
||||
expect(symbols.size).toBe(2);
|
||||
expect(symbols.get('ApplicationRef')).toBe('@angular/core');
|
||||
expect(symbols.get('FormGroup')).toBe('@angular/forms');
|
||||
});
|
||||
|
||||
it('should not extract imported symbols from non angular packages', () => {
|
||||
env.write(
|
||||
'index.ts',
|
||||
`
|
||||
import {ApplicationRef} from '@not-angular/core';
|
||||
import {FormGroup} from '@angular/forms';
|
||||
|
||||
export function getApp(): ApplicationRef {
|
||||
}
|
||||
|
||||
export function getForm(): FormGroup {
|
||||
}
|
||||
`,
|
||||
);
|
||||
|
||||
const symbols = env.driveDocsExtractionForSymbols('index.ts');
|
||||
|
||||
expect(symbols.size).toBe(1);
|
||||
expect(symbols.get('FormGroup')).toBe('@angular/forms');
|
||||
});
|
||||
|
||||
it('should not extract private symbols', () => {
|
||||
env.write(
|
||||
'index.ts',
|
||||
`
|
||||
import {ɵSafeHtml} from '@angular/core';
|
||||
import {FormGroup} from '@angular/forms';
|
||||
|
||||
export function getApp(): ApplicationRef {
|
||||
}
|
||||
|
||||
export function getForm(): FormGroup {
|
||||
}
|
||||
`,
|
||||
);
|
||||
|
||||
const symbols = env.driveDocsExtractionForSymbols('index.ts');
|
||||
|
||||
expect(symbols.size).toBe(1);
|
||||
expect(symbols.get('FormGroup')).toBe('@angular/forms');
|
||||
});
|
||||
|
||||
it('should not extract symbols from private packages', () => {
|
||||
env.write(
|
||||
'index.ts',
|
||||
`
|
||||
import {REACTIVE_NODE} from '@core/primitives/signals';
|
||||
import {FormGroup} from '@angular/forms';
|
||||
|
||||
export function getApp(): ApplicationRef {
|
||||
}
|
||||
|
||||
export function getForm(): FormGroup {
|
||||
}
|
||||
`,
|
||||
);
|
||||
|
||||
const symbols = env.driveDocsExtractionForSymbols('index.ts');
|
||||
|
||||
expect(symbols.size).toBe(1);
|
||||
expect(symbols.get('FormGroup')).toBe('@angular/forms');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -321,7 +321,14 @@ export class NgtscTestEnvironment {
|
||||
const {rootNames, options} = readNgcCommandLineAndConfiguration(this.commandLineArgs);
|
||||
const host = createCompilerHost({options});
|
||||
const program = createProgram({rootNames, host, options});
|
||||
return (program as NgtscProgram).getApiDocumentation(entryPoint);
|
||||
return (program as NgtscProgram).getApiDocumentation(entryPoint).entries;
|
||||
}
|
||||
|
||||
driveDocsExtractionForSymbols(entryPoint: string): Map<string, string> {
|
||||
const {rootNames, options} = readNgcCommandLineAndConfiguration(this.commandLineArgs);
|
||||
const host = createCompilerHost({options});
|
||||
const program = createProgram({rootNames, host, options});
|
||||
return (program as NgtscProgram).getApiDocumentation(entryPoint).symbols;
|
||||
}
|
||||
|
||||
driveXi18n(format: string, outputFileName: string, locale: string | null = null): void {
|
||||
|
||||
Reference in New Issue
Block a user