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.
- package/CHANGELOG.md +332 -0
- package/README.md +9 -10
- package/bin/eslint.js +115 -77
- package/conf/category-list.json +0 -1
- package/conf/environments.js +2 -1
- package/lib/api.js +2 -0
- package/lib/cli-engine/cascading-config-array-factory.js +16 -2
- package/lib/cli-engine/cli-engine.js +53 -47
- package/lib/cli-engine/config-array/config-array.js +30 -1
- package/lib/cli-engine/config-array/ignore-pattern.js +7 -1
- package/lib/cli-engine/config-array-factory.js +244 -235
- package/lib/cli.js +181 -95
- package/lib/eslint/eslint.js +656 -0
- package/lib/eslint/index.js +7 -0
- package/lib/init/autoconfig.js +4 -4
- package/lib/init/config-file.js +2 -2
- package/lib/init/config-initializer.js +3 -4
- package/lib/init/source-code-utils.js +2 -2
- package/lib/linter/linter.js +2 -1
- package/lib/linter/node-event-generator.js +2 -2
- package/lib/options.js +0 -1
- package/lib/rule-tester/rule-tester.js +132 -22
- package/lib/rules/accessor-pairs.js +1 -1
- package/lib/rules/array-callback-return.js +3 -18
- package/lib/rules/arrow-parens.js +19 -3
- package/lib/rules/block-spacing.js +19 -2
- package/lib/rules/callback-return.js +4 -0
- package/lib/rules/camelcase.js +38 -1
- package/lib/rules/comma-style.js +3 -8
- package/lib/rules/func-call-spacing.js +4 -3
- package/lib/rules/getter-return.js +2 -12
- package/lib/rules/global-require.js +4 -0
- package/lib/rules/handle-callback-err.js +4 -0
- package/lib/rules/id-blacklist.js +138 -102
- package/lib/rules/index.js +1 -0
- package/lib/rules/key-spacing.js +1 -1
- package/lib/rules/linebreak-style.js +8 -2
- package/lib/rules/max-lines-per-function.js +1 -1
- package/lib/rules/new-cap.js +1 -1
- package/lib/rules/newline-per-chained-call.js +6 -3
- package/lib/rules/no-alert.js +5 -3
- package/lib/rules/no-buffer-constructor.js +4 -0
- package/lib/rules/no-empty-function.js +4 -2
- package/lib/rules/no-eval.js +2 -1
- package/lib/rules/no-extra-bind.js +1 -1
- package/lib/rules/no-extra-boolean-cast.js +102 -23
- package/lib/rules/no-extra-parens.js +9 -5
- package/lib/rules/no-implied-eval.js +83 -101
- package/lib/rules/no-inner-declarations.js +31 -39
- package/lib/rules/no-lone-blocks.js +1 -1
- package/lib/rules/no-loss-of-precision.js +198 -0
- package/lib/rules/no-magic-numbers.js +72 -37
- package/lib/rules/no-mixed-requires.js +4 -0
- package/lib/rules/no-new-func.js +22 -19
- package/lib/rules/no-new-object.js +15 -3
- package/lib/rules/no-new-require.js +4 -0
- package/lib/rules/no-new-symbol.js +2 -1
- package/lib/rules/no-new-wrappers.js +1 -1
- package/lib/rules/no-obj-calls.js +24 -5
- package/lib/rules/no-path-concat.js +4 -0
- package/lib/rules/no-plusplus.js +39 -3
- package/lib/rules/no-process-env.js +4 -0
- package/lib/rules/no-process-exit.js +4 -0
- package/lib/rules/no-prototype-builtins.js +1 -1
- package/lib/rules/no-restricted-modules.js +4 -0
- package/lib/rules/no-sync.js +4 -0
- package/lib/rules/no-unexpected-multiline.js +22 -12
- package/lib/rules/no-useless-concat.js +1 -1
- package/lib/rules/one-var-declaration-per-line.js +1 -1
- package/lib/rules/operator-assignment.js +3 -3
- package/lib/rules/operator-linebreak.js +4 -16
- package/lib/rules/padded-blocks.js +17 -4
- package/lib/rules/prefer-numeric-literals.js +3 -3
- package/lib/rules/prefer-object-spread.js +2 -2
- package/lib/rules/require-await.js +1 -1
- package/lib/rules/rest-spread-spacing.js +3 -6
- package/lib/rules/semi-spacing.js +32 -8
- package/lib/rules/space-before-function-paren.js +5 -2
- package/lib/rules/template-curly-spacing.js +59 -42
- package/lib/rules/utils/ast-utils.js +116 -10
- package/lib/rules/yoda.js +101 -51
- package/lib/shared/relative-module-resolver.js +1 -0
- package/lib/shared/types.js +9 -2
- package/lib/source-code/source-code.js +1 -0
- package/messages/extend-config-missing.txt +1 -1
- package/messages/no-config-found.txt +1 -1
- package/messages/plugin-conflict.txt +7 -0
- package/messages/plugin-missing.txt +1 -1
- package/messages/whitespace-found.txt +1 -1
- 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(
|
311
|
-
const
|
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] ===
|
316
|
-
!astUtils.canTokensBeAdjacent(tokenBeforeLeftParen,
|
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
|
-
|
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
|
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
|
-
*
|
45
|
-
* @param {
|
46
|
-
* @returns {
|
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
|
50
|
-
|
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
|
55
|
-
* @param {ASTNode} node
|
56
|
-
* @
|
57
|
-
* @
|
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
|
60
|
-
|
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
|
-
*
|
69
|
-
*
|
70
|
-
*
|
71
|
-
* @param {
|
72
|
-
* @returns {boolean}
|
73
|
-
*
|
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
|
76
|
-
|
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
|
-
*
|
87
|
-
* @param {ASTNode} node
|
88
|
-
* @returns {
|
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
|
86
|
+
function reportImpliedEvalCallExpression(node) {
|
87
|
+
const [firstArgument] = node.arguments;
|
92
88
|
|
93
|
-
|
94
|
-
|
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
|
-
*
|
102
|
-
*
|
103
|
-
*
|
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
|
109
|
-
|
110
|
+
function reportImpliedEvalViaGlobal(globalVar) {
|
111
|
+
const { references, name } = globalVar;
|
110
112
|
|
111
|
-
|
112
|
-
const
|
113
|
+
references.forEach(ref => {
|
114
|
+
const identifier = ref.identifier;
|
115
|
+
let node = identifier.parent;
|
113
116
|
|
114
|
-
|
115
|
-
node
|
116
|
-
|
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 (
|
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
|
-
|
165
|
-
|
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
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|
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
|
+
};
|