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

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

Update repo after prototype presentation

  • 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 (nodeType === 'CallExpression') {
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 (node.key || ['MethodDefinition', 'Property'].indexOf(node.type) !== -1) {
152 return node.key;
153 }
154 if (node.type === 'MemberExpression') {
155 return node.property;
156 }
157 return null;
158}
159
160/**
161 * Get properties name
162 * @param {Object} node - Property.
163 * @returns {String} Property name.
164 */
165function getPropertyName(node) {
166 const nameNode = getPropertyNameNode(node);
167 return nameNode ? nameNode.name : '';
168}
169
170/**
171 * Get properties for a given AST node
172 * @param {ASTNode} node The AST node being checked.
173 * @returns {Array} Properties array.
174 */
175function getComponentProperties(node) {
176 switch (node.type) {
177 case 'ClassDeclaration':
178 case 'ClassExpression':
179 return node.body.body;
180 case 'ObjectExpression':
181 return node.properties;
182 default:
183 return [];
184 }
185}
186
187/**
188 * Gets the first node in a line from the initial node, excluding whitespace.
189 * @param {Object} context The node to check
190 * @param {ASTNode} node The node to check
191 * @return {ASTNode} the first node in the line
192 */
193function getFirstNodeInLine(context, node) {
194 const sourceCode = getSourceCode(context);
195 let token = node;
196 let lines;
197 do {
198 token = sourceCode.getTokenBefore(token);
199 lines = token.type === 'JSXText'
200 ? token.value.split('\n')
201 : null;
202 } while (
203 token.type === 'JSXText'
204 && /^\s*$/.test(lines[lines.length - 1])
205 );
206 return token;
207}
208
209/**
210 * Checks if the node is the first in its line, excluding whitespace.
211 * @param {Object} context The node to check
212 * @param {ASTNode} node The node to check
213 * @return {Boolean} true if it's the first node in its line
214 */
215function isNodeFirstInLine(context, node) {
216 const token = getFirstNodeInLine(context, node);
217 const startLine = node.loc.start.line;
218 const endLine = token ? token.loc.end.line : -1;
219 return startLine !== endLine;
220}
221
222/**
223 * Checks if the node is a function or arrow function expression.
224 * @param {ASTNode} node The node to check
225 * @return {Boolean} true if it's a function-like expression
226 */
227function isFunctionLikeExpression(node) {
228 return node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression';
229}
230
231/**
232 * Checks if the node is a function.
233 * @param {ASTNode} node The node to check
234 * @return {Boolean} true if it's a function
235 */
236function isFunction(node) {
237 return node.type === 'FunctionExpression' || node.type === 'FunctionDeclaration';
238}
239
240/**
241 * Checks if node is a function declaration or expression or arrow function.
242 * @param {ASTNode} node The node to check
243 * @return {Boolean} true if it's a function-like
244 */
245function isFunctionLike(node) {
246 return node.type === 'FunctionDeclaration' || isFunctionLikeExpression(node);
247}
248
249/**
250 * Checks if the node is a class.
251 * @param {ASTNode} node The node to check
252 * @return {Boolean} true if it's a class
253 */
254function isClass(node) {
255 return node.type === 'ClassDeclaration' || node.type === 'ClassExpression';
256}
257
258/**
259 * Check if we are in a class constructor
260 * @param {Context} context
261 * @param {ASTNode} node The AST node being checked.
262 * @return {boolean}
263 */
264function inConstructor(context, node) {
265 let scope = getScope(context, node);
266 while (scope) {
267 // @ts-ignore
268 if (scope.block && scope.block.parent && scope.block.parent.kind === 'constructor') {
269 return true;
270 }
271 scope = scope.upper;
272 }
273 return false;
274}
275
276/**
277 * Removes quotes from around an identifier.
278 * @param {string} string the identifier to strip
279 * @returns {string}
280 */
281function stripQuotes(string) {
282 return string.replace(/^'|'$/g, '');
283}
284
285/**
286 * Retrieve the name of a key node
287 * @param {Context} context The AST node with the key.
288 * @param {any} node The AST node with the key.
289 * @return {string | undefined} the name of the key
290 */
291function getKeyValue(context, node) {
292 if (node.type === 'ObjectTypeProperty') {
293 const tokens = getFirstTokens(context, node, 2);
294 return (tokens[0].value === '+' || tokens[0].value === '-'
295 ? tokens[1].value
296 : stripQuotes(tokens[0].value)
297 );
298 }
299 if (node.type === 'GenericTypeAnnotation') {
300 return node.id.name;
301 }
302 if (node.type === 'ObjectTypeAnnotation') {
303 return;
304 }
305 const key = node.key || node.argument;
306 if (!key) {
307 return;
308 }
309 return key.type === 'Identifier' ? key.name : key.value;
310}
311
312/**
313 * Checks if a node is surrounded by parenthesis.
314 *
315 * @param {object} context - Context from the rule
316 * @param {ASTNode} node - Node to be checked
317 * @returns {boolean}
318 */
319function isParenthesized(context, node) {
320 const sourceCode = getSourceCode(context);
321 const previousToken = sourceCode.getTokenBefore(node);
322 const nextToken = sourceCode.getTokenAfter(node);
323
324 return !!previousToken && !!nextToken
325 && previousToken.value === '(' && previousToken.range[1] <= node.range[0]
326 && nextToken.value === ')' && nextToken.range[0] >= node.range[1];
327}
328
329/**
330 * Checks if a node is being assigned a value: props.bar = 'bar'
331 * @param {ASTNode} node The AST node being checked.
332 * @returns {Boolean}
333 */
334function isAssignmentLHS(node) {
335 return (
336 node.parent
337 && node.parent.type === 'AssignmentExpression'
338 && node.parent.left === node
339 );
340}
341
342/**
343 * Extracts the expression node that is wrapped inside a TS type assertion
344 *
345 * @param {ASTNode} node - potential TS node
346 * @returns {ASTNode} - unwrapped expression node
347 */
348function unwrapTSAsExpression(node) {
349 if (node && node.type === 'TSAsExpression') return node.expression;
350 return node;
351}
352
353function isTSTypeReference(node) {
354 if (!node) return false;
355 const nodeType = node.type;
356 return nodeType === 'TSTypeReference';
357}
358
359function isTSTypeAnnotation(node) {
360 if (!node) return false;
361 const nodeType = node.type;
362 return nodeType === 'TSTypeAnnotation';
363}
364
365function isTSTypeLiteral(node) {
366 if (!node) return false;
367 const nodeType = node.type;
368 return nodeType === 'TSTypeLiteral';
369}
370
371function isTSIntersectionType(node) {
372 if (!node) return false;
373 const nodeType = node.type;
374 return nodeType === 'TSIntersectionType';
375}
376
377function isTSInterfaceHeritage(node) {
378 if (!node) return false;
379 const nodeType = node.type;
380 return nodeType === 'TSInterfaceHeritage';
381}
382
383function isTSInterfaceDeclaration(node) {
384 if (!node) return false;
385 let nodeType = node.type;
386 if (node.type === 'ExportNamedDeclaration' && node.declaration) {
387 nodeType = node.declaration.type;
388 }
389 return nodeType === 'TSInterfaceDeclaration';
390}
391
392function isTSTypeDeclaration(node) {
393 if (!node) return false;
394 let nodeType = node.type;
395 let nodeKind = node.kind;
396 if (node.type === 'ExportNamedDeclaration' && node.declaration) {
397 nodeType = node.declaration.type;
398 nodeKind = node.declaration.kind;
399 }
400 return nodeType === 'VariableDeclaration' && nodeKind === 'type';
401}
402
403function isTSTypeAliasDeclaration(node) {
404 if (!node) return false;
405 let nodeType = node.type;
406 if (node.type === 'ExportNamedDeclaration' && node.declaration) {
407 nodeType = node.declaration.type;
408 return nodeType === 'TSTypeAliasDeclaration' && node.exportKind === 'type';
409 }
410 return nodeType === 'TSTypeAliasDeclaration';
411}
412
413function isTSParenthesizedType(node) {
414 if (!node) return false;
415 const nodeType = node.type;
416 return nodeType === 'TSTypeAliasDeclaration';
417}
418
419function isTSFunctionType(node) {
420 if (!node) return false;
421 const nodeType = node.type;
422 return nodeType === 'TSFunctionType';
423}
424
425function isTSTypeQuery(node) {
426 if (!node) return false;
427 const nodeType = node.type;
428 return nodeType === 'TSTypeQuery';
429}
430
431function isTSTypeParameterInstantiation(node) {
432 if (!node) return false;
433 const nodeType = node.type;
434 return nodeType === 'TSTypeParameterInstantiation';
435}
436
437module.exports = {
438 traverse,
439 findReturnStatement,
440 getFirstNodeInLine,
441 getPropertyName,
442 getPropertyNameNode,
443 getComponentProperties,
444 getKeyValue,
445 isParenthesized,
446 isAssignmentLHS,
447 isClass,
448 isFunction,
449 isFunctionLikeExpression,
450 isFunctionLike,
451 inConstructor,
452 isNodeFirstInLine,
453 unwrapTSAsExpression,
454 traverseReturns,
455 isTSTypeReference,
456 isTSTypeAnnotation,
457 isTSTypeLiteral,
458 isTSIntersectionType,
459 isTSInterfaceHeritage,
460 isTSInterfaceDeclaration,
461 isTSTypeAliasDeclaration,
462 isTSParenthesizedType,
463 isTSFunctionType,
464 isTSTypeQuery,
465 isTSTypeParameterInstantiation,
466 isTSTypeDeclaration,
467};
Note: See TracBrowser for help on using the repository browser.