eslint 4.11.0 → 4.13.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 CHANGED
@@ -1,3 +1,37 @@
1
+ v4.13.1 - December 11, 2017
2
+
3
+ * b72dc83 Fix: eol-last allow empty-string to always pass (refs #9534) (#9696) (Kevin Partington)
4
+ * d80aa7c Fix: camelcase destructure leading/trailing underscore (fixes #9700) (#9701) (Kevin Partington)
5
+ * d49d9d0 Docs: Add missing period to the README (#9702) (Kevin Partington)
6
+ * 4564fe0 Chore: no-invalid-meta crash if no export assignment (refs #9534) (#9698) (Kevin Partington)
7
+
8
+ v4.13.0 - December 8, 2017
9
+
10
+ * 256481b Update: update handling of destructuring in camelcase (fixes #8511) (#9468) (Erin)
11
+ * d067ae1 Docs: Don’t use undocumented array-style configuration for max-len (#9690) (Jed Fox)
12
+ * 1ad3091 Chore: fix test-suite to work with node master (#9688) (Myles Borins)
13
+ * cdb1488 Docs: Adds an example with try/catch. (#9672) (Jaap Taal)
14
+
15
+ v4.12.1 - November 30, 2017
16
+
17
+ * 1e362a0 Revert "Fix: Use XML 1.1 on XML formatters (fixes #9607) (#9608)" (#9667) (Kevin Partington)
18
+
19
+ v4.12.0 - November 25, 2017
20
+
21
+ * 76dab18 Upgrade: doctrine@^2.0.2 (#9656) (Kevin Partington)
22
+ * 28c9c8e New: add a Linter#defineParser function (#9321) (Ives van Hoorne)
23
+ * 5619910 Update: Add autofix for `sort-vars` (#9496) (Trevin Hofmann)
24
+ * 71eedbf Update: add `beforeStatementContinuationChars` to semi (fixes #9521) (#9594) (Toru Nagashima)
25
+ * 4118f14 New: Adds implicit-arrow-linebreak rule (refs #9510) (#9629) (Sharmila Jesupaul)
26
+ * 208fb0f Fix: Use XML 1.1 on XML formatters (fixes #9607) (#9608) (Daniel Reigada)
27
+ * 6e04f14 Upgrade: `globals` to 11.0.1 (fixes #9614) (#9632) (Toru Nagashima)
28
+ * e13d439 Fix: space-in-parens crash (#9655) (Toru Nagashima)
29
+ * 92171cc Docs: Updating migration guide for single-line disable (#9385) (Justin Helmer)
30
+ * f39ffe7 Docs: remove extra punctuation from readme (#9640) (Teddy Katz)
31
+ * a015234 Fix: prefer-destructuring false positive on "super" (fixes #9625) (#9626) (Kei Ito)
32
+ * 0cf081e Update: add importNames option to no-restricted-imports (#9506) (Benjamin R Gibson)
33
+ * 332c214 Docs: Add @platinumazure to TSC (#9618) (Ilya Volodin)
34
+
1
35
  v4.11.0 - November 10, 2017
2
36
 
3
37
  * d4557a6 Docs: disallow use of the comma operator using no-restricted-syntax (#9585) (薛定谔的猫)
package/README.md CHANGED
@@ -116,6 +116,7 @@ These folks keep the project moving and are resources for help.
116
116
  * Alberto Rodríguez ([@alberto](https://github.com/alberto))
117
117
  * Kai Cataldo ([@kaicataldo](https://github.com/kaicataldo))
118
118
  * Teddy Katz ([@not-an-aardvark](https://github.com/not-an-aardvark))
119
+ * Kevin Partington ([@platinumazure](https://github.com/platinumazure))
119
120
 
120
121
  ### Development Team
121
122
 
@@ -130,7 +131,6 @@ These folks keep the project moving and are resources for help.
130
131
  * Henry Zhu ([@hzoo](https://github.com/hzoo))
131
132
  * Marat Dulin ([@mdevils](https://github.com/mdevils))
132
133
  * Alexej Yaroshevich ([@zxqfox](https://github.com/zxqfox))
133
- * Kevin Partington ([@platinumazure](https://github.com/platinumazure))
134
134
  * Vitor Balocco ([@vitorbal](https://github.com/vitorbal))
135
135
  * James Henry ([@JamesHenry](https://github.com/JamesHenry))
136
136
  * Reyad Attiyat ([@soda0289](https://github.com/soda0289))
@@ -225,7 +225,7 @@ In all cases, make sure your plugins' peerDependencies have been installed as we
225
225
 
226
226
  ### Does ESLint support JSX?
227
227
 
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.
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.
229
229
 
230
230
  ### What about ECMAScript 6 support?
231
231
 
@@ -239,7 +239,7 @@ Once a language feature has been adopted into the ECMAScript standard (stage 4 a
239
239
 
240
240
  ### Where to ask for help?
241
241
 
242
- Join our [Mailing List](https://groups.google.com/group/eslint) or [Chatroom](https://gitter.im/eslint/eslint)
242
+ Join our [Mailing List](https://groups.google.com/group/eslint) or [Chatroom](https://gitter.im/eslint/eslint).
243
243
 
244
244
 
245
245
  [npm-image]: https://img.shields.io/npm/v/eslint.svg?style=flat-square
@@ -17,10 +17,126 @@ const globals = require("globals");
17
17
  module.exports = {
18
18
  builtin: globals.es5,
19
19
  browser: {
20
- globals: globals.browser
20
+
21
+ /*
22
+ * For backward compatibility.
23
+ * Remove those on the next major release.
24
+ */
25
+ globals: Object.assign(
26
+ {
27
+ AutocompleteErrorEvent: false,
28
+ CDATASection: false,
29
+ ClientRect: false,
30
+ ClientRectList: false,
31
+ CSSAnimation: false,
32
+ CSSTransition: false,
33
+ CSSUnknownRule: false,
34
+ CSSViewportRule: false,
35
+ Debug: false,
36
+ DocumentTimeline: false,
37
+ DOMSettableTokenList: false,
38
+ ElementTimeControl: false,
39
+ FederatedCredential: false,
40
+ FileError: false,
41
+ HTMLAppletElement: false,
42
+ HTMLBlockquoteElement: false,
43
+ HTMLIsIndexElement: false,
44
+ HTMLKeygenElement: false,
45
+ HTMLLayerElement: false,
46
+ IDBEnvironment: false,
47
+ InputMethodContext: false,
48
+ MediaKeyError: false,
49
+ MediaKeyEvent: false,
50
+ MediaKeys: false,
51
+ opera: false,
52
+ PasswordCredential: false,
53
+ ReadableByteStream: false,
54
+ SharedKeyframeList: false,
55
+ showModalDialog: false,
56
+ SiteBoundCredential: false,
57
+ SVGAltGlyphDefElement: false,
58
+ SVGAltGlyphElement: false,
59
+ SVGAltGlyphItemElement: false,
60
+ SVGAnimateColorElement: false,
61
+ SVGAnimatedPathData: false,
62
+ SVGAnimatedPoints: false,
63
+ SVGColor: false,
64
+ SVGColorProfileElement: false,
65
+ SVGColorProfileRule: false,
66
+ SVGCSSRule: false,
67
+ SVGCursorElement: false,
68
+ SVGDocument: false,
69
+ SVGElementInstance: false,
70
+ SVGElementInstanceList: false,
71
+ SVGEvent: false,
72
+ SVGExternalResourcesRequired: false,
73
+ SVGFilterPrimitiveStandardAttributes: false,
74
+ SVGFitToViewBox: false,
75
+ SVGFontElement: false,
76
+ SVGFontFaceElement: false,
77
+ SVGFontFaceFormatElement: false,
78
+ SVGFontFaceNameElement: false,
79
+ SVGFontFaceSrcElement: false,
80
+ SVGFontFaceUriElement: false,
81
+ SVGGlyphElement: false,
82
+ SVGGlyphRefElement: false,
83
+ SVGHKernElement: false,
84
+ SVGICCColor: false,
85
+ SVGLangSpace: false,
86
+ SVGLocatable: false,
87
+ SVGMissingGlyphElement: false,
88
+ SVGPaint: false,
89
+ SVGPathSeg: false,
90
+ SVGPathSegArcAbs: false,
91
+ SVGPathSegArcRel: false,
92
+ SVGPathSegClosePath: false,
93
+ SVGPathSegCurvetoCubicAbs: false,
94
+ SVGPathSegCurvetoCubicRel: false,
95
+ SVGPathSegCurvetoCubicSmoothAbs: false,
96
+ SVGPathSegCurvetoCubicSmoothRel: false,
97
+ SVGPathSegCurvetoQuadraticAbs: false,
98
+ SVGPathSegCurvetoQuadraticRel: false,
99
+ SVGPathSegCurvetoQuadraticSmoothAbs: false,
100
+ SVGPathSegCurvetoQuadraticSmoothRel: false,
101
+ SVGPathSegLinetoAbs: false,
102
+ SVGPathSegLinetoHorizontalAbs: false,
103
+ SVGPathSegLinetoHorizontalRel: false,
104
+ SVGPathSegLinetoRel: false,
105
+ SVGPathSegLinetoVerticalAbs: false,
106
+ SVGPathSegLinetoVerticalRel: false,
107
+ SVGPathSegList: false,
108
+ SVGPathSegMovetoAbs: false,
109
+ SVGPathSegMovetoRel: false,
110
+ SVGRenderingIntent: false,
111
+ SVGStylable: false,
112
+ SVGTests: false,
113
+ SVGTransformable: false,
114
+ SVGTRefElement: false,
115
+ SVGURIReference: false,
116
+ SVGViewSpec: false,
117
+ SVGVKernElement: false,
118
+ SVGZoomAndPan: false,
119
+ SVGZoomEvent: false,
120
+ TimeEvent: false,
121
+ XDomainRequest: false,
122
+ XMLHttpRequestProgressEvent: false,
123
+ XPathException: false,
124
+ XPathNamespace: false,
125
+ XPathNSResolver: false
126
+ },
127
+ globals.browser
128
+ )
21
129
  },
22
130
  node: {
23
- globals: globals.node,
131
+
132
+ /*
133
+ * For backward compatibility.
134
+ * Remove those on the next major release.
135
+ */
136
+ globals: Object.assign(
137
+ { arguments: false, GLOBAL: false, root: false },
138
+ globals.node
139
+ ),
24
140
  parserOptions: {
25
141
  ecmaFeatures: {
26
142
  globalReturn: true
@@ -51,7 +167,15 @@ module.exports = {
51
167
  globals: globals.jasmine
52
168
  },
53
169
  jest: {
54
- globals: globals.jest
170
+
171
+ /*
172
+ * For backward compatibility.
173
+ * Remove those on the next major release.
174
+ */
175
+ globals: Object.assign(
176
+ { check: false, gen: false },
177
+ globals.jest
178
+ )
55
179
  },
56
180
  phantomjs: {
57
181
  globals: globals.phantomjs
@@ -96,7 +220,7 @@ module.exports = {
96
220
  globals: globals.webextensions
97
221
  },
98
222
  es6: {
99
- globals: globals.es6,
223
+ globals: globals.es2015,
100
224
  parserOptions: {
101
225
  ecmaVersion: 6
102
226
  }
@@ -53,6 +53,7 @@ module.exports = {
53
53
  "id-blacklist": "off",
54
54
  "id-length": "off",
55
55
  "id-match": "off",
56
+ "implicit-arrow-linebreak": "off",
56
57
  indent: "off",
57
58
  "indent-legacy": "off",
58
59
  "init-declarations": "off",
package/lib/linter.js CHANGED
@@ -507,13 +507,13 @@ function getRuleOptions(ruleConfig) {
507
507
  * as possible
508
508
  * @param {string} text The text to parse.
509
509
  * @param {Object} providedParserOptions Options to pass to the parser
510
- * @param {string} parserName The name of the parser
510
+ * @param {Object} parser The parser module
511
511
  * @param {string} filePath The path to the file being parsed.
512
512
  * @returns {{success: false, error: Problem}|{success: true,ast: ASTNode, services: Object}}
513
513
  * An object containing the AST and parser services if parsing was successful, or the error if parsing failed
514
514
  * @private
515
515
  */
516
- function parse(text, providedParserOptions, parserName, filePath) {
516
+ function parse(text, providedParserOptions, parser, filePath) {
517
517
 
518
518
  const parserOptions = Object.assign({}, providedParserOptions, {
519
519
  loc: true,
@@ -524,25 +524,6 @@ function parse(text, providedParserOptions, parserName, filePath) {
524
524
  filePath
525
525
  });
526
526
 
527
- let parser;
528
-
529
- try {
530
- parser = require(parserName);
531
- } catch (ex) {
532
- return {
533
- success: false,
534
- error: {
535
- ruleId: null,
536
- fatal: true,
537
- severity: 2,
538
- source: null,
539
- message: ex.message,
540
- line: 0,
541
- column: 0
542
- }
543
- };
544
- }
545
-
546
527
  /*
547
528
  * Check for parsing errors first. If there's a parsing error, nothing
548
529
  * else can happen. However, a parsing error does not throw an error
@@ -706,6 +687,7 @@ module.exports = class Linter {
706
687
  this.version = pkg.version;
707
688
 
708
689
  this.rules = new Rules();
690
+ this._parsers = new Map();
709
691
  this.environments = new Environments();
710
692
  }
711
693
 
@@ -785,10 +767,25 @@ module.exports = class Linter {
785
767
  return [];
786
768
  }
787
769
 
770
+ let parser;
771
+
772
+ try {
773
+ parser = this._parsers.get(config.parser) || require(config.parser);
774
+ } catch (ex) {
775
+ return [{
776
+ ruleId: null,
777
+ fatal: true,
778
+ severity: 2,
779
+ source: null,
780
+ message: ex.message,
781
+ line: 0,
782
+ column: 0
783
+ }];
784
+ }
788
785
  const parseResult = parse(
789
786
  stripUnicodeBOM(text).replace(astUtils.SHEBANG_MATCHER, (match, captured) => `//${captured}`),
790
787
  config.parserOptions,
791
- config.parser,
788
+ parser,
792
789
  filename
793
790
  );
794
791
 
@@ -1036,6 +1033,16 @@ module.exports = class Linter {
1036
1033
  return this.rules.getAllLoadedRules();
1037
1034
  }
1038
1035
 
1036
+ /**
1037
+ * Define a new parser module
1038
+ * @param {any} parserId Name of the parser
1039
+ * @param {any} parserModule The parser object
1040
+ * @returns {void}
1041
+ */
1042
+ defineParser(parserId, parserModule) {
1043
+ this._parsers.set(parserId, parserModule);
1044
+ }
1045
+
1039
1046
  /**
1040
1047
  * Performs multiple autofix passes over the text until as many fixes as possible
1041
1048
  * have been applied.
@@ -92,34 +92,45 @@ module.exports = {
92
92
  }
93
93
 
94
94
  // Always report underscored object names
95
- if (node.parent.object.type === "Identifier" &&
96
- node.parent.object.name === node.name &&
97
- isUnderscored(name)) {
95
+ if (node.parent.object.type === "Identifier" && node.parent.object.name === node.name && isUnderscored(name)) {
98
96
  report(node);
99
97
 
100
98
  // Report AssignmentExpressions only if they are the left side of the assignment
101
- } else if (effectiveParent.type === "AssignmentExpression" &&
102
- isUnderscored(name) &&
103
- (effectiveParent.right.type !== "MemberExpression" ||
104
- effectiveParent.left.type === "MemberExpression" &&
105
- effectiveParent.left.property.name === node.name)) {
99
+ } else if (effectiveParent.type === "AssignmentExpression" && isUnderscored(name) && (effectiveParent.right.type !== "MemberExpression" || effectiveParent.left.type === "MemberExpression" && effectiveParent.left.property.name === node.name)) {
106
100
  report(node);
107
101
  }
108
102
 
109
- // Properties have their own rules
110
- } else if (node.parent.type === "Property") {
103
+ /*
104
+ * Properties have their own rules, and
105
+ * AssignmentPattern nodes can be treated like Properties:
106
+ * e.g.: const { no_camelcased = false } = bar;
107
+ */
108
+ } else if (node.parent.type === "Property" || node.parent.type === "AssignmentPattern") {
111
109
 
112
- // "never" check properties
113
- if (properties === "never") {
114
- return;
110
+ if (node.parent.parent && node.parent.parent.type === "ObjectPattern") {
111
+
112
+ if (node.parent.shorthand && node.parent.value.left && isUnderscored(name)) {
113
+
114
+ report(node);
115
+ }
116
+
117
+ // prevent checking righthand side of destructured object
118
+ if (node.parent.key === node && node.parent.value !== node) {
119
+ return;
120
+ }
121
+
122
+ if (node.parent.value.name && isUnderscored(name)) {
123
+ report(node);
124
+ }
115
125
  }
116
126
 
117
- if (node.parent.parent && node.parent.parent.type === "ObjectPattern" &&
118
- node.parent.key === node && node.parent.value !== node) {
127
+ // "never" check properties
128
+ if (properties === "never") {
119
129
  return;
120
130
  }
121
131
 
122
- if (isUnderscored(name) && !ALLOWED_PARENT_TYPES.has(effectiveParent.type)) {
132
+ // don't check right hand side of AssignmentExpression to prevent duplicate warnings
133
+ if (isUnderscored(name) && !ALLOWED_PARENT_TYPES.has(effectiveParent.type) && !(node.parent.right === node)) {
123
134
  report(node);
124
135
  }
125
136
 
@@ -46,6 +46,14 @@ module.exports = {
46
46
  CRLF = `\r${LF}`,
47
47
  endsWithNewline = lodash.endsWith(src, LF);
48
48
 
49
+ /*
50
+ * Empty source is always valid: No content in file so we don't
51
+ * need to lint for a newline on the last line of content.
52
+ */
53
+ if (!src.length) {
54
+ return;
55
+ }
56
+
49
57
  let mode = context.options[0] || "always",
50
58
  appendCRLF = false;
51
59
 
@@ -0,0 +1,86 @@
1
+ /**
2
+ * @fileoverview enforce the location of arrow function bodies
3
+ * @author Sharmila Jesupaul
4
+ */
5
+ "use strict";
6
+
7
+ //------------------------------------------------------------------------------
8
+ // Rule Definition
9
+ //------------------------------------------------------------------------------
10
+ module.exports = {
11
+ meta: {
12
+ docs: {
13
+ description: "enforce the location of arrow function bodies",
14
+ category: "Stylistic Issues",
15
+ recommended: false
16
+ },
17
+ fixable: "whitespace",
18
+ schema: [
19
+ {
20
+ enum: ["beside", "below"]
21
+ }
22
+ ]
23
+ },
24
+
25
+ create(context) {
26
+ const sourceCode = context.getSourceCode();
27
+
28
+ //----------------------------------------------------------------------
29
+ // Helpers
30
+ //----------------------------------------------------------------------
31
+ /**
32
+ * Gets the applicable preference for a particular keyword
33
+ * @returns {string} The applicable option for the keyword, e.g. 'beside'
34
+ */
35
+ function getOption() {
36
+ return context.options[0] || "beside";
37
+ }
38
+
39
+ /**
40
+ * Validates the location of an arrow function body
41
+ * @param {ASTNode} node The arrow function body
42
+ * @param {string} keywordName The applicable keyword name for the arrow function body
43
+ * @returns {void}
44
+ */
45
+ function validateExpression(node) {
46
+ const option = getOption();
47
+
48
+ let tokenBefore = sourceCode.getTokenBefore(node.body);
49
+ const hasParens = tokenBefore.value === "(";
50
+
51
+ if (node.type === "BlockStatement") {
52
+ return;
53
+ }
54
+
55
+ let fixerTarget = node.body;
56
+
57
+ if (hasParens) {
58
+
59
+ // Gets the first token before the function body that is not an open paren
60
+ tokenBefore = sourceCode.getTokenBefore(node.body, token => token.value !== "(");
61
+ fixerTarget = sourceCode.getTokenAfter(tokenBefore);
62
+ }
63
+
64
+ if (tokenBefore.loc.end.line === fixerTarget.loc.start.line && option === "below") {
65
+ context.report({
66
+ node: fixerTarget,
67
+ message: "Expected a linebreak before this expression.",
68
+ fix: fixer => fixer.insertTextBefore(fixerTarget, "\n")
69
+ });
70
+ } else if (tokenBefore.loc.end.line !== fixerTarget.loc.start.line && option === "beside") {
71
+ context.report({
72
+ node: fixerTarget,
73
+ message: "Expected no linebreak before this expression.",
74
+ fix: fixer => fixer.replaceTextRange([tokenBefore.range[1], fixerTarget.range[0]], " ")
75
+ });
76
+ }
77
+ }
78
+
79
+ //----------------------------------------------------------------------
80
+ // Public
81
+ //----------------------------------------------------------------------
82
+ return {
83
+ ArrowFunctionExpression: node => validateExpression(node)
84
+ };
85
+ }
86
+ };
@@ -8,8 +8,8 @@
8
8
  // Helpers
9
9
  //------------------------------------------------------------------------------
10
10
 
11
- const DEFAULT_MESSAGE_TEMPLATE = "'{{importName}}' import is restricted from being used.";
12
- const CUSTOM_MESSAGE_TEMPLATE = "'{{importName}}' import is restricted from being used. {{customMessage}}";
11
+ const DEFAULT_MESSAGE_TEMPLATE = "'{{importSource}}' import is restricted from being used.";
12
+ const CUSTOM_MESSAGE_TEMPLATE = "'{{importSource}}' import is restricted from being used. {{customMessage}}";
13
13
 
14
14
  //------------------------------------------------------------------------------
15
15
  // Rule Definition
@@ -35,6 +35,12 @@ const arrayOfStringsOrObjects = {
35
35
  message: {
36
36
  type: "string",
37
37
  minLength: 1
38
+ },
39
+ importNames: {
40
+ type: "array",
41
+ items: {
42
+ type: "string"
43
+ }
38
44
  }
39
45
  },
40
46
  additionalProperties: false,
@@ -81,11 +87,14 @@ module.exports = {
81
87
  const restrictedPaths = (isPathAndPatternsObject ? options[0].paths : context.options) || [];
82
88
  const restrictedPatterns = (isPathAndPatternsObject ? options[0].patterns : []) || [];
83
89
 
84
- const restrictedPathMessages = restrictedPaths.reduce((memo, importName) => {
85
- if (typeof importName === "string") {
86
- memo[importName] = null;
90
+ const restrictedPathMessages = restrictedPaths.reduce((memo, importSource) => {
91
+ if (typeof importSource === "string") {
92
+ memo[importSource] = { message: null };
87
93
  } else {
88
- memo[importName.name] = importName.message;
94
+ memo[importSource.name] = {
95
+ message: importSource.message,
96
+ importNames: importSource.importNames
97
+ };
89
98
  }
90
99
  return memo;
91
100
  }, {});
@@ -95,7 +104,16 @@ module.exports = {
95
104
  return {};
96
105
  }
97
106
 
98
- const ig = ignore().add(restrictedPatterns);
107
+ const restrictedPatternsMatcher = ignore().add(restrictedPatterns);
108
+
109
+ /**
110
+ * Checks to see if "*" is being used to import everything.
111
+ * @param {Set.<string>} importNames - Set of import names that are being imported
112
+ * @returns {boolean} whether everything is imported or not
113
+ */
114
+ function isEverythingImported(importNames) {
115
+ return importNames.has("*");
116
+ }
99
117
 
100
118
  /**
101
119
  * Report a restricted path.
@@ -104,8 +122,8 @@ module.exports = {
104
122
  * @private
105
123
  */
106
124
  function reportPath(node) {
107
- const importName = node.source.value.trim();
108
- const customMessage = restrictedPathMessages[importName];
125
+ const importSource = node.source.value.trim();
126
+ const customMessage = restrictedPathMessages[importSource] && restrictedPathMessages[importSource].message;
109
127
  const message = customMessage
110
128
  ? CUSTOM_MESSAGE_TEMPLATE
111
129
  : DEFAULT_MESSAGE_TEMPLATE;
@@ -114,39 +132,130 @@ module.exports = {
114
132
  node,
115
133
  message,
116
134
  data: {
117
- importName,
135
+ importSource,
118
136
  customMessage
119
137
  }
120
138
  });
121
139
  }
122
140
 
123
141
  /**
124
- * Check if the given name is a restricted path name.
125
- * @param {string} name name of a variable
142
+ * Report a restricted path specifically for patterns.
143
+ * @param {node} node - representing the restricted path reference
144
+ * @returns {void}
145
+ * @private
146
+ */
147
+ function reportPathForPatterns(node) {
148
+ const importSource = node.source.value.trim();
149
+
150
+ context.report({
151
+ node,
152
+ message: "'{{importSource}}' import is restricted from being used by a pattern.",
153
+ data: {
154
+ importSource
155
+ }
156
+ });
157
+ }
158
+
159
+ /**
160
+ * Report a restricted path specifically when using the '*' import.
161
+ * @param {string} importSource - path of the import
162
+ * @param {node} node - representing the restricted path reference
163
+ * @returns {void}
164
+ * @private
165
+ */
166
+ function reportPathForEverythingImported(importSource, node) {
167
+ const importNames = restrictedPathMessages[importSource].importNames;
168
+
169
+ context.report({
170
+ node,
171
+ message: "* import is invalid because '{{importNames}}' from '{{importSource}}' is restricted.",
172
+ data: {
173
+ importSource,
174
+ importNames
175
+ }
176
+ });
177
+ }
178
+
179
+ /**
180
+ * Check if the given importSource is restricted because '*' is being imported.
181
+ * @param {string} importSource - path of the import
182
+ * @param {Set.<string>} importNames - Set of import names that are being imported
183
+ * @returns {boolean} whether the path is restricted
184
+ * @private
185
+ */
186
+ function isRestrictedForEverythingImported(importSource, importNames) {
187
+ return Object.prototype.hasOwnProperty.call(restrictedPathMessages, importSource) &&
188
+ restrictedPathMessages[importSource].importNames &&
189
+ isEverythingImported(importNames);
190
+ }
191
+
192
+ /**
193
+ * Check if the given importNames are restricted given a list of restrictedImportNames.
194
+ * @param {Set.<string>} importNames - Set of import names that are being imported
195
+ * @param {[string]} restrictedImportNames - array of import names that are restricted for this import
196
+ * @returns {boolean} whether the objectName is restricted
197
+ * @private
198
+ */
199
+ function isRestrictedObject(importNames, restrictedImportNames) {
200
+ return restrictedImportNames.some(restrictedObjectName => (
201
+ importNames.has(restrictedObjectName)
202
+ ));
203
+ }
204
+
205
+ /**
206
+ * Check if the given importSource is a restricted path.
207
+ * @param {string} importSource - path of the import
208
+ * @param {Set.<string>} importNames - Set of import names that are being imported
126
209
  * @returns {boolean} whether the variable is a restricted path or not
127
210
  * @private
128
211
  */
129
- function isRestrictedPath(name) {
130
- return Object.prototype.hasOwnProperty.call(restrictedPathMessages, name);
212
+ function isRestrictedPath(importSource, importNames) {
213
+ let isRestricted = false;
214
+
215
+ if (Object.prototype.hasOwnProperty.call(restrictedPathMessages, importSource)) {
216
+ if (restrictedPathMessages[importSource].importNames) {
217
+ isRestricted = isRestrictedObject(importNames, restrictedPathMessages[importSource].importNames);
218
+ } else {
219
+ isRestricted = true;
220
+ }
221
+ }
222
+
223
+ return isRestricted;
224
+ }
225
+
226
+ /**
227
+ * Check if the given importSource is restricted by a pattern.
228
+ * @param {string} importSource - path of the import
229
+ * @returns {boolean} whether the variable is a restricted pattern or not
230
+ * @private
231
+ */
232
+ function isRestrictedPattern(importSource) {
233
+ return restrictedPatterns.length > 0 && restrictedPatternsMatcher.ignores(importSource);
131
234
  }
132
235
 
133
236
  return {
134
237
  ImportDeclaration(node) {
135
- if (node && node.source && node.source.value) {
136
- const importName = node.source.value.trim();
137
-
138
- if (isRestrictedPath(importName)) {
139
- reportPath(node);
140
- }
141
- if (restrictedPatterns.length > 0 && ig.ignores(importName)) {
142
- context.report({
143
- node,
144
- message: "'{{importName}}' import is restricted from being used by a pattern.",
145
- data: {
146
- importName
147
- }
148
- });
238
+ const importSource = node.source.value.trim();
239
+ const importNames = node.specifiers.reduce((set, specifier) => {
240
+ if (specifier.type === "ImportDefaultSpecifier") {
241
+ set.add("default");
242
+ } else if (specifier.type === "ImportNamespaceSpecifier") {
243
+ set.add("*");
244
+ } else {
245
+ set.add(specifier.imported.name);
149
246
  }
247
+ return set;
248
+ }, new Set());
249
+
250
+ if (isRestrictedForEverythingImported(importSource, importNames)) {
251
+ reportPathForEverythingImported(importSource, node);
252
+ }
253
+
254
+ if (isRestrictedPath(importSource, importNames)) {
255
+ reportPath(node);
256
+ }
257
+ if (isRestrictedPattern(importSource)) {
258
+ reportPathForPatterns(node);
150
259
  }
151
260
  }
152
261
  };
@@ -145,7 +145,7 @@ module.exports = {
145
145
  * @returns {void}
146
146
  */
147
147
  function performCheck(leftNode, rightNode, reportNode) {
148
- if (rightNode.type !== "MemberExpression") {
148
+ if (rightNode.type !== "MemberExpression" || rightNode.object.type === "Super") {
149
149
  return;
150
150
  }
151
151
 
package/lib/rules/semi.js CHANGED
@@ -32,10 +32,19 @@ module.exports = {
32
32
  items: [
33
33
  {
34
34
  enum: ["never"]
35
+ },
36
+ {
37
+ type: "object",
38
+ properties: {
39
+ beforeStatementContinuationChars: {
40
+ enum: ["always", "any", "never"]
41
+ }
42
+ },
43
+ additionalProperties: false
35
44
  }
36
45
  ],
37
46
  minItems: 0,
38
- maxItems: 1
47
+ maxItems: 2
39
48
  },
40
49
  {
41
50
  type: "array",
@@ -62,9 +71,10 @@ module.exports = {
62
71
 
63
72
  const OPT_OUT_PATTERN = /^[-[(/+`]/; // One of [(/+-`
64
73
  const options = context.options[1];
65
- const never = context.options[0] === "never",
66
- exceptOneLine = options && options.omitLastInOneLineBlock === true,
67
- sourceCode = context.getSourceCode();
74
+ const never = context.options[0] === "never";
75
+ const exceptOneLine = Boolean(options && options.omitLastInOneLineBlock);
76
+ const beforeStatementContinuationChars = (options && options.beforeStatementContinuationChars) || "any";
77
+ const sourceCode = context.getSourceCode();
68
78
 
69
79
  //--------------------------------------------------------------------------
70
80
  // Helpers
@@ -114,29 +124,115 @@ module.exports = {
114
124
  }
115
125
 
116
126
  /**
117
- * Check if a semicolon is unnecessary, only true if:
118
- * - next token is on a new line and is not one of the opt-out tokens
119
- * - next token is a valid statement divider
120
- * @param {Token} lastToken last token of current node.
121
- * @returns {boolean} whether the semicolon is unnecessary.
127
+ * Check whether a given semicolon token is redandant.
128
+ * @param {Token} semiToken A semicolon token to check.
129
+ * @returns {boolean} `true` if the next token is `;` or `}`.
130
+ */
131
+ function isRedundantSemi(semiToken) {
132
+ const nextToken = sourceCode.getTokenAfter(semiToken);
133
+
134
+ return (
135
+ !nextToken ||
136
+ astUtils.isClosingBraceToken(nextToken) ||
137
+ astUtils.isSemicolonToken(nextToken)
138
+ );
139
+ }
140
+
141
+ /**
142
+ * Check whether a given token is the closing brace of an arrow function.
143
+ * @param {Token} lastToken A token to check.
144
+ * @returns {boolean} `true` if the token is the closing brace of an arrow function.
122
145
  */
123
- function isUnnecessarySemicolon(lastToken) {
124
- if (!astUtils.isSemicolonToken(lastToken)) {
146
+ function isEndOfArrowBlock(lastToken) {
147
+ if (!astUtils.isClosingBraceToken(lastToken)) {
125
148
  return false;
126
149
  }
150
+ const node = sourceCode.getNodeByRangeIndex(lastToken.range[0]);
127
151
 
128
- const nextToken = sourceCode.getTokenAfter(lastToken);
152
+ return (
153
+ node.type === "BlockStatement" &&
154
+ node.parent.type === "ArrowFunctionExpression"
155
+ );
156
+ }
157
+
158
+ /**
159
+ * Check whether a given node is on the same line with the next token.
160
+ * @param {Node} node A statement node to check.
161
+ * @returns {boolean} `true` if the node is on the same line with the next token.
162
+ */
163
+ function isOnSameLineWithNextToken(node) {
164
+ const prevToken = sourceCode.getLastToken(node, 1);
165
+ const nextToken = sourceCode.getTokenAfter(node);
166
+
167
+ return !!nextToken && astUtils.isTokenOnSameLine(prevToken, nextToken);
168
+ }
169
+
170
+ /**
171
+ * Check whether a given node can connect the next line if the next line is unreliable.
172
+ * @param {Node} node A statement node to check.
173
+ * @returns {boolean} `true` if the node can connect the next line.
174
+ */
175
+ function maybeAsiHazardAfter(node) {
176
+ const t = node.type;
129
177
 
130
- if (!nextToken) {
131
- return true;
178
+ if (t === "DoWhileStatement" ||
179
+ t === "BreakStatement" ||
180
+ t === "ContinueStatement" ||
181
+ t === "DebuggerStatement" ||
182
+ t === "ImportDeclaration" ||
183
+ t === "ExportAllDeclaration"
184
+ ) {
185
+ return false;
186
+ }
187
+ if (t === "ReturnStatement") {
188
+ return Boolean(node.argument);
189
+ }
190
+ if (t === "ExportNamedDeclaration") {
191
+ return Boolean(node.declaration);
192
+ }
193
+ if (isEndOfArrowBlock(sourceCode.getLastToken(node, 1))) {
194
+ return false;
132
195
  }
133
196
 
134
- const lastTokenLine = lastToken.loc.end.line;
135
- const nextTokenLine = nextToken.loc.start.line;
136
- const isOptOutToken = OPT_OUT_PATTERN.test(nextToken.value) && nextToken.value !== "++" && nextToken.value !== "--";
137
- const isDivider = (astUtils.isClosingBraceToken(nextToken) || astUtils.isSemicolonToken(nextToken));
197
+ return true;
198
+ }
138
199
 
139
- return (lastTokenLine !== nextTokenLine && !isOptOutToken) || isDivider;
200
+ /**
201
+ * Check whether a given token can connect the previous statement.
202
+ * @param {Token} token A token to check.
203
+ * @returns {boolean} `true` if the token is one of `[`, `(`, `/`, `+`, `-`, ```, `++`, and `--`.
204
+ */
205
+ function maybeAsiHazardBefore(token) {
206
+ return (
207
+ Boolean(token) &&
208
+ OPT_OUT_PATTERN.test(token.value) &&
209
+ token.value !== "++" &&
210
+ token.value !== "--"
211
+ );
212
+ }
213
+
214
+ /**
215
+ * Check if the semicolon of a given node is unnecessary, only true if:
216
+ * - next token is a valid statement divider (`;` or `}`).
217
+ * - next token is on a new line and the node is not connectable to the new line.
218
+ * @param {Node} node A statement node to check.
219
+ * @returns {boolean} whether the semicolon is unnecessary.
220
+ */
221
+ function canRemoveSemicolon(node) {
222
+ if (isRedundantSemi(sourceCode.getLastToken(node))) {
223
+ return true; // `;;` or `;}`
224
+ }
225
+ if (isOnSameLineWithNextToken(node)) {
226
+ return false; // One liner.
227
+ }
228
+ if (beforeStatementContinuationChars === "never" && !maybeAsiHazardAfter(node)) {
229
+ return true; // ASI works. This statement doesn't connect to the next.
230
+ }
231
+ if (!maybeAsiHazardBefore(sourceCode.getTokenAfter(node))) {
232
+ return true; // ASI works. The next token doesn't connect to this statement.
233
+ }
234
+
235
+ return false;
140
236
  }
141
237
 
142
238
  /**
@@ -145,16 +241,17 @@ module.exports = {
145
241
  * @returns {boolean} whether the node is in a one-liner block statement.
146
242
  */
147
243
  function isOneLinerBlock(node) {
244
+ const parent = node.parent;
148
245
  const nextToken = sourceCode.getTokenAfter(node);
149
246
 
150
247
  if (!nextToken || nextToken.value !== "}") {
151
248
  return false;
152
249
  }
153
-
154
- const parent = node.parent;
155
-
156
- return parent && parent.type === "BlockStatement" &&
157
- parent.loc.start.line === parent.loc.end.line;
250
+ return (
251
+ !!parent &&
252
+ parent.type === "BlockStatement" &&
253
+ parent.loc.start.line === parent.loc.end.line
254
+ );
158
255
  }
159
256
 
160
257
  /**
@@ -163,21 +260,21 @@ module.exports = {
163
260
  * @returns {void}
164
261
  */
165
262
  function checkForSemicolon(node) {
166
- const lastToken = sourceCode.getLastToken(node);
263
+ const isSemi = astUtils.isSemicolonToken(sourceCode.getLastToken(node));
167
264
 
168
265
  if (never) {
169
- if (isUnnecessarySemicolon(lastToken)) {
266
+ if (isSemi && canRemoveSemicolon(node)) {
170
267
  report(node, true);
268
+ } else if (!isSemi && beforeStatementContinuationChars === "always" && maybeAsiHazardBefore(sourceCode.getTokenAfter(node))) {
269
+ report(node);
171
270
  }
172
271
  } else {
173
- if (!astUtils.isSemicolonToken(lastToken)) {
174
- if (!exceptOneLine || !isOneLinerBlock(node)) {
175
- report(node);
176
- }
177
- } else {
178
- if (exceptOneLine && isOneLinerBlock(node)) {
179
- report(node, true);
180
- }
272
+ const oneLinerBlock = (exceptOneLine && isOneLinerBlock(node));
273
+
274
+ if (isSemi && oneLinerBlock) {
275
+ report(node, true);
276
+ } else if (!isSemi && !oneLinerBlock) {
277
+ report(node);
181
278
  }
182
279
  }
183
280
  }
@@ -188,9 +285,7 @@ module.exports = {
188
285
  * @returns {void}
189
286
  */
190
287
  function checkForSemicolonForVariableDeclaration(node) {
191
- const ancestors = context.getAncestors(),
192
- parentIndex = ancestors.length - 1,
193
- parent = ancestors[parentIndex];
288
+ const parent = node.parent;
194
289
 
195
290
  if ((parent.type !== "ForStatement" || parent.init !== node) &&
196
291
  (!/^For(?:In|Of)Statement/.test(parent.type) || parent.left !== node)
@@ -27,29 +27,64 @@ module.exports = {
27
27
  },
28
28
  additionalProperties: false
29
29
  }
30
- ]
30
+ ],
31
+
32
+ fixable: "code"
31
33
  },
32
34
 
33
35
  create(context) {
34
36
 
35
37
  const configuration = context.options[0] || {},
36
- ignoreCase = configuration.ignoreCase || false;
38
+ ignoreCase = configuration.ignoreCase || false,
39
+ sourceCode = context.getSourceCode();
37
40
 
38
41
  return {
39
42
  VariableDeclaration(node) {
40
43
  const idDeclarations = node.declarations.filter(decl => decl.id.type === "Identifier");
44
+ const getSortableName = ignoreCase ? decl => decl.id.name.toLowerCase() : decl => decl.id.name;
45
+ const unfixable = idDeclarations.some(decl => decl.init !== null && decl.init.type !== "Literal");
46
+ let fixed = false;
41
47
 
42
48
  idDeclarations.slice(1).reduce((memo, decl) => {
43
- let lastVariableName = memo.id.name,
44
- currenVariableName = decl.id.name;
49
+ const lastVariableName = getSortableName(memo),
50
+ currentVariableName = getSortableName(decl);
45
51
 
46
- if (ignoreCase) {
47
- lastVariableName = lastVariableName.toLowerCase();
48
- currenVariableName = currenVariableName.toLowerCase();
49
- }
52
+ if (currentVariableName < lastVariableName) {
53
+ context.report({
54
+ node: decl,
55
+ message: "Variables within the same declaration block should be sorted alphabetically.",
56
+ fix(fixer) {
57
+ if (unfixable || fixed) {
58
+ return null;
59
+ }
60
+ return fixer.replaceTextRange(
61
+ [idDeclarations[0].range[0], idDeclarations[idDeclarations.length - 1].range[1]],
62
+ idDeclarations
63
+
64
+ // Clone the idDeclarations array to avoid mutating it
65
+ .slice()
66
+
67
+ // Sort the array into the desired order
68
+ .sort((declA, declB) => {
69
+ const aName = getSortableName(declA);
70
+ const bName = getSortableName(declB);
71
+
72
+ return aName > bName ? 1 : -1;
73
+ })
74
+
75
+ // Build a string out of the sorted list of identifier declarations and the text between the originals
76
+ .reduce((sourceText, identifier, index) => {
77
+ const textAfterIdentifier = index === idDeclarations.length - 1
78
+ ? ""
79
+ : sourceCode.getText().slice(idDeclarations[index].range[1], idDeclarations[index + 1].range[0]);
80
+
81
+ return sourceText + sourceCode.getText(identifier) + textAfterIdentifier;
82
+ }, "")
50
83
 
51
- if (currenVariableName < lastVariableName) {
52
- context.report({ node: decl, message: "Variables within the same declaration block should be sorted alphabetically." });
84
+ );
85
+ }
86
+ });
87
+ fixed = true;
53
88
  return memo;
54
89
  }
55
90
  return decl;
@@ -45,8 +45,7 @@ module.exports = {
45
45
  const MISSING_SPACE_MESSAGE = "There must be a space inside this paren.",
46
46
  REJECTED_SPACE_MESSAGE = "There should be no spaces inside this paren.",
47
47
  ALWAYS = context.options[0] === "always",
48
-
49
- exceptionsArrayOptions = (context.options.length === 2) ? context.options[1].exceptions : [],
48
+ exceptionsArrayOptions = (context.options[1] && context.options[1].exceptions) || [],
50
49
  options = {};
51
50
  let exceptions;
52
51
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint",
3
- "version": "4.11.0",
3
+ "version": "4.13.1",
4
4
  "author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
5
5
  "description": "An AST-based pattern checker for JavaScript.",
6
6
  "bin": {
@@ -40,7 +40,7 @@
40
40
  "concat-stream": "^1.6.0",
41
41
  "cross-spawn": "^5.1.0",
42
42
  "debug": "^3.0.1",
43
- "doctrine": "^2.0.0",
43
+ "doctrine": "^2.0.2",
44
44
  "eslint-scope": "^3.7.1",
45
45
  "espree": "^3.5.2",
46
46
  "esquery": "^1.0.0",
@@ -49,7 +49,7 @@
49
49
  "file-entry-cache": "^2.0.0",
50
50
  "functional-red-black-tree": "^1.0.1",
51
51
  "glob": "^7.1.2",
52
- "globals": "^9.17.0",
52
+ "globals": "^11.0.1",
53
53
  "ignore": "^3.3.3",
54
54
  "imurmurhash": "^0.1.4",
55
55
  "inquirer": "^3.0.6",