tjs-lang 0.7.7 → 0.8.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/CLAUDE.md +99 -33
- package/bin/docs.js +4 -1
- package/demo/docs.json +104 -22
- package/demo/src/examples.test.ts +1 -0
- package/demo/src/imports.test.ts +16 -4
- package/demo/src/imports.ts +60 -15
- package/demo/src/playground-shared.ts +9 -8
- package/demo/src/tfs-worker.js +205 -147
- package/demo/src/tjs-playground.ts +34 -10
- package/demo/src/ts-examples.ts +8 -8
- package/demo/src/ts-playground.ts +24 -8
- package/dist/index.js +118 -101
- package/dist/index.js.map +4 -4
- package/dist/src/lang/bool-coercion.d.ts +50 -0
- package/dist/src/lang/docs.d.ts +31 -6
- package/dist/src/lang/linter.d.ts +8 -0
- package/dist/src/lang/parser-transforms.d.ts +18 -0
- package/dist/src/lang/parser-types.d.ts +2 -0
- package/dist/src/lang/parser.d.ts +3 -0
- package/dist/src/lang/runtime.d.ts +34 -0
- package/dist/src/lang/types.d.ts +9 -1
- package/dist/src/rbac/index.d.ts +1 -1
- package/dist/src/vm/runtime.d.ts +1 -1
- package/dist/tjs-eval.js +38 -36
- package/dist/tjs-eval.js.map +4 -4
- package/dist/tjs-from-ts.js +20 -20
- package/dist/tjs-from-ts.js.map +3 -3
- package/dist/tjs-lang.js +85 -83
- package/dist/tjs-lang.js.map +4 -4
- package/dist/tjs-vm.js +47 -45
- package/dist/tjs-vm.js.map +4 -4
- package/llms.txt +79 -0
- package/package.json +9 -4
- package/src/cli/commands/convert.test.ts +16 -21
- package/src/lang/bool-coercion.test.ts +203 -0
- package/src/lang/bool-coercion.ts +314 -0
- package/src/lang/codegen.test.ts +137 -0
- package/src/lang/docs.test.ts +476 -1
- package/src/lang/docs.ts +471 -37
- package/src/lang/emitters/ast.ts +11 -12
- package/src/lang/emitters/dts.test.ts +41 -0
- package/src/lang/emitters/dts.ts +9 -0
- package/src/lang/emitters/js-tests.ts +9 -4
- package/src/lang/emitters/js-wasm.ts +57 -65
- package/src/lang/emitters/js.ts +198 -3
- package/src/lang/features.test.ts +4 -3
- package/src/lang/index.ts +9 -0
- package/src/lang/inference.ts +54 -0
- package/src/lang/linter.test.ts +104 -1
- package/src/lang/linter.ts +124 -1
- package/src/lang/module-loader.test.ts +318 -0
- package/src/lang/module-loader.ts +419 -0
- package/src/lang/parser-params.ts +31 -0
- package/src/lang/parser-transforms.ts +640 -0
- package/src/lang/parser-types.ts +35 -0
- package/src/lang/parser.test.ts +73 -1
- package/src/lang/parser.ts +77 -3
- package/src/lang/runtime.ts +98 -0
- package/src/lang/types.ts +6 -0
- package/src/lang/wasm.test.ts +1293 -2
- package/src/lang/wasm.ts +470 -87
- package/src/linalg/index.tjs +119 -0
- package/src/linalg/linalg.test.ts +294 -0
- package/src/linalg/vector-search.bench.test.ts +395 -0
- package/src/rbac/index.ts +2 -2
- package/src/rbac/rules.tjs.d.ts +9 -0
- package/src/vm/atoms/batteries.ts +2 -2
- package/src/vm/runtime.ts +10 -3
- package/dist/src/rbac/rules.d.ts +0 -184
- package/src/rbac/rules.js +0 -338
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transpile-time module loader.
|
|
3
|
+
*
|
|
4
|
+
* Until now the transpiler has preserved import statements verbatim — runtime
|
|
5
|
+
* resolvers (the bun plugin, the playground service worker, the browser ESM
|
|
6
|
+
* loader) handle them on demand. That's the right default for regular JS
|
|
7
|
+
* interop, but Phase 3 of the wasm-library plan needs *static* visibility into
|
|
8
|
+
* imported sources: given `import { dot } from 'tjs-lang/linalg'`, we have to
|
|
9
|
+
* read linalg's source at transpile time so cross-file `wasm function`
|
|
10
|
+
* declarations can be composed into the consumer's wasm module.
|
|
11
|
+
*
|
|
12
|
+
* This loader is the foundation for that work. It's also useful as a building
|
|
13
|
+
* block for any future cross-file static analysis (type flow, dead code, etc.)
|
|
14
|
+
* — hence the name "module loader" rather than "wasm import resolver."
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* const loader = new ModuleLoader({ baseDir: '/path/to/project' })
|
|
18
|
+
* const mod = loader.load('./math.tjs', '/path/to/project/app.tjs')
|
|
19
|
+
* if (mod) { console.log(mod.exports) }
|
|
20
|
+
*
|
|
21
|
+
* Resolution rules (in order):
|
|
22
|
+
* - URL specifiers (http://, https://, data:): not loadable, returns null
|
|
23
|
+
* - Relative paths (./foo, ../bar): resolved against importer's dir
|
|
24
|
+
* - Absolute paths (/foo/bar): used as-is
|
|
25
|
+
* - Bare specifiers (foo, foo/bar): walk up importer looking for
|
|
26
|
+
* node_modules/<spec>; also
|
|
27
|
+
* try bareSpecifierRoots
|
|
28
|
+
*
|
|
29
|
+
* For each candidate, we try extensions in order: `.tjs`, `.ts`, `.js`.
|
|
30
|
+
* (Index files: `<dir>/index.<ext>`.)
|
|
31
|
+
*
|
|
32
|
+
* The loader does NOT mutate transpiler behavior. It's an additive capability;
|
|
33
|
+
* Phase 3 (cross-file wasm composition) is the first caller.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
37
|
+
import { dirname, isAbsolute, resolve as pathResolve, sep } from 'node:path'
|
|
38
|
+
import { parse as parseTjs } from './parser'
|
|
39
|
+
|
|
40
|
+
const SUPPORTED_EXTENSIONS = ['.tjs', '.ts', '.js']
|
|
41
|
+
|
|
42
|
+
/** Pluggable filesystem hook. Default uses node:fs. */
|
|
43
|
+
export interface FileSystem {
|
|
44
|
+
/** Return source text for an absolute path, or null if it doesn't exist. */
|
|
45
|
+
readFile(path: string): string | null
|
|
46
|
+
/** Return true if the path exists and is a file. */
|
|
47
|
+
exists(path: string): boolean
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const defaultFileSystem: FileSystem = {
|
|
51
|
+
readFile(path) {
|
|
52
|
+
try {
|
|
53
|
+
return readFileSync(path, 'utf8')
|
|
54
|
+
} catch {
|
|
55
|
+
return null
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
exists(path) {
|
|
59
|
+
try {
|
|
60
|
+
return existsSync(path)
|
|
61
|
+
} catch {
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface ModuleLoaderOptions {
|
|
68
|
+
/** Filesystem hook (default: node:fs based) */
|
|
69
|
+
fs?: FileSystem
|
|
70
|
+
/** Where to resolve bare specifiers from when no importer is given. */
|
|
71
|
+
baseDir?: string
|
|
72
|
+
/**
|
|
73
|
+
* Extra roots tried for bare specifiers BEFORE the standard node_modules
|
|
74
|
+
* walk. Useful for monorepos or tests that want to point at a virtual
|
|
75
|
+
* package directory. Each root is treated as if it were a `node_modules/`.
|
|
76
|
+
*/
|
|
77
|
+
bareSpecifierRoots?: string[]
|
|
78
|
+
/** Cache size cap. 0 disables caching. (default 256) */
|
|
79
|
+
cacheLimit?: number
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** A single import / re-export specifier extracted from the AST */
|
|
83
|
+
export interface ImportEntry {
|
|
84
|
+
/** Original module specifier (e.g. './math.tjs', 'tjs-lang/linalg') */
|
|
85
|
+
specifier: string
|
|
86
|
+
/** Local name in the importing module */
|
|
87
|
+
local: string
|
|
88
|
+
/** Imported name in the source module ('default' for default imports) */
|
|
89
|
+
imported: string
|
|
90
|
+
/** True if this came from `import * as X` */
|
|
91
|
+
namespace: boolean
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** A top-level export from a module */
|
|
95
|
+
export interface ExportEntry {
|
|
96
|
+
/** Exported name (or 'default' for default exports) */
|
|
97
|
+
name: string
|
|
98
|
+
/** Kind of declaration being exported */
|
|
99
|
+
kind: 'function' | 'class' | 'variable' | 're-export' | 'unknown'
|
|
100
|
+
/** For re-exports, the source specifier */
|
|
101
|
+
fromSpecifier?: string
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface LoadedModule {
|
|
105
|
+
/** Resolved absolute path */
|
|
106
|
+
path: string
|
|
107
|
+
/** Original source text */
|
|
108
|
+
source: string
|
|
109
|
+
/** AST + tjs preprocessing output (lazy — only computed once per module) */
|
|
110
|
+
parseResult: ReturnType<typeof parseTjs>
|
|
111
|
+
/** Imports declared by this module */
|
|
112
|
+
imports: ImportEntry[]
|
|
113
|
+
/** Top-level exports */
|
|
114
|
+
exports: ExportEntry[]
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export class ModuleLoader {
|
|
118
|
+
private cache = new Map<string, LoadedModule>()
|
|
119
|
+
private fs: FileSystem
|
|
120
|
+
private baseDir: string
|
|
121
|
+
private bareSpecifierRoots: string[]
|
|
122
|
+
private cacheLimit: number
|
|
123
|
+
|
|
124
|
+
constructor(options: ModuleLoaderOptions = {}) {
|
|
125
|
+
this.fs = options.fs ?? defaultFileSystem
|
|
126
|
+
this.baseDir = options.baseDir ?? process.cwd()
|
|
127
|
+
this.bareSpecifierRoots = options.bareSpecifierRoots ?? []
|
|
128
|
+
this.cacheLimit = options.cacheLimit ?? 256
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Resolve a specifier to an absolute path. Returns null when the specifier
|
|
133
|
+
* is not loadable as a local TJS/TS/JS file (URLs, missing files, unknown
|
|
134
|
+
* bare specifiers all return null — the caller falls back to verbatim
|
|
135
|
+
* import preservation).
|
|
136
|
+
*/
|
|
137
|
+
resolve(specifier: string, importerPath?: string): string | null {
|
|
138
|
+
// Reject URL-style and data: specifiers — runtime resolvers handle these
|
|
139
|
+
if (
|
|
140
|
+
specifier.startsWith('http://') ||
|
|
141
|
+
specifier.startsWith('https://') ||
|
|
142
|
+
specifier.startsWith('data:') ||
|
|
143
|
+
specifier.startsWith('file://')
|
|
144
|
+
) {
|
|
145
|
+
return null
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (specifier.startsWith('./') || specifier.startsWith('../')) {
|
|
149
|
+
return this.tryExtensions(
|
|
150
|
+
pathResolve(
|
|
151
|
+
importerPath ? dirname(importerPath) : this.baseDir,
|
|
152
|
+
specifier
|
|
153
|
+
)
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (isAbsolute(specifier)) {
|
|
158
|
+
return this.tryExtensions(specifier)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return this.resolveBare(specifier, importerPath)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Load a module by specifier. Returns null if not resolvable (caller treats
|
|
166
|
+
* this as "leave the import statement alone").
|
|
167
|
+
*
|
|
168
|
+
* Repeated calls with the same specifier+importer combination hit the cache.
|
|
169
|
+
*/
|
|
170
|
+
load(specifier: string, importerPath?: string): LoadedModule | null {
|
|
171
|
+
const path = this.resolve(specifier, importerPath)
|
|
172
|
+
if (!path) return null
|
|
173
|
+
|
|
174
|
+
const cached = this.cache.get(path)
|
|
175
|
+
if (cached) return cached
|
|
176
|
+
|
|
177
|
+
const source = this.fs.readFile(path)
|
|
178
|
+
if (source === null) return null
|
|
179
|
+
|
|
180
|
+
let parseResult: ReturnType<typeof parseTjs>
|
|
181
|
+
try {
|
|
182
|
+
parseResult = parseTjs(source, { filename: path })
|
|
183
|
+
} catch {
|
|
184
|
+
// Parse failure: don't cache, don't claim to have loaded it.
|
|
185
|
+
// Caller falls back to verbatim import preservation.
|
|
186
|
+
return null
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const imports = collectImports(parseResult.ast)
|
|
190
|
+
const exports = collectExports(parseResult.ast)
|
|
191
|
+
|
|
192
|
+
const loaded: LoadedModule = {
|
|
193
|
+
path,
|
|
194
|
+
source,
|
|
195
|
+
parseResult,
|
|
196
|
+
imports,
|
|
197
|
+
exports,
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (this.cacheLimit > 0) {
|
|
201
|
+
// Naive eviction: drop the first inserted entry when over the limit.
|
|
202
|
+
// Modules are small and the cache is short-lived per transpile session.
|
|
203
|
+
if (this.cache.size >= this.cacheLimit) {
|
|
204
|
+
const firstKey = this.cache.keys().next().value
|
|
205
|
+
if (firstKey !== undefined) this.cache.delete(firstKey)
|
|
206
|
+
}
|
|
207
|
+
this.cache.set(path, loaded)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return loaded
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Drop all cached modules. */
|
|
214
|
+
clearCache(): void {
|
|
215
|
+
this.cache.clear()
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Try each supported extension; return the first existing path or null. */
|
|
219
|
+
private tryExtensions(basePath: string): string | null {
|
|
220
|
+
// If the path already has one of our extensions, try it directly first.
|
|
221
|
+
if (SUPPORTED_EXTENSIONS.some((ext) => basePath.endsWith(ext))) {
|
|
222
|
+
return this.fs.exists(basePath) ? basePath : null
|
|
223
|
+
}
|
|
224
|
+
for (const ext of SUPPORTED_EXTENSIONS) {
|
|
225
|
+
const withExt = basePath + ext
|
|
226
|
+
if (this.fs.exists(withExt)) return withExt
|
|
227
|
+
}
|
|
228
|
+
// Try as directory (look for index.<ext>)
|
|
229
|
+
for (const ext of SUPPORTED_EXTENSIONS) {
|
|
230
|
+
const indexPath = pathResolve(basePath, 'index' + ext)
|
|
231
|
+
if (this.fs.exists(indexPath)) return indexPath
|
|
232
|
+
}
|
|
233
|
+
return null
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Resolve a bare specifier (e.g. `tjs-lang/linalg`, `lodash`) by walking
|
|
238
|
+
* up the importer's directory tree looking for `node_modules/<spec>`.
|
|
239
|
+
* Also checks each configured `bareSpecifierRoots` entry first.
|
|
240
|
+
*/
|
|
241
|
+
private resolveBare(specifier: string, importerPath?: string): string | null {
|
|
242
|
+
// Try configured roots first (test fixtures, monorepo packages, etc.)
|
|
243
|
+
for (const root of this.bareSpecifierRoots) {
|
|
244
|
+
const candidate = this.tryExtensions(pathResolve(root, specifier))
|
|
245
|
+
if (candidate) return candidate
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Walk up from the importer (or baseDir) looking for node_modules
|
|
249
|
+
let dir = importerPath ? dirname(importerPath) : this.baseDir
|
|
250
|
+
// Ensure absolute to avoid an infinite loop on relative inputs
|
|
251
|
+
dir = pathResolve(dir)
|
|
252
|
+
while (true) {
|
|
253
|
+
const nodeModulesCandidate = this.tryExtensions(
|
|
254
|
+
pathResolve(dir, 'node_modules', specifier)
|
|
255
|
+
)
|
|
256
|
+
if (nodeModulesCandidate) return nodeModulesCandidate
|
|
257
|
+
|
|
258
|
+
// Also try resolving via package.json's "main" or "exports" — for now,
|
|
259
|
+
// we're conservative: the standard tjs library layout uses a
|
|
260
|
+
// `src/index.tjs` entry point, which tryExtensions will find via the
|
|
261
|
+
// "directory with index.<ext>" branch above. Full package.json
|
|
262
|
+
// exports-field resolution can come later if needed.
|
|
263
|
+
|
|
264
|
+
const parent = dirname(dir)
|
|
265
|
+
if (parent === dir) break // hit filesystem root
|
|
266
|
+
dir = parent
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return null
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ============================================================================
|
|
274
|
+
// AST inspection helpers
|
|
275
|
+
// ============================================================================
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Extract import declarations from a Program AST.
|
|
279
|
+
* Acorn types: `ImportDeclaration` with `specifiers` (array of
|
|
280
|
+
* ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier).
|
|
281
|
+
*/
|
|
282
|
+
function collectImports(ast: any): ImportEntry[] {
|
|
283
|
+
const imports: ImportEntry[] = []
|
|
284
|
+
if (!ast || !Array.isArray(ast.body)) return imports
|
|
285
|
+
|
|
286
|
+
for (const node of ast.body) {
|
|
287
|
+
if (node.type !== 'ImportDeclaration') continue
|
|
288
|
+
const specifier = node.source?.value
|
|
289
|
+
if (typeof specifier !== 'string') continue
|
|
290
|
+
|
|
291
|
+
for (const spec of node.specifiers ?? []) {
|
|
292
|
+
const local = spec.local?.name
|
|
293
|
+
if (typeof local !== 'string') continue
|
|
294
|
+
|
|
295
|
+
switch (spec.type) {
|
|
296
|
+
case 'ImportSpecifier':
|
|
297
|
+
imports.push({
|
|
298
|
+
specifier,
|
|
299
|
+
local,
|
|
300
|
+
imported: spec.imported?.name ?? local,
|
|
301
|
+
namespace: false,
|
|
302
|
+
})
|
|
303
|
+
break
|
|
304
|
+
case 'ImportDefaultSpecifier':
|
|
305
|
+
imports.push({
|
|
306
|
+
specifier,
|
|
307
|
+
local,
|
|
308
|
+
imported: 'default',
|
|
309
|
+
namespace: false,
|
|
310
|
+
})
|
|
311
|
+
break
|
|
312
|
+
case 'ImportNamespaceSpecifier':
|
|
313
|
+
imports.push({
|
|
314
|
+
specifier,
|
|
315
|
+
local,
|
|
316
|
+
imported: '*',
|
|
317
|
+
namespace: true,
|
|
318
|
+
})
|
|
319
|
+
break
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return imports
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Extract top-level exports from a Program AST.
|
|
329
|
+
*
|
|
330
|
+
* Covers:
|
|
331
|
+
* - `export function foo() {}`
|
|
332
|
+
* - `export class Foo {}`
|
|
333
|
+
* - `export const x = ...`, `export let`, `export var`
|
|
334
|
+
* - `export { a, b as c }`
|
|
335
|
+
* - `export { a } from './other'`
|
|
336
|
+
* - `export * from './other'`
|
|
337
|
+
* - `export default ...`
|
|
338
|
+
*
|
|
339
|
+
* Does NOT yet recognize `wasm function` — Phase 1 will introduce that
|
|
340
|
+
* declaration kind and a follow-up will surface it here as kind: 'wasm-function'.
|
|
341
|
+
*/
|
|
342
|
+
function collectExports(ast: any): ExportEntry[] {
|
|
343
|
+
const exports: ExportEntry[] = []
|
|
344
|
+
if (!ast || !Array.isArray(ast.body)) return exports
|
|
345
|
+
|
|
346
|
+
for (const node of ast.body) {
|
|
347
|
+
if (node.type === 'ExportNamedDeclaration') {
|
|
348
|
+
// export function foo() {} | export class Foo {} | export const x = ...
|
|
349
|
+
if (node.declaration) {
|
|
350
|
+
const decl = node.declaration
|
|
351
|
+
if (decl.type === 'FunctionDeclaration' && decl.id?.name) {
|
|
352
|
+
exports.push({ name: decl.id.name, kind: 'function' })
|
|
353
|
+
} else if (decl.type === 'ClassDeclaration' && decl.id?.name) {
|
|
354
|
+
exports.push({ name: decl.id.name, kind: 'class' })
|
|
355
|
+
} else if (decl.type === 'VariableDeclaration') {
|
|
356
|
+
for (const v of decl.declarations) {
|
|
357
|
+
if (v.id?.type === 'Identifier' && v.id.name) {
|
|
358
|
+
exports.push({ name: v.id.name, kind: 'variable' })
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
// export { a, b as c } [from './other']
|
|
364
|
+
if (Array.isArray(node.specifiers)) {
|
|
365
|
+
for (const spec of node.specifiers) {
|
|
366
|
+
const exportedName = spec.exported?.name
|
|
367
|
+
if (typeof exportedName !== 'string') continue
|
|
368
|
+
exports.push({
|
|
369
|
+
name: exportedName,
|
|
370
|
+
kind: node.source ? 're-export' : 'unknown',
|
|
371
|
+
fromSpecifier: node.source?.value,
|
|
372
|
+
})
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
} else if (node.type === 'ExportDefaultDeclaration') {
|
|
376
|
+
const decl = node.declaration
|
|
377
|
+
const kind: ExportEntry['kind'] =
|
|
378
|
+
decl?.type === 'FunctionDeclaration' ||
|
|
379
|
+
decl?.type === 'FunctionExpression' ||
|
|
380
|
+
decl?.type === 'ArrowFunctionExpression'
|
|
381
|
+
? 'function'
|
|
382
|
+
: decl?.type === 'ClassDeclaration' ||
|
|
383
|
+
decl?.type === 'ClassExpression'
|
|
384
|
+
? 'class'
|
|
385
|
+
: 'unknown'
|
|
386
|
+
exports.push({ name: 'default', kind })
|
|
387
|
+
} else if (node.type === 'ExportAllDeclaration') {
|
|
388
|
+
// export * from './other'
|
|
389
|
+
const fromSpecifier = node.source?.value
|
|
390
|
+
if (typeof fromSpecifier === 'string') {
|
|
391
|
+
exports.push({
|
|
392
|
+
name: '*',
|
|
393
|
+
kind: 're-export',
|
|
394
|
+
fromSpecifier,
|
|
395
|
+
})
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
return exports
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Helper for tests / advanced use: build a FileSystem from a plain
|
|
405
|
+
* `Map<string, string>`. Keys must be absolute paths.
|
|
406
|
+
*/
|
|
407
|
+
export function inMemoryFileSystem(
|
|
408
|
+
files: Map<string, string> | Record<string, string>
|
|
409
|
+
): FileSystem {
|
|
410
|
+
const map =
|
|
411
|
+
files instanceof Map ? files : new Map(Object.entries(files))
|
|
412
|
+
// Normalize separators so path.resolve()'s output matches map keys
|
|
413
|
+
const normalize = (p: string) => p.split('/').join(sep)
|
|
414
|
+
const lookup = (p: string) => map.get(p) ?? map.get(normalize(p)) ?? null
|
|
415
|
+
return {
|
|
416
|
+
readFile: (p) => lookup(p),
|
|
417
|
+
exists: (p) => lookup(p) !== null,
|
|
418
|
+
}
|
|
419
|
+
}
|
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
ContextFrame,
|
|
13
13
|
TjsModes,
|
|
14
14
|
} from './parser-types'
|
|
15
|
+
import { locAt } from './parser-transforms'
|
|
15
16
|
|
|
16
17
|
export function transformParenExpressions(
|
|
17
18
|
source: string,
|
|
@@ -331,6 +332,23 @@ export function transformParenExpressions(
|
|
|
331
332
|
i = typeResult.endPos
|
|
332
333
|
}
|
|
333
334
|
}
|
|
335
|
+
|
|
336
|
+
// Catch a common mistake: writing `=> {` after a function declaration's
|
|
337
|
+
// return type (or after `)`), as if it were an arrow function. Without
|
|
338
|
+
// this check, the `=>` would pass through to Acorn, which complains
|
|
339
|
+
// with a generic "Unexpected token" at a misleading position.
|
|
340
|
+
let arrowCheck = i
|
|
341
|
+
while (arrowCheck < source.length && /\s/.test(source[arrowCheck]))
|
|
342
|
+
arrowCheck++
|
|
343
|
+
if (source[arrowCheck] === '=' && source[arrowCheck + 1] === '>') {
|
|
344
|
+
throw new SyntaxError(
|
|
345
|
+
"Unexpected '=>' after function declaration. " +
|
|
346
|
+
'Function declarations use `function name(params) { body }`, ' +
|
|
347
|
+
'not arrow syntax. Remove the `=>`.',
|
|
348
|
+
locAt(ctx.originalSource, arrowCheck),
|
|
349
|
+
ctx.originalSource
|
|
350
|
+
)
|
|
351
|
+
}
|
|
334
352
|
continue
|
|
335
353
|
}
|
|
336
354
|
|
|
@@ -410,6 +428,19 @@ export function transformParenExpressions(
|
|
|
410
428
|
}
|
|
411
429
|
}
|
|
412
430
|
|
|
431
|
+
// Same `=>` check for class methods.
|
|
432
|
+
let k = i
|
|
433
|
+
while (k < source.length && /\s/.test(source[k])) k++
|
|
434
|
+
if (source[k] === '=' && source[k + 1] === '>') {
|
|
435
|
+
throw new SyntaxError(
|
|
436
|
+
"Unexpected '=>' after method declaration. " +
|
|
437
|
+
'Methods use `name(params) { body }`, not arrow syntax. ' +
|
|
438
|
+
'Remove the `=>`.',
|
|
439
|
+
locAt(ctx.originalSource, k),
|
|
440
|
+
ctx.originalSource
|
|
441
|
+
)
|
|
442
|
+
}
|
|
443
|
+
|
|
413
444
|
continue
|
|
414
445
|
}
|
|
415
446
|
|