From 4aa51b751bd6b59571afcdd5add3ca0ba4f4b37a Mon Sep 17 00:00:00 2001 From: JoostK Date: Thu, 24 Oct 2019 20:24:39 +0200 Subject: [PATCH] feat(ivy): verify whether TypeScript version is supported (#33377) During the creation of an Angular program in the compiler, a check is done to verify whether the version of TypeScript is considered supported, producing an error if it is not. This check was missing in the Ivy compiler, so users may have ended up running an unsupported TypeScript version inadvertently. Resolves FW-1643 PR Close #33377 --- packages/compiler-cli/src/ngtsc/program.ts | 5 ++ .../compiler-cli/src/transformers/program.ts | 46 ++------------- .../compiler-cli/src/typescript_support.ts | 59 +++++++++++++++++++ .../compiler-cli/test/ngtsc/ngtsc_spec.ts | 43 ++++++++++++++ .../test/transformers/program_spec.ts | 32 +--------- .../test/typescript_support_spec.ts | 35 +++++++++++ 6 files changed, 147 insertions(+), 73 deletions(-) create mode 100644 packages/compiler-cli/src/typescript_support.ts create mode 100644 packages/compiler-cli/test/typescript_support_spec.ts diff --git a/packages/compiler-cli/src/ngtsc/program.ts b/packages/compiler-cli/src/ngtsc/program.ts index c4914f86abc46..963d4c448cdf4 100644 --- a/packages/compiler-cli/src/ngtsc/program.ts +++ b/packages/compiler-cli/src/ngtsc/program.ts @@ -11,6 +11,7 @@ import * as ts from 'typescript'; import * as api from '../transformers/api'; import {nocollapseHack} from '../transformers/nocollapse_hack'; +import {verifySupportedTypeScriptVersion} from '../typescript_support'; import {ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, NoopReferencesRegistry, PipeDecoratorHandler, ReferencesRegistry} from './annotations'; import {BaseDefDecoratorHandler} from './annotations/src/base_def'; @@ -74,6 +75,10 @@ export class NgtscProgram implements api.Program { constructor( rootNames: ReadonlyArray, private options: api.CompilerOptions, private host: api.CompilerHost, oldProgram?: NgtscProgram) { + if (!options.disableTypeScriptVersionCheck) { + verifySupportedTypeScriptVersion(); + } + if (shouldEnablePerfTracing(options)) { this.perfTracker = PerfTracker.zeroedToNow(); this.perfRecorder = this.perfTracker; diff --git a/packages/compiler-cli/src/transformers/program.ts b/packages/compiler-cli/src/transformers/program.ts index b687dd1b498c6..a3cc752bb4619 100644 --- a/packages/compiler-cli/src/transformers/program.ts +++ b/packages/compiler-cli/src/transformers/program.ts @@ -13,9 +13,9 @@ import * as path from 'path'; import * as ts from 'typescript'; import {TypeCheckHost, translateDiagnostics} from '../diagnostics/translate_diagnostics'; -import {compareVersions} from '../diagnostics/typescript_version'; import {MetadataCollector, ModuleMetadata, createBundleIndexHost} from '../metadata'; import {NgtscProgram} from '../ngtsc/program'; +import {verifySupportedTypeScriptVersion} from '../typescript_support'; import {CompilerHost, CompilerOptions, CustomTransformers, DEFAULT_ERROR_CODE, Diagnostic, DiagnosticMessageChain, EmitFlags, LazyRoute, LibrarySummary, Program, SOURCE, TsEmitArguments, TsEmitCallback, TsMergeEmitResultsCallback} from './api'; import {CodeGenerator, TsCompilerAotCompilerTypeCheckHostAdapter, getOriginalReferences} from './compiler_host'; @@ -67,19 +67,6 @@ const defaultEmitCallback: TsEmitCallback = program.emit( targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers); -/** - * Minimum supported TypeScript version - * ∀ supported typescript version v, v >= MIN_TS_VERSION - */ -const MIN_TS_VERSION = '3.6.4'; - -/** - * Supremum of supported TypeScript versions - * ∀ supported typescript version v, v < MAX_TS_VERSION - * MAX_TS_VERSION is not considered as a supported TypeScript version - */ -const MAX_TS_VERSION = '3.7.0'; - class AngularCompilerProgram implements Program { private rootNames: string[]; private metadataCache: MetadataCache; @@ -116,7 +103,9 @@ class AngularCompilerProgram implements Program { private host: CompilerHost, oldProgram?: Program) { this.rootNames = [...rootNames]; - checkVersion(ts.version, MIN_TS_VERSION, MAX_TS_VERSION, options.disableTypeScriptVersionCheck); + if (!options.disableTypeScriptVersionCheck) { + verifySupportedTypeScriptVersion(); + } this.oldTsProgram = oldProgram ? oldProgram.getTsProgram() : undefined; if (oldProgram) { @@ -864,33 +853,6 @@ class AngularCompilerProgram implements Program { } } -/** - * Checks whether a given version ∈ [minVersion, maxVersion[ - * An error will be thrown if the following statements are simultaneously true: - * - the given version ∉ [minVersion, maxVersion[, - * - the result of the version check is not meant to be bypassed (the parameter disableVersionCheck - * is false) - * - * @param version The version on which the check will be performed - * @param minVersion The lower bound version. A valid version needs to be greater than minVersion - * @param maxVersion The upper bound version. A valid version needs to be strictly less than - * maxVersion - * @param disableVersionCheck Indicates whether version check should be bypassed - * - * @throws Will throw an error if the following statements are simultaneously true: - * - the given version ∉ [minVersion, maxVersion[, - * - the result of the version check is not meant to be bypassed (the parameter disableVersionCheck - * is false) - */ -export function checkVersion( - version: string, minVersion: string, maxVersion: string, - disableVersionCheck: boolean | undefined) { - if ((compareVersions(version, minVersion) < 0 || compareVersions(version, maxVersion) >= 0) && - !disableVersionCheck) { - throw new Error( - `The Angular Compiler requires TypeScript >=${minVersion} and <${maxVersion} but ${version} was found instead.`); - } -} export function createProgram({rootNames, options, host, oldProgram}: { rootNames: ReadonlyArray, diff --git a/packages/compiler-cli/src/typescript_support.ts b/packages/compiler-cli/src/typescript_support.ts new file mode 100644 index 0000000000000..9a4c430ba5568 --- /dev/null +++ b/packages/compiler-cli/src/typescript_support.ts @@ -0,0 +1,59 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ +import * as ts from 'typescript'; +import {compareVersions} from './diagnostics/typescript_version'; + +/** + * Minimum supported TypeScript version + * ∀ supported typescript version v, v >= MIN_TS_VERSION + */ +const MIN_TS_VERSION = '3.6.4'; + +/** + * Supremum of supported TypeScript versions + * ∀ supported typescript version v, v < MAX_TS_VERSION + * MAX_TS_VERSION is not considered as a supported TypeScript version + */ +const MAX_TS_VERSION = '3.7.0'; + +/** + * The currently used version of TypeScript, which can be adjusted for testing purposes using + * `setTypeScriptVersionForTesting` and `restoreTypeScriptVersionForTesting` below. + */ +let tsVersion = ts.version; + +export function setTypeScriptVersionForTesting(version: string): void { + tsVersion = version; +} + +export function restoreTypeScriptVersionForTesting(): void { + tsVersion = ts.version; +} + +/** + * Checks whether a given version ∈ [minVersion, maxVersion[ + * An error will be thrown if the following statements are simultaneously true: + * - the given version ∉ [minVersion, maxVersion[, + * + * @param version The version on which the check will be performed + * @param minVersion The lower bound version. A valid version needs to be greater than minVersion + * @param maxVersion The upper bound version. A valid version needs to be strictly less than + * maxVersion + * + * @throws Will throw an error if the given version ∉ [minVersion, maxVersion[ + */ +export function checkVersion(version: string, minVersion: string, maxVersion: string) { + if ((compareVersions(version, minVersion) < 0 || compareVersions(version, maxVersion) >= 0)) { + throw new Error( + `The Angular Compiler requires TypeScript >=${minVersion} and <${maxVersion} but ${version} was found instead.`); + } +} + +export function verifySupportedTypeScriptVersion(): void { + checkVersion(tsVersion, MIN_TS_VERSION, MAX_TS_VERSION); +} diff --git a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts index a38c2c3e71d93..c764535ff0a39 100644 --- a/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts +++ b/packages/compiler-cli/test/ngtsc/ngtsc_spec.ts @@ -13,6 +13,7 @@ import {ErrorCode, ngErrorCode} from '../../src/ngtsc/diagnostics'; import {absoluteFrom} from '../../src/ngtsc/file_system'; import {runInEachFileSystem} from '../../src/ngtsc/file_system/testing'; import {LazyRoute} from '../../src/ngtsc/routing'; +import {restoreTypeScriptVersionForTesting, setTypeScriptVersionForTesting} from '../../src/typescript_support'; import {loadStandardTestFiles} from '../helpers/src/mock_file_loading'; import {NgtscTestEnvironment} from './env'; @@ -4766,6 +4767,48 @@ export const Foo = Foo__PRE_R3__; }); }); + describe('disableTypeScriptVersionCheck', () => { + afterEach(() => restoreTypeScriptVersionForTesting()); + + it('produces an error when not supported and version check is enabled', () => { + setTypeScriptVersionForTesting('3.4.0'); + env.tsconfig({disableTypeScriptVersionCheck: false}); + env.write('empty.ts', ''); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(1); + expect(diags[0].messageText).toContain('but 3.4.0 was found instead'); + }); + + it('does not produce an error when supported and version check is enabled', () => { + env.tsconfig({disableTypeScriptVersionCheck: false}); + env.write('empty.ts', ''); + + // The TypeScript version is not overwritten, so the version + // that is actually used should be supported + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(0); + }); + + it('does not produce an error when not supported but version check is disabled', () => { + setTypeScriptVersionForTesting('3.4.0'); + env.tsconfig({disableTypeScriptVersionCheck: true}); + env.write('empty.ts', ''); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(0); + }); + + it('produces an error when not supported using default configuration', () => { + setTypeScriptVersionForTesting('3.4.0'); + env.write('empty.ts', ''); + + const diags = env.driveDiagnostics(); + expect(diags.length).toBe(1); + expect(diags[0].messageText).toContain('but 3.4.0 was found instead'); + }); + }); + describe('inline resources', () => { it('should process inline