-
Notifications
You must be signed in to change notification settings - Fork 21
Expand file tree
/
Copy pathanalyze.ts
More file actions
127 lines (111 loc) · 3.5 KB
/
analyze.ts
File metadata and controls
127 lines (111 loc) · 3.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import { walk } from 'vite-plugin-utils/function'
// ①(🎯): Top-level scope statement types, it also means statements that can be converted
// 顶级作用于语句类型,这种可以被无缝换成 import
export enum TopScopeType {
// require('foo')[.bar]
ExpressionStatement = 'ExpressionStatement',
// const bar = require('foo')[.bar]
VariableDeclaration = 'VariableDeclaration',
}
export interface RequireStatement {
/** CallExpression */
node: AcornNode
ancestors: AcornNode[]
sideEffect: boolean
/**
* If require statement located top-level scope ant it is convertible, this will have a value(🎯-①)
* 如果 require 在顶级作用于,并且是可转换 import 的,那么 topScopeNode 将会被赋值
* @deprecated 🤔
*/
topScopeNode?: AcornNode & { type: TopScopeType }
dynamic?:
| 'dynamic'
// e.g. require(`@/foo/bar.js`)
| 'Literal'
}
export interface ExportsStatement {
node: AcornNode
// module(left).exports(right) = 'foo'
// exports(left).bar(right) = 'bar'
token: {
left: string
right: string
}
}
export interface Analyzed {
ast: AcornNode
code: string
id: string
require: RequireStatement[]
exports: ExportsStatement[]
}
/**
* `require` statement analyzer
* require 语法分析器
*/
export function analyzer(ast: AcornNode, code: string, id: string): Analyzed {
const analyzed: Analyzed = {
ast,
code,
id,
require: [],
exports: [],
}
walk.sync(ast, {
CallExpression(node, ancestors) {
if (node.callee.name !== 'require') return
const dynamic = checkDynamicId(node)
analyzed.require.push({
node,
ancestors,
sideEffect: ancestors.at(-2)?.type === 'ExpressionStatement',
topScopeNode: dynamic === 'dynamic'
? undefined
: findTopLevelScope(ancestors) as RequireStatement['topScopeNode'],
dynamic,
})
},
AssignmentExpression(node) {
if (node.left.type !== 'MemberExpression') return
// only `module.exports`, `exports.xxx`
if (!['module', 'exports'].includes(node.left.object.name)) return
analyzed.exports.push({
node,
token: {
left: node.left.object.name,
right: node.left.property.name,
},
})
},
})
return analyzed
}
function checkDynamicId(node: AcornNode): RequireStatement['dynamic'] {
if (
node.arguments[0]?.type === 'TemplateLiteral' &&
node.arguments[0]?.quasis.length === 1
) {
// e.g. require(`@/foo/bar.js`)
return 'Literal'
}
// Only `require` with one-argument is supported
return node.arguments[0]?.type !== 'Literal' ? 'dynamic' : undefined
}
// At present, only the "MemberExpression" of the one-depth is considered as the top-level scope
// 当前,只认为一层的 MemberExpression 顶级作用域
// e.g.
// ✅ require('foo').bar
// ❌ require('foo').bar.baz
//
// Will be return nearset scope ancestor node (🎯-①)
// 这将返回最近作用域的祖先节点
function findTopLevelScope(ancestors: AcornNode[]): AcornNode | undefined {
const ances = ancestors.map(an => an.type).join()
const arr = [...ancestors].reverse()
// TODO: better top-scope detect
if (/Program,ExpressionStatement,(MemberExpression,)?CallExpression$/.test(ances)) {
// Program,ExpressionStatement,CallExpression | require('foo')
// Program,ExpressionStatement,MemberExpression,CallExpression | require('foo').bar
return arr.find(e => e.type === TopScopeType.ExpressionStatement)
}
}