From 77e5c210e78c4b2cc48d2a05f2b469a230a7a56d Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Fri, 18 Jun 2021 19:08:49 +0200 Subject: [PATCH] build: run unit tests with partial compilation and Angular linker (#22979) Instead of running e2e tests against a linker-processed application, we run all of our unit tests using partial compilation and the Angular linker. This ensures we test our components more throroughly against the Angular linker & partial compilation. This also solves our CI flakiness issue due to the large amount of webdriver e2e tests running concurrently on CI (especially since all e2e tests ran on snapshot builds). --- .circleci/config.yml | 21 ++-- package.json | 2 +- src/e2e-app/BUILD.bazel | 43 ------- src/e2e-app/rollup-with-linker.config.js | 76 ------------- src/e2e-app/test_suite.bzl | 9 +- tools/defaults.bzl | 17 ++- tools/linker-process/BUILD.bazel | 30 +++++ tools/linker-process/index.bzl | 100 ++++++++++++++++ tools/linker-process/linker-process.ts | 139 +++++++++++++++++++++++ tools/tsconfig-ci.json | 3 +- 10 files changed, 297 insertions(+), 143 deletions(-) delete mode 100644 src/e2e-app/rollup-with-linker.config.js create mode 100644 tools/linker-process/BUILD.bazel create mode 100644 tools/linker-process/index.bzl create mode 100644 tools/linker-process/linker-process.ts diff --git a/.circleci/config.yml b/.circleci/config.yml index b24e3cbf5547..ebfd18ab4ddf 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -197,8 +197,7 @@ jobs: # Exclude release and docs packages here as those will be built within # the "build_release_packages" and "publish_snapshots" jobs. - # TODO(devversion): remove target exclusion once https://github.com/bazelbuild/rules_nodejs/pull/2646 is available. - - run: bazel build --build_tag_filters=-docs-package,-release-package -- src/... -//src/e2e-app:devserver_with_linked_declarations.MF + - run: bazel build --build_tag_filters=-docs-package,-release-package -- src/... - *slack_notify_on_failure # ----------------------------------- @@ -220,8 +219,7 @@ jobs: # Exclude release and docs packages here as those will be built within # the "build_release_packages" and "publish_snapshots" jobs. - # TODO(devversion): remove target exclusion once https://github.com/bazelbuild/rules_nodejs/pull/2646 is available. - - run: bazel build --build_tag_filters=-docs-package,-release-package --config=view-engine -- src/... -//src/e2e-app:devserver_with_linked_declarations.MF + - run: bazel build --build_tag_filters=-docs-package,-release-package --config=view-engine -- src/... - *slack_notify_on_failure # -------------------------------------------------------------------------------------------- @@ -283,8 +281,7 @@ jobs: - *yarn_install - *setup_bazel_binary - # TODO(devversion): remove target exclusion once https://github.com/bazelbuild/rules_nodejs/pull/2646 is available. - - run: bazel test --build_tag_filters=-e2e --test_tag_filters=-e2e --build_tests_only -- src/... -//src/e2e-app:devserver_with_linked_declarations.MF + - run: bazel test --build_tag_filters=-e2e --test_tag_filters=-e2e --build_tests_only -- src/... - *slack_notify_on_failure # ---------------------------------------------------------------------------- @@ -536,8 +533,7 @@ jobs: - *yarn_install_loose_lockfile - *setup_bazel_binary - # TODO(devversion): remove target exclusion once https://github.com/bazelbuild/rules_nodejs/pull/2646 is available. - - run: bazel test --build_tag_filters=-e2e --test_tag_filters=-e2e --build_tests_only -- src/... -//src/e2e-app:devserver_with_linked_declarations.MF + - run: bazel test --build_tag_filters=-e2e --test_tag_filters=-e2e --build_tests_only -- src/... - *slack_notify_on_failure # ---------------------------------------------------------------------------- @@ -558,8 +554,7 @@ jobs: - *setup_bazel_binary # Run project tests with NGC and View Engine. - # TODO(devversion): remove target exclusion once https://github.com/bazelbuild/rules_nodejs/pull/2646 is available. - - run: bazel test --build_tag_filters=-docs-package,-e2e --test_tag_filters=-e2e --config=view-engine --build_tests_only -- src/... -//src/e2e-app:devserver_with_linked_declarations.MF + - run: bazel test --build_tag_filters=-docs-package,-e2e --test_tag_filters=-e2e --config=view-engine --build_tests_only -- src/... - *slack_notify_on_failure # ---------------------------------------------------------------------------- @@ -580,8 +575,7 @@ jobs: - *setup_bazel_binary # Run project tests with NGC and View Engine. - # TODO(devversion): remove target exclusion once https://github.com/bazelbuild/rules_nodejs/pull/2646 is available. - - run: bazel test --build_tag_filters=-docs-package,-e2e --test_tag_filters=-e2e --config=view-engine --build_tests_only -- src/... -//src/e2e-app:devserver_with_linked_declarations.MF + - run: bazel test --build_tag_filters=-docs-package,-e2e --test_tag_filters=-e2e --config=view-engine --build_tests_only -- src/... - *slack_notify_on_failure # ---------------------------------------------------------------------------- @@ -654,8 +648,7 @@ jobs: # Setup the components repository to use the MDC snapshot builds. # Run project tests with the MDC canary builds. - # TODO(devversion): remove target exclusion once https://github.com/bazelbuild/rules_nodejs/pull/2646 is available. - - run: bazel test --build_tag_filters=-docs-package,-e2e --test_tag_filters=-e2e --build_tests_only -- src/... -//src/e2e-app:devserver_with_linked_declarations.MF + - run: bazel test --build_tag_filters=-docs-package,-e2e --test_tag_filters=-e2e --build_tests_only -- src/... - *slack_notify_on_failure # ---------------------------------------------------------------------------------------- diff --git a/package.json b/package.json index 574eba9930c3..f017975a3395 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "approve-size-tests": "node ./scripts/approve-size-golden.js", "integration-tests": "bazel test --test_tag_filters=-view-engine-only,-linker-integration-test --build_tests_only -- //integration/... -//integration/size-test/...", "integration-tests:view-engine": "bazel test --test_tag_filters=view-engine-only --build_tests_only -- //integration/... -//integration/size-test/...", - "integration-tests:partial-ivy": "bazel test --//tools:partial_compilation=True --test_tag_filters=partial-compilation-integration --build_tests_only -- //integration/... //src/...", + "integration-tests:partial-ivy": "bazel test --//tools:partial_compilation=True --test_tag_filters=partial-compilation-integration,-firefox --build_tests_only -- //integration/... //src/...", "integration-tests:size-test": "bazel test //integration/size-test/...", "check-mdc-tests": "ts-node --project scripts/tsconfig.json scripts/check-mdc-tests.ts", "check-mdc-exports": "ts-node --project scripts/tsconfig.json scripts/check-mdc-exports.ts", diff --git a/src/e2e-app/BUILD.bazel b/src/e2e-app/BUILD.bazel index a022118f0280..2230fb14b3da 100644 --- a/src/e2e-app/BUILD.bazel +++ b/src/e2e-app/BUILD.bazel @@ -1,4 +1,3 @@ -load("@npm//@bazel/rollup:index.bzl", "rollup_bundle") load("@npm//@bazel/concatjs:index.bzl", "concatjs_devserver") load("//:packages.bzl", "getAngularUmdTargets") load("//tools:defaults.bzl", "ng_module", "sass_binary") @@ -117,45 +116,3 @@ concatjs_devserver( tags = ["manual"], deps = [":e2e-app"], ) - -# Bundles the `e2e-app` using rollup while running the Angular linker to transform -# partial declarations to their corresponding Angular definitions. This target should -# only be run if the e2e-app is built in partial compilation mode. Technically this -# target could be run with full compilation mode too, but we want to raise an error if -# the partial compilation is not enabled (as the mode has been forgotten most likely). -rollup_bundle( - name = "linked_app_bundle", - testonly = True, - config_file = ":rollup-with-linker.config.js", - entry_points = { - "main.ts": "bundle", - }, - sourcemap = "false", - tags = ["manual"], - deps = select( - { - # We rely on the partial compilation build setting here to ensure that the target - # cannot be built if partial compilation is not enabled. - "//tools:partial_compilation_enabled": [ - ":e2e-app", - "@npm//rollup-plugin-node-resolve", - "@npm//@angular/compiler-cli", - "@npm//@rollup/plugin-babel", - "@npm//@rollup/plugin-commonjs", - "@npm//glob", - ], - }, - no_match_error = "Partial compilation needs to be enabled. The following flag enables partial " + - "compilation: --//tools:partial_compilation=True", - ), -) - -# Basic web server that serves the e2e-app processed by the Angular linker. -concatjs_devserver( - name = "devserver_with_linked_declarations", - testonly = True, - additional_root_paths = ["npm/node_modules"], - port = 4200, - static_files = [":linked_app_bundle"] + devserverIndexHtmlDependencies, - tags = ["manual"], -) diff --git a/src/e2e-app/rollup-with-linker.config.js b/src/e2e-app/rollup-with-linker.config.js deleted file mode 100644 index a899a527c4d4..000000000000 --- a/src/e2e-app/rollup-with-linker.config.js +++ /dev/null @@ -1,76 +0,0 @@ -const node = require('rollup-plugin-node-resolve'); -const {ConsoleLogger, LogLevel} = require("@angular/compiler-cli/src/ngtsc/logging"); -const {NodeJSFileSystem} = require("@angular/compiler-cli/src/ngtsc/file_system"); -const {createEs2015LinkerPlugin} = require('@angular/compiler-cli/linker/babel'); -const {babel} = require('@rollup/plugin-babel'); -const commonjs = require('@rollup/plugin-commonjs'); -const {sync: globSync} = require('glob'); -const {join} = require('path'); - -/** File system used by the Angular linker plugin. */ -const fileSystem = new NodeJSFileSystem(); -/** Logger used by the Angular linker plugin. */ -const logger = new ConsoleLogger(LogLevel.info); -/** Babel plugin that runs the Angular linker. */ -const linkerPlugin = createEs2015LinkerPlugin({fileSystem, logger}); - -/** - * Custom rollup plugin that enables bundling of the component examples which are - * usually loaded lazily. We cannot use lazy loading here as we want to process - * all sources with the linker, and rollup isn't able to process dynamic imports - * which are not statically analyzable. We work around this by transforming the - * dynamic non-static imports to statically analyzable dynamic imports that can be - * processed by rollup. e.g. "import(`@angular/components-examples/${module}/index.js`") - * will be transformed into an object that merges exports from all possible `${module}` values. - */ -const lazyExamplesPlugin = { - transform: (code, id) => { - if (!id.includes('load-example.mjs')) { - return; - } - - // In Bazel actions, the exec root is the current working directory. - const execRoot = process.cwd(); - const examplesPackageDir = join(execRoot, 'node_modules/@angular/components-examples'); - const moduleImports = globSync('*/**/index?(.ngfactory).mjs', {cwd: examplesPackageDir}) - .map(m => `...yield import("@angular/components-examples/${m}")`); - - // Replaces the call to `loadModuleWithFactory` with a statically analyzable object literal that can - // be processed by rollup. We merge all exports of the examples to a single object literal to avoid - // dynamic non-analyzable imports that rollup cannot handle. - return code.replace(/yield loadModuleWithFactory\([^)]+\);/, ` - { - moduleExports: {${moduleImports.join(',')}}, - moduleFactoryExports: {${moduleImports.join(',')}}, - } - `); - }, -} - -module.exports = { - output: { - // Inline all dynamic imports in order to avoid generating multiple chunks. Multiple chunks - // would require enabling `output_dir` which would inherently complicate the devserver setup. - inlineDynamicImports: true, - }, - plugins: [ - commonjs(), - node({ - // The e2e-app runs with Ivy. We need to ensure the NGCC processed entry points are - // loaded for the Angular dependencies. - mainFields: ['es2015_ivy_ngcc', 'module_ivy_ngcc','es2015', 'module'], - }), - lazyExamplesPlugin, - babel({ - plugins: [linkerPlugin], - // There should be no babel helpers required as we process JavaScript code that has - // been generated by TypeScript and uses tslib. We still set this option explicitly - // to avoid a warning by Babel. Also in case there are unprocessed helpers, this - // ensures that the bundle works in the browser as expected. - babelHelpers: 'bundled', - // Due to the large size of referenced files and the potential slow-down, we avoid compression - // by the Babel plugin. - compact: false - }), - ], -}; diff --git a/src/e2e-app/test_suite.bzl b/src/e2e-app/test_suite.bzl index b0431f25e329..e854ca93c718 100644 --- a/src/e2e-app/test_suite.bzl +++ b/src/e2e-app/test_suite.bzl @@ -1,6 +1,6 @@ load("//tools:defaults.bzl", "protractor_web_test_suite") -def e2e_test_suite(name, data = [], tags = ["e2e", "partial-compilation-integration"], deps = []): +def e2e_test_suite(name, data = [], tags = ["e2e"], deps = []): protractor_web_test_suite( name = name, configuration = "//src/e2e-app:protractor.conf.js", @@ -9,12 +9,7 @@ def e2e_test_suite(name, data = [], tags = ["e2e", "partial-compilation-integrat "@npm//@axe-core/webdriverjs", ] + data, on_prepare = "//src/e2e-app:start-devserver.js", - # Based on whether the partial compilation mode is enabled, test either with the default e2e-app - # server, or test with a server that processed all sources with the Angular linker. - server = select({ - "//conditions:default": "//src/e2e-app:devserver", - "//tools:partial_compilation_enabled": "//src/e2e-app:devserver_with_linked_declarations", - }), + server = "//src/e2e-app:devserver", tags = tags, deps = deps, ) diff --git a/tools/defaults.bzl b/tools/defaults.bzl index 1f677373e74a..e7f17d771d6c 100644 --- a/tools/defaults.bzl +++ b/tools/defaults.bzl @@ -9,6 +9,7 @@ load("@npm//@bazel/typescript:index.bzl", _ts_library = "ts_library") load("//:packages.bzl", "VERSION_PLACEHOLDER_REPLACEMENTS", "getAngularUmdTargets") load("//:rollup-globals.bzl", "ROLLUP_GLOBALS") load("//tools/markdown-to-html:index.bzl", _markdown_to_html = "markdown_to_html") +load("//tools/linker-process:index.bzl", "linker_process") _DEFAULT_TSCONFIG_BUILD = "//src:bazel-tsconfig-build.json" _DEFAULT_TSCONFIG_TEST = "//src:tsconfig-test" @@ -161,8 +162,22 @@ def ng_e2e_test_library(deps = [], tsconfig = None, **kwargs): def karma_web_test_suite(name, **kwargs): web_test_args = {} + test_deps = ["//tools/rxjs:rxjs_umd_modules"] + kwargs.get("deps", []) + kwargs["srcs"] = ["@npm//:node_modules/tslib/tslib.js"] + getAngularUmdTargets() + kwargs.get("srcs", []) - kwargs["deps"] = ["//tools/rxjs:rxjs_umd_modules"] + kwargs.get("deps", []) + kwargs["tags"] = ["partial-compilation-integration"] + kwargs.get("tags", []) + kwargs["deps"] = select({ + # Based on whether partial compilation is enabled, use the linker processed dependencies. + "//tools:partial_compilation_enabled": ["%s_linker_processed_deps" % name], + "//conditions:default": test_deps, + }) + + linker_process( + name = "%s_linker_processed_deps" % name, + srcs = test_deps, + testonly = True, + tags = ["manual"], + ) # Set up default browsers if no explicit `browsers` have been specified. if not hasattr(kwargs, "browsers"): diff --git a/tools/linker-process/BUILD.bazel b/tools/linker-process/BUILD.bazel new file mode 100644 index 000000000000..ee77beb5cfe1 --- /dev/null +++ b/tools/linker-process/BUILD.bazel @@ -0,0 +1,30 @@ +load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary") +load("//tools:defaults.bzl", "ts_library") + +package(default_visibility = ["//visibility:public"]) + +ts_library( + name = "sources", + srcs = glob(["**/*.ts"]), + deps = [ + "@npm//@angular/compiler-cli", + "@npm//@babel/core", + "@npm//@babel/traverse", + "@npm//@types/node", + ], +) + +# Exposes the `linker-process` tool as executable so that it can be used as +# build tool (for `ctx.actions.run`) within the `linker_process` custom Bazel rule. +nodejs_binary( + name = "linker-process", + data = [ + ":sources", + ], + entry_point = ":linker-process.ts", + templated_args = [ + # TODO(josephperrott): update dependency usages to no longer need bazel patch module resolver + # See: https://github.com/bazelbuild/rules_nodejs/wiki#--bazel_patch_module_resolver-now-defaults-to-false-2324 + "--bazel_patch_module_resolver", + ], +) diff --git a/tools/linker-process/index.bzl b/tools/linker-process/index.bzl new file mode 100644 index 000000000000..f551a5c0a79e --- /dev/null +++ b/tools/linker-process/index.bzl @@ -0,0 +1,100 @@ +""" + Exposes a custom Bazel rule for processing sources, which are extracted from Bazel + targets, with the Angular linker plugin. +""" + +load("@build_bazel_rules_nodejs//:providers.bzl", "JSModuleInfo", "JSNamedModuleInfo") + +""" +Gets the Bazel manifest path for a given file. Manifest paths are used within Bazel runfile +manifests and are formatted as followed: `/` +""" + +def _to_manifest_path(ctx, file): + # If a file resides outside of the current workspace, we omit the leading `../` + # segment as the rest will contain the workspace name. e.g. `../npm/node_modules/<..>`. + if file.short_path.startswith("../"): + return file.short_path[3:] + else: + return ctx.workspace_name + "/" + file.short_path + +"""Extracts all source files from the specified list of dependencies.""" + +def _extract_source_files(deps): + depsets = [] + for dep in deps: + if JSNamedModuleInfo in dep: + depsets.append(dep[JSNamedModuleInfo].sources) + elif JSModuleInfo in dep: + depsets.append(dep[JSModuleInfo].sources) + elif hasattr(dep, "files"): + depsets.append(dep.files) + return depset(transitive = depsets) + +def _linker_process(ctx): + args = ctx.actions.args() + sources = _extract_source_files(ctx.attr.srcs) + tmp_dir_name = ctx.label.name + + # The output directory manifest path. e.g `angular_material/src/cdk/a11y/linker_processed`. + output_dir_manifest_path = "%s/%s/%s" % (ctx.workspace_name, ctx.label.package, tmp_dir_name) + + # The output directory exec path. e.g `bazel_out/<..>/src/cdk/a11y/linker_processed`. + output_dir_exec_path = "%s/%s/%s" % (ctx.bin_dir.path, ctx.label.package, tmp_dir_name) + + # Given the sources being transformed and written to a new location, the AMD module names + # need to be rewritten. This file maps AMD modules as per the new location to the AMD modules + # as they appear in the sources. i.e. we generate AMD module aliases. + amd_module_mapping_file = ctx.actions.declare_file("%s/_module_mappings.js" % tmp_dir_name) + + args.add(output_dir_exec_path) + args.add(output_dir_manifest_path) + args.add(amd_module_mapping_file.path) + + outputs = [amd_module_mapping_file] + + # Iterate through the determined sources and pass them to the tool as argument. + for input_file in sources.to_list(): + output_pkg_path = _to_manifest_path(ctx, input_file) + args.add("%s:%s" % (input_file.path, output_pkg_path)) + outputs.append(ctx.actions.declare_file("%s/%s" % (tmp_dir_name, output_pkg_path))) + + # Support passing arguments through a parameter file. This is necessary because on Windows + # there is an argument limit and we need to handle a large amount of input files. Bazel + # switches between parameter file and normal argument passing based on the operating system. + # Read more here: https://docs.bazel.build/versions/master/skylark/lib/Args.html#use_param_file + args.use_param_file(param_file_arg = "--param-file=%s", use_always = True) + + ctx.actions.run( + inputs = sources, + outputs = outputs, + executable = ctx.executable._linker_process_tool, + arguments = [args], + progress_message = "NgLinkerProcess", + ) + + outputs_depset = depset(outputs) + + return [ + DefaultInfo(files = outputs_depset), + ] + +""" + Rule definition for the "linker_process" rule that can process a list of targets + with the Angular linker. The processed files can be retrieved through the default + files provider, or through the `JSNamedModuleInfo` provider. +""" +linker_process = rule( + implementation = _linker_process, + attrs = { + "srcs": attr.label_list( + allow_files = True, + doc = """List of sources that should be processed with the Angular linker.""", + ), + "_linker_process_tool": attr.label( + default = Label("//tools/linker-process"), + executable = True, + cfg = "host", + ), + }, +) diff --git a/tools/linker-process/linker-process.ts b/tools/linker-process/linker-process.ts new file mode 100644 index 000000000000..42c2bc4b82c5 --- /dev/null +++ b/tools/linker-process/linker-process.ts @@ -0,0 +1,139 @@ +import {readFileSync, writeFileSync} from 'fs'; +import {join} from 'path'; + +// These imports to `@angular/compiler-cli` need to explicitly specify the `.js` extension as +// otherwise the Bazel NodeJS module resolution would end up resolving the ESM2015 `.mjs` files. +const {NodeJSFileSystem} = require('@angular/compiler-cli/src/ngtsc/file_system/index.js'); +const {ConsoleLogger, LogLevel} = require('@angular/compiler-cli/src/ngtsc/logging/index.js'); +const {createEs2015LinkerPlugin} = require('@angular/compiler-cli/linker/babel/index.js'); + +// There are no types installed for these packages. +const babel = require('@babel/core'); +const {default: traverse} = require('@babel/traverse'); + +/** File system used by the Angular linker plugin. */ +const fileSystem = new NodeJSFileSystem(); +/** Logger used by the Angular linker plugin. */ +const logger = new ConsoleLogger(LogLevel.info); + +/** Basic interface describing a Babel AST node path. */ +interface NodePath { + type: string; + parentPath: NodePath|undefined; + node: any; + buildCodeFrameError(message: string, ctor: T): T; +} + +/** + * Determines the command line arguments for the current Bazel action. Since this action can + * have a large set of input files, Bazel may write the arguments into a parameter file. + * This function is responsible for handling normal argument passing or Bazel parameter files. + * Read more here: https://docs.bazel.build/versions/master/skylark/lib/Args.html#use_param_file + */ +function getBazelActionArguments() { + const args = process.argv.slice(2); + + // If Bazel uses a parameter file, we've specified that it passes the file in the following + // format: "arg0 arg1 --param-file={path_to_param_file}" + if (args[0].startsWith('--param-file=')) { + return readFileSync(args[0].split('=')[1], 'utf8').trim().split('\n'); + } + + return args; +} + +/** Naively checks whether this node path resolves to an Angular declare invocation. */ +function isNgDeclareCallExpression(nodePath: NodePath): boolean { + if (!nodePath.node.name.startsWith('ɵɵngDeclare')) { + return false; + } + + // Expect the `ngDeclare` identifier to be used as part of a property access that + // is invoked within a call expression. e.g. `i0.ɵɵngDeclare<>`. + return nodePath.parentPath?.type === 'MemberExpression' && + nodePath.parentPath.parentPath?.type === 'CallExpression'; +} + +/** Gets the AMD module name for a given Bazel manifest path */ +function manifestPathToAmdName(manifestPath: string): string { + return manifestPath.substring(0, manifestPath.lastIndexOf('.')); +} + +/** Generates a JavaScript file that maps certain AMD modules to their associate. */ +function generateAmdModuleMappingFile(mappings: Map): string { + let amdMappingFileContent = ` + function registerAlias(oldModuleName, newModuleName) { + define(newModuleName, ['require', 'exports', oldModuleName], (require, exports) => { + var source = require(oldModuleName); + Object.keys(source).forEach(function(key) { + exports[key] = source[key]; + }); + }); + } + `; + + for (const [oldModuleName, newModuleName] of mappings.entries()) { + amdMappingFileContent += `registerAlias("${oldModuleName}", "${newModuleName}");\n`; + } + + return amdMappingFileContent; +} + +/** Processes the given file with the Angular linker plugin. */ +function processFileWithLinker(diskFilePath: string, fileContent: string): string { + // We run the linker with JIT mode so that the processed Angular declarations could be + // run within unit tests that rely on JIT information to be available. + const linkerPlugin = createEs2015LinkerPlugin({fileSystem, logger, linkerJitMode: true}); + const {code, ast} = babel.transformSync(fileContent, { + ast: true, + filename: diskFilePath, + filenameRelative: diskFilePath, + plugins: [linkerPlugin], + compact: false, + }); + + // Naively check if there are any Angular declarations left that haven't been linked. + traverse(ast, { + Identifier: (astPath: NodePath) => { + if (isNgDeclareCallExpression(astPath)) { + throw astPath.buildCodeFrameError( + 'Found Angular declaration that has not been linked.', Error); + } + } + }); + + return code; +} + +if (require.main === module) { + const [outputDirExecPath, outputDirManifestPath, amdMappingFileExecPath, ...inputFiles] = + getBazelActionArguments(); + const amdModuleMappings = new Map(); + + for (const inputFileArg of inputFiles) { + const [inputFileExecPath, manifestPath] = inputFileArg.split(':'); + const outputExecPath = join(outputDirExecPath, manifestPath); + const manifestOutputPath = `${outputDirManifestPath}/${manifestPath}`; + const fileContent = readFileSync(inputFileExecPath, 'utf8'); + const oldAmdModuleName = manifestPathToAmdName(manifestPath); + const newAmdModuleName = manifestPathToAmdName(manifestOutputPath); + const processedContent = processFileWithLinker(inputFileExecPath, fileContent); + + // Keep track of the old AMD module name, and the expected new one. The AMD module + // names are based on the file location within the repository. This matches the + // AMD module names being generated by the NodeJS Bazel rules. + amdModuleMappings.set(oldAmdModuleName, newAmdModuleName); + + writeFileSync(outputExecPath, processedContent); + } + + // Generate a mapping file for AMD module names. Given we move sources to a new location, their + // existing AMD module names like `angular_material/src/cdk/a11y/a11y.spec` are no longer valid. + // Since we do not want to modify the AMD module code directly (and rewrite potential imports), + // we keep the original AMD module names, but also generate a new file that maps the "new" AMD + // module names (based on their new location) to the old AMD modules. This is necessary so that + // imports continue to work, and that tests can be run within the `karma_web_test` rule. + // https://github.com/bazelbuild/rules_nodejs/blob/a611b600b5d2f1242ee615ac4b1c8cd03d0c3b03/packages/concatjs/web_test/karma.conf.js#L310-L319 + const mappingContent = generateAmdModuleMappingFile(amdModuleMappings); + writeFileSync(amdMappingFileExecPath, mappingContent); +} diff --git a/tools/tsconfig-ci.json b/tools/tsconfig-ci.json index a0ac1216c4cc..8ca964ba351d 100644 --- a/tools/tsconfig-ci.json +++ b/tools/tsconfig-ci.json @@ -10,6 +10,7 @@ "lib": ["es2015"], "skipLibCheck": true, // Don't emit to the file system, because we only want to check for compilation errors. - "noEmit": true + "noEmit": true, + "downlevelIteration": true } }