Skip to content

Commit 9f6015f

Browse files
committed
build: update to typescript v3.8
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.
1 parent 807498d commit 9f6015f

File tree

10 files changed

+310
-79
lines changed

10 files changed

+310
-79
lines changed

.circleci/config.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,23 @@ jobs:
484484
# Run project tests with NGC and View Engine.
485485
- run: bazel test src/... --build_tag_filters=-docs-package,-e2e --test_tag_filters=-e2e --config=view-engine --build_tests_only
486486

487+
# ----------------------------------------------------------------------------
488+
# Job that runs all Bazel integration tests.
489+
# ----------------------------------------------------------------------------
490+
integration_tests:
491+
<<: *job_defaults
492+
resource_class: xlarge
493+
environment:
494+
GCP_DECRYPT_TOKEN: *gcp_decrypt_token
495+
steps:
496+
- *checkout_code
497+
- *restore_cache
498+
- *setup_bazel_ci_config
499+
- *setup_bazel_remote_execution
500+
- *setup_bazel_binary
501+
# Integration tests run with --config=view-engine because we release with View Engine.
502+
- run: bazel test integration/... --build_tests_only --config=view-engine
503+
487504
# ----------------------------------------------------------------------------
488505
# Job that runs all Bazel tests against material-components-web@canary
489506
# ----------------------------------------------------------------------------
@@ -525,6 +542,8 @@ workflows:
525542
filters: *ignore_presubmit_branch_filter
526543
- api_golden_checks:
527544
filters: *ignore_presubmit_branch_filter
545+
- integration_tests:
546+
filters: *ignore_presubmit_branch_filter
528547
- tests_local_browsers:
529548
filters: *ignore_presubmit_branch_filter
530549
- tests_browserstack:

.github/CODEOWNERS

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,9 +241,12 @@
241241
# Universal app
242242
/src/universal-app/** @jelbourn
243243

244+
# Integration tests
245+
/integration/** @jelbourn @devversion
246+
244247
# Tooling
245248
/.circleci/** @angular/dev-infra-components
246-
/.yarn/** @angular/dev-infra-components
249+
/.yarn/** @angular/dev-infra-components
247250
/scripts/** @angular/dev-infra-components
248251
/test/** @angular/dev-infra-components
249252
/tools/** @angular/dev-infra-components

integration/ts-compat/BUILD.bazel

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package(default_visibility = ["//visibility:public"])
2+
3+
load("@bazel_skylib//rules:write_file.bzl", "write_file")
4+
load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_test")
5+
load("//integration/ts-compat:import-all-entry-points.bzl", "generate_import_all_entry_points_file")
6+
7+
write_file(
8+
name = "import-all-entry-points-file",
9+
out = "import-all-entry-pooints.ts",
10+
content = [generate_import_all_entry_points_file()],
11+
)
12+
13+
# List of TypeScript packages that we want to run the compatibility test against.
14+
# The list contains NPM module names that resolve to the desired TypeScript version.
15+
typescript_version_packages = [
16+
"typescript-3.6",
17+
"typescript-3.7",
18+
"typescript",
19+
]
20+
21+
# Generates a NodeJS test for each configured TypeScript version.
22+
[
23+
nodejs_test(
24+
name = ts_pkg_name,
25+
args = [ts_pkg_name],
26+
data = [
27+
"helpers.js",
28+
"test.js",
29+
":import-all-entry-points-file",
30+
"//src/cdk:npm_package",
31+
"//src/cdk-experimental:npm_package",
32+
"//src/google-maps:npm_package",
33+
"//src/material:npm_package",
34+
"//src/material-experimental:npm_package",
35+
"//src/youtube-player:npm_package",
36+
"@npm//shelljs",
37+
"@npm//%s" % ts_pkg_name,
38+
"@npm//@types/node",
39+
],
40+
entry_point = "test.js",
41+
tags = [
42+
"integration",
43+
],
44+
)
45+
for ts_pkg_name in typescript_version_packages
46+
]

integration/ts-compat/helpers.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
const {relative, sep, join} = require('path');
2+
const {readdirSync, readFileSync, existsSync} = require('fs');
3+
const {set, ln, rm, mkdir} = require('shelljs');
4+
const {fork} = require('child_process');
5+
const runfiles = require(process.env.BAZEL_NODE_RUNFILES_HELPER);
6+
7+
// Exit if any command fails.
8+
set('-e');
9+
10+
// List of NPM packages that have been built for the current test target.
11+
const npmPackages = getNpmPackagesFromRunfiles();
12+
// Path to the node modules of the workspace.
13+
const nodeModulesDir = runfiles.resolve('npm/node_modules');
14+
// Path to the generated file that imports all entry-points.
15+
const testFilePath = require.resolve('./imports-all-entry-points.ts');
16+
17+
/**
18+
* Runs the TypeScript compatibility test with the specified tsc binary. The
19+
* compatibility test, links the built release packages into `node_modules` and
20+
* compiles a test file using the specified tsc binary which imports all entry-points.
21+
*/
22+
exports.runTypeScriptCompatibilityTest = async (tscBinPath) => {
23+
return new Promise((resolve, reject) => {
24+
const angularDir = join(nodeModulesDir, '@angular/');
25+
26+
// Create the `node_modules/@angular` directory in case it's not present.
27+
mkdir('-p', angularDir);
28+
29+
// Symlink npm packages into `node_modules/` so that the project can
30+
// be compiled without path mappings (simulating a real project).
31+
for (const {name, pkgPath} of npmPackages) {
32+
console.info(`Linking "@angular/${name}" into node modules..`);
33+
ln('-s', pkgPath, join(angularDir, name));
34+
}
35+
36+
const tscArgs = [
37+
'--strict',
38+
'--lib', 'es2015,dom',
39+
// Ensures that `node_modules` can be resolved. By default, in sandbox environments the
40+
// node modules cannot be resolved because they are wrapped in the `npm/node_modules` folder
41+
'--baseUrl', nodeModulesDir,
42+
testFilePath
43+
];
44+
// Run `tsc` to compile the project. The stdout/stderr output is inherited, so that
45+
// warnings and errors are printed to the console.
46+
const tscProcess = fork(tscBinPath, tscArgs, {stdio: 'inherit'});
47+
48+
tscProcess.on('exit', (exitCode) => {
49+
// Remove symlinks to keep a clean repository state.
50+
for (const {name} of npmPackages) {
51+
console.info(`Removing link for "@angular/${name}"..`);
52+
rm(join(angularDir, name));
53+
}
54+
exitCode === 0 ? resolve() : reject();
55+
});
56+
});
57+
};
58+
59+
/**
60+
* Gets all built Angular NPM package artifacts by querying the Bazel runfiles.
61+
* In case there is a runfiles manifest (e.g. on Windows), the packages are resolved
62+
* through the manifest because the runfiles are not symlinked and cannot be searched
63+
* within the real filesystem. TODO: Remove if Bazel on Windows uses runfile symlinking.
64+
*/
65+
function getNpmPackagesFromRunfiles() {
66+
// Path to the Bazel runfiles manifest if present. This file is present if runfiles are
67+
// not symlinked into the runfiles directory.
68+
const runfilesManifestPath = process.env.RUNFILES_MANIFEST_FILE;
69+
const workspacePath = 'angular_material/src';
70+
if (!runfilesManifestPath) {
71+
const packageRunfilesDir = join(process.env.RUNFILES, workspacePath);
72+
return readdirSync(packageRunfilesDir)
73+
.map(name => ({name, pkgPath: join(packageRunfilesDir, name, 'npm_package/')}))
74+
.filter(({pkgPath}) => existsSync(pkgPath));
75+
}
76+
const workspaceManifestPathRegex = new RegExp(`^${workspacePath}/[\\w-]+/npm_package$`);
77+
return readFileSync(runfilesManifestPath, 'utf8')
78+
.split('\n')
79+
.map(mapping => mapping.split(' '))
80+
.filter(([runfilePath]) => runfilePath.match(workspaceManifestPathRegex))
81+
.map(([runfilePath, realPath]) => ({
82+
name: relative(workspacePath, runfilePath).split(sep)[0],
83+
pkgPath: realPath,
84+
}));
85+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
load("//src/cdk:config.bzl", "CDK_ENTRYPOINTS")
2+
load("//src/cdk-experimental:config.bzl", "CDK_EXPERIMENTAL_ENTRYPOINTS")
3+
load("//src/material:config.bzl", "MATERIAL_ENTRYPOINTS", "MATERIAL_TESTING_ENTRYPOINTS")
4+
load("//src/material-experimental:config.bzl", "MATERIAL_EXPERIMENTAL_ENTRYPOINTS", "MATERIAL_EXPERIMENTAL_TESTING_ENTRYPOINTS")
5+
6+
"""Converts the given string to an identifier."""
7+
8+
def convert_to_identifier(name):
9+
return name.replace("/", "_").replace("-", "_")
10+
11+
"""Creates imports and exports for the given entry-point and package."""
12+
13+
def create_import_export(entry_point, pkg_name):
14+
identifier = "%s_%s" % (convert_to_identifier(pkg_name), convert_to_identifier(entry_point))
15+
return """
16+
import * as {0} from "@angular/{1}/{2}";
17+
export {{ {0} }};
18+
""".format(identifier, pkg_name, entry_point)
19+
20+
"""
21+
Creates a file that imports all entry-points as namespace. The namespaces will be
22+
re-exported. This ensures that all entry-points can successfully compile.
23+
"""
24+
25+
def generate_import_all_entry_points_file():
26+
output = """
27+
import * as cdk from "@angular/cdk";
28+
import * as cdk_experimental from "@angular/cdk-experimental";
29+
// Note: The primary entry-point for Angular Material does not have
30+
// any exports, so it cannot be imported as module.
31+
import * as material_experimental from "@angular/material-experimental";
32+
import * as google_maps from "@angular/google-maps";
33+
import * as youtube_player from "@angular/youtube-player";
34+
export {cdk, cdk_experimental, material_experimental, google_maps, youtube_player};
35+
"""
36+
for ep in CDK_ENTRYPOINTS:
37+
output += create_import_export(ep, "cdk")
38+
for ep in CDK_EXPERIMENTAL_ENTRYPOINTS:
39+
output += create_import_export(ep, "cdk-experimental")
40+
for ep in MATERIAL_ENTRYPOINTS + MATERIAL_TESTING_ENTRYPOINTS:
41+
output += create_import_export(ep, "material")
42+
for ep in MATERIAL_EXPERIMENTAL_ENTRYPOINTS + MATERIAL_EXPERIMENTAL_TESTING_ENTRYPOINTS:
43+
output += create_import_export(ep, "material-experimental")
44+
return output

integration/ts-compat/test.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* Test script that runs the TypeScript compatibility tests against a specified
3+
* TypeScript package that is passed through command line. The script is executed
4+
* by a Bazel `nodejs_test` target and relies on Bazel runfile resolution.
5+
*/
6+
7+
const {runTypeScriptCompatibilityTest} = require('./helpers');
8+
9+
if (module === require.main) {
10+
const [pkgName] = process.argv.slice(2);
11+
if (!pkgName) {
12+
console.error('No TypeScript package specified. Exiting..');
13+
process.exit(1);
14+
}
15+
runTypeScriptCompatibilityTest(require.resolve(`${pkgName}/bin/tsc`)).catch(e => {
16+
console.error(e);
17+
process.exit(1);
18+
});
19+
}

package.json

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,13 @@
4545
},
4646
"version": "9.1.2",
4747
"dependencies": {
48-
"@angular/animations": "^9.0.5",
49-
"@angular/common": "^9.0.5",
50-
"@angular/compiler": "^9.0.5",
51-
"@angular/core": "^9.0.5",
52-
"@angular/elements": "^9.0.5",
53-
"@angular/forms": "^9.0.5",
54-
"@angular/platform-browser": "^9.0.5",
48+
"@angular/animations": "^9.1.0-next.4",
49+
"@angular/common": "^9.1.0-next.4",
50+
"@angular/compiler": "^9.1.0-next.4",
51+
"@angular/core": "^9.1.0-next.4",
52+
"@angular/elements": "^9.1.0-next.4",
53+
"@angular/forms": "^9.1.0-next.4",
54+
"@angular/platform-browser": "^9.1.0-next.4",
5555
"@types/googlemaps": "^3.37.0",
5656
"@types/youtube": "^0.0.38",
5757
"@webcomponents/custom-elements": "^1.1.0",
@@ -65,11 +65,11 @@
6565
"devDependencies": {
6666
"@angular-devkit/core": "^9.0.4",
6767
"@angular-devkit/schematics": "^9.0.4",
68-
"@angular/bazel": "^9.0.5",
69-
"@angular/compiler-cli": "^9.0.5",
70-
"@angular/platform-browser-dynamic": "^9.0.5",
71-
"@angular/platform-server": "^9.0.5",
72-
"@angular/router": "^9.0.5",
68+
"@angular/bazel": "^9.1.0-next.4",
69+
"@angular/compiler-cli": "^9.1.0-next.4",
70+
"@angular/platform-browser-dynamic": "^9.1.0-next.4",
71+
"@angular/platform-server": "^9.1.0-next.4",
72+
"@angular/router": "^9.1.0-next.4",
7373
"@bazel/bazelisk": "^1.3.0",
7474
"@bazel/buildifier": "^0.29.0",
7575
"@bazel/ibazel": "0.12.0",
@@ -151,14 +151,16 @@
151151
"terser": "^4.3.9",
152152
"ts-api-guardian": "^0.5.0",
153153
"ts-node": "^3.0.4",
154-
"tsickle": "0.38.0",
154+
"tsickle": "0.38.1",
155155
"tslint": "^6.0.0",
156156
"tsutils": "^3.0.0",
157-
"typescript": "~3.7.4",
157+
"typescript": "~3.8.3",
158+
"typescript-3.6": "npm:typescript@~3.6.4",
159+
"typescript-3.7": "npm:typescript@~3.7.0",
158160
"vrsource-tslint-rules": "5.1.1"
159161
},
160162
"resolutions": {
161-
"dgeni-packages/typescript": "3.7.4",
163+
"dgeni-packages/typescript": "3.8.3",
162164
"**/graceful-fs": "4.2.2"
163165
}
164166
}

src/cdk/schematics/ng-update/upgrade-rules/class-names-rule.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import {getVersionUpgradeData, RuleUpgradeData} from '../upgrade-data';
2525
* Rule that walks through every identifier that is part of Angular Material or thr CDK
2626
* and replaces the outdated name with the new one if specified in the upgrade data.
2727
*/
28+
// TODO: rework this rule to identify symbols using the import identifier resolver. This
29+
// makes it more robust, less AST convoluted and is more TypeScript AST idiomatic. COMP-300.
2830
export class ClassNamesRule extends MigrationRule<RuleUpgradeData> {
2931
/** Change data that upgrades to the specified target version. */
3032
data: ClassNameUpgradeData[] = getVersionUpgradeData(this, 'classNames');

src/cdk/schematics/utils/ast/ng-module-imports.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export function hasNgModuleImport(tree: Tree, modulePath: string, className: str
4848
function resolveIdentifierOfExpression(expression: ts.Expression): ts.Identifier | null {
4949
if (ts.isIdentifier(expression)) {
5050
return expression;
51-
} else if (ts.isPropertyAccessExpression(expression)) {
51+
} else if (ts.isPropertyAccessExpression(expression) && ts.isIdentifier(expression.name)) {
5252
return expression.name;
5353
}
5454
return null;
@@ -84,6 +84,7 @@ function isNgModuleCallExpression(callExpression: ts.CallExpression): boolean {
8484
return false;
8585
}
8686

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

0 commit comments

Comments
 (0)