@remix-run/assets 0.0.0 → 0.1.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 (67) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +319 -2
  3. package/dist/assets.d.ts +3 -0
  4. package/dist/assets.d.ts.map +1 -0
  5. package/dist/assets.js +1 -0
  6. package/dist/lib/access.d.ts +10 -0
  7. package/dist/lib/access.d.ts.map +1 -0
  8. package/dist/lib/access.js +14 -0
  9. package/dist/lib/asset-server.d.ts +137 -0
  10. package/dist/lib/asset-server.d.ts.map +1 -0
  11. package/dist/lib/asset-server.js +242 -0
  12. package/dist/lib/compilation-error.d.ts +33 -0
  13. package/dist/lib/compilation-error.d.ts.map +1 -0
  14. package/dist/lib/compilation-error.js +32 -0
  15. package/dist/lib/file-matcher.d.ts +6 -0
  16. package/dist/lib/file-matcher.d.ts.map +1 -0
  17. package/dist/lib/file-matcher.js +43 -0
  18. package/dist/lib/fingerprint.d.ts +12 -0
  19. package/dist/lib/fingerprint.d.ts.map +1 -0
  20. package/dist/lib/fingerprint.js +49 -0
  21. package/dist/lib/paths.d.ts +8 -0
  22. package/dist/lib/paths.d.ts.map +1 -0
  23. package/dist/lib/paths.js +50 -0
  24. package/dist/lib/routes.d.ts +13 -0
  25. package/dist/lib/routes.d.ts.map +1 -0
  26. package/dist/lib/routes.js +94 -0
  27. package/dist/lib/scripts/cjs-check.d.ts +3 -0
  28. package/dist/lib/scripts/cjs-check.d.ts.map +1 -0
  29. package/dist/lib/scripts/cjs-check.js +398 -0
  30. package/dist/lib/scripts/compiler.d.ts +62 -0
  31. package/dist/lib/scripts/compiler.d.ts.map +1 -0
  32. package/dist/lib/scripts/compiler.js +435 -0
  33. package/dist/lib/scripts/emit.d.ts +25 -0
  34. package/dist/lib/scripts/emit.d.ts.map +1 -0
  35. package/dist/lib/scripts/emit.js +63 -0
  36. package/dist/lib/scripts/resolve.d.ts +60 -0
  37. package/dist/lib/scripts/resolve.d.ts.map +1 -0
  38. package/dist/lib/scripts/resolve.js +230 -0
  39. package/dist/lib/scripts/store.d.ts +40 -0
  40. package/dist/lib/scripts/store.d.ts.map +1 -0
  41. package/dist/lib/scripts/store.js +228 -0
  42. package/dist/lib/scripts/transform.d.ts +62 -0
  43. package/dist/lib/scripts/transform.d.ts.map +1 -0
  44. package/dist/lib/scripts/transform.js +362 -0
  45. package/dist/lib/source-maps.d.ts +4 -0
  46. package/dist/lib/source-maps.d.ts.map +1 -0
  47. package/dist/lib/source-maps.js +56 -0
  48. package/dist/lib/watch.d.ts +22 -0
  49. package/dist/lib/watch.d.ts.map +1 -0
  50. package/dist/lib/watch.js +96 -0
  51. package/package.json +50 -12
  52. package/src/assets.ts +2 -0
  53. package/src/lib/access.ts +24 -0
  54. package/src/lib/asset-server.ts +415 -0
  55. package/src/lib/compilation-error.ts +61 -0
  56. package/src/lib/file-matcher.ts +62 -0
  57. package/src/lib/fingerprint.ts +65 -0
  58. package/src/lib/paths.ts +66 -0
  59. package/src/lib/routes.ts +164 -0
  60. package/src/lib/scripts/cjs-check.ts +476 -0
  61. package/src/lib/scripts/compiler.ts +622 -0
  62. package/src/lib/scripts/emit.ts +122 -0
  63. package/src/lib/scripts/resolve.ts +422 -0
  64. package/src/lib/scripts/store.ts +327 -0
  65. package/src/lib/scripts/transform.ts +594 -0
  66. package/src/lib/source-maps.ts +68 -0
  67. package/src/lib/watch.ts +136 -0
@@ -0,0 +1,164 @@
1
+ import * as path from 'node:path'
2
+ import { RoutePattern } from '@remix-run/route-pattern'
3
+
4
+ import {
5
+ isAbsoluteFilePath,
6
+ normalizeFilePath,
7
+ normalizePathname,
8
+ resolveFilePath,
9
+ } from './paths.ts'
10
+
11
+ export interface AssetRouteDefinition {
12
+ urlPattern: string
13
+ filePattern: string
14
+ }
15
+
16
+ interface CompiledRoute {
17
+ rootDir: string
18
+ urlPattern: RoutePattern
19
+ filePattern: RoutePattern
20
+ }
21
+
22
+ export interface CompiledRoutes {
23
+ resolveUrlPathname(pathname: string): string | null
24
+ toUrlPathname(filePath: string): string | null
25
+ }
26
+
27
+ function normalizeFilePattern(pattern: string): string {
28
+ if (isAbsoluteFilePath(pattern)) {
29
+ throw new Error(
30
+ `File route patterns must be relative to the asset server root.\nPattern: ${pattern}`,
31
+ )
32
+ }
33
+
34
+ return normalizePathname(pattern)
35
+ }
36
+
37
+ export function compileRoutes(options: {
38
+ fileMap: Readonly<Record<string, string>>
39
+ rootDir: string
40
+ }): CompiledRoutes {
41
+ if (Object.keys(options.fileMap).length === 0) {
42
+ throw new Error('createAssetServer() requires at least one configured fileMap entry.')
43
+ }
44
+
45
+ let compiledRoutes = Object.entries(options.fileMap).map(([urlPattern, filePattern]) =>
46
+ compileRoute(
47
+ {
48
+ urlPattern,
49
+ filePattern,
50
+ },
51
+ { rootDir: options.rootDir },
52
+ ),
53
+ )
54
+
55
+ return {
56
+ resolveUrlPathname(pathname) {
57
+ let normalizedPathname = normalizePathname(pathname)
58
+
59
+ for (let route of compiledRoutes) {
60
+ let match = route.urlPattern.match(`http://remix.run${normalizedPathname}`)
61
+ if (!match) continue
62
+ let relativeFilePath = route.filePattern.href(match.params).replace(/^\/+/, '')
63
+ return resolveFilePath(route.rootDir, relativeFilePath)
64
+ }
65
+
66
+ return null
67
+ },
68
+ toUrlPathname(filePath) {
69
+ let normalizedFilePath = normalizeFilePath(filePath)
70
+
71
+ for (let route of compiledRoutes) {
72
+ let relativeFilePath = getRelativeFilePath(normalizedFilePath, route.rootDir)
73
+ if (relativeFilePath === null) continue
74
+ let match = route.filePattern.ast.pathname.match(relativeFilePath)
75
+ if (!match) continue
76
+ return normalizePathname(route.urlPattern.href(getPathnameParams(route.filePattern, match)))
77
+ }
78
+
79
+ return null
80
+ },
81
+ }
82
+ }
83
+
84
+ function compileRoute(
85
+ route: AssetRouteDefinition,
86
+ options: {
87
+ rootDir: string
88
+ },
89
+ ): CompiledRoute {
90
+ let urlPatternSource = normalizePathname(route.urlPattern)
91
+ let filePatternSource = normalizeFilePattern(route.filePattern)
92
+
93
+ let urlPattern = new RoutePattern(urlPatternSource)
94
+ let filePattern = new RoutePattern(filePatternSource)
95
+
96
+ validateNoUnnamedWildcards(urlPattern, 'URL')
97
+ validateNoUnnamedWildcards(filePattern, 'File')
98
+ validateRoutePatterns(urlPattern, filePattern)
99
+
100
+ return {
101
+ rootDir: normalizeFilePath(options.rootDir).replace(/\/+$/, ''),
102
+ urlPattern,
103
+ filePattern,
104
+ }
105
+ }
106
+
107
+ function getRelativeFilePath(filePath: string, rootDir: string): string | null {
108
+ if (filePath[1] === ':' && rootDir[1] === ':' && filePath[0] !== rootDir[0]) return null
109
+ return path.posix.relative(rootDir, filePath)
110
+ }
111
+
112
+ function getPathnameParams(
113
+ pattern: RoutePattern,
114
+ match: Array<{ name: string; type: ':' | '*'; value: string }>,
115
+ ): Record<string, string | undefined> {
116
+ let params: Record<string, string | undefined> = {}
117
+
118
+ for (let param of pattern.ast.pathname.params) {
119
+ if (param.name === '*') continue
120
+ params[param.name] = undefined
121
+ }
122
+
123
+ for (let param of match) {
124
+ if (param.name === '*') continue
125
+ params[param.name] = param.value
126
+ }
127
+
128
+ return params
129
+ }
130
+
131
+ function validateRoutePatterns(urlPattern: RoutePattern, filePattern: RoutePattern): void {
132
+ let urlParams = urlPattern.ast.pathname.params.map(
133
+ (param: { name: string; type: ':' | '*' }) => `${param.type}:${param.name}`,
134
+ )
135
+ let fileParams = filePattern.ast.pathname.params.map(
136
+ (param: { name: string; type: ':' | '*' }) => `${param.type}:${param.name}`,
137
+ )
138
+
139
+ if (urlParams.length !== fileParams.length) {
140
+ throw new Error(
141
+ `Route patterns must have matching capture structure.\nURL: ${urlPattern}\nFile: ${filePattern}`,
142
+ )
143
+ }
144
+
145
+ for (let i = 0; i < urlParams.length; i++) {
146
+ if (urlParams[i] !== fileParams[i]) {
147
+ throw new Error(
148
+ `Route patterns must have matching capture structure.\nURL: ${urlPattern}\nFile: ${filePattern}`,
149
+ )
150
+ }
151
+ }
152
+ }
153
+
154
+ function validateNoUnnamedWildcards(pattern: RoutePattern, label: string): void {
155
+ if (
156
+ pattern.ast.pathname.params.some(
157
+ (param: { name: string; type: ':' | '*' }) => param.type === '*' && param.name === '*',
158
+ )
159
+ ) {
160
+ throw new Error(
161
+ `${label} route patterns must use named wildcards for reversible mapping.\nPattern: ${pattern}`,
162
+ )
163
+ }
164
+ }
@@ -0,0 +1,476 @@
1
+ import { parseSync, visitorKeys } from 'oxc-parser'
2
+
3
+ const suspiciousCommonJSPattern =
4
+ /\brequire\s*(?:\(|\.|\[)|\bmodule\s*(?:\.\s*exports|\[\s*['"`]exports['"`]\s*\])|\bexports\s*(?:\.|=|\[)/
5
+
6
+ interface AstNode {
7
+ type: string
8
+ [key: string]: unknown
9
+ }
10
+
11
+ interface IdentifierNode extends AstNode {
12
+ name: string
13
+ }
14
+
15
+ type Scope = {
16
+ bindings: Set<string>
17
+ kind: 'block' | 'function' | 'module'
18
+ parent: Scope | null
19
+ }
20
+
21
+ export function mayContainCommonJSModuleGlobals(source: string): boolean {
22
+ return suspiciousCommonJSPattern.test(source)
23
+ }
24
+
25
+ // Detects CommonJS module globals that cannot be served as ES modules.
26
+ export function isCommonJS(source: string): boolean {
27
+ try {
28
+ let result = parseSync('module.js', source, {
29
+ lang: 'js',
30
+ sourceType: 'module',
31
+ })
32
+ if (result.errors.length > 0) {
33
+ return suspiciousCommonJSPattern.test(source)
34
+ }
35
+ return containsCommonJSModuleGlobals(result.program as unknown as AstNode)
36
+ } catch {
37
+ return suspiciousCommonJSPattern.test(source)
38
+ }
39
+ }
40
+
41
+ function containsCommonJSModuleGlobals(program: AstNode): boolean {
42
+ let moduleScope = createScope(null, 'module')
43
+ let nodeScopes = new WeakMap<object, Scope>()
44
+ nodeScopes.set(program, moduleScope)
45
+ collectScopeBindings(program, moduleScope, nodeScopes)
46
+ return walkForCommonJS(program, nodeScopes, moduleScope)
47
+ }
48
+
49
+ function walkForCommonJS(
50
+ node: AstNode,
51
+ nodeScopes: WeakMap<object, Scope>,
52
+ currentScope: Scope,
53
+ parent: AstNode | null = null,
54
+ key?: string,
55
+ ): boolean {
56
+ let nextScope = nodeScopes.get(node) ?? currentScope
57
+
58
+ switch (node.type) {
59
+ case 'ImportDeclaration':
60
+ return false
61
+ case 'VariableDeclarator':
62
+ if (isAstNode(node.init) && walkForCommonJS(node.init, nodeScopes, nextScope, node, 'init')) {
63
+ return true
64
+ }
65
+ return walkPatternForCommonJS(node.id, nodeScopes, nextScope)
66
+ case 'FunctionDeclaration':
67
+ case 'FunctionExpression':
68
+ case 'ArrowFunctionExpression':
69
+ for (let param of getNodeArray(node.params)) {
70
+ if (walkPatternForCommonJS(param, nodeScopes, nextScope)) return true
71
+ }
72
+ return isAstNode(node.body)
73
+ ? walkForCommonJS(node.body, nodeScopes, nextScope, node, 'body')
74
+ : false
75
+ case 'CatchClause':
76
+ if (walkPatternForCommonJS(node.param, nodeScopes, nextScope)) return true
77
+ return isAstNode(node.body)
78
+ ? walkForCommonJS(node.body, nodeScopes, nextScope, node, 'body')
79
+ : false
80
+ case 'Property':
81
+ if (
82
+ node.computed &&
83
+ isAstNode(node.key) &&
84
+ walkForCommonJS(node.key, nodeScopes, nextScope, node, 'key')
85
+ ) {
86
+ return true
87
+ }
88
+ return isAstNode(node.value)
89
+ ? walkForCommonJS(node.value, nodeScopes, nextScope, node, 'value')
90
+ : false
91
+ case 'MemberExpression':
92
+ if (
93
+ isAstNode(node.object) &&
94
+ walkForCommonJS(node.object, nodeScopes, nextScope, node, 'object')
95
+ ) {
96
+ return true
97
+ }
98
+ return (
99
+ !!node.computed &&
100
+ isAstNode(node.property) &&
101
+ walkForCommonJS(node.property, nodeScopes, nextScope, node, 'property')
102
+ )
103
+ case 'ExportSpecifier':
104
+ return isAstNode(node.local)
105
+ ? walkForCommonJS(node.local, nodeScopes, nextScope, node, 'local')
106
+ : false
107
+ case 'Identifier':
108
+ if (!isIdentifier(node) || !isReferenceIdentifier(node, parent, key)) return false
109
+ if (resolveBindingKind(node.name, nextScope) !== null) return false
110
+ return isCommonJSReference(node, parent, key)
111
+ }
112
+
113
+ for (let child of getChildNodes(node)) {
114
+ if (walkForCommonJS(child.node, nodeScopes, nextScope, node, child.key)) {
115
+ return true
116
+ }
117
+ }
118
+
119
+ return false
120
+ }
121
+
122
+ function walkPatternForCommonJS(
123
+ node: unknown,
124
+ nodeScopes: WeakMap<object, Scope>,
125
+ currentScope: Scope,
126
+ ): boolean {
127
+ if (!isAstNode(node)) return false
128
+
129
+ switch (node.type) {
130
+ case 'Identifier':
131
+ return false
132
+ case 'AssignmentPattern':
133
+ return (
134
+ walkPatternForCommonJS(node.left, nodeScopes, currentScope) ||
135
+ (isAstNode(node.right) && walkForCommonJS(node.right, nodeScopes, currentScope))
136
+ )
137
+ case 'RestElement':
138
+ return walkPatternForCommonJS(node.argument, nodeScopes, currentScope)
139
+ case 'ArrayPattern':
140
+ return getNodeArray(node.elements).some((element) =>
141
+ walkPatternForCommonJS(element, nodeScopes, currentScope),
142
+ )
143
+ case 'ObjectPattern':
144
+ return getNodeArray(node.properties).some((property) => {
145
+ if (property.type === 'Property') {
146
+ return (
147
+ (property.computed &&
148
+ isAstNode(property.key) &&
149
+ walkForCommonJS(property.key, nodeScopes, currentScope, property, 'key')) ||
150
+ walkPatternForCommonJS(property.value, nodeScopes, currentScope)
151
+ )
152
+ }
153
+ return walkPatternForCommonJS(property.argument, nodeScopes, currentScope)
154
+ })
155
+ }
156
+
157
+ return false
158
+ }
159
+
160
+ function collectScopeBindings(
161
+ node: AstNode,
162
+ currentScope: Scope,
163
+ nodeScopes: WeakMap<object, Scope>,
164
+ ) {
165
+ switch (node.type) {
166
+ case 'Program':
167
+ forEachChildNode(node, (child) => collectScopeBindings(child, currentScope, nodeScopes))
168
+ return
169
+ case 'ImportDeclaration':
170
+ for (let specifier of getNodeArray(node.specifiers)) {
171
+ if (isAstNode(specifier) && isIdentifier(specifier.local)) {
172
+ currentScope.bindings.add(specifier.local.name)
173
+ }
174
+ }
175
+ return
176
+ case 'VariableDeclaration': {
177
+ let targetScope = node.kind === 'var' ? getFunctionScope(currentScope) : currentScope
178
+ for (let declaration of getNodeArray(node.declarations)) {
179
+ collectPatternBindings(declaration.id, targetScope)
180
+ if (isAstNode(declaration.init)) {
181
+ collectScopeBindings(declaration.init, currentScope, nodeScopes)
182
+ }
183
+ }
184
+ return
185
+ }
186
+ case 'FunctionDeclaration': {
187
+ if (isIdentifier(node.id)) {
188
+ currentScope.bindings.add(node.id.name)
189
+ }
190
+ let functionScope = createScope(currentScope, 'function')
191
+ nodeScopes.set(node, functionScope)
192
+ if (isIdentifier(node.id)) {
193
+ functionScope.bindings.add(node.id.name)
194
+ }
195
+ for (let param of getNodeArray(node.params)) {
196
+ collectPatternBindings(param, functionScope)
197
+ collectPatternScopeBindings(param, functionScope, nodeScopes)
198
+ }
199
+ if (isAstNode(node.body)) {
200
+ collectScopeBindings(node.body, functionScope, nodeScopes)
201
+ }
202
+ return
203
+ }
204
+ case 'FunctionExpression':
205
+ case 'ArrowFunctionExpression': {
206
+ let functionScope = createScope(currentScope, 'function')
207
+ nodeScopes.set(node, functionScope)
208
+ if (node.type === 'FunctionExpression' && isIdentifier(node.id)) {
209
+ functionScope.bindings.add(node.id.name)
210
+ }
211
+ for (let param of getNodeArray(node.params)) {
212
+ collectPatternBindings(param, functionScope)
213
+ collectPatternScopeBindings(param, functionScope, nodeScopes)
214
+ }
215
+ if (isAstNode(node.body)) {
216
+ collectScopeBindings(node.body, functionScope, nodeScopes)
217
+ }
218
+ return
219
+ }
220
+ case 'ClassDeclaration':
221
+ if (isIdentifier(node.id)) {
222
+ currentScope.bindings.add(node.id.name)
223
+ }
224
+ break
225
+ case 'ClassExpression':
226
+ if (isIdentifier(node.id)) {
227
+ let classScope = createScope(currentScope, 'block')
228
+ classScope.bindings.add(node.id.name)
229
+ nodeScopes.set(node, classScope)
230
+ forEachChildNode(node, (child, key) => {
231
+ if (key !== 'id') {
232
+ collectScopeBindings(child, classScope, nodeScopes)
233
+ }
234
+ })
235
+ return
236
+ }
237
+ break
238
+ case 'BlockStatement':
239
+ case 'ForStatement':
240
+ case 'ForInStatement':
241
+ case 'ForOfStatement':
242
+ case 'SwitchStatement': {
243
+ let blockScope = createScope(currentScope, 'block')
244
+ nodeScopes.set(node, blockScope)
245
+ forEachChildNode(node, (child) => collectScopeBindings(child, blockScope, nodeScopes))
246
+ return
247
+ }
248
+ case 'CatchClause': {
249
+ let catchScope = createScope(currentScope, 'block')
250
+ nodeScopes.set(node, catchScope)
251
+ collectPatternBindings(node.param, catchScope)
252
+ collectPatternScopeBindings(node.param, catchScope, nodeScopes)
253
+ if (isAstNode(node.body)) {
254
+ collectScopeBindings(node.body, catchScope, nodeScopes)
255
+ }
256
+ return
257
+ }
258
+ }
259
+
260
+ forEachChildNode(node, (child) => collectScopeBindings(child, currentScope, nodeScopes))
261
+ }
262
+
263
+ function collectPatternBindings(node: unknown, scope: Scope): void {
264
+ if (!isAstNode(node)) return
265
+
266
+ switch (node.type) {
267
+ case 'Identifier':
268
+ if (isIdentifier(node)) {
269
+ scope.bindings.add(node.name)
270
+ }
271
+ return
272
+ case 'RestElement':
273
+ collectPatternBindings(node.argument, scope)
274
+ return
275
+ case 'AssignmentPattern':
276
+ collectPatternBindings(node.left, scope)
277
+ return
278
+ case 'ArrayPattern':
279
+ for (let element of getNodeArray(node.elements)) {
280
+ collectPatternBindings(element, scope)
281
+ }
282
+ return
283
+ case 'ObjectPattern':
284
+ for (let property of getNodeArray(node.properties)) {
285
+ if (property.type === 'Property') {
286
+ collectPatternBindings(property.value, scope)
287
+ } else {
288
+ collectPatternBindings(property.argument, scope)
289
+ }
290
+ }
291
+ return
292
+ }
293
+ }
294
+
295
+ function collectPatternScopeBindings(
296
+ node: unknown,
297
+ currentScope: Scope,
298
+ nodeScopes: WeakMap<object, Scope>,
299
+ ): void {
300
+ if (!isAstNode(node)) return
301
+
302
+ switch (node.type) {
303
+ case 'AssignmentPattern':
304
+ collectPatternScopeBindings(node.left, currentScope, nodeScopes)
305
+ if (isAstNode(node.right)) {
306
+ collectScopeBindings(node.right, currentScope, nodeScopes)
307
+ }
308
+ return
309
+ case 'ArrayPattern':
310
+ for (let element of getNodeArray(node.elements)) {
311
+ collectPatternScopeBindings(element, currentScope, nodeScopes)
312
+ }
313
+ return
314
+ case 'ObjectPattern':
315
+ for (let property of getNodeArray(node.properties)) {
316
+ if (property.type === 'Property') {
317
+ if (property.computed && isAstNode(property.key)) {
318
+ collectScopeBindings(property.key, currentScope, nodeScopes)
319
+ }
320
+ collectPatternScopeBindings(property.value, currentScope, nodeScopes)
321
+ } else {
322
+ collectPatternScopeBindings(property.argument, currentScope, nodeScopes)
323
+ }
324
+ }
325
+ return
326
+ case 'RestElement':
327
+ collectPatternScopeBindings(node.argument, currentScope, nodeScopes)
328
+ return
329
+ }
330
+ }
331
+
332
+ function isCommonJSReference(node: IdentifierNode, parent: AstNode | null, key?: string): boolean {
333
+ if (parent === null) return false
334
+
335
+ if (node.name === 'require') {
336
+ return (
337
+ (parent.type === 'CallExpression' && key === 'callee') ||
338
+ (parent.type === 'MemberExpression' && key === 'object')
339
+ )
340
+ }
341
+
342
+ if (node.name === 'exports') {
343
+ return (
344
+ (parent.type === 'AssignmentExpression' && key === 'left') ||
345
+ (parent.type === 'MemberExpression' && key === 'object')
346
+ )
347
+ }
348
+
349
+ return (
350
+ node.name === 'module' &&
351
+ parent.type === 'MemberExpression' &&
352
+ key === 'object' &&
353
+ isMemberPropertyNamed(parent.property, 'exports')
354
+ )
355
+ }
356
+
357
+ function resolveBindingKind(name: string, currentScope: Scope): 'local' | null {
358
+ let scope: Scope | null = currentScope
359
+ while (scope !== null) {
360
+ if (scope.bindings.has(name)) return 'local'
361
+ scope = scope.parent
362
+ }
363
+ return null
364
+ }
365
+
366
+ function createScope(parent: Scope | null, kind: Scope['kind']): Scope {
367
+ return {
368
+ bindings: new Set(),
369
+ kind,
370
+ parent,
371
+ }
372
+ }
373
+
374
+ function getFunctionScope(scope: Scope): Scope {
375
+ let current = scope
376
+ while (current.kind === 'block' && current.parent !== null) {
377
+ current = current.parent
378
+ }
379
+ return current
380
+ }
381
+
382
+ function isReferenceIdentifier(
383
+ node: IdentifierNode,
384
+ parent: AstNode | null,
385
+ key?: string,
386
+ ): boolean {
387
+ if (parent === null) return false
388
+ if (parent.type === 'ClassDeclaration' || parent.type === 'ClassExpression') {
389
+ return key !== 'id'
390
+ }
391
+ if (parent.type === 'Property' && key === 'key' && !parent.computed) {
392
+ return false
393
+ }
394
+ if (
395
+ (parent.type === 'PropertyDefinition' || parent.type === 'MethodDefinition') &&
396
+ key === 'key' &&
397
+ !parent.computed
398
+ ) {
399
+ return false
400
+ }
401
+ if (parent.type === 'MemberExpression' && key === 'property' && !parent.computed) {
402
+ return false
403
+ }
404
+ if (parent.type === 'MetaProperty') return false
405
+ if (
406
+ (parent.type === 'LabeledStatement' ||
407
+ parent.type === 'BreakStatement' ||
408
+ parent.type === 'ContinueStatement') &&
409
+ key === 'label'
410
+ ) {
411
+ return false
412
+ }
413
+ if (parent.type === 'ExportSpecifier' && key === 'exported') return false
414
+ return true
415
+ }
416
+
417
+ function getChildNodes(node: AstNode): Array<{ key: string; node: AstNode }> {
418
+ let children: Array<{ key: string; node: AstNode }> = []
419
+ forEachChildNode(node, (child, key) => {
420
+ children.push({ key, node: child })
421
+ })
422
+ return children
423
+ }
424
+
425
+ function forEachChildNode(node: AstNode, callback: (child: AstNode, key: string) => void): void {
426
+ for (let key of visitorKeys[node.type] ?? []) {
427
+ let value = node[key]
428
+ if (Array.isArray(value)) {
429
+ for (let child of value) {
430
+ if (isAstNode(child)) {
431
+ callback(child, key)
432
+ }
433
+ }
434
+ continue
435
+ }
436
+ if (isAstNode(value)) {
437
+ callback(value, key)
438
+ }
439
+ }
440
+ }
441
+
442
+ function getNodeArray(value: unknown): AstNode[] {
443
+ return Array.isArray(value) ? value.filter(isAstNode) : []
444
+ }
445
+
446
+ function isMemberPropertyNamed(node: unknown, name: string): boolean {
447
+ if (isIdentifier(node)) return node.name === name
448
+ return isStaticStringValue(node, name)
449
+ }
450
+
451
+ function isStaticStringValue(node: unknown, value: string): boolean {
452
+ if (!isAstNode(node)) return false
453
+
454
+ if (node.type === 'Literal') return node.value === value
455
+
456
+ return (
457
+ node.type === 'TemplateLiteral' &&
458
+ Array.isArray(node.expressions) &&
459
+ node.expressions.length === 0 &&
460
+ Array.isArray(node.quasis) &&
461
+ node.quasis.length === 1 &&
462
+ isAstNode(node.quasis[0]) &&
463
+ !!node.quasis[0].value &&
464
+ typeof node.quasis[0].value === 'object' &&
465
+ 'raw' in node.quasis[0].value &&
466
+ node.quasis[0].value.raw === value
467
+ )
468
+ }
469
+
470
+ function isAstNode(node: unknown): node is AstNode {
471
+ return !!node && typeof node === 'object' && 'type' in node && typeof node.type === 'string'
472
+ }
473
+
474
+ function isIdentifier(node: unknown): node is IdentifierNode {
475
+ return isAstNode(node) && node.type === 'Identifier' && typeof node.name === 'string'
476
+ }