eslint 7.0.0-alpha.3 → 7.2.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 (89) hide show
  1. package/CHANGELOG.md +303 -0
  2. package/README.md +10 -11
  3. package/bin/eslint.js +115 -77
  4. package/conf/category-list.json +0 -1
  5. package/lib/api.js +2 -0
  6. package/lib/cli-engine/cascading-config-array-factory.js +12 -0
  7. package/lib/cli-engine/cli-engine.js +53 -48
  8. package/lib/cli-engine/config-array/config-array.js +1 -1
  9. package/lib/cli-engine/config-array/ignore-pattern.js +7 -1
  10. package/lib/cli-engine/config-array-factory.js +3 -3
  11. package/lib/cli.js +181 -95
  12. package/lib/eslint/eslint.js +656 -0
  13. package/lib/eslint/index.js +7 -0
  14. package/lib/init/autoconfig.js +4 -4
  15. package/lib/init/config-initializer.js +5 -10
  16. package/lib/init/source-code-utils.js +2 -2
  17. package/lib/linter/code-path-analysis/code-path-analyzer.js +2 -2
  18. package/lib/linter/code-path-analysis/code-path-state.js +34 -12
  19. package/lib/linter/config-comment-parser.js +1 -1
  20. package/lib/linter/linter.js +2 -1
  21. package/lib/options.js +0 -1
  22. package/lib/rule-tester/rule-tester.js +6 -1
  23. package/lib/rules/accessor-pairs.js +1 -1
  24. package/lib/rules/array-callback-return.js +3 -18
  25. package/lib/rules/arrow-parens.js +19 -3
  26. package/lib/rules/block-spacing.js +19 -2
  27. package/lib/rules/callback-return.js +4 -0
  28. package/lib/rules/comma-style.js +3 -8
  29. package/lib/rules/func-call-spacing.js +22 -6
  30. package/lib/rules/getter-return.js +2 -12
  31. package/lib/rules/global-require.js +4 -0
  32. package/lib/rules/handle-callback-err.js +4 -0
  33. package/lib/rules/index.js +1 -0
  34. package/lib/rules/key-spacing.js +1 -1
  35. package/lib/rules/keyword-spacing.js +9 -2
  36. package/lib/rules/linebreak-style.js +8 -2
  37. package/lib/rules/max-lines-per-function.js +1 -1
  38. package/lib/rules/multiline-ternary.js +44 -25
  39. package/lib/rules/new-cap.js +1 -1
  40. package/lib/rules/newline-per-chained-call.js +6 -3
  41. package/lib/rules/no-buffer-constructor.js +4 -0
  42. package/lib/rules/no-control-regex.js +1 -1
  43. package/lib/rules/no-empty-function.js +1 -1
  44. package/lib/rules/no-extra-boolean-cast.js +3 -0
  45. package/lib/rules/no-extra-parens.js +35 -3
  46. package/lib/rules/no-inner-declarations.js +31 -39
  47. package/lib/rules/no-invalid-regexp.js +1 -1
  48. package/lib/rules/no-lone-blocks.js +1 -1
  49. package/lib/rules/no-loss-of-precision.js +198 -0
  50. package/lib/rules/no-misleading-character-class.js +1 -1
  51. package/lib/rules/no-mixed-operators.js +3 -2
  52. package/lib/rules/no-mixed-requires.js +4 -0
  53. package/lib/rules/no-mixed-spaces-and-tabs.js +14 -6
  54. package/lib/rules/no-new-func.js +22 -19
  55. package/lib/rules/no-new-object.js +15 -3
  56. package/lib/rules/no-new-require.js +4 -0
  57. package/lib/rules/no-new-symbol.js +2 -1
  58. package/lib/rules/no-path-concat.js +4 -0
  59. package/lib/rules/no-process-env.js +4 -0
  60. package/lib/rules/no-process-exit.js +4 -0
  61. package/lib/rules/no-regex-spaces.js +1 -1
  62. package/lib/rules/no-restricted-exports.js +6 -0
  63. package/lib/rules/no-restricted-modules.js +4 -0
  64. package/lib/rules/no-sync.js +4 -0
  65. package/lib/rules/no-unexpected-multiline.js +22 -12
  66. package/lib/rules/no-unneeded-ternary.js +6 -4
  67. package/lib/rules/no-unused-expressions.js +1 -1
  68. package/lib/rules/no-unused-vars.js +3 -1
  69. package/lib/rules/no-useless-backreference.js +1 -1
  70. package/lib/rules/no-useless-concat.js +1 -1
  71. package/lib/rules/one-var-declaration-per-line.js +1 -1
  72. package/lib/rules/padded-blocks.js +17 -4
  73. package/lib/rules/prefer-named-capture-group.js +1 -1
  74. package/lib/rules/quote-props.js +2 -2
  75. package/lib/rules/rest-spread-spacing.js +3 -6
  76. package/lib/rules/semi-spacing.js +32 -8
  77. package/lib/rules/space-before-function-paren.js +5 -2
  78. package/lib/rules/template-tag-spacing.js +8 -2
  79. package/lib/rules/utils/ast-utils.js +106 -9
  80. package/lib/rules/yoda.js +101 -51
  81. package/lib/shared/relative-module-resolver.js +1 -0
  82. package/lib/shared/types.js +7 -0
  83. package/lib/source-code/source-code.js +1 -0
  84. package/messages/extend-config-missing.txt +1 -1
  85. package/messages/no-config-found.txt +1 -1
  86. package/messages/plugin-conflict.txt +1 -1
  87. package/messages/plugin-missing.txt +1 -1
  88. package/messages/whitespace-found.txt +1 -1
  89. package/package.json +27 -26
@@ -39,25 +39,7 @@ module.exports = {
39
39
  const option = context.options[0];
40
40
  const multiline = option !== "never";
41
41
  const allowSingleLine = option === "always-multiline";
42
-
43
- //--------------------------------------------------------------------------
44
- // Helpers
45
- //--------------------------------------------------------------------------
46
-
47
- /**
48
- * Tests whether node is preceded by supplied tokens
49
- * @param {ASTNode} node node to check
50
- * @param {ASTNode} parentNode parent of node to report
51
- * @param {boolean} expected whether newline was expected or not
52
- * @returns {void}
53
- * @private
54
- */
55
- function reportError(node, parentNode, expected) {
56
- context.report({
57
- node,
58
- messageId: `${expected ? "expected" : "unexpected"}${node === parentNode.test ? "TestCons" : "ConsAlt"}`
59
- });
60
- }
42
+ const sourceCode = context.getSourceCode();
61
43
 
62
44
  //--------------------------------------------------------------------------
63
45
  // Public
@@ -65,16 +47,39 @@ module.exports = {
65
47
 
66
48
  return {
67
49
  ConditionalExpression(node) {
68
- const areTestAndConsequentOnSameLine = astUtils.isTokenOnSameLine(node.test, node.consequent);
69
- const areConsequentAndAlternateOnSameLine = astUtils.isTokenOnSameLine(node.consequent, node.alternate);
50
+ const questionToken = sourceCode.getTokenAfter(node.test, astUtils.isNotClosingParenToken);
51
+ const colonToken = sourceCode.getTokenAfter(node.consequent, astUtils.isNotClosingParenToken);
52
+
53
+ const firstTokenOfTest = sourceCode.getFirstToken(node);
54
+ const lastTokenOfTest = sourceCode.getTokenBefore(questionToken);
55
+ const firstTokenOfConsequent = sourceCode.getTokenAfter(questionToken);
56
+ const lastTokenOfConsequent = sourceCode.getTokenBefore(colonToken);
57
+ const firstTokenOfAlternate = sourceCode.getTokenAfter(colonToken);
58
+
59
+ const areTestAndConsequentOnSameLine = astUtils.isTokenOnSameLine(lastTokenOfTest, firstTokenOfConsequent);
60
+ const areConsequentAndAlternateOnSameLine = astUtils.isTokenOnSameLine(lastTokenOfConsequent, firstTokenOfAlternate);
70
61
 
71
62
  if (!multiline) {
72
63
  if (!areTestAndConsequentOnSameLine) {
73
- reportError(node.test, node, false);
64
+ context.report({
65
+ node: node.test,
66
+ loc: {
67
+ start: firstTokenOfTest.loc.start,
68
+ end: lastTokenOfTest.loc.end
69
+ },
70
+ messageId: "unexpectedTestCons"
71
+ });
74
72
  }
75
73
 
76
74
  if (!areConsequentAndAlternateOnSameLine) {
77
- reportError(node.consequent, node, false);
75
+ context.report({
76
+ node: node.consequent,
77
+ loc: {
78
+ start: firstTokenOfConsequent.loc.start,
79
+ end: lastTokenOfConsequent.loc.end
80
+ },
81
+ messageId: "unexpectedConsAlt"
82
+ });
78
83
  }
79
84
  } else {
80
85
  if (allowSingleLine && node.loc.start.line === node.loc.end.line) {
@@ -82,11 +87,25 @@ module.exports = {
82
87
  }
83
88
 
84
89
  if (areTestAndConsequentOnSameLine) {
85
- reportError(node.test, node, true);
90
+ context.report({
91
+ node: node.test,
92
+ loc: {
93
+ start: firstTokenOfTest.loc.start,
94
+ end: lastTokenOfTest.loc.end
95
+ },
96
+ messageId: "expectedTestCons"
97
+ });
86
98
  }
87
99
 
88
100
  if (areConsequentAndAlternateOnSameLine) {
89
- reportError(node.consequent, node, true);
101
+ context.report({
102
+ node: node.consequent,
103
+ loc: {
104
+ start: firstTokenOfConsequent.loc.start,
105
+ end: lastTokenOfConsequent.loc.end
106
+ },
107
+ messageId: "expectedConsAlt"
108
+ });
90
109
  }
91
110
  }
92
111
  }
@@ -235,7 +235,7 @@ module.exports = {
235
235
  callee = callee.property;
236
236
  }
237
237
 
238
- context.report({ node, loc: callee.loc.start, messageId });
238
+ context.report({ node, loc: callee.loc, messageId });
239
239
  }
240
240
 
241
241
  //--------------------------------------------------------------------------
@@ -90,16 +90,19 @@ module.exports = {
90
90
  }
91
91
 
92
92
  if (depth > ignoreChainWithDepth && astUtils.isTokenOnSameLine(callee.object, callee.property)) {
93
+ const firstTokenAfterObject = sourceCode.getTokenAfter(callee.object, astUtils.isNotClosingParenToken);
94
+
93
95
  context.report({
94
96
  node: callee.property,
95
- loc: callee.property.loc.start,
97
+ loc: {
98
+ start: firstTokenAfterObject.loc.start,
99
+ end: callee.loc.end
100
+ },
96
101
  messageId: "expected",
97
102
  data: {
98
103
  callee: getPropertyText(callee)
99
104
  },
100
105
  fix(fixer) {
101
- const firstTokenAfterObject = sourceCode.getTokenAfter(callee.object, astUtils.isNotClosingParenToken);
102
-
103
106
  return fixer.insertTextBefore(firstTokenAfterObject, "\n");
104
107
  }
105
108
  });
@@ -10,6 +10,10 @@
10
10
 
11
11
  module.exports = {
12
12
  meta: {
13
+ deprecated: true,
14
+
15
+ replacedBy: [],
16
+
13
17
  type: "problem",
14
18
 
15
19
  docs: {
@@ -35,7 +35,7 @@ const collector = new (class {
35
35
  try {
36
36
  this._source = regexpStr;
37
37
  this._validator.validatePattern(regexpStr); // Call onCharacter hook
38
- } catch (err) {
38
+ } catch {
39
39
 
40
40
  // Ignore syntax errors in RegExp.
41
41
  }
@@ -151,7 +151,7 @@ module.exports = {
151
151
  ) {
152
152
  context.report({
153
153
  node,
154
- loc: node.body.loc.start,
154
+ loc: node.body.loc,
155
155
  messageId: "unexpected",
156
156
  data: { name }
157
157
  });
@@ -172,6 +172,9 @@ module.exports = {
172
172
  case "UnaryExpression":
173
173
  return precedence(node) < precedence(parent);
174
174
  case "LogicalExpression":
175
+ if (astUtils.isMixedLogicalAndCoalesceExpressions(node, parent)) {
176
+ return true;
177
+ }
175
178
  if (previousNode === parent.left) {
176
179
  return precedence(node) < precedence(parent);
177
180
  }
@@ -51,7 +51,8 @@ module.exports = {
51
51
  ignoreJSX: { enum: ["none", "all", "single-line", "multi-line"] },
52
52
  enforceForArrowConditionals: { type: "boolean" },
53
53
  enforceForSequenceExpressions: { type: "boolean" },
54
- enforceForNewInMemberExpressions: { type: "boolean" }
54
+ enforceForNewInMemberExpressions: { type: "boolean" },
55
+ enforceForFunctionPrototypeMethods: { type: "boolean" }
55
56
  },
56
57
  additionalProperties: false
57
58
  }
@@ -83,12 +84,28 @@ module.exports = {
83
84
  context.options[1].enforceForSequenceExpressions === false;
84
85
  const IGNORE_NEW_IN_MEMBER_EXPR = ALL_NODES && context.options[1] &&
85
86
  context.options[1].enforceForNewInMemberExpressions === false;
87
+ const IGNORE_FUNCTION_PROTOTYPE_METHODS = ALL_NODES && context.options[1] &&
88
+ context.options[1].enforceForFunctionPrototypeMethods === false;
86
89
 
87
90
  const PRECEDENCE_OF_ASSIGNMENT_EXPR = precedence({ type: "AssignmentExpression" });
88
91
  const PRECEDENCE_OF_UPDATE_EXPR = precedence({ type: "UpdateExpression" });
89
92
 
90
93
  let reportsBuffer;
91
94
 
95
+ /**
96
+ * Determines whether the given node is a `call` or `apply` method call, invoked directly on a `FunctionExpression` node.
97
+ * Example: function(){}.call()
98
+ * @param {ASTNode} node The node to be checked.
99
+ * @returns {boolean} True if the node is an immediate `call` or `apply` method call.
100
+ * @private
101
+ */
102
+ function isImmediateFunctionPrototypeMethodCall(node) {
103
+ return node.type === "CallExpression" &&
104
+ node.callee.type === "MemberExpression" &&
105
+ node.callee.object.type === "FunctionExpression" &&
106
+ ["call", "apply"].includes(astUtils.getStaticPropertyName(node.callee));
107
+ }
108
+
92
109
  /**
93
110
  * Determines if this rule should be enforced for a node given the current configuration.
94
111
  * @param {ASTNode} node The node to be checked.
@@ -125,6 +142,10 @@ module.exports = {
125
142
  return false;
126
143
  }
127
144
 
145
+ if (isImmediateFunctionPrototypeMethodCall(node) && IGNORE_FUNCTION_PROTOTYPE_METHODS) {
146
+ return false;
147
+ }
148
+
128
149
  return ALL_NODES || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
129
150
  }
130
151
 
@@ -478,6 +499,7 @@ module.exports = {
478
499
  if (!shouldSkipLeft && hasExcessParens(node.left)) {
479
500
  if (
480
501
  !(node.left.type === "UnaryExpression" && isExponentiation) &&
502
+ !astUtils.isMixedLogicalAndCoalesceExpressions(node.left, node) &&
481
503
  (leftPrecedence > prec || (leftPrecedence === prec && !isExponentiation)) ||
482
504
  isParenthesisedTwice(node.left)
483
505
  ) {
@@ -487,6 +509,7 @@ module.exports = {
487
509
 
488
510
  if (!shouldSkipRight && hasExcessParens(node.right)) {
489
511
  if (
512
+ !astUtils.isMixedLogicalAndCoalesceExpressions(node.right, node) &&
490
513
  (rightPrecedence > prec || (rightPrecedence === prec && isExponentiation)) ||
491
514
  isParenthesisedTwice(node.right)
492
515
  ) {
@@ -560,7 +583,11 @@ module.exports = {
560
583
  tokensToIgnore.add(secondToken);
561
584
  }
562
585
 
563
- if (hasExcessParens(node)) {
586
+ const hasExtraParens = node.parent.type === "ExportDefaultDeclaration"
587
+ ? hasExcessParensWithPrecedence(node, PRECEDENCE_OF_ASSIGNMENT_EXPR)
588
+ : hasExcessParens(node);
589
+
590
+ if (hasExtraParens) {
564
591
  report(node);
565
592
  }
566
593
  }
@@ -923,7 +950,12 @@ module.exports = {
923
950
  LogicalExpression: checkBinaryLogical,
924
951
 
925
952
  MemberExpression(node) {
926
- const nodeObjHasExcessParens = hasExcessParens(node.object);
953
+ const nodeObjHasExcessParens = hasExcessParens(node.object) &&
954
+ !(
955
+ isImmediateFunctionPrototypeMethodCall(node.parent) &&
956
+ node.parent.callee === node &&
957
+ IGNORE_FUNCTION_PROTOTYPE_METHODS
958
+ );
927
959
 
928
960
  if (
929
961
  nodeObjHasExcessParens &&
@@ -5,10 +5,19 @@
5
5
 
6
6
  "use strict";
7
7
 
8
+ //------------------------------------------------------------------------------
9
+ // Requirements
10
+ //------------------------------------------------------------------------------
11
+
12
+ const astUtils = require("./utils/ast-utils");
13
+
8
14
  //------------------------------------------------------------------------------
9
15
  // Rule Definition
10
16
  //------------------------------------------------------------------------------
11
17
 
18
+ const validParent = new Set(["Program", "ExportNamedDeclaration", "ExportDefaultDeclaration"]);
19
+ const validBlockStatementParent = new Set(["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"]);
20
+
12
21
  module.exports = {
13
22
  meta: {
14
23
  type: "problem",
@@ -33,54 +42,37 @@ module.exports = {
33
42
 
34
43
  create(context) {
35
44
 
36
- /**
37
- * Find the nearest Program or Function ancestor node.
38
- * @returns {Object} Ancestor's type and distance from node.
39
- */
40
- function nearestBody() {
41
- const ancestors = context.getAncestors();
42
- let ancestor = ancestors.pop(),
43
- generation = 1;
44
-
45
- while (ancestor && ["Program", "FunctionDeclaration",
46
- "FunctionExpression", "ArrowFunctionExpression"
47
- ].indexOf(ancestor.type) < 0) {
48
- generation += 1;
49
- ancestor = ancestors.pop();
50
- }
51
-
52
- return {
53
-
54
- // Type of containing ancestor
55
- type: ancestor.type,
56
-
57
- // Separation between ancestor and node
58
- distance: generation
59
- };
60
- }
61
-
62
45
  /**
63
46
  * Ensure that a given node is at a program or function body's root.
64
47
  * @param {ASTNode} node Declaration node to check.
65
48
  * @returns {void}
66
49
  */
67
50
  function check(node) {
68
- const body = nearestBody(),
69
- valid = ((body.type === "Program" && body.distance === 1) ||
70
- body.distance === 2);
71
-
72
- if (!valid) {
73
- context.report({
74
- node,
75
- messageId: "moveDeclToRoot",
76
- data: {
77
- type: (node.type === "FunctionDeclaration" ? "function" : "variable"),
78
- body: (body.type === "Program" ? "program" : "function body")
79
- }
80
- });
51
+ const parent = node.parent;
52
+
53
+ if (
54
+ parent.type === "BlockStatement" && validBlockStatementParent.has(parent.parent.type)
55
+ ) {
56
+ return;
57
+ }
58
+
59
+ if (validParent.has(parent.type)) {
60
+ return;
81
61
  }
62
+
63
+ const upperFunction = astUtils.getUpperFunction(parent);
64
+
65
+ context.report({
66
+ node,
67
+ messageId: "moveDeclToRoot",
68
+ data: {
69
+ type: (node.type === "FunctionDeclaration" ? "function" : "variable"),
70
+ body: (upperFunction === null ? "program" : "function body")
71
+ }
72
+ });
82
73
  }
83
74
 
75
+
84
76
  return {
85
77
 
86
78
  FunctionDeclaration: check,
@@ -93,7 +93,7 @@ module.exports = {
93
93
  try {
94
94
  validator.validateFlags(flags);
95
95
  return null;
96
- } catch (err) {
96
+ } catch {
97
97
  return `Invalid flags supplied to RegExp constructor '${flags}'`;
98
98
  }
99
99
  }
@@ -49,7 +49,7 @@ module.exports = {
49
49
  }
50
50
 
51
51
  /**
52
- * Checks for any ocurrence of a BlockStatement in a place where lists of statements can appear
52
+ * Checks for any occurrence of a BlockStatement in a place where lists of statements can appear
53
53
  * @param {ASTNode} node The node to check
54
54
  * @returns {boolean} True if the node is a lone block.
55
55
  */
@@ -0,0 +1,198 @@
1
+ /**
2
+ * @fileoverview Rule to flag numbers that will lose significant figure precision at runtime
3
+ * @author Jacob Moore
4
+ */
5
+
6
+ "use strict";
7
+
8
+ //------------------------------------------------------------------------------
9
+ // Rule Definition
10
+ //------------------------------------------------------------------------------
11
+
12
+ module.exports = {
13
+ meta: {
14
+ type: "problem",
15
+
16
+ docs: {
17
+ description: "disallow literal numbers that lose precision",
18
+ category: "Possible Errors",
19
+ recommended: false,
20
+ url: "https://eslint.org/docs/rules/no-loss-of-precision"
21
+ },
22
+ schema: [],
23
+ messages: {
24
+ noLossOfPrecision: "This number literal will lose precision at runtime."
25
+ }
26
+ },
27
+
28
+ create(context) {
29
+
30
+ /**
31
+ * Returns whether the node is number literal
32
+ * @param {Node} node the node literal being evaluated
33
+ * @returns {boolean} true if the node is a number literal
34
+ */
35
+ function isNumber(node) {
36
+ return typeof node.value === "number";
37
+ }
38
+
39
+
40
+ /**
41
+ * Checks whether the number is base ten
42
+ * @param {ASTNode} node the node being evaluated
43
+ * @returns {boolean} true if the node is in base ten
44
+ */
45
+ function isBaseTen(node) {
46
+ const prefixes = ["0x", "0X", "0b", "0B", "0o", "0O"];
47
+
48
+ return prefixes.every(prefix => !node.raw.startsWith(prefix)) &&
49
+ !/^0[0-7]+$/u.test(node.raw);
50
+ }
51
+
52
+ /**
53
+ * Checks that the user-intended non-base ten number equals the actual number after is has been converted to the Number type
54
+ * @param {Node} node the node being evaluated
55
+ * @returns {boolean} true if they do not match
56
+ */
57
+ function notBaseTenLosesPrecision(node) {
58
+ const rawString = node.raw.toUpperCase();
59
+ let base = 0;
60
+
61
+ if (rawString.startsWith("0B")) {
62
+ base = 2;
63
+ } else if (rawString.startsWith("0X")) {
64
+ base = 16;
65
+ } else {
66
+ base = 8;
67
+ }
68
+
69
+ return !rawString.endsWith(node.value.toString(base).toUpperCase());
70
+ }
71
+
72
+ /**
73
+ * Adds a decimal point to the numeric string at index 1
74
+ * @param {string} stringNumber the numeric string without any decimal point
75
+ * @returns {string} the numeric string with a decimal point in the proper place
76
+ */
77
+ function addDecimalPointToNumber(stringNumber) {
78
+ return `${stringNumber.slice(0, 1)}.${stringNumber.slice(1)}`;
79
+ }
80
+
81
+ /**
82
+ * Returns the number stripped of leading zeros
83
+ * @param {string} numberAsString the string representation of the number
84
+ * @returns {string} the stripped string
85
+ */
86
+ function removeLeadingZeros(numberAsString) {
87
+ return numberAsString.replace(/^0*/u, "");
88
+ }
89
+
90
+ /**
91
+ * Returns the number stripped of trailing zeros
92
+ * @param {string} numberAsString the string representation of the number
93
+ * @returns {string} the stripped string
94
+ */
95
+ function removeTrailingZeros(numberAsString) {
96
+ return numberAsString.replace(/0*$/u, "");
97
+ }
98
+
99
+ /**
100
+ * Converts an integer to to an object containing the the integer's coefficient and order of magnitude
101
+ * @param {string} stringInteger the string representation of the integer being converted
102
+ * @returns {Object} the object containing the the integer's coefficient and order of magnitude
103
+ */
104
+ function normalizeInteger(stringInteger) {
105
+ const significantDigits = removeTrailingZeros(removeLeadingZeros(stringInteger));
106
+
107
+ return {
108
+ magnitude: stringInteger.startsWith("0") ? stringInteger.length - 2 : stringInteger.length - 1,
109
+ coefficient: addDecimalPointToNumber(significantDigits)
110
+ };
111
+ }
112
+
113
+ /**
114
+ *
115
+ * Converts a float to to an object containing the the floats's coefficient and order of magnitude
116
+ * @param {string} stringFloat the string representation of the float being converted
117
+ * @returns {Object} the object containing the the integer's coefficient and order of magnitude
118
+ */
119
+ function normalizeFloat(stringFloat) {
120
+ const trimmedFloat = removeLeadingZeros(stringFloat);
121
+
122
+ if (trimmedFloat.startsWith(".")) {
123
+ const decimalDigits = trimmedFloat.split(".").pop();
124
+ const significantDigits = removeLeadingZeros(decimalDigits);
125
+
126
+ return {
127
+ magnitude: significantDigits.length - decimalDigits.length - 1,
128
+ coefficient: addDecimalPointToNumber(significantDigits)
129
+ };
130
+
131
+ }
132
+ return {
133
+ magnitude: trimmedFloat.indexOf(".") - 1,
134
+ coefficient: addDecimalPointToNumber(trimmedFloat.replace(".", ""))
135
+
136
+ };
137
+ }
138
+
139
+
140
+ /**
141
+ * Converts a base ten number to proper scientific notation
142
+ * @param {string} stringNumber the string representation of the base ten number to be converted
143
+ * @returns {string} the number converted to scientific notation
144
+ */
145
+ function convertNumberToScientificNotation(stringNumber) {
146
+ const splitNumber = stringNumber.replace("E", "e").split("e");
147
+ const originalCoefficient = splitNumber[0];
148
+ const normalizedNumber = stringNumber.includes(".") ? normalizeFloat(originalCoefficient)
149
+ : normalizeInteger(originalCoefficient);
150
+ const normalizedCoefficient = normalizedNumber.coefficient;
151
+ const magnitude = splitNumber.length > 1 ? (parseInt(splitNumber[1], 10) + normalizedNumber.magnitude)
152
+ : normalizedNumber.magnitude;
153
+
154
+ return `${normalizedCoefficient}e${magnitude}`;
155
+
156
+ }
157
+
158
+ /**
159
+ * Checks that the user-intended base ten number equals the actual number after is has been converted to the Number type
160
+ * @param {Node} node the node being evaluated
161
+ * @returns {boolean} true if they do not match
162
+ */
163
+ function baseTenLosesPrecision(node) {
164
+ const normalizedRawNumber = convertNumberToScientificNotation(node.raw);
165
+ const requestedPrecision = normalizedRawNumber.split("e")[0].replace(".", "").length;
166
+
167
+ if (requestedPrecision > 100) {
168
+ return true;
169
+ }
170
+ const storedNumber = node.value.toPrecision(requestedPrecision);
171
+ const normalizedStoredNumber = convertNumberToScientificNotation(storedNumber);
172
+
173
+ return normalizedRawNumber !== normalizedStoredNumber;
174
+ }
175
+
176
+
177
+ /**
178
+ * Checks that the user-intended number equals the actual number after is has been converted to the Number type
179
+ * @param {Node} node the node being evaluated
180
+ * @returns {boolean} true if they do not match
181
+ */
182
+ function losesPrecision(node) {
183
+ return isBaseTen(node) ? baseTenLosesPrecision(node) : notBaseTenLosesPrecision(node);
184
+ }
185
+
186
+
187
+ return {
188
+ Literal(node) {
189
+ if (node.value && isNumber(node) && losesPrecision(node)) {
190
+ context.report({
191
+ messageId: "noLossOfPrecision",
192
+ node
193
+ });
194
+ }
195
+ }
196
+ };
197
+ }
198
+ };
@@ -147,7 +147,7 @@ module.exports = {
147
147
  pattern.length,
148
148
  flags.includes("u")
149
149
  );
150
- } catch (e) {
150
+ } catch {
151
151
 
152
152
  // Ignore regular expressions with syntax errors
153
153
  return;
@@ -21,13 +21,15 @@ const COMPARISON_OPERATORS = ["==", "!=", "===", "!==", ">", ">=", "<", "<="];
21
21
  const LOGICAL_OPERATORS = ["&&", "||"];
22
22
  const RELATIONAL_OPERATORS = ["in", "instanceof"];
23
23
  const TERNARY_OPERATOR = ["?:"];
24
+ const COALESCE_OPERATOR = ["??"];
24
25
  const ALL_OPERATORS = [].concat(
25
26
  ARITHMETIC_OPERATORS,
26
27
  BITWISE_OPERATORS,
27
28
  COMPARISON_OPERATORS,
28
29
  LOGICAL_OPERATORS,
29
30
  RELATIONAL_OPERATORS,
30
- TERNARY_OPERATOR
31
+ TERNARY_OPERATOR,
32
+ COALESCE_OPERATOR
31
33
  );
32
34
  const DEFAULT_GROUPS = [
33
35
  ARITHMETIC_OPERATORS,
@@ -236,7 +238,6 @@ module.exports = {
236
238
  return {
237
239
  BinaryExpression: check,
238
240
  LogicalExpression: check
239
-
240
241
  };
241
242
  }
242
243
  };
@@ -11,6 +11,10 @@
11
11
 
12
12
  module.exports = {
13
13
  meta: {
14
+ deprecated: true,
15
+
16
+ replacedBy: [],
17
+
14
18
  type: "suggestion",
15
19
 
16
20
  docs: {
@@ -67,7 +67,7 @@ module.exports = {
67
67
  * or the reverse before non-tab/-space
68
68
  * characters begin.
69
69
  */
70
- let regex = /^(?=[\t ]*(\t | \t))/u;
70
+ let regex = /^(?=( +|\t+))\1(?:\t| )/u;
71
71
 
72
72
  if (smartTabs) {
73
73
 
@@ -75,19 +75,27 @@ module.exports = {
75
75
  * At least one space followed by a tab
76
76
  * before non-tab/-space characters begin.
77
77
  */
78
- regex = /^(?=[\t ]* \t)/u;
78
+ regex = /^(?=(\t*))\1(?=( +))\2\t/u;
79
79
  }
80
80
 
81
81
  lines.forEach((line, i) => {
82
82
  const match = regex.exec(line);
83
83
 
84
84
  if (match) {
85
- const lineNumber = i + 1,
86
- column = match.index + 1,
87
- loc = { line: lineNumber, column };
85
+ const lineNumber = i + 1;
86
+ const loc = {
87
+ start: {
88
+ line: lineNumber,
89
+ column: match[0].length - 2
90
+ },
91
+ end: {
92
+ line: lineNumber,
93
+ column: match[0].length
94
+ }
95
+ };
88
96
 
89
97
  if (!ignoredCommentLines.has(lineNumber)) {
90
- const containingNode = sourceCode.getNodeByRangeIndex(sourceCode.getIndexFromLoc(loc));
98
+ const containingNode = sourceCode.getNodeByRangeIndex(sourceCode.getIndexFromLoc(loc.start));
91
99
 
92
100
  if (!(containingNode && ["Literal", "TemplateElement"].includes(containingNode.type))) {
93
101
  context.report({