Skip to content

Commit b9b0b28

Browse files
authored
build: add checks for public-api.ts formats (#25586)
* check that all exports from public-api.ts are explicit exports * check that all exported symbols contain 'legacy'
1 parent f379e80 commit b9b0b28

File tree

1 file changed

+99
-0
lines changed

1 file changed

+99
-0
lines changed

scripts/check-public-api-formats.js

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
const ts = require('typescript');
4+
const chalk = require('chalk');
5+
6+
/** -------------------------------------------------------------------------------------------- */
7+
/** -------------------------------------- ERROR HANDLING -------------------------------------- */
8+
/** -------------------------------------------------------------------------------------------- */
9+
10+
/** Stores all errors that are caught during execution. */
11+
const errors = [];
12+
13+
/** Throws an error explaining that the given export node must be explicit. */
14+
function throwExplicitExportError(path, content, node) {
15+
const invalidExport = content
16+
.substring(node.jsDoc ? node.jsDoc[0].end : node.pos, node.end)
17+
.trim();
18+
errors.push(
19+
chalk.red(
20+
`ERROR ${errors.length + 1}:` +
21+
`\n File: ${path}` +
22+
`\n Line: "${invalidExport}"` +
23+
`\n Re-exports must be explicit. For example:` +
24+
`\n [x] export * from './foo';` +
25+
`\n [✓] export {MatFoo} from './foo';`,
26+
),
27+
);
28+
}
29+
30+
/** Throws an error explaining that the name(s) of given export node must contain "legacy". */
31+
function throwLegacyNamingError(path, node) {
32+
const invalidSymbol = node.exportClause.elements
33+
.map(n => n.name.escapedText)
34+
.filter(n => !n.includes('legacy'))
35+
.join('\n * ');
36+
errors.push(
37+
chalk.red(
38+
`ERROR ${errors.length + 1}:` +
39+
`\n File: ${path}` +
40+
`\n The following exported symbols do not contain 'legacy':` +
41+
`\n ${invalidSymbol}`,
42+
),
43+
);
44+
}
45+
46+
/** -------------------------------------------------------------------------------------------- */
47+
/** --------------------------------------- BEGIN SCRIPT --------------------------------------- */
48+
/** -------------------------------------------------------------------------------------------- */
49+
50+
/** An array of the file paths of all public-api.ts files for legacy components. */
51+
const paths = fs
52+
.readdirSync(path.join(path.dirname(__dirname), 'src/material'))
53+
.filter(dir => dir.startsWith('legacy-'))
54+
.filter(dir => dir !== 'legacy-prebuilt-themes' && dir !== 'legacy-core')
55+
.flatMap(dir => {
56+
const base = path.join(path.dirname(__dirname), 'src/material', dir);
57+
return [path.join(base, 'public-api.ts'), path.join(base, 'testing/public-api.ts')];
58+
});
59+
60+
for (let i = 0; i < paths.length; i++) {
61+
const content = fs.readFileSync(paths[i], 'utf8');
62+
ts.createSourceFile(paths[i], content, ts.ScriptTarget.Latest).forEachChild(child => {
63+
// todo: consider enforcing this for all public-api.ts files.
64+
if (!ts.isExportDeclaration(child)) {
65+
return;
66+
}
67+
68+
// Do not allow wildcard forwarding of exports.
69+
// E.g. `export * from './foo';` vs `export {Foo} from './foo';`.
70+
if (!child.exportClause) {
71+
throwExplicitExportError(paths[i], content, child);
72+
return;
73+
}
74+
75+
// Ensure all exports from public-api.ts files nested
76+
// under src/legacy-* directory contain the word "legacy".
77+
for (let j = 0; j < child.exportClause.elements.length; j++) {
78+
if (!child.exportClause.elements[j].name.escapedText.toLowerCase().includes('legacy')) {
79+
throwLegacyNamingError(paths[i], child);
80+
return;
81+
}
82+
}
83+
});
84+
}
85+
86+
/** -------------------------------------------------------------------------------------------- */
87+
/** ------------------------------------- ERROR REPORTING -------------------------------------- */
88+
/** -------------------------------------------------------------------------------------------- */
89+
90+
if (errors.length) {
91+
const separator = chalk.red('\n-----------------------------------------------------\n');
92+
console.log(
93+
chalk.red(`\nPublic APIs check failed with ${errors.length} error(s)`),
94+
separator,
95+
errors.join(`${separator}`),
96+
separator,
97+
);
98+
process.exitCode = 1;
99+
}

0 commit comments

Comments
 (0)