2019-04-28 20:48:35 +01:00
|
|
|
/**
|
|
|
|
|
* @license
|
2020-05-19 12:08:49 -07:00
|
|
|
* Copyright Google LLC All Rights Reserved.
|
2019-04-28 20:48:35 +01:00
|
|
|
*
|
|
|
|
|
* 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 MagicString from 'magic-string';
|
|
|
|
|
import * as ts from 'typescript';
|
2020-04-06 08:30:08 +01:00
|
|
|
|
2019-06-06 20:22:32 +01:00
|
|
|
import {FileSystem} from '../../../src/ngtsc/file_system';
|
2019-10-14 13:04:42 -07:00
|
|
|
import {Reexport} from '../../../src/ngtsc/imports';
|
2019-06-06 20:22:32 +01:00
|
|
|
import {CompileResult} from '../../../src/ngtsc/transform';
|
2020-04-06 08:30:08 +01:00
|
|
|
import {ImportManager, translateType} from '../../../src/ngtsc/translator';
|
|
|
|
|
import {ModuleWithProvidersAnalyses, ModuleWithProvidersInfo} from '../analysis/module_with_providers_analyzer';
|
|
|
|
|
import {ExportInfo, PrivateDeclarationsAnalyses} from '../analysis/private_declarations_analyzer';
|
2019-07-18 21:05:32 +01:00
|
|
|
import {DecorationAnalyses} from '../analysis/types';
|
2019-04-28 20:48:35 +01:00
|
|
|
import {IMPORT_PREFIX} from '../constants';
|
|
|
|
|
import {NgccReflectionHost} from '../host/ngcc_host';
|
|
|
|
|
import {Logger} from '../logging/logger';
|
2020-04-06 08:30:08 +01:00
|
|
|
import {EntryPointBundle} from '../packages/entry_point_bundle';
|
|
|
|
|
|
2019-04-28 20:48:35 +01:00
|
|
|
import {RenderingFormatter} from './rendering_formatter';
|
2020-02-16 21:07:30 +01:00
|
|
|
import {renderSourceAndMap} from './source_maps';
|
2020-04-06 08:30:08 +01:00
|
|
|
import {FileToWrite, getImportRewriter} from './utils';
|
2019-04-28 20:48:35 +01:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A structure that captures information about what needs to be rendered
|
|
|
|
|
* in a typings file.
|
|
|
|
|
*
|
|
|
|
|
* It is created as a result of processing the analysis passed to the renderer.
|
|
|
|
|
*
|
|
|
|
|
* The `renderDtsFile()` method consumes it when rendering a typings file.
|
|
|
|
|
*/
|
|
|
|
|
class DtsRenderInfo {
|
|
|
|
|
classInfo: DtsClassInfo[] = [];
|
|
|
|
|
moduleWithProviders: ModuleWithProvidersInfo[] = [];
|
|
|
|
|
privateExports: ExportInfo[] = [];
|
2019-10-14 13:04:42 -07:00
|
|
|
reexports: Reexport[] = [];
|
2019-04-28 20:48:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Information about a class in a typings file.
|
|
|
|
|
*/
|
|
|
|
|
export interface DtsClassInfo {
|
|
|
|
|
dtsDeclaration: ts.Declaration;
|
|
|
|
|
compilation: CompileResult[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* A base-class for rendering an `AnalyzedFile`.
|
|
|
|
|
*
|
|
|
|
|
* Package formats have output files that must be rendered differently. Concrete sub-classes must
|
|
|
|
|
* implement the `addImports`, `addDefinitions` and `removeDecorators` abstract methods.
|
|
|
|
|
*/
|
|
|
|
|
export class DtsRenderer {
|
|
|
|
|
constructor(
|
|
|
|
|
private dtsFormatter: RenderingFormatter, private fs: FileSystem, private logger: Logger,
|
2019-05-25 20:38:33 +01:00
|
|
|
private host: NgccReflectionHost, private bundle: EntryPointBundle) {}
|
2019-04-28 20:48:35 +01:00
|
|
|
|
|
|
|
|
renderProgram(
|
|
|
|
|
decorationAnalyses: DecorationAnalyses,
|
|
|
|
|
privateDeclarationsAnalyses: PrivateDeclarationsAnalyses,
|
|
|
|
|
moduleWithProvidersAnalyses: ModuleWithProvidersAnalyses|null): FileToWrite[] {
|
|
|
|
|
const renderedFiles: FileToWrite[] = [];
|
|
|
|
|
|
|
|
|
|
// Transform the .d.ts files
|
|
|
|
|
if (this.bundle.dts) {
|
|
|
|
|
const dtsFiles = this.getTypingsFilesToRender(
|
|
|
|
|
decorationAnalyses, privateDeclarationsAnalyses, moduleWithProvidersAnalyses);
|
|
|
|
|
|
|
|
|
|
// If the dts entry-point is not already there (it did not have compiled classes)
|
|
|
|
|
// then add it now, to ensure it gets its extra exports rendered.
|
|
|
|
|
if (!dtsFiles.has(this.bundle.dts.file)) {
|
|
|
|
|
dtsFiles.set(this.bundle.dts.file, new DtsRenderInfo());
|
|
|
|
|
}
|
|
|
|
|
dtsFiles.forEach(
|
|
|
|
|
(renderInfo, file) => renderedFiles.push(...this.renderDtsFile(file, renderInfo)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return renderedFiles;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
renderDtsFile(dtsFile: ts.SourceFile, renderInfo: DtsRenderInfo): FileToWrite[] {
|
2020-02-16 21:07:30 +01:00
|
|
|
const outputText = new MagicString(dtsFile.text);
|
2019-04-28 20:48:35 +01:00
|
|
|
const printer = ts.createPrinter();
|
|
|
|
|
const importManager = new ImportManager(
|
2020-04-06 08:30:08 +01:00
|
|
|
getImportRewriter(this.bundle.dts!.r3SymbolsFile, this.bundle.isCore, false),
|
2019-05-25 20:38:33 +01:00
|
|
|
IMPORT_PREFIX);
|
2019-04-28 20:48:35 +01:00
|
|
|
|
|
|
|
|
renderInfo.classInfo.forEach(dtsClass => {
|
|
|
|
|
const endOfClass = dtsClass.dtsDeclaration.getEnd();
|
|
|
|
|
dtsClass.compilation.forEach(declaration => {
|
|
|
|
|
const type = translateType(declaration.type, importManager);
|
feat(compiler): add dependency info and ng-content selectors to metadata (#35695)
This commit augments the `FactoryDef` declaration of Angular decorated
classes to contain information about the parameter decorators used in
the constructor. If no constructor is present, or none of the parameters
have any Angular decorators, then this will be represented using the
`null` type. Otherwise, a tuple type is used where the entry at index `i`
corresponds with parameter `i`. Each tuple entry can be one of two types:
1. If the associated parameter does not have any Angular decorators,
the tuple entry will be the `null` type.
2. Otherwise, a type literal is used that may declare at least one of
the following properties:
- "attribute": if `@Attribute` is present. The injected attribute's
name is used as string literal type, or the `unknown` type if the
attribute name is not a string literal.
- "self": if `@Self` is present, always of type `true`.
- "skipSelf": if `@SkipSelf` is present, always of type `true`.
- "host": if `@Host` is present, always of type `true`.
- "optional": if `@Optional` is present, always of type `true`.
A property is only present if the corresponding decorator is used.
Note that the `@Inject` decorator is currently not included, as it's
non-trivial to properly convert the token's value expression to a
type that is valid in a declaration file.
Additionally, the `ComponentDefWithMeta` declaration that is created for
Angular components has been extended to include all selectors on
`ng-content` elements within the component's template.
This additional metadata is useful for tooling such as the Angular
Language Service, as it provides the ability to offer suggestions for
directives/components defined in libraries. At the moment, such
tooling extracts the necessary information from the _metadata.json_
manifest file as generated by ngc, however this metadata representation
is being replaced by the information emitted into the declaration files.
Resolves FW-1870
PR Close #35695
2020-02-26 22:05:44 +01:00
|
|
|
markForEmitAsSingleLine(type);
|
2019-04-28 20:48:35 +01:00
|
|
|
const typeStr = printer.printNode(ts.EmitHint.Unspecified, type, dtsFile);
|
|
|
|
|
const newStatement = ` static ${declaration.name}: ${typeStr};\n`;
|
|
|
|
|
outputText.appendRight(endOfClass - 1, newStatement);
|
|
|
|
|
});
|
2019-11-07 19:34:40 +01:00
|
|
|
});
|
2019-10-14 13:04:42 -07:00
|
|
|
|
2019-11-07 19:34:40 +01:00
|
|
|
if (renderInfo.reexports.length > 0) {
|
|
|
|
|
for (const e of renderInfo.reexports) {
|
|
|
|
|
const newStatement = `\nexport {${e.symbolName} as ${e.asAlias}} from '${e.fromModule}';`;
|
|
|
|
|
outputText.append(newStatement);
|
2019-10-14 13:04:42 -07:00
|
|
|
}
|
2019-11-07 19:34:40 +01:00
|
|
|
}
|
2019-04-28 20:48:35 +01:00
|
|
|
|
|
|
|
|
this.dtsFormatter.addModuleWithProvidersParams(
|
|
|
|
|
outputText, renderInfo.moduleWithProviders, importManager);
|
|
|
|
|
this.dtsFormatter.addExports(
|
|
|
|
|
outputText, dtsFile.fileName, renderInfo.privateExports, importManager, dtsFile);
|
|
|
|
|
this.dtsFormatter.addImports(
|
|
|
|
|
outputText, importManager.getAllImports(dtsFile.fileName), dtsFile);
|
|
|
|
|
|
2020-02-27 19:50:14 +00:00
|
|
|
return renderSourceAndMap(this.logger, this.fs, dtsFile, outputText);
|
2019-04-28 20:48:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private getTypingsFilesToRender(
|
|
|
|
|
decorationAnalyses: DecorationAnalyses,
|
|
|
|
|
privateDeclarationsAnalyses: PrivateDeclarationsAnalyses,
|
|
|
|
|
moduleWithProvidersAnalyses: ModuleWithProvidersAnalyses|
|
|
|
|
|
null): Map<ts.SourceFile, DtsRenderInfo> {
|
|
|
|
|
const dtsMap = new Map<ts.SourceFile, DtsRenderInfo>();
|
|
|
|
|
|
|
|
|
|
// Capture the rendering info from the decoration analyses
|
|
|
|
|
decorationAnalyses.forEach(compiledFile => {
|
2020-01-06 23:12:19 +01:00
|
|
|
let appliedReexports = false;
|
2019-04-28 20:48:35 +01:00
|
|
|
compiledFile.compiledClasses.forEach(compiledClass => {
|
|
|
|
|
const dtsDeclaration = this.host.getDtsDeclaration(compiledClass.declaration);
|
|
|
|
|
if (dtsDeclaration) {
|
|
|
|
|
const dtsFile = dtsDeclaration.getSourceFile();
|
2020-04-06 08:30:08 +01:00
|
|
|
const renderInfo = dtsMap.has(dtsFile) ? dtsMap.get(dtsFile)! : new DtsRenderInfo();
|
2019-04-28 20:48:35 +01:00
|
|
|
renderInfo.classInfo.push({dtsDeclaration, compilation: compiledClass.compilation});
|
2019-10-14 13:04:42 -07:00
|
|
|
// Only add re-exports if the .d.ts tree is overlayed with the .js tree, as re-exports in
|
|
|
|
|
// ngcc are only used to support deep imports into e.g. commonjs code. For a deep import
|
|
|
|
|
// to work, the typing file and JS file must be in parallel trees. This logic will detect
|
|
|
|
|
// the simplest version of this case, which is sufficient to handle most commonjs
|
|
|
|
|
// libraries.
|
2020-01-06 23:12:19 +01:00
|
|
|
if (!appliedReexports &&
|
|
|
|
|
compiledClass.declaration.getSourceFile().fileName ===
|
|
|
|
|
dtsFile.fileName.replace(/\.d\.ts$/, '.js')) {
|
|
|
|
|
renderInfo.reexports.push(...compiledFile.reexports);
|
|
|
|
|
appliedReexports = true;
|
2019-10-14 13:04:42 -07:00
|
|
|
}
|
2019-04-28 20:48:35 +01:00
|
|
|
dtsMap.set(dtsFile, renderInfo);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Capture the ModuleWithProviders functions/methods that need updating
|
|
|
|
|
if (moduleWithProvidersAnalyses !== null) {
|
|
|
|
|
moduleWithProvidersAnalyses.forEach((moduleWithProvidersToFix, dtsFile) => {
|
2020-04-06 08:30:08 +01:00
|
|
|
const renderInfo = dtsMap.has(dtsFile) ? dtsMap.get(dtsFile)! : new DtsRenderInfo();
|
2019-04-28 20:48:35 +01:00
|
|
|
renderInfo.moduleWithProviders = moduleWithProvidersToFix;
|
|
|
|
|
dtsMap.set(dtsFile, renderInfo);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Capture the private declarations that need to be re-exported
|
|
|
|
|
if (privateDeclarationsAnalyses.length) {
|
|
|
|
|
privateDeclarationsAnalyses.forEach(e => {
|
2019-12-18 14:03:05 +00:00
|
|
|
if (!e.dtsFrom) {
|
2019-04-28 20:48:35 +01:00
|
|
|
throw new Error(
|
|
|
|
|
`There is no typings path for ${e.identifier} in ${e.from}.\n` +
|
|
|
|
|
`We need to add an export for this class to a .d.ts typings file because ` +
|
|
|
|
|
`Angular compiler needs to be able to reference this class in compiled code, such as templates.\n` +
|
|
|
|
|
`The simplest fix for this is to ensure that this class is exported from the package's entry-point.`);
|
|
|
|
|
}
|
|
|
|
|
});
|
2020-04-06 08:30:08 +01:00
|
|
|
const dtsEntryPoint = this.bundle.dts!.file;
|
2019-04-28 20:48:35 +01:00
|
|
|
const renderInfo =
|
2020-04-06 08:30:08 +01:00
|
|
|
dtsMap.has(dtsEntryPoint) ? dtsMap.get(dtsEntryPoint)! : new DtsRenderInfo();
|
2019-04-28 20:48:35 +01:00
|
|
|
renderInfo.privateExports = privateDeclarationsAnalyses;
|
|
|
|
|
dtsMap.set(dtsEntryPoint, renderInfo);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return dtsMap;
|
|
|
|
|
}
|
|
|
|
|
}
|
feat(compiler): add dependency info and ng-content selectors to metadata (#35695)
This commit augments the `FactoryDef` declaration of Angular decorated
classes to contain information about the parameter decorators used in
the constructor. If no constructor is present, or none of the parameters
have any Angular decorators, then this will be represented using the
`null` type. Otherwise, a tuple type is used where the entry at index `i`
corresponds with parameter `i`. Each tuple entry can be one of two types:
1. If the associated parameter does not have any Angular decorators,
the tuple entry will be the `null` type.
2. Otherwise, a type literal is used that may declare at least one of
the following properties:
- "attribute": if `@Attribute` is present. The injected attribute's
name is used as string literal type, or the `unknown` type if the
attribute name is not a string literal.
- "self": if `@Self` is present, always of type `true`.
- "skipSelf": if `@SkipSelf` is present, always of type `true`.
- "host": if `@Host` is present, always of type `true`.
- "optional": if `@Optional` is present, always of type `true`.
A property is only present if the corresponding decorator is used.
Note that the `@Inject` decorator is currently not included, as it's
non-trivial to properly convert the token's value expression to a
type that is valid in a declaration file.
Additionally, the `ComponentDefWithMeta` declaration that is created for
Angular components has been extended to include all selectors on
`ng-content` elements within the component's template.
This additional metadata is useful for tooling such as the Angular
Language Service, as it provides the ability to offer suggestions for
directives/components defined in libraries. At the moment, such
tooling extracts the necessary information from the _metadata.json_
manifest file as generated by ngc, however this metadata representation
is being replaced by the information emitted into the declaration files.
Resolves FW-1870
PR Close #35695
2020-02-26 22:05:44 +01:00
|
|
|
|
|
|
|
|
function markForEmitAsSingleLine(node: ts.Node) {
|
|
|
|
|
ts.setEmitFlags(node, ts.EmitFlags.SingleLine);
|
|
|
|
|
ts.forEachChild(node, markForEmitAsSingleLine);
|
|
|
|
|
}
|