[d565449] | 1 | /**
|
---|
| 2 | * @fileoverview Common utils for AST.
|
---|
| 3 | * @author Gyandeep Singh
|
---|
| 4 | */
|
---|
| 5 |
|
---|
| 6 | "use strict";
|
---|
| 7 |
|
---|
| 8 | //------------------------------------------------------------------------------
|
---|
| 9 | // Requirements
|
---|
| 10 | //------------------------------------------------------------------------------
|
---|
| 11 |
|
---|
| 12 | const { KEYS: eslintVisitorKeys } = require("eslint-visitor-keys");
|
---|
| 13 | const esutils = require("esutils");
|
---|
| 14 | const espree = require("espree");
|
---|
| 15 | const escapeRegExp = require("escape-string-regexp");
|
---|
| 16 | const {
|
---|
| 17 | breakableTypePattern,
|
---|
| 18 | createGlobalLinebreakMatcher,
|
---|
| 19 | lineBreakPattern,
|
---|
| 20 | shebangPattern
|
---|
| 21 | } = require("../../shared/ast-utils");
|
---|
| 22 |
|
---|
| 23 | //------------------------------------------------------------------------------
|
---|
| 24 | // Helpers
|
---|
| 25 | //------------------------------------------------------------------------------
|
---|
| 26 |
|
---|
| 27 | const anyFunctionPattern = /^(?:Function(?:Declaration|Expression)|ArrowFunctionExpression)$/u;
|
---|
| 28 | const anyLoopPattern = /^(?:DoWhile|For|ForIn|ForOf|While)Statement$/u;
|
---|
| 29 | const arrayMethodWithThisArgPattern = /^(?:every|filter|find(?:Last)?(?:Index)?|flatMap|forEach|map|some)$/u;
|
---|
| 30 | const arrayOrTypedArrayPattern = /Array$/u;
|
---|
| 31 | const bindOrCallOrApplyPattern = /^(?:bind|call|apply)$/u;
|
---|
| 32 | const thisTagPattern = /^[\s*]*@this/mu;
|
---|
| 33 |
|
---|
| 34 |
|
---|
| 35 | const COMMENTS_IGNORE_PATTERN = /^\s*(?:eslint|jshint\s+|jslint\s+|istanbul\s+|globals?\s+|exported\s+|jscs)/u;
|
---|
| 36 | const ESLINT_DIRECTIVE_PATTERN = /^(?:eslint[- ]|(?:globals?|exported) )/u;
|
---|
| 37 | const LINEBREAKS = new Set(["\r\n", "\r", "\n", "\u2028", "\u2029"]);
|
---|
| 38 |
|
---|
| 39 | // A set of node types that can contain a list of statements
|
---|
| 40 | const STATEMENT_LIST_PARENTS = new Set(["Program", "BlockStatement", "StaticBlock", "SwitchCase"]);
|
---|
| 41 |
|
---|
| 42 | const DECIMAL_INTEGER_PATTERN = /^(?:0|0[0-7]*[89]\d*|[1-9](?:_?\d)*)$/u;
|
---|
| 43 |
|
---|
| 44 | // Tests the presence of at least one LegacyOctalEscapeSequence or NonOctalDecimalEscapeSequence in a raw string
|
---|
| 45 | const OCTAL_OR_NON_OCTAL_DECIMAL_ESCAPE_PATTERN = /^(?:[^\\]|\\.)*\\(?:[1-9]|0[0-9])/su;
|
---|
| 46 |
|
---|
| 47 | const LOGICAL_ASSIGNMENT_OPERATORS = new Set(["&&=", "||=", "??="]);
|
---|
| 48 |
|
---|
| 49 | /**
|
---|
| 50 | * Checks reference if is non initializer and writable.
|
---|
| 51 | * @param {Reference} reference A reference to check.
|
---|
| 52 | * @param {int} index The index of the reference in the references.
|
---|
| 53 | * @param {Reference[]} references The array that the reference belongs to.
|
---|
| 54 | * @returns {boolean} Success/Failure
|
---|
| 55 | * @private
|
---|
| 56 | */
|
---|
| 57 | function isModifyingReference(reference, index, references) {
|
---|
| 58 | const identifier = reference.identifier;
|
---|
| 59 |
|
---|
| 60 | /*
|
---|
| 61 | * Destructuring assignments can have multiple default value, so
|
---|
| 62 | * possibly there are multiple writeable references for the same
|
---|
| 63 | * identifier.
|
---|
| 64 | */
|
---|
| 65 | const modifyingDifferentIdentifier = index === 0 ||
|
---|
| 66 | references[index - 1].identifier !== identifier;
|
---|
| 67 |
|
---|
| 68 | return (identifier &&
|
---|
| 69 | reference.init === false &&
|
---|
| 70 | reference.isWrite() &&
|
---|
| 71 | modifyingDifferentIdentifier
|
---|
| 72 | );
|
---|
| 73 | }
|
---|
| 74 |
|
---|
| 75 | /**
|
---|
| 76 | * Checks whether the given string starts with uppercase or not.
|
---|
| 77 | * @param {string} s The string to check.
|
---|
| 78 | * @returns {boolean} `true` if the string starts with uppercase.
|
---|
| 79 | */
|
---|
| 80 | function startsWithUpperCase(s) {
|
---|
| 81 | return s[0] !== s[0].toLocaleLowerCase();
|
---|
| 82 | }
|
---|
| 83 |
|
---|
| 84 | /**
|
---|
| 85 | * Checks whether or not a node is a constructor.
|
---|
| 86 | * @param {ASTNode} node A function node to check.
|
---|
| 87 | * @returns {boolean} Whether or not a node is a constructor.
|
---|
| 88 | */
|
---|
| 89 | function isES5Constructor(node) {
|
---|
| 90 | return (node.id && startsWithUpperCase(node.id.name));
|
---|
| 91 | }
|
---|
| 92 |
|
---|
| 93 | /**
|
---|
| 94 | * Finds a function node from ancestors of a node.
|
---|
| 95 | * @param {ASTNode} node A start node to find.
|
---|
| 96 | * @returns {Node|null} A found function node.
|
---|
| 97 | */
|
---|
| 98 | function getUpperFunction(node) {
|
---|
| 99 | for (let currentNode = node; currentNode; currentNode = currentNode.parent) {
|
---|
| 100 | if (anyFunctionPattern.test(currentNode.type)) {
|
---|
| 101 | return currentNode;
|
---|
| 102 | }
|
---|
| 103 | }
|
---|
| 104 | return null;
|
---|
| 105 | }
|
---|
| 106 |
|
---|
| 107 | /**
|
---|
| 108 | * Checks whether a given node is a function node or not.
|
---|
| 109 | * The following types are function nodes:
|
---|
| 110 | *
|
---|
| 111 | * - ArrowFunctionExpression
|
---|
| 112 | * - FunctionDeclaration
|
---|
| 113 | * - FunctionExpression
|
---|
| 114 | * @param {ASTNode|null} node A node to check.
|
---|
| 115 | * @returns {boolean} `true` if the node is a function node.
|
---|
| 116 | */
|
---|
| 117 | function isFunction(node) {
|
---|
| 118 | return Boolean(node && anyFunctionPattern.test(node.type));
|
---|
| 119 | }
|
---|
| 120 |
|
---|
| 121 | /**
|
---|
| 122 | * Checks whether a given node is a loop node or not.
|
---|
| 123 | * The following types are loop nodes:
|
---|
| 124 | *
|
---|
| 125 | * - DoWhileStatement
|
---|
| 126 | * - ForInStatement
|
---|
| 127 | * - ForOfStatement
|
---|
| 128 | * - ForStatement
|
---|
| 129 | * - WhileStatement
|
---|
| 130 | * @param {ASTNode|null} node A node to check.
|
---|
| 131 | * @returns {boolean} `true` if the node is a loop node.
|
---|
| 132 | */
|
---|
| 133 | function isLoop(node) {
|
---|
| 134 | return Boolean(node && anyLoopPattern.test(node.type));
|
---|
| 135 | }
|
---|
| 136 |
|
---|
| 137 | /**
|
---|
| 138 | * Checks whether the given node is in a loop or not.
|
---|
| 139 | * @param {ASTNode} node The node to check.
|
---|
| 140 | * @returns {boolean} `true` if the node is in a loop.
|
---|
| 141 | */
|
---|
| 142 | function isInLoop(node) {
|
---|
| 143 | for (let currentNode = node; currentNode && !isFunction(currentNode); currentNode = currentNode.parent) {
|
---|
| 144 | if (isLoop(currentNode)) {
|
---|
| 145 | return true;
|
---|
| 146 | }
|
---|
| 147 | }
|
---|
| 148 |
|
---|
| 149 | return false;
|
---|
| 150 | }
|
---|
| 151 |
|
---|
| 152 | /**
|
---|
| 153 | * Determines whether the given node is a `null` literal.
|
---|
| 154 | * @param {ASTNode} node The node to check
|
---|
| 155 | * @returns {boolean} `true` if the node is a `null` literal
|
---|
| 156 | */
|
---|
| 157 | function isNullLiteral(node) {
|
---|
| 158 |
|
---|
| 159 | /*
|
---|
| 160 | * Checking `node.value === null` does not guarantee that a literal is a null literal.
|
---|
| 161 | * When parsing values that cannot be represented in the current environment (e.g. unicode
|
---|
| 162 | * regexes in Node 4), `node.value` is set to `null` because it wouldn't be possible to
|
---|
| 163 | * set `node.value` to a unicode regex. To make sure a literal is actually `null`, check
|
---|
| 164 | * `node.regex` instead. Also see: https://github.com/eslint/eslint/issues/8020
|
---|
| 165 | */
|
---|
| 166 | return node.type === "Literal" && node.value === null && !node.regex && !node.bigint;
|
---|
| 167 | }
|
---|
| 168 |
|
---|
| 169 | /**
|
---|
| 170 | * Checks whether or not a node is `null` or `undefined`.
|
---|
| 171 | * @param {ASTNode} node A node to check.
|
---|
| 172 | * @returns {boolean} Whether or not the node is a `null` or `undefined`.
|
---|
| 173 | * @public
|
---|
| 174 | */
|
---|
| 175 | function isNullOrUndefined(node) {
|
---|
| 176 | return (
|
---|
| 177 | isNullLiteral(node) ||
|
---|
| 178 | (node.type === "Identifier" && node.name === "undefined") ||
|
---|
| 179 | (node.type === "UnaryExpression" && node.operator === "void")
|
---|
| 180 | );
|
---|
| 181 | }
|
---|
| 182 |
|
---|
| 183 | /**
|
---|
| 184 | * Checks whether or not a node is callee.
|
---|
| 185 | * @param {ASTNode} node A node to check.
|
---|
| 186 | * @returns {boolean} Whether or not the node is callee.
|
---|
| 187 | */
|
---|
| 188 | function isCallee(node) {
|
---|
| 189 | return node.parent.type === "CallExpression" && node.parent.callee === node;
|
---|
| 190 | }
|
---|
| 191 |
|
---|
| 192 | /**
|
---|
| 193 | * Returns the result of the string conversion applied to the evaluated value of the given expression node,
|
---|
| 194 | * if it can be determined statically.
|
---|
| 195 | *
|
---|
| 196 | * This function returns a `string` value for all `Literal` nodes and simple `TemplateLiteral` nodes only.
|
---|
| 197 | * In all other cases, this function returns `null`.
|
---|
| 198 | * @param {ASTNode} node Expression node.
|
---|
| 199 | * @returns {string|null} String value if it can be determined. Otherwise, `null`.
|
---|
| 200 | */
|
---|
| 201 | function getStaticStringValue(node) {
|
---|
| 202 | switch (node.type) {
|
---|
| 203 | case "Literal":
|
---|
| 204 | if (node.value === null) {
|
---|
| 205 | if (isNullLiteral(node)) {
|
---|
| 206 | return String(node.value); // "null"
|
---|
| 207 | }
|
---|
| 208 | if (node.regex) {
|
---|
| 209 | return `/${node.regex.pattern}/${node.regex.flags}`;
|
---|
| 210 | }
|
---|
| 211 | if (node.bigint) {
|
---|
| 212 | return node.bigint;
|
---|
| 213 | }
|
---|
| 214 |
|
---|
| 215 | // Otherwise, this is an unknown literal. The function will return null.
|
---|
| 216 |
|
---|
| 217 | } else {
|
---|
| 218 | return String(node.value);
|
---|
| 219 | }
|
---|
| 220 | break;
|
---|
| 221 | case "TemplateLiteral":
|
---|
| 222 | if (node.expressions.length === 0 && node.quasis.length === 1) {
|
---|
| 223 | return node.quasis[0].value.cooked;
|
---|
| 224 | }
|
---|
| 225 | break;
|
---|
| 226 |
|
---|
| 227 | // no default
|
---|
| 228 | }
|
---|
| 229 |
|
---|
| 230 | return null;
|
---|
| 231 | }
|
---|
| 232 |
|
---|
| 233 | /**
|
---|
| 234 | * Gets the property name of a given node.
|
---|
| 235 | * The node can be a MemberExpression, a Property, or a MethodDefinition.
|
---|
| 236 | *
|
---|
| 237 | * If the name is dynamic, this returns `null`.
|
---|
| 238 | *
|
---|
| 239 | * For examples:
|
---|
| 240 | *
|
---|
| 241 | * a.b // => "b"
|
---|
| 242 | * a["b"] // => "b"
|
---|
| 243 | * a['b'] // => "b"
|
---|
| 244 | * a[`b`] // => "b"
|
---|
| 245 | * a[100] // => "100"
|
---|
| 246 | * a[b] // => null
|
---|
| 247 | * a["a" + "b"] // => null
|
---|
| 248 | * a[tag`b`] // => null
|
---|
| 249 | * a[`${b}`] // => null
|
---|
| 250 | *
|
---|
| 251 | * let a = {b: 1} // => "b"
|
---|
| 252 | * let a = {["b"]: 1} // => "b"
|
---|
| 253 | * let a = {['b']: 1} // => "b"
|
---|
| 254 | * let a = {[`b`]: 1} // => "b"
|
---|
| 255 | * let a = {[100]: 1} // => "100"
|
---|
| 256 | * let a = {[b]: 1} // => null
|
---|
| 257 | * let a = {["a" + "b"]: 1} // => null
|
---|
| 258 | * let a = {[tag`b`]: 1} // => null
|
---|
| 259 | * let a = {[`${b}`]: 1} // => null
|
---|
| 260 | * @param {ASTNode} node The node to get.
|
---|
| 261 | * @returns {string|null} The property name if static. Otherwise, null.
|
---|
| 262 | */
|
---|
| 263 | function getStaticPropertyName(node) {
|
---|
| 264 | let prop;
|
---|
| 265 |
|
---|
| 266 | switch (node && node.type) {
|
---|
| 267 | case "ChainExpression":
|
---|
| 268 | return getStaticPropertyName(node.expression);
|
---|
| 269 |
|
---|
| 270 | case "Property":
|
---|
| 271 | case "PropertyDefinition":
|
---|
| 272 | case "MethodDefinition":
|
---|
| 273 | prop = node.key;
|
---|
| 274 | break;
|
---|
| 275 |
|
---|
| 276 | case "MemberExpression":
|
---|
| 277 | prop = node.property;
|
---|
| 278 | break;
|
---|
| 279 |
|
---|
| 280 | // no default
|
---|
| 281 | }
|
---|
| 282 |
|
---|
| 283 | if (prop) {
|
---|
| 284 | if (prop.type === "Identifier" && !node.computed) {
|
---|
| 285 | return prop.name;
|
---|
| 286 | }
|
---|
| 287 |
|
---|
| 288 | return getStaticStringValue(prop);
|
---|
| 289 | }
|
---|
| 290 |
|
---|
| 291 | return null;
|
---|
| 292 | }
|
---|
| 293 |
|
---|
| 294 | /**
|
---|
| 295 | * Retrieve `ChainExpression#expression` value if the given node a `ChainExpression` node. Otherwise, pass through it.
|
---|
| 296 | * @param {ASTNode} node The node to address.
|
---|
| 297 | * @returns {ASTNode} The `ChainExpression#expression` value if the node is a `ChainExpression` node. Otherwise, the node.
|
---|
| 298 | */
|
---|
| 299 | function skipChainExpression(node) {
|
---|
| 300 | return node && node.type === "ChainExpression" ? node.expression : node;
|
---|
| 301 | }
|
---|
| 302 |
|
---|
| 303 | /**
|
---|
| 304 | * Check if the `actual` is an expected value.
|
---|
| 305 | * @param {string} actual The string value to check.
|
---|
| 306 | * @param {string | RegExp} expected The expected string value or pattern.
|
---|
| 307 | * @returns {boolean} `true` if the `actual` is an expected value.
|
---|
| 308 | */
|
---|
| 309 | function checkText(actual, expected) {
|
---|
| 310 | return typeof expected === "string"
|
---|
| 311 | ? actual === expected
|
---|
| 312 | : expected.test(actual);
|
---|
| 313 | }
|
---|
| 314 |
|
---|
| 315 | /**
|
---|
| 316 | * Check if a given node is an Identifier node with a given name.
|
---|
| 317 | * @param {ASTNode} node The node to check.
|
---|
| 318 | * @param {string | RegExp} name The expected name or the expected pattern of the object name.
|
---|
| 319 | * @returns {boolean} `true` if the node is an Identifier node with the name.
|
---|
| 320 | */
|
---|
| 321 | function isSpecificId(node, name) {
|
---|
| 322 | return node.type === "Identifier" && checkText(node.name, name);
|
---|
| 323 | }
|
---|
| 324 |
|
---|
| 325 | /**
|
---|
| 326 | * Check if a given node is member access with a given object name and property name pair.
|
---|
| 327 | * This is regardless of optional or not.
|
---|
| 328 | * @param {ASTNode} node The node to check.
|
---|
| 329 | * @param {string | RegExp | null} objectName The expected name or the expected pattern of the object name. If this is nullish, this method doesn't check object.
|
---|
| 330 | * @param {string | RegExp | null} propertyName The expected name or the expected pattern of the property name. If this is nullish, this method doesn't check property.
|
---|
| 331 | * @returns {boolean} `true` if the node is member access with the object name and property name pair.
|
---|
| 332 | * The node is a `MemberExpression` or `ChainExpression`.
|
---|
| 333 | */
|
---|
| 334 | function isSpecificMemberAccess(node, objectName, propertyName) {
|
---|
| 335 | const checkNode = skipChainExpression(node);
|
---|
| 336 |
|
---|
| 337 | if (checkNode.type !== "MemberExpression") {
|
---|
| 338 | return false;
|
---|
| 339 | }
|
---|
| 340 |
|
---|
| 341 | if (objectName && !isSpecificId(checkNode.object, objectName)) {
|
---|
| 342 | return false;
|
---|
| 343 | }
|
---|
| 344 |
|
---|
| 345 | if (propertyName) {
|
---|
| 346 | const actualPropertyName = getStaticPropertyName(checkNode);
|
---|
| 347 |
|
---|
| 348 | if (typeof actualPropertyName !== "string" || !checkText(actualPropertyName, propertyName)) {
|
---|
| 349 | return false;
|
---|
| 350 | }
|
---|
| 351 | }
|
---|
| 352 |
|
---|
| 353 | return true;
|
---|
| 354 | }
|
---|
| 355 |
|
---|
| 356 | /**
|
---|
| 357 | * Check if two literal nodes are the same value.
|
---|
| 358 | * @param {ASTNode} left The Literal node to compare.
|
---|
| 359 | * @param {ASTNode} right The other Literal node to compare.
|
---|
| 360 | * @returns {boolean} `true` if the two literal nodes are the same value.
|
---|
| 361 | */
|
---|
| 362 | function equalLiteralValue(left, right) {
|
---|
| 363 |
|
---|
| 364 | // RegExp literal.
|
---|
| 365 | if (left.regex || right.regex) {
|
---|
| 366 | return Boolean(
|
---|
| 367 | left.regex &&
|
---|
| 368 | right.regex &&
|
---|
| 369 | left.regex.pattern === right.regex.pattern &&
|
---|
| 370 | left.regex.flags === right.regex.flags
|
---|
| 371 | );
|
---|
| 372 | }
|
---|
| 373 |
|
---|
| 374 | // BigInt literal.
|
---|
| 375 | if (left.bigint || right.bigint) {
|
---|
| 376 | return left.bigint === right.bigint;
|
---|
| 377 | }
|
---|
| 378 |
|
---|
| 379 | return left.value === right.value;
|
---|
| 380 | }
|
---|
| 381 |
|
---|
| 382 | /**
|
---|
| 383 | * Check if two expressions reference the same value. For example:
|
---|
| 384 | * a = a
|
---|
| 385 | * a.b = a.b
|
---|
| 386 | * a[0] = a[0]
|
---|
| 387 | * a['b'] = a['b']
|
---|
| 388 | * @param {ASTNode} left The left side of the comparison.
|
---|
| 389 | * @param {ASTNode} right The right side of the comparison.
|
---|
| 390 | * @param {boolean} [disableStaticComputedKey] Don't address `a.b` and `a["b"]` are the same if `true`. For backward compatibility.
|
---|
| 391 | * @returns {boolean} `true` if both sides match and reference the same value.
|
---|
| 392 | */
|
---|
| 393 | function isSameReference(left, right, disableStaticComputedKey = false) {
|
---|
| 394 | if (left.type !== right.type) {
|
---|
| 395 |
|
---|
| 396 | // Handle `a.b` and `a?.b` are samely.
|
---|
| 397 | if (left.type === "ChainExpression") {
|
---|
| 398 | return isSameReference(left.expression, right, disableStaticComputedKey);
|
---|
| 399 | }
|
---|
| 400 | if (right.type === "ChainExpression") {
|
---|
| 401 | return isSameReference(left, right.expression, disableStaticComputedKey);
|
---|
| 402 | }
|
---|
| 403 |
|
---|
| 404 | return false;
|
---|
| 405 | }
|
---|
| 406 |
|
---|
| 407 | switch (left.type) {
|
---|
| 408 | case "Super":
|
---|
| 409 | case "ThisExpression":
|
---|
| 410 | return true;
|
---|
| 411 |
|
---|
| 412 | case "Identifier":
|
---|
| 413 | case "PrivateIdentifier":
|
---|
| 414 | return left.name === right.name;
|
---|
| 415 | case "Literal":
|
---|
| 416 | return equalLiteralValue(left, right);
|
---|
| 417 |
|
---|
| 418 | case "ChainExpression":
|
---|
| 419 | return isSameReference(left.expression, right.expression, disableStaticComputedKey);
|
---|
| 420 |
|
---|
| 421 | case "MemberExpression": {
|
---|
| 422 | if (!disableStaticComputedKey) {
|
---|
| 423 | const nameA = getStaticPropertyName(left);
|
---|
| 424 |
|
---|
| 425 | // x.y = x["y"]
|
---|
| 426 | if (nameA !== null) {
|
---|
| 427 | return (
|
---|
| 428 | isSameReference(left.object, right.object, disableStaticComputedKey) &&
|
---|
| 429 | nameA === getStaticPropertyName(right)
|
---|
| 430 | );
|
---|
| 431 | }
|
---|
| 432 | }
|
---|
| 433 |
|
---|
| 434 | /*
|
---|
| 435 | * x[0] = x[0]
|
---|
| 436 | * x[y] = x[y]
|
---|
| 437 | * x.y = x.y
|
---|
| 438 | */
|
---|
| 439 | return (
|
---|
| 440 | left.computed === right.computed &&
|
---|
| 441 | isSameReference(left.object, right.object, disableStaticComputedKey) &&
|
---|
| 442 | isSameReference(left.property, right.property, disableStaticComputedKey)
|
---|
| 443 | );
|
---|
| 444 | }
|
---|
| 445 |
|
---|
| 446 | default:
|
---|
| 447 | return false;
|
---|
| 448 | }
|
---|
| 449 | }
|
---|
| 450 |
|
---|
| 451 | /**
|
---|
| 452 | * Checks whether or not a node is `Reflect.apply`.
|
---|
| 453 | * @param {ASTNode} node A node to check.
|
---|
| 454 | * @returns {boolean} Whether or not the node is a `Reflect.apply`.
|
---|
| 455 | */
|
---|
| 456 | function isReflectApply(node) {
|
---|
| 457 | return isSpecificMemberAccess(node, "Reflect", "apply");
|
---|
| 458 | }
|
---|
| 459 |
|
---|
| 460 | /**
|
---|
| 461 | * Checks whether or not a node is `Array.from`.
|
---|
| 462 | * @param {ASTNode} node A node to check.
|
---|
| 463 | * @returns {boolean} Whether or not the node is a `Array.from`.
|
---|
| 464 | */
|
---|
| 465 | function isArrayFromMethod(node) {
|
---|
| 466 | return isSpecificMemberAccess(node, arrayOrTypedArrayPattern, "from");
|
---|
| 467 | }
|
---|
| 468 |
|
---|
| 469 | /**
|
---|
| 470 | * Checks whether or not a node is a method which expects a function as a first argument, and `thisArg` as a second argument.
|
---|
| 471 | * @param {ASTNode} node A node to check.
|
---|
| 472 | * @returns {boolean} Whether or not the node is a method which expects a function as a first argument, and `thisArg` as a second argument.
|
---|
| 473 | */
|
---|
| 474 | function isMethodWhichHasThisArg(node) {
|
---|
| 475 | return isSpecificMemberAccess(node, null, arrayMethodWithThisArgPattern);
|
---|
| 476 | }
|
---|
| 477 |
|
---|
| 478 | /**
|
---|
| 479 | * Creates the negate function of the given function.
|
---|
| 480 | * @param {Function} f The function to negate.
|
---|
| 481 | * @returns {Function} Negated function.
|
---|
| 482 | */
|
---|
| 483 | function negate(f) {
|
---|
| 484 | return token => !f(token);
|
---|
| 485 | }
|
---|
| 486 |
|
---|
| 487 | /**
|
---|
| 488 | * Checks whether or not a node has a `@this` tag in its comments.
|
---|
| 489 | * @param {ASTNode} node A node to check.
|
---|
| 490 | * @param {SourceCode} sourceCode A SourceCode instance to get comments.
|
---|
| 491 | * @returns {boolean} Whether or not the node has a `@this` tag in its comments.
|
---|
| 492 | */
|
---|
| 493 | function hasJSDocThisTag(node, sourceCode) {
|
---|
| 494 | const jsdocComment = sourceCode.getJSDocComment(node);
|
---|
| 495 |
|
---|
| 496 | if (jsdocComment && thisTagPattern.test(jsdocComment.value)) {
|
---|
| 497 | return true;
|
---|
| 498 | }
|
---|
| 499 |
|
---|
| 500 | // Checks `@this` in its leading comments for callbacks,
|
---|
| 501 | // because callbacks don't have its JSDoc comment.
|
---|
| 502 | // e.g.
|
---|
| 503 | // sinon.test(/* @this sinon.Sandbox */function() { this.spy(); });
|
---|
| 504 | return sourceCode.getCommentsBefore(node).some(comment => thisTagPattern.test(comment.value));
|
---|
| 505 | }
|
---|
| 506 |
|
---|
| 507 | /**
|
---|
| 508 | * Determines if a node is surrounded by parentheses.
|
---|
| 509 | * @param {SourceCode} sourceCode The ESLint source code object
|
---|
| 510 | * @param {ASTNode} node The node to be checked.
|
---|
| 511 | * @returns {boolean} True if the node is parenthesised.
|
---|
| 512 | * @private
|
---|
| 513 | */
|
---|
| 514 | function isParenthesised(sourceCode, node) {
|
---|
| 515 | const previousToken = sourceCode.getTokenBefore(node),
|
---|
| 516 | nextToken = sourceCode.getTokenAfter(node);
|
---|
| 517 |
|
---|
| 518 | return Boolean(previousToken && nextToken) &&
|
---|
| 519 | previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
|
---|
| 520 | nextToken.value === ")" && nextToken.range[0] >= node.range[1];
|
---|
| 521 | }
|
---|
| 522 |
|
---|
| 523 | /**
|
---|
| 524 | * Checks if the given token is a `=` token or not.
|
---|
| 525 | * @param {Token} token The token to check.
|
---|
| 526 | * @returns {boolean} `true` if the token is a `=` token.
|
---|
| 527 | */
|
---|
| 528 | function isEqToken(token) {
|
---|
| 529 | return token.value === "=" && token.type === "Punctuator";
|
---|
| 530 | }
|
---|
| 531 |
|
---|
| 532 | /**
|
---|
| 533 | * Checks if the given token is an arrow token or not.
|
---|
| 534 | * @param {Token} token The token to check.
|
---|
| 535 | * @returns {boolean} `true` if the token is an arrow token.
|
---|
| 536 | */
|
---|
| 537 | function isArrowToken(token) {
|
---|
| 538 | return token.value === "=>" && token.type === "Punctuator";
|
---|
| 539 | }
|
---|
| 540 |
|
---|
| 541 | /**
|
---|
| 542 | * Checks if the given token is a comma token or not.
|
---|
| 543 | * @param {Token} token The token to check.
|
---|
| 544 | * @returns {boolean} `true` if the token is a comma token.
|
---|
| 545 | */
|
---|
| 546 | function isCommaToken(token) {
|
---|
| 547 | return token.value === "," && token.type === "Punctuator";
|
---|
| 548 | }
|
---|
| 549 |
|
---|
| 550 | /**
|
---|
| 551 | * Checks if the given token is a dot token or not.
|
---|
| 552 | * @param {Token} token The token to check.
|
---|
| 553 | * @returns {boolean} `true` if the token is a dot token.
|
---|
| 554 | */
|
---|
| 555 | function isDotToken(token) {
|
---|
| 556 | return token.value === "." && token.type === "Punctuator";
|
---|
| 557 | }
|
---|
| 558 |
|
---|
| 559 | /**
|
---|
| 560 | * Checks if the given token is a `?.` token or not.
|
---|
| 561 | * @param {Token} token The token to check.
|
---|
| 562 | * @returns {boolean} `true` if the token is a `?.` token.
|
---|
| 563 | */
|
---|
| 564 | function isQuestionDotToken(token) {
|
---|
| 565 | return token.value === "?." && token.type === "Punctuator";
|
---|
| 566 | }
|
---|
| 567 |
|
---|
| 568 | /**
|
---|
| 569 | * Checks if the given token is a semicolon token or not.
|
---|
| 570 | * @param {Token} token The token to check.
|
---|
| 571 | * @returns {boolean} `true` if the token is a semicolon token.
|
---|
| 572 | */
|
---|
| 573 | function isSemicolonToken(token) {
|
---|
| 574 | return token.value === ";" && token.type === "Punctuator";
|
---|
| 575 | }
|
---|
| 576 |
|
---|
| 577 | /**
|
---|
| 578 | * Checks if the given token is a colon token or not.
|
---|
| 579 | * @param {Token} token The token to check.
|
---|
| 580 | * @returns {boolean} `true` if the token is a colon token.
|
---|
| 581 | */
|
---|
| 582 | function isColonToken(token) {
|
---|
| 583 | return token.value === ":" && token.type === "Punctuator";
|
---|
| 584 | }
|
---|
| 585 |
|
---|
| 586 | /**
|
---|
| 587 | * Checks if the given token is an opening parenthesis token or not.
|
---|
| 588 | * @param {Token} token The token to check.
|
---|
| 589 | * @returns {boolean} `true` if the token is an opening parenthesis token.
|
---|
| 590 | */
|
---|
| 591 | function isOpeningParenToken(token) {
|
---|
| 592 | return token.value === "(" && token.type === "Punctuator";
|
---|
| 593 | }
|
---|
| 594 |
|
---|
| 595 | /**
|
---|
| 596 | * Checks if the given token is a closing parenthesis token or not.
|
---|
| 597 | * @param {Token} token The token to check.
|
---|
| 598 | * @returns {boolean} `true` if the token is a closing parenthesis token.
|
---|
| 599 | */
|
---|
| 600 | function isClosingParenToken(token) {
|
---|
| 601 | return token.value === ")" && token.type === "Punctuator";
|
---|
| 602 | }
|
---|
| 603 |
|
---|
| 604 | /**
|
---|
| 605 | * Checks if the given token is an opening square bracket token or not.
|
---|
| 606 | * @param {Token} token The token to check.
|
---|
| 607 | * @returns {boolean} `true` if the token is an opening square bracket token.
|
---|
| 608 | */
|
---|
| 609 | function isOpeningBracketToken(token) {
|
---|
| 610 | return token.value === "[" && token.type === "Punctuator";
|
---|
| 611 | }
|
---|
| 612 |
|
---|
| 613 | /**
|
---|
| 614 | * Checks if the given token is a closing square bracket token or not.
|
---|
| 615 | * @param {Token} token The token to check.
|
---|
| 616 | * @returns {boolean} `true` if the token is a closing square bracket token.
|
---|
| 617 | */
|
---|
| 618 | function isClosingBracketToken(token) {
|
---|
| 619 | return token.value === "]" && token.type === "Punctuator";
|
---|
| 620 | }
|
---|
| 621 |
|
---|
| 622 | /**
|
---|
| 623 | * Checks if the given token is an opening brace token or not.
|
---|
| 624 | * @param {Token} token The token to check.
|
---|
| 625 | * @returns {boolean} `true` if the token is an opening brace token.
|
---|
| 626 | */
|
---|
| 627 | function isOpeningBraceToken(token) {
|
---|
| 628 | return token.value === "{" && token.type === "Punctuator";
|
---|
| 629 | }
|
---|
| 630 |
|
---|
| 631 | /**
|
---|
| 632 | * Checks if the given token is a closing brace token or not.
|
---|
| 633 | * @param {Token} token The token to check.
|
---|
| 634 | * @returns {boolean} `true` if the token is a closing brace token.
|
---|
| 635 | */
|
---|
| 636 | function isClosingBraceToken(token) {
|
---|
| 637 | return token.value === "}" && token.type === "Punctuator";
|
---|
| 638 | }
|
---|
| 639 |
|
---|
| 640 | /**
|
---|
| 641 | * Checks if the given token is a comment token or not.
|
---|
| 642 | * @param {Token} token The token to check.
|
---|
| 643 | * @returns {boolean} `true` if the token is a comment token.
|
---|
| 644 | */
|
---|
| 645 | function isCommentToken(token) {
|
---|
| 646 | return token.type === "Line" || token.type === "Block" || token.type === "Shebang";
|
---|
| 647 | }
|
---|
| 648 |
|
---|
| 649 | /**
|
---|
| 650 | * Checks if the given token is a keyword token or not.
|
---|
| 651 | * @param {Token} token The token to check.
|
---|
| 652 | * @returns {boolean} `true` if the token is a keyword token.
|
---|
| 653 | */
|
---|
| 654 | function isKeywordToken(token) {
|
---|
| 655 | return token.type === "Keyword";
|
---|
| 656 | }
|
---|
| 657 |
|
---|
| 658 | /**
|
---|
| 659 | * Gets the `(` token of the given function node.
|
---|
| 660 | * @param {ASTNode} node The function node to get.
|
---|
| 661 | * @param {SourceCode} sourceCode The source code object to get tokens.
|
---|
| 662 | * @returns {Token} `(` token.
|
---|
| 663 | */
|
---|
| 664 | function getOpeningParenOfParams(node, sourceCode) {
|
---|
| 665 |
|
---|
| 666 | // If the node is an arrow function and doesn't have parens, this returns the identifier of the first param.
|
---|
| 667 | if (node.type === "ArrowFunctionExpression" && node.params.length === 1) {
|
---|
| 668 | const argToken = sourceCode.getFirstToken(node.params[0]);
|
---|
| 669 | const maybeParenToken = sourceCode.getTokenBefore(argToken);
|
---|
| 670 |
|
---|
| 671 | return isOpeningParenToken(maybeParenToken) ? maybeParenToken : argToken;
|
---|
| 672 | }
|
---|
| 673 |
|
---|
| 674 | // Otherwise, returns paren.
|
---|
| 675 | return node.id
|
---|
| 676 | ? sourceCode.getTokenAfter(node.id, isOpeningParenToken)
|
---|
| 677 | : sourceCode.getFirstToken(node, isOpeningParenToken);
|
---|
| 678 | }
|
---|
| 679 |
|
---|
| 680 | /**
|
---|
| 681 | * Checks whether or not the tokens of two given nodes are same.
|
---|
| 682 | * @param {ASTNode} left A node 1 to compare.
|
---|
| 683 | * @param {ASTNode} right A node 2 to compare.
|
---|
| 684 | * @param {SourceCode} sourceCode The ESLint source code object.
|
---|
| 685 | * @returns {boolean} the source code for the given node.
|
---|
| 686 | */
|
---|
| 687 | function equalTokens(left, right, sourceCode) {
|
---|
| 688 | const tokensL = sourceCode.getTokens(left);
|
---|
| 689 | const tokensR = sourceCode.getTokens(right);
|
---|
| 690 |
|
---|
| 691 | if (tokensL.length !== tokensR.length) {
|
---|
| 692 | return false;
|
---|
| 693 | }
|
---|
| 694 | for (let i = 0; i < tokensL.length; ++i) {
|
---|
| 695 | if (tokensL[i].type !== tokensR[i].type ||
|
---|
| 696 | tokensL[i].value !== tokensR[i].value
|
---|
| 697 | ) {
|
---|
| 698 | return false;
|
---|
| 699 | }
|
---|
| 700 | }
|
---|
| 701 |
|
---|
| 702 | return true;
|
---|
| 703 | }
|
---|
| 704 |
|
---|
| 705 | /**
|
---|
| 706 | * Check if the given node is a true logical expression or not.
|
---|
| 707 | *
|
---|
| 708 | * The three binary expressions logical-or (`||`), logical-and (`&&`), and
|
---|
| 709 | * coalesce (`??`) are known as `ShortCircuitExpression`.
|
---|
| 710 | * But ESTree represents those by `LogicalExpression` node.
|
---|
| 711 | *
|
---|
| 712 | * This function rejects coalesce expressions of `LogicalExpression` node.
|
---|
| 713 | * @param {ASTNode} node The node to check.
|
---|
| 714 | * @returns {boolean} `true` if the node is `&&` or `||`.
|
---|
| 715 | * @see https://tc39.es/ecma262/#prod-ShortCircuitExpression
|
---|
| 716 | */
|
---|
| 717 | function isLogicalExpression(node) {
|
---|
| 718 | return (
|
---|
| 719 | node.type === "LogicalExpression" &&
|
---|
| 720 | (node.operator === "&&" || node.operator === "||")
|
---|
| 721 | );
|
---|
| 722 | }
|
---|
| 723 |
|
---|
| 724 | /**
|
---|
| 725 | * Check if the given node is a nullish coalescing expression or not.
|
---|
| 726 | *
|
---|
| 727 | * The three binary expressions logical-or (`||`), logical-and (`&&`), and
|
---|
| 728 | * coalesce (`??`) are known as `ShortCircuitExpression`.
|
---|
| 729 | * But ESTree represents those by `LogicalExpression` node.
|
---|
| 730 | *
|
---|
| 731 | * This function finds only coalesce expressions of `LogicalExpression` node.
|
---|
| 732 | * @param {ASTNode} node The node to check.
|
---|
| 733 | * @returns {boolean} `true` if the node is `??`.
|
---|
| 734 | */
|
---|
| 735 | function isCoalesceExpression(node) {
|
---|
| 736 | return node.type === "LogicalExpression" && node.operator === "??";
|
---|
| 737 | }
|
---|
| 738 |
|
---|
| 739 | /**
|
---|
| 740 | * Check if given two nodes are the pair of a logical expression and a coalesce expression.
|
---|
| 741 | * @param {ASTNode} left A node to check.
|
---|
| 742 | * @param {ASTNode} right Another node to check.
|
---|
| 743 | * @returns {boolean} `true` if the two nodes are the pair of a logical expression and a coalesce expression.
|
---|
| 744 | */
|
---|
| 745 | function isMixedLogicalAndCoalesceExpressions(left, right) {
|
---|
| 746 | return (
|
---|
| 747 | (isLogicalExpression(left) && isCoalesceExpression(right)) ||
|
---|
| 748 | (isCoalesceExpression(left) && isLogicalExpression(right))
|
---|
| 749 | );
|
---|
| 750 | }
|
---|
| 751 |
|
---|
| 752 | /**
|
---|
| 753 | * Checks if the given operator is a logical assignment operator.
|
---|
| 754 | * @param {string} operator The operator to check.
|
---|
| 755 | * @returns {boolean} `true` if the operator is a logical assignment operator.
|
---|
| 756 | */
|
---|
| 757 | function isLogicalAssignmentOperator(operator) {
|
---|
| 758 | return LOGICAL_ASSIGNMENT_OPERATORS.has(operator);
|
---|
| 759 | }
|
---|
| 760 |
|
---|
| 761 | /**
|
---|
| 762 | * Get the colon token of the given SwitchCase node.
|
---|
| 763 | * @param {ASTNode} node The SwitchCase node to get.
|
---|
| 764 | * @param {SourceCode} sourceCode The source code object to get tokens.
|
---|
| 765 | * @returns {Token} The colon token of the node.
|
---|
| 766 | */
|
---|
| 767 | function getSwitchCaseColonToken(node, sourceCode) {
|
---|
| 768 | if (node.test) {
|
---|
| 769 | return sourceCode.getTokenAfter(node.test, isColonToken);
|
---|
| 770 | }
|
---|
| 771 | return sourceCode.getFirstToken(node, 1);
|
---|
| 772 | }
|
---|
| 773 |
|
---|
| 774 | /**
|
---|
| 775 | * Gets ESM module export name represented by the given node.
|
---|
| 776 | * @param {ASTNode} node `Identifier` or string `Literal` node in a position
|
---|
| 777 | * that represents a module export name:
|
---|
| 778 | * - `ImportSpecifier#imported`
|
---|
| 779 | * - `ExportSpecifier#local` (if it is a re-export from another module)
|
---|
| 780 | * - `ExportSpecifier#exported`
|
---|
| 781 | * - `ExportAllDeclaration#exported`
|
---|
| 782 | * @returns {string} The module export name.
|
---|
| 783 | */
|
---|
| 784 | function getModuleExportName(node) {
|
---|
| 785 | if (node.type === "Identifier") {
|
---|
| 786 | return node.name;
|
---|
| 787 | }
|
---|
| 788 |
|
---|
| 789 | // string literal
|
---|
| 790 | return node.value;
|
---|
| 791 | }
|
---|
| 792 |
|
---|
| 793 | /**
|
---|
| 794 | * Returns literal's value converted to the Boolean type
|
---|
| 795 | * @param {ASTNode} node any `Literal` node
|
---|
| 796 | * @returns {boolean | null} `true` when node is truthy, `false` when node is falsy,
|
---|
| 797 | * `null` when it cannot be determined.
|
---|
| 798 | */
|
---|
| 799 | function getBooleanValue(node) {
|
---|
| 800 | if (node.value === null) {
|
---|
| 801 |
|
---|
| 802 | /*
|
---|
| 803 | * it might be a null literal or bigint/regex literal in unsupported environments .
|
---|
| 804 | * https://github.com/estree/estree/blob/14df8a024956ea289bd55b9c2226a1d5b8a473ee/es5.md#regexpliteral
|
---|
| 805 | * https://github.com/estree/estree/blob/14df8a024956ea289bd55b9c2226a1d5b8a473ee/es2020.md#bigintliteral
|
---|
| 806 | */
|
---|
| 807 |
|
---|
| 808 | if (node.raw === "null") {
|
---|
| 809 | return false;
|
---|
| 810 | }
|
---|
| 811 |
|
---|
| 812 | // regex is always truthy
|
---|
| 813 | if (typeof node.regex === "object") {
|
---|
| 814 | return true;
|
---|
| 815 | }
|
---|
| 816 |
|
---|
| 817 | return null;
|
---|
| 818 | }
|
---|
| 819 |
|
---|
| 820 | return !!node.value;
|
---|
| 821 | }
|
---|
| 822 |
|
---|
| 823 | /**
|
---|
| 824 | * Checks if a branch node of LogicalExpression short circuits the whole condition
|
---|
| 825 | * @param {ASTNode} node The branch of main condition which needs to be checked
|
---|
| 826 | * @param {string} operator The operator of the main LogicalExpression.
|
---|
| 827 | * @returns {boolean} true when condition short circuits whole condition
|
---|
| 828 | */
|
---|
| 829 | function isLogicalIdentity(node, operator) {
|
---|
| 830 | switch (node.type) {
|
---|
| 831 | case "Literal":
|
---|
| 832 | return (operator === "||" && getBooleanValue(node) === true) ||
|
---|
| 833 | (operator === "&&" && getBooleanValue(node) === false);
|
---|
| 834 |
|
---|
| 835 | case "UnaryExpression":
|
---|
| 836 | return (operator === "&&" && node.operator === "void");
|
---|
| 837 |
|
---|
| 838 | case "LogicalExpression":
|
---|
| 839 |
|
---|
| 840 | /*
|
---|
| 841 | * handles `a && false || b`
|
---|
| 842 | * `false` is an identity element of `&&` but not `||`
|
---|
| 843 | */
|
---|
| 844 | return operator === node.operator &&
|
---|
| 845 | (
|
---|
| 846 | isLogicalIdentity(node.left, operator) ||
|
---|
| 847 | isLogicalIdentity(node.right, operator)
|
---|
| 848 | );
|
---|
| 849 |
|
---|
| 850 | case "AssignmentExpression":
|
---|
| 851 | return ["||=", "&&="].includes(node.operator) &&
|
---|
| 852 | operator === node.operator.slice(0, -1) &&
|
---|
| 853 | isLogicalIdentity(node.right, operator);
|
---|
| 854 |
|
---|
| 855 | // no default
|
---|
| 856 | }
|
---|
| 857 | return false;
|
---|
| 858 | }
|
---|
| 859 |
|
---|
| 860 | /**
|
---|
| 861 | * Checks if an identifier is a reference to a global variable.
|
---|
| 862 | * @param {Scope} scope The scope in which the identifier is referenced.
|
---|
| 863 | * @param {ASTNode} node An identifier node to check.
|
---|
| 864 | * @returns {boolean} `true` if the identifier is a reference to a global variable.
|
---|
| 865 | */
|
---|
| 866 | function isReferenceToGlobalVariable(scope, node) {
|
---|
| 867 | const reference = scope.references.find(ref => ref.identifier === node);
|
---|
| 868 |
|
---|
| 869 | return Boolean(
|
---|
| 870 | reference &&
|
---|
| 871 | reference.resolved &&
|
---|
| 872 | reference.resolved.scope.type === "global" &&
|
---|
| 873 | reference.resolved.defs.length === 0
|
---|
| 874 | );
|
---|
| 875 | }
|
---|
| 876 |
|
---|
| 877 |
|
---|
| 878 | /**
|
---|
| 879 | * Checks if a node has a constant truthiness value.
|
---|
| 880 | * @param {Scope} scope Scope in which the node appears.
|
---|
| 881 | * @param {ASTNode} node The AST node to check.
|
---|
| 882 | * @param {boolean} inBooleanPosition `true` if checking the test of a
|
---|
| 883 | * condition. `false` in all other cases. When `false`, checks if -- for
|
---|
| 884 | * both string and number -- if coerced to that type, the value will
|
---|
| 885 | * be constant.
|
---|
| 886 | * @returns {boolean} true when node's truthiness is constant
|
---|
| 887 | * @private
|
---|
| 888 | */
|
---|
| 889 | function isConstant(scope, node, inBooleanPosition) {
|
---|
| 890 |
|
---|
| 891 | // node.elements can return null values in the case of sparse arrays ex. [,]
|
---|
| 892 | if (!node) {
|
---|
| 893 | return true;
|
---|
| 894 | }
|
---|
| 895 | switch (node.type) {
|
---|
| 896 | case "Literal":
|
---|
| 897 | case "ArrowFunctionExpression":
|
---|
| 898 | case "FunctionExpression":
|
---|
| 899 | return true;
|
---|
| 900 | case "ClassExpression":
|
---|
| 901 | case "ObjectExpression":
|
---|
| 902 |
|
---|
| 903 | /**
|
---|
| 904 | * In theory objects like:
|
---|
| 905 | *
|
---|
| 906 | * `{toString: () => a}`
|
---|
| 907 | * `{valueOf: () => a}`
|
---|
| 908 | *
|
---|
| 909 | * Or a classes like:
|
---|
| 910 | *
|
---|
| 911 | * `class { static toString() { return a } }`
|
---|
| 912 | * `class { static valueOf() { return a } }`
|
---|
| 913 | *
|
---|
| 914 | * Are not constant verifiably when `inBooleanPosition` is
|
---|
| 915 | * false, but it's an edge case we've opted not to handle.
|
---|
| 916 | */
|
---|
| 917 | return true;
|
---|
| 918 | case "TemplateLiteral":
|
---|
| 919 | return (inBooleanPosition && node.quasis.some(quasi => quasi.value.cooked.length)) ||
|
---|
| 920 | node.expressions.every(exp => isConstant(scope, exp, false));
|
---|
| 921 |
|
---|
| 922 | case "ArrayExpression": {
|
---|
| 923 | if (!inBooleanPosition) {
|
---|
| 924 | return node.elements.every(element => isConstant(scope, element, false));
|
---|
| 925 | }
|
---|
| 926 | return true;
|
---|
| 927 | }
|
---|
| 928 |
|
---|
| 929 | case "UnaryExpression":
|
---|
| 930 | if (
|
---|
| 931 | node.operator === "void" ||
|
---|
| 932 | node.operator === "typeof" && inBooleanPosition
|
---|
| 933 | ) {
|
---|
| 934 | return true;
|
---|
| 935 | }
|
---|
| 936 |
|
---|
| 937 | if (node.operator === "!") {
|
---|
| 938 | return isConstant(scope, node.argument, true);
|
---|
| 939 | }
|
---|
| 940 |
|
---|
| 941 | return isConstant(scope, node.argument, false);
|
---|
| 942 |
|
---|
| 943 | case "BinaryExpression":
|
---|
| 944 | return isConstant(scope, node.left, false) &&
|
---|
| 945 | isConstant(scope, node.right, false) &&
|
---|
| 946 | node.operator !== "in";
|
---|
| 947 |
|
---|
| 948 | case "LogicalExpression": {
|
---|
| 949 | const isLeftConstant = isConstant(scope, node.left, inBooleanPosition);
|
---|
| 950 | const isRightConstant = isConstant(scope, node.right, inBooleanPosition);
|
---|
| 951 | const isLeftShortCircuit = (isLeftConstant && isLogicalIdentity(node.left, node.operator));
|
---|
| 952 | const isRightShortCircuit = (inBooleanPosition && isRightConstant && isLogicalIdentity(node.right, node.operator));
|
---|
| 953 |
|
---|
| 954 | return (isLeftConstant && isRightConstant) ||
|
---|
| 955 | isLeftShortCircuit ||
|
---|
| 956 | isRightShortCircuit;
|
---|
| 957 | }
|
---|
| 958 | case "NewExpression":
|
---|
| 959 | return inBooleanPosition;
|
---|
| 960 | case "AssignmentExpression":
|
---|
| 961 | if (node.operator === "=") {
|
---|
| 962 | return isConstant(scope, node.right, inBooleanPosition);
|
---|
| 963 | }
|
---|
| 964 |
|
---|
| 965 | if (["||=", "&&="].includes(node.operator) && inBooleanPosition) {
|
---|
| 966 | return isLogicalIdentity(node.right, node.operator.slice(0, -1));
|
---|
| 967 | }
|
---|
| 968 |
|
---|
| 969 | return false;
|
---|
| 970 |
|
---|
| 971 | case "SequenceExpression":
|
---|
| 972 | return isConstant(scope, node.expressions[node.expressions.length - 1], inBooleanPosition);
|
---|
| 973 | case "SpreadElement":
|
---|
| 974 | return isConstant(scope, node.argument, inBooleanPosition);
|
---|
| 975 | case "CallExpression":
|
---|
| 976 | if (node.callee.type === "Identifier" && node.callee.name === "Boolean") {
|
---|
| 977 | if (node.arguments.length === 0 || isConstant(scope, node.arguments[0], true)) {
|
---|
| 978 | return isReferenceToGlobalVariable(scope, node.callee);
|
---|
| 979 | }
|
---|
| 980 | }
|
---|
| 981 | return false;
|
---|
| 982 | case "Identifier":
|
---|
| 983 | return node.name === "undefined" && isReferenceToGlobalVariable(scope, node);
|
---|
| 984 |
|
---|
| 985 | // no default
|
---|
| 986 | }
|
---|
| 987 | return false;
|
---|
| 988 | }
|
---|
| 989 |
|
---|
| 990 | /**
|
---|
| 991 | * Checks whether a node is an ExpressionStatement at the top level of a file or function body.
|
---|
| 992 | * A top-level ExpressionStatement node is a directive if it contains a single unparenthesized
|
---|
| 993 | * string literal and if it occurs either as the first sibling or immediately after another
|
---|
| 994 | * directive.
|
---|
| 995 | * @param {ASTNode} node The node to check.
|
---|
| 996 | * @returns {boolean} Whether or not the node is an ExpressionStatement at the top level of a
|
---|
| 997 | * file or function body.
|
---|
| 998 | */
|
---|
| 999 | function isTopLevelExpressionStatement(node) {
|
---|
| 1000 | if (node.type !== "ExpressionStatement") {
|
---|
| 1001 | return false;
|
---|
| 1002 | }
|
---|
| 1003 | const parent = node.parent;
|
---|
| 1004 |
|
---|
| 1005 | return parent.type === "Program" || (parent.type === "BlockStatement" && isFunction(parent.parent));
|
---|
| 1006 |
|
---|
| 1007 | }
|
---|
| 1008 |
|
---|
| 1009 | /**
|
---|
| 1010 | * Check whether the given node is a part of a directive prologue or not.
|
---|
| 1011 | * @param {ASTNode} node The node to check.
|
---|
| 1012 | * @returns {boolean} `true` if the node is a part of directive prologue.
|
---|
| 1013 | */
|
---|
| 1014 | function isDirective(node) {
|
---|
| 1015 | return node.type === "ExpressionStatement" && typeof node.directive === "string";
|
---|
| 1016 | }
|
---|
| 1017 |
|
---|
| 1018 | /**
|
---|
| 1019 | * Tests if a node appears at the beginning of an ancestor ExpressionStatement node.
|
---|
| 1020 | * @param {ASTNode} node The node to check.
|
---|
| 1021 | * @returns {boolean} Whether the node appears at the beginning of an ancestor ExpressionStatement node.
|
---|
| 1022 | */
|
---|
| 1023 | function isStartOfExpressionStatement(node) {
|
---|
| 1024 | const start = node.range[0];
|
---|
| 1025 | let ancestor = node;
|
---|
| 1026 |
|
---|
| 1027 | while ((ancestor = ancestor.parent) && ancestor.range[0] === start) {
|
---|
| 1028 | if (ancestor.type === "ExpressionStatement") {
|
---|
| 1029 | return true;
|
---|
| 1030 | }
|
---|
| 1031 | }
|
---|
| 1032 | return false;
|
---|
| 1033 | }
|
---|
| 1034 |
|
---|
| 1035 | /**
|
---|
| 1036 | * Determines whether an opening parenthesis `(`, bracket `[` or backtick ``` ` ``` needs to be preceded by a semicolon.
|
---|
| 1037 | * This opening parenthesis or bracket should be at the start of an `ExpressionStatement` or at the start of the body of an `ArrowFunctionExpression`.
|
---|
| 1038 | * @type {(sourceCode: SourceCode, node: ASTNode) => boolean}
|
---|
| 1039 | * @param {SourceCode} sourceCode The source code object.
|
---|
| 1040 | * @param {ASTNode} node A node at the position where an opening parenthesis or bracket will be inserted.
|
---|
| 1041 | * @returns {boolean} Whether a semicolon is required before the opening parenthesis or braket.
|
---|
| 1042 | */
|
---|
| 1043 | let needsPrecedingSemicolon;
|
---|
| 1044 |
|
---|
| 1045 | {
|
---|
| 1046 | const BREAK_OR_CONTINUE = new Set(["BreakStatement", "ContinueStatement"]);
|
---|
| 1047 |
|
---|
| 1048 | // Declaration types that must contain a string Literal node at the end.
|
---|
| 1049 | const DECLARATIONS = new Set(["ExportAllDeclaration", "ExportNamedDeclaration", "ImportDeclaration"]);
|
---|
| 1050 |
|
---|
| 1051 | const IDENTIFIER_OR_KEYWORD = new Set(["Identifier", "Keyword"]);
|
---|
| 1052 |
|
---|
| 1053 | // Keywords that can immediately precede an ExpressionStatement node, mapped to the their node types.
|
---|
| 1054 | const NODE_TYPES_BY_KEYWORD = {
|
---|
| 1055 | __proto__: null,
|
---|
| 1056 | break: "BreakStatement",
|
---|
| 1057 | continue: "ContinueStatement",
|
---|
| 1058 | debugger: "DebuggerStatement",
|
---|
| 1059 | do: "DoWhileStatement",
|
---|
| 1060 | else: "IfStatement",
|
---|
| 1061 | return: "ReturnStatement",
|
---|
| 1062 | yield: "YieldExpression"
|
---|
| 1063 | };
|
---|
| 1064 |
|
---|
| 1065 | /*
|
---|
| 1066 | * Before an opening parenthesis, postfix `++` and `--` always trigger ASI;
|
---|
| 1067 | * the tokens `:`, `;`, `{` and `=>` don't expect a semicolon, as that would count as an empty statement.
|
---|
| 1068 | */
|
---|
| 1069 | const PUNCTUATORS = new Set([":", ";", "{", "=>", "++", "--"]);
|
---|
| 1070 |
|
---|
| 1071 | /*
|
---|
| 1072 | * Statements that can contain an `ExpressionStatement` after a closing parenthesis.
|
---|
| 1073 | * DoWhileStatement is an exception in that it always triggers ASI after the closing parenthesis.
|
---|
| 1074 | */
|
---|
| 1075 | const STATEMENTS = new Set([
|
---|
| 1076 | "DoWhileStatement",
|
---|
| 1077 | "ForInStatement",
|
---|
| 1078 | "ForOfStatement",
|
---|
| 1079 | "ForStatement",
|
---|
| 1080 | "IfStatement",
|
---|
| 1081 | "WhileStatement",
|
---|
| 1082 | "WithStatement"
|
---|
| 1083 | ]);
|
---|
| 1084 |
|
---|
| 1085 | needsPrecedingSemicolon =
|
---|
| 1086 | function(sourceCode, node) {
|
---|
| 1087 | const prevToken = sourceCode.getTokenBefore(node);
|
---|
| 1088 |
|
---|
| 1089 | if (!prevToken || prevToken.type === "Punctuator" && PUNCTUATORS.has(prevToken.value)) {
|
---|
| 1090 | return false;
|
---|
| 1091 | }
|
---|
| 1092 |
|
---|
| 1093 | const prevNode = sourceCode.getNodeByRangeIndex(prevToken.range[0]);
|
---|
| 1094 |
|
---|
| 1095 | if (isClosingParenToken(prevToken)) {
|
---|
| 1096 | return !STATEMENTS.has(prevNode.type);
|
---|
| 1097 | }
|
---|
| 1098 |
|
---|
| 1099 | if (isClosingBraceToken(prevToken)) {
|
---|
| 1100 | return (
|
---|
| 1101 | prevNode.type === "BlockStatement" && prevNode.parent.type === "FunctionExpression" ||
|
---|
| 1102 | prevNode.type === "ClassBody" && prevNode.parent.type === "ClassExpression" ||
|
---|
| 1103 | prevNode.type === "ObjectExpression"
|
---|
| 1104 | );
|
---|
| 1105 | }
|
---|
| 1106 |
|
---|
| 1107 | if (IDENTIFIER_OR_KEYWORD.has(prevToken.type)) {
|
---|
| 1108 | if (BREAK_OR_CONTINUE.has(prevNode.parent.type)) {
|
---|
| 1109 | return false;
|
---|
| 1110 | }
|
---|
| 1111 |
|
---|
| 1112 | const keyword = prevToken.value;
|
---|
| 1113 | const nodeType = NODE_TYPES_BY_KEYWORD[keyword];
|
---|
| 1114 |
|
---|
| 1115 | return prevNode.type !== nodeType;
|
---|
| 1116 | }
|
---|
| 1117 |
|
---|
| 1118 | if (prevToken.type === "String") {
|
---|
| 1119 | return !DECLARATIONS.has(prevNode.parent.type);
|
---|
| 1120 | }
|
---|
| 1121 |
|
---|
| 1122 | return true;
|
---|
| 1123 | };
|
---|
| 1124 | }
|
---|
| 1125 |
|
---|
| 1126 | //------------------------------------------------------------------------------
|
---|
| 1127 | // Public Interface
|
---|
| 1128 | //------------------------------------------------------------------------------
|
---|
| 1129 |
|
---|
| 1130 | module.exports = {
|
---|
| 1131 | COMMENTS_IGNORE_PATTERN,
|
---|
| 1132 | LINEBREAKS,
|
---|
| 1133 | LINEBREAK_MATCHER: lineBreakPattern,
|
---|
| 1134 | SHEBANG_MATCHER: shebangPattern,
|
---|
| 1135 | STATEMENT_LIST_PARENTS,
|
---|
| 1136 |
|
---|
| 1137 | /**
|
---|
| 1138 | * Determines whether two adjacent tokens are on the same line.
|
---|
| 1139 | * @param {Object} left The left token object.
|
---|
| 1140 | * @param {Object} right The right token object.
|
---|
| 1141 | * @returns {boolean} Whether or not the tokens are on the same line.
|
---|
| 1142 | * @public
|
---|
| 1143 | */
|
---|
| 1144 | isTokenOnSameLine(left, right) {
|
---|
| 1145 | return left.loc.end.line === right.loc.start.line;
|
---|
| 1146 | },
|
---|
| 1147 |
|
---|
| 1148 | isNullOrUndefined,
|
---|
| 1149 | isCallee,
|
---|
| 1150 | isES5Constructor,
|
---|
| 1151 | getUpperFunction,
|
---|
| 1152 | isFunction,
|
---|
| 1153 | isLoop,
|
---|
| 1154 | isInLoop,
|
---|
| 1155 | isArrayFromMethod,
|
---|
| 1156 | isParenthesised,
|
---|
| 1157 | createGlobalLinebreakMatcher,
|
---|
| 1158 | equalTokens,
|
---|
| 1159 |
|
---|
| 1160 | isArrowToken,
|
---|
| 1161 | isClosingBraceToken,
|
---|
| 1162 | isClosingBracketToken,
|
---|
| 1163 | isClosingParenToken,
|
---|
| 1164 | isColonToken,
|
---|
| 1165 | isCommaToken,
|
---|
| 1166 | isCommentToken,
|
---|
| 1167 | isDotToken,
|
---|
| 1168 | isQuestionDotToken,
|
---|
| 1169 | isKeywordToken,
|
---|
| 1170 | isNotClosingBraceToken: negate(isClosingBraceToken),
|
---|
| 1171 | isNotClosingBracketToken: negate(isClosingBracketToken),
|
---|
| 1172 | isNotClosingParenToken: negate(isClosingParenToken),
|
---|
| 1173 | isNotColonToken: negate(isColonToken),
|
---|
| 1174 | isNotCommaToken: negate(isCommaToken),
|
---|
| 1175 | isNotDotToken: negate(isDotToken),
|
---|
| 1176 | isNotQuestionDotToken: negate(isQuestionDotToken),
|
---|
| 1177 | isNotOpeningBraceToken: negate(isOpeningBraceToken),
|
---|
| 1178 | isNotOpeningBracketToken: negate(isOpeningBracketToken),
|
---|
| 1179 | isNotOpeningParenToken: negate(isOpeningParenToken),
|
---|
| 1180 | isNotSemicolonToken: negate(isSemicolonToken),
|
---|
| 1181 | isOpeningBraceToken,
|
---|
| 1182 | isOpeningBracketToken,
|
---|
| 1183 | isOpeningParenToken,
|
---|
| 1184 | isSemicolonToken,
|
---|
| 1185 | isEqToken,
|
---|
| 1186 |
|
---|
| 1187 | /**
|
---|
| 1188 | * Checks whether or not a given node is a string literal.
|
---|
| 1189 | * @param {ASTNode} node A node to check.
|
---|
| 1190 | * @returns {boolean} `true` if the node is a string literal.
|
---|
| 1191 | */
|
---|
| 1192 | isStringLiteral(node) {
|
---|
| 1193 | return (
|
---|
| 1194 | (node.type === "Literal" && typeof node.value === "string") ||
|
---|
| 1195 | node.type === "TemplateLiteral"
|
---|
| 1196 | );
|
---|
| 1197 | },
|
---|
| 1198 |
|
---|
| 1199 | /**
|
---|
| 1200 | * Checks whether a given node is a breakable statement or not.
|
---|
| 1201 | * The node is breakable if the node is one of the following type:
|
---|
| 1202 | *
|
---|
| 1203 | * - DoWhileStatement
|
---|
| 1204 | * - ForInStatement
|
---|
| 1205 | * - ForOfStatement
|
---|
| 1206 | * - ForStatement
|
---|
| 1207 | * - SwitchStatement
|
---|
| 1208 | * - WhileStatement
|
---|
| 1209 | * @param {ASTNode} node A node to check.
|
---|
| 1210 | * @returns {boolean} `true` if the node is breakable.
|
---|
| 1211 | */
|
---|
| 1212 | isBreakableStatement(node) {
|
---|
| 1213 | return breakableTypePattern.test(node.type);
|
---|
| 1214 | },
|
---|
| 1215 |
|
---|
| 1216 | /**
|
---|
| 1217 | * Gets references which are non initializer and writable.
|
---|
| 1218 | * @param {Reference[]} references An array of references.
|
---|
| 1219 | * @returns {Reference[]} An array of only references which are non initializer and writable.
|
---|
| 1220 | * @public
|
---|
| 1221 | */
|
---|
| 1222 | getModifyingReferences(references) {
|
---|
| 1223 | return references.filter(isModifyingReference);
|
---|
| 1224 | },
|
---|
| 1225 |
|
---|
| 1226 | /**
|
---|
| 1227 | * Validate that a string passed in is surrounded by the specified character
|
---|
| 1228 | * @param {string} val The text to check.
|
---|
| 1229 | * @param {string} character The character to see if it's surrounded by.
|
---|
| 1230 | * @returns {boolean} True if the text is surrounded by the character, false if not.
|
---|
| 1231 | * @private
|
---|
| 1232 | */
|
---|
| 1233 | isSurroundedBy(val, character) {
|
---|
| 1234 | return val[0] === character && val[val.length - 1] === character;
|
---|
| 1235 | },
|
---|
| 1236 |
|
---|
| 1237 | /**
|
---|
| 1238 | * Returns whether the provided node is an ESLint directive comment or not
|
---|
| 1239 | * @param {Line|Block} node The comment token to be checked
|
---|
| 1240 | * @returns {boolean} `true` if the node is an ESLint directive comment
|
---|
| 1241 | */
|
---|
| 1242 | isDirectiveComment(node) {
|
---|
| 1243 | const comment = node.value.trim();
|
---|
| 1244 |
|
---|
| 1245 | return (
|
---|
| 1246 | node.type === "Line" && comment.startsWith("eslint-") ||
|
---|
| 1247 | node.type === "Block" && ESLINT_DIRECTIVE_PATTERN.test(comment)
|
---|
| 1248 | );
|
---|
| 1249 | },
|
---|
| 1250 |
|
---|
| 1251 | /**
|
---|
| 1252 | * Gets the trailing statement of a given node.
|
---|
| 1253 | *
|
---|
| 1254 | * if (code)
|
---|
| 1255 | * consequent;
|
---|
| 1256 | *
|
---|
| 1257 | * When taking this `IfStatement`, returns `consequent;` statement.
|
---|
| 1258 | * @param {ASTNode} A node to get.
|
---|
| 1259 | * @returns {ASTNode|null} The trailing statement's node.
|
---|
| 1260 | */
|
---|
| 1261 | getTrailingStatement: esutils.ast.trailingStatement,
|
---|
| 1262 |
|
---|
| 1263 | /**
|
---|
| 1264 | * Finds the variable by a given name in a given scope and its upper scopes.
|
---|
| 1265 | * @param {eslint-scope.Scope} initScope A scope to start find.
|
---|
| 1266 | * @param {string} name A variable name to find.
|
---|
| 1267 | * @returns {eslint-scope.Variable|null} A found variable or `null`.
|
---|
| 1268 | */
|
---|
| 1269 | getVariableByName(initScope, name) {
|
---|
| 1270 | let scope = initScope;
|
---|
| 1271 |
|
---|
| 1272 | while (scope) {
|
---|
| 1273 | const variable = scope.set.get(name);
|
---|
| 1274 |
|
---|
| 1275 | if (variable) {
|
---|
| 1276 | return variable;
|
---|
| 1277 | }
|
---|
| 1278 |
|
---|
| 1279 | scope = scope.upper;
|
---|
| 1280 | }
|
---|
| 1281 |
|
---|
| 1282 | return null;
|
---|
| 1283 | },
|
---|
| 1284 |
|
---|
| 1285 | /**
|
---|
| 1286 | * Checks whether or not a given function node is the default `this` binding.
|
---|
| 1287 | *
|
---|
| 1288 | * First, this checks the node:
|
---|
| 1289 | *
|
---|
| 1290 | * - The given node is not in `PropertyDefinition#value` position.
|
---|
| 1291 | * - The given node is not `StaticBlock`.
|
---|
| 1292 | * - The function name does not start with uppercase. It's a convention to capitalize the names
|
---|
| 1293 | * of constructor functions. This check is not performed if `capIsConstructor` is set to `false`.
|
---|
| 1294 | * - The function does not have a JSDoc comment that has a @this tag.
|
---|
| 1295 | *
|
---|
| 1296 | * Next, this checks the location of the node.
|
---|
| 1297 | * If the location is below, this judges `this` is valid.
|
---|
| 1298 | *
|
---|
| 1299 | * - The location is not on an object literal.
|
---|
| 1300 | * - The location is not assigned to a variable which starts with an uppercase letter. Applies to anonymous
|
---|
| 1301 | * functions only, as the name of the variable is considered to be the name of the function in this case.
|
---|
| 1302 | * This check is not performed if `capIsConstructor` is set to `false`.
|
---|
| 1303 | * - The location is not on an ES2015 class.
|
---|
| 1304 | * - Its `bind`/`call`/`apply` method is not called directly.
|
---|
| 1305 | * - The function is not a callback of array methods (such as `.forEach()`) if `thisArg` is given.
|
---|
| 1306 | * @param {ASTNode} node A function node to check. It also can be an implicit function, like `StaticBlock`
|
---|
| 1307 | * or any expression that is `PropertyDefinition#value` node.
|
---|
| 1308 | * @param {SourceCode} sourceCode A SourceCode instance to get comments.
|
---|
| 1309 | * @param {boolean} [capIsConstructor = true] `false` disables the assumption that functions which name starts
|
---|
| 1310 | * with an uppercase or are assigned to a variable which name starts with an uppercase are constructors.
|
---|
| 1311 | * @returns {boolean} The function node is the default `this` binding.
|
---|
| 1312 | */
|
---|
| 1313 | isDefaultThisBinding(node, sourceCode, { capIsConstructor = true } = {}) {
|
---|
| 1314 |
|
---|
| 1315 | /*
|
---|
| 1316 | * Class field initializers are implicit functions, but ESTree doesn't have the AST node of field initializers.
|
---|
| 1317 | * Therefore, A expression node at `PropertyDefinition#value` is a function.
|
---|
| 1318 | * In this case, `this` is always not default binding.
|
---|
| 1319 | */
|
---|
| 1320 | if (node.parent.type === "PropertyDefinition" && node.parent.value === node) {
|
---|
| 1321 | return false;
|
---|
| 1322 | }
|
---|
| 1323 |
|
---|
| 1324 | // Class static blocks are implicit functions. In this case, `this` is always not default binding.
|
---|
| 1325 | if (node.type === "StaticBlock") {
|
---|
| 1326 | return false;
|
---|
| 1327 | }
|
---|
| 1328 |
|
---|
| 1329 | if (
|
---|
| 1330 | (capIsConstructor && isES5Constructor(node)) ||
|
---|
| 1331 | hasJSDocThisTag(node, sourceCode)
|
---|
| 1332 | ) {
|
---|
| 1333 | return false;
|
---|
| 1334 | }
|
---|
| 1335 | const isAnonymous = node.id === null;
|
---|
| 1336 | let currentNode = node;
|
---|
| 1337 |
|
---|
| 1338 | while (currentNode) {
|
---|
| 1339 | const parent = currentNode.parent;
|
---|
| 1340 |
|
---|
| 1341 | switch (parent.type) {
|
---|
| 1342 |
|
---|
| 1343 | /*
|
---|
| 1344 | * Looks up the destination.
|
---|
| 1345 | * e.g., obj.foo = nativeFoo || function foo() { ... };
|
---|
| 1346 | */
|
---|
| 1347 | case "LogicalExpression":
|
---|
| 1348 | case "ConditionalExpression":
|
---|
| 1349 | case "ChainExpression":
|
---|
| 1350 | currentNode = parent;
|
---|
| 1351 | break;
|
---|
| 1352 |
|
---|
| 1353 | /*
|
---|
| 1354 | * If the upper function is IIFE, checks the destination of the return value.
|
---|
| 1355 | * e.g.
|
---|
| 1356 | * obj.foo = (function() {
|
---|
| 1357 | * // setup...
|
---|
| 1358 | * return function foo() { ... };
|
---|
| 1359 | * })();
|
---|
| 1360 | * obj.foo = (() =>
|
---|
| 1361 | * function foo() { ... }
|
---|
| 1362 | * )();
|
---|
| 1363 | */
|
---|
| 1364 | case "ReturnStatement": {
|
---|
| 1365 | const func = getUpperFunction(parent);
|
---|
| 1366 |
|
---|
| 1367 | if (func === null || !isCallee(func)) {
|
---|
| 1368 | return true;
|
---|
| 1369 | }
|
---|
| 1370 | currentNode = func.parent;
|
---|
| 1371 | break;
|
---|
| 1372 | }
|
---|
| 1373 | case "ArrowFunctionExpression":
|
---|
| 1374 | if (currentNode !== parent.body || !isCallee(parent)) {
|
---|
| 1375 | return true;
|
---|
| 1376 | }
|
---|
| 1377 | currentNode = parent.parent;
|
---|
| 1378 | break;
|
---|
| 1379 |
|
---|
| 1380 | /*
|
---|
| 1381 | * e.g.
|
---|
| 1382 | * var obj = { foo() { ... } };
|
---|
| 1383 | * var obj = { foo: function() { ... } };
|
---|
| 1384 | * class A { constructor() { ... } }
|
---|
| 1385 | * class A { foo() { ... } }
|
---|
| 1386 | * class A { get foo() { ... } }
|
---|
| 1387 | * class A { set foo() { ... } }
|
---|
| 1388 | * class A { static foo() { ... } }
|
---|
| 1389 | * class A { foo = function() { ... } }
|
---|
| 1390 | */
|
---|
| 1391 | case "Property":
|
---|
| 1392 | case "PropertyDefinition":
|
---|
| 1393 | case "MethodDefinition":
|
---|
| 1394 | return parent.value !== currentNode;
|
---|
| 1395 |
|
---|
| 1396 | /*
|
---|
| 1397 | * e.g.
|
---|
| 1398 | * obj.foo = function foo() { ... };
|
---|
| 1399 | * Foo = function() { ... };
|
---|
| 1400 | * [obj.foo = function foo() { ... }] = a;
|
---|
| 1401 | * [Foo = function() { ... }] = a;
|
---|
| 1402 | */
|
---|
| 1403 | case "AssignmentExpression":
|
---|
| 1404 | case "AssignmentPattern":
|
---|
| 1405 | if (parent.left.type === "MemberExpression") {
|
---|
| 1406 | return false;
|
---|
| 1407 | }
|
---|
| 1408 | if (
|
---|
| 1409 | capIsConstructor &&
|
---|
| 1410 | isAnonymous &&
|
---|
| 1411 | parent.left.type === "Identifier" &&
|
---|
| 1412 | startsWithUpperCase(parent.left.name)
|
---|
| 1413 | ) {
|
---|
| 1414 | return false;
|
---|
| 1415 | }
|
---|
| 1416 | return true;
|
---|
| 1417 |
|
---|
| 1418 | /*
|
---|
| 1419 | * e.g.
|
---|
| 1420 | * var Foo = function() { ... };
|
---|
| 1421 | */
|
---|
| 1422 | case "VariableDeclarator":
|
---|
| 1423 | return !(
|
---|
| 1424 | capIsConstructor &&
|
---|
| 1425 | isAnonymous &&
|
---|
| 1426 | parent.init === currentNode &&
|
---|
| 1427 | parent.id.type === "Identifier" &&
|
---|
| 1428 | startsWithUpperCase(parent.id.name)
|
---|
| 1429 | );
|
---|
| 1430 |
|
---|
| 1431 | /*
|
---|
| 1432 | * e.g.
|
---|
| 1433 | * var foo = function foo() { ... }.bind(obj);
|
---|
| 1434 | * (function foo() { ... }).call(obj);
|
---|
| 1435 | * (function foo() { ... }).apply(obj, []);
|
---|
| 1436 | */
|
---|
| 1437 | case "MemberExpression":
|
---|
| 1438 | if (
|
---|
| 1439 | parent.object === currentNode &&
|
---|
| 1440 | isSpecificMemberAccess(parent, null, bindOrCallOrApplyPattern)
|
---|
| 1441 | ) {
|
---|
| 1442 | const maybeCalleeNode = parent.parent.type === "ChainExpression"
|
---|
| 1443 | ? parent.parent
|
---|
| 1444 | : parent;
|
---|
| 1445 |
|
---|
| 1446 | return !(
|
---|
| 1447 | isCallee(maybeCalleeNode) &&
|
---|
| 1448 | maybeCalleeNode.parent.arguments.length >= 1 &&
|
---|
| 1449 | !isNullOrUndefined(maybeCalleeNode.parent.arguments[0])
|
---|
| 1450 | );
|
---|
| 1451 | }
|
---|
| 1452 | return true;
|
---|
| 1453 |
|
---|
| 1454 | /*
|
---|
| 1455 | * e.g.
|
---|
| 1456 | * Reflect.apply(function() {}, obj, []);
|
---|
| 1457 | * Array.from([], function() {}, obj);
|
---|
| 1458 | * list.forEach(function() {}, obj);
|
---|
| 1459 | */
|
---|
| 1460 | case "CallExpression":
|
---|
| 1461 | if (isReflectApply(parent.callee)) {
|
---|
| 1462 | return (
|
---|
| 1463 | parent.arguments.length !== 3 ||
|
---|
| 1464 | parent.arguments[0] !== currentNode ||
|
---|
| 1465 | isNullOrUndefined(parent.arguments[1])
|
---|
| 1466 | );
|
---|
| 1467 | }
|
---|
| 1468 | if (isArrayFromMethod(parent.callee)) {
|
---|
| 1469 | return (
|
---|
| 1470 | parent.arguments.length !== 3 ||
|
---|
| 1471 | parent.arguments[1] !== currentNode ||
|
---|
| 1472 | isNullOrUndefined(parent.arguments[2])
|
---|
| 1473 | );
|
---|
| 1474 | }
|
---|
| 1475 | if (isMethodWhichHasThisArg(parent.callee)) {
|
---|
| 1476 | return (
|
---|
| 1477 | parent.arguments.length !== 2 ||
|
---|
| 1478 | parent.arguments[0] !== currentNode ||
|
---|
| 1479 | isNullOrUndefined(parent.arguments[1])
|
---|
| 1480 | );
|
---|
| 1481 | }
|
---|
| 1482 | return true;
|
---|
| 1483 |
|
---|
| 1484 | // Otherwise `this` is default.
|
---|
| 1485 | default:
|
---|
| 1486 | return true;
|
---|
| 1487 | }
|
---|
| 1488 | }
|
---|
| 1489 |
|
---|
| 1490 | /* c8 ignore next */
|
---|
| 1491 | return true;
|
---|
| 1492 | },
|
---|
| 1493 |
|
---|
| 1494 | /**
|
---|
| 1495 | * Get the precedence level based on the node type
|
---|
| 1496 | * @param {ASTNode} node node to evaluate
|
---|
| 1497 | * @returns {int} precedence level
|
---|
| 1498 | * @private
|
---|
| 1499 | */
|
---|
| 1500 | getPrecedence(node) {
|
---|
| 1501 | switch (node.type) {
|
---|
| 1502 | case "SequenceExpression":
|
---|
| 1503 | return 0;
|
---|
| 1504 |
|
---|
| 1505 | case "AssignmentExpression":
|
---|
| 1506 | case "ArrowFunctionExpression":
|
---|
| 1507 | case "YieldExpression":
|
---|
| 1508 | return 1;
|
---|
| 1509 |
|
---|
| 1510 | case "ConditionalExpression":
|
---|
| 1511 | return 3;
|
---|
| 1512 |
|
---|
| 1513 | case "LogicalExpression":
|
---|
| 1514 | switch (node.operator) {
|
---|
| 1515 | case "||":
|
---|
| 1516 | case "??":
|
---|
| 1517 | return 4;
|
---|
| 1518 | case "&&":
|
---|
| 1519 | return 5;
|
---|
| 1520 |
|
---|
| 1521 | // no default
|
---|
| 1522 | }
|
---|
| 1523 |
|
---|
| 1524 | /* falls through */
|
---|
| 1525 |
|
---|
| 1526 | case "BinaryExpression":
|
---|
| 1527 |
|
---|
| 1528 | switch (node.operator) {
|
---|
| 1529 | case "|":
|
---|
| 1530 | return 6;
|
---|
| 1531 | case "^":
|
---|
| 1532 | return 7;
|
---|
| 1533 | case "&":
|
---|
| 1534 | return 8;
|
---|
| 1535 | case "==":
|
---|
| 1536 | case "!=":
|
---|
| 1537 | case "===":
|
---|
| 1538 | case "!==":
|
---|
| 1539 | return 9;
|
---|
| 1540 | case "<":
|
---|
| 1541 | case "<=":
|
---|
| 1542 | case ">":
|
---|
| 1543 | case ">=":
|
---|
| 1544 | case "in":
|
---|
| 1545 | case "instanceof":
|
---|
| 1546 | return 10;
|
---|
| 1547 | case "<<":
|
---|
| 1548 | case ">>":
|
---|
| 1549 | case ">>>":
|
---|
| 1550 | return 11;
|
---|
| 1551 | case "+":
|
---|
| 1552 | case "-":
|
---|
| 1553 | return 12;
|
---|
| 1554 | case "*":
|
---|
| 1555 | case "/":
|
---|
| 1556 | case "%":
|
---|
| 1557 | return 13;
|
---|
| 1558 | case "**":
|
---|
| 1559 | return 15;
|
---|
| 1560 |
|
---|
| 1561 | // no default
|
---|
| 1562 | }
|
---|
| 1563 |
|
---|
| 1564 | /* falls through */
|
---|
| 1565 |
|
---|
| 1566 | case "UnaryExpression":
|
---|
| 1567 | case "AwaitExpression":
|
---|
| 1568 | return 16;
|
---|
| 1569 |
|
---|
| 1570 | case "UpdateExpression":
|
---|
| 1571 | return 17;
|
---|
| 1572 |
|
---|
| 1573 | case "CallExpression":
|
---|
| 1574 | case "ChainExpression":
|
---|
| 1575 | case "ImportExpression":
|
---|
| 1576 | return 18;
|
---|
| 1577 |
|
---|
| 1578 | case "NewExpression":
|
---|
| 1579 | return 19;
|
---|
| 1580 |
|
---|
| 1581 | default:
|
---|
| 1582 | if (node.type in eslintVisitorKeys) {
|
---|
| 1583 | return 20;
|
---|
| 1584 | }
|
---|
| 1585 |
|
---|
| 1586 | /*
|
---|
| 1587 | * if the node is not a standard node that we know about, then assume it has the lowest precedence
|
---|
| 1588 | * this will mean that rules will wrap unknown nodes in parentheses where applicable instead of
|
---|
| 1589 | * unwrapping them and potentially changing the meaning of the code or introducing a syntax error.
|
---|
| 1590 | */
|
---|
| 1591 | return -1;
|
---|
| 1592 | }
|
---|
| 1593 | },
|
---|
| 1594 |
|
---|
| 1595 | /**
|
---|
| 1596 | * Checks whether the given node is an empty block node or not.
|
---|
| 1597 | * @param {ASTNode|null} node The node to check.
|
---|
| 1598 | * @returns {boolean} `true` if the node is an empty block.
|
---|
| 1599 | */
|
---|
| 1600 | isEmptyBlock(node) {
|
---|
| 1601 | return Boolean(node && node.type === "BlockStatement" && node.body.length === 0);
|
---|
| 1602 | },
|
---|
| 1603 |
|
---|
| 1604 | /**
|
---|
| 1605 | * Checks whether the given node is an empty function node or not.
|
---|
| 1606 | * @param {ASTNode|null} node The node to check.
|
---|
| 1607 | * @returns {boolean} `true` if the node is an empty function.
|
---|
| 1608 | */
|
---|
| 1609 | isEmptyFunction(node) {
|
---|
| 1610 | return isFunction(node) && module.exports.isEmptyBlock(node.body);
|
---|
| 1611 | },
|
---|
| 1612 |
|
---|
| 1613 | /**
|
---|
| 1614 | * Get directives from directive prologue of a Program or Function node.
|
---|
| 1615 | * @param {ASTNode} node The node to check.
|
---|
| 1616 | * @returns {ASTNode[]} The directives found in the directive prologue.
|
---|
| 1617 | */
|
---|
| 1618 | getDirectivePrologue(node) {
|
---|
| 1619 | const directives = [];
|
---|
| 1620 |
|
---|
| 1621 | // Directive prologues only occur at the top of files or functions.
|
---|
| 1622 | if (
|
---|
| 1623 | node.type === "Program" ||
|
---|
| 1624 | node.type === "FunctionDeclaration" ||
|
---|
| 1625 | node.type === "FunctionExpression" ||
|
---|
| 1626 |
|
---|
| 1627 | /*
|
---|
| 1628 | * Do not check arrow functions with implicit return.
|
---|
| 1629 | * `() => "use strict";` returns the string `"use strict"`.
|
---|
| 1630 | */
|
---|
| 1631 | (node.type === "ArrowFunctionExpression" && node.body.type === "BlockStatement")
|
---|
| 1632 | ) {
|
---|
| 1633 | const statements = node.type === "Program" ? node.body : node.body.body;
|
---|
| 1634 |
|
---|
| 1635 | for (const statement of statements) {
|
---|
| 1636 | if (
|
---|
| 1637 | statement.type === "ExpressionStatement" &&
|
---|
| 1638 | statement.expression.type === "Literal"
|
---|
| 1639 | ) {
|
---|
| 1640 | directives.push(statement);
|
---|
| 1641 | } else {
|
---|
| 1642 | break;
|
---|
| 1643 | }
|
---|
| 1644 | }
|
---|
| 1645 | }
|
---|
| 1646 |
|
---|
| 1647 | return directives;
|
---|
| 1648 | },
|
---|
| 1649 |
|
---|
| 1650 | /**
|
---|
| 1651 | * Determines whether this node is a decimal integer literal. If a node is a decimal integer literal, a dot added
|
---|
| 1652 | * after the node will be parsed as a decimal point, rather than a property-access dot.
|
---|
| 1653 | * @param {ASTNode} node The node to check.
|
---|
| 1654 | * @returns {boolean} `true` if this node is a decimal integer.
|
---|
| 1655 | * @example
|
---|
| 1656 | *
|
---|
| 1657 | * 0 // true
|
---|
| 1658 | * 5 // true
|
---|
| 1659 | * 50 // true
|
---|
| 1660 | * 5_000 // true
|
---|
| 1661 | * 1_234_56 // true
|
---|
| 1662 | * 08 // true
|
---|
| 1663 | * 0192 // true
|
---|
| 1664 | * 5. // false
|
---|
| 1665 | * .5 // false
|
---|
| 1666 | * 5.0 // false
|
---|
| 1667 | * 5.00_00 // false
|
---|
| 1668 | * 05 // false
|
---|
| 1669 | * 0x5 // false
|
---|
| 1670 | * 0b101 // false
|
---|
| 1671 | * 0b11_01 // false
|
---|
| 1672 | * 0o5 // false
|
---|
| 1673 | * 5e0 // false
|
---|
| 1674 | * 5e1_000 // false
|
---|
| 1675 | * 5n // false
|
---|
| 1676 | * 1_000n // false
|
---|
| 1677 | * "5" // false
|
---|
| 1678 | *
|
---|
| 1679 | */
|
---|
| 1680 | isDecimalInteger(node) {
|
---|
| 1681 | return node.type === "Literal" && typeof node.value === "number" &&
|
---|
| 1682 | DECIMAL_INTEGER_PATTERN.test(node.raw);
|
---|
| 1683 | },
|
---|
| 1684 |
|
---|
| 1685 | /**
|
---|
| 1686 | * Determines whether this token is a decimal integer numeric token.
|
---|
| 1687 | * This is similar to isDecimalInteger(), but for tokens.
|
---|
| 1688 | * @param {Token} token The token to check.
|
---|
| 1689 | * @returns {boolean} `true` if this token is a decimal integer.
|
---|
| 1690 | */
|
---|
| 1691 | isDecimalIntegerNumericToken(token) {
|
---|
| 1692 | return token.type === "Numeric" && DECIMAL_INTEGER_PATTERN.test(token.value);
|
---|
| 1693 | },
|
---|
| 1694 |
|
---|
| 1695 | /**
|
---|
| 1696 | * Gets the name and kind of the given function node.
|
---|
| 1697 | *
|
---|
| 1698 | * - `function foo() {}` .................... `function 'foo'`
|
---|
| 1699 | * - `(function foo() {})` .................. `function 'foo'`
|
---|
| 1700 | * - `(function() {})` ...................... `function`
|
---|
| 1701 | * - `function* foo() {}` ................... `generator function 'foo'`
|
---|
| 1702 | * - `(function* foo() {})` ................. `generator function 'foo'`
|
---|
| 1703 | * - `(function*() {})` ..................... `generator function`
|
---|
| 1704 | * - `() => {}` ............................. `arrow function`
|
---|
| 1705 | * - `async () => {}` ....................... `async arrow function`
|
---|
| 1706 | * - `({ foo: function foo() {} })` ......... `method 'foo'`
|
---|
| 1707 | * - `({ foo: function() {} })` ............. `method 'foo'`
|
---|
| 1708 | * - `({ ['foo']: function() {} })` ......... `method 'foo'`
|
---|
| 1709 | * - `({ [foo]: function() {} })` ........... `method`
|
---|
| 1710 | * - `({ foo() {} })` ....................... `method 'foo'`
|
---|
| 1711 | * - `({ foo: function* foo() {} })` ........ `generator method 'foo'`
|
---|
| 1712 | * - `({ foo: function*() {} })` ............ `generator method 'foo'`
|
---|
| 1713 | * - `({ ['foo']: function*() {} })` ........ `generator method 'foo'`
|
---|
| 1714 | * - `({ [foo]: function*() {} })` .......... `generator method`
|
---|
| 1715 | * - `({ *foo() {} })` ...................... `generator method 'foo'`
|
---|
| 1716 | * - `({ foo: async function foo() {} })` ... `async method 'foo'`
|
---|
| 1717 | * - `({ foo: async function() {} })` ....... `async method 'foo'`
|
---|
| 1718 | * - `({ ['foo']: async function() {} })` ... `async method 'foo'`
|
---|
| 1719 | * - `({ [foo]: async function() {} })` ..... `async method`
|
---|
| 1720 | * - `({ async foo() {} })` ................. `async method 'foo'`
|
---|
| 1721 | * - `({ get foo() {} })` ................... `getter 'foo'`
|
---|
| 1722 | * - `({ set foo(a) {} })` .................. `setter 'foo'`
|
---|
| 1723 | * - `class A { constructor() {} }` ......... `constructor`
|
---|
| 1724 | * - `class A { foo() {} }` ................. `method 'foo'`
|
---|
| 1725 | * - `class A { *foo() {} }` ................ `generator method 'foo'`
|
---|
| 1726 | * - `class A { async foo() {} }` ........... `async method 'foo'`
|
---|
| 1727 | * - `class A { ['foo']() {} }` ............. `method 'foo'`
|
---|
| 1728 | * - `class A { *['foo']() {} }` ............ `generator method 'foo'`
|
---|
| 1729 | * - `class A { async ['foo']() {} }` ....... `async method 'foo'`
|
---|
| 1730 | * - `class A { [foo]() {} }` ............... `method`
|
---|
| 1731 | * - `class A { *[foo]() {} }` .............. `generator method`
|
---|
| 1732 | * - `class A { async [foo]() {} }` ......... `async method`
|
---|
| 1733 | * - `class A { get foo() {} }` ............. `getter 'foo'`
|
---|
| 1734 | * - `class A { set foo(a) {} }` ............ `setter 'foo'`
|
---|
| 1735 | * - `class A { static foo() {} }` .......... `static method 'foo'`
|
---|
| 1736 | * - `class A { static *foo() {} }` ......... `static generator method 'foo'`
|
---|
| 1737 | * - `class A { static async foo() {} }` .... `static async method 'foo'`
|
---|
| 1738 | * - `class A { static get foo() {} }` ...... `static getter 'foo'`
|
---|
| 1739 | * - `class A { static set foo(a) {} }` ..... `static setter 'foo'`
|
---|
| 1740 | * - `class A { foo = () => {}; }` .......... `method 'foo'`
|
---|
| 1741 | * - `class A { foo = function() {}; }` ..... `method 'foo'`
|
---|
| 1742 | * - `class A { foo = function bar() {}; }` . `method 'foo'`
|
---|
| 1743 | * - `class A { static foo = () => {}; }` ... `static method 'foo'`
|
---|
| 1744 | * - `class A { '#foo' = () => {}; }` ....... `method '#foo'`
|
---|
| 1745 | * - `class A { #foo = () => {}; }` ......... `private method #foo`
|
---|
| 1746 | * - `class A { static #foo = () => {}; }` .. `static private method #foo`
|
---|
| 1747 | * - `class A { '#foo'() {} }` .............. `method '#foo'`
|
---|
| 1748 | * - `class A { #foo() {} }` ................ `private method #foo`
|
---|
| 1749 | * - `class A { static #foo() {} }` ......... `static private method #foo`
|
---|
| 1750 | * @param {ASTNode} node The function node to get.
|
---|
| 1751 | * @returns {string} The name and kind of the function node.
|
---|
| 1752 | */
|
---|
| 1753 | getFunctionNameWithKind(node) {
|
---|
| 1754 | const parent = node.parent;
|
---|
| 1755 | const tokens = [];
|
---|
| 1756 |
|
---|
| 1757 | if (parent.type === "MethodDefinition" || parent.type === "PropertyDefinition") {
|
---|
| 1758 |
|
---|
| 1759 | // The proposal uses `static` word consistently before visibility words: https://github.com/tc39/proposal-static-class-features
|
---|
| 1760 | if (parent.static) {
|
---|
| 1761 | tokens.push("static");
|
---|
| 1762 | }
|
---|
| 1763 | if (!parent.computed && parent.key.type === "PrivateIdentifier") {
|
---|
| 1764 | tokens.push("private");
|
---|
| 1765 | }
|
---|
| 1766 | }
|
---|
| 1767 | if (node.async) {
|
---|
| 1768 | tokens.push("async");
|
---|
| 1769 | }
|
---|
| 1770 | if (node.generator) {
|
---|
| 1771 | tokens.push("generator");
|
---|
| 1772 | }
|
---|
| 1773 |
|
---|
| 1774 | if (parent.type === "Property" || parent.type === "MethodDefinition") {
|
---|
| 1775 | if (parent.kind === "constructor") {
|
---|
| 1776 | return "constructor";
|
---|
| 1777 | }
|
---|
| 1778 | if (parent.kind === "get") {
|
---|
| 1779 | tokens.push("getter");
|
---|
| 1780 | } else if (parent.kind === "set") {
|
---|
| 1781 | tokens.push("setter");
|
---|
| 1782 | } else {
|
---|
| 1783 | tokens.push("method");
|
---|
| 1784 | }
|
---|
| 1785 | } else if (parent.type === "PropertyDefinition") {
|
---|
| 1786 | tokens.push("method");
|
---|
| 1787 | } else {
|
---|
| 1788 | if (node.type === "ArrowFunctionExpression") {
|
---|
| 1789 | tokens.push("arrow");
|
---|
| 1790 | }
|
---|
| 1791 | tokens.push("function");
|
---|
| 1792 | }
|
---|
| 1793 |
|
---|
| 1794 | if (parent.type === "Property" || parent.type === "MethodDefinition" || parent.type === "PropertyDefinition") {
|
---|
| 1795 | if (!parent.computed && parent.key.type === "PrivateIdentifier") {
|
---|
| 1796 | tokens.push(`#${parent.key.name}`);
|
---|
| 1797 | } else {
|
---|
| 1798 | const name = getStaticPropertyName(parent);
|
---|
| 1799 |
|
---|
| 1800 | if (name !== null) {
|
---|
| 1801 | tokens.push(`'${name}'`);
|
---|
| 1802 | } else if (node.id) {
|
---|
| 1803 | tokens.push(`'${node.id.name}'`);
|
---|
| 1804 | }
|
---|
| 1805 | }
|
---|
| 1806 | } else if (node.id) {
|
---|
| 1807 | tokens.push(`'${node.id.name}'`);
|
---|
| 1808 | }
|
---|
| 1809 |
|
---|
| 1810 | return tokens.join(" ");
|
---|
| 1811 | },
|
---|
| 1812 |
|
---|
| 1813 | /**
|
---|
| 1814 | * Gets the location of the given function node for reporting.
|
---|
| 1815 | *
|
---|
| 1816 | * - `function foo() {}`
|
---|
| 1817 | * ^^^^^^^^^^^^
|
---|
| 1818 | * - `(function foo() {})`
|
---|
| 1819 | * ^^^^^^^^^^^^
|
---|
| 1820 | * - `(function() {})`
|
---|
| 1821 | * ^^^^^^^^
|
---|
| 1822 | * - `function* foo() {}`
|
---|
| 1823 | * ^^^^^^^^^^^^^
|
---|
| 1824 | * - `(function* foo() {})`
|
---|
| 1825 | * ^^^^^^^^^^^^^
|
---|
| 1826 | * - `(function*() {})`
|
---|
| 1827 | * ^^^^^^^^^
|
---|
| 1828 | * - `() => {}`
|
---|
| 1829 | * ^^
|
---|
| 1830 | * - `async () => {}`
|
---|
| 1831 | * ^^
|
---|
| 1832 | * - `({ foo: function foo() {} })`
|
---|
| 1833 | * ^^^^^^^^^^^^^^^^^
|
---|
| 1834 | * - `({ foo: function() {} })`
|
---|
| 1835 | * ^^^^^^^^^^^^^
|
---|
| 1836 | * - `({ ['foo']: function() {} })`
|
---|
| 1837 | * ^^^^^^^^^^^^^^^^^
|
---|
| 1838 | * - `({ [foo]: function() {} })`
|
---|
| 1839 | * ^^^^^^^^^^^^^^^
|
---|
| 1840 | * - `({ foo() {} })`
|
---|
| 1841 | * ^^^
|
---|
| 1842 | * - `({ foo: function* foo() {} })`
|
---|
| 1843 | * ^^^^^^^^^^^^^^^^^^
|
---|
| 1844 | * - `({ foo: function*() {} })`
|
---|
| 1845 | * ^^^^^^^^^^^^^^
|
---|
| 1846 | * - `({ ['foo']: function*() {} })`
|
---|
| 1847 | * ^^^^^^^^^^^^^^^^^^
|
---|
| 1848 | * - `({ [foo]: function*() {} })`
|
---|
| 1849 | * ^^^^^^^^^^^^^^^^
|
---|
| 1850 | * - `({ *foo() {} })`
|
---|
| 1851 | * ^^^^
|
---|
| 1852 | * - `({ foo: async function foo() {} })`
|
---|
| 1853 | * ^^^^^^^^^^^^^^^^^^^^^^^
|
---|
| 1854 | * - `({ foo: async function() {} })`
|
---|
| 1855 | * ^^^^^^^^^^^^^^^^^^^
|
---|
| 1856 | * - `({ ['foo']: async function() {} })`
|
---|
| 1857 | * ^^^^^^^^^^^^^^^^^^^^^^^
|
---|
| 1858 | * - `({ [foo]: async function() {} })`
|
---|
| 1859 | * ^^^^^^^^^^^^^^^^^^^^^
|
---|
| 1860 | * - `({ async foo() {} })`
|
---|
| 1861 | * ^^^^^^^^^
|
---|
| 1862 | * - `({ get foo() {} })`
|
---|
| 1863 | * ^^^^^^^
|
---|
| 1864 | * - `({ set foo(a) {} })`
|
---|
| 1865 | * ^^^^^^^
|
---|
| 1866 | * - `class A { constructor() {} }`
|
---|
| 1867 | * ^^^^^^^^^^^
|
---|
| 1868 | * - `class A { foo() {} }`
|
---|
| 1869 | * ^^^
|
---|
| 1870 | * - `class A { *foo() {} }`
|
---|
| 1871 | * ^^^^
|
---|
| 1872 | * - `class A { async foo() {} }`
|
---|
| 1873 | * ^^^^^^^^^
|
---|
| 1874 | * - `class A { ['foo']() {} }`
|
---|
| 1875 | * ^^^^^^^
|
---|
| 1876 | * - `class A { *['foo']() {} }`
|
---|
| 1877 | * ^^^^^^^^
|
---|
| 1878 | * - `class A { async ['foo']() {} }`
|
---|
| 1879 | * ^^^^^^^^^^^^^
|
---|
| 1880 | * - `class A { [foo]() {} }`
|
---|
| 1881 | * ^^^^^
|
---|
| 1882 | * - `class A { *[foo]() {} }`
|
---|
| 1883 | * ^^^^^^
|
---|
| 1884 | * - `class A { async [foo]() {} }`
|
---|
| 1885 | * ^^^^^^^^^^^
|
---|
| 1886 | * - `class A { get foo() {} }`
|
---|
| 1887 | * ^^^^^^^
|
---|
| 1888 | * - `class A { set foo(a) {} }`
|
---|
| 1889 | * ^^^^^^^
|
---|
| 1890 | * - `class A { static foo() {} }`
|
---|
| 1891 | * ^^^^^^^^^^
|
---|
| 1892 | * - `class A { static *foo() {} }`
|
---|
| 1893 | * ^^^^^^^^^^^
|
---|
| 1894 | * - `class A { static async foo() {} }`
|
---|
| 1895 | * ^^^^^^^^^^^^^^^^
|
---|
| 1896 | * - `class A { static get foo() {} }`
|
---|
| 1897 | * ^^^^^^^^^^^^^^
|
---|
| 1898 | * - `class A { static set foo(a) {} }`
|
---|
| 1899 | * ^^^^^^^^^^^^^^
|
---|
| 1900 | * - `class A { foo = function() {} }`
|
---|
| 1901 | * ^^^^^^^^^^^^^^
|
---|
| 1902 | * - `class A { static foo = function() {} }`
|
---|
| 1903 | * ^^^^^^^^^^^^^^^^^^^^^
|
---|
| 1904 | * - `class A { foo = (a, b) => {} }`
|
---|
| 1905 | * ^^^^^^
|
---|
| 1906 | * @param {ASTNode} node The function node to get.
|
---|
| 1907 | * @param {SourceCode} sourceCode The source code object to get tokens.
|
---|
| 1908 | * @returns {string} The location of the function node for reporting.
|
---|
| 1909 | */
|
---|
| 1910 | getFunctionHeadLoc(node, sourceCode) {
|
---|
| 1911 | const parent = node.parent;
|
---|
| 1912 | let start = null;
|
---|
| 1913 | let end = null;
|
---|
| 1914 |
|
---|
| 1915 | if (parent.type === "Property" || parent.type === "MethodDefinition" || parent.type === "PropertyDefinition") {
|
---|
| 1916 | start = parent.loc.start;
|
---|
| 1917 | end = getOpeningParenOfParams(node, sourceCode).loc.start;
|
---|
| 1918 | } else if (node.type === "ArrowFunctionExpression") {
|
---|
| 1919 | const arrowToken = sourceCode.getTokenBefore(node.body, isArrowToken);
|
---|
| 1920 |
|
---|
| 1921 | start = arrowToken.loc.start;
|
---|
| 1922 | end = arrowToken.loc.end;
|
---|
| 1923 | } else {
|
---|
| 1924 | start = node.loc.start;
|
---|
| 1925 | end = getOpeningParenOfParams(node, sourceCode).loc.start;
|
---|
| 1926 | }
|
---|
| 1927 |
|
---|
| 1928 | return {
|
---|
| 1929 | start: Object.assign({}, start),
|
---|
| 1930 | end: Object.assign({}, end)
|
---|
| 1931 | };
|
---|
| 1932 | },
|
---|
| 1933 |
|
---|
| 1934 | /**
|
---|
| 1935 | * Gets next location when the result is not out of bound, otherwise returns null.
|
---|
| 1936 | *
|
---|
| 1937 | * Assumptions:
|
---|
| 1938 | *
|
---|
| 1939 | * - The given location represents a valid location in the given source code.
|
---|
| 1940 | * - Columns are 0-based.
|
---|
| 1941 | * - Lines are 1-based.
|
---|
| 1942 | * - Column immediately after the last character in a line (not incl. linebreaks) is considered to be a valid location.
|
---|
| 1943 | * - If the source code ends with a linebreak, `sourceCode.lines` array will have an extra element (empty string) at the end.
|
---|
| 1944 | * The start (column 0) of that extra line is considered to be a valid location.
|
---|
| 1945 | *
|
---|
| 1946 | * Examples of successive locations (line, column):
|
---|
| 1947 | *
|
---|
| 1948 | * code: foo
|
---|
| 1949 | * locations: (1, 0) -> (1, 1) -> (1, 2) -> (1, 3) -> null
|
---|
| 1950 | *
|
---|
| 1951 | * code: foo<LF>
|
---|
| 1952 | * locations: (1, 0) -> (1, 1) -> (1, 2) -> (1, 3) -> (2, 0) -> null
|
---|
| 1953 | *
|
---|
| 1954 | * code: foo<CR><LF>
|
---|
| 1955 | * locations: (1, 0) -> (1, 1) -> (1, 2) -> (1, 3) -> (2, 0) -> null
|
---|
| 1956 | *
|
---|
| 1957 | * code: a<LF>b
|
---|
| 1958 | * locations: (1, 0) -> (1, 1) -> (2, 0) -> (2, 1) -> null
|
---|
| 1959 | *
|
---|
| 1960 | * code: a<LF>b<LF>
|
---|
| 1961 | * locations: (1, 0) -> (1, 1) -> (2, 0) -> (2, 1) -> (3, 0) -> null
|
---|
| 1962 | *
|
---|
| 1963 | * code: a<CR><LF>b<CR><LF>
|
---|
| 1964 | * locations: (1, 0) -> (1, 1) -> (2, 0) -> (2, 1) -> (3, 0) -> null
|
---|
| 1965 | *
|
---|
| 1966 | * code: a<LF><LF>
|
---|
| 1967 | * locations: (1, 0) -> (1, 1) -> (2, 0) -> (3, 0) -> null
|
---|
| 1968 | *
|
---|
| 1969 | * code: <LF>
|
---|
| 1970 | * locations: (1, 0) -> (2, 0) -> null
|
---|
| 1971 | *
|
---|
| 1972 | * code:
|
---|
| 1973 | * locations: (1, 0) -> null
|
---|
| 1974 | * @param {SourceCode} sourceCode The sourceCode
|
---|
| 1975 | * @param {{line: number, column: number}} location The location
|
---|
| 1976 | * @returns {{line: number, column: number} | null} Next location
|
---|
| 1977 | */
|
---|
| 1978 | getNextLocation(sourceCode, { line, column }) {
|
---|
| 1979 | if (column < sourceCode.lines[line - 1].length) {
|
---|
| 1980 | return {
|
---|
| 1981 | line,
|
---|
| 1982 | column: column + 1
|
---|
| 1983 | };
|
---|
| 1984 | }
|
---|
| 1985 |
|
---|
| 1986 | if (line < sourceCode.lines.length) {
|
---|
| 1987 | return {
|
---|
| 1988 | line: line + 1,
|
---|
| 1989 | column: 0
|
---|
| 1990 | };
|
---|
| 1991 | }
|
---|
| 1992 |
|
---|
| 1993 | return null;
|
---|
| 1994 | },
|
---|
| 1995 |
|
---|
| 1996 | /**
|
---|
| 1997 | * Gets the parenthesized text of a node. This is similar to sourceCode.getText(node), but it also includes any parentheses
|
---|
| 1998 | * surrounding the node.
|
---|
| 1999 | * @param {SourceCode} sourceCode The source code object
|
---|
| 2000 | * @param {ASTNode} node An expression node
|
---|
| 2001 | * @returns {string} The text representing the node, with all surrounding parentheses included
|
---|
| 2002 | */
|
---|
| 2003 | getParenthesisedText(sourceCode, node) {
|
---|
| 2004 | let leftToken = sourceCode.getFirstToken(node);
|
---|
| 2005 | let rightToken = sourceCode.getLastToken(node);
|
---|
| 2006 |
|
---|
| 2007 | while (
|
---|
| 2008 | sourceCode.getTokenBefore(leftToken) &&
|
---|
| 2009 | sourceCode.getTokenBefore(leftToken).type === "Punctuator" &&
|
---|
| 2010 | sourceCode.getTokenBefore(leftToken).value === "(" &&
|
---|
| 2011 | sourceCode.getTokenAfter(rightToken) &&
|
---|
| 2012 | sourceCode.getTokenAfter(rightToken).type === "Punctuator" &&
|
---|
| 2013 | sourceCode.getTokenAfter(rightToken).value === ")"
|
---|
| 2014 | ) {
|
---|
| 2015 | leftToken = sourceCode.getTokenBefore(leftToken);
|
---|
| 2016 | rightToken = sourceCode.getTokenAfter(rightToken);
|
---|
| 2017 | }
|
---|
| 2018 |
|
---|
| 2019 | return sourceCode.getText().slice(leftToken.range[0], rightToken.range[1]);
|
---|
| 2020 | },
|
---|
| 2021 |
|
---|
| 2022 | /**
|
---|
| 2023 | * Determine if a node has a possibility to be an Error object
|
---|
| 2024 | * @param {ASTNode} node ASTNode to check
|
---|
| 2025 | * @returns {boolean} True if there is a chance it contains an Error obj
|
---|
| 2026 | */
|
---|
| 2027 | couldBeError(node) {
|
---|
| 2028 | switch (node.type) {
|
---|
| 2029 | case "Identifier":
|
---|
| 2030 | case "CallExpression":
|
---|
| 2031 | case "NewExpression":
|
---|
| 2032 | case "MemberExpression":
|
---|
| 2033 | case "TaggedTemplateExpression":
|
---|
| 2034 | case "YieldExpression":
|
---|
| 2035 | case "AwaitExpression":
|
---|
| 2036 | case "ChainExpression":
|
---|
| 2037 | return true; // possibly an error object.
|
---|
| 2038 |
|
---|
| 2039 | case "AssignmentExpression":
|
---|
| 2040 | if (["=", "&&="].includes(node.operator)) {
|
---|
| 2041 | return module.exports.couldBeError(node.right);
|
---|
| 2042 | }
|
---|
| 2043 |
|
---|
| 2044 | if (["||=", "??="].includes(node.operator)) {
|
---|
| 2045 | return module.exports.couldBeError(node.left) || module.exports.couldBeError(node.right);
|
---|
| 2046 | }
|
---|
| 2047 |
|
---|
| 2048 | /**
|
---|
| 2049 | * All other assignment operators are mathematical assignment operators (arithmetic or bitwise).
|
---|
| 2050 | * An assignment expression with a mathematical operator can either evaluate to a primitive value,
|
---|
| 2051 | * or throw, depending on the operands. Thus, it cannot evaluate to an `Error` object.
|
---|
| 2052 | */
|
---|
| 2053 | return false;
|
---|
| 2054 |
|
---|
| 2055 | case "SequenceExpression": {
|
---|
| 2056 | const exprs = node.expressions;
|
---|
| 2057 |
|
---|
| 2058 | return exprs.length !== 0 && module.exports.couldBeError(exprs[exprs.length - 1]);
|
---|
| 2059 | }
|
---|
| 2060 |
|
---|
| 2061 | case "LogicalExpression":
|
---|
| 2062 |
|
---|
| 2063 | /*
|
---|
| 2064 | * If the && operator short-circuits, the left side was falsy and therefore not an error, and if it
|
---|
| 2065 | * doesn't short-circuit, it takes the value from the right side, so the right side must always be
|
---|
| 2066 | * a plausible error. A future improvement could verify that the left side could be truthy by
|
---|
| 2067 | * excluding falsy literals.
|
---|
| 2068 | */
|
---|
| 2069 | if (node.operator === "&&") {
|
---|
| 2070 | return module.exports.couldBeError(node.right);
|
---|
| 2071 | }
|
---|
| 2072 |
|
---|
| 2073 | return module.exports.couldBeError(node.left) || module.exports.couldBeError(node.right);
|
---|
| 2074 |
|
---|
| 2075 | case "ConditionalExpression":
|
---|
| 2076 | return module.exports.couldBeError(node.consequent) || module.exports.couldBeError(node.alternate);
|
---|
| 2077 |
|
---|
| 2078 | default:
|
---|
| 2079 | return false;
|
---|
| 2080 | }
|
---|
| 2081 | },
|
---|
| 2082 |
|
---|
| 2083 | /**
|
---|
| 2084 | * Check if a given node is a numeric literal or not.
|
---|
| 2085 | * @param {ASTNode} node The node to check.
|
---|
| 2086 | * @returns {boolean} `true` if the node is a number or bigint literal.
|
---|
| 2087 | */
|
---|
| 2088 | isNumericLiteral(node) {
|
---|
| 2089 | return (
|
---|
| 2090 | node.type === "Literal" &&
|
---|
| 2091 | (typeof node.value === "number" || Boolean(node.bigint))
|
---|
| 2092 | );
|
---|
| 2093 | },
|
---|
| 2094 |
|
---|
| 2095 | /**
|
---|
| 2096 | * Determines whether two tokens can safely be placed next to each other without merging into a single token
|
---|
| 2097 | * @param {Token|string} leftValue The left token. If this is a string, it will be tokenized and the last token will be used.
|
---|
| 2098 | * @param {Token|string} rightValue The right token. If this is a string, it will be tokenized and the first token will be used.
|
---|
| 2099 | * @returns {boolean} If the tokens cannot be safely placed next to each other, returns `false`. If the tokens can be placed
|
---|
| 2100 | * next to each other, behavior is undefined (although it should return `true` in most cases).
|
---|
| 2101 | */
|
---|
| 2102 | canTokensBeAdjacent(leftValue, rightValue) {
|
---|
| 2103 | const espreeOptions = {
|
---|
| 2104 | ecmaVersion: espree.latestEcmaVersion,
|
---|
| 2105 | comment: true,
|
---|
| 2106 | range: true
|
---|
| 2107 | };
|
---|
| 2108 |
|
---|
| 2109 | let leftToken;
|
---|
| 2110 |
|
---|
| 2111 | if (typeof leftValue === "string") {
|
---|
| 2112 | let tokens;
|
---|
| 2113 |
|
---|
| 2114 | try {
|
---|
| 2115 | tokens = espree.tokenize(leftValue, espreeOptions);
|
---|
| 2116 | } catch {
|
---|
| 2117 | return false;
|
---|
| 2118 | }
|
---|
| 2119 |
|
---|
| 2120 | const comments = tokens.comments;
|
---|
| 2121 |
|
---|
| 2122 | leftToken = tokens[tokens.length - 1];
|
---|
| 2123 | if (comments.length) {
|
---|
| 2124 | const lastComment = comments[comments.length - 1];
|
---|
| 2125 |
|
---|
| 2126 | if (!leftToken || lastComment.range[0] > leftToken.range[0]) {
|
---|
| 2127 | leftToken = lastComment;
|
---|
| 2128 | }
|
---|
| 2129 | }
|
---|
| 2130 | } else {
|
---|
| 2131 | leftToken = leftValue;
|
---|
| 2132 | }
|
---|
| 2133 |
|
---|
| 2134 | /*
|
---|
| 2135 | * If a hashbang comment was passed as a token object from SourceCode,
|
---|
| 2136 | * its type will be "Shebang" because of the way ESLint itself handles hashbangs.
|
---|
| 2137 | * If a hashbang comment was passed in a string and then tokenized in this function,
|
---|
| 2138 | * its type will be "Hashbang" because of the way Espree tokenizes hashbangs.
|
---|
| 2139 | */
|
---|
| 2140 | if (leftToken.type === "Shebang" || leftToken.type === "Hashbang") {
|
---|
| 2141 | return false;
|
---|
| 2142 | }
|
---|
| 2143 |
|
---|
| 2144 | let rightToken;
|
---|
| 2145 |
|
---|
| 2146 | if (typeof rightValue === "string") {
|
---|
| 2147 | let tokens;
|
---|
| 2148 |
|
---|
| 2149 | try {
|
---|
| 2150 | tokens = espree.tokenize(rightValue, espreeOptions);
|
---|
| 2151 | } catch {
|
---|
| 2152 | return false;
|
---|
| 2153 | }
|
---|
| 2154 |
|
---|
| 2155 | const comments = tokens.comments;
|
---|
| 2156 |
|
---|
| 2157 | rightToken = tokens[0];
|
---|
| 2158 | if (comments.length) {
|
---|
| 2159 | const firstComment = comments[0];
|
---|
| 2160 |
|
---|
| 2161 | if (!rightToken || firstComment.range[0] < rightToken.range[0]) {
|
---|
| 2162 | rightToken = firstComment;
|
---|
| 2163 | }
|
---|
| 2164 | }
|
---|
| 2165 | } else {
|
---|
| 2166 | rightToken = rightValue;
|
---|
| 2167 | }
|
---|
| 2168 |
|
---|
| 2169 | if (leftToken.type === "Punctuator" || rightToken.type === "Punctuator") {
|
---|
| 2170 | if (leftToken.type === "Punctuator" && rightToken.type === "Punctuator") {
|
---|
| 2171 | const PLUS_TOKENS = new Set(["+", "++"]);
|
---|
| 2172 | const MINUS_TOKENS = new Set(["-", "--"]);
|
---|
| 2173 |
|
---|
| 2174 | return !(
|
---|
| 2175 | PLUS_TOKENS.has(leftToken.value) && PLUS_TOKENS.has(rightToken.value) ||
|
---|
| 2176 | MINUS_TOKENS.has(leftToken.value) && MINUS_TOKENS.has(rightToken.value)
|
---|
| 2177 | );
|
---|
| 2178 | }
|
---|
| 2179 | if (leftToken.type === "Punctuator" && leftToken.value === "/") {
|
---|
| 2180 | return !["Block", "Line", "RegularExpression"].includes(rightToken.type);
|
---|
| 2181 | }
|
---|
| 2182 | return true;
|
---|
| 2183 | }
|
---|
| 2184 |
|
---|
| 2185 | if (
|
---|
| 2186 | leftToken.type === "String" || rightToken.type === "String" ||
|
---|
| 2187 | leftToken.type === "Template" || rightToken.type === "Template"
|
---|
| 2188 | ) {
|
---|
| 2189 | return true;
|
---|
| 2190 | }
|
---|
| 2191 |
|
---|
| 2192 | if (leftToken.type !== "Numeric" && rightToken.type === "Numeric" && rightToken.value.startsWith(".")) {
|
---|
| 2193 | return true;
|
---|
| 2194 | }
|
---|
| 2195 |
|
---|
| 2196 | if (leftToken.type === "Block" || rightToken.type === "Block" || rightToken.type === "Line") {
|
---|
| 2197 | return true;
|
---|
| 2198 | }
|
---|
| 2199 |
|
---|
| 2200 | if (rightToken.type === "PrivateIdentifier") {
|
---|
| 2201 | return true;
|
---|
| 2202 | }
|
---|
| 2203 |
|
---|
| 2204 | return false;
|
---|
| 2205 | },
|
---|
| 2206 |
|
---|
| 2207 | /**
|
---|
| 2208 | * Get the `loc` object of a given name in a `/*globals` directive comment.
|
---|
| 2209 | * @param {SourceCode} sourceCode The source code to convert index to loc.
|
---|
| 2210 | * @param {Comment} comment The `/*globals` directive comment which include the name.
|
---|
| 2211 | * @param {string} name The name to find.
|
---|
| 2212 | * @returns {SourceLocation} The `loc` object.
|
---|
| 2213 | */
|
---|
| 2214 | getNameLocationInGlobalDirectiveComment(sourceCode, comment, name) {
|
---|
| 2215 | const namePattern = new RegExp(`[\\s,]${escapeRegExp(name)}(?:$|[\\s,:])`, "gu");
|
---|
| 2216 |
|
---|
| 2217 | // To ignore the first text "global".
|
---|
| 2218 | namePattern.lastIndex = comment.value.indexOf("global") + 6;
|
---|
| 2219 |
|
---|
| 2220 | // Search a given variable name.
|
---|
| 2221 | const match = namePattern.exec(comment.value);
|
---|
| 2222 |
|
---|
| 2223 | // Convert the index to loc.
|
---|
| 2224 | const start = sourceCode.getLocFromIndex(
|
---|
| 2225 | comment.range[0] +
|
---|
| 2226 | "/*".length +
|
---|
| 2227 | (match ? match.index + 1 : 0)
|
---|
| 2228 | );
|
---|
| 2229 | const end = {
|
---|
| 2230 | line: start.line,
|
---|
| 2231 | column: start.column + (match ? name.length : 1)
|
---|
| 2232 | };
|
---|
| 2233 |
|
---|
| 2234 | return { start, end };
|
---|
| 2235 | },
|
---|
| 2236 |
|
---|
| 2237 | /**
|
---|
| 2238 | * Determines whether the given raw string contains an octal escape sequence
|
---|
| 2239 | * or a non-octal decimal escape sequence ("\8", "\9").
|
---|
| 2240 | *
|
---|
| 2241 | * "\1", "\2" ... "\7", "\8", "\9"
|
---|
| 2242 | * "\00", "\01" ... "\07", "\08", "\09"
|
---|
| 2243 | *
|
---|
| 2244 | * "\0", when not followed by a digit, is not an octal escape sequence.
|
---|
| 2245 | * @param {string} rawString A string in its raw representation.
|
---|
| 2246 | * @returns {boolean} `true` if the string contains at least one octal escape sequence
|
---|
| 2247 | * or at least one non-octal decimal escape sequence.
|
---|
| 2248 | */
|
---|
| 2249 | hasOctalOrNonOctalDecimalEscapeSequence(rawString) {
|
---|
| 2250 | return OCTAL_OR_NON_OCTAL_DECIMAL_ESCAPE_PATTERN.test(rawString);
|
---|
| 2251 | },
|
---|
| 2252 |
|
---|
| 2253 | /**
|
---|
| 2254 | * Determines whether the given node is a template literal without expressions.
|
---|
| 2255 | * @param {ASTNode} node Node to check.
|
---|
| 2256 | * @returns {boolean} True if the node is a template literal without expressions.
|
---|
| 2257 | */
|
---|
| 2258 | isStaticTemplateLiteral(node) {
|
---|
| 2259 | return node.type === "TemplateLiteral" && node.expressions.length === 0;
|
---|
| 2260 | },
|
---|
| 2261 |
|
---|
| 2262 | isReferenceToGlobalVariable,
|
---|
| 2263 | isLogicalExpression,
|
---|
| 2264 | isCoalesceExpression,
|
---|
| 2265 | isMixedLogicalAndCoalesceExpressions,
|
---|
| 2266 | isNullLiteral,
|
---|
| 2267 | getStaticStringValue,
|
---|
| 2268 | getStaticPropertyName,
|
---|
| 2269 | skipChainExpression,
|
---|
| 2270 | isSpecificId,
|
---|
| 2271 | isSpecificMemberAccess,
|
---|
| 2272 | equalLiteralValue,
|
---|
| 2273 | isSameReference,
|
---|
| 2274 | isLogicalAssignmentOperator,
|
---|
| 2275 | getSwitchCaseColonToken,
|
---|
| 2276 | getModuleExportName,
|
---|
| 2277 | isConstant,
|
---|
| 2278 | isTopLevelExpressionStatement,
|
---|
| 2279 | isDirective,
|
---|
| 2280 | isStartOfExpressionStatement,
|
---|
| 2281 | needsPrecedingSemicolon
|
---|
| 2282 | };
|
---|