Skip to content

Commit f1a9e1e

Browse files
hanslmatsko
authored andcommitted
build: add lint rule for global flags in rollup config (angular#20028)
We now verify that every imports is part of the globals defined in the files rollup.config.js. PR Close angular#20028
1 parent 54480f7 commit f1a9e1e

File tree

2 files changed

+148
-0
lines changed

2 files changed

+148
-0
lines changed

tools/tslint/rollupConfigRule.ts

+147
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. 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+
import {RuleFailure} from 'tslint/lib';
12+
import {AbstractRule} from 'tslint/lib/rules';
13+
import * as ts from 'typescript';
14+
15+
16+
function _isRollupPath(path: string) {
17+
return /rollup\.config\.js$/.test(path);
18+
}
19+
20+
// Regexes to blacklist.
21+
const sourceFilePathBlacklist = [
22+
/\.spec\.ts$/,
23+
/_spec\.ts$/,
24+
/_perf\.ts$/,
25+
/_example\.ts$/,
26+
/[/\\]test[/\\]/,
27+
/[/\\]testing_internal\.ts$/,
28+
/[/\\]integrationtest[/\\]/,
29+
/[/\\]packages[/\\]bazel[/\\]/,
30+
/[/\\]packages[/\\]benchpress[/\\]/,
31+
/[/\\]packages[/\\]examples[/\\]/,
32+
33+
// language-service bundles everything in its UMD, so we don't need a globals. There are
34+
// exceptions but we simply ignore those files from this rule.
35+
/[/\\]packages[/\\]language-service[/\\]/,
36+
37+
// service-worker is a special package that has more than one rollup config. It confuses
38+
// this lint rule and we simply ignore those files.
39+
/[/\\]packages[/\\]service-worker[/\\]/,
40+
];
41+
42+
// Import package name whitelist. These will be ignored.
43+
const importsWhitelist = [
44+
'@angular/compiler-cli', // Not used in a browser.
45+
'@angular/compiler-cli/src/language_services', // Deep import from language-service.
46+
'chokidar', // Not part of compiler-cli/browser, but still imported.
47+
'reflect-metadata',
48+
'tsickle',
49+
'url', // Part of node, no need to alias in rollup.
50+
'zone.js',
51+
];
52+
53+
const packageScopedImportWhitelist: [RegExp, string[]][] = [
54+
[/service-worker[/\\]cli/, ['@angular/service-worker']],
55+
];
56+
57+
58+
// Return true if the file should be linted.
59+
function _pathShouldBeLinted(path: string) {
60+
return /[/\\]packages[/\\]/.test(path) && sourceFilePathBlacklist.every(re => !re.test(path));
61+
}
62+
63+
64+
interface RollupMatchInfo {
65+
filePath: string;
66+
globals: {[packageName: string]: string};
67+
}
68+
const rollupConfigMap = new Map<string, RollupMatchInfo>();
69+
70+
71+
export class Rule extends AbstractRule {
72+
public apply(sourceFile: ts.SourceFile): RuleFailure[] {
73+
const allImports = <ts.ImportDeclaration[]>sourceFile.statements.filter(
74+
x => x.kind === ts.SyntaxKind.ImportDeclaration);
75+
76+
// Ignore specs, non-package files, and examples.
77+
if (!_pathShouldBeLinted(sourceFile.fileName)) {
78+
return [];
79+
}
80+
81+
// Find the rollup.config.js from this location, if it exists.
82+
// If rollup cannot be found, this is an error.
83+
let p = path.dirname(sourceFile.fileName);
84+
let checkedPaths = [];
85+
let match: RollupMatchInfo;
86+
87+
while (p.startsWith(process.cwd())) {
88+
if (rollupConfigMap.has(p)) {
89+
// We already resolved for this directory, just return it.
90+
match = rollupConfigMap.get(p);
91+
break;
92+
}
93+
94+
const allFiles = fs.readdirSync(p);
95+
const maybeRollupPath = allFiles.find(x => _isRollupPath(path.join(p, x)));
96+
if (maybeRollupPath) {
97+
const rollupFilePath = path.join(p, maybeRollupPath);
98+
const rollupConfig = require(rollupFilePath).default;
99+
match = {filePath: rollupFilePath, globals: rollupConfig && rollupConfig.globals};
100+
101+
// Update all paths that we checked along the way.
102+
checkedPaths.forEach(path => rollupConfigMap.set(path, match));
103+
rollupConfigMap.set(rollupFilePath, match);
104+
break;
105+
}
106+
107+
checkedPaths.push(p);
108+
p = path.dirname(p);
109+
}
110+
if (!match) {
111+
throw new Error(
112+
`Could not find rollup.config.js for ${JSON.stringify(sourceFile.fileName)}.`);
113+
}
114+
115+
const rollupFilePath = match.filePath;
116+
const globalConfig = match.globals || Object.create(null);
117+
118+
return allImports
119+
.map(importStatement => {
120+
const modulePath = (importStatement.moduleSpecifier as ts.StringLiteral).text;
121+
if (modulePath.startsWith('.')) {
122+
return null;
123+
}
124+
125+
if (importsWhitelist.indexOf(modulePath) != -1) {
126+
return null;
127+
}
128+
129+
for (const [re, arr] of packageScopedImportWhitelist) {
130+
if (re.test(sourceFile.fileName) && arr.indexOf(modulePath) != -1) {
131+
return null;
132+
}
133+
}
134+
135+
if (!(modulePath in globalConfig)) {
136+
return new RuleFailure(
137+
sourceFile, importStatement.getStart(), importStatement.getWidth(),
138+
`Import ${JSON.stringify(modulePath)} could not be found in the rollup config ` +
139+
`at path ${JSON.stringify(rollupFilePath)}.`,
140+
this.ruleName, );
141+
}
142+
143+
return null;
144+
})
145+
.filter(x => !!x);
146+
}
147+
}

tslint.json

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"no-jasmine-focus": true,
1313
"no-var-keyword": true,
1414
"require-internal-with-underscore": true,
15+
"rollup-config": true,
1516
"semicolon": [true],
1617
"variable-name": [true, "ban-keywords"],
1718
"no-inner-declarations": [true, "function"]

0 commit comments

Comments
 (0)