@tanstack/start-plugin-core 1.167.35 → 1.169.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.
Files changed (187) hide show
  1. package/dist/esm/import-protection/adapterUtils.d.ts +27 -0
  2. package/dist/esm/import-protection/adapterUtils.js +31 -0
  3. package/dist/esm/import-protection/adapterUtils.js.map +1 -0
  4. package/dist/esm/import-protection/analysis.d.ts +36 -0
  5. package/dist/esm/import-protection/analysis.js +407 -0
  6. package/dist/esm/import-protection/analysis.js.map +1 -0
  7. package/dist/esm/{import-protection-plugin → import-protection}/ast.js +1 -1
  8. package/dist/esm/import-protection/ast.js.map +1 -0
  9. package/dist/esm/import-protection/constants.d.ts +11 -0
  10. package/dist/esm/{import-protection-plugin → import-protection}/constants.js +7 -2
  11. package/dist/esm/import-protection/constants.js.map +1 -0
  12. package/dist/esm/{import-protection-plugin → import-protection}/defaults.js +1 -1
  13. package/dist/esm/import-protection/defaults.js.map +1 -0
  14. package/dist/esm/{import-protection-plugin → import-protection}/extensionlessAbsoluteIdResolver.js +2 -2
  15. package/dist/esm/import-protection/extensionlessAbsoluteIdResolver.js.map +1 -0
  16. package/dist/esm/{import-protection-plugin → import-protection}/matchers.js +1 -1
  17. package/dist/esm/import-protection/matchers.js.map +1 -0
  18. package/dist/esm/{import-protection-plugin/rewriteDeniedImports.d.ts → import-protection/rewrite.d.ts} +0 -4
  19. package/dist/esm/import-protection/rewrite.js +121 -0
  20. package/dist/esm/import-protection/rewrite.js.map +1 -0
  21. package/dist/esm/{import-protection-plugin → import-protection}/sourceLocation.d.ts +32 -3
  22. package/dist/esm/{import-protection-plugin → import-protection}/sourceLocation.js +65 -10
  23. package/dist/esm/import-protection/sourceLocation.js.map +1 -0
  24. package/dist/esm/{import-protection-plugin → import-protection}/trace.d.ts +0 -1
  25. package/dist/esm/{import-protection-plugin → import-protection}/trace.js +1 -1
  26. package/dist/esm/import-protection/trace.js.map +1 -0
  27. package/dist/esm/{import-protection-plugin → import-protection}/utils.d.ts +18 -1
  28. package/dist/esm/{import-protection-plugin → import-protection}/utils.js +13 -20
  29. package/dist/esm/import-protection/utils.js.map +1 -0
  30. package/dist/esm/import-protection/virtualModules.d.ts +25 -0
  31. package/dist/esm/{import-protection-plugin → import-protection}/virtualModules.js +5 -117
  32. package/dist/esm/import-protection/virtualModules.js.map +1 -0
  33. package/dist/esm/index.d.ts +1 -5
  34. package/dist/esm/index.js +2 -4
  35. package/dist/esm/post-build.d.ts +9 -0
  36. package/dist/esm/post-build.js +37 -0
  37. package/dist/esm/post-build.js.map +1 -0
  38. package/dist/esm/prerender.d.ts +11 -0
  39. package/dist/esm/prerender.js +159 -0
  40. package/dist/esm/prerender.js.map +1 -0
  41. package/dist/esm/rsbuild/dev-server.d.ts +21 -0
  42. package/dist/esm/rsbuild/dev-server.js +76 -0
  43. package/dist/esm/rsbuild/dev-server.js.map +1 -0
  44. package/dist/esm/rsbuild/import-protection.d.ts +10 -0
  45. package/dist/esm/rsbuild/import-protection.js +775 -0
  46. package/dist/esm/rsbuild/import-protection.js.map +1 -0
  47. package/dist/esm/rsbuild/index.d.ts +4 -0
  48. package/dist/esm/rsbuild/index.js +3 -0
  49. package/dist/esm/rsbuild/normalized-client-build.d.ts +18 -0
  50. package/dist/esm/rsbuild/normalized-client-build.js +207 -0
  51. package/dist/esm/rsbuild/normalized-client-build.js.map +1 -0
  52. package/dist/esm/rsbuild/planning.d.ts +52 -0
  53. package/dist/esm/rsbuild/planning.js +108 -0
  54. package/dist/esm/rsbuild/planning.js.map +1 -0
  55. package/dist/esm/rsbuild/plugin.d.ts +4 -0
  56. package/dist/esm/rsbuild/plugin.js +344 -0
  57. package/dist/esm/rsbuild/plugin.js.map +1 -0
  58. package/dist/esm/rsbuild/post-build.d.ts +6 -0
  59. package/dist/esm/rsbuild/post-build.js +57 -0
  60. package/dist/esm/rsbuild/post-build.js.map +1 -0
  61. package/dist/esm/rsbuild/schema.d.ts +3372 -0
  62. package/dist/esm/rsbuild/schema.js +12 -0
  63. package/dist/esm/rsbuild/schema.js.map +1 -0
  64. package/dist/esm/rsbuild/start-compiler-host.d.ts +20 -0
  65. package/dist/esm/rsbuild/start-compiler-host.js +150 -0
  66. package/dist/esm/rsbuild/start-compiler-host.js.map +1 -0
  67. package/dist/esm/rsbuild/start-router-plugin.d.ts +18 -0
  68. package/dist/esm/rsbuild/start-router-plugin.js +63 -0
  69. package/dist/esm/rsbuild/start-router-plugin.js.map +1 -0
  70. package/dist/esm/rsbuild/swc-rsc.d.ts +14 -0
  71. package/dist/esm/rsbuild/swc-rsc.js +93 -0
  72. package/dist/esm/rsbuild/swc-rsc.js.map +1 -0
  73. package/dist/esm/rsbuild/types.d.ts +17 -0
  74. package/dist/esm/rsbuild/types.js +0 -0
  75. package/dist/esm/rsbuild/virtual-modules.d.ts +53 -0
  76. package/dist/esm/rsbuild/virtual-modules.js +287 -0
  77. package/dist/esm/rsbuild/virtual-modules.js.map +1 -0
  78. package/dist/esm/schema.d.ts +43 -43
  79. package/dist/esm/start-compiler/compiler.d.ts +1 -1
  80. package/dist/esm/start-compiler/compiler.js +80 -9
  81. package/dist/esm/start-compiler/compiler.js.map +1 -1
  82. package/dist/esm/start-compiler/handleCreateServerFn.js +9 -0
  83. package/dist/esm/start-compiler/handleCreateServerFn.js.map +1 -1
  84. package/dist/esm/start-compiler/host.js +5 -1
  85. package/dist/esm/start-compiler/host.js.map +1 -1
  86. package/dist/esm/start-compiler/types.d.ts +1 -0
  87. package/dist/esm/utils.d.ts +1 -0
  88. package/dist/esm/utils.js +10 -1
  89. package/dist/esm/utils.js.map +1 -1
  90. package/dist/esm/{import-protection-plugin → vite/import-protection-plugin}/plugin.js +41 -92
  91. package/dist/esm/vite/import-protection-plugin/plugin.js.map +1 -0
  92. package/dist/esm/{import-protection-plugin → vite/import-protection-plugin}/types.d.ts +5 -5
  93. package/dist/esm/vite/import-protection-plugin/virtualModules.d.ts +8 -0
  94. package/dist/esm/vite/import-protection-plugin/virtualModules.js +49 -0
  95. package/dist/esm/vite/import-protection-plugin/virtualModules.js.map +1 -0
  96. package/dist/esm/vite/index.d.ts +5 -0
  97. package/dist/esm/vite/index.js +4 -0
  98. package/dist/esm/vite/plugin.js +1 -1
  99. package/dist/esm/vite/plugin.js.map +1 -1
  100. package/dist/esm/vite/post-server-build.js +14 -32
  101. package/dist/esm/vite/post-server-build.js.map +1 -1
  102. package/dist/esm/vite/prerender.d.ts +2 -2
  103. package/dist/esm/vite/prerender.js +17 -147
  104. package/dist/esm/vite/prerender.js.map +1 -1
  105. package/dist/esm/vite/schema.d.ts +23 -23
  106. package/dist/esm/vite/start-compiler-plugin/hot-update.d.ts +2 -0
  107. package/dist/esm/vite/start-compiler-plugin/hot-update.js +16 -0
  108. package/dist/esm/vite/start-compiler-plugin/hot-update.js.map +1 -0
  109. package/dist/esm/vite/start-compiler-plugin/module-specifier.js +9 -4
  110. package/dist/esm/vite/start-compiler-plugin/module-specifier.js.map +1 -1
  111. package/dist/esm/vite/start-compiler-plugin/plugin.js +86 -13
  112. package/dist/esm/vite/start-compiler-plugin/plugin.js.map +1 -1
  113. package/package.json +32 -4
  114. package/src/import-protection/INTERNALS.md +266 -0
  115. package/src/import-protection/adapterUtils.ts +94 -0
  116. package/src/import-protection/analysis.ts +853 -0
  117. package/src/{import-protection-plugin → import-protection}/constants.ts +7 -0
  118. package/src/import-protection/rewrite.ts +229 -0
  119. package/src/{import-protection-plugin → import-protection}/sourceLocation.ts +125 -9
  120. package/src/{import-protection-plugin → import-protection}/trace.ts +0 -1
  121. package/src/{import-protection-plugin → import-protection}/utils.ts +36 -21
  122. package/src/{import-protection-plugin → import-protection}/virtualModules.ts +30 -177
  123. package/src/index.ts +1 -8
  124. package/src/post-build.ts +64 -0
  125. package/src/prerender.ts +292 -0
  126. package/src/rsbuild/INTERNALS-import-protection.md +169 -0
  127. package/src/rsbuild/dev-server.ts +129 -0
  128. package/src/rsbuild/import-protection.ts +1599 -0
  129. package/src/rsbuild/index.ts +4 -0
  130. package/src/rsbuild/normalized-client-build.ts +346 -0
  131. package/src/rsbuild/planning.ts +234 -0
  132. package/src/rsbuild/plugin.ts +754 -0
  133. package/src/rsbuild/post-build.ts +96 -0
  134. package/src/rsbuild/schema.ts +31 -0
  135. package/src/rsbuild/start-compiler-host.ts +250 -0
  136. package/src/rsbuild/start-router-plugin.ts +86 -0
  137. package/src/rsbuild/swc-rsc.ts +166 -0
  138. package/src/rsbuild/types.ts +20 -0
  139. package/src/rsbuild/virtual-modules.ts +565 -0
  140. package/src/start-compiler/compiler.ts +153 -19
  141. package/src/start-compiler/handleCreateServerFn.ts +18 -0
  142. package/src/start-compiler/types.ts +1 -0
  143. package/src/utils.ts +14 -0
  144. package/src/vite/import-protection-plugin/INTERNALS.md +187 -0
  145. package/src/{import-protection-plugin → vite/import-protection-plugin}/plugin.ts +73 -158
  146. package/src/{import-protection-plugin → vite/import-protection-plugin}/types.ts +5 -5
  147. package/src/vite/import-protection-plugin/virtualModules.ts +122 -0
  148. package/src/vite/index.ts +8 -0
  149. package/src/vite/plugin.ts +1 -1
  150. package/src/vite/post-server-build.ts +14 -57
  151. package/src/vite/prerender.ts +19 -260
  152. package/src/vite/start-compiler-plugin/hot-update.ts +24 -0
  153. package/src/vite/start-compiler-plugin/module-specifier.ts +15 -5
  154. package/src/vite/start-compiler-plugin/plugin.ts +193 -18
  155. package/dist/esm/import-protection-plugin/ast.js.map +0 -1
  156. package/dist/esm/import-protection-plugin/constants.d.ts +0 -6
  157. package/dist/esm/import-protection-plugin/constants.js.map +0 -1
  158. package/dist/esm/import-protection-plugin/defaults.js.map +0 -1
  159. package/dist/esm/import-protection-plugin/extensionlessAbsoluteIdResolver.js.map +0 -1
  160. package/dist/esm/import-protection-plugin/matchers.js.map +0 -1
  161. package/dist/esm/import-protection-plugin/plugin.js.map +0 -1
  162. package/dist/esm/import-protection-plugin/postCompileUsage.d.ts +0 -13
  163. package/dist/esm/import-protection-plugin/postCompileUsage.js +0 -63
  164. package/dist/esm/import-protection-plugin/postCompileUsage.js.map +0 -1
  165. package/dist/esm/import-protection-plugin/rewriteDeniedImports.js +0 -205
  166. package/dist/esm/import-protection-plugin/rewriteDeniedImports.js.map +0 -1
  167. package/dist/esm/import-protection-plugin/sourceLocation.js.map +0 -1
  168. package/dist/esm/import-protection-plugin/trace.js.map +0 -1
  169. package/dist/esm/import-protection-plugin/utils.js.map +0 -1
  170. package/dist/esm/import-protection-plugin/virtualModules.d.ts +0 -78
  171. package/dist/esm/import-protection-plugin/virtualModules.js.map +0 -1
  172. package/dist/esm/start-compiler/load-module.d.ts +0 -14
  173. package/dist/esm/start-compiler/load-module.js +0 -18
  174. package/dist/esm/start-compiler/load-module.js.map +0 -1
  175. package/src/import-protection-plugin/INTERNALS.md +0 -700
  176. package/src/import-protection-plugin/postCompileUsage.ts +0 -100
  177. package/src/import-protection-plugin/rewriteDeniedImports.ts +0 -379
  178. package/src/start-compiler/load-module.ts +0 -31
  179. /package/dist/esm/{import-protection-plugin → import-protection}/ast.d.ts +0 -0
  180. /package/dist/esm/{import-protection-plugin → import-protection}/defaults.d.ts +0 -0
  181. /package/dist/esm/{import-protection-plugin → import-protection}/extensionlessAbsoluteIdResolver.d.ts +0 -0
  182. /package/dist/esm/{import-protection-plugin → import-protection}/matchers.d.ts +0 -0
  183. /package/dist/esm/{import-protection-plugin → vite/import-protection-plugin}/plugin.d.ts +0 -0
  184. /package/src/{import-protection-plugin → import-protection}/ast.ts +0 -0
  185. /package/src/{import-protection-plugin → import-protection}/defaults.ts +0 -0
  186. /package/src/{import-protection-plugin → import-protection}/extensionlessAbsoluteIdResolver.ts +0 -0
  187. /package/src/{import-protection-plugin → import-protection}/matchers.ts +0 -0
@@ -2,6 +2,13 @@ import { SERVER_FN_LOOKUP } from '../constants'
2
2
 
3
3
  export const SERVER_FN_LOOKUP_QUERY = `?${SERVER_FN_LOOKUP}`
4
4
 
5
+ export const MOCK_MODULE_ID = 'tanstack-start-import-protection:mock'
6
+ export const MOCK_BUILD_PREFIX = 'tanstack-start-import-protection:mock:build:'
7
+ export const MOCK_EDGE_PREFIX = 'tanstack-start-import-protection:mock-edge:'
8
+ export const MOCK_RUNTIME_PREFIX =
9
+ 'tanstack-start-import-protection:mock-runtime:'
10
+ export const MARKER_PREFIX = 'tanstack-start-import-protection:marker:'
11
+
5
12
  export const IMPORT_PROTECTION_DEBUG =
6
13
  process.env.TSR_IMPORT_PROTECTION_DEBUG === '1' ||
7
14
  process.env.TSR_IMPORT_PROTECTION_DEBUG === 'true'
@@ -0,0 +1,229 @@
1
+ import * as t from '@babel/types'
2
+ import { generateFromAst } from '@tanstack/router-utils'
3
+
4
+ import { parseImportProtectionAst } from './ast'
5
+ import { MOCK_MODULE_ID } from './constants'
6
+ import type { ParsedAst } from './ast'
7
+ import type { SourceMapLike } from './sourceLocation'
8
+
9
+ function getModuleExportName(node: t.Identifier | t.StringLiteral): string {
10
+ return t.isIdentifier(node) ? node.name : node.value
11
+ }
12
+
13
+ function toMemberExpressionProperty(name: string): {
14
+ property: t.Identifier | t.StringLiteral
15
+ computed: boolean
16
+ } {
17
+ return t.isValidIdentifier(name)
18
+ ? { property: t.identifier(name), computed: false }
19
+ : { property: t.stringLiteral(name), computed: true }
20
+ }
21
+
22
+ function toModuleExportNameNode(name: string): t.Identifier | t.StringLiteral {
23
+ return t.isValidIdentifier(name) ? t.identifier(name) : t.stringLiteral(name)
24
+ }
25
+
26
+ function createInternalReexportVarName(
27
+ localName: string,
28
+ mockIndex: number,
29
+ usedNames: Set<string>,
30
+ ): string {
31
+ const baseName = t.isValidIdentifier(localName)
32
+ ? `__tss_reexport_${localName}`
33
+ : `__tss_reexport_${mockIndex}`
34
+
35
+ let candidate = baseName
36
+ let suffix = 0
37
+ while (usedNames.has(candidate)) {
38
+ suffix++
39
+ candidate = `${baseName}_${suffix}`
40
+ }
41
+
42
+ usedNames.add(candidate)
43
+ return candidate
44
+ }
45
+
46
+ /**
47
+ * Rewrite static imports/re-exports from denied sources using Babel AST transforms.
48
+ *
49
+ * Transforms:
50
+ * import { a as b, c } from 'denied'
51
+ * Into:
52
+ * import __tss_deny_0 from 'tanstack-start-import-protection:mock'
53
+ * const b = __tss_deny_0.a
54
+ * const c = __tss_deny_0.c
55
+ *
56
+ * Also handles:
57
+ * import def from 'denied' -> import def from mock
58
+ * import * as ns from 'denied' -> import ns from mock
59
+ * export { x } from 'denied' -> export const x = mock.x
60
+ * export * from 'denied' -> removed
61
+ * export { x as y } from 'denied' -> export const y = mock.x
62
+ */
63
+ export function rewriteDeniedImports(
64
+ code: string,
65
+ id: string,
66
+ deniedSources: Set<string>,
67
+ getMockModuleId: (source: string) => string = () => MOCK_MODULE_ID,
68
+ ): { code: string; map?: SourceMapLike } | undefined {
69
+ return rewriteDeniedImportsFromAst(
70
+ parseImportProtectionAst(code),
71
+ id,
72
+ deniedSources,
73
+ getMockModuleId,
74
+ )
75
+ }
76
+
77
+ function rewriteDeniedImportsFromAst(
78
+ ast: ParsedAst,
79
+ id: string,
80
+ deniedSources: Set<string>,
81
+ getMockModuleId: (source: string) => string = () => MOCK_MODULE_ID,
82
+ ): { code: string; map?: SourceMapLike } | undefined {
83
+ let modified = false
84
+ let mockCounter = 0
85
+
86
+ // Walk program body in reverse so splice indices stay valid
87
+ for (let i = ast.program.body.length - 1; i >= 0; i--) {
88
+ const node = ast.program.body[i]!
89
+
90
+ if (t.isImportDeclaration(node)) {
91
+ if (node.importKind === 'type') continue
92
+ if (!deniedSources.has(node.source.value)) continue
93
+
94
+ const mockVar = `__tss_deny_${mockCounter++}`
95
+ const replacements: Array<t.Statement> = []
96
+
97
+ replacements.push(
98
+ t.importDeclaration(
99
+ [t.importDefaultSpecifier(t.identifier(mockVar))],
100
+ t.stringLiteral(getMockModuleId(node.source.value)),
101
+ ),
102
+ )
103
+
104
+ for (const specifier of node.specifiers) {
105
+ if (
106
+ t.isImportDefaultSpecifier(specifier) ||
107
+ t.isImportNamespaceSpecifier(specifier)
108
+ ) {
109
+ replacements.push(
110
+ t.variableDeclaration('const', [
111
+ t.variableDeclarator(
112
+ t.identifier(specifier.local.name),
113
+ t.identifier(mockVar),
114
+ ),
115
+ ]),
116
+ )
117
+ } else if (t.isImportSpecifier(specifier)) {
118
+ if (specifier.importKind === 'type') continue
119
+ const importedName = getModuleExportName(specifier.imported)
120
+ const memberProperty = toMemberExpressionProperty(importedName)
121
+ replacements.push(
122
+ t.variableDeclaration('const', [
123
+ t.variableDeclarator(
124
+ t.identifier(specifier.local.name),
125
+ t.memberExpression(
126
+ t.identifier(mockVar),
127
+ memberProperty.property,
128
+ memberProperty.computed,
129
+ ),
130
+ ),
131
+ ]),
132
+ )
133
+ }
134
+ }
135
+
136
+ ast.program.body.splice(i, 1, ...replacements)
137
+ modified = true
138
+ continue
139
+ }
140
+
141
+ if (t.isExportNamedDeclaration(node) && node.source) {
142
+ if (node.exportKind === 'type') continue
143
+ if (!deniedSources.has(node.source.value)) continue
144
+
145
+ const mockIndex = mockCounter++
146
+ const mockVar = `__tss_deny_${mockIndex}`
147
+ const replacements: Array<t.Statement> = []
148
+
149
+ replacements.push(
150
+ t.importDeclaration(
151
+ [t.importDefaultSpecifier(t.identifier(mockVar))],
152
+ t.stringLiteral(getMockModuleId(node.source.value)),
153
+ ),
154
+ )
155
+ const usedInternalVars = new Set<string>()
156
+ const exportSpecifiers: Array<{
157
+ localName: string
158
+ exportedName: string
159
+ }> = []
160
+ for (const specifier of node.specifiers) {
161
+ if (t.isExportSpecifier(specifier)) {
162
+ if (specifier.exportKind === 'type') continue
163
+ const localName = getModuleExportName(specifier.local)
164
+ const exportedName = getModuleExportName(specifier.exported)
165
+ const memberProperty = toMemberExpressionProperty(localName)
166
+
167
+ const internalVar = createInternalReexportVarName(
168
+ localName,
169
+ mockIndex,
170
+ usedInternalVars,
171
+ )
172
+ replacements.push(
173
+ t.variableDeclaration('const', [
174
+ t.variableDeclarator(
175
+ t.identifier(internalVar),
176
+ t.memberExpression(
177
+ t.identifier(mockVar),
178
+ memberProperty.property,
179
+ memberProperty.computed,
180
+ ),
181
+ ),
182
+ ]),
183
+ )
184
+ exportSpecifiers.push({ localName: internalVar, exportedName })
185
+ }
186
+ }
187
+
188
+ if (exportSpecifiers.length > 0) {
189
+ replacements.push(
190
+ t.exportNamedDeclaration(
191
+ null,
192
+ exportSpecifiers.map((s) =>
193
+ t.exportSpecifier(
194
+ t.identifier(s.localName),
195
+ toModuleExportNameNode(s.exportedName),
196
+ ),
197
+ ),
198
+ ),
199
+ )
200
+ }
201
+
202
+ ast.program.body.splice(i, 1, ...replacements)
203
+ modified = true
204
+ continue
205
+ }
206
+
207
+ if (t.isExportAllDeclaration(node)) {
208
+ if (node.exportKind === 'type') continue
209
+ if (!deniedSources.has(node.source.value)) continue
210
+
211
+ ast.program.body.splice(i, 1)
212
+ modified = true
213
+ continue
214
+ }
215
+ }
216
+
217
+ if (!modified) return undefined
218
+
219
+ const result = generateFromAst(ast, {
220
+ sourceMaps: true,
221
+ sourceFileName: id,
222
+ filename: id,
223
+ })
224
+
225
+ return {
226
+ code: result.code,
227
+ ...(result.map ? { map: result.map as SourceMapLike } : {}),
228
+ }
229
+ }
@@ -1,8 +1,14 @@
1
1
  import { SourceMapConsumer } from 'source-map'
2
2
  import * as path from 'pathe'
3
3
 
4
- import { findPostCompileUsagePos } from './postCompileUsage'
4
+ import {
5
+ findOriginalUnsafeUsagePosFromResult,
6
+ findPostCompileUsagePosFromResult,
7
+ getImportSpecifierLocationFromResult,
8
+ } from './analysis'
5
9
  import { getOrCreate, normalizeFilePath } from './utils'
10
+ import type { ImportAnalysis } from './analysis'
11
+ import type { ParsedAst } from './ast'
6
12
  import type { Loc } from './trace'
7
13
  import type { RawSourceMap } from 'source-map'
8
14
 
@@ -27,8 +33,11 @@ export interface TransformResult {
27
33
  code: string
28
34
  map: SourceMapLike | undefined
29
35
  originalCode: string | undefined
36
+ originalResult?: TransformResult
30
37
  /** Precomputed line index for `code` (index → line/col). */
31
38
  lineIndex?: LineIndex
39
+ parsedAst?: ParsedAst
40
+ analysis?: ImportAnalysis
32
41
  }
33
42
 
34
43
  /**
@@ -41,6 +50,10 @@ export interface TransformResultProvider {
41
50
  getTransformResult: (id: string) => TransformResult | undefined
42
51
  }
43
52
 
53
+ export interface ImportSpecifierLocationIndex {
54
+ find: FindImportSpecifierLocationIndex
55
+ }
56
+
44
57
  // Index → line/column conversion
45
58
 
46
59
  export type LineIndex = {
@@ -81,6 +94,42 @@ function indexToLineColWithIndex(
81
94
  return { line, column0: Math.max(0, idx - lineStart) }
82
95
  }
83
96
 
97
+ export function indexToLineColumn(
98
+ lineIndex: LineIndex,
99
+ idx: number,
100
+ ): { line: number; column: number } {
101
+ const { line, column0 } = indexToLineColWithIndex(lineIndex, idx)
102
+ return {
103
+ line,
104
+ column: column0 + 1,
105
+ }
106
+ }
107
+
108
+ export function normalizeSourceMap(map: SourceMapLike | null | undefined):
109
+ | {
110
+ version: number
111
+ file: string
112
+ sourceRoot?: string
113
+ sources: Array<string>
114
+ names: Array<string>
115
+ sourcesContent?: Array<string>
116
+ mappings: string
117
+ }
118
+ | undefined {
119
+ if (!map) {
120
+ return undefined
121
+ }
122
+
123
+ return {
124
+ ...map,
125
+ version: Number(map.version),
126
+ file: map.file ?? '',
127
+ names: Array.isArray(map.names) ? map.names : [],
128
+ sourcesContent:
129
+ map.sourcesContent?.map((value) => value ?? '') ?? undefined,
130
+ }
131
+ }
132
+
84
133
  /**
85
134
  * Pick the most-likely original source text for `importerFile` from
86
135
  * a sourcemap that may contain multiple sources.
@@ -271,7 +320,40 @@ export class ImportLocCache {
271
320
  }
272
321
  }
273
322
 
274
- export type FindImportSpecifierIndex = (code: string, source: string) => number
323
+ export type FindImportSpecifierLocationIndex = (
324
+ result: TransformResult,
325
+ source: string,
326
+ ) => number
327
+
328
+ export function getOrCreateOriginalTransformResult(
329
+ result: TransformResult,
330
+ ): TransformResult | undefined {
331
+ if (!result.originalCode) {
332
+ return undefined
333
+ }
334
+
335
+ if (!result.originalResult) {
336
+ result.originalResult = {
337
+ code: result.originalCode,
338
+ map: undefined,
339
+ originalCode: result.originalCode,
340
+ }
341
+ }
342
+
343
+ return result.originalResult
344
+ }
345
+
346
+ export function createImportSpecifierLocationIndex(): ImportSpecifierLocationIndex {
347
+ return {
348
+ find(result: TransformResult, source: string): number {
349
+ if (!result.code.includes(source)) {
350
+ return -1
351
+ }
352
+
353
+ return getImportSpecifierLocationFromResult(result, source)
354
+ },
355
+ }
356
+ }
275
357
 
276
358
  /**
277
359
  * Find the location of an import statement in a transformed module
@@ -283,7 +365,7 @@ export async function findImportStatementLocationFromTransformed(
283
365
  importerId: string,
284
366
  source: string,
285
367
  importLocCache: ImportLocCache,
286
- findImportSpecifierIndex: FindImportSpecifierIndex,
368
+ findImportSpecifierLocationIndex: FindImportSpecifierLocationIndex,
287
369
  ): Promise<Loc | undefined> {
288
370
  const importerFile = normalizeFilePath(importerId)
289
371
  const cacheKey = `${importerFile}::${source}`
@@ -298,11 +380,12 @@ export async function findImportStatementLocationFromTransformed(
298
380
  return undefined
299
381
  }
300
382
 
301
- const { code, map } = res
383
+ const { map } = res
302
384
 
303
- const lineIndex = res.lineIndex ?? buildLineIndex(code)
385
+ const lineIndex =
386
+ res.lineIndex ?? (res.lineIndex = buildLineIndex(res.code))
304
387
 
305
- const idx = findImportSpecifierIndex(code, source)
388
+ const idx = findImportSpecifierLocationIndex(res, source)
306
389
  if (idx === -1) {
307
390
  importLocCache.set(cacheKey, null)
308
391
  return undefined
@@ -338,7 +421,7 @@ export async function findPostCompileUsageLocation(
338
421
  res.lineIndex = buildLineIndex(code)
339
422
  }
340
423
 
341
- const pos = findPostCompileUsagePos(code, source)
424
+ const pos = findPostCompileUsagePosFromResult(res, source)
342
425
  if (!pos) return undefined
343
426
 
344
427
  return await mapGeneratedToOriginal(map, pos, importerFile)
@@ -347,6 +430,39 @@ export async function findPostCompileUsageLocation(
347
430
  }
348
431
  }
349
432
 
433
+ /**
434
+ * Best-effort original-source usage lookup for cases where a later transform
435
+ * removes or rewrites the import from emitted code but preserves the original
436
+ * source in `sourcesContent`.
437
+ */
438
+ export function findOriginalUsageLocation(
439
+ provider: TransformResultProvider,
440
+ importerId: string,
441
+ source: string,
442
+ envType?: 'client' | 'server',
443
+ ): Loc | undefined {
444
+ try {
445
+ const importerFile = normalizeFilePath(importerId)
446
+ const res = provider.getTransformResult(importerId)
447
+ if (!res) return undefined
448
+ const originalResult = getOrCreateOriginalTransformResult(res)
449
+ if (!originalResult) return undefined
450
+
451
+ const pos = envType
452
+ ? findOriginalUnsafeUsagePosFromResult(originalResult, source, envType)
453
+ : findPostCompileUsagePosFromResult(originalResult, source)
454
+ if (!pos) return undefined
455
+
456
+ return {
457
+ file: importerFile,
458
+ line: pos.line,
459
+ column: pos.column0 + 1,
460
+ }
461
+ } catch {
462
+ return undefined
463
+ }
464
+ }
465
+
350
466
  /**
351
467
  * Annotate each trace hop with the location of the import that created the
352
468
  * edge (file:line:col). Skips steps that already have a location.
@@ -360,7 +476,7 @@ export async function addTraceImportLocations(
360
476
  column?: number
361
477
  }>,
362
478
  importLocCache: ImportLocCache,
363
- findImportSpecifierIndex: FindImportSpecifierIndex,
479
+ findImportSpecifierLocationIndex: FindImportSpecifierLocationIndex,
364
480
  ): Promise<void> {
365
481
  for (const step of trace) {
366
482
  if (!step.specifier) continue
@@ -370,7 +486,7 @@ export async function addTraceImportLocations(
370
486
  step.file,
371
487
  step.specifier,
372
488
  importLocCache,
373
- findImportSpecifierIndex,
489
+ findImportSpecifierLocationIndex,
374
490
  )
375
491
  if (!loc) continue
376
492
  step.line = loc.line
@@ -166,7 +166,6 @@ export interface ViolationInfo {
166
166
  importerLoc?: Loc
167
167
  resolved?: string
168
168
  trace: Array<TraceStep>
169
- message: string
170
169
  /** Vitest-style code snippet showing the offending usage in the leaf module. */
171
170
  snippet?: {
172
171
  lines: Array<string>
@@ -4,7 +4,7 @@ import {
4
4
  relative,
5
5
  resolve as resolvePath,
6
6
  } from 'node:path'
7
- import { normalizePath } from 'vite'
7
+ import { normalizePath } from '../utils'
8
8
 
9
9
  import {
10
10
  IMPORT_PROTECTION_DEBUG,
@@ -26,6 +26,41 @@ export function dedupePatterns(patterns: Array<Pattern>): Array<Pattern> {
26
26
  return out
27
27
  }
28
28
 
29
+ export interface FileMatchers {
30
+ files: Array<{ pattern: Pattern; test: (value: string) => boolean }>
31
+ excludeFiles: Array<{ pattern: Pattern; test: (value: string) => boolean }>
32
+ }
33
+
34
+ export function isFileExcluded(
35
+ relativePath: string,
36
+ matchers: Pick<FileMatchers, 'excludeFiles'>,
37
+ ): boolean {
38
+ return (
39
+ matchers.excludeFiles.length > 0 &&
40
+ matchers.excludeFiles.some((matcher) => matcher.test(relativePath))
41
+ )
42
+ }
43
+
44
+ export function checkFileDenial(
45
+ relativePath: string,
46
+ matchers: FileMatchers,
47
+ ): FileMatchers['files'][number] | undefined {
48
+ if (isFileExcluded(relativePath, matchers)) {
49
+ return undefined
50
+ }
51
+
52
+ return matchers.files.find((matcher) => matcher.test(relativePath))
53
+ }
54
+
55
+ export function dedupeViolationKey(info: {
56
+ type: string
57
+ importer: string
58
+ specifier: string
59
+ resolved?: string
60
+ }): string {
61
+ return `${info.type}:${info.importer}:${info.specifier}:${info.resolved ?? ''}`
62
+ }
63
+
29
64
  /** Strip both `?query` and `#hash` from a module ID. */
30
65
  export function stripQueryAndHash(id: string): string {
31
66
  const q = id.indexOf('?')
@@ -57,15 +92,6 @@ export function clearNormalizeFilePathCache(): void {
57
92
  normalizeFilePathCache.clear()
58
93
  }
59
94
 
60
- /**
61
- * Lightweight regex to extract all import/re-export source strings from
62
- * post-transform code. Matches:
63
- * - `from "..."` / `from '...'` (static import/export)
64
- * - `import("...")` / `import('...')` (dynamic import)
65
- */
66
- const importSourceRe =
67
- /\bfrom\s+(?:"([^"]+)"|'([^']+)')|import\s*\(\s*(?:"([^"]+)"|'([^']+)')\s*\)/g
68
-
69
95
  export function escapeRegExp(s: string): string {
70
96
  return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
71
97
  }
@@ -93,17 +119,6 @@ export function relativizePath(p: string, root: string): string {
93
119
  return ch === 47 ? p.slice(root.length + 1) : p.slice(root.length)
94
120
  }
95
121
 
96
- export function extractImportSources(code: string): Array<string> {
97
- const sources: Array<string> = []
98
- let m: RegExpExecArray | null
99
- importSourceRe.lastIndex = 0
100
- while ((m = importSourceRe.exec(code)) !== null) {
101
- const src = m[1] ?? m[2] ?? m[3] ?? m[4]
102
- if (src) sources.push(src)
103
- }
104
- return sources
105
- }
106
-
107
122
  /** Log import-protection debug output when debug mode is enabled. */
108
123
  export function debugLog(...args: Array<unknown>): void {
109
124
  if (!IMPORT_PROTECTION_DEBUG) return