@tanstack/start-plugin-core 1.160.2 → 1.161.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/dev-server-plugin/plugin.js +1 -1
- package/dist/esm/dev-server-plugin/plugin.js.map +1 -1
- package/dist/esm/import-protection-plugin/defaults.d.ts +17 -0
- package/dist/esm/import-protection-plugin/defaults.js +36 -0
- package/dist/esm/import-protection-plugin/defaults.js.map +1 -0
- package/dist/esm/import-protection-plugin/matchers.d.ts +13 -0
- package/dist/esm/import-protection-plugin/matchers.js +31 -0
- package/dist/esm/import-protection-plugin/matchers.js.map +1 -0
- package/dist/esm/import-protection-plugin/plugin.d.ts +16 -0
- package/dist/esm/import-protection-plugin/plugin.js +699 -0
- package/dist/esm/import-protection-plugin/plugin.js.map +1 -0
- package/dist/esm/import-protection-plugin/postCompileUsage.d.ts +11 -0
- package/dist/esm/import-protection-plugin/postCompileUsage.js +177 -0
- package/dist/esm/import-protection-plugin/postCompileUsage.js.map +1 -0
- package/dist/esm/import-protection-plugin/rewriteDeniedImports.d.ts +27 -0
- package/dist/esm/import-protection-plugin/rewriteDeniedImports.js +51 -0
- package/dist/esm/import-protection-plugin/rewriteDeniedImports.js.map +1 -0
- package/dist/esm/import-protection-plugin/sourceLocation.d.ts +132 -0
- package/dist/esm/import-protection-plugin/sourceLocation.js +255 -0
- package/dist/esm/import-protection-plugin/sourceLocation.js.map +1 -0
- package/dist/esm/import-protection-plugin/trace.d.ts +67 -0
- package/dist/esm/import-protection-plugin/trace.js +204 -0
- package/dist/esm/import-protection-plugin/trace.js.map +1 -0
- package/dist/esm/import-protection-plugin/utils.d.ts +8 -0
- package/dist/esm/import-protection-plugin/utils.js +29 -0
- package/dist/esm/import-protection-plugin/utils.js.map +1 -0
- package/dist/esm/import-protection-plugin/virtualModules.d.ts +25 -0
- package/dist/esm/import-protection-plugin/virtualModules.js +235 -0
- package/dist/esm/import-protection-plugin/virtualModules.js.map +1 -0
- package/dist/esm/plugin.js +7 -0
- package/dist/esm/plugin.js.map +1 -1
- package/dist/esm/prerender.js +3 -3
- package/dist/esm/prerender.js.map +1 -1
- package/dist/esm/schema.d.ts +260 -0
- package/dist/esm/schema.js +35 -1
- package/dist/esm/schema.js.map +1 -1
- package/dist/esm/start-compiler-plugin/compiler.js +5 -1
- package/dist/esm/start-compiler-plugin/compiler.js.map +1 -1
- package/dist/esm/start-compiler-plugin/handleCreateServerFn.js +2 -2
- package/dist/esm/start-compiler-plugin/handleCreateServerFn.js.map +1 -1
- package/dist/esm/start-compiler-plugin/plugin.js.map +1 -1
- package/dist/esm/start-router-plugin/plugin.js +5 -5
- package/dist/esm/start-router-plugin/plugin.js.map +1 -1
- package/package.json +6 -3
- package/src/dev-server-plugin/plugin.ts +1 -1
- package/src/import-protection-plugin/defaults.ts +56 -0
- package/src/import-protection-plugin/matchers.ts +48 -0
- package/src/import-protection-plugin/plugin.ts +1173 -0
- package/src/import-protection-plugin/postCompileUsage.ts +266 -0
- package/src/import-protection-plugin/rewriteDeniedImports.ts +255 -0
- package/src/import-protection-plugin/sourceLocation.ts +524 -0
- package/src/import-protection-plugin/trace.ts +296 -0
- package/src/import-protection-plugin/utils.ts +32 -0
- package/src/import-protection-plugin/virtualModules.ts +300 -0
- package/src/plugin.ts +7 -0
- package/src/schema.ts +58 -0
- package/src/start-compiler-plugin/compiler.ts +12 -1
- package/src/start-compiler-plugin/plugin.ts +3 -3
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import * as t from '@babel/types'
|
|
2
|
+
import { parseAst } from '@tanstack/router-utils'
|
|
3
|
+
|
|
4
|
+
export type UsagePos = { line: number; column0: number }
|
|
5
|
+
|
|
6
|
+
function collectPatternBindings(
|
|
7
|
+
node: t.Node | null | undefined,
|
|
8
|
+
out: Set<string>,
|
|
9
|
+
): void {
|
|
10
|
+
if (!node) return
|
|
11
|
+
if (t.isIdentifier(node)) {
|
|
12
|
+
out.add(node.name)
|
|
13
|
+
return
|
|
14
|
+
}
|
|
15
|
+
if (t.isRestElement(node)) {
|
|
16
|
+
collectPatternBindings(node.argument, out)
|
|
17
|
+
return
|
|
18
|
+
}
|
|
19
|
+
if (t.isAssignmentPattern(node)) {
|
|
20
|
+
collectPatternBindings(node.left, out)
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
if (t.isObjectPattern(node)) {
|
|
24
|
+
for (const prop of node.properties) {
|
|
25
|
+
if (t.isRestElement(prop)) {
|
|
26
|
+
collectPatternBindings(prop.argument, out)
|
|
27
|
+
} else if (t.isObjectProperty(prop)) {
|
|
28
|
+
collectPatternBindings(prop.value as t.Node, out)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
if (t.isArrayPattern(node)) {
|
|
34
|
+
for (const el of node.elements) {
|
|
35
|
+
collectPatternBindings(el, out)
|
|
36
|
+
}
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function isBindingPosition(node: t.Node, parent: t.Node | null): boolean {
|
|
42
|
+
if (!parent) return false
|
|
43
|
+
if (t.isFunctionDeclaration(parent) && parent.id === node) return true
|
|
44
|
+
if (t.isFunctionExpression(parent) && parent.id === node) return true
|
|
45
|
+
if (t.isClassDeclaration(parent) && parent.id === node) return true
|
|
46
|
+
if (t.isClassExpression(parent) && parent.id === node) return true
|
|
47
|
+
if (t.isVariableDeclarator(parent) && parent.id === node) return true
|
|
48
|
+
if (t.isImportSpecifier(parent) && parent.local === node) return true
|
|
49
|
+
if (t.isImportDefaultSpecifier(parent) && parent.local === node) return true
|
|
50
|
+
if (t.isImportNamespaceSpecifier(parent) && parent.local === node) return true
|
|
51
|
+
if (
|
|
52
|
+
t.isObjectProperty(parent) &&
|
|
53
|
+
parent.key === node &&
|
|
54
|
+
!parent.computed &&
|
|
55
|
+
// In `{ foo }`, the identifier is also a value reference and must count as
|
|
56
|
+
// usage. Babel represents this as `shorthand: true`.
|
|
57
|
+
!parent.shorthand
|
|
58
|
+
)
|
|
59
|
+
return true
|
|
60
|
+
if (t.isObjectMethod(parent) && parent.key === node && !parent.computed)
|
|
61
|
+
return true
|
|
62
|
+
if (t.isExportSpecifier(parent) && parent.exported === node) return true
|
|
63
|
+
return false
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function isPreferredUsage(node: t.Node, parent: t.Node | null): boolean {
|
|
67
|
+
if (!parent) return false
|
|
68
|
+
if (t.isCallExpression(parent) && parent.callee === node) return true
|
|
69
|
+
if (t.isNewExpression(parent) && parent.callee === node) return true
|
|
70
|
+
if (t.isMemberExpression(parent) && parent.object === node) return true
|
|
71
|
+
return false
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function isScopeNode(node: t.Node): boolean {
|
|
75
|
+
return (
|
|
76
|
+
t.isProgram(node) ||
|
|
77
|
+
t.isFunctionDeclaration(node) ||
|
|
78
|
+
t.isFunctionExpression(node) ||
|
|
79
|
+
t.isArrowFunctionExpression(node) ||
|
|
80
|
+
t.isBlockStatement(node) ||
|
|
81
|
+
t.isCatchClause(node)
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** `var` hoists to the nearest function or program scope, not block scopes. */
|
|
86
|
+
function isFunctionScopeNode(node: t.Node): boolean {
|
|
87
|
+
return (
|
|
88
|
+
t.isProgram(node) ||
|
|
89
|
+
t.isFunctionDeclaration(node) ||
|
|
90
|
+
t.isFunctionExpression(node) ||
|
|
91
|
+
t.isArrowFunctionExpression(node)
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function collectScopeBindings(node: t.Node, out: Set<string>): void {
|
|
96
|
+
if (
|
|
97
|
+
t.isFunctionDeclaration(node) ||
|
|
98
|
+
t.isFunctionExpression(node) ||
|
|
99
|
+
t.isArrowFunctionExpression(node)
|
|
100
|
+
) {
|
|
101
|
+
for (const p of node.params) {
|
|
102
|
+
collectPatternBindings(p, out)
|
|
103
|
+
}
|
|
104
|
+
return
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (t.isCatchClause(node)) {
|
|
108
|
+
collectPatternBindings(node.param, out)
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Given transformed code, returns the first "meaningful" usage position for an
|
|
115
|
+
* import from `source` that survives compilation.
|
|
116
|
+
*
|
|
117
|
+
* The returned column is 0-based (Babel loc semantics).
|
|
118
|
+
*/
|
|
119
|
+
export function findPostCompileUsagePos(
|
|
120
|
+
code: string,
|
|
121
|
+
source: string,
|
|
122
|
+
): UsagePos | undefined {
|
|
123
|
+
const ast = parseAst({ code })
|
|
124
|
+
|
|
125
|
+
// 1) Determine local names bound from this specifier
|
|
126
|
+
const imported = new Set<string>()
|
|
127
|
+
for (const node of ast.program.body) {
|
|
128
|
+
if (t.isImportDeclaration(node) && node.source.value === source) {
|
|
129
|
+
if (node.importKind === 'type') continue
|
|
130
|
+
for (const s of node.specifiers) {
|
|
131
|
+
if (t.isImportSpecifier(s) && s.importKind === 'type') continue
|
|
132
|
+
imported.add(s.local.name)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (imported.size === 0) return undefined
|
|
137
|
+
|
|
138
|
+
let preferred: UsagePos | undefined
|
|
139
|
+
let anyUsage: UsagePos | undefined
|
|
140
|
+
|
|
141
|
+
// Scope stack (module scope at index 0).
|
|
142
|
+
// Each entry tracks bindings and whether it is a function/program scope
|
|
143
|
+
// (needed for `var` hoisting).
|
|
144
|
+
interface ScopeEntry {
|
|
145
|
+
bindings: Set<string>
|
|
146
|
+
isFnScope: boolean
|
|
147
|
+
}
|
|
148
|
+
const scopes: Array<ScopeEntry> = [{ bindings: new Set(), isFnScope: true }]
|
|
149
|
+
|
|
150
|
+
function isShadowed(name: string): boolean {
|
|
151
|
+
// Check inner scopes only
|
|
152
|
+
for (let i = scopes.length - 1; i >= 1; i--) {
|
|
153
|
+
if (scopes[i]!.bindings.has(name)) return true
|
|
154
|
+
}
|
|
155
|
+
return false
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function record(node: t.Node, kind: 'preferred' | 'any') {
|
|
159
|
+
const loc = node.loc?.start
|
|
160
|
+
if (!loc) return
|
|
161
|
+
const pos: UsagePos = { line: loc.line, column0: loc.column }
|
|
162
|
+
if (kind === 'preferred') {
|
|
163
|
+
preferred ||= pos
|
|
164
|
+
} else {
|
|
165
|
+
anyUsage ||= pos
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function pushScope(node: t.Node): void {
|
|
170
|
+
const bindings = new Set<string>()
|
|
171
|
+
collectScopeBindings(node, bindings)
|
|
172
|
+
scopes.push({ bindings, isFnScope: isFunctionScopeNode(node) })
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function popScope(): void {
|
|
176
|
+
scopes.pop()
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Find the nearest function/program scope entry in the stack. */
|
|
180
|
+
function nearestFnScope(): ScopeEntry {
|
|
181
|
+
for (let i = scopes.length - 1; i >= 0; i--) {
|
|
182
|
+
if (scopes[i]!.isFnScope) return scopes[i]!
|
|
183
|
+
}
|
|
184
|
+
// Should never happen (index 0 is always a function scope).
|
|
185
|
+
return scopes[0]!
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// The walker accepts AST nodes, arrays (from node children like
|
|
189
|
+
// `body`, `params`, etc.), or null/undefined for optional children.
|
|
190
|
+
type Walkable =
|
|
191
|
+
| t.Node
|
|
192
|
+
| ReadonlyArray<t.Node | null | undefined>
|
|
193
|
+
| null
|
|
194
|
+
| undefined
|
|
195
|
+
|
|
196
|
+
function walk(node: Walkable, parent: t.Node | null) {
|
|
197
|
+
if (!node) return
|
|
198
|
+
if (preferred && anyUsage) return
|
|
199
|
+
|
|
200
|
+
if (Array.isArray(node)) {
|
|
201
|
+
for (const n of node) walk(n, parent)
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// After the array check + early return, node is guaranteed to be t.Node.
|
|
206
|
+
// TypeScript doesn't narrow ReadonlyArray from the union, so we assert.
|
|
207
|
+
const astNode = node as t.Node
|
|
208
|
+
|
|
209
|
+
// Skip import declarations entirely
|
|
210
|
+
if (t.isImportDeclaration(astNode)) return
|
|
211
|
+
|
|
212
|
+
const enterScope = isScopeNode(astNode)
|
|
213
|
+
if (enterScope) {
|
|
214
|
+
pushScope(astNode)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Add lexical bindings for variable declarations and class/function decls.
|
|
218
|
+
// Note: function/class *declaration* identifiers bind in the parent scope,
|
|
219
|
+
// so we register them before walking children.
|
|
220
|
+
if (t.isFunctionDeclaration(astNode) && astNode.id) {
|
|
221
|
+
scopes[scopes.length - 2]?.bindings.add(astNode.id.name)
|
|
222
|
+
}
|
|
223
|
+
if (t.isClassDeclaration(astNode) && astNode.id) {
|
|
224
|
+
scopes[scopes.length - 2]?.bindings.add(astNode.id.name)
|
|
225
|
+
}
|
|
226
|
+
if (t.isVariableDeclarator(astNode)) {
|
|
227
|
+
// `var` hoists to the nearest function/program scope, not block scope.
|
|
228
|
+
const isVar = t.isVariableDeclaration(parent) && parent.kind === 'var'
|
|
229
|
+
const target = isVar
|
|
230
|
+
? nearestFnScope().bindings
|
|
231
|
+
: scopes[scopes.length - 1]!.bindings
|
|
232
|
+
collectPatternBindings(astNode.id, target)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (t.isIdentifier(astNode) && imported.has(astNode.name)) {
|
|
236
|
+
if (!isBindingPosition(astNode, parent) && !isShadowed(astNode.name)) {
|
|
237
|
+
if (isPreferredUsage(astNode, parent)) {
|
|
238
|
+
record(astNode, 'preferred')
|
|
239
|
+
} else {
|
|
240
|
+
record(astNode, 'any')
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Iterate child properties of this AST node. We use a Record cast since
|
|
246
|
+
// Babel node types don't expose an index signature, but we need to walk
|
|
247
|
+
// all child properties generically.
|
|
248
|
+
const record_ = astNode as unknown as Record<string, unknown>
|
|
249
|
+
for (const key of Object.keys(record_)) {
|
|
250
|
+
const value = record_[key]
|
|
251
|
+
if (!value) continue
|
|
252
|
+
if (key === 'loc' || key === 'start' || key === 'end') continue
|
|
253
|
+
if (key === 'parent') continue
|
|
254
|
+
if (typeof value === 'string' || typeof value === 'number') continue
|
|
255
|
+
walk(value as Walkable, astNode)
|
|
256
|
+
if (preferred && anyUsage) break
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (enterScope) {
|
|
260
|
+
popScope()
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
walk(ast.program, null)
|
|
265
|
+
return preferred ?? anyUsage
|
|
266
|
+
}
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import * as t from '@babel/types'
|
|
2
|
+
import { generateFromAst, parseAst } from '@tanstack/router-utils'
|
|
3
|
+
|
|
4
|
+
import { MOCK_MODULE_ID } from './virtualModules'
|
|
5
|
+
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Export name collection (for dev mock-edge modules)
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
|
|
10
|
+
export function isValidExportName(name: string): boolean {
|
|
11
|
+
if (name === 'default') return false
|
|
12
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Best-effort static analysis of an importer's source to determine which
|
|
17
|
+
* named exports are needed per specifier, to keep native ESM valid in dev.
|
|
18
|
+
*/
|
|
19
|
+
export function collectMockExportNamesBySource(
|
|
20
|
+
code: string,
|
|
21
|
+
): Map<string, Array<string>> {
|
|
22
|
+
const ast = parseAst({ code })
|
|
23
|
+
|
|
24
|
+
const namesBySource = new Map<string, Set<string>>()
|
|
25
|
+
const add = (source: string, name: string) => {
|
|
26
|
+
if (!isValidExportName(name)) return
|
|
27
|
+
let set = namesBySource.get(source)
|
|
28
|
+
if (!set) {
|
|
29
|
+
set = new Set<string>()
|
|
30
|
+
namesBySource.set(source, set)
|
|
31
|
+
}
|
|
32
|
+
set.add(name)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for (const node of ast.program.body) {
|
|
36
|
+
if (t.isImportDeclaration(node)) {
|
|
37
|
+
if (node.importKind === 'type') continue
|
|
38
|
+
const source = node.source.value
|
|
39
|
+
for (const s of node.specifiers) {
|
|
40
|
+
if (!t.isImportSpecifier(s)) continue
|
|
41
|
+
if (s.importKind === 'type') continue
|
|
42
|
+
const importedName = t.isIdentifier(s.imported)
|
|
43
|
+
? s.imported.name
|
|
44
|
+
: s.imported.value
|
|
45
|
+
// `import { default as x } from 'm'` only requires a default export.
|
|
46
|
+
if (importedName === 'default') continue
|
|
47
|
+
add(source, importedName)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (t.isExportNamedDeclaration(node) && node.source?.value) {
|
|
52
|
+
if (node.exportKind === 'type') continue
|
|
53
|
+
const source = node.source.value
|
|
54
|
+
for (const s of node.specifiers) {
|
|
55
|
+
if (!t.isExportSpecifier(s)) continue
|
|
56
|
+
if (s.exportKind === 'type') continue
|
|
57
|
+
add(source, s.local.name)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const out = new Map<string, Array<string>>()
|
|
63
|
+
for (const [source, set] of namesBySource) {
|
|
64
|
+
out.set(source, Array.from(set).sort())
|
|
65
|
+
}
|
|
66
|
+
return out
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// AST-based import rewriting
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Rewrite static imports/re-exports from denied sources using Babel AST transforms.
|
|
75
|
+
*
|
|
76
|
+
* Transforms:
|
|
77
|
+
* import { a as b, c } from 'denied'
|
|
78
|
+
* Into:
|
|
79
|
+
* import __tss_deny_0 from 'tanstack-start-import-protection:mock'
|
|
80
|
+
* const b = __tss_deny_0.a
|
|
81
|
+
* const c = __tss_deny_0.c
|
|
82
|
+
*
|
|
83
|
+
* Also handles:
|
|
84
|
+
* import def from 'denied' -> import def from mock
|
|
85
|
+
* import * as ns from 'denied' -> import ns from mock
|
|
86
|
+
* export { x } from 'denied' -> export const x = mock.x
|
|
87
|
+
* export * from 'denied' -> removed
|
|
88
|
+
* export { x as y } from 'denied' -> export const y = mock.x
|
|
89
|
+
*/
|
|
90
|
+
export function rewriteDeniedImports(
|
|
91
|
+
code: string,
|
|
92
|
+
id: string,
|
|
93
|
+
deniedSources: Set<string>,
|
|
94
|
+
getMockModuleId: (source: string) => string = () => MOCK_MODULE_ID,
|
|
95
|
+
): { code: string; map?: object | null } | undefined {
|
|
96
|
+
const ast = parseAst({ code })
|
|
97
|
+
let modified = false
|
|
98
|
+
let mockCounter = 0
|
|
99
|
+
|
|
100
|
+
// Walk program body in reverse so splice indices stay valid
|
|
101
|
+
for (let i = ast.program.body.length - 1; i >= 0; i--) {
|
|
102
|
+
const node = ast.program.body[i]!
|
|
103
|
+
|
|
104
|
+
// --- import declarations ---
|
|
105
|
+
if (t.isImportDeclaration(node)) {
|
|
106
|
+
// Skip type-only imports
|
|
107
|
+
if (node.importKind === 'type') continue
|
|
108
|
+
if (!deniedSources.has(node.source.value)) continue
|
|
109
|
+
|
|
110
|
+
const mockVar = `__tss_deny_${mockCounter++}`
|
|
111
|
+
const replacements: Array<t.Statement> = []
|
|
112
|
+
|
|
113
|
+
// import __tss_deny_N from '<mock>'
|
|
114
|
+
replacements.push(
|
|
115
|
+
t.importDeclaration(
|
|
116
|
+
[t.importDefaultSpecifier(t.identifier(mockVar))],
|
|
117
|
+
t.stringLiteral(getMockModuleId(node.source.value)),
|
|
118
|
+
),
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
for (const specifier of node.specifiers) {
|
|
122
|
+
if (t.isImportDefaultSpecifier(specifier)) {
|
|
123
|
+
// import def from 'denied' -> const def = __tss_deny_N
|
|
124
|
+
replacements.push(
|
|
125
|
+
t.variableDeclaration('const', [
|
|
126
|
+
t.variableDeclarator(
|
|
127
|
+
t.identifier(specifier.local.name),
|
|
128
|
+
t.identifier(mockVar),
|
|
129
|
+
),
|
|
130
|
+
]),
|
|
131
|
+
)
|
|
132
|
+
} else if (t.isImportNamespaceSpecifier(specifier)) {
|
|
133
|
+
// import * as ns from 'denied' -> const ns = __tss_deny_N
|
|
134
|
+
replacements.push(
|
|
135
|
+
t.variableDeclaration('const', [
|
|
136
|
+
t.variableDeclarator(
|
|
137
|
+
t.identifier(specifier.local.name),
|
|
138
|
+
t.identifier(mockVar),
|
|
139
|
+
),
|
|
140
|
+
]),
|
|
141
|
+
)
|
|
142
|
+
} else if (t.isImportSpecifier(specifier)) {
|
|
143
|
+
// Skip type-only specifiers
|
|
144
|
+
if (specifier.importKind === 'type') continue
|
|
145
|
+
// import { a as b } from 'denied' -> const b = __tss_deny_N.a
|
|
146
|
+
const importedName = t.isIdentifier(specifier.imported)
|
|
147
|
+
? specifier.imported.name
|
|
148
|
+
: specifier.imported.value
|
|
149
|
+
replacements.push(
|
|
150
|
+
t.variableDeclaration('const', [
|
|
151
|
+
t.variableDeclarator(
|
|
152
|
+
t.identifier(specifier.local.name),
|
|
153
|
+
t.memberExpression(
|
|
154
|
+
t.identifier(mockVar),
|
|
155
|
+
t.identifier(importedName),
|
|
156
|
+
),
|
|
157
|
+
),
|
|
158
|
+
]),
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
ast.program.body.splice(i, 1, ...replacements)
|
|
164
|
+
modified = true
|
|
165
|
+
continue
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// --- export { x } from 'denied' ---
|
|
169
|
+
if (t.isExportNamedDeclaration(node) && node.source) {
|
|
170
|
+
if (node.exportKind === 'type') continue
|
|
171
|
+
if (!deniedSources.has(node.source.value)) continue
|
|
172
|
+
|
|
173
|
+
const mockVar = `__tss_deny_${mockCounter++}`
|
|
174
|
+
const replacements: Array<t.Statement> = []
|
|
175
|
+
|
|
176
|
+
// import __tss_deny_N from '<mock>'
|
|
177
|
+
replacements.push(
|
|
178
|
+
t.importDeclaration(
|
|
179
|
+
[t.importDefaultSpecifier(t.identifier(mockVar))],
|
|
180
|
+
t.stringLiteral(getMockModuleId(node.source.value)),
|
|
181
|
+
),
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
// For each re-exported specifier, create an exported const
|
|
185
|
+
const exportSpecifiers: Array<{
|
|
186
|
+
localName: string
|
|
187
|
+
exportedName: string
|
|
188
|
+
}> = []
|
|
189
|
+
for (const specifier of node.specifiers) {
|
|
190
|
+
if (t.isExportSpecifier(specifier)) {
|
|
191
|
+
if (specifier.exportKind === 'type') continue
|
|
192
|
+
const localName = specifier.local.name
|
|
193
|
+
const exportedName = t.isIdentifier(specifier.exported)
|
|
194
|
+
? specifier.exported.name
|
|
195
|
+
: specifier.exported.value
|
|
196
|
+
|
|
197
|
+
const internalVar = `__tss_reexport_${localName}`
|
|
198
|
+
// const __tss_reexport_x = __tss_deny_N.x
|
|
199
|
+
replacements.push(
|
|
200
|
+
t.variableDeclaration('const', [
|
|
201
|
+
t.variableDeclarator(
|
|
202
|
+
t.identifier(internalVar),
|
|
203
|
+
t.memberExpression(
|
|
204
|
+
t.identifier(mockVar),
|
|
205
|
+
t.identifier(localName),
|
|
206
|
+
),
|
|
207
|
+
),
|
|
208
|
+
]),
|
|
209
|
+
)
|
|
210
|
+
exportSpecifiers.push({ localName: internalVar, exportedName })
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// export { __tss_reexport_x as x, ... }
|
|
215
|
+
if (exportSpecifiers.length > 0) {
|
|
216
|
+
replacements.push(
|
|
217
|
+
t.exportNamedDeclaration(
|
|
218
|
+
null,
|
|
219
|
+
exportSpecifiers.map((s) =>
|
|
220
|
+
t.exportSpecifier(
|
|
221
|
+
t.identifier(s.localName),
|
|
222
|
+
t.identifier(s.exportedName),
|
|
223
|
+
),
|
|
224
|
+
),
|
|
225
|
+
),
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
ast.program.body.splice(i, 1, ...replacements)
|
|
230
|
+
modified = true
|
|
231
|
+
continue
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// --- export * from 'denied' ---
|
|
235
|
+
if (t.isExportAllDeclaration(node)) {
|
|
236
|
+
if (node.exportKind === 'type') continue
|
|
237
|
+
if (!deniedSources.has(node.source.value)) continue
|
|
238
|
+
|
|
239
|
+
// Remove the star re-export entirely
|
|
240
|
+
ast.program.body.splice(i, 1)
|
|
241
|
+
modified = true
|
|
242
|
+
continue
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (!modified) return undefined
|
|
247
|
+
|
|
248
|
+
const result = generateFromAst(ast, {
|
|
249
|
+
sourceMaps: true,
|
|
250
|
+
sourceFileName: id,
|
|
251
|
+
filename: id,
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
return { code: result.code, map: result.map }
|
|
255
|
+
}
|