ast.js 12 KB

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