eslint 4.4.0 → 4.6.1
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 +56 -0
- package/bin/eslint.js +2 -1
- package/conf/eslint-recommended.js +1 -0
- package/lib/ast-utils.js +11 -17
- package/lib/code-path-analysis/code-path-analyzer.js +8 -4
- package/lib/code-path-analysis/code-path-segment.js +2 -1
- package/lib/code-path-analysis/code-path-state.js +18 -9
- package/lib/code-path-analysis/code-path.js +2 -1
- package/lib/code-path-analysis/fork-context.js +2 -1
- package/lib/config/config-initializer.js +3 -1
- package/lib/config.js +8 -12
- package/lib/formatters/junit.js +2 -8
- package/lib/formatters/stylish.js +2 -1
- package/lib/ignored-paths.js +0 -2
- package/lib/linter.js +320 -318
- package/lib/report-translator.js +274 -0
- package/lib/rules/function-paren-newline.js +221 -0
- package/lib/rules/generator-star-spacing.js +70 -19
- package/lib/rules/indent-legacy.js +2 -1
- package/lib/rules/indent.js +137 -64
- package/lib/rules/no-extra-parens.js +37 -32
- package/lib/rules/no-invalid-this.js +2 -1
- package/lib/rules/no-multi-spaces.js +5 -2
- package/lib/rules/no-unused-vars.js +47 -4
- package/lib/rules/padded-blocks.js +2 -2
- package/lib/rules/prefer-arrow-callback.js +1 -2
- package/lib/testers/rule-tester.js +11 -9
- package/lib/timing.js +2 -2
- package/lib/util/fix-tracker.js +1 -2
- package/lib/util/npm-util.js +21 -4
- package/lib/util/source-code-fixer.js +5 -14
- package/package.json +3 -2
- package/lib/rule-context.js +0 -241
- package/lib/testers/event-generator-tester.js +0 -62
- package/lib/testers/test-parser.js +0 -48
@@ -0,0 +1,274 @@
|
|
1
|
+
/**
|
2
|
+
* @fileoverview A helper that translates context.report() calls from the rule API into generic problem objects
|
3
|
+
* @author Teddy Katz
|
4
|
+
*/
|
5
|
+
|
6
|
+
"use strict";
|
7
|
+
|
8
|
+
//------------------------------------------------------------------------------
|
9
|
+
// Requirements
|
10
|
+
//------------------------------------------------------------------------------
|
11
|
+
|
12
|
+
const assert = require("assert");
|
13
|
+
const ruleFixer = require("./util/rule-fixer");
|
14
|
+
|
15
|
+
//------------------------------------------------------------------------------
|
16
|
+
// Typedefs
|
17
|
+
//------------------------------------------------------------------------------
|
18
|
+
|
19
|
+
/**
|
20
|
+
* An error message description
|
21
|
+
* @typedef {Object} MessageDescriptor
|
22
|
+
* @property {ASTNode} [node] The reported node
|
23
|
+
* @property {Location} loc The location of the problem.
|
24
|
+
* @property {string} message The problem message.
|
25
|
+
* @property {Object} [data] Optional data to use to fill in placeholders in the
|
26
|
+
* message.
|
27
|
+
* @property {Function} [fix] The function to call that creates a fix command.
|
28
|
+
*/
|
29
|
+
|
30
|
+
//------------------------------------------------------------------------------
|
31
|
+
// Module Definition
|
32
|
+
//------------------------------------------------------------------------------
|
33
|
+
|
34
|
+
|
35
|
+
/**
|
36
|
+
* Translates a multi-argument context.report() call into a single object argument call
|
37
|
+
* @param {...*} arguments A list of arguments passed to `context.report`
|
38
|
+
* @returns {MessageDescriptor} A normalized object containing report information
|
39
|
+
*/
|
40
|
+
function normalizeMultiArgReportCall() {
|
41
|
+
|
42
|
+
// If there is one argument, it is considered to be a new-style call already.
|
43
|
+
if (arguments.length === 1) {
|
44
|
+
return arguments[0];
|
45
|
+
}
|
46
|
+
|
47
|
+
// If the second argument is a string, the arguments are interpreted as [node, message, data, fix].
|
48
|
+
if (typeof arguments[1] === "string") {
|
49
|
+
return {
|
50
|
+
node: arguments[0],
|
51
|
+
message: arguments[1],
|
52
|
+
data: arguments[2],
|
53
|
+
fix: arguments[3]
|
54
|
+
};
|
55
|
+
}
|
56
|
+
|
57
|
+
// Otherwise, the arguments are interpreted as [node, loc, message, data, fix].
|
58
|
+
return {
|
59
|
+
node: arguments[0],
|
60
|
+
loc: arguments[1],
|
61
|
+
message: arguments[2],
|
62
|
+
data: arguments[3],
|
63
|
+
fix: arguments[4]
|
64
|
+
};
|
65
|
+
}
|
66
|
+
|
67
|
+
/**
|
68
|
+
* Asserts that either a loc or a node was provided, and the node is valid if it was provided.
|
69
|
+
* @param {MessageDescriptor} descriptor A descriptor to validate
|
70
|
+
* @returns {void}
|
71
|
+
* @throws AssertionError if neither a node nor a loc was provided, or if the node is not an object
|
72
|
+
*/
|
73
|
+
function assertValidNodeInfo(descriptor) {
|
74
|
+
if (descriptor.node) {
|
75
|
+
assert(typeof descriptor.node === "object", "Node must be an object");
|
76
|
+
} else {
|
77
|
+
assert(descriptor.loc, "Node must be provided when reporting error if location is not provided");
|
78
|
+
}
|
79
|
+
}
|
80
|
+
|
81
|
+
/**
|
82
|
+
* Normalizes a MessageDescriptor to always have a `loc` with `start` and `end` properties
|
83
|
+
* @param {MessageDescriptor} descriptor A descriptor for the report from a rule.
|
84
|
+
* @returns {{start: Location, end: (Location|null)}} An updated location that infers the `start` and `end` properties
|
85
|
+
* from the `node` of the original descriptor, or infers the `start` from the `loc` of the original descriptor.
|
86
|
+
*/
|
87
|
+
function normalizeReportLoc(descriptor) {
|
88
|
+
if (descriptor.loc) {
|
89
|
+
if (descriptor.loc.start) {
|
90
|
+
return descriptor.loc;
|
91
|
+
}
|
92
|
+
return { start: descriptor.loc, end: null };
|
93
|
+
}
|
94
|
+
return descriptor.node.loc;
|
95
|
+
}
|
96
|
+
|
97
|
+
/**
|
98
|
+
* Interpolates data placeholders in report messages
|
99
|
+
* @param {MessageDescriptor} descriptor The report message descriptor.
|
100
|
+
* @returns {string} The interpolated message for the descriptor
|
101
|
+
*/
|
102
|
+
function normalizeMessagePlaceholders(descriptor) {
|
103
|
+
if (!descriptor.data) {
|
104
|
+
return descriptor.message;
|
105
|
+
}
|
106
|
+
return descriptor.message.replace(/\{\{\s*([^{}]+?)\s*\}\}/g, (fullMatch, term) => {
|
107
|
+
if (term in descriptor.data) {
|
108
|
+
return descriptor.data[term];
|
109
|
+
}
|
110
|
+
|
111
|
+
return fullMatch;
|
112
|
+
});
|
113
|
+
}
|
114
|
+
|
115
|
+
/**
|
116
|
+
* Compares items in a fixes array by range.
|
117
|
+
* @param {Fix} a The first message.
|
118
|
+
* @param {Fix} b The second message.
|
119
|
+
* @returns {int} -1 if a comes before b, 1 if a comes after b, 0 if equal.
|
120
|
+
* @private
|
121
|
+
*/
|
122
|
+
function compareFixesByRange(a, b) {
|
123
|
+
return a.range[0] - b.range[0] || a.range[1] - b.range[1];
|
124
|
+
}
|
125
|
+
|
126
|
+
/**
|
127
|
+
* Merges the given fixes array into one.
|
128
|
+
* @param {Fix[]} fixes The fixes to merge.
|
129
|
+
* @param {SourceCode} sourceCode The source code object to get the text between fixes.
|
130
|
+
* @returns {{text: string, range: [number, number]}} The merged fixes
|
131
|
+
*/
|
132
|
+
function mergeFixes(fixes, sourceCode) {
|
133
|
+
if (fixes.length === 0) {
|
134
|
+
return null;
|
135
|
+
}
|
136
|
+
if (fixes.length === 1) {
|
137
|
+
return fixes[0];
|
138
|
+
}
|
139
|
+
|
140
|
+
fixes.sort(compareFixesByRange);
|
141
|
+
|
142
|
+
const originalText = sourceCode.text;
|
143
|
+
const start = fixes[0].range[0];
|
144
|
+
const end = fixes[fixes.length - 1].range[1];
|
145
|
+
let text = "";
|
146
|
+
let lastPos = Number.MIN_SAFE_INTEGER;
|
147
|
+
|
148
|
+
for (const fix of fixes) {
|
149
|
+
assert(fix.range[0] >= lastPos, "Fix objects must not be overlapped in a report.");
|
150
|
+
|
151
|
+
if (fix.range[0] >= 0) {
|
152
|
+
text += originalText.slice(Math.max(0, start, lastPos), fix.range[0]);
|
153
|
+
}
|
154
|
+
text += fix.text;
|
155
|
+
lastPos = fix.range[1];
|
156
|
+
}
|
157
|
+
text += originalText.slice(Math.max(0, start, lastPos), end);
|
158
|
+
|
159
|
+
return { range: [start, end], text };
|
160
|
+
}
|
161
|
+
|
162
|
+
/**
|
163
|
+
* Gets one fix object from the given descriptor.
|
164
|
+
* If the descriptor retrieves multiple fixes, this merges those to one.
|
165
|
+
* @param {MessageDescriptor} descriptor The report descriptor.
|
166
|
+
* @param {SourceCode} sourceCode The source code object to get text between fixes.
|
167
|
+
* @returns {({text: string, range: [number, number]}|null)} The fix for the descriptor
|
168
|
+
*/
|
169
|
+
function normalizeFixes(descriptor, sourceCode) {
|
170
|
+
if (typeof descriptor.fix !== "function") {
|
171
|
+
return null;
|
172
|
+
}
|
173
|
+
|
174
|
+
// @type {null | Fix | Fix[] | IterableIterator<Fix>}
|
175
|
+
const fix = descriptor.fix(ruleFixer);
|
176
|
+
|
177
|
+
// Merge to one.
|
178
|
+
if (fix && Symbol.iterator in fix) {
|
179
|
+
return mergeFixes(Array.from(fix), sourceCode);
|
180
|
+
}
|
181
|
+
return fix;
|
182
|
+
}
|
183
|
+
|
184
|
+
/**
|
185
|
+
* Creates information about the report from a descriptor
|
186
|
+
* @param {{
|
187
|
+
* ruleId: string,
|
188
|
+
* severity: (0|1|2),
|
189
|
+
* node: (ASTNode|null),
|
190
|
+
* message: string,
|
191
|
+
* loc: {start: SourceLocation, end: (SourceLocation|null)},
|
192
|
+
* fix: ({text: string, range: [number, number]}|null),
|
193
|
+
* sourceLines: string[]
|
194
|
+
* }} options Information about the problem
|
195
|
+
* @returns {function(...args): {
|
196
|
+
* ruleId: string,
|
197
|
+
* severity: (0|1|2),
|
198
|
+
* message: string,
|
199
|
+
* line: number,
|
200
|
+
* column: number,
|
201
|
+
* endLine: (number|undefined),
|
202
|
+
* endColumn: (number|undefined),
|
203
|
+
* nodeType: (string|null),
|
204
|
+
* source: string,
|
205
|
+
* fix: ({text: string, range: [number, number]}|null)
|
206
|
+
* }} Information about the report
|
207
|
+
*/
|
208
|
+
function createProblem(options) {
|
209
|
+
const problem = {
|
210
|
+
ruleId: options.ruleId,
|
211
|
+
severity: options.severity,
|
212
|
+
message: options.message,
|
213
|
+
line: options.loc.start.line,
|
214
|
+
column: options.loc.start.column + 1,
|
215
|
+
nodeType: options.node && options.node.type || null,
|
216
|
+
source: options.sourceLines[options.loc.start.line - 1] || ""
|
217
|
+
};
|
218
|
+
|
219
|
+
if (options.loc.end) {
|
220
|
+
problem.endLine = options.loc.end.line;
|
221
|
+
problem.endColumn = options.loc.end.column + 1;
|
222
|
+
}
|
223
|
+
|
224
|
+
if (options.fix) {
|
225
|
+
problem.fix = options.fix;
|
226
|
+
}
|
227
|
+
|
228
|
+
return problem;
|
229
|
+
}
|
230
|
+
|
231
|
+
/**
|
232
|
+
* Returns a function that converts the arguments of a `context.report` call from a rule into a reported
|
233
|
+
* problem for the Node.js API.
|
234
|
+
* @param {{ruleId: string, severity: number, sourceCode: SourceCode}} metadata Metadata for the reported problem
|
235
|
+
* @param {SourceCode} sourceCode The `SourceCode` instance for the text being linted
|
236
|
+
* @returns {function(...args): {
|
237
|
+
* ruleId: string,
|
238
|
+
* severity: (0|1|2),
|
239
|
+
* message: string,
|
240
|
+
* line: number,
|
241
|
+
* column: number,
|
242
|
+
* endLine: (number|undefined),
|
243
|
+
* endColumn: (number|undefined),
|
244
|
+
* nodeType: (string|null),
|
245
|
+
* source: string,
|
246
|
+
* fix: ({text: string, range: [number, number]}|null)
|
247
|
+
* }}
|
248
|
+
* Information about the report
|
249
|
+
*/
|
250
|
+
|
251
|
+
module.exports = function createReportTranslator(metadata) {
|
252
|
+
|
253
|
+
/*
|
254
|
+
* `createReportTranslator` gets called once per enabled rule per file. It needs to be very performant.
|
255
|
+
* The report translator itself (i.e. the function that `createReportTranslator` returns) gets
|
256
|
+
* called every time a rule reports a problem, which happens much less frequently (usually, the vast
|
257
|
+
* majority of rules don't report any problems for a given file).
|
258
|
+
*/
|
259
|
+
return function() {
|
260
|
+
const descriptor = normalizeMultiArgReportCall.apply(null, arguments);
|
261
|
+
|
262
|
+
assertValidNodeInfo(descriptor);
|
263
|
+
|
264
|
+
return createProblem({
|
265
|
+
ruleId: metadata.ruleId,
|
266
|
+
severity: metadata.severity,
|
267
|
+
node: descriptor.node,
|
268
|
+
message: normalizeMessagePlaceholders(descriptor),
|
269
|
+
loc: normalizeReportLoc(descriptor),
|
270
|
+
fix: normalizeFixes(descriptor, metadata.sourceCode),
|
271
|
+
sourceLines: metadata.sourceCode.lines
|
272
|
+
});
|
273
|
+
};
|
274
|
+
};
|
@@ -0,0 +1,221 @@
|
|
1
|
+
/**
|
2
|
+
* @fileoverview enforce consistent line breaks inside function parentheses
|
3
|
+
* @author Teddy Katz
|
4
|
+
*/
|
5
|
+
"use strict";
|
6
|
+
|
7
|
+
//------------------------------------------------------------------------------
|
8
|
+
// Requirements
|
9
|
+
//------------------------------------------------------------------------------
|
10
|
+
|
11
|
+
const astUtils = require("../ast-utils");
|
12
|
+
|
13
|
+
//------------------------------------------------------------------------------
|
14
|
+
// Rule Definition
|
15
|
+
//------------------------------------------------------------------------------
|
16
|
+
|
17
|
+
module.exports = {
|
18
|
+
meta: {
|
19
|
+
docs: {
|
20
|
+
description: "enforce consistent line breaks inside function parentheses",
|
21
|
+
category: "Stylistic Issues",
|
22
|
+
recommended: false
|
23
|
+
},
|
24
|
+
fixable: "whitespace",
|
25
|
+
schema: [
|
26
|
+
{
|
27
|
+
oneOf: [
|
28
|
+
{
|
29
|
+
enum: ["always", "never", "consistent", "multiline"]
|
30
|
+
},
|
31
|
+
{
|
32
|
+
type: "object",
|
33
|
+
properties: {
|
34
|
+
minItems: {
|
35
|
+
type: "integer",
|
36
|
+
minimum: 0
|
37
|
+
}
|
38
|
+
},
|
39
|
+
additionalProperties: false
|
40
|
+
}
|
41
|
+
]
|
42
|
+
}
|
43
|
+
]
|
44
|
+
},
|
45
|
+
|
46
|
+
create(context) {
|
47
|
+
const sourceCode = context.getSourceCode();
|
48
|
+
const rawOption = context.options[0] || "multiline";
|
49
|
+
const multilineOption = rawOption === "multiline";
|
50
|
+
const consistentOption = rawOption === "consistent";
|
51
|
+
let minItems;
|
52
|
+
|
53
|
+
if (typeof rawOption === "object") {
|
54
|
+
minItems = rawOption.minItems;
|
55
|
+
} else if (rawOption === "always") {
|
56
|
+
minItems = 0;
|
57
|
+
} else if (rawOption === "never") {
|
58
|
+
minItems = Infinity;
|
59
|
+
} else {
|
60
|
+
minItems = null;
|
61
|
+
}
|
62
|
+
|
63
|
+
//----------------------------------------------------------------------
|
64
|
+
// Helpers
|
65
|
+
//----------------------------------------------------------------------
|
66
|
+
|
67
|
+
/**
|
68
|
+
* Determines whether there should be newlines inside function parens
|
69
|
+
* @param {ASTNode[]} elements The arguments or parameters in the list
|
70
|
+
* @param {boolean} hasLeftNewline `true` if the left paren has a newline in the current code.
|
71
|
+
* @returns {boolean} `true` if there should be newlines inside the function parens
|
72
|
+
*/
|
73
|
+
function shouldHaveNewlines(elements, hasLeftNewline) {
|
74
|
+
if (multilineOption) {
|
75
|
+
return elements.some((element, index) => index !== elements.length - 1 && element.loc.end.line !== elements[index + 1].loc.start.line);
|
76
|
+
}
|
77
|
+
if (consistentOption) {
|
78
|
+
return hasLeftNewline;
|
79
|
+
}
|
80
|
+
return elements.length >= minItems;
|
81
|
+
}
|
82
|
+
|
83
|
+
/**
|
84
|
+
* Validates a list of arguments or parameters
|
85
|
+
* @param {Object} parens An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token
|
86
|
+
* @param {ASTNode[]} elements The arguments or parameters in the list
|
87
|
+
* @returns {void}
|
88
|
+
*/
|
89
|
+
function validateParens(parens, elements) {
|
90
|
+
const leftParen = parens.leftParen;
|
91
|
+
const rightParen = parens.rightParen;
|
92
|
+
const tokenAfterLeftParen = sourceCode.getTokenAfter(leftParen);
|
93
|
+
const tokenBeforeRightParen = sourceCode.getTokenBefore(rightParen);
|
94
|
+
const hasLeftNewline = !astUtils.isTokenOnSameLine(leftParen, tokenAfterLeftParen);
|
95
|
+
const hasRightNewline = !astUtils.isTokenOnSameLine(tokenBeforeRightParen, rightParen);
|
96
|
+
const needsNewlines = shouldHaveNewlines(elements, hasLeftNewline);
|
97
|
+
|
98
|
+
if (hasLeftNewline && !needsNewlines) {
|
99
|
+
context.report({
|
100
|
+
node: leftParen,
|
101
|
+
message: "Unexpected newline after '('.",
|
102
|
+
fix(fixer) {
|
103
|
+
return sourceCode.getText().slice(leftParen.range[1], tokenAfterLeftParen.range[0]).trim()
|
104
|
+
|
105
|
+
// If there is a comment between the ( and the first element, don't do a fix.
|
106
|
+
? null
|
107
|
+
: fixer.removeRange([leftParen.range[1], tokenAfterLeftParen.range[0]]);
|
108
|
+
}
|
109
|
+
});
|
110
|
+
} else if (!hasLeftNewline && needsNewlines) {
|
111
|
+
context.report({
|
112
|
+
node: leftParen,
|
113
|
+
message: "Expected a newline after '('.",
|
114
|
+
fix: fixer => fixer.insertTextAfter(leftParen, "\n")
|
115
|
+
});
|
116
|
+
}
|
117
|
+
|
118
|
+
if (hasRightNewline && !needsNewlines) {
|
119
|
+
context.report({
|
120
|
+
node: rightParen,
|
121
|
+
message: "Unexpected newline before ')'.",
|
122
|
+
fix(fixer) {
|
123
|
+
return sourceCode.getText().slice(tokenBeforeRightParen.range[1], rightParen.range[0]).trim()
|
124
|
+
|
125
|
+
// If there is a comment between the last element and the ), don't do a fix.
|
126
|
+
? null
|
127
|
+
: fixer.removeRange([tokenBeforeRightParen.range[1], rightParen.range[0]]);
|
128
|
+
}
|
129
|
+
});
|
130
|
+
} else if (!hasRightNewline && needsNewlines) {
|
131
|
+
context.report({
|
132
|
+
node: rightParen,
|
133
|
+
message: "Expected a newline before ')'.",
|
134
|
+
fix: fixer => fixer.insertTextBefore(rightParen, "\n")
|
135
|
+
});
|
136
|
+
}
|
137
|
+
}
|
138
|
+
|
139
|
+
/**
|
140
|
+
* Gets the left paren and right paren tokens of a node.
|
141
|
+
* @param {ASTNode} node The node with parens
|
142
|
+
* @returns {Object} An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token.
|
143
|
+
* Can also return `null` if an expression has no parens (e.g. a NewExpression with no arguments, or an ArrowFunctionExpression
|
144
|
+
* with a single parameter)
|
145
|
+
*/
|
146
|
+
function getParenTokens(node) {
|
147
|
+
switch (node.type) {
|
148
|
+
case "NewExpression":
|
149
|
+
if (!node.arguments.length && !(
|
150
|
+
astUtils.isOpeningParenToken(sourceCode.getLastToken(node, { skip: 1 })) &&
|
151
|
+
astUtils.isClosingParenToken(sourceCode.getLastToken(node))
|
152
|
+
)) {
|
153
|
+
|
154
|
+
// If the NewExpression does not have parens (e.g. `new Foo`), return null.
|
155
|
+
return null;
|
156
|
+
}
|
157
|
+
|
158
|
+
// falls through
|
159
|
+
|
160
|
+
case "CallExpression":
|
161
|
+
return {
|
162
|
+
leftParen: sourceCode.getTokenAfter(node.callee, astUtils.isOpeningParenToken),
|
163
|
+
rightParen: sourceCode.getLastToken(node)
|
164
|
+
};
|
165
|
+
|
166
|
+
case "FunctionDeclaration":
|
167
|
+
case "FunctionExpression": {
|
168
|
+
const leftParen = sourceCode.getFirstToken(node, astUtils.isOpeningParenToken);
|
169
|
+
const rightParen = node.params.length
|
170
|
+
? sourceCode.getTokenAfter(node.params[node.params.length - 1], astUtils.isClosingParenToken)
|
171
|
+
: sourceCode.getTokenAfter(leftParen);
|
172
|
+
|
173
|
+
return { leftParen, rightParen };
|
174
|
+
}
|
175
|
+
|
176
|
+
case "ArrowFunctionExpression": {
|
177
|
+
const firstToken = sourceCode.getFirstToken(node);
|
178
|
+
|
179
|
+
if (!astUtils.isOpeningParenToken(firstToken)) {
|
180
|
+
|
181
|
+
// If the ArrowFunctionExpression has a single param without parens, return null.
|
182
|
+
return null;
|
183
|
+
}
|
184
|
+
|
185
|
+
return {
|
186
|
+
leftParen: firstToken,
|
187
|
+
rightParen: sourceCode.getTokenBefore(node.body, astUtils.isClosingParenToken)
|
188
|
+
};
|
189
|
+
}
|
190
|
+
|
191
|
+
default:
|
192
|
+
throw new TypeError(`unexpected node with type ${node.type}`);
|
193
|
+
}
|
194
|
+
}
|
195
|
+
|
196
|
+
/**
|
197
|
+
* Validates the parentheses for a node
|
198
|
+
* @param {ASTNode} node The node with parens
|
199
|
+
* @returns {void}
|
200
|
+
*/
|
201
|
+
function validateNode(node) {
|
202
|
+
const parens = getParenTokens(node);
|
203
|
+
|
204
|
+
if (parens) {
|
205
|
+
validateParens(parens, astUtils.isFunction(node) ? node.params : node.arguments);
|
206
|
+
}
|
207
|
+
}
|
208
|
+
|
209
|
+
//----------------------------------------------------------------------
|
210
|
+
// Public
|
211
|
+
//----------------------------------------------------------------------
|
212
|
+
|
213
|
+
return {
|
214
|
+
ArrowFunctionExpression: validateNode,
|
215
|
+
CallExpression: validateNode,
|
216
|
+
FunctionDeclaration: validateNode,
|
217
|
+
FunctionExpression: validateNode,
|
218
|
+
NewExpression: validateNode
|
219
|
+
};
|
220
|
+
}
|
221
|
+
};
|
@@ -9,6 +9,22 @@
|
|
9
9
|
// Rule Definition
|
10
10
|
//------------------------------------------------------------------------------
|
11
11
|
|
12
|
+
const OVERRIDE_SCHEMA = {
|
13
|
+
oneOf: [
|
14
|
+
{
|
15
|
+
enum: ["before", "after", "both", "neither"]
|
16
|
+
},
|
17
|
+
{
|
18
|
+
type: "object",
|
19
|
+
properties: {
|
20
|
+
before: { type: "boolean" },
|
21
|
+
after: { type: "boolean" }
|
22
|
+
},
|
23
|
+
additionalProperties: false
|
24
|
+
}
|
25
|
+
]
|
26
|
+
};
|
27
|
+
|
12
28
|
module.exports = {
|
13
29
|
meta: {
|
14
30
|
docs: {
|
@@ -29,7 +45,10 @@ module.exports = {
|
|
29
45
|
type: "object",
|
30
46
|
properties: {
|
31
47
|
before: { type: "boolean" },
|
32
|
-
after: { type: "boolean" }
|
48
|
+
after: { type: "boolean" },
|
49
|
+
named: OVERRIDE_SCHEMA,
|
50
|
+
anonymous: OVERRIDE_SCHEMA,
|
51
|
+
method: OVERRIDE_SCHEMA
|
33
52
|
},
|
34
53
|
additionalProperties: false
|
35
54
|
}
|
@@ -40,16 +59,39 @@ module.exports = {
|
|
40
59
|
|
41
60
|
create(context) {
|
42
61
|
|
43
|
-
const
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
62
|
+
const optionDefinitions = {
|
63
|
+
before: { before: true, after: false },
|
64
|
+
after: { before: false, after: true },
|
65
|
+
both: { before: true, after: true },
|
66
|
+
neither: { before: false, after: false }
|
67
|
+
};
|
68
|
+
|
69
|
+
/**
|
70
|
+
* Returns resolved option definitions based on an option and defaults
|
71
|
+
*
|
72
|
+
* @param {any} option - The option object or string value
|
73
|
+
* @param {Object} defaults - The defaults to use if options are not present
|
74
|
+
* @returns {Object} the resolved object definition
|
75
|
+
*/
|
76
|
+
function optionToDefinition(option, defaults) {
|
77
|
+
if (!option) {
|
78
|
+
return defaults;
|
51
79
|
}
|
52
|
-
|
80
|
+
|
81
|
+
return typeof option === "string"
|
82
|
+
? optionDefinitions[option]
|
83
|
+
: Object.assign({}, defaults, option);
|
84
|
+
}
|
85
|
+
|
86
|
+
const modes = (function(option) {
|
87
|
+
option = option || {};
|
88
|
+
const defaults = optionToDefinition(option, optionDefinitions.before);
|
89
|
+
|
90
|
+
return {
|
91
|
+
named: optionToDefinition(option.named, defaults),
|
92
|
+
anonymous: optionToDefinition(option.anonymous, defaults),
|
93
|
+
method: optionToDefinition(option.method, defaults)
|
94
|
+
};
|
53
95
|
}(context.options[0]));
|
54
96
|
|
55
97
|
const sourceCode = context.getSourceCode();
|
@@ -79,6 +121,8 @@ module.exports = {
|
|
79
121
|
|
80
122
|
/**
|
81
123
|
* Checks the spacing between two tokens before or after the star token.
|
124
|
+
*
|
125
|
+
* @param {string} kind Either "named", "anonymous", or "method"
|
82
126
|
* @param {string} side Either "before" or "after".
|
83
127
|
* @param {Token} leftToken `function` keyword token if side is "before", or
|
84
128
|
* star token if side is "after".
|
@@ -86,10 +130,10 @@ module.exports = {
|
|
86
130
|
* token if side is "after".
|
87
131
|
* @returns {void}
|
88
132
|
*/
|
89
|
-
function checkSpacing(side, leftToken, rightToken) {
|
90
|
-
if (!!(rightToken.range[0] - leftToken.range[1]) !==
|
133
|
+
function checkSpacing(kind, side, leftToken, rightToken) {
|
134
|
+
if (!!(rightToken.range[0] - leftToken.range[1]) !== modes[kind][side]) {
|
91
135
|
const after = leftToken.value === "*";
|
92
|
-
const spaceRequired =
|
136
|
+
const spaceRequired = modes[kind][side];
|
93
137
|
const node = after ? leftToken : rightToken;
|
94
138
|
const type = spaceRequired ? "Missing" : "Unexpected";
|
95
139
|
const message = "{{type}} space {{side}} *.";
|
@@ -117,6 +161,7 @@ module.exports = {
|
|
117
161
|
|
118
162
|
/**
|
119
163
|
* Enforces the spacing around the star if node is a generator function.
|
164
|
+
*
|
120
165
|
* @param {ASTNode} node A function expression or declaration node.
|
121
166
|
* @returns {void}
|
122
167
|
*/
|
@@ -126,17 +171,23 @@ module.exports = {
|
|
126
171
|
}
|
127
172
|
|
128
173
|
const starToken = getStarToken(node);
|
129
|
-
|
130
|
-
// Only check before when preceded by `function`|`static` keyword
|
131
174
|
const prevToken = sourceCode.getTokenBefore(starToken);
|
175
|
+
const nextToken = sourceCode.getTokenAfter(starToken);
|
132
176
|
|
133
|
-
|
134
|
-
|
177
|
+
let kind = "named";
|
178
|
+
|
179
|
+
if (node.parent.type === "MethodDefinition" || (node.parent.type === "Property" && node.parent.method)) {
|
180
|
+
kind = "method";
|
181
|
+
} else if (!node.id) {
|
182
|
+
kind = "anonymous";
|
135
183
|
}
|
136
184
|
|
137
|
-
|
185
|
+
// Only check before when preceded by `function`|`static` keyword
|
186
|
+
if (!(kind === "method" && starToken === sourceCode.getFirstToken(node.parent))) {
|
187
|
+
checkSpacing(kind, "before", prevToken, starToken);
|
188
|
+
}
|
138
189
|
|
139
|
-
checkSpacing("after", starToken, nextToken);
|
190
|
+
checkSpacing(kind, "after", starToken, nextToken);
|
140
191
|
}
|
141
192
|
|
142
193
|
return {
|
@@ -965,7 +965,8 @@ module.exports = {
|
|
965
965
|
const regex = /^return\s*?\(\s*?\);*?/;
|
966
966
|
|
967
967
|
const statementWithoutArgument = sourceCode.getText(node).replace(
|
968
|
-
sourceCode.getText(node.argument), ""
|
968
|
+
sourceCode.getText(node.argument), ""
|
969
|
+
);
|
969
970
|
|
970
971
|
return regex.test(statementWithoutArgument);
|
971
972
|
}
|