From 7eddd12bf001bab9e2e320339cc5c9f698f4cfd8 Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Fri, 16 Jul 2021 20:18:18 +0200 Subject: [PATCH] feat(dev-infra): add option to setup global types in API golden test (#42876) Previously we disabled automatic type-resolution for the API extractor because in non-sandbox environments this resulted in different API reports. There are cases where global types are still needed for analysis of an entry-point. To support this, we add a new property called `types` which allows for explicit type targets being specified. Note that we do not want to determine types from the `data` runfiles because API extractor itself also brings in types which should not always be part of the API report analysis. PR Close #42876 --- dev-infra/bazel/api-golden/index.bzl | 42 +++++++++++++++---- dev-infra/bazel/api-golden/index.ts | 16 +++---- .../bazel/api-golden/index_npm_packages.ts | 9 ++-- dev-infra/bazel/api-golden/test_api_report.ts | 11 +++-- 4 files changed, 57 insertions(+), 21 deletions(-) diff --git a/dev-infra/bazel/api-golden/index.bzl b/dev-infra/bazel/api-golden/index.bzl index 9b854c9f77..6f2142fbe9 100644 --- a/dev-infra/bazel/api-golden/index.bzl +++ b/dev-infra/bazel/api-golden/index.bzl @@ -17,6 +17,27 @@ default_strip_export_pattern = "^ɵ(?!ɵdefineInjectable|ɵinject|ɵInjectableDe def _escape_regex_for_arg(value): return "\"%s\"" % value +""" + Extracts type names from a list of NPM type targets. + + For example: Consider the `@npm//@types/node` target. This function extracts `node` + from the label. This is needed so that the Node types can be wired up within a + TypeScript program using the `types` tsconfig option. +""" + +def extract_type_names_from_labels(type_targets): + type_names = [] + for type_target in type_targets: + type_package = Label(type_target).package + + if (type_package.startswith("@types/")): + type_names.append(type_package[len("@types/"):]) + else: + fail("Expected type target to match the following format: " + + "`@//@types/`, but got: %s" % type_target) + + return type_names + """ Builds an API report for the specified entry-point and compares it against the specified golden @@ -28,6 +49,7 @@ def api_golden_test( entry_point, data = [], strip_export_pattern = default_strip_export_pattern, + types = [], **kwargs): quoted_export_pattern = _escape_regex_for_arg(strip_export_pattern) @@ -46,13 +68,15 @@ def api_golden_test( include_default_files = False, ) - test_data = ["//dev-infra/bazel/api-golden", "//:package.json", ":%s_data_typings" % name] + data + test_data = ["//dev-infra/bazel/api-golden", "//:package.json", ":%s_data_typings" % name] + \ + data + types nodejs_test( name = name, data = test_data, entry_point = "//dev-infra/bazel/api-golden:index.ts", - templated_args = nodejs_test_args + [golden, entry_point, "false", quoted_export_pattern], + templated_args = nodejs_test_args + [golden, entry_point, "false", quoted_export_pattern] + + extract_type_names_from_labels(types), **kwargs ) @@ -61,7 +85,8 @@ def api_golden_test( testonly = True, data = test_data, entry_point = "//dev-infra/bazel/api-golden:index.ts", - templated_args = nodejs_test_args + [golden, entry_point, "true", quoted_export_pattern], + templated_args = nodejs_test_args + [golden, entry_point, "true", quoted_export_pattern] + + extract_type_names_from_labels(types), **kwargs ) @@ -76,6 +101,7 @@ def api_golden_test_npm_package( npm_package, data = [], strip_export_pattern = default_strip_export_pattern, + types = [], **kwargs): quoted_export_pattern = _escape_regex_for_arg(strip_export_pattern) @@ -83,17 +109,19 @@ def api_golden_test_npm_package( nodejs_test( name = name, - data = ["//dev-infra/bazel/api-golden"] + data, + data = ["//dev-infra/bazel/api-golden"] + data + types, entry_point = "//dev-infra/bazel/api-golden:index_npm_packages.ts", - templated_args = nodejs_test_args + [golden_dir, npm_package, "false", quoted_export_pattern], + templated_args = nodejs_test_args + [golden_dir, npm_package, "false", quoted_export_pattern] + + extract_type_names_from_labels(types), **kwargs ) nodejs_binary( name = name + ".accept", testonly = True, - data = ["//dev-infra/bazel/api-golden"] + data, + data = ["//dev-infra/bazel/api-golden"] + data + types, entry_point = "//dev-infra/bazel/api-golden:index_npm_packages.ts", - templated_args = nodejs_test_args + [golden_dir, npm_package, "true", quoted_export_pattern], + templated_args = nodejs_test_args + [golden_dir, npm_package, "true", quoted_export_pattern] + + extract_type_names_from_labels(types), **kwargs ) diff --git a/dev-infra/bazel/api-golden/index.ts b/dev-infra/bazel/api-golden/index.ts index 59ad79ac02..3de18aa7ca 100644 --- a/dev-infra/bazel/api-golden/index.ts +++ b/dev-infra/bazel/api-golden/index.ts @@ -17,9 +17,9 @@ import {testApiGolden} from './test_api_report'; */ async function main( goldenFilePath: string, entryPointFilePath: string, approveGolden: boolean, - stripExportPattern: RegExp) { - const {succeeded, apiReportChanged} = - await testApiGolden(goldenFilePath, entryPointFilePath, approveGolden, stripExportPattern); + stripExportPattern: RegExp, typeNames: string[]) { + const {succeeded, apiReportChanged} = await testApiGolden( + goldenFilePath, entryPointFilePath, approveGolden, stripExportPattern, typeNames); if (!succeeded && apiReportChanged) { console.error(chalk.red(`The API signature has changed and the golden file is outdated.`)); @@ -37,9 +37,11 @@ if (require.main === module) { const entryPointFilePath = runfiles.resolve(args[1]); const approveGolden = args[2] === 'true'; const stripExportPattern = new RegExp(args[3]); + const typeNames = args.slice(4); - main(goldenFilePath, entryPointFilePath, approveGolden, stripExportPattern).catch(e => { - console.error(e); - process.exit(1); - }); + main(goldenFilePath, entryPointFilePath, approveGolden, stripExportPattern, typeNames) + .catch(e => { + console.error(e); + process.exit(1); + }); } diff --git a/dev-infra/bazel/api-golden/index_npm_packages.ts b/dev-infra/bazel/api-golden/index_npm_packages.ts index 74f7d9d64a..20dc2a4d3d 100644 --- a/dev-infra/bazel/api-golden/index_npm_packages.ts +++ b/dev-infra/bazel/api-golden/index_npm_packages.ts @@ -19,7 +19,8 @@ import {testApiGolden} from './test_api_report'; * against golden files within the given golden directory. */ async function main( - goldenDir: string, npmPackageDir: string, approveGolden: boolean, stripExportPattern: RegExp) { + goldenDir: string, npmPackageDir: string, approveGolden: boolean, stripExportPattern: RegExp, + typeNames: string[]) { const entryPoints = findEntryPointsWithinNpmPackage(npmPackageDir); const outdatedGoldens: string[] = []; let allTestsSucceeding = true; @@ -35,7 +36,8 @@ async function main( const goldenFilePath = join(goldenDir, goldenName); const {succeeded, apiReportChanged} = await testApiGolden( - goldenFilePath, typesEntryPointPath, approveGolden, stripExportPattern, packageJsonPath); + goldenFilePath, typesEntryPointPath, approveGolden, stripExportPattern, typeNames, + packageJsonPath); // Keep track of outdated goldens. if (!succeeded && apiReportChanged) { @@ -63,8 +65,9 @@ if (require.main === module) { const npmPackageDir = runfiles.resolve(args[1]); const approveGolden = args[2] === 'true'; const stripExportPattern = new RegExp(args[3]); + const typeNames = args.slice(4); - main(goldenDir, npmPackageDir, approveGolden, stripExportPattern).catch(e => { + main(goldenDir, npmPackageDir, approveGolden, stripExportPattern, typeNames).catch(e => { console.error(e); process.exit(1); }); diff --git a/dev-infra/bazel/api-golden/test_api_report.ts b/dev-infra/bazel/api-golden/test_api_report.ts index d840964571..45d82713c0 100644 --- a/dev-infra/bazel/api-golden/test_api_report.ts +++ b/dev-infra/bazel/api-golden/test_api_report.ts @@ -28,13 +28,15 @@ const _origFetchAstModuleExportInfo = ExportAnalyzer.prototype.fetchAstModuleExp * @param approveGolden Whether the golden file should be updated. * @param stripExportPattern Regular Expression that can be used to filter out exports * from the API report. + * @param typeNames Name of types which should be included for analysis of the entry-point. + * Types are expected to exist within the default `node_modules/@types/` folder. * @param packageJsonPath Optional path to a `package.json` file that contains the entry * point. Note that the `package.json` is currently only used by `api-extractor` to determine * the package name displayed within the API golden. */ export async function testApiGolden( goldenFilePath: string, indexFilePath: string, approveGolden: boolean, - stripExportPattern: RegExp, + stripExportPattern: RegExp, typeNames: string[] = [], packageJsonPath = resolveWorkspacePackageJsonPath()): Promise { // If no `TEST_TMPDIR` is defined, then this script runs using `bazel run`. We use // the runfile directory as temporary directory for API extractor. @@ -43,9 +45,10 @@ export async function testApiGolden( const configObject: IConfigFile = { compiler: { overrideTsconfig: - // We disable inclusion of all `@types` installed as this throws-off API reports - // and causes different goldens when the API test is run outside sandbox. - {files: [indexFilePath], compilerOptions: {types: [], lib: ['esnext', 'dom']}} + // We disable automatic `@types` resolution as this throws-off API reports + // when the API test is run outside sandbox. Instead we expect a list of + // hard-coded types that should be included. This works in non-sandbox too. + {files: [indexFilePath], compilerOptions: {types: typeNames, lib: ['esnext', 'dom']}} }, projectFolder: dirname(packageJsonPath), mainEntryPointFilePath: indexFilePath,