ast.js 12 KB

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