豆豆友情提示:这是一个非官方 GitHub 代理镜像,主要用于网络测试或访问加速。请勿在此进行登录、注册或处理任何敏感信息。进行这些操作请务必访问官方网站 github.com。 Raw 内容也通过此代理提供。
Skip to content

Commit e80739b

Browse files
authored
feat(bazel/api-golden): allow for module types to be provided
* feat(bazel/api-golden): allow for module types to be provided Currently the API golden bazel test rule will just add `@types/<..>` modules to the TSConfig `types` section. This is unreliable and relies on the Bazel NodeJS linker which does not work well on Windows. This commit refactors the logic to rely on path mappings for type resolution inside the API golden Bazel-based tests. This guarantees a working rule on Windows and also supports scoped packages. Currently scoped package types do not work, like `@types/babel__core` which is actually mapping internally to `@babel/core`. Our trick will be to use path mappings as mentioned before, manually computing the scoped package types (like with `@babel/core`). Additionally referenced types will still be loaded directly as part of the compilation inputs to support global type definitions like `NodeJS.ProcessEnv`. The solution is not 100% ideal but it's less breaky than how type resolution works within `@bazel/typescript` currently.. The biggest issue is that we assume for global types that the file is called `index.d.ts`. We can allow this to be provided in the future though, if something comes up
1 parent 9b5b3d4 commit e80739b

File tree

12 files changed

+241
-56
lines changed

12 files changed

+241
-56
lines changed

bazel/api-golden/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ ts_library(
1313
"find_entry_points.ts",
1414
"index.ts",
1515
"index_npm_packages.ts",
16+
"module_mappings.ts",
1617
"path-normalize.ts",
1718
"test_api_report.ts",
1819
],

bazel/api-golden/index.bzl

Lines changed: 44 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,43 +5,35 @@ nodejs_test_args = [
55
# Needed so that node doesn't walk back to the source directory.
66
# From there, the relative imports would point to .ts files.
77
"--node_options=--preserve-symlinks",
8-
# TODO(josephperrott): update dependency usages to no longer need bazel patch module resolver
9-
# See: https://github.com/bazelbuild/rules_nodejs/wiki#--bazel_patch_module_resolver-now-defaults-to-false-2324
10-
"--bazel_patch_module_resolver",
8+
"--nobazel_run_linker",
119
]
1210

1311
default_strip_export_pattern = "^ɵ(?!ɵdefineInjectable|ɵinject|ɵInjectableDef)"
1412

15-
"""Escapes a Regular expression so that it can be passed as process argument."""
16-
1713
def _escape_regex_for_arg(value):
14+
"""Escapes a Regular expression so that it can be passed as process argument."""
1815
return "\"%s\"" % value
1916

20-
"""
21-
Extracts type names from a list of NPM type targets.
17+
def extract_module_names_from_npm_targets(type_targets):
18+
"""Extracts the module names from a list of NPM targets.
2219
23-
For example: Consider the `@npm//@types/node` target. This function extracts `node`
24-
from the label. This is needed so that the Node types can be wired up within a
25-
TypeScript program using the `types` tsconfig option.
26-
"""
20+
For example: Consider the `@npm//@types/node` target. This function extracts
21+
`@types/node` from the label. This is needed so that the Node types can be
22+
resolved from within the test runner through runfile resolution.
23+
"""
24+
module_names = []
2725

28-
def extract_type_names_from_labels(type_targets):
29-
type_names = []
3026
for type_target in type_targets:
31-
type_package = Label(type_target).package
27+
type_label = Label(type_target)
28+
type_package = type_label.package
3229

33-
if (type_package.startswith("@types/")):
34-
type_names.append(type_package[len("@types/"):])
35-
else:
36-
fail("Expected type target to match the following format: " +
37-
"`@<npm_workspace>//@types/<name>`, but got: %s" % type_target)
30+
if type_label.workspace_name != "npm":
31+
fail("Expected type targets to be part of the `@npm` workspace." +
32+
"e.g. `@npm//@types/nodes`.")
3833

39-
return type_names
34+
module_names.append(type_package)
4035

41-
"""
42-
Builds an API report for the specified entry-point and compares it against the
43-
specified golden
44-
"""
36+
return module_names
4537

4638
def api_golden_test(
4739
name,
@@ -51,6 +43,18 @@ def api_golden_test(
5143
strip_export_pattern = default_strip_export_pattern,
5244
types = [],
5345
**kwargs):
46+
"""Builds an API report for the specified entry-point and compares it against the
47+
specified golden
48+
49+
Args;
50+
name: Name of the test target
51+
golden: Manifest path to the golden file
52+
entry_point: Manifest path to the type definition entry-point.
53+
data: Runtime dependenices needed for the rule (e.g. transitive type definitions)
54+
strip_export_pattern: An optional regular expression to filter out exports from the golden.
55+
types: Optional list of type targets to make available in the API report generation.
56+
"""
57+
5458
quoted_export_pattern = _escape_regex_for_arg(strip_export_pattern)
5559

5660
kwargs["tags"] = kwargs.get("tags", []) + ["api_guard"]
@@ -73,7 +77,7 @@ def api_golden_test(
7377
data = test_data,
7478
entry_point = "//bazel/api-golden:index.ts",
7579
templated_args = nodejs_test_args + [golden, entry_point, "false", quoted_export_pattern] +
76-
extract_type_names_from_labels(types),
80+
extract_module_names_from_npm_targets(types),
7781
**kwargs
7882
)
7983

@@ -83,15 +87,10 @@ def api_golden_test(
8387
data = test_data,
8488
entry_point = "//bazel/api-golden:index.ts",
8589
templated_args = nodejs_test_args + [golden, entry_point, "true", quoted_export_pattern] +
86-
extract_type_names_from_labels(types),
90+
extract_module_names_from_npm_targets(types),
8791
**kwargs
8892
)
8993

90-
"""
91-
Builds an API report for all entrypoints within the given NPM package and compares it
92-
against goldens within the specified directory.
93-
"""
94-
9594
def api_golden_test_npm_package(
9695
name,
9796
golden_dir,
@@ -100,6 +99,18 @@ def api_golden_test_npm_package(
10099
strip_export_pattern = default_strip_export_pattern,
101100
types = [],
102101
**kwargs):
102+
"""Builds an API report for all entry-points within the given NPM package and compares it
103+
against goldens within the specified directory.
104+
105+
Args;
106+
name: Name of the test target
107+
golden_dir: Manifest path to the golden directory
108+
npm_package: Manifest path to the NPM package.
109+
data: Runtime dependenices needed for the rule (e.g. the tree artifact of the NPM package)
110+
strip_export_pattern: An optional regular expression to filter out exports from the golden.
111+
types: Optional list of type targets to make available in the API report generation.
112+
"""
113+
103114
quoted_export_pattern = _escape_regex_for_arg(strip_export_pattern)
104115

105116
kwargs["tags"] = kwargs.get("tags", []) + ["api_guard"]
@@ -109,7 +120,7 @@ def api_golden_test_npm_package(
109120
data = ["//bazel/api-golden"] + data + types,
110121
entry_point = "//bazel/api-golden:index_npm_packages.ts",
111122
templated_args = nodejs_test_args + [golden_dir, npm_package, "false", quoted_export_pattern] +
112-
extract_type_names_from_labels(types),
123+
extract_module_names_from_npm_targets(types),
113124
**kwargs
114125
)
115126

@@ -119,6 +130,6 @@ def api_golden_test_npm_package(
119130
data = ["//bazel/api-golden"] + data + types,
120131
entry_point = "//bazel/api-golden:index_npm_packages.ts",
121132
templated_args = nodejs_test_args + [golden_dir, npm_package, "true", quoted_export_pattern] +
122-
extract_type_names_from_labels(types),
133+
extract_module_names_from_npm_targets(types),
123134
**kwargs
124135
)

bazel/api-golden/index.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {runfiles} from '@bazel/runfiles';
109
import * as chalk from 'chalk';
1110

11+
import {runfiles} from '@bazel/runfiles';
1212
import {testApiGolden} from './test_api_report';
1313

1414
/**
@@ -20,14 +20,14 @@ async function main(
2020
entryPointFilePath: string,
2121
approveGolden: boolean,
2222
stripExportPattern: RegExp,
23-
typeNames: string[],
23+
typePackageNames: string[],
2424
) {
2525
const {succeeded, apiReportChanged} = await testApiGolden(
2626
goldenFilePath,
2727
entryPointFilePath,
2828
approveGolden,
2929
stripExportPattern,
30-
typeNames,
30+
typePackageNames,
3131
);
3232

3333
if (!succeeded && apiReportChanged) {
@@ -49,12 +49,16 @@ if (require.main === module) {
4949
const entryPointFilePath = runfiles.resolve(args[1]);
5050
const approveGolden = args[2] === 'true';
5151
const stripExportPattern = new RegExp(args[3]);
52-
const typeNames = args.slice(4);
52+
const typePackageNames = args.slice(4);
5353

54-
main(goldenFilePath, entryPointFilePath, approveGolden, stripExportPattern, typeNames).catch(
55-
(e) => {
56-
console.error(e);
57-
process.exit(1);
58-
},
59-
);
54+
main(
55+
goldenFilePath,
56+
entryPointFilePath,
57+
approveGolden,
58+
stripExportPattern,
59+
typePackageNames,
60+
).catch((e) => {
61+
console.error(e);
62+
process.exit(1);
63+
});
6064
}

bazel/api-golden/index_npm_packages.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ async function main(
3333
npmPackageDir: string,
3434
approveGolden: boolean,
3535
stripExportPattern: RegExp,
36-
typeNames: string[],
36+
typePackageNames: string[],
3737
) {
3838
const packageJsonPath = join(npmPackageDir, 'package.json');
3939
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')) as PackageJson;
@@ -56,7 +56,7 @@ async function main(
5656
typesEntryPointPath,
5757
approveGolden,
5858
stripExportPattern,
59-
typeNames,
59+
typePackageNames,
6060
packageJsonPath,
6161
moduleName,
6262
);
@@ -90,9 +90,9 @@ if (require.main === module) {
9090
const npmPackageDir = runfiles.resolve(args[1]);
9191
const approveGolden = args[2] === 'true';
9292
const stripExportPattern = new RegExp(args[3]);
93-
const typeNames = args.slice(4);
93+
const typePackageNames = args.slice(4);
9494

95-
main(goldenDir, npmPackageDir, approveGolden, stripExportPattern, typeNames).catch((e) => {
95+
main(goldenDir, npmPackageDir, approveGolden, stripExportPattern, typePackageNames).catch((e) => {
9696
console.error(e);
9797
process.exit(1);
9898
});
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import * as fs from 'fs';
10+
import * as path from 'path';
11+
12+
import {runfiles} from '@bazel/runfiles';
13+
14+
/** Regular expression that matches a scoped type package name. */
15+
const scopedTypesPackageRegex = /^@types\/([^_\/]+)__(.+)/;
16+
17+
/**
18+
* Resolves type modules and returns corresponding path mappings and a
19+
* list of referenced files.
20+
*/
21+
export async function resolveTypePackages(typePackageNames: string[]): Promise<{
22+
paths: Record<string, string[]>;
23+
typeFiles: string[];
24+
}> {
25+
const typeFiles = [];
26+
const paths: Record<string, string[]> = {};
27+
28+
for (const typePackageName of typePackageNames) {
29+
const moduleNames = getModuleNamesForTypePackage(typePackageName);
30+
const {entryPointTypeFile, resolvedPackageDir} = await resolveTypeDeclarationOfPackage(
31+
typePackageName,
32+
);
33+
34+
typeFiles.push(entryPointTypeFile);
35+
36+
for (const moduleName of moduleNames) {
37+
paths[moduleName] = [resolvedPackageDir];
38+
}
39+
}
40+
41+
return {paths, typeFiles};
42+
}
43+
44+
/** Resolves the type declaration entry-point file of a given package. */
45+
async function resolveTypeDeclarationOfPackage(moduleName: string): Promise<{
46+
entryPointTypeFile: string;
47+
resolvedPackageDir: string;
48+
}> {
49+
const pkgJsonPath = runfiles.resolve(`npm/node_modules/${moduleName}/package.json`);
50+
const pkgJson = JSON.parse(await fs.promises.readFile(pkgJsonPath, 'utf8')) as {
51+
types?: string;
52+
typings?: string;
53+
};
54+
const typesRelativePath = pkgJson.types ?? pkgJson.typings;
55+
56+
if (typesRelativePath === undefined) {
57+
throw new Error(`Unable to resolve type definition for "${moduleName}".`);
58+
}
59+
60+
return {
61+
entryPointTypeFile: path.join(path.dirname(pkgJsonPath), typesRelativePath),
62+
resolvedPackageDir: path.dirname(pkgJsonPath),
63+
};
64+
}
65+
66+
/**
67+
* Gets the module names for a given type package name.
68+
*
69+
* As an example, for `@types/babel__core` this returns both the `babel__core`
70+
* and `@babel/core` module names.
71+
*/
72+
function getModuleNamesForTypePackage(name: string): string[] {
73+
if (!name.startsWith('@types/')) {
74+
return [name];
75+
}
76+
77+
const moduleName = name.slice('@types/'.length);
78+
const matches = name.match(scopedTypesPackageRegex);
79+
80+
if (matches === null) {
81+
return [moduleName];
82+
}
83+
84+
// Support potential alternative module names for scoped packages. e.g.
85+
// the `@types/babel__core` package could also be for `@babel/core`.
86+
return [moduleName, `@${matches[1]}/${matches[2]}`];
87+
}

bazel/api-golden/test/BUILD.bazel

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ api_golden_test(
88
],
99
entry_point = "dev-infra/bazel/api-golden/test/fixtures/test_fixture.d.ts",
1010
golden = "dev-infra/bazel/api-golden/test/goldens/test_golden.md",
11+
# API extractor type resolution is prone to non-sandbox errors, so we test Windows.
12+
tags = ["windows"],
1113
types = ["@npm//@types/node"],
1214
)
1315

@@ -19,6 +21,11 @@ api_golden_test_npm_package(
1921
],
2022
golden_dir = "dev-infra/bazel/api-golden/test/goldens/test_package",
2123
npm_package = "dev-infra/bazel/api-golden/test/fixtures/test_package",
24+
# API extractor type resolution is prone to non-sandbox errors, so we test Windows.
25+
tags = ["windows"],
26+
# API extractor needs to be able to resolve `@babel/core` due to an aliased namespace
27+
# we expose as part of the `nested.d.ts` fake entry-point.
28+
types = ["@npm//@types/babel__core"],
2229
)
2330

2431
api_golden_test_npm_package(
@@ -29,4 +36,6 @@ api_golden_test_npm_package(
2936
],
3037
golden_dir = "dev-infra/bazel/api-golden/test/goldens/pkg_no_exports_field",
3138
npm_package = "dev-infra/bazel/api-golden/test/fixtures/pkg_no_exports_field",
39+
# API extractor type resolution is prone to non-sandbox errors, so we test Windows.
40+
tags = ["windows"],
3241
)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import * as _babelNamespace from '@babel/core';
2+
3+
// Alias namespace.
4+
import aliasNamespace = _babelNamespace.types;
5+
6+
export import types = aliasNamespace;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
1+
import {types} from './namespace-alias';
2+
3+
import aliased = types;
4+
export import reexposed = aliased;
5+
6+
export declare function acceptVersion(v: aliased.ExistsTypeAnnotation): void;
17
export declare const nestedFile = true;

bazel/api-golden/test/goldens/test_package/testing/nested/index.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,16 @@
44
55
```ts
66

7+
import * as reexposed from '@babel/types';
8+
9+
// @public (undocumented)
10+
export function acceptVersion(v: reexposed.ExistsTypeAnnotation): void;
11+
712
// @public (undocumented)
813
export const nestedFile = true;
914

15+
export { reexposed }
16+
1017
// (No @packageDocumentation comment for this package)
1118

1219
```

0 commit comments

Comments
 (0)