eslint 7.0.0-alpha.2 → 7.1.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 (90) hide show
  1. package/CHANGELOG.md +332 -0
  2. package/README.md +9 -10
  3. package/bin/eslint.js +115 -77
  4. package/conf/category-list.json +0 -1
  5. package/conf/environments.js +2 -1
  6. package/lib/api.js +2 -0
  7. package/lib/cli-engine/cascading-config-array-factory.js +16 -2
  8. package/lib/cli-engine/cli-engine.js +53 -47
  9. package/lib/cli-engine/config-array/config-array.js +30 -1
  10. package/lib/cli-engine/config-array/ignore-pattern.js +7 -1
  11. package/lib/cli-engine/config-array-factory.js +244 -235
  12. package/lib/cli.js +181 -95
  13. package/lib/eslint/eslint.js +656 -0
  14. package/lib/eslint/index.js +7 -0
  15. package/lib/init/autoconfig.js +4 -4
  16. package/lib/init/config-file.js +2 -2
  17. package/lib/init/config-initializer.js +3 -4
  18. package/lib/init/source-code-utils.js +2 -2
  19. package/lib/linter/linter.js +2 -1
  20. package/lib/linter/node-event-generator.js +2 -2
  21. package/lib/options.js +0 -1
  22. package/lib/rule-tester/rule-tester.js +132 -22
  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/camelcase.js +38 -1
  29. package/lib/rules/comma-style.js +3 -8
  30. package/lib/rules/func-call-spacing.js +4 -3
  31. package/lib/rules/getter-return.js +2 -12
  32. package/lib/rules/global-require.js +4 -0
  33. package/lib/rules/handle-callback-err.js +4 -0
  34. package/lib/rules/id-blacklist.js +138 -102
  35. package/lib/rules/index.js +1 -0
  36. package/lib/rules/key-spacing.js +1 -1
  37. package/lib/rules/linebreak-style.js +8 -2
  38. package/lib/rules/max-lines-per-function.js +1 -1
  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-alert.js +5 -3
  42. package/lib/rules/no-buffer-constructor.js +4 -0
  43. package/lib/rules/no-empty-function.js +4 -2
  44. package/lib/rules/no-eval.js +2 -1
  45. package/lib/rules/no-extra-bind.js +1 -1
  46. package/lib/rules/no-extra-boolean-cast.js +102 -23
  47. package/lib/rules/no-extra-parens.js +9 -5
  48. package/lib/rules/no-implied-eval.js +83 -101
  49. package/lib/rules/no-inner-declarations.js +31 -39
  50. package/lib/rules/no-lone-blocks.js +1 -1
  51. package/lib/rules/no-loss-of-precision.js +198 -0
  52. package/lib/rules/no-magic-numbers.js +72 -37
  53. package/lib/rules/no-mixed-requires.js +4 -0
  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-new-wrappers.js +1 -1
  59. package/lib/rules/no-obj-calls.js +24 -5
  60. package/lib/rules/no-path-concat.js +4 -0
  61. package/lib/rules/no-plusplus.js +39 -3
  62. package/lib/rules/no-process-env.js +4 -0
  63. package/lib/rules/no-process-exit.js +4 -0
  64. package/lib/rules/no-prototype-builtins.js +1 -1
  65. package/lib/rules/no-restricted-modules.js +4 -0
  66. package/lib/rules/no-sync.js +4 -0
  67. package/lib/rules/no-unexpected-multiline.js +22 -12
  68. package/lib/rules/no-useless-concat.js +1 -1
  69. package/lib/rules/one-var-declaration-per-line.js +1 -1
  70. package/lib/rules/operator-assignment.js +3 -3
  71. package/lib/rules/operator-linebreak.js +4 -16
  72. package/lib/rules/padded-blocks.js +17 -4
  73. package/lib/rules/prefer-numeric-literals.js +3 -3
  74. package/lib/rules/prefer-object-spread.js +2 -2
  75. package/lib/rules/require-await.js +1 -1
  76. package/lib/rules/rest-spread-spacing.js +3 -6
  77. package/lib/rules/semi-spacing.js +32 -8
  78. package/lib/rules/space-before-function-paren.js +5 -2
  79. package/lib/rules/template-curly-spacing.js +59 -42
  80. package/lib/rules/utils/ast-utils.js +116 -10
  81. package/lib/rules/yoda.js +101 -51
  82. package/lib/shared/relative-module-resolver.js +1 -0
  83. package/lib/shared/types.js +9 -2
  84. package/lib/source-code/source-code.js +1 -0
  85. package/messages/extend-config-missing.txt +1 -1
  86. package/messages/no-config-found.txt +1 -1
  87. package/messages/plugin-conflict.txt +7 -0
  88. package/messages/plugin-missing.txt +1 -1
  89. package/messages/whitespace-found.txt +1 -1
  90. package/package.json +27 -26
@@ -307,13 +307,13 @@ module.exports = {
307
307
  */
308
308
  function requiresLeadingSpace(node) {
309
309
  const leftParenToken = sourceCode.getTokenBefore(node);
310
- const tokenBeforeLeftParen = sourceCode.getTokenBefore(node, 1);
311
- const firstToken = sourceCode.getFirstToken(node);
310
+ const tokenBeforeLeftParen = sourceCode.getTokenBefore(leftParenToken, { includeComments: true });
311
+ const tokenAfterLeftParen = sourceCode.getTokenAfter(leftParenToken, { includeComments: true });
312
312
 
313
313
  return tokenBeforeLeftParen &&
314
314
  tokenBeforeLeftParen.range[1] === leftParenToken.range[0] &&
315
- leftParenToken.range[1] === firstToken.range[0] &&
316
- !astUtils.canTokensBeAdjacent(tokenBeforeLeftParen, firstToken);
315
+ leftParenToken.range[1] === tokenAfterLeftParen.range[0] &&
316
+ !astUtils.canTokensBeAdjacent(tokenBeforeLeftParen, tokenAfterLeftParen);
317
317
  }
318
318
 
319
319
  /**
@@ -560,7 +560,11 @@ module.exports = {
560
560
  tokensToIgnore.add(secondToken);
561
561
  }
562
562
 
563
- if (hasExcessParens(node)) {
563
+ const hasExtraParens = node.parent.type === "ExportDefaultDeclaration"
564
+ ? hasExcessParensWithPrecedence(node, PRECEDENCE_OF_ASSIGNMENT_EXPR)
565
+ : hasExcessParens(node);
566
+
567
+ if (hasExtraParens) {
564
568
  report(node);
565
569
  }
566
570
  }
@@ -5,6 +5,13 @@
5
5
 
6
6
  "use strict";
7
7
 
8
+ //------------------------------------------------------------------------------
9
+ // Requirements
10
+ //------------------------------------------------------------------------------
11
+
12
+ const astUtils = require("./utils/ast-utils");
13
+ const { getStaticValue } = require("eslint-utils");
14
+
8
15
  //------------------------------------------------------------------------------
9
16
  // Rule Definition
10
17
  //------------------------------------------------------------------------------
@@ -28,94 +35,97 @@ module.exports = {
28
35
  },
29
36
 
30
37
  create(context) {
31
- const CALLEE_RE = /^(setTimeout|setInterval|execScript)$/u;
32
-
33
- /*
34
- * Figures out if we should inspect a given binary expression. Is a stack
35
- * of stacks, where the first element in each substack is a CallExpression.
36
- */
37
- const impliedEvalAncestorsStack = [];
38
-
39
- //--------------------------------------------------------------------------
40
- // Helpers
41
- //--------------------------------------------------------------------------
38
+ const EVAL_LIKE_FUNCS = Object.freeze(["setTimeout", "execScript", "setInterval"]);
39
+ const GLOBAL_CANDIDATES = Object.freeze(["global", "window", "globalThis"]);
42
40
 
43
41
  /**
44
- * Get the last element of an array, without modifying arr, like pop(), but non-destructive.
45
- * @param {Array} arr What to inspect
46
- * @returns {*} The last element of arr
47
- * @private
42
+ * Checks whether a node is evaluated as a string or not.
43
+ * @param {ASTNode} node A node to check.
44
+ * @returns {boolean} True if the node is evaluated as a string.
48
45
  */
49
- function last(arr) {
50
- return arr ? arr[arr.length - 1] : null;
46
+ function isEvaluatedString(node) {
47
+ if (
48
+ (node.type === "Literal" && typeof node.value === "string") ||
49
+ node.type === "TemplateLiteral"
50
+ ) {
51
+ return true;
52
+ }
53
+ if (node.type === "BinaryExpression" && node.operator === "+") {
54
+ return isEvaluatedString(node.left) || isEvaluatedString(node.right);
55
+ }
56
+ return false;
51
57
  }
52
58
 
53
59
  /**
54
- * Checks if the given MemberExpression node is a potentially implied eval identifier on window.
55
- * @param {ASTNode} node The MemberExpression node to check.
56
- * @returns {boolean} Whether or not the given node is potentially an implied eval.
57
- * @private
60
+ * Checks whether a node is an Identifier node named one of the specified names.
61
+ * @param {ASTNode} node A node to check.
62
+ * @param {string[]} specifiers Array of specified name.
63
+ * @returns {boolean} True if the node is a Identifier node which has specified name.
58
64
  */
59
- function isImpliedEvalMemberExpression(node) {
60
- const object = node.object,
61
- property = node.property,
62
- hasImpliedEvalName = CALLEE_RE.test(property.name) || CALLEE_RE.test(property.value);
63
-
64
- return object.name === "window" && hasImpliedEvalName;
65
+ function isSpecifiedIdentifier(node, specifiers) {
66
+ return node.type === "Identifier" && specifiers.includes(node.name);
65
67
  }
66
68
 
67
69
  /**
68
- * Determines if a node represents a call to a potentially implied eval.
69
- *
70
- * This checks the callee name and that there's an argument, but not the type of the argument.
71
- * @param {ASTNode} node The CallExpression to check.
72
- * @returns {boolean} True if the node matches, false if not.
73
- * @private
70
+ * Checks a given node is a MemberExpression node which has the specified name's
71
+ * property.
72
+ * @param {ASTNode} node A node to check.
73
+ * @param {string[]} specifiers Array of specified name.
74
+ * @returns {boolean} `true` if the node is a MemberExpression node which has
75
+ * the specified name's property
74
76
  */
75
- function isImpliedEvalCallExpression(node) {
76
- const isMemberExpression = (node.callee.type === "MemberExpression"),
77
- isIdentifier = (node.callee.type === "Identifier"),
78
- isImpliedEvalCallee =
79
- (isIdentifier && CALLEE_RE.test(node.callee.name)) ||
80
- (isMemberExpression && isImpliedEvalMemberExpression(node.callee));
81
-
82
- return isImpliedEvalCallee && node.arguments.length;
77
+ function isSpecifiedMember(node, specifiers) {
78
+ return node.type === "MemberExpression" && specifiers.includes(astUtils.getStaticPropertyName(node));
83
79
  }
84
80
 
85
81
  /**
86
- * Checks that the parent is a direct descendent of an potential implied eval CallExpression, and if the parent is a CallExpression, that we're the first argument.
87
- * @param {ASTNode} node The node to inspect the parent of.
88
- * @returns {boolean} Was the parent a direct descendent, and is the child therefore potentially part of a dangerous argument?
89
- * @private
82
+ * Reports if the `CallExpression` node has evaluated argument.
83
+ * @param {ASTNode} node A CallExpression to check.
84
+ * @returns {void}
90
85
  */
91
- function hasImpliedEvalParent(node) {
86
+ function reportImpliedEvalCallExpression(node) {
87
+ const [firstArgument] = node.arguments;
92
88
 
93
- // make sure our parent is marked
94
- return node.parent === last(last(impliedEvalAncestorsStack)) &&
89
+ if (firstArgument) {
90
+
91
+ const staticValue = getStaticValue(firstArgument, context.getScope());
92
+ const isStaticString = staticValue && typeof staticValue.value === "string";
93
+ const isString = isStaticString || isEvaluatedString(firstArgument);
94
+
95
+ if (isString) {
96
+ context.report({
97
+ node,
98
+ messageId: "impliedEval"
99
+ });
100
+ }
101
+ }
95
102
 
96
- // if our parent is a CallExpression, make sure we're the first argument
97
- (node.parent.type !== "CallExpression" || node === node.parent.arguments[0]);
98
103
  }
99
104
 
100
105
  /**
101
- * Checks if our parent is marked as part of an implied eval argument. If
102
- * so, collapses the top of impliedEvalAncestorsStack and reports on the
103
- * original CallExpression.
104
- * @param {ASTNode} node The CallExpression to check.
105
- * @returns {boolean} True if the node matches, false if not.
106
- * @private
106
+ * Reports calls of `implied eval` via the global references.
107
+ * @param {Variable} globalVar A global variable to check.
108
+ * @returns {void}
107
109
  */
108
- function checkString(node) {
109
- if (hasImpliedEvalParent(node)) {
110
+ function reportImpliedEvalViaGlobal(globalVar) {
111
+ const { references, name } = globalVar;
110
112
 
111
- // remove the entire substack, to avoid duplicate reports
112
- const substack = impliedEvalAncestorsStack.pop();
113
+ references.forEach(ref => {
114
+ const identifier = ref.identifier;
115
+ let node = identifier.parent;
113
116
 
114
- context.report({
115
- node: substack[0],
116
- messageId: "impliedEval"
117
- });
118
- }
117
+ while (isSpecifiedMember(node, [name])) {
118
+ node = node.parent;
119
+ }
120
+
121
+ if (isSpecifiedMember(node, EVAL_LIKE_FUNCS)) {
122
+ const parent = node.parent;
123
+
124
+ if (parent.type === "CallExpression" && parent.callee === node) {
125
+ reportImpliedEvalCallExpression(parent);
126
+ }
127
+ }
128
+ });
119
129
  }
120
130
 
121
131
  //--------------------------------------------------------------------------
@@ -124,45 +134,17 @@ module.exports = {
124
134
 
125
135
  return {
126
136
  CallExpression(node) {
127
- if (isImpliedEvalCallExpression(node)) {
128
-
129
- // call expressions create a new substack
130
- impliedEvalAncestorsStack.push([node]);
131
- }
132
- },
133
-
134
- "CallExpression:exit"(node) {
135
- if (node === last(last(impliedEvalAncestorsStack))) {
136
-
137
- /*
138
- * Destroys the entire sub-stack, rather than just using
139
- * last(impliedEvalAncestorsStack).pop(), as a CallExpression is
140
- * always the bottom of a impliedEvalAncestorsStack substack.
141
- */
142
- impliedEvalAncestorsStack.pop();
143
- }
144
- },
145
-
146
- BinaryExpression(node) {
147
- if (node.operator === "+" && hasImpliedEvalParent(node)) {
148
- last(impliedEvalAncestorsStack).push(node);
149
- }
150
- },
151
-
152
- "BinaryExpression:exit"(node) {
153
- if (node === last(last(impliedEvalAncestorsStack))) {
154
- last(impliedEvalAncestorsStack).pop();
155
- }
156
- },
157
-
158
- Literal(node) {
159
- if (typeof node.value === "string") {
160
- checkString(node);
137
+ if (isSpecifiedIdentifier(node.callee, EVAL_LIKE_FUNCS)) {
138
+ reportImpliedEvalCallExpression(node);
161
139
  }
162
140
  },
141
+ "Program:exit"() {
142
+ const globalScope = context.getScope();
163
143
 
164
- TemplateLiteral(node) {
165
- checkString(node);
144
+ GLOBAL_CANDIDATES
145
+ .map(candidate => astUtils.getVariableByName(globalScope, candidate))
146
+ .filter(globalVar => !!globalVar && globalVar.defs.length === 0)
147
+ .forEach(reportImpliedEvalViaGlobal);
166
148
  }
167
149
  };
168
150
 
@@ -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,
@@ -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
+ };