Skip to content

Commit f1b9b2c

Browse files
devversiontinayuangao
authored andcommitted
chore(forbidden-identifiers): detect incorrect line endings (#1722)
* chore(forbidden-identifiers): detect incorrect line endings * Removes stale scope checking (from `@angular2-material/$COMPONENT` times) * Adds support for error grouping (useful when having a file full of invalid line endings) * Moves chains into exta functions to understand procession easily. * Update comment wording
1 parent cb3bb7a commit f1b9b2c

File tree

1 file changed

+85
-116
lines changed

1 file changed

+85
-116
lines changed

scripts/ci/forbidden-identifiers.js

Lines changed: 85 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,11 @@ const blocked_statements = [
2626
'\\bxit\\(',
2727
'\\bdebugger;',
2828
'from \\\'rxjs/Rx\\\'',
29+
'\\r'
2930
];
3031

3132
const sourceFolders = ['./src', './e2e'];
32-
const libRoot = 'src/lib';
3333
const blockedRegex = new RegExp(blocked_statements.join('|'), 'g');
34-
const importRegex = /from\s+'(.*)';/g;
3534

3635
/*
3736
* Verify that the current PR is not adding any forbidden identifiers.
@@ -47,69 +46,105 @@ findTestFiles()
4746
.then(files => files.map(name => ({ name, content: fs.readFileSync(name, 'utf-8') })))
4847

4948
/* Run checks against content of the filtered files. */
50-
.then(diffList => {
49+
.then(diffList => findErrors(diffList))
5150

52-
return diffList.reduce((errors, diffFile) => {
53-
let fileName = diffFile.name;
54-
let content = diffFile.content.split('\n');
55-
let lineCount = 0;
51+
/* Groups similar errors to simplify console output */
52+
.then(errors => groupErrors(errors))
5653

57-
content.forEach(line => {
58-
lineCount++;
54+
/* Print the resolved errors to the console */
55+
.then(errors => printErrors(errors))
56+
57+
.catch(err => {
58+
// An error occurred in this script. Output the error and the stack.
59+
console.error(err.stack || err);
60+
process.exit(2);
61+
});
62+
63+
/**
64+
* Finds errors inside of changed files by running them against the blocked statements.
65+
* @param {{name: string, content: string}[]} diffList
66+
*/
67+
function findErrors(diffList) {
68+
return diffList.reduce((errors, diffFile) => {
69+
let fileName = diffFile.name;
70+
let content = diffFile.content.split('\n');
71+
let lineNumber = 0;
5972

60-
let matches = line.match(blockedRegex);
61-
let scopeImport = path.relative(libRoot, fileName).startsWith('..')
62-
? isRelativeScopeImport(fileName, line)
63-
: false;
73+
content.forEach(line => {
74+
lineNumber++;
6475

65-
if (matches || scopeImport) {
76+
let matches = line.match(blockedRegex);
6677

67-
let error = {
68-
fileName: fileName,
69-
lineNumber: lineCount,
70-
statement: matches && matches[0] || scopeImport.invalidStatement
71-
};
78+
if (matches) {
7279

73-
if (scopeImport) {
74-
error.messages = [
75-
'You are using an import statement, which imports a file from another scope package.',
76-
`Please import the file by using the following path: ${scopeImport.scopeImportPath}`
77-
];
78-
}
80+
errors.push({
81+
fileName,
82+
lineNumber,
83+
statement: matches[0]
84+
});
7985

80-
errors.push(error);
81-
}
82-
});
86+
}
87+
});
8388

84-
return errors;
89+
return errors;
8590

86-
}, []);
87-
})
91+
}, []);
92+
}
8893

89-
/* Print the resolved errors to the console */
90-
.then(errors => {
91-
if (errors.length > 0) {
92-
console.error('Error: You are using one or more blocked statements:\n');
94+
/**
95+
* Groups similar errors in the same file which are present over a range of lines.
96+
* @param {{fileName: string, lineNumber: number, statement: string}[]} errors
97+
*/
98+
function groupErrors(errors) {
9399

94-
errors.forEach(entry => {
95-
if (entry.messages) {
96-
entry.messages.forEach(message => console.error(` ${message}`))
97-
}
100+
let initialError, initialLine, previousLine;
98101

99-
console.error(` ${entry.fileName}@${entry.lineNumber}, Statement: ${entry.statement}.\n`);
100-
});
102+
return errors.filter(error => {
101103

102-
process.exit(1);
104+
if (initialError && isSimilarError(error)) {
105+
previousLine = error.lineNumber;
106+
107+
// Overwrite the lineNumber with a string, which indicates the range of lines.
108+
initialError.lineNumber = `${initialLine}-${previousLine}`;
109+
110+
return false;
103111
}
104-
})
105112

106-
.catch(err => {
107-
// An error occurred in this script. Output the error and the stack.
108-
console.error('An error occurred during execution:');
109-
console.error(err.stack || err);
110-
process.exit(2);
113+
initialLine = previousLine = error.lineNumber;
114+
initialError = error;
115+
116+
return true;
111117
});
112118

119+
/** Checks whether the given error is similar to the previous one. */
120+
function isSimilarError(error) {
121+
return initialError.fileName === error.fileName &&
122+
initialError.statement === error.statement &&
123+
previousLine === (error.lineNumber - 1);
124+
}
125+
}
126+
127+
/**
128+
* Prints all errors to the console and terminates the process if errors were present.
129+
* @param {{fileName: string, lineNumber: number, statement: string}[]} errors
130+
*/
131+
function printErrors(errors) {
132+
if (errors.length > 0) {
133+
134+
console.error('Error: You are using one or more blocked statements:\n');
135+
136+
errors.forEach(entry => {
137+
138+
// Stringify the statement to represent line-endings or other unescaped characters.
139+
let statement = JSON.stringify(entry.statement);
140+
141+
console.error(` ${entry.fileName}@${entry.lineNumber}, Statement: ${statement}.\n`);
142+
});
143+
144+
// Exit the process with an error exit code to notify the CI.
145+
process.exit(1);
146+
}
147+
}
113148

114149
/**
115150
* Resolves all files, which should run against the forbidden identifiers check.
@@ -120,8 +155,8 @@ function findTestFiles() {
120155
return findChangedFiles();
121156
}
122157

123-
var files = sourceFolders.map(folder => {
124-
return glob(`${folder}/**/*.ts`);
158+
let files = sourceFolders.map(folder => {
159+
return glob(`${folder}/**/*`);
125160
}).reduce((files, fileSet) => files.concat(fileSet), []);
126161

127162
return Promise.resolve(files);
@@ -150,72 +185,6 @@ function findChangedFiles() {
150185
});
151186
}
152187

153-
/**
154-
* Checks the line for any relative imports of a scope package, which should be imported by using
155-
* the scope package name instead of the relative path.
156-
* @param fileName Filename to validate the path
157-
* @param line Line to be checked.
158-
*/
159-
function isRelativeScopeImport(fileName, line) {
160-
if (fileName.startsWith(libRoot)) {
161-
return false;
162-
}
163-
164-
let importMatch = importRegex.exec(line);
165-
166-
// We have to reset the last index of the import regex, otherwise we
167-
// would have incorrect matches in the next execution.
168-
importRegex.lastIndex = 0;
169-
170-
// Skip the check if the current line doesn't match any imports.
171-
if (!importMatch) {
172-
return false;
173-
}
174-
175-
let importPath = importMatch[1];
176-
177-
// Skip the check when the import doesn't start with a dot, because the line
178-
// isn't importing any file relatively. Also applies to scope packages starting
179-
// with `@`.
180-
if (!importPath.startsWith('.')) {
181-
return false;
182-
}
183-
184-
// Transform the relative import path into an absolute path.
185-
importPath = path.resolve(path.dirname(fileName), importPath);
186-
187-
let fileScope = findScope(fileName);
188-
let importScope = findScope(importPath);
189-
190-
if (fileScope.path !== importScope.path) {
191-
192-
// Creates a valid import statement, which uses the correct scope package.
193-
let validImportPath = `@angular/material`;
194-
195-
return {
196-
fileScope: fileScope.name,
197-
importScope: importScope.name,
198-
invalidStatement: importMatch[0],
199-
scopeImportPath: validImportPath
200-
}
201-
}
202-
203-
function findScope(filePath) {
204-
// Normalize the filePath, to avoid issues with the different environments path delimiter.
205-
filePath = path.normalize(filePath);
206-
207-
// Iterate through all scope paths and try to find them inside of the file path.
208-
var scopePath = filePath.indexOf(path.normalize(libRoot)) == -1 ? libRoot : filePath;
209-
210-
// Return an object containing the name of the scope and the associated path.
211-
return {
212-
name: path.basename(scopePath),
213-
path: scopePath
214-
}
215-
}
216-
217-
}
218-
219188
/**
220189
* Executes a process command and wraps it inside of a promise.
221190
* @returns {Promise.<String>}

0 commit comments

Comments
 (0)