Skip to content

Commit

Permalink
build: update to typescript v3.8 (angular#18789)
Browse files Browse the repository at this point in the history
Updates to TypeScript v3.8. To ensure that Angular Components is still
compatible with older supported TypeScript versions, a new integration
test has been introduced.
  • Loading branch information
devversion committed Mar 12, 2020
1 parent e3d2051 commit b4d2838
Show file tree
Hide file tree
Showing 10 changed files with 315 additions and 84 deletions.
19 changes: 19 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,23 @@ jobs:
# Run project tests with NGC and View Engine.
- run: bazel test src/... --build_tag_filters=-docs-package,-e2e --test_tag_filters=-e2e --config=view-engine --build_tests_only

# ----------------------------------------------------------------------------
# Job that runs all Bazel integration tests.
# ----------------------------------------------------------------------------
integration_tests:
<<: *job_defaults
resource_class: xlarge
environment:
GCP_DECRYPT_TOKEN: *gcp_decrypt_token
steps:
- *checkout_code
- *restore_cache
- *setup_bazel_ci_config
- *setup_bazel_remote_execution
- *setup_bazel_binary
# Integration tests run with --config=view-engine because we release with View Engine.
- run: bazel test integration/... --build_tests_only --config=view-engine

# ----------------------------------------------------------------------------
# Job that runs all Bazel tests against material-components-web@canary
# ----------------------------------------------------------------------------
Expand Down Expand Up @@ -525,6 +542,8 @@ workflows:
filters: *ignore_presubmit_branch_filter
- api_golden_checks:
filters: *ignore_presubmit_branch_filter
- integration_tests:
filters: *ignore_presubmit_branch_filter
- tests_local_browsers:
filters: *ignore_presubmit_branch_filter
- tests_browserstack:
Expand Down
5 changes: 4 additions & 1 deletion .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -241,9 +241,12 @@
# Universal app
/src/universal-app/** @jelbourn

# Integration tests
/integration/** @jelbourn @devversion

# Tooling
/.circleci/** @angular/dev-infra-components
/.yarn/** @angular/dev-infra-components
/.yarn/** @angular/dev-infra-components
/scripts/** @angular/dev-infra-components
/test/** @angular/dev-infra-components
/tools/** @angular/dev-infra-components
Expand Down
46 changes: 46 additions & 0 deletions integration/ts-compat/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package(default_visibility = ["//visibility:public"])

load("@bazel_skylib//rules:write_file.bzl", "write_file")
load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_test")
load("//integration/ts-compat:import-all-entry-points.bzl", "generate_import_all_entry_points_file")

write_file(
name = "import-all-entry-points-file",
out = "import-all-entry-points.ts",
content = [generate_import_all_entry_points_file()],
)

# List of TypeScript packages that we want to run the compatibility test against.
# The list contains NPM module names that resolve to the desired TypeScript version.
typescript_version_packages = [
"typescript-3.6",
"typescript-3.7",
"typescript",
]

# Generates a NodeJS test for each configured TypeScript version.
[
nodejs_test(
name = ts_pkg_name,
args = [ts_pkg_name],
data = [
"helpers.js",
"test.js",
":import-all-entry-points-file",
"//src/cdk:npm_package",
"//src/cdk-experimental:npm_package",
"//src/google-maps:npm_package",
"//src/material:npm_package",
"//src/material-experimental:npm_package",
"//src/youtube-player:npm_package",
"@npm//shelljs",
"@npm//%s" % ts_pkg_name,
"@npm//@types/node",
],
entry_point = "test.js",
tags = [
"integration",
],
)
for ts_pkg_name in typescript_version_packages
]
85 changes: 85 additions & 0 deletions integration/ts-compat/helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
const {relative, sep, join} = require('path');
const {readdirSync, readFileSync, existsSync} = require('fs');
const {set, ln, rm, mkdir} = require('shelljs');
const {fork} = require('child_process');
const runfiles = require(process.env.BAZEL_NODE_RUNFILES_HELPER);

// Exit if any command fails.
set('-e');

// List of NPM packages that have been built for the current test target.
const npmPackages = getNpmPackagesFromRunfiles();
// Path to the node modules of the workspace.
const nodeModulesDir = runfiles.resolve('npm/node_modules');
// Path to the generated file that imports all entry-points.
const testFilePath = require.resolve('./import-all-entry-points.ts');

/**
* Runs the TypeScript compatibility test with the specified tsc binary. The
* compatibility test, links the built release packages into `node_modules` and
* compiles a test file using the specified tsc binary which imports all entry-points.
*/
exports.runTypeScriptCompatibilityTest = async (tscBinPath) => {
return new Promise((resolve, reject) => {
const angularDir = join(nodeModulesDir, '@angular/');

// Create the `node_modules/@angular` directory in case it's not present.
mkdir('-p', angularDir);

// Symlink npm packages into `node_modules/` so that the project can
// be compiled without path mappings (simulating a real project).
for (const {name, pkgPath} of npmPackages) {
console.info(`Linking "@angular/${name}" into node modules..`);
ln('-s', pkgPath, join(angularDir, name));
}

const tscArgs = [
'--strict',
'--lib', 'es2015,dom',
// Ensures that `node_modules` can be resolved. By default, in sandbox environments the
// node modules cannot be resolved because they are wrapped in the `npm/node_modules` folder
'--baseUrl', nodeModulesDir,
testFilePath
];
// Run `tsc` to compile the project. The stdout/stderr output is inherited, so that
// warnings and errors are printed to the console.
const tscProcess = fork(tscBinPath, tscArgs, {stdio: 'inherit'});

tscProcess.on('exit', (exitCode) => {
// Remove symlinks to keep a clean repository state.
for (const {name} of npmPackages) {
console.info(`Removing link for "@angular/${name}"..`);
rm(join(angularDir, name));
}
exitCode === 0 ? resolve() : reject();
});
});
};

/**
* Gets all built Angular NPM package artifacts by querying the Bazel runfiles.
* In case there is a runfiles manifest (e.g. on Windows), the packages are resolved
* through the manifest because the runfiles are not symlinked and cannot be searched
* within the real filesystem. TODO: Remove if Bazel on Windows uses runfile symlinking.
*/
function getNpmPackagesFromRunfiles() {
// Path to the Bazel runfiles manifest if present. This file is present if runfiles are
// not symlinked into the runfiles directory.
const runfilesManifestPath = process.env.RUNFILES_MANIFEST_FILE;
const workspacePath = 'angular_material/src';
if (!runfilesManifestPath) {
const packageRunfilesDir = join(process.env.RUNFILES, workspacePath);
return readdirSync(packageRunfilesDir)
.map(name => ({name, pkgPath: join(packageRunfilesDir, name, 'npm_package/')}))
.filter(({pkgPath}) => existsSync(pkgPath));
}
const workspaceManifestPathRegex = new RegExp(`^${workspacePath}/[\\w-]+/npm_package$`);
return readFileSync(runfilesManifestPath, 'utf8')
.split('\n')
.map(mapping => mapping.split(' '))
.filter(([runfilePath]) => runfilePath.match(workspaceManifestPathRegex))
.map(([runfilePath, realPath]) => ({
name: relative(workspacePath, runfilePath).split(sep)[0],
pkgPath: realPath,
}));
}
44 changes: 44 additions & 0 deletions integration/ts-compat/import-all-entry-points.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
load("//src/cdk:config.bzl", "CDK_ENTRYPOINTS")
load("//src/cdk-experimental:config.bzl", "CDK_EXPERIMENTAL_ENTRYPOINTS")
load("//src/material:config.bzl", "MATERIAL_ENTRYPOINTS", "MATERIAL_TESTING_ENTRYPOINTS")
load("//src/material-experimental:config.bzl", "MATERIAL_EXPERIMENTAL_ENTRYPOINTS", "MATERIAL_EXPERIMENTAL_TESTING_ENTRYPOINTS")

"""Converts the given string to an identifier."""

def convert_to_identifier(name):
return name.replace("/", "_").replace("-", "_")

"""Creates imports and exports for the given entry-point and package."""

def create_import_export(entry_point, pkg_name):
identifier = "%s_%s" % (convert_to_identifier(pkg_name), convert_to_identifier(entry_point))
return """
import * as {0} from "@angular/{1}/{2}";
export {{ {0} }};
""".format(identifier, pkg_name, entry_point)

"""
Creates a file that imports all entry-points as namespace. The namespaces will be
re-exported. This ensures that all entry-points can successfully compile.
"""

def generate_import_all_entry_points_file():
output = """
import * as cdk from "@angular/cdk";
import * as cdk_experimental from "@angular/cdk-experimental";
// Note: The primary entry-point for Angular Material does not have
// any exports, so it cannot be imported as module.
import * as material_experimental from "@angular/material-experimental";
import * as google_maps from "@angular/google-maps";
import * as youtube_player from "@angular/youtube-player";
export {cdk, cdk_experimental, material_experimental, google_maps, youtube_player};
"""
for ep in CDK_ENTRYPOINTS:
output += create_import_export(ep, "cdk")
for ep in CDK_EXPERIMENTAL_ENTRYPOINTS:
output += create_import_export(ep, "cdk-experimental")
for ep in MATERIAL_ENTRYPOINTS + MATERIAL_TESTING_ENTRYPOINTS:
output += create_import_export(ep, "material")
for ep in MATERIAL_EXPERIMENTAL_ENTRYPOINTS + MATERIAL_EXPERIMENTAL_TESTING_ENTRYPOINTS:
output += create_import_export(ep, "material-experimental")
return output
19 changes: 19 additions & 0 deletions integration/ts-compat/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Test script that runs the TypeScript compatibility tests against a specified
* TypeScript package that is passed through command line. The script is executed
* by a Bazel `nodejs_test` target and relies on Bazel runfile resolution.
*/

const {runTypeScriptCompatibilityTest} = require('./helpers');

if (module === require.main) {
const [pkgName] = process.argv.slice(2);
if (!pkgName) {
console.error('No TypeScript package specified. Exiting..');
process.exit(1);
}
runTypeScriptCompatibilityTest(require.resolve(`${pkgName}/bin/tsc`)).catch(e => {
console.error(e);
process.exit(1);
});
}
34 changes: 18 additions & 16 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@
},
"version": "9.1.2",
"dependencies": {
"@angular/animations": "^9.0.5",
"@angular/common": "^9.0.5",
"@angular/compiler": "^9.0.5",
"@angular/core": "^9.0.5",
"@angular/elements": "^9.0.5",
"@angular/forms": "^9.0.5",
"@angular/platform-browser": "^9.0.5",
"@angular/animations": "^9.1.0-next.4",
"@angular/common": "^9.1.0-next.4",
"@angular/compiler": "^9.1.0-next.4",
"@angular/core": "^9.1.0-next.4",
"@angular/elements": "^9.1.0-next.4",
"@angular/forms": "^9.1.0-next.4",
"@angular/platform-browser": "^9.1.0-next.4",
"@types/googlemaps": "^3.37.0",
"@types/youtube": "^0.0.38",
"@webcomponents/custom-elements": "^1.1.0",
Expand All @@ -65,11 +65,11 @@
"devDependencies": {
"@angular-devkit/core": "^9.0.4",
"@angular-devkit/schematics": "^9.0.4",
"@angular/bazel": "^9.0.5",
"@angular/compiler-cli": "^9.0.5",
"@angular/platform-browser-dynamic": "^9.0.5",
"@angular/platform-server": "^9.0.5",
"@angular/router": "^9.0.5",
"@angular/bazel": "^9.1.0-next.4",
"@angular/compiler-cli": "^9.1.0-next.4",
"@angular/platform-browser-dynamic": "^9.1.0-next.4",
"@angular/platform-server": "^9.1.0-next.4",
"@angular/router": "^9.1.0-next.4",
"@bazel/bazelisk": "^1.3.0",
"@bazel/buildifier": "^0.29.0",
"@bazel/ibazel": "0.12.0",
Expand Down Expand Up @@ -133,7 +133,7 @@
"moment": "^2.18.1",
"node-fetch": "^2.6.0",
"parse5": "^5.0.0",
"protractor": "^5.4.2",
"protractor": "^5.4.3",
"requirejs": "^2.3.6",
"rollup": "~1.25.0",
"rollup-plugin-alias": "^1.4.0",
Expand All @@ -151,14 +151,16 @@
"terser": "^4.3.9",
"ts-api-guardian": "^0.5.0",
"ts-node": "^3.0.4",
"tsickle": "0.38.0",
"tsickle": "0.38.1",
"tslint": "^6.0.0",
"tsutils": "^3.0.0",
"typescript": "~3.7.4",
"typescript": "~3.8.3",
"typescript-3.6": "npm:typescript@~3.6.4",
"typescript-3.7": "npm:typescript@~3.7.0",
"vrsource-tslint-rules": "5.1.1"
},
"resolutions": {
"dgeni-packages/typescript": "3.7.4",
"dgeni-packages/typescript": "3.8.3",
"**/graceful-fs": "4.2.2"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import {getVersionUpgradeData, RuleUpgradeData} from '../upgrade-data';
* Rule that walks through every identifier that is part of Angular Material or thr CDK
* and replaces the outdated name with the new one if specified in the upgrade data.
*/
// TODO: rework this rule to identify symbols using the import identifier resolver. This
// makes it more robust, less AST convoluted and is more TypeScript AST idiomatic. COMP-300.
export class ClassNamesRule extends MigrationRule<RuleUpgradeData> {
/** Change data that upgrades to the specified target version. */
data: ClassNameUpgradeData[] = getVersionUpgradeData(this, 'classNames');
Expand Down
3 changes: 2 additions & 1 deletion src/cdk/schematics/utils/ast/ng-module-imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function hasNgModuleImport(tree: Tree, modulePath: string, className: str
function resolveIdentifierOfExpression(expression: ts.Expression): ts.Identifier | null {
if (ts.isIdentifier(expression)) {
return expression;
} else if (ts.isPropertyAccessExpression(expression)) {
} else if (ts.isPropertyAccessExpression(expression) && ts.isIdentifier(expression.name)) {
return expression.name;
}
return null;
Expand Down Expand Up @@ -84,6 +84,7 @@ function isNgModuleCallExpression(callExpression: ts.CallExpression): boolean {
return false;
}

// The `NgModule` call expression name is never referring to a `PrivateIdentifier`.
const decoratorIdentifier = resolveIdentifierOfExpression(callExpression.expression);
return decoratorIdentifier ? decoratorIdentifier.text === 'NgModule' : false;
}

0 comments on commit b4d2838

Please sign in to comment.