From 9886b1b198d16b013ebc957ab0d4be1e46ae3f54 Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Mon, 10 Sep 2018 16:20:16 +0200 Subject: [PATCH] refactor(schematics): avoid typescript version conflicts (#12989) The Angular Material schematics parse TypeScript sources files and pass the AST to the `@schematics/angular` package which defined an explicit dependency on `typescript`. Since we currently just always require the flattened `typescript` dependency (`node_modules/typescript`), there could be either no TypeScript version or a different TypeScript version that causes the AST operations of the `@schematics/angular` utility functions to not work properly (e.g. different `SyntaxKind` ids) We should primarily try to load the same TypeScript version that has been shipped with the `@schematics/angular`. If that one couldn't be found, fall back to the top level `typescript` version. Includes workaround for Bazel ts module resolution --- .circleci/config.yml | 6 +++- src/lib/schematics/utils/ast.ts | 10 +++--- src/lib/schematics/utils/build-component.ts | 32 ++++++++++-------- .../utils/version-agnostic-typescript.ts | 33 +++++++++++++++++++ 4 files changed, 62 insertions(+), 19 deletions(-) create mode 100644 src/lib/schematics/utils/version-agnostic-typescript.ts diff --git a/.circleci/config.yml b/.circleci/config.yml index ffa93b531e1..40878872875 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -47,7 +47,11 @@ jobs: <<: *post_checkout - restore_cache: key: *cache_key - - run: echo "Temporarily disabled until Bazel setup can be fixed" + + - run: bazel run @nodejs//:npm install + # TODO(jelbourn): Update this command to run all tests if the Bazel issues have been fixed. + - run: bazel test src/lib/schematics:unit_tests + - save_cache: key: *cache_key paths: diff --git a/src/lib/schematics/utils/ast.ts b/src/lib/schematics/utils/ast.ts index 5f8d010deda..06cb9b14cf2 100644 --- a/src/lib/schematics/utils/ast.ts +++ b/src/lib/schematics/utils/ast.ts @@ -13,12 +13,11 @@ import {InsertChange} from '@schematics/angular/utility/change'; import {getWorkspace, WorkspaceProject} from '@schematics/angular/utility/config'; import {findModuleFromOptions as internalFindModule} from '@schematics/angular/utility/find-module'; import {getAppModulePath} from '@schematics/angular/utility/ng-ast-utils'; -import * as ts from 'typescript'; import {getProjectMainFile} from './project-main-file'; - +import {ts} from './version-agnostic-typescript'; /** Reads file given path and returns TypeScript source file. */ -export function getSourceFile(host: Tree, path: string): ts.SourceFile { +export function getSourceFile(host: Tree, path: string) { const buffer = host.read(path); if (!buffer) { throw new SchematicsException(`Could not find file for path: ${path}`); @@ -50,8 +49,9 @@ export function addModuleImportToModule(host: Tree, modulePath: string, moduleNa throw new SchematicsException(`Module not found: ${modulePath}`); } - // TODO: cast to any, because the types for ts.SourceFile - // aren't compatible with `strictFunctionTypes`. + // TODO(devversion): Cast to any because the Bazel typescript rules seem to incorrectly resolve + // the the required TypeScript version for the @schematics/angular utility functions. Meaning + // that is a type signature mismatch at compilation which is not valid. const changes = addImportToModule(moduleSource as any, modulePath, moduleName, src); const recorder = host.beginUpdate(modulePath); diff --git a/src/lib/schematics/utils/build-component.ts b/src/lib/schematics/utils/build-component.ts index 6e2c6f64504..0c2b0927ea6 100644 --- a/src/lib/schematics/utils/build-component.ts +++ b/src/lib/schematics/utils/build-component.ts @@ -38,18 +38,17 @@ import {buildDefaultPath} from '@schematics/angular/utility/project'; import {validateHtmlSelector, validateName} from '@schematics/angular/utility/validation'; import {readFileSync} from 'fs'; import {dirname, join, resolve} from 'path'; -import * as ts from 'typescript'; import {getProjectFromWorkspace} from './get-project'; import {getDefaultComponentOptions} from './schematic-options'; +import {ts} from './version-agnostic-typescript'; -function readIntoSourceFile(host: Tree, modulePath: string): ts.SourceFile { +function readIntoSourceFile(host: Tree, modulePath: string) { const text = host.read(modulePath); if (text === null) { throw new SchematicsException(`File ${modulePath} does not exist.`); } - const sourceText = text.toString('utf-8'); - return ts.createSourceFile(modulePath, sourceText, ts.ScriptTarget.Latest, true); + return ts.createSourceFile(modulePath, text.toString('utf-8'), ts.ScriptTarget.Latest, true); } function addDeclarationToNgModule(options: ComponentOptions): Rule { @@ -68,9 +67,11 @@ function addDeclarationToNgModule(options: ComponentOptions): Rule { const relativePath = buildRelativePath(modulePath, componentPath); const classifiedName = strings.classify(`${options.name}Component`); - // TODO: cast to any, because the types for ts.SourceFile - // aren't compatible with `strictFunctionTypes`. - const declarationChanges = addDeclarationToModule(source as any, + // TODO(devversion): Cast to any because the Bazel typescript rules seem to incorrectly resolve + // the the required TypeScript version for the @schematics/angular utility functions. Meaning + // that is a type signature mismatch at compilation which is not valid. + const declarationChanges = addDeclarationToModule( + source as any, modulePath, classifiedName, relativePath); @@ -88,9 +89,12 @@ function addDeclarationToNgModule(options: ComponentOptions): Rule { const source = readIntoSourceFile(host, modulePath); const exportRecorder = host.beginUpdate(modulePath); - // TODO: cast to any, because the types for ts.SourceFile - // aren't compatible with `strictFunctionTypes`. - const exportChanges = addExportToModule(source as any, modulePath, + // TODO(devversion): Cast to any because the Bazel typescript rules seem to incorrectly resolve + // the the required TypeScript version for the @schematics/angular utility functions. Meaning + // that is a type signature mismatch at compilation which is not valid. + const exportChanges = addExportToModule( + source as any, + modulePath, strings.classify(`${options.name}Component`), relativePath); @@ -107,10 +111,12 @@ function addDeclarationToNgModule(options: ComponentOptions): Rule { const source = readIntoSourceFile(host, modulePath); const entryComponentRecorder = host.beginUpdate(modulePath); - // TODO: cast to any, because the types for ts.SourceFile - // aren't compatible with `strictFunctionTypes`. + // TODO(devversion): Cast to any because the Bazel typescript rules seem to incorrectly resolve + // the the required TypeScript version for the @schematics/angular utility functions. Meaning + // that is a type signature mismatch at compilation which is not valid. const entryComponentChanges = addEntryComponentToModule( - source as any, modulePath, + source as any, + modulePath, strings.classify(`${options.name}Component`), relativePath); diff --git a/src/lib/schematics/utils/version-agnostic-typescript.ts b/src/lib/schematics/utils/version-agnostic-typescript.ts new file mode 100644 index 00000000000..d52e90f3599 --- /dev/null +++ b/src/lib/schematics/utils/version-agnostic-typescript.ts @@ -0,0 +1,33 @@ +/** + * @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 + */ + +/** This is just a type import and won't be generated in the release output. */ +import typescript = require('@schematics/angular/node_modules/typescript'); + +/** + * This is an agnostic re-export of TypeScript. Depending on the context, this module file will + * return the TypeScript version that is being shipped within the `@schematics/angular` package, + * or fall back to the TypeScript version that has been flattened in the node modules. + * + * This is necessary because we parse TypeScript files and pass the resolved AST to the + * `@schematics/angular` package which might have a different TypeScript version installed. + */ +let ts: typeof typescript; + +try { + ts = require('@schematics/angular/node_modules/typescript'); +} catch { + try { + ts = require('typescript'); + } catch { + throw new Error('Error: Could not find TypeScript for the Angular Material schematics. ' + + 'Please report an issue on the Angular Material repository.'); + } +} + +export {ts};