1 | /**
|
2 | * @fileoverview Common utils for AST.
|
3 | * @author Gyandeep Singh
|
4 | */
|
5 |
|
6 | ;
|
7 |
|
8 | //------------------------------------------------------------------------------
|
9 | // Requirements
|
10 | //------------------------------------------------------------------------------
|
11 |
|
12 | //------------------------------------------------------------------------------
|
13 | // Helpers
|
14 | //------------------------------------------------------------------------------
|
15 |
|
16 | const anyFunctionPattern = /^(?:Function(?:Declaration|Expression)|ArrowFunctionExpression)$/;
|
17 | const anyLoopPattern = /^(?:DoWhile|For|ForIn|ForOf|While)Statement$/;
|
18 | const arrayOrTypedArrayPattern = /Array$/;
|
19 | const arrayMethodPattern = /^(?:every|filter|find|findIndex|forEach|map|some)$/;
|
20 | const bindOrCallOrApplyPattern = /^(?:bind|call|apply)$/;
|
21 | const breakableTypePattern = /^(?:(?:Do)?While|For(?:In|Of)?|Switch)Statement$/;
|
22 | const thisTagPattern = /^[\s\*]*@this/m;
|
23 |
|
24 | /**
|
25 | * Checks reference if is non initializer and writable.
|
26 | * @param {Reference} reference - A reference to check.
|
27 | * @param {int} index - The index of the reference in the references.
|
28 | * @param {Reference[]} references - The array that the reference belongs to.
|
29 | * @returns {boolean} Success/Failure
|
30 | * @private
|
31 | */
|
32 | function isModifyingReference(reference, index, references) {
|
33 | const identifier = reference.identifier;
|
34 |
|
35 | /*
|
36 | * Destructuring assignments can have multiple default value, so
|
37 | * possibly there are multiple writeable references for the same
|
38 | * identifier.
|
39 | */
|
40 | const modifyingDifferentIdentifier = index === 0 ||
|
41 | references[index - 1].identifier !== identifier;
|
42 |
|
43 | return (identifier &&
|
44 | reference.init === false &&
|
45 | reference.isWrite() &&
|
46 | modifyingDifferentIdentifier
|
47 | );
|
48 | }
|
49 |
|
50 | /**
|
51 | * Checks whether the given string starts with uppercase or not.
|
52 | *
|
53 | * @param {string} s - The string to check.
|
54 | * @returns {boolean} `true` if the string starts with uppercase.
|
55 | */
|
56 | function startsWithUpperCase(s) {
|
57 | return s[0] !== s[0].toLocaleLowerCase();
|
58 | }
|
59 |
|
60 | /**
|
61 | * Checks whether or not a node is a constructor.
|
62 | * @param {ASTNode} node - A function node to check.
|
63 | * @returns {boolean} Wehether or not a node is a constructor.
|
64 | */
|
65 | function isES5Constructor(node) {
|
66 | return (node.id && startsWithUpperCase(node.id.name));
|
67 | }
|
68 |
|
69 | /**
|
70 | * Finds a function node from ancestors of a node.
|
71 | * @param {ASTNode} node - A start node to find.
|
72 | * @returns {Node|null} A found function node.
|
73 | */
|
74 | function getUpperFunction(node) {
|
75 | while (node) {
|
76 | if (anyFunctionPattern.test(node.type)) {
|
77 | return node;
|
78 | }
|
79 | node = node.parent;
|
80 | }
|
81 | return null;
|
82 | }
|
83 |
|
84 | /**
|
85 | * Checks whether or not a node is `null` or `undefined`.
|
86 | * @param {ASTNode} node - A node to check.
|
87 | * @returns {boolean} Whether or not the node is a `null` or `undefined`.
|
88 | * @public
|
89 | */
|
90 | function isNullOrUndefined(node) {
|
91 | return (
|
92 | (node.type === "Literal" && node.value === null) ||
|
93 | (node.type === "Identifier" && node.name === "undefined") ||
|
94 | (node.type === "UnaryExpression" && node.operator === "void")
|
95 | );
|
96 | }
|
97 |
|
98 | /**
|
99 | * Checks whether or not a node is callee.
|
100 | * @param {ASTNode} node - A node to check.
|
101 | * @returns {boolean} Whether or not the node is callee.
|
102 | */
|
103 | function isCallee(node) {
|
104 | return node.parent.type === "CallExpression" && node.parent.callee === node;
|
105 | }
|
106 |
|
107 | /**
|
108 | * Checks whether or not a node is `Reclect.apply`.
|
109 | * @param {ASTNode} node - A node to check.
|
110 | * @returns {boolean} Whether or not the node is a `Reclect.apply`.
|
111 | */
|
112 | function isReflectApply(node) {
|
113 | return (
|
114 | node.type === "MemberExpression" &&
|
115 | node.object.type === "Identifier" &&
|
116 | node.object.name === "Reflect" &&
|
117 | node.property.type === "Identifier" &&
|
118 | node.property.name === "apply" &&
|
119 | node.computed === false
|
120 | );
|
121 | }
|
122 |
|
123 | /**
|
124 | * Checks whether or not a node is `Array.from`.
|
125 | * @param {ASTNode} node - A node to check.
|
126 | * @returns {boolean} Whether or not the node is a `Array.from`.
|
127 | */
|
128 | function isArrayFromMethod(node) {
|
129 | return (
|
130 | node.type === "MemberExpression" &&
|
131 | node.object.type === "Identifier" &&
|
132 | arrayOrTypedArrayPattern.test(node.object.name) &&
|
133 | node.property.type === "Identifier" &&
|
134 | node.property.name === "from" &&
|
135 | node.computed === false
|
136 | );
|
137 | }
|
138 |
|
139 | /**
|
140 | * Checks whether or not a node is a method which has `thisArg`.
|
141 | * @param {ASTNode} node - A node to check.
|
142 | * @returns {boolean} Whether or not the node is a method which has `thisArg`.
|
143 | */
|
144 | function isMethodWhichHasThisArg(node) {
|
145 | while (node) {
|
146 | if (node.type === "Identifier") {
|
147 | return arrayMethodPattern.test(node.name);
|
148 | }
|
149 | if (node.type === "MemberExpression" && !node.computed) {
|
150 | node = node.property;
|
151 | continue;
|
152 | }
|
153 |
|
154 | break;
|
155 | }
|
156 |
|
157 | return false;
|
158 | }
|
159 |
|
160 | /**
|
161 | * Checks whether or not a node has a `@this` tag in its comments.
|
162 | * @param {ASTNode} node - A node to check.
|
163 | * @param {SourceCode} sourceCode - A SourceCode instance to get comments.
|
164 | * @returns {boolean} Whether or not the node has a `@this` tag in its comments.
|
165 | */
|
166 | function hasJSDocThisTag(node, sourceCode) {
|
167 | const jsdocComment = sourceCode.getJSDocComment(node);
|
168 |
|
169 | if (jsdocComment && thisTagPattern.test(jsdocComment.value)) {
|
170 | return true;
|
171 | }
|
172 |
|
173 | // Checks `@this` in its leading comments for callbacks,
|
174 | // because callbacks don't have its JSDoc comment.
|
175 | // e.g.
|
176 | // sinon.test(/* @this sinon.Sandbox */function() { this.spy(); });
|
177 | return sourceCode.getComments(node).leading.some(function(comment) {
|
178 | return thisTagPattern.test(comment.value);
|
179 | });
|
180 | }
|
181 |
|
182 | /**
|
183 | * Determines if a node is surrounded by parentheses.
|
184 | * @param {SourceCode} sourceCode The ESLint source code object
|
185 | * @param {ASTNode} node The node to be checked.
|
186 | * @returns {boolean} True if the node is parenthesised.
|
187 | * @private
|
188 | */
|
189 | function isParenthesised(sourceCode, node) {
|
190 | const previousToken = sourceCode.getTokenBefore(node),
|
191 | nextToken = sourceCode.getTokenAfter(node);
|
192 |
|
193 | return Boolean(previousToken && nextToken) &&
|
194 | previousToken.value === "(" && previousToken.range[1] <= node.range[0] &&
|
195 | nextToken.value === ")" && nextToken.range[0] >= node.range[1];
|
196 | }
|
197 |
|
198 | //------------------------------------------------------------------------------
|
199 | // Public Interface
|
200 | //------------------------------------------------------------------------------
|
201 |
|
202 | module.exports = {
|
203 |
|
204 | /**
|
205 | * Determines whether two adjacent tokens are on the same line.
|
206 | * @param {Object} left - The left token object.
|
207 | * @param {Object} right - The right token object.
|
208 | * @returns {boolean} Whether or not the tokens are on the same line.
|
209 | * @public
|
210 | */
|
211 | isTokenOnSameLine(left, right) {
|
212 | return left.loc.end.line === right.loc.start.line;
|
213 | },
|
214 |
|
215 | isNullOrUndefined,
|
216 | isCallee,
|
217 | isES5Constructor,
|
218 | getUpperFunction,
|
219 | isArrayFromMethod,
|
220 | isParenthesised,
|
221 |
|
222 | /**
|
223 | * Checks whether or not a given node is a string literal.
|
224 | * @param {ASTNode} node - A node to check.
|
225 | * @returns {boolean} `true` if the node is a string literal.
|
226 | */
|
227 | isStringLiteral(node) {
|
228 | return (
|
229 | (node.type === "Literal" && typeof node.value === "string") ||
|
230 | node.type === "TemplateLiteral"
|
231 | );
|
232 | },
|
233 |
|
234 | /**
|
235 | * Checks whether a given node is a breakable statement or not.
|
236 | * The node is breakable if the node is one of the following type:
|
237 | *
|
238 | * - DoWhileStatement
|
239 | * - ForInStatement
|
240 | * - ForOfStatement
|
241 | * - ForStatement
|
242 | * - SwitchStatement
|
243 | * - WhileStatement
|
244 | *
|
245 | * @param {ASTNode} node - A node to check.
|
246 | * @returns {boolean} `true` if the node is breakable.
|
247 | */
|
248 | isBreakableStatement(node) {
|
249 | return breakableTypePattern.test(node.type);
|
250 | },
|
251 |
|
252 | /**
|
253 | * Gets the label if the parent node of a given node is a LabeledStatement.
|
254 | *
|
255 | * @param {ASTNode} node - A node to get.
|
256 | * @returns {string|null} The label or `null`.
|
257 | */
|
258 | getLabel(node) {
|
259 | if (node.parent.type === "LabeledStatement") {
|
260 | return node.parent.label.name;
|
261 | }
|
262 | return null;
|
263 | },
|
264 |
|
265 | /**
|
266 | * Gets references which are non initializer and writable.
|
267 | * @param {Reference[]} references - An array of references.
|
268 | * @returns {Reference[]} An array of only references which are non initializer and writable.
|
269 | * @public
|
270 | */
|
271 | getModifyingReferences(references) {
|
272 | return references.filter(isModifyingReference);
|
273 | },
|
274 |
|
275 | /**
|
276 | * Validate that a string passed in is surrounded by the specified character
|
277 | * @param {string} val The text to check.
|
278 | * @param {string} character The character to see if it's surrounded by.
|
279 | * @returns {boolean} True if the text is surrounded by the character, false if not.
|
280 | * @private
|
281 | */
|
282 | isSurroundedBy(val, character) {
|
283 | return val[0] === character && val[val.length - 1] === character;
|
284 | },
|
285 |
|
286 | /**
|
287 | * Returns whether the provided node is an ESLint directive comment or not
|
288 | * @param {LineComment|BlockComment} node The node to be checked
|
289 | * @returns {boolean} `true` if the node is an ESLint directive comment
|
290 | */
|
291 | isDirectiveComment(node) {
|
292 | const comment = node.value.trim();
|
293 |
|
294 | return (
|
295 | node.type === "Line" && comment.indexOf("eslint-") === 0 ||
|
296 | node.type === "Block" && (
|
297 | comment.indexOf("global ") === 0 ||
|
298 | comment.indexOf("eslint ") === 0 ||
|
299 | comment.indexOf("eslint-") === 0
|
300 | )
|
301 | );
|
302 | },
|
303 |
|
304 | /**
|
305 | * Finds the variable by a given name in a given scope and its upper scopes.
|
306 | *
|
307 | * @param {escope.Scope} initScope - A scope to start find.
|
308 | * @param {string} name - A variable name to find.
|
309 | * @returns {escope.Variable|null} A found variable or `null`.
|
310 | */
|
311 | getVariableByName(initScope, name) {
|
312 | let scope = initScope;
|
313 |
|
314 | while (scope) {
|
315 | const variable = scope.set.get(name);
|
316 |
|
317 | if (variable) {
|
318 | return variable;
|
319 | }
|
320 |
|
321 | scope = scope.upper;
|
322 | }
|
323 |
|
324 | return null;
|
325 | },
|
326 |
|
327 | /**
|
328 | * Checks whether or not a given function node is the default `this` binding.
|
329 | *
|
330 | * First, this checks the node:
|
331 | *
|
332 | * - The function name does not start with uppercase (it's a constructor).
|
333 | * - The function does not have a JSDoc comment that has a @this tag.
|
334 | *
|
335 | * Next, this checks the location of the node.
|
336 | * If the location is below, this judges `this` is valid.
|
337 | *
|
338 | * - The location is not on an object literal.
|
339 | * - The location is not assigned to a variable which starts with an uppercase letter.
|
340 | * - The location is not on an ES2015 class.
|
341 | * - Its `bind`/`call`/`apply` method is not called directly.
|
342 | * - The function is not a callback of array methods (such as `.forEach()`) if `thisArg` is given.
|
343 | *
|
344 | * @param {ASTNode} node - A function node to check.
|
345 | * @param {SourceCode} sourceCode - A SourceCode instance to get comments.
|
346 | * @returns {boolean} The function node is the default `this` binding.
|
347 | */
|
348 | isDefaultThisBinding(node, sourceCode) {
|
349 | if (isES5Constructor(node) || hasJSDocThisTag(node, sourceCode)) {
|
350 | return false;
|
351 | }
|
352 | const isAnonymous = node.id === null;
|
353 |
|
354 | while (node) {
|
355 | const parent = node.parent;
|
356 |
|
357 | switch (parent.type) {
|
358 |
|
359 | /*
|
360 | * Looks up the destination.
|
361 | * e.g., obj.foo = nativeFoo || function foo() { ... };
|
362 | */
|
363 | case "LogicalExpression":
|
364 | case "ConditionalExpression":
|
365 | node = parent;
|
366 | break;
|
367 |
|
368 | // If the upper function is IIFE, checks the destination of the return value.
|
369 | // e.g.
|
370 | // obj.foo = (function() {
|
371 | // // setup...
|
372 | // return function foo() { ... };
|
373 | // })();
|
374 | case "ReturnStatement": {
|
375 | const func = getUpperFunction(parent);
|
376 |
|
377 | if (func === null || !isCallee(func)) {
|
378 | return true;
|
379 | }
|
380 | node = func.parent;
|
381 | break;
|
382 | }
|
383 |
|
384 | // e.g.
|
385 | // var obj = { foo() { ... } };
|
386 | // var obj = { foo: function() { ... } };
|
387 | // class A { constructor() { ... } }
|
388 | // class A { foo() { ... } }
|
389 | // class A { get foo() { ... } }
|
390 | // class A { set foo() { ... } }
|
391 | // class A { static foo() { ... } }
|
392 | case "Property":
|
393 | case "MethodDefinition":
|
394 | return parent.value !== node;
|
395 |
|
396 | // e.g.
|
397 | // obj.foo = function foo() { ... };
|
398 | // Foo = function() { ... };
|
399 | // [obj.foo = function foo() { ... }] = a;
|
400 | // [Foo = function() { ... }] = a;
|
401 | case "AssignmentExpression":
|
402 | case "AssignmentPattern":
|
403 | if (parent.right === node) {
|
404 | if (parent.left.type === "MemberExpression") {
|
405 | return false;
|
406 | }
|
407 | if (isAnonymous &&
|
408 | parent.left.type === "Identifier" &&
|
409 | startsWithUpperCase(parent.left.name)
|
410 | ) {
|
411 | return false;
|
412 | }
|
413 | }
|
414 | return true;
|
415 |
|
416 | // e.g.
|
417 | // var Foo = function() { ... };
|
418 | case "VariableDeclarator":
|
419 | return !(
|
420 | isAnonymous &&
|
421 | parent.init === node &&
|
422 | parent.id.type === "Identifier" &&
|
423 | startsWithUpperCase(parent.id.name)
|
424 | );
|
425 |
|
426 | // e.g.
|
427 | // var foo = function foo() { ... }.bind(obj);
|
428 | // (function foo() { ... }).call(obj);
|
429 | // (function foo() { ... }).apply(obj, []);
|
430 | case "MemberExpression":
|
431 | return (
|
432 | parent.object !== node ||
|
433 | parent.property.type !== "Identifier" ||
|
434 | !bindOrCallOrApplyPattern.test(parent.property.name) ||
|
435 | !isCallee(parent) ||
|
436 | parent.parent.arguments.length === 0 ||
|
437 | isNullOrUndefined(parent.parent.arguments[0])
|
438 | );
|
439 |
|
440 | // e.g.
|
441 | // Reflect.apply(function() {}, obj, []);
|
442 | // Array.from([], function() {}, obj);
|
443 | // list.forEach(function() {}, obj);
|
444 | case "CallExpression":
|
445 | if (isReflectApply(parent.callee)) {
|
446 | return (
|
447 | parent.arguments.length !== 3 ||
|
448 | parent.arguments[0] !== node ||
|
449 | isNullOrUndefined(parent.arguments[1])
|
450 | );
|
451 | }
|
452 | if (isArrayFromMethod(parent.callee)) {
|
453 | return (
|
454 | parent.arguments.length !== 3 ||
|
455 | parent.arguments[1] !== node ||
|
456 | isNullOrUndefined(parent.arguments[2])
|
457 | );
|
458 | }
|
459 | if (isMethodWhichHasThisArg(parent.callee)) {
|
460 | return (
|
461 | parent.arguments.length !== 2 ||
|
462 | parent.arguments[0] !== node ||
|
463 | isNullOrUndefined(parent.arguments[1])
|
464 | );
|
465 | }
|
466 | return true;
|
467 |
|
468 | // Otherwise `this` is default.
|
469 | default:
|
470 | return true;
|
471 | }
|
472 | }
|
473 |
|
474 | /* istanbul ignore next */
|
475 | return true;
|
476 | },
|
477 |
|
478 | /**
|
479 | * Get the precedence level based on the node type
|
480 | * @param {ASTNode} node node to evaluate
|
481 | * @returns {int} precedence level
|
482 | * @private
|
483 | */
|
484 | getPrecedence(node) {
|
485 | switch (node.type) {
|
486 | case "SequenceExpression":
|
487 | return 0;
|
488 |
|
489 | case "AssignmentExpression":
|
490 | case "ArrowFunctionExpression":
|
491 | case "YieldExpression":
|
492 | return 1;
|
493 |
|
494 | case "ConditionalExpression":
|
495 | return 3;
|
496 |
|
497 | case "LogicalExpression":
|
498 | switch (node.operator) {
|
499 | case "||":
|
500 | return 4;
|
501 | case "&&":
|
502 | return 5;
|
503 |
|
504 | // no default
|
505 | }
|
506 |
|
507 | /* falls through */
|
508 |
|
509 | case "BinaryExpression":
|
510 |
|
511 | switch (node.operator) {
|
512 | case "|":
|
513 | return 6;
|
514 | case "^":
|
515 | return 7;
|
516 | case "&":
|
517 | return 8;
|
518 | case "==":
|
519 | case "!=":
|
520 | case "===":
|
521 | case "!==":
|
522 | return 9;
|
523 | case "<":
|
524 | case "<=":
|
525 | case ">":
|
526 | case ">=":
|
527 | case "in":
|
528 | case "instanceof":
|
529 | return 10;
|
530 | case "<<":
|
531 | case ">>":
|
532 | case ">>>":
|
533 | return 11;
|
534 | case "+":
|
535 | case "-":
|
536 | return 12;
|
537 | case "*":
|
538 | case "/":
|
539 | case "%":
|
540 | return 13;
|
541 |
|
542 | // no default
|
543 | }
|
544 |
|
545 | /* falls through */
|
546 |
|
547 | case "UnaryExpression":
|
548 | case "AwaitExpression":
|
549 | return 14;
|
550 |
|
551 | case "UpdateExpression":
|
552 | return 15;
|
553 |
|
554 | case "CallExpression":
|
555 |
|
556 | // IIFE is allowed to have parens in any position (#655)
|
557 | if (node.callee.type === "FunctionExpression") {
|
558 | return -1;
|
559 | }
|
560 | return 16;
|
561 |
|
562 | case "NewExpression":
|
563 | return 17;
|
564 |
|
565 | // no default
|
566 | }
|
567 | return 18;
|
568 | },
|
569 |
|
570 | /**
|
571 | * Checks whether a given node is a loop node or not.
|
572 | * The following types are loop nodes:
|
573 | *
|
574 | * - DoWhileStatement
|
575 | * - ForInStatement
|
576 | * - ForOfStatement
|
577 | * - ForStatement
|
578 | * - WhileStatement
|
579 | *
|
580 | * @param {ASTNode|null} node - A node to check.
|
581 | * @returns {boolean} `true` if the node is a loop node.
|
582 | */
|
583 | isLoop(node) {
|
584 | return Boolean(node && anyLoopPattern.test(node.type));
|
585 | },
|
586 |
|
587 | /**
|
588 | * Checks whether a given node is a function node or not.
|
589 | * The following types are function nodes:
|
590 | *
|
591 | * - ArrowFunctionExpression
|
592 | * - FunctionDeclaration
|
593 | * - FunctionExpression
|
594 | *
|
595 | * @param {ASTNode|null} node - A node to check.
|
596 | * @returns {boolean} `true` if the node is a function node.
|
597 | */
|
598 | isFunction(node) {
|
599 | return Boolean(node && anyFunctionPattern.test(node.type));
|
600 | },
|
601 |
|
602 | /**
|
603 | * Gets the property name of a given node.
|
604 | * The node can be a MemberExpression, a Property, or a MethodDefinition.
|
605 | *
|
606 | * If the name is dynamic, this returns `null`.
|
607 | *
|
608 | * For examples:
|
609 | *
|
610 | * a.b // => "b"
|
611 | * a["b"] // => "b"
|
612 | * a['b'] // => "b"
|
613 | * a[`b`] // => "b"
|
614 | * a[100] // => "100"
|
615 | * a[b] // => null
|
616 | * a["a" + "b"] // => null
|
617 | * a[tag`b`] // => null
|
618 | * a[`${b}`] // => null
|
619 | *
|
620 | * let a = {b: 1} // => "b"
|
621 | * let a = {["b"]: 1} // => "b"
|
622 | * let a = {['b']: 1} // => "b"
|
623 | * let a = {[`b`]: 1} // => "b"
|
624 | * let a = {[100]: 1} // => "100"
|
625 | * let a = {[b]: 1} // => null
|
626 | * let a = {["a" + "b"]: 1} // => null
|
627 | * let a = {[tag`b`]: 1} // => null
|
628 | * let a = {[`${b}`]: 1} // => null
|
629 | *
|
630 | * @param {ASTNode} node - The node to get.
|
631 | * @returns {string|null} The property name if static. Otherwise, null.
|
632 | */
|
633 | getStaticPropertyName(node) {
|
634 | let prop;
|
635 |
|
636 | switch (node && node.type) {
|
637 | case "Property":
|
638 | case "MethodDefinition":
|
639 | prop = node.key;
|
640 | break;
|
641 |
|
642 | case "MemberExpression":
|
643 | prop = node.property;
|
644 | break;
|
645 |
|
646 | // no default
|
647 | }
|
648 |
|
649 | switch (prop && prop.type) {
|
650 | case "Literal":
|
651 | return String(prop.value);
|
652 |
|
653 | case "TemplateLiteral":
|
654 | if (prop.expressions.length === 0 && prop.quasis.length === 1) {
|
655 | return prop.quasis[0].value.cooked;
|
656 | }
|
657 | break;
|
658 |
|
659 | case "Identifier":
|
660 | if (!node.computed) {
|
661 | return prop.name;
|
662 | }
|
663 | break;
|
664 |
|
665 | // no default
|
666 | }
|
667 |
|
668 | return null;
|
669 | },
|
670 |
|
671 | /**
|
672 | * Get directives from directive prologue of a Program or Function node.
|
673 | * @param {ASTNode} node - The node to check.
|
674 | * @returns {ASTNode[]} The directives found in the directive prologue.
|
675 | */
|
676 | getDirectivePrologue(node) {
|
677 | const directives = [];
|
678 |
|
679 | // Directive prologues only occur at the top of files or functions.
|
680 | if (
|
681 | node.type === "Program" ||
|
682 | node.type === "FunctionDeclaration" ||
|
683 | node.type === "FunctionExpression" ||
|
684 |
|
685 | // Do not check arrow functions with implicit return.
|
686 | // `() => "use strict";` returns the string `"use strict"`.
|
687 | (node.type === "ArrowFunctionExpression" && node.body.type === "BlockStatement")
|
688 | ) {
|
689 | const statements = node.type === "Program" ? node.body : node.body.body;
|
690 |
|
691 | for (const statement of statements) {
|
692 | if (
|
693 | statement.type === "ExpressionStatement" &&
|
694 | statement.expression.type === "Literal"
|
695 | ) {
|
696 | directives.push(statement);
|
697 | } else {
|
698 | break;
|
699 | }
|
700 | }
|
701 | }
|
702 |
|
703 | return directives;
|
704 | },
|
705 |
|
706 |
|
707 | /**
|
708 | * Determines whether this node is a decimal integer literal. If a node is a decimal integer literal, a dot added
|
709 | after the node will be parsed as a decimal point, rather than a property-access dot.
|
710 | * @param {ASTNode} node - The node to check.
|
711 | * @returns {boolean} `true` if this node is a decimal integer.
|
712 | * @example
|
713 | *
|
714 | * 5 // true
|
715 | * 5. // false
|
716 | * 5.0 // false
|
717 | * 05 // false
|
718 | * 0x5 // false
|
719 | * 0b101 // false
|
720 | * 0o5 // false
|
721 | * 5e0 // false
|
722 | * '5' // false
|
723 | */
|
724 | isDecimalInteger(node) {
|
725 | return node.type === "Literal" && typeof node.value === "number" && /^(0|[1-9]\d*)$/.test(node.raw);
|
726 | }
|
727 | };
|