eslint 8.43.0 → 8.45.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.
@@ -188,15 +188,19 @@ class TokenInfo {
188
188
  */
189
189
  constructor(sourceCode) {
190
190
  this.sourceCode = sourceCode;
191
- this.firstTokensByLineNumber = sourceCode.tokensAndComments.reduce((map, token) => {
192
- if (!map.has(token.loc.start.line)) {
193
- map.set(token.loc.start.line, token);
191
+ this.firstTokensByLineNumber = new Map();
192
+ const tokens = sourceCode.tokensAndComments;
193
+
194
+ for (let i = 0; i < tokens.length; i++) {
195
+ const token = tokens[i];
196
+
197
+ if (!this.firstTokensByLineNumber.has(token.loc.start.line)) {
198
+ this.firstTokensByLineNumber.set(token.loc.start.line, token);
194
199
  }
195
- if (!map.has(token.loc.end.line) && sourceCode.text.slice(token.range[1] - token.loc.end.column, token.range[1]).trim()) {
196
- map.set(token.loc.end.line, token);
200
+ if (!this.firstTokensByLineNumber.has(token.loc.end.line) && sourceCode.text.slice(token.range[1] - token.loc.end.column, token.range[1]).trim()) {
201
+ this.firstTokensByLineNumber.set(token.loc.end.line, token);
197
202
  }
198
- return map;
199
- }, new Map());
203
+ }
200
204
  }
201
205
 
202
206
  /**
@@ -964,19 +968,19 @@ module.exports = {
964
968
  const parenStack = [];
965
969
  const parenPairs = [];
966
970
 
967
- tokens.forEach(nextToken => {
971
+ for (let i = 0; i < tokens.length; i++) {
972
+ const nextToken = tokens[i];
968
973
 
969
- // Accumulate a list of parenthesis pairs
970
974
  if (astUtils.isOpeningParenToken(nextToken)) {
971
975
  parenStack.push(nextToken);
972
976
  } else if (astUtils.isClosingParenToken(nextToken)) {
973
- parenPairs.unshift({ left: parenStack.pop(), right: nextToken });
977
+ parenPairs.push({ left: parenStack.pop(), right: nextToken });
974
978
  }
975
- });
979
+ }
976
980
 
977
- parenPairs.forEach(pair => {
978
- const leftParen = pair.left;
979
- const rightParen = pair.right;
981
+ for (let i = parenPairs.length - 1; i >= 0; i--) {
982
+ const leftParen = parenPairs[i].left;
983
+ const rightParen = parenPairs[i].right;
980
984
 
981
985
  // We only want to handle parens around expressions, so exclude parentheses that are in function parameters and function call arguments.
982
986
  if (!parameterParens.has(leftParen) && !parameterParens.has(rightParen)) {
@@ -990,7 +994,7 @@ module.exports = {
990
994
  }
991
995
 
992
996
  offsets.setDesiredOffset(rightParen, leftParen, 0);
993
- });
997
+ }
994
998
  }
995
999
 
996
1000
  /**
@@ -1246,7 +1250,7 @@ module.exports = {
1246
1250
 
1247
1251
  IfStatement(node) {
1248
1252
  addBlocklessNodeIndent(node.consequent);
1249
- if (node.alternate && node.alternate.type !== "IfStatement") {
1253
+ if (node.alternate) {
1250
1254
  addBlocklessNodeIndent(node.alternate);
1251
1255
  }
1252
1256
  },
@@ -1711,9 +1715,13 @@ module.exports = {
1711
1715
  }
1712
1716
 
1713
1717
  // Invoke the queued offset listeners for the nodes that aren't ignored.
1714
- listenerCallQueue
1715
- .filter(nodeInfo => !ignoredNodes.has(nodeInfo.node))
1716
- .forEach(nodeInfo => nodeInfo.listener(nodeInfo.node));
1718
+ for (let i = 0; i < listenerCallQueue.length; i++) {
1719
+ const nodeInfo = listenerCallQueue[i];
1720
+
1721
+ if (!ignoredNodes.has(nodeInfo.node)) {
1722
+ nodeInfo.listener(nodeInfo.node);
1723
+ }
1724
+ }
1717
1725
 
1718
1726
  // Update the offsets for ignored nodes to prevent their child tokens from being reported.
1719
1727
  ignoredNodes.forEach(ignoreNode);
@@ -1724,27 +1732,31 @@ module.exports = {
1724
1732
  * Create a Map from (tokenOrComment) => (precedingToken).
1725
1733
  * This is necessary because sourceCode.getTokenBefore does not handle a comment as an argument correctly.
1726
1734
  */
1727
- const precedingTokens = sourceCode.ast.comments.reduce((commentMap, comment) => {
1735
+ const precedingTokens = new WeakMap();
1736
+
1737
+ for (let i = 0; i < sourceCode.ast.comments.length; i++) {
1738
+ const comment = sourceCode.ast.comments[i];
1739
+
1728
1740
  const tokenOrCommentBefore = sourceCode.getTokenBefore(comment, { includeComments: true });
1741
+ const hasToken = precedingTokens.has(tokenOrCommentBefore) ? precedingTokens.get(tokenOrCommentBefore) : tokenOrCommentBefore;
1729
1742
 
1730
- return commentMap.set(comment, commentMap.has(tokenOrCommentBefore) ? commentMap.get(tokenOrCommentBefore) : tokenOrCommentBefore);
1731
- }, new WeakMap());
1743
+ precedingTokens.set(comment, hasToken);
1744
+ }
1732
1745
 
1733
- sourceCode.lines.forEach((line, lineIndex) => {
1734
- const lineNumber = lineIndex + 1;
1746
+ for (let i = 1; i < sourceCode.lines.length + 1; i++) {
1735
1747
 
1736
- if (!tokenInfo.firstTokensByLineNumber.has(lineNumber)) {
1748
+ if (!tokenInfo.firstTokensByLineNumber.has(i)) {
1737
1749
 
1738
1750
  // Don't check indentation on blank lines
1739
- return;
1751
+ continue;
1740
1752
  }
1741
1753
 
1742
- const firstTokenOfLine = tokenInfo.firstTokensByLineNumber.get(lineNumber);
1754
+ const firstTokenOfLine = tokenInfo.firstTokensByLineNumber.get(i);
1743
1755
 
1744
- if (firstTokenOfLine.loc.start.line !== lineNumber) {
1756
+ if (firstTokenOfLine.loc.start.line !== i) {
1745
1757
 
1746
1758
  // Don't check the indentation of multi-line tokens (e.g. template literals or block comments) twice.
1747
- return;
1759
+ continue;
1748
1760
  }
1749
1761
 
1750
1762
  if (astUtils.isCommentToken(firstTokenOfLine)) {
@@ -1769,18 +1781,18 @@ module.exports = {
1769
1781
  mayAlignWithBefore && validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(tokenBefore)) ||
1770
1782
  mayAlignWithAfter && validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(tokenAfter))
1771
1783
  ) {
1772
- return;
1784
+ continue;
1773
1785
  }
1774
1786
  }
1775
1787
 
1776
1788
  // If the token matches the expected indentation, don't report it.
1777
1789
  if (validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(firstTokenOfLine))) {
1778
- return;
1790
+ continue;
1779
1791
  }
1780
1792
 
1781
1793
  // Otherwise, report the token/comment.
1782
1794
  report(firstTokenOfLine, offsets.getDesiredIndent(firstTokenOfLine));
1783
- });
1795
+ }
1784
1796
  }
1785
1797
  }
1786
1798
  );
@@ -370,8 +370,11 @@ module.exports = {
370
370
  return;
371
371
  }
372
372
 
373
- const requiresOuterParenthesis = logical.parent.type !== "ExpressionStatement" &&
374
- (astUtils.getPrecedence({ type: "AssignmentExpression" }) < astUtils.getPrecedence(logical.parent));
373
+ const parentPrecedence = astUtils.getPrecedence(logical.parent);
374
+ const requiresOuterParenthesis = logical.parent.type !== "ExpressionStatement" && (
375
+ parentPrecedence === -1 ||
376
+ astUtils.getPrecedence({ type: "AssignmentExpression" }) < parentPrecedence
377
+ );
375
378
 
376
379
  if (!astUtils.isParenthesised(sourceCode, logical) && requiresOuterParenthesis) {
377
380
  yield ruleFixer.insertTextBefore(logical, "(");
@@ -252,19 +252,23 @@ module.exports = {
252
252
  return sourceCode.ast.tokens.filter(token => token.type === "RegularExpression");
253
253
  }
254
254
 
255
-
256
255
  /**
257
- * A reducer to group an AST node by line number, both start and end.
258
- * @param {Object} acc the accumulator
259
- * @param {ASTNode} node the AST node in question
260
- * @returns {Object} the modified accumulator
261
- * @private
256
+ *
257
+ * reduce an array of AST nodes by line number, both start and end.
258
+ * @param {ASTNode[]} arr array of AST nodes
259
+ * @returns {Object} accululated AST nodes
262
260
  */
263
- function groupByLineNumber(acc, node) {
264
- for (let i = node.loc.start.line; i <= node.loc.end.line; ++i) {
265
- ensureArrayAndPush(acc, i, node);
261
+ function groupArrayByLineNumber(arr) {
262
+ const obj = {};
263
+
264
+ for (let i = 0; i < arr.length; i++) {
265
+ const node = arr[i];
266
+
267
+ for (let j = node.loc.start.line; j <= node.loc.end.line; ++j) {
268
+ ensureArrayAndPush(obj, j, node);
269
+ }
266
270
  }
267
- return acc;
271
+ return obj;
268
272
  }
269
273
 
270
274
  /**
@@ -312,13 +316,13 @@ module.exports = {
312
316
  let commentsIndex = 0;
313
317
 
314
318
  const strings = getAllStrings();
315
- const stringsByLine = strings.reduce(groupByLineNumber, {});
319
+ const stringsByLine = groupArrayByLineNumber(strings);
316
320
 
317
321
  const templateLiterals = getAllTemplateLiterals();
318
- const templateLiteralsByLine = templateLiterals.reduce(groupByLineNumber, {});
322
+ const templateLiteralsByLine = groupArrayByLineNumber(templateLiterals);
319
323
 
320
324
  const regExpLiterals = getAllRegExpLiterals();
321
- const regExpLiteralsByLine = regExpLiterals.reduce(groupByLineNumber, {});
325
+ const regExpLiteralsByLine = groupArrayByLineNumber(regExpLiterals);
322
326
 
323
327
  lines.forEach((line, i) => {
324
328
 
@@ -46,6 +46,7 @@ module.exports = {
46
46
  type: "object",
47
47
  properties: {
48
48
  conditionalAssign: { type: "boolean" },
49
+ ternaryOperandBinaryExpressions: { type: "boolean" },
49
50
  nestedBinaryExpressions: { type: "boolean" },
50
51
  returnAssign: { type: "boolean" },
51
52
  ignoreJSX: { enum: ["none", "all", "single-line", "multi-line"] },
@@ -76,6 +77,7 @@ module.exports = {
76
77
  const precedence = astUtils.getPrecedence;
77
78
  const ALL_NODES = context.options[0] !== "functions";
78
79
  const EXCEPT_COND_ASSIGN = ALL_NODES && context.options[1] && context.options[1].conditionalAssign === false;
80
+ const EXCEPT_COND_TERNARY = ALL_NODES && context.options[1] && context.options[1].ternaryOperandBinaryExpressions === false;
79
81
  const NESTED_BINARY = ALL_NODES && context.options[1] && context.options[1].nestedBinaryExpressions === false;
80
82
  const EXCEPT_RETURN_ASSIGN = ALL_NODES && context.options[1] && context.options[1].returnAssign === false;
81
83
  const IGNORE_JSX = ALL_NODES && context.options[1] && context.options[1].ignoreJSX;
@@ -886,18 +888,26 @@ module.exports = {
886
888
  if (isReturnAssignException(node)) {
887
889
  return;
888
890
  }
891
+
892
+ const availableTypes = new Set(["BinaryExpression", "LogicalExpression"]);
893
+
889
894
  if (
895
+ !(EXCEPT_COND_TERNARY && availableTypes.has(node.test.type)) &&
890
896
  !isCondAssignException(node) &&
891
897
  hasExcessParensWithPrecedence(node.test, precedence({ type: "LogicalExpression", operator: "||" }))
892
898
  ) {
893
899
  report(node.test);
894
900
  }
895
901
 
896
- if (hasExcessParensWithPrecedence(node.consequent, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
902
+ if (
903
+ !(EXCEPT_COND_TERNARY && availableTypes.has(node.consequent.type)) &&
904
+ hasExcessParensWithPrecedence(node.consequent, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
897
905
  report(node.consequent);
898
906
  }
899
907
 
900
- if (hasExcessParensWithPrecedence(node.alternate, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
908
+ if (
909
+ !(EXCEPT_COND_TERNARY && availableTypes.has(node.alternate.type)) &&
910
+ hasExcessParensWithPrecedence(node.alternate, PRECEDENCE_OF_ASSIGNMENT_EXPR)) {
901
911
  report(node.alternate);
902
912
  }
903
913
  },
@@ -38,6 +38,23 @@ module.exports = {
38
38
  create(context) {
39
39
  const sourceCode = context.sourceCode;
40
40
 
41
+ /**
42
+ * Checks if a node or token is fixable.
43
+ * A node is fixable if it can be removed without turning a subsequent statement into a directive after fixing other nodes.
44
+ * @param {Token} nodeOrToken The node or token to check.
45
+ * @returns {boolean} Whether or not the node is fixable.
46
+ */
47
+ function isFixable(nodeOrToken) {
48
+ const nextToken = sourceCode.getTokenAfter(nodeOrToken);
49
+
50
+ if (!nextToken || nextToken.type !== "String") {
51
+ return true;
52
+ }
53
+ const stringNode = sourceCode.getNodeByRangeIndex(nextToken.range[0]);
54
+
55
+ return !astUtils.isTopLevelExpressionStatement(stringNode.parent);
56
+ }
57
+
41
58
  /**
42
59
  * Reports an unnecessary semicolon error.
43
60
  * @param {Node|Token} nodeOrToken A node or a token to be reported.
@@ -47,17 +64,18 @@ module.exports = {
47
64
  context.report({
48
65
  node: nodeOrToken,
49
66
  messageId: "unexpected",
50
- fix(fixer) {
51
-
52
- /*
53
- * Expand the replacement range to include the surrounding
54
- * tokens to avoid conflicting with semi.
55
- * https://github.com/eslint/eslint/issues/7928
56
- */
57
- return new FixTracker(fixer, context.sourceCode)
58
- .retainSurroundingTokens(nodeOrToken)
59
- .remove(nodeOrToken);
60
- }
67
+ fix: isFixable(nodeOrToken)
68
+ ? fixer =>
69
+
70
+ /*
71
+ * Expand the replacement range to include the surrounding
72
+ * tokens to avoid conflicting with semi.
73
+ * https://github.com/eslint/eslint/issues/7928
74
+ */
75
+ new FixTracker(fixer, context.sourceCode)
76
+ .retainSurroundingTokens(nodeOrToken)
77
+ .remove(nodeOrToken)
78
+ : null
61
79
  });
62
80
  }
63
81
 
@@ -83,7 +83,7 @@ module.exports = {
83
83
  * @returns {string} the numeric string with a decimal point in the proper place
84
84
  */
85
85
  function addDecimalPointToNumber(stringNumber) {
86
- return `${stringNumber.slice(0, 1)}.${stringNumber.slice(1)}`;
86
+ return `${stringNumber[0]}.${stringNumber.slice(1)}`;
87
87
  }
88
88
 
89
89
  /**
@@ -92,7 +92,12 @@ module.exports = {
92
92
  * @returns {string} the stripped string
93
93
  */
94
94
  function removeLeadingZeros(numberAsString) {
95
- return numberAsString.replace(/^0*/u, "");
95
+ for (let i = 0; i < numberAsString.length; i++) {
96
+ if (numberAsString[i] !== "0") {
97
+ return numberAsString.slice(i);
98
+ }
99
+ }
100
+ return numberAsString;
96
101
  }
97
102
 
98
103
  /**
@@ -101,7 +106,12 @@ module.exports = {
101
106
  * @returns {string} the stripped string
102
107
  */
103
108
  function removeTrailingZeros(numberAsString) {
104
- return numberAsString.replace(/0*$/u, "");
109
+ for (let i = numberAsString.length - 1; i >= 0; i--) {
110
+ if (numberAsString[i] !== "0") {
111
+ return numberAsString.slice(0, i + 1);
112
+ }
113
+ }
114
+ return numberAsString;
105
115
  }
106
116
 
107
117
  /**
@@ -128,7 +138,7 @@ module.exports = {
128
138
  const trimmedFloat = removeLeadingZeros(stringFloat);
129
139
 
130
140
  if (trimmedFloat.startsWith(".")) {
131
- const decimalDigits = trimmedFloat.split(".").pop();
141
+ const decimalDigits = trimmedFloat.slice(1);
132
142
  const significantDigits = removeLeadingZeros(decimalDigits);
133
143
 
134
144
  return {
@@ -144,7 +154,6 @@ module.exports = {
144
154
  };
145
155
  }
146
156
 
147
-
148
157
  /**
149
158
  * Converts a base ten number to proper scientific notation
150
159
  * @param {string} stringNumber the string representation of the base ten number to be converted
@@ -160,7 +169,6 @@ module.exports = {
160
169
  : normalizedNumber.magnitude;
161
170
 
162
171
  return `${normalizedCoefficient}e${magnitude}`;
163
-
164
172
  }
165
173
 
166
174
  /**
@@ -5,6 +5,12 @@
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
  //------------------------------------------------------------------------------
@@ -116,15 +122,6 @@ module.exports = {
116
122
  return node && node.type === "Literal" && typeof node.value === "string";
117
123
  }
118
124
 
119
- /**
120
- * Function to check if a node is a static string template literal.
121
- * @param {ASTNode} node The node to check.
122
- * @returns {boolean} If the node is a string template literal.
123
- */
124
- function isStaticTemplateLiteral(node) {
125
- return node && node.type === "TemplateLiteral" && node.expressions.length === 0;
126
- }
127
-
128
125
  /**
129
126
  * Function to check if a node is a require call.
130
127
  * @param {ASTNode} node The node to check.
@@ -144,7 +141,7 @@ module.exports = {
144
141
  return node.value.trim();
145
142
  }
146
143
 
147
- if (isStaticTemplateLiteral(node)) {
144
+ if (astUtils.isStaticTemplateLiteral(node)) {
148
145
  return node.quasis[0].value.cooked.trim();
149
146
  }
150
147
 
@@ -5,6 +5,12 @@
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
  //------------------------------------------------------------------------------
@@ -46,6 +52,45 @@ module.exports = {
46
52
  };
47
53
  }
48
54
 
55
+ /**
56
+ * Checks if a `LabeledStatement` node is fixable.
57
+ * For a node to be fixable, there must be no comments between the label and the body.
58
+ * Furthermore, is must be possible to remove the label without turning the body statement into a
59
+ * directive after other fixes are applied.
60
+ * @param {ASTNode} node The node to evaluate.
61
+ * @returns {boolean} Whether or not the node is fixable.
62
+ */
63
+ function isFixable(node) {
64
+
65
+ /*
66
+ * Only perform a fix if there are no comments between the label and the body. This will be the case
67
+ * when there is exactly one token/comment (the ":") between the label and the body.
68
+ */
69
+ if (sourceCode.getTokenAfter(node.label, { includeComments: true }) !==
70
+ sourceCode.getTokenBefore(node.body, { includeComments: true })) {
71
+ return false;
72
+ }
73
+
74
+ // Looking for the node's deepest ancestor which is not a `LabeledStatement`.
75
+ let ancestor = node.parent;
76
+
77
+ while (ancestor.type === "LabeledStatement") {
78
+ ancestor = ancestor.parent;
79
+ }
80
+
81
+ if (ancestor.type === "Program" ||
82
+ (ancestor.type === "BlockStatement" && astUtils.isFunction(ancestor.parent))) {
83
+ const { body } = node;
84
+
85
+ if (body.type === "ExpressionStatement" &&
86
+ ((body.expression.type === "Literal" && typeof body.expression.value === "string") ||
87
+ astUtils.isStaticTemplateLiteral(body.expression))) {
88
+ return false; // potential directive
89
+ }
90
+ }
91
+ return true;
92
+ }
93
+
49
94
  /**
50
95
  * Removes the top of the stack.
51
96
  * At the same time, this reports the label if it's never used.
@@ -58,19 +103,7 @@ module.exports = {
58
103
  node: node.label,
59
104
  messageId: "unused",
60
105
  data: node.label,
61
- fix(fixer) {
62
-
63
- /*
64
- * Only perform a fix if there are no comments between the label and the body. This will be the case
65
- * when there is exactly one token/comment (the ":") between the label and the body.
66
- */
67
- if (sourceCode.getTokenAfter(node.label, { includeComments: true }) ===
68
- sourceCode.getTokenBefore(node.body, { includeComments: true })) {
69
- return fixer.removeRange([node.range[0], node.body.range[0]]);
70
- }
71
-
72
- return null;
73
- }
106
+ fix: isFixable(node) ? fixer => fixer.removeRange([node.range[0], node.body.range[0]]) : null
74
107
  });
75
108
  }
76
109
 
@@ -466,7 +466,8 @@ module.exports = {
466
466
  (
467
467
  parent.type === "AssignmentExpression" &&
468
468
  parent.left === id &&
469
- isUnusedExpression(parent)
469
+ isUnusedExpression(parent) &&
470
+ !astUtils.isLogicalAssignmentOperator(parent.operator)
470
471
  ) ||
471
472
  (
472
473
  parent.type === "UpdateExpression" &&
@@ -94,6 +94,7 @@ module.exports = {
94
94
  messages: {
95
95
  unnecessaryEscape: "Unnecessary escape character: \\{{character}}.",
96
96
  removeEscape: "Remove the `\\`. This maintains the current functionality.",
97
+ removeEscapeDoNotKeepSemantics: "Remove the `\\` if it was inserted by mistake.",
97
98
  escapeBackslash: "Replace the `\\` with `\\\\` to include the actual backslash character."
98
99
  },
99
100
 
@@ -125,7 +126,10 @@ module.exports = {
125
126
  data: { character },
126
127
  suggest: [
127
128
  {
128
- messageId: "removeEscape",
129
+
130
+ // Removing unnecessary `\` characters in a directive is not guaranteed to maintain functionality.
131
+ messageId: astUtils.isDirective(node.parent)
132
+ ? "removeEscapeDoNotKeepSemantics" : "removeEscape",
129
133
  fix(fixer) {
130
134
  return fixer.removeRange(range);
131
135
  }
@@ -130,42 +130,6 @@ function isBlockLikeStatement(sourceCode, node) {
130
130
  );
131
131
  }
132
132
 
133
- /**
134
- * Check whether the given node is a directive or not.
135
- * @param {ASTNode} node The node to check.
136
- * @param {SourceCode} sourceCode The source code object to get tokens.
137
- * @returns {boolean} `true` if the node is a directive.
138
- */
139
- function isDirective(node, sourceCode) {
140
- return (
141
- astUtils.isTopLevelExpressionStatement(node) &&
142
- node.expression.type === "Literal" &&
143
- typeof node.expression.value === "string" &&
144
- !astUtils.isParenthesised(sourceCode, node.expression)
145
- );
146
- }
147
-
148
- /**
149
- * Check whether the given node is a part of directive prologue or not.
150
- * @param {ASTNode} node The node to check.
151
- * @param {SourceCode} sourceCode The source code object to get tokens.
152
- * @returns {boolean} `true` if the node is a part of directive prologue.
153
- */
154
- function isDirectivePrologue(node, sourceCode) {
155
- if (isDirective(node, sourceCode)) {
156
- for (const sibling of node.parent.body) {
157
- if (sibling === node) {
158
- break;
159
- }
160
- if (!isDirective(sibling, sourceCode)) {
161
- return false;
162
- }
163
- }
164
- return true;
165
- }
166
- return false;
167
- }
168
-
169
133
  /**
170
134
  * Gets the actual last token.
171
135
  *
@@ -359,12 +323,10 @@ const StatementTypes = {
359
323
  CJS_IMPORT.test(sourceCode.getText(node.declarations[0].init))
360
324
  },
361
325
  directive: {
362
- test: isDirectivePrologue
326
+ test: astUtils.isDirective
363
327
  },
364
328
  expression: {
365
- test: (node, sourceCode) =>
366
- node.type === "ExpressionStatement" &&
367
- !isDirectivePrologue(node, sourceCode)
329
+ test: node => node.type === "ExpressionStatement" && !astUtils.isDirective(node)
368
330
  },
369
331
  iife: {
370
332
  test: isIIFEStatement
@@ -375,10 +337,10 @@ const StatementTypes = {
375
337
  isBlockLikeStatement(sourceCode, node)
376
338
  },
377
339
  "multiline-expression": {
378
- test: (node, sourceCode) =>
340
+ test: node =>
379
341
  node.loc.start.line !== node.loc.end.line &&
380
342
  node.type === "ExpressionStatement" &&
381
- !isDirectivePrologue(node, sourceCode)
343
+ !astUtils.isDirective(node)
382
344
  },
383
345
 
384
346
  "multiline-const": newMultilineKeywordTester("const"),
@@ -55,11 +55,12 @@ function doesExponentNeedParens(exponent) {
55
55
  function doesExponentiationExpressionNeedParens(node, sourceCode) {
56
56
  const parent = node.parent.type === "ChainExpression" ? node.parent.parent : node.parent;
57
57
 
58
+ const parentPrecedence = astUtils.getPrecedence(parent);
58
59
  const needsParens = (
59
60
  parent.type === "ClassDeclaration" ||
60
61
  (
61
62
  parent.type.endsWith("Expression") &&
62
- astUtils.getPrecedence(parent) >= PRECEDENCE_OF_EXPONENTIATION_EXPR &&
63
+ (parentPrecedence === -1 || parentPrecedence >= PRECEDENCE_OF_EXPONENTIATION_EXPR) &&
63
64
  !(parent.type === "BinaryExpression" && parent.operator === "**" && parent.right === node) &&
64
65
  !((parent.type === "CallExpression" || parent.type === "NewExpression") && parent.arguments.includes(node)) &&
65
66
  !(parent.type === "MemberExpression" && parent.computed && parent.property === node) &&
@@ -37,15 +37,6 @@ function isRegexLiteral(node) {
37
37
  return node.type === "Literal" && Object.prototype.hasOwnProperty.call(node, "regex");
38
38
  }
39
39
 
40
- /**
41
- * Determines whether the given node is a template literal without expressions.
42
- * @param {ASTNode} node Node to check.
43
- * @returns {boolean} True if the node is a template literal without expressions.
44
- */
45
- function isStaticTemplateLiteral(node) {
46
- return node.type === "TemplateLiteral" && node.expressions.length === 0;
47
- }
48
-
49
40
  const validPrecedingTokens = new Set([
50
41
  "(",
51
42
  ";",
@@ -178,7 +169,7 @@ module.exports = {
178
169
  return node.type === "TaggedTemplateExpression" &&
179
170
  astUtils.isSpecificMemberAccess(node.tag, "String", "raw") &&
180
171
  isGlobalReference(astUtils.skipChainExpression(node.tag).object) &&
181
- isStaticTemplateLiteral(node.quasi);
172
+ astUtils.isStaticTemplateLiteral(node.quasi);
182
173
  }
183
174
 
184
175
  /**
@@ -191,7 +182,7 @@ module.exports = {
191
182
  return node.value;
192
183
  }
193
184
 
194
- if (isStaticTemplateLiteral(node)) {
185
+ if (astUtils.isStaticTemplateLiteral(node)) {
195
186
  return node.quasis[0].value.cooked;
196
187
  }
197
188
 
@@ -209,7 +200,7 @@ module.exports = {
209
200
  */
210
201
  function isStaticString(node) {
211
202
  return isStringLiteral(node) ||
212
- isStaticTemplateLiteral(node) ||
203
+ astUtils.isStaticTemplateLiteral(node) ||
213
204
  isStringRawTaggedStaticTemplateLiteral(node);
214
205
  }
215
206