eslint 3.17.1 → 3.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/lib/code-path-analysis/code-path-analyzer.js +2 -7
- package/lib/code-path-analysis/debug-helpers.js +17 -16
- package/lib/config/config-file.js +68 -38
- package/lib/eslint.js +5 -5
- package/lib/formatters/stylish.js +5 -4
- package/lib/ignored-paths.js +6 -0
- package/lib/rules/func-name-matching.js +18 -7
- package/lib/rules/no-extra-parens.js +64 -19
- package/lib/rules/no-global-assign.js +1 -1
- package/lib/rules/no-new-func.js +6 -8
- package/lib/rules/no-new.js +2 -6
- package/lib/rules/no-process-exit.js +2 -10
- package/lib/rules/no-restricted-syntax.js +6 -22
- package/lib/rules/no-sync.js +8 -13
- package/lib/rules/no-unused-expressions.js +10 -1
- package/lib/rules/no-useless-escape.js +7 -1
- package/lib/rules/sort-vars.js +3 -5
- package/lib/testers/rule-tester.js +5 -4
- package/lib/util/node-event-generator.js +274 -4
- package/lib/util/traverser.js +16 -25
- package/package.json +5 -4
package/CHANGELOG.md
CHANGED
@@ -1,3 +1,29 @@
|
|
1
|
+
v3.18.0 - March 17, 2017
|
2
|
+
|
3
|
+
* 85f74ca Fix: broken code path of direct nested loops (fixes #8248) (#8274) (Toru Nagashima)
|
4
|
+
* a61c359 Fix: Ignore hidden folders when resolving globs (fixes #8259) (#8270) (Ian VanSchooten)
|
5
|
+
* 6f05546 Chore: convert StubModuleResolver in config tests to ES6 class (#8265) (Teddy Katz)
|
6
|
+
* 0c0fc31 Fix: false positive of no-extra-parens about spread and sequense (#8275) (Toru Nagashima)
|
7
|
+
* e104973 Docs: remove self-reference in no-restricted-syntax docs (#8277) (Vitor Balocco)
|
8
|
+
* 23eca51 Update: Add allowTaggedTemplates to no-unused-expressions (fixes #7632) (#8253) (Kevin Partington)
|
9
|
+
* f9ede3f Upgrade: doctrine to 2.0.0 (#8269) (alberto)
|
10
|
+
* 1b678a6 New: allow rules to listen for AST selectors (fixes #5407) (#7833) (Teddy Katz)
|
11
|
+
* 63ca0c5 Chore: use precalculated counts in stylish formatter (#8251) (alberto)
|
12
|
+
* 47c3171 Fix: typo in console.error (#8258) (Jan Peer Stöcklmair)
|
13
|
+
* e74ed6d Chore: convert Traverser to ES6 class (refs #7849) (#8232) (Teddy Katz)
|
14
|
+
* 13eead9 Fix: sort-vars crash on mixed destructuring declarations (#8245) (Teddy Katz)
|
15
|
+
* 133f489 Fix: func-name-matching crash on destructuring assignment to functions (#8247) (Teddy Katz)
|
16
|
+
* a34b9c4 Fix: func-name-matching crash on non-string literal computed keys (#8246) (Teddy Katz)
|
17
|
+
* 7276e6d Docs: remove unneeded semicolons in arrow-parens.md (#8249) (Dmitry Gershun)
|
18
|
+
* 8c40a25 concat-stream known to be vulnerable prior 1.5.2 (#8228) (Samuel)
|
19
|
+
* 149c055 Upgrade: mock-fs to v4.2.0 (fixes #8194) (#8243) (Teddy Katz)
|
20
|
+
* a83bff9 Build: remove unneeded json config in demo (fixes #8237) (#8242) (alberto)
|
21
|
+
* df12137 Docs: fix typos (#8235) (Gyandeep Singh)
|
22
|
+
* b5e9788 Chore: rename no-extra-parens methods (#8225) (Vitor Balocco)
|
23
|
+
* 7f8afe6 Update: no-extra-parens overlooked spread and superClass (fixes #8175) (#8209) (Toru Nagashima)
|
24
|
+
* ce6ff56 Docs: set recommended true for no-global-assign (fixes #8215) (#8218) (BinYi LIU)
|
25
|
+
* 5b5c236 Fix: wrong comment when module not found in config (fixes #8192) (#8196) (alberto)
|
26
|
+
|
1
27
|
v3.17.1 - March 6, 2017
|
2
28
|
|
3
29
|
* f8c8e6e Build: change mock-fs path without SSH (fixes #8207) (#8208) (Toru Nagashima)
|
@@ -512,13 +512,8 @@ function processCodePathToExit(analyzer, node) {
|
|
512
512
|
break;
|
513
513
|
}
|
514
514
|
|
515
|
-
|
516
|
-
|
517
|
-
* the node type is the same as the parent node type.
|
518
|
-
*/
|
519
|
-
if (!dontForward && (!node.parent || node.type !== node.parent.type)) {
|
520
|
-
|
521
|
-
// Emits onCodePathSegmentStart events if updated.
|
515
|
+
// Emits onCodePathSegmentStart events if updated.
|
516
|
+
if (!dontForward) {
|
522
517
|
forwardCurrentToHead(analyzer, node);
|
523
518
|
}
|
524
519
|
debug.dumpState(node, state, true);
|
@@ -107,22 +107,23 @@ module.exports = {
|
|
107
107
|
text += "style=\"rounded,dashed,filled\",fillcolor=\"#FF9800\",label=\"<<unreachable>>\\n";
|
108
108
|
}
|
109
109
|
|
110
|
-
if (segment.internal.nodes.length > 0) {
|
111
|
-
text +=
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
110
|
+
if (segment.internal.nodes.length > 0 || segment.internal.exitNodes.length > 0) {
|
111
|
+
text += [].concat(
|
112
|
+
segment.internal.nodes.map(node => {
|
113
|
+
switch (node.type) {
|
114
|
+
case "Identifier": return `${node.type} (${node.name})`;
|
115
|
+
case "Literal": return `${node.type} (${node.value})`;
|
116
|
+
default: return node.type;
|
117
|
+
}
|
118
|
+
}),
|
119
|
+
segment.internal.exitNodes.map(node => {
|
120
|
+
switch (node.type) {
|
121
|
+
case "Identifier": return `${node.type}:exit (${node.name})`;
|
122
|
+
case "Literal": return `${node.type}:exit (${node.value})`;
|
123
|
+
default: return `${node.type}:exit`;
|
124
|
+
}
|
125
|
+
})
|
126
|
+
).join("\\n");
|
126
127
|
} else {
|
127
128
|
text += "????";
|
128
129
|
}
|
@@ -183,6 +183,22 @@ function loadPackageJSONConfigFile(filePath) {
|
|
183
183
|
}
|
184
184
|
}
|
185
185
|
|
186
|
+
/**
|
187
|
+
* Creates an error to notify about a missing config to extend from.
|
188
|
+
* @param {string} configName The name of the missing config.
|
189
|
+
* @returns {Error} The error object to throw
|
190
|
+
* @private
|
191
|
+
*/
|
192
|
+
function configMissingError(configName) {
|
193
|
+
const error = new Error(`Failed to load config "${configName}" to extend from.`);
|
194
|
+
|
195
|
+
error.messageTemplate = "extend-config-missing";
|
196
|
+
error.messageData = {
|
197
|
+
configName
|
198
|
+
};
|
199
|
+
return error;
|
200
|
+
}
|
201
|
+
|
186
202
|
/**
|
187
203
|
* Loads a configuration file regardless of the source. Inspects the file path
|
188
204
|
* to determine the correctly way to load the config file.
|
@@ -199,6 +215,9 @@ function loadConfigFile(file) {
|
|
199
215
|
config = loadJSConfigFile(filePath);
|
200
216
|
if (file.configName) {
|
201
217
|
config = config.configs[file.configName];
|
218
|
+
if (!config) {
|
219
|
+
throw configMissingError(file.configFullName);
|
220
|
+
}
|
202
221
|
}
|
203
222
|
break;
|
204
223
|
|
@@ -340,6 +359,33 @@ function getLookupPath(configFilePath) {
|
|
340
359
|
return path.join(basedir, "node_modules");
|
341
360
|
}
|
342
361
|
|
362
|
+
/**
|
363
|
+
* Resolves a eslint core config path
|
364
|
+
* @param {string} name The eslint config name.
|
365
|
+
* @returns {string} The resolved path of the config.
|
366
|
+
* @private
|
367
|
+
*/
|
368
|
+
function getEslintCoreConfigPath(name) {
|
369
|
+
if (name === "eslint:recommended") {
|
370
|
+
|
371
|
+
/*
|
372
|
+
* Add an explicit substitution for eslint:recommended to
|
373
|
+
* conf/eslint-recommended.js.
|
374
|
+
*/
|
375
|
+
return path.resolve(__dirname, "../../conf/eslint-recommended.js");
|
376
|
+
}
|
377
|
+
|
378
|
+
if (name === "eslint:all") {
|
379
|
+
|
380
|
+
/*
|
381
|
+
* Add an explicit substitution for eslint:all to conf/eslint-all.js
|
382
|
+
*/
|
383
|
+
return path.resolve(__dirname, "../../conf/eslint-all.js");
|
384
|
+
}
|
385
|
+
|
386
|
+
throw configMissingError(name);
|
387
|
+
}
|
388
|
+
|
343
389
|
/**
|
344
390
|
* Applies values from the "extends" field in a configuration file.
|
345
391
|
* @param {Object} config The configuration information.
|
@@ -360,43 +406,23 @@ function applyExtends(config, filePath, relativeTo) {
|
|
360
406
|
|
361
407
|
// Make the last element in an array take the highest precedence
|
362
408
|
config = configExtends.reduceRight((previousValue, parentPath) => {
|
363
|
-
|
364
|
-
if (parentPath === "eslint:recommended") {
|
365
|
-
|
366
|
-
/*
|
367
|
-
* Add an explicit substitution for eslint:recommended to
|
368
|
-
* conf/eslint-recommended.js.
|
369
|
-
*/
|
370
|
-
parentPath = path.resolve(__dirname, "../../conf/eslint-recommended.js");
|
371
|
-
} else if (parentPath === "eslint:all") {
|
372
|
-
|
373
|
-
/*
|
374
|
-
* Add an explicit substitution for eslint:all to conf/eslint-all.js
|
375
|
-
*/
|
376
|
-
parentPath = path.resolve(__dirname, "../../conf/eslint-all.js");
|
377
|
-
} else if (isFilePath(parentPath)) {
|
378
|
-
|
379
|
-
/*
|
380
|
-
* If the `extends` path is relative, use the directory of the current configuration
|
381
|
-
* file as the reference point. Otherwise, use as-is.
|
382
|
-
*/
|
383
|
-
parentPath = (!path.isAbsolute(parentPath)
|
384
|
-
? path.join(relativeTo || path.dirname(filePath), parentPath)
|
385
|
-
: parentPath
|
386
|
-
);
|
387
|
-
}
|
388
|
-
|
389
409
|
try {
|
410
|
+
if (parentPath.startsWith("eslint:")) {
|
411
|
+
parentPath = getEslintCoreConfigPath(parentPath);
|
412
|
+
} else if (isFilePath(parentPath)) {
|
413
|
+
|
414
|
+
/*
|
415
|
+
* If the `extends` path is relative, use the directory of the current configuration
|
416
|
+
* file as the reference point. Otherwise, use as-is.
|
417
|
+
*/
|
418
|
+
parentPath = (path.isAbsolute(parentPath)
|
419
|
+
? parentPath
|
420
|
+
: path.join(relativeTo || path.dirname(filePath), parentPath)
|
421
|
+
);
|
422
|
+
}
|
390
423
|
debug(`Loading ${parentPath}`);
|
391
424
|
return ConfigOps.merge(load(parentPath, false, relativeTo), previousValue);
|
392
425
|
} catch (e) {
|
393
|
-
if (parentPath.indexOf("plugin:") === 0 || parentPath.indexOf("eslint:") === 0) {
|
394
|
-
e.message = `Failed to load config "${parentPath}" to extend from.`;
|
395
|
-
e.messageTemplate = "extend-config-missing";
|
396
|
-
e.messageData = {
|
397
|
-
configName: parentPath
|
398
|
-
};
|
399
|
-
}
|
400
426
|
|
401
427
|
/*
|
402
428
|
* If the file referenced by `extends` failed to load, add the path
|
@@ -462,7 +488,10 @@ function normalizePackageName(name, prefix) {
|
|
462
488
|
* or package name.
|
463
489
|
* @param {string} filePath The filepath to resolve.
|
464
490
|
* @param {string} [relativeTo] The path to resolve relative to.
|
465
|
-
* @returns {Object}
|
491
|
+
* @returns {Object} An object containing 3 properties:
|
492
|
+
* - 'filePath' (required) the resolved path that can be used directly to load the configuration.
|
493
|
+
* - 'configName' the name of the configuration inside the plugin.
|
494
|
+
* - 'configFullName' the name of the configuration as used in the eslint config (e.g. 'plugin:node/recommended').
|
466
495
|
* @private
|
467
496
|
*/
|
468
497
|
function resolve(filePath, relativeTo) {
|
@@ -471,14 +500,15 @@ function resolve(filePath, relativeTo) {
|
|
471
500
|
}
|
472
501
|
let normalizedPackageName;
|
473
502
|
|
474
|
-
if (filePath.
|
475
|
-
const
|
503
|
+
if (filePath.startsWith("plugin:")) {
|
504
|
+
const configFullName = filePath;
|
505
|
+
const pluginName = filePath.substr(7, filePath.lastIndexOf("/") - 7);
|
476
506
|
const configName = filePath.substr(filePath.lastIndexOf("/") + 1, filePath.length - filePath.lastIndexOf("/") - 1);
|
477
507
|
|
478
|
-
normalizedPackageName = normalizePackageName(
|
508
|
+
normalizedPackageName = normalizePackageName(pluginName, "eslint-plugin");
|
479
509
|
debug(`Attempting to resolve ${normalizedPackageName}`);
|
480
510
|
filePath = resolver.resolve(normalizedPackageName, getLookupPath(relativeTo));
|
481
|
-
return { filePath, configName };
|
511
|
+
return { filePath, configName, configFullName };
|
482
512
|
}
|
483
513
|
normalizedPackageName = normalizePackageName(filePath, "eslint-config");
|
484
514
|
debug(`Attempting to resolve ${normalizedPackageName}`);
|
package/lib/eslint.js
CHANGED
@@ -867,11 +867,11 @@ module.exports = (function() {
|
|
867
867
|
const rule = ruleCreator.create ? ruleCreator.create(ruleContext)
|
868
868
|
: ruleCreator(ruleContext);
|
869
869
|
|
870
|
-
// add all the
|
871
|
-
Object.keys(rule).forEach(
|
872
|
-
api.on(
|
873
|
-
? timing.time(key, rule[
|
874
|
-
: rule[
|
870
|
+
// add all the selectors from the rule as listeners
|
871
|
+
Object.keys(rule).forEach(selector => {
|
872
|
+
api.on(selector, timing.enabled
|
873
|
+
? timing.time(key, rule[selector])
|
874
|
+
: rule[selector]
|
875
875
|
);
|
876
876
|
});
|
877
877
|
} catch (ex) {
|
@@ -28,7 +28,6 @@ function pluralize(word, count) {
|
|
28
28
|
module.exports = function(results) {
|
29
29
|
|
30
30
|
let output = "\n",
|
31
|
-
total = 0,
|
32
31
|
errors = 0,
|
33
32
|
warnings = 0,
|
34
33
|
summaryColor = "yellow";
|
@@ -40,7 +39,9 @@ module.exports = function(results) {
|
|
40
39
|
return;
|
41
40
|
}
|
42
41
|
|
43
|
-
|
42
|
+
errors += result.errorCount;
|
43
|
+
warnings += result.warningCount;
|
44
|
+
|
44
45
|
output += `${chalk.underline(result.filePath)}\n`;
|
45
46
|
|
46
47
|
output += `${table(
|
@@ -50,10 +51,8 @@ module.exports = function(results) {
|
|
50
51
|
if (message.fatal || message.severity === 2) {
|
51
52
|
messageType = chalk.red("error");
|
52
53
|
summaryColor = "red";
|
53
|
-
errors++;
|
54
54
|
} else {
|
55
55
|
messageType = chalk.yellow("warning");
|
56
|
-
warnings++;
|
57
56
|
}
|
58
57
|
|
59
58
|
return [
|
@@ -74,6 +73,8 @@ module.exports = function(results) {
|
|
74
73
|
).split("\n").map(el => el.replace(/(\d+)\s+(\d+)/, (m, p1, p2) => chalk.dim(`${p1}:${p2}`))).join("\n")}\n\n`;
|
75
74
|
});
|
76
75
|
|
76
|
+
const total = errors + warnings;
|
77
|
+
|
77
78
|
if (total > 0) {
|
78
79
|
output += chalk[summaryColor].bold([
|
79
80
|
"\u2716 ", total, pluralize(" problem", total),
|
package/lib/ignored-paths.js
CHANGED
@@ -201,6 +201,12 @@ class IgnoredPaths {
|
|
201
201
|
|
202
202
|
const ig = ignore().add(DEFAULT_IGNORE_DIRS);
|
203
203
|
|
204
|
+
if (this.options.dotfiles !== true) {
|
205
|
+
|
206
|
+
// Ignore hidden folders. (This cannot be ".*", or else it's not possible to unignore hidden files)
|
207
|
+
ig.add([".*/*", "!../"]);
|
208
|
+
}
|
209
|
+
|
204
210
|
if (this.options.ignore) {
|
205
211
|
ig.add(this.ig.custom);
|
206
212
|
}
|
@@ -132,6 +132,15 @@ module.exports = {
|
|
132
132
|
});
|
133
133
|
}
|
134
134
|
|
135
|
+
/**
|
136
|
+
* Determines whether a given node is a string literal
|
137
|
+
* @param {ASTNode} node The node to check
|
138
|
+
* @returns {boolean} `true` if the node is a string literal
|
139
|
+
*/
|
140
|
+
function isStringLiteral(node) {
|
141
|
+
return node.type === "Literal" && typeof node.value === "string";
|
142
|
+
}
|
143
|
+
|
135
144
|
//--------------------------------------------------------------------------
|
136
145
|
// Public
|
137
146
|
//--------------------------------------------------------------------------
|
@@ -139,7 +148,7 @@ module.exports = {
|
|
139
148
|
return {
|
140
149
|
|
141
150
|
VariableDeclarator(node) {
|
142
|
-
if (!node.init || node.init.type !== "FunctionExpression") {
|
151
|
+
if (!node.init || node.init.type !== "FunctionExpression" || node.id.type !== "Identifier") {
|
143
152
|
return;
|
144
153
|
}
|
145
154
|
if (node.init.id && shouldWarn(node.id.name, node.init.id.name)) {
|
@@ -148,10 +157,12 @@ module.exports = {
|
|
148
157
|
},
|
149
158
|
|
150
159
|
AssignmentExpression(node) {
|
151
|
-
if (
|
152
|
-
|
153
|
-
(
|
154
|
-
|
160
|
+
if (
|
161
|
+
node.right.type !== "FunctionExpression" ||
|
162
|
+
(node.left.computed && node.left.property.type !== "Literal") ||
|
163
|
+
(!includeModuleExports && isModuleExports(node.left)) ||
|
164
|
+
(node.left.type !== "Identifier" && node.left.type !== "MemberExpression")
|
165
|
+
) {
|
155
166
|
return;
|
156
167
|
}
|
157
168
|
|
@@ -164,13 +175,13 @@ module.exports = {
|
|
164
175
|
},
|
165
176
|
|
166
177
|
Property(node) {
|
167
|
-
if (node.value.type !== "FunctionExpression" || !node.value.id || node.computed && node.key
|
178
|
+
if (node.value.type !== "FunctionExpression" || !node.value.id || node.computed && !isStringLiteral(node.key)) {
|
168
179
|
return;
|
169
180
|
}
|
170
181
|
if (node.key.type === "Identifier" && shouldWarn(node.key.name, node.value.id.name)) {
|
171
182
|
report(node, node.key.name, node.value.id.name, true);
|
172
183
|
} else if (
|
173
|
-
node.key
|
184
|
+
isStringLiteral(node.key) &&
|
174
185
|
isIdentifier(node.key.value, ecmaVersion) &&
|
175
186
|
shouldWarn(node.key.value, node.value.id.name)
|
176
187
|
) {
|
@@ -67,6 +67,8 @@ module.exports = {
|
|
67
67
|
const NESTED_BINARY = ALL_NODES && context.options[1] && context.options[1].nestedBinaryExpressions === false;
|
68
68
|
const EXCEPT_RETURN_ASSIGN = ALL_NODES && context.options[1] && context.options[1].returnAssign === false;
|
69
69
|
const IGNORE_JSX = ALL_NODES && context.options[1] && context.options[1].ignoreJSX;
|
70
|
+
const PRECEDENCE_OF_ASSIGNMENT_EXPR = precedence({ type: "AssignmentExpression" });
|
71
|
+
const PRECEDENCE_OF_UPDATE_EXPR = precedence({ type: "UpdateExpression" });
|
70
72
|
|
71
73
|
/**
|
72
74
|
* Determines if this rule should be enforced for a node given the current configuration.
|
@@ -365,7 +367,7 @@ module.exports = {
|
|
365
367
|
* @returns {void}
|
366
368
|
* @private
|
367
369
|
*/
|
368
|
-
function
|
370
|
+
function checkUnaryUpdate(node) {
|
369
371
|
if (node.type === "UnaryExpression" && node.argument.type === "BinaryExpression" && node.argument.operator === "**") {
|
370
372
|
return;
|
371
373
|
}
|
@@ -381,7 +383,7 @@ module.exports = {
|
|
381
383
|
* @returns {void}
|
382
384
|
* @private
|
383
385
|
*/
|
384
|
-
function
|
386
|
+
function checkCallNew(node) {
|
385
387
|
if (hasExcessParens(node.callee) && precedence(node.callee) >= precedence(node) && !(
|
386
388
|
node.type === "CallExpression" &&
|
387
389
|
(node.callee.type === "FunctionExpression" ||
|
@@ -393,12 +395,12 @@ module.exports = {
|
|
393
395
|
report(node.callee);
|
394
396
|
}
|
395
397
|
if (node.arguments.length === 1) {
|
396
|
-
if (hasDoubleExcessParens(node.arguments[0]) && precedence(node.arguments[0]) >=
|
398
|
+
if (hasDoubleExcessParens(node.arguments[0]) && precedence(node.arguments[0]) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
|
397
399
|
report(node.arguments[0]);
|
398
400
|
}
|
399
401
|
} else {
|
400
402
|
[].forEach.call(node.arguments, arg => {
|
401
|
-
if (hasExcessParens(arg) && precedence(arg) >=
|
403
|
+
if (hasExcessParens(arg) && precedence(arg) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
|
402
404
|
report(arg);
|
403
405
|
}
|
404
406
|
});
|
@@ -411,7 +413,7 @@ module.exports = {
|
|
411
413
|
* @returns {void}
|
412
414
|
* @private
|
413
415
|
*/
|
414
|
-
function
|
416
|
+
function checkBinaryLogical(node) {
|
415
417
|
const prec = precedence(node);
|
416
418
|
const leftPrecedence = precedence(node.left);
|
417
419
|
const rightPrecedence = precedence(node.right);
|
@@ -428,10 +430,46 @@ module.exports = {
|
|
428
430
|
}
|
429
431
|
}
|
430
432
|
|
433
|
+
/**
|
434
|
+
* Check the parentheses around the super class of the given class definition.
|
435
|
+
* @param {ASTNode} node The node of class declarations to check.
|
436
|
+
* @returns {void}
|
437
|
+
*/
|
438
|
+
function checkClass(node) {
|
439
|
+
if (!node.superClass) {
|
440
|
+
return;
|
441
|
+
}
|
442
|
+
|
443
|
+
// If `node.superClass` is a LeftHandSideExpression, parentheses are extra.
|
444
|
+
// Otherwise, parentheses are needed.
|
445
|
+
const hasExtraParens = precedence(node.superClass) > PRECEDENCE_OF_UPDATE_EXPR
|
446
|
+
? hasExcessParens(node.superClass)
|
447
|
+
: hasDoubleExcessParens(node.superClass);
|
448
|
+
|
449
|
+
if (hasExtraParens) {
|
450
|
+
report(node.superClass);
|
451
|
+
}
|
452
|
+
}
|
453
|
+
|
454
|
+
/**
|
455
|
+
* Check the parentheses around the argument of the given spread operator.
|
456
|
+
* @param {ASTNode} node The node of spread elements/properties to check.
|
457
|
+
* @returns {void}
|
458
|
+
*/
|
459
|
+
function checkSpreadOperator(node) {
|
460
|
+
const hasExtraParens = precedence(node.argument) >= PRECEDENCE_OF_ASSIGNMENT_EXPR
|
461
|
+
? hasExcessParens(node.argument)
|
462
|
+
: hasDoubleExcessParens(node.argument);
|
463
|
+
|
464
|
+
if (hasExtraParens) {
|
465
|
+
report(node.argument);
|
466
|
+
}
|
467
|
+
}
|
468
|
+
|
431
469
|
return {
|
432
470
|
ArrayExpression(node) {
|
433
471
|
[].forEach.call(node.elements, e => {
|
434
|
-
if (e && hasExcessParens(e) && precedence(e) >=
|
472
|
+
if (e && hasExcessParens(e) && precedence(e) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
|
435
473
|
report(e);
|
436
474
|
}
|
437
475
|
});
|
@@ -443,7 +481,7 @@ module.exports = {
|
|
443
481
|
}
|
444
482
|
|
445
483
|
if (node.body.type !== "BlockStatement") {
|
446
|
-
if (sourceCode.getFirstToken(node.body).value !== "{" && hasExcessParens(node.body) && precedence(node.body) >=
|
484
|
+
if (sourceCode.getFirstToken(node.body).value !== "{" && hasExcessParens(node.body) && precedence(node.body) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
|
447
485
|
report(node.body);
|
448
486
|
return;
|
449
487
|
}
|
@@ -465,8 +503,8 @@ module.exports = {
|
|
465
503
|
}
|
466
504
|
},
|
467
505
|
|
468
|
-
BinaryExpression:
|
469
|
-
CallExpression:
|
506
|
+
BinaryExpression: checkBinaryLogical,
|
507
|
+
CallExpression: checkCallNew,
|
470
508
|
|
471
509
|
ConditionalExpression(node) {
|
472
510
|
if (isReturnAssignException(node)) {
|
@@ -477,11 +515,11 @@ module.exports = {
|
|
477
515
|
report(node.test);
|
478
516
|
}
|
479
517
|
|
480
|
-
if (hasExcessParens(node.consequent) && precedence(node.consequent) >=
|
518
|
+
if (hasExcessParens(node.consequent) && precedence(node.consequent) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
|
481
519
|
report(node.consequent);
|
482
520
|
}
|
483
521
|
|
484
|
-
if (hasExcessParens(node.alternate) && precedence(node.alternate) >=
|
522
|
+
if (hasExcessParens(node.alternate) && precedence(node.alternate) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
|
485
523
|
report(node.alternate);
|
486
524
|
}
|
487
525
|
},
|
@@ -546,7 +584,7 @@ module.exports = {
|
|
546
584
|
}
|
547
585
|
},
|
548
586
|
|
549
|
-
LogicalExpression:
|
587
|
+
LogicalExpression: checkBinaryLogical,
|
550
588
|
|
551
589
|
MemberExpression(node) {
|
552
590
|
if (
|
@@ -576,13 +614,13 @@ module.exports = {
|
|
576
614
|
}
|
577
615
|
},
|
578
616
|
|
579
|
-
NewExpression:
|
617
|
+
NewExpression: checkCallNew,
|
580
618
|
|
581
619
|
ObjectExpression(node) {
|
582
620
|
[].forEach.call(node.properties, e => {
|
583
621
|
const v = e.value;
|
584
622
|
|
585
|
-
if (v && hasExcessParens(v) && precedence(v) >=
|
623
|
+
if (v && hasExcessParens(v) && precedence(v) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
|
586
624
|
report(v);
|
587
625
|
}
|
588
626
|
});
|
@@ -632,13 +670,13 @@ module.exports = {
|
|
632
670
|
}
|
633
671
|
},
|
634
672
|
|
635
|
-
UnaryExpression:
|
636
|
-
UpdateExpression:
|
637
|
-
AwaitExpression:
|
673
|
+
UnaryExpression: checkUnaryUpdate,
|
674
|
+
UpdateExpression: checkUnaryUpdate,
|
675
|
+
AwaitExpression: checkUnaryUpdate,
|
638
676
|
|
639
677
|
VariableDeclarator(node) {
|
640
678
|
if (node.init && hasExcessParens(node.init) &&
|
641
|
-
precedence(node.init) >=
|
679
|
+
precedence(node.init) >= PRECEDENCE_OF_ASSIGNMENT_EXPR &&
|
642
680
|
|
643
681
|
// RegExp literal is allowed to have parens (#1589)
|
644
682
|
!(node.init.type === "Literal" && node.init.regex)) {
|
@@ -668,7 +706,14 @@ module.exports = {
|
|
668
706
|
report(node.argument);
|
669
707
|
}
|
670
708
|
}
|
671
|
-
}
|
709
|
+
},
|
710
|
+
|
711
|
+
ClassDeclaration: checkClass,
|
712
|
+
ClassExpression: checkClass,
|
713
|
+
|
714
|
+
SpreadElement: checkSpreadOperator,
|
715
|
+
SpreadProperty: checkSpreadOperator,
|
716
|
+
ExperimentalSpreadProperty: checkSpreadOperator
|
672
717
|
};
|
673
718
|
|
674
719
|
}
|
package/lib/rules/no-new-func.js
CHANGED
@@ -27,20 +27,18 @@ module.exports = {
|
|
27
27
|
//--------------------------------------------------------------------------
|
28
28
|
|
29
29
|
/**
|
30
|
-
*
|
31
|
-
* @param {ASTNode} node The node to
|
30
|
+
* Reports a node.
|
31
|
+
* @param {ASTNode} node The node to report
|
32
32
|
* @returns {void}
|
33
33
|
* @private
|
34
34
|
*/
|
35
|
-
function
|
36
|
-
|
37
|
-
context.report({ node, message: "The Function constructor is eval." });
|
38
|
-
}
|
35
|
+
function report(node) {
|
36
|
+
context.report({ node, message: "The Function constructor is eval." });
|
39
37
|
}
|
40
38
|
|
41
39
|
return {
|
42
|
-
NewExpression:
|
43
|
-
CallExpression:
|
40
|
+
"NewExpression[callee.name = 'Function']": report,
|
41
|
+
"CallExpression[callee.name = 'Function']": report
|
44
42
|
};
|
45
43
|
|
46
44
|
}
|
package/lib/rules/no-new.js
CHANGED
@@ -24,12 +24,8 @@ module.exports = {
|
|
24
24
|
create(context) {
|
25
25
|
|
26
26
|
return {
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
if (node.expression.type === "NewExpression") {
|
31
|
-
context.report({ node, message: "Do not use 'new' for side effects." });
|
32
|
-
}
|
27
|
+
"ExpressionStatement > NewExpression"(node) {
|
28
|
+
context.report({ node: node.parent, message: "Do not use 'new' for side effects." });
|
33
29
|
}
|
34
30
|
};
|
35
31
|
|
@@ -26,17 +26,9 @@ module.exports = {
|
|
26
26
|
//--------------------------------------------------------------------------
|
27
27
|
|
28
28
|
return {
|
29
|
-
|
30
|
-
|
31
|
-
const callee = node.callee;
|
32
|
-
|
33
|
-
if (callee.type === "MemberExpression" && callee.object.name === "process" &&
|
34
|
-
callee.property.name === "exit"
|
35
|
-
) {
|
36
|
-
context.report({ node, message: "Don't use process.exit(); throw an error instead." });
|
37
|
-
}
|
29
|
+
"CallExpression > MemberExpression.callee[object.name = 'process'][property.name = 'exit']"(node) {
|
30
|
+
context.report({ node: node.parent, message: "Don't use process.exit(); throw an error instead." });
|
38
31
|
}
|
39
|
-
|
40
32
|
};
|
41
33
|
|
42
34
|
}
|
@@ -8,8 +8,6 @@
|
|
8
8
|
// Rule Definition
|
9
9
|
//------------------------------------------------------------------------------
|
10
10
|
|
11
|
-
const nodeTypes = require("espree").Syntax;
|
12
|
-
|
13
11
|
module.exports = {
|
14
12
|
meta: {
|
15
13
|
docs: {
|
@@ -20,32 +18,18 @@ module.exports = {
|
|
20
18
|
|
21
19
|
schema: {
|
22
20
|
type: "array",
|
23
|
-
items: [
|
24
|
-
{
|
25
|
-
enum: Object.keys(nodeTypes).map(k => nodeTypes[k])
|
26
|
-
}
|
27
|
-
],
|
21
|
+
items: [{ type: "string" }],
|
28
22
|
uniqueItems: true,
|
29
23
|
minItems: 0
|
30
24
|
}
|
31
25
|
},
|
32
26
|
|
33
27
|
create(context) {
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
*/
|
40
|
-
function warn(node) {
|
41
|
-
context.report({ node, message: "Using '{{type}}' is not allowed.", data: node });
|
42
|
-
}
|
43
|
-
|
44
|
-
return context.options.reduce((result, nodeType) => {
|
45
|
-
result[nodeType] = warn;
|
46
|
-
|
47
|
-
return result;
|
48
|
-
}, {});
|
28
|
+
return context.options.reduce((result, selector) => Object.assign(result, {
|
29
|
+
[selector](node) {
|
30
|
+
context.report({ node, message: "Using '{{selector}}' is not allowed.", data: { selector } });
|
31
|
+
}
|
32
|
+
}), {});
|
49
33
|
|
50
34
|
}
|
51
35
|
};
|
package/lib/rules/no-sync.js
CHANGED
@@ -26,19 +26,14 @@ module.exports = {
|
|
26
26
|
|
27
27
|
return {
|
28
28
|
|
29
|
-
MemberExpression(node) {
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
data: {
|
38
|
-
propertyName
|
39
|
-
}
|
40
|
-
});
|
41
|
-
}
|
29
|
+
"MemberExpression[property.name=/.*Sync$/]"(node) {
|
30
|
+
context.report({
|
31
|
+
node,
|
32
|
+
message: "Unexpected sync method: '{{propertyName}}'.",
|
33
|
+
data: {
|
34
|
+
propertyName: node.property.name
|
35
|
+
}
|
36
|
+
});
|
42
37
|
}
|
43
38
|
};
|
44
39
|
|
@@ -25,6 +25,9 @@ module.exports = {
|
|
25
25
|
},
|
26
26
|
allowTernary: {
|
27
27
|
type: "boolean"
|
28
|
+
},
|
29
|
+
allowTaggedTemplates: {
|
30
|
+
type: "boolean"
|
28
31
|
}
|
29
32
|
},
|
30
33
|
additionalProperties: false
|
@@ -35,7 +38,8 @@ module.exports = {
|
|
35
38
|
create(context) {
|
36
39
|
const config = context.options[0] || {},
|
37
40
|
allowShortCircuit = config.allowShortCircuit || false,
|
38
|
-
allowTernary = config.allowTernary || false
|
41
|
+
allowTernary = config.allowTernary || false,
|
42
|
+
allowTaggedTemplates = config.allowTaggedTemplates || false;
|
39
43
|
|
40
44
|
/**
|
41
45
|
* @param {ASTNode} node - any node
|
@@ -95,12 +99,17 @@ module.exports = {
|
|
95
99
|
return isValidExpression(node.consequent) && isValidExpression(node.alternate);
|
96
100
|
}
|
97
101
|
}
|
102
|
+
|
98
103
|
if (allowShortCircuit) {
|
99
104
|
if (node.type === "LogicalExpression") {
|
100
105
|
return isValidExpression(node.right);
|
101
106
|
}
|
102
107
|
}
|
103
108
|
|
109
|
+
if (allowTaggedTemplates && node.type === "TaggedTemplateExpression") {
|
110
|
+
return true;
|
111
|
+
}
|
112
|
+
|
104
113
|
return /^(?:Assignment|Call|New|Update|Yield|Await)Expression$/.test(node.type) ||
|
105
114
|
(node.type === "UnaryExpression" && ["delete", "void"].indexOf(node.operator) >= 0);
|
106
115
|
}
|
@@ -147,7 +147,13 @@ module.exports = {
|
|
147
147
|
function check(node) {
|
148
148
|
const isTemplateElement = node.type === "TemplateElement";
|
149
149
|
|
150
|
-
if (
|
150
|
+
if (
|
151
|
+
isTemplateElement &&
|
152
|
+
node.parent &&
|
153
|
+
node.parent.parent &&
|
154
|
+
node.parent.parent.type === "TaggedTemplateExpression" &&
|
155
|
+
node.parent === node.parent.parent.quasi
|
156
|
+
) {
|
151
157
|
|
152
158
|
// Don't report tagged template literals, because the backslash character is accessible to the tag function.
|
153
159
|
return;
|
package/lib/rules/sort-vars.js
CHANGED
@@ -37,11 +37,9 @@ module.exports = {
|
|
37
37
|
|
38
38
|
return {
|
39
39
|
VariableDeclaration(node) {
|
40
|
-
node.declarations.
|
41
|
-
if (decl.id.type === "ObjectPattern" || decl.id.type === "ArrayPattern") {
|
42
|
-
return memo;
|
43
|
-
}
|
40
|
+
const idDeclarations = node.declarations.filter(decl => decl.id.type === "Identifier");
|
44
41
|
|
42
|
+
idDeclarations.slice(1).reduce((memo, decl) => {
|
45
43
|
let lastVariableName = memo.id.name,
|
46
44
|
currenVariableName = decl.id.name;
|
47
45
|
|
@@ -56,7 +54,7 @@ module.exports = {
|
|
56
54
|
}
|
57
55
|
return decl;
|
58
56
|
|
59
|
-
},
|
57
|
+
}, idDeclarations[0]);
|
60
58
|
}
|
61
59
|
};
|
62
60
|
}
|
@@ -343,12 +343,13 @@ RuleTester.prototype = {
|
|
343
343
|
* running the rule under test.
|
344
344
|
*/
|
345
345
|
eslint.reset();
|
346
|
+
|
346
347
|
eslint.on("Program", node => {
|
347
348
|
beforeAST = cloneDeeplyExcludesParent(node);
|
349
|
+
});
|
348
350
|
|
349
|
-
|
350
|
-
|
351
|
-
});
|
351
|
+
eslint.on("Program:exit", node => {
|
352
|
+
afterAST = node;
|
352
353
|
});
|
353
354
|
|
354
355
|
// Freezes rule-context properties.
|
@@ -385,7 +386,7 @@ RuleTester.prototype = {
|
|
385
386
|
return {
|
386
387
|
messages: eslint.verify(code, config, filename, true),
|
387
388
|
beforeAST,
|
388
|
-
afterAST
|
389
|
+
afterAST: cloneDeeplyExcludesParent(afterAST)
|
389
390
|
};
|
390
391
|
} finally {
|
391
392
|
rules.get = originalGet;
|
@@ -5,6 +5,185 @@
|
|
5
5
|
|
6
6
|
"use strict";
|
7
7
|
|
8
|
+
//------------------------------------------------------------------------------
|
9
|
+
// Requirements
|
10
|
+
//------------------------------------------------------------------------------
|
11
|
+
|
12
|
+
const esquery = require("esquery");
|
13
|
+
const lodash = require("lodash");
|
14
|
+
|
15
|
+
//------------------------------------------------------------------------------
|
16
|
+
// Typedefs
|
17
|
+
//------------------------------------------------------------------------------
|
18
|
+
|
19
|
+
/**
|
20
|
+
* An object describing an AST selector
|
21
|
+
* @typedef {Object} ASTSelector
|
22
|
+
* @property {string} rawSelector The string that was parsed into this selector
|
23
|
+
* @property {boolean} isExit `true` if this should be emitted when exiting the node rather than when entering
|
24
|
+
* @property {Object} parsedSelector An object (from esquery) describing the matching behavior of the selector
|
25
|
+
* @property {string[]|null} listenerTypes A list of node types that could possibly cause the selector to match,
|
26
|
+
* or `null` if all node types could cause a match
|
27
|
+
* @property {number} attributeCount The total number of classes, pseudo-classes, and attribute queries in this selector
|
28
|
+
* @property {number} identifierCount The total number of identifier queries in this selector
|
29
|
+
*/
|
30
|
+
|
31
|
+
//------------------------------------------------------------------------------
|
32
|
+
// Helpers
|
33
|
+
//------------------------------------------------------------------------------
|
34
|
+
|
35
|
+
/**
|
36
|
+
* Gets the possible types of a selector
|
37
|
+
* @param {Object} parsedSelector An object (from esquery) describing the matching behavior of the selector
|
38
|
+
* @returns {string[]|null} The node types that could possibly trigger this selector, or `null` if all node types could trigger it
|
39
|
+
*/
|
40
|
+
function getPossibleTypes(parsedSelector) {
|
41
|
+
switch (parsedSelector.type) {
|
42
|
+
case "identifier":
|
43
|
+
return [parsedSelector.value];
|
44
|
+
|
45
|
+
case "matches": {
|
46
|
+
const typesForComponents = parsedSelector.selectors.map(getPossibleTypes);
|
47
|
+
|
48
|
+
if (typesForComponents.every(typesForComponent => typesForComponent)) {
|
49
|
+
return lodash.union.apply(null, typesForComponents);
|
50
|
+
}
|
51
|
+
return null;
|
52
|
+
}
|
53
|
+
|
54
|
+
case "compound": {
|
55
|
+
const typesForComponents = parsedSelector.selectors.map(getPossibleTypes).filter(typesForComponent => typesForComponent);
|
56
|
+
|
57
|
+
// If all of the components could match any type, then the compound could also match any type.
|
58
|
+
if (!typesForComponents.length) {
|
59
|
+
return null;
|
60
|
+
}
|
61
|
+
|
62
|
+
/*
|
63
|
+
* If at least one of the components could only match a particular type, the compound could only match
|
64
|
+
* the intersection of those types.
|
65
|
+
*/
|
66
|
+
return lodash.intersection.apply(null, typesForComponents);
|
67
|
+
}
|
68
|
+
|
69
|
+
case "child":
|
70
|
+
case "descendant":
|
71
|
+
case "sibling":
|
72
|
+
case "adjacent":
|
73
|
+
return getPossibleTypes(parsedSelector.right);
|
74
|
+
|
75
|
+
default:
|
76
|
+
return null;
|
77
|
+
|
78
|
+
}
|
79
|
+
}
|
80
|
+
|
81
|
+
/**
|
82
|
+
* Counts the number of class, pseudo-class, and attribute queries in this selector
|
83
|
+
* @param {Object} parsedSelector An object (from esquery) describing the selector's matching behavior
|
84
|
+
* @returns {number} The number of class, pseudo-class, and attribute queries in this selector
|
85
|
+
*/
|
86
|
+
function countClassAttributes(parsedSelector) {
|
87
|
+
switch (parsedSelector.type) {
|
88
|
+
case "child":
|
89
|
+
case "descendant":
|
90
|
+
case "sibling":
|
91
|
+
case "adjacent":
|
92
|
+
return countClassAttributes(parsedSelector.left) + countClassAttributes(parsedSelector.right);
|
93
|
+
|
94
|
+
case "compound":
|
95
|
+
case "not":
|
96
|
+
case "matches":
|
97
|
+
return parsedSelector.selectors.reduce((sum, childSelector) => sum + countClassAttributes(childSelector), 0);
|
98
|
+
|
99
|
+
case "attribute":
|
100
|
+
case "field":
|
101
|
+
case "nth-child":
|
102
|
+
case "nth-last-child":
|
103
|
+
return 1;
|
104
|
+
|
105
|
+
default:
|
106
|
+
return 0;
|
107
|
+
}
|
108
|
+
}
|
109
|
+
|
110
|
+
/**
|
111
|
+
* Counts the number of identifier queries in this selector
|
112
|
+
* @param {Object} parsedSelector An object (from esquery) describing the selector's matching behavior
|
113
|
+
* @returns {number} The number of identifier queries
|
114
|
+
*/
|
115
|
+
function countIdentifiers(parsedSelector) {
|
116
|
+
switch (parsedSelector.type) {
|
117
|
+
case "child":
|
118
|
+
case "descendant":
|
119
|
+
case "sibling":
|
120
|
+
case "adjacent":
|
121
|
+
return countIdentifiers(parsedSelector.left) + countIdentifiers(parsedSelector.right);
|
122
|
+
|
123
|
+
case "compound":
|
124
|
+
case "not":
|
125
|
+
case "matches":
|
126
|
+
return parsedSelector.selectors.reduce((sum, childSelector) => sum + countIdentifiers(childSelector), 0);
|
127
|
+
|
128
|
+
case "identifier":
|
129
|
+
return 1;
|
130
|
+
|
131
|
+
default:
|
132
|
+
return 0;
|
133
|
+
}
|
134
|
+
}
|
135
|
+
|
136
|
+
/**
|
137
|
+
* Compares the specificity of two selector objects, with CSS-like rules.
|
138
|
+
* @param {ASTSelector} selectorA An AST selector descriptor
|
139
|
+
* @param {ASTSelector} selectorB Another AST selector descriptor
|
140
|
+
* @returns {number}
|
141
|
+
* a value less than 0 if selectorA is less specific than selectorB
|
142
|
+
* a value greater than 0 if selectorA is more specific than selectorB
|
143
|
+
* a value less than 0 if selectorA and selectorB have the same specificity, and selectorA <= selectorB alphabetically
|
144
|
+
* a value greater than 0 if selectorA and selectorB have the same specificity, and selectorA > selectorB alphabetically
|
145
|
+
*/
|
146
|
+
function compareSpecificity(selectorA, selectorB) {
|
147
|
+
return selectorA.attributeCount - selectorB.attributeCount ||
|
148
|
+
selectorA.identifierCount - selectorB.identifierCount ||
|
149
|
+
(selectorA.rawSelector <= selectorB.rawSelector ? -1 : 1);
|
150
|
+
}
|
151
|
+
|
152
|
+
/**
|
153
|
+
* Parses a raw selector string, and throws a useful error if parsing fails.
|
154
|
+
* @param {string} rawSelector A raw AST selector
|
155
|
+
* @returns {Object} An object (from esquery) describing the matching behavior of this selector
|
156
|
+
* @throws {Error} An error if the selector is invalid
|
157
|
+
*/
|
158
|
+
function tryParseSelector(rawSelector) {
|
159
|
+
try {
|
160
|
+
return esquery.parse(rawSelector.replace(/:exit$/, ""));
|
161
|
+
} catch (err) {
|
162
|
+
if (typeof err.offset === "number") {
|
163
|
+
throw new Error(`Syntax error in selector "${rawSelector}" at position ${err.offset}: ${err.message}`);
|
164
|
+
}
|
165
|
+
throw err;
|
166
|
+
}
|
167
|
+
}
|
168
|
+
|
169
|
+
/**
|
170
|
+
* Parses a raw selector string, and returns the parsed selector along with specificity and type information.
|
171
|
+
* @param {string} rawSelector A raw AST selector
|
172
|
+
* @returns {ASTSelector} A selector descriptor
|
173
|
+
*/
|
174
|
+
const parseSelector = lodash.memoize(rawSelector => {
|
175
|
+
const parsedSelector = tryParseSelector(rawSelector);
|
176
|
+
|
177
|
+
return {
|
178
|
+
rawSelector,
|
179
|
+
isExit: rawSelector.endsWith(":exit"),
|
180
|
+
parsedSelector,
|
181
|
+
listenerTypes: getPossibleTypes(parsedSelector),
|
182
|
+
attributeCount: countClassAttributes(parsedSelector),
|
183
|
+
identifierCount: countIdentifiers(parsedSelector)
|
184
|
+
};
|
185
|
+
});
|
186
|
+
|
8
187
|
//------------------------------------------------------------------------------
|
9
188
|
// Public Interface
|
10
189
|
//------------------------------------------------------------------------------
|
@@ -24,10 +203,97 @@
|
|
24
203
|
class NodeEventGenerator {
|
25
204
|
|
26
205
|
/**
|
27
|
-
|
28
|
-
|
206
|
+
* @param {EventEmitter} emitter - An event emitter which is the destination of events. This emitter must already
|
207
|
+
* have registered listeners for all of the events that it needs to listen for.
|
208
|
+
* @returns {NodeEventGenerator} new instance
|
209
|
+
*/
|
29
210
|
constructor(emitter) {
|
30
211
|
this.emitter = emitter;
|
212
|
+
this.currentAncestry = [];
|
213
|
+
this.enterSelectorsByNodeType = new Map();
|
214
|
+
this.exitSelectorsByNodeType = new Map();
|
215
|
+
this.anyTypeEnterSelectors = [];
|
216
|
+
this.anyTypeExitSelectors = [];
|
217
|
+
|
218
|
+
const eventNames = typeof emitter.eventNames === "function"
|
219
|
+
|
220
|
+
// Use the built-in eventNames() function if available (Node 6+)
|
221
|
+
? emitter.eventNames()
|
222
|
+
|
223
|
+
/*
|
224
|
+
* Otherwise, use the private _events property.
|
225
|
+
* Using a private property isn't ideal here, but this seems to
|
226
|
+
* be the best way to get a list of event names without overriding
|
227
|
+
* addEventListener, which would hurt performance. This property
|
228
|
+
* is widely used and unlikely to be removed in a future version
|
229
|
+
* (see https://github.com/nodejs/node/issues/1817). Also, future
|
230
|
+
* node versions will have eventNames() anyway.
|
231
|
+
*/
|
232
|
+
: Object.keys(emitter._events); // eslint-disable-line no-underscore-dangle
|
233
|
+
|
234
|
+
eventNames.forEach(rawSelector => {
|
235
|
+
const selector = parseSelector(rawSelector);
|
236
|
+
|
237
|
+
if (selector.listenerTypes) {
|
238
|
+
selector.listenerTypes.forEach(nodeType => {
|
239
|
+
const typeMap = selector.isExit ? this.exitSelectorsByNodeType : this.enterSelectorsByNodeType;
|
240
|
+
|
241
|
+
if (!typeMap.has(nodeType)) {
|
242
|
+
typeMap.set(nodeType, []);
|
243
|
+
}
|
244
|
+
typeMap.get(nodeType).push(selector);
|
245
|
+
});
|
246
|
+
} else {
|
247
|
+
(selector.isExit ? this.anyTypeExitSelectors : this.anyTypeEnterSelectors).push(selector);
|
248
|
+
}
|
249
|
+
});
|
250
|
+
|
251
|
+
this.anyTypeEnterSelectors.sort(compareSpecificity);
|
252
|
+
this.anyTypeExitSelectors.sort(compareSpecificity);
|
253
|
+
this.enterSelectorsByNodeType.forEach(selectorList => selectorList.sort(compareSpecificity));
|
254
|
+
this.exitSelectorsByNodeType.forEach(selectorList => selectorList.sort(compareSpecificity));
|
255
|
+
}
|
256
|
+
|
257
|
+
/**
|
258
|
+
* Checks a selector against a node, and emits it if it matches
|
259
|
+
* @param {ASTNode} node The node to check
|
260
|
+
* @param {ASTSelector} selector An AST selector descriptor
|
261
|
+
* @returns {void}
|
262
|
+
*/
|
263
|
+
applySelector(node, selector) {
|
264
|
+
if (esquery.matches(node, selector.parsedSelector, this.currentAncestry)) {
|
265
|
+
this.emitter.emit(selector.rawSelector, node);
|
266
|
+
}
|
267
|
+
}
|
268
|
+
|
269
|
+
/**
|
270
|
+
* Applies all appropriate selectors to a node, in specificity order
|
271
|
+
* @param {ASTNode} node The node to check
|
272
|
+
* @param {boolean} isExit `false` if the node is currently being entered, `true` if it's currently being exited
|
273
|
+
* @returns {void}
|
274
|
+
*/
|
275
|
+
applySelectors(node, isExit) {
|
276
|
+
const selectorsByNodeType = (isExit ? this.exitSelectorsByNodeType : this.enterSelectorsByNodeType).get(node.type) || [];
|
277
|
+
const anyTypeSelectors = isExit ? this.anyTypeExitSelectors : this.anyTypeEnterSelectors;
|
278
|
+
|
279
|
+
/*
|
280
|
+
* selectorsByNodeType and anyTypeSelectors were already sorted by specificity in the constructor.
|
281
|
+
* Iterate through each of them, applying selectors in the right order.
|
282
|
+
*/
|
283
|
+
let selectorsByTypeIndex = 0;
|
284
|
+
let anyTypeSelectorsIndex = 0;
|
285
|
+
|
286
|
+
while (selectorsByTypeIndex < selectorsByNodeType.length || anyTypeSelectorsIndex < anyTypeSelectors.length) {
|
287
|
+
if (
|
288
|
+
selectorsByTypeIndex >= selectorsByNodeType.length ||
|
289
|
+
anyTypeSelectorsIndex < anyTypeSelectors.length &&
|
290
|
+
compareSpecificity(anyTypeSelectors[anyTypeSelectorsIndex], selectorsByNodeType[selectorsByTypeIndex]) < 0
|
291
|
+
) {
|
292
|
+
this.applySelector(node, anyTypeSelectors[anyTypeSelectorsIndex++]);
|
293
|
+
} else {
|
294
|
+
this.applySelector(node, selectorsByNodeType[selectorsByTypeIndex++]);
|
295
|
+
}
|
296
|
+
}
|
31
297
|
}
|
32
298
|
|
33
299
|
/**
|
@@ -36,7 +302,10 @@ class NodeEventGenerator {
|
|
36
302
|
* @returns {void}
|
37
303
|
*/
|
38
304
|
enterNode(node) {
|
39
|
-
|
305
|
+
if (node.parent) {
|
306
|
+
this.currentAncestry.unshift(node.parent);
|
307
|
+
}
|
308
|
+
this.applySelectors(node, false);
|
40
309
|
}
|
41
310
|
|
42
311
|
/**
|
@@ -45,7 +314,8 @@ class NodeEventGenerator {
|
|
45
314
|
* @returns {void}
|
46
315
|
*/
|
47
316
|
leaveNode(node) {
|
48
|
-
this.
|
317
|
+
this.applySelectors(node, true);
|
318
|
+
this.currentAncestry.shift();
|
49
319
|
}
|
50
320
|
}
|
51
321
|
|
package/lib/util/traverser.js
CHANGED
@@ -14,41 +14,32 @@ const estraverse = require("estraverse");
|
|
14
14
|
// Helpers
|
15
15
|
//------------------------------------------------------------------------------
|
16
16
|
|
17
|
-
const KEY_BLACKLIST = [
|
17
|
+
const KEY_BLACKLIST = new Set([
|
18
18
|
"parent",
|
19
19
|
"leadingComments",
|
20
20
|
"trailingComments"
|
21
|
-
];
|
21
|
+
]);
|
22
22
|
|
23
23
|
/**
|
24
24
|
* Wrapper around an estraverse controller that ensures the correct keys
|
25
25
|
* are visited.
|
26
26
|
* @constructor
|
27
27
|
*/
|
28
|
-
|
29
|
-
|
30
|
-
const controller = Object.create(new estraverse.Controller()),
|
31
|
-
originalTraverse = controller.traverse;
|
32
|
-
|
33
|
-
// intercept call to traverse() and add the fallback key to the visitor
|
34
|
-
controller.traverse = function(node, visitor) {
|
28
|
+
class Traverser extends estraverse.Controller {
|
29
|
+
traverse(node, visitor) {
|
35
30
|
visitor.fallback = Traverser.getKeys;
|
36
|
-
return
|
37
|
-
}
|
38
|
-
|
39
|
-
|
31
|
+
return super.traverse(node, visitor);
|
32
|
+
}
|
33
|
+
|
34
|
+
/**
|
35
|
+
* Calculates the keys to use for traversal.
|
36
|
+
* @param {ASTNode} node The node to read keys from.
|
37
|
+
* @returns {string[]} An array of keys to visit on the node.
|
38
|
+
* @private
|
39
|
+
*/
|
40
|
+
static getKeys(node) {
|
41
|
+
return Object.keys(node).filter(key => !KEY_BLACKLIST.has(key));
|
42
|
+
}
|
40
43
|
}
|
41
44
|
|
42
|
-
/**
|
43
|
-
* Calculates the keys to use for traversal.
|
44
|
-
* @param {ASTNode} node The node to read keys from.
|
45
|
-
* @returns {string[]} An array of keys to visit on the node.
|
46
|
-
* @private
|
47
|
-
*/
|
48
|
-
Traverser.getKeys = function(node) {
|
49
|
-
return Object.keys(node).filter(key => KEY_BLACKLIST.indexOf(key) === -1);
|
50
|
-
};
|
51
|
-
|
52
45
|
module.exports = Traverser;
|
53
|
-
|
54
|
-
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "eslint",
|
3
|
-
"version": "3.
|
3
|
+
"version": "3.18.0",
|
4
4
|
"author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
|
5
5
|
"description": "An AST-based pattern checker for JavaScript.",
|
6
6
|
"bin": {
|
@@ -36,11 +36,12 @@
|
|
36
36
|
"dependencies": {
|
37
37
|
"babel-code-frame": "^6.16.0",
|
38
38
|
"chalk": "^1.1.3",
|
39
|
-
"concat-stream": "^1.
|
39
|
+
"concat-stream": "^1.5.2",
|
40
40
|
"debug": "^2.1.1",
|
41
|
-
"doctrine": "^
|
41
|
+
"doctrine": "^2.0.0",
|
42
42
|
"escope": "^3.6.0",
|
43
43
|
"espree": "^3.4.0",
|
44
|
+
"esquery": "^1.0.0",
|
44
45
|
"estraverse": "^4.2.0",
|
45
46
|
"esutils": "^2.0.2",
|
46
47
|
"file-entry-cache": "^2.0.0",
|
@@ -97,7 +98,7 @@
|
|
97
98
|
"load-perf": "^0.2.0",
|
98
99
|
"markdownlint": "^0.3.1",
|
99
100
|
"mocha": "^2.4.5",
|
100
|
-
"mock-fs": "
|
101
|
+
"mock-fs": "^4.2.0",
|
101
102
|
"npm-license": "^0.3.2",
|
102
103
|
"phantomjs-prebuilt": "^2.1.7",
|
103
104
|
"proxyquire": "^1.7.10",
|