| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557 |
- // should only use types from @babel/types
- // do not import runtime methods
- import type {
- BlockStatement,
- ForInStatement,
- ForOfStatement,
- ForStatement,
- Function,
- Identifier,
- Node,
- ObjectProperty,
- Program,
- SwitchCase,
- SwitchStatement,
- } from '@babel/types'
- import { walk } from 'estree-walker'
- /**
- * Return value indicates whether the AST walked can be a constant
- */
- export function walkIdentifiers(
- root: Node,
- onIdentifier: (
- node: Identifier,
- parent: Node | null,
- parentStack: Node[],
- isReference: boolean,
- isLocal: boolean,
- ) => void,
- includeAll = false,
- parentStack: Node[] = [],
- knownIds: Record<string, number> = Object.create(null),
- ): void {
- if (__BROWSER__) {
- return
- }
- const rootExp =
- root.type === 'Program'
- ? root.body[0].type === 'ExpressionStatement' && root.body[0].expression
- : root
- walk(root, {
- enter(node: Node & { scopeIds?: Set<string> }, parent: Node | null) {
- parent && parentStack.push(parent)
- if (
- parent &&
- parent.type.startsWith('TS') &&
- !TS_NODE_TYPES.includes(parent.type)
- ) {
- return this.skip()
- }
- if (node.type === 'Identifier') {
- const isLocal = !!knownIds[node.name]
- const isRefed = isReferencedIdentifier(node, parent, parentStack)
- if (includeAll || (isRefed && !isLocal)) {
- onIdentifier(node, parent, parentStack, isRefed, isLocal)
- }
- } else if (
- node.type === 'ObjectProperty' &&
- // eslint-disable-next-line no-restricted-syntax
- parent?.type === 'ObjectPattern'
- ) {
- // mark property in destructure pattern
- ;(node as any).inPattern = true
- } else if (isFunctionType(node)) {
- if (node.scopeIds) {
- node.scopeIds.forEach(id => markKnownIds(id, knownIds))
- } else {
- // walk function expressions and add its arguments to known identifiers
- // so that we don't prefix them
- walkFunctionParams(node, id =>
- markScopeIdentifier(node, id, knownIds),
- )
- }
- } else if (node.type === 'BlockStatement') {
- if (node.scopeIds) {
- node.scopeIds.forEach(id => markKnownIds(id, knownIds))
- } else {
- // #3445 record block-level local variables
- walkBlockDeclarations(node, id =>
- markScopeIdentifier(node, id, knownIds),
- )
- }
- } else if (node.type === 'SwitchStatement') {
- if (node.scopeIds) {
- node.scopeIds.forEach(id => markKnownIds(id, knownIds))
- } else {
- // record switch case block-level local variables
- walkSwitchStatement(node, false, id =>
- markScopeIdentifier(node, id, knownIds),
- )
- }
- } else if (node.type === 'CatchClause' && node.param) {
- if (node.scopeIds) {
- node.scopeIds.forEach(id => markKnownIds(id, knownIds))
- } else {
- for (const id of extractIdentifiers(node.param)) {
- markScopeIdentifier(node, id, knownIds)
- }
- }
- } else if (isForStatement(node)) {
- if (node.scopeIds) {
- node.scopeIds.forEach(id => markKnownIds(id, knownIds))
- } else {
- walkForStatement(node, false, id =>
- markScopeIdentifier(node, id, knownIds),
- )
- }
- }
- },
- leave(node: Node & { scopeIds?: Set<string> }, parent: Node | null) {
- parent && parentStack.pop()
- if (node !== rootExp && node.scopeIds) {
- for (const id of node.scopeIds) {
- knownIds[id]--
- if (knownIds[id] === 0) {
- delete knownIds[id]
- }
- }
- }
- },
- })
- }
- export function isReferencedIdentifier(
- id: Identifier,
- parent: Node | null,
- parentStack: Node[],
- ): boolean {
- if (__BROWSER__) {
- return false
- }
- if (!parent) {
- return true
- }
- // is a special keyword but parsed as identifier
- if (id.name === 'arguments') {
- return false
- }
- if (isReferenced(id, parent, parentStack[parentStack.length - 2])) {
- return true
- }
- // babel's isReferenced check returns false for ids being assigned to, so we
- // need to cover those cases here
- switch (parent.type) {
- case 'AssignmentExpression':
- case 'AssignmentPattern':
- return true
- case 'ObjectProperty':
- return parent.key !== id && isInDestructureAssignment(parent, parentStack)
- case 'ArrayPattern':
- return isInDestructureAssignment(parent, parentStack)
- }
- return false
- }
- export function isInDestructureAssignment(
- parent: Node,
- parentStack: Node[],
- ): boolean {
- if (
- parent &&
- (parent.type === 'ObjectProperty' || parent.type === 'ArrayPattern')
- ) {
- let i = parentStack.length
- while (i--) {
- const p = parentStack[i]
- if (p.type === 'AssignmentExpression') {
- return true
- } else if (p.type !== 'ObjectProperty' && !p.type.endsWith('Pattern')) {
- break
- }
- }
- }
- return false
- }
- export function isInNewExpression(parentStack: Node[]): boolean {
- let i = parentStack.length
- while (i--) {
- const p = parentStack[i]
- if (p.type === 'NewExpression') {
- return true
- } else if (p.type !== 'MemberExpression') {
- break
- }
- }
- return false
- }
- export function walkFunctionParams(
- node: Function,
- onIdent: (id: Identifier) => void,
- ): void {
- for (const p of node.params) {
- for (const id of extractIdentifiers(p)) {
- onIdent(id)
- }
- }
- }
- export function walkBlockDeclarations(
- block: BlockStatement | SwitchCase | Program,
- onIdent: (node: Identifier) => void,
- ): void {
- const body = block.type === 'SwitchCase' ? block.consequent : block.body
- for (const stmt of body) {
- if (stmt.type === 'VariableDeclaration') {
- if (stmt.declare) continue
- for (const decl of stmt.declarations) {
- for (const id of extractIdentifiers(decl.id)) {
- onIdent(id)
- }
- }
- } else if (
- stmt.type === 'FunctionDeclaration' ||
- stmt.type === 'ClassDeclaration'
- ) {
- if (stmt.declare || !stmt.id) continue
- onIdent(stmt.id)
- } else if (isForStatement(stmt)) {
- walkForStatement(stmt, true, onIdent)
- } else if (stmt.type === 'SwitchStatement') {
- walkSwitchStatement(stmt, true, onIdent)
- }
- }
- }
- function isForStatement(
- stmt: Node,
- ): stmt is ForStatement | ForOfStatement | ForInStatement {
- return (
- stmt.type === 'ForOfStatement' ||
- stmt.type === 'ForInStatement' ||
- stmt.type === 'ForStatement'
- )
- }
- function walkForStatement(
- stmt: ForStatement | ForOfStatement | ForInStatement,
- isVar: boolean,
- onIdent: (id: Identifier) => void,
- ) {
- const variable = stmt.type === 'ForStatement' ? stmt.init : stmt.left
- if (
- variable &&
- variable.type === 'VariableDeclaration' &&
- (variable.kind === 'var' ? isVar : !isVar)
- ) {
- for (const decl of variable.declarations) {
- for (const id of extractIdentifiers(decl.id)) {
- onIdent(id)
- }
- }
- }
- }
- function walkSwitchStatement(
- stmt: SwitchStatement,
- isVar: boolean,
- onIdent: (id: Identifier) => void,
- ) {
- for (const cs of stmt.cases) {
- for (const stmt of cs.consequent) {
- if (
- stmt.type === 'VariableDeclaration' &&
- (stmt.kind === 'var' ? isVar : !isVar)
- ) {
- for (const decl of stmt.declarations) {
- for (const id of extractIdentifiers(decl.id)) {
- onIdent(id)
- }
- }
- }
- }
- walkBlockDeclarations(cs, onIdent)
- }
- }
- export function extractIdentifiers(
- param: Node,
- nodes: Identifier[] = [],
- ): Identifier[] {
- switch (param.type) {
- case 'Identifier':
- nodes.push(param)
- break
- case 'MemberExpression':
- let object: any = param
- while (object.type === 'MemberExpression') {
- object = object.object
- }
- nodes.push(object)
- break
- case 'ObjectPattern':
- for (const prop of param.properties) {
- if (prop.type === 'RestElement') {
- extractIdentifiers(prop.argument, nodes)
- } else {
- extractIdentifiers(prop.value, nodes)
- }
- }
- break
- case 'ArrayPattern':
- param.elements.forEach(element => {
- if (element) extractIdentifiers(element, nodes)
- })
- break
- case 'RestElement':
- extractIdentifiers(param.argument, nodes)
- break
- case 'AssignmentPattern':
- extractIdentifiers(param.left, nodes)
- break
- }
- return nodes
- }
- function markKnownIds(name: string, knownIds: Record<string, number>) {
- if (name in knownIds) {
- knownIds[name]++
- } else {
- knownIds[name] = 1
- }
- }
- function markScopeIdentifier(
- node: Node & { scopeIds?: Set<string> },
- child: Identifier,
- knownIds: Record<string, number>,
- ) {
- const { name } = child
- if (node.scopeIds && node.scopeIds.has(name)) {
- return
- }
- markKnownIds(name, knownIds)
- ;(node.scopeIds || (node.scopeIds = new Set())).add(name)
- }
- export const isFunctionType = (node: Node): node is Function => {
- return /Function(?:Expression|Declaration)$|Method$/.test(node.type)
- }
- export const isStaticProperty = (node: Node): node is ObjectProperty =>
- node &&
- (node.type === 'ObjectProperty' || node.type === 'ObjectMethod') &&
- !node.computed
- export const isStaticPropertyKey = (node: Node, parent: Node): boolean =>
- isStaticProperty(parent) && parent.key === node
- /**
- * Copied from https://github.com/babel/babel/blob/main/packages/babel-types/src/validators/isReferenced.ts
- * To avoid runtime dependency on @babel/types (which includes process references)
- * This file should not change very often in babel but we may need to keep it
- * up-to-date from time to time.
- *
- * https://github.com/babel/babel/blob/main/LICENSE
- *
- */
- function isReferenced(node: Node, parent: Node, grandparent?: Node): boolean {
- switch (parent.type) {
- // yes: PARENT[NODE]
- // yes: NODE.child
- // no: parent.NODE
- case 'MemberExpression':
- case 'OptionalMemberExpression':
- if (parent.property === node) {
- return !!parent.computed
- }
- return parent.object === node
- case 'JSXMemberExpression':
- return parent.object === node
- // no: let NODE = init;
- // yes: let id = NODE;
- case 'VariableDeclarator':
- return parent.init === node
- // yes: () => NODE
- // no: (NODE) => {}
- case 'ArrowFunctionExpression':
- return parent.body === node
- // no: class { #NODE; }
- // no: class { get #NODE() {} }
- // no: class { #NODE() {} }
- // no: class { fn() { return this.#NODE; } }
- case 'PrivateName':
- return false
- // no: class { NODE() {} }
- // yes: class { [NODE]() {} }
- // no: class { foo(NODE) {} }
- case 'ClassMethod':
- case 'ClassPrivateMethod':
- case 'ObjectMethod':
- if (parent.key === node) {
- return !!parent.computed
- }
- return false
- // yes: { [NODE]: "" }
- // no: { NODE: "" }
- // depends: { NODE }
- // depends: { key: NODE }
- case 'ObjectProperty':
- if (parent.key === node) {
- return !!parent.computed
- }
- // parent.value === node
- return !grandparent || grandparent.type !== 'ObjectPattern'
- // no: class { NODE = value; }
- // yes: class { [NODE] = value; }
- // yes: class { key = NODE; }
- case 'ClassProperty':
- if (parent.key === node) {
- return !!parent.computed
- }
- return true
- case 'ClassPrivateProperty':
- return parent.key !== node
- // no: class NODE {}
- // yes: class Foo extends NODE {}
- case 'ClassDeclaration':
- case 'ClassExpression':
- return parent.superClass === node
- // yes: left = NODE;
- // no: NODE = right;
- case 'AssignmentExpression':
- return parent.right === node
- // no: [NODE = foo] = [];
- // yes: [foo = NODE] = [];
- case 'AssignmentPattern':
- return parent.right === node
- // no: NODE: for (;;) {}
- case 'LabeledStatement':
- return false
- // no: try {} catch (NODE) {}
- case 'CatchClause':
- return false
- // no: function foo(...NODE) {}
- case 'RestElement':
- return false
- case 'BreakStatement':
- case 'ContinueStatement':
- return false
- // no: function NODE() {}
- // no: function foo(NODE) {}
- case 'FunctionDeclaration':
- case 'FunctionExpression':
- return false
- // no: export NODE from "foo";
- // no: export * as NODE from "foo";
- case 'ExportNamespaceSpecifier':
- case 'ExportDefaultSpecifier':
- return false
- // no: export { foo as NODE };
- // yes: export { NODE as foo };
- // no: export { NODE as foo } from "foo";
- case 'ExportSpecifier':
- // @ts-expect-error
- // eslint-disable-next-line no-restricted-syntax
- if (grandparent?.source) {
- return false
- }
- return parent.local === node
- // no: import NODE from "foo";
- // no: import * as NODE from "foo";
- // no: import { NODE as foo } from "foo";
- // no: import { foo as NODE } from "foo";
- // no: import NODE from "bar";
- case 'ImportDefaultSpecifier':
- case 'ImportNamespaceSpecifier':
- case 'ImportSpecifier':
- return false
- // no: import "foo" assert { NODE: "json" }
- case 'ImportAttribute':
- return false
- // no: <div NODE="foo" />
- case 'JSXAttribute':
- return false
- // no: [NODE] = [];
- // no: ({ NODE }) = [];
- case 'ObjectPattern':
- case 'ArrayPattern':
- return false
- // no: new.NODE
- // no: NODE.target
- case 'MetaProperty':
- return false
- // yes: type X = { someProperty: NODE }
- // no: type X = { NODE: OtherType }
- case 'ObjectTypeProperty':
- return parent.key !== node
- // yes: enum X { Foo = NODE }
- // no: enum X { NODE }
- case 'TSEnumMember':
- return parent.id !== node
- // yes: { [NODE]: value }
- // no: { NODE: value }
- case 'TSPropertySignature':
- if (parent.key === node) {
- return !!parent.computed
- }
- return true
- }
- return true
- }
- export const TS_NODE_TYPES: string[] = [
- 'TSAsExpression', // foo as number
- 'TSTypeAssertion', // (<number>foo)
- 'TSNonNullExpression', // foo!
- 'TSInstantiationExpression', // foo<string>
- 'TSSatisfiesExpression', // foo satisfies T
- ]
- export function unwrapTSNode(node: Node): Node {
- if (TS_NODE_TYPES.includes(node.type)) {
- return unwrapTSNode((node as any).expression)
- } else {
- return node
- }
- }
|