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
RevLine 
[d565449]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
[0c6b92a]95 if (astUtil.isCallExpression(ASTNode)) {
[d565449]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) {
[0c6b92a]151 if (
152 node.key
153 || node.type === 'MethodDefinition'
154 || node.type === 'Property'
155 ) {
[d565449]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.
[0c6b92a]167 * @returns {string} Property name.
[d565449]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
[0c6b92a]217 * @return {boolean} true if it's the first node in its line
[d565449]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
[0c6b92a]229 * @return {boolean} true if it's a function-like expression
[d565449]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
[0c6b92a]238 * @return {boolean} true if it's a function
[d565449]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
[0c6b92a]247 * @return {boolean} true if it's a function-like
[d565449]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
[0c6b92a]256 * @return {boolean} true if it's a class
[d565449]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.
[0c6b92a]336 * @returns {boolean}
[d565449]337 */
338function isAssignmentLHS(node) {
339 return (
340 node.parent
341 && node.parent.type === 'AssignmentExpression'
342 && node.parent.left === node
343 );
344}
345
[0c6b92a]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
[d565449]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) {
[0c6b92a]366 return isTSAsExpression(node) ? node.expression : node;
[d565449]367}
368
369function isTSTypeReference(node) {
370 if (!node) return false;
[0c6b92a]371
372 return node.type === 'TSTypeReference';
[d565449]373}
374
375function isTSTypeAnnotation(node) {
[0c6b92a]376 if (!node) { return false; }
377
378 return node.type === 'TSTypeAnnotation';
[d565449]379}
380
381function isTSTypeLiteral(node) {
[0c6b92a]382 if (!node) { return false; }
383
384 return node.type === 'TSTypeLiteral';
[d565449]385}
386
387function isTSIntersectionType(node) {
[0c6b92a]388 if (!node) { return false; }
389
390 return node.type === 'TSIntersectionType';
[d565449]391}
392
393function isTSInterfaceHeritage(node) {
[0c6b92a]394 if (!node) { return false; }
395
396 return node.type === 'TSInterfaceHeritage';
[d565449]397}
398
399function isTSInterfaceDeclaration(node) {
[0c6b92a]400 if (!node) { return false; }
401
402 return (node.type === 'ExportNamedDeclaration' && node.declaration
403 ? node.declaration.type
404 : node.type
405 ) === 'TSInterfaceDeclaration';
[d565449]406}
407
408function isTSTypeDeclaration(node) {
[0c6b92a]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';
[d565449]416}
417
418function isTSTypeAliasDeclaration(node) {
[0c6b92a]419 if (!node) { return false; }
420
[d565449]421 if (node.type === 'ExportNamedDeclaration' && node.declaration) {
[0c6b92a]422 return node.declaration.type === 'TSTypeAliasDeclaration' && node.exportKind === 'type';
[d565449]423 }
[0c6b92a]424 return node.type === 'TSTypeAliasDeclaration';
[d565449]425}
426
427function isTSParenthesizedType(node) {
[0c6b92a]428 if (!node) { return false; }
429
430 return node.type === 'TSTypeAliasDeclaration';
[d565449]431}
432
433function isTSFunctionType(node) {
[0c6b92a]434 if (!node) { return false; }
435
436 return node.type === 'TSFunctionType';
[d565449]437}
438
439function isTSTypeQuery(node) {
[0c6b92a]440 if (!node) { return false; }
441
442 return node.type === 'TSTypeQuery';
[d565449]443}
444
445function isTSTypeParameterInstantiation(node) {
[0c6b92a]446 if (!node) { return false; }
447
448 return node.type === 'TSTypeParameterInstantiation';
[d565449]449}
450
451module.exports = {
452 findReturnStatement,
[0c6b92a]453 getComponentProperties,
[d565449]454 getFirstNodeInLine,
[0c6b92a]455 getKeyValue,
[d565449]456 getPropertyName,
457 getPropertyNameNode,
[0c6b92a]458 inConstructor,
[d565449]459 isAssignmentLHS,
[0c6b92a]460 isCallExpression,
[d565449]461 isClass,
462 isFunction,
463 isFunctionLike,
[0c6b92a]464 isFunctionLikeExpression,
[d565449]465 isNodeFirstInLine,
[0c6b92a]466 isParenthesized,
467 isTSAsExpression,
468 isTSFunctionType,
[d565449]469 isTSInterfaceDeclaration,
[0c6b92a]470 isTSInterfaceHeritage,
471 isTSIntersectionType,
[d565449]472 isTSParenthesizedType,
[0c6b92a]473 isTSTypeAliasDeclaration,
474 isTSTypeAnnotation,
[d565449]475 isTSTypeDeclaration,
[0c6b92a]476 isTSTypeLiteral,
477 isTSTypeParameterInstantiation,
478 isTSTypeQuery,
479 isTSTypeReference,
480 traverse,
481 traverseReturns,
482 unwrapTSAsExpression,
[d565449]483};
Note: See TracBrowser for help on using the repository browser.