babelUtils.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. // should only use types from @babel/types
  2. // do not import runtime methods
  3. import type {
  4. BlockStatement,
  5. ForInStatement,
  6. ForOfStatement,
  7. ForStatement,
  8. Function,
  9. Identifier,
  10. Node,
  11. ObjectProperty,
  12. Program,
  13. SwitchCase,
  14. SwitchStatement,
  15. } from '@babel/types'
  16. import { walk } from 'estree-walker'
  17. /**
  18. * Return value indicates whether the AST walked can be a constant
  19. */
  20. export function walkIdentifiers(
  21. root: Node,
  22. onIdentifier: (
  23. node: Identifier,
  24. parent: Node | null,
  25. parentStack: Node[],
  26. isReference: boolean,
  27. isLocal: boolean,
  28. ) => void,
  29. includeAll = false,
  30. parentStack: Node[] = [],
  31. knownIds: Record<string, number> = Object.create(null),
  32. ): void {
  33. if (__BROWSER__) {
  34. return
  35. }
  36. const rootExp =
  37. root.type === 'Program'
  38. ? root.body[0].type === 'ExpressionStatement' && root.body[0].expression
  39. : root
  40. walk(root, {
  41. enter(node: Node & { scopeIds?: Set<string> }, parent: Node | null) {
  42. parent && parentStack.push(parent)
  43. if (
  44. parent &&
  45. parent.type.startsWith('TS') &&
  46. !TS_NODE_TYPES.includes(parent.type)
  47. ) {
  48. return this.skip()
  49. }
  50. if (node.type === 'Identifier') {
  51. const isLocal = !!knownIds[node.name]
  52. const isRefed = isReferencedIdentifier(node, parent, parentStack)
  53. if (includeAll || (isRefed && !isLocal)) {
  54. onIdentifier(node, parent, parentStack, isRefed, isLocal)
  55. }
  56. } else if (
  57. node.type === 'ObjectProperty' &&
  58. // eslint-disable-next-line no-restricted-syntax
  59. parent?.type === 'ObjectPattern'
  60. ) {
  61. // mark property in destructure pattern
  62. ;(node as any).inPattern = true
  63. } else if (isFunctionType(node)) {
  64. if (node.scopeIds) {
  65. node.scopeIds.forEach(id => markKnownIds(id, knownIds))
  66. } else {
  67. // walk function expressions and add its arguments to known identifiers
  68. // so that we don't prefix them
  69. walkFunctionParams(node, id =>
  70. markScopeIdentifier(node, id, knownIds),
  71. )
  72. }
  73. } else if (node.type === 'BlockStatement') {
  74. if (node.scopeIds) {
  75. node.scopeIds.forEach(id => markKnownIds(id, knownIds))
  76. } else {
  77. // #3445 record block-level local variables
  78. walkBlockDeclarations(node, id =>
  79. markScopeIdentifier(node, id, knownIds),
  80. )
  81. }
  82. } else if (node.type === 'SwitchStatement') {
  83. if (node.scopeIds) {
  84. node.scopeIds.forEach(id => markKnownIds(id, knownIds))
  85. } else {
  86. // record switch case block-level local variables
  87. walkSwitchStatement(node, false, id =>
  88. markScopeIdentifier(node, id, knownIds),
  89. )
  90. }
  91. } else if (node.type === 'CatchClause' && node.param) {
  92. if (node.scopeIds) {
  93. node.scopeIds.forEach(id => markKnownIds(id, knownIds))
  94. } else {
  95. for (const id of extractIdentifiers(node.param)) {
  96. markScopeIdentifier(node, id, knownIds)
  97. }
  98. }
  99. } else if (isForStatement(node)) {
  100. if (node.scopeIds) {
  101. node.scopeIds.forEach(id => markKnownIds(id, knownIds))
  102. } else {
  103. walkForStatement(node, false, id =>
  104. markScopeIdentifier(node, id, knownIds),
  105. )
  106. }
  107. }
  108. },
  109. leave(node: Node & { scopeIds?: Set<string> }, parent: Node | null) {
  110. parent && parentStack.pop()
  111. if (node !== rootExp && node.scopeIds) {
  112. for (const id of node.scopeIds) {
  113. knownIds[id]--
  114. if (knownIds[id] === 0) {
  115. delete knownIds[id]
  116. }
  117. }
  118. }
  119. },
  120. })
  121. }
  122. export function isReferencedIdentifier(
  123. id: Identifier,
  124. parent: Node | null,
  125. parentStack: Node[],
  126. ): boolean {
  127. if (__BROWSER__) {
  128. return false
  129. }
  130. if (!parent) {
  131. return true
  132. }
  133. // is a special keyword but parsed as identifier
  134. if (id.name === 'arguments') {
  135. return false
  136. }
  137. if (isReferenced(id, parent, parentStack[parentStack.length - 2])) {
  138. return true
  139. }
  140. // babel's isReferenced check returns false for ids being assigned to, so we
  141. // need to cover those cases here
  142. switch (parent.type) {
  143. case 'AssignmentExpression':
  144. case 'AssignmentPattern':
  145. return true
  146. case 'ObjectProperty':
  147. return parent.key !== id && isInDestructureAssignment(parent, parentStack)
  148. case 'ArrayPattern':
  149. return isInDestructureAssignment(parent, parentStack)
  150. }
  151. return false
  152. }
  153. export function isInDestructureAssignment(
  154. parent: Node,
  155. parentStack: Node[],
  156. ): boolean {
  157. if (
  158. parent &&
  159. (parent.type === 'ObjectProperty' || parent.type === 'ArrayPattern')
  160. ) {
  161. let i = parentStack.length
  162. while (i--) {
  163. const p = parentStack[i]
  164. if (p.type === 'AssignmentExpression') {
  165. return true
  166. } else if (p.type !== 'ObjectProperty' && !p.type.endsWith('Pattern')) {
  167. break
  168. }
  169. }
  170. }
  171. return false
  172. }
  173. export function isInNewExpression(parentStack: Node[]): boolean {
  174. let i = parentStack.length
  175. while (i--) {
  176. const p = parentStack[i]
  177. if (p.type === 'NewExpression') {
  178. return true
  179. } else if (p.type !== 'MemberExpression') {
  180. break
  181. }
  182. }
  183. return false
  184. }
  185. export function walkFunctionParams(
  186. node: Function,
  187. onIdent: (id: Identifier) => void,
  188. ): void {
  189. for (const p of node.params) {
  190. for (const id of extractIdentifiers(p)) {
  191. onIdent(id)
  192. }
  193. }
  194. }
  195. export function walkBlockDeclarations(
  196. block: BlockStatement | SwitchCase | Program,
  197. onIdent: (node: Identifier) => void,
  198. ): void {
  199. const body = block.type === 'SwitchCase' ? block.consequent : block.body
  200. for (const stmt of body) {
  201. if (stmt.type === 'VariableDeclaration') {
  202. if (stmt.declare) continue
  203. for (const decl of stmt.declarations) {
  204. for (const id of extractIdentifiers(decl.id)) {
  205. onIdent(id)
  206. }
  207. }
  208. } else if (
  209. stmt.type === 'FunctionDeclaration' ||
  210. stmt.type === 'ClassDeclaration'
  211. ) {
  212. if (stmt.declare || !stmt.id) continue
  213. onIdent(stmt.id)
  214. } else if (isForStatement(stmt)) {
  215. walkForStatement(stmt, true, onIdent)
  216. } else if (stmt.type === 'SwitchStatement') {
  217. walkSwitchStatement(stmt, true, onIdent)
  218. }
  219. }
  220. }
  221. function isForStatement(
  222. stmt: Node,
  223. ): stmt is ForStatement | ForOfStatement | ForInStatement {
  224. return (
  225. stmt.type === 'ForOfStatement' ||
  226. stmt.type === 'ForInStatement' ||
  227. stmt.type === 'ForStatement'
  228. )
  229. }
  230. function walkForStatement(
  231. stmt: ForStatement | ForOfStatement | ForInStatement,
  232. isVar: boolean,
  233. onIdent: (id: Identifier) => void,
  234. ) {
  235. const variable = stmt.type === 'ForStatement' ? stmt.init : stmt.left
  236. if (
  237. variable &&
  238. variable.type === 'VariableDeclaration' &&
  239. (variable.kind === 'var' ? isVar : !isVar)
  240. ) {
  241. for (const decl of variable.declarations) {
  242. for (const id of extractIdentifiers(decl.id)) {
  243. onIdent(id)
  244. }
  245. }
  246. }
  247. }
  248. function walkSwitchStatement(
  249. stmt: SwitchStatement,
  250. isVar: boolean,
  251. onIdent: (id: Identifier) => void,
  252. ) {
  253. for (const cs of stmt.cases) {
  254. for (const stmt of cs.consequent) {
  255. if (
  256. stmt.type === 'VariableDeclaration' &&
  257. (stmt.kind === 'var' ? isVar : !isVar)
  258. ) {
  259. for (const decl of stmt.declarations) {
  260. for (const id of extractIdentifiers(decl.id)) {
  261. onIdent(id)
  262. }
  263. }
  264. }
  265. }
  266. walkBlockDeclarations(cs, onIdent)
  267. }
  268. }
  269. export function extractIdentifiers(
  270. param: Node,
  271. nodes: Identifier[] = [],
  272. ): Identifier[] {
  273. switch (param.type) {
  274. case 'Identifier':
  275. nodes.push(param)
  276. break
  277. case 'MemberExpression':
  278. let object: any = param
  279. while (object.type === 'MemberExpression') {
  280. object = object.object
  281. }
  282. nodes.push(object)
  283. break
  284. case 'ObjectPattern':
  285. for (const prop of param.properties) {
  286. if (prop.type === 'RestElement') {
  287. extractIdentifiers(prop.argument, nodes)
  288. } else {
  289. extractIdentifiers(prop.value, nodes)
  290. }
  291. }
  292. break
  293. case 'ArrayPattern':
  294. param.elements.forEach(element => {
  295. if (element) extractIdentifiers(element, nodes)
  296. })
  297. break
  298. case 'RestElement':
  299. extractIdentifiers(param.argument, nodes)
  300. break
  301. case 'AssignmentPattern':
  302. extractIdentifiers(param.left, nodes)
  303. break
  304. }
  305. return nodes
  306. }
  307. function markKnownIds(name: string, knownIds: Record<string, number>) {
  308. if (name in knownIds) {
  309. knownIds[name]++
  310. } else {
  311. knownIds[name] = 1
  312. }
  313. }
  314. function markScopeIdentifier(
  315. node: Node & { scopeIds?: Set<string> },
  316. child: Identifier,
  317. knownIds: Record<string, number>,
  318. ) {
  319. const { name } = child
  320. if (node.scopeIds && node.scopeIds.has(name)) {
  321. return
  322. }
  323. markKnownIds(name, knownIds)
  324. ;(node.scopeIds || (node.scopeIds = new Set())).add(name)
  325. }
  326. export const isFunctionType = (node: Node): node is Function => {
  327. return /Function(?:Expression|Declaration)$|Method$/.test(node.type)
  328. }
  329. export const isStaticProperty = (node: Node): node is ObjectProperty =>
  330. node &&
  331. (node.type === 'ObjectProperty' || node.type === 'ObjectMethod') &&
  332. !node.computed
  333. export const isStaticPropertyKey = (node: Node, parent: Node): boolean =>
  334. isStaticProperty(parent) && parent.key === node
  335. /**
  336. * Copied from https://github.com/babel/babel/blob/main/packages/babel-types/src/validators/isReferenced.ts
  337. * To avoid runtime dependency on @babel/types (which includes process references)
  338. * This file should not change very often in babel but we may need to keep it
  339. * up-to-date from time to time.
  340. *
  341. * https://github.com/babel/babel/blob/main/LICENSE
  342. *
  343. */
  344. function isReferenced(node: Node, parent: Node, grandparent?: Node): boolean {
  345. switch (parent.type) {
  346. // yes: PARENT[NODE]
  347. // yes: NODE.child
  348. // no: parent.NODE
  349. case 'MemberExpression':
  350. case 'OptionalMemberExpression':
  351. if (parent.property === node) {
  352. return !!parent.computed
  353. }
  354. return parent.object === node
  355. case 'JSXMemberExpression':
  356. return parent.object === node
  357. // no: let NODE = init;
  358. // yes: let id = NODE;
  359. case 'VariableDeclarator':
  360. return parent.init === node
  361. // yes: () => NODE
  362. // no: (NODE) => {}
  363. case 'ArrowFunctionExpression':
  364. return parent.body === node
  365. // no: class { #NODE; }
  366. // no: class { get #NODE() {} }
  367. // no: class { #NODE() {} }
  368. // no: class { fn() { return this.#NODE; } }
  369. case 'PrivateName':
  370. return false
  371. // no: class { NODE() {} }
  372. // yes: class { [NODE]() {} }
  373. // no: class { foo(NODE) {} }
  374. case 'ClassMethod':
  375. case 'ClassPrivateMethod':
  376. case 'ObjectMethod':
  377. if (parent.key === node) {
  378. return !!parent.computed
  379. }
  380. return false
  381. // yes: { [NODE]: "" }
  382. // no: { NODE: "" }
  383. // depends: { NODE }
  384. // depends: { key: NODE }
  385. case 'ObjectProperty':
  386. if (parent.key === node) {
  387. return !!parent.computed
  388. }
  389. // parent.value === node
  390. return !grandparent || grandparent.type !== 'ObjectPattern'
  391. // no: class { NODE = value; }
  392. // yes: class { [NODE] = value; }
  393. // yes: class { key = NODE; }
  394. case 'ClassProperty':
  395. if (parent.key === node) {
  396. return !!parent.computed
  397. }
  398. return true
  399. case 'ClassPrivateProperty':
  400. return parent.key !== node
  401. // no: class NODE {}
  402. // yes: class Foo extends NODE {}
  403. case 'ClassDeclaration':
  404. case 'ClassExpression':
  405. return parent.superClass === node
  406. // yes: left = NODE;
  407. // no: NODE = right;
  408. case 'AssignmentExpression':
  409. return parent.right === node
  410. // no: [NODE = foo] = [];
  411. // yes: [foo = NODE] = [];
  412. case 'AssignmentPattern':
  413. return parent.right === node
  414. // no: NODE: for (;;) {}
  415. case 'LabeledStatement':
  416. return false
  417. // no: try {} catch (NODE) {}
  418. case 'CatchClause':
  419. return false
  420. // no: function foo(...NODE) {}
  421. case 'RestElement':
  422. return false
  423. case 'BreakStatement':
  424. case 'ContinueStatement':
  425. return false
  426. // no: function NODE() {}
  427. // no: function foo(NODE) {}
  428. case 'FunctionDeclaration':
  429. case 'FunctionExpression':
  430. return false
  431. // no: export NODE from "foo";
  432. // no: export * as NODE from "foo";
  433. case 'ExportNamespaceSpecifier':
  434. case 'ExportDefaultSpecifier':
  435. return false
  436. // no: export { foo as NODE };
  437. // yes: export { NODE as foo };
  438. // no: export { NODE as foo } from "foo";
  439. case 'ExportSpecifier':
  440. // @ts-expect-error
  441. // eslint-disable-next-line no-restricted-syntax
  442. if (grandparent?.source) {
  443. return false
  444. }
  445. return parent.local === node
  446. // no: import NODE from "foo";
  447. // no: import * as NODE from "foo";
  448. // no: import { NODE as foo } from "foo";
  449. // no: import { foo as NODE } from "foo";
  450. // no: import NODE from "bar";
  451. case 'ImportDefaultSpecifier':
  452. case 'ImportNamespaceSpecifier':
  453. case 'ImportSpecifier':
  454. return false
  455. // no: import "foo" assert { NODE: "json" }
  456. case 'ImportAttribute':
  457. return false
  458. // no: <div NODE="foo" />
  459. case 'JSXAttribute':
  460. return false
  461. // no: [NODE] = [];
  462. // no: ({ NODE }) = [];
  463. case 'ObjectPattern':
  464. case 'ArrayPattern':
  465. return false
  466. // no: new.NODE
  467. // no: NODE.target
  468. case 'MetaProperty':
  469. return false
  470. // yes: type X = { someProperty: NODE }
  471. // no: type X = { NODE: OtherType }
  472. case 'ObjectTypeProperty':
  473. return parent.key !== node
  474. // yes: enum X { Foo = NODE }
  475. // no: enum X { NODE }
  476. case 'TSEnumMember':
  477. return parent.id !== node
  478. // yes: { [NODE]: value }
  479. // no: { NODE: value }
  480. case 'TSPropertySignature':
  481. if (parent.key === node) {
  482. return !!parent.computed
  483. }
  484. return true
  485. }
  486. return true
  487. }
  488. export const TS_NODE_TYPES: string[] = [
  489. 'TSAsExpression', // foo as number
  490. 'TSTypeAssertion', // (<number>foo)
  491. 'TSNonNullExpression', // foo!
  492. 'TSInstantiationExpression', // foo<string>
  493. 'TSSatisfiesExpression', // foo satisfies T
  494. ]
  495. export function unwrapTSNode(node: Node): Node {
  496. if (TS_NODE_TYPES.includes(node.type)) {
  497. return unwrapTSNode((node as any).expression)
  498. } else {
  499. return node
  500. }
  501. }