187 lines
7.7 KiB
TypeScript
Raw Normal View History

/**
* @license
* Copyright Google Inc. 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 MagicString from 'magic-string';
import * as ts from 'typescript';
refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921) To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
2019-06-06 20:22:32 +01:00
import {FileSystem} from '../../../src/ngtsc/file_system';
import {Reexport} from '../../../src/ngtsc/imports';
refactor(ivy): implement a virtual file-system layer in ngtsc + ngcc (#30921) To improve cross platform support, all file access (and path manipulation) is now done through a well known interface (`FileSystem`). For testing a number of `MockFileSystem` implementations are provided. These provide an in-memory file-system which emulates operating systems like OS/X, Unix and Windows. The current file system is always available via the static method, `FileSystem.getFileSystem()`. This is also used by a number of static methods on `AbsoluteFsPath` and `PathSegment`, to avoid having to pass `FileSystem` objects around all the time. The result of this is that one must be careful to ensure that the file-system has been initialized before using any of these static methods. To prevent this happening accidentally the current file system always starts out as an instance of `InvalidFileSystem`, which will throw an error if any of its methods are called. You can set the current file-system by calling `FileSystem.setFileSystem()`. During testing you can call the helper function `initMockFileSystem(os)` which takes a string name of the OS to emulate, and will also monkey-patch aspects of the TypeScript library to ensure that TS is also using the current file-system. Finally there is the `NgtscCompilerHost` to be used for any TypeScript compilation, which uses a given file-system. All tests that interact with the file-system should be tested against each of the mock file-systems. A series of helpers have been provided to support such tests: * `runInEachFileSystem()` - wrap your tests in this helper to run all the wrapped tests in each of the mock file-systems. * `addTestFilesToFileSystem()` - use this to add files and their contents to the mock file system for testing. * `loadTestFilesFromDisk()` - use this to load a mirror image of files on disk into the in-memory mock file-system. * `loadFakeCore()` - use this to load a fake version of `@angular/core` into the mock file-system. All ngcc and ngtsc source and tests now use this virtual file-system setup. PR Close #30921
2019-06-06 20:22:32 +01:00
import {CompileResult} from '../../../src/ngtsc/transform';
import {ImportManager, translateType} from '../../../src/ngtsc/translator';
import {ModuleWithProvidersAnalyses, ModuleWithProvidersInfo} from '../analysis/module_with_providers_analyzer';
import {ExportInfo, PrivateDeclarationsAnalyses} from '../analysis/private_declarations_analyzer';
import {DecorationAnalyses} from '../analysis/types';
import {IMPORT_PREFIX} from '../constants';
import {NgccReflectionHost} from '../host/ngcc_host';
import {Logger} from '../logging/logger';
import {EntryPointBundle} from '../packages/entry_point_bundle';
import {RenderingFormatter} from './rendering_formatter';
import {renderSourceAndMap} from './source_maps';
import {FileToWrite, getImportRewriter} from './utils';
/**
* 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[] = [];
reexports: Reexport[] = [];
}
/**
* 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,
private host: NgccReflectionHost, private bundle: EntryPointBundle) {}
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[] {
const outputText = new MagicString(dtsFile.text);
const printer = ts.createPrinter();
const importManager = new ImportManager(
getImportRewriter(this.bundle.dts!.r3SymbolsFile, this.bundle.isCore, false),
IMPORT_PREFIX);
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);
const typeStr = printer.printNode(ts.EmitHint.Unspecified, type, dtsFile);
const newStatement = ` static ${declaration.name}: ${typeStr};\n`;
outputText.appendRight(endOfClass - 1, newStatement);
});
});
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);
}
}
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);
return renderSourceAndMap(this.logger, this.fs, dtsFile, outputText);
}
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 => {
let appliedReexports = false;
compiledFile.compiledClasses.forEach(compiledClass => {
const dtsDeclaration = this.host.getDtsDeclaration(compiledClass.declaration);
if (dtsDeclaration) {
const dtsFile = dtsDeclaration.getSourceFile();
const renderInfo = dtsMap.has(dtsFile) ? dtsMap.get(dtsFile)! : new DtsRenderInfo();
renderInfo.classInfo.push({dtsDeclaration, compilation: compiledClass.compilation});
// 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.
if (!appliedReexports &&
compiledClass.declaration.getSourceFile().fileName ===
dtsFile.fileName.replace(/\.d\.ts$/, '.js')) {
renderInfo.reexports.push(...compiledFile.reexports);
appliedReexports = true;
}
dtsMap.set(dtsFile, renderInfo);
}
});
});
// Capture the ModuleWithProviders functions/methods that need updating
if (moduleWithProvidersAnalyses !== null) {
moduleWithProvidersAnalyses.forEach((moduleWithProvidersToFix, dtsFile) => {
const renderInfo = dtsMap.has(dtsFile) ? dtsMap.get(dtsFile)! : new DtsRenderInfo();
renderInfo.moduleWithProviders = moduleWithProvidersToFix;
dtsMap.set(dtsFile, renderInfo);
});
}
// Capture the private declarations that need to be re-exported
if (privateDeclarationsAnalyses.length) {
privateDeclarationsAnalyses.forEach(e => {
if (!e.dtsFrom) {
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.`);
}
});
const dtsEntryPoint = this.bundle.dts!.file;
const renderInfo =
dtsMap.has(dtsEntryPoint) ? dtsMap.get(dtsEntryPoint)! : new DtsRenderInfo();
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);
}