从官方版本合并到本地
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
load("//tools:defaults.bzl", "api_golden_test", "pkg_npm", "ts_config", "ts_library")
|
||||
|
||||
load("//tools:defaults.bzl", "pkg_npm", "ts_api_guardian_test", "ts_config", "ts_library")
|
||||
# Load ng_perf_flag explicitly from ng_perf.bzl as it's private API, and not exposed to other
|
||||
# consumers of @angular/bazel.
|
||||
load("//packages/bazel/src:ng_perf.bzl", "ng_perf_flag")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
# Load ng_perf_flag explicitly from ng_perf.bzl as it's private API, and not exposed to other
|
||||
# consumers of @angular/bazel.
|
||||
@@ -33,6 +37,7 @@ ts_library(
|
||||
"//packages/compiler-cli/src/ngtsc/incremental",
|
||||
"//packages/compiler-cli/src/ngtsc/indexer",
|
||||
"//packages/compiler-cli/src/ngtsc/perf",
|
||||
"//packages/compiler-cli/src/ngtsc/program_driver",
|
||||
"//packages/compiler-cli/src/ngtsc/reflection",
|
||||
"//packages/compiler-cli/src/ngtsc/shims",
|
||||
"//packages/compiler-cli/src/ngtsc/translator",
|
||||
@@ -41,7 +46,6 @@ ts_library(
|
||||
"@npm//@bazel/typescript",
|
||||
"@npm//@types/node",
|
||||
"@npm//chokidar",
|
||||
"@npm//fs-extra",
|
||||
"@npm//minimist",
|
||||
"@npm//reflect-metadata",
|
||||
"@npm//tsickle",
|
||||
@@ -73,24 +77,30 @@ pkg_npm(
|
||||
],
|
||||
)
|
||||
|
||||
ts_api_guardian_test(
|
||||
api_golden_test(
|
||||
name = "error_code_api",
|
||||
actual = "angular/packages/compiler-cli/npm_package/src/ngtsc/diagnostics/src/error_code.d.ts",
|
||||
data = [
|
||||
":npm_package",
|
||||
"//goldens:public-api",
|
||||
],
|
||||
golden = "angular/goldens/public-api/compiler-cli/error_code.d.ts",
|
||||
entry_point = "angular/packages/compiler-cli/npm_package/src/ngtsc/diagnostics/src/error_code.d.ts",
|
||||
golden = "angular/goldens/public-api/compiler-cli/error_code.md",
|
||||
)
|
||||
|
||||
ts_api_guardian_test(
|
||||
api_golden_test(
|
||||
name = "compiler_options_api",
|
||||
actual = "angular/packages/compiler-cli/npm_package/src/ngtsc/core/api/src/public_options.d.ts",
|
||||
data = [
|
||||
":npm_package",
|
||||
"//goldens:public-api",
|
||||
],
|
||||
golden = "angular/goldens/public-api/compiler-cli/compiler_options.d.ts",
|
||||
entry_point = "angular/packages/compiler-cli/npm_package/src/ngtsc/core/api/src/public_options.d.ts",
|
||||
golden = "angular/goldens/public-api/compiler-cli/compiler_options.md",
|
||||
)
|
||||
|
||||
# Controls whether the Ivy compiler produces performance traces as part of each build
|
||||
ng_perf_flag(
|
||||
name = "ng_perf",
|
||||
build_setting_default = False,
|
||||
)
|
||||
|
||||
# Controls whether the Ivy compiler produces performance traces as part of each build
|
||||
|
||||
@@ -31,7 +31,6 @@ nodejs_test(
|
||||
"@nodejs//:node",
|
||||
"@npm//domino",
|
||||
"@npm//chokidar",
|
||||
"@npm//fs-extra",
|
||||
"@npm//source-map-support",
|
||||
"@npm//shelljs",
|
||||
"@npm//typescript",
|
||||
|
||||
@@ -46,7 +46,6 @@ const requiredNodeModules = {
|
||||
'tslib': resolveNpmTreeArtifact('npm/node_modules/tslib'),
|
||||
'domino': resolveNpmTreeArtifact('npm/node_modules/domino'),
|
||||
'xhr2': resolveNpmTreeArtifact('npm/node_modules/xhr2'),
|
||||
'fs-extra': resolveNpmTreeArtifact('npm/node_modules/fs-extra'),
|
||||
|
||||
// Fine grained dependencies which are used by the integration test Angular modules, and
|
||||
// need to be symlinked so that they can be resolved by NodeJS or NGC.
|
||||
|
||||
@@ -5,4 +5,7 @@
|
||||
* 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 {createEs2015LinkerPlugin} from './src/es2015_linker_plugin';
|
||||
import {defaultLinkerPlugin} from './src/babel_plugin';
|
||||
|
||||
export {createEs2015LinkerPlugin} from './src/es2015_linker_plugin';
|
||||
export default defaultLinkerPlugin;
|
||||
|
||||
@@ -39,6 +39,7 @@ export class BabelAstFactory implements AstFactory<t.Statement, t.Expression> {
|
||||
switch (operator) {
|
||||
case '&&':
|
||||
case '||':
|
||||
case '??':
|
||||
return t.logicalExpression(operator, leftOperand, rightOperand);
|
||||
default:
|
||||
return t.binaryExpression(operator, leftOperand, rightOperand);
|
||||
|
||||
@@ -38,11 +38,18 @@ export class BabelAstHost implements AstHost<t.Expression> {
|
||||
return num.value;
|
||||
}
|
||||
|
||||
isBooleanLiteral = t.isBooleanLiteral;
|
||||
isBooleanLiteral(bool: t.Expression): boolean {
|
||||
return t.isBooleanLiteral(bool) || isMinifiedBooleanLiteral(bool);
|
||||
}
|
||||
|
||||
parseBooleanLiteral(bool: t.Expression): boolean {
|
||||
assert(bool, t.isBooleanLiteral, 'a boolean literal');
|
||||
return bool.value;
|
||||
if (t.isBooleanLiteral(bool)) {
|
||||
return bool.value;
|
||||
} else if (isMinifiedBooleanLiteral(bool)) {
|
||||
return !bool.argument.value;
|
||||
} else {
|
||||
throw new FatalLinkerError(bool, 'Unsupported syntax, expected a boolean literal.');
|
||||
}
|
||||
}
|
||||
|
||||
isArrayLiteral = t.isArrayExpression;
|
||||
@@ -165,3 +172,13 @@ type ArgumentType = t.CallExpression['arguments'][number];
|
||||
function isNotSpreadArgument(arg: ArgumentType): arg is Exclude<ArgumentType, t.SpreadElement> {
|
||||
return !t.isSpreadElement(arg);
|
||||
}
|
||||
|
||||
type MinifiedBooleanLiteral = t.Expression&t.UnaryExpression&{argument: t.NumericLiteral};
|
||||
|
||||
/**
|
||||
* Return true if the node is either `!0` or `!1`.
|
||||
*/
|
||||
function isMinifiedBooleanLiteral(node: t.Expression): node is MinifiedBooleanLiteral {
|
||||
return t.isUnaryExpression(node) && node.prefix && node.operator === '!' &&
|
||||
t.isNumericLiteral(node.argument) && (node.argument.value === 0 || node.argument.value === 1);
|
||||
}
|
||||
|
||||
@@ -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 {ConfigAPI, PluginObj} from '@babel/core';
|
||||
|
||||
import {NodeJSFileSystem} from '../../../src/ngtsc/file_system';
|
||||
import {ConsoleLogger, LogLevel} from '../../../src/ngtsc/logging';
|
||||
import {LinkerOptions} from '../../src/file_linker/linker_options';
|
||||
|
||||
import {createEs2015LinkerPlugin} from './es2015_linker_plugin';
|
||||
|
||||
/**
|
||||
* This is the Babel plugin definition that is provided as a default export from the package, such
|
||||
* that the plugin can be used using the module specifier of the package. This is the recommended
|
||||
* way of integrating the Angular Linker into a build pipeline other than the Angular CLI.
|
||||
*
|
||||
* When the module specifier `@angular/compiler-cli/linker/babel` is used as a plugin in a Babel
|
||||
* configuration, Babel invokes this function (by means of the default export) to create the plugin
|
||||
* instance according to the provided options.
|
||||
*
|
||||
* The linker plugin that is created uses the native NodeJS filesystem APIs to interact with the
|
||||
* filesystem. Any logging output is printed to the console.
|
||||
*
|
||||
* @param api Provides access to the Babel environment that is configuring this plugin.
|
||||
* @param options The plugin options that have been configured.
|
||||
*/
|
||||
export function defaultLinkerPlugin(api: ConfigAPI, options: Partial<LinkerOptions>): PluginObj {
|
||||
api.assertVersion(7);
|
||||
|
||||
return createEs2015LinkerPlugin({
|
||||
...options,
|
||||
fileSystem: new NodeJSFileSystem(),
|
||||
logger: new ConsoleLogger(LogLevel.info),
|
||||
});
|
||||
}
|
||||
@@ -139,6 +139,8 @@ function getCalleeName(call: NodePath<t.CallExpression>): string|null {
|
||||
return callee.name;
|
||||
} else if (t.isMemberExpression(callee) && t.isIdentifier(callee.property)) {
|
||||
return callee.property.name;
|
||||
} else if (t.isMemberExpression(callee) && t.isStringLiteral(callee.property)) {
|
||||
return callee.property.value;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -96,6 +96,11 @@ describe('BabelAstHost', () => {
|
||||
expect(host.isBooleanLiteral(expr('false'))).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true if the expression is a minified boolean literal', () => {
|
||||
expect(host.isBooleanLiteral(expr('!0'))).toBe(true);
|
||||
expect(host.isBooleanLiteral(expr('!1'))).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if the expression is not a boolean literal', () => {
|
||||
expect(host.isBooleanLiteral(expr('"moo"'))).toBe(false);
|
||||
expect(host.isBooleanLiteral(expr('\'moo\''))).toBe(false);
|
||||
@@ -106,6 +111,8 @@ describe('BabelAstHost', () => {
|
||||
expect(host.isBooleanLiteral(expr('null'))).toBe(false);
|
||||
expect(host.isBooleanLiteral(expr('\'a\' + \'b\''))).toBe(false);
|
||||
expect(host.isBooleanLiteral(expr('\`moo\`'))).toBe(false);
|
||||
expect(host.isBooleanLiteral(expr('!2'))).toBe(false);
|
||||
expect(host.isBooleanLiteral(expr('~1'))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -115,6 +122,11 @@ describe('BabelAstHost', () => {
|
||||
expect(host.parseBooleanLiteral(expr('false'))).toEqual(false);
|
||||
});
|
||||
|
||||
it('should extract a minified boolean value', () => {
|
||||
expect(host.parseBooleanLiteral(expr('!0'))).toEqual(true);
|
||||
expect(host.parseBooleanLiteral(expr('!1'))).toEqual(false);
|
||||
});
|
||||
|
||||
it('should error if the value is not a boolean literal', () => {
|
||||
expect(() => host.parseBooleanLiteral(expr('"moo"')))
|
||||
.toThrowError('Unsupported syntax, expected a boolean literal.');
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* @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 {transformSync} from '@babel/core';
|
||||
|
||||
describe('default babel plugin entry-point', () => {
|
||||
it('should work as a Babel plugin using the module specifier', () => {
|
||||
const result = transformSync(
|
||||
`
|
||||
import * as i0 from "@angular/core";
|
||||
|
||||
export class MyMod {}
|
||||
export class MyComponent {}
|
||||
|
||||
MyMod.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyMod, declarations: [MyComponent] });
|
||||
`,
|
||||
{
|
||||
plugins: [
|
||||
'@angular/compiler-cli/linker/babel',
|
||||
],
|
||||
filename: 'test.js',
|
||||
})!;
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.code).not.toContain('ɵɵngDeclareNgModule');
|
||||
expect(result.code).toContain('i0.ɵɵdefineNgModule');
|
||||
expect(result.code).not.toMatch(/declarations:\s*\[MyComponent]/);
|
||||
});
|
||||
|
||||
it('should be configurable', () => {
|
||||
const result = transformSync(
|
||||
`
|
||||
import * as i0 from "@angular/core";
|
||||
|
||||
export class MyMod {}
|
||||
export class MyComponent {}
|
||||
|
||||
MyMod.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyMod, declarations: [MyComponent] });
|
||||
`,
|
||||
{
|
||||
plugins: [
|
||||
['@angular/compiler-cli/linker/babel', {linkerJitMode: true}],
|
||||
],
|
||||
filename: 'test.js',
|
||||
})!;
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result.code).not.toContain('ɵɵngDeclareNgModule');
|
||||
expect(result.code).toContain('i0.ɵɵdefineNgModule');
|
||||
expect(result.code).toMatch(/declarations:\s*\[MyComponent]/);
|
||||
});
|
||||
});
|
||||
@@ -69,10 +69,11 @@ describe('createEs2015LinkerPlugin()', () => {
|
||||
transformSync(
|
||||
[
|
||||
'var core;',
|
||||
`ɵɵngDeclareDirective({version: '0.0.0-PLACEHOLDER', ngImport: core, x: 1});`,
|
||||
`ɵɵngDeclareComponent({version: '0.0.0-PLACEHOLDER', ngImport: core, foo: () => ɵɵngDeclareDirective({version: '0.0.0-PLACEHOLDER', ngImport: core, x: 2})});`,
|
||||
`x.qux(() => ɵɵngDeclareDirective({version: '0.0.0-PLACEHOLDER', ngImport: core, x: 3}));`,
|
||||
`ɵɵngDeclareDirective({minVersion: '0.0.0-PLACEHOLDER', version: '0.0.0-PLACEHOLDER', ngImport: core, x: 1});`,
|
||||
`i0.ɵɵngDeclareComponent({minVersion: '0.0.0-PLACEHOLDER', version: '0.0.0-PLACEHOLDER', ngImport: core, foo: () => ɵɵngDeclareDirective({minVersion: '0.0.0-PLACEHOLDER', version: '0.0.0-PLACEHOLDER', ngImport: core, x: 2})});`,
|
||||
`x.qux(() => ɵɵngDeclareDirective({minVersion: '0.0.0-PLACEHOLDER', version: '0.0.0-PLACEHOLDER', ngImport: core, x: 3}));`,
|
||||
'spread(...x);',
|
||||
`i0['ɵɵngDeclareDirective']({minVersion: '0.0.0-PLACEHOLDER', version: '0.0.0-PLACEHOLDER', ngImport: core, x: 4});`,
|
||||
].join('\n'),
|
||||
{
|
||||
plugins: [createEs2015LinkerPlugin({fileSystem, logger})],
|
||||
@@ -81,13 +82,23 @@ describe('createEs2015LinkerPlugin()', () => {
|
||||
});
|
||||
|
||||
expect(humanizeLinkerCalls(linkSpy.calls)).toEqual([
|
||||
['ɵɵngDeclareDirective', '{version:\'0.0.0-PLACEHOLDER\',ngImport:core,x:1}'],
|
||||
[
|
||||
'ɵɵngDeclareDirective',
|
||||
'{minVersion:\'0.0.0-PLACEHOLDER\',version:\'0.0.0-PLACEHOLDER\',ngImport:core,x:1}'
|
||||
],
|
||||
[
|
||||
'ɵɵngDeclareComponent',
|
||||
'{version:\'0.0.0-PLACEHOLDER\',ngImport:core,foo:()=>ɵɵngDeclareDirective({version:\'0.0.0-PLACEHOLDER\',ngImport:core,x:2})}'
|
||||
'{minVersion:\'0.0.0-PLACEHOLDER\',version:\'0.0.0-PLACEHOLDER\',ngImport:core,foo:()=>ɵɵngDeclareDirective({minVersion:\'0.0.0-PLACEHOLDER\',version:\'0.0.0-PLACEHOLDER\',ngImport:core,x:2})}'
|
||||
],
|
||||
// Note we do not process `x:2` declaration since it is nested within another declaration
|
||||
['ɵɵngDeclareDirective', '{version:\'0.0.0-PLACEHOLDER\',ngImport:core,x:3}']
|
||||
[
|
||||
'ɵɵngDeclareDirective',
|
||||
'{minVersion:\'0.0.0-PLACEHOLDER\',version:\'0.0.0-PLACEHOLDER\',ngImport:core,x:3}'
|
||||
],
|
||||
[
|
||||
'ɵɵngDeclareDirective',
|
||||
'{minVersion:\'0.0.0-PLACEHOLDER\',version:\'0.0.0-PLACEHOLDER\',ngImport:core,x:4}'
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -125,9 +136,9 @@ describe('createEs2015LinkerPlugin()', () => {
|
||||
[
|
||||
'import * as core from \'some-module\';',
|
||||
'import {id} from \'other-module\';',
|
||||
`ɵɵngDeclareDirective({version: '0.0.0-PLACEHOLDER', ngImport: core})`,
|
||||
`ɵɵngDeclareDirective({version: '0.0.0-PLACEHOLDER', ngImport: core})`,
|
||||
`ɵɵngDeclareDirective({version: '0.0.0-PLACEHOLDER', ngImport: core})`,
|
||||
`ɵɵngDeclareDirective({minVersion: '0.0.0-PLACEHOLDER', version: '0.0.0-PLACEHOLDER', ngImport: core})`,
|
||||
`ɵɵngDeclareDirective({minVersion: '0.0.0-PLACEHOLDER', version: '0.0.0-PLACEHOLDER', ngImport: core})`,
|
||||
`ɵɵngDeclareDirective({minVersion: '0.0.0-PLACEHOLDER', version: '0.0.0-PLACEHOLDER', ngImport: core})`,
|
||||
].join('\n'),
|
||||
{
|
||||
plugins: [createEs2015LinkerPlugin({fileSystem, logger})],
|
||||
@@ -149,9 +160,9 @@ describe('createEs2015LinkerPlugin()', () => {
|
||||
const result = transformSync(
|
||||
[
|
||||
'var core;',
|
||||
`ɵɵngDeclareDirective({version: '0.0.0-PLACEHOLDER', ngImport: core})`,
|
||||
`ɵɵngDeclareDirective({version: '0.0.0-PLACEHOLDER', ngImport: core})`,
|
||||
`ɵɵngDeclareDirective({version: '0.0.0-PLACEHOLDER', ngImport: core})`,
|
||||
`ɵɵngDeclareDirective({minVersion: '0.0.0-PLACEHOLDER', version: '0.0.0-PLACEHOLDER', ngImport: core})`,
|
||||
`ɵɵngDeclareDirective({minVersion: '0.0.0-PLACEHOLDER', version: '0.0.0-PLACEHOLDER', ngImport: core})`,
|
||||
`ɵɵngDeclareDirective({minVersion: '0.0.0-PLACEHOLDER', version: '0.0.0-PLACEHOLDER', ngImport: core})`,
|
||||
].join('\n'),
|
||||
{
|
||||
plugins: [createEs2015LinkerPlugin({fileSystem, logger})],
|
||||
@@ -174,9 +185,10 @@ describe('createEs2015LinkerPlugin()', () => {
|
||||
const result = transformSync(
|
||||
[
|
||||
'function run(core) {',
|
||||
` ɵɵngDeclareDirective({version: '0.0.0-PLACEHOLDER', ngImport: core})`,
|
||||
` ɵɵngDeclareDirective({version: '0.0.0-PLACEHOLDER', ngImport: core})`,
|
||||
` ɵɵngDeclareDirective({version: '0.0.0-PLACEHOLDER', ngImport: core})`, '}'
|
||||
` ɵɵngDeclareDirective({minVersion: '0.0.0-PLACEHOLDER', version: '0.0.0-PLACEHOLDER', ngImport: core})`,
|
||||
` ɵɵngDeclareDirective({minVersion: '0.0.0-PLACEHOLDER', version: '0.0.0-PLACEHOLDER', ngImport: core})`,
|
||||
` ɵɵngDeclareDirective({minVersion: '0.0.0-PLACEHOLDER', version: '0.0.0-PLACEHOLDER', ngImport: core})`,
|
||||
'}'
|
||||
].join('\n'),
|
||||
{
|
||||
plugins: [createEs2015LinkerPlugin({fileSystem, logger})],
|
||||
@@ -198,9 +210,9 @@ describe('createEs2015LinkerPlugin()', () => {
|
||||
const result = transformSync(
|
||||
[
|
||||
'function run() {',
|
||||
` ɵɵngDeclareDirective({version: '0.0.0-PLACEHOLDER', ngImport: core})`,
|
||||
` ɵɵngDeclareDirective({version: '0.0.0-PLACEHOLDER', ngImport: core})`,
|
||||
` ɵɵngDeclareDirective({version: '0.0.0-PLACEHOLDER', ngImport: core})`,
|
||||
` ɵɵngDeclareDirective({minVersion: '0.0.0-PLACEHOLDER', version: '0.0.0-PLACEHOLDER', ngImport: core})`,
|
||||
` ɵɵngDeclareDirective({minVersion: '0.0.0-PLACEHOLDER', version: '0.0.0-PLACEHOLDER', ngImport: core})`,
|
||||
` ɵɵngDeclareDirective({minVersion: '0.0.0-PLACEHOLDER', version: '0.0.0-PLACEHOLDER', ngImport: core})`,
|
||||
'}',
|
||||
].join('\n'),
|
||||
{
|
||||
@@ -226,7 +238,7 @@ describe('createEs2015LinkerPlugin()', () => {
|
||||
const plugin = createEs2015LinkerPlugin({fileSystem, logger});
|
||||
const result = transformSync(
|
||||
[
|
||||
`ɵɵngDeclareDirective({version: '0.0.0-PLACEHOLDER', ngImport: core}); FOO;`,
|
||||
`ɵɵngDeclareDirective({minVersion: '0.0.0-PLACEHOLDER', version: '0.0.0-PLACEHOLDER', ngImport: core}); FOO;`,
|
||||
].join('\n'),
|
||||
{
|
||||
plugins: [
|
||||
@@ -267,7 +279,7 @@ describe('createEs2015LinkerPlugin()', () => {
|
||||
const result = transformSync(
|
||||
[
|
||||
'import * as core from \'some-module\';',
|
||||
`ɵɵngDeclareDirective({version: '0.0.0-PLACEHOLDER', ngImport: core})`,
|
||||
`ɵɵngDeclareDirective({minVersion: '0.0.0-PLACEHOLDER', version: '0.0.0-PLACEHOLDER', ngImport: core})`,
|
||||
].join('\n'),
|
||||
{
|
||||
plugins: [createEs2015LinkerPlugin({fileSystem, logger})],
|
||||
|
||||
@@ -36,11 +36,17 @@ export interface AstHost<TExpression> {
|
||||
parseNumericLiteral(num: TExpression): number;
|
||||
|
||||
/**
|
||||
* Return `true` if the given expression is a boolean literal, or false otherwise.
|
||||
* Return `true` if the given expression can be considered a boolean literal, or false otherwise.
|
||||
*
|
||||
* Note that this should also cover the special case of some minified code where `true` and
|
||||
* `false` are replaced by `!0` and `!1` respectively.
|
||||
*/
|
||||
isBooleanLiteral(node: TExpression): boolean;
|
||||
/**
|
||||
* Parse the boolean value from the given expression, or throw if it is not a boolean literal.
|
||||
*
|
||||
* Note that this should also cover the special case of some minified code where `true` and
|
||||
* `false` are replaced by `!0` and `!1` respectively.
|
||||
*/
|
||||
parseBooleanLiteral(bool: TExpression): boolean;
|
||||
|
||||
|
||||
@@ -47,13 +47,18 @@ export class TypeScriptAstHost implements AstHost<ts.Expression> {
|
||||
return parseInt(num.text);
|
||||
}
|
||||
|
||||
isBooleanLiteral(node: ts.Expression): node is ts.FalseLiteral|ts.TrueLiteral {
|
||||
return node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword;
|
||||
isBooleanLiteral(node: ts.Expression): boolean {
|
||||
return isBooleanLiteral(node) || isMinifiedBooleanLiteral(node);
|
||||
}
|
||||
|
||||
parseBooleanLiteral(bool: ts.Expression): boolean {
|
||||
assert(bool, this.isBooleanLiteral, 'a boolean literal');
|
||||
return bool.kind === ts.SyntaxKind.TrueKeyword;
|
||||
if (isBooleanLiteral(bool)) {
|
||||
return bool.kind === ts.SyntaxKind.TrueKeyword;
|
||||
} else if (isMinifiedBooleanLiteral(bool)) {
|
||||
return !(+bool.operand.text);
|
||||
} else {
|
||||
throw new FatalLinkerError(bool, 'Unsupported syntax, expected a boolean literal.');
|
||||
}
|
||||
}
|
||||
|
||||
isArrayLiteral = ts.isArrayLiteralExpression;
|
||||
@@ -160,3 +165,20 @@ function isNotSpreadElement(e: ts.Expression|ts.SpreadElement): e is ts.Expressi
|
||||
function isPropertyName(e: ts.PropertyName): e is ts.Identifier|ts.StringLiteral|ts.NumericLiteral {
|
||||
return ts.isIdentifier(e) || ts.isStringLiteral(e) || ts.isNumericLiteral(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the node is either `true` or `false` literals.
|
||||
*/
|
||||
function isBooleanLiteral(node: ts.Expression): node is ts.TrueLiteral|ts.FalseLiteral {
|
||||
return node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword;
|
||||
}
|
||||
|
||||
type MinifiedBooleanLiteral = ts.PrefixUnaryExpression&{operand: ts.NumericLiteral};
|
||||
|
||||
/**
|
||||
* Return true if the node is either `!0` or `!1`.
|
||||
*/
|
||||
function isMinifiedBooleanLiteral(node: ts.Expression): node is MinifiedBooleanLiteral {
|
||||
return ts.isPrefixUnaryExpression(node) && node.operator === ts.SyntaxKind.ExclamationToken &&
|
||||
ts.isNumericLiteral(node.operand) && (node.operand.text === '0' || node.operand.text === '1');
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ export class IifeEmitScope<TStatement, TExpression> extends EmitScope<TStatement
|
||||
* Wraps the output from `EmitScope.translateDefinition()` and `EmitScope.getConstantStatements()`
|
||||
* in an IIFE.
|
||||
*/
|
||||
translateDefinition(definition: o.Expression): TExpression {
|
||||
override translateDefinition(definition: o.Expression): TExpression {
|
||||
const constantStatements = super.getConstantStatements();
|
||||
|
||||
const returnStatement =
|
||||
@@ -44,7 +44,7 @@ export class IifeEmitScope<TStatement, TExpression> extends EmitScope<TStatement
|
||||
* It is not valid to call this method, since there will be no shared constant statements - they
|
||||
* are already emitted in the IIFE alongside the translated definition.
|
||||
*/
|
||||
getConstantStatements(): TStatement[] {
|
||||
override getConstantStatements(): TStatement[] {
|
||||
throw new Error('BUG - IifeEmitScope should not expose any constant statements');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import {DeclarationScope} from './declaration_scope';
|
||||
import {EmitScope} from './emit_scopes/emit_scope';
|
||||
import {IifeEmitScope} from './emit_scopes/iife_emit_scope';
|
||||
import {LinkerEnvironment} from './linker_environment';
|
||||
import {PartialLinkerSelector} from './partial_linkers/partial_linker_selector';
|
||||
import {createLinkerMap, PartialLinkerSelector} from './partial_linkers/partial_linker_selector';
|
||||
|
||||
export const NO_STATEMENTS: Readonly<any[]> = [] as const;
|
||||
|
||||
@@ -20,14 +20,15 @@ export const NO_STATEMENTS: Readonly<any[]> = [] as const;
|
||||
* This class is responsible for linking all the partial declarations found in a single file.
|
||||
*/
|
||||
export class FileLinker<TConstantScope, TStatement, TExpression> {
|
||||
private linkerSelector: PartialLinkerSelector<TStatement, TExpression>;
|
||||
private linkerSelector: PartialLinkerSelector<TExpression>;
|
||||
private emitScopes = new Map<TConstantScope, EmitScope<TStatement, TExpression>>();
|
||||
|
||||
constructor(
|
||||
private linkerEnvironment: LinkerEnvironment<TStatement, TExpression>,
|
||||
sourceUrl: AbsoluteFsPath, code: string) {
|
||||
this.linkerSelector =
|
||||
new PartialLinkerSelector<TStatement, TExpression>(this.linkerEnvironment, sourceUrl, code);
|
||||
this.linkerSelector = new PartialLinkerSelector<TExpression>(
|
||||
createLinkerMap(this.linkerEnvironment, sourceUrl, code), this.linkerEnvironment.logger,
|
||||
this.linkerEnvironment.options.unknownDeclarationVersionHandling);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,8 +64,9 @@ export class FileLinker<TConstantScope, TStatement, TExpression> {
|
||||
const ngImport = metaObj.getNode('ngImport');
|
||||
const emitScope = this.getEmitScope(ngImport, declarationScope);
|
||||
|
||||
const minVersion = metaObj.getString('minVersion');
|
||||
const version = metaObj.getString('version');
|
||||
const linker = this.linkerSelector.getLinker(declarationFn, version);
|
||||
const linker = this.linkerSelector.getLinker(declarationFn, minVersion, version);
|
||||
const definition = linker.linkPartialDeclaration(emitScope.constantPool, metaObj);
|
||||
|
||||
return emitScope.translateDefinition(definition);
|
||||
|
||||
@@ -29,13 +29,10 @@ export class LinkerEnvironment<TStatement, TExpression> {
|
||||
factory: AstFactory<TStatement, TExpression>,
|
||||
options: Partial<LinkerOptions>): LinkerEnvironment<TStatement, TExpression> {
|
||||
return new LinkerEnvironment(fileSystem, logger, host, factory, {
|
||||
enableI18nLegacyMessageIdFormat: options.enableI18nLegacyMessageIdFormat ??
|
||||
DEFAULT_LINKER_OPTIONS.enableI18nLegacyMessageIdFormat,
|
||||
i18nNormalizeLineEndingsInICUs: options.i18nNormalizeLineEndingsInICUs ??
|
||||
DEFAULT_LINKER_OPTIONS.i18nNormalizeLineEndingsInICUs,
|
||||
i18nUseExternalIds: options.i18nUseExternalIds ?? DEFAULT_LINKER_OPTIONS.i18nUseExternalIds,
|
||||
sourceMapping: options.sourceMapping ?? DEFAULT_LINKER_OPTIONS.sourceMapping,
|
||||
linkerJitMode: options.linkerJitMode ?? DEFAULT_LINKER_OPTIONS.linkerJitMode,
|
||||
unknownDeclarationVersionHandling: options.unknownDeclarationVersionHandling ??
|
||||
DEFAULT_LINKER_OPTIONS.unknownDeclarationVersionHandling,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,24 +10,6 @@
|
||||
* Options to configure the linking behavior.
|
||||
*/
|
||||
export interface LinkerOptions {
|
||||
/**
|
||||
* Whether to generate legacy i18n message ids.
|
||||
* The default is `true`.
|
||||
*/
|
||||
enableI18nLegacyMessageIdFormat: boolean;
|
||||
/**
|
||||
* Whether to convert all line-endings in ICU expressions to `\n` characters.
|
||||
* The default is `false`.
|
||||
*/
|
||||
i18nNormalizeLineEndingsInICUs: boolean;
|
||||
|
||||
/**
|
||||
* Whether translation variable name should contain external message id
|
||||
* (used by Closure Compiler's output of `goog.getMsg` for transition period)
|
||||
* The default is `false`.
|
||||
*/
|
||||
i18nUseExternalIds: boolean;
|
||||
|
||||
/**
|
||||
* Whether to use source-mapping to compute the original source for external templates.
|
||||
* The default is `true`.
|
||||
@@ -41,15 +23,27 @@ export interface LinkerOptions {
|
||||
* `exports`, etc, which are otherwise not needed.
|
||||
*/
|
||||
linkerJitMode: boolean;
|
||||
|
||||
/**
|
||||
* How to handle a situation where a partial declaration matches none of the supported
|
||||
* partial-linker versions.
|
||||
*
|
||||
* - `error` - the version mismatch is a fatal error.
|
||||
* - `warn` - a warning is sent to the logger but the most recent partial-linker
|
||||
* will attempt to process the declaration anyway.
|
||||
* - `ignore` - the most recent partial-linker will, silently, attempt to process
|
||||
* the declaration.
|
||||
*
|
||||
* The default is `error`.
|
||||
*/
|
||||
unknownDeclarationVersionHandling: 'ignore'|'warn'|'error';
|
||||
}
|
||||
|
||||
/**
|
||||
* The default linker options to use if properties are not provided.
|
||||
*/
|
||||
export const DEFAULT_LINKER_OPTIONS: LinkerOptions = {
|
||||
enableI18nLegacyMessageIdFormat: true,
|
||||
i18nNormalizeLineEndingsInICUs: false,
|
||||
i18nUseExternalIds: false,
|
||||
sourceMapping: true,
|
||||
linkerJitMode: false,
|
||||
unknownDeclarationVersionHandling: 'error',
|
||||
};
|
||||
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* @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 {compileClassMetadata, ConstantPool, R3ClassMetadata, R3DeclareClassMetadata, R3PartialDeclaration} from '@angular/compiler';
|
||||
import * as o from '@angular/compiler/src/output/output_ast';
|
||||
|
||||
import {AstObject} from '../../ast/ast_value';
|
||||
|
||||
import {PartialLinker} from './partial_linker';
|
||||
|
||||
/**
|
||||
* A `PartialLinker` that is designed to process `ɵɵngDeclareClassMetadata()` call expressions.
|
||||
*/
|
||||
export class PartialClassMetadataLinkerVersion1<TExpression> implements PartialLinker<TExpression> {
|
||||
linkPartialDeclaration(
|
||||
constantPool: ConstantPool,
|
||||
metaObj: AstObject<R3PartialDeclaration, TExpression>): o.Expression {
|
||||
const meta = toR3ClassMetadata(metaObj);
|
||||
return compileClassMetadata(meta);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives the `R3ClassMetadata` structure from the AST object.
|
||||
*/
|
||||
export function toR3ClassMetadata<TExpression>(
|
||||
metaObj: AstObject<R3DeclareClassMetadata, TExpression>): R3ClassMetadata {
|
||||
return {
|
||||
type: metaObj.getOpaque('type'),
|
||||
decorators: metaObj.getOpaque('decorators'),
|
||||
ctorParameters: metaObj.has('ctorParameters') ? metaObj.getOpaque('ctorParameters') : null,
|
||||
propDecorators: metaObj.has('propDecorators') ? metaObj.getOpaque('propDecorators') : null,
|
||||
};
|
||||
}
|
||||
+10
-56
@@ -14,24 +14,17 @@ import {Range} from '../../ast/ast_host';
|
||||
import {AstObject, AstValue} from '../../ast/ast_value';
|
||||
import {FatalLinkerError} from '../../fatal_linker_error';
|
||||
import {GetSourceFileFn} from '../get_source_file';
|
||||
import {LinkerEnvironment} from '../linker_environment';
|
||||
|
||||
import {toR3DirectiveMeta} from './partial_directive_linker_1';
|
||||
import {PartialLinker} from './partial_linker';
|
||||
import {extractForwardRef} from './util';
|
||||
|
||||
/**
|
||||
* A `PartialLinker` that is designed to process `ɵɵngDeclareComponent()` call expressions.
|
||||
*/
|
||||
export class PartialComponentLinkerVersion1<TStatement, TExpression> implements
|
||||
PartialLinker<TExpression> {
|
||||
private readonly i18nNormalizeLineEndingsInICUs =
|
||||
this.environment.options.i18nNormalizeLineEndingsInICUs;
|
||||
private readonly enableI18nLegacyMessageIdFormat =
|
||||
this.environment.options.enableI18nLegacyMessageIdFormat;
|
||||
private readonly i18nUseExternalIds = this.environment.options.i18nUseExternalIds;
|
||||
|
||||
constructor(
|
||||
private readonly environment: LinkerEnvironment<TStatement, TExpression>,
|
||||
private readonly getSourceFile: GetSourceFileFn, private sourceUrl: AbsoluteFsPath,
|
||||
private code: string) {}
|
||||
|
||||
@@ -53,18 +46,15 @@ export class PartialComponentLinkerVersion1<TStatement, TExpression> implements
|
||||
const isInline = metaObj.has('isInline') ? metaObj.getBoolean('isInline') : false;
|
||||
const templateInfo = this.getTemplateInfo(templateSource, isInline);
|
||||
|
||||
// We always normalize line endings if the template is inline.
|
||||
const i18nNormalizeLineEndingsInICUs = isInline || this.i18nNormalizeLineEndingsInICUs;
|
||||
|
||||
const template = parseTemplate(templateInfo.code, templateInfo.sourceUrl, {
|
||||
escapedString: templateInfo.isEscaped,
|
||||
interpolationConfig: interpolation,
|
||||
range: templateInfo.range,
|
||||
enableI18nLegacyMessageIdFormat: this.enableI18nLegacyMessageIdFormat,
|
||||
enableI18nLegacyMessageIdFormat: false,
|
||||
preserveWhitespaces:
|
||||
metaObj.has('preserveWhitespaces') ? metaObj.getBoolean('preserveWhitespaces') : false,
|
||||
i18nNormalizeLineEndingsInICUs,
|
||||
isInline,
|
||||
// We normalize line endings if the template is was inline.
|
||||
i18nNormalizeLineEndingsInICUs: isInline,
|
||||
});
|
||||
if (template.errors !== null) {
|
||||
const errors = template.errors.map(err => err.toString()).join('\n');
|
||||
@@ -81,10 +71,8 @@ export class PartialComponentLinkerVersion1<TStatement, TExpression> implements
|
||||
const type = directiveExpr.getValue('type');
|
||||
const selector = directiveExpr.getString('selector');
|
||||
|
||||
let typeExpr = type.getOpaque();
|
||||
const forwardRefType = extractForwardRef(type);
|
||||
if (forwardRefType !== null) {
|
||||
typeExpr = forwardRefType;
|
||||
const {expression: typeExpr, isForwardRef} = extractForwardRef(type);
|
||||
if (isForwardRef) {
|
||||
declarationListEmitMode = DeclarationListEmitMode.Closure;
|
||||
}
|
||||
|
||||
@@ -115,13 +103,11 @@ export class PartialComponentLinkerVersion1<TStatement, TExpression> implements
|
||||
let pipes = new Map<string, o.Expression>();
|
||||
if (metaObj.has('pipes')) {
|
||||
pipes = metaObj.getObject('pipes').toMap(pipe => {
|
||||
const forwardRefType = extractForwardRef(pipe);
|
||||
if (forwardRefType !== null) {
|
||||
const {expression: pipeType, isForwardRef} = extractForwardRef(pipe);
|
||||
if (isForwardRef) {
|
||||
declarationListEmitMode = DeclarationListEmitMode.Closure;
|
||||
return forwardRefType;
|
||||
} else {
|
||||
return pipe.getOpaque();
|
||||
}
|
||||
return pipeType;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -144,7 +130,7 @@ export class PartialComponentLinkerVersion1<TStatement, TExpression> implements
|
||||
ChangeDetectionStrategy.Default,
|
||||
animations: metaObj.has('animations') ? metaObj.getOpaque('animations') : null,
|
||||
relativeContextFilePath: this.sourceUrl,
|
||||
i18nUseExternalIds: this.i18nUseExternalIds,
|
||||
i18nUseExternalIds: false,
|
||||
pipes,
|
||||
directives,
|
||||
};
|
||||
@@ -277,35 +263,3 @@ function parseChangeDetectionStrategy<TExpression>(
|
||||
}
|
||||
return enumValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the type reference expression from a `forwardRef` function call. For example, the
|
||||
* expression `forwardRef(function() { return FooDir; })` returns `FooDir`. Note that this
|
||||
* expression is required to be wrapped in a closure, as otherwise the forward reference would be
|
||||
* resolved before initialization.
|
||||
*/
|
||||
function extractForwardRef<TExpression>(expr: AstValue<unknown, TExpression>):
|
||||
o.WrappedNodeExpr<TExpression>|null {
|
||||
if (!expr.isCallExpression()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const callee = expr.getCallee();
|
||||
if (callee.getSymbolName() !== 'forwardRef') {
|
||||
throw new FatalLinkerError(
|
||||
callee.expression, 'Unsupported directive type, expected forwardRef or a type reference');
|
||||
}
|
||||
|
||||
const args = expr.getArguments();
|
||||
if (args.length !== 1) {
|
||||
throw new FatalLinkerError(expr, 'Unsupported forwardRef call, expected a single argument');
|
||||
}
|
||||
|
||||
const wrapperFn = args[0] as AstValue<Function, TExpression>;
|
||||
if (!wrapperFn.isFunction()) {
|
||||
throw new FatalLinkerError(
|
||||
wrapperFn, 'Unsupported forwardRef call, expected a function argument');
|
||||
}
|
||||
|
||||
return wrapperFn.getFunctionReturnValue().getOpaque();
|
||||
}
|
||||
|
||||
+5
-27
@@ -5,14 +5,14 @@
|
||||
* 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 {compileFactoryFunction, ConstantPool, FactoryTarget, R3DeclareDependencyMetadata, R3DeclareFactoryMetadata, R3DependencyMetadata, R3FactoryMetadata, R3PartialDeclaration} from '@angular/compiler';
|
||||
import {compileFactoryFunction, ConstantPool, FactoryTarget, R3DeclareFactoryMetadata, R3DependencyMetadata, R3FactoryMetadata, R3PartialDeclaration} from '@angular/compiler';
|
||||
import * as o from '@angular/compiler/src/output/output_ast';
|
||||
|
||||
import {AstObject} from '../../ast/ast_value';
|
||||
import {FatalLinkerError} from '../../fatal_linker_error';
|
||||
|
||||
import {PartialLinker} from './partial_linker';
|
||||
import {parseEnum, wrapReference} from './util';
|
||||
import {getDependency, parseEnum, wrapReference} from './util';
|
||||
|
||||
/**
|
||||
* A `PartialLinker` that is designed to process `ɵɵngDeclareFactory()` call expressions.
|
||||
@@ -45,11 +45,11 @@ export function toR3FactoryMeta<TExpression>(
|
||||
internalType: metaObj.getOpaque('type'),
|
||||
typeArgumentCount: 0,
|
||||
target: parseEnum(metaObj.getValue('target'), FactoryTarget),
|
||||
deps: getDeps(metaObj, 'deps'),
|
||||
deps: getDependencies(metaObj, 'deps'),
|
||||
};
|
||||
}
|
||||
|
||||
function getDeps<TExpression>(
|
||||
function getDependencies<TExpression>(
|
||||
metaObj: AstObject<R3DeclareFactoryMetadata, TExpression>,
|
||||
propName: keyof R3DeclareFactoryMetadata): R3DependencyMetadata[]|null|'invalid' {
|
||||
if (!metaObj.has(propName)) {
|
||||
@@ -57,32 +57,10 @@ function getDeps<TExpression>(
|
||||
}
|
||||
const deps = metaObj.getValue(propName);
|
||||
if (deps.isArray()) {
|
||||
return deps.getArray().map(dep => getDep(dep.getObject()));
|
||||
return deps.getArray().map(dep => getDependency(dep.getObject()));
|
||||
}
|
||||
if (deps.isString()) {
|
||||
return 'invalid';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getDep<TExpression>(depObj: AstObject<R3DeclareDependencyMetadata, TExpression>):
|
||||
R3DependencyMetadata {
|
||||
const isAttribute = depObj.has('attribute') && depObj.getBoolean('attribute');
|
||||
const token = depObj.getOpaque('token');
|
||||
// Normally `attribute` is a string literal and so its `attributeNameType` is the same string
|
||||
// literal. If the `attribute` is some other expression, the `attributeNameType` would be the
|
||||
// `unknown` type. It is not possible to generate this when linking, since it only deals with JS
|
||||
// and not typings. When linking the existence of the `attributeNameType` only acts as a marker to
|
||||
// change the injection instruction that is generated, so we just pass the literal string
|
||||
// `"unknown"`.
|
||||
const attributeNameType = isAttribute ? o.literal('unknown') : null;
|
||||
const dep: R3DependencyMetadata = {
|
||||
token,
|
||||
attributeNameType,
|
||||
host: depObj.has('host') && depObj.getBoolean('host'),
|
||||
optional: depObj.has('optional') && depObj.getBoolean('optional'),
|
||||
self: depObj.has('self') && depObj.getBoolean('self'),
|
||||
skipSelf: depObj.has('skipSelf') && depObj.getBoolean('skipSelf'),
|
||||
};
|
||||
return dep;
|
||||
}
|
||||
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* @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 {compileInjectable, ConstantPool, createR3ProviderExpression, R3DeclareInjectableMetadata, R3InjectableMetadata, R3PartialDeclaration} from '@angular/compiler';
|
||||
import * as o from '@angular/compiler/src/output/output_ast';
|
||||
|
||||
import {AstObject} from '../../ast/ast_value';
|
||||
import {FatalLinkerError} from '../../fatal_linker_error';
|
||||
|
||||
import {PartialLinker} from './partial_linker';
|
||||
import {extractForwardRef, getDependency, wrapReference} from './util';
|
||||
|
||||
/**
|
||||
* A `PartialLinker` that is designed to process `ɵɵngDeclareInjectable()` call expressions.
|
||||
*/
|
||||
export class PartialInjectableLinkerVersion1<TExpression> implements PartialLinker<TExpression> {
|
||||
linkPartialDeclaration(
|
||||
constantPool: ConstantPool,
|
||||
metaObj: AstObject<R3PartialDeclaration, TExpression>): o.Expression {
|
||||
const meta = toR3InjectableMeta(metaObj);
|
||||
const def = compileInjectable(meta, /* resolveForwardRefs */ false);
|
||||
return def.expression;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives the `R3InjectableMetadata` structure from the AST object.
|
||||
*/
|
||||
export function toR3InjectableMeta<TExpression>(
|
||||
metaObj: AstObject<R3DeclareInjectableMetadata, TExpression>): R3InjectableMetadata {
|
||||
const typeExpr = metaObj.getValue('type');
|
||||
const typeName = typeExpr.getSymbolName();
|
||||
if (typeName === null) {
|
||||
throw new FatalLinkerError(
|
||||
typeExpr.expression, 'Unsupported type, its name could not be determined');
|
||||
}
|
||||
|
||||
const meta: R3InjectableMetadata = {
|
||||
name: typeName,
|
||||
type: wrapReference(typeExpr.getOpaque()),
|
||||
internalType: typeExpr.getOpaque(),
|
||||
typeArgumentCount: 0,
|
||||
providedIn: metaObj.has('providedIn') ? extractForwardRef(metaObj.getValue('providedIn')) :
|
||||
createR3ProviderExpression(o.literal(null), false),
|
||||
};
|
||||
|
||||
if (metaObj.has('useClass')) {
|
||||
meta.useClass = extractForwardRef(metaObj.getValue('useClass'));
|
||||
}
|
||||
if (metaObj.has('useFactory')) {
|
||||
meta.useFactory = metaObj.getOpaque('useFactory');
|
||||
}
|
||||
if (metaObj.has('useExisting')) {
|
||||
meta.useExisting = extractForwardRef(metaObj.getValue('useExisting'));
|
||||
}
|
||||
if (metaObj.has('useValue')) {
|
||||
meta.useValue = extractForwardRef(metaObj.getValue('useValue'));
|
||||
}
|
||||
|
||||
if (metaObj.has('deps')) {
|
||||
meta.deps = metaObj.getArray('deps').map(dep => getDependency(dep.getObject()));
|
||||
}
|
||||
|
||||
return meta;
|
||||
}
|
||||
+129
-66
@@ -5,59 +5,126 @@
|
||||
* 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 {satisfies} from 'semver';
|
||||
import {intersects, Range, SemVer} from 'semver';
|
||||
|
||||
import {AbsoluteFsPath} from '../../../../src/ngtsc/file_system';
|
||||
import {Logger} from '../../../../src/ngtsc/logging';
|
||||
import {createGetSourceFile} from '../get_source_file';
|
||||
import {LinkerEnvironment} from '../linker_environment';
|
||||
|
||||
import {PartialClassMetadataLinkerVersion1} from './partial_class_metadata_linker_1';
|
||||
import {PartialComponentLinkerVersion1} from './partial_component_linker_1';
|
||||
import {PartialDirectiveLinkerVersion1} from './partial_directive_linker_1';
|
||||
import {PartialFactoryLinkerVersion1} from './partial_factory_linker_1';
|
||||
import {PartialInjectableLinkerVersion1} from './partial_injectable_linker_1';
|
||||
import {PartialInjectorLinkerVersion1} from './partial_injector_linker_1';
|
||||
import {PartialLinker} from './partial_linker';
|
||||
import {PartialNgModuleLinkerVersion1} from './partial_ng_module_linker_1';
|
||||
import {PartialPipeLinkerVersion1} from './partial_pipe_linker_1';
|
||||
|
||||
export const ɵɵngDeclareDirective = 'ɵɵngDeclareDirective';
|
||||
export const ɵɵngDeclareClassMetadata = 'ɵɵngDeclareClassMetadata';
|
||||
export const ɵɵngDeclareComponent = 'ɵɵngDeclareComponent';
|
||||
export const ɵɵngDeclareFactory = 'ɵɵngDeclareFactory';
|
||||
export const ɵɵngDeclareInjectable = 'ɵɵngDeclareInjectable';
|
||||
export const ɵɵngDeclareInjector = 'ɵɵngDeclareInjector';
|
||||
export const ɵɵngDeclareNgModule = 'ɵɵngDeclareNgModule';
|
||||
export const ɵɵngDeclarePipe = 'ɵɵngDeclarePipe';
|
||||
export const declarationFunctions = [
|
||||
ɵɵngDeclareDirective, ɵɵngDeclareComponent, ɵɵngDeclareFactory, ɵɵngDeclareInjector,
|
||||
ɵɵngDeclareNgModule, ɵɵngDeclarePipe
|
||||
ɵɵngDeclareDirective, ɵɵngDeclareClassMetadata, ɵɵngDeclareComponent, ɵɵngDeclareFactory,
|
||||
ɵɵngDeclareInjectable, ɵɵngDeclareInjector, ɵɵngDeclareNgModule, ɵɵngDeclarePipe
|
||||
];
|
||||
|
||||
interface LinkerRange<TExpression> {
|
||||
range: string;
|
||||
export interface LinkerRange<TExpression> {
|
||||
range: Range;
|
||||
linker: PartialLinker<TExpression>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a mapping between partial-declaration call name and collections of partial-linkers.
|
||||
*
|
||||
* Each collection of partial-linkers will contain a version range that will be matched against the
|
||||
* `minVersion` of the partial-declaration. (Additionally, a partial-linker may modify its behaviour
|
||||
* internally based on the `version` property of the declaration.)
|
||||
*
|
||||
* Versions should be sorted in ascending order. The most recent partial-linker will be used as the
|
||||
* fallback linker if none of the other version ranges match. For example:
|
||||
*
|
||||
* ```
|
||||
* {range: getRange('<=', '13.0.0'), linker PartialDirectiveLinkerVersion2(...) },
|
||||
* {range: getRange('<=', '13.1.0'), linker PartialDirectiveLinkerVersion3(...) },
|
||||
* {range: getRange('<=', '14.0.0'), linker PartialDirectiveLinkerVersion4(...) },
|
||||
* {range: LATEST_VERSION_RANGE, linker: new PartialDirectiveLinkerVersion1(...)},
|
||||
* ```
|
||||
*
|
||||
* If the `LATEST_VERSION_RANGE` is `<=15.0.0` then the fallback linker would be
|
||||
* `PartialDirectiveLinkerVersion1` for any version greater than `15.0.0`.
|
||||
*
|
||||
* When there is a change to a declaration interface that requires a new partial-linker, the
|
||||
* `minVersion` of the partial-declaration should be updated, the new linker implementation should
|
||||
* be added to the end of the collection, and the version of the previous linker should be updated.
|
||||
*/
|
||||
export function createLinkerMap<TStatement, TExpression>(
|
||||
environment: LinkerEnvironment<TStatement, TExpression>, sourceUrl: AbsoluteFsPath,
|
||||
code: string): Map<string, LinkerRange<TExpression>[]> {
|
||||
const linkers = new Map<string, LinkerRange<TExpression>[]>();
|
||||
const LATEST_VERSION_RANGE = getRange('<=', '0.0.0-PLACEHOLDER');
|
||||
|
||||
linkers.set(ɵɵngDeclareDirective, [
|
||||
{range: LATEST_VERSION_RANGE, linker: new PartialDirectiveLinkerVersion1(sourceUrl, code)},
|
||||
]);
|
||||
linkers.set(ɵɵngDeclareClassMetadata, [
|
||||
{range: LATEST_VERSION_RANGE, linker: new PartialClassMetadataLinkerVersion1()},
|
||||
]);
|
||||
linkers.set(ɵɵngDeclareComponent, [
|
||||
{
|
||||
range: LATEST_VERSION_RANGE,
|
||||
linker: new PartialComponentLinkerVersion1(
|
||||
createGetSourceFile(sourceUrl, code, environment.sourceFileLoader), sourceUrl, code)
|
||||
},
|
||||
]);
|
||||
linkers.set(ɵɵngDeclareFactory, [
|
||||
{range: LATEST_VERSION_RANGE, linker: new PartialFactoryLinkerVersion1()},
|
||||
]);
|
||||
linkers.set(ɵɵngDeclareInjectable, [
|
||||
{range: LATEST_VERSION_RANGE, linker: new PartialInjectableLinkerVersion1()},
|
||||
]);
|
||||
linkers.set(ɵɵngDeclareInjector, [
|
||||
{range: LATEST_VERSION_RANGE, linker: new PartialInjectorLinkerVersion1()},
|
||||
]);
|
||||
linkers.set(ɵɵngDeclareNgModule, [
|
||||
{
|
||||
range: LATEST_VERSION_RANGE,
|
||||
linker: new PartialNgModuleLinkerVersion1(environment.options.linkerJitMode)
|
||||
},
|
||||
]);
|
||||
linkers.set(ɵɵngDeclarePipe, [
|
||||
{range: LATEST_VERSION_RANGE, linker: new PartialPipeLinkerVersion1()},
|
||||
]);
|
||||
|
||||
return linkers;
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper that selects the appropriate `PartialLinker` for a given declaration.
|
||||
*
|
||||
* The selection is made from a database of linker instances, chosen if their given semver range
|
||||
* satisfies the version found in the code to be linked.
|
||||
* satisfies the `minVersion` of the partial declaration to be linked.
|
||||
*
|
||||
* Note that the ranges are checked in order, and the first matching range will be selected, so
|
||||
* ranges should be most restrictive first.
|
||||
* Note that the ranges are checked in order, and the first matching range will be selected. So
|
||||
* ranges should be most restrictive first. In practice, since ranges are always `<=X.Y.Z` this
|
||||
* means that ranges should be in ascending order.
|
||||
*
|
||||
* Also, ranges are matched to include "pre-releases", therefore if the range is `>=11.1.0-next.1`
|
||||
* then this includes `11.1.0-next.2` and also `12.0.0-next.1`.
|
||||
*
|
||||
* Finally, note that we always start with the current version (i.e. `0.0.0-PLACEHOLDER`). This
|
||||
* allows the linker to work on local builds effectively.
|
||||
* Note that any "pre-release" versions are stripped from ranges. Therefore if a `minVersion` is
|
||||
* `11.1.0-next.1` then this would match `11.1.0-next.2` and also `12.0.0-next.1`. (This is
|
||||
* different to standard semver range checking, where pre-release versions do not cross full version
|
||||
* boundaries.)
|
||||
*/
|
||||
export class PartialLinkerSelector<TStatement, TExpression> {
|
||||
private readonly linkers: Map<string, LinkerRange<TExpression>[]>;
|
||||
|
||||
export class PartialLinkerSelector<TExpression> {
|
||||
constructor(
|
||||
environment: LinkerEnvironment<TStatement, TExpression>, sourceUrl: AbsoluteFsPath,
|
||||
code: string) {
|
||||
this.linkers = this.createLinkerMap(environment, sourceUrl, code);
|
||||
}
|
||||
private readonly linkers: Map<string, LinkerRange<TExpression>[]>,
|
||||
private readonly logger: Logger,
|
||||
private readonly unknownDeclarationVersionHandling: 'ignore'|'warn'|'error') {}
|
||||
|
||||
/**
|
||||
* Returns true if there are `PartialLinker` classes that can handle functions with this name.
|
||||
@@ -70,59 +137,55 @@ export class PartialLinkerSelector<TStatement, TExpression> {
|
||||
* Returns the `PartialLinker` that can handle functions with the given name and version.
|
||||
* Throws an error if there is none.
|
||||
*/
|
||||
getLinker(functionName: string, version: string): PartialLinker<TExpression> {
|
||||
getLinker(functionName: string, minVersion: string, version: string): PartialLinker<TExpression> {
|
||||
if (!this.linkers.has(functionName)) {
|
||||
throw new Error(`Unknown partial declaration function ${functionName}.`);
|
||||
}
|
||||
const versions = this.linkers.get(functionName)!;
|
||||
for (const {range, linker} of versions) {
|
||||
if (satisfies(version, range, {includePrerelease: true})) {
|
||||
const linkerRanges = this.linkers.get(functionName)!;
|
||||
|
||||
if (version === '0.0.0-PLACEHOLDER') {
|
||||
// Special case if the `version` is the same as the current compiler version.
|
||||
// This helps with compliance tests where the version placeholders have not been replaced.
|
||||
return linkerRanges[linkerRanges.length - 1].linker;
|
||||
}
|
||||
|
||||
const declarationRange = getRange('>=', minVersion);
|
||||
for (const {range: linkerRange, linker} of linkerRanges) {
|
||||
if (intersects(declarationRange, linkerRange)) {
|
||||
return linker;
|
||||
}
|
||||
}
|
||||
throw new Error(
|
||||
`Unsupported partial declaration version ${version} for ${functionName}.\n` +
|
||||
'Valid version ranges are:\n' + versions.map(v => ` - ${v.range}`).join('\n'));
|
||||
}
|
||||
|
||||
private createLinkerMap(
|
||||
environment: LinkerEnvironment<TStatement, TExpression>, sourceUrl: AbsoluteFsPath,
|
||||
code: string): Map<string, LinkerRange<TExpression>[]> {
|
||||
const partialDirectiveLinkerVersion1 = new PartialDirectiveLinkerVersion1(sourceUrl, code);
|
||||
const partialComponentLinkerVersion1 = new PartialComponentLinkerVersion1(
|
||||
environment, createGetSourceFile(sourceUrl, code, environment.sourceFileLoader), sourceUrl,
|
||||
code);
|
||||
const partialFactoryLinkerVersion1 = new PartialFactoryLinkerVersion1();
|
||||
const partialInjectorLinkerVersion1 = new PartialInjectorLinkerVersion1();
|
||||
const partialNgModuleLinkerVersion1 =
|
||||
new PartialNgModuleLinkerVersion1(environment.options.linkerJitMode);
|
||||
const partialPipeLinkerVersion1 = new PartialPipeLinkerVersion1();
|
||||
const message =
|
||||
`This application depends upon a library published using Angular version ${version}, ` +
|
||||
`which requires Angular version ${minVersion} or newer to work correctly.\n` +
|
||||
`Consider upgrading your application to use a more recent version of Angular.`;
|
||||
|
||||
const linkers = new Map<string, LinkerRange<TExpression>[]>();
|
||||
linkers.set(ɵɵngDeclareDirective, [
|
||||
{range: '0.0.0-PLACEHOLDER', linker: partialDirectiveLinkerVersion1},
|
||||
{range: '>=11.1.0-next.1', linker: partialDirectiveLinkerVersion1},
|
||||
]);
|
||||
linkers.set(ɵɵngDeclareComponent, [
|
||||
{range: '0.0.0-PLACEHOLDER', linker: partialComponentLinkerVersion1},
|
||||
{range: '>=11.1.0-next.1', linker: partialComponentLinkerVersion1},
|
||||
]);
|
||||
linkers.set(ɵɵngDeclareFactory, [
|
||||
{range: '0.0.0-PLACEHOLDER', linker: partialFactoryLinkerVersion1},
|
||||
{range: '>=11.1.0-next.1', linker: partialFactoryLinkerVersion1},
|
||||
]);
|
||||
linkers.set(ɵɵngDeclareInjector, [
|
||||
{range: '0.0.0-PLACEHOLDER', linker: partialInjectorLinkerVersion1},
|
||||
{range: '>=11.1.0-next.1', linker: partialInjectorLinkerVersion1},
|
||||
]);
|
||||
linkers.set(ɵɵngDeclareNgModule, [
|
||||
{range: '0.0.0-PLACEHOLDER', linker: partialNgModuleLinkerVersion1},
|
||||
{range: '>=11.1.0-next.1', linker: partialNgModuleLinkerVersion1},
|
||||
]);
|
||||
linkers.set(ɵɵngDeclarePipe, [
|
||||
{range: '0.0.0-PLACEHOLDER', linker: partialPipeLinkerVersion1},
|
||||
{range: '>=11.1.0-next.1', linker: partialPipeLinkerVersion1},
|
||||
]);
|
||||
return linkers;
|
||||
if (this.unknownDeclarationVersionHandling === 'error') {
|
||||
throw new Error(message);
|
||||
} else if (this.unknownDeclarationVersionHandling === 'warn') {
|
||||
this.logger.warn(`${message}\nAttempting to continue using this version of Angular.`);
|
||||
}
|
||||
|
||||
// No linker was matched for this declaration, so just use the most recent one.
|
||||
return linkerRanges[linkerRanges.length - 1].linker;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a semver Range from the `version` and comparator.
|
||||
*
|
||||
* The range is computed as any version greater/less than or equal to the given `versionStr`
|
||||
* depending upon the `comparator` (ignoring any prerelease versions).
|
||||
*
|
||||
* @param comparator a string that determines whether the version specifies a minimum or a maximum
|
||||
* range.
|
||||
* @param versionStr the version given in the partial declaration
|
||||
* @returns A semver range for the provided `version` and comparator.
|
||||
*/
|
||||
function getRange(comparator: '<='|'>=', versionStr: string): Range {
|
||||
const version = new SemVer(versionStr);
|
||||
// Wipe out any prerelease versions
|
||||
version.prerelease = [];
|
||||
return new Range(`${comparator}${version.format()}`);
|
||||
}
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
* 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 {R3Reference} from '@angular/compiler';
|
||||
import {createR3ProviderExpression, R3DeclareDependencyMetadata, R3DependencyMetadata, R3ProviderExpression, R3Reference} from '@angular/compiler';
|
||||
import * as o from '@angular/compiler/src/output/output_ast';
|
||||
import {AstValue} from '../../ast/ast_value';
|
||||
|
||||
import {AstObject, AstValue} from '../../ast/ast_value';
|
||||
import {FatalLinkerError} from '../../fatal_linker_error';
|
||||
|
||||
export function wrapReference<TExpression>(wrapped: o.WrappedNodeExpr<TExpression>): R3Reference {
|
||||
@@ -29,3 +30,66 @@ export function parseEnum<TExpression, TEnum>(
|
||||
}
|
||||
return enumValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a dependency structure from an AST object.
|
||||
*/
|
||||
export function getDependency<TExpression>(
|
||||
depObj: AstObject<R3DeclareDependencyMetadata, TExpression>): R3DependencyMetadata {
|
||||
const isAttribute = depObj.has('attribute') && depObj.getBoolean('attribute');
|
||||
const token = depObj.getOpaque('token');
|
||||
// Normally `attribute` is a string literal and so its `attributeNameType` is the same string
|
||||
// literal. If the `attribute` is some other expression, the `attributeNameType` would be the
|
||||
// `unknown` type. It is not possible to generate this when linking, since it only deals with JS
|
||||
// and not typings. When linking the existence of the `attributeNameType` only acts as a marker to
|
||||
// change the injection instruction that is generated, so we just pass the literal string
|
||||
// `"unknown"`.
|
||||
const attributeNameType = isAttribute ? o.literal('unknown') : null;
|
||||
return {
|
||||
token,
|
||||
attributeNameType,
|
||||
host: depObj.has('host') && depObj.getBoolean('host'),
|
||||
optional: depObj.has('optional') && depObj.getBoolean('optional'),
|
||||
self: depObj.has('self') && depObj.getBoolean('self'),
|
||||
skipSelf: depObj.has('skipSelf') && depObj.getBoolean('skipSelf'),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return an `R3ProviderExpression` that represents either the extracted type reference expression
|
||||
* from a `forwardRef` function call, or the type itself.
|
||||
*
|
||||
* For example, the expression `forwardRef(function() { return FooDir; })` returns `FooDir`. Note
|
||||
* that this expression is required to be wrapped in a closure, as otherwise the forward reference
|
||||
* would be resolved before initialization.
|
||||
*
|
||||
* If there is no forwardRef call expression then we just return the opaque type.
|
||||
*/
|
||||
export function extractForwardRef<TExpression>(expr: AstValue<unknown, TExpression>):
|
||||
R3ProviderExpression<o.WrappedNodeExpr<TExpression>> {
|
||||
if (!expr.isCallExpression()) {
|
||||
return createR3ProviderExpression(expr.getOpaque(), /* isForwardRef */ false);
|
||||
}
|
||||
|
||||
const callee = expr.getCallee();
|
||||
if (callee.getSymbolName() !== 'forwardRef') {
|
||||
throw new FatalLinkerError(
|
||||
callee.expression,
|
||||
'Unsupported expression, expected a `forwardRef()` call or a type reference');
|
||||
}
|
||||
|
||||
const args = expr.getArguments();
|
||||
if (args.length !== 1) {
|
||||
throw new FatalLinkerError(
|
||||
expr, 'Unsupported `forwardRef(fn)` call, expected a single argument');
|
||||
}
|
||||
|
||||
const wrapperFn = args[0] as AstValue<Function, TExpression>;
|
||||
if (!wrapperFn.isFunction()) {
|
||||
throw new FatalLinkerError(
|
||||
wrapperFn, 'Unsupported `forwardRef(fn)` call, expected its argument to be a function');
|
||||
}
|
||||
|
||||
return createR3ProviderExpression(wrapperFn.getFunctionReturnValue().getOpaque(), true);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ ts_library(
|
||||
"//packages/compiler-cli/src/ngtsc/file_system/testing",
|
||||
"//packages/compiler-cli/src/ngtsc/logging/testing",
|
||||
"//packages/compiler-cli/src/ngtsc/translator",
|
||||
"@npm//@types/semver",
|
||||
"@npm//semver",
|
||||
"@npm//typescript",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -94,6 +94,11 @@ describe('TypeScriptAstHost', () => {
|
||||
expect(host.isBooleanLiteral(expr('false'))).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true if the expression is a minified boolean literal', () => {
|
||||
expect(host.isBooleanLiteral(expr('!0'))).toBe(true);
|
||||
expect(host.isBooleanLiteral(expr('!1'))).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if the expression is not a boolean literal', () => {
|
||||
expect(host.isBooleanLiteral(expr('"moo"'))).toBe(false);
|
||||
expect(host.isBooleanLiteral(expr('\'moo\''))).toBe(false);
|
||||
@@ -104,6 +109,8 @@ describe('TypeScriptAstHost', () => {
|
||||
expect(host.isBooleanLiteral(expr('null'))).toBe(false);
|
||||
expect(host.isBooleanLiteral(expr('\'a\' + \'b\''))).toBe(false);
|
||||
expect(host.isBooleanLiteral(expr('\`moo\`'))).toBe(false);
|
||||
expect(host.isBooleanLiteral(expr('!2'))).toBe(false);
|
||||
expect(host.isBooleanLiteral(expr('~1'))).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -113,6 +120,11 @@ describe('TypeScriptAstHost', () => {
|
||||
expect(host.parseBooleanLiteral(expr('false'))).toEqual(false);
|
||||
});
|
||||
|
||||
it('should extract a minified boolean value', () => {
|
||||
expect(host.parseBooleanLiteral(expr('!0'))).toEqual(true);
|
||||
expect(host.parseBooleanLiteral(expr('!1'))).toEqual(false);
|
||||
});
|
||||
|
||||
it('should error if the value is not a boolean literal', () => {
|
||||
expect(() => host.parseBooleanLiteral(expr('"moo"')))
|
||||
.toThrowError('Unsupported syntax, expected a boolean literal.');
|
||||
|
||||
@@ -44,6 +44,7 @@ describe('FileLinker', () => {
|
||||
const version = factory.createLiteral('0.0.0-PLACEHOLDER');
|
||||
const ngImport = factory.createIdentifier('core');
|
||||
const declarationArg = factory.createObjectLiteral([
|
||||
{propertyName: 'minVersion', quoted: false, value: version},
|
||||
{propertyName: 'version', quoted: false, value: version},
|
||||
{propertyName: 'ngImport', quoted: false, value: ngImport},
|
||||
]);
|
||||
@@ -53,10 +54,26 @@ describe('FileLinker', () => {
|
||||
.toThrowError('Unknown partial declaration function foo.');
|
||||
});
|
||||
|
||||
it('should throw an error if the metadata object does not have a `version` property', () => {
|
||||
it('should throw an error if the metadata object does not have a `minVersion` property', () => {
|
||||
const {fileLinker} = createFileLinker();
|
||||
const version = factory.createLiteral('0.0.0-PLACEHOLDER');
|
||||
const ngImport = factory.createIdentifier('core');
|
||||
const declarationArg = factory.createObjectLiteral([
|
||||
{propertyName: 'version', quoted: false, value: version},
|
||||
{propertyName: 'ngImport', quoted: false, value: ngImport},
|
||||
]);
|
||||
expect(
|
||||
() => fileLinker.linkPartialDeclaration(
|
||||
'ɵɵngDeclareDirective', [declarationArg], new MockDeclarationScope()))
|
||||
.toThrowError(`Expected property 'minVersion' to be present.`);
|
||||
});
|
||||
|
||||
it('should throw an error if the metadata object does not have a `version` property', () => {
|
||||
const {fileLinker} = createFileLinker();
|
||||
const version = factory.createLiteral('0.0.0-PLACEHOLDER');
|
||||
const ngImport = factory.createIdentifier('core');
|
||||
const declarationArg = factory.createObjectLiteral([
|
||||
{propertyName: 'minVersion', quoted: false, value: version},
|
||||
{propertyName: 'ngImport', quoted: false, value: ngImport},
|
||||
]);
|
||||
expect(
|
||||
@@ -67,9 +84,10 @@ describe('FileLinker', () => {
|
||||
|
||||
it('should throw an error if the metadata object does not have a `ngImport` property', () => {
|
||||
const {fileLinker} = createFileLinker();
|
||||
const ngImport = factory.createIdentifier('core');
|
||||
const version = factory.createLiteral('0.0.0-PLACEHOLDER');
|
||||
const declarationArg = factory.createObjectLiteral([
|
||||
{propertyName: 'version', quoted: false, value: ngImport},
|
||||
{propertyName: 'minVersion', quoted: false, value: version},
|
||||
{propertyName: 'version', quoted: false, value: version},
|
||||
]);
|
||||
expect(
|
||||
() => fileLinker.linkPartialDeclaration(
|
||||
@@ -86,6 +104,7 @@ describe('FileLinker', () => {
|
||||
const version = factory.createLiteral('0.0.0-PLACEHOLDER');
|
||||
const declarationArg = factory.createObjectLiteral([
|
||||
{propertyName: 'ngImport', quoted: false, value: ngImport},
|
||||
{propertyName: 'minVersion', quoted: false, value: version},
|
||||
{propertyName: 'version', quoted: false, value: version},
|
||||
]);
|
||||
|
||||
@@ -107,6 +126,11 @@ describe('FileLinker', () => {
|
||||
// constant statements.
|
||||
const declarationArg = factory.createObjectLiteral([
|
||||
{propertyName: 'ngImport', quoted: false, value: factory.createIdentifier('core')},
|
||||
{
|
||||
propertyName: 'minVersion',
|
||||
quoted: false,
|
||||
value: factory.createLiteral('0.0.0-PLACEHOLDER')
|
||||
},
|
||||
{propertyName: 'version', quoted: false, value: factory.createLiteral('0.0.0-PLACEHOLDER')},
|
||||
]);
|
||||
|
||||
@@ -130,6 +154,11 @@ describe('FileLinker', () => {
|
||||
// statements to be emitted in an IIFE rather than added to the shared constant scope.
|
||||
const declarationArg = factory.createObjectLiteral([
|
||||
{propertyName: 'ngImport', quoted: false, value: factory.createLiteral('not-a-module')},
|
||||
{
|
||||
propertyName: 'minVersion',
|
||||
quoted: false,
|
||||
value: factory.createLiteral('0.0.0-PLACEHOLDER')
|
||||
},
|
||||
{
|
||||
propertyName: 'version',
|
||||
quoted: false,
|
||||
|
||||
+87
-94
@@ -5,126 +5,119 @@
|
||||
* 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 * as ts from 'typescript';
|
||||
import {Range} from 'semver';
|
||||
|
||||
import {DEFAULT_LINKER_OPTIONS, LinkerOptions} from '../../..';
|
||||
import {FileSystem} from '../../../../src/ngtsc/file_system';
|
||||
import {MockFileSystemNative} from '../../../../src/ngtsc/file_system/testing';
|
||||
import {MockLogger} from '../../../../src/ngtsc/logging/testing';
|
||||
import {TypeScriptAstFactory} from '../../../../src/ngtsc/translator';
|
||||
import {TypeScriptAstHost} from '../../../src/ast/typescript/typescript_ast_host';
|
||||
import {LinkerEnvironment} from '../../../src/file_linker/linker_environment';
|
||||
import {PartialComponentLinkerVersion1} from '../../../src/file_linker/partial_linkers/partial_component_linker_1';
|
||||
import {PartialDirectiveLinkerVersion1} from '../../../src/file_linker/partial_linkers/partial_directive_linker_1';
|
||||
import {PartialFactoryLinkerVersion1} from '../../../src/file_linker/partial_linkers/partial_factory_linker_1';
|
||||
import {PartialInjectorLinkerVersion1} from '../../../src/file_linker/partial_linkers/partial_injector_linker_1';
|
||||
import {PartialLinkerSelector} from '../../../src/file_linker/partial_linkers/partial_linker_selector';
|
||||
import {PartialNgModuleLinkerVersion1} from '../../../src/file_linker/partial_linkers/partial_ng_module_linker_1';
|
||||
import {PartialPipeLinkerVersion1} from '../../../src/file_linker/partial_linkers/partial_pipe_linker_1';
|
||||
import {PartialLinker} from '../../../src/file_linker/partial_linkers/partial_linker';
|
||||
import {LinkerRange, PartialLinkerSelector} from '../../../src/file_linker/partial_linkers/partial_linker_selector';
|
||||
|
||||
describe('PartialLinkerSelector', () => {
|
||||
const options: LinkerOptions = DEFAULT_LINKER_OPTIONS;
|
||||
let environment: LinkerEnvironment<ts.Statement, ts.Expression>;
|
||||
let fs: FileSystem;
|
||||
let logger: MockLogger;
|
||||
const linkerA = {name: 'linkerA'} as any;
|
||||
const linkerA2 = {name: 'linkerA2'} as any;
|
||||
const linkerB = {name: 'linkerB'} as any;
|
||||
const linkerB2 = {name: 'linkerB2'} as any;
|
||||
|
||||
beforeEach(() => {
|
||||
fs = new MockFileSystemNative();
|
||||
const logger = new MockLogger();
|
||||
environment = LinkerEnvironment.create<ts.Statement, ts.Expression>(
|
||||
fs, logger, new TypeScriptAstHost(),
|
||||
new TypeScriptAstFactory(/* annotateForClosureCompiler */ false), options);
|
||||
logger = new MockLogger();
|
||||
});
|
||||
|
||||
describe('supportsDeclaration()', () => {
|
||||
it('should return true if there is at least one linker that matches the given function name',
|
||||
() => {
|
||||
const selector = new PartialLinkerSelector(
|
||||
environment, fs.resolve('/some/path/to/file.js'), 'some file contents');
|
||||
expect(selector.supportsDeclaration('ɵɵngDeclareDirective')).toBe(true);
|
||||
expect(selector.supportsDeclaration('ɵɵngDeclareComponent')).toBe(true);
|
||||
expect(selector.supportsDeclaration('ɵɵngDeclareFactory')).toBe(true);
|
||||
expect(selector.supportsDeclaration('ɵɵngDeclareInjector')).toBe(true);
|
||||
expect(selector.supportsDeclaration('ɵɵngDeclareNgModule')).toBe(true);
|
||||
expect(selector.supportsDeclaration('ɵɵngDeclarePipe')).toBe(true);
|
||||
expect(selector.supportsDeclaration('$foo')).toBe(false);
|
||||
const selector = createSelector('error');
|
||||
expect(selector.supportsDeclaration('declareA')).toBe(true);
|
||||
expect(selector.supportsDeclaration('invalid')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for methods on `Object`', () => {
|
||||
const selector = new PartialLinkerSelector(
|
||||
environment, fs.resolve('/some/path/to/file.js'), 'some file contents');
|
||||
const selector = createSelector('error');
|
||||
expect(selector.supportsDeclaration('toString')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLinker()', () => {
|
||||
it('should return the latest linker if the version is "0.0.0-PLACEHOLDER"', () => {
|
||||
const selector = new PartialLinkerSelector(
|
||||
environment, fs.resolve('/some/path/to/file.js'), 'some file contents');
|
||||
expect(selector.getLinker('ɵɵngDeclareDirective', '0.0.0-PLACEHOLDER'))
|
||||
.toBeInstanceOf(PartialDirectiveLinkerVersion1);
|
||||
expect(selector.getLinker('ɵɵngDeclareComponent', '0.0.0-PLACEHOLDER'))
|
||||
.toBeInstanceOf(PartialComponentLinkerVersion1);
|
||||
expect(selector.getLinker('ɵɵngDeclareFactory', '0.0.0-PLACEHOLDER'))
|
||||
.toBeInstanceOf(PartialFactoryLinkerVersion1);
|
||||
expect(selector.getLinker('ɵɵngDeclareInjector', '0.0.0-PLACEHOLDER'))
|
||||
.toBeInstanceOf(PartialInjectorLinkerVersion1);
|
||||
expect(selector.getLinker('ɵɵngDeclareNgModule', '0.0.0-PLACEHOLDER'))
|
||||
.toBeInstanceOf(PartialNgModuleLinkerVersion1);
|
||||
expect(selector.getLinker('ɵɵngDeclarePipe', '0.0.0-PLACEHOLDER'))
|
||||
.toBeInstanceOf(PartialPipeLinkerVersion1);
|
||||
it('should return the linker that matches the name and version', () => {
|
||||
const selector = createSelector('error');
|
||||
expect(selector.getLinker('declareA', '11.1.2', '11.1.4')).toBe(linkerA);
|
||||
expect(selector.getLinker('declareA', '12.0.0', '12.1.0')).toBe(linkerA);
|
||||
expect(selector.getLinker('declareA', '12.0.1', '12.1.0')).toBe(linkerA2);
|
||||
expect(selector.getLinker('declareB', '11.2.5', '11.3.0')).toBe(linkerB);
|
||||
expect(selector.getLinker('declareB', '12.0.5', '12.0.5')).toBe(linkerB2);
|
||||
});
|
||||
|
||||
it('should return the linker that matches the name and valid full version', () => {
|
||||
const selector = new PartialLinkerSelector(
|
||||
environment, fs.resolve('/some/path/to/file.js'), 'some file contents');
|
||||
expect(selector.getLinker('ɵɵngDeclareDirective', '11.1.2'))
|
||||
.toBeInstanceOf(PartialDirectiveLinkerVersion1);
|
||||
expect(selector.getLinker('ɵɵngDeclareDirective', '11.2.5'))
|
||||
.toBeInstanceOf(PartialDirectiveLinkerVersion1);
|
||||
expect(selector.getLinker('ɵɵngDeclareDirective', '12.0.0'))
|
||||
.toBeInstanceOf(PartialDirectiveLinkerVersion1);
|
||||
it('should return the linker that matches the name and version, ignoring pre-releases', () => {
|
||||
const selector = createSelector('error');
|
||||
expect(selector.getLinker('declareA', '11.1.0-next.1', '11.1.0-next.1')).toBe(linkerA);
|
||||
expect(selector.getLinker('declareA', '11.1.0-next.7', '11.1.0-next.7')).toBe(linkerA);
|
||||
expect(selector.getLinker('declareA', '12.0.0-next.7', '12.0.0-next.7')).toBe(linkerA);
|
||||
expect(selector.getLinker('declareA', '12.0.1-next.7', '12.0.1-next.7')).toBe(linkerA2);
|
||||
});
|
||||
|
||||
it('should return the linker that matches the name and valid pre-release versions', () => {
|
||||
const selector = new PartialLinkerSelector(
|
||||
environment, fs.resolve('/some/path/to/file.js'), 'some file contents');
|
||||
expect(selector.getLinker('ɵɵngDeclareDirective', '11.1.0-next.1'))
|
||||
.toBeInstanceOf(PartialDirectiveLinkerVersion1);
|
||||
expect(selector.getLinker('ɵɵngDeclareDirective', '11.1.0-next.7'))
|
||||
.toBeInstanceOf(PartialDirectiveLinkerVersion1);
|
||||
expect(selector.getLinker('ɵɵngDeclareDirective', '12.0.0-next.7'))
|
||||
.toBeInstanceOf(PartialDirectiveLinkerVersion1);
|
||||
});
|
||||
it('should return the most recent linker if `version` is `0.0.0-PLACEHOLDER`, regardless of `minVersion`',
|
||||
() => {
|
||||
const selector = createSelector('error');
|
||||
expect(selector.getLinker('declareA', '11.1.2', '0.0.0-PLACEHOLDER')).toBe(linkerA2);
|
||||
expect(selector.getLinker('declareA', '0.0.0-PLACEHOLDER', '11.1.2')).toBe(linkerA);
|
||||
expect(selector.getLinker('declareA', '0.0.0-PLACEHOLDER', '0.0.0-PLACEHOLDER'))
|
||||
.toBe(linkerA2);
|
||||
});
|
||||
|
||||
it('should throw an error if there is no linker that matches the given name or version', () => {
|
||||
const selector = new PartialLinkerSelector(
|
||||
environment, fs.resolve('/some/path/to/file.js'), 'some file contents');
|
||||
// `$foo` is not a valid name, even though `0.0.0-PLACEHOLDER` is a valid version
|
||||
expect(() => selector.getLinker('$foo', '0.0.0-PLACEHOLDER'))
|
||||
.toThrowError('Unknown partial declaration function $foo.');
|
||||
// `$foo` is not a valid name, even though `11.1.0` is a valid version
|
||||
expect(() => selector.getLinker('$foo', '11.1.0'))
|
||||
it('should throw an error if there is no linker that matches the given name', () => {
|
||||
const selector = createSelector('error');
|
||||
// `$foo` is not a valid name, even though `11.1.2` is a valid version for other declarations
|
||||
expect(() => selector.getLinker('$foo', '11.1.2', '11.2.0'))
|
||||
.toThrowError('Unknown partial declaration function $foo.');
|
||||
});
|
||||
|
||||
// There are no linkers earlier than 11.1.0-next.1
|
||||
expect(() => selector.getLinker('ɵɵngDeclareDirective', '10.2.1'))
|
||||
.toThrowError(
|
||||
'Unsupported partial declaration version 10.2.1 for ɵɵngDeclareDirective.\n' +
|
||||
'Valid version ranges are:\n' +
|
||||
' - 0.0.0-PLACEHOLDER\n' +
|
||||
' - >=11.1.0-next.1');
|
||||
expect(() => selector.getLinker('ɵɵngDeclareDirective', '11.0.2'))
|
||||
.toThrowError(
|
||||
'Unsupported partial declaration version 11.0.2 for ɵɵngDeclareDirective.\n' +
|
||||
'Valid version ranges are:\n' +
|
||||
' - 0.0.0-PLACEHOLDER\n' +
|
||||
' - >=11.1.0-next.1');
|
||||
expect(() => selector.getLinker('ɵɵngDeclareDirective', '11.1.0-next.0'))
|
||||
.toThrowError(
|
||||
'Unsupported partial declaration version 11.1.0-next.0 for ɵɵngDeclareDirective.\n' +
|
||||
'Valid version ranges are:\n' +
|
||||
' - 0.0.0-PLACEHOLDER\n' +
|
||||
' - >=11.1.0-next.1');
|
||||
describe('[unknown declaration version]', () => {
|
||||
describe('[unknownDeclarationVersionHandling is "ignore"]', () => {
|
||||
it('should use the most recent linker, with no log warning', () => {
|
||||
const selector = createSelector('ignore');
|
||||
expect(selector.getLinker('declareA', '13.1.0', '13.1.5')).toBe(linkerA2);
|
||||
expect(logger.logs.warn).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('[unknownDeclarationVersionHandling is "warn"]', () => {
|
||||
it('should use the most recent linker and log a warning', () => {
|
||||
const selector = createSelector('warn');
|
||||
expect(selector.getLinker('declareA', '13.1.0', '14.0.5')).toBe(linkerA2);
|
||||
expect(logger.logs.warn).toEqual([
|
||||
[`This application depends upon a library published using Angular version 14.0.5, ` +
|
||||
`which requires Angular version 13.1.0 or newer to work correctly.\n` +
|
||||
`Consider upgrading your application to use a more recent version of Angular.\n` +
|
||||
'Attempting to continue using this version of Angular.']
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('[unknownDeclarationVersionHandling is "error"]', () => {
|
||||
it('should throw an error', () => {
|
||||
const selector = createSelector('error');
|
||||
expect(() => selector.getLinker('declareA', '13.1.0', '14.0.5'))
|
||||
.toThrowError(
|
||||
`This application depends upon a library published using Angular version 14.0.5, ` +
|
||||
`which requires Angular version 13.1.0 or newer to work correctly.\n` +
|
||||
`Consider upgrading your application to use a more recent version of Angular.`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Create a selector for testing
|
||||
*/
|
||||
function createSelector(unknownDeclarationVersionHandling: 'error'|'warn'|'ignore') {
|
||||
const linkerMap = new Map<string, LinkerRange<unknown>[]>();
|
||||
linkerMap.set('declareA', [
|
||||
{range: new Range('<=12.0.0'), linker: linkerA},
|
||||
{range: new Range('<=13.0.0'), linker: linkerA2}
|
||||
]);
|
||||
linkerMap.set('declareB', [
|
||||
{range: new Range('<=12.0.0'), linker: linkerB},
|
||||
{range: new Range('<=12.1.0'), linker: linkerB2},
|
||||
]);
|
||||
return new PartialLinkerSelector(linkerMap, logger, unknownDeclarationVersionHandling);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ import {ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecorato
|
||||
import {CycleAnalyzer, CycleHandlingStrategy, ImportGraph} from '../../../src/ngtsc/cycles';
|
||||
import {isFatalDiagnosticError} from '../../../src/ngtsc/diagnostics';
|
||||
import {absoluteFromSourceFile, LogicalFileSystem, ReadonlyFileSystem} from '../../../src/ngtsc/file_system';
|
||||
import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, NOOP_DEFAULT_IMPORT_RECORDER, PrivateExportAliasingHost, Reexport, ReferenceEmitter} from '../../../src/ngtsc/imports';
|
||||
import {AbsoluteModuleStrategy, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, PrivateExportAliasingHost, Reexport, ReferenceEmitter} from '../../../src/ngtsc/imports';
|
||||
import {SemanticSymbol} from '../../../src/ngtsc/incremental/semantic_graph';
|
||||
import {CompoundMetadataReader, CompoundMetadataRegistry, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry, ResourceRegistry} from '../../../src/ngtsc/metadata';
|
||||
import {PartialEvaluator} from '../../../src/ngtsc/partial_evaluator';
|
||||
@@ -107,8 +107,8 @@ export class DecorationAnalyzer {
|
||||
/* i18nUseExternalIds */ true, this.bundle.enableI18nLegacyMessageIdFormat,
|
||||
/* usePoisonedData */ false,
|
||||
/* i18nNormalizeLineEndingsInICUs */ false, this.moduleResolver, this.cycleAnalyzer,
|
||||
CycleHandlingStrategy.UseRemoteScoping, this.refEmitter, NOOP_DEFAULT_IMPORT_RECORDER,
|
||||
NOOP_DEPENDENCY_TRACKER, this.injectableRegistry,
|
||||
CycleHandlingStrategy.UseRemoteScoping, this.refEmitter, NOOP_DEPENDENCY_TRACKER,
|
||||
this.injectableRegistry,
|
||||
/* semanticDepGraphUpdater */ null, !!this.compilerOptions.annotateForClosureCompiler,
|
||||
NOOP_PERF_RECORDER),
|
||||
|
||||
@@ -116,7 +116,7 @@ export class DecorationAnalyzer {
|
||||
// clang-format off
|
||||
new DirectiveDecoratorHandler(
|
||||
this.reflectionHost, this.evaluator, this.fullRegistry, this.scopeRegistry,
|
||||
this.fullMetaReader, NOOP_DEFAULT_IMPORT_RECORDER, this.injectableRegistry, this.isCore,
|
||||
this.fullMetaReader, this.injectableRegistry, this.isCore,
|
||||
/* semanticDepGraphUpdater */ null,
|
||||
!!this.compilerOptions.annotateForClosureCompiler,
|
||||
// In ngcc we want to compile undecorated classes with Angular features. As of
|
||||
@@ -131,18 +131,17 @@ export class DecorationAnalyzer {
|
||||
// before injectable factories (so injectable factories can delegate to them)
|
||||
new PipeDecoratorHandler(
|
||||
this.reflectionHost, this.evaluator, this.metaRegistry, this.scopeRegistry,
|
||||
NOOP_DEFAULT_IMPORT_RECORDER, this.injectableRegistry, this.isCore, NOOP_PERF_RECORDER),
|
||||
this.injectableRegistry, this.isCore, NOOP_PERF_RECORDER),
|
||||
new InjectableDecoratorHandler(
|
||||
this.reflectionHost, NOOP_DEFAULT_IMPORT_RECORDER, this.isCore,
|
||||
this.reflectionHost, this.isCore,
|
||||
/* strictCtorDeps */ false, this.injectableRegistry, NOOP_PERF_RECORDER,
|
||||
/* errorOnDuplicateProv */ false),
|
||||
new NgModuleDecoratorHandler(
|
||||
this.reflectionHost, this.evaluator, this.fullMetaReader, this.fullRegistry,
|
||||
this.scopeRegistry, this.referencesRegistry, this.isCore, /* routeAnalyzer */ null,
|
||||
this.refEmitter,
|
||||
/* factoryTracker */ null, NOOP_DEFAULT_IMPORT_RECORDER,
|
||||
!!this.compilerOptions.annotateForClosureCompiler, this.injectableRegistry,
|
||||
NOOP_PERF_RECORDER),
|
||||
/* factoryTracker */ null, !!this.compilerOptions.annotateForClosureCompiler,
|
||||
this.injectableRegistry, NOOP_PERF_RECORDER),
|
||||
];
|
||||
compiler = new NgccTraitCompiler(this.handlers, this.reflectionHost);
|
||||
migrations: Migration[] = [
|
||||
|
||||
@@ -85,7 +85,7 @@ export class NgccTraitCompiler extends TraitCompiler {
|
||||
}
|
||||
|
||||
class NoIncrementalBuild implements IncrementalBuild<any, any> {
|
||||
priorWorkFor(sf: ts.SourceFile): any[]|null {
|
||||
priorAnalysisFor(sf: ts.SourceFile): any[]|null {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,11 +16,11 @@ import {DependencyHostBase} from './dependency_host';
|
||||
* Helper functions for computing dependencies.
|
||||
*/
|
||||
export class CommonJsDependencyHost extends DependencyHostBase {
|
||||
protected canSkipFile(fileContents: string): boolean {
|
||||
protected override canSkipFile(fileContents: string): boolean {
|
||||
return !hasRequireCalls(fileContents);
|
||||
}
|
||||
|
||||
protected extractImports(file: AbsoluteFsPath, fileContents: string): Set<string> {
|
||||
protected override extractImports(file: AbsoluteFsPath, fileContents: string): Set<string> {
|
||||
// Parse the source into a TypeScript AST and then walk it looking for imports and re-exports.
|
||||
const sf =
|
||||
ts.createSourceFile(file, fileContents, ts.ScriptTarget.ES2015, false, ts.ScriptKind.JS);
|
||||
|
||||
@@ -23,7 +23,7 @@ export class DtsDependencyHost extends EsmDependencyHost {
|
||||
/**
|
||||
* Attempts to process the `importPath` directly and also inside `@types/...`.
|
||||
*/
|
||||
protected processImport(
|
||||
protected override processImport(
|
||||
importPath: string, file: AbsoluteFsPath, dependencies: Set<AbsoluteFsPath>,
|
||||
missing: Set<string>, deepImports: Set<string>, alreadySeen: Set<AbsoluteFsPath>): boolean {
|
||||
return super.processImport(importPath, file, dependencies, missing, deepImports, alreadySeen) ||
|
||||
|
||||
@@ -23,7 +23,7 @@ export class EsmDependencyHost extends DependencyHostBase {
|
||||
// It has no relevance to capturing imports.
|
||||
private scanner = ts.createScanner(ts.ScriptTarget.Latest, /* skipTrivia */ true);
|
||||
|
||||
protected canSkipFile(fileContents: string): boolean {
|
||||
protected override canSkipFile(fileContents: string): boolean {
|
||||
return !hasImportOrReexportStatements(fileContents);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ export class EsmDependencyHost extends DependencyHostBase {
|
||||
* Specifically, backticked strings are particularly challenging since it is possible
|
||||
* to recursively nest backticks and TypeScript expressions within each other.
|
||||
*/
|
||||
protected extractImports(file: AbsoluteFsPath, fileContents: string): Set<string> {
|
||||
protected override extractImports(file: AbsoluteFsPath, fileContents: string): Set<string> {
|
||||
const imports = new Set<string>();
|
||||
const templateStack: ts.SyntaxKind[] = [];
|
||||
let lastToken: ts.SyntaxKind = ts.SyntaxKind.Unknown;
|
||||
|
||||
@@ -17,11 +17,11 @@ import {DependencyHostBase} from './dependency_host';
|
||||
* Helper functions for computing dependencies.
|
||||
*/
|
||||
export class UmdDependencyHost extends DependencyHostBase {
|
||||
protected canSkipFile(fileContents: string): boolean {
|
||||
protected override canSkipFile(fileContents: string): boolean {
|
||||
return !hasRequireCalls(fileContents);
|
||||
}
|
||||
|
||||
protected extractImports(file: AbsoluteFsPath, fileContents: string): Set<string> {
|
||||
protected override extractImports(file: AbsoluteFsPath, fileContents: string): Set<string> {
|
||||
// Parse the source into a TypeScript AST and then walk it looking for imports and re-exports.
|
||||
const sf =
|
||||
ts.createSourceFile(file, fileContents, ts.ScriptTarget.ES2015, false, ts.ScriptKind.JS);
|
||||
|
||||
+3
-2
@@ -45,7 +45,7 @@ export class ProgramBasedEntryPointFinder extends TracingEntryPointFinder {
|
||||
* Return an array containing the external import paths that were extracted from the source-files
|
||||
* of the program defined by the tsconfig.json.
|
||||
*/
|
||||
protected getInitialEntryPointPaths(): AbsoluteFsPath[] {
|
||||
protected override getInitialEntryPointPaths(): AbsoluteFsPath[] {
|
||||
const moduleResolver = new ModuleResolver(this.fs, this.pathMappings, ['', '.ts', '/index.ts']);
|
||||
const host = new EsmDependencyHost(this.fs, moduleResolver);
|
||||
const dependencies = createDependencyInfo();
|
||||
@@ -71,7 +71,8 @@ export class ProgramBasedEntryPointFinder extends TracingEntryPointFinder {
|
||||
* @returns the entry-point and its dependencies or `null` if the entry-point is not compiled by
|
||||
* Angular or cannot be determined.
|
||||
*/
|
||||
protected getEntryPointWithDeps(entryPointPath: AbsoluteFsPath): EntryPointWithDependencies|null {
|
||||
protected override getEntryPointWithDeps(entryPointPath: AbsoluteFsPath):
|
||||
EntryPointWithDependencies|null {
|
||||
const entryPoints = this.findOrLoadEntryPoints();
|
||||
if (!entryPoints.has(entryPointPath)) {
|
||||
return null;
|
||||
|
||||
@@ -35,7 +35,7 @@ export class TargetedEntryPointFinder extends TracingEntryPointFinder {
|
||||
* Search for Angular entry-points that can be reached from the entry-point specified by the given
|
||||
* `targetPath`.
|
||||
*/
|
||||
findEntryPoints(): SortedEntryPointsInfo {
|
||||
override findEntryPoints(): SortedEntryPointsInfo {
|
||||
const entryPoints = super.findEntryPoints();
|
||||
|
||||
const invalidTarget =
|
||||
@@ -83,7 +83,7 @@ export class TargetedEntryPointFinder extends TracingEntryPointFinder {
|
||||
/**
|
||||
* Return an array containing the `targetPath` from which to start the trace.
|
||||
*/
|
||||
protected getInitialEntryPointPaths(): AbsoluteFsPath[] {
|
||||
protected override getInitialEntryPointPaths(): AbsoluteFsPath[] {
|
||||
return [this.targetPath];
|
||||
}
|
||||
|
||||
@@ -97,7 +97,8 @@ export class TargetedEntryPointFinder extends TracingEntryPointFinder {
|
||||
* @returns the entry-point and its dependencies or `null` if the entry-point is not compiled by
|
||||
* Angular or cannot be determined.
|
||||
*/
|
||||
protected getEntryPointWithDeps(entryPointPath: AbsoluteFsPath): EntryPointWithDependencies|null {
|
||||
protected override getEntryPointWithDeps(entryPointPath: AbsoluteFsPath):
|
||||
EntryPointWithDependencies|null {
|
||||
const packagePath = this.computePackagePath(entryPointPath);
|
||||
const entryPoint =
|
||||
getEntryPointInfo(this.fs, this.config, this.logger, packagePath, entryPointPath);
|
||||
|
||||
@@ -27,7 +27,7 @@ export class ParallelTaskQueue extends BaseTaskQueue {
|
||||
this.blockedTasks = getBlockedTasks(dependencies);
|
||||
}
|
||||
|
||||
computeNextTask(): Task|null {
|
||||
override computeNextTask(): Task|null {
|
||||
// Look for the first available (i.e. not blocked) task.
|
||||
// (NOTE: Since tasks are sorted by priority, the first available one is the best choice.)
|
||||
const nextTaskIdx = this.tasks.findIndex(task => !this.blockedTasks.has(task));
|
||||
@@ -41,7 +41,7 @@ export class ParallelTaskQueue extends BaseTaskQueue {
|
||||
return nextTask;
|
||||
}
|
||||
|
||||
markAsCompleted(task: Task): void {
|
||||
override markAsCompleted(task: Task): void {
|
||||
super.markAsCompleted(task);
|
||||
|
||||
if (!this.dependencies.has(task)) {
|
||||
@@ -62,7 +62,7 @@ export class ParallelTaskQueue extends BaseTaskQueue {
|
||||
}
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
override toString(): string {
|
||||
return `${super.toString()}\n` +
|
||||
` Blocked tasks (${this.blockedTasks.size}): ${this.stringifyBlockedTasks(' ')}`;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import {BaseTaskQueue} from './base_task_queue';
|
||||
* before requesting the next one.
|
||||
*/
|
||||
export class SerialTaskQueue extends BaseTaskQueue {
|
||||
computeNextTask(): Task|null {
|
||||
override computeNextTask(): Task|null {
|
||||
const nextTask = this.tasks.shift() || null;
|
||||
|
||||
if (nextTask) {
|
||||
|
||||
@@ -35,7 +35,7 @@ export class CommonJsReflectionHost extends Esm5ReflectionHost {
|
||||
this.compilerHost = src.host;
|
||||
}
|
||||
|
||||
getImportOfIdentifier(id: ts.Identifier): Import|null {
|
||||
override getImportOfIdentifier(id: ts.Identifier): Import|null {
|
||||
const requireCall = this.findCommonJsImport(id);
|
||||
if (requireCall === null) {
|
||||
return null;
|
||||
@@ -43,11 +43,11 @@ export class CommonJsReflectionHost extends Esm5ReflectionHost {
|
||||
return {from: requireCall.arguments[0].text, name: id.text};
|
||||
}
|
||||
|
||||
getDeclarationOfIdentifier(id: ts.Identifier): Declaration|null {
|
||||
override getDeclarationOfIdentifier(id: ts.Identifier): Declaration|null {
|
||||
return this.getCommonJsModuleDeclaration(id) || super.getDeclarationOfIdentifier(id);
|
||||
}
|
||||
|
||||
getExportsOfModule(module: ts.Node): Map<string, Declaration>|null {
|
||||
override getExportsOfModule(module: ts.Node): Map<string, Declaration>|null {
|
||||
return super.getExportsOfModule(module) || this.commonJsExports.get(module.getSourceFile());
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ export class CommonJsReflectionHost extends Esm5ReflectionHost {
|
||||
* in.
|
||||
* @returns an array of nodes of calls to the helper with the given name.
|
||||
*/
|
||||
protected getHelperCallsForClass(classSymbol: NgccClassSymbol, helperNames: string[]):
|
||||
protected override getHelperCallsForClass(classSymbol: NgccClassSymbol, helperNames: string[]):
|
||||
ts.CallExpression[] {
|
||||
const esm5HelperCalls = super.getHelperCallsForClass(classSymbol, helperNames);
|
||||
if (esm5HelperCalls.length > 0) {
|
||||
@@ -221,7 +221,7 @@ export class CommonJsReflectionHost extends Esm5ReflectionHost {
|
||||
* If this is an IFE then try to grab the outer and inner classes otherwise fallback on the super
|
||||
* class.
|
||||
*/
|
||||
protected getDeclarationOfExpression(expression: ts.Expression): Declaration|null {
|
||||
protected override getDeclarationOfExpression(expression: ts.Expression): Declaration|null {
|
||||
const inner = getInnerClassDeclaration(expression);
|
||||
if (inner !== null) {
|
||||
const outer = getOuterNodeFromInnerDeclaration(inner);
|
||||
|
||||
@@ -161,4 +161,8 @@ export class DelegatingReflectionHost implements NgccReflectionHost {
|
||||
detectKnownDeclaration<T extends Declaration>(decl: T): T {
|
||||
return this.ngccHost.detectKnownDeclaration(decl);
|
||||
}
|
||||
|
||||
isStaticallyExported(clazz: ClassDeclaration): boolean {
|
||||
return this.ngccHost.isStaticallyExported(clazz);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import * as ts from 'typescript';
|
||||
import {absoluteFromSourceFile} from '../../../src/ngtsc/file_system';
|
||||
import {Logger} from '../../../src/ngtsc/logging';
|
||||
import {ClassDeclaration, ClassMember, ClassMemberKind, CtorParameter, Declaration, DeclarationNode, Decorator, EnumMember, Import, isConcreteDeclaration, isDecoratorIdentifier, isNamedClassDeclaration, isNamedFunctionDeclaration, isNamedVariableDeclaration, KnownDeclaration, reflectObjectLiteral, SpecialDeclarationKind, TypeScriptReflectionHost, TypeValueReference, TypeValueReferenceKind, ValueUnavailableKind} from '../../../src/ngtsc/reflection';
|
||||
import {isSymbolWithValueDeclaration, SymbolWithValueDeclaration} from '../../../src/ngtsc/util/src/typescript';
|
||||
import {isWithinPackage} from '../analysis/util';
|
||||
import {BundleProgram} from '../packages/bundle_program';
|
||||
import {findAll, getNameText, hasNameIdentifier, isDefined, stripDollarSuffix} from '../utils';
|
||||
@@ -150,7 +151,7 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
* `null` if either no decorators were present or if the declaration is not of a decoratable
|
||||
* type.
|
||||
*/
|
||||
getDecoratorsOfDeclaration(declaration: DeclarationNode): Decorator[]|null {
|
||||
override getDecoratorsOfDeclaration(declaration: DeclarationNode): Decorator[]|null {
|
||||
const symbol = this.getClassSymbol(declaration);
|
||||
if (!symbol) {
|
||||
return null;
|
||||
@@ -168,7 +169,7 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
*
|
||||
* @throws if `declaration` does not resolve to a class declaration.
|
||||
*/
|
||||
getMembersOfClass(clazz: ClassDeclaration): ClassMember[] {
|
||||
override getMembersOfClass(clazz: ClassDeclaration): ClassMember[] {
|
||||
const classSymbol = this.getClassSymbol(clazz);
|
||||
if (!classSymbol) {
|
||||
throw new Error(`Attempted to get members of a non-class: "${clazz.getText()}"`);
|
||||
@@ -191,7 +192,7 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
*
|
||||
* @throws if `declaration` does not resolve to a class declaration.
|
||||
*/
|
||||
getConstructorParameters(clazz: ClassDeclaration): CtorParameter[]|null {
|
||||
override getConstructorParameters(clazz: ClassDeclaration): CtorParameter[]|null {
|
||||
const classSymbol = this.getClassSymbol(clazz);
|
||||
if (!classSymbol) {
|
||||
throw new Error(
|
||||
@@ -204,7 +205,7 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
return null;
|
||||
}
|
||||
|
||||
getBaseClassExpression(clazz: ClassDeclaration): ts.Expression|null {
|
||||
override getBaseClassExpression(clazz: ClassDeclaration): ts.Expression|null {
|
||||
// First try getting the base class from an ES2015 class declaration
|
||||
const superBaseClassIdentifier = super.getBaseClassExpression(clazz);
|
||||
if (superBaseClassIdentifier) {
|
||||
@@ -213,14 +214,14 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
|
||||
// That didn't work so now try getting it from the "inner" declaration.
|
||||
const classSymbol = this.getClassSymbol(clazz);
|
||||
if (classSymbol === undefined ||
|
||||
if (classSymbol?.implementation.valueDeclaration === undefined ||
|
||||
!isNamedDeclaration(classSymbol.implementation.valueDeclaration)) {
|
||||
return null;
|
||||
}
|
||||
return super.getBaseClassExpression(classSymbol.implementation.valueDeclaration);
|
||||
}
|
||||
|
||||
getInternalNameOfClass(clazz: ClassDeclaration): ts.Identifier {
|
||||
override getInternalNameOfClass(clazz: ClassDeclaration): ts.Identifier {
|
||||
const classSymbol = this.getClassSymbol(clazz);
|
||||
if (classSymbol === undefined) {
|
||||
throw new Error(`getInternalNameOfClass() called on a non-class: expected ${
|
||||
@@ -230,7 +231,7 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
classSymbol, classSymbol.implementation.valueDeclaration);
|
||||
}
|
||||
|
||||
getAdjacentNameOfClass(clazz: ClassDeclaration): ts.Identifier {
|
||||
override getAdjacentNameOfClass(clazz: ClassDeclaration): ts.Identifier {
|
||||
const classSymbol = this.getClassSymbol(clazz);
|
||||
if (classSymbol === undefined) {
|
||||
throw new Error(`getAdjacentNameOfClass() called on a non-class: expected ${
|
||||
@@ -241,7 +242,7 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
}
|
||||
|
||||
private getNameFromClassSymbolDeclaration(
|
||||
classSymbol: NgccClassSymbol, declaration: ts.Declaration): ts.Identifier {
|
||||
classSymbol: NgccClassSymbol, declaration: ts.Declaration|undefined): ts.Identifier {
|
||||
if (declaration === undefined) {
|
||||
throw new Error(
|
||||
`getInternalNameOfClass() called on a class with an undefined internal declaration. External class name: ${
|
||||
@@ -258,7 +259,7 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
/**
|
||||
* Check whether the given node actually represents a class.
|
||||
*/
|
||||
isClass(node: ts.Node): node is ClassDeclaration {
|
||||
override isClass(node: ts.Node): node is ClassDeclaration {
|
||||
return super.isClass(node) || this.getClassSymbol(node) !== undefined;
|
||||
}
|
||||
|
||||
@@ -278,7 +279,7 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
* @returns metadata about the `Declaration` if the original declaration is found, or `null`
|
||||
* otherwise.
|
||||
*/
|
||||
getDeclarationOfIdentifier(id: ts.Identifier): Declaration|null {
|
||||
override getDeclarationOfIdentifier(id: ts.Identifier): Declaration|null {
|
||||
const superDeclaration = super.getDeclarationOfIdentifier(id);
|
||||
|
||||
// If no declaration was found, return.
|
||||
@@ -354,7 +355,7 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
[];
|
||||
}
|
||||
|
||||
getVariableValue(declaration: ts.VariableDeclaration): ts.Expression|null {
|
||||
override getVariableValue(declaration: ts.VariableDeclaration): ts.Expression|null {
|
||||
const value = super.getVariableValue(declaration);
|
||||
if (value) {
|
||||
return value;
|
||||
@@ -433,7 +434,7 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
* @returns the number of type parameters of the class, if known, or `null` if the declaration
|
||||
* is not a class or has an unknown number of type parameters.
|
||||
*/
|
||||
getGenericArityOfClass(clazz: ClassDeclaration): number|null {
|
||||
override getGenericArityOfClass(clazz: ClassDeclaration): number|null {
|
||||
const dtsDeclaration = this.getDtsDeclaration(clazz);
|
||||
if (dtsDeclaration && ts.isClassDeclaration(dtsDeclaration)) {
|
||||
return dtsDeclaration.typeParameters ? dtsDeclaration.typeParameters.length : 0;
|
||||
@@ -453,7 +454,7 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
* Note that the `ts.ClassDeclaration` returned from this function may not be from the same
|
||||
* `ts.Program` as the input declaration.
|
||||
*/
|
||||
getDtsDeclaration(declaration: DeclarationNode): ts.Declaration|null {
|
||||
override getDtsDeclaration(declaration: DeclarationNode): ts.Declaration|null {
|
||||
if (this.dts === null) {
|
||||
return null;
|
||||
}
|
||||
@@ -721,12 +722,12 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let implementationSymbol = declarationSymbol;
|
||||
let implementationSymbol: ts.Symbol|undefined = declarationSymbol;
|
||||
if (innerDeclaration !== null && isNamedDeclaration(innerDeclaration)) {
|
||||
implementationSymbol = this.checker.getSymbolAtLocation(innerDeclaration.name) as ClassSymbol;
|
||||
implementationSymbol = this.checker.getSymbolAtLocation(innerDeclaration.name);
|
||||
}
|
||||
|
||||
if (implementationSymbol === undefined) {
|
||||
if (!isSymbolWithValueDeclaration(implementationSymbol)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -740,8 +741,9 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
return classSymbol;
|
||||
}
|
||||
|
||||
private getAdjacentSymbol(declarationSymbol: ClassSymbol, implementationSymbol: ClassSymbol):
|
||||
ClassSymbol|undefined {
|
||||
private getAdjacentSymbol(
|
||||
declarationSymbol: ClassSymbol,
|
||||
implementationSymbol: SymbolWithValueDeclaration): SymbolWithValueDeclaration|undefined {
|
||||
if (declarationSymbol === implementationSymbol) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -755,9 +757,9 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
if (adjacentDeclaration === undefined || !isNamedVariableDeclaration(adjacentDeclaration)) {
|
||||
return undefined;
|
||||
}
|
||||
const adjacentSymbol =
|
||||
this.checker.getSymbolAtLocation(adjacentDeclaration.name) as ClassSymbol;
|
||||
if (adjacentSymbol === declarationSymbol || adjacentSymbol === implementationSymbol) {
|
||||
const adjacentSymbol = this.checker.getSymbolAtLocation(adjacentDeclaration.name);
|
||||
if (adjacentSymbol === declarationSymbol || adjacentSymbol === implementationSymbol ||
|
||||
!isSymbolWithValueDeclaration(adjacentSymbol)) {
|
||||
return undefined;
|
||||
}
|
||||
return adjacentSymbol;
|
||||
@@ -767,8 +769,8 @@ export class Esm2015ReflectionHost extends TypeScriptReflectionHost implements N
|
||||
* Resolve a `ts.Symbol` to its declaration and detect whether it corresponds with a known
|
||||
* declaration.
|
||||
*/
|
||||
protected getDeclarationOfSymbol(symbol: ts.Symbol, originalId: ts.Identifier|null): Declaration
|
||||
|null {
|
||||
protected override getDeclarationOfSymbol(symbol: ts.Symbol, originalId: ts.Identifier|null):
|
||||
Declaration|null {
|
||||
const declaration = super.getDeclarationOfSymbol(symbol, originalId);
|
||||
if (declaration === null) {
|
||||
return null;
|
||||
|
||||
@@ -34,7 +34,7 @@ import {NgccClassSymbol} from './ngcc_host';
|
||||
*
|
||||
*/
|
||||
export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
||||
getBaseClassExpression(clazz: ClassDeclaration): ts.Expression|null {
|
||||
override getBaseClassExpression(clazz: ClassDeclaration): ts.Expression|null {
|
||||
const superBaseClassExpression = super.getBaseClassExpression(clazz);
|
||||
if (superBaseClassExpression !== null) {
|
||||
return superBaseClassExpression;
|
||||
@@ -71,7 +71,7 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
||||
* @returns metadata about the `Declaration` if the original declaration is found, or `null`
|
||||
* otherwise.
|
||||
*/
|
||||
getDeclarationOfIdentifier(id: ts.Identifier): Declaration|null {
|
||||
override getDeclarationOfIdentifier(id: ts.Identifier): Declaration|null {
|
||||
const declaration = super.getDeclarationOfIdentifier(id);
|
||||
|
||||
if (declaration === null) {
|
||||
@@ -127,7 +127,7 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
||||
* @param node the function declaration to parse.
|
||||
* @returns an object containing the node, statements and parameters of the function.
|
||||
*/
|
||||
getDefinitionOfFunction(node: ts.Node): FunctionDefinition|null {
|
||||
override getDefinitionOfFunction(node: ts.Node): FunctionDefinition|null {
|
||||
const definition = super.getDefinitionOfFunction(node);
|
||||
if (definition === null) {
|
||||
return null;
|
||||
@@ -155,7 +155,7 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
||||
* @param decl The `Declaration` to check.
|
||||
* @return The passed in `Declaration` (potentially enhanced with a `KnownDeclaration`).
|
||||
*/
|
||||
detectKnownDeclaration<T extends Declaration>(decl: T): T {
|
||||
override detectKnownDeclaration<T extends Declaration>(decl: T): T {
|
||||
decl = super.detectKnownDeclaration(decl);
|
||||
|
||||
// Also check for TS helpers
|
||||
@@ -180,7 +180,8 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
||||
* @param declaration the declaration whose symbol we are finding.
|
||||
* @returns the symbol for the node or `undefined` if it is not a "class" or has no symbol.
|
||||
*/
|
||||
protected getClassSymbolFromInnerDeclaration(declaration: ts.Node): NgccClassSymbol|undefined {
|
||||
protected override getClassSymbolFromInnerDeclaration(declaration: ts.Node): NgccClassSymbol
|
||||
|undefined {
|
||||
const classSymbol = super.getClassSymbolFromInnerDeclaration(declaration);
|
||||
if (classSymbol !== undefined) {
|
||||
return classSymbol;
|
||||
@@ -210,7 +211,7 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
||||
* @returns an array of `ts.ParameterDeclaration` objects representing each of the parameters in
|
||||
* the class's constructor or `null` if there is no constructor.
|
||||
*/
|
||||
protected getConstructorParameterDeclarations(classSymbol: NgccClassSymbol):
|
||||
protected override getConstructorParameterDeclarations(classSymbol: NgccClassSymbol):
|
||||
ts.ParameterDeclaration[]|null {
|
||||
const constructor = classSymbol.implementation.valueDeclaration;
|
||||
if (!ts.isFunctionDeclaration(constructor)) return null;
|
||||
@@ -248,7 +249,8 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
||||
* @param paramDecoratorsProperty the property that holds the parameter info we want to get.
|
||||
* @returns an array of objects containing the type and decorators for each parameter.
|
||||
*/
|
||||
protected getParamInfoFromStaticProperty(paramDecoratorsProperty: ts.Symbol): ParamInfo[]|null {
|
||||
protected override getParamInfoFromStaticProperty(paramDecoratorsProperty: ts.Symbol):
|
||||
ParamInfo[]|null {
|
||||
const paramDecorators = getPropertyValueFromSymbol(paramDecoratorsProperty);
|
||||
// The decorators array may be wrapped in a function. If so unwrap it.
|
||||
const returnStatement = getReturnStatement(paramDecorators);
|
||||
@@ -284,15 +286,15 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
||||
* @param isStatic true if this member is static, false if it is an instance property.
|
||||
* @returns the reflected member information, or null if the symbol is not a member.
|
||||
*/
|
||||
protected reflectMembers(symbol: ts.Symbol, decorators?: Decorator[], isStatic?: boolean):
|
||||
ClassMember[]|null {
|
||||
protected override reflectMembers(
|
||||
symbol: ts.Symbol, decorators?: Decorator[], isStatic?: boolean): ClassMember[]|null {
|
||||
const node = symbol.valueDeclaration || symbol.declarations && symbol.declarations[0];
|
||||
const propertyDefinition = node && getPropertyDefinition(node);
|
||||
if (propertyDefinition) {
|
||||
const members: ClassMember[] = [];
|
||||
if (propertyDefinition.setter) {
|
||||
members.push({
|
||||
node,
|
||||
node: node!,
|
||||
implementation: propertyDefinition.setter,
|
||||
kind: ClassMemberKind.Setter,
|
||||
type: null,
|
||||
@@ -310,7 +312,7 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
||||
}
|
||||
if (propertyDefinition.getter) {
|
||||
members.push({
|
||||
node,
|
||||
node: node!,
|
||||
implementation: propertyDefinition.getter,
|
||||
kind: ClassMemberKind.Getter,
|
||||
type: null,
|
||||
@@ -348,7 +350,7 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
|
||||
* to reference the inner identifier inside the IIFE.
|
||||
* @returns an array of statements that may contain helper calls.
|
||||
*/
|
||||
protected getStatementsForClass(classSymbol: NgccClassSymbol): ts.Statement[] {
|
||||
protected override getStatementsForClass(classSymbol: NgccClassSymbol): ts.Statement[] {
|
||||
const classDeclarationParent = classSymbol.implementation.valueDeclaration.parent;
|
||||
return ts.isBlock(classDeclarationParent) ? Array.from(classDeclarationParent.statements) : [];
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ClassDeclaration, Declaration, Decorator, ReflectionHost} from '../../../src/ngtsc/reflection';
|
||||
import {SymbolWithValueDeclaration} from '../../../src/ngtsc/util/src/typescript';
|
||||
|
||||
export const PRE_R3_MARKER = '__PRE_R3__';
|
||||
export const POST_R3_MARKER = '__POST_R3__';
|
||||
@@ -47,13 +48,13 @@ export interface NgccClassSymbol {
|
||||
* inner declaration does not need to satisfy the requirements imposed on a publicly visible class
|
||||
* declaration.
|
||||
*/
|
||||
implementation: ts.Symbol;
|
||||
implementation: SymbolWithValueDeclaration;
|
||||
|
||||
/**
|
||||
* Represents the symbol corresponding to a variable within a class IIFE that may be used to
|
||||
* attach static properties or decorated.
|
||||
*/
|
||||
adjacent?: ts.Symbol;
|
||||
adjacent?: SymbolWithValueDeclaration;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -36,7 +36,7 @@ export class UmdReflectionHost extends Esm5ReflectionHost {
|
||||
this.compilerHost = src.host;
|
||||
}
|
||||
|
||||
getImportOfIdentifier(id: ts.Identifier): Import|null {
|
||||
override getImportOfIdentifier(id: ts.Identifier): Import|null {
|
||||
// Is `id` a namespaced property access, e.g. `Directive` in `core.Directive`?
|
||||
// If so capture the symbol of the namespace, e.g. `core`.
|
||||
const nsIdentifier = findNamespaceOfIdentifier(id);
|
||||
@@ -45,7 +45,7 @@ export class UmdReflectionHost extends Esm5ReflectionHost {
|
||||
return from !== null ? {from, name: id.text} : null;
|
||||
}
|
||||
|
||||
getDeclarationOfIdentifier(id: ts.Identifier): Declaration|null {
|
||||
override getDeclarationOfIdentifier(id: ts.Identifier): Declaration|null {
|
||||
// First we try one of the following:
|
||||
// 1. The `exports` identifier - referring to the current file/module.
|
||||
// 2. An identifier (e.g. `foo`) that refers to an imported UMD module.
|
||||
@@ -83,7 +83,7 @@ export class UmdReflectionHost extends Esm5ReflectionHost {
|
||||
};
|
||||
}
|
||||
|
||||
getExportsOfModule(module: ts.Node): Map<string, Declaration>|null {
|
||||
override getExportsOfModule(module: ts.Node): Map<string, Declaration>|null {
|
||||
return super.getExportsOfModule(module) || this.umdExports.get(module.getSourceFile());
|
||||
}
|
||||
|
||||
@@ -107,12 +107,13 @@ export class UmdReflectionHost extends Esm5ReflectionHost {
|
||||
* @param sourceFile The module whose statements we want.
|
||||
* @returns An array of top level statements for the given module.
|
||||
*/
|
||||
protected getModuleStatements(sourceFile: ts.SourceFile): ts.Statement[] {
|
||||
protected override getModuleStatements(sourceFile: ts.SourceFile): ts.Statement[] {
|
||||
const umdModule = this.getUmdModule(sourceFile);
|
||||
return umdModule !== null ? Array.from(umdModule.factoryFn.body.statements) : [];
|
||||
}
|
||||
|
||||
protected getClassSymbolFromOuterDeclaration(declaration: ts.Node): NgccClassSymbol|undefined {
|
||||
protected override getClassSymbolFromOuterDeclaration(declaration: ts.Node): NgccClassSymbol
|
||||
|undefined {
|
||||
const superSymbol = super.getClassSymbolFromOuterDeclaration(declaration);
|
||||
if (superSymbol) {
|
||||
return superSymbol;
|
||||
@@ -143,7 +144,8 @@ export class UmdReflectionHost extends Esm5ReflectionHost {
|
||||
}
|
||||
|
||||
|
||||
protected getClassSymbolFromInnerDeclaration(declaration: ts.Node): NgccClassSymbol|undefined {
|
||||
protected override getClassSymbolFromInnerDeclaration(declaration: ts.Node): NgccClassSymbol
|
||||
|undefined {
|
||||
const superClassSymbol = super.getClassSymbolFromInnerDeclaration(declaration);
|
||||
if (superClassSymbol !== undefined) {
|
||||
return superClassSymbol;
|
||||
@@ -164,7 +166,7 @@ export class UmdReflectionHost extends Esm5ReflectionHost {
|
||||
/**
|
||||
* Extract all "classes" from the `statement` and add them to the `classes` map.
|
||||
*/
|
||||
protected addClassSymbolsFromStatement(
|
||||
protected override addClassSymbolsFromStatement(
|
||||
classes: Map<ts.Symbol, NgccClassSymbol>, statement: ts.Statement): void {
|
||||
super.addClassSymbolsFromStatement(classes, statement);
|
||||
|
||||
@@ -184,7 +186,7 @@ export class UmdReflectionHost extends Esm5ReflectionHost {
|
||||
*
|
||||
* @param statement The statement that needs to be preprocessed.
|
||||
*/
|
||||
protected preprocessStatement(statement: ts.Statement): void {
|
||||
protected override preprocessStatement(statement: ts.Statement): void {
|
||||
super.preprocessStatement(statement);
|
||||
|
||||
if (!isExportsStatement(statement)) {
|
||||
@@ -421,7 +423,7 @@ export class UmdReflectionHost extends Esm5ReflectionHost {
|
||||
const exportsSymbol = this.checker.getSymbolsInScope(id, ts.SymbolFlags.Variable)
|
||||
.find(symbol => symbol.name === 'exports');
|
||||
|
||||
const node = exportsSymbol !== undefined &&
|
||||
const node = exportsSymbol?.valueDeclaration !== undefined &&
|
||||
!ts.isFunctionExpression(exportsSymbol.valueDeclaration.parent) ?
|
||||
// There is a locally defined `exports` variable that is not a function parameter.
|
||||
// So this `exports` identifier must be a local variable and does not represent the module.
|
||||
@@ -474,7 +476,7 @@ export class UmdReflectionHost extends Esm5ReflectionHost {
|
||||
* If this is an IIFE then try to grab the outer and inner classes otherwise fallback on the super
|
||||
* class.
|
||||
*/
|
||||
protected getDeclarationOfExpression(expression: ts.Expression): Declaration|null {
|
||||
protected override getDeclarationOfExpression(expression: ts.Expression): Declaration|null {
|
||||
const inner = getInnerClassDeclaration(expression);
|
||||
if (inner !== null) {
|
||||
const outer = getOuterNodeFromInnerDeclaration(inner);
|
||||
|
||||
@@ -25,6 +25,12 @@ export interface NgccProjectConfig<T = RawNgccPackageConfig> {
|
||||
* Options that control how locking the process is handled.
|
||||
*/
|
||||
locking?: ProcessLockingConfiguration;
|
||||
/**
|
||||
* Name of hash algorithm used to generate hashes of the configuration.
|
||||
*
|
||||
* Defaults to `sha256`.
|
||||
*/
|
||||
hashAlgorithm?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -230,10 +236,12 @@ export class NgccConfiguration {
|
||||
private projectConfig: PartiallyProcessedConfig;
|
||||
private cache = new Map<string, VersionedPackageConfig>();
|
||||
readonly hash: string;
|
||||
readonly hashAlgorithm: string;
|
||||
|
||||
constructor(private fs: ReadonlyFileSystem, baseDir: AbsoluteFsPath) {
|
||||
this.defaultConfig = this.processProjectConfig(DEFAULT_NGCC_CONFIG);
|
||||
this.projectConfig = this.processProjectConfig(this.loadProjectConfig(baseDir));
|
||||
this.hashAlgorithm = this.projectConfig.hashAlgorithm;
|
||||
this.hash = this.computeHash();
|
||||
}
|
||||
|
||||
@@ -299,7 +307,8 @@ export class NgccConfiguration {
|
||||
}
|
||||
|
||||
private processProjectConfig(projectConfig: NgccProjectConfig): PartiallyProcessedConfig {
|
||||
const processedConfig: PartiallyProcessedConfig = {packages: {}, locking: {}};
|
||||
const processedConfig:
|
||||
PartiallyProcessedConfig = {packages: {}, locking: {}, hashAlgorithm: 'sha256'};
|
||||
|
||||
// locking configuration
|
||||
if (projectConfig.locking !== undefined) {
|
||||
@@ -317,6 +326,11 @@ export class NgccConfiguration {
|
||||
}
|
||||
}
|
||||
|
||||
// hash algorithm config
|
||||
if (projectConfig.hashAlgorithm !== undefined) {
|
||||
processedConfig.hashAlgorithm = projectConfig.hashAlgorithm;
|
||||
}
|
||||
|
||||
return processedConfig;
|
||||
}
|
||||
|
||||
@@ -378,7 +392,7 @@ export class NgccConfiguration {
|
||||
}
|
||||
|
||||
private computeHash(): string {
|
||||
return createHash('md5').update(JSON.stringify(this.projectConfig)).digest('hex');
|
||||
return createHash(this.hashAlgorithm).update(JSON.stringify(this.projectConfig)).digest('hex');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -159,7 +159,7 @@ export class EntryPointManifest {
|
||||
const lockFilePath = this.fs.resolve(directory, lockFileName);
|
||||
if (this.fs.exists(lockFilePath)) {
|
||||
const lockFileContents = this.fs.readFile(lockFilePath);
|
||||
return createHash('md5').update(lockFileContents).digest('hex');
|
||||
return createHash(this.config.hashAlgorithm).update(lockFileContents).digest('hex');
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -175,7 +175,8 @@ export class EntryPointManifest {
|
||||
* is called.
|
||||
*/
|
||||
export class InvalidatingEntryPointManifest extends EntryPointManifest {
|
||||
readEntryPointsUsingManifest(_basePath: AbsoluteFsPath): EntryPointWithDependencies[]|null {
|
||||
override readEntryPointsUsingManifest(_basePath: AbsoluteFsPath):
|
||||
EntryPointWithDependencies[]|null {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,8 @@ export class NgccSourcesCompilerHost extends NgtscCompilerHost {
|
||||
super(fs, options);
|
||||
}
|
||||
|
||||
getSourceFile(fileName: string, languageVersion: ts.ScriptTarget): ts.SourceFile|undefined {
|
||||
override getSourceFile(fileName: string, languageVersion: ts.ScriptTarget): ts.SourceFile
|
||||
|undefined {
|
||||
return this.cache.getCachedSourceFile(fileName, languageVersion);
|
||||
}
|
||||
|
||||
@@ -78,7 +79,8 @@ export class NgccDtsCompilerHost extends NgtscCompilerHost {
|
||||
super(fs, options);
|
||||
}
|
||||
|
||||
getSourceFile(fileName: string, languageVersion: ts.ScriptTarget): ts.SourceFile|undefined {
|
||||
override getSourceFile(fileName: string, languageVersion: ts.ScriptTarget): ts.SourceFile
|
||||
|undefined {
|
||||
return this.cache.getCachedSourceFile(fileName, languageVersion);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ export class CommonJsRenderingFormatter extends Esm5RenderingFormatter {
|
||||
/**
|
||||
* Add the imports below any in situ imports as `require` calls.
|
||||
*/
|
||||
addImports(output: MagicString, imports: Import[], file: ts.SourceFile): void {
|
||||
override addImports(output: MagicString, imports: Import[], file: ts.SourceFile): void {
|
||||
// Avoid unnecessary work if there are no imports to add.
|
||||
if (imports.length === 0) {
|
||||
return;
|
||||
@@ -46,7 +46,7 @@ export class CommonJsRenderingFormatter extends Esm5RenderingFormatter {
|
||||
/**
|
||||
* Add the exports to the bottom of the file.
|
||||
*/
|
||||
addExports(
|
||||
override addExports(
|
||||
output: MagicString, entryPointBasePath: string, exports: ExportInfo[],
|
||||
importManager: ImportManager, file: ts.SourceFile): void {
|
||||
exports.forEach(e => {
|
||||
@@ -61,7 +61,7 @@ export class CommonJsRenderingFormatter extends Esm5RenderingFormatter {
|
||||
});
|
||||
}
|
||||
|
||||
addDirectExports(
|
||||
override addDirectExports(
|
||||
output: MagicString, exports: Reexport[], importManager: ImportManager,
|
||||
file: ts.SourceFile): void {
|
||||
for (const e of exports) {
|
||||
@@ -72,7 +72,7 @@ export class CommonJsRenderingFormatter extends Esm5RenderingFormatter {
|
||||
}
|
||||
}
|
||||
|
||||
protected findEndOfImports(sf: ts.SourceFile): number {
|
||||
protected override findEndOfImports(sf: ts.SourceFile): number {
|
||||
for (const statement of sf.statements) {
|
||||
if (ts.isExpressionStatement(statement) && isRequireCall(statement.expression)) {
|
||||
continue;
|
||||
|
||||
@@ -24,7 +24,8 @@ export class Esm5RenderingFormatter extends EsmRenderingFormatter {
|
||||
* Add the definitions, directly before the return statement, inside the IIFE of each decorated
|
||||
* class.
|
||||
*/
|
||||
addDefinitions(output: MagicString, compiledClass: CompiledClass, definitions: string): void {
|
||||
override addDefinitions(output: MagicString, compiledClass: CompiledClass, definitions: string):
|
||||
void {
|
||||
const classSymbol = this.host.getClassSymbol(compiledClass.declaration);
|
||||
if (!classSymbol) {
|
||||
throw new Error(
|
||||
@@ -63,7 +64,8 @@ export class Esm5RenderingFormatter extends EsmRenderingFormatter {
|
||||
*
|
||||
* @return The JavaScript code corresponding to `stmt` (in the appropriate format).
|
||||
*/
|
||||
printStatement(stmt: Statement, sourceFile: ts.SourceFile, importManager: ImportManager): string {
|
||||
override printStatement(stmt: Statement, sourceFile: ts.SourceFile, importManager: ImportManager):
|
||||
string {
|
||||
const node = translateStatement(
|
||||
stmt, importManager, {downlevelTaggedTemplates: true, downlevelVariableDeclarations: true});
|
||||
const code = this.printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
|
||||
|
||||
@@ -50,7 +50,7 @@ export class UmdRenderingFormatter extends Esm5RenderingFormatter {
|
||||
*
|
||||
* (See that the `z` import is not being used by the factory function.)
|
||||
*/
|
||||
addImports(output: MagicString, imports: Import[], file: ts.SourceFile): void {
|
||||
override addImports(output: MagicString, imports: Import[], file: ts.SourceFile): void {
|
||||
if (imports.length === 0) {
|
||||
return;
|
||||
}
|
||||
@@ -73,7 +73,7 @@ export class UmdRenderingFormatter extends Esm5RenderingFormatter {
|
||||
/**
|
||||
* Add the exports to the bottom of the UMD module factory function.
|
||||
*/
|
||||
addExports(
|
||||
override addExports(
|
||||
output: MagicString, entryPointBasePath: string, exports: ExportInfo[],
|
||||
importManager: ImportManager, file: ts.SourceFile): void {
|
||||
const umdModule = this.umdHost.getUmdModule(file);
|
||||
@@ -97,7 +97,7 @@ export class UmdRenderingFormatter extends Esm5RenderingFormatter {
|
||||
});
|
||||
}
|
||||
|
||||
addDirectExports(
|
||||
override addDirectExports(
|
||||
output: MagicString, exports: Reexport[], importManager: ImportManager,
|
||||
file: ts.SourceFile): void {
|
||||
const umdModule = this.umdHost.getUmdModule(file);
|
||||
@@ -120,7 +120,7 @@ export class UmdRenderingFormatter extends Esm5RenderingFormatter {
|
||||
/**
|
||||
* Add the constants to the top of the UMD factory function.
|
||||
*/
|
||||
addConstants(output: MagicString, constants: string, file: ts.SourceFile): void {
|
||||
override addConstants(output: MagicString, constants: string, file: ts.SourceFile): void {
|
||||
if (constants === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ export class NewEntryPointFileWriter extends InPlaceFileWriter {
|
||||
super(fs, logger, errorOnFailedEntryPoint);
|
||||
}
|
||||
|
||||
writeBundle(
|
||||
override writeBundle(
|
||||
bundle: EntryPointBundle, transformedFiles: FileToWrite[],
|
||||
formatProperties: EntryPointJsonProperty[]) {
|
||||
// The new folder is at the root of the overall package
|
||||
@@ -45,7 +45,7 @@ export class NewEntryPointFileWriter extends InPlaceFileWriter {
|
||||
this.updatePackageJson(entryPoint, formatProperties, ngccFolder);
|
||||
}
|
||||
|
||||
revertBundle(
|
||||
override revertBundle(
|
||||
entryPoint: EntryPoint, transformedFilePaths: AbsoluteFsPath[],
|
||||
formatProperties: EntryPointJsonProperty[]): void {
|
||||
// IMPLEMENTATION NOTE:
|
||||
|
||||
@@ -323,7 +323,8 @@ class TestHandler implements DecoratorHandler<unknown, unknown, null, unknown> {
|
||||
}
|
||||
|
||||
class AlwaysDetectHandler extends TestHandler {
|
||||
detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult<unknown>|undefined {
|
||||
override detect(node: ClassDeclaration, decorators: Decorator[]|null):
|
||||
DetectResult<unknown>|undefined {
|
||||
super.detect(node, decorators);
|
||||
const decorator = decorators !== null ? decorators[0] : null;
|
||||
return {trigger: node, decorator, metadata: {}};
|
||||
@@ -331,11 +332,12 @@ class AlwaysDetectHandler extends TestHandler {
|
||||
}
|
||||
|
||||
class DetectDecoratorHandler extends TestHandler {
|
||||
constructor(private decorator: string, readonly precedence: HandlerPrecedence) {
|
||||
constructor(private decorator: string, override readonly precedence: HandlerPrecedence) {
|
||||
super(decorator, []);
|
||||
}
|
||||
|
||||
detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult<unknown>|undefined {
|
||||
override detect(node: ClassDeclaration, decorators: Decorator[]|null):
|
||||
DetectResult<unknown>|undefined {
|
||||
super.detect(node, decorators);
|
||||
if (decorators === null) {
|
||||
return undefined;
|
||||
@@ -349,7 +351,7 @@ class DetectDecoratorHandler extends TestHandler {
|
||||
}
|
||||
|
||||
class DiagnosticProducingHandler extends AlwaysDetectHandler {
|
||||
analyze(node: ClassDeclaration): AnalysisOutput<any> {
|
||||
override analyze(node: ClassDeclaration): AnalysisOutput<any> {
|
||||
super.analyze(node);
|
||||
return {diagnostics: [makeDiagnostic(9999, node, 'test diagnostic')]};
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
import {DepGraph} from 'dependency-graph';
|
||||
import {DtsProcessing, PartiallyOrderedTasks, Task} from '../../src/execution/tasks/api';
|
||||
import {EntryPoint} from '../../src/packages/entry_point';
|
||||
import {EntryPoint, EntryPointJsonProperty} from '../../src/packages/entry_point';
|
||||
|
||||
/**
|
||||
* Create a set of tasks and a graph of their interdependencies.
|
||||
@@ -53,7 +53,13 @@ export function createTasksAndGraph(
|
||||
|
||||
for (let tIdx = 0; tIdx < tasksPerEntryPointCount; tIdx++) {
|
||||
const processDts = tIdx === 0 ? DtsProcessing.Yes : DtsProcessing.No;
|
||||
tasks.push({entryPoint, formatProperty: `prop-${tIdx}`, processDts} as Task);
|
||||
const formatProperty = `prop-${tIdx}` as EntryPointJsonProperty;
|
||||
tasks.push({
|
||||
entryPoint,
|
||||
formatProperty: formatProperty,
|
||||
formatPropertiesToMarkAsProcessed: [],
|
||||
processDts
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,12 @@ describe('SerialTaskQueue', () => {
|
||||
const entryPoint = {name: `entry-point-${i}`, path: `/path/to/entry/point/${i}`} as
|
||||
EntryPoint;
|
||||
const processDts = i % 2 === 0 ? DtsProcessing.Yes : DtsProcessing.No;
|
||||
tasks.push({entryPoint: entryPoint, formatProperty: `prop-${i}`, processDts} as Task);
|
||||
tasks.push({
|
||||
entryPoint: entryPoint,
|
||||
formatProperty: `prop-${i}`,
|
||||
formatPropertiesToMarkAsProcessed: [],
|
||||
processDts
|
||||
} as Task);
|
||||
graph.addNode(entryPoint.path);
|
||||
}
|
||||
const dependencies = computeTaskDependencies(tasks, graph);
|
||||
|
||||
@@ -2506,7 +2506,7 @@ runInEachFileSystem(() => {
|
||||
const externalLibWithoutTypingsIndex = _('/an_external_lib_without_typings/index.js');
|
||||
|
||||
class TestEsm2015ReflectionHost extends Esm2015ReflectionHost {
|
||||
getExportsOfModule(node: ts.Node) {
|
||||
override getExportsOfModule(node: ts.Node) {
|
||||
if (ts.isSourceFile(node) && (node.fileName === externalLibWithoutTypingsIndex)) {
|
||||
throw new Error(
|
||||
`'getExportsOfModule()' called on '${externalLibWithoutTypingsIndex}'.`);
|
||||
|
||||
@@ -829,7 +829,7 @@ runInEachFileSystem(() => {
|
||||
it('should use the correct type name in typings files when an export has a different name in source files',
|
||||
() => {
|
||||
// We need to make sure that changes to the typings files use the correct name
|
||||
// static ɵprov: ɵngcc0.ɵɵInjectableDef<ɵangular_packages_common_common_a>;
|
||||
// static ɵprov: ɵngcc0.ɵɵInjectableDeclaration<ɵangular_packages_common_common_a>;
|
||||
mainNgcc({
|
||||
basePath: '/node_modules',
|
||||
targetEntryPointPath: '@angular/common',
|
||||
|
||||
@@ -28,20 +28,20 @@ runInEachFileSystem(() => {
|
||||
super(fs, new MockLogger());
|
||||
fs.ensureDir(fs.dirname(this.path));
|
||||
}
|
||||
remove() {
|
||||
override remove() {
|
||||
this.log.push('remove()');
|
||||
super.remove();
|
||||
}
|
||||
write() {
|
||||
override write() {
|
||||
this.log.push('write()');
|
||||
super.write();
|
||||
}
|
||||
read() {
|
||||
override read() {
|
||||
const contents = super.read();
|
||||
this.log.push('read() => ' + contents);
|
||||
return contents;
|
||||
}
|
||||
createUnlocker(): ChildProcess {
|
||||
override createUnlocker(): ChildProcess {
|
||||
this.log = this.log || [];
|
||||
this.log.push('createUnlocker()');
|
||||
const log = this.log;
|
||||
|
||||
@@ -48,9 +48,9 @@ runInEachFileSystem(() => {
|
||||
}]);
|
||||
const project1Conf = new NgccConfiguration(fs, project1);
|
||||
const expectedProject1Config =
|
||||
`{"packages":{"package-1":[{"entryPoints":{"./entry-point-1":{}},"versionRange":"*"}]},"locking":{}}`;
|
||||
`{"packages":{"package-1":[{"entryPoints":{"./entry-point-1":{}},"versionRange":"*"}]},"locking":{},"hashAlgorithm":"sha256"}`;
|
||||
expect(project1Conf.hash)
|
||||
.toEqual(createHash('md5').update(expectedProject1Config).digest('hex'));
|
||||
.toEqual(createHash('sha256').update(expectedProject1Config).digest('hex'));
|
||||
|
||||
const project2 = _Abs('/project-2');
|
||||
const project2Config = fs.resolve(project2, 'ngcc.config.js');
|
||||
@@ -66,18 +66,41 @@ runInEachFileSystem(() => {
|
||||
}]);
|
||||
const project2Conf = new NgccConfiguration(fs, project2);
|
||||
const expectedProject2Config =
|
||||
`{"packages":{"package-1":[{"entryPoints":{"./entry-point-1":{"ignore":true}},"versionRange":"*"}]},"locking":{}}`;
|
||||
`{"packages":{"package-1":[{"entryPoints":{"./entry-point-1":{"ignore":true}},"versionRange":"*"}]},"locking":{},"hashAlgorithm":"sha256"}`;
|
||||
expect(project2Conf.hash)
|
||||
.toEqual(createHash('md5').update(expectedProject2Config).digest('hex'));
|
||||
.toEqual(createHash('sha256').update(expectedProject2Config).digest('hex'));
|
||||
});
|
||||
|
||||
it('should compute a hash even if there is no project configuration', () => {
|
||||
loadTestFiles([{name: _Abs('/project-1/empty.js'), contents: ``}]);
|
||||
const configuration = new NgccConfiguration(fs, _Abs('/project-1'));
|
||||
expect(configuration.hash)
|
||||
.toEqual(createHash('md5')
|
||||
.update(JSON.stringify({packages: {}, locking: {}}))
|
||||
.digest('hex'));
|
||||
.toEqual(
|
||||
createHash('sha256')
|
||||
.update(JSON.stringify({packages: {}, locking: {}, hashAlgorithm: 'sha256'}))
|
||||
.digest('hex'));
|
||||
});
|
||||
|
||||
it('should use a custom hash algorithm if specified in the config', () => {
|
||||
const project1 = _Abs('/project-1');
|
||||
const project1Config = fs.resolve(project1, 'ngcc.config.js');
|
||||
|
||||
loadTestFiles([{
|
||||
name: project1Config,
|
||||
contents: `
|
||||
module.exports = {
|
||||
packages: {
|
||||
'package-1': {entryPoints: {'./entry-point-1': {}}},
|
||||
},
|
||||
hashAlgorithm: 'md5',
|
||||
};`
|
||||
}]);
|
||||
const project1Conf = new NgccConfiguration(fs, project1);
|
||||
const expectedProject1Config =
|
||||
`{"packages":{"package-1":[{"entryPoints":{"./entry-point-1":{}},"versionRange":"*"}]},"locking":{},"hashAlgorithm":"md5"}`;
|
||||
expect(JSON.stringify((project1Conf as any).projectConfig)).toEqual(expectedProject1Config);
|
||||
expect(project1Conf.hash)
|
||||
.toEqual(createHash('md5').update(expectedProject1Config).digest('hex'));
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ runInEachFileSystem(() => {
|
||||
beforeEach(() => {
|
||||
manifestFile = {
|
||||
ngccVersion: NGCC_VERSION,
|
||||
lockFileHash: createHash('md5').update('LOCK FILE CONTENTS').digest('hex'),
|
||||
lockFileHash: createHash('sha256').update('LOCK FILE CONTENTS').digest('hex'),
|
||||
configFileHash: config.hash,
|
||||
entryPointPaths: []
|
||||
};
|
||||
@@ -278,7 +278,7 @@ runInEachFileSystem(() => {
|
||||
JSON.parse(fs.readFile(_Abs('/project/node_modules/__ngcc_entry_points__.json'))) as
|
||||
EntryPointManifestFile;
|
||||
expect(file.lockFileHash)
|
||||
.toEqual(createHash('md5').update('LOCK FILE CONTENTS').digest('hex'));
|
||||
.toEqual(createHash('sha256').update('LOCK FILE CONTENTS').digest('hex'));
|
||||
});
|
||||
|
||||
it('should write a hash of the package-lock.json file', () => {
|
||||
@@ -288,7 +288,7 @@ runInEachFileSystem(() => {
|
||||
JSON.parse(fs.readFile(_Abs('/project/node_modules/__ngcc_entry_points__.json'))) as
|
||||
EntryPointManifestFile;
|
||||
expect(file.lockFileHash)
|
||||
.toEqual(createHash('md5').update('LOCK FILE CONTENTS').digest('hex'));
|
||||
.toEqual(createHash('sha256').update('LOCK FILE CONTENTS').digest('hex'));
|
||||
});
|
||||
|
||||
it('should write a hash of the project config', () => {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {Statement} from '@angular/compiler';
|
||||
import {fromObject, generateMapFileComment, SourceMapConverter} from 'convert-source-map';
|
||||
import {fromObject, fromSource, generateMapFileComment, SourceMapConverter} from 'convert-source-map';
|
||||
import MagicString from 'magic-string';
|
||||
import {encode, SourceMapMappings} from 'sourcemap-codec';
|
||||
import * as ts from 'typescript';
|
||||
@@ -201,19 +201,84 @@ runInEachFileSystem(() => {
|
||||
'file': 'file.js',
|
||||
'sources': ['file.js'],
|
||||
'names': [],
|
||||
'mappings': encode([[], [], [], [], [], [], [], [], [], [], [], [], [[0, 0, 0, 0]]]),
|
||||
'mappings': encode([
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[[0, 0, 0, 0]],
|
||||
[[0, 0, 1, 0]],
|
||||
[[0, 0, 2, 0]],
|
||||
[[0, 0, 3, 0]],
|
||||
[[0, 0, 4, 0]],
|
||||
[[0, 0, 5, 0]],
|
||||
[[0, 0, 6, 0]],
|
||||
[[0, 0, 7, 0]],
|
||||
[[0, 0, 8, 0]]
|
||||
]),
|
||||
'sourcesContent': [JS_CONTENT.contents],
|
||||
});
|
||||
|
||||
const MERGED_OUTPUT_PROGRAM_MAPPINGS: SourceMapMappings =
|
||||
[[], [], [], [], [], [], [], [], [], [], [], [], ...JS_CONTENT_MAPPINGS];
|
||||
|
||||
MERGED_OUTPUT_PROGRAM_MAP = fromObject({
|
||||
'version': 3,
|
||||
'file': 'file.js',
|
||||
'sources': ['file.ts'],
|
||||
'names': [],
|
||||
'mappings': encode(MERGED_OUTPUT_PROGRAM_MAPPINGS),
|
||||
'mappings': encode([
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[
|
||||
[0, 0, 0, 0], [7, 0, 0, 7], [9, 0, 0, 8], [18, 0, 0, 17], [20, 0, 0, 18],
|
||||
[26, 0, 0, 24], [41, 0, 0, 39], [42, 0, 0, 40]
|
||||
],
|
||||
[
|
||||
[0, 0, 2, 0], [4, 0, 2, 13], [5, 0, 2, 14], [8, 0, 2, 0], [14, 0, 2, 13], [15, 0, 2, 14]
|
||||
],
|
||||
[[0, 0, 2, 16], [4, 0, 3, 2], [7, 0, 3, 5], [8, 0, 3, 6], [9, 0, 3, 15]],
|
||||
[
|
||||
[0, 0, 3, 27], [7, 0, 3, 34], [8, 0, 3, 35], [9, 0, 3, 36], [10, 0, 3, 37],
|
||||
[11, 0, 3, 38], [1, 0, 4, 1], [2, 0, 4, 1]
|
||||
],
|
||||
[[0, 0, 2, 13], [1, 0, 2, 14]],
|
||||
[[0, 0, 3, 3]],
|
||||
[
|
||||
[0, 0, 3, 5], [2, 0, 1, 1], [11, 0, 1, 10], [12, 0, 1, 11], [14, 0, 1, 12],
|
||||
[3, 0, 2, 13], [4, 0, 2, 14], [5, 0, 4, 1]
|
||||
],
|
||||
[
|
||||
[0, 0, 4, 13], [5, 0, 1, 20], [7, 0, 1, 22], [12, 0, 1, 27], [14, 0, 1, 28],
|
||||
[15, 0, 1, 29], [9, 0, 2, 13], [10, 0, 2, 14]
|
||||
],
|
||||
[[0, 0, 4, 2]],
|
||||
[],
|
||||
[
|
||||
[0, 0, 0, 2], [0, 0, 0, 2], [0, 0, 0, 2], [0, 0, 0, 2], [0, 0, 0, 2], [0, 0, 0, 2],
|
||||
[0, 0, 0, 2], [0, 0, 0, 2], [0, 0, 2, 2], [0, 0, 2, 2], [0, 0, 2, 2], [0, 0, 2, 2],
|
||||
[0, 0, 2, 2], [0, 0, 2, 2], [0, 0, 3, 2], [0, 0, 3, 2], [0, 0, 3, 2], [0, 0, 3, 2],
|
||||
[0, 0, 3, 2], [0, 0, 3, 2], [0, 0, 3, 2], [0, 0, 3, 2], [0, 0, 3, 2], [0, 0, 3, 2],
|
||||
[0, 0, 4, 2], [0, 0, 4, 2], [0, 0, 2, 2], [0, 0, 2, 2], [0, 0, 1, 2], [0, 0, 1, 2],
|
||||
[0, 0, 1, 2], [0, 0, 1, 2], [0, 0, 2, 2], [0, 0, 2, 2], [0, 0, 4, 2], [0, 0, 1, 2],
|
||||
[0, 0, 1, 2], [0, 0, 1, 2], [0, 0, 1, 2], [0, 0, 1, 2], [0, 0, 2, 2], [0, 0, 2, 2]
|
||||
],
|
||||
]),
|
||||
'sourcesContent': [TS_CONTENT.contents],
|
||||
});
|
||||
});
|
||||
@@ -226,8 +291,9 @@ runInEachFileSystem(() => {
|
||||
const [sourceFile, mapFile] = renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
||||
expect(sourceFile.path).toEqual(_('/node_modules/test-package/src/file.js'));
|
||||
expect(sourceFile.contents)
|
||||
.toEqual(RENDERED_CONTENTS + '\n' + OUTPUT_PROGRAM_MAP.toComment());
|
||||
expect(sourceFile.contents).toContain(RENDERED_CONTENTS);
|
||||
expect(fromSource(sourceFile.contents)!.toObject())
|
||||
.toEqual(OUTPUT_PROGRAM_MAP.toObject());
|
||||
expect(mapFile).toBeUndefined();
|
||||
});
|
||||
|
||||
@@ -616,8 +682,9 @@ UndecoratedBase.ɵdir = /*@__PURE__*/ ɵngcc0.ɵɵdefineDirective({ type: Undeco
|
||||
const [sourceFile, mapFile] = renderer.renderProgram(
|
||||
decorationAnalyses, switchMarkerAnalyses, privateDeclarationsAnalyses);
|
||||
expect(sourceFile.path).toEqual(_('/node_modules/test-package/src/file.js'));
|
||||
expect(sourceFile.contents)
|
||||
.toEqual(RENDERED_CONTENTS + '\n' + MERGED_OUTPUT_PROGRAM_MAP.toComment());
|
||||
expect(sourceFile.contents).toContain(RENDERED_CONTENTS);
|
||||
expect(fromSource(sourceFile.contents)!.toObject())
|
||||
.toEqual(MERGED_OUTPUT_PROGRAM_MAP.toObject());
|
||||
expect(mapFile).toBeUndefined();
|
||||
});
|
||||
|
||||
|
||||
@@ -18,21 +18,17 @@
|
||||
"canonical-path": "1.0.0",
|
||||
"chokidar": "^3.0.0",
|
||||
"convert-source-map": "^1.5.1",
|
||||
"dependency-graph": "^0.7.2",
|
||||
"fs-extra": "4.0.2",
|
||||
"dependency-graph": "^0.11.0",
|
||||
"magic-string": "^0.25.0",
|
||||
"semver": "^6.3.0",
|
||||
"semver": "^7.0.0",
|
||||
"source-map": "^0.6.1",
|
||||
"sourcemap-codec": "^1.4.8",
|
||||
"tslib": "^2.1.0",
|
||||
"yargs": "^16.2.0"
|
||||
"tslib": "^2.2.0",
|
||||
"yargs": "^17.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/compiler": "0.0.0-PLACEHOLDER",
|
||||
"typescript": ">=4.2.3 <4.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0"
|
||||
"typescript": ">=4.2.3 <4.4"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -44,14 +40,14 @@
|
||||
"compiler"
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.14.1 || >=14.0.0"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/angular/angular/issues"
|
||||
},
|
||||
"homepage": "https://github.com/angular/angular/tree/master/packages/compiler-cli",
|
||||
"ng-update": {
|
||||
"packageGroup": "NG_UPDATE_PACKAGE_GROUP"
|
||||
},
|
||||
"publishConfig": {
|
||||
"registry": "https://wombat-dressing-room.appspot.com"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,7 +145,7 @@ export function isMetadataSymbolicExpression(value: any): value is MetadataSymbo
|
||||
export interface MetadataSymbolicBinaryExpression {
|
||||
__symbolic: 'binary';
|
||||
operator: '&&'|'||'|'|'|'^'|'&'|'=='|'!='|'==='|'!=='|'<'|'>'|'<='|'>='|'instanceof'|'in'|'as'|
|
||||
'<<'|'>>'|'>>>'|'+'|'-'|'*'|'/'|'%'|'**';
|
||||
'<<'|'>>'|'>>>'|'+'|'-'|'*'|'/'|'%'|'**'|'??';
|
||||
left: MetadataValue;
|
||||
right: MetadataValue;
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ ts_library(
|
||||
"//packages/compiler-cli/src/ngtsc/typecheck/api",
|
||||
"//packages/compiler-cli/src/ngtsc/typecheck/diagnostics",
|
||||
"//packages/compiler-cli/src/ngtsc/util",
|
||||
"//packages/compiler-cli/src/ngtsc/xi18n",
|
||||
"@npm//@types/node",
|
||||
"@npm//typescript",
|
||||
],
|
||||
|
||||
@@ -6,31 +6,32 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {compileComponentFromMetadata, compileDeclareComponentFromMetadata, ConstantPool, CssSelector, DeclarationListEmitMode, DEFAULT_INTERPOLATION_CONFIG, DomElementSchemaRegistry, Expression, ExternalExpr, FactoryTarget, InterpolationConfig, LexerRange, makeBindingParser, ParsedTemplate, ParseSourceFile, parseTemplate, R3ComponentMetadata, R3FactoryMetadata, R3TargetBinder, R3UsedDirectiveMetadata, SelectorMatcher, Statement, TmplAstNode, WrappedNodeExpr} from '@angular/compiler';
|
||||
import {compileClassMetadata, compileComponentFromMetadata, compileDeclareClassMetadata, compileDeclareComponentFromMetadata, ConstantPool, CssSelector, DeclarationListEmitMode, DeclareComponentTemplateInfo, DEFAULT_INTERPOLATION_CONFIG, DomElementSchemaRegistry, Expression, ExternalExpr, FactoryTarget, InterpolationConfig, LexerRange, makeBindingParser, ParsedTemplate, ParseSourceFile, parseTemplate, R3ClassMetadata, R3ComponentMetadata, R3TargetBinder, R3UsedDirectiveMetadata, SelectorMatcher, Statement, TmplAstNode, WrappedNodeExpr} from '@angular/compiler';
|
||||
import {ViewEncapsulation} from '@angular/compiler/src/core';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Cycle, CycleAnalyzer, CycleHandlingStrategy} from '../../cycles';
|
||||
import {ErrorCode, FatalDiagnosticError, makeRelatedInformation} from '../../diagnostics';
|
||||
import {ErrorCode, FatalDiagnosticError, makeDiagnostic, makeRelatedInformation} from '../../diagnostics';
|
||||
import {absoluteFrom, relative} from '../../file_system';
|
||||
import {DefaultImportRecorder, ImportedFile, ModuleResolver, Reference, ReferenceEmitter} from '../../imports';
|
||||
import {ImportedFile, ModuleResolver, Reference, ReferenceEmitter} from '../../imports';
|
||||
import {DependencyTracker} from '../../incremental/api';
|
||||
import {extractSemanticTypeParameters, isArrayEqual, isReferenceEqual, SemanticDepGraphUpdater, SemanticReference, SemanticSymbol} from '../../incremental/semantic_graph';
|
||||
import {IndexingContext} from '../../indexer';
|
||||
import {ClassPropertyMapping, ComponentResources, DirectiveMeta, DirectiveTypeCheckMeta, extractDirectiveTypeCheckMeta, InjectableClassRegistry, MetadataReader, MetadataRegistry, Resource, ResourceRegistry} from '../../metadata';
|
||||
import {ClassPropertyMapping, ComponentResources, DirectiveMeta, DirectiveTypeCheckMeta, extractDirectiveTypeCheckMeta, InjectableClassRegistry, MetadataReader, MetadataRegistry, MetaType, Resource, ResourceRegistry} from '../../metadata';
|
||||
import {EnumValue, PartialEvaluator, ResolvedValue} from '../../partial_evaluator';
|
||||
import {PerfEvent, PerfRecorder} from '../../perf';
|
||||
import {ClassDeclaration, DeclarationNode, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
||||
import {ComponentScopeReader, LocalModuleScopeRegistry, TypeCheckScopeRegistry} from '../../scope';
|
||||
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerFlags, HandlerPrecedence, ResolveResult} from '../../transform';
|
||||
import {TemplateSourceMapping, TypeCheckContext} from '../../typecheck/api';
|
||||
import {tsSourceMapBug29300Fixed} from '../../util/src/ts_source_map_bug_29300';
|
||||
import {SubsetOfKeys} from '../../util/src/typescript';
|
||||
import {Xi18nContext} from '../../xi18n';
|
||||
|
||||
import {ResourceLoader} from './api';
|
||||
import {createValueHasWrongTypeError, getDirectiveDiagnostics, getProviderDiagnostics} from './diagnostics';
|
||||
import {DirectiveSymbol, extractDirectiveMetadata, parseFieldArrayValue} from './directive';
|
||||
import {compileDeclareFactory, compileNgFactoryDefField} from './factory';
|
||||
import {generateSetClassMetadataCall} from './metadata';
|
||||
import {extractClassMetadata} from './metadata';
|
||||
import {NgModuleSymbol} from './ng_module';
|
||||
import {compileResults, findAngularDecorator, isAngularCoreReference, isExpressionForwardReference, readBaseClass, resolveProvidersRequiringFactory, toFactoryMetadata, unwrapExpression, wrapFunctionExpressionsInParens} from './util';
|
||||
|
||||
@@ -55,7 +56,7 @@ export interface ComponentAnalysisData {
|
||||
baseClass: Reference<ClassDeclaration>|'dynamic'|null;
|
||||
typeCheckMeta: DirectiveTypeCheckMeta;
|
||||
template: ParsedTemplateWithSource;
|
||||
metadataStmt: Statement|null;
|
||||
classMetadata: R3ClassMetadata|null;
|
||||
|
||||
inputs: ClassPropertyMapping;
|
||||
outputs: ClassPropertyMapping;
|
||||
@@ -122,7 +123,8 @@ export class ComponentSymbol extends DirectiveSymbol {
|
||||
usedPipes: SemanticReference[] = [];
|
||||
isRemotelyScoped = false;
|
||||
|
||||
isEmitAffected(previousSymbol: SemanticSymbol, publicApiAffected: Set<SemanticSymbol>): boolean {
|
||||
override isEmitAffected(previousSymbol: SemanticSymbol, publicApiAffected: Set<SemanticSymbol>):
|
||||
boolean {
|
||||
if (!(previousSymbol instanceof ComponentSymbol)) {
|
||||
return true;
|
||||
}
|
||||
@@ -145,7 +147,7 @@ export class ComponentSymbol extends DirectiveSymbol {
|
||||
!isArrayEqual(this.usedPipes, previousSymbol.usedPipes, isSymbolUnaffected);
|
||||
}
|
||||
|
||||
isTypeCheckBlockAffected(
|
||||
override isTypeCheckBlockAffected(
|
||||
previousSymbol: SemanticSymbol, typeCheckApiAffected: Set<SemanticSymbol>): boolean {
|
||||
if (!(previousSymbol instanceof ComponentSymbol)) {
|
||||
return true;
|
||||
@@ -205,7 +207,6 @@ export class ComponentDecoratorHandler implements
|
||||
private i18nNormalizeLineEndingsInICUs: boolean|undefined,
|
||||
private moduleResolver: ModuleResolver, private cycleAnalyzer: CycleAnalyzer,
|
||||
private cycleHandlingStrategy: CycleHandlingStrategy, private refEmitter: ReferenceEmitter,
|
||||
private defaultImportRecorder: DefaultImportRecorder,
|
||||
private depTracker: DependencyTracker|null,
|
||||
private injectableRegistry: InjectableClassRegistry,
|
||||
private semanticDepGraphUpdater: SemanticDepGraphUpdater|null,
|
||||
@@ -262,13 +263,16 @@ export class ComponentDecoratorHandler implements
|
||||
const component = reflectObjectLiteral(meta);
|
||||
const containingFile = node.getSourceFile().fileName;
|
||||
|
||||
const resolveStyleUrl =
|
||||
(styleUrl: string, nodeForError: ts.Node,
|
||||
resourceType: ResourceTypeForDiagnostics): Promise<void>|undefined => {
|
||||
const resourceUrl =
|
||||
this._resolveResourceOrThrow(styleUrl, containingFile, nodeForError, resourceType);
|
||||
return this.resourceLoader.preload(resourceUrl, {type: 'style', containingFile});
|
||||
};
|
||||
const resolveStyleUrl = (styleUrl: string): Promise<void>|undefined => {
|
||||
try {
|
||||
const resourceUrl = this.resourceLoader.resolve(styleUrl, containingFile);
|
||||
return this.resourceLoader.preload(resourceUrl, {type: 'style', containingFile});
|
||||
} catch {
|
||||
// Don't worry about failures to preload. We can handle this problem during analysis by
|
||||
// producing a diagnostic.
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
// A Promise that waits for the template and all <link>ed styles within it to be preloaded.
|
||||
const templateAndTemplateStyleResources =
|
||||
@@ -278,12 +282,7 @@ export class ComponentDecoratorHandler implements
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const nodeForError = getTemplateDeclarationNodeForError(template.declaration);
|
||||
return Promise
|
||||
.all(template.styleUrls.map(
|
||||
styleUrl => resolveStyleUrl(
|
||||
styleUrl, nodeForError,
|
||||
ResourceTypeForDiagnostics.StylesheetFromTemplate)))
|
||||
return Promise.all(template.styleUrls.map(styleUrl => resolveStyleUrl(styleUrl)))
|
||||
.then(() => undefined);
|
||||
});
|
||||
|
||||
@@ -305,16 +304,15 @@ export class ComponentDecoratorHandler implements
|
||||
this.preanalyzeStylesCache.set(node, styles);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.preanalyzeStylesCache.set(node, null);
|
||||
}
|
||||
|
||||
// Wait for both the template and all styleUrl resources to resolve.
|
||||
return Promise
|
||||
.all([
|
||||
templateAndTemplateStyleResources, inlineStyles,
|
||||
...componentStyleUrls.map(
|
||||
styleUrl => resolveStyleUrl(
|
||||
styleUrl.url, styleUrl.nodeForError,
|
||||
ResourceTypeForDiagnostics.StylesheetFromDecorator))
|
||||
...componentStyleUrls.map(styleUrl => resolveStyleUrl(styleUrl.url))
|
||||
])
|
||||
.then(() => undefined);
|
||||
}
|
||||
@@ -326,11 +324,13 @@ export class ComponentDecoratorHandler implements
|
||||
const containingFile = node.getSourceFile().fileName;
|
||||
this.literalCache.delete(decorator);
|
||||
|
||||
let diagnostics: ts.Diagnostic[]|undefined;
|
||||
let isPoisoned = false;
|
||||
// @Component inherits @Directive, so begin by extracting the @Directive metadata and building
|
||||
// on it.
|
||||
const directiveResult = extractDirectiveMetadata(
|
||||
node, decorator, this.reflector, this.evaluator, this.defaultImportRecorder, this.isCore,
|
||||
flags, this.annotateForClosureCompiler,
|
||||
node, decorator, this.reflector, this.evaluator, this.isCore, flags,
|
||||
this.annotateForClosureCompiler,
|
||||
this.elementSchemaRegistry.getDefaultComponentElementName());
|
||||
if (directiveResult === undefined) {
|
||||
// `extractDirectiveMetadata` returns undefined when the @Directive has `jit: true`. In this
|
||||
@@ -341,6 +341,16 @@ export class ComponentDecoratorHandler implements
|
||||
|
||||
// Next, read the `@Component`-specific fields.
|
||||
const {decorator: component, metadata, inputs, outputs} = directiveResult;
|
||||
const encapsulation: number =
|
||||
this._resolveEnumValue(component, 'encapsulation', 'ViewEncapsulation') ??
|
||||
ViewEncapsulation.Emulated;
|
||||
const changeDetection: number|null =
|
||||
this._resolveEnumValue(component, 'changeDetection', 'ChangeDetectionStrategy');
|
||||
|
||||
let animations: Expression|null = null;
|
||||
if (component.has('animations')) {
|
||||
animations = new WrappedNodeExpr(component.get('animations')!);
|
||||
}
|
||||
|
||||
// Go through the root directories for this project, and select the one with the smallest
|
||||
// relative path representation.
|
||||
@@ -392,7 +402,7 @@ export class ComponentDecoratorHandler implements
|
||||
template = this.extractTemplate(node, templateDecl);
|
||||
}
|
||||
const templateResource =
|
||||
template.isInline ? {path: null, expression: component.get('template')!} : {
|
||||
template.declaration.isInline ? {path: null, expression: component.get('template')!} : {
|
||||
path: absoluteFrom(template.declaration.resolvedTemplateUrl),
|
||||
expression: template.sourceMapping.node
|
||||
};
|
||||
@@ -408,16 +418,36 @@ export class ComponentDecoratorHandler implements
|
||||
];
|
||||
|
||||
for (const styleUrl of styleUrls) {
|
||||
const resourceType = styleUrl.source === ResourceTypeForDiagnostics.StylesheetFromDecorator ?
|
||||
ResourceTypeForDiagnostics.StylesheetFromDecorator :
|
||||
ResourceTypeForDiagnostics.StylesheetFromTemplate;
|
||||
const resourceUrl = this._resolveResourceOrThrow(
|
||||
styleUrl.url, containingFile, styleUrl.nodeForError, resourceType);
|
||||
const resourceStr = this.resourceLoader.load(resourceUrl);
|
||||
try {
|
||||
const resourceUrl = this.resourceLoader.resolve(styleUrl.url, containingFile);
|
||||
const resourceStr = this.resourceLoader.load(resourceUrl);
|
||||
styles.push(resourceStr);
|
||||
if (this.depTracker !== null) {
|
||||
this.depTracker.addResourceDependency(node.getSourceFile(), absoluteFrom(resourceUrl));
|
||||
}
|
||||
} catch {
|
||||
if (diagnostics === undefined) {
|
||||
diagnostics = [];
|
||||
}
|
||||
const resourceType =
|
||||
styleUrl.source === ResourceTypeForDiagnostics.StylesheetFromDecorator ?
|
||||
ResourceTypeForDiagnostics.StylesheetFromDecorator :
|
||||
ResourceTypeForDiagnostics.StylesheetFromTemplate;
|
||||
diagnostics.push(
|
||||
this.makeResourceNotFoundError(styleUrl.url, styleUrl.nodeForError, resourceType)
|
||||
.toDiagnostic());
|
||||
}
|
||||
}
|
||||
|
||||
styles.push(resourceStr);
|
||||
if (this.depTracker !== null) {
|
||||
this.depTracker.addResourceDependency(node.getSourceFile(), absoluteFrom(resourceUrl));
|
||||
if (encapsulation === ViewEncapsulation.ShadowDom && metadata.selector !== null) {
|
||||
const selectorError = checkCustomElementSelectorForErrors(metadata.selector);
|
||||
if (selectorError !== null) {
|
||||
if (diagnostics === undefined) {
|
||||
diagnostics = [];
|
||||
}
|
||||
diagnostics.push(makeDiagnostic(
|
||||
ErrorCode.COMPONENT_INVALID_SHADOW_DOM_SELECTOR, component.get('selector')!,
|
||||
selectorError));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -450,17 +480,6 @@ export class ComponentDecoratorHandler implements
|
||||
styles.push(...template.styles);
|
||||
}
|
||||
|
||||
const encapsulation: number =
|
||||
this._resolveEnumValue(component, 'encapsulation', 'ViewEncapsulation') || 0;
|
||||
|
||||
const changeDetection: number|null =
|
||||
this._resolveEnumValue(component, 'changeDetection', 'ChangeDetectionStrategy');
|
||||
|
||||
let animations: Expression|null = null;
|
||||
if (component.has('animations')) {
|
||||
animations = new WrappedNodeExpr(component.get('animations')!);
|
||||
}
|
||||
|
||||
const output: AnalysisOutput<ComponentAnalysisData> = {
|
||||
analysis: {
|
||||
baseClass: readBaseClass(node, this.reflector, this.evaluator),
|
||||
@@ -484,9 +503,8 @@ export class ComponentDecoratorHandler implements
|
||||
relativeContextFilePath,
|
||||
},
|
||||
typeCheckMeta: extractDirectiveTypeCheckMeta(node, inputs, this.reflector),
|
||||
metadataStmt: generateSetClassMetadataCall(
|
||||
node, this.reflector, this.defaultImportRecorder, this.isCore,
|
||||
this.annotateForClosureCompiler),
|
||||
classMetadata: extractClassMetadata(
|
||||
node, this.reflector, this.isCore, this.annotateForClosureCompiler),
|
||||
template,
|
||||
providersRequiringFactory,
|
||||
viewProvidersRequiringFactory,
|
||||
@@ -496,8 +514,9 @@ export class ComponentDecoratorHandler implements
|
||||
styles: styleResources,
|
||||
template: templateResource,
|
||||
},
|
||||
isPoisoned: false,
|
||||
isPoisoned,
|
||||
},
|
||||
diagnostics,
|
||||
};
|
||||
if (changeDetection !== null) {
|
||||
output.analysis!.meta.changeDetection = changeDetection;
|
||||
@@ -518,6 +537,7 @@ export class ComponentDecoratorHandler implements
|
||||
// the information about the component is available during the compile() phase.
|
||||
const ref = new Reference(node);
|
||||
this.metaRegistry.registerDirectiveMetadata({
|
||||
type: MetaType.Directive,
|
||||
ref,
|
||||
name: node.name.text,
|
||||
selector: analysis.meta.selector,
|
||||
@@ -565,7 +585,7 @@ export class ComponentDecoratorHandler implements
|
||||
selector,
|
||||
boundTemplate,
|
||||
templateMeta: {
|
||||
isInline: analysis.template.isInline,
|
||||
isInline: analysis.template.declaration.isInline,
|
||||
file: analysis.template.file,
|
||||
},
|
||||
});
|
||||
@@ -819,6 +839,13 @@ export class ComponentDecoratorHandler implements
|
||||
return {data};
|
||||
}
|
||||
|
||||
xi18n(ctx: Xi18nContext, node: ClassDeclaration, analysis: Readonly<ComponentAnalysisData>):
|
||||
void {
|
||||
ctx.updateFromTemplate(
|
||||
analysis.template.content, analysis.template.declaration.resolvedTemplateUrl,
|
||||
analysis.template.interpolationConfig ?? DEFAULT_INTERPOLATION_CONFIG);
|
||||
}
|
||||
|
||||
updateResources(node: ClassDeclaration, analysis: ComponentAnalysisData): void {
|
||||
const containingFile = node.getSourceFile().fileName;
|
||||
|
||||
@@ -833,14 +860,14 @@ export class ComponentDecoratorHandler implements
|
||||
let styles: string[] = [];
|
||||
if (analysis.styleUrls !== null) {
|
||||
for (const styleUrl of analysis.styleUrls) {
|
||||
const resourceType =
|
||||
styleUrl.source === ResourceTypeForDiagnostics.StylesheetFromDecorator ?
|
||||
ResourceTypeForDiagnostics.StylesheetFromDecorator :
|
||||
ResourceTypeForDiagnostics.StylesheetFromTemplate;
|
||||
const resolvedStyleUrl = this._resolveResourceOrThrow(
|
||||
styleUrl.url, containingFile, styleUrl.nodeForError, resourceType);
|
||||
const styleText = this.resourceLoader.load(resolvedStyleUrl);
|
||||
styles.push(styleText);
|
||||
try {
|
||||
const resolvedStyleUrl = this.resourceLoader.resolve(styleUrl.url, containingFile);
|
||||
const styleText = this.resourceLoader.load(resolvedStyleUrl);
|
||||
styles.push(styleText);
|
||||
} catch (e) {
|
||||
// Resource resolve failures should already be in the diagnostics list from the analyze
|
||||
// stage. We do not need to do anything with them when updating resources.
|
||||
}
|
||||
}
|
||||
}
|
||||
if (analysis.inlineStyles !== null) {
|
||||
@@ -864,7 +891,10 @@ export class ComponentDecoratorHandler implements
|
||||
const meta: R3ComponentMetadata = {...analysis.meta, ...resolution};
|
||||
const fac = compileNgFactoryDefField(toFactoryMetadata(meta, FactoryTarget.Component));
|
||||
const def = compileComponentFromMetadata(meta, pool, makeBindingParser());
|
||||
return compileResults(fac, def, analysis.metadataStmt, 'ɵcmp');
|
||||
const classMetadata = analysis.classMetadata !== null ?
|
||||
compileClassMetadata(analysis.classMetadata).toStmt() :
|
||||
null;
|
||||
return compileResults(fac, def, classMetadata, 'ɵcmp');
|
||||
}
|
||||
|
||||
compilePartial(
|
||||
@@ -873,10 +903,21 @@ export class ComponentDecoratorHandler implements
|
||||
if (analysis.template.errors !== null && analysis.template.errors.length > 0) {
|
||||
return [];
|
||||
}
|
||||
const templateInfo: DeclareComponentTemplateInfo = {
|
||||
content: analysis.template.content,
|
||||
sourceUrl: analysis.template.declaration.resolvedTemplateUrl,
|
||||
isInline: analysis.template.declaration.isInline,
|
||||
inlineTemplateLiteralExpression: analysis.template.sourceMapping.type === 'direct' ?
|
||||
new WrappedNodeExpr(analysis.template.sourceMapping.node) :
|
||||
null,
|
||||
};
|
||||
const meta: R3ComponentMetadata = {...analysis.meta, ...resolution};
|
||||
const fac = compileDeclareFactory(toFactoryMetadata(meta, FactoryTarget.Component));
|
||||
const def = compileDeclareComponentFromMetadata(meta, analysis.template);
|
||||
return compileResults(fac, def, analysis.metadataStmt, 'ɵcmp');
|
||||
const def = compileDeclareComponentFromMetadata(meta, analysis.template, templateInfo);
|
||||
const classMetadata = analysis.classMetadata !== null ?
|
||||
compileDeclareClassMetadata(analysis.classMetadata).toStmt() :
|
||||
null;
|
||||
return compileResults(fac, def, classMetadata, 'ɵcmp');
|
||||
}
|
||||
|
||||
private _resolveLiteral(decorator: Decorator): ts.ObjectLiteralExpression {
|
||||
@@ -978,10 +1019,14 @@ export class ComponentDecoratorHandler implements
|
||||
const styleUrlsExpr = component.get('styleUrls');
|
||||
if (styleUrlsExpr !== undefined && ts.isArrayLiteralExpression(styleUrlsExpr)) {
|
||||
for (const expression of stringLiteralElements(styleUrlsExpr)) {
|
||||
const resourceUrl = this._resolveResourceOrThrow(
|
||||
expression.text, containingFile, expression,
|
||||
ResourceTypeForDiagnostics.StylesheetFromDecorator);
|
||||
styles.add({path: absoluteFrom(resourceUrl), expression});
|
||||
try {
|
||||
const resourceUrl = this.resourceLoader.resolve(expression.text, containingFile);
|
||||
styles.add({path: absoluteFrom(resourceUrl), expression});
|
||||
} catch {
|
||||
// Errors in style resource extraction do not need to be handled here. We will produce
|
||||
// diagnostics for each one that fails in the analysis, after we evaluate the `styleUrls`
|
||||
// expression to determine _all_ style resources, not just the string literals.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1006,22 +1051,27 @@ export class ComponentDecoratorHandler implements
|
||||
throw createValueHasWrongTypeError(
|
||||
templateUrlExpr, templateUrl, 'templateUrl must be a string');
|
||||
}
|
||||
const resourceUrl = this._resolveResourceOrThrow(
|
||||
templateUrl, containingFile, templateUrlExpr, ResourceTypeForDiagnostics.Template);
|
||||
const templatePromise =
|
||||
this.resourceLoader.preload(resourceUrl, {type: 'template', containingFile});
|
||||
try {
|
||||
const resourceUrl = this.resourceLoader.resolve(templateUrl, containingFile);
|
||||
const templatePromise =
|
||||
this.resourceLoader.preload(resourceUrl, {type: 'template', containingFile});
|
||||
|
||||
// If the preload worked, then actually load and parse the template, and wait for any style
|
||||
// URLs to resolve.
|
||||
if (templatePromise !== undefined) {
|
||||
return templatePromise.then(() => {
|
||||
const templateDecl = this.parseTemplateDeclaration(decorator, component, containingFile);
|
||||
const template = this.extractTemplate(node, templateDecl);
|
||||
this.preanalyzeTemplateCache.set(node, template);
|
||||
return template;
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve(null);
|
||||
// If the preload worked, then actually load and parse the template, and wait for any style
|
||||
// URLs to resolve.
|
||||
if (templatePromise !== undefined) {
|
||||
return templatePromise.then(() => {
|
||||
const templateDecl =
|
||||
this.parseTemplateDeclaration(decorator, component, containingFile);
|
||||
const template = this.extractTemplate(node, templateDecl);
|
||||
this.preanalyzeTemplateCache.set(node, template);
|
||||
return template;
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
} catch (e) {
|
||||
throw this.makeResourceNotFoundError(
|
||||
templateUrl, templateUrlExpr, ResourceTypeForDiagnostics.Template);
|
||||
}
|
||||
} else {
|
||||
const templateDecl = this.parseTemplateDeclaration(decorator, component, containingFile);
|
||||
@@ -1034,48 +1084,57 @@ export class ComponentDecoratorHandler implements
|
||||
private extractTemplate(node: ClassDeclaration, template: TemplateDeclaration):
|
||||
ParsedTemplateWithSource {
|
||||
if (template.isInline) {
|
||||
let templateStr: string;
|
||||
let templateLiteral: ts.Node|null = null;
|
||||
let templateUrl: string = '';
|
||||
let templateRange: LexerRange|null = null;
|
||||
let sourceStr: string;
|
||||
let sourceParseRange: LexerRange|null = null;
|
||||
let templateContent: string;
|
||||
let sourceMapping: TemplateSourceMapping;
|
||||
let escapedString = false;
|
||||
let sourceMapUrl: string|null;
|
||||
// We only support SourceMaps for inline templates that are simple string literals.
|
||||
if (ts.isStringLiteral(template.expression) ||
|
||||
ts.isNoSubstitutionTemplateLiteral(template.expression)) {
|
||||
// the start and end of the `templateExpr` node includes the quotation marks, which we must
|
||||
// strip
|
||||
templateRange = getTemplateRange(template.expression);
|
||||
templateStr = template.expression.getSourceFile().text;
|
||||
templateLiteral = template.expression;
|
||||
templateUrl = template.templateUrl;
|
||||
sourceParseRange = getTemplateRange(template.expression);
|
||||
sourceStr = template.expression.getSourceFile().text;
|
||||
templateContent = template.expression.text;
|
||||
escapedString = true;
|
||||
sourceMapping = {
|
||||
type: 'direct',
|
||||
node: template.expression,
|
||||
};
|
||||
sourceMapUrl = template.resolvedTemplateUrl;
|
||||
} else {
|
||||
const resolvedTemplate = this.evaluator.evaluate(template.expression);
|
||||
if (typeof resolvedTemplate !== 'string') {
|
||||
throw createValueHasWrongTypeError(
|
||||
template.expression, resolvedTemplate, 'template must be a string');
|
||||
}
|
||||
templateStr = resolvedTemplate;
|
||||
// We do not parse the template directly from the source file using a lexer range, so
|
||||
// the template source and content are set to the statically resolved template.
|
||||
sourceStr = resolvedTemplate;
|
||||
templateContent = resolvedTemplate;
|
||||
sourceMapping = {
|
||||
type: 'indirect',
|
||||
node: template.expression,
|
||||
componentClass: node,
|
||||
template: templateStr,
|
||||
template: templateContent,
|
||||
};
|
||||
|
||||
// Indirect templates cannot be mapped to a particular byte range of any input file, since
|
||||
// they're computed by expressions that may span many files. Don't attempt to map them back
|
||||
// to a given file.
|
||||
sourceMapUrl = null;
|
||||
}
|
||||
|
||||
return {
|
||||
...this._parseTemplate(template, templateStr, templateRange, escapedString),
|
||||
...this._parseTemplate(template, sourceStr, sourceParseRange, escapedString, sourceMapUrl),
|
||||
content: templateContent,
|
||||
sourceMapping,
|
||||
declaration: template,
|
||||
};
|
||||
} else {
|
||||
const templateStr = this.resourceLoader.load(template.resolvedTemplateUrl);
|
||||
const templateContent = this.resourceLoader.load(template.resolvedTemplateUrl);
|
||||
if (this.depTracker !== null) {
|
||||
this.depTracker.addResourceDependency(
|
||||
node.getSourceFile(), absoluteFrom(template.resolvedTemplateUrl));
|
||||
@@ -1083,15 +1142,17 @@ export class ComponentDecoratorHandler implements
|
||||
|
||||
return {
|
||||
...this._parseTemplate(
|
||||
template, templateStr, /* templateRange */ null,
|
||||
/* escapedString */ false),
|
||||
template, /* sourceStr */ templateContent, /* sourceParseRange */ null,
|
||||
/* escapedString */ false,
|
||||
/* sourceMapUrl */ template.resolvedTemplateUrl),
|
||||
content: templateContent,
|
||||
sourceMapping: {
|
||||
type: 'external',
|
||||
componentClass: node,
|
||||
// TODO(alxhub): TS in g3 is unable to make this inference on its own, so cast it here
|
||||
// until g3 is able to figure this out.
|
||||
node: (template as ExternalTemplateDeclaration).templateUrlExpression,
|
||||
template: templateStr,
|
||||
template: templateContent,
|
||||
templateUrl: template.resolvedTemplateUrl,
|
||||
},
|
||||
declaration: template,
|
||||
@@ -1100,19 +1161,18 @@ export class ComponentDecoratorHandler implements
|
||||
}
|
||||
|
||||
private _parseTemplate(
|
||||
template: TemplateDeclaration, templateStr: string, templateRange: LexerRange|null,
|
||||
escapedString: boolean): ParsedComponentTemplate {
|
||||
template: TemplateDeclaration, sourceStr: string, sourceParseRange: LexerRange|null,
|
||||
escapedString: boolean, sourceMapUrl: string|null): ParsedComponentTemplate {
|
||||
// We always normalize line endings if the template has been escaped (i.e. is inline).
|
||||
const i18nNormalizeLineEndingsInICUs = escapedString || this.i18nNormalizeLineEndingsInICUs;
|
||||
|
||||
const parsedTemplate = parseTemplate(templateStr, template.sourceMapUrl, {
|
||||
const parsedTemplate = parseTemplate(sourceStr, sourceMapUrl ?? '', {
|
||||
preserveWhitespaces: template.preserveWhitespaces,
|
||||
interpolationConfig: template.interpolationConfig,
|
||||
range: templateRange ?? undefined,
|
||||
range: sourceParseRange ?? undefined,
|
||||
escapedString,
|
||||
enableI18nLegacyMessageIdFormat: this.enableI18nLegacyMessageIdFormat,
|
||||
i18nNormalizeLineEndingsInICUs,
|
||||
isInline: template.isInline,
|
||||
alwaysAttemptHtmlToR3AstConversion: this.usePoisonedData,
|
||||
});
|
||||
|
||||
@@ -1131,26 +1191,22 @@ export class ComponentDecoratorHandler implements
|
||||
// In order to guarantee the correctness of diagnostics, templates are parsed a second time
|
||||
// with the above options set to preserve source mappings.
|
||||
|
||||
const {nodes: diagNodes} = parseTemplate(templateStr, template.sourceMapUrl, {
|
||||
const {nodes: diagNodes} = parseTemplate(sourceStr, sourceMapUrl ?? '', {
|
||||
preserveWhitespaces: true,
|
||||
preserveLineEndings: true,
|
||||
interpolationConfig: template.interpolationConfig,
|
||||
range: templateRange ?? undefined,
|
||||
range: sourceParseRange ?? undefined,
|
||||
escapedString,
|
||||
enableI18nLegacyMessageIdFormat: this.enableI18nLegacyMessageIdFormat,
|
||||
i18nNormalizeLineEndingsInICUs,
|
||||
leadingTriviaChars: [],
|
||||
isInline: template.isInline,
|
||||
alwaysAttemptHtmlToR3AstConversion: this.usePoisonedData,
|
||||
});
|
||||
|
||||
return {
|
||||
...parsedTemplate,
|
||||
diagNodes,
|
||||
template: template.isInline ? new WrappedNodeExpr(template.expression) : templateStr,
|
||||
templateUrl: template.resolvedTemplateUrl,
|
||||
isInline: template.isInline,
|
||||
file: new ParseSourceFile(templateStr, template.resolvedTemplateUrl),
|
||||
file: new ParseSourceFile(sourceStr, sourceMapUrl ?? ''),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1186,18 +1242,20 @@ export class ComponentDecoratorHandler implements
|
||||
throw createValueHasWrongTypeError(
|
||||
templateUrlExpr, templateUrl, 'templateUrl must be a string');
|
||||
}
|
||||
const resourceUrl = this._resolveResourceOrThrow(
|
||||
templateUrl, containingFile, templateUrlExpr, ResourceTypeForDiagnostics.Template);
|
||||
|
||||
return {
|
||||
isInline: false,
|
||||
interpolationConfig,
|
||||
preserveWhitespaces,
|
||||
templateUrl,
|
||||
templateUrlExpression: templateUrlExpr,
|
||||
resolvedTemplateUrl: resourceUrl,
|
||||
sourceMapUrl: sourceMapUrl(resourceUrl),
|
||||
};
|
||||
try {
|
||||
const resourceUrl = this.resourceLoader.resolve(templateUrl, containingFile);
|
||||
return {
|
||||
isInline: false,
|
||||
interpolationConfig,
|
||||
preserveWhitespaces,
|
||||
templateUrl,
|
||||
templateUrlExpression: templateUrlExpr,
|
||||
resolvedTemplateUrl: resourceUrl,
|
||||
};
|
||||
} catch (e) {
|
||||
throw this.makeResourceNotFoundError(
|
||||
templateUrl, templateUrlExpr, ResourceTypeForDiagnostics.Template);
|
||||
}
|
||||
} else if (component.has('template')) {
|
||||
return {
|
||||
isInline: true,
|
||||
@@ -1206,7 +1264,6 @@ export class ComponentDecoratorHandler implements
|
||||
expression: component.get('template')!,
|
||||
templateUrl: containingFile,
|
||||
resolvedTemplateUrl: containingFile,
|
||||
sourceMapUrl: containingFile,
|
||||
};
|
||||
} else {
|
||||
throw new FatalDiagnosticError(
|
||||
@@ -1260,33 +1317,24 @@ export class ComponentDecoratorHandler implements
|
||||
this.cycleAnalyzer.recordSyntheticImport(origin, imported);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the url of a resource relative to the file that contains the reference to it.
|
||||
*
|
||||
* Throws a FatalDiagnosticError when unable to resolve the file.
|
||||
*/
|
||||
private _resolveResourceOrThrow(
|
||||
file: string, basePath: string, nodeForError: ts.Node,
|
||||
resourceType: ResourceTypeForDiagnostics): string {
|
||||
try {
|
||||
return this.resourceLoader.resolve(file, basePath);
|
||||
} catch (e) {
|
||||
let errorText: string;
|
||||
switch (resourceType) {
|
||||
case ResourceTypeForDiagnostics.Template:
|
||||
errorText = `Could not find template file '${file}'.`;
|
||||
break;
|
||||
case ResourceTypeForDiagnostics.StylesheetFromTemplate:
|
||||
errorText = `Could not find stylesheet file '${file}' linked from the template.`;
|
||||
break;
|
||||
case ResourceTypeForDiagnostics.StylesheetFromDecorator:
|
||||
errorText = `Could not find stylesheet file '${file}'.`;
|
||||
break;
|
||||
}
|
||||
|
||||
throw new FatalDiagnosticError(
|
||||
ErrorCode.COMPONENT_RESOURCE_NOT_FOUND, nodeForError, errorText);
|
||||
private makeResourceNotFoundError(
|
||||
file: string, nodeForError: ts.Node,
|
||||
resourceType: ResourceTypeForDiagnostics): FatalDiagnosticError {
|
||||
let errorText: string;
|
||||
switch (resourceType) {
|
||||
case ResourceTypeForDiagnostics.Template:
|
||||
errorText = `Could not find template file '${file}'.`;
|
||||
break;
|
||||
case ResourceTypeForDiagnostics.StylesheetFromTemplate:
|
||||
errorText = `Could not find stylesheet file '${file}' linked from the template.`;
|
||||
break;
|
||||
case ResourceTypeForDiagnostics.StylesheetFromDecorator:
|
||||
errorText = `Could not find stylesheet file '${file}'.`;
|
||||
break;
|
||||
}
|
||||
|
||||
return new FatalDiagnosticError(
|
||||
ErrorCode.COMPONENT_RESOURCE_NOT_FOUND, nodeForError, errorText);
|
||||
}
|
||||
|
||||
private _extractTemplateStyleUrls(template: ParsedTemplateWithSource): StyleUrlMeta[] {
|
||||
@@ -1312,17 +1360,6 @@ function getTemplateRange(templateExpr: ts.Expression) {
|
||||
};
|
||||
}
|
||||
|
||||
function sourceMapUrl(resourceUrl: string): string {
|
||||
if (!tsSourceMapBug29300Fixed()) {
|
||||
// By removing the template URL we are telling the translator not to try to
|
||||
// map the external source file to the generated code, since the version
|
||||
// of TS that is running does not support it.
|
||||
return '';
|
||||
} else {
|
||||
return resourceUrl;
|
||||
}
|
||||
}
|
||||
|
||||
/** Determines if the result of an evaluation is a string array. */
|
||||
function isStringArray(resolvedValue: ResolvedValue): resolvedValue is string[] {
|
||||
return Array.isArray(resolvedValue) && resolvedValue.every(elem => typeof elem === 'string');
|
||||
@@ -1348,12 +1385,6 @@ function getTemplateDeclarationNodeForError(declaration: TemplateDeclaration): t
|
||||
* some of which might be useful for re-parsing the template with different options.
|
||||
*/
|
||||
export interface ParsedComponentTemplate extends ParsedTemplate {
|
||||
/**
|
||||
* True if the original template was stored inline;
|
||||
* False if the template was in an external file.
|
||||
*/
|
||||
isInline: boolean;
|
||||
|
||||
/**
|
||||
* The template AST, parsed in a manner which preserves source map information for diagnostics.
|
||||
*
|
||||
@@ -1368,6 +1399,8 @@ export interface ParsedComponentTemplate extends ParsedTemplate {
|
||||
}
|
||||
|
||||
export interface ParsedTemplateWithSource extends ParsedComponentTemplate {
|
||||
/** The string contents of the template. */
|
||||
content: string;
|
||||
sourceMapping: TemplateSourceMapping;
|
||||
declaration: TemplateDeclaration;
|
||||
}
|
||||
@@ -1380,7 +1413,6 @@ interface CommonTemplateDeclaration {
|
||||
interpolationConfig: InterpolationConfig;
|
||||
templateUrl: string;
|
||||
resolvedTemplateUrl: string;
|
||||
sourceMapUrl: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1402,7 +1434,7 @@ interface ExternalTemplateDeclaration extends CommonTemplateDeclaration {
|
||||
/**
|
||||
* The declaration of a template extracted from a component decorator.
|
||||
*
|
||||
* This data is extracted and stored separately to faciliate re-interpreting the template
|
||||
* This data is extracted and stored separately to facilitate re-interpreting the template
|
||||
* declaration whenever the compiler is notified of a change to a template file. With this
|
||||
* information, `ComponentDecoratorHandler` is able to re-read the template and update the component
|
||||
* record without needing to parse the original decorator again.
|
||||
@@ -1420,3 +1452,31 @@ function makeCyclicImportInfo(
|
||||
`The ${type} '${name}' is used in the template but importing it would create a cycle: `;
|
||||
return makeRelatedInformation(ref.node, message + path);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether a selector is a valid custom element tag name.
|
||||
* Based loosely on https://github.com/sindresorhus/validate-element-name.
|
||||
*/
|
||||
function checkCustomElementSelectorForErrors(selector: string): string|null {
|
||||
// Avoid flagging components with an attribute or class selector. This isn't bulletproof since it
|
||||
// won't catch cases like `foo[]bar`, but we don't need it to be. This is mainly to avoid flagging
|
||||
// something like `foo-bar[baz]` incorrectly.
|
||||
if (selector.includes('.') || (selector.includes('[') && selector.includes(']'))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(/^[a-z]/.test(selector))) {
|
||||
return 'Selector of a ShadowDom-encapsulated component must start with a lower case letter.';
|
||||
}
|
||||
|
||||
if (/[A-Z]/.test(selector)) {
|
||||
return 'Selector of a ShadowDom-encapsulated component must all be in lower case.';
|
||||
}
|
||||
|
||||
if (!selector.includes('-')) {
|
||||
return 'Selector of a component that uses ViewEncapsulation.ShadowDom must contain a hyphen.';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {compileDeclareDirectiveFromMetadata, compileDirectiveFromMetadata, ConstantPool, Expression, ExternalExpr, FactoryTarget, getSafePropertyAccessString, makeBindingParser, ParsedHostBindings, ParseError, parseHostBindings, R3DirectiveMetadata, R3FactoryMetadata, R3QueryMetadata, Statement, verifyHostBindings, WrappedNodeExpr} from '@angular/compiler';
|
||||
import {compileClassMetadata, compileDeclareClassMetadata, compileDeclareDirectiveFromMetadata, compileDirectiveFromMetadata, ConstantPool, Expression, ExternalExpr, FactoryTarget, getSafePropertyAccessString, makeBindingParser, ParsedHostBindings, ParseError, parseHostBindings, R3ClassMetadata, R3DirectiveMetadata, R3FactoryMetadata, R3QueryMetadata, Statement, verifyHostBindings, WrappedNodeExpr} from '@angular/compiler';
|
||||
import {emitDistinctChangesOnlyDefaultValue} from '@angular/compiler/src/core';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||
import {DefaultImportRecorder, Reference} from '../../imports';
|
||||
import {Reference} from '../../imports';
|
||||
import {areTypeParametersEqual, extractSemanticTypeParameters, isArrayEqual, isSetEqual, isSymbolEqual, SemanticDepGraphUpdater, SemanticSymbol, SemanticTypeParameter} from '../../incremental/semantic_graph';
|
||||
import {BindingPropertyName, ClassPropertyMapping, ClassPropertyName, DirectiveTypeCheckMeta, InjectableClassRegistry, MetadataReader, MetadataRegistry, TemplateGuardMeta} from '../../metadata';
|
||||
import {BindingPropertyName, ClassPropertyMapping, ClassPropertyName, DirectiveTypeCheckMeta, InjectableClassRegistry, MetadataReader, MetadataRegistry, MetaType, TemplateGuardMeta} from '../../metadata';
|
||||
import {extractDirectiveTypeCheckMeta} from '../../metadata/src/util';
|
||||
import {DynamicValue, EnumValue, PartialEvaluator} from '../../partial_evaluator';
|
||||
import {PerfEvent, PerfRecorder} from '../../perf';
|
||||
@@ -23,8 +23,8 @@ import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerFl
|
||||
|
||||
import {createValueHasWrongTypeError, getDirectiveDiagnostics, getProviderDiagnostics, getUndecoratedClassWithAngularFeaturesDiagnostic} from './diagnostics';
|
||||
import {compileDeclareFactory, compileNgFactoryDefField} from './factory';
|
||||
import {generateSetClassMetadataCall} from './metadata';
|
||||
import {compileResults, createSourceSpan, findAngularDecorator, getConstructorDependencies, isAngularDecorator, readBaseClass, resolveProvidersRequiringFactory, toFactoryMetadata, unwrapConstructorDependencies, unwrapExpression, unwrapForwardRef, validateConstructorDependencies, wrapFunctionExpressionsInParens, wrapTypeReference} from './util';
|
||||
import {extractClassMetadata} from './metadata';
|
||||
import {compileResults, createSourceSpan, findAngularDecorator, getConstructorDependencies, isAngularDecorator, readBaseClass, resolveProvidersRequiringFactory, toFactoryMetadata, tryUnwrapForwardRef, unwrapConstructorDependencies, unwrapExpression, validateConstructorDependencies, wrapFunctionExpressionsInParens, wrapTypeReference} from './util';
|
||||
|
||||
const EMPTY_OBJECT: {[key: string]: string} = {};
|
||||
const FIELD_DECORATORS = [
|
||||
@@ -40,7 +40,7 @@ export interface DirectiveHandlerData {
|
||||
baseClass: Reference<ClassDeclaration>|'dynamic'|null;
|
||||
typeCheckMeta: DirectiveTypeCheckMeta;
|
||||
meta: R3DirectiveMetadata;
|
||||
metadataStmt: Statement|null;
|
||||
classMetadata: R3ClassMetadata|null;
|
||||
providersRequiringFactory: Set<Reference<ClassDeclaration>>|null;
|
||||
inputs: ClassPropertyMapping;
|
||||
outputs: ClassPropertyMapping;
|
||||
@@ -64,7 +64,7 @@ export class DirectiveSymbol extends SemanticSymbol {
|
||||
super(decl);
|
||||
}
|
||||
|
||||
isPublicApiAffected(previousSymbol: SemanticSymbol): boolean {
|
||||
override isPublicApiAffected(previousSymbol: SemanticSymbol): boolean {
|
||||
// Note: since components and directives have exactly the same items contributing to their
|
||||
// public API, it is okay for a directive to change into a component and vice versa without
|
||||
// the API being affected.
|
||||
@@ -83,7 +83,7 @@ export class DirectiveSymbol extends SemanticSymbol {
|
||||
!isArrayEqual(this.exportAs, previousSymbol.exportAs);
|
||||
}
|
||||
|
||||
isTypeCheckApiAffected(previousSymbol: SemanticSymbol): boolean {
|
||||
override isTypeCheckApiAffected(previousSymbol: SemanticSymbol): boolean {
|
||||
// If the public API of the directive has changed, then so has its type-check API.
|
||||
if (this.isPublicApiAffected(previousSymbol)) {
|
||||
return true;
|
||||
@@ -177,9 +177,8 @@ export class DirectiveDecoratorHandler implements
|
||||
constructor(
|
||||
private reflector: ReflectionHost, private evaluator: PartialEvaluator,
|
||||
private metaRegistry: MetadataRegistry, private scopeRegistry: LocalModuleScopeRegistry,
|
||||
private metaReader: MetadataReader, private defaultImportRecorder: DefaultImportRecorder,
|
||||
private injectableRegistry: InjectableClassRegistry, private isCore: boolean,
|
||||
private semanticDepGraphUpdater: SemanticDepGraphUpdater|null,
|
||||
private metaReader: MetadataReader, private injectableRegistry: InjectableClassRegistry,
|
||||
private isCore: boolean, private semanticDepGraphUpdater: SemanticDepGraphUpdater|null,
|
||||
private annotateForClosureCompiler: boolean,
|
||||
private compileUndecoratedClassesWithAngularFeatures: boolean, private perf: PerfRecorder) {}
|
||||
|
||||
@@ -215,8 +214,8 @@ export class DirectiveDecoratorHandler implements
|
||||
this.perf.eventCount(PerfEvent.AnalyzeDirective);
|
||||
|
||||
const directiveResult = extractDirectiveMetadata(
|
||||
node, decorator, this.reflector, this.evaluator, this.defaultImportRecorder, this.isCore,
|
||||
flags, this.annotateForClosureCompiler);
|
||||
node, decorator, this.reflector, this.evaluator, this.isCore, flags,
|
||||
this.annotateForClosureCompiler);
|
||||
if (directiveResult === undefined) {
|
||||
return {};
|
||||
}
|
||||
@@ -233,9 +232,8 @@ export class DirectiveDecoratorHandler implements
|
||||
inputs: directiveResult.inputs,
|
||||
outputs: directiveResult.outputs,
|
||||
meta: analysis,
|
||||
metadataStmt: generateSetClassMetadataCall(
|
||||
node, this.reflector, this.defaultImportRecorder, this.isCore,
|
||||
this.annotateForClosureCompiler),
|
||||
classMetadata: extractClassMetadata(
|
||||
node, this.reflector, this.isCore, this.annotateForClosureCompiler),
|
||||
baseClass: readBaseClass(node, this.reflector, this.evaluator),
|
||||
typeCheckMeta: extractDirectiveTypeCheckMeta(node, directiveResult.inputs, this.reflector),
|
||||
providersRequiringFactory,
|
||||
@@ -258,6 +256,7 @@ export class DirectiveDecoratorHandler implements
|
||||
// the information about the directive is available during the compile() phase.
|
||||
const ref = new Reference(node);
|
||||
this.metaRegistry.registerDirectiveMetadata({
|
||||
type: MetaType.Directive,
|
||||
ref,
|
||||
name: node.name.text,
|
||||
selector: analysis.meta.selector,
|
||||
@@ -304,7 +303,10 @@ export class DirectiveDecoratorHandler implements
|
||||
resolution: Readonly<unknown>, pool: ConstantPool): CompileResult[] {
|
||||
const fac = compileNgFactoryDefField(toFactoryMetadata(analysis.meta, FactoryTarget.Directive));
|
||||
const def = compileDirectiveFromMetadata(analysis.meta, pool, makeBindingParser());
|
||||
return compileResults(fac, def, analysis.metadataStmt, 'ɵdir');
|
||||
const classMetadata = analysis.classMetadata !== null ?
|
||||
compileClassMetadata(analysis.classMetadata).toStmt() :
|
||||
null;
|
||||
return compileResults(fac, def, classMetadata, 'ɵdir');
|
||||
}
|
||||
|
||||
compilePartial(
|
||||
@@ -312,7 +314,10 @@ export class DirectiveDecoratorHandler implements
|
||||
resolution: Readonly<unknown>): CompileResult[] {
|
||||
const fac = compileDeclareFactory(toFactoryMetadata(analysis.meta, FactoryTarget.Directive));
|
||||
const def = compileDeclareDirectiveFromMetadata(analysis.meta);
|
||||
return compileResults(fac, def, analysis.metadataStmt, 'ɵdir');
|
||||
const classMetadata = analysis.classMetadata !== null ?
|
||||
compileDeclareClassMetadata(analysis.classMetadata).toStmt() :
|
||||
null;
|
||||
return compileResults(fac, def, classMetadata, 'ɵdir');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -345,9 +350,8 @@ export class DirectiveDecoratorHandler implements
|
||||
*/
|
||||
export function extractDirectiveMetadata(
|
||||
clazz: ClassDeclaration, decorator: Readonly<Decorator|null>, reflector: ReflectionHost,
|
||||
evaluator: PartialEvaluator, defaultImportRecorder: DefaultImportRecorder, isCore: boolean,
|
||||
flags: HandlerFlags, annotateForClosureCompiler: boolean,
|
||||
defaultSelector: string|null = null): {
|
||||
evaluator: PartialEvaluator, isCore: boolean, flags: HandlerFlags,
|
||||
annotateForClosureCompiler: boolean, defaultSelector: string|null = null): {
|
||||
decorator: Map<string, ts.Expression>,
|
||||
metadata: R3DirectiveMetadata,
|
||||
inputs: ClassPropertyMapping,
|
||||
@@ -467,7 +471,7 @@ export function extractDirectiveMetadata(
|
||||
exportAs = resolved.split(',').map(part => part.trim());
|
||||
}
|
||||
|
||||
const rawCtorDeps = getConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore);
|
||||
const rawCtorDeps = getConstructorDependencies(clazz, reflector, isCore);
|
||||
|
||||
// Non-abstract directives (those with a selector) require valid constructor dependencies, whereas
|
||||
// abstract directives are allowed to have invalid dependencies, given that a subclass may call
|
||||
@@ -528,7 +532,7 @@ export function extractQueryMetadata(
|
||||
ErrorCode.DECORATOR_ARITY_WRONG, exprNode, `@${name} must have arguments`);
|
||||
}
|
||||
const first = name === 'ViewChild' || name === 'ContentChild';
|
||||
const node = unwrapForwardRef(args[0], reflector);
|
||||
const node = tryUnwrapForwardRef(args[0], reflector) ?? args[0];
|
||||
const arg = evaluator.evaluate(node);
|
||||
|
||||
/** Whether or not this query should collect only static results (see view/api.ts) */
|
||||
|
||||
@@ -10,6 +10,8 @@ import {compileDeclareFactoryFunction, compileFactoryFunction, R3FactoryMetadata
|
||||
|
||||
import {CompileResult} from '../../transform';
|
||||
|
||||
export type CompileFactoryFn = (metadata: R3FactoryMetadata) => CompileResult;
|
||||
|
||||
export function compileNgFactoryDefField(metadata: R3FactoryMetadata): CompileResult {
|
||||
const res = compileFactoryFunction(metadata);
|
||||
return {name: 'ɵfac', initializer: res.expression, statements: res.statements, type: res.type};
|
||||
|
||||
@@ -6,35 +6,33 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {compileInjectable as compileIvyInjectable, Expression, FactoryTarget, LiteralExpr, R3DependencyMetadata, R3FactoryMetadata, R3InjectableMetadata, Statement, WrappedNodeExpr} from '@angular/compiler';
|
||||
import {compileClassMetadata, CompileClassMetadataFn, compileDeclareClassMetadata, compileDeclareInjectableFromMetadata, compileInjectable, createR3ProviderExpression, Expression, FactoryTarget, LiteralExpr, R3ClassMetadata, R3CompiledExpression, R3DependencyMetadata, R3InjectableMetadata, R3ProviderExpression, Statement, WrappedNodeExpr} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||
import {DefaultImportRecorder} from '../../imports';
|
||||
import {InjectableClassRegistry} from '../../metadata';
|
||||
import {PerfEvent, PerfRecorder} from '../../perf';
|
||||
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
||||
import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPrecedence} from '../../transform';
|
||||
|
||||
import {compileDeclareFactory, compileNgFactoryDefField} from './factory';
|
||||
import {generateSetClassMetadataCall} from './metadata';
|
||||
import {findAngularDecorator, getConstructorDependencies, getValidConstructorDependencies, isAngularCore, toFactoryMetadata, unwrapConstructorDependencies, unwrapForwardRef, validateConstructorDependencies, wrapTypeReference} from './util';
|
||||
import {compileDeclareFactory, CompileFactoryFn, compileNgFactoryDefField} from './factory';
|
||||
import {extractClassMetadata} from './metadata';
|
||||
import {findAngularDecorator, getConstructorDependencies, getValidConstructorDependencies, isAngularCore, toFactoryMetadata, tryUnwrapForwardRef, unwrapConstructorDependencies, validateConstructorDependencies, wrapTypeReference} from './util';
|
||||
|
||||
export interface InjectableHandlerData {
|
||||
meta: R3InjectableMetadata;
|
||||
metadataStmt: Statement|null;
|
||||
classMetadata: R3ClassMetadata|null;
|
||||
ctorDeps: R3DependencyMetadata[]|'invalid'|null;
|
||||
needsFactory: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapts the `compileIvyInjectable` compiler for `@Injectable` decorators to the Ivy compiler.
|
||||
* Adapts the `compileInjectable` compiler for `@Injectable` decorators to the Ivy compiler.
|
||||
*/
|
||||
export class InjectableDecoratorHandler implements
|
||||
DecoratorHandler<Decorator, InjectableHandlerData, null, unknown> {
|
||||
constructor(
|
||||
private reflector: ReflectionHost, private defaultImportRecorder: DefaultImportRecorder,
|
||||
private isCore: boolean, private strictCtorDeps: boolean,
|
||||
private reflector: ReflectionHost, private isCore: boolean, private strictCtorDeps: boolean,
|
||||
private injectableRegistry: InjectableClassRegistry, private perf: PerfRecorder,
|
||||
/**
|
||||
* What to do if the injectable already contains a ɵprov property.
|
||||
@@ -74,10 +72,8 @@ export class InjectableDecoratorHandler implements
|
||||
analysis: {
|
||||
meta,
|
||||
ctorDeps: extractInjectableCtorDeps(
|
||||
node, meta, decorator, this.reflector, this.defaultImportRecorder, this.isCore,
|
||||
this.strictCtorDeps),
|
||||
metadataStmt: generateSetClassMetadataCall(
|
||||
node, this.reflector, this.defaultImportRecorder, this.isCore),
|
||||
node, meta, decorator, this.reflector, this.isCore, this.strictCtorDeps),
|
||||
classMetadata: extractClassMetadata(node, this.reflector, this.isCore),
|
||||
// Avoid generating multiple factories if a class has
|
||||
// more Angular decorators, apart from Injectable.
|
||||
needsFactory: !decorators ||
|
||||
@@ -95,48 +91,31 @@ export class InjectableDecoratorHandler implements
|
||||
}
|
||||
|
||||
compileFull(node: ClassDeclaration, analysis: Readonly<InjectableHandlerData>): CompileResult[] {
|
||||
const res = compileIvyInjectable(analysis.meta);
|
||||
const statements = res.statements;
|
||||
const results: CompileResult[] = [];
|
||||
|
||||
if (analysis.needsFactory) {
|
||||
const meta = analysis.meta;
|
||||
const factoryRes = compileNgFactoryDefField(
|
||||
toFactoryMetadata({...meta, deps: analysis.ctorDeps}, FactoryTarget.Injectable));
|
||||
if (analysis.metadataStmt !== null) {
|
||||
factoryRes.statements.push(analysis.metadataStmt);
|
||||
}
|
||||
results.push(factoryRes);
|
||||
}
|
||||
|
||||
const ɵprov = this.reflector.getMembersOfClass(node).find(member => member.name === 'ɵprov');
|
||||
if (ɵprov !== undefined && this.errorOnDuplicateProv) {
|
||||
throw new FatalDiagnosticError(
|
||||
ErrorCode.INJECTABLE_DUPLICATE_PROV, ɵprov.nameNode || ɵprov.node || node,
|
||||
'Injectables cannot contain a static ɵprov property, because the compiler is going to generate one.');
|
||||
}
|
||||
|
||||
if (ɵprov === undefined) {
|
||||
// Only add a new ɵprov if there is not one already
|
||||
results.push({name: 'ɵprov', initializer: res.expression, statements, type: res.type});
|
||||
}
|
||||
|
||||
|
||||
return results;
|
||||
return this.compile(
|
||||
compileNgFactoryDefField, meta => compileInjectable(meta, false), compileClassMetadata,
|
||||
node, analysis);
|
||||
}
|
||||
|
||||
compilePartial(node: ClassDeclaration, analysis: Readonly<InjectableHandlerData>):
|
||||
CompileResult[] {
|
||||
const res = compileIvyInjectable(analysis.meta);
|
||||
const statements = res.statements;
|
||||
return this.compile(
|
||||
compileDeclareFactory, compileDeclareInjectableFromMetadata, compileDeclareClassMetadata,
|
||||
node, analysis);
|
||||
}
|
||||
|
||||
private compile(
|
||||
compileFactoryFn: CompileFactoryFn,
|
||||
compileInjectableFn: (meta: R3InjectableMetadata) => R3CompiledExpression,
|
||||
compileClassMetadataFn: CompileClassMetadataFn, node: ClassDeclaration,
|
||||
analysis: Readonly<InjectableHandlerData>): CompileResult[] {
|
||||
const results: CompileResult[] = [];
|
||||
|
||||
if (analysis.needsFactory) {
|
||||
const meta = analysis.meta;
|
||||
const factoryRes = compileDeclareFactory(
|
||||
const factoryRes = compileFactoryFn(
|
||||
toFactoryMetadata({...meta, deps: analysis.ctorDeps}, FactoryTarget.Injectable));
|
||||
if (analysis.metadataStmt !== null) {
|
||||
factoryRes.statements.push(analysis.metadataStmt);
|
||||
if (analysis.classMetadata !== null) {
|
||||
factoryRes.statements.push(compileClassMetadataFn(analysis.classMetadata).toStmt());
|
||||
}
|
||||
results.push(factoryRes);
|
||||
}
|
||||
@@ -150,7 +129,9 @@ export class InjectableDecoratorHandler implements
|
||||
|
||||
if (ɵprov === undefined) {
|
||||
// Only add a new ɵprov if there is not one already
|
||||
results.push({name: 'ɵprov', initializer: res.expression, statements, type: res.type});
|
||||
const res = compileInjectableFn(analysis.meta);
|
||||
results.push(
|
||||
{name: 'ɵprov', initializer: res.expression, statements: res.statements, type: res.type});
|
||||
}
|
||||
|
||||
return results;
|
||||
@@ -159,7 +140,7 @@ export class InjectableDecoratorHandler implements
|
||||
|
||||
/**
|
||||
* Read metadata from the `@Injectable` decorator and produce the `IvyInjectableMetadata`, the
|
||||
* input metadata needed to run `compileIvyInjectable`.
|
||||
* input metadata needed to run `compileInjectable`.
|
||||
*
|
||||
* A `null` return value indicates this is @Injectable has invalid data.
|
||||
*/
|
||||
@@ -181,7 +162,7 @@ function extractInjectableMetadata(
|
||||
type,
|
||||
typeArgumentCount,
|
||||
internalType,
|
||||
providedIn: new LiteralExpr(null),
|
||||
providedIn: createR3ProviderExpression(new LiteralExpr(null), false),
|
||||
};
|
||||
} else if (decorator.args.length === 1) {
|
||||
const metaNode = decorator.args[0];
|
||||
@@ -196,12 +177,12 @@ function extractInjectableMetadata(
|
||||
|
||||
// Resolve the fields of the literal into a map of field name to expression.
|
||||
const meta = reflectObjectLiteral(metaNode);
|
||||
let providedIn: Expression = new LiteralExpr(null);
|
||||
if (meta.has('providedIn')) {
|
||||
providedIn = new WrappedNodeExpr(meta.get('providedIn')!);
|
||||
}
|
||||
|
||||
let userDeps: R3DependencyMetadata[]|undefined = undefined;
|
||||
const providedIn = meta.has('providedIn') ?
|
||||
getProviderExpression(meta.get('providedIn')!, reflector) :
|
||||
createR3ProviderExpression(new LiteralExpr(null), false);
|
||||
|
||||
let deps: R3DependencyMetadata[]|undefined = undefined;
|
||||
if ((meta.has('useClass') || meta.has('useFactory')) && meta.has('deps')) {
|
||||
const depsExpr = meta.get('deps')!;
|
||||
if (!ts.isArrayLiteralExpression(depsExpr)) {
|
||||
@@ -209,62 +190,45 @@ function extractInjectableMetadata(
|
||||
ErrorCode.VALUE_NOT_LITERAL, depsExpr,
|
||||
`@Injectable deps metadata must be an inline array`);
|
||||
}
|
||||
userDeps = depsExpr.elements.map(dep => getDep(dep, reflector));
|
||||
deps = depsExpr.elements.map(dep => getDep(dep, reflector));
|
||||
}
|
||||
|
||||
const result: R3InjectableMetadata = {name, type, typeArgumentCount, internalType, providedIn};
|
||||
if (meta.has('useValue')) {
|
||||
return {
|
||||
name,
|
||||
type,
|
||||
typeArgumentCount,
|
||||
internalType,
|
||||
providedIn,
|
||||
useValue: new WrappedNodeExpr(unwrapForwardRef(meta.get('useValue')!, reflector)),
|
||||
};
|
||||
result.useValue = getProviderExpression(meta.get('useValue')!, reflector);
|
||||
} else if (meta.has('useExisting')) {
|
||||
return {
|
||||
name,
|
||||
type,
|
||||
typeArgumentCount,
|
||||
internalType,
|
||||
providedIn,
|
||||
useExisting: new WrappedNodeExpr(unwrapForwardRef(meta.get('useExisting')!, reflector)),
|
||||
};
|
||||
result.useExisting = getProviderExpression(meta.get('useExisting')!, reflector);
|
||||
} else if (meta.has('useClass')) {
|
||||
return {
|
||||
name,
|
||||
type,
|
||||
typeArgumentCount,
|
||||
internalType,
|
||||
providedIn,
|
||||
useClass: new WrappedNodeExpr(unwrapForwardRef(meta.get('useClass')!, reflector)),
|
||||
userDeps,
|
||||
};
|
||||
result.useClass = getProviderExpression(meta.get('useClass')!, reflector);
|
||||
result.deps = deps;
|
||||
} else if (meta.has('useFactory')) {
|
||||
// useFactory is special - the 'deps' property must be analyzed.
|
||||
const factory = new WrappedNodeExpr(meta.get('useFactory')!);
|
||||
return {
|
||||
name,
|
||||
type,
|
||||
typeArgumentCount,
|
||||
internalType,
|
||||
providedIn,
|
||||
useFactory: factory,
|
||||
userDeps,
|
||||
};
|
||||
} else {
|
||||
return {name, type, typeArgumentCount, internalType, providedIn};
|
||||
result.useFactory = new WrappedNodeExpr(meta.get('useFactory')!);
|
||||
result.deps = deps;
|
||||
}
|
||||
return result;
|
||||
} else {
|
||||
throw new FatalDiagnosticError(
|
||||
ErrorCode.DECORATOR_ARITY_WRONG, decorator.args[2], 'Too many arguments to @Injectable');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the `R3ProviderExpression` for this `expression`.
|
||||
*
|
||||
* The `useValue`, `useExisting` and `useClass` properties might be wrapped in a `ForwardRef`, which
|
||||
* needs to be unwrapped. This function will do that unwrapping and set a flag on the returned
|
||||
* object to indicate whether the value needed unwrapping.
|
||||
*/
|
||||
function getProviderExpression(
|
||||
expression: ts.Expression, reflector: ReflectionHost): R3ProviderExpression {
|
||||
const forwardRefValue = tryUnwrapForwardRef(expression, reflector);
|
||||
return createR3ProviderExpression(
|
||||
new WrappedNodeExpr(forwardRefValue ?? expression), forwardRefValue !== null);
|
||||
}
|
||||
|
||||
function extractInjectableCtorDeps(
|
||||
clazz: ClassDeclaration, meta: R3InjectableMetadata, decorator: Decorator,
|
||||
reflector: ReflectionHost, defaultImportRecorder: DefaultImportRecorder, isCore: boolean,
|
||||
strictCtorDeps: boolean) {
|
||||
reflector: ReflectionHost, isCore: boolean, strictCtorDeps: boolean) {
|
||||
if (decorator.args === null) {
|
||||
throw new FatalDiagnosticError(
|
||||
ErrorCode.DECORATOR_NOT_CALLED, Decorator.nodeForError(decorator),
|
||||
@@ -283,15 +247,15 @@ function extractInjectableCtorDeps(
|
||||
// constructor signature does not work for DI then a factory definition (ɵfac) that throws is
|
||||
// generated.
|
||||
if (strictCtorDeps) {
|
||||
ctorDeps = getValidConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore);
|
||||
ctorDeps = getValidConstructorDependencies(clazz, reflector, isCore);
|
||||
} else {
|
||||
ctorDeps = unwrapConstructorDependencies(
|
||||
getConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore));
|
||||
ctorDeps =
|
||||
unwrapConstructorDependencies(getConstructorDependencies(clazz, reflector, isCore));
|
||||
}
|
||||
|
||||
return ctorDeps;
|
||||
} else if (decorator.args.length === 1) {
|
||||
const rawCtorDeps = getConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore);
|
||||
const rawCtorDeps = getConstructorDependencies(clazz, reflector, isCore);
|
||||
|
||||
if (strictCtorDeps && meta.useValue === undefined && meta.useExisting === undefined &&
|
||||
meta.useClass === undefined && meta.useFactory === undefined) {
|
||||
|
||||
@@ -6,15 +6,13 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {devOnlyGuardedExpression, Expression, ExternalExpr, FunctionExpr, Identifiers, InvokeFunctionExpr, LiteralArrayExpr, LiteralExpr, literalMap, NONE_TYPE, ReturnStatement, Statement, WrappedNodeExpr} from '@angular/compiler';
|
||||
import {Expression, FunctionExpr, LiteralArrayExpr, LiteralExpr, literalMap, R3ClassMetadata, ReturnStatement, WrappedNodeExpr} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {DefaultImportRecorder} from '../../imports';
|
||||
import {CtorParameter, DeclarationNode, Decorator, ReflectionHost, TypeValueReferenceKind} from '../../reflection';
|
||||
|
||||
import {valueReferenceToExpression, wrapFunctionExpressionsInParens} from './util';
|
||||
|
||||
|
||||
/**
|
||||
* Given a class declaration, generate a call to `setClassMetadata` with the Angular metadata
|
||||
* present on the class or its member fields. An ngDevMode guard is used to allow the call to be
|
||||
@@ -23,10 +21,9 @@ import {valueReferenceToExpression, wrapFunctionExpressionsInParens} from './uti
|
||||
* If no such metadata is present, this function returns `null`. Otherwise, the call is returned
|
||||
* as a `Statement` for inclusion along with the class.
|
||||
*/
|
||||
export function generateSetClassMetadataCall(
|
||||
clazz: DeclarationNode, reflection: ReflectionHost,
|
||||
defaultImportRecorder: DefaultImportRecorder, isCore: boolean,
|
||||
annotateForClosureCompiler?: boolean): Statement|null {
|
||||
export function extractClassMetadata(
|
||||
clazz: DeclarationNode, reflection: ReflectionHost, isCore: boolean,
|
||||
annotateForClosureCompiler?: boolean): R3ClassMetadata|null {
|
||||
if (!reflection.isClass(clazz)) {
|
||||
return null;
|
||||
}
|
||||
@@ -50,21 +47,20 @@ export function generateSetClassMetadataCall(
|
||||
if (ngClassDecorators.length === 0) {
|
||||
return null;
|
||||
}
|
||||
const metaDecorators = ts.createArrayLiteral(ngClassDecorators);
|
||||
const metaDecorators = new WrappedNodeExpr(ts.createArrayLiteral(ngClassDecorators));
|
||||
|
||||
// Convert the constructor parameters to metadata, passing null if none are present.
|
||||
let metaCtorParameters: Expression = new LiteralExpr(null);
|
||||
let metaCtorParameters: Expression|null = null;
|
||||
const classCtorParameters = reflection.getConstructorParameters(clazz);
|
||||
if (classCtorParameters !== null) {
|
||||
const ctorParameters = classCtorParameters.map(
|
||||
param => ctorParameterToMetadata(param, defaultImportRecorder, isCore));
|
||||
const ctorParameters = classCtorParameters.map(param => ctorParameterToMetadata(param, isCore));
|
||||
metaCtorParameters = new FunctionExpr([], [
|
||||
new ReturnStatement(new LiteralArrayExpr(ctorParameters)),
|
||||
]);
|
||||
}
|
||||
|
||||
// Do the same for property decorators.
|
||||
let metaPropDecorators: ts.Expression = ts.createNull();
|
||||
let metaPropDecorators: Expression|null = null;
|
||||
const classMembers = reflection.getMembersOfClass(clazz).filter(
|
||||
member => !member.isStatic && member.decorators !== null && member.decorators.length > 0);
|
||||
const duplicateDecoratedMemberNames =
|
||||
@@ -80,34 +76,25 @@ export function generateSetClassMetadataCall(
|
||||
const decoratedMembers = classMembers.map(
|
||||
member => classMemberToMetadata(member.nameNode ?? member.name, member.decorators!, isCore));
|
||||
if (decoratedMembers.length > 0) {
|
||||
metaPropDecorators = ts.createObjectLiteral(decoratedMembers);
|
||||
metaPropDecorators = new WrappedNodeExpr(ts.createObjectLiteral(decoratedMembers));
|
||||
}
|
||||
|
||||
// Generate a pure call to setClassMetadata with the class identifier and its metadata.
|
||||
const setClassMetadata = new ExternalExpr(Identifiers.setClassMetadata);
|
||||
const fnCall = new InvokeFunctionExpr(
|
||||
/* fn */ setClassMetadata,
|
||||
/* args */
|
||||
[
|
||||
new WrappedNodeExpr(id),
|
||||
new WrappedNodeExpr(metaDecorators),
|
||||
metaCtorParameters,
|
||||
new WrappedNodeExpr(metaPropDecorators),
|
||||
]);
|
||||
const iife = new FunctionExpr([], [devOnlyGuardedExpression(fnCall).toStmt()]);
|
||||
return iife.callFn([]).toStmt();
|
||||
return {
|
||||
type: new WrappedNodeExpr(id),
|
||||
decorators: metaDecorators,
|
||||
ctorParameters: metaCtorParameters,
|
||||
propDecorators: metaPropDecorators,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a reflected constructor parameter to metadata.
|
||||
*/
|
||||
function ctorParameterToMetadata(
|
||||
param: CtorParameter, defaultImportRecorder: DefaultImportRecorder,
|
||||
isCore: boolean): Expression {
|
||||
function ctorParameterToMetadata(param: CtorParameter, isCore: boolean): Expression {
|
||||
// Parameters sometimes have a type that can be referenced. If so, then use it, otherwise
|
||||
// its type is undefined.
|
||||
const type = param.typeValueReference.kind !== TypeValueReferenceKind.UNAVAILABLE ?
|
||||
valueReferenceToExpression(param.typeValueReference, defaultImportRecorder) :
|
||||
valueReferenceToExpression(param.typeValueReference) :
|
||||
new LiteralExpr(undefined);
|
||||
|
||||
const mapEntries: {key: string, value: Expression, quoted: false}[] = [
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {compileDeclareInjectorFromMetadata, compileDeclareNgModuleFromMetadata, compileInjector, compileNgModule, CUSTOM_ELEMENTS_SCHEMA, Expression, ExternalExpr, FactoryTarget, Identifiers as R3, InvokeFunctionExpr, LiteralArrayExpr, LiteralExpr, NO_ERRORS_SCHEMA, R3CompiledExpression, R3FactoryMetadata, R3Identifiers, R3InjectorMetadata, R3NgModuleMetadata, R3Reference, SchemaMetadata, Statement, STRING_TYPE, WrappedNodeExpr} from '@angular/compiler';
|
||||
import {compileClassMetadata, compileDeclareClassMetadata, compileDeclareInjectorFromMetadata, compileDeclareNgModuleFromMetadata, compileInjector, compileNgModule, CUSTOM_ELEMENTS_SCHEMA, Expression, ExternalExpr, FactoryTarget, Identifiers as R3, InvokeFunctionExpr, LiteralArrayExpr, LiteralExpr, NO_ERRORS_SCHEMA, R3ClassMetadata, R3CompiledExpression, R3FactoryMetadata, R3Identifiers, R3InjectorMetadata, R3NgModuleMetadata, R3Reference, SchemaMetadata, Statement, STRING_TYPE, WrappedNodeExpr} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ErrorCode, FatalDiagnosticError, makeDiagnostic, makeRelatedInformation} from '../../diagnostics';
|
||||
import {DefaultImportRecorder, Reference, ReferenceEmitter} from '../../imports';
|
||||
import {Reference, ReferenceEmitter} from '../../imports';
|
||||
import {isArrayEqual, isReferenceEqual, isSymbolEqual, SemanticReference, SemanticSymbol} from '../../incremental/semantic_graph';
|
||||
import {InjectableClassRegistry, MetadataReader, MetadataRegistry} from '../../metadata';
|
||||
import {PartialEvaluator, ResolvedValue} from '../../partial_evaluator';
|
||||
@@ -24,7 +24,7 @@ import {getSourceFile} from '../../util/src/typescript';
|
||||
|
||||
import {createValueHasWrongTypeError, getProviderDiagnostics} from './diagnostics';
|
||||
import {compileDeclareFactory, compileNgFactoryDefField} from './factory';
|
||||
import {generateSetClassMetadataCall} from './metadata';
|
||||
import {extractClassMetadata} from './metadata';
|
||||
import {ReferencesRegistry} from './references_registry';
|
||||
import {combineResolvers, findAngularDecorator, forwardRefResolver, getValidConstructorDependencies, isExpressionForwardReference, resolveProvidersRequiringFactory, toR3Reference, unwrapExpression, wrapFunctionExpressionsInParens, wrapTypeReference} from './util';
|
||||
|
||||
@@ -32,7 +32,7 @@ export interface NgModuleAnalysis {
|
||||
mod: R3NgModuleMetadata;
|
||||
inj: R3InjectorMetadata;
|
||||
fac: R3FactoryMetadata;
|
||||
metadataStmt: Statement|null;
|
||||
classMetadata: R3ClassMetadata|null;
|
||||
declarations: Reference<ClassDeclaration>[];
|
||||
rawDeclarations: ts.Expression|null;
|
||||
schemas: SchemaMetadata[];
|
||||
@@ -58,7 +58,7 @@ export class NgModuleSymbol extends SemanticSymbol {
|
||||
usedPipes: SemanticReference[]
|
||||
}[] = [];
|
||||
|
||||
isPublicApiAffected(previousSymbol: SemanticSymbol): boolean {
|
||||
override isPublicApiAffected(previousSymbol: SemanticSymbol): boolean {
|
||||
if (!(previousSymbol instanceof NgModuleSymbol)) {
|
||||
return true;
|
||||
}
|
||||
@@ -67,7 +67,7 @@ export class NgModuleSymbol extends SemanticSymbol {
|
||||
return false;
|
||||
}
|
||||
|
||||
isEmitAffected(previousSymbol: SemanticSymbol): boolean {
|
||||
override isEmitAffected(previousSymbol: SemanticSymbol): boolean {
|
||||
if (!(previousSymbol instanceof NgModuleSymbol)) {
|
||||
return true;
|
||||
}
|
||||
@@ -104,7 +104,7 @@ export class NgModuleSymbol extends SemanticSymbol {
|
||||
return false;
|
||||
}
|
||||
|
||||
isTypeCheckApiAffected(previousSymbol: SemanticSymbol): boolean {
|
||||
override isTypeCheckApiAffected(previousSymbol: SemanticSymbol): boolean {
|
||||
if (!(previousSymbol instanceof NgModuleSymbol)) {
|
||||
return true;
|
||||
}
|
||||
@@ -130,9 +130,7 @@ export class NgModuleDecoratorHandler implements
|
||||
private scopeRegistry: LocalModuleScopeRegistry,
|
||||
private referencesRegistry: ReferencesRegistry, private isCore: boolean,
|
||||
private routeAnalyzer: NgModuleRouteAnalyzer|null, private refEmitter: ReferenceEmitter,
|
||||
private factoryTracker: FactoryTracker|null,
|
||||
private defaultImportRecorder: DefaultImportRecorder,
|
||||
private annotateForClosureCompiler: boolean,
|
||||
private factoryTracker: FactoryTracker|null, private annotateForClosureCompiler: boolean,
|
||||
private injectableRegistry: InjectableClassRegistry, private perf: PerfRecorder,
|
||||
private localeId?: string) {}
|
||||
|
||||
@@ -350,8 +348,7 @@ export class NgModuleDecoratorHandler implements
|
||||
type,
|
||||
internalType,
|
||||
typeArgumentCount: 0,
|
||||
deps: getValidConstructorDependencies(
|
||||
node, this.reflector, this.defaultImportRecorder, this.isCore),
|
||||
deps: getValidConstructorDependencies(node, this.reflector, this.isCore),
|
||||
target: FactoryTarget.NgModule,
|
||||
};
|
||||
|
||||
@@ -370,9 +367,8 @@ export class NgModuleDecoratorHandler implements
|
||||
providersRequiringFactory: rawProviders ?
|
||||
resolveProvidersRequiringFactory(rawProviders, this.reflector, this.evaluator) :
|
||||
null,
|
||||
metadataStmt: generateSetClassMetadataCall(
|
||||
node, this.reflector, this.defaultImportRecorder, this.isCore,
|
||||
this.annotateForClosureCompiler),
|
||||
classMetadata: extractClassMetadata(
|
||||
node, this.reflector, this.isCore, this.annotateForClosureCompiler),
|
||||
factorySymbolName: node.name.text,
|
||||
},
|
||||
};
|
||||
@@ -463,26 +459,28 @@ export class NgModuleDecoratorHandler implements
|
||||
|
||||
compileFull(
|
||||
node: ClassDeclaration,
|
||||
{inj, mod, fac, metadataStmt, declarations}: Readonly<NgModuleAnalysis>,
|
||||
{inj, mod, fac, classMetadata, declarations}: Readonly<NgModuleAnalysis>,
|
||||
{injectorImports}: Readonly<NgModuleResolution>): CompileResult[] {
|
||||
const factoryFn = compileNgFactoryDefField(fac);
|
||||
const ngInjectorDef = compileInjector(this.mergeInjectorImports(inj, injectorImports));
|
||||
const ngModuleDef = compileNgModule(mod);
|
||||
const statements = ngModuleDef.statements;
|
||||
this.insertMetadataStatement(statements, metadataStmt);
|
||||
const metadata = classMetadata !== null ? compileClassMetadata(classMetadata) : null;
|
||||
this.insertMetadataStatement(statements, metadata);
|
||||
this.appendRemoteScopingStatements(statements, node, declarations);
|
||||
|
||||
return this.compileNgModule(factoryFn, ngInjectorDef, ngModuleDef);
|
||||
}
|
||||
|
||||
compilePartial(
|
||||
node: ClassDeclaration, {inj, fac, mod, metadataStmt}: Readonly<NgModuleAnalysis>,
|
||||
node: ClassDeclaration, {inj, fac, mod, classMetadata}: Readonly<NgModuleAnalysis>,
|
||||
{injectorImports}: Readonly<NgModuleResolution>): CompileResult[] {
|
||||
const factoryFn = compileDeclareFactory(fac);
|
||||
const injectorDef =
|
||||
compileDeclareInjectorFromMetadata(this.mergeInjectorImports(inj, injectorImports));
|
||||
const ngModuleDef = compileDeclareNgModuleFromMetadata(mod);
|
||||
this.insertMetadataStatement(ngModuleDef.statements, metadataStmt);
|
||||
const metadata = classMetadata !== null ? compileDeclareClassMetadata(classMetadata) : null;
|
||||
this.insertMetadataStatement(ngModuleDef.statements, metadata);
|
||||
// NOTE: no remote scoping required as this is banned in partial compilation.
|
||||
return this.compileNgModule(factoryFn, injectorDef, ngModuleDef);
|
||||
}
|
||||
@@ -499,10 +497,10 @@ export class NgModuleDecoratorHandler implements
|
||||
/**
|
||||
* Add class metadata statements, if provided, to the `ngModuleStatements`.
|
||||
*/
|
||||
private insertMetadataStatement(ngModuleStatements: Statement[], metadataStmt: Statement|null):
|
||||
private insertMetadataStatement(ngModuleStatements: Statement[], metadata: Expression|null):
|
||||
void {
|
||||
if (metadataStmt !== null) {
|
||||
ngModuleStatements.unshift(metadataStmt);
|
||||
if (metadata !== null) {
|
||||
ngModuleStatements.unshift(metadata.toStmt());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {compileDeclarePipeFromMetadata, compilePipeFromMetadata, FactoryTarget, R3PipeMetadata, Statement, WrappedNodeExpr} from '@angular/compiler';
|
||||
import {compileClassMetadata, compileDeclareClassMetadata, compileDeclarePipeFromMetadata, compilePipeFromMetadata, FactoryTarget, R3ClassMetadata, R3PipeMetadata, Statement, WrappedNodeExpr} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||
import {DefaultImportRecorder, Reference} from '../../imports';
|
||||
import {Reference} from '../../imports';
|
||||
import {SemanticSymbol} from '../../incremental/semantic_graph';
|
||||
import {InjectableClassRegistry, MetadataRegistry} from '../../metadata';
|
||||
import {InjectableClassRegistry, MetadataRegistry, MetaType} from '../../metadata';
|
||||
import {PartialEvaluator} from '../../partial_evaluator';
|
||||
import {PerfEvent, PerfRecorder} from '../../perf';
|
||||
import {ClassDeclaration, Decorator, ReflectionHost, reflectObjectLiteral} from '../../reflection';
|
||||
@@ -21,12 +21,13 @@ import {AnalysisOutput, CompileResult, DecoratorHandler, DetectResult, HandlerPr
|
||||
|
||||
import {createValueHasWrongTypeError} from './diagnostics';
|
||||
import {compileDeclareFactory, compileNgFactoryDefField} from './factory';
|
||||
import {generateSetClassMetadataCall} from './metadata';
|
||||
import {extractClassMetadata} from './metadata';
|
||||
import {compileResults, findAngularDecorator, getValidConstructorDependencies, makeDuplicateDeclarationError, toFactoryMetadata, unwrapExpression, wrapTypeReference} from './util';
|
||||
|
||||
export interface PipeHandlerData {
|
||||
meta: R3PipeMetadata;
|
||||
metadataStmt: Statement|null;
|
||||
classMetadata: R3ClassMetadata|null;
|
||||
pipeNameExpr: ts.Expression;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,7 +38,7 @@ export class PipeSymbol extends SemanticSymbol {
|
||||
super(decl);
|
||||
}
|
||||
|
||||
isPublicApiAffected(previousSymbol: SemanticSymbol): boolean {
|
||||
override isPublicApiAffected(previousSymbol: SemanticSymbol): boolean {
|
||||
if (!(previousSymbol instanceof PipeSymbol)) {
|
||||
return true;
|
||||
}
|
||||
@@ -45,7 +46,7 @@ export class PipeSymbol extends SemanticSymbol {
|
||||
return this.name !== previousSymbol.name;
|
||||
}
|
||||
|
||||
isTypeCheckApiAffected(previousSymbol: SemanticSymbol): boolean {
|
||||
override isTypeCheckApiAffected(previousSymbol: SemanticSymbol): boolean {
|
||||
return this.isPublicApiAffected(previousSymbol);
|
||||
}
|
||||
}
|
||||
@@ -55,7 +56,6 @@ export class PipeDecoratorHandler implements
|
||||
constructor(
|
||||
private reflector: ReflectionHost, private evaluator: PartialEvaluator,
|
||||
private metaRegistry: MetadataRegistry, private scopeRegistry: LocalModuleScopeRegistry,
|
||||
private defaultImportRecorder: DefaultImportRecorder,
|
||||
private injectableRegistry: InjectableClassRegistry, private isCore: boolean,
|
||||
private perf: PerfRecorder) {}
|
||||
|
||||
@@ -131,12 +131,11 @@ export class PipeDecoratorHandler implements
|
||||
internalType,
|
||||
typeArgumentCount: this.reflector.getGenericArityOfClass(clazz) || 0,
|
||||
pipeName,
|
||||
deps: getValidConstructorDependencies(
|
||||
clazz, this.reflector, this.defaultImportRecorder, this.isCore),
|
||||
deps: getValidConstructorDependencies(clazz, this.reflector, this.isCore),
|
||||
pure,
|
||||
},
|
||||
metadataStmt: generateSetClassMetadataCall(
|
||||
clazz, this.reflector, this.defaultImportRecorder, this.isCore),
|
||||
classMetadata: extractClassMetadata(clazz, this.reflector, this.isCore),
|
||||
pipeNameExpr,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -147,7 +146,8 @@ export class PipeDecoratorHandler implements
|
||||
|
||||
register(node: ClassDeclaration, analysis: Readonly<PipeHandlerData>): void {
|
||||
const ref = new Reference(node);
|
||||
this.metaRegistry.registerPipeMetadata({ref, name: analysis.meta.pipeName});
|
||||
this.metaRegistry.registerPipeMetadata(
|
||||
{type: MetaType.Pipe, ref, name: analysis.meta.pipeName, nameExpr: analysis.pipeNameExpr});
|
||||
|
||||
this.injectableRegistry.registerInjectable(node);
|
||||
}
|
||||
@@ -167,12 +167,18 @@ export class PipeDecoratorHandler implements
|
||||
compileFull(node: ClassDeclaration, analysis: Readonly<PipeHandlerData>): CompileResult[] {
|
||||
const fac = compileNgFactoryDefField(toFactoryMetadata(analysis.meta, FactoryTarget.Pipe));
|
||||
const def = compilePipeFromMetadata(analysis.meta);
|
||||
return compileResults(fac, def, analysis.metadataStmt, 'ɵpipe');
|
||||
const classMetadata = analysis.classMetadata !== null ?
|
||||
compileClassMetadata(analysis.classMetadata).toStmt() :
|
||||
null;
|
||||
return compileResults(fac, def, classMetadata, 'ɵpipe');
|
||||
}
|
||||
|
||||
compilePartial(node: ClassDeclaration, analysis: Readonly<PipeHandlerData>): CompileResult[] {
|
||||
const fac = compileDeclareFactory(toFactoryMetadata(analysis.meta, FactoryTarget.Pipe));
|
||||
const def = compileDeclarePipeFromMetadata(analysis.meta);
|
||||
return compileResults(fac, def, analysis.metadataStmt, 'ɵpipe');
|
||||
const classMetadata = analysis.classMetadata !== null ?
|
||||
compileDeclareClassMetadata(analysis.classMetadata).toStmt() :
|
||||
null;
|
||||
return compileResults(fac, def, classMetadata, 'ɵpipe');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,8 @@ import {FactoryTarget} from '@angular/compiler/src/render3/partial/api';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ErrorCode, FatalDiagnosticError, makeDiagnostic, makeRelatedInformation} from '../../diagnostics';
|
||||
import {DefaultImportRecorder, ImportFlags, Reference, ReferenceEmitter} from '../../imports';
|
||||
import {ImportFlags, Reference, ReferenceEmitter} from '../../imports';
|
||||
import {attachDefaultImportDeclaration} from '../../imports/src/default';
|
||||
import {ForeignFunctionResolver, PartialEvaluator} from '../../partial_evaluator';
|
||||
import {ClassDeclaration, CtorParameter, Decorator, Import, ImportedTypeValueReference, isNamedClassDeclaration, LocalTypeValueReference, ReflectionHost, TypeValueReference, TypeValueReferenceKind, UnavailableValue, ValueUnavailableKind} from '../../reflection';
|
||||
import {DeclarationData} from '../../scope';
|
||||
@@ -32,8 +33,7 @@ export interface ConstructorDepError {
|
||||
}
|
||||
|
||||
export function getConstructorDependencies(
|
||||
clazz: ClassDeclaration, reflector: ReflectionHost,
|
||||
defaultImportRecorder: DefaultImportRecorder, isCore: boolean): ConstructorDeps|null {
|
||||
clazz: ClassDeclaration, reflector: ReflectionHost, isCore: boolean): ConstructorDeps|null {
|
||||
const deps: R3DependencyMetadata[] = [];
|
||||
const errors: ConstructorDepError[] = [];
|
||||
let ctorParams = reflector.getConstructorParameters(clazz);
|
||||
@@ -45,7 +45,7 @@ export function getConstructorDependencies(
|
||||
}
|
||||
}
|
||||
ctorParams.forEach((param, idx) => {
|
||||
let token = valueReferenceToExpression(param.typeValueReference, defaultImportRecorder);
|
||||
let token = valueReferenceToExpression(param.typeValueReference);
|
||||
let attributeNameType: Expression|null = null;
|
||||
let optional = false, self = false, skipSelf = false, host = false;
|
||||
|
||||
@@ -115,22 +115,18 @@ export function getConstructorDependencies(
|
||||
* references are converted to an `ExternalExpr`. Note that this is only valid in the context of the
|
||||
* file in which the `TypeValueReference` originated.
|
||||
*/
|
||||
export function valueReferenceToExpression(
|
||||
valueRef: LocalTypeValueReference|ImportedTypeValueReference,
|
||||
defaultImportRecorder: DefaultImportRecorder): Expression;
|
||||
export function valueReferenceToExpression(
|
||||
valueRef: TypeValueReference, defaultImportRecorder: DefaultImportRecorder): Expression|null;
|
||||
export function valueReferenceToExpression(
|
||||
valueRef: TypeValueReference, defaultImportRecorder: DefaultImportRecorder): Expression|null {
|
||||
export function valueReferenceToExpression(valueRef: LocalTypeValueReference|
|
||||
ImportedTypeValueReference): Expression;
|
||||
export function valueReferenceToExpression(valueRef: TypeValueReference): Expression|null;
|
||||
export function valueReferenceToExpression(valueRef: TypeValueReference): Expression|null {
|
||||
if (valueRef.kind === TypeValueReferenceKind.UNAVAILABLE) {
|
||||
return null;
|
||||
} else if (valueRef.kind === TypeValueReferenceKind.LOCAL) {
|
||||
if (defaultImportRecorder !== null && valueRef.defaultImportStatement !== null &&
|
||||
ts.isIdentifier(valueRef.expression)) {
|
||||
defaultImportRecorder.recordImportedIdentifier(
|
||||
valueRef.expression, valueRef.defaultImportStatement);
|
||||
const expr = new WrappedNodeExpr(valueRef.expression);
|
||||
if (valueRef.defaultImportStatement !== null) {
|
||||
attachDefaultImportDeclaration(expr, valueRef.defaultImportStatement);
|
||||
}
|
||||
return new WrappedNodeExpr(valueRef.expression);
|
||||
return expr;
|
||||
} else {
|
||||
let importExpr: Expression =
|
||||
new ExternalExpr({moduleName: valueRef.moduleName, name: valueRef.importedName});
|
||||
@@ -163,10 +159,10 @@ export function unwrapConstructorDependencies(deps: ConstructorDeps|null): R3Dep
|
||||
}
|
||||
|
||||
export function getValidConstructorDependencies(
|
||||
clazz: ClassDeclaration, reflector: ReflectionHost,
|
||||
defaultImportRecorder: DefaultImportRecorder, isCore: boolean): R3DependencyMetadata[]|null {
|
||||
clazz: ClassDeclaration, reflector: ReflectionHost, isCore: boolean): R3DependencyMetadata[]|
|
||||
null {
|
||||
return validateConstructorDependencies(
|
||||
clazz, getConstructorDependencies(clazz, reflector, defaultImportRecorder, isCore));
|
||||
clazz, getConstructorDependencies(clazz, reflector, isCore));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -332,36 +328,40 @@ function expandForwardRef(arg: ts.Expression): ts.Expression|null {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Possibly resolve a forwardRef() expression into the inner value.
|
||||
* If the given `node` is a forwardRef() expression then resolve its inner value, otherwise return
|
||||
* `null`.
|
||||
*
|
||||
* @param node the forwardRef() expression to resolve
|
||||
* @param reflector a ReflectionHost
|
||||
* @returns the resolved expression, if the original expression was a forwardRef(), or the original
|
||||
* expression otherwise
|
||||
* @returns the resolved expression, if the original expression was a forwardRef(), or `null`
|
||||
* otherwise.
|
||||
*/
|
||||
export function unwrapForwardRef(node: ts.Expression, reflector: ReflectionHost): ts.Expression {
|
||||
export function tryUnwrapForwardRef(node: ts.Expression, reflector: ReflectionHost): ts.Expression|
|
||||
null {
|
||||
node = unwrapExpression(node);
|
||||
if (!ts.isCallExpression(node) || node.arguments.length !== 1) {
|
||||
return node;
|
||||
return null;
|
||||
}
|
||||
|
||||
const fn =
|
||||
ts.isPropertyAccessExpression(node.expression) ? node.expression.name : node.expression;
|
||||
if (!ts.isIdentifier(fn)) {
|
||||
return node;
|
||||
return null;
|
||||
}
|
||||
|
||||
const expr = expandForwardRef(node.arguments[0]);
|
||||
if (expr === null) {
|
||||
return node;
|
||||
return null;
|
||||
}
|
||||
|
||||
const imp = reflector.getImportOfIdentifier(fn);
|
||||
if (imp === null || imp.from !== '@angular/core' || imp.name !== 'forwardRef') {
|
||||
return node;
|
||||
} else {
|
||||
return expr;
|
||||
return null;
|
||||
}
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,7 +13,7 @@ import {CycleAnalyzer, CycleHandlingStrategy, ImportGraph} from '../../cycles';
|
||||
import {ErrorCode, FatalDiagnosticError} from '../../diagnostics';
|
||||
import {absoluteFrom} from '../../file_system';
|
||||
import {runInEachFileSystem} from '../../file_system/testing';
|
||||
import {ModuleResolver, NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../imports';
|
||||
import {ModuleResolver, ReferenceEmitter} from '../../imports';
|
||||
import {CompoundMetadataReader, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry, ResourceRegistry} from '../../metadata';
|
||||
import {PartialEvaluator} from '../../partial_evaluator';
|
||||
import {NOOP_PERF_RECORDER} from '../../perf';
|
||||
@@ -81,7 +81,6 @@ function setup(program: ts.Program, options: ts.CompilerOptions, host: ts.Compil
|
||||
cycleAnalyzer,
|
||||
CycleHandlingStrategy.UseRemoteScoping,
|
||||
refEmitter,
|
||||
NOOP_DEFAULT_IMPORT_RECORDER,
|
||||
/* depTracker */ null,
|
||||
injectableRegistry,
|
||||
/* semanticDepGraphUpdater */ null,
|
||||
@@ -230,6 +229,36 @@ runInEachFileSystem(() => {
|
||||
expect(analysis?.resources.styles.size).toBe(3);
|
||||
});
|
||||
|
||||
it('should use an empty source map URL for an indirect template', () => {
|
||||
const template = '<span>indirect</span>';
|
||||
const {program, options, host} = makeProgram([
|
||||
{
|
||||
name: _('/node_modules/@angular/core/index.d.ts'),
|
||||
contents: 'export const Component: any;',
|
||||
},
|
||||
{
|
||||
name: _('/entry.ts'),
|
||||
contents: `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
const TEMPLATE = '${template}';
|
||||
|
||||
@Component({
|
||||
template: TEMPLATE,
|
||||
}) class TestCmp {}
|
||||
`
|
||||
},
|
||||
]);
|
||||
const {reflectionHost, handler} = setup(program, options, host);
|
||||
const TestCmp = getDeclaration(program, _('/entry.ts'), 'TestCmp', isNamedClassDeclaration);
|
||||
const detected = handler.detect(TestCmp, reflectionHost.getDecoratorsOfDeclaration(TestCmp));
|
||||
if (detected === undefined) {
|
||||
return fail('Failed to recognize @Component');
|
||||
}
|
||||
const {analysis} = handler.analyze(TestCmp, detected.metadata);
|
||||
expect(analysis?.template.file?.url).toEqual('');
|
||||
});
|
||||
|
||||
it('does not emit a program with template parse errors', () => {
|
||||
const template = '{{x ? y }}';
|
||||
const {program, options, host} = makeProgram([
|
||||
@@ -303,6 +332,74 @@ runInEachFileSystem(() => {
|
||||
const {analysis} = handler.analyze(TestCmp, detected.metadata);
|
||||
expect(analysis?.inlineStyles).toEqual(jasmine.arrayWithExactContents(['.xyz {}']));
|
||||
});
|
||||
|
||||
it('should error if canPreprocess is true and async analyze is not used', async () => {
|
||||
const {program, options, host} = makeProgram([
|
||||
{
|
||||
name: _('/node_modules/@angular/core/index.d.ts'),
|
||||
contents: 'export const Component: any;',
|
||||
},
|
||||
{
|
||||
name: _('/entry.ts'),
|
||||
contents: `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: '',
|
||||
styles: ['.abc {}']
|
||||
}) class TestCmp {}
|
||||
`
|
||||
},
|
||||
]);
|
||||
const {reflectionHost, handler, resourceLoader} = setup(program, options, host);
|
||||
resourceLoader.canPreload = true;
|
||||
resourceLoader.canPreprocess = true;
|
||||
|
||||
const TestCmp = getDeclaration(program, _('/entry.ts'), 'TestCmp', isNamedClassDeclaration);
|
||||
const detected = handler.detect(TestCmp, reflectionHost.getDecoratorsOfDeclaration(TestCmp));
|
||||
if (detected === undefined) {
|
||||
return fail('Failed to recognize @Component');
|
||||
}
|
||||
|
||||
expect(() => handler.analyze(TestCmp, detected.metadata))
|
||||
.toThrowError('Inline resource processing requires asynchronous preanalyze.');
|
||||
});
|
||||
|
||||
it('should not error if component has no inline styles and canPreprocess is true', async () => {
|
||||
const {program, options, host} = makeProgram([
|
||||
{
|
||||
name: _('/node_modules/@angular/core/index.d.ts'),
|
||||
contents: 'export const Component: any;',
|
||||
},
|
||||
{
|
||||
name: _('/entry.ts'),
|
||||
contents: `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: '',
|
||||
}) class TestCmp {}
|
||||
`
|
||||
},
|
||||
]);
|
||||
const {reflectionHost, handler, resourceLoader} = setup(program, options, host);
|
||||
resourceLoader.canPreload = true;
|
||||
resourceLoader.canPreprocess = true;
|
||||
resourceLoader.preprocessInline = async function(data, context) {
|
||||
fail('preprocessInline should not have been called.');
|
||||
return data;
|
||||
};
|
||||
|
||||
const TestCmp = getDeclaration(program, _('/entry.ts'), 'TestCmp', isNamedClassDeclaration);
|
||||
const detected = handler.detect(TestCmp, reflectionHost.getDecoratorsOfDeclaration(TestCmp));
|
||||
if (detected === undefined) {
|
||||
return fail('Failed to recognize @Component');
|
||||
}
|
||||
|
||||
await handler.preanalyze(TestCmp, detected.metadata);
|
||||
|
||||
expect(() => handler.analyze(TestCmp, detected.metadata)).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
function ivyCode(code: ErrorCode): number {
|
||||
|
||||
@@ -10,7 +10,7 @@ import * as ts from 'typescript';
|
||||
|
||||
import {absoluteFrom} from '../../file_system';
|
||||
import {runInEachFileSystem} from '../../file_system/testing';
|
||||
import {NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../imports';
|
||||
import {ReferenceEmitter} from '../../imports';
|
||||
import {DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry} from '../../metadata';
|
||||
import {PartialEvaluator} from '../../partial_evaluator';
|
||||
import {NOOP_PERF_RECORDER} from '../../perf';
|
||||
@@ -153,7 +153,7 @@ runInEachFileSystem(() => {
|
||||
super(checker);
|
||||
}
|
||||
|
||||
hasBaseClass(_class: ClassDeclaration): boolean {
|
||||
override hasBaseClass(_class: ClassDeclaration): boolean {
|
||||
return hasBaseClass;
|
||||
}
|
||||
}
|
||||
@@ -168,8 +168,8 @@ runInEachFileSystem(() => {
|
||||
null);
|
||||
const injectableRegistry = new InjectableClassRegistry(reflectionHost);
|
||||
const handler = new DirectiveDecoratorHandler(
|
||||
reflectionHost, evaluator, scopeRegistry, scopeRegistry, metaReader,
|
||||
NOOP_DEFAULT_IMPORT_RECORDER, injectableRegistry, /*isCore*/ false,
|
||||
reflectionHost, evaluator, scopeRegistry, scopeRegistry, metaReader, injectableRegistry,
|
||||
/*isCore*/ false,
|
||||
/*semanticDepGraphUpdater*/ null,
|
||||
/*annotateForClosureCompiler*/ false,
|
||||
/*detectUndecoratedClassesWithAngularFeatures*/ false, NOOP_PERF_RECORDER);
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
import {ErrorCode, FatalDiagnosticError, ngErrorCode} from '../../diagnostics';
|
||||
import {absoluteFrom} from '../../file_system';
|
||||
import {runInEachFileSystem} from '../../file_system/testing';
|
||||
import {NOOP_DEFAULT_IMPORT_RECORDER} from '../../imports';
|
||||
import {InjectableClassRegistry} from '../../metadata';
|
||||
import {NOOP_PERF_RECORDER} from '../../perf';
|
||||
import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
|
||||
@@ -70,7 +69,7 @@ function setupHandler(errorOnDuplicateProv: boolean) {
|
||||
const reflectionHost = new TypeScriptReflectionHost(checker);
|
||||
const injectableRegistry = new InjectableClassRegistry(reflectionHost);
|
||||
const handler = new InjectableDecoratorHandler(
|
||||
reflectionHost, NOOP_DEFAULT_IMPORT_RECORDER, /* isCore */ false,
|
||||
reflectionHost, /* isCore */ false,
|
||||
/* strictCtorDeps */ false, injectableRegistry, NOOP_PERF_RECORDER, errorOnDuplicateProv);
|
||||
const TestClass = getDeclaration(program, ENTRY_FILE, 'TestClass', isNamedClassDeclaration);
|
||||
const ɵprov = reflectionHost.getMembersOfClass(TestClass).find(member => member.name === 'ɵprov');
|
||||
|
||||
@@ -5,15 +5,16 @@
|
||||
* 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 {compileClassMetadata} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {absoluteFrom, getSourceFileOrError} from '../../file_system';
|
||||
import {runInEachFileSystem, TestFile} from '../../file_system/testing';
|
||||
import {NOOP_DEFAULT_IMPORT_RECORDER, NoopImportRewriter} from '../../imports';
|
||||
import {NoopImportRewriter} from '../../imports';
|
||||
import {TypeScriptReflectionHost} from '../../reflection';
|
||||
import {getDeclaration, makeProgram} from '../../testing';
|
||||
import {ImportManager, translateStatement} from '../../translator';
|
||||
import {generateSetClassMetadataCall} from '../src/metadata';
|
||||
import {extractClassMetadata} from '../src/metadata';
|
||||
|
||||
runInEachFileSystem(() => {
|
||||
describe('ngtsc setClassMetadata converter', () => {
|
||||
@@ -127,13 +128,14 @@ runInEachFileSystem(() => {
|
||||
{target: ts.ScriptTarget.ES2015});
|
||||
const host = new TypeScriptReflectionHost(program.getTypeChecker());
|
||||
const target = getDeclaration(program, _('/index.ts'), 'Target', ts.isClassDeclaration);
|
||||
const call = generateSetClassMetadataCall(target, host, NOOP_DEFAULT_IMPORT_RECORDER, false);
|
||||
const call = extractClassMetadata(target, host, false);
|
||||
if (call === null) {
|
||||
return '';
|
||||
}
|
||||
const sf = getSourceFileOrError(program, _('/index.ts'));
|
||||
const im = new ImportManager(new NoopImportRewriter(), 'i');
|
||||
const tsStatement = translateStatement(call, im);
|
||||
const stmt = compileClassMetadata(call).toStmt();
|
||||
const tsStatement = translateStatement(stmt, im);
|
||||
const res = ts.createPrinter().printNode(ts.EmitHint.Unspecified, tsStatement, sf);
|
||||
return res.replace(/\s+/g, ' ');
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import * as ts from 'typescript';
|
||||
|
||||
import {absoluteFrom} from '../../file_system';
|
||||
import {runInEachFileSystem} from '../../file_system/testing';
|
||||
import {LocalIdentifierStrategy, NOOP_DEFAULT_IMPORT_RECORDER, ReferenceEmitter} from '../../imports';
|
||||
import {LocalIdentifierStrategy, ReferenceEmitter} from '../../imports';
|
||||
import {CompoundMetadataReader, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry} from '../../metadata';
|
||||
import {PartialEvaluator} from '../../partial_evaluator';
|
||||
import {NOOP_PERF_RECORDER} from '../../perf';
|
||||
@@ -72,8 +72,7 @@ runInEachFileSystem(() => {
|
||||
const handler = new NgModuleDecoratorHandler(
|
||||
reflectionHost, evaluator, metaReader, metaRegistry, scopeRegistry, referencesRegistry,
|
||||
/* isCore */ false, /* routeAnalyzer */ null, refEmitter, /* factoryTracker */ null,
|
||||
NOOP_DEFAULT_IMPORT_RECORDER, /* annotateForClosureCompiler */ false, injectableRegistry,
|
||||
NOOP_PERF_RECORDER);
|
||||
/* annotateForClosureCompiler */ false, injectableRegistry, NOOP_PERF_RECORDER);
|
||||
const TestModule =
|
||||
getDeclaration(program, _('/entry.ts'), 'TestModule', isNamedClassDeclaration);
|
||||
const detected =
|
||||
|
||||
@@ -23,9 +23,9 @@ ts_library(
|
||||
"//packages/compiler-cli/src/ngtsc/incremental/semantic_graph",
|
||||
"//packages/compiler-cli/src/ngtsc/indexer",
|
||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
||||
"//packages/compiler-cli/src/ngtsc/modulewithproviders",
|
||||
"//packages/compiler-cli/src/ngtsc/partial_evaluator",
|
||||
"//packages/compiler-cli/src/ngtsc/perf",
|
||||
"//packages/compiler-cli/src/ngtsc/program_driver",
|
||||
"//packages/compiler-cli/src/ngtsc/reflection",
|
||||
"//packages/compiler-cli/src/ngtsc/resource",
|
||||
"//packages/compiler-cli/src/ngtsc/routing",
|
||||
@@ -38,6 +38,7 @@ ts_library(
|
||||
"//packages/compiler-cli/src/ngtsc/typecheck/api",
|
||||
"//packages/compiler-cli/src/ngtsc/typecheck/diagnostics",
|
||||
"//packages/compiler-cli/src/ngtsc/util",
|
||||
"//packages/compiler-cli/src/ngtsc/xi18n",
|
||||
"@npm//typescript",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -33,8 +33,13 @@ export interface ResourceHost {
|
||||
/**
|
||||
* Converts a file path for a resource that is used in a source file or another resource
|
||||
* into a filepath.
|
||||
*
|
||||
* The optional `fallbackResolve` method can be used as a way to attempt a fallback resolution if
|
||||
* the implementation's `resourceNameToFileName` resolution fails.
|
||||
*/
|
||||
resourceNameToFileName(resourceName: string, containingFilePath: string): string|null;
|
||||
resourceNameToFileName(
|
||||
resourceName: string, containingFilePath: string,
|
||||
fallbackResolve?: (url: string, fromFile: string) => string | null): string|null;
|
||||
|
||||
/**
|
||||
* Load a referenced resource either statically or asynchronously. If the host returns a
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {BazelAndG3Options, I18nOptions, LegacyNgcOptions, MiscOptions, NgcCompatibilityOptions, StrictTemplateOptions} from './public_options';
|
||||
import {BazelAndG3Options, I18nOptions, LegacyNgcOptions, MiscOptions, NgcCompatibilityOptions, StrictTemplateOptions, TargetOptions} from './public_options';
|
||||
|
||||
|
||||
/**
|
||||
@@ -36,22 +36,6 @@ export interface TestOnlyOptions {
|
||||
tracePerformance?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options that specify compilation target.
|
||||
*/
|
||||
export interface TargetOptions {
|
||||
/**
|
||||
* Specifies the compilation mode to use. The following modes are available:
|
||||
* - 'full': generates fully AOT compiled code using Ivy instructions.
|
||||
* - 'partial': generates code in a stable, but intermediate form suitable to be published to NPM.
|
||||
*
|
||||
* To become public once the linker is ready.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
compilationMode?: 'full'|'partial';
|
||||
}
|
||||
|
||||
/**
|
||||
* A merged interface of all of the various Angular compiler options, as well as the standard
|
||||
* `ts.CompilerOptions`.
|
||||
|
||||
@@ -309,6 +309,22 @@ export interface I18nOptions {
|
||||
*/
|
||||
i18nInLocale?: string;
|
||||
|
||||
/**
|
||||
* Export format (xlf, xlf2 or xmb) when the xi18n operation is requested.
|
||||
*/
|
||||
i18nOutFormat?: string;
|
||||
|
||||
/**
|
||||
* Path to the extracted message file to emit when the xi18n operation is requested.
|
||||
*/
|
||||
i18nOutFile?: string;
|
||||
|
||||
|
||||
/**
|
||||
* Locale of the application (used when xi18n is requested).
|
||||
*/
|
||||
i18nOutLocale?: string;
|
||||
|
||||
/**
|
||||
* Render `$localize` messages with legacy format ids.
|
||||
*
|
||||
@@ -340,6 +356,22 @@ export interface I18nOptions {
|
||||
i18nNormalizeLineEndingsInICUs?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options that specify compilation target.
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export interface TargetOptions {
|
||||
/**
|
||||
* Specifies the compilation mode to use. The following modes are available:
|
||||
* - 'full': generates fully AOT compiled code using Ivy instructions.
|
||||
* - 'partial': generates code in a stable, but intermediate form suitable for publication to NPM.
|
||||
*
|
||||
* The default value is 'full'.
|
||||
*/
|
||||
compilationMode?: 'full'|'partial';
|
||||
}
|
||||
|
||||
/**
|
||||
* Miscellaneous options that don't fall into any other category
|
||||
*
|
||||
|
||||
@@ -7,4 +7,4 @@
|
||||
*/
|
||||
|
||||
export * from './src/compiler';
|
||||
export {NgCompilerHost} from './src/host';
|
||||
export {NgCompilerHost} from './src/host';
|
||||
|
||||
@@ -6,24 +6,21 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Type} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, NoopReferencesRegistry, PipeDecoratorHandler, ReferencesRegistry} from '../../annotations';
|
||||
import {CycleAnalyzer, CycleHandlingStrategy, ImportGraph} from '../../cycles';
|
||||
import {COMPILER_ERRORS_WITH_GUIDES, ERROR_DETAILS_PAGE_BASE_URL, ErrorCode, ngErrorCode} from '../../diagnostics';
|
||||
import {checkForPrivateExports, ReferenceGraph} from '../../entry_point';
|
||||
import {LogicalFileSystem, resolve} from '../../file_system';
|
||||
import {absoluteFromSourceFile, AbsoluteFsPath, LogicalFileSystem, resolve} from '../../file_system';
|
||||
import {AbsoluteModuleStrategy, AliasingHost, AliasStrategy, DefaultImportTracker, ImportRewriter, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, NoopImportRewriter, PrivateExportAliasingHost, R3SymbolsImportRewriter, Reference, ReferenceEmitStrategy, ReferenceEmitter, RelativePathStrategy, UnifiedModulesAliasingHost, UnifiedModulesStrategy} from '../../imports';
|
||||
import {IncrementalBuildStrategy, IncrementalDriver} from '../../incremental';
|
||||
import {IncrementalBuildStrategy, IncrementalCompilation, IncrementalState} from '../../incremental';
|
||||
import {SemanticSymbol} from '../../incremental/semantic_graph';
|
||||
import {generateAnalysis, IndexedComponent, IndexingContext} from '../../indexer';
|
||||
import {ComponentResources, CompoundMetadataReader, CompoundMetadataRegistry, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry, MetadataReader, ResourceRegistry} from '../../metadata';
|
||||
import {ModuleWithProvidersScanner} from '../../modulewithproviders';
|
||||
import {ComponentResources, CompoundMetadataReader, CompoundMetadataRegistry, DirectiveMeta, DtsMetadataReader, InjectableClassRegistry, LocalMetadataRegistry, MetadataReader, PipeMeta, ResourceRegistry} from '../../metadata';
|
||||
import {PartialEvaluator} from '../../partial_evaluator';
|
||||
import {ActivePerfRecorder} from '../../perf';
|
||||
import {PerfCheckpoint, PerfEvent, PerfPhase} from '../../perf/src/api';
|
||||
import {DelegatingPerfRecorder} from '../../perf/src/recorder';
|
||||
import {ActivePerfRecorder, DelegatingPerfRecorder, PerfCheckpoint, PerfEvent, PerfPhase} from '../../perf';
|
||||
import {FileUpdate, ProgramDriver, UpdateMode} from '../../program_driver';
|
||||
import {DeclarationNode, isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
|
||||
import {AdapterResourceLoader} from '../../resource';
|
||||
import {entryPointKeyFor, NgModuleRouteAnalyzer} from '../../routing';
|
||||
@@ -32,8 +29,9 @@ import {generatedFactoryTransform} from '../../shims';
|
||||
import {ivySwitchTransform} from '../../switch';
|
||||
import {aliasTransformFactory, CompilationMode, declarationTransformFactory, DecoratorHandler, DtsTransformRegistry, ivyTransformFactory, TraitCompiler} from '../../transform';
|
||||
import {TemplateTypeCheckerImpl} from '../../typecheck';
|
||||
import {OptimizeFor, TemplateTypeChecker, TypeCheckingConfig, TypeCheckingProgramStrategy} from '../../typecheck/api';
|
||||
import {getSourceFileOrNull, isDtsPath, resolveModuleName} from '../../util/src/typescript';
|
||||
import {OptimizeFor, TemplateTypeChecker, TypeCheckingConfig} from '../../typecheck/api';
|
||||
import {getSourceFileOrNull, isDtsPath, resolveModuleName, toUnredirectedSourceFile} from '../../util/src/typescript';
|
||||
import {Xi18nContext} from '../../xi18n';
|
||||
import {LazyRoute, NgCompilerAdapter, NgCompilerOptions} from '../api';
|
||||
|
||||
import {compileUndecoratedClassesWithAngularFeatures} from './config';
|
||||
@@ -52,8 +50,6 @@ interface LazyCompilationState {
|
||||
exportReferenceGraph: ReferenceGraph|null;
|
||||
routeAnalyzer: NgModuleRouteAnalyzer;
|
||||
dtsTransforms: DtsTransformRegistry;
|
||||
mwpScanner: ModuleWithProvidersScanner;
|
||||
defaultImportTracker: DefaultImportTracker;
|
||||
aliasingHost: AliasingHost|null;
|
||||
refEmitter: ReferenceEmitter;
|
||||
templateTypeChecker: TemplateTypeChecker;
|
||||
@@ -78,7 +74,7 @@ export interface FreshCompilationTicket {
|
||||
kind: CompilationTicketKind.Fresh;
|
||||
options: NgCompilerOptions;
|
||||
incrementalBuildStrategy: IncrementalBuildStrategy;
|
||||
typeCheckingProgramStrategy: TypeCheckingProgramStrategy;
|
||||
programDriver: ProgramDriver;
|
||||
enableTemplateTypeChecker: boolean;
|
||||
usePoisonedData: boolean;
|
||||
tsProgram: ts.Program;
|
||||
@@ -91,11 +87,10 @@ export interface FreshCompilationTicket {
|
||||
export interface IncrementalTypeScriptCompilationTicket {
|
||||
kind: CompilationTicketKind.IncrementalTypeScript;
|
||||
options: NgCompilerOptions;
|
||||
oldProgram: ts.Program;
|
||||
newProgram: ts.Program;
|
||||
incrementalBuildStrategy: IncrementalBuildStrategy;
|
||||
typeCheckingProgramStrategy: TypeCheckingProgramStrategy;
|
||||
newDriver: IncrementalDriver;
|
||||
incrementalCompilation: IncrementalCompilation;
|
||||
programDriver: ProgramDriver;
|
||||
enableTemplateTypeChecker: boolean;
|
||||
usePoisonedData: boolean;
|
||||
perfRecorder: ActivePerfRecorder;
|
||||
@@ -123,15 +118,15 @@ export type CompilationTicket = FreshCompilationTicket|IncrementalTypeScriptComp
|
||||
*/
|
||||
export function freshCompilationTicket(
|
||||
tsProgram: ts.Program, options: NgCompilerOptions,
|
||||
incrementalBuildStrategy: IncrementalBuildStrategy,
|
||||
typeCheckingProgramStrategy: TypeCheckingProgramStrategy, perfRecorder: ActivePerfRecorder|null,
|
||||
enableTemplateTypeChecker: boolean, usePoisonedData: boolean): CompilationTicket {
|
||||
incrementalBuildStrategy: IncrementalBuildStrategy, programDriver: ProgramDriver,
|
||||
perfRecorder: ActivePerfRecorder|null, enableTemplateTypeChecker: boolean,
|
||||
usePoisonedData: boolean): CompilationTicket {
|
||||
return {
|
||||
kind: CompilationTicketKind.Fresh,
|
||||
tsProgram,
|
||||
options,
|
||||
incrementalBuildStrategy,
|
||||
typeCheckingProgramStrategy,
|
||||
programDriver,
|
||||
enableTemplateTypeChecker,
|
||||
usePoisonedData,
|
||||
perfRecorder: perfRecorder ?? ActivePerfRecorder.zeroedToNow(),
|
||||
@@ -144,25 +139,26 @@ export function freshCompilationTicket(
|
||||
*/
|
||||
export function incrementalFromCompilerTicket(
|
||||
oldCompiler: NgCompiler, newProgram: ts.Program,
|
||||
incrementalBuildStrategy: IncrementalBuildStrategy,
|
||||
typeCheckingProgramStrategy: TypeCheckingProgramStrategy, modifiedResourceFiles: Set<string>,
|
||||
incrementalBuildStrategy: IncrementalBuildStrategy, programDriver: ProgramDriver,
|
||||
modifiedResourceFiles: Set<AbsoluteFsPath>,
|
||||
perfRecorder: ActivePerfRecorder|null): CompilationTicket {
|
||||
const oldProgram = oldCompiler.getNextProgram();
|
||||
const oldDriver = oldCompiler.incrementalStrategy.getIncrementalDriver(oldProgram);
|
||||
if (oldDriver === null) {
|
||||
const oldProgram = oldCompiler.getCurrentProgram();
|
||||
const oldState = oldCompiler.incrementalStrategy.getIncrementalState(oldProgram);
|
||||
if (oldState === null) {
|
||||
// No incremental step is possible here, since no IncrementalDriver was found for the old
|
||||
// program.
|
||||
return freshCompilationTicket(
|
||||
newProgram, oldCompiler.options, incrementalBuildStrategy, typeCheckingProgramStrategy,
|
||||
perfRecorder, oldCompiler.enableTemplateTypeChecker, oldCompiler.usePoisonedData);
|
||||
newProgram, oldCompiler.options, incrementalBuildStrategy, programDriver, perfRecorder,
|
||||
oldCompiler.enableTemplateTypeChecker, oldCompiler.usePoisonedData);
|
||||
}
|
||||
|
||||
if (perfRecorder === null) {
|
||||
perfRecorder = ActivePerfRecorder.zeroedToNow();
|
||||
}
|
||||
|
||||
const newDriver = IncrementalDriver.reconcile(
|
||||
oldProgram, oldDriver, newProgram, modifiedResourceFiles, perfRecorder);
|
||||
const incrementalCompilation = IncrementalCompilation.incremental(
|
||||
newProgram, versionMapFromProgram(newProgram, programDriver), oldProgram, oldState,
|
||||
modifiedResourceFiles, perfRecorder);
|
||||
|
||||
return {
|
||||
kind: CompilationTicketKind.IncrementalTypeScript,
|
||||
@@ -170,9 +166,8 @@ export function incrementalFromCompilerTicket(
|
||||
usePoisonedData: oldCompiler.usePoisonedData,
|
||||
options: oldCompiler.options,
|
||||
incrementalBuildStrategy,
|
||||
typeCheckingProgramStrategy,
|
||||
newDriver,
|
||||
oldProgram,
|
||||
incrementalCompilation,
|
||||
programDriver,
|
||||
newProgram,
|
||||
perfRecorder,
|
||||
};
|
||||
@@ -182,26 +177,25 @@ export function incrementalFromCompilerTicket(
|
||||
* Create a `CompilationTicket` directly from an old `ts.Program` and associated Angular compilation
|
||||
* state, along with a new `ts.Program`.
|
||||
*/
|
||||
export function incrementalFromDriverTicket(
|
||||
oldProgram: ts.Program, oldDriver: IncrementalDriver, newProgram: ts.Program,
|
||||
export function incrementalFromStateTicket(
|
||||
oldProgram: ts.Program, oldState: IncrementalState, newProgram: ts.Program,
|
||||
options: NgCompilerOptions, incrementalBuildStrategy: IncrementalBuildStrategy,
|
||||
typeCheckingProgramStrategy: TypeCheckingProgramStrategy, modifiedResourceFiles: Set<string>,
|
||||
programDriver: ProgramDriver, modifiedResourceFiles: Set<AbsoluteFsPath>,
|
||||
perfRecorder: ActivePerfRecorder|null, enableTemplateTypeChecker: boolean,
|
||||
usePoisonedData: boolean): CompilationTicket {
|
||||
if (perfRecorder === null) {
|
||||
perfRecorder = ActivePerfRecorder.zeroedToNow();
|
||||
}
|
||||
|
||||
const newDriver = IncrementalDriver.reconcile(
|
||||
oldProgram, oldDriver, newProgram, modifiedResourceFiles, perfRecorder);
|
||||
const incrementalCompilation = IncrementalCompilation.incremental(
|
||||
newProgram, versionMapFromProgram(newProgram, programDriver), oldProgram, oldState,
|
||||
modifiedResourceFiles, perfRecorder);
|
||||
return {
|
||||
kind: CompilationTicketKind.IncrementalTypeScript,
|
||||
oldProgram,
|
||||
newProgram,
|
||||
options,
|
||||
incrementalBuildStrategy,
|
||||
newDriver,
|
||||
typeCheckingProgramStrategy,
|
||||
incrementalCompilation,
|
||||
programDriver,
|
||||
enableTemplateTypeChecker,
|
||||
usePoisonedData,
|
||||
perfRecorder,
|
||||
@@ -255,7 +249,7 @@ export class NgCompiler {
|
||||
private nonTemplateDiagnostics: ts.Diagnostic[]|null = null;
|
||||
|
||||
private closureCompilerEnabled: boolean;
|
||||
private nextProgram: ts.Program;
|
||||
private currentProgram: ts.Program;
|
||||
private entryPoint: ts.SourceFile|null;
|
||||
private moduleResolver: ModuleResolver;
|
||||
private resourceManager: AdapterResourceLoader;
|
||||
@@ -286,9 +280,10 @@ export class NgCompiler {
|
||||
adapter,
|
||||
ticket.options,
|
||||
ticket.tsProgram,
|
||||
ticket.typeCheckingProgramStrategy,
|
||||
ticket.programDriver,
|
||||
ticket.incrementalBuildStrategy,
|
||||
IncrementalDriver.fresh(ticket.tsProgram),
|
||||
IncrementalCompilation.fresh(
|
||||
ticket.tsProgram, versionMapFromProgram(ticket.tsProgram, ticket.programDriver)),
|
||||
ticket.enableTemplateTypeChecker,
|
||||
ticket.usePoisonedData,
|
||||
ticket.perfRecorder,
|
||||
@@ -298,9 +293,9 @@ export class NgCompiler {
|
||||
adapter,
|
||||
ticket.options,
|
||||
ticket.newProgram,
|
||||
ticket.typeCheckingProgramStrategy,
|
||||
ticket.programDriver,
|
||||
ticket.incrementalBuildStrategy,
|
||||
ticket.newDriver,
|
||||
ticket.incrementalCompilation,
|
||||
ticket.enableTemplateTypeChecker,
|
||||
ticket.usePoisonedData,
|
||||
ticket.perfRecorder,
|
||||
@@ -315,10 +310,10 @@ export class NgCompiler {
|
||||
private constructor(
|
||||
private adapter: NgCompilerAdapter,
|
||||
readonly options: NgCompilerOptions,
|
||||
private tsProgram: ts.Program,
|
||||
readonly typeCheckingProgramStrategy: TypeCheckingProgramStrategy,
|
||||
private inputProgram: ts.Program,
|
||||
readonly programDriver: ProgramDriver,
|
||||
readonly incrementalStrategy: IncrementalBuildStrategy,
|
||||
readonly incrementalDriver: IncrementalDriver,
|
||||
readonly incrementalCompilation: IncrementalCompilation,
|
||||
readonly enableTemplateTypeChecker: boolean,
|
||||
readonly usePoisonedData: boolean,
|
||||
private livePerfRecorder: ActivePerfRecorder,
|
||||
@@ -329,11 +324,11 @@ export class NgCompiler {
|
||||
this.constructionDiagnostics.push(incompatibleTypeCheckOptionsDiagnostic);
|
||||
}
|
||||
|
||||
this.nextProgram = tsProgram;
|
||||
this.currentProgram = inputProgram;
|
||||
this.closureCompilerEnabled = !!this.options.annotateForClosureCompiler;
|
||||
|
||||
this.entryPoint =
|
||||
adapter.entryPoint !== null ? getSourceFileOrNull(tsProgram, adapter.entryPoint) : null;
|
||||
adapter.entryPoint !== null ? getSourceFileOrNull(inputProgram, adapter.entryPoint) : null;
|
||||
|
||||
const moduleResolutionCache = ts.createModuleResolutionCache(
|
||||
this.adapter.getCurrentDirectory(),
|
||||
@@ -343,19 +338,19 @@ export class NgCompiler {
|
||||
// way into all kinds of places inside TS internal objects.
|
||||
this.adapter.getCanonicalFileName.bind(this.adapter));
|
||||
this.moduleResolver =
|
||||
new ModuleResolver(tsProgram, this.options, this.adapter, moduleResolutionCache);
|
||||
new ModuleResolver(inputProgram, this.options, this.adapter, moduleResolutionCache);
|
||||
this.resourceManager = new AdapterResourceLoader(adapter, this.options);
|
||||
this.cycleAnalyzer =
|
||||
new CycleAnalyzer(new ImportGraph(tsProgram.getTypeChecker(), this.delegatingPerfRecorder));
|
||||
this.incrementalStrategy.setIncrementalDriver(this.incrementalDriver, tsProgram);
|
||||
this.cycleAnalyzer = new CycleAnalyzer(
|
||||
new ImportGraph(inputProgram.getTypeChecker(), this.delegatingPerfRecorder));
|
||||
this.incrementalStrategy.setIncrementalState(this.incrementalCompilation.state, inputProgram);
|
||||
|
||||
this.ignoreForDiagnostics =
|
||||
new Set(tsProgram.getSourceFiles().filter(sf => this.adapter.isShim(sf)));
|
||||
new Set(inputProgram.getSourceFiles().filter(sf => this.adapter.isShim(sf)));
|
||||
this.ignoreForEmit = this.adapter.ignoreForEmit;
|
||||
|
||||
let dtsFileCount = 0;
|
||||
let nonDtsFileCount = 0;
|
||||
for (const sf of tsProgram.getSourceFiles()) {
|
||||
for (const sf of inputProgram.getSourceFiles()) {
|
||||
if (sf.isDeclarationFile) {
|
||||
dtsFileCount++;
|
||||
} else {
|
||||
@@ -371,6 +366,16 @@ export class NgCompiler {
|
||||
return this.livePerfRecorder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes the `IncrementalCompilation` under an old property name that the CLI uses, avoiding a
|
||||
* chicken-and-egg problem with the rename to `incrementalCompilation`.
|
||||
*
|
||||
* TODO(alxhub): remove when the CLI uses the new name.
|
||||
*/
|
||||
get incrementalDriver(): IncrementalCompilation {
|
||||
return this.incrementalCompilation;
|
||||
}
|
||||
|
||||
private updateWithChangedResources(
|
||||
changedResources: Set<string>, perfRecorder: ActivePerfRecorder): void {
|
||||
this.livePerfRecorder = perfRecorder;
|
||||
@@ -415,7 +420,7 @@ export class NgCompiler {
|
||||
getResourceDependencies(file: ts.SourceFile): string[] {
|
||||
this.ensureAnalyzed();
|
||||
|
||||
return this.incrementalDriver.depGraph.getResourceDependencies(file);
|
||||
return this.incrementalCompilation.depGraph.getResourceDependencies(file);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -462,16 +467,22 @@ export class NgCompiler {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the `ts.Program` to use as a starting point when spawning a subsequent incremental
|
||||
* compilation.
|
||||
* Get the current `ts.Program` known to this `NgCompiler`.
|
||||
*
|
||||
* The `NgCompiler` spawns an internal incremental TypeScript compilation (inheriting the
|
||||
* consumer's `ts.Program` into a new one for the purposes of template type-checking). After this
|
||||
* operation, the consumer's `ts.Program` is no longer usable for starting a new incremental
|
||||
* compilation. `getNextProgram` retrieves the `ts.Program` which can be used instead.
|
||||
* Compilation begins with an input `ts.Program`, and during template type-checking operations new
|
||||
* `ts.Program`s may be produced using the `ProgramDriver`. The most recent such `ts.Program` to
|
||||
* be produced is available here.
|
||||
*
|
||||
* This `ts.Program` serves two key purposes:
|
||||
*
|
||||
* * As an incremental starting point for creating the next `ts.Program` based on files that the
|
||||
* user has changed (for clients using the TS compiler program APIs).
|
||||
*
|
||||
* * As the "before" point for an incremental compilation invocation, to determine what's changed
|
||||
* between the old and new programs (for all compilations).
|
||||
*/
|
||||
getNextProgram(): ts.Program {
|
||||
return this.nextProgram;
|
||||
getCurrentProgram(): ts.Program {
|
||||
return this.currentProgram;
|
||||
}
|
||||
|
||||
getTemplateTypeChecker(): TemplateTypeChecker {
|
||||
@@ -515,6 +526,19 @@ export class NgCompiler {
|
||||
return {styles, template};
|
||||
}
|
||||
|
||||
getMeta(classDecl: DeclarationNode): PipeMeta|DirectiveMeta|null {
|
||||
if (!isNamedClassDeclaration(classDecl)) {
|
||||
return null;
|
||||
}
|
||||
const ref = new Reference(classDecl);
|
||||
const {metaReader} = this.ensureAnalyzed();
|
||||
const meta = metaReader.getPipeMetadata(ref) ?? metaReader.getDirectiveMetadata(ref);
|
||||
if (meta === null) {
|
||||
return null;
|
||||
}
|
||||
return meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform Angular's analysis step (as a precursor to `getDiagnostics` or `prepareEmit`)
|
||||
* asynchronously.
|
||||
@@ -533,13 +557,12 @@ export class NgCompiler {
|
||||
this.compilation = this.makeCompilation();
|
||||
|
||||
const promises: Promise<void>[] = [];
|
||||
for (const sf of this.tsProgram.getSourceFiles()) {
|
||||
for (const sf of this.inputProgram.getSourceFiles()) {
|
||||
if (sf.isDeclarationFile) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let analysisPromise = this.compilation.traitCompiler.analyzeAsync(sf);
|
||||
this.scanForMwp(sf);
|
||||
if (analysisPromise !== undefined) {
|
||||
promises.push(analysisPromise);
|
||||
}
|
||||
@@ -579,7 +602,7 @@ export class NgCompiler {
|
||||
//
|
||||
// In all cases above, the `containingFile` argument is ignored, so we can just take the first
|
||||
// of the root files.
|
||||
const containingFile = this.tsProgram.getRootFileNames()[0];
|
||||
const containingFile = this.inputProgram.getRootFileNames()[0];
|
||||
const [entryPath, moduleName] = entryRoute.split('#');
|
||||
const resolvedModule =
|
||||
resolveModuleName(entryPath, containingFile, this.options, this.adapter, null);
|
||||
@@ -602,7 +625,7 @@ export class NgCompiler {
|
||||
} {
|
||||
const compilation = this.ensureAnalyzed();
|
||||
|
||||
const coreImportsFrom = compilation.isCore ? getR3SymbolsFile(this.tsProgram) : null;
|
||||
const coreImportsFrom = compilation.isCore ? getR3SymbolsFile(this.inputProgram) : null;
|
||||
let importRewriter: ImportRewriter;
|
||||
if (coreImportsFrom !== null) {
|
||||
importRewriter = new R3SymbolsImportRewriter(coreImportsFrom.fileName);
|
||||
@@ -610,13 +633,14 @@ export class NgCompiler {
|
||||
importRewriter = new NoopImportRewriter();
|
||||
}
|
||||
|
||||
const defaultImportTracker = new DefaultImportTracker();
|
||||
|
||||
const before = [
|
||||
ivyTransformFactory(
|
||||
compilation.traitCompiler, compilation.reflector, importRewriter,
|
||||
compilation.defaultImportTracker, this.delegatingPerfRecorder, compilation.isCore,
|
||||
this.closureCompilerEnabled),
|
||||
compilation.traitCompiler, compilation.reflector, importRewriter, defaultImportTracker,
|
||||
this.delegatingPerfRecorder, compilation.isCore, this.closureCompilerEnabled),
|
||||
aliasTransformFactory(compilation.traitCompiler.exportStatements),
|
||||
compilation.defaultImportTracker.importPreservingTransformer(),
|
||||
defaultImportTracker.importPreservingTransformer(),
|
||||
];
|
||||
|
||||
const afterDeclarations: ts.TransformerFactory<ts.SourceFile>[] = [];
|
||||
@@ -651,6 +675,16 @@ export class NgCompiler {
|
||||
return generateAnalysis(context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collect i18n messages into the `Xi18nContext`.
|
||||
*/
|
||||
xi18n(ctx: Xi18nContext): void {
|
||||
// Note that the 'resolve' phase is not strictly necessary for xi18n, but this is not currently
|
||||
// optimized.
|
||||
const compilation = this.ensureAnalyzed();
|
||||
compilation.traitCompiler.xi18n(ctx);
|
||||
}
|
||||
|
||||
private ensureAnalyzed(this: NgCompiler): LazyCompilationState {
|
||||
if (this.compilation === null) {
|
||||
this.analyzeSync();
|
||||
@@ -661,12 +695,11 @@ export class NgCompiler {
|
||||
private analyzeSync(): void {
|
||||
this.perfRecorder.inPhase(PerfPhase.Analysis, () => {
|
||||
this.compilation = this.makeCompilation();
|
||||
for (const sf of this.tsProgram.getSourceFiles()) {
|
||||
for (const sf of this.inputProgram.getSourceFiles()) {
|
||||
if (sf.isDeclarationFile) {
|
||||
continue;
|
||||
}
|
||||
this.compilation.traitCompiler.analyzeSync(sf);
|
||||
this.scanForMwp(sf);
|
||||
}
|
||||
|
||||
this.perfRecorder.memory(PerfCheckpoint.Analysis);
|
||||
@@ -681,7 +714,7 @@ export class NgCompiler {
|
||||
|
||||
// At this point, analysis is complete and the compiler can now calculate which files need to
|
||||
// be emitted, so do that.
|
||||
this.incrementalDriver.recordSuccessfulAnalysis(traitCompiler);
|
||||
this.incrementalCompilation.recordSuccessfulAnalysis(traitCompiler);
|
||||
|
||||
this.perfRecorder.memory(PerfCheckpoint.Resolve);
|
||||
});
|
||||
@@ -703,7 +736,7 @@ export class NgCompiler {
|
||||
// is not disabled when `strictTemplates` is enabled.
|
||||
const strictTemplates = !!this.options.strictTemplates;
|
||||
|
||||
const useInlineTypeConstructors = this.typeCheckingProgramStrategy.supportsInlineOperations;
|
||||
const useInlineTypeConstructors = this.programDriver.supportsInlineOperations;
|
||||
|
||||
// First select a type-checking configuration, based on whether full template type-checking is
|
||||
// requested.
|
||||
@@ -816,7 +849,7 @@ export class NgCompiler {
|
||||
|
||||
// Get the diagnostics.
|
||||
const diagnostics: ts.Diagnostic[] = [];
|
||||
for (const sf of this.tsProgram.getSourceFiles()) {
|
||||
for (const sf of this.inputProgram.getSourceFiles()) {
|
||||
if (sf.isDeclarationFile || this.adapter.isShim(sf)) {
|
||||
continue;
|
||||
}
|
||||
@@ -825,9 +858,9 @@ export class NgCompiler {
|
||||
...compilation.templateTypeChecker.getDiagnosticsForFile(sf, OptimizeFor.WholeProgram));
|
||||
}
|
||||
|
||||
const program = this.typeCheckingProgramStrategy.getProgram();
|
||||
this.incrementalStrategy.setIncrementalDriver(this.incrementalDriver, program);
|
||||
this.nextProgram = program;
|
||||
const program = this.programDriver.getProgram();
|
||||
this.incrementalStrategy.setIncrementalState(this.incrementalCompilation.state, program);
|
||||
this.currentProgram = program;
|
||||
|
||||
return diagnostics;
|
||||
}
|
||||
@@ -842,9 +875,9 @@ export class NgCompiler {
|
||||
diagnostics.push(...compilation.templateTypeChecker.getDiagnosticsForFile(sf, optimizeFor));
|
||||
}
|
||||
|
||||
const program = this.typeCheckingProgramStrategy.getProgram();
|
||||
this.incrementalStrategy.setIncrementalDriver(this.incrementalDriver, program);
|
||||
this.nextProgram = program;
|
||||
const program = this.programDriver.getProgram();
|
||||
this.incrementalStrategy.setIncrementalState(this.incrementalCompilation.state, program);
|
||||
this.currentProgram = program;
|
||||
|
||||
return diagnostics;
|
||||
}
|
||||
@@ -855,24 +888,14 @@ export class NgCompiler {
|
||||
this.nonTemplateDiagnostics = [...compilation.traitCompiler.diagnostics];
|
||||
if (this.entryPoint !== null && compilation.exportReferenceGraph !== null) {
|
||||
this.nonTemplateDiagnostics.push(...checkForPrivateExports(
|
||||
this.entryPoint, this.tsProgram.getTypeChecker(), compilation.exportReferenceGraph));
|
||||
this.entryPoint, this.inputProgram.getTypeChecker(), compilation.exportReferenceGraph));
|
||||
}
|
||||
}
|
||||
return this.nonTemplateDiagnostics;
|
||||
}
|
||||
|
||||
private scanForMwp(sf: ts.SourceFile): void {
|
||||
this.compilation!.mwpScanner.scan(sf, {
|
||||
addTypeReplacement: (node: ts.Declaration, type: Type): void => {
|
||||
// Only obtain the return type transform for the source file once there's a type to replace,
|
||||
// so that no transform is allocated when there's nothing to do.
|
||||
this.compilation!.dtsTransforms!.getReturnTypeTransform(sf).addTypeReplacement(node, type);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private makeCompilation(): LazyCompilationState {
|
||||
const checker = this.tsProgram.getTypeChecker();
|
||||
const checker = this.inputProgram.getTypeChecker();
|
||||
|
||||
const reflector = new TypeScriptReflectionHost(checker);
|
||||
|
||||
@@ -904,7 +927,7 @@ export class NgCompiler {
|
||||
// First, try to use local identifiers if available.
|
||||
new LocalIdentifierStrategy(),
|
||||
// Next, attempt to use an absolute import.
|
||||
new AbsoluteModuleStrategy(this.tsProgram, checker, this.moduleResolver, reflector),
|
||||
new AbsoluteModuleStrategy(this.inputProgram, checker, this.moduleResolver, reflector),
|
||||
// Finally, check if the reference is being written into a file within the project's .ts
|
||||
// sources, and use a relative import if so. If this fails, ReferenceEmitter will throw
|
||||
// an error.
|
||||
@@ -932,7 +955,8 @@ export class NgCompiler {
|
||||
aliasingHost = new UnifiedModulesAliasingHost(this.adapter.unifiedModulesHost);
|
||||
}
|
||||
|
||||
const evaluator = new PartialEvaluator(reflector, checker, this.incrementalDriver.depGraph);
|
||||
const evaluator =
|
||||
new PartialEvaluator(reflector, checker, this.incrementalCompilation.depGraph);
|
||||
const dtsReader = new DtsMetadataReader(checker, reflector);
|
||||
const localMetaRegistry = new LocalMetadataRegistry();
|
||||
const localMetaReader: MetadataReader = localMetaRegistry;
|
||||
@@ -940,7 +964,7 @@ export class NgCompiler {
|
||||
const scopeRegistry =
|
||||
new LocalModuleScopeRegistry(localMetaReader, depScopeReader, refEmitter, aliasingHost);
|
||||
const scopeReader: ComponentScopeReader = scopeRegistry;
|
||||
const semanticDepGraphUpdater = this.incrementalDriver.getSemanticDepGraphUpdater();
|
||||
const semanticDepGraphUpdater = this.incrementalCompilation.semanticDepGraphUpdater;
|
||||
const metaRegistry = new CompoundMetadataRegistry([localMetaRegistry, scopeRegistry]);
|
||||
const injectableRegistry = new InjectableClassRegistry(reflector);
|
||||
|
||||
@@ -964,11 +988,8 @@ export class NgCompiler {
|
||||
|
||||
const dtsTransforms = new DtsTransformRegistry();
|
||||
|
||||
const mwpScanner = new ModuleWithProvidersScanner(reflector, evaluator, refEmitter);
|
||||
const isCore = isAngularCorePackage(this.inputProgram);
|
||||
|
||||
const isCore = isAngularCorePackage(this.tsProgram);
|
||||
|
||||
const defaultImportTracker = new DefaultImportTracker();
|
||||
const resourceRegistry = new ResourceRegistry();
|
||||
|
||||
const compilationMode =
|
||||
@@ -990,7 +1011,7 @@ export class NgCompiler {
|
||||
this.options.i18nUseExternalIds !== false,
|
||||
this.options.enableI18nLegacyMessageIdFormat !== false, this.usePoisonedData,
|
||||
this.options.i18nNormalizeLineEndingsInICUs, this.moduleResolver, this.cycleAnalyzer,
|
||||
cycleHandlingStrategy, refEmitter, defaultImportTracker, this.incrementalDriver.depGraph,
|
||||
cycleHandlingStrategy, refEmitter, this.incrementalCompilation.depGraph,
|
||||
injectableRegistry, semanticDepGraphUpdater, this.closureCompilerEnabled,
|
||||
this.delegatingPerfRecorder),
|
||||
|
||||
@@ -999,7 +1020,7 @@ export class NgCompiler {
|
||||
// clang-format off
|
||||
new DirectiveDecoratorHandler(
|
||||
reflector, evaluator, metaRegistry, scopeRegistry, metaReader,
|
||||
defaultImportTracker, injectableRegistry, isCore, semanticDepGraphUpdater,
|
||||
injectableRegistry, isCore, semanticDepGraphUpdater,
|
||||
this.closureCompilerEnabled, compileUndecoratedClassesWithAngularFeatures,
|
||||
this.delegatingPerfRecorder,
|
||||
) as Readonly<DecoratorHandler<unknown, unknown, SemanticSymbol | null,unknown>>,
|
||||
@@ -1007,27 +1028,34 @@ export class NgCompiler {
|
||||
// Pipe handler must be before injectable handler in list so pipe factories are printed
|
||||
// before injectable factories (so injectable factories can delegate to them)
|
||||
new PipeDecoratorHandler(
|
||||
reflector, evaluator, metaRegistry, scopeRegistry, defaultImportTracker,
|
||||
injectableRegistry, isCore, this.delegatingPerfRecorder),
|
||||
reflector, evaluator, metaRegistry, scopeRegistry, injectableRegistry, isCore,
|
||||
this.delegatingPerfRecorder),
|
||||
new InjectableDecoratorHandler(
|
||||
reflector, defaultImportTracker, isCore, this.options.strictInjectionParameters || false,
|
||||
injectableRegistry, this.delegatingPerfRecorder),
|
||||
reflector, isCore, this.options.strictInjectionParameters || false, injectableRegistry,
|
||||
this.delegatingPerfRecorder),
|
||||
new NgModuleDecoratorHandler(
|
||||
reflector, evaluator, metaReader, metaRegistry, scopeRegistry, referencesRegistry, isCore,
|
||||
routeAnalyzer, refEmitter, this.adapter.factoryTracker, defaultImportTracker,
|
||||
this.closureCompilerEnabled, injectableRegistry, this.delegatingPerfRecorder,
|
||||
this.options.i18nInLocale),
|
||||
routeAnalyzer, refEmitter, this.adapter.factoryTracker, this.closureCompilerEnabled,
|
||||
injectableRegistry, this.delegatingPerfRecorder, this.options.i18nInLocale),
|
||||
];
|
||||
|
||||
const traitCompiler = new TraitCompiler(
|
||||
handlers, reflector, this.delegatingPerfRecorder, this.incrementalDriver,
|
||||
handlers, reflector, this.delegatingPerfRecorder, this.incrementalCompilation,
|
||||
this.options.compileNonExportedClasses !== false, compilationMode, dtsTransforms,
|
||||
semanticDepGraphUpdater);
|
||||
|
||||
// Template type-checking may use the `ProgramDriver` to produce new `ts.Program`(s). If this
|
||||
// happens, they need to be tracked by the `NgCompiler`.
|
||||
const notifyingDriver =
|
||||
new NotifyingProgramDriverWrapper(this.programDriver, (program: ts.Program) => {
|
||||
this.incrementalStrategy.setIncrementalState(this.incrementalCompilation.state, program);
|
||||
this.currentProgram = program;
|
||||
});
|
||||
|
||||
const templateTypeChecker = new TemplateTypeCheckerImpl(
|
||||
this.tsProgram, this.typeCheckingProgramStrategy, traitCompiler,
|
||||
this.getTypeCheckingConfig(), refEmitter, reflector, this.adapter, this.incrementalDriver,
|
||||
scopeRegistry, typeCheckScopeRegistry, this.delegatingPerfRecorder);
|
||||
this.inputProgram, notifyingDriver, traitCompiler, this.getTypeCheckingConfig(), refEmitter,
|
||||
reflector, this.adapter, this.incrementalCompilation, scopeRegistry, typeCheckScopeRegistry,
|
||||
this.delegatingPerfRecorder);
|
||||
|
||||
return {
|
||||
isCore,
|
||||
@@ -1037,10 +1065,8 @@ export class NgCompiler {
|
||||
dtsTransforms,
|
||||
exportReferenceGraph,
|
||||
routeAnalyzer,
|
||||
mwpScanner,
|
||||
metaReader,
|
||||
typeCheckScopeRegistry,
|
||||
defaultImportTracker,
|
||||
aliasingHost,
|
||||
refEmitter,
|
||||
templateTypeChecker,
|
||||
@@ -1141,3 +1167,37 @@ class ReferenceGraphAdapter implements ReferencesRegistry {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class NotifyingProgramDriverWrapper implements ProgramDriver {
|
||||
constructor(
|
||||
private delegate: ProgramDriver, private notifyNewProgram: (program: ts.Program) => void) {}
|
||||
|
||||
get supportsInlineOperations() {
|
||||
return this.delegate.supportsInlineOperations;
|
||||
}
|
||||
|
||||
getProgram(): ts.Program {
|
||||
return this.delegate.getProgram();
|
||||
}
|
||||
|
||||
updateFiles(contents: Map<AbsoluteFsPath, FileUpdate>, updateMode: UpdateMode): void {
|
||||
this.delegate.updateFiles(contents, updateMode);
|
||||
this.notifyNewProgram(this.delegate.getProgram());
|
||||
}
|
||||
|
||||
getSourceFileVersion = this.delegate.getSourceFileVersion?.bind(this);
|
||||
}
|
||||
|
||||
function versionMapFromProgram(
|
||||
program: ts.Program, driver: ProgramDriver): Map<AbsoluteFsPath, string>|null {
|
||||
if (driver.getSourceFileVersion === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const versions = new Map<AbsoluteFsPath, string>();
|
||||
for (const possiblyRedirectedSourceFile of program.getSourceFiles()) {
|
||||
const sf = toUnredirectedSourceFile(possiblyRedirectedSourceFile);
|
||||
versions.set(absoluteFromSourceFile(sf), driver.getSourceFileVersion(sf));
|
||||
}
|
||||
return versions;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ ts_library(
|
||||
"//packages/compiler-cli/src/ngtsc/file_system",
|
||||
"//packages/compiler-cli/src/ngtsc/file_system/testing",
|
||||
"//packages/compiler-cli/src/ngtsc/incremental",
|
||||
"//packages/compiler-cli/src/ngtsc/program_driver",
|
||||
"//packages/compiler-cli/src/ngtsc/reflection",
|
||||
"//packages/compiler-cli/src/ngtsc/typecheck",
|
||||
"//packages/compiler-cli/src/ngtsc/typecheck/api",
|
||||
|
||||
@@ -11,9 +11,9 @@ import * as ts from 'typescript';
|
||||
import {absoluteFrom as _, FileSystem, getFileSystem, getSourceFileOrError, NgtscCompilerHost, setFileSystem} from '../../file_system';
|
||||
import {runInEachFileSystem} from '../../file_system/testing';
|
||||
import {IncrementalBuildStrategy, NoopIncrementalBuildStrategy} from '../../incremental';
|
||||
import {ProgramDriver, TsCreateProgramDriver} from '../../program_driver';
|
||||
import {ClassDeclaration, isNamedClassDeclaration} from '../../reflection';
|
||||
import {ReusedProgramStrategy} from '../../typecheck';
|
||||
import {OptimizeFor, TypeCheckingProgramStrategy} from '../../typecheck/api';
|
||||
import {OptimizeFor} from '../../typecheck/api';
|
||||
|
||||
import {NgCompilerOptions} from '../api';
|
||||
|
||||
@@ -22,7 +22,7 @@ import {NgCompilerHost} from '../src/host';
|
||||
|
||||
function makeFreshCompiler(
|
||||
host: NgCompilerHost, options: NgCompilerOptions, program: ts.Program,
|
||||
programStrategy: TypeCheckingProgramStrategy, incrementalStrategy: IncrementalBuildStrategy,
|
||||
programStrategy: ProgramDriver, incrementalStrategy: IncrementalBuildStrategy,
|
||||
enableTemplateTypeChecker: boolean, usePoisonedData: boolean): NgCompiler {
|
||||
const ticket = freshCompilationTicket(
|
||||
program, options, incrementalStrategy, programStrategy, /* perfRecorder */ null,
|
||||
@@ -61,7 +61,7 @@ runInEachFileSystem(() => {
|
||||
const host = NgCompilerHost.wrap(baseHost, [COMPONENT], options, /* oldProgram */ null);
|
||||
const program = ts.createProgram({host, options, rootNames: host.inputFiles});
|
||||
const compiler = makeFreshCompiler(
|
||||
host, options, program, new ReusedProgramStrategy(program, host, options, []),
|
||||
host, options, program, new TsCreateProgramDriver(program, host, options, []),
|
||||
new NoopIncrementalBuildStrategy(), /** enableTemplateTypeChecker */ false,
|
||||
/* usePoisonedData */ false);
|
||||
|
||||
@@ -113,7 +113,7 @@ runInEachFileSystem(() => {
|
||||
const CmpC = getClass(getSourceFileOrError(program, cmpCFile), 'CmpC');
|
||||
|
||||
const compiler = makeFreshCompiler(
|
||||
host, options, program, new ReusedProgramStrategy(program, host, options, []),
|
||||
host, options, program, new TsCreateProgramDriver(program, host, options, []),
|
||||
new NoopIncrementalBuildStrategy(), /** enableTemplateTypeChecker */ false,
|
||||
/* usePoisonedData */ false);
|
||||
const components = compiler.getComponentsWithTemplateFile(templateFile);
|
||||
@@ -165,7 +165,7 @@ runInEachFileSystem(() => {
|
||||
const CmpA = getClass(getSourceFileOrError(program, cmpAFile), 'CmpA');
|
||||
const CmpC = getClass(getSourceFileOrError(program, cmpCFile), 'CmpC');
|
||||
const compiler = makeFreshCompiler(
|
||||
host, options, program, new ReusedProgramStrategy(program, host, options, []),
|
||||
host, options, program, new TsCreateProgramDriver(program, host, options, []),
|
||||
new NoopIncrementalBuildStrategy(), /** enableTemplateTypeChecker */ false,
|
||||
/* usePoisonedData */ false);
|
||||
const components = compiler.getComponentsWithStyleFile(styleFile);
|
||||
@@ -199,7 +199,7 @@ runInEachFileSystem(() => {
|
||||
const program = ts.createProgram({host, options, rootNames: host.inputFiles});
|
||||
const CmpA = getClass(getSourceFileOrError(program, cmpAFile), 'CmpA');
|
||||
const compiler = makeFreshCompiler(
|
||||
host, options, program, new ReusedProgramStrategy(program, host, options, []),
|
||||
host, options, program, new TsCreateProgramDriver(program, host, options, []),
|
||||
new NoopIncrementalBuildStrategy(), /** enableTemplateTypeChecker */ false,
|
||||
/* usePoisonedData */ false);
|
||||
const resources = compiler.getComponentResources(CmpA);
|
||||
@@ -235,7 +235,7 @@ runInEachFileSystem(() => {
|
||||
const program = ts.createProgram({host, options, rootNames: host.inputFiles});
|
||||
const CmpA = getClass(getSourceFileOrError(program, cmpAFile), 'CmpA');
|
||||
const compiler = makeFreshCompiler(
|
||||
host, options, program, new ReusedProgramStrategy(program, host, options, []),
|
||||
host, options, program, new TsCreateProgramDriver(program, host, options, []),
|
||||
new NoopIncrementalBuildStrategy(), /** enableTemplateTypeChecker */ false,
|
||||
/* usePoisonedData */ false);
|
||||
const resources = compiler.getComponentResources(CmpA);
|
||||
@@ -267,7 +267,7 @@ runInEachFileSystem(() => {
|
||||
const host = NgCompilerHost.wrap(baseHost, [COMPONENT], options, /* oldProgram */ null);
|
||||
const program = ts.createProgram({host, options, rootNames: host.inputFiles});
|
||||
const compiler = makeFreshCompiler(
|
||||
host, options, program, new ReusedProgramStrategy(program, host, options, []),
|
||||
host, options, program, new TsCreateProgramDriver(program, host, options, []),
|
||||
new NoopIncrementalBuildStrategy(), /** enableTemplateTypeChecker */ false,
|
||||
/* usePoisonedData */ false);
|
||||
|
||||
@@ -301,7 +301,7 @@ runInEachFileSystem(() => {
|
||||
const host = NgCompilerHost.wrap(baseHost, [COMPONENT], options, /* oldProgram */ null);
|
||||
const program = ts.createProgram({host, options, rootNames: host.inputFiles});
|
||||
const compilerA = makeFreshCompiler(
|
||||
host, options, program, new ReusedProgramStrategy(program, host, options, []),
|
||||
host, options, program, new TsCreateProgramDriver(program, host, options, []),
|
||||
new NoopIncrementalBuildStrategy(), /** enableTemplateTypeChecker */ false,
|
||||
/* usePoisonedData */ false);
|
||||
|
||||
|
||||
@@ -14,6 +14,14 @@ import {ImportGraph} from './imports';
|
||||
* Analyzes a `ts.Program` for cycles.
|
||||
*/
|
||||
export class CycleAnalyzer {
|
||||
/**
|
||||
* Cycle detection is requested with the same `from` source file for all used directives and pipes
|
||||
* within a component, which makes it beneficial to cache the results as long as the `from` source
|
||||
* file has not changed. This avoids visiting the import graph that is reachable from multiple
|
||||
* directives/pipes more than once.
|
||||
*/
|
||||
private cachedResults: CycleResults|null = null;
|
||||
|
||||
constructor(private importGraph: ImportGraph) {}
|
||||
|
||||
/**
|
||||
@@ -24,10 +32,13 @@ export class CycleAnalyzer {
|
||||
* otherwise.
|
||||
*/
|
||||
wouldCreateCycle(from: ts.SourceFile, to: ts.SourceFile): Cycle|null {
|
||||
// Try to reuse the cached results as long as the `from` source file is the same.
|
||||
if (this.cachedResults === null || this.cachedResults.from !== from) {
|
||||
this.cachedResults = new CycleResults(from, this.importGraph);
|
||||
}
|
||||
|
||||
// Import of 'from' -> 'to' is illegal if an edge 'to' -> 'from' already exists.
|
||||
return this.importGraph.transitiveImportsOf(to).has(from) ?
|
||||
new Cycle(this.importGraph, from, to) :
|
||||
null;
|
||||
return this.cachedResults.wouldBeCyclic(to) ? new Cycle(this.importGraph, from, to) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,10 +48,83 @@ export class CycleAnalyzer {
|
||||
* import graph for cycle creation.
|
||||
*/
|
||||
recordSyntheticImport(from: ts.SourceFile, to: ts.SourceFile): void {
|
||||
this.cachedResults = null;
|
||||
this.importGraph.addSyntheticImport(from, to);
|
||||
}
|
||||
}
|
||||
|
||||
const NgCyclicResult = Symbol('NgCyclicResult');
|
||||
type CyclicResultMarker = {
|
||||
__brand: 'CyclicResultMarker';
|
||||
};
|
||||
type CyclicSourceFile = ts.SourceFile&{[NgCyclicResult]?: CyclicResultMarker};
|
||||
|
||||
/**
|
||||
* Stores the results of cycle detection in a memory efficient manner. A symbol is attached to
|
||||
* source files that indicate what the cyclic analysis result is, as indicated by two markers that
|
||||
* are unique to this instance. This alleviates memory pressure in large import graphs, as each
|
||||
* execution is able to store its results in the same memory location (i.e. in the symbol
|
||||
* on the source file) as earlier executions.
|
||||
*/
|
||||
class CycleResults {
|
||||
private readonly cyclic = {} as CyclicResultMarker;
|
||||
private readonly acyclic = {} as CyclicResultMarker;
|
||||
|
||||
constructor(readonly from: ts.SourceFile, private importGraph: ImportGraph) {}
|
||||
|
||||
wouldBeCyclic(sf: ts.SourceFile): boolean {
|
||||
const cached = this.getCachedResult(sf);
|
||||
if (cached !== null) {
|
||||
// The result for this source file has already been computed, so return its result.
|
||||
return cached;
|
||||
}
|
||||
|
||||
if (sf === this.from) {
|
||||
// We have reached the source file that we want to create an import from, which means that
|
||||
// doing so would create a cycle.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Assume for now that the file will be acyclic; this prevents infinite recursion in the case
|
||||
// that `sf` is visited again as part of an existing cycle in the graph.
|
||||
this.markAcyclic(sf);
|
||||
|
||||
const imports = this.importGraph.importsOf(sf);
|
||||
for (const imported of imports) {
|
||||
if (this.wouldBeCyclic(imported)) {
|
||||
this.markCyclic(sf);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the source file is already known to be cyclic, or `null` if the result is not
|
||||
* yet known.
|
||||
*/
|
||||
private getCachedResult(sf: CyclicSourceFile): boolean|null {
|
||||
const result = sf[NgCyclicResult];
|
||||
if (result === this.cyclic) {
|
||||
return true;
|
||||
} else if (result === this.acyclic) {
|
||||
return false;
|
||||
} else {
|
||||
// Either the symbol is missing or its value does not correspond with one of the current
|
||||
// result markers. As such, the result is unknown.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private markCyclic(sf: CyclicSourceFile): void {
|
||||
sf[NgCyclicResult] = this.cyclic;
|
||||
}
|
||||
|
||||
private markAcyclic(sf: CyclicSourceFile): void {
|
||||
sf[NgCyclicResult] = this.acyclic;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an import cycle between `from` and `to` in the program.
|
||||
*
|
||||
|
||||
@@ -17,7 +17,7 @@ import {PerfPhase, PerfRecorder} from '../../perf';
|
||||
* dependencies within the same program are tracked; imports into packages on NPM are not.
|
||||
*/
|
||||
export class ImportGraph {
|
||||
private map = new Map<ts.SourceFile, Set<ts.SourceFile>>();
|
||||
private imports = new Map<ts.SourceFile, Set<ts.SourceFile>>();
|
||||
|
||||
constructor(private checker: ts.TypeChecker, private perf: PerfRecorder) {}
|
||||
|
||||
@@ -27,29 +27,10 @@ export class ImportGraph {
|
||||
* This operation is cached.
|
||||
*/
|
||||
importsOf(sf: ts.SourceFile): Set<ts.SourceFile> {
|
||||
if (!this.map.has(sf)) {
|
||||
this.map.set(sf, this.scanImports(sf));
|
||||
if (!this.imports.has(sf)) {
|
||||
this.imports.set(sf, this.scanImports(sf));
|
||||
}
|
||||
return this.map.get(sf)!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists the transitive imports of a given `ts.SourceFile`.
|
||||
*/
|
||||
transitiveImportsOf(sf: ts.SourceFile): Set<ts.SourceFile> {
|
||||
const imports = new Set<ts.SourceFile>();
|
||||
this.transitiveImportsOfHelper(sf, imports);
|
||||
return imports;
|
||||
}
|
||||
|
||||
private transitiveImportsOfHelper(sf: ts.SourceFile, results: Set<ts.SourceFile>): void {
|
||||
if (results.has(sf)) {
|
||||
return;
|
||||
}
|
||||
results.add(sf);
|
||||
this.importsOf(sf).forEach(imported => {
|
||||
this.transitiveImportsOfHelper(imported, results);
|
||||
});
|
||||
return this.imports.get(sf)!;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,6 +91,13 @@ export class ImportGraph {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ts.isImportDeclaration(stmt) && stmt.importClause !== undefined &&
|
||||
stmt.importClause.isTypeOnly) {
|
||||
// Exclude type-only imports as they are always elided, so they don't contribute to
|
||||
// cycles.
|
||||
continue;
|
||||
}
|
||||
|
||||
const symbol = this.checker.getSymbolAtLocation(stmt.moduleSpecifier);
|
||||
if (symbol === undefined || symbol.valueDeclaration === undefined) {
|
||||
// No symbol could be found to skip over this import/export.
|
||||
|
||||
@@ -36,6 +36,22 @@ runInEachFileSystem(() => {
|
||||
expect(importPath(cycle!.getPath())).toEqual('b,a,b');
|
||||
});
|
||||
|
||||
it('should deal with cycles', () => {
|
||||
// a -> b -> c -> d
|
||||
// ^---------/
|
||||
const {program, analyzer} = makeAnalyzer('a:b;b:c;c:d;d:b');
|
||||
const a = getSourceFileOrError(program, (_('/a.ts')));
|
||||
const b = getSourceFileOrError(program, (_('/b.ts')));
|
||||
const c = getSourceFileOrError(program, (_('/c.ts')));
|
||||
const d = getSourceFileOrError(program, (_('/d.ts')));
|
||||
expect(analyzer.wouldCreateCycle(a, b)).toBe(null);
|
||||
expect(analyzer.wouldCreateCycle(a, c)).toBe(null);
|
||||
expect(analyzer.wouldCreateCycle(a, d)).toBe(null);
|
||||
expect(analyzer.wouldCreateCycle(b, a)).not.toBe(null);
|
||||
expect(analyzer.wouldCreateCycle(b, c)).not.toBe(null);
|
||||
expect(analyzer.wouldCreateCycle(b, d)).not.toBe(null);
|
||||
});
|
||||
|
||||
it('should detect a cycle with a re-export in the chain', () => {
|
||||
const {program, analyzer} = makeAnalyzer('a:*b;b:c;c');
|
||||
const a = getSourceFileOrError(program, (_('/a.ts')));
|
||||
@@ -70,6 +86,17 @@ runInEachFileSystem(() => {
|
||||
expect(cycle).toBeInstanceOf(Cycle);
|
||||
expect(importPath(cycle!.getPath())).toEqual('b,c,b');
|
||||
});
|
||||
|
||||
it('should not consider type-only imports', () => {
|
||||
const {program, analyzer} = makeAnalyzer('a:b,c!;b;c');
|
||||
const a = getSourceFileOrError(program, (_('/a.ts')));
|
||||
const b = getSourceFileOrError(program, (_('/b.ts')));
|
||||
const c = getSourceFileOrError(program, (_('/c.ts')));
|
||||
expect(analyzer.wouldCreateCycle(c, a)).toBe(null);
|
||||
const cycle = analyzer.wouldCreateCycle(b, a);
|
||||
expect(cycle).toBeInstanceOf(Cycle);
|
||||
expect(importPath(cycle!.getPath())).toEqual('b,a,b');
|
||||
});
|
||||
});
|
||||
|
||||
function makeAnalyzer(graph: string): {program: ts.Program, analyzer: CycleAnalyzer} {
|
||||
|
||||
@@ -28,34 +28,6 @@ runInEachFileSystem(() => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('transitiveImportsOf()', () => {
|
||||
it('should calculate transitive imports of a simple program', () => {
|
||||
const {program, graph} = makeImportGraph('a:b;b:c;c');
|
||||
const a = getSourceFileOrError(program, (_('/a.ts')));
|
||||
const b = getSourceFileOrError(program, (_('/b.ts')));
|
||||
const c = getSourceFileOrError(program, (_('/c.ts')));
|
||||
expect(importsToString(graph.transitiveImportsOf(a))).toBe('a,b,c');
|
||||
});
|
||||
|
||||
it('should calculate transitive imports in a more complex program (with a cycle)', () => {
|
||||
const {program, graph} = makeImportGraph('a:*b,*c;b:*e,*f;c:*g,*h;e:f;f;g:e;h:g');
|
||||
const c = getSourceFileOrError(program, (_('/c.ts')));
|
||||
expect(importsToString(graph.transitiveImportsOf(c))).toBe('c,e,f,g,h');
|
||||
});
|
||||
|
||||
it('should reflect the addition of a synthetic import', () => {
|
||||
const {program, graph} = makeImportGraph('a:b,c,d;b;c;d:b');
|
||||
const b = getSourceFileOrError(program, (_('/b.ts')));
|
||||
const c = getSourceFileOrError(program, (_('/c.ts')));
|
||||
const d = getSourceFileOrError(program, (_('/d.ts')));
|
||||
expect(importsToString(graph.importsOf(b))).toEqual('');
|
||||
expect(importsToString(graph.transitiveImportsOf(d))).toEqual('b,d');
|
||||
graph.addSyntheticImport(b, c);
|
||||
expect(importsToString(graph.importsOf(b))).toEqual('c');
|
||||
expect(importsToString(graph.transitiveImportsOf(d))).toEqual('b,c,d');
|
||||
});
|
||||
});
|
||||
|
||||
describe('findPath()', () => {
|
||||
it('should be able to compute the path between two source files if there is a cycle', () => {
|
||||
const {program, graph} = makeImportGraph('a:*b,*c;b:*e,*f;c:*g,*h;e:f;f;g:e;h:g');
|
||||
|
||||
@@ -30,6 +30,8 @@ import {makeProgram} from '../../testing';
|
||||
* "a:*b,c;b;c"
|
||||
*
|
||||
* represents a program where a.ts exports from b.ts and imports from c.ts.
|
||||
*
|
||||
* An import can be suffixed with ! to make it a type-only import.
|
||||
*/
|
||||
export function makeProgramFromGraph(fs: PathManipulation, graph: string): {
|
||||
program: ts.Program,
|
||||
@@ -43,6 +45,9 @@ export function makeProgramFromGraph(fs: PathManipulation, graph: string): {
|
||||
if (i.startsWith('*')) {
|
||||
const sym = i.substr(1);
|
||||
return `export {${sym}} from './${sym}';`;
|
||||
} else if (i.endsWith('!')) {
|
||||
const sym = i.substr(0, i.length - 1);
|
||||
return `import type {${sym}} from './${sym}';`;
|
||||
} else {
|
||||
return `import {${i}} from './${i}';`;
|
||||
}
|
||||
|
||||
@@ -50,6 +50,12 @@ export enum ErrorCode {
|
||||
*/
|
||||
COMPONENT_RESOURCE_NOT_FOUND = 2008,
|
||||
|
||||
/**
|
||||
* Raised when a component uses `ShadowDom` view encapsulation, but its selector
|
||||
* does not match the shadow DOM tag name requirements.
|
||||
*/
|
||||
COMPONENT_INVALID_SHADOW_DOM_SELECTOR = 2009,
|
||||
|
||||
SYMBOL_NOT_EXPORTED = 3001,
|
||||
SYMBOL_EXPORTED_UNDER_DIFFERENT_NAME = 3002,
|
||||
/**
|
||||
@@ -110,6 +116,12 @@ export enum ErrorCode {
|
||||
*/
|
||||
NGMODULE_DECLARATION_NOT_UNIQUE = 6007,
|
||||
|
||||
/**
|
||||
* Not actually raised by the compiler, but reserved for documentation of a View Engine error when
|
||||
* a View Engine build depends on an Ivy-compiled NgModule.
|
||||
*/
|
||||
NGMODULE_VE_DEPENDENCY_ON_IVY_LIB = 6999,
|
||||
|
||||
/**
|
||||
* An element name failed validation against the DOM schema.
|
||||
*/
|
||||
@@ -154,6 +166,22 @@ export enum ErrorCode {
|
||||
*/
|
||||
DUPLICATE_VARIABLE_DECLARATION = 8006,
|
||||
|
||||
/**
|
||||
* A template has a two way binding (two bindings created by a single syntactial element)
|
||||
* in which the input and output are going to different places.
|
||||
*/
|
||||
SPLIT_TWO_WAY_BINDING = 8007,
|
||||
|
||||
/**
|
||||
* A two way binding in a template has an incorrect syntax,
|
||||
* parentheses outside brackets. For example:
|
||||
*
|
||||
* ```
|
||||
* <div ([foo])="bar" />
|
||||
* ```
|
||||
*/
|
||||
INVALID_BANANA_IN_BOX = 8101,
|
||||
|
||||
/**
|
||||
* The template type-checking engine would need to generate an inline type check block for a
|
||||
* component, but the current type-checking environment doesn't support it.
|
||||
@@ -209,6 +237,7 @@ export const COMPILER_ERRORS_WITH_GUIDES = new Set([
|
||||
ErrorCode.SCHEMA_INVALID_ELEMENT,
|
||||
ErrorCode.SCHEMA_INVALID_ATTRIBUTE,
|
||||
ErrorCode.MISSING_REFERENCE_TARGET,
|
||||
ErrorCode.COMPONENT_INVALID_SHADOW_DOM_SELECTOR,
|
||||
]);
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,9 +8,7 @@ ts_library(
|
||||
"src/**/*.ts",
|
||||
]),
|
||||
deps = [
|
||||
"@npm//@types/fs-extra",
|
||||
"@npm//@types/node",
|
||||
"@npm//fs-extra",
|
||||
"@npm//typescript",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -29,11 +29,21 @@ export function absoluteFrom(path: string): AbsoluteFsPath {
|
||||
return fs.resolve(path);
|
||||
}
|
||||
|
||||
const ABSOLUTE_PATH = Symbol('AbsolutePath');
|
||||
|
||||
/**
|
||||
* Extract an `AbsoluteFsPath` from a `ts.SourceFile`.
|
||||
* Extract an `AbsoluteFsPath` from a `ts.SourceFile`-like object.
|
||||
*/
|
||||
export function absoluteFromSourceFile(sf: ts.SourceFile): AbsoluteFsPath {
|
||||
return fs.resolve(sf.fileName);
|
||||
export function absoluteFromSourceFile(sf: {fileName: string}): AbsoluteFsPath {
|
||||
const sfWithPatch = sf as {fileName: string, [ABSOLUTE_PATH]?: AbsoluteFsPath};
|
||||
|
||||
if (sfWithPatch[ABSOLUTE_PATH] === undefined) {
|
||||
sfWithPatch[ABSOLUTE_PATH] = fs.resolve(sfWithPatch.fileName);
|
||||
}
|
||||
|
||||
// Non-null assertion needed since TS doesn't narrow the type of fields that use a symbol as a key
|
||||
// apparently.
|
||||
return sfWithPatch[ABSOLUTE_PATH]!;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
*/
|
||||
/// <reference types="node" />
|
||||
import * as fs from 'fs';
|
||||
import * as fsExtra from 'fs-extra';
|
||||
import * as p from 'path';
|
||||
import {AbsoluteFsPath, FileStats, FileSystem, PathManipulation, PathSegment, PathString, ReadonlyFileSystem} from './types';
|
||||
|
||||
@@ -121,7 +120,7 @@ export class NodeJSFileSystem extends NodeJSReadonlyFileSystem implements FileSy
|
||||
}
|
||||
}
|
||||
removeDeep(path: AbsoluteFsPath): void {
|
||||
fsExtra.removeSync(path);
|
||||
fs.rmdirSync(path, {recursive: true});
|
||||
}
|
||||
|
||||
private safeMkdir(path: AbsoluteFsPath): void {
|
||||
|
||||
@@ -11,7 +11,6 @@ ts_library(
|
||||
deps = [
|
||||
"//packages/compiler-cli/src/ngtsc/file_system",
|
||||
"//packages/compiler-cli/src/ngtsc/file_system/testing",
|
||||
"@npm//@types/fs-extra",
|
||||
"@npm//typescript",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import * as realFs from 'fs';
|
||||
import * as fsExtra from 'fs-extra';
|
||||
import * as os from 'os';
|
||||
import {NodeJSFileSystem, NodeJSPathManipulation, NodeJSReadonlyFileSystem} from '../src/node_js_file_system';
|
||||
import {AbsoluteFsPath, PathSegment} from '../src/types';
|
||||
@@ -269,10 +268,10 @@ describe('NodeJSFileSystem', () => {
|
||||
});
|
||||
|
||||
describe('removeDeep()', () => {
|
||||
it('should delegate to fsExtra.remove()', () => {
|
||||
const spy = spyOn(fsExtra, 'removeSync');
|
||||
it('should delegate to rmdirSync()', () => {
|
||||
const spy = spyOn(realFs, 'rmdirSync');
|
||||
fs.removeDeep(abcPath);
|
||||
expect(spy).toHaveBeenCalledWith(abcPath);
|
||||
expect(spy).toHaveBeenCalledWith(abcPath, {recursive: true});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+10
-10
@@ -21,36 +21,36 @@ export class MockFileSystemNative extends MockFileSystem {
|
||||
|
||||
// Delegate to the real NodeJSFileSystem for these path related methods
|
||||
|
||||
resolve(...paths: string[]): AbsoluteFsPath {
|
||||
override resolve(...paths: string[]): AbsoluteFsPath {
|
||||
return NodeJSFileSystem.prototype.resolve.call(this, this.pwd(), ...paths);
|
||||
}
|
||||
dirname<T extends string>(file: T): T {
|
||||
override dirname<T extends string>(file: T): T {
|
||||
return NodeJSFileSystem.prototype.dirname.call(this, file) as T;
|
||||
}
|
||||
join<T extends string>(basePath: T, ...paths: string[]): T {
|
||||
override join<T extends string>(basePath: T, ...paths: string[]): T {
|
||||
return NodeJSFileSystem.prototype.join.call(this, basePath, ...paths) as T;
|
||||
}
|
||||
relative<T extends PathString>(from: T, to: T): PathSegment|AbsoluteFsPath {
|
||||
override relative<T extends PathString>(from: T, to: T): PathSegment|AbsoluteFsPath {
|
||||
return NodeJSFileSystem.prototype.relative.call(this, from, to);
|
||||
}
|
||||
|
||||
basename(filePath: string, extension?: string): PathSegment {
|
||||
override basename(filePath: string, extension?: string): PathSegment {
|
||||
return NodeJSFileSystem.prototype.basename.call(this, filePath, extension);
|
||||
}
|
||||
|
||||
isCaseSensitive() {
|
||||
override isCaseSensitive() {
|
||||
return NodeJSFileSystem.prototype.isCaseSensitive.call(this);
|
||||
}
|
||||
|
||||
isRooted(path: string): boolean {
|
||||
override isRooted(path: string): boolean {
|
||||
return NodeJSFileSystem.prototype.isRooted.call(this, path);
|
||||
}
|
||||
|
||||
isRoot(path: AbsoluteFsPath): boolean {
|
||||
override isRoot(path: AbsoluteFsPath): boolean {
|
||||
return NodeJSFileSystem.prototype.isRoot.call(this, path);
|
||||
}
|
||||
|
||||
normalize<T extends PathString>(path: T): T {
|
||||
override normalize<T extends PathString>(path: T): T {
|
||||
// When running in Windows, absolute paths are normalized to always include a drive letter. This
|
||||
// ensures that rooted posix paths used in tests will be normalized to real Windows paths, i.e.
|
||||
// including a drive letter. Note that the same normalization is done in emulated Windows mode
|
||||
@@ -63,7 +63,7 @@ export class MockFileSystemNative extends MockFileSystem {
|
||||
return NodeJSFileSystem.prototype.normalize.call(this, path) as T;
|
||||
}
|
||||
|
||||
protected splitPath<T>(path: string): string[] {
|
||||
protected override splitPath<T>(path: string): string[] {
|
||||
return path.split(/[\\\/]/);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,36 +12,36 @@ import {AbsoluteFsPath, PathSegment, PathString} from '../../src/types';
|
||||
import {MockFileSystem} from './mock_file_system';
|
||||
|
||||
export class MockFileSystemPosix extends MockFileSystem {
|
||||
resolve(...paths: string[]): AbsoluteFsPath {
|
||||
override resolve(...paths: string[]): AbsoluteFsPath {
|
||||
const resolved = p.posix.resolve(this.pwd(), ...paths);
|
||||
return this.normalize(resolved) as AbsoluteFsPath;
|
||||
}
|
||||
|
||||
dirname<T extends string>(file: T): T {
|
||||
override dirname<T extends string>(file: T): T {
|
||||
return this.normalize(p.posix.dirname(file)) as T;
|
||||
}
|
||||
|
||||
join<T extends string>(basePath: T, ...paths: string[]): T {
|
||||
override join<T extends string>(basePath: T, ...paths: string[]): T {
|
||||
return this.normalize(p.posix.join(basePath, ...paths)) as T;
|
||||
}
|
||||
|
||||
relative<T extends PathString>(from: T, to: T): PathSegment|AbsoluteFsPath {
|
||||
override relative<T extends PathString>(from: T, to: T): PathSegment|AbsoluteFsPath {
|
||||
return this.normalize(p.posix.relative(from, to)) as PathSegment | AbsoluteFsPath;
|
||||
}
|
||||
|
||||
basename(filePath: string, extension?: string): PathSegment {
|
||||
override basename(filePath: string, extension?: string): PathSegment {
|
||||
return p.posix.basename(filePath, extension) as PathSegment;
|
||||
}
|
||||
|
||||
isRooted(path: string): boolean {
|
||||
override isRooted(path: string): boolean {
|
||||
return path.startsWith('/');
|
||||
}
|
||||
|
||||
protected splitPath<T extends PathString>(path: T): string[] {
|
||||
protected override splitPath<T extends PathString>(path: T): string[] {
|
||||
return path.split('/');
|
||||
}
|
||||
|
||||
normalize<T extends PathString>(path: T): T {
|
||||
override normalize<T extends PathString>(path: T): T {
|
||||
return path.replace(/^[a-z]:\//i, '/').replace(/\\/g, '/') as T;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,36 +12,36 @@ import {AbsoluteFsPath, PathSegment, PathString} from '../../src/types';
|
||||
import {MockFileSystem} from './mock_file_system';
|
||||
|
||||
export class MockFileSystemWindows extends MockFileSystem {
|
||||
resolve(...paths: string[]): AbsoluteFsPath {
|
||||
override resolve(...paths: string[]): AbsoluteFsPath {
|
||||
const resolved = p.win32.resolve(this.pwd(), ...paths);
|
||||
return this.normalize(resolved as AbsoluteFsPath);
|
||||
}
|
||||
|
||||
dirname<T extends string>(path: T): T {
|
||||
override dirname<T extends string>(path: T): T {
|
||||
return this.normalize(p.win32.dirname(path) as T);
|
||||
}
|
||||
|
||||
join<T extends string>(basePath: T, ...paths: string[]): T {
|
||||
override join<T extends string>(basePath: T, ...paths: string[]): T {
|
||||
return this.normalize(p.win32.join(basePath, ...paths)) as T;
|
||||
}
|
||||
|
||||
relative<T extends PathString>(from: T, to: T): PathSegment|AbsoluteFsPath {
|
||||
override relative<T extends PathString>(from: T, to: T): PathSegment|AbsoluteFsPath {
|
||||
return this.normalize(p.win32.relative(from, to)) as PathSegment | AbsoluteFsPath;
|
||||
}
|
||||
|
||||
basename(filePath: string, extension?: string): PathSegment {
|
||||
override basename(filePath: string, extension?: string): PathSegment {
|
||||
return p.win32.basename(filePath, extension) as PathSegment;
|
||||
}
|
||||
|
||||
isRooted(path: string): boolean {
|
||||
override isRooted(path: string): boolean {
|
||||
return /^([A-Z]:)?([\\\/]|$)/i.test(path);
|
||||
}
|
||||
|
||||
protected splitPath<T extends PathString>(path: T): string[] {
|
||||
protected override splitPath<T extends PathString>(path: T): string[] {
|
||||
return path.split(/[\\\/]/);
|
||||
}
|
||||
|
||||
normalize<T extends PathString>(path: T): T {
|
||||
override normalize<T extends PathString>(path: T): T {
|
||||
return path.replace(/^[\/\\]/i, 'C:/').replace(/\\/g, '/') as T;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,8 +167,6 @@ It consists of two mechanisms:
|
||||
|
||||
1. A `DefaultImportTracker`, which records information about both default imports encountered in the program as well as usages of those imports added during compilation.
|
||||
|
||||
A `DefaultImportRecorder` interface is used to allow for a noop implementation in cases (like ngcc) where this tracking isn't necessary.
|
||||
|
||||
2. A TypeScript transformer which processes default import statements and can preserve those which are actually used.
|
||||
|
||||
This is accessed via `DefaultImportTracker.importPreservingTransformer`.
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
export {AliasingHost, AliasStrategy, PrivateExportAliasingHost, UnifiedModulesAliasingHost} from './src/alias';
|
||||
export {ImportRewriter, NoopImportRewriter, R3SymbolsImportRewriter, validateAndRewriteCoreSymbol} from './src/core';
|
||||
export {DefaultImportRecorder, DefaultImportTracker, NOOP_DEFAULT_IMPORT_RECORDER} from './src/default';
|
||||
export {DefaultImportTracker} from './src/default';
|
||||
export {AbsoluteModuleStrategy, EmittedReference, ImportedFile, ImportFlags, LocalIdentifierStrategy, LogicalProjectStrategy, ReferenceEmitStrategy, ReferenceEmitter, RelativePathStrategy, UnifiedModulesStrategy} from './src/emitter';
|
||||
export {Reexport} from './src/reexport';
|
||||
export {OwningModule, Reference} from './src/references';
|
||||
|
||||
@@ -61,7 +61,7 @@ const CORE_SUPPORTED_SYMBOLS = new Map<string, string>([
|
||||
['ɵɵinject', 'ɵɵinject'],
|
||||
['ɵɵFactoryDeclaration', 'ɵɵFactoryDeclaration'],
|
||||
['ɵsetClassMetadata', 'setClassMetadata'],
|
||||
['ɵɵInjectableDef', 'ɵɵInjectableDef'],
|
||||
['ɵɵInjectableDeclaration', 'ɵɵInjectableDeclaration'],
|
||||
['ɵɵInjectorDeclaration', 'ɵɵInjectorDeclaration'],
|
||||
['ɵɵNgModuleDeclaration', 'ɵɵNgModuleDeclaration'],
|
||||
['ɵNgModuleFactory', 'NgModuleFactory'],
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user