feat(core): support TypeScript 4.5 (#44164)

Adds support for TypeScript 4.5. Includes the following changes:
* Bumping the package versions.
* Fixing a few calls to `createExportSpecifier` and `createImportSpecifier` that require an extra parameter.
* Adding some missing methods to the TS compiler hosts.
* Fixing an issue in the TS mocks for the ngcc tests where a regex was too agressive and was trying to match a path like `/node_modules/@typescript/lib-es5`.
* Accounting for type-only import specifiers when reporting DI errors (see #43620).

Fixes #43620.

PR Close #44164
This commit is contained in:
Kristiyan Kostadinov
2021-11-30 11:47:11 +01:00
committed by Dylan Hunn
parent 0fcf18d5f4
commit d56e3f43a1
44 changed files with 419 additions and 108 deletions
@@ -49,6 +49,6 @@
"supertest": "^4.0.2",
"tslint": "^6.1.3",
"tslint-jasmine-noSkipOrFocus": "^1.0.9",
"typescript": "~4.4.2"
"typescript": "~4.5.2"
}
}
@@ -2563,10 +2563,10 @@ typedarray-to-buffer@^3.1.5:
dependencies:
is-typedarray "^1.0.0"
typescript@~4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.2.tgz#6d618640d430e3569a1dfb44f7d7e600ced3ee86"
integrity sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ==
typescript@~4.5.2:
version "4.5.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.2.tgz#8ac1fba9f52256fdb06fb89e4122fa6a346c2998"
integrity sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==
undefsafe@^2.0.2:
version "2.0.2"
+1 -1
View File
@@ -175,7 +175,7 @@
"ts-node": "^10.0.0",
"tsec": "^0.1.5",
"tslint": "~6.1.3",
"typescript": "~4.4.3",
"typescript": "~4.5.2",
"uglify-js": "^3.13.3",
"unist-util-filter": "^2.0.3",
"unist-util-source": "^3.0.0",
@@ -46,6 +46,6 @@
"karma-jasmine-html-reporter": "~1.7.0",
"protractor": "~7.0.0",
"ts-node": "~10.1.0",
"typescript": "~4.4.3"
"typescript": "~4.5.2"
}
}
@@ -43,6 +43,6 @@
"karma-jasmine-html-reporter": "~1.7.0",
"protractor": "~7.0.0",
"ts-node": "~10.1.0",
"typescript": "~4.4.3"
"typescript": "~4.5.2"
}
}
@@ -43,6 +43,6 @@
"karma-jasmine-html-reporter": "~1.7.0",
"protractor": "~7.0.0",
"ts-node": "~10.1.0",
"typescript": "~4.4.3"
"typescript": "~4.5.2"
}
}
@@ -46,6 +46,6 @@
"karma-jasmine-html-reporter": "~1.7.0",
"protractor": "~7.0.0",
"ts-node": "~10.1.0",
"typescript": "~4.4.3"
"typescript": "~4.5.2"
}
}
@@ -43,6 +43,6 @@
"karma-jasmine-html-reporter": "~1.7.0",
"protractor": "~7.0.0",
"ts-node": "~10.1.0",
"typescript": "~4.4.3"
"typescript": "~4.5.2"
}
}
@@ -63,6 +63,6 @@
"rollup-plugin-commonjs": "^9.2.1",
"rollup-plugin-node-resolve": "^4.0.0",
"rollup-plugin-terser": "^5.3.0",
"typescript": "~4.4.3"
"typescript": "~4.5.2"
}
}
@@ -51,6 +51,6 @@
"karma-jasmine-html-reporter": "~1.7.0",
"protractor": "~7.0.0",
"ts-node": "~10.1.0",
"typescript": "~4.4.3"
"typescript": "~4.5.2"
}
}
+1 -1
View File
@@ -82,6 +82,6 @@
"rollup-plugin-terser": "^5.3.0",
"source-map-explorer": "^1.3.2",
"ts-node": "~10.1.0",
"typescript": "~4.4.3"
"typescript": "~4.5.2"
}
}
+4 -4
View File
@@ -11066,10 +11066,10 @@ typed-assert@^1.0.8:
resolved "https://registry.yarnpkg.com/typed-assert/-/typed-assert-1.0.8.tgz#4bf9f1ce7f3f974d09c3afd7c68d12e1391a233c"
integrity sha512-5NkbXZUlmCE73Fs7gvkp1XXJWHYetPkg60QnQ2NXQmBYNFxbBr2zA8GCtaH4K2s2WhOmSlgiSTmrjrcm5tnM5g==
typescript@~4.4.3:
version "4.4.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.4.tgz#2cd01a1a1f160704d3101fd5a58ff0f9fcb8030c"
integrity sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==
typescript@~4.5.2:
version "4.5.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.2.tgz#8ac1fba9f52256fdb06fb89e4122fa6a346c2998"
integrity sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==
ua-parser-js@0.7.17:
version "0.7.17"
+4 -4
View File
@@ -11776,10 +11776,10 @@ typescript@~3.2.2:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.2.4.tgz#c585cb952912263d915b462726ce244ba510ef3d"
integrity sha512-0RNDbSdEokBeEAkgNbxJ+BLwSManFy9TeXz8uW+48j/xhEXv1ePME60olyzw2XzUqUBNAYFeJadIqAgNqIACwg==
typescript@~4.4.3:
version "4.4.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.4.tgz#2cd01a1a1f160704d3101fd5a58ff0f9fcb8030c"
integrity sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==
typescript@~4.5.2:
version "4.5.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.2.tgz#8ac1fba9f52256fdb06fb89e4122fa6a346c2998"
integrity sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==
ua-parser-js@^0.7.30:
version "0.7.31"
+6
View File
@@ -100,6 +100,12 @@ INTEGRATION_TESTS = {
# root @npm//typescript package.
"pinned_npm_packages": ["typescript"],
},
"typings_test_ts45": {
# Special case for `typings_test_ts45` test as we want to pin
# `typescript` at version 4.5.x for that test and not link to the
# root @npm//typescript package.
"pinned_npm_packages": ["typescript"],
},
}
[
@@ -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 * as animations from '@angular/animations';
import * as animationsBrowser from '@angular/animations/browser';
import * as animationsBrowserTesting from '@angular/animations/browser/testing';
import * as common from '@angular/common';
import * as commonHttp from '@angular/common/http';
import * as commonTesting from '@angular/common/testing';
import * as commonHttpTesting from '@angular/common/testing';
import * as compiler from '@angular/compiler';
import * as compilerTesting from '@angular/compiler/testing';
import * as core from '@angular/core';
import * as coreTesting from '@angular/core/testing';
import * as elements from '@angular/elements';
import * as forms from '@angular/forms';
import * as platformBrowser from '@angular/platform-browser';
import * as platformBrowserDynamic from '@angular/platform-browser-dynamic';
import * as platformBrowserDynamicTesting from '@angular/platform-browser-dynamic/testing';
import * as platformBrowserAnimations from '@angular/platform-browser/animations';
import * as platformBrowserTesting from '@angular/platform-browser/testing';
import * as platformServer from '@angular/platform-server';
import * as platformServerInit from '@angular/platform-server/init';
import * as platformServerTesting from '@angular/platform-server/testing';
import * as router from '@angular/router';
import * as routerTesting from '@angular/router/testing';
import * as routerUpgrade from '@angular/router/upgrade';
import * as serviceWorker from '@angular/service-worker';
import * as upgrade from '@angular/upgrade';
import * as upgradeStatic from '@angular/upgrade/static';
import * as upgradeTesting from '@angular/upgrade/static/testing';
export default {
animations,
animationsBrowser,
animationsBrowserTesting,
common,
commonTesting,
commonHttp,
commonHttpTesting,
compiler,
compilerTesting,
core,
coreTesting,
elements,
forms,
platformBrowser,
platformBrowserTesting,
platformBrowserDynamic,
platformBrowserDynamicTesting,
platformBrowserAnimations,
platformServer,
platformServerInit,
platformServerTesting,
router,
routerTesting,
routerUpgrade,
serviceWorker,
upgrade,
upgradeStatic,
upgradeTesting,
};
@@ -0,0 +1,28 @@
{
"name": "angular-integration",
"description": "Assert that users with TypeScript 4.5 can type-check an Angular application",
"version": "0.0.0",
"license": "MIT",
"dependencies": {
"@angular/animations": "file:../../dist/packages-dist/animations",
"@angular/common": "file:../../dist/packages-dist/common",
"@angular/compiler": "file:../../dist/packages-dist/compiler",
"@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli",
"@angular/core": "file:../../dist/packages-dist/core",
"@angular/elements": "file:../../dist/packages-dist/elements",
"@angular/forms": "file:../../dist/packages-dist/forms",
"@angular/platform-browser": "file:../../dist/packages-dist/platform-browser",
"@angular/platform-browser-dynamic": "file:../../dist/packages-dist/platform-browser-dynamic",
"@angular/platform-server": "file:../../dist/packages-dist/platform-server",
"@angular/router": "file:../../dist/packages-dist/router",
"@angular/service-worker": "file:../../dist/packages-dist/service-worker",
"@angular/upgrade": "file:../../dist/packages-dist/upgrade",
"@types/jasmine": "file:../../node_modules/@types/jasmine",
"rxjs": "file:../../node_modules/rxjs",
"typescript": "4.5.2",
"zone.js": "file:../../dist/zone.js-dist/archive/zone.js.tgz"
},
"scripts": {
"test": "tsc"
}
}
@@ -0,0 +1,26 @@
{
"compilerOptions": {
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"experimentalDecorators": true,
"module": "commonjs",
"moduleResolution": "node",
"outDir": "./dist/out-tsc",
"rootDir": ".",
"target": "es5",
"lib": [
"es5",
"dom",
"es2015.collection",
"es2015.iterable",
"es2015.promise"
],
"types": [],
},
"files": [
"include-all.ts",
"node_modules/@types/jasmine/index.d.ts"
]
}
+1 -1
View File
@@ -152,7 +152,7 @@
"tsickle": "0.38.1",
"tslib": "^2.3.0",
"tslint": "6.1.3",
"typescript": "~4.4.2",
"typescript": "~4.5.2",
"xhr2": "0.2.1",
"yargs": "^17.2.1"
},
+1 -1
View File
@@ -35,7 +35,7 @@
"rollup": "^2.56.3",
"rollup-plugin-sourcemaps": "^0.6.3",
"terser": "^5.9.0",
"typescript": ">=4.4.2 <4.5"
"typescript": ">=4.4.2 <4.6"
},
"peerDependenciesMeta": {
"terser": {
+1
View File
@@ -105,6 +105,7 @@ ts_library(
"//packages/compiler-cli/src/ngtsc/translator",
"//packages/compiler-cli/src/ngtsc/typecheck",
"//packages/compiler-cli/src/ngtsc/typecheck/api",
"//packages/compiler-cli/src/ngtsc/util",
"//packages/compiler-cli/src/transformers/downlevel_decorators_transform",
"@npm//@bazel/typescript",
"@npm//@types/node",
@@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import {DepGraph} from 'dependency-graph';
import {DtsProcessing, PartiallyOrderedTasks} from '../../src/execution/tasks/api';
import {EntryPoint, EntryPointJsonProperty} from '../../src/packages/entry_point';
@@ -53,7 +54,7 @@ export function createTasksAndGraph(
for (let tIdx = 0; tIdx < tasksPerEntryPointCount; tIdx++) {
const processDts = tIdx === 0 ? DtsProcessing.Yes : DtsProcessing.No;
const formatProperty = `prop-${tIdx}` as EntryPointJsonProperty;
const formatProperty = `prop-${tIdx}` as unknown as EntryPointJsonProperty;
tasks.push({
entryPoint,
formatProperty: formatProperty,
@@ -11,7 +11,7 @@ import {MockLogger} from '../../../../../src/ngtsc/logging/testing';
import {DtsProcessing, PartiallyOrderedTasks, Task, TaskQueue} from '../../../../src/execution/tasks/api';
import {SerialTaskQueue} from '../../../../src/execution/tasks/queues/serial_task_queue';
import {computeTaskDependencies} from '../../../../src/execution/tasks/utils';
import {EntryPoint} from '../../../../src/packages/entry_point';
import {EntryPoint, EntryPointJsonProperty} from '../../../../src/packages/entry_point';
describe('SerialTaskQueue', () => {
@@ -35,7 +35,7 @@ describe('SerialTaskQueue', () => {
const processDts = i % 2 === 0 ? DtsProcessing.Yes : DtsProcessing.No;
tasks.push({
entryPoint: entryPoint,
formatProperty: `prop-${i}`,
formatProperty: `prop-${i}` as unknown as EntryPointJsonProperty,
formatPropertiesToMarkAsProcessed: [],
processDts
} as Task);
+1 -1
View File
@@ -67,7 +67,7 @@
},
"peerDependencies": {
"@angular/compiler": "0.0.0-PLACEHOLDER",
"typescript": ">=4.4.2 <4.5"
"typescript": ">=4.4.2 <4.6"
},
"repository": {
"type": "git",
@@ -219,7 +219,7 @@ function createUnsuitableInjectionTokenError(
makeRelatedInformation(
reason.typeNode,
'This type is imported using a type-only import, which prevents it from being usable as an injection token.'),
makeRelatedInformation(reason.importClause, 'The type-only import occurs here.'),
makeRelatedInformation(reason.node, 'The type-only import occurs here.'),
];
break;
case ValueUnavailableKind.NAMESPACE:
@@ -67,6 +67,10 @@ export class DelegatingCompilerHost implements
trace = this.delegateMethod('trace');
useCaseSensitiveFileNames = this.delegateMethod('useCaseSensitiveFileNames');
writeFile = this.delegateMethod('writeFile');
// TODO(crisbeto): the cast to `any` is here since g3 is still on TS 4.4.
// Should be cleaned up when g3 has been updated.
getModuleResolutionCache = this.delegateMethod('getModuleResolutionCache' as any);
}
/**
@@ -92,7 +92,7 @@ export class ImportGraph {
}
if (ts.isImportDeclaration(stmt) && stmt.importClause !== undefined &&
stmt.importClause.isTypeOnly) {
isTypeOnlyImportClause(stmt.importClause)) {
// Exclude type-only imports as they are always elided, so they don't contribute to
// cycles.
continue;
@@ -118,6 +118,23 @@ function isLocalFile(sf: ts.SourceFile): boolean {
return !sf.isDeclarationFile;
}
function isTypeOnlyImportClause(node: ts.ImportClause): boolean {
// The clause itself is type-only (e.g. `import type {foo} from '...'`).
if (node.isTypeOnly) {
return true;
}
// All the specifiers in the cause are type-only (e.g. `import {type a, type b} from '...'`).
if (node.namedBindings !== undefined && ts.isNamedImports(node.namedBindings) &&
// TODO(crisbeto): the cast to `any` is here since g3 is still on TS 4.4.
// Should be cleaned up when g3 has been updated.
node.namedBindings.elements.every(specifier => (specifier as any).isTypeOnly)) {
return true;
}
return false;
}
/**
* A helper class to track which SourceFiles are being processed when searching for a path in
* `getPath()` above.
@@ -51,6 +51,10 @@ export class DelegatingCompilerHost implements
resolveTypeReferenceDirectives = this.delegateMethod('resolveTypeReferenceDirectives');
trace = this.delegateMethod('trace');
useCaseSensitiveFileNames = this.delegateMethod('useCaseSensitiveFileNames');
// TODO(crisbeto): the cast to `any` is here since g3 is still on TS 4.4.
// Should be cleaned up when g3 has been updated.
getModuleResolutionCache = this.delegateMethod('getModuleResolutionCache' as any);
}
/**
@@ -342,7 +342,7 @@ export interface NoValueDeclaration {
export interface TypeOnlyImport {
kind: ValueUnavailableKind.TYPE_ONLY_IMPORT;
typeNode: ts.TypeNode;
importClause: ts.ImportClause;
node: ts.ImportClause|ts.ImportSpecifier;
}
export interface NamespaceImport {
@@ -72,8 +72,16 @@ export function typeToValue(
// or
// import {Foo as Bar} from 'foo';
// TODO(crisbeto): the cast to `any` is here since g3 is still on TS 4.4.
// Should be cleaned up when g3 has been updated.
if ((firstDecl as any).isTypeOnly) {
// The import specifier can't be type-only (e.g. `import {type Foo} from '...')`.
return typeOnlyImport(typeNode, firstDecl);
}
if (firstDecl.parent.parent.isTypeOnly) {
// Type-only imports cannot be represented as value.
// The import specifier can't be inside a type-only import clause
// (e.g. `import type {Foo} from '...')`.
return typeOnlyImport(typeNode, firstDecl.parent.parent);
}
@@ -151,11 +159,11 @@ function noValueDeclaration(
};
}
function typeOnlyImport(
typeNode: ts.TypeNode, importClause: ts.ImportClause): UnavailableTypeValueReference {
function typeOnlyImport(typeNode: ts.TypeNode, node: ts.ImportClause|ts.ImportSpecifier):
UnavailableTypeValueReference {
return {
kind: TypeValueReferenceKind.UNAVAILABLE,
reason: {kind: ValueUnavailableKind.TYPE_ONLY_IMPORT, typeNode, importClause},
reason: {kind: ValueUnavailableKind.TYPE_ONLY_IMPORT, typeNode, node},
};
}
@@ -237,6 +237,8 @@ export class AdapterResourceLoader implements ResourceLoader {
*/
function createLookupResolutionHost(adapter: NgCompilerAdapter):
RequiredDelegations<ts.ModuleResolutionHost> {
// TODO(crisbeto): the cast to `any` is here since g3 is still on TS 4.4.
// Should be cleaned up when g3 has been updated.
return {
directoryExists(directoryName: string): boolean {
if (directoryName.includes(RESOURCE_MARKER)) {
@@ -261,5 +263,8 @@ function createLookupResolutionHost(adapter: NgCompilerAdapter):
getDirectories: adapter.getDirectories?.bind(adapter),
realpath: adapter.realpath?.bind(adapter),
trace: adapter.trace?.bind(adapter),
};
useCaseSensitiveFileNames: typeof (adapter as any).useCaseSensitiveFileNames === 'function' ?
(adapter as any).useCaseSensitiveFileNames.bind(adapter) :
(adapter as any).useCaseSensitiveFileNames
} as any;
}
@@ -159,9 +159,11 @@ function transformFactorySourceFile(
const rewrittenModuleSpecifier =
importRewriter.rewriteSpecifier('@angular/core', sourceFilePath);
if (rewrittenModuleSpecifier !== stmt.moduleSpecifier.text) {
transformedStatements.push(ts.updateImportDeclaration(
// TODO(crisbeto): the cast to `any` is here since g3 is still on TS 4.4.
// Should be cleaned up when g3 has been updated.
transformedStatements.push((ts.updateImportDeclaration as any)(
stmt, stmt.decorators, stmt.modifiers, stmt.importClause,
ts.createStringLiteral(rewrittenModuleSpecifier)));
ts.createStringLiteral(rewrittenModuleSpecifier), undefined));
// Record the identifier by which this imported module goes, so references to its symbols
// can be discovered later.
@@ -8,6 +8,8 @@
import ts from 'typescript';
import {createExportSpecifier} from '../../util/src/typescript';
export function aliasTransformFactory(exportStatements: Map<string, Map<string, [string, string]>>):
ts.TransformerFactory<ts.SourceFile> {
return (context: ts.TransformationContext) => {
@@ -21,9 +23,8 @@ export function aliasTransformFactory(exportStatements: Map<string, Map<string,
const stmt = ts.createExportDeclaration(
/* decorators */ undefined,
/* modifiers */ undefined,
/* exportClause */ ts.createNamedExports([ts.createExportSpecifier(
/* propertyName */ symbolName,
/* name */ aliasName)]),
/* exportClause */ ts.createNamedExports([createExportSpecifier(
symbolName, aliasName)]),
/* moduleSpecifier */ ts.createStringLiteral(moduleName));
statements.push(stmt);
});
@@ -13,6 +13,8 @@ import ts from 'typescript';
import {AbsoluteFsPath, getFileSystem} from '../../file_system';
import {DeclarationNode} from '../../reflection';
const PARSED_TS_VERSION = parseFloat(ts.versionMajorMinor);
/**
* Type describing a symbol that is guaranteed to have a value declaration.
*/
@@ -203,3 +205,36 @@ export function toUnredirectedSourceFile(sf: ts.SourceFile): ts.SourceFile {
}
return redirectInfo.unredirected;
}
/**
* Backwards-compatible version of `ts.createExportSpecifier`
* to handle a breaking change between 4.4 and 4.5.
*/
export function createExportSpecifier(
propertyName: string|ts.Identifier|undefined, name: string|ts.Identifier,
isTypeOnly = false): ts.ExportSpecifier {
return PARSED_TS_VERSION > 4.4 ?
// TODO(crisbeto): the function is cast to `any` here since g3 is still on TS 4.4.
// Should be cleaned up when g3 has been updated.
(ts.createExportSpecifier as any)(isTypeOnly, propertyName, name) :
// TODO(crisbeto): backwards-compatibility layer for TS 4.4.
// Should be cleaned up when we drop support for it.
(ts.createExportSpecifier as any)(propertyName, name);
}
/**
* Backwards-compatible version of `ts.createImportSpecifier`
* to handle a breaking change between 4.4 and 4.5.
*/
export function createImportSpecifier(
propertyName: string|ts.Identifier|undefined, name: string|ts.Identifier,
isTypeOnly = false): ts.ImportSpecifier {
return PARSED_TS_VERSION > 4.4 ?
// TODO(crisbeto): the function is cast to `any` here since g3 is still on TS 4.4.
// Should be cleaned up when g3 has been updated.
(ts.createImportSpecifier as any)(isTypeOnly, propertyName, name) :
// TODO(crisbeto): backwards-compatibility layer for TS 4.4.
// Should be cleaned up when we drop support for it.
(ts.createImportSpecifier as any)(propertyName, name);
}
@@ -10,6 +10,7 @@ import {createLoweredSymbol, isLoweredSymbol} from '@angular/compiler';
import ts from 'typescript';
import {CollectorOptions, isMetadataGlobalReferenceExpression, MetadataCollector, MetadataValue, ModuleMetadata} from '../metadata/index';
import {createExportSpecifier} from '../ngtsc/util/src/typescript';
import {MetadataCache, MetadataTransformer, ValueTransform} from './metadata_cache';
@@ -162,7 +163,7 @@ function transformSourceFile(
(accumulator, insert) => [...accumulator, ...insert.declarations],
[] as Declaration[])
.map(
declaration => ts.createExportSpecifier(
declaration => createExportSpecifier(
/* propertyName */ undefined, declaration.name)))));
newStatements = tmpStatements;
@@ -10,6 +10,8 @@ import {AssertNotNull, BinaryOperator, BinaryOperatorExpr, BuiltinMethod, Builti
import ts from 'typescript';
import {attachComments} from '../ngtsc/translator';
import {createExportSpecifier} from '../ngtsc/util/src/typescript';
import {error} from './util';
export interface Node {
@@ -222,7 +224,7 @@ export class NodeEmitterVisitor implements StatementVisitor, ExpressionVisitor {
/* decorators */ undefined,
/* modifiers */ undefined,
ts.createNamedExports(
reexports.map(({name, as}) => ts.createExportSpecifier(name, as))),
reexports.map(({name, as}) => createExportSpecifier(name, as))),
/* moduleSpecifier */ createLiteral(exportedFilePath)));
}
@@ -350,7 +352,7 @@ export class NodeEmitterVisitor implements StatementVisitor, ExpressionVisitor {
stmt,
ts.createExportDeclaration(
/*decorators*/ undefined, /*modifiers*/ undefined,
ts.createNamedExports([ts.createExportSpecifier(stmt.name, stmt.name)])));
ts.createNamedExports([createExportSpecifier(stmt.name, stmt.name)])));
return [tsVarStmt, exportStmt];
}
return this.postProcess(stmt, ts.createVariableStatement(this.getModifiers(stmt), varDeclList));
@@ -751,22 +753,6 @@ export class NodeEmitterVisitor implements StatementVisitor, ExpressionVisitor {
}
}
function getMethodName(methodRef: {name: string|null; builtin: BuiltinMethod | null}): string {
if (methodRef.name) {
return methodRef.name;
} else {
switch (methodRef.builtin) {
case BuiltinMethod.Bind:
return 'bind';
case BuiltinMethod.ConcatArray:
return 'concat';
case BuiltinMethod.SubscribeObservable:
return 'subscribe';
}
}
throw new Error('Unexpected method reference form');
}
function modifierFromModifier(modifier: StmtModifier): ts.Modifier {
switch (modifier) {
case StmtModifier.Exported:
@@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/
import ts from 'typescript';
import {compareVersions} from './diagnostics/typescript_version';
/**
@@ -25,7 +26,7 @@ const MIN_TS_VERSION = '4.4.2';
* Note: this check is disabled in g3, search for
* `angularCompilerOptions.disableTypeScriptVersionCheck` config param value in g3.
*/
const MAX_TS_VERSION = '4.5.0';
const MAX_TS_VERSION = '4.6.0';
/**
* The currently used version of TypeScript, which can be adjusted for testing purposes using
@@ -67,7 +67,7 @@ export class Host implements ts.LanguageServiceHost {
if (this.overrides.has(fileName)) {
return this.overrides.get(fileName);
}
if (/lib(.*)\.d\.ts/.test(fileName)) {
if (/^lib(.*)\.d\.ts/.test(fileName)) {
const libDirPath = path.dirname(ts.getDefaultLibFilePath(this.getCompilationSettings()));
const libPath = path.join(libDirPath, fileName);
return fs.readFileSync(libPath, 'utf8');
+89 -7
View File
@@ -2380,7 +2380,8 @@ function allTests(os: string) {
.toBe('This type is not supported as injection token.');
});
it('should report an error when using a type-only import as injection token', () => {
it('should report an error when using a symbol from a type-only import clause as injection token',
() => {
env.tsconfig({strictInjectionParameters: true});
env.write(`types.ts`, `
export class TypeOnly {}
@@ -2411,6 +2412,38 @@ function allTests(os: string) {
.toBe('The type-only import occurs here.');
});
it('should report an error when using a symbol from a type-only import specifier as injection token',
() => {
env.tsconfig({strictInjectionParameters: true});
env.write(`types.ts`, `
export class TypeOnly {}
`);
env.write(`test.ts`, `
import {Injectable} from '@angular/core';
import {type TypeOnly} from './types';
@Injectable()
export class MyService {
constructor(param: TypeOnly) {}
}
`);
const diags = env.driveDiagnostics();
expect(diags.length).toBe(1);
expect(ts.flattenDiagnosticMessageText(diags[0].messageText, '\n'))
.toBe(
`No suitable injection token for parameter 'param' of class 'MyService'.\n` +
` Consider changing the type-only import to a regular import, ` +
`or use the @Inject decorator to specify an injection token.`);
expect(diags[0].relatedInformation!.length).toBe(2);
expect(diags[0].relatedInformation![0].messageText)
.toBe(
'This type is imported using a type-only import, ' +
'which prevents it from being usable as an injection token.');
expect(diags[0].relatedInformation![1].messageText)
.toBe('The type-only import occurs here.');
});
it('should report an error when using a primitive type as injection token', () => {
env.tsconfig({strictInjectionParameters: true});
env.write(`test.ts`, `
@@ -4770,12 +4803,14 @@ function allTests(os: string) {
() => {
env.write(`types.ts`, `
export default class {}
export class TypeOnly {}
export class TypeOnlyOne {}
export class TypeOnlyTwo {}
`);
env.write(`test.ts`, `
import {Component, Inject, Injectable} from '@angular/core';
import type DefaultImport from './types';
import type {TypeOnly} from './types';
import type {TypeOnlyOne} from './types';
import {type TypeOnlyTwo} from './types';
import type * as types from './types';
@Component({
@@ -4784,9 +4819,10 @@ function allTests(os: string) {
})
export class SomeComp {
constructor(
@Inject('token') namedImport: TypeOnly,
@Inject('token') namedImport: TypeOnlyOne,
@Inject('token') defaultImport: DefaultImport,
@Inject('token') namespacedImport: types.TypeOnly,
@Inject('token') namespacedImport: types.TypeOnlyOne,
@Inject('token') typeOnlySpecifier: TypeOnlyTwo,
) {}
}
`);
@@ -4798,7 +4834,9 @@ function allTests(os: string) {
// Default type-only import should not be emitted
expect(jsContents).not.toContain('DefaultImport');
// Named type-only import should not be emitted
expect(jsContents).not.toContain('TypeOnly');
expect(jsContents).not.toContain('TypeOnlyOne');
// Symbols from type-only specifiers should not be emitted
expect(jsContents).not.toContain('TypeOnlyTwo');
// The parameter type in class metadata should be undefined
expect(jsContents).toMatch(setClassMetadataRegExp('type: undefined'));
});
@@ -4950,7 +4988,7 @@ function allTests(os: string) {
expect(jsContents).not.toContain('setComponentScope');
});
it('should not consider type-only imports during cycle detection', () => {
it('should not consider type-only import clauses during cycle detection', () => {
env.write('test.ts', `
import {NgModule} from '@angular/core';
import {ACmp} from './a';
@@ -4985,6 +5023,50 @@ function allTests(os: string) {
expect(jsContents).not.toContain('setComponentScope');
});
it('should not consider import clauses where all the specifiers are type-only during cycle detection',
() => {
env.write('test.ts', `
import {NgModule} from '@angular/core';
import {SharedOne, SharedTwo} from './shared';
import {CyclicCmp} from './cyclic';
@NgModule({declarations: [SharedOne, SharedTwo, CyclicCmp]})
export class Module {}
`);
env.write('shared.ts', `
import {Component} from '@angular/core';
@Component({
selector: 'shared-one-cmp',
template: '<cyclic-cmp></cyclic-cmp>',
})
export class SharedOne {}
@Component({
selector: 'shared-two-cmp',
template: '<cyclic-cmp></cyclic-cmp>',
})
export class SharedTwo {}
`);
env.write('cyclic.ts', `
import {Component} from '@angular/core';
import {type SharedOne, type SharedTwo} from './shared';
@Component({
selector: 'cyclic-cmp',
template: 'does not use shared components',
})
export class CyclicCmp {
one: SharedOne;
two: SharedTwo;
}
`);
env.driveMain();
const jsContents = env.getContents('test.js');
expect(jsContents).not.toContain('setComponentScope');
});
it('should only pass components actually used to setComponentScope', () => {
env.write('test.ts', `
import {Component, NgModule} from '@angular/core';
@@ -9,6 +9,8 @@
import {dirname, resolve} from 'path';
import ts from 'typescript';
const PARSED_TS_VERSION = parseFloat(ts.versionMajorMinor);
/** Update recorder for managing imports. */
export interface ImportManagerUpdateRecorder {
addNewImport(start: number, importText: string): void;
@@ -151,7 +153,7 @@ export class ImportManager {
undefined, undefined,
ts.createImportClause(
undefined,
ts.createNamedImports([ts.createImportSpecifier(
ts.createNamedImports([createImportSpecifier(
needsGeneratedUniqueName ? propertyIdentifier : undefined, identifier)])),
ts.createStringLiteral(moduleName));
} else {
@@ -190,7 +192,7 @@ export class ImportManager {
const newNamedBindings = ts.updateNamedImports(
namedBindings,
namedBindings.elements.concat(expressions.map(
({propertyName, importName}) => ts.createImportSpecifier(propertyName, importName))));
({propertyName, importName}) => createImportSpecifier(propertyName, importName))));
const newNamedBindingsText =
this.printer.printNode(ts.EmitHint.Unspecified, newNamedBindings, sourceFile);
@@ -257,3 +259,20 @@ export class ImportManager {
return commentRanges[commentRanges.length - 1]!.end;
}
}
/**
* Backwards-compatible version of `ts.createImportSpecifier`
* to handle a breaking change between 4.4 and 4.5.
*/
export function createImportSpecifier(
propertyName: string|ts.Identifier|undefined, name: string|ts.Identifier,
isTypeOnly = false): ts.ImportSpecifier {
return PARSED_TS_VERSION > 4.4 ?
// TODO(crisbeto): the function is cast to `any` here since g3 is still on TS 4.4.
// Should be cleaned up when g3 has been updated.
(ts.createImportSpecifier as any)(isTypeOnly, propertyName, name) :
// TODO(crisbeto): backwards-compatibility layer for TS 4.4.
// Should be cleaned up when we drop support for it.
(ts.createImportSpecifier as any)(propertyName, name);
}
@@ -14,6 +14,8 @@ export type Import = {
node: ts.ImportDeclaration
};
const PARSED_TS_VERSION = parseFloat(ts.versionMajorMinor);
/** Gets import information about the specified identifier by using the Type checker. */
export function getImportOfIdentifier(typeChecker: ts.TypeChecker, node: ts.Identifier): Import|
null {
@@ -98,13 +100,21 @@ export function replaceImport(
return node;
}
const importPropertyName =
existingImportNode.propertyName ? ts.createIdentifier(newImportName) : undefined;
const importName = existingImportNode.propertyName ? existingImportNode.name :
ts.createIdentifier(newImportName);
return ts.updateNamedImports(node, [
...node.elements.filter(current => current !== existingImportNode),
// Create a new import while trying to preserve the alias of the old one.
ts.createImportSpecifier(
existingImportNode.propertyName ? ts.createIdentifier(newImportName) : undefined,
existingImportNode.propertyName ? existingImportNode.name :
ts.createIdentifier(newImportName))
PARSED_TS_VERSION > 4.4 ?
// TODO(crisbeto): the function is cast to `any` here since g3 is still on TS 4.4.
// Should be cleaned up when g3 has been updated.
(ts.createImportSpecifier as any)(false, importPropertyName, importName) :
// TODO(crisbeto): backwards-compatibility layer for TS 4.4.
// Should be cleaned up when we drop support for it.
(ts.createImportSpecifier as any)(importPropertyName, importName)
]);
}
+1 -1
View File
@@ -19,7 +19,7 @@
"mocha": "^9.0.0",
"mock-require": "3.0.3",
"promises-aplus-tests": "^2.1.2",
"typescript": "~4.4.2"
"typescript": "~4.5.2"
},
"scripts": {
"closuretest": "./scripts/closure/closure_compiler.sh",
+1 -1
View File
@@ -14,6 +14,6 @@
"zone.js": "file:../../../../dist/bin/packages/zone.js/npm_package"
},
"devDependencies": {
"typescript": "~4.4.2"
"typescript": "~4.5.2"
}
}
+4 -4
View File
@@ -3983,10 +3983,10 @@ typedarray-to-buffer@^3.1.5:
dependencies:
is-typedarray "^1.0.0"
typescript@~4.4.2:
version "4.4.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.4.tgz#2cd01a1a1f160704d3101fd5a58ff0f9fcb8030c"
integrity sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==
typescript@~4.5.2:
version "4.5.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.2.tgz#8ac1fba9f52256fdb06fb89e4122fa6a346c2998"
integrity sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==
unbox-primitive@^1.0.1:
version "1.0.1"
+5
View File
@@ -14104,6 +14104,11 @@ typescript@^3.9.5, typescript@^3.9.7:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8"
integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==
typescript@~4.5.2:
version "4.5.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.2.tgz#8ac1fba9f52256fdb06fb89e4122fa6a346c2998"
integrity sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==
ua-parser-js@^0.7.21, ua-parser-js@^0.7.30:
version "0.7.31"
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.31.tgz#649a656b191dffab4f21d5e053e27ca17cbff5c6"