-
Notifications
You must be signed in to change notification settings - Fork 17
/
update_eslint_disable_lines.js
executable file
·176 lines (153 loc) · 5.74 KB
/
update_eslint_disable_lines.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
#!/usr/bin/env node
/* eslint-disable no-console, max-len */
//
// Update eslint-disable messages
//
// usage (from khan/webapp):
// ../devtools/khan-linter/update_eslint_disable_lines.js ./javascript
//
// The updated message will appear the top of the file and look like:
// /* eslint-disable ... */
// /* TODO(csilvers): fix these lint errors ... */
// /* To fix, remove an entry above, run ka-lint, and fix the errors. */
//
// If the file include a flow pragma on any lines, we add it back in the
// header.
//
// TODO(kevinb): add --todo_target option to control the name in the TODO
// We want to avoid changing the name in messages where the list of rules
// hasn't changed so we'll want to parse existing eslint-disable lines.
//
const eslint = require('eslint');
const fs = require('fs');
const path = require('path');
const process = require('process');
/**
* Recursively walk the given path, and add any relevant .js or .jsx files to
* the given `filelist` array. `filelist` is updated in-place.
*/
const getFilePaths = (dirOrFile, filelist) => {
if (fs.statSync(dirOrFile).isDirectory()) {
// It's a directory!
const dir = dirOrFile;
// Skip node_modules.
if (dir === 'node_modules') {
return;
}
// Recurse into the directory's contents.
const childFileNames = fs.readdirSync(dir);
for (const childFileName of childFileNames) {
const childFilePath = path.join(dir, childFileName);
getFilePaths(childFilePath, filelist);
}
} else {
// It's a file!
const file = dirOrFile;
// Skip symbolic links.
if (fs.lstatSync(file).isSymbolicLink()) {
return;
}
// Skip non-JS and non-JSX files.
if (!/\.jsx?$/.test(file)) {
return;
}
// Add the file to the list of files to update.
filelist.push(file);
}
};
if (process.argv.length < 3) {
console.warn('usage: update_lint_message.js file_or_dir_1 [file_or_dir_2] [...]');
process.exit(1);
}
const filePaths = [];
for (let i = 2; i < process.argv.length; i++) {
getFilePaths(process.argv[i], filePaths);
}
const FIX_TEXT = '/* To fix, remove an entry above, run ka-lint, and fix errors. */';
const TODO_TEXT = '/* TODO(csilvers): fix these lint errors (http://eslint.org/docs/rules): */';
const cli = new eslint.CLIEngine({
configFile: path.join(__dirname, "eslintrc"),
});
const total = filePaths.length;
filePaths.forEach((filePath, index) => {
const originalSource = fs.readFileSync(filePath, {encoding: 'utf-8'});
const originalLines = originalSource.split('\n');
let usesFlow = false;
// Remove existing lint message.
let inHeader = true;
const filteredLines = originalLines.filter((line) => {
if (line.startsWith('// @flow')) {
// Don't filter this -- flow rules rely on this being set for
// linting.
usesFlow = true;
return true;
} else if (!inHeader) {
return true;
} else if (line.startsWith('/* eslint-disable ')) {
return false;
} else if (line === TODO_TEXT) {
return false;
} else if (line.startsWith('/* To fix, remove an entry above')) {
return false;
} else if (line.match(/^\s*$/)) {
// Empty lines immediately below the header count as part of the
// header, and are removed.
return false;
} else {
inHeader = false;
return true;
}
});
const report = cli.executeOnText(filteredLines.join('\n'));
const result = report.results[0];
const rules = {};
result.messages.forEach((message) => {
// TODO(kevinb): check that the ruleIds match for @Nolint(ruleId)
// There are two times where we don't want to suppress @Nolint:
// - The source line for a max-lines violation is the first line of the
// file, but the violation itself applies to the whole file so ignore
// @Nolint in that situation.
// - react/no-unsafe includes the entire component, so is likely to
// include unrelated @Nolints
let anyTargetLineHasNolint = false;
for (let i = message.line; i <= message.endLine; ++i) {
// Lines are 1-indexed.
if (/@Nolint/.test(filteredLines[i - 1])) {
anyTargetLineHasNolint = true;
}
}
if (anyTargetLineHasNolint && ["max-lines", "react/no-unsafe"
].indexOf(message.ruleId) === -1) {
return;
} else if (message.ruleId === 'max-len' &&
/require\(['"][^'"]+['"]\)| from ['"]/.test(message.source)) {
return;
} else if (message.ruleId === 'max-len' &&
/\.fixture\.jsx?$/.test(filePath)) {
return;
} else {
rules[message.ruleId] = true;
}
});
const violations = Object.keys(rules).sort();
const headerLines = [];
if (violations.length > 0) {
headerLines.push(`/* eslint-disable ${violations.join(', ')} */`);
headerLines.push(TODO_TEXT);
headerLines.push(FIX_TEXT);
}
if (usesFlow) {
headerLines.push('// @flow');
}
const updatedLines = headerLines.concat(
// We needed to keep flow pragmas in what we linted, but we're writing
// flow pragmas into the header, so get rid of them in the body.
filteredLines.filter(line => !line.startsWith('// @flow'))
);
const updatedSource = updatedLines.join('\n');
if (originalSource !== updatedSource) {
fs.writeFileSync(filePath, updatedSource, {encoding: 'utf-8'});
}
process.stdout.write(`progress: ${index + 1} of ${total}\r`);
});
console.log('');