eslint 4.9.0 → 4.10.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 CHANGED
@@ -1,3 +1,64 @@
1
+ v4.10.0 - October 27, 2017
2
+
3
+ * bb6e60a Fix: Improve the doc for no-restricted-modules rule (fixes #9437) (#9495) (vibss2397)
4
+ * c529de9 Docs: Amend rule document to correct and complete it (refs #6251). (#9498) (Jonathan Pool)
5
+ * f9c6673 Chore: Add tests to cover array and object values and leading commas. (#9502) (Jonathan Pool)
6
+ * 9169258 Chore: remove `npm run check-commit` script (#9513) (Teddy Katz)
7
+ * 7d390b2 Docs: Revise contributor documentation on issue labels. (#9469) (Jonathan Pool)
8
+ * d80b9d0 Fix: no-var don't fix globals (fixes #9520) (#9525) (Toru Nagashima)
9
+ * b8aa071 Fix: allow linting the empty string from stdin (fixes #9515) (#9517) (Teddy Katz)
10
+ * 350a72c Chore: regex.test => string.startsWith (#9518) (薛定谔的猫)
11
+ * de0bef4 Chore: remove obsolete eslintbot templates (#9512) (Teddy Katz)
12
+ * 720b6d5 Docs: Update ISSUE_TEMPLATE.md (#9504) (薛定谔的猫)
13
+ * 2fa64b7 Fix: should not convert non-consecutive line comments to a single blo… (#9475) (薛定谔的猫)
14
+ * 9725146 Fix: multiline-comment-style fix produces invalid code (fixes #9461). (#9463) (薛定谔的猫)
15
+ * b12cff8 Fix: Expected order of jsdoc tags (fixes #9412) (#9451) (Orlando Wenzinger)
16
+ * f054ab5 Docs: add `.md` to link (for github users) (#9501) (薛定谔的猫)
17
+ * 5ed9cfc Docs: Correct violations of “Variable Declarations” in Code Conventions (#9447) (Jonathan Pool)
18
+ * 3171097 Docs: Clears confusion on usage of global and local plugins.(#9492) (Vasili Sviridov)
19
+ * 3204773 Chore: enable max-len. (#9414) (薛定谔的猫)
20
+ * 0f71fef Docs: Unquote booleans in lines-between-class-members docs (#9497) (Brandon Mills)
21
+ * b3d7532 Docs: use consistent terminology & fix link etc. (#9490) (薛定谔的猫)
22
+ * 87db8ae Docs: Fix broken links (#9488) (gpiress)
23
+ * 51bdb2f Docs: Incorrect link to related rule (#9477) (Gavin King)
24
+ * 1a962e8 Docs: Add FAQ for when ESLint cannot find plugin (#9467) (Kevin Partington)
25
+ * 8768b2d Fix: multiline-comment-style autofixer added trailing space (#9454) (Teddy Katz)
26
+ * e830aa1 Fix: multiline-comment-style reports block comments followed by code (#9450) (Teddy Katz)
27
+ * b12e5fe Docs: Repair broken links and add migration links. (#9473) (Jonathan Pool)
28
+ * eca01ed Docs: Add missing info about special status of home-dir config files. (#9472) (Jonathan Pool)
29
+ * eb8cfb1 Fix: change err report in constant condition (fixes #9398) (#9436) (Victor Hom)
30
+ * da77eb4 Chore: Revise no-config-file test to prevent false failure. (#9443) (Jonathan Pool)
31
+ * 47e5f6f Docs: ensure "good commit message" examples actually follow guidelines (#9466) (Teddy Katz)
32
+ * ebb530d Update: Don't ignore comments (no-trailing-spaces) (#9416) (Chris van Marle)
33
+ * 5012661 Build: fix `npm run profile` script (fixes #9397) (#9455) (Teddy Katz)
34
+ * ecac0fd Docs: Remove blockBindings references (#9446) (Jan Pilzer)
35
+ * 0b89865 Chore: ensure tests for internal rules get run (#9453) (Teddy Katz)
36
+ * 052c504 Docs: suggest deleting branches after merging PRs (#9449) (Teddy Katz)
37
+ * b31e55a Chore: move internal rules out of lib/ (#9448) (Teddy Katz)
38
+ * a7521e3 Docs: improve examples for multiline-comment-style (#9440) (Teddy Katz)
39
+ * 235c7dd 4.9.0 (ESLint Jenkins)
40
+ * b6f31a9 Build: changelog update for 4.9.0 (ESLint Jenkins)
41
+ * 85388fb Fix: Correct error and test messages to fit config search path (#9428) (Jonathan Pool)
42
+ * 62a323c Fix: Add class options for `lines-around-comment` (fixes #8564) (#8565) (Ed Lee)
43
+ * 8eb4aae New: multiline-comment-style rule (fixes #8320) (#9389) (薛定谔的猫)
44
+ * db41408 Chore: avoid applying eslint-env comments twice (#9278) (Teddy Katz)
45
+ * febb897 Chore: avoid loose equality assertions (#9415) (Teddy Katz)
46
+ * 2247efa Update: Add FunctionExpression to require-jsdoc (fixes #5867) (#9395) (Kai Cataldo)
47
+ * 6791d18 Docs: Corrected noun to verb. (#9438) (Jonathan Pool)
48
+ * b02fbb6 Update: custom messages for no-restricted-* (refs #8400) (Maja Wichrowska)
49
+ * 02732bd Docs: Reorganized to avoid misunderstandings. (#9434) (Jonathan Pool)
50
+ * d9466b8 Docs: Correct time forecast for tests. (#9432) (Jonathan Pool)
51
+ * f7ed84f Docs: Add instruction re home-directory config files (refs #7729) (#9426) (Jonathan Pool)
52
+ * 30d018b Chore: Add Aladdin-ADD & VictorHom to README (#9424) (Kai Cataldo)
53
+ * 2d8a303 Docs: fix examples for prefer-numeric-literals (#9155) (Lutz Lengemann)
54
+ * d7610f5 Docs: Add jquery warning to prefer-destructuring (#9409) (Thomas Grainger)
55
+ * e835dd1 Docs: clarify no-mixed-operators (fixes #8051) (Ruxandra Fediuc)
56
+ * 51360c8 Docs: update block-spacing details (fixes #8743) (#9375) (Victor Hom)
57
+ * 6767857 Update: fix ignored nodes in indent rule when using tabs (fixes #9392) (#9393) (Robin Houston)
58
+ * 37dde77 Chore: Refactor SourceCode#getJSDocComment (#9403) (Kai Cataldo)
59
+ * 9fedd51 Chore: Add missing space in blog post template (#9407) (Kevin Partington)
60
+ * 7654c99 Docs: add installing prerequisites in readme. (#9401) (薛定谔的猫)
61
+
1
62
  v4.9.0 - October 14, 2017
2
63
 
3
64
  * 85388fb Fix: Correct error and test messages to fit config search path (#9428) (Jonathan Pool)
package/README.md CHANGED
@@ -206,11 +206,23 @@ Maybe, depending on how much you need it. [JSCS has reached end of life](https:/
206
206
 
207
207
  If you are having issues with JSCS, you can try to move to ESLint. We are focusing our time and energy on JSCS compatibility issues.
208
208
 
209
-
210
209
  ### Is ESLint just linting or does it also check style?
211
210
 
212
211
  ESLint does both traditional linting (looking for problematic patterns) and style checking (enforcement of conventions). You can use it for both.
213
212
 
213
+ ### Why can't ESLint find my plugins?
214
+
215
+ ESLint can be [globally or locally installed](#installation-and-usage). If you install ESLint globally, your plugins must also be installed globally; if you install ESLint locally, your plugins must also be installed locally.
216
+
217
+ If you are trying to run globally, make sure your plugins are installed globally (use `npm ls -g`).
218
+
219
+ If you are trying to run locally:
220
+
221
+ * Make sure your plugins (and ESLint) are both in your project's `package.json` as devDependencies (or dependencies, if your project uses ESLint at runtime).
222
+ * Make sure you have run `npm install` and all your dependencies are installed.
223
+
224
+ In all cases, make sure your plugins' peerDependencies have been installed as well. You can use `npm view eslint-plugin-myplugin peerDepencies` to see what peer dependencies `eslint-plugin-myplugin` has.
225
+
214
226
  ### Does ESLint support JSX?
215
227
 
216
228
  Yes, ESLint natively supports parsing JSX syntax (this must be enabled in [configuration](https://eslint.org/docs/user-guide/configuring).). Please note that supporting JSX syntax *is not* the same as supporting React. React applies specific semantics to JSX syntax that ESLint doesn't recognize. We recommend using [eslint-plugin-react](https://www.npmjs.com/package/eslint-plugin-react) if you are using React and want React semantics.
package/lib/cli-engine.js CHANGED
@@ -240,8 +240,8 @@ function processFile(filename, configHelper, options, linter) {
240
240
  function createIgnoreResult(filePath, baseDir) {
241
241
  let message;
242
242
  const isHidden = /^\./.test(path.basename(filePath));
243
- const isInNodeModules = baseDir && /^node_modules/.test(path.relative(baseDir, filePath));
244
- const isInBowerComponents = baseDir && /^bower_components/.test(path.relative(baseDir, filePath));
243
+ const isInNodeModules = baseDir && path.relative(baseDir, filePath).startsWith("node_modules");
244
+ const isInBowerComponents = baseDir && path.relative(baseDir, filePath).startsWith("bower_components");
245
245
 
246
246
  if (isHidden) {
247
247
  message = "File ignored by default. Use a negated ignore pattern (like \"--ignore-pattern '!<relative/path/to/filename>'\") to override.";
package/lib/cli.js CHANGED
@@ -144,6 +144,8 @@ const cli = {
144
144
 
145
145
  const files = currentOptions._;
146
146
 
147
+ const useStdin = typeof text === "string";
148
+
147
149
  if (currentOptions.version) { // version from package.json
148
150
 
149
151
  log.info(`v${require("../package.json").version}`);
@@ -153,7 +155,7 @@ const cli = {
153
155
  log.error("The --print-config option must be used with exactly one file name.");
154
156
  return 1;
155
157
  }
156
- if (text) {
158
+ if (useStdin) {
157
159
  log.error("The --print-config option is not available for piped-in code.");
158
160
  return 1;
159
161
  }
@@ -164,27 +166,27 @@ const cli = {
164
166
 
165
167
  log.info(JSON.stringify(fileConfig, null, " "));
166
168
  return 0;
167
- } else if (currentOptions.help || (!files.length && !text)) {
169
+ } else if (currentOptions.help || (!files.length && !useStdin)) {
168
170
 
169
171
  log.info(options.generateHelp());
170
172
 
171
173
  } else {
172
174
 
173
- debug(`Running on ${text ? "text" : "files"}`);
175
+ debug(`Running on ${useStdin ? "text" : "files"}`);
174
176
 
175
177
  if (currentOptions.fix && currentOptions.fixDryRun) {
176
178
  log.error("The --fix option and the --fix-dry-run option cannot be used together.");
177
179
  return 1;
178
180
  }
179
181
 
180
- if (text && currentOptions.fix) {
182
+ if (useStdin && currentOptions.fix) {
181
183
  log.error("The --fix option is not available for piped-in code; use --fix-dry-run instead.");
182
184
  return 1;
183
185
  }
184
186
 
185
187
  const engine = new CLIEngine(translateOptions(currentOptions));
186
188
 
187
- const report = text ? engine.executeOnText(text, currentOptions.stdinFilename, true) : engine.executeOnFiles(files);
189
+ const report = useStdin ? engine.executeOnText(text, currentOptions.stdinFilename, true) : engine.executeOnFiles(files);
188
190
 
189
191
  if (currentOptions.fix) {
190
192
  debug("Fix mode enabled - applying fixes");
@@ -208,7 +208,9 @@ module.exports = {
208
208
  if (item) {
209
209
  const tokenAfterItem = sourceCode.getTokenAfter(item, astUtils.isNotClosingParenToken);
210
210
 
211
- previousItemToken = tokenAfterItem ? sourceCode.getTokenBefore(tokenAfterItem) : sourceCode.ast.tokens[sourceCode.ast.tokens.length - 1];
211
+ previousItemToken = tokenAfterItem
212
+ ? sourceCode.getTokenBefore(tokenAfterItem)
213
+ : sourceCode.ast.tokens[sourceCode.ast.tokens.length - 1];
212
214
  }
213
215
  });
214
216
 
@@ -68,7 +68,7 @@ module.exports = {
68
68
 
69
69
  /**
70
70
  * Returns resolved option definitions based on an option and defaults
71
- *
71
+ *
72
72
  * @param {any} option - The option object or string value
73
73
  * @param {Object} defaults - The defaults to use if options are not present
74
74
  * @returns {Object} the resolved object definition
@@ -121,7 +121,7 @@ module.exports = {
121
121
 
122
122
  /**
123
123
  * Checks the spacing between two tokens before or after the star token.
124
- *
124
+ *
125
125
  * @param {string} kind Either "named", "anonymous", or "method"
126
126
  * @param {string} side Either "before" or "after".
127
127
  * @param {Token} leftToken `function` keyword token if side is "before", or
@@ -161,7 +161,7 @@ module.exports = {
161
161
 
162
162
  /**
163
163
  * Enforces the spacing around the star if node is a generator function.
164
- *
164
+ *
165
165
  * @param {ASTNode} node A function expression or declaration node.
166
166
  * @returns {void}
167
167
  */
@@ -733,7 +733,9 @@ module.exports = {
733
733
  } else if (parent.type === "ObjectExpression" || parent.type === "ArrayExpression") {
734
734
  const parentElements = node.parent.type === "ObjectExpression" ? node.parent.properties : node.parent.elements;
735
735
 
736
- if (parentElements[0] && parentElements[0].loc.start.line === parent.loc.start.line && parentElements[0].loc.end.line !== parent.loc.start.line) {
736
+ if (parentElements[0] &&
737
+ parentElements[0].loc.start.line === parent.loc.start.line &&
738
+ parentElements[0].loc.end.line !== parent.loc.start.line) {
737
739
 
738
740
  /*
739
741
  * If the first element of the array spans multiple lines, don't increase the expected indentation of the rest.
@@ -797,7 +799,8 @@ module.exports = {
797
799
  }
798
800
  }
799
801
 
800
- checkLastNodeLineIndent(node, nodeIndent + (isNodeInVarOnTop(node, parentVarNode) ? options.VariableDeclarator[parentVarNode.parent.kind] * indentSize : 0));
802
+ checkLastNodeLineIndent(node, nodeIndent +
803
+ (isNodeInVarOnTop(node, parentVarNode) ? options.VariableDeclarator[parentVarNode.parent.kind] * indentSize : 0));
801
804
  }
802
805
 
803
806
  /**
@@ -1245,7 +1245,9 @@ module.exports = {
1245
1245
  NewExpression(node) {
1246
1246
 
1247
1247
  // Only indent the arguments if the NewExpression has parens (e.g. `new Foo(bar)` or `new Foo()`, but not `new Foo`
1248
- if (node.arguments.length > 0 || astUtils.isClosingParenToken(sourceCode.getLastToken(node)) && astUtils.isOpeningParenToken(sourceCode.getLastToken(node, 1))) {
1248
+ if (node.arguments.length > 0 ||
1249
+ astUtils.isClosingParenToken(sourceCode.getLastToken(node)) &&
1250
+ astUtils.isOpeningParenToken(sourceCode.getLastToken(node, 1))) {
1249
1251
  addFunctionCallIndent(node);
1250
1252
  }
1251
1253
  },
@@ -308,7 +308,10 @@ module.exports = {
308
308
  nextLineNum = token.loc.end.line + 1,
309
309
  commentIsNotAlone = codeAroundComment(token);
310
310
 
311
- const blockStartAllowed = options.allowBlockStart && isCommentAtBlockStart(token) && !(options.allowClassStart === false && isCommentAtClassStart(token)),
311
+ const blockStartAllowed = options.allowBlockStart &&
312
+ isCommentAtBlockStart(token) &&
313
+ !(options.allowClassStart === false &&
314
+ isCommentAtClassStart(token)),
312
315
  blockEndAllowed = options.allowBlockEnd && isCommentAtBlockEnd(token) && !(options.allowClassEnd === false && isCommentAtClassEnd(token)),
313
316
  classStartAllowed = options.allowClassStart && isCommentAtClassStart(token),
314
317
  classEndAllowed = options.allowClassEnd && isCommentAtClassEnd(token),
@@ -125,10 +125,12 @@ module.exports = {
125
125
  },
126
126
  message: EXPECTED_BLOCK_ERROR,
127
127
  fix(fixer) {
128
- return fixer.replaceTextRange(
129
- [commentGroup[0].range[0], commentGroup[commentGroup.length - 1].range[1]],
130
- `/*${convertToStarredBlock(commentGroup[0], commentLines)}*/`
131
- );
128
+ const range = [commentGroup[0].range[0], commentGroup[commentGroup.length - 1].range[1]];
129
+ const starredBlock = `/*${convertToStarredBlock(commentGroup[0], commentLines)}*/`;
130
+
131
+ return commentLines.some(value => value.startsWith("/"))
132
+ ? null
133
+ : fixer.replaceTextRange(range, starredBlock);
132
134
  }
133
135
  });
134
136
  } else {
@@ -174,8 +176,12 @@ module.exports = {
174
176
  : MISSING_STAR_ERROR,
175
177
  fix(fixer) {
176
178
  const lineStartIndex = sourceCode.getIndexFromLoc({ line: lineNumber, column: 0 });
177
- const commentStartIndex = lineStartIndex + lineText.match(/^\s*\*? ?/)[0].length;
178
- const replacementText = lineNumber === block.loc.end.line ? expectedLinePrefix : `${expectedLinePrefix} `;
179
+ const linePrefixLength = lineText.match(/^\s*\*? ?/)[0].length;
180
+ const commentStartIndex = lineStartIndex + linePrefixLength;
181
+
182
+ const replacementText = lineNumber === block.loc.end.line || lineText.length === linePrefixLength
183
+ ? expectedLinePrefix
184
+ : `${expectedLinePrefix} `;
179
185
 
180
186
  return fixer.replaceTextRange([lineStartIndex, commentStartIndex], replacementText);
181
187
  }
@@ -188,6 +194,11 @@ module.exports = {
188
194
  if (!isJSDoc(commentGroup) && commentGroup[0].type === "Block") {
189
195
  const commentLines = getCommentLines(commentGroup);
190
196
  const block = commentGroup[0];
197
+ const tokenAfter = sourceCode.getTokenAfter(block, { includeComments: true });
198
+
199
+ if (tokenAfter && block.loc.end.line === tokenAfter.loc.start.line) {
200
+ return;
201
+ }
191
202
 
192
203
  context.report({
193
204
  loc: {
@@ -260,10 +271,13 @@ module.exports = {
260
271
  return !tokenBefore || tokenBefore.loc.end.line < comment.loc.start.line;
261
272
  })
262
273
  .reduce((commentGroups, comment, index, commentList) => {
274
+ const tokenBefore = sourceCode.getTokenBefore(comment, { includeComments: true });
275
+
263
276
  if (
264
277
  comment.type === "Line" &&
265
278
  index && commentList[index - 1].type === "Line" &&
266
- sourceCode.getTokenBefore(comment, { includeComments: true }) === commentList[index - 1]
279
+ tokenBefore && tokenBefore.loc.end.line === comment.loc.start.line - 1 &&
280
+ tokenBefore === commentList[index - 1]
267
281
  ) {
268
282
  commentGroups[commentGroups.length - 1].push(comment);
269
283
  } else {
@@ -51,7 +51,7 @@ module.exports = {
51
51
  CatchClause(node) {
52
52
  let scope = context.getScope();
53
53
 
54
- // When blockBindings is enabled, CatchClause creates its own scope
54
+ // When ecmaVersion >= 6, CatchClause creates its own scope
55
55
  // so start from one upper scope to exclude the current node
56
56
  if (scope.block === node) {
57
57
  scope = scope.upper;
@@ -138,7 +138,7 @@ module.exports = {
138
138
  function checkConstantConditionLoopInSet(node) {
139
139
  if (loopsInCurrentScope.has(node)) {
140
140
  loopsInCurrentScope.delete(node);
141
- context.report({ node, message: "Unexpected constant condition." });
141
+ context.report({ node: node.test, message: "Unexpected constant condition." });
142
142
  }
143
143
  }
144
144
 
@@ -150,7 +150,7 @@ module.exports = {
150
150
  */
151
151
  function reportIfConstant(node) {
152
152
  if (node.test && isConstant(node.test, true)) {
153
- context.report({ node, message: "Unexpected constant condition." });
153
+ context.report({ node: node.test, message: "Unexpected constant condition." });
154
154
  }
155
155
  }
156
156
 
@@ -45,7 +45,8 @@ module.exports = {
45
45
  const lastIfToken = sourceCode.getLastToken(node.consequent);
46
46
  const sourceText = sourceCode.getText();
47
47
 
48
- if (sourceText.slice(openingElseCurly.range[1], node.range[0]).trim() || sourceText.slice(node.range[1], closingElseCurly.range[0]).trim()) {
48
+ if (sourceText.slice(openingElseCurly.range[1],
49
+ node.range[0]).trim() || sourceText.slice(node.range[1], closingElseCurly.range[0]).trim()) {
49
50
 
50
51
  // Don't fix if there are any non-whitespace characters interfering (e.g. comments)
51
52
  return null;
@@ -49,7 +49,7 @@ module.exports = {
49
49
 
50
50
  const options = context.options[0] || {},
51
51
  skipBlankLines = options.skipBlankLines || false,
52
- ignoreComments = typeof options.ignoreComments === "undefined" || options.ignoreComments;
52
+ ignoreComments = typeof options.ignoreComments === "boolean" && options.ignoreComments;
53
53
 
54
54
  /**
55
55
  * Report the error message
@@ -72,8 +72,10 @@ module.exports = {
72
72
  node.right,
73
73
  token => token.value === node.operator
74
74
  );
75
+ const text = sourceCode.getText();
75
76
 
76
- return sourceCode.getText().slice(node.range[0], operatorToken.range[0]) + OPERATOR_INVERSES[node.operator] + sourceCode.getText().slice(operatorToken.range[1], node.range[1]);
77
+ return text.slice(node.range[0],
78
+ operatorToken.range[0]) + OPERATOR_INVERSES[node.operator] + text.slice(operatorToken.range[1], node.range[1]);
77
79
  }
78
80
 
79
81
  if (astUtils.getPrecedence(node) < astUtils.getPrecedence({ type: "UnaryExpression" })) {
@@ -59,7 +59,8 @@ module.exports = {
59
59
  * Only perform a fix if there are no comments between the label and the body. This will be the case
60
60
  * when there is exactly one token/comment (the ":") between the label and the body.
61
61
  */
62
- if (sourceCode.getTokenAfter(node.label, { includeComments: true }) === sourceCode.getTokenBefore(node.body, { includeComments: true })) {
62
+ if (sourceCode.getTokenAfter(node.label, { includeComments: true }) ===
63
+ sourceCode.getTokenBefore(node.body, { includeComments: true })) {
63
64
  return fixer.removeRange([node.range[0], node.body.range[0]]);
64
65
  }
65
66
 
@@ -50,7 +50,8 @@ module.exports = {
50
50
  const rightSquareBracket = sourceCode.getFirstTokenBetween(node.key, node.value, astUtils.isClosingBracketToken);
51
51
  const tokensBetween = sourceCode.getTokensBetween(leftSquareBracket, rightSquareBracket, 1);
52
52
 
53
- if (tokensBetween.slice(0, -1).some((token, index) => sourceCode.getText().slice(token.range[1], tokensBetween[index + 1].range[0]).trim())) {
53
+ if (tokensBetween.slice(0, -1).some((token, index) =>
54
+ sourceCode.getText().slice(token.range[1], tokensBetween[index + 1].range[0]).trim())) {
54
55
 
55
56
  // If there are comments between the brackets and the property name, don't do a fix.
56
57
  return null;
@@ -63,7 +63,14 @@ function parseRegExp(regExpText) {
63
63
  return Object.assign(state, { inCharClass: false, startingCharClass: false });
64
64
  }
65
65
  }
66
- charList.push({ text: char, index, escaped: state.escapeNextChar, inCharClass: state.inCharClass, startsCharClass: state.startingCharClass, endsCharClass: false });
66
+ charList.push({
67
+ text: char,
68
+ index,
69
+ escaped: state.escapeNextChar,
70
+ inCharClass: state.inCharClass,
71
+ startsCharClass: state.startingCharClass,
72
+ endsCharClass: false
73
+ });
67
74
  return Object.assign(state, { escapeNextChar: false, startingCharClass: false });
68
75
  }, { escapeNextChar: false, inCharClass: false, startingCharClass: false });
69
76
 
@@ -15,6 +15,15 @@ const astUtils = require("../ast-utils");
15
15
  // Helpers
16
16
  //------------------------------------------------------------------------------
17
17
 
18
+ /**
19
+ * Check whether a given variable is a global variable or not.
20
+ * @param {eslint-scope.Variable} variable The variable to check.
21
+ * @returns {boolean} `true` if the variable is a global variable.
22
+ */
23
+ function isGlobal(variable) {
24
+ return Boolean(variable.scope) && variable.scope.type === "global";
25
+ }
26
+
18
27
  /**
19
28
  * Finds the nearest function scope or global scope walking up the scope
20
29
  * hierarchy.
@@ -203,6 +212,7 @@ module.exports = {
203
212
  * Checks whether it can fix a given variable declaration or not.
204
213
  * It cannot fix if the following cases:
205
214
  *
215
+ * - A variable is a global variable.
206
216
  * - A variable is declared on a SwitchCase node.
207
217
  * - A variable is redeclared.
208
218
  * - A variable is used from outside the scope.
@@ -256,6 +266,7 @@ module.exports = {
256
266
 
257
267
  if (node.parent.type === "SwitchCase" ||
258
268
  node.declarations.some(hasSelfReferenceInTDZ) ||
269
+ variables.some(isGlobal) ||
259
270
  variables.some(isRedeclared) ||
260
271
  variables.some(isUsedFromOutsideOf(scopeNode))
261
272
  ) {
@@ -215,8 +215,12 @@ module.exports = {
215
215
  * @returns {Object} A fix for this node
216
216
  */
217
217
  function makeFunctionShorthand(fixer, node) {
218
- const firstKeyToken = node.computed ? sourceCode.getFirstToken(node, astUtils.isOpeningBracketToken) : sourceCode.getFirstToken(node.key);
219
- const lastKeyToken = node.computed ? sourceCode.getFirstTokenBetween(node.key, node.value, astUtils.isClosingBracketToken) : sourceCode.getLastToken(node.key);
218
+ const firstKeyToken = node.computed
219
+ ? sourceCode.getFirstToken(node, astUtils.isOpeningBracketToken)
220
+ : sourceCode.getFirstToken(node.key);
221
+ const lastKeyToken = node.computed
222
+ ? sourceCode.getFirstTokenBetween(node.key, node.value, astUtils.isClosingBracketToken)
223
+ : sourceCode.getLastToken(node.key);
220
224
  const keyText = sourceCode.text.slice(firstKeyToken.range[0], lastKeyToken.range[1]);
221
225
  let keyPrefix = "";
222
226
 
@@ -87,7 +87,9 @@ module.exports = {
87
87
  if (hasLinebreakBefore !== hasLinebreakAfter && desiredStyle !== "none") {
88
88
 
89
89
  // If there is a comment before and after the operator, don't do a fix.
90
- if (sourceCode.getTokenBefore(operatorToken, { includeComments: true }) !== tokenBefore && sourceCode.getTokenAfter(operatorToken, { includeComments: true }) !== tokenAfter) {
90
+ if (sourceCode.getTokenBefore(operatorToken, { includeComments: true }) !== tokenBefore &&
91
+ sourceCode.getTokenAfter(operatorToken, { includeComments: true }) !== tokenAfter) {
92
+
91
93
  return null;
92
94
  }
93
95
 
@@ -151,7 +151,8 @@ module.exports = {
151
151
  message: "Member '{{memberName}}' of the import declaration should be sorted alphabetically.",
152
152
  data: { memberName: importSpecifiers[firstUnsortedIndex].local.name },
153
153
  fix(fixer) {
154
- if (importSpecifiers.some(specifier => sourceCode.getCommentsBefore(specifier).length || sourceCode.getCommentsAfter(specifier).length)) {
154
+ if (importSpecifiers.some(specifier =>
155
+ sourceCode.getCommentsBefore(specifier).length || sourceCode.getCommentsAfter(specifier).length)) {
155
156
 
156
157
  // If there are comments in the ImportSpecifier list, don't rearrange the specifiers.
157
158
  return null;
@@ -226,8 +226,10 @@ module.exports = {
226
226
  function checkJSDoc(node) {
227
227
  const jsdocNode = sourceCode.getJSDocComment(node),
228
228
  functionData = fns.pop(),
229
- params = Object.create(null);
229
+ params = Object.create(null),
230
+ paramsTags = [];
230
231
  let hasReturns = false,
232
+ returnsTag,
231
233
  hasConstructor = false,
232
234
  isInterface = false,
233
235
  isOverride = false,
@@ -261,43 +263,13 @@ module.exports = {
261
263
  case "param":
262
264
  case "arg":
263
265
  case "argument":
264
- if (!tag.type) {
265
- context.report({ node: jsdocNode, message: "Missing JSDoc parameter type for '{{name}}'.", data: { name: tag.name } });
266
- }
267
-
268
- if (!tag.description && requireParamDescription) {
269
- context.report({ node: jsdocNode, message: "Missing JSDoc parameter description for '{{name}}'.", data: { name: tag.name } });
270
- }
271
-
272
- if (params[tag.name]) {
273
- context.report({ node: jsdocNode, message: "Duplicate JSDoc parameter '{{name}}'.", data: { name: tag.name } });
274
- } else if (tag.name.indexOf(".") === -1) {
275
- params[tag.name] = 1;
276
- }
266
+ paramsTags.push(tag);
277
267
  break;
278
268
 
279
269
  case "return":
280
270
  case "returns":
281
271
  hasReturns = true;
282
-
283
- if (!requireReturn && !functionData.returnPresent && (tag.type === null || !isValidReturnType(tag)) && !isAbstract) {
284
- context.report({
285
- node: jsdocNode,
286
- message: "Unexpected @{{title}} tag; function has no return statement.",
287
- data: {
288
- title: tag.title
289
- }
290
- });
291
- } else {
292
- if (requireReturnType && !tag.type) {
293
- context.report({ node: jsdocNode, message: "Missing JSDoc return type." });
294
- }
295
-
296
- if (!isValidReturnType(tag) && !tag.description && requireReturnDescription) {
297
- context.report({ node: jsdocNode, message: "Missing JSDoc return description." });
298
- }
299
- }
300
-
272
+ returnsTag = tag;
301
273
  break;
302
274
 
303
275
  case "constructor":
@@ -333,6 +305,40 @@ module.exports = {
333
305
  }
334
306
  });
335
307
 
308
+ paramsTags.forEach(param => {
309
+ if (!param.type) {
310
+ context.report({ node: jsdocNode, message: "Missing JSDoc parameter type for '{{name}}'.", data: { name: param.name } });
311
+ }
312
+ if (!param.description && requireParamDescription) {
313
+ context.report({ node: jsdocNode, message: "Missing JSDoc parameter description for '{{name}}'.", data: { name: param.name } });
314
+ }
315
+ if (params[param.name]) {
316
+ context.report({ node: jsdocNode, message: "Duplicate JSDoc parameter '{{name}}'.", data: { name: param.name } });
317
+ } else if (param.name.indexOf(".") === -1) {
318
+ params[param.name] = 1;
319
+ }
320
+ });
321
+
322
+ if (hasReturns) {
323
+ if (!requireReturn && !functionData.returnPresent && (returnsTag.type === null || !isValidReturnType(returnsTag)) && !isAbstract) {
324
+ context.report({
325
+ node: jsdocNode,
326
+ message: "Unexpected @{{title}} tag; function has no return statement.",
327
+ data: {
328
+ title: returnsTag.title
329
+ }
330
+ });
331
+ } else {
332
+ if (requireReturnType && !returnsTag.type) {
333
+ context.report({ node: jsdocNode, message: "Missing JSDoc return type." });
334
+ }
335
+
336
+ if (!isValidReturnType(returnsTag) && !returnsTag.description && requireReturnDescription) {
337
+ context.report({ node: jsdocNode, message: "Missing JSDoc return description." });
338
+ }
339
+ }
340
+ }
341
+
336
342
  // check for functions missing @returns
337
343
  if (!isOverride && !hasReturns && !hasConstructor && !isInterface &&
338
344
  node.parent.kind !== "get" && node.parent.kind !== "constructor" &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint",
3
- "version": "4.9.0",
3
+ "version": "4.10.0",
4
4
  "author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
5
5
  "description": "An AST-based pattern checker for JavaScript.",
6
6
  "bin": {
@@ -20,8 +20,7 @@
20
20
  "browserify": "node Makefile.js browserify",
21
21
  "perf": "node Makefile.js perf",
22
22
  "profile": "beefy tests/bench/bench.js --open -- -t brfs -t ./tests/bench/xform-rules.js -r espree",
23
- "coveralls": "cat ./coverage/lcov.info | coveralls",
24
- "check-commit": "node Makefile.js checkGitCommit"
23
+ "coveralls": "cat ./coverage/lcov.info | coveralls"
25
24
  },
26
25
  "files": [
27
26
  "LICENSE",
@@ -1,3 +0,0 @@
1
- rules:
2
- internal-no-invalid-meta: "error"
3
- internal-consistent-docs-description: "error"
@@ -1,130 +0,0 @@
1
- /**
2
- * @fileoverview Internal rule to enforce meta.docs.description conventions.
3
- * @author Vitor Balocco
4
- */
5
-
6
- "use strict";
7
-
8
- const ALLOWED_FIRST_WORDS = [
9
- "enforce",
10
- "require",
11
- "disallow"
12
- ];
13
-
14
- //------------------------------------------------------------------------------
15
- // Helpers
16
- //------------------------------------------------------------------------------
17
-
18
- /**
19
- * Gets the property of the Object node passed in that has the name specified.
20
- *
21
- * @param {string} property Name of the property to return.
22
- * @param {ASTNode} node The ObjectExpression node.
23
- * @returns {ASTNode} The Property node or null if not found.
24
- */
25
- function getPropertyFromObject(property, node) {
26
- const properties = node.properties;
27
-
28
- for (let i = 0; i < properties.length; i++) {
29
- if (properties[i].key.name === property) {
30
- return properties[i];
31
- }
32
- }
33
-
34
- return null;
35
- }
36
-
37
- /**
38
- * Verifies that the meta.docs.description property follows our internal conventions.
39
- *
40
- * @param {RuleContext} context The ESLint rule context.
41
- * @param {ASTNode} exportsNode ObjectExpression node that the rule exports.
42
- * @returns {void}
43
- */
44
- function checkMetaDocsDescription(context, exportsNode) {
45
- if (exportsNode.type !== "ObjectExpression") {
46
-
47
- // if the exported node is not the correct format, "internal-no-invalid-meta" will already report this.
48
- return;
49
- }
50
-
51
- const metaProperty = getPropertyFromObject("meta", exportsNode);
52
- const metaDocs = metaProperty && getPropertyFromObject("docs", metaProperty.value);
53
- const metaDocsDescription = metaDocs && getPropertyFromObject("description", metaDocs.value);
54
-
55
- if (!metaDocsDescription) {
56
-
57
- // if there is no `meta.docs.description` property, "internal-no-invalid-meta" will already report this.
58
- return;
59
- }
60
-
61
- const description = metaDocsDescription.value.value;
62
-
63
- if (typeof description !== "string") {
64
- context.report({
65
- node: metaDocsDescription.value,
66
- message: "`meta.docs.description` should be a string."
67
- });
68
- return;
69
- }
70
-
71
- if (description === "") {
72
- context.report({
73
- node: metaDocsDescription.value,
74
- message: "`meta.docs.description` should not be empty."
75
- });
76
- return;
77
- }
78
-
79
- if (description.indexOf(" ") === 0) {
80
- context.report({
81
- node: metaDocsDescription.value,
82
- message: "`meta.docs.description` should not start with whitespace."
83
- });
84
- return;
85
- }
86
-
87
- const firstWord = description.split(" ")[0];
88
-
89
- if (ALLOWED_FIRST_WORDS.indexOf(firstWord) === -1) {
90
- context.report({
91
- node: metaDocsDescription.value,
92
- message: "`meta.docs.description` should start with one of the following words: {{ allowedWords }}. Started with \"{{ firstWord }}\" instead.",
93
- data: {
94
- allowedWords: ALLOWED_FIRST_WORDS.join(", "),
95
- firstWord
96
- }
97
- });
98
- }
99
- }
100
-
101
- //------------------------------------------------------------------------------
102
- // Rule Definition
103
- //------------------------------------------------------------------------------
104
-
105
- module.exports = {
106
- meta: {
107
- docs: {
108
- description: "enforce correct conventions of `meta.docs.description` property in core rules",
109
- category: "Internal",
110
- recommended: false
111
- },
112
-
113
- schema: []
114
- },
115
-
116
- create(context) {
117
- return {
118
- AssignmentExpression(node) {
119
- if (node.left &&
120
- node.right &&
121
- node.left.type === "MemberExpression" &&
122
- node.left.object.name === "module" &&
123
- node.left.property.name === "exports") {
124
-
125
- checkMetaDocsDescription(context, node.right);
126
- }
127
- }
128
- };
129
- }
130
- };
@@ -1,188 +0,0 @@
1
- /**
2
- * @fileoverview Internal rule to prevent missing or invalid meta property in core rules.
3
- * @author Vitor Balocco
4
- */
5
-
6
- "use strict";
7
-
8
- //------------------------------------------------------------------------------
9
- // Helpers
10
- //------------------------------------------------------------------------------
11
-
12
- /**
13
- * Gets the property of the Object node passed in that has the name specified.
14
- *
15
- * @param {string} property Name of the property to return.
16
- * @param {ASTNode} node The ObjectExpression node.
17
- * @returns {ASTNode} The Property node or null if not found.
18
- */
19
- function getPropertyFromObject(property, node) {
20
- const properties = node.properties;
21
-
22
- for (let i = 0; i < properties.length; i++) {
23
- if (properties[i].key.name === property) {
24
- return properties[i];
25
- }
26
- }
27
-
28
- return null;
29
- }
30
-
31
- /**
32
- * Extracts the `meta` property from the ObjectExpression that all rules export.
33
- *
34
- * @param {ASTNode} exportsNode ObjectExpression node that the rule exports.
35
- * @returns {ASTNode} The `meta` Property node or null if not found.
36
- */
37
- function getMetaPropertyFromExportsNode(exportsNode) {
38
- return getPropertyFromObject("meta", exportsNode);
39
- }
40
-
41
- /**
42
- * Whether this `meta` ObjectExpression has a `docs` property defined or not.
43
- *
44
- * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule.
45
- * @returns {boolean} `true` if a `docs` property exists.
46
- */
47
- function hasMetaDocs(metaPropertyNode) {
48
- return Boolean(getPropertyFromObject("docs", metaPropertyNode.value));
49
- }
50
-
51
- /**
52
- * Whether this `meta` ObjectExpression has a `docs.description` property defined or not.
53
- *
54
- * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule.
55
- * @returns {boolean} `true` if a `docs.description` property exists.
56
- */
57
- function hasMetaDocsDescription(metaPropertyNode) {
58
- const metaDocs = getPropertyFromObject("docs", metaPropertyNode.value);
59
-
60
- return metaDocs && getPropertyFromObject("description", metaDocs.value);
61
- }
62
-
63
- /**
64
- * Whether this `meta` ObjectExpression has a `docs.category` property defined or not.
65
- *
66
- * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule.
67
- * @returns {boolean} `true` if a `docs.category` property exists.
68
- */
69
- function hasMetaDocsCategory(metaPropertyNode) {
70
- const metaDocs = getPropertyFromObject("docs", metaPropertyNode.value);
71
-
72
- return metaDocs && getPropertyFromObject("category", metaDocs.value);
73
- }
74
-
75
- /**
76
- * Whether this `meta` ObjectExpression has a `docs.recommended` property defined or not.
77
- *
78
- * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule.
79
- * @returns {boolean} `true` if a `docs.recommended` property exists.
80
- */
81
- function hasMetaDocsRecommended(metaPropertyNode) {
82
- const metaDocs = getPropertyFromObject("docs", metaPropertyNode.value);
83
-
84
- return metaDocs && getPropertyFromObject("recommended", metaDocs.value);
85
- }
86
-
87
- /**
88
- * Whether this `meta` ObjectExpression has a `schema` property defined or not.
89
- *
90
- * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule.
91
- * @returns {boolean} `true` if a `schema` property exists.
92
- */
93
- function hasMetaSchema(metaPropertyNode) {
94
- return getPropertyFromObject("schema", metaPropertyNode.value);
95
- }
96
-
97
- /**
98
- * Checks the validity of the meta definition of this rule and reports any errors found.
99
- *
100
- * @param {RuleContext} context The ESLint rule context.
101
- * @param {ASTNode} exportsNode ObjectExpression node that the rule exports.
102
- * @param {boolean} ruleIsFixable whether the rule is fixable or not.
103
- * @returns {void}
104
- */
105
- function checkMetaValidity(context, exportsNode) {
106
- const metaProperty = getMetaPropertyFromExportsNode(exportsNode);
107
-
108
- if (!metaProperty) {
109
- context.report(exportsNode, "Rule is missing a meta property.");
110
- return;
111
- }
112
-
113
- if (!hasMetaDocs(metaProperty)) {
114
- context.report(metaProperty, "Rule is missing a meta.docs property.");
115
- return;
116
- }
117
-
118
- if (!hasMetaDocsDescription(metaProperty)) {
119
- context.report(metaProperty, "Rule is missing a meta.docs.description property.");
120
- return;
121
- }
122
-
123
- if (!hasMetaDocsCategory(metaProperty)) {
124
- context.report(metaProperty, "Rule is missing a meta.docs.category property.");
125
- return;
126
- }
127
-
128
- if (!hasMetaDocsRecommended(metaProperty)) {
129
- context.report(metaProperty, "Rule is missing a meta.docs.recommended property.");
130
- return;
131
- }
132
-
133
- if (!hasMetaSchema(metaProperty)) {
134
- context.report(metaProperty, "Rule is missing a meta.schema property.");
135
- }
136
- }
137
-
138
- /**
139
- * Whether this node is the correct format for a rule definition or not.
140
- *
141
- * @param {ASTNode} node node that the rule exports.
142
- * @returns {boolean} `true` if the exported node is the correct format for a rule definition
143
- */
144
- function isCorrectExportsFormat(node) {
145
- return node.type === "ObjectExpression";
146
- }
147
-
148
- //------------------------------------------------------------------------------
149
- // Rule Definition
150
- //------------------------------------------------------------------------------
151
-
152
- module.exports = {
153
- meta: {
154
- docs: {
155
- description: "enforce correct use of `meta` property in core rules",
156
- category: "Internal",
157
- recommended: false
158
- },
159
-
160
- schema: []
161
- },
162
-
163
- create(context) {
164
- let exportsNode;
165
-
166
- return {
167
- AssignmentExpression(node) {
168
- if (node.left &&
169
- node.right &&
170
- node.left.type === "MemberExpression" &&
171
- node.left.object.name === "module" &&
172
- node.left.property.name === "exports") {
173
-
174
- exportsNode = node.right;
175
- }
176
- },
177
-
178
- "Program:exit"() {
179
- if (!isCorrectExportsFormat(exportsNode)) {
180
- context.report({ node: exportsNode, message: "Rule does not export an Object. Make sure the rule follows the new rule format." });
181
- return;
182
- }
183
-
184
- checkMetaValidity(context, exportsNode);
185
- }
186
- };
187
- }
188
- };