source: imaps-frontend/node_modules/eslint-plugin-react/lib/util/ast.js@ 0c6b92a

main
Last change on this file since 0c6b92a was 0c6b92a, checked in by stefan toskovski <stefantoska84@…>, 5 weeks ago

Pred finalna verzija

  • Property mode set to 100644
File size: 11.9 KB
Line 
1/**
2 * @fileoverview Utility functions for AST
3 */
4
5'use strict';
6
7const estraverse = require('estraverse');
8const eslintUtil = require('./eslint');
9
10const getFirstTokens = eslintUtil.getFirstTokens;
11const getScope = eslintUtil.getScope;
12const getSourceCode = eslintUtil.getSourceCode;
13// const pragmaUtil = require('./pragma');
14
15/**
16 * Wrapper for estraverse.traverse
17 *
18 * @param {ASTNode} ASTnode The AST node being checked
19 * @param {Object} visitor Visitor Object for estraverse
20 */
21function traverse(ASTnode, visitor) {
22 const opts = Object.assign({}, {
23 fallback(node) {
24 return Object.keys(node).filter((key) => key === 'children' || key === 'argument');
25 },
26 }, visitor);
27
28 opts.keys = Object.assign({}, visitor.keys, {
29 JSXElement: ['children'],
30 JSXFragment: ['children'],
31 });
32
33 estraverse.traverse(ASTnode, opts);
34}
35
36function loopNodes(nodes) {
37 for (let i = nodes.length - 1; i >= 0; i--) {
38 if (nodes[i].type === 'ReturnStatement') {
39 return nodes[i];
40 }
41 if (nodes[i].type === 'SwitchStatement') {
42 const j = nodes[i].cases.length - 1;
43 if (j >= 0) {
44 return loopNodes(nodes[i].cases[j].consequent);
45 }
46 }
47 }
48 return false;
49}
50
51/**
52 * Find a return statement in the current node
53 *
54 * @param {ASTNode} node The AST node being checked
55 * @returns {ASTNode | false}
56 */
57function findReturnStatement(node) {
58 if (
59 (!node.value || !node.value.body || !node.value.body.body)
60 && (!node.body || !node.body.body)
61 ) {
62 return false;
63 }
64
65 const bodyNodes = node.value ? node.value.body.body : node.body.body;
66
67 return loopNodes(bodyNodes);
68}
69
70// eslint-disable-next-line valid-jsdoc -- valid-jsdoc cannot parse function types.
71/**
72 * Helper function for traversing "returns" (return statements or the
73 * returned expression in the case of an arrow function) of a function
74 *
75 * @param {ASTNode} ASTNode The AST node being checked
76 * @param {Context} context The context of `ASTNode`.
77 * @param {(returnValue: ASTNode, breakTraverse: () => void) => void} onReturn
78 * Function to execute for each returnStatement found
79 * @returns {undefined}
80 */
81function traverseReturns(ASTNode, context, onReturn) {
82 const nodeType = ASTNode.type;
83
84 if (nodeType === 'ReturnStatement') {
85 onReturn(ASTNode.argument, () => {});
86 return;
87 }
88
89 if (nodeType === 'ArrowFunctionExpression' && ASTNode.expression) {
90 onReturn(ASTNode.body, () => {});
91 return;
92 }
93
94 /* TODO: properly warn on React.forwardRefs having typo properties
95 if (astUtil.isCallExpression(ASTNode)) {
96 const callee = ASTNode.callee;
97 const pragma = pragmaUtil.getFromContext(context);
98 if (
99 callee.type === 'MemberExpression'
100 && callee.object.type === 'Identifier'
101 && callee.object.name === pragma
102 && callee.property.type === 'Identifier'
103 && callee.property.name === 'forwardRef'
104 && ASTNode.arguments.length > 0
105 ) {
106 return enterFunc(ASTNode.arguments[0]);
107 }
108 return;
109 }
110 */
111
112 if (
113 nodeType !== 'FunctionExpression'
114 && nodeType !== 'FunctionDeclaration'
115 && nodeType !== 'ArrowFunctionExpression'
116 && nodeType !== 'MethodDefinition'
117 ) {
118 return;
119 }
120
121 traverse(ASTNode.body, {
122 enter(node) {
123 const breakTraverse = () => {
124 this.break();
125 };
126 switch (node.type) {
127 case 'ReturnStatement':
128 this.skip();
129 onReturn(node.argument, breakTraverse);
130 return;
131 case 'BlockStatement':
132 case 'IfStatement':
133 case 'ForStatement':
134 case 'WhileStatement':
135 case 'SwitchStatement':
136 case 'SwitchCase':
137 return;
138 default:
139 this.skip();
140 }
141 },
142 });
143}
144
145/**
146 * Get node with property's name
147 * @param {Object} node - Property.
148 * @returns {Object} Property name node.
149 */
150function getPropertyNameNode(node) {
151 if (
152 node.key
153 || node.type === 'MethodDefinition'
154 || node.type === 'Property'
155 ) {
156 return node.key;
157 }
158 if (node.type === 'MemberExpression') {
159 return node.property;
160 }
161 return null;
162}
163
164/**
165 * Get properties name
166 * @param {Object} node - Property.
167 * @returns {string} Property name.
168 */
169function getPropertyName(node) {
170 const nameNode = getPropertyNameNode(node);
171 return nameNode ? nameNode.name : '';
172}
173
174/**
175 * Get properties for a given AST node
176 * @param {ASTNode} node The AST node being checked.
177 * @returns {Array} Properties array.
178 */
179function getComponentProperties(node) {
180 switch (node.type) {
181 case 'ClassDeclaration':
182 case 'ClassExpression':
183 return node.body.body;
184 case 'ObjectExpression':
185 return node.properties;
186 default:
187 return [];
188 }
189}
190
191/**
192 * Gets the first node in a line from the initial node, excluding whitespace.
193 * @param {Object} context The node to check
194 * @param {ASTNode} node The node to check
195 * @return {ASTNode} the first node in the line
196 */
197function getFirstNodeInLine(context, node) {
198 const sourceCode = getSourceCode(context);
199 let token = node;
200 let lines;
201 do {
202 token = sourceCode.getTokenBefore(token);
203 lines = token.type === 'JSXText'
204 ? token.value.split('\n')
205 : null;
206 } while (
207 token.type === 'JSXText'
208 && /^\s*$/.test(lines[lines.length - 1])
209 );
210 return token;
211}
212
213/**
214 * Checks if the node is the first in its line, excluding whitespace.
215 * @param {Object} context The node to check
216 * @param {ASTNode} node The node to check
217 * @return {boolean} true if it's the first node in its line
218 */
219function isNodeFirstInLine(context, node) {
220 const token = getFirstNodeInLine(context, node);
221 const startLine = node.loc.start.line;
222 const endLine = token ? token.loc.end.line : -1;
223 return startLine !== endLine;
224}
225
226/**
227 * Checks if the node is a function or arrow function expression.
228 * @param {ASTNode} node The node to check
229 * @return {boolean} true if it's a function-like expression
230 */
231function isFunctionLikeExpression(node) {
232 return node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression';
233}
234
235/**
236 * Checks if the node is a function.
237 * @param {ASTNode} node The node to check
238 * @return {boolean} true if it's a function
239 */
240function isFunction(node) {
241 return node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration';
242}
243
244/**
245 * Checks if node is a function declaration or expression or arrow function.
246 * @param {ASTNode} node The node to check
247 * @return {boolean} true if it's a function-like
248 */
249function isFunctionLike(node) {
250 return node.type === 'FunctionDeclaration' || isFunctionLikeExpression(node);
251}
252
253/**
254 * Checks if the node is a class.
255 * @param {ASTNode} node The node to check
256 * @return {boolean} true if it's a class
257 */
258function isClass(node) {
259 return node.type === 'ClassDeclaration' || node.type === 'ClassExpression';
260}
261
262/**
263 * Check if we are in a class constructor
264 * @param {Context} context
265 * @param {ASTNode} node The AST node being checked.
266 * @return {boolean}
267 */
268function inConstructor(context, node) {
269 let scope = getScope(context, node);
270 while (scope) {
271 // @ts-ignore
272 if (scope.block && scope.block.parent && scope.block.parent.kind === 'constructor') {
273 return true;
274 }
275 scope = scope.upper;
276 }
277 return false;
278}
279
280/**
281 * Removes quotes from around an identifier.
282 * @param {string} string the identifier to strip
283 * @returns {string}
284 */
285function stripQuotes(string) {
286 return string.replace(/^'|'$/g, '');
287}
288
289/**
290 * Retrieve the name of a key node
291 * @param {Context} context The AST node with the key.
292 * @param {any} node The AST node with the key.
293 * @return {string | undefined} the name of the key
294 */
295function getKeyValue(context, node) {
296 if (node.type === 'ObjectTypeProperty') {
297 const tokens = getFirstTokens(context, node, 2);
298 return (tokens[0].value === '+' || tokens[0].value === '-'
299 ? tokens[1].value
300 : stripQuotes(tokens[0].value)
301 );
302 }
303 if (node.type === 'GenericTypeAnnotation') {
304 return node.id.name;
305 }
306 if (node.type === 'ObjectTypeAnnotation') {
307 return;
308 }
309 const key = node.key || node.argument;
310 if (!key) {
311 return;
312 }
313 return key.type === 'Identifier' ? key.name : key.value;
314}
315
316/**
317 * Checks if a node is surrounded by parenthesis.
318 *
319 * @param {object} context - Context from the rule
320 * @param {ASTNode} node - Node to be checked
321 * @returns {boolean}
322 */
323function isParenthesized(context, node) {
324 const sourceCode = getSourceCode(context);
325 const previousToken = sourceCode.getTokenBefore(node);
326 const nextToken = sourceCode.getTokenAfter(node);
327
328 return !!previousToken && !!nextToken
329 && previousToken.value === '(' && previousToken.range[1] <= node.range[0]
330 && nextToken.value === ')' && nextToken.range[0] >= node.range[1];
331}
332
333/**
334 * Checks if a node is being assigned a value: props.bar = 'bar'
335 * @param {ASTNode} node The AST node being checked.
336 * @returns {boolean}
337 */
338function isAssignmentLHS(node) {
339 return (
340 node.parent
341 && node.parent.type === 'AssignmentExpression'
342 && node.parent.left === node
343 );
344}
345
346function isTSAsExpression(node) {
347 return node && node.type === 'TSAsExpression';
348}
349
350/**
351 * Matcher used to check whether given node is a `CallExpression`
352 * @param {ASTNode} node The AST node
353 * @returns {boolean} True if node is a `CallExpression`, false if not
354 */
355function isCallExpression(node) {
356 return node && node.type === 'CallExpression';
357}
358
359/**
360 * Extracts the expression node that is wrapped inside a TS type assertion
361 *
362 * @param {ASTNode} node - potential TS node
363 * @returns {ASTNode} - unwrapped expression node
364 */
365function unwrapTSAsExpression(node) {
366 return isTSAsExpression(node) ? node.expression : node;
367}
368
369function isTSTypeReference(node) {
370 if (!node) return false;
371
372 return node.type === 'TSTypeReference';
373}
374
375function isTSTypeAnnotation(node) {
376 if (!node) { return false; }
377
378 return node.type === 'TSTypeAnnotation';
379}
380
381function isTSTypeLiteral(node) {
382 if (!node) { return false; }
383
384 return node.type === 'TSTypeLiteral';
385}
386
387function isTSIntersectionType(node) {
388 if (!node) { return false; }
389
390 return node.type === 'TSIntersectionType';
391}
392
393function isTSInterfaceHeritage(node) {
394 if (!node) { return false; }
395
396 return node.type === 'TSInterfaceHeritage';
397}
398
399function isTSInterfaceDeclaration(node) {
400 if (!node) { return false; }
401
402 return (node.type === 'ExportNamedDeclaration' && node.declaration
403 ? node.declaration.type
404 : node.type
405 ) === 'TSInterfaceDeclaration';
406}
407
408function isTSTypeDeclaration(node) {
409 if (!node) { return false; }
410
411 const nodeToCheck = node.type === 'ExportNamedDeclaration' && node.declaration
412 ? node.declaration
413 : node;
414
415 return nodeToCheck.type === 'VariableDeclaration' && nodeToCheck.kind === 'type';
416}
417
418function isTSTypeAliasDeclaration(node) {
419 if (!node) { return false; }
420
421 if (node.type === 'ExportNamedDeclaration' && node.declaration) {
422 return node.declaration.type === 'TSTypeAliasDeclaration' && node.exportKind === 'type';
423 }
424 return node.type === 'TSTypeAliasDeclaration';
425}
426
427function isTSParenthesizedType(node) {
428 if (!node) { return false; }
429
430 return node.type === 'TSTypeAliasDeclaration';
431}
432
433function isTSFunctionType(node) {
434 if (!node) { return false; }
435
436 return node.type === 'TSFunctionType';
437}
438
439function isTSTypeQuery(node) {
440 if (!node) { return false; }
441
442 return node.type === 'TSTypeQuery';
443}
444
445function isTSTypeParameterInstantiation(node) {
446 if (!node) { return false; }
447
448 return node.type === 'TSTypeParameterInstantiation';
449}
450
451module.exports = {
452 findReturnStatement,
453 getComponentProperties,
454 getFirstNodeInLine,
455 getKeyValue,
456 getPropertyName,
457 getPropertyNameNode,
458 inConstructor,
459 isAssignmentLHS,
460 isCallExpression,
461 isClass,
462 isFunction,
463 isFunctionLike,
464 isFunctionLikeExpression,
465 isNodeFirstInLine,
466 isParenthesized,
467 isTSAsExpression,
468 isTSFunctionType,
469 isTSInterfaceDeclaration,
470 isTSInterfaceHeritage,
471 isTSIntersectionType,
472 isTSParenthesizedType,
473 isTSTypeAliasDeclaration,
474 isTSTypeAnnotation,
475 isTSTypeDeclaration,
476 isTSTypeLiteral,
477 isTSTypeParameterInstantiation,
478 isTSTypeQuery,
479 isTSTypeReference,
480 traverse,
481 traverseReturns,
482 unwrapTSAsExpression,
483};
Note: See TracBrowser for help on using the repository browser.