feat(core): support TypeScript 4.4 (#43281)

Adds support for TypeScript 4.4. High-level overview of the changes made in this PR:

* Bumps the various packages to `typescript@4.4.2` and `tslib@2.3.0`.
* The `useUnknownInCatchVariables` compiler option has been disabled so that we don't have to cast error objects explicitly everywhere.
* TS now passes in a third argument to the `__spreadArray` call inside child class constructors. I had to update a couple of places in the runtime and ngcc to be able to pick up the calls correctly.
* TS now generates code like `(0, foo)(arg1, arg2)` for imported function calls. I had to update a few of our tests to account for it. See https://github.com/microsoft/TypeScript/pull/44624.
* Our `ngtsc` test setup calls the private `matchFiles` function from TS. I had to update our usage, because a new parameter was added.
* There was one place where we were setting the readonly `hasTrailingComma` property. I updated the usage to pass in the value when constructing the object instead.
* Some browser types were updated which meant that I had to resolve some trivial type errors.
* The downlevel decorators tranform was running into an issue where the Closure synthetic comments were being emitted twice. I've worked around it by recreating the class declaration node instead of cloning it.

PR Close #43281
This commit is contained in:
Kristiyan Kostadinov
2021-09-22 20:50:59 +02:00
committed by Alex Rickabaugh
parent cc427d18c4
commit ea61ec2562
55 changed files with 250 additions and 126 deletions
@@ -573,6 +573,7 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
*
* 1. `__spread(arguments)`
* 2. `__spreadArray([], __read(arguments))`
* 3. `__spreadArray([], __read(arguments), false)`
*
* The tslib helpers may have been emitted inline as in the above example, or they may be read
* from a namespace import.
@@ -587,8 +588,8 @@ export class Esm5ReflectionHost extends Esm2015ReflectionHost {
// `__spread(arguments)`
return call.args.length === 1 && isArgumentsIdentifier(call.args[0]);
} else if (call.helper === KnownDeclaration.TsHelperSpreadArray) {
// `__spreadArray([], __read(arguments))`
if (call.args.length !== 2) {
// `__spreadArray([], __read(arguments), false)`
if (call.args.length !== 2 && call.args.length !== 3) {
return false;
}
@@ -381,7 +381,7 @@ runInEachFileSystem(() => {
const jsContents = fs.readFile(_(`/node_modules/test-package/esm5/src/index.js`));
// Verify that the ES5 bundle does contain the expected downleveling syntax.
expect(jsContents).toContain('__spreadArray([], __read(arguments))');
expect(jsContents).toContain('__spreadArray([], __read(arguments), false)');
expect(jsContents)
.toContain(
'var ɵSubClass_BaseFactory; return function SubClass_Factory(t) { return (ɵSubClass_BaseFactory || (ɵSubClass_BaseFactory = ɵngcc0.ɵɵgetInheritedFactory(SubClass)))(t || SubClass); };');
@@ -420,7 +420,7 @@ runInEachFileSystem(() => {
const jsContents = fs.readFile(_(`/node_modules/test-package/esm5/src/index.js`));
// Verify that the ES5 bundle does contain the expected downleveling syntax.
expect(jsContents).toContain('__spreadArray([], __read(arguments))');
expect(jsContents).toContain('__spreadArray([], __read(arguments), false)');
expect(jsContents)
.toContain(
'var ɵSubClass_BaseFactory; return function SubClass_Factory(t) { return (ɵSubClass_BaseFactory || (ɵSubClass_BaseFactory = ɵngcc0.ɵɵgetInheritedFactory(SubClass)))(t || SubClass); };');
+2 -2
View File
@@ -23,12 +23,12 @@
"semver": "^7.0.0",
"source-map": "^0.6.1",
"sourcemap-codec": "^1.4.8",
"tslib": "^2.2.0",
"tslib": "^2.3.0",
"yargs": "^17.0.0"
},
"peerDependencies": {
"@angular/compiler": "0.0.0-PLACEHOLDER",
"typescript": ">=4.2.3 <4.4"
"typescript": ">=4.2.3 <4.5"
},
"repository": {
"type": "git",
@@ -69,7 +69,7 @@ runInEachFileSystem.windows = (callback: (os: string) => void) =>
export function initMockFileSystem(os: string, cwd?: AbsoluteFsPath): MockFileSystem {
const fs = createMockFileSystem(os, cwd);
setFileSystem(fs);
monkeyPatchTypeScript(os, fs);
monkeyPatchTypeScript(fs);
return fs;
}
@@ -88,11 +88,7 @@ function createMockFileSystem(os: string, cwd?: AbsoluteFsPath): MockFileSystem
}
}
function monkeyPatchTypeScript(os: string, fs: MockFileSystem) {
ts.sys.directoryExists = path => {
const absPath = fs.resolve(path);
return fs.exists(absPath) && fs.stat(absPath).isDirectory();
};
function monkeyPatchTypeScript(fs: MockFileSystem) {
ts.sys.fileExists = path => {
const absPath = fs.resolve(path);
return fs.exists(absPath) && fs.stat(absPath).isFile();
@@ -102,6 +98,7 @@ function monkeyPatchTypeScript(os: string, fs: MockFileSystem) {
ts.sys.readFile = fs.readFile.bind(fs);
ts.sys.resolvePath = fs.resolve.bind(fs);
ts.sys.writeFile = fs.writeFile.bind(fs);
ts.sys.directoryExists = directoryExists;
ts.sys.readDirectory = readDirectory;
function getDirectories(path: string): string[] {
@@ -132,21 +129,26 @@ function monkeyPatchTypeScript(os: string, fs: MockFileSystem) {
return fs.realpath(fs.resolve(path));
}
function directoryExists(path: string) {
const absPath = fs.resolve(path);
return fs.exists(absPath) && fs.stat(absPath).isDirectory();
}
// Rather than completely re-implementing we are using the `ts.matchFiles` function,
// which is internal to the `ts` namespace.
const tsMatchFiles: (
path: string, extensions: ReadonlyArray<string>|undefined,
excludes: ReadonlyArray<string>|undefined, includes: ReadonlyArray<string>|undefined,
useCaseSensitiveFileNames: boolean, currentDirectory: string, depth: number|undefined,
getFileSystemEntries: (path: string) => FileSystemEntries,
realpath: (path: string) => string) => string[] = (ts as any).matchFiles;
getFileSystemEntries: (path: string) => FileSystemEntries, realpath: (path: string) => string,
directoryExists: (path: string) => boolean) => string[] = (ts as any).matchFiles;
function readDirectory(
path: string, extensions?: ReadonlyArray<string>, excludes?: ReadonlyArray<string>,
includes?: ReadonlyArray<string>, depth?: number): string[] {
return tsMatchFiles(
path, extensions, excludes, includes, fs.isCaseSensitive(), fs.pwd(), depth,
getFileSystemEntries, realPath);
getFileSystemEntries, realPath, directoryExists);
}
}
@@ -67,8 +67,7 @@ function extractMetadataFromSingleDecorator(
for (const arg of call.arguments) {
args.push(arg);
}
const argsArrayLiteral = ts.createArrayLiteral(args);
argsArrayLiteral.elements.hasTrailingComma = true;
const argsArrayLiteral = ts.createArrayLiteral(ts.createNodeArray(args, true));
metadataProperties.push(ts.createPropertyAssignment('args', argsArrayLiteral));
}
break;
@@ -25,7 +25,7 @@ const MIN_TS_VERSION = '4.2.3';
* Note: this check is disabled in g3, search for
* `angularCompilerOptions.disableTypeScriptVersionCheck` config param value in g3.
*/
const MAX_TS_VERSION = '4.4.0';
const MAX_TS_VERSION = '4.5.0';
/**
* The currently used version of TypeScript, which can be adjusted for testing purposes using
@@ -182,7 +182,7 @@ describe('downlevel decorator transform', () => {
expect(diagnostics.length).toBe(0);
expect(output).toContain(dedent`
MyClass = tslib_1.__decorate([
MyClass = (0, tslib_1.__decorate)([
SomeUnknownDecorator()
], MyClass);
`);
@@ -197,7 +197,7 @@ describe('downlevel decorator transform', () => {
expect(diagnostics.length).toBe(0);
expect(output).toContain(dedent`
MyClass = tslib_1.__decorate([
MyClass = (0, tslib_1.__decorate)([
DecoratorBuilder().customClassDecorator
], MyClass);
`);
@@ -231,7 +231,7 @@ describe('downlevel decorator transform', () => {
expect(diagnostics.length).toBe(0);
expect(output).toContain(dedent`
tslib_1.__decorate([
(0, tslib_1.__decorate)([
SomeDecorator()
], MyDir.prototype, "disabled", void 0);
`);
@@ -428,6 +428,8 @@ describe('downlevel decorator transform', () => {
MyDir.decorators = [
{ type: core_1.Directive }
];
`);
expect(output).toContain(dedent`
/**
* @type {function(): !Array<(null|{
* type: ?,
@@ -671,8 +673,8 @@ describe('downlevel decorator transform', () => {
expect(diagnostics.length).toBe(0);
expect(output).not.toContain('MyService.decorators');
expect(output).toContain(dedent`
MyService = tslib_1.__decorate([
core_1.Injectable()
MyService = (0, tslib_1.__decorate)([
(0, core_1.Injectable)()
], MyService);
`);
});
@@ -697,8 +699,8 @@ describe('downlevel decorator transform', () => {
MyService.ctorParameters = () => [
{ type: InjectClass }
];
MyService = tslib_1.__decorate([
core_1.Injectable()
MyService = (0, tslib_1.__decorate)([
(0, core_1.Injectable)()
], MyService);
`);
});
@@ -723,8 +725,8 @@ describe('downlevel decorator transform', () => {
MyService.ctorParameters = () => [
{ type: InjectClass, decorators: [{ type: core_1.Inject, args: ['test',] }] }
];
MyService = tslib_1.__decorate([
core_1.Injectable()
MyService = (0, tslib_1.__decorate)([
(0, core_1.Injectable)()
], MyService);
`);
});
@@ -47,7 +47,7 @@ describe('inline resources transformer', () => {
expect(actual).not.toContain('templateUrl:');
expect(actual.replace(/\s+/g, ' '))
.toContain(
'Foo = __decorate([ core_1.Component({ template: "Some template", otherProp: 3 }) ], Foo)');
'Foo = __decorate([ (0, core_1.Component)({ template: "Some template", otherProp: 3 }) ], Foo)');
});
it('should allow different quotes', () => {
const actual = convert(`import {Component} from '@angular/core';
@@ -172,8 +172,6 @@ function convert(source: string) {
const context = new MockAotContext('/', {[baseFileName + '.ts']: source});
const host = new MockCompilerHost(context);
const sourceFile =
ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, /* setParentNodes */ true);
const program = ts.createProgram(
[fileName], {
module: ts.ModuleKind.CommonJS,
@@ -186,7 +184,7 @@ function convert(source: string) {
program, {loadResource, resourceNameToFileName: (u: string) => u})]
};
let result = '';
const emitResult = program.emit(
program.emit(
moduleSourceFile, (emittedFileName, data, writeByteOrderMark, onError, sourceFiles) => {
if (fileName.startsWith(moduleName)) {
result = data;