eslint 5.9.0 → 5.12.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 (49) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +2 -2
  3. package/conf/eslint-recommended.js +1 -0
  4. package/lib/cli-engine.js +1 -1
  5. package/lib/config/autoconfig.js +0 -1
  6. package/lib/config/config-file.js +2 -2
  7. package/lib/config/config-rule.js +4 -4
  8. package/lib/config/config-validator.js +2 -2
  9. package/lib/config.js +15 -2
  10. package/lib/linter.js +8 -120
  11. package/lib/rules/array-element-newline.js +0 -1
  12. package/lib/rules/block-spacing.js +2 -2
  13. package/lib/rules/camelcase.js +24 -14
  14. package/lib/rules/capitalized-comments.js +2 -2
  15. package/lib/rules/comma-style.js +7 -2
  16. package/lib/rules/eqeqeq.js +0 -1
  17. package/lib/rules/func-name-matching.js +4 -4
  18. package/lib/rules/handle-callback-err.js +1 -1
  19. package/lib/rules/implicit-arrow-linebreak.js +143 -2
  20. package/lib/rules/indent-legacy.js +0 -3
  21. package/lib/rules/indent.js +29 -23
  22. package/lib/rules/keyword-spacing.js +5 -1
  23. package/lib/rules/max-classes-per-file.js +1 -1
  24. package/lib/rules/newline-before-return.js +1 -1
  25. package/lib/rules/no-constant-condition.js +0 -1
  26. package/lib/rules/no-else-return.js +0 -1
  27. package/lib/rules/no-implied-eval.js +1 -1
  28. package/lib/rules/no-irregular-whitespace.js +1 -1
  29. package/lib/rules/no-param-reassign.js +8 -0
  30. package/lib/rules/no-restricted-imports.js +1 -1
  31. package/lib/rules/no-this-before-super.js +0 -1
  32. package/lib/rules/no-useless-catch.js +52 -0
  33. package/lib/rules/object-curly-newline.js +0 -1
  34. package/lib/rules/one-var.js +0 -1
  35. package/lib/rules/padding-line-between-statements.js +37 -0
  36. package/lib/rules/prefer-object-spread.js +2 -2
  37. package/lib/rules/quotes.js +48 -25
  38. package/lib/rules/require-jsdoc.js +4 -1
  39. package/lib/rules/sort-imports.js +43 -37
  40. package/lib/rules/space-in-parens.js +0 -1
  41. package/lib/rules/space-infix-ops.js +4 -1
  42. package/lib/rules/valid-jsdoc.js +4 -1
  43. package/lib/util/config-comment-parser.js +144 -0
  44. package/lib/util/glob-utils.js +1 -1
  45. package/lib/{ignored-paths.js → util/ignored-paths.js} +4 -4
  46. package/lib/{report-translator.js → util/report-translator.js} +2 -2
  47. package/lib/util/source-code.js +2 -1
  48. package/messages/all-files-ignored.txt +1 -1
  49. package/package.json +7 -8
@@ -4,6 +4,12 @@
4
4
  */
5
5
  "use strict";
6
6
 
7
+ const {
8
+ isArrowToken,
9
+ isParenthesised,
10
+ isOpeningParenToken
11
+ } = require("../util/ast-utils");
12
+
7
13
  //------------------------------------------------------------------------------
8
14
  // Rule Definition
9
15
  //------------------------------------------------------------------------------
@@ -41,10 +47,145 @@ module.exports = {
41
47
  return context.options[0] || "beside";
42
48
  }
43
49
 
50
+ /**
51
+ * Formats the comments depending on whether it's a line or block comment.
52
+ * @param {Comment[]} comments The array of comments between the arrow and body
53
+ * @param {Integer} column The column number of the first token
54
+ * @returns {string} A string of comment text joined by line breaks
55
+ */
56
+ function formatComments(comments, column) {
57
+ const whiteSpaces = " ".repeat(column);
58
+
59
+ return `${comments.map(comment => {
60
+
61
+ if (comment.type === "Line") {
62
+ return `//${comment.value}`;
63
+ }
64
+
65
+ return `/*${comment.value}*/`;
66
+ }).join(`\n${whiteSpaces}`)}\n${whiteSpaces}`;
67
+ }
68
+
69
+ /**
70
+ * Finds the first token to prepend comments to depending on the parent type
71
+ * @param {Node} node The validated node
72
+ * @returns {Token|Node} The node to prepend comments to
73
+ */
74
+ function findFirstToken(node) {
75
+ switch (node.parent.type) {
76
+ case "VariableDeclarator":
77
+
78
+ // If the parent is first or only declarator, return the declaration, else, declarator
79
+ return sourceCode.getFirstToken(
80
+ node.parent.parent.declarations.length === 1 ||
81
+ node.parent.parent.declarations[0].id.name === node.parent.id.name
82
+ ? node.parent.parent : node.parent
83
+ );
84
+ case "CallExpression":
85
+ case "Property":
86
+
87
+ // find the object key
88
+ return sourceCode.getFirstToken(node.parent);
89
+ default:
90
+ return node;
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Helper function for adding parentheses fixes for nodes containing nested arrow functions
96
+ * @param {Fixer} fixer Fixer
97
+ * @param {Token} arrow - The arrow token
98
+ * @param {ASTNode} arrowBody - The arrow function body
99
+ * @returns {Function[]} autofixer -- wraps function bodies with parentheses
100
+ */
101
+ function addParentheses(fixer, arrow, arrowBody) {
102
+ const parenthesesFixes = [];
103
+ let closingParentheses = "";
104
+
105
+ let followingBody = arrowBody;
106
+ let currentArrow = arrow;
107
+
108
+ while (currentArrow) {
109
+ if (!isParenthesised(sourceCode, followingBody)) {
110
+ parenthesesFixes.push(
111
+ fixer.insertTextAfter(currentArrow, " (")
112
+ );
113
+
114
+ const paramsToken = sourceCode.getTokenBefore(currentArrow, token =>
115
+ isOpeningParenToken(token) || token.type === "Identifier");
116
+
117
+ const whiteSpaces = " ".repeat(paramsToken.loc.start.column);
118
+
119
+ closingParentheses = `\n${whiteSpaces})${closingParentheses}`;
120
+ }
121
+
122
+ currentArrow = sourceCode.getTokenAfter(currentArrow, isArrowToken);
123
+
124
+ if (currentArrow) {
125
+ followingBody = sourceCode.getTokenAfter(currentArrow, token => !isOpeningParenToken(token));
126
+ }
127
+ }
128
+
129
+ return [...parenthesesFixes,
130
+ fixer.insertTextAfter(arrowBody, closingParentheses)
131
+ ];
132
+ }
133
+
134
+ /**
135
+ * Autofixes the function body to collapse onto the same line as the arrow.
136
+ * If comments exist, prepends the comments before the arrow function.
137
+ * If the function body contains arrow functions, appends the function bodies with parentheses.
138
+ * @param {Token} arrowToken The arrow token.
139
+ * @param {ASTNode} arrowBody the function body
140
+ * @param {ASTNode} node The evaluated node
141
+ * @returns {Function} autofixer -- validates the node to adhere to besides
142
+ */
143
+ function autoFixBesides(arrowToken, arrowBody, node) {
144
+ return fixer => {
145
+ const placeBesides = fixer.replaceTextRange([arrowToken.range[1], arrowBody.range[0]], " ");
146
+
147
+ const comments = sourceCode.getCommentsInside(node).filter(comment =>
148
+ comment.loc.start.line < arrowBody.loc.start.line);
149
+
150
+ if (comments.length) {
151
+
152
+ // If the grandparent is not a variable declarator
153
+ if (
154
+ arrowBody.parent &&
155
+ arrowBody.parent.parent &&
156
+ arrowBody.parent.parent.type !== "VariableDeclarator"
157
+ ) {
158
+
159
+ // If any arrow functions follow, return the necessary parens fixes.
160
+ if (sourceCode.getTokenAfter(arrowToken, isArrowToken) && arrowBody.parent.parent.type !== "VariableDeclarator") {
161
+ return addParentheses(fixer, arrowToken, arrowBody);
162
+ }
163
+
164
+ // If any arrow functions precede, the necessary fixes have already been returned, so return null.
165
+ if (sourceCode.getTokenBefore(arrowToken, isArrowToken) && arrowBody.parent.parent.type !== "VariableDeclarator") {
166
+ return null;
167
+ }
168
+ }
169
+
170
+ const firstToken = findFirstToken(node);
171
+
172
+ const commentText = formatComments(comments, firstToken.loc.start.column);
173
+
174
+ const commentBeforeExpression = fixer.insertTextBeforeRange(
175
+ firstToken.range,
176
+ commentText
177
+ );
178
+
179
+ return [placeBesides, commentBeforeExpression];
180
+ }
181
+
182
+ return placeBesides;
183
+ };
184
+ }
185
+
44
186
  /**
45
187
  * Validates the location of an arrow function body
46
188
  * @param {ASTNode} node The arrow function body
47
- * @param {string} keywordName The applicable keyword name for the arrow function body
48
189
  * @returns {void}
49
190
  */
50
191
  function validateExpression(node) {
@@ -76,7 +217,7 @@ module.exports = {
76
217
  context.report({
77
218
  node: fixerTarget,
78
219
  message: "Expected no linebreak before this expression.",
79
- fix: fixer => fixer.replaceTextRange([tokenBefore.range[1], fixerTarget.range[0]], " ")
220
+ fix: autoFixBesides(tokenBefore, fixerTarget, node)
80
221
  });
81
222
  }
82
223
  }
@@ -300,7 +300,6 @@ module.exports = {
300
300
  * @param {int} gottenTabs Indentation tab count in the actual node/code
301
301
  * @param {Object=} loc Error line and column location
302
302
  * @param {boolean} isLastNodeCheck Is the error for last node check
303
- * @param {int} lastNodeCheckEndOffset Number of charecters to skip from the end
304
303
  * @returns {void}
305
304
  */
306
305
  function report(node, needed, gottenSpaces, gottenTabs, loc, isLastNodeCheck) {
@@ -365,7 +364,6 @@ module.exports = {
365
364
  * Check indent for node
366
365
  * @param {ASTNode} node Node to check
367
366
  * @param {int} neededIndent needed indent
368
- * @param {boolean} [excludeCommas=false] skip comma on start of line
369
367
  * @returns {void}
370
368
  */
371
369
  function checkNodeIndent(node, neededIndent) {
@@ -413,7 +411,6 @@ module.exports = {
413
411
  * Check indent for nodes list
414
412
  * @param {ASTNode[]} nodes list of node objects
415
413
  * @param {int} indent needed indent
416
- * @param {boolean} [excludeCommas=false] skip comma on start of line
417
414
  * @returns {void}
418
415
  */
419
416
  function checkNodesIndent(nodes, indent) {
@@ -522,25 +522,13 @@ module.exports = {
522
522
  },
523
523
  VariableDeclarator: {
524
524
  oneOf: [
525
- {
526
- type: "integer",
527
- minimum: 0
528
- },
525
+ ELEMENT_LIST_SCHEMA,
529
526
  {
530
527
  type: "object",
531
528
  properties: {
532
- var: {
533
- type: "integer",
534
- minimum: 0
535
- },
536
- let: {
537
- type: "integer",
538
- minimum: 0
539
- },
540
- const: {
541
- type: "integer",
542
- minimum: 0
543
- }
529
+ var: ELEMENT_LIST_SCHEMA,
530
+ let: ELEMENT_LIST_SCHEMA,
531
+ const: ELEMENT_LIST_SCHEMA
544
532
  },
545
533
  additionalProperties: false
546
534
  }
@@ -661,7 +649,7 @@ module.exports = {
661
649
  if (context.options[1]) {
662
650
  lodash.merge(options, context.options[1]);
663
651
 
664
- if (typeof options.VariableDeclarator === "number") {
652
+ if (typeof options.VariableDeclarator === "number" || options.VariableDeclarator === "first") {
665
653
  options.VariableDeclarator = {
666
654
  var: options.VariableDeclarator,
667
655
  let: options.VariableDeclarator,
@@ -1229,9 +1217,13 @@ module.exports = {
1229
1217
  }
1230
1218
 
1231
1219
  const fromToken = sourceCode.getLastToken(node, token => token.type === "Identifier" && token.value === "from");
1220
+ const sourceToken = sourceCode.getLastToken(node, token => token.type === "String");
1221
+ const semiToken = sourceCode.getLastToken(node, token => token.type === "Punctuator" && token.value === ";");
1232
1222
 
1233
1223
  if (fromToken) {
1234
- offsets.setDesiredOffsets([fromToken.range[0], node.range[1]], sourceCode.getFirstToken(node), 1);
1224
+ const end = semiToken && semiToken.range[1] === sourceToken.range[1] ? node.range[1] : sourceToken.range[1];
1225
+
1226
+ offsets.setDesiredOffsets([fromToken.range[0], end], sourceCode.getFirstToken(node), 1);
1235
1227
  }
1236
1228
  },
1237
1229
 
@@ -1345,10 +1337,27 @@ module.exports = {
1345
1337
  },
1346
1338
 
1347
1339
  VariableDeclaration(node) {
1348
- const variableIndent = Object.prototype.hasOwnProperty.call(options.VariableDeclarator, node.kind)
1340
+ let variableIndent = Object.prototype.hasOwnProperty.call(options.VariableDeclarator, node.kind)
1349
1341
  ? options.VariableDeclarator[node.kind]
1350
1342
  : DEFAULT_VARIABLE_INDENT;
1351
1343
 
1344
+ const firstToken = sourceCode.getFirstToken(node),
1345
+ lastToken = sourceCode.getLastToken(node);
1346
+
1347
+ if (options.VariableDeclarator[node.kind] === "first") {
1348
+ if (node.declarations.length > 1) {
1349
+ addElementListIndent(
1350
+ node.declarations,
1351
+ firstToken,
1352
+ lastToken,
1353
+ "first"
1354
+ );
1355
+ return;
1356
+ }
1357
+
1358
+ variableIndent = DEFAULT_VARIABLE_INDENT;
1359
+ }
1360
+
1352
1361
  if (node.declarations[node.declarations.length - 1].loc.start.line > node.loc.start.line) {
1353
1362
 
1354
1363
  /*
@@ -1370,13 +1379,10 @@ module.exports = {
1370
1379
  * on the same line as the start of the declaration, provided that there are declarators that
1371
1380
  * follow this one.
1372
1381
  */
1373
- const firstToken = sourceCode.getFirstToken(node);
1374
-
1375
1382
  offsets.setDesiredOffsets(node.range, firstToken, variableIndent, true);
1376
1383
  } else {
1377
- offsets.setDesiredOffsets(node.range, sourceCode.getFirstToken(node), variableIndent);
1384
+ offsets.setDesiredOffsets(node.range, firstToken, variableIndent);
1378
1385
  }
1379
- const lastToken = sourceCode.getLastToken(node);
1380
1386
 
1381
1387
  if (astUtils.isSemicolonToken(lastToken)) {
1382
1388
  offsets.ignoreToken(lastToken);
@@ -453,6 +453,10 @@ module.exports = {
453
453
  checkSpacingBefore(firstToken, PREV_TOKEN_M);
454
454
  checkSpacingAfter(firstToken, NEXT_TOKEN_M);
455
455
 
456
+ if (node.type === "ExportDefaultDeclaration") {
457
+ checkSpacingAround(sourceCode.getTokenAfter(firstToken));
458
+ }
459
+
456
460
  if (node.source) {
457
461
  const fromToken = sourceCode.getTokenBefore(node.source);
458
462
 
@@ -554,7 +558,7 @@ module.exports = {
554
558
  // Statements - Declarations
555
559
  ClassDeclaration: checkSpacingForClass,
556
560
  ExportNamedDeclaration: checkSpacingForModuleDeclaration,
557
- ExportDefaultDeclaration: checkSpacingAroundFirstToken,
561
+ ExportDefaultDeclaration: checkSpacingForModuleDeclaration,
558
562
  ExportAllDeclaration: checkSpacingForModuleDeclaration,
559
563
  FunctionDeclaration: checkSpacingForFunction,
560
564
  ImportDeclaration: checkSpacingForModuleDeclaration,
@@ -32,7 +32,7 @@ module.exports = {
32
32
  ],
33
33
 
34
34
  messages: {
35
- maximumExceeded: "Number of classes per file must not exceed {{ max }}"
35
+ maximumExceeded: "Number of classes per file must not exceed {{ max }}."
36
36
  }
37
37
  },
38
38
  create(context) {
@@ -36,7 +36,7 @@ module.exports = {
36
36
  /**
37
37
  * Tests whether node is preceded by supplied tokens
38
38
  * @param {ASTNode} node - node to check
39
- * @param {array} testTokens - array of tokens to test against
39
+ * @param {Array} testTokens - array of tokens to test against
40
40
  * @returns {boolean} Whether or not the node is preceded by one of the supplied tokens
41
41
  * @private
42
42
  */
@@ -173,7 +173,6 @@ module.exports = {
173
173
 
174
174
  /**
175
175
  * Reports when the set still contains stored constant conditions
176
- * @param {ASTNode} node The AST node to check.
177
176
  * @returns {void}
178
177
  * @private
179
178
  */
@@ -183,7 +183,6 @@ module.exports = {
183
183
  * code paths.
184
184
  *
185
185
  * @param {Node} node The consequent or body node
186
- * @param {Node} alternate The alternate node
187
186
  * @returns {boolean} `true` if it is a Return/If node that always returns.
188
187
  */
189
188
  function checkForReturnOrIf(node) {
@@ -38,7 +38,7 @@ module.exports = {
38
38
 
39
39
  /**
40
40
  * Get the last element of an array, without modifying arr, like pop(), but non-destructive.
41
- * @param {array} arr What to inspect
41
+ * @param {Array} arr What to inspect
42
42
  * @returns {*} The last element of arr
43
43
  * @private
44
44
  */
@@ -30,7 +30,7 @@ module.exports = {
30
30
  type: "problem",
31
31
 
32
32
  docs: {
33
- description: "disallow irregular whitespace outside of strings and comments",
33
+ description: "disallow irregular whitespace",
34
34
  category: "Possible Errors",
35
35
  recommended: true,
36
36
  url: "https://eslint.org/docs/rules/no-irregular-whitespace"
@@ -107,6 +107,14 @@ module.exports = {
107
107
 
108
108
  break;
109
109
 
110
+ // EXCLUDES: e.g. (foo ? a : b).c = bar;
111
+ case "ConditionalExpression":
112
+ if (parent.test === node) {
113
+ return false;
114
+ }
115
+
116
+ break;
117
+
110
118
  // no default
111
119
  }
112
120
 
@@ -195,7 +195,7 @@ module.exports = {
195
195
  /**
196
196
  * Check if the given importNames are restricted given a list of restrictedImportNames.
197
197
  * @param {Set.<string>} importNames - Set of import names that are being imported
198
- * @param {[string]} restrictedImportNames - array of import names that are restricted for this import
198
+ * @param {string[]} restrictedImportNames - array of import names that are restricted for this import
199
199
  * @returns {boolean} whether the objectName is restricted
200
200
  * @private
201
201
  */
@@ -171,7 +171,6 @@ module.exports = {
171
171
  * invalid node.
172
172
  *
173
173
  * @param {CodePath} codePath - A code path which was ended.
174
- * @param {ASTNode} node - The current node.
175
174
  * @returns {void}
176
175
  */
177
176
  onCodePathEnd(codePath) {
@@ -0,0 +1,52 @@
1
+ /**
2
+ * @fileoverview Reports useless `catch` clauses that just rethrow their error.
3
+ * @author Teddy Katz
4
+ */
5
+
6
+ "use strict";
7
+
8
+ //------------------------------------------------------------------------------
9
+ // Rule Definition
10
+ //------------------------------------------------------------------------------
11
+
12
+ module.exports = {
13
+ meta: {
14
+ type: "suggestion",
15
+
16
+ docs: {
17
+ description: "disallow unnecessary `catch` clauses",
18
+ category: "Best Practices",
19
+ recommended: false,
20
+ url: "https://eslint.org/docs/rules/no-useless-catch"
21
+ },
22
+
23
+ schema: []
24
+ },
25
+
26
+ create(context) {
27
+ return {
28
+ CatchClause(node) {
29
+ if (
30
+ node.param &&
31
+ node.param.type === "Identifier" &&
32
+ node.body.body.length &&
33
+ node.body.body[0].type === "ThrowStatement" &&
34
+ node.body.body[0].argument.type === "Identifier" &&
35
+ node.body.body[0].argument.name === node.param.name
36
+ ) {
37
+ if (node.parent.finalizer) {
38
+ context.report({
39
+ node,
40
+ message: "Unnecessary catch clause."
41
+ });
42
+ } else {
43
+ context.report({
44
+ node: node.parent,
45
+ message: "Unnecessary try/catch wrapper."
46
+ });
47
+ }
48
+ }
49
+ }
50
+ };
51
+ }
52
+ };
@@ -172,7 +172,6 @@ module.exports = {
172
172
  /**
173
173
  * Reports a given node if it violated this rule.
174
174
  * @param {ASTNode} node - A node to check. This is an ObjectExpression, ObjectPattern, ImportDeclaration or ExportNamedDeclaration node.
175
- * @param {{multiline: boolean, minProperties: number, consistent: boolean}} options - An option object.
176
175
  * @returns {void}
177
176
  */
178
177
  function check(node) {
@@ -310,7 +310,6 @@ module.exports = {
310
310
  /**
311
311
  * Fixer to split a VariableDeclaration into individual declarations
312
312
  * @param {VariableDeclaration} declaration The `VariableDeclaration` to split
313
- * @param {?Function} filter Function to filter the declarations
314
313
  * @returns {Function} The fixer function
315
314
  */
316
315
  function splitDeclarations(declaration) {
@@ -36,6 +36,36 @@ function newKeywordTester(keyword) {
36
36
  };
37
37
  }
38
38
 
39
+ /**
40
+ * Creates tester which check if a node starts with specific keyword and spans a single line.
41
+ *
42
+ * @param {string} keyword The keyword to test.
43
+ * @returns {Object} the created tester.
44
+ * @private
45
+ */
46
+ function newSinglelineKeywordTester(keyword) {
47
+ return {
48
+ test: (node, sourceCode) =>
49
+ node.loc.start.line === node.loc.end.line &&
50
+ sourceCode.getFirstToken(node).value === keyword
51
+ };
52
+ }
53
+
54
+ /**
55
+ * Creates tester which check if a node starts with specific keyword and spans multiple lines.
56
+ *
57
+ * @param {string} keyword The keyword to test.
58
+ * @returns {Object} the created tester.
59
+ * @private
60
+ */
61
+ function newMultilineKeywordTester(keyword) {
62
+ return {
63
+ test: (node, sourceCode) =>
64
+ node.loc.start.line !== node.loc.end.line &&
65
+ sourceCode.getFirstToken(node).value === keyword
66
+ };
67
+ }
68
+
39
69
  /**
40
70
  * Creates tester which check if a node is specific type.
41
71
  *
@@ -368,6 +398,13 @@ const StatementTypes = {
368
398
  !isDirectivePrologue(node, sourceCode)
369
399
  },
370
400
 
401
+ "multiline-const": newMultilineKeywordTester("const"),
402
+ "multiline-let": newMultilineKeywordTester("let"),
403
+ "multiline-var": newMultilineKeywordTester("var"),
404
+ "singleline-const": newSinglelineKeywordTester("const"),
405
+ "singleline-let": newSinglelineKeywordTester("let"),
406
+ "singleline-var": newSinglelineKeywordTester("var"),
407
+
371
408
  block: newNodeTypeTester("BlockStatement"),
372
409
  empty: newNodeTypeTester("EmptyStatement"),
373
410
  function: newNodeTypeTester("FunctionDeclaration"),
@@ -226,8 +226,8 @@ module.exports = {
226
226
  fixable: "code",
227
227
 
228
228
  messages: {
229
- useSpreadMessage: "Use an object spread instead of `Object.assign` eg: `{ ...foo }`",
230
- useLiteralMessage: "Use an object literal instead of `Object.assign`. eg: `{ foo: bar }`"
229
+ useSpreadMessage: "Use an object spread instead of `Object.assign` eg: `{ ...foo }`.",
230
+ useLiteralMessage: "Use an object literal instead of `Object.assign`. eg: `{ foo: bar }`."
231
231
  }
232
232
  },
233
233
 
@@ -228,6 +228,34 @@ module.exports = {
228
228
  }
229
229
  }
230
230
 
231
+ /**
232
+ * Checks whether or not a given TemplateLiteral node is actually using any of the special features provided by template literal strings.
233
+ * @param {ASTNode} node - A TemplateLiteral node to check.
234
+ * @returns {boolean} Whether or not the TemplateLiteral node is using any of the special features provided by template literal strings.
235
+ * @private
236
+ */
237
+ function isUsingFeatureOfTemplateLiteral(node) {
238
+ const hasTag = node.parent.type === "TaggedTemplateExpression" && node === node.parent.quasi;
239
+
240
+ if (hasTag) {
241
+ return true;
242
+ }
243
+
244
+ const hasStringInterpolation = node.expressions.length > 0;
245
+
246
+ if (hasStringInterpolation) {
247
+ return true;
248
+ }
249
+
250
+ const isMultilineString = node.quasis.length >= 1 && UNESCAPED_LINEBREAK_PATTERN.test(node.quasis[0].value.raw);
251
+
252
+ if (isMultilineString) {
253
+ return true;
254
+ }
255
+
256
+ return false;
257
+ }
258
+
231
259
  return {
232
260
 
233
261
  Literal(node) {
@@ -260,39 +288,34 @@ module.exports = {
260
288
 
261
289
  TemplateLiteral(node) {
262
290
 
263
- // If backticks are expected or it's a tagged template, then this shouldn't throw an errors
291
+ // Don't throw an error if backticks are expected or a template literal feature is in use.
264
292
  if (
265
293
  allowTemplateLiterals ||
266
294
  quoteOption === "backtick" ||
267
- node.parent.type === "TaggedTemplateExpression" && node === node.parent.quasi
295
+ isUsingFeatureOfTemplateLiteral(node)
268
296
  ) {
269
297
  return;
270
298
  }
271
299
 
272
- // A warning should be produced if the template literal only has one TemplateElement, and has no unescaped newlines.
273
- const shouldWarn = node.quasis.length === 1 && !UNESCAPED_LINEBREAK_PATTERN.test(node.quasis[0].value.raw);
274
-
275
- if (shouldWarn) {
276
- context.report({
277
- node,
278
- message: "Strings must use {{description}}.",
279
- data: {
280
- description: settings.description
281
- },
282
- fix(fixer) {
283
- if (isPartOfDirectivePrologue(node)) {
284
-
285
- /*
286
- * TemplateLiterals in a directive prologue aren't actually directives, but if they're
287
- * in the directive prologue, then fixing them might turn them into directives and change
288
- * the behavior of the code.
289
- */
290
- return null;
291
- }
292
- return fixer.replaceText(node, settings.convert(sourceCode.getText(node)));
300
+ context.report({
301
+ node,
302
+ message: "Strings must use {{description}}.",
303
+ data: {
304
+ description: settings.description
305
+ },
306
+ fix(fixer) {
307
+ if (isPartOfDirectivePrologue(node)) {
308
+
309
+ /*
310
+ * TemplateLiterals in a directive prologue aren't actually directives, but if they're
311
+ * in the directive prologue, then fixing them might turn them into directives and change
312
+ * the behavior of the code.
313
+ */
314
+ return null;
293
315
  }
294
- });
295
- }
316
+ return fixer.replaceText(node, settings.convert(sourceCode.getText(node)));
317
+ }
318
+ });
296
319
  }
297
320
  };
298
321
 
@@ -43,7 +43,10 @@ module.exports = {
43
43
  },
44
44
  additionalProperties: false
45
45
  }
46
- ]
46
+ ],
47
+
48
+ deprecated: true,
49
+ replacedBy: []
47
50
  },
48
51
 
49
52
  create(context) {