From 0fc44e0436fdcbcb43057c57ee38fadb670ac4ad Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Wed, 15 Jul 2020 12:21:04 +0200 Subject: [PATCH] feat(compiler-cli): add support for TypeScript 4.0 (#38076) With this change we add support for TypeScript 4.0 PR Close #38076 --- goldens/public-api/common/common.d.ts | 2 +- goldens/public-api/elements/elements.d.ts | 2 +- integration/BUILD.bazel | 6 ++ integration/typings_test_ts40/include-all.ts | 71 +++++++++++++++++++ integration/typings_test_ts40/package.json | 30 ++++++++ integration/typings_test_ts40/tsconfig.json | 26 +++++++ package.json | 4 +- packages/bazel/package.json | 2 +- packages/compiler-cli/package.json | 2 +- .../compiler-cli/src/metadata/evaluator.ts | 15 ++-- packages/compiler-cli/src/metadata/symbols.ts | 8 +-- .../src/ngtsc/metadata/src/util.ts | 51 +++++++------ .../src/ngtsc/reflection/src/typescript.ts | 6 +- .../src/ngtsc/shims/src/factory_generator.ts | 5 +- .../src/ngtsc/switch/src/switch.ts | 5 +- .../src/ngtsc/transform/src/alias.ts | 4 +- .../src/ngtsc/transform/src/declaration.ts | 60 ++++++++++------ .../src/ngtsc/transform/src/transform.ts | 4 +- .../src/ngtsc/transform/src/utils.ts | 5 +- .../src/ngtsc/translator/src/translator.ts | 10 ++- .../src/ngtsc/typecheck/src/type_emitter.ts | 5 ++ .../ngtsc/typecheck/test/diagnostics_spec.ts | 2 +- .../downlevel_decorators_transform.ts | 38 ++++++---- .../src/transformers/lower_expressions.ts | 16 ++--- .../compiler-cli/src/typescript_support.ts | 2 +- .../test/diagnostics/check_types_spec.ts | 2 +- .../test/ngtsc/template_typecheck_spec.ts | 34 ++++----- .../downlevel_decorators_transform_spec.ts | 2 +- .../test/transformers/program_spec.ts | 13 ++-- .../utils/tslint/tslint_html_source_file.ts | 2 +- .../elements/src/create-custom-element.ts | 3 +- .../src/typescript_symbols.ts | 2 +- .../language-service/test/ts_plugin_spec.ts | 2 +- packages/router/src/router.ts | 3 +- packages/zone.js/package.json | 2 +- tools/ts-api-guardian/package.json | 2 +- yarn.lock | 8 +-- 37 files changed, 315 insertions(+), 141 deletions(-) create mode 100644 integration/typings_test_ts40/include-all.ts create mode 100644 integration/typings_test_ts40/package.json create mode 100644 integration/typings_test_ts40/tsconfig.json diff --git a/goldens/public-api/common/common.d.ts b/goldens/public-api/common/common.d.ts index 339f0b753d..5481b3eae4 100644 --- a/goldens/public-api/common/common.d.ts +++ b/goldens/public-api/common/common.d.ts @@ -216,7 +216,7 @@ export declare class NgComponentOutlet implements OnChanges, OnDestroy { } export declare class NgForOf = NgIterable> implements DoCheck { - set ngForOf(ngForOf: (U & NgIterable) | undefined | null); + set ngForOf(ngForOf: U & NgIterable | undefined | null); set ngForTemplate(value: TemplateRef>); set ngForTrackBy(fn: TrackByFunction); get ngForTrackBy(): TrackByFunction; diff --git a/goldens/public-api/elements/elements.d.ts b/goldens/public-api/elements/elements.d.ts index 04f108ca60..baafc1693d 100644 --- a/goldens/public-api/elements/elements.d.ts +++ b/goldens/public-api/elements/elements.d.ts @@ -2,7 +2,7 @@ export declare function createCustomElement

(component: Type, config: NgE export declare abstract class NgElement extends HTMLElement { protected ngElementEventsSubscription: Subscription | null; - protected ngElementStrategy: NgElementStrategy; + protected abstract ngElementStrategy: NgElementStrategy; abstract attributeChangedCallback(attrName: string, oldValue: string | null, newValue: string, namespace?: string): void; abstract connectedCallback(): void; abstract disconnectedCallback(): void; diff --git a/integration/BUILD.bazel b/integration/BUILD.bazel index 3e43c6ef34..a58be0c1d5 100644 --- a/integration/BUILD.bazel +++ b/integration/BUILD.bazel @@ -85,6 +85,12 @@ INTEGRATION_TESTS = { # root @npm//typescript package. "pinned_npm_packages": ["typescript"], }, + "typings_test_ts40": { + # Special case for `typings_test_ts40` test as we want to pin + # `typescript` at version 4.0.x for that test and not link to the + # root @npm//typescript package. + "pinned_npm_packages": ["typescript"], + }, } [ diff --git a/integration/typings_test_ts40/include-all.ts b/integration/typings_test_ts40/include-all.ts new file mode 100644 index 0000000000..81d94b0a01 --- /dev/null +++ b/integration/typings_test_ts40/include-all.ts @@ -0,0 +1,71 @@ +/** + * @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 platformServerTesting from '@angular/platform-server/testing'; +import * as platformWebworker from '@angular/platform-webworker'; +import * as platformWebworkerDynamic from '@angular/platform-webworker-dynamic'; +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, + platformServerTesting, + platformWebworker, + platformWebworkerDynamic, + router, + routerTesting, + routerUpgrade, + serviceWorker, + upgrade, + upgradeStatic, + upgradeTesting, +}; diff --git a/integration/typings_test_ts40/package.json b/integration/typings_test_ts40/package.json new file mode 100644 index 0000000000..c184501a81 --- /dev/null +++ b/integration/typings_test_ts40/package.json @@ -0,0 +1,30 @@ +{ + "name": "angular-integration", + "description": "Assert that users with TypeScript 4.0 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/platform-webworker": "file:../../dist/packages-dist/platform-webworker", + "@angular/platform-webworker-dynamic": "file:../../dist/packages-dist/platform-webworker-dynamic", + "@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.0.2", + "zone.js": "file:../../dist/zone.js-dist/zone.js" + }, + "scripts": { + "test": "tsc" + } +} diff --git a/integration/typings_test_ts40/tsconfig.json b/integration/typings_test_ts40/tsconfig.json new file mode 100644 index 0000000000..30e25c2209 --- /dev/null +++ b/integration/typings_test_ts40/tsconfig.json @@ -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" + ] +} diff --git a/package.json b/package.json index 5052f97979..73f4d37002 100644 --- a/package.json +++ b/package.json @@ -149,8 +149,8 @@ "terser": "^4.4.0", "tsickle": "0.38.1", "tslib": "^2.0.0", - "tslint": "6.0.0", - "typescript": "~3.9.5", + "tslint": "6.1.3", + "typescript": "~4.0.2", "xhr2": "0.2.0", "yaml": "^1.7.2", "yargs": "^15.4.1" diff --git a/packages/bazel/package.json b/packages/bazel/package.json index bcc704af05..a5828feb56 100644 --- a/packages/bazel/package.json +++ b/packages/bazel/package.json @@ -34,7 +34,7 @@ "@angular/compiler-cli": "0.0.0-PLACEHOLDER", "@bazel/typescript": ">=1.0.0", "terser": "^4.3.1", - "typescript": ">=3.9 <4.0", + "typescript": ">=3.9 <4.1", "rollup": ">=1.20.0", "rollup-plugin-commonjs": ">=9.0.0", "rollup-plugin-node-resolve": ">=4.2.0", diff --git a/packages/compiler-cli/package.json b/packages/compiler-cli/package.json index 785e0fdd3a..d1940a59d3 100644 --- a/packages/compiler-cli/package.json +++ b/packages/compiler-cli/package.json @@ -27,7 +27,7 @@ }, "peerDependencies": { "@angular/compiler": "0.0.0-PLACEHOLDER", - "typescript": ">=3.9 <4.0" + "typescript": ">=3.9 <4.1" }, "engines": { "node": ">=10.0" diff --git a/packages/compiler-cli/src/metadata/evaluator.ts b/packages/compiler-cli/src/metadata/evaluator.ts index f064afea73..a7afb4e01d 100644 --- a/packages/compiler-cli/src/metadata/evaluator.ts +++ b/packages/compiler-cli/src/metadata/evaluator.ts @@ -476,13 +476,16 @@ export class Evaluator { return recordEntry(typeReference, node); case ts.SyntaxKind.UnionType: const unionType = node; - // Remove null and undefined from the list of unions. - const references = unionType.types - .filter( - n => n.kind != ts.SyntaxKind.NullKeyword && - n.kind != ts.SyntaxKind.UndefinedKeyword) - .map(n => this.evaluateNode(n)); + // TODO(alan-agius4): remove `n.kind !== ts.SyntaxKind.NullKeyword` when + // TS 3.9 support is dropped. In TS 4.0 NullKeyword is a child of LiteralType. + const references = + unionType.types + .filter( + n => n.kind !== ts.SyntaxKind.NullKeyword && + n.kind !== ts.SyntaxKind.UndefinedKeyword && + !(ts.isLiteralTypeNode(n) && n.literal.kind === ts.SyntaxKind.NullKeyword)) + .map(n => this.evaluateNode(n)); // The remmaining reference must be the same. If two have type arguments consider them // different even if the type arguments are the same. diff --git a/packages/compiler-cli/src/metadata/symbols.ts b/packages/compiler-cli/src/metadata/symbols.ts index 262056014c..21400f45bb 100644 --- a/packages/compiler-cli/src/metadata/symbols.ts +++ b/packages/compiler-cli/src/metadata/symbols.ts @@ -62,8 +62,8 @@ export class Symbols { // even if the `SourceFile` was not type checked (which looks for `SourceFile` // in the parent chain). This doesn't damage the node as the binder unconditionally // sets the parent. - externalReference.expression.parent = externalReference; - externalReference.parent = this.sourceFile as any; + (externalReference.expression.parent as ts.Node) = externalReference; + (externalReference.parent as ts.Node) = this.sourceFile; } const from = stripQuotes(externalReference.expression.getText()); symbols.set( @@ -83,8 +83,8 @@ export class Symbols { } if (!importDecl.moduleSpecifier.parent) { // See note above in the `ImportEqualDeclaration` case. - importDecl.moduleSpecifier.parent = importDecl; - importDecl.parent = this.sourceFile; + (importDecl.moduleSpecifier.parent as ts.Node) = importDecl; + (importDecl.parent as ts.Node) = this.sourceFile; } const from = stripQuotes(importDecl.moduleSpecifier.getText()); if (importDecl.importClause.name) { diff --git a/packages/compiler-cli/src/ngtsc/metadata/src/util.ts b/packages/compiler-cli/src/ngtsc/metadata/src/util.ts index dbb7e77e94..2f465dd977 100644 --- a/packages/compiler-cli/src/ngtsc/metadata/src/util.ts +++ b/packages/compiler-cli/src/ngtsc/metadata/src/util.ts @@ -20,22 +20,26 @@ export function extractReferencesFromType( if (!ts.isTupleTypeNode(def)) { return []; } - return def.elementTypes.map(element => { - if (!ts.isTypeQueryNode(element)) { - throw new Error(`Expected TypeQueryNode: ${nodeDebugInfo(element)}`); - } - const type = element.exprName; - const {node, from} = reflectTypeEntityToDeclaration(type, checker); - if (!isNamedClassDeclaration(node)) { - throw new Error(`Expected named ClassDeclaration: ${nodeDebugInfo(node)}`); - } - const specifier = (from !== null && !from.startsWith('.') ? from : ngModuleImportedFrom); - if (specifier !== null) { - return new Reference(node, {specifier, resolutionContext}); - } else { - return new Reference(node); - } - }); + + // TODO(alan-agius4): remove `def.elementTypes` and casts when TS 3.9 support is dropped and G3 is + // using TS 4.0. + return (((def as any).elements || (def as any).elementTypes) as ts.NodeArray) + .map(element => { + if (!ts.isTypeQueryNode(element)) { + throw new Error(`Expected TypeQueryNode: ${nodeDebugInfo(element)}`); + } + const type = element.exprName; + const {node, from} = reflectTypeEntityToDeclaration(type, checker); + if (!isNamedClassDeclaration(node)) { + throw new Error(`Expected named ClassDeclaration: ${nodeDebugInfo(node)}`); + } + const specifier = (from !== null && !from.startsWith('.') ? from : ngModuleImportedFrom); + if (specifier !== null) { + return new Reference(node, {specifier, resolutionContext}); + } else { + return new Reference(node); + } + }); } export function readStringType(type: ts.TypeNode): string|null { @@ -69,12 +73,15 @@ export function readStringArrayType(type: ts.TypeNode): string[] { return []; } const res: string[] = []; - type.elementTypes.forEach(el => { - if (!ts.isLiteralTypeNode(el) || !ts.isStringLiteral(el.literal)) { - return; - } - res.push(el.literal.text); - }); + // TODO(alan-agius4): remove `def.elementTypes` and casts when TS 3.9 support is dropped and G3 is + // using TS 4.0. + (((type as any).elements || (type as any).elementTypes) as ts.NodeArray) + .forEach(el => { + if (!ts.isLiteralTypeNode(el) || !ts.isStringLiteral(el.literal)) { + return; + } + res.push(el.literal.text); + }); return res; } diff --git a/packages/compiler-cli/src/ngtsc/reflection/src/typescript.ts b/packages/compiler-cli/src/ngtsc/reflection/src/typescript.ts index fa8626c78a..85be99477c 100644 --- a/packages/compiler-cli/src/ngtsc/reflection/src/typescript.ts +++ b/packages/compiler-cli/src/ngtsc/reflection/src/typescript.ts @@ -64,7 +64,11 @@ export class TypeScriptReflectionHost implements ReflectionHost { // optional tokes that don't have providers. if (typeNode && ts.isUnionTypeNode(typeNode)) { let childTypeNodes = typeNode.types.filter( - childTypeNode => childTypeNode.kind !== ts.SyntaxKind.NullKeyword); + // TODO(alan-agius4): remove `childTypeNode.kind !== ts.SyntaxKind.NullKeyword` when + // TS 3.9 support is dropped. In TS 4.0 NullKeyword is a child of LiteralType. + childTypeNode => childTypeNode.kind !== ts.SyntaxKind.NullKeyword && + !(ts.isLiteralTypeNode(childTypeNode) && + childTypeNode.literal.kind === ts.SyntaxKind.NullKeyword)); if (childTypeNodes.length === 1) { typeNode = childTypeNodes[0]; diff --git a/packages/compiler-cli/src/ngtsc/shims/src/factory_generator.ts b/packages/compiler-cli/src/ngtsc/shims/src/factory_generator.ts index 4ee78ec513..6746a8e0d2 100644 --- a/packages/compiler-cli/src/ngtsc/shims/src/factory_generator.ts +++ b/packages/compiler-cli/src/ngtsc/shims/src/factory_generator.ts @@ -128,8 +128,6 @@ function transformFactorySourceFile( const {moduleSymbols, sourceFilePath} = factoryMap.get(file.fileName)!; - file = ts.getMutableClone(file); - // Not every exported factory statement is valid. They were generated before the program was // analyzed, and before ngtsc knew which symbols were actually NgModules. factoryMap contains // that knowledge now, so this transform filters the statement list and removes exported factories @@ -221,7 +219,8 @@ function transformFactorySourceFile( // satisfy closure compiler. transformedStatements.push(nonEmptyExport); } - file.statements = ts.createNodeArray(transformedStatements); + + file = ts.updateSourceFileNode(file, transformedStatements); // If any imports to @angular/core were detected and rewritten (which happens when compiling // @angular/core), go through the SourceFile and rewrite references to symbols imported from core. diff --git a/packages/compiler-cli/src/ngtsc/switch/src/switch.ts b/packages/compiler-cli/src/ngtsc/switch/src/switch.ts index 9697b6a53e..aa8897d74c 100644 --- a/packages/compiler-cli/src/ngtsc/switch/src/switch.ts +++ b/packages/compiler-cli/src/ngtsc/switch/src/switch.ts @@ -42,8 +42,7 @@ function flipIvySwitchInFile(sf: ts.SourceFile): ts.SourceFile { // Only update the statements in the SourceFile if any have changed. if (newStatements !== undefined) { - sf = ts.getMutableClone(sf); - sf.statements = ts.createNodeArray(newStatements); + return ts.updateSourceFileNode(sf, newStatements); } return sf; } @@ -105,7 +104,7 @@ function flipIvySwitchesInVariableStatement( // Find the post-switch variable identifier. If one can't be found, it's an error. This is // reported as a thrown error and not a diagnostic as transformers cannot output diagnostics. - let newIdentifier = findPostSwitchIdentifier(statements, postSwitchName); + const newIdentifier = findPostSwitchIdentifier(statements, postSwitchName); if (newIdentifier === null) { throw new Error(`Unable to find identifier ${postSwitchName} in ${ stmt.getSourceFile().fileName} for the Ivy switch.`); diff --git a/packages/compiler-cli/src/ngtsc/transform/src/alias.ts b/packages/compiler-cli/src/ngtsc/transform/src/alias.ts index ea21926e68..a5ba308a92 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/alias.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/alias.ts @@ -28,9 +28,7 @@ export function aliasTransformFactory(exportStatements: Map canEmitTypeReference(type), visitArrayTypeNode: type => canEmitTypeWorker(type.elementType), visitKeywordType: () => true, + visitLiteralType: () => true, visitOtherType: () => false, }); } @@ -111,6 +112,7 @@ export class TypeEmitter { visitTypeReferenceNode: type => this.emitTypeReference(type), visitArrayTypeNode: type => ts.updateArrayTypeNode(type, this.emitType(type.elementType)), visitKeywordType: type => type, + visitLiteralType: type => type, visitOtherType: () => { throw new Error('Unable to emit a complex type'); }, @@ -159,6 +161,7 @@ interface TypeEmitterVisitor { visitTypeReferenceNode(type: ts.TypeReferenceNode): R; visitArrayTypeNode(type: ts.ArrayTypeNode): R; visitKeywordType(type: ts.KeywordTypeNode): R; + visitLiteralType(type: ts.LiteralTypeNode): R; visitOtherType(type: ts.TypeNode): R; } @@ -167,6 +170,8 @@ function visitTypeNode(type: ts.TypeNode, visitor: TypeEmitterVisitor): R return visitor.visitTypeReferenceNode(type); } else if (ts.isArrayTypeNode(type)) { return visitor.visitArrayTypeNode(type); + } else if (ts.isLiteralTypeNode(type)) { + return visitor.visitLiteralType(type); } switch (type.kind) { diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/diagnostics_spec.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/diagnostics_spec.ts index 4a19284ea1..5838f2fc8e 100644 --- a/packages/compiler-cli/src/ngtsc/typecheck/test/diagnostics_spec.ts +++ b/packages/compiler-cli/src/ngtsc/typecheck/test/diagnostics_spec.ts @@ -456,7 +456,7 @@ class TestComponent { }`); expect(messages).toEqual( - [`TestComponent.html(1, 15): Type '2' is not assignable to type 'string'.`]); + [`TestComponent.html(1, 15): Type 'number' is not assignable to type 'string'.`]); }); }); }); diff --git a/packages/compiler-cli/src/transformers/downlevel_decorators_transform.ts b/packages/compiler-cli/src/transformers/downlevel_decorators_transform.ts index 87bbd085fb..97a0465b0a 100644 --- a/packages/compiler-cli/src/transformers/downlevel_decorators_transform.ts +++ b/packages/compiler-cli/src/transformers/downlevel_decorators_transform.ts @@ -140,10 +140,16 @@ function createCtorParametersClassPropertyType(): ts.TypeNode { undefined), ])), undefined)); - return ts.createFunctionTypeNode( - undefined, [], - ts.createArrayTypeNode( - ts.createUnionTypeNode([ts.createTypeLiteralNode(typeElements), ts.createNull()]))); + + // TODO(alan-agius4): Remove when we no longer support TS 3.9 + const nullLiteral = ts.createNull() as any; + const nullType = ts.versionMajorMinor.charAt(0) === '4' ? + ts.createLiteralTypeNode(nullLiteral as any) : + nullLiteral; + return ts.createFunctionTypeNode(undefined, [], ts.createArrayTypeNode(ts.createUnionTypeNode([ + ts.createTypeLiteralNode(typeElements), + nullType, + ]))); } /** @@ -287,8 +293,13 @@ function typeReferenceToExpression( // Ignore any generic types, just return the base type. return entityNameToExpression(typeRef.typeName); case ts.SyntaxKind.UnionType: + // TODO(alan-agius4): remove `t.kind !== ts.SyntaxKind.NullKeyword` when + // TS 3.9 support is dropped. In TS 4.0 NullKeyword is a child of LiteralType. const childTypeNodes = - (node as ts.UnionTypeNode).types.filter(t => t.kind !== ts.SyntaxKind.NullKeyword); + (node as ts.UnionTypeNode) + .types.filter( + t => t.kind !== ts.SyntaxKind.NullKeyword && + !(ts.isLiteralTypeNode(t) && t.literal.kind === ts.SyntaxKind.NullKeyword)); return childTypeNodes.length === 1 ? typeReferenceToExpression(entityNameToExpression, childTypeNodes[0]) : undefined; @@ -434,7 +445,7 @@ export function getDownlevelDecoratorsTransform( const name = (element.name as ts.Identifier).text; const mutable = ts.getMutableClone(element); - mutable.decorators = decoratorsToKeep.length ? + (mutable as any).decorators = decoratorsToKeep.length ? ts.setTextRange(ts.createNodeArray(decoratorsToKeep), mutable.decorators) : undefined; return [name, mutable, toLower]; @@ -551,8 +562,6 @@ export function getDownlevelDecoratorsTransform( } } - const newClassDeclaration = ts.getMutableClone(classDecl); - if (decoratorsToLower.length) { newMembers.push(createDecoratorClassProperty(decoratorsToLower)); } @@ -567,12 +576,13 @@ export function getDownlevelDecoratorsTransform( if (decoratedProperties.size) { newMembers.push(createPropDecoratorsClassProperty(diagnostics, decoratedProperties)); } - newClassDeclaration.members = ts.setTextRange( - ts.createNodeArray(newMembers, newClassDeclaration.members.hasTrailingComma), - classDecl.members); - newClassDeclaration.decorators = - decoratorsToKeep.length ? ts.createNodeArray(decoratorsToKeep) : undefined; - return newClassDeclaration; + + const members = ts.setTextRange( + ts.createNodeArray(newMembers, classDecl.members.hasTrailingComma), classDecl.members); + + return ts.updateClassDeclaration( + classDecl, decoratorsToKeep.length ? decoratorsToKeep : undefined, classDecl.modifiers, + classDecl.name, classDecl.typeParameters, classDecl.heritageClauses, members); } /** diff --git a/packages/compiler-cli/src/transformers/lower_expressions.ts b/packages/compiler-cli/src/transformers/lower_expressions.ts index 15b24c3469..8a5dc858e1 100644 --- a/packages/compiler-cli/src/transformers/lower_expressions.ts +++ b/packages/compiler-cli/src/transformers/lower_expressions.ts @@ -167,14 +167,13 @@ function transformSourceFile( newStatements = tmpStatements; } - // Note: We cannot use ts.updateSourcefile here as - // it does not work well with decorators. - // See https://github.com/Microsoft/TypeScript/issues/17384 - const newSf = ts.getMutableClone(sourceFile); + + const newSf = ts.updateSourceFileNode( + sourceFile, ts.setTextRange(ts.createNodeArray(newStatements), sourceFile.statements)); if (!(sourceFile.flags & ts.NodeFlags.Synthesized)) { - newSf.flags &= ~ts.NodeFlags.Synthesized; + (newSf.flags as ts.NodeFlags) &= ~ts.NodeFlags.Synthesized; } - newSf.statements = ts.setTextRange(ts.createNodeArray(newStatements), sourceFile.statements); + return newSf; } @@ -209,11 +208,6 @@ export interface RequestsMap { getRequests(sourceFile: ts.SourceFile): RequestLocationMap; } -interface MetadataAndLoweringRequests { - metadata: ModuleMetadata|undefined; - requests: RequestLocationMap; -} - function isEligibleForLowering(node: ts.Node|undefined): boolean { if (node) { switch (node.kind) { diff --git a/packages/compiler-cli/src/typescript_support.ts b/packages/compiler-cli/src/typescript_support.ts index 3adef9b986..c3e4165036 100644 --- a/packages/compiler-cli/src/typescript_support.ts +++ b/packages/compiler-cli/src/typescript_support.ts @@ -19,7 +19,7 @@ const MIN_TS_VERSION = '3.9.2'; * ∀ supported typescript version v, v < MAX_TS_VERSION * MAX_TS_VERSION is not considered as a supported TypeScript version */ -const MAX_TS_VERSION = '4.0.0'; +const MAX_TS_VERSION = '4.1.0'; /** * The currently used version of TypeScript, which can be adjusted for testing purposes using diff --git a/packages/compiler-cli/test/diagnostics/check_types_spec.ts b/packages/compiler-cli/test/diagnostics/check_types_spec.ts index 68d3f28114..13fff9ad35 100644 --- a/packages/compiler-cli/test/diagnostics/check_types_spec.ts +++ b/packages/compiler-cli/test/diagnostics/check_types_spec.ts @@ -787,7 +787,7 @@ describe('ng type checker', () => { it('should report an invalid call to a pipe', () => { rejectOnlyWithFullTemplateTypeCheck( '

{{"hello" | aPipe}}
', - `Argument of type '"hello"' is not assignable to parameter of type 'number'.`, '0:5'); + `Argument of type 'string' is not assignable to parameter of type 'number'.`, '0:5'); }); it('should report an invalid property on an exportAs directive', () => { rejectOnlyWithFullTemplateTypeCheck( diff --git a/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts b/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts index dc8dd1af02..63c6a168eb 100644 --- a/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts +++ b/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts @@ -136,7 +136,7 @@ export declare class AnimationEvent { const diags = env.driveDiagnostics(); expect(diags.length).toBe(1); - expect(diags[0].messageText).toEqual(`Type '"2"' is not assignable to type 'number'.`); + expect(diags[0].messageText).toEqual(`Type 'string' is not assignable to type 'number'.`); // The reported error code should be in the TS error space, not a -99 "NG" code. expect(diags[0].code).toBeGreaterThan(0); }); @@ -168,8 +168,8 @@ export declare class AnimationEvent { const diags = env.driveDiagnostics(); expect(diags.length).toBe(2); - expect(diags[0].messageText).toEqual(`Type '"2"' is not assignable to type 'number'.`); - expect(diags[1].messageText).toEqual(`Type '"2"' is not assignable to type 'number'.`); + expect(diags[0].messageText).toEqual(`Type 'string' is not assignable to type 'number'.`); + expect(diags[1].messageText).toEqual(`Type 'string' is not assignable to type 'number'.`); }); it('should support inputs and outputs with names that are not JavaScript identifiers', () => { @@ -204,7 +204,7 @@ export declare class AnimationEvent { const diags = env.driveDiagnostics(); expect(diags.length).toBe(2); - expect(diags[0].messageText).toEqual(`Type '2' is not assignable to type 'string'.`); + expect(diags[0].messageText).toEqual(`Type 'number' is not assignable to type 'string'.`); expect(diags[1].messageText) .toEqual(`Argument of type 'string' is not assignable to parameter of type 'number'.`); }); @@ -380,7 +380,7 @@ export declare class AnimationEvent { const diags = env.driveDiagnostics(); expect(diags.length).toBe(2); - expect(diags[0].messageText).toEqual(`Type '1' is not assignable to type 'string'.`); + expect(diags[0].messageText).toEqual(`Type 'number' is not assignable to type 'string'.`); expect(diags[1].messageText) .toEqual(`Property 'invalid' does not exist on type 'TestCmp'.`); }); @@ -390,7 +390,7 @@ export declare class AnimationEvent { const diags = env.driveDiagnostics(); expect(diags.length).toBe(2); - expect(diags[0].messageText).toEqual(`Type '1' is not assignable to type 'string'.`); + expect(diags[0].messageText).toEqual(`Type 'number' is not assignable to type 'string'.`); expect(diags[1].messageText) .toEqual(`Property 'invalid' does not exist on type 'TestCmp'.`); }); @@ -716,8 +716,8 @@ export declare class AnimationEvent { const diags = env.driveDiagnostics(); expect(diags.length).toBe(2); - expect(diags[0].messageText).toEqual(`Type '""' is not assignable to type 'boolean'.`); - expect(diags[1].messageText).toEqual(`Type '"3"' is not assignable to type 'number'.`); + expect(diags[0].messageText).toEqual(`Type 'string' is not assignable to type 'boolean'.`); + expect(diags[1].messageText).toEqual(`Type 'string' is not assignable to type 'number'.`); }); it('should produce an error for text attributes when overall strictness is enabled', () => { @@ -725,8 +725,8 @@ export declare class AnimationEvent { const diags = env.driveDiagnostics(); expect(diags.length).toBe(2); - expect(diags[0].messageText).toEqual(`Type '""' is not assignable to type 'boolean'.`); - expect(diags[1].messageText).toEqual(`Type '"3"' is not assignable to type 'number'.`); + expect(diags[0].messageText).toEqual(`Type 'string' is not assignable to type 'boolean'.`); + expect(diags[1].messageText).toEqual(`Type 'string' is not assignable to type 'number'.`); }); it('should not produce an error for text attributes when not enabled', () => { @@ -1119,7 +1119,7 @@ export declare class AnimationEvent { const allErrors = [ `'does_not_exist' does not exist on type '{ name: string; }'`, `Expected 2 arguments, but got 3.`, - `Argument of type '"test"' is not assignable to parameter of type 'number'`, + `Argument of type 'string' is not assignable to parameter of type 'number'`, `Argument of type '{ name: string; }' is not assignable to parameter of type 'unknown[]'`, ]; @@ -1241,11 +1241,11 @@ export declare class AnimationEvent { const diags = env.driveDiagnostics(); expect(diags.length).toBe(3); - expect(diags[0].messageText).toBe(`Type 'true' is not assignable to type 'number'.`); + expect(diags[0].messageText).toBe(`Type 'boolean' is not assignable to type 'number'.`); expect(getSourceCodeForDiagnostic(diags[0])).toEqual('[fromAbstract]="true"'); - expect(diags[1].messageText).toBe(`Type '3' is not assignable to type 'string'.`); + expect(diags[1].messageText).toBe(`Type 'number' is not assignable to type 'string'.`); expect(getSourceCodeForDiagnostic(diags[1])).toEqual('[fromBase]="3"'); - expect(diags[2].messageText).toBe(`Type '4' is not assignable to type 'boolean'.`); + expect(diags[2].messageText).toBe(`Type 'number' is not assignable to type 'boolean'.`); expect(getSourceCodeForDiagnostic(diags[2])).toEqual('[fromChild]="4"'); }); @@ -1298,11 +1298,11 @@ export declare class AnimationEvent { const diags = env.driveDiagnostics(); expect(diags.length).toBe(3); - expect(diags[0].messageText).toBe(`Type 'true' is not assignable to type 'number'.`); + expect(diags[0].messageText).toBe(`Type 'boolean' is not assignable to type 'number'.`); expect(getSourceCodeForDiagnostic(diags[0])).toEqual('[fromAbstract]="true"'); - expect(diags[1].messageText).toBe(`Type '3' is not assignable to type 'string'.`); + expect(diags[1].messageText).toBe(`Type 'number' is not assignable to type 'string'.`); expect(getSourceCodeForDiagnostic(diags[1])).toEqual('[fromBase]="3"'); - expect(diags[2].messageText).toBe(`Type '4' is not assignable to type 'boolean'.`); + expect(diags[2].messageText).toBe(`Type 'number' is not assignable to type 'boolean'.`); expect(getSourceCodeForDiagnostic(diags[2])).toEqual('[fromChild]="4"'); }); diff --git a/packages/compiler-cli/test/transformers/downlevel_decorators_transform_spec.ts b/packages/compiler-cli/test/transformers/downlevel_decorators_transform_spec.ts index 51e316cb6b..daf5d946a0 100644 --- a/packages/compiler-cli/test/transformers/downlevel_decorators_transform_spec.ts +++ b/packages/compiler-cli/test/transformers/downlevel_decorators_transform_spec.ts @@ -584,7 +584,7 @@ describe('downlevel decorator transform', () => { const visitNode = (node: ts.Node): ts.Node => { if (ts.isClassDeclaration(node) || ts.isClassElement(node)) { const cloned = ts.getMutableClone(node); - cloned.decorators = undefined; + (cloned.decorators as undefined) = undefined; return cloned; } return ts.visitEachChild(node, visitNode, context); diff --git a/packages/compiler-cli/test/transformers/program_spec.ts b/packages/compiler-cli/test/transformers/program_spec.ts index a4186583b1..683612d723 100644 --- a/packages/compiler-cli/test/transformers/program_spec.ts +++ b/packages/compiler-cli/test/transformers/program_spec.ts @@ -492,11 +492,11 @@ describe('ng program', () => { .toBe(true); switch (checks.shouldBe) { case ShouldBe.Empty: - expect(writeData!.data).toMatch(/^(\s*\/\*([^*]|\*[^/])*\*\/\s*)?$/); + expect(writeData!.data).toMatch(/^(\s*\/\*([^*]|\*[^\/])*\*\/\s*)?$/); break; case ShouldBe.EmptyExport: expect(writeData!.data) - .toMatch(/^((\s*\/\*([^*]|\*[^/])*\*\/\s*)|(\s*export\s*{\s*}\s*;\s*)|())$/); + .toMatch(/^((\s*\/\*([^*]|\*[^\/])*\*\/\s*)|(\s*export\s*{\s*};\s*))$/m); break; case ShouldBe.NoneEmpty: expect(writeData!.data).not.toBe(''); @@ -505,12 +505,14 @@ describe('ng program', () => { } assertGenFile( - 'built/src/util.ngfactory.js', {originalFileName: 'src/util.ts', shouldBe: ShouldBe.Empty}); + 'built/src/util.ngfactory.js', + {originalFileName: 'src/util.ts', shouldBe: ShouldBe.EmptyExport}); assertGenFile( 'built/src/util.ngfactory.d.ts', {originalFileName: 'src/util.ts', shouldBe: ShouldBe.EmptyExport}); assertGenFile( - 'built/src/util.ngsummary.js', {originalFileName: 'src/util.ts', shouldBe: ShouldBe.Empty}); + 'built/src/util.ngsummary.js', + {originalFileName: 'src/util.ts', shouldBe: ShouldBe.EmptyExport}); assertGenFile( 'built/src/util.ngsummary.d.ts', {originalFileName: 'src/util.ts', shouldBe: ShouldBe.EmptyExport}); @@ -987,7 +989,8 @@ describe('ng program', () => { const errorDiags = program1.emit().diagnostics.filter(d => d.category === ts.DiagnosticCategory.Error); expect(stripAnsi(formatDiagnostics(errorDiags))) - .toContain(`src/main.ts:5:13 - error TS2322: Type '1' is not assignable to type 'string'.`); + .toContain( + `src/main.ts:5:13 - error TS2322: Type 'number' is not assignable to type 'string'.`); expect(stripAnsi(formatDiagnostics(errorDiags))) .toContain( `src/main.html:1:1 - error TS100: Property 'nonExistent' does not exist on type 'MyComp'.`); diff --git a/packages/core/schematics/utils/tslint/tslint_html_source_file.ts b/packages/core/schematics/utils/tslint/tslint_html_source_file.ts index a900a435ae..bf4ad3bf80 100644 --- a/packages/core/schematics/utils/tslint/tslint_html_source_file.ts +++ b/packages/core/schematics/utils/tslint/tslint_html_source_file.ts @@ -17,7 +17,7 @@ export function createHtmlSourceFile(filePath: string, content: string): ts.Sour // Subtract two characters because the string literal quotes are only needed for parsing // and are not part of the actual source file. - sourceFile.end = sourceFile.end - 2; + (sourceFile.end as number) = sourceFile.end - 2; // Note: This does not affect the way TSLint applies replacements for external resource files. // At the time of writing, TSLint loads files manually if the actual rule source file is not diff --git a/packages/elements/src/create-custom-element.ts b/packages/elements/src/create-custom-element.ts index ed484c1eb3..6c29a45a7d 100644 --- a/packages/elements/src/create-custom-element.ts +++ b/packages/elements/src/create-custom-element.ts @@ -45,8 +45,7 @@ export abstract class NgElement extends HTMLElement { /** * The strategy that controls how a component is transformed in a custom element. */ - // TODO(issue/24571): remove '!'. - protected ngElementStrategy!: NgElementStrategy; + protected abstract ngElementStrategy: NgElementStrategy; /** * A subscription to change, connect, and disconnect events in the custom element. */ diff --git a/packages/language-service/src/typescript_symbols.ts b/packages/language-service/src/typescript_symbols.ts index 408adff8c5..4412a1e5ac 100644 --- a/packages/language-service/src/typescript_symbols.ts +++ b/packages/language-service/src/typescript_symbols.ts @@ -849,7 +849,7 @@ function getTsTypeFromBuiltinType(builtinType: BuiltinType, ctx: TypeContext): t `Internal error, unhandled literal kind ${builtinType}:${BuiltinType[builtinType]}`); } const node = ts.createNode(syntaxKind); - node.parent = ts.createEmptyStatement(); + (node.parent as ts.Node) = ts.createEmptyStatement(); return ctx.checker.getTypeAtLocation(node); } diff --git a/packages/language-service/test/ts_plugin_spec.ts b/packages/language-service/test/ts_plugin_spec.ts index dc82a9cf58..a57b9af930 100644 --- a/packages/language-service/test/ts_plugin_spec.ts +++ b/packages/language-service/test/ts_plugin_spec.ts @@ -50,7 +50,7 @@ describe('plugin', () => { const diags = plugin.getSemanticDiagnostics(fileName); expect(diags.length).toBe(1); expect(diags[0].messageText) - .toBe(`Argument of type '"hello"' is not assignable to parameter of type 'number'.`); + .toBe(`Argument of type 'string' is not assignable to parameter of type 'number'.`); }); it('should not report TypeScript errors on tour of heroes', () => { diff --git a/packages/router/src/router.ts b/packages/router/src/router.ts index 5d05c48305..d8bc23fd30 100644 --- a/packages/router/src/router.ts +++ b/packages/router/src/router.ts @@ -33,7 +33,6 @@ import {Checks, getAllRouteGuards} from './utils/preactivation'; import {isUrlTree} from './utils/type_guards'; - /** * @description * @@ -922,7 +921,7 @@ export class Router { const {source, state, urlTree} = currentChange; const extras: NavigationExtras = {replaceUrl: true}; if (state) { - const stateCopy = {...state}; + const stateCopy = {...state} as Partial; delete stateCopy.navigationId; if (Object.keys(stateCopy).length !== 0) { extras.state = stateCopy; diff --git a/packages/zone.js/package.json b/packages/zone.js/package.json index 46ff790fef..a5ad328a58 100644 --- a/packages/zone.js/package.json +++ b/packages/zone.js/package.json @@ -16,7 +16,7 @@ "mocha": "^3.1.2", "mock-require": "3.0.3", "promises-aplus-tests": "^2.1.2", - "typescript": "^3.8.3" + "typescript": "4.0.2" }, "scripts": { "electrontest": "cd test/extra && node electron.js", diff --git a/tools/ts-api-guardian/package.json b/tools/ts-api-guardian/package.json index 9e9b6bf82d..4df0b9840e 100644 --- a/tools/ts-api-guardian/package.json +++ b/tools/ts-api-guardian/package.json @@ -33,7 +33,7 @@ "chai": "^4.1.2", "jasmine": "^3.1.0", "source-map-support": "^0.5.9", - "typescript": "~3.9.5" + "typescript": "4.0.2" }, "repository": {}, "keywords": [ diff --git a/yarn.lock b/yarn.lock index 62934308c6..6fbef7c4c0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15329,10 +15329,10 @@ typescript@~3.7.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae" integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw== -typescript@~3.9.5: - version "3.9.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.5.tgz#586f0dba300cde8be52dd1ac4f7e1009c1b13f36" - integrity sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ== +typescript@~4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.2.tgz#7ea7c88777c723c681e33bf7988be5d008d05ac2" + integrity sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ== uglify-js@^1.3.3: version "1.3.5"