eslint 4.1.1 → 4.4.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.
Files changed (55) hide show
  1. package/CHANGELOG.md +106 -0
  2. package/bin/eslint.js +5 -4
  3. package/conf/category-list.json +2 -2
  4. package/conf/config-schema.js +3 -1
  5. package/conf/eslint-recommended.js +12 -14
  6. package/lib/cli-engine.js +4 -3
  7. package/lib/cli.js +12 -1
  8. package/lib/config/config-file.js +5 -5
  9. package/lib/config/config-initializer.js +123 -14
  10. package/lib/config/config-validator.js +43 -14
  11. package/lib/config/plugins.js +13 -1
  12. package/lib/linter.js +26 -15
  13. package/lib/rule-context.js +53 -41
  14. package/lib/rules/arrow-parens.js +5 -2
  15. package/lib/rules/comma-dangle.js +40 -40
  16. package/lib/rules/curly.js +1 -1
  17. package/lib/rules/dot-notation.js +9 -0
  18. package/lib/rules/getter-return.js +176 -0
  19. package/lib/rules/id-blacklist.js +7 -3
  20. package/lib/rules/id-match.js +8 -4
  21. package/lib/rules/indent-legacy.js +2 -2
  22. package/lib/rules/indent.js +354 -349
  23. package/lib/rules/key-spacing.js +2 -2
  24. package/lib/rules/multiline-ternary.js +8 -2
  25. package/lib/rules/no-cond-assign.js +7 -3
  26. package/lib/rules/no-constant-condition.js +62 -6
  27. package/lib/rules/no-debugger.js +6 -1
  28. package/lib/rules/no-else-return.js +1 -1
  29. package/lib/rules/no-extra-parens.js +24 -11
  30. package/lib/rules/no-inner-declarations.js +8 -4
  31. package/lib/rules/no-multi-spaces.js +53 -115
  32. package/lib/rules/no-regex-spaces.js +4 -4
  33. package/lib/rules/no-restricted-globals.js +50 -9
  34. package/lib/rules/no-restricted-properties.js +19 -11
  35. package/lib/rules/no-sync.js +15 -3
  36. package/lib/rules/no-tabs.js +8 -4
  37. package/lib/rules/no-underscore-dangle.js +28 -1
  38. package/lib/rules/object-curly-newline.js +18 -0
  39. package/lib/rules/object-curly-spacing.js +1 -1
  40. package/lib/rules/padded-blocks.js +2 -2
  41. package/lib/rules/padding-line-between-statements.js +1 -1
  42. package/lib/rules/prefer-destructuring.js +70 -32
  43. package/lib/rules/prefer-numeric-literals.js +36 -7
  44. package/lib/rules/prefer-reflect.js +8 -4
  45. package/lib/rules/prefer-template.js +2 -2
  46. package/lib/rules/space-infix-ops.js +1 -1
  47. package/lib/rules/spaced-comment.js +2 -2
  48. package/lib/rules/valid-jsdoc.js +15 -7
  49. package/lib/testers/rule-tester.js +23 -30
  50. package/lib/testers/test-parser.js +48 -0
  51. package/lib/util/ajv.js +29 -0
  52. package/lib/util/npm-util.js +9 -8
  53. package/lib/util/source-code-fixer.js +47 -19
  54. package/package.json +11 -7
  55. package/conf/json-schema-schema.json +0 -150
@@ -9,7 +9,8 @@
9
9
  // Requirements
10
10
  //------------------------------------------------------------------------------
11
11
 
12
- const schemaValidator = require("is-my-json-valid"),
12
+ const ajv = require("../util/ajv"),
13
+ lodash = require("lodash"),
13
14
  configSchema = require("../../conf/config-schema.js"),
14
15
  util = require("util");
15
16
 
@@ -20,6 +21,7 @@ const validators = {
20
21
  //------------------------------------------------------------------------------
21
22
  // Private
22
23
  //------------------------------------------------------------------------------
24
+ let validateSchema;
23
25
 
24
26
  /**
25
27
  * Gets a complete options schema for a rule.
@@ -79,7 +81,7 @@ function validateRuleSchema(id, localOptions, rulesContext) {
79
81
  const schema = getRuleOptionsSchema(id, rulesContext);
80
82
 
81
83
  if (!validators.rules[id] && schema) {
82
- validators.rules[id] = schemaValidator(schema, { verbose: true });
84
+ validators.rules[id] = ajv.compile(schema);
83
85
  }
84
86
 
85
87
  const validateRule = validators.rules[id];
@@ -87,7 +89,7 @@ function validateRuleSchema(id, localOptions, rulesContext) {
87
89
  if (validateRule) {
88
90
  validateRule(localOptions);
89
91
  if (validateRule.errors) {
90
- throw new Error(validateRule.errors.map(error => `\tValue "${error.value}" ${error.message}.\n`).join(""));
92
+ throw new Error(validateRule.errors.map(error => `\tValue "${error.data}" ${error.message}.\n`).join(""));
91
93
  }
92
94
  }
93
95
  }
@@ -158,22 +160,45 @@ function validateRules(rulesConfig, source, rulesContext) {
158
160
  * @returns {string} Formatted error message
159
161
  */
160
162
  function formatErrors(errors) {
161
-
162
163
  return errors.map(error => {
163
- if (error.message === "has additional properties") {
164
- return `Unexpected top-level property "${error.value.replace(/^data\./, "")}"`;
164
+ if (error.keyword === "additionalProperties") {
165
+ const formattedPropertyPath = error.dataPath.length ? `${error.dataPath.slice(1)}.${error.params.additionalProperty}` : error.params.additionalProperty;
166
+
167
+ return `Unexpected top-level property "${formattedPropertyPath}"`;
165
168
  }
166
- if (error.message === "is the wrong type") {
167
- const formattedField = error.field.replace(/^data\./, "");
168
- const formattedExpectedType = typeof error.type === "string" ? error.type : error.type.join("/");
169
- const formattedValue = JSON.stringify(error.value);
169
+ if (error.keyword === "type") {
170
+ const formattedField = error.dataPath.slice(1);
171
+ const formattedExpectedType = Array.isArray(error.schema) ? error.schema.join("/") : error.schema;
172
+ const formattedValue = JSON.stringify(error.data);
170
173
 
171
174
  return `Property "${formattedField}" is the wrong type (expected ${formattedExpectedType} but got \`${formattedValue}\`)`;
172
175
  }
173
- return `"${error.field.replace(/^(data\.)/, "")}" ${error.message}. Value: ${JSON.stringify(error.value)}`;
176
+
177
+ const field = error.dataPath[0] === "." ? error.dataPath.slice(1) : error.dataPath;
178
+
179
+ return `"${field}" ${error.message}. Value: ${JSON.stringify(error.data)}`;
174
180
  }).map(message => `\t- ${message}.\n`).join("");
175
181
  }
176
182
 
183
+ /**
184
+ * Emits a deprecation warning containing a given filepath. A new deprecation warning is emitted
185
+ * for each unique file path, but repeated invocations with the same file path have no effect.
186
+ * No warnings are emitted if the `--no-deprecation` or `--no-warnings` Node runtime flags are active.
187
+ * @param {string} source The name of the configuration source to report the warning for.
188
+ * @returns {void}
189
+ */
190
+ const emitEcmaFeaturesWarning = lodash.memoize(source => {
191
+
192
+ /*
193
+ * util.deprecate seems to be the only way to emit a warning in Node 4.x while respecting the --no-warnings flag.
194
+ * (In Node 6+, process.emitWarning could be used instead.)
195
+ */
196
+ util.deprecate(
197
+ () => {},
198
+ `[eslint] The 'ecmaFeatures' config file property is deprecated, and has no effect. (found in ${source})`
199
+ )();
200
+ });
201
+
177
202
  /**
178
203
  * Validates the top level properties of the config object.
179
204
  * @param {Object} config The config object to validate.
@@ -181,10 +206,14 @@ function formatErrors(errors) {
181
206
  * @returns {void}
182
207
  */
183
208
  function validateConfigSchema(config, source) {
184
- const validator = schemaValidator(configSchema, { verbose: true });
209
+ validateSchema = validateSchema || ajv.compile(configSchema);
210
+
211
+ if (!validateSchema(config)) {
212
+ throw new Error(`ESLint configuration in ${source} is invalid:\n${formatErrors(validateSchema.errors)}`);
213
+ }
185
214
 
186
- if (!validator(config)) {
187
- throw new Error(`${source}:\n\tESLint configuration is invalid:\n${formatErrors(validator.errors)}`);
215
+ if (Object.prototype.hasOwnProperty.call(config, "ecmaFeatures")) {
216
+ emitEcmaFeaturesWarning(source);
188
217
  }
189
218
  }
190
219
 
@@ -43,7 +43,7 @@ class Plugins {
43
43
  * @returns {string} The name of the plugin without prefix.
44
44
  */
45
45
  static removePrefix(pluginName) {
46
- return pluginName.startsWith(PLUGIN_NAME_PREFIX) ? pluginName.substring(PLUGIN_NAME_PREFIX.length) : pluginName;
46
+ return pluginName.startsWith(PLUGIN_NAME_PREFIX) ? pluginName.slice(PLUGIN_NAME_PREFIX.length) : pluginName;
47
47
  }
48
48
 
49
49
  /**
@@ -156,8 +156,20 @@ class Plugins {
156
156
  * @param {string[]} pluginNames An array of plugins names.
157
157
  * @returns {void}
158
158
  * @throws {Error} If a plugin cannot be loaded.
159
+ * @throws {Error} If "plugins" in config is not an array
159
160
  */
160
161
  loadAll(pluginNames) {
162
+
163
+ // if "plugins" in config is not an array, throw an error so user can fix their config.
164
+ if (!Array.isArray(pluginNames)) {
165
+ const pluginNotArrayMessage = "ESLint configuration error: \"plugins\" value must be an array";
166
+
167
+ debug(`${pluginNotArrayMessage}: ${JSON.stringify(pluginNames)}`);
168
+
169
+ throw new Error(pluginNotArrayMessage);
170
+ }
171
+
172
+ // load each plugin by name
161
173
  pluginNames.forEach(this.load, this);
162
174
  }
163
175
  }
package/lib/linter.js CHANGED
@@ -70,8 +70,8 @@ function parseBooleanConfig(string, comment) {
70
70
  let value;
71
71
 
72
72
  if (pos !== -1) {
73
- value = name.substring(pos + 1, name.length);
74
- name = name.substring(0, pos);
73
+ value = name.slice(pos + 1);
74
+ name = name.slice(0, pos);
75
75
  }
76
76
 
77
77
  items[name] = {
@@ -338,7 +338,7 @@ function modifyConfigsFromComments(filename, ast, config, linterContext) {
338
338
  const match = /^(eslint(-\w+){0,3}|exported|globals?)(\s|$)/.exec(value);
339
339
 
340
340
  if (match) {
341
- value = value.substring(match.index + match[1].length);
341
+ value = value.slice(match.index + match[1].length);
342
342
 
343
343
  if (comment.type === "Block") {
344
344
  switch (match[1]) {
@@ -519,8 +519,11 @@ function createStubRule(message) {
519
519
  */
520
520
  function createRuleModule(context) {
521
521
  return {
522
- Program(node) {
523
- context.report(node, message);
522
+ Program() {
523
+ context.report({
524
+ loc: { line: 1, column: 0 },
525
+ message
526
+ });
524
527
  }
525
528
  };
526
529
  }
@@ -1197,6 +1200,7 @@ class Linter extends EventEmitter {
1197
1200
  * @param {string} options.filename The filename from which the text was read.
1198
1201
  * @param {boolean} options.allowInlineConfig Flag indicating if inline comments
1199
1202
  * should be allowed.
1203
+ * @param {boolean|Function} options.fix Determines whether fixes should be applied
1200
1204
  * @returns {Object} The result of the fix operation as returned from the
1201
1205
  * SourceCodeFixer.
1202
1206
  */
@@ -1205,6 +1209,8 @@ class Linter extends EventEmitter {
1205
1209
  fixedResult,
1206
1210
  fixed = false,
1207
1211
  passNumber = 0;
1212
+ const debugTextDescription = options && options.filename || `${text.slice(0, 10)}...`;
1213
+ const shouldFix = options && options.fix || true;
1208
1214
 
1209
1215
  /**
1210
1216
  * This loop continues until one of the following is true:
@@ -1218,11 +1224,11 @@ class Linter extends EventEmitter {
1218
1224
  do {
1219
1225
  passNumber++;
1220
1226
 
1221
- debug(`Linting code for ${options.filename} (pass ${passNumber})`);
1227
+ debug(`Linting code for ${debugTextDescription} (pass ${passNumber})`);
1222
1228
  messages = this.verify(text, config, options);
1223
1229
 
1224
- debug(`Generating fixed text for ${options.filename} (pass ${passNumber})`);
1225
- fixedResult = SourceCodeFixer.applyFixes(this.getSourceCode(), messages);
1230
+ debug(`Generating fixed text for ${debugTextDescription} (pass ${passNumber})`);
1231
+ fixedResult = SourceCodeFixer.applyFixes(this.getSourceCode(), messages, shouldFix);
1226
1232
 
1227
1233
  // stop if there are any syntax errors.
1228
1234
  // 'fixedResult.output' is a empty string.
@@ -1285,13 +1291,18 @@ const externalMethods = {
1285
1291
  Object.keys(externalMethods).forEach(methodName => {
1286
1292
  const exMethodName = externalMethods[methodName];
1287
1293
 
1288
- // All functions expected to have less arguments than 5.
1289
- Linter.prototype[methodName] = function(a, b, c, d, e) {
1290
- if (this.sourceCode) {
1291
- return this.sourceCode[exMethodName](a, b, c, d, e);
1292
- }
1293
- return null;
1294
- };
1294
+ // Applies the SourceCode methods to the Linter prototype
1295
+ Object.defineProperty(Linter.prototype, methodName, {
1296
+ value() {
1297
+ if (this.sourceCode) {
1298
+ return this.sourceCode[exMethodName].apply(this.sourceCode, arguments);
1299
+ }
1300
+ return null;
1301
+ },
1302
+ configurable: true,
1303
+ writable: true,
1304
+ enumerable: false
1305
+ });
1295
1306
  });
1296
1307
 
1297
1308
  module.exports = Linter;
@@ -20,6 +20,7 @@ const PASSTHROUGHS = [
20
20
  "getDeclaredVariables",
21
21
  "getFilename",
22
22
  "getScope",
23
+ "getSourceCode",
23
24
  "markVariableAsUsed",
24
25
 
25
26
  // DEPRECATED
@@ -58,7 +59,7 @@ const PASSTHROUGHS = [
58
59
  */
59
60
 
60
61
  //------------------------------------------------------------------------------
61
- // Rule Definition
62
+ // Module Definition
62
63
  //------------------------------------------------------------------------------
63
64
 
64
65
  /**
@@ -132,13 +133,13 @@ function getFix(descriptor, sourceCode) {
132
133
 
133
134
  /**
134
135
  * Rule context class
135
- * Acts as an abstraction layer between rules and the main eslint object.
136
+ * Acts as an abstraction layer between rules and the main linter object.
136
137
  */
137
138
  class RuleContext {
138
139
 
139
140
  /**
140
141
  * @param {string} ruleId The ID of the rule using this object.
141
- * @param {eslint} eslint The eslint object.
142
+ * @param {Linter} linter The linter object.
142
143
  * @param {number} severity The configured severity level of the rule.
143
144
  * @param {Array} options The configuration information to be added to the rule.
144
145
  * @param {Object} settings The configuration settings passed from the config file.
@@ -147,7 +148,7 @@ class RuleContext {
147
148
  * @param {Object} meta The metadata of the rule
148
149
  * @param {Object} parserServices The parser services for the rule.
149
150
  */
150
- constructor(ruleId, eslint, severity, options, settings, parserOptions, parserPath, meta, parserServices) {
151
+ constructor(ruleId, linter, severity, options, settings, parserOptions, parserPath, meta, parserServices) {
151
152
 
152
153
  // public.
153
154
  this.id = ruleId;
@@ -161,22 +162,14 @@ class RuleContext {
161
162
  this.parserServices = Object.freeze(Object.assign({}, parserServices));
162
163
 
163
164
  // private.
164
- this.eslint = eslint;
165
- this.severity = severity;
165
+ this._linter = linter;
166
+ this._severity = severity;
166
167
 
167
168
  Object.freeze(this);
168
169
  }
169
170
 
170
171
  /**
171
- * Passthrough to eslint.getSourceCode().
172
- * @returns {SourceCode} The SourceCode object for the code.
173
- */
174
- getSourceCode() {
175
- return this.eslint.getSourceCode();
176
- }
177
-
178
- /**
179
- * Passthrough to eslint.report() that automatically assigns the rule ID and severity.
172
+ * Passthrough to Linter#report() that automatically assigns the rule ID and severity.
180
173
  * @param {ASTNode|MessageDescriptor} nodeOrDescriptor The AST node related to the message or a message
181
174
  * descriptor.
182
175
  * @param {Object=} location The location of the error.
@@ -192,38 +185,57 @@ class RuleContext {
192
185
  const descriptor = nodeOrDescriptor;
193
186
  const fix = getFix(descriptor, this.getSourceCode());
194
187
 
195
- this.eslint.report(
188
+ if (descriptor.loc) {
189
+ this._linter.report(
190
+ this.id,
191
+ this._severity,
192
+ descriptor.node,
193
+ descriptor.loc,
194
+ descriptor.message,
195
+ descriptor.data,
196
+ fix,
197
+ this.meta
198
+ );
199
+ } else {
200
+ this._linter.report(
201
+ this.id,
202
+ this._severity,
203
+ descriptor.node,
204
+
205
+ /* loc not provided */
206
+ descriptor.message,
207
+ descriptor.data,
208
+ fix,
209
+ this.meta
210
+ );
211
+ }
212
+
213
+ } else {
214
+
215
+ // old style call
216
+ this._linter.report(
196
217
  this.id,
197
- this.severity,
198
- descriptor.node,
199
- descriptor.loc || descriptor.node.loc.start,
200
- descriptor.message,
201
- descriptor.data,
202
- fix,
218
+ this._severity,
219
+ nodeOrDescriptor,
220
+ location,
221
+ message,
222
+ opts,
203
223
  this.meta
204
224
  );
205
-
206
- return;
207
225
  }
208
-
209
- // old style call
210
- this.eslint.report(
211
- this.id,
212
- this.severity,
213
- nodeOrDescriptor,
214
- location,
215
- message,
216
- opts,
217
- this.meta
218
- );
219
226
  }
220
227
  }
221
228
 
222
- // Copy over passthrough methods. All functions will have 5 or fewer parameters.
223
- PASSTHROUGHS.forEach(function(name) {
224
- this[name] = function(a, b, c, d, e) {
225
- return this.eslint[name](a, b, c, d, e);
226
- };
227
- }, RuleContext.prototype);
229
+ // Copy over passthrough methods.
230
+ PASSTHROUGHS.forEach(name => {
231
+ Object.defineProperty(RuleContext.prototype, name, {
232
+ value() {
233
+ return this._linter[name].apply(this._linter, arguments);
234
+ },
235
+ configurable: true,
236
+ writable: true,
237
+ enumerable: false
238
+ });
239
+ });
228
240
 
229
241
  module.exports = RuleContext;
@@ -66,9 +66,12 @@ module.exports = {
66
66
  */
67
67
  function fixParamsWithParenthesis(fixer) {
68
68
  const paramToken = sourceCode.getTokenAfter(firstTokenOfParam);
69
- const closingParenToken = sourceCode.getTokenAfter(paramToken);
69
+
70
+ // ES8 allows Trailing commas in function parameter lists and calls
71
+ // https://github.com/eslint/eslint/issues/8834
72
+ const closingParenToken = sourceCode.getTokenAfter(paramToken, astUtils.isClosingParenToken);
70
73
  const asyncToken = isAsync ? sourceCode.getTokenBefore(firstTokenOfParam) : null;
71
- const shouldAddSpaceForAsync = asyncToken && (asyncToken.end === firstTokenOfParam.start);
74
+ const shouldAddSpaceForAsync = asyncToken && (asyncToken.range[1] === firstTokenOfParam.range[0]);
72
75
 
73
76
  return fixer.replaceTextRange([
74
77
  firstTokenOfParam.range[0],
@@ -81,49 +81,49 @@ module.exports = {
81
81
  category: "Stylistic Issues",
82
82
  recommended: false
83
83
  },
84
-
85
84
  fixable: "code",
86
-
87
- schema: [
88
- {
89
- defs: {
90
- value: {
91
- enum: [
92
- "always",
93
- "always-multiline",
94
- "only-multiline",
95
- "never"
96
- ]
97
- },
98
- valueWithIgnore: {
99
- anyOf: [
100
- {
101
- $ref: "#/defs/value"
102
- },
103
- {
104
- enum: ["ignore"]
105
- }
106
- ]
107
- }
85
+ schema: {
86
+ definitions: {
87
+ value: {
88
+ enum: [
89
+ "always-multiline",
90
+ "always",
91
+ "never",
92
+ "only-multiline"
93
+ ]
108
94
  },
109
- anyOf: [
110
- {
111
- $ref: "#/defs/value"
112
- },
113
- {
114
- type: "object",
115
- properties: {
116
- arrays: { $refs: "#/defs/valueWithIgnore" },
117
- objects: { $refs: "#/defs/valueWithIgnore" },
118
- imports: { $refs: "#/defs/valueWithIgnore" },
119
- exports: { $refs: "#/defs/valueWithIgnore" },
120
- functions: { $refs: "#/defs/valueWithIgnore" }
95
+ valueWithIgnore: {
96
+ enum: [
97
+ "always-multiline",
98
+ "always",
99
+ "ignore",
100
+ "never",
101
+ "only-multiline"
102
+ ]
103
+ }
104
+ },
105
+ type: "array",
106
+ items: [
107
+ {
108
+ oneOf: [
109
+ {
110
+ $ref: "#/definitions/value"
121
111
  },
122
- additionalProperties: false
123
- }
124
- ]
125
- }
126
- ]
112
+ {
113
+ type: "object",
114
+ properties: {
115
+ arrays: { $ref: "#/definitions/valueWithIgnore" },
116
+ objects: { $ref: "#/definitions/valueWithIgnore" },
117
+ imports: { $ref: "#/definitions/valueWithIgnore" },
118
+ exports: { $ref: "#/definitions/valueWithIgnore" },
119
+ functions: { $ref: "#/definitions/valueWithIgnore" }
120
+ },
121
+ additionalProperties: false
122
+ }
123
+ ]
124
+ }
125
+ ]
126
+ }
127
127
  },
128
128
 
129
129
  create(context) {
@@ -238,7 +238,7 @@ module.exports = {
238
238
  // `do while` expressions sometimes need a space to be inserted after `do`.
239
239
  // e.g. `do{foo()} while (bar)` should be corrected to `do foo() while (bar)`
240
240
  const needsPrecedingSpace = node.type === "DoWhileStatement" &&
241
- sourceCode.getTokenBefore(bodyNode).end === bodyNode.start &&
241
+ sourceCode.getTokenBefore(bodyNode).range[1] === bodyNode.range[0] &&
242
242
  !astUtils.canTokensBeAdjacent("do", sourceCode.getFirstToken(bodyNode, { skip: 1 }));
243
243
 
244
244
  const openingBracket = sourceCode.getFirstToken(bodyNode);
@@ -116,6 +116,15 @@ module.exports = {
116
116
  return null;
117
117
  }
118
118
 
119
+ if (node.object.type === "Identifier" && node.object.name === "let") {
120
+
121
+ /*
122
+ * A statement that starts with `let[` is parsed as a destructuring variable declaration, not
123
+ * a MemberExpression.
124
+ */
125
+ return null;
126
+ }
127
+
119
128
  return fixer.replaceTextRange(
120
129
  [dot.range[0], node.property.range[1]],
121
130
  `[${textAfterDot}"${node.property.name}"]`