eslint 4.7.1 → 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.
Files changed (65) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/README.md +34 -19
  3. package/conf/default-cli-options.js +2 -1
  4. package/conf/eslint-recommended.js +2 -0
  5. package/lib/ast-utils.js +2 -1
  6. package/lib/cli-engine.js +26 -5
  7. package/lib/cli.js +17 -9
  8. package/lib/code-path-analysis/code-path-segment.js +39 -39
  9. package/lib/code-path-analysis/code-path-state.js +3 -0
  10. package/lib/formatters/html-template-message.html +1 -1
  11. package/lib/formatters/html-template-page.html +3 -1
  12. package/lib/formatters/html.js +2 -1
  13. package/lib/ignored-paths.js +1 -1
  14. package/lib/linter.js +43 -71
  15. package/lib/logging.js +2 -2
  16. package/lib/options.js +12 -0
  17. package/lib/rules/array-bracket-newline.js +19 -5
  18. package/lib/rules/block-spacing.js +1 -1
  19. package/lib/rules/callback-return.js +2 -1
  20. package/lib/rules/capitalized-comments.js +2 -1
  21. package/lib/rules/comma-style.js +3 -1
  22. package/lib/rules/dot-notation.js +56 -35
  23. package/lib/rules/generator-star-spacing.js +3 -3
  24. package/lib/rules/indent-legacy.js +5 -2
  25. package/lib/rules/indent.js +25 -19
  26. package/lib/rules/lines-around-comment.js +33 -4
  27. package/lib/rules/lines-between-class-members.js +91 -0
  28. package/lib/rules/max-len.js +2 -3
  29. package/lib/rules/multiline-comment-style.js +294 -0
  30. package/lib/rules/new-cap.js +2 -1
  31. package/lib/rules/newline-before-return.js +4 -2
  32. package/lib/rules/no-alert.js +7 -15
  33. package/lib/rules/no-catch-shadow.js +1 -1
  34. package/lib/rules/no-constant-condition.js +2 -2
  35. package/lib/rules/no-control-regex.js +2 -1
  36. package/lib/rules/no-else-return.js +43 -8
  37. package/lib/rules/no-extra-parens.js +6 -3
  38. package/lib/rules/no-lonely-if.js +2 -1
  39. package/lib/rules/no-loop-func.js +2 -3
  40. package/lib/rules/no-mixed-requires.js +8 -4
  41. package/lib/rules/no-restricted-imports.js +86 -17
  42. package/lib/rules/no-restricted-modules.js +84 -15
  43. package/lib/rules/no-trailing-spaces.js +1 -1
  44. package/lib/rules/no-unneeded-ternary.js +3 -1
  45. package/lib/rules/no-unused-labels.js +2 -1
  46. package/lib/rules/no-useless-computed-key.js +2 -1
  47. package/lib/rules/no-useless-escape.js +8 -1
  48. package/lib/rules/no-var.js +11 -0
  49. package/lib/rules/object-shorthand.js +6 -2
  50. package/lib/rules/operator-linebreak.js +3 -1
  51. package/lib/rules/padding-line-between-statements.js +2 -2
  52. package/lib/rules/require-jsdoc.js +11 -18
  53. package/lib/rules/sort-imports.js +6 -3
  54. package/lib/rules/space-unary-ops.js +6 -8
  55. package/lib/rules/valid-jsdoc.js +39 -33
  56. package/lib/testers/rule-tester.js +20 -6
  57. package/lib/util/apply-disable-directives.js +56 -27
  58. package/lib/util/node-event-generator.js +6 -20
  59. package/lib/util/safe-emitter.js +54 -0
  60. package/lib/util/source-code.js +73 -67
  61. package/messages/no-config-found.txt +1 -1
  62. package/package.json +3 -4
  63. package/lib/internal-rules/.eslintrc.yml +0 -3
  64. package/lib/internal-rules/internal-consistent-docs-description.js +0 -130
  65. package/lib/internal-rules/internal-no-invalid-meta.js +0 -188
@@ -0,0 +1,54 @@
1
+ /**
2
+ * @fileoverview A variant of EventEmitter which does not give listeners information about each other
3
+ * @author Teddy Katz
4
+ */
5
+
6
+ "use strict";
7
+
8
+ //------------------------------------------------------------------------------
9
+ // Typedefs
10
+ //------------------------------------------------------------------------------
11
+
12
+ /**
13
+ * An object describing an AST selector
14
+ * @typedef {Object} SafeEmitter
15
+ * @property {function(eventName: string, listenerFunc: Function): void} on Adds a listener for a given event name
16
+ * @property {function(eventName: string, arg1?: any, arg2?: any, arg3?: any)} emit Emits an event with a given name.
17
+ * This calls all the listeners that were listening for that name, with `arg1`, `arg2`, and `arg3` as arguments.
18
+ * @property {function(): string[]} eventNames Gets the list of event names that have registered listeners.
19
+ */
20
+
21
+ /**
22
+ * Creates an object which can listen for and emit events.
23
+ * This is similar to the EventEmitter API in Node's standard library, but it has a few differences.
24
+ * The goal is to allow multiple modules to attach arbitrary listeners to the same emitter, without
25
+ * letting the modules know about each other at all.
26
+ * 1. It has no special keys like `error` and `newListener`, which would allow modules to detect when
27
+ * another module throws an error or registers a listener.
28
+ * 2. It calls listener functions without any `this` value. (`EventEmitter` calls listeners with a
29
+ * `this` value of the emitter instance, which would give listeners access to other listeners.)
30
+ * 3. Events can be emitted with at most 3 arguments. (For example: when using `emitter.emit('foo', a, b, c)`,
31
+ * the arguments `a`, `b`, and `c` will be passed to the listener functions.)
32
+ * @returns {SafeEmitter} An emitter
33
+ */
34
+ module.exports = () => {
35
+ const listeners = Object.create(null);
36
+
37
+ return Object.freeze({
38
+ on(eventName, listener) {
39
+ if (eventName in listeners) {
40
+ listeners[eventName].push(listener);
41
+ } else {
42
+ listeners[eventName] = [listener];
43
+ }
44
+ },
45
+ emit(eventName, a, b, c) {
46
+ if (eventName in listeners) {
47
+ listeners[eventName].forEach(listener => listener(a, b, c));
48
+ }
49
+ },
50
+ eventNames() {
51
+ return Object.keys(listeners);
52
+ }
53
+ });
54
+ };
@@ -25,7 +25,6 @@ const TokenStore = require("../token-store"),
25
25
  * @private
26
26
  */
27
27
  function validate(ast) {
28
-
29
28
  if (!ast.tokens) {
30
29
  throw new Error("AST is missing the tokens array.");
31
30
  }
@@ -44,34 +43,9 @@ function validate(ast) {
44
43
  }
45
44
 
46
45
  /**
47
- * Finds a JSDoc comment node in an array of comment nodes.
48
- * @param {ASTNode[]} comments The array of comment nodes to search.
49
- * @param {int} line Line number to look around
50
- * @returns {ASTNode} The node if found, null if not.
51
- * @private
52
- */
53
- function findJSDocComment(comments, line) {
54
-
55
- if (comments) {
56
- for (let i = comments.length - 1; i >= 0; i--) {
57
- if (comments[i].type === "Block" && comments[i].value.charAt(0) === "*") {
58
-
59
- if (line - comments[i].loc.end.line <= 1) {
60
- return comments[i];
61
- }
62
- break;
63
-
64
- }
65
- }
66
- }
67
-
68
- return null;
69
- }
70
-
71
- /**
72
- * Check to see if its a ES6 export declaration
73
- * @param {ASTNode} astNode - any node
74
- * @returns {boolean} whether the given node represents a export declaration
46
+ * Check to see if its a ES6 export declaration.
47
+ * @param {ASTNode} astNode An AST node.
48
+ * @returns {boolean} whether the given node represents an export declaration.
75
49
  * @private
76
50
  */
77
51
  function looksLikeExport(astNode) {
@@ -80,10 +54,11 @@ function looksLikeExport(astNode) {
80
54
  }
81
55
 
82
56
  /**
83
- * Merges two sorted lists into a larger sorted list in O(n) time
84
- * @param {Token[]} tokens The list of tokens
85
- * @param {Token[]} comments The list of comments
86
- * @returns {Token[]} A sorted list of tokens and comments
57
+ * Merges two sorted lists into a larger sorted list in O(n) time.
58
+ * @param {Token[]} tokens The list of tokens.
59
+ * @param {Token[]} comments The list of comments.
60
+ * @returns {Token[]} A sorted list of tokens and comments.
61
+ * @private
87
62
  */
88
63
  function sortedMerge(tokens, comments) {
89
64
  const result = [];
@@ -182,9 +157,9 @@ class SourceCode extends TokenStore {
182
157
  }
183
158
 
184
159
  /**
185
- * Split the source code into multiple lines based on the line delimiters
186
- * @param {string} text Source code as a string
187
- * @returns {string[]} Array of source code lines
160
+ * Split the source code into multiple lines based on the line delimiters.
161
+ * @param {string} text Source code as a string.
162
+ * @returns {string[]} Array of source code lines.
188
163
  * @public
189
164
  */
190
165
  static splitLines(text) {
@@ -197,6 +172,7 @@ class SourceCode extends TokenStore {
197
172
  * @param {int=} beforeCount The number of characters before the node to retrieve.
198
173
  * @param {int=} afterCount The number of characters after the node to retrieve.
199
174
  * @returns {string} The text representing the AST node.
175
+ * @public
200
176
  */
201
177
  getText(node, beforeCount, afterCount) {
202
178
  if (node) {
@@ -209,6 +185,7 @@ class SourceCode extends TokenStore {
209
185
  /**
210
186
  * Gets the entire source text split into an array of lines.
211
187
  * @returns {Array} The source text as an array of lines.
188
+ * @public
212
189
  */
213
190
  getLines() {
214
191
  return this.lines;
@@ -217,6 +194,7 @@ class SourceCode extends TokenStore {
217
194
  /**
218
195
  * Retrieves an array containing all comments in the source code.
219
196
  * @returns {ASTNode[]} An array of comment nodes.
197
+ * @public
220
198
  */
221
199
  getAllComments() {
222
200
  return this.ast.comments;
@@ -225,7 +203,8 @@ class SourceCode extends TokenStore {
225
203
  /**
226
204
  * Gets all comments for the given node.
227
205
  * @param {ASTNode} node The AST node to get the comments for.
228
- * @returns {Object} The list of comments indexed by their position.
206
+ * @returns {Object} An object containing a leading and trailing array
207
+ * of comments indexed by their position.
229
208
  * @public
230
209
  */
231
210
  getComments(node) {
@@ -297,47 +276,68 @@ class SourceCode extends TokenStore {
297
276
  /**
298
277
  * Retrieves the JSDoc comment for a given node.
299
278
  * @param {ASTNode} node The AST node to get the comment for.
300
- * @returns {ASTNode} The Block comment node containing the JSDoc for the
301
- * given node or null if not found.
279
+ * @returns {Token|null} The Block comment token containing the JSDoc comment
280
+ * for the given node or null if not found.
302
281
  * @public
303
282
  */
304
283
  getJSDocComment(node) {
284
+
285
+ /**
286
+ * Checks for the presence of a JSDoc comment for the given node and returns it.
287
+ * @param {ASTNode} astNode The AST node to get the comment for.
288
+ * @returns {Token|null} The Block comment token containing the JSDoc comment
289
+ * for the given node or null if not found.
290
+ * @private
291
+ */
292
+ const findJSDocComment = astNode => {
293
+ const tokenBefore = this.getTokenBefore(astNode, { includeComments: true });
294
+
295
+ if (
296
+ tokenBefore &&
297
+ astUtils.isCommentToken(tokenBefore) &&
298
+ tokenBefore.type === "Block" &&
299
+ tokenBefore.value.charAt(0) === "*" &&
300
+ astNode.loc.start.line - tokenBefore.loc.end.line <= 1
301
+ ) {
302
+ return tokenBefore;
303
+ }
304
+
305
+ return null;
306
+ };
305
307
  let parent = node.parent;
306
- const leadingComments = this.getCommentsBefore(node);
307
308
 
308
309
  switch (node.type) {
309
310
  case "ClassDeclaration":
310
311
  case "FunctionDeclaration":
311
- if (looksLikeExport(parent)) {
312
- return findJSDocComment(this.getCommentsBefore(parent), parent.loc.start.line);
313
- }
314
- return findJSDocComment(leadingComments, node.loc.start.line);
312
+ return findJSDocComment(looksLikeExport(parent) ? parent : node);
315
313
 
316
314
  case "ClassExpression":
317
- return findJSDocComment(this.getCommentsBefore(parent.parent), parent.parent.loc.start.line);
315
+ return findJSDocComment(parent.parent);
318
316
 
319
317
  case "ArrowFunctionExpression":
320
318
  case "FunctionExpression":
321
319
  if (parent.type !== "CallExpression" && parent.type !== "NewExpression") {
322
- let parentLeadingComments = this.getCommentsBefore(parent);
323
-
324
- while (!parentLeadingComments.length && !/Function/.test(parent.type) && parent.type !== "MethodDefinition" && parent.type !== "Property") {
320
+ while (
321
+ !this.getCommentsBefore(parent).length &&
322
+ !/Function/.test(parent.type) &&
323
+ parent.type !== "MethodDefinition" &&
324
+ parent.type !== "Property"
325
+ ) {
325
326
  parent = parent.parent;
326
327
 
327
328
  if (!parent) {
328
329
  break;
329
330
  }
330
-
331
- parentLeadingComments = this.getCommentsBefore(parent);
332
331
  }
333
332
 
334
- return parent && parent.type !== "FunctionDeclaration" && parent.type !== "Program" ? findJSDocComment(parentLeadingComments, parent.loc.start.line) : null;
335
- } else if (leadingComments.length) {
336
- return findJSDocComment(leadingComments, node.loc.start.line);
333
+ if (parent && parent.type !== "FunctionDeclaration" && parent.type !== "Program") {
334
+ return findJSDocComment(parent);
335
+ }
337
336
  }
338
337
 
339
- // falls through
338
+ return findJSDocComment(node);
340
339
 
340
+ // falls through
341
341
  default:
342
342
  return null;
343
343
  }
@@ -347,15 +347,18 @@ class SourceCode extends TokenStore {
347
347
  * Gets the deepest node containing a range index.
348
348
  * @param {int} index Range index of the desired node.
349
349
  * @returns {ASTNode} The node if found or null if not found.
350
+ * @public
350
351
  */
351
352
  getNodeByRangeIndex(index) {
352
- let result = null;
353
+ let result = null,
354
+ resultParent = null;
353
355
  const traverser = new Traverser();
354
356
 
355
357
  traverser.traverse(this.ast, {
356
- enter(node) {
358
+ enter(node, parent) {
357
359
  if (node.range[0] <= index && index < node.range[1]) {
358
360
  result = node;
361
+ resultParent = parent;
359
362
  } else {
360
363
  this.skip();
361
364
  }
@@ -367,7 +370,7 @@ class SourceCode extends TokenStore {
367
370
  }
368
371
  });
369
372
 
370
- return result;
373
+ return result ? Object.assign({ parent: resultParent }, result) : null;
371
374
  }
372
375
 
373
376
  /**
@@ -378,6 +381,7 @@ class SourceCode extends TokenStore {
378
381
  * @param {Token} second The token to check before.
379
382
  * @returns {boolean} True if there is only space between tokens, false
380
383
  * if there is anything other than whitespace between tokens.
384
+ * @public
381
385
  */
382
386
  isSpaceBetweenTokens(first, second) {
383
387
  const text = this.text.slice(first.range[1], second.range[0]);
@@ -386,10 +390,11 @@ class SourceCode extends TokenStore {
386
390
  }
387
391
 
388
392
  /**
389
- * Converts a source text index into a (line, column) pair.
390
- * @param {number} index The index of a character in a file
391
- * @returns {Object} A {line, column} location object with a 0-indexed column
392
- */
393
+ * Converts a source text index into a (line, column) pair.
394
+ * @param {number} index The index of a character in a file
395
+ * @returns {Object} A {line, column} location object with a 0-indexed column
396
+ * @public
397
+ */
393
398
  getLocFromIndex(index) {
394
399
  if (typeof index !== "number") {
395
400
  throw new TypeError("Expected `index` to be a number.");
@@ -420,12 +425,13 @@ class SourceCode extends TokenStore {
420
425
  }
421
426
 
422
427
  /**
423
- * Converts a (line, column) pair into a range index.
424
- * @param {Object} loc A line/column location
425
- * @param {number} loc.line The line number of the location (1-indexed)
426
- * @param {number} loc.column The column number of the location (0-indexed)
427
- * @returns {number} The range index of the location in the file.
428
- */
428
+ * Converts a (line, column) pair into a range index.
429
+ * @param {Object} loc A line/column location
430
+ * @param {number} loc.line The line number of the location (1-indexed)
431
+ * @param {number} loc.column The column number of the location (0-indexed)
432
+ * @returns {number} The range index of the location in the file.
433
+ * @public
434
+ */
429
435
  getIndexFromLoc(loc) {
430
436
  if (typeof loc !== "object" || typeof loc.line !== "number" || typeof loc.column !== "number") {
431
437
  throw new TypeError("Expected `loc` to be an object with numeric `line` and `column` properties.");
@@ -2,6 +2,6 @@ ESLint couldn't find a configuration file. To set up a configuration file for th
2
2
 
3
3
  eslint --init
4
4
 
5
- ESLint looked for configuration files in <%= directory %> and its ancestors.
5
+ ESLint looked for configuration files in <%= directory %> and its ancestors. If it found none, it then looked in your home directory.
6
6
 
7
7
  If you think you already have a configuration file or if you need more help, please stop by the ESLint chat room: https://gitter.im/eslint/eslint
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint",
3
- "version": "4.7.1",
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",
@@ -32,7 +31,7 @@
32
31
  "messages"
33
32
  ],
34
33
  "repository": "eslint/eslint",
35
- "homepage": "http://eslint.org",
34
+ "homepage": "https://eslint.org",
36
35
  "bugs": "https://github.com/eslint/eslint/issues/",
37
36
  "dependencies": {
38
37
  "ajv": "^5.2.0",
@@ -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
- };