tsnite 0.1.5 β 0.2.1
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/README.md +32 -80
- package/dist/cache.js +1 -21
- package/dist/cli.js +14 -9
- package/dist/loader.js +43 -152
- package/dist/util.js +33 -0
- package/package.json +8 -9
- package/dist/parse.js +0 -435
package/README.md
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
<h1 align="center">
|
|
2
|
+
<br>
|
|
3
|
+
<img alt="tsnite" src="https://github.com/luas10c/tsnite/blob/main/tsnite.png?raw=true">
|
|
4
|
+
<br><br>
|
|
5
|
+
<a href="https://npm.im/tsnite"><img src="https://badgen.net/npm/v/tsnite"></a>
|
|
6
|
+
<a href="https://npm.im/tsnite"><img src="https://badgen.net/npm/dm/tsnite"></a>
|
|
7
|
+
<a href="https://npm.im/tsnite"><img src="https://img.shields.io/badge/ESLint-3A33D1?logo=eslint" alt="eslint"></a>
|
|
8
|
+
<a href="https://npm.im/tsnite"><img src="https://img.shields.io/badge/Prettier-21323b?logo=prettier&logoColor=ffffff" alt="prettier"></a>
|
|
9
|
+
<a href="https://npm.im/tsnite"><img src="https://img.shields.io/github/license/luas10c/tsnite" alt="github license"></a>
|
|
10
|
+
</h1>
|
|
11
|
+
|
|
12
|
+
<p align="center">
|
|
13
|
+
TypeScript at full throttleβfast, safe, unstoppable. π
|
|
14
|
+
<br><br>
|
|
15
|
+
<a href="https://github.com/luas10c/tsnite">Documentation</a> | <a href="https://github.com/luas10c/tsnite">Getting started β</a>
|
|
16
|
+
</p>
|
|
9
17
|
|
|
10
18
|
## Install
|
|
11
19
|
|
|
@@ -13,7 +21,15 @@ TypeScript runner for Node.js with watch mode, `tsconfig` path alias support, an
|
|
|
13
21
|
npm install --save-dev tsnite
|
|
14
22
|
```
|
|
15
23
|
|
|
16
|
-
##
|
|
24
|
+
## Features
|
|
25
|
+
|
|
26
|
+
- ESM-friendly runtime
|
|
27
|
+
- Watch mode with automatic restart
|
|
28
|
+
- Supports `compilerOptions.paths`
|
|
29
|
+
- Resolves extensionless TypeScript imports
|
|
30
|
+
- Decorator support
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
17
33
|
|
|
18
34
|
Run a TypeScript entry file:
|
|
19
35
|
|
|
@@ -44,16 +60,6 @@ Use in `package.json`:
|
|
|
44
60
|
}
|
|
45
61
|
```
|
|
46
62
|
|
|
47
|
-
## Features
|
|
48
|
-
|
|
49
|
-
- Run `.ts`, `.tsx`, `.mts`, and `.cts` files directly in Node.js
|
|
50
|
-
- Watch mode with automatic restart
|
|
51
|
-
- Reads the current project's `tsconfig.json`
|
|
52
|
-
- Resolves `compilerOptions.paths`
|
|
53
|
-
- Resolves extensionless TypeScript imports
|
|
54
|
-
- ESM-friendly runtime
|
|
55
|
-
- Decorator support in transpilation
|
|
56
|
-
|
|
57
63
|
## Import Resolution
|
|
58
64
|
|
|
59
65
|
`tsnite` resolves TypeScript files without requiring explicit extensions.
|
|
@@ -78,29 +84,6 @@ It will try these TypeScript candidates:
|
|
|
78
84
|
|
|
79
85
|
Explicit JavaScript imports such as `.js`, `.mjs`, and `.cjs` are delegated to Node.js.
|
|
80
86
|
|
|
81
|
-
## `tsconfig` Paths
|
|
82
|
-
|
|
83
|
-
`tsnite` supports `compilerOptions.paths` aliases.
|
|
84
|
-
|
|
85
|
-
```json
|
|
86
|
-
{
|
|
87
|
-
"compilerOptions": {
|
|
88
|
-
"baseUrl": ".",
|
|
89
|
-
"paths": {
|
|
90
|
-
"#/*": ["src/*"]
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
Then this works without an extension:
|
|
97
|
-
|
|
98
|
-
```ts
|
|
99
|
-
import { getMetadata } from '#/common/metadata'
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
If `baseUrl` is not defined, `tsnite` uses the project root by default.
|
|
103
|
-
|
|
104
87
|
## Watch Mode
|
|
105
88
|
|
|
106
89
|
Customize watched paths and extensions:
|
|
@@ -123,45 +106,14 @@ Defaults:
|
|
|
123
106
|
- ext: `ts,tsx,js,jsx,json`
|
|
124
107
|
- source root: `.`
|
|
125
108
|
|
|
126
|
-
##
|
|
127
|
-
|
|
128
|
-
`tsconfig.json`
|
|
129
|
-
|
|
130
|
-
```json
|
|
131
|
-
{
|
|
132
|
-
"compilerOptions": {
|
|
133
|
-
"target": "es2024",
|
|
134
|
-
"module": "es2022",
|
|
135
|
-
"moduleResolution": "bundler",
|
|
136
|
-
"baseUrl": ".",
|
|
137
|
-
"paths": {
|
|
138
|
-
"#/*": ["src/*"]
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
`src/index.ts`
|
|
145
|
-
|
|
146
|
-
```ts
|
|
147
|
-
import { startServer } from '#/server/start'
|
|
148
|
-
|
|
149
|
-
await startServer()
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
Run it:
|
|
153
|
-
|
|
154
|
-
```bash
|
|
155
|
-
npx tsnite src/index.ts
|
|
156
|
-
```
|
|
109
|
+
## Support
|
|
157
110
|
|
|
158
|
-
|
|
111
|
+
Enjoying this tool? Consider supporting the project.
|
|
159
112
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
113
|
+
<p>
|
|
114
|
+
<a href="https://buymeacoffee.com/luas10c" target="_blank" rel="noopener noreferrer"><img src="https://github.com/luas10c/tsnite/blob/main/buymeacoffe.png?raw=true" alt="Buy me a coffee" width="180" /></a>
|
|
115
|
+
</p>
|
|
163
116
|
|
|
164
|
-
##
|
|
117
|
+
## License
|
|
165
118
|
|
|
166
|
-
|
|
167
|
-
- repository: <https://github.com/luas10c/tsnite>
|
|
119
|
+
[MIT](LICENSE)
|
package/dist/cache.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { mkdir, rm
|
|
2
|
+
import { mkdir, rm } from 'node:fs/promises';
|
|
3
3
|
import { join, resolve } from 'node:path';
|
|
4
4
|
export const resolveCache = new Map();
|
|
5
|
-
const statCache = new Map();
|
|
6
5
|
function getTranspileCacheDir() {
|
|
7
6
|
return join(import.meta.dirname, '..', 'node_modules', '.cache', 'tsnite');
|
|
8
7
|
}
|
|
@@ -12,27 +11,9 @@ function getTSConfigPath() {
|
|
|
12
11
|
function hash(value) {
|
|
13
12
|
return createHash('sha1').update(value).digest('hex');
|
|
14
13
|
}
|
|
15
|
-
export async function existsWithCache(filePath) {
|
|
16
|
-
const cached = statCache.get(filePath);
|
|
17
|
-
if (cached)
|
|
18
|
-
return cached.exists;
|
|
19
|
-
try {
|
|
20
|
-
const stats = await stat(filePath);
|
|
21
|
-
const exists = stats.isFile();
|
|
22
|
-
statCache.set(filePath, { exists, mtime: stats.mtimeMs });
|
|
23
|
-
return exists;
|
|
24
|
-
}
|
|
25
|
-
catch {
|
|
26
|
-
statCache.set(filePath, { exists: false });
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
14
|
export function clearResolveCache() {
|
|
31
15
|
resolveCache.clear();
|
|
32
16
|
}
|
|
33
|
-
export function invalidateStatCache(filePath) {
|
|
34
|
-
statCache.delete(filePath);
|
|
35
|
-
}
|
|
36
17
|
export function getTranspileCacheFile(filePath) {
|
|
37
18
|
return join(getTranspileCacheDir(), `${hash(filePath)}.json`);
|
|
38
19
|
}
|
|
@@ -46,7 +27,6 @@ export function isTSConfigPath(filePath) {
|
|
|
46
27
|
return resolve(filePath) === getTSConfigPath();
|
|
47
28
|
}
|
|
48
29
|
export async function invalidateFileCaches(filePath) {
|
|
49
|
-
invalidateStatCache(filePath);
|
|
50
30
|
clearResolveCache();
|
|
51
31
|
if (isTSConfigPath(filePath)) {
|
|
52
32
|
await clearTranspileCache();
|
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import { fork } from 'node:child_process';
|
|
|
6
6
|
import { watch } from 'chokidar';
|
|
7
7
|
import { name, description, version } from './metadata.js';
|
|
8
8
|
import { clearResolveCache, invalidateFileCaches } from './cache.js';
|
|
9
|
-
import { debounce, yellow } from './util.js';
|
|
9
|
+
import { debounce, cyan, gray, red, yellow } from './util.js';
|
|
10
10
|
const DEFAULT_INCLUDE_PATHS = ['.'];
|
|
11
11
|
const DEFAULT_EXCLUDE_PATHS = ['dist', 'build', 'coverage'];
|
|
12
12
|
const DEFAULT_WATCH_EXTENSIONS = ['ts', 'tsx', 'js', 'jsx', 'json'];
|
|
@@ -20,7 +20,7 @@ program
|
|
|
20
20
|
.version(version, '-v, --version', 'Output the current version')
|
|
21
21
|
.showSuggestionAfterError();
|
|
22
22
|
function cleanup(signal) {
|
|
23
|
-
process.stdout.write(`${yellow(`Received ${signal}. Stopping watcher and child processes
|
|
23
|
+
process.stdout.write(`${yellow(`Received`)} ${cyan(signal)}${yellow('. Stopping watcher and child processes...')}\n`);
|
|
24
24
|
for (const child of children.values()) {
|
|
25
25
|
try {
|
|
26
26
|
child.kill('SIGTERM');
|
|
@@ -37,10 +37,14 @@ process.on('SIGINT', function () {
|
|
|
37
37
|
process.on('SIGTERM', function () {
|
|
38
38
|
cleanup('SIGTERM');
|
|
39
39
|
});
|
|
40
|
-
function spawn(entry, nodeArgs) {
|
|
40
|
+
function spawn(entry, nodeArgs, shouldLogTranspile) {
|
|
41
41
|
const entryPath = isAbsolute(entry) ? entry : resolve(process.cwd(), entry);
|
|
42
42
|
const child = fork(entryPath, {
|
|
43
43
|
stdio: 'inherit',
|
|
44
|
+
env: {
|
|
45
|
+
...process.env,
|
|
46
|
+
TSNITE_LOG_TRANSPILE: shouldLogTranspile ? '1' : '0'
|
|
47
|
+
},
|
|
44
48
|
execArgv: [
|
|
45
49
|
'--enable-source-maps',
|
|
46
50
|
'--no-experimental-strip-types',
|
|
@@ -135,7 +139,7 @@ async function handler(entry, options, nodeArgs, isWatch) {
|
|
|
135
139
|
async function restart(reason) {
|
|
136
140
|
process.stdout.write('\x1Bc');
|
|
137
141
|
if (reason) {
|
|
138
|
-
console.log(
|
|
142
|
+
console.log(gray(reason));
|
|
139
143
|
}
|
|
140
144
|
for (const child of children.values()) {
|
|
141
145
|
try {
|
|
@@ -147,7 +151,7 @@ async function handler(entry, options, nodeArgs, isWatch) {
|
|
|
147
151
|
const exited = await waitForChildExit(child);
|
|
148
152
|
if (exited)
|
|
149
153
|
continue;
|
|
150
|
-
console.log(yellow(
|
|
154
|
+
console.log(`${yellow('restart')} ${gray('process unresponsive, sending')} ${red('SIGKILL')}`);
|
|
151
155
|
try {
|
|
152
156
|
child.kill('SIGKILL');
|
|
153
157
|
}
|
|
@@ -156,14 +160,14 @@ async function handler(entry, options, nodeArgs, isWatch) {
|
|
|
156
160
|
}
|
|
157
161
|
}
|
|
158
162
|
clearResolveCache();
|
|
159
|
-
spawn(runtimeEntry, nodeArgs);
|
|
163
|
+
spawn(runtimeEntry, nodeArgs, isWatch);
|
|
160
164
|
}
|
|
161
165
|
const restartDebounced = debounce(restart, WATCH_DEBOUNCE_MS);
|
|
162
166
|
process.stdout.write('\x1Bc');
|
|
163
167
|
if (isWatch) {
|
|
164
168
|
console.log(yellow('Watching for changes...'));
|
|
165
169
|
}
|
|
166
|
-
spawn(runtimeEntry, nodeArgs);
|
|
170
|
+
spawn(runtimeEntry, nodeArgs, isWatch);
|
|
167
171
|
if (!isWatch)
|
|
168
172
|
return;
|
|
169
173
|
const { ignored, paths } = createWatchConfig(options);
|
|
@@ -178,8 +182,9 @@ async function handler(entry, options, nodeArgs, isWatch) {
|
|
|
178
182
|
eventName !== 'unlink') {
|
|
179
183
|
return;
|
|
180
184
|
}
|
|
181
|
-
|
|
182
|
-
|
|
185
|
+
const absoluteChangedPath = isAbsolute(changedPath) ? changedPath : (resolve(process.cwd(), changedPath));
|
|
186
|
+
await invalidateFileCaches(absoluteChangedPath);
|
|
187
|
+
restartDebounced();
|
|
183
188
|
});
|
|
184
189
|
}
|
|
185
190
|
function parseCsv(value) {
|
package/dist/loader.js
CHANGED
|
@@ -1,72 +1,25 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { transform } from 'oxc-transform';
|
|
2
|
+
import { ResolverFactory } from 'oxc-resolver';
|
|
2
3
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
3
4
|
import { readFile, stat, writeFile } from 'node:fs/promises';
|
|
4
|
-
import {
|
|
5
|
+
import { writeSync } from 'node:fs';
|
|
6
|
+
import { relative } from 'node:path';
|
|
5
7
|
import { createHash } from 'node:crypto';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
const tsconfigCache = { paths: null, baseUrl: null };
|
|
8
|
+
import { ensureTranspileCacheDir, getTranspileCacheFile, resolveCache } from './cache.js';
|
|
9
|
+
import { gray, green, yellow } from './util.js';
|
|
9
10
|
const transpileCache = new Map();
|
|
10
11
|
const MAX_TRANSPILE_CACHE_ENTRIES = 256;
|
|
11
12
|
const TS_EXTENSIONS = ['.cts', '.mts', '.tsx', '.ts'];
|
|
13
|
+
const SHOULD_LOG_TRANSPILE = process.env.TSNITE_LOG_TRANSPILE === '1';
|
|
14
|
+
const TRANSPILE_CONFIG_HASH = hash('target:es2024|jsx:automatic|decorator:legacy');
|
|
15
|
+
const resolver = new ResolverFactory({
|
|
16
|
+
conditionNames: ['node', 'import'],
|
|
17
|
+
extensions: ['.cts', '.mts', '.tsx', '.ts', '.cjs', '.mjs', '.js', '.json'],
|
|
18
|
+
tsconfig: 'auto'
|
|
19
|
+
});
|
|
12
20
|
function hasTypeScriptExtension(value) {
|
|
13
21
|
return TS_EXTENSIONS.some((extension) => value.endsWith(extension));
|
|
14
22
|
}
|
|
15
|
-
function isTypeScriptSpecifier(specifier) {
|
|
16
|
-
const extension = extname(specifier);
|
|
17
|
-
return extension === '' || hasTypeScriptExtension(extension);
|
|
18
|
-
}
|
|
19
|
-
function getTypeScriptTryFiles(basePath) {
|
|
20
|
-
return [
|
|
21
|
-
basePath,
|
|
22
|
-
...TS_EXTENSIONS.map((extension) => basePath + extension),
|
|
23
|
-
...TS_EXTENSIONS.map((extension) => join(basePath, 'index' + extension))
|
|
24
|
-
];
|
|
25
|
-
}
|
|
26
|
-
async function resolveTypeScriptFile(basePath) {
|
|
27
|
-
for (const file of getTypeScriptTryFiles(basePath)) {
|
|
28
|
-
if (await existsWithCache(file)) {
|
|
29
|
-
return file;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
function matchPathPattern(specifier, pattern) {
|
|
35
|
-
const starIndex = pattern.indexOf('*');
|
|
36
|
-
if (starIndex === -1) {
|
|
37
|
-
return specifier === pattern ? [] : null;
|
|
38
|
-
}
|
|
39
|
-
const prefix = pattern.slice(0, starIndex);
|
|
40
|
-
const suffix = pattern.slice(starIndex + 1);
|
|
41
|
-
if (!specifier.startsWith(prefix) || !specifier.endsWith(suffix)) {
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
return [specifier.slice(prefix.length, specifier.length - suffix.length)];
|
|
45
|
-
}
|
|
46
|
-
function applyPathMapping(target, matches) {
|
|
47
|
-
let mapped = target;
|
|
48
|
-
for (const match of matches) {
|
|
49
|
-
mapped = mapped.replace('*', match);
|
|
50
|
-
}
|
|
51
|
-
return mapped;
|
|
52
|
-
}
|
|
53
|
-
async function resolveTsConfigPath(specifier, paths, baseUrl) {
|
|
54
|
-
if (!paths || !baseUrl || !isTypeScriptSpecifier(specifier)) {
|
|
55
|
-
return null;
|
|
56
|
-
}
|
|
57
|
-
for (const [pattern, targets] of Object.entries(paths)) {
|
|
58
|
-
const matches = matchPathPattern(specifier, pattern);
|
|
59
|
-
if (!matches)
|
|
60
|
-
continue;
|
|
61
|
-
for (const target of targets) {
|
|
62
|
-
const mappedTarget = applyPathMapping(target, matches);
|
|
63
|
-
const resolved = await resolveTypeScriptFile(join(baseUrl, mappedTarget));
|
|
64
|
-
if (resolved)
|
|
65
|
-
return resolved;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
23
|
function hash(value) {
|
|
71
24
|
return createHash('sha1').update(value).digest('hex');
|
|
72
25
|
}
|
|
@@ -122,42 +75,11 @@ async function writeCachedTranspile(filename, entry) {
|
|
|
122
75
|
// Ignore cache write failures and continue with fresh output.
|
|
123
76
|
}
|
|
124
77
|
}
|
|
125
|
-
async function loadTSConfig() {
|
|
126
|
-
if (tsconfigCache.paths !== null && tsconfigCache.baseUrl !== null) {
|
|
127
|
-
return tsconfigCache;
|
|
128
|
-
}
|
|
129
|
-
try {
|
|
130
|
-
const data = await readFile(join(process.cwd(), 'tsconfig.json'), 'utf-8');
|
|
131
|
-
const { compilerOptions } = parse(data);
|
|
132
|
-
const paths = compilerOptions?.paths ?? null;
|
|
133
|
-
const baseUrl = compilerOptions?.baseUrl;
|
|
134
|
-
tsconfigCache.paths = paths || null;
|
|
135
|
-
tsconfigCache.baseUrl =
|
|
136
|
-
baseUrl ? join(process.cwd(), baseUrl) : process.cwd();
|
|
137
|
-
return tsconfigCache;
|
|
138
|
-
}
|
|
139
|
-
catch {
|
|
140
|
-
tsconfigCache.paths = null;
|
|
141
|
-
tsconfigCache.baseUrl = process.cwd();
|
|
142
|
-
return tsconfigCache;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
78
|
export async function resolve(specifier, ctx, next) {
|
|
146
|
-
|
|
147
|
-
const resolvedTsConfigPath = await resolveTsConfigPath(specifier, paths, baseUrl);
|
|
148
|
-
if (resolvedTsConfigPath) {
|
|
149
|
-
const url = pathToFileURL(resolvedTsConfigPath).href;
|
|
150
|
-
resolveCache.set(`${ctx.parentURL}::${specifier}`, url);
|
|
151
|
-
return {
|
|
152
|
-
url,
|
|
153
|
-
format: 'module',
|
|
154
|
-
shortCircuit: true
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
if (!specifier.startsWith('.') && !specifier.startsWith('/')) {
|
|
79
|
+
if (typeof ctx.parentURL !== 'string') {
|
|
158
80
|
return next(specifier, ctx);
|
|
159
81
|
}
|
|
160
|
-
if (
|
|
82
|
+
if (ctx.parentURL.startsWith('file://') === false) {
|
|
161
83
|
return next(specifier, ctx);
|
|
162
84
|
}
|
|
163
85
|
const cacheKey = `${ctx.parentURL}::${specifier}`;
|
|
@@ -173,28 +95,10 @@ export async function resolve(specifier, ctx, next) {
|
|
|
173
95
|
};
|
|
174
96
|
}
|
|
175
97
|
const parentPath = fileURLToPath(ctx.parentURL);
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
basePath + '.ts',
|
|
181
|
-
basePath + '.tsx',
|
|
182
|
-
basePath + '.mts',
|
|
183
|
-
basePath + '.cts',
|
|
184
|
-
basePath + '.js',
|
|
185
|
-
basePath + '.mjs',
|
|
186
|
-
basePath + '.cjs',
|
|
187
|
-
join(basePath, 'index.ts'),
|
|
188
|
-
join(basePath, 'index.tsx'),
|
|
189
|
-
join(basePath, 'index.mts'),
|
|
190
|
-
join(basePath, 'index.cts'),
|
|
191
|
-
join(basePath, 'index.js'),
|
|
192
|
-
join(basePath, 'index.mjs'),
|
|
193
|
-
join(basePath, 'index.cjs')
|
|
194
|
-
];
|
|
195
|
-
for (const file of tryFiles) {
|
|
196
|
-
if (await existsWithCache(file)) {
|
|
197
|
-
const url = pathToFileURL(file).href;
|
|
98
|
+
try {
|
|
99
|
+
const result = await resolver.resolveFileAsync(parentPath, specifier);
|
|
100
|
+
if (result.path) {
|
|
101
|
+
const url = pathToFileURL(result.path).href;
|
|
198
102
|
resolveCache.set(cacheKey, url);
|
|
199
103
|
return {
|
|
200
104
|
url,
|
|
@@ -203,6 +107,10 @@ export async function resolve(specifier, ctx, next) {
|
|
|
203
107
|
};
|
|
204
108
|
}
|
|
205
109
|
}
|
|
110
|
+
catch {
|
|
111
|
+
resolveCache.set(cacheKey, null);
|
|
112
|
+
return next(specifier, ctx);
|
|
113
|
+
}
|
|
206
114
|
resolveCache.set(cacheKey, null);
|
|
207
115
|
return next(specifier, ctx);
|
|
208
116
|
}
|
|
@@ -210,54 +118,37 @@ export async function load(url, ctx, next) {
|
|
|
210
118
|
if (!url.startsWith('file://') || !hasTypeScriptExtension(url)) {
|
|
211
119
|
return next(url, ctx);
|
|
212
120
|
}
|
|
213
|
-
const { paths, baseUrl } = await loadTSConfig();
|
|
214
121
|
const filename = fileURLToPath(url);
|
|
215
122
|
const fileStats = await stat(filename);
|
|
216
|
-
const
|
|
217
|
-
baseUrl: baseUrl || process.cwd(),
|
|
218
|
-
paths: paths ?? {}
|
|
219
|
-
}));
|
|
220
|
-
const cachedCode = await readCachedTranspile(filename, fileStats.mtimeMs, fileStats.size, configHash);
|
|
123
|
+
const cachedCode = await readCachedTranspile(filename, fileStats.mtimeMs, fileStats.size, TRANSPILE_CONFIG_HASH);
|
|
221
124
|
if (cachedCode !== null) {
|
|
222
125
|
return { format: 'module', source: cachedCode, shortCircuit: true };
|
|
223
126
|
}
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
tsx: url.endsWith('.tsx'),
|
|
231
|
-
decorators: true
|
|
232
|
-
},
|
|
233
|
-
target: 'es2022',
|
|
234
|
-
keepClassNames: true,
|
|
235
|
-
experimental: {
|
|
236
|
-
emitAssertForImportAttributes: true
|
|
237
|
-
},
|
|
238
|
-
transform: {
|
|
239
|
-
decoratorMetadata: true,
|
|
240
|
-
legacyDecorator: true,
|
|
241
|
-
react: {
|
|
242
|
-
runtime: 'automatic',
|
|
243
|
-
development: true
|
|
244
|
-
}
|
|
245
|
-
},
|
|
246
|
-
paths: paths ?? {}
|
|
247
|
-
},
|
|
248
|
-
module: {
|
|
249
|
-
type: 'es6',
|
|
250
|
-
strict: true,
|
|
251
|
-
outFileExtension: 'ts',
|
|
252
|
-
resolveFully: true
|
|
127
|
+
const source = await readFile(filename, 'utf8');
|
|
128
|
+
const transpileStart = Date.now();
|
|
129
|
+
const { code } = await transform(filename, source, {
|
|
130
|
+
jsx: {
|
|
131
|
+
runtime: 'automatic',
|
|
132
|
+
development: true
|
|
253
133
|
},
|
|
254
|
-
|
|
134
|
+
sourcemap: true,
|
|
135
|
+
sourceType: 'module',
|
|
136
|
+
target: 'es2024',
|
|
137
|
+
decorator: {
|
|
138
|
+
emitDecoratorMetadata: true,
|
|
139
|
+
legacy: true
|
|
140
|
+
}
|
|
255
141
|
});
|
|
142
|
+
if (SHOULD_LOG_TRANSPILE) {
|
|
143
|
+
const transpileTimeMs = Date.now() - transpileStart;
|
|
144
|
+
const displayPath = relative(process.cwd(), filename);
|
|
145
|
+
writeSync(1, `${gray('Transpiled')} ${yellow(displayPath)} ${gray('in')} ${green(`${transpileTimeMs}ms`)}\n`);
|
|
146
|
+
}
|
|
256
147
|
await writeCachedTranspile(filename, {
|
|
257
148
|
code,
|
|
258
149
|
mtimeMs: fileStats.mtimeMs,
|
|
259
150
|
size: fileStats.size,
|
|
260
|
-
configHash
|
|
151
|
+
configHash: TRANSPILE_CONFIG_HASH
|
|
261
152
|
});
|
|
262
153
|
return { format: 'module', source: code, shortCircuit: true };
|
|
263
154
|
}
|
package/dist/util.js
CHANGED
|
@@ -9,6 +9,39 @@ export function debounce(callback, waitMs) {
|
|
|
9
9
|
function color(code, value) {
|
|
10
10
|
return `\u001B[${code}m${value}\u001B[0m`;
|
|
11
11
|
}
|
|
12
|
+
function style(codes, value) {
|
|
13
|
+
return `\u001B[${codes.join(';')}m${value}\u001B[0m`;
|
|
14
|
+
}
|
|
12
15
|
export function yellow(value) {
|
|
13
16
|
return color(33, value);
|
|
14
17
|
}
|
|
18
|
+
export function purple(value) {
|
|
19
|
+
return color(95, value);
|
|
20
|
+
}
|
|
21
|
+
export function red(value) {
|
|
22
|
+
return color(31, value);
|
|
23
|
+
}
|
|
24
|
+
export function blue(value) {
|
|
25
|
+
return color(34, value);
|
|
26
|
+
}
|
|
27
|
+
export function white(value) {
|
|
28
|
+
return color(97, value);
|
|
29
|
+
}
|
|
30
|
+
export function pink(value) {
|
|
31
|
+
return color(95, value);
|
|
32
|
+
}
|
|
33
|
+
export function cyan(value) {
|
|
34
|
+
return color(96, value);
|
|
35
|
+
}
|
|
36
|
+
export function green(value) {
|
|
37
|
+
return color(32, value);
|
|
38
|
+
}
|
|
39
|
+
export function gray(value) {
|
|
40
|
+
return color(90, value);
|
|
41
|
+
}
|
|
42
|
+
export function bold(value) {
|
|
43
|
+
return style([1], value);
|
|
44
|
+
}
|
|
45
|
+
export function accent(value) {
|
|
46
|
+
return style([1, 36], value);
|
|
47
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tsnite",
|
|
3
3
|
"description": "TypeScript at full throttleβfast, safe, unstoppable. π",
|
|
4
|
-
"version": "0.1
|
|
4
|
+
"version": "0.2.1",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
7
7
|
"esm",
|
|
@@ -20,7 +20,6 @@
|
|
|
20
20
|
},
|
|
21
21
|
"type": "module",
|
|
22
22
|
"scripts": {
|
|
23
|
-
"prebuild": "rimraf dist",
|
|
24
23
|
"build": "tsc -p tsconfig.build.json",
|
|
25
24
|
"postbuild": "tsc-alias -f -p tsconfig.build.json",
|
|
26
25
|
"lint": "eslint . --cache",
|
|
@@ -32,9 +31,10 @@
|
|
|
32
31
|
"prepare": "husky"
|
|
33
32
|
},
|
|
34
33
|
"dependencies": {
|
|
35
|
-
"@swc/core": "^1.15.24",
|
|
36
34
|
"chokidar": "^5.0.0",
|
|
37
|
-
"commander": "^14.0.3"
|
|
35
|
+
"commander": "^14.0.3",
|
|
36
|
+
"oxc-resolver": "^11.19.1",
|
|
37
|
+
"oxc-transform": "^0.127.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@commitlint/cli": "^20.5.0",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"@eslint/js": "^10.0.1",
|
|
43
43
|
"@jest/globals": "^30.3.0",
|
|
44
44
|
"@swc/jest": "^0.2.39",
|
|
45
|
-
"eslint": "^10.2.
|
|
45
|
+
"eslint": "^10.2.1",
|
|
46
46
|
"eslint-plugin-jest": "^29.15.2",
|
|
47
47
|
"eslint-plugin-prettier": "^5.5.5",
|
|
48
48
|
"globals": "^17.5.0",
|
|
@@ -50,11 +50,10 @@
|
|
|
50
50
|
"jest": "^30.3.0",
|
|
51
51
|
"jest-environment-node": "^30.3.0",
|
|
52
52
|
"lint-staged": "^16.4.0",
|
|
53
|
-
"prettier": "^3.8.
|
|
54
|
-
"rimraf": "^6.1.3",
|
|
53
|
+
"prettier": "^3.8.3",
|
|
55
54
|
"tsc-alias": "^1.8.16",
|
|
56
|
-
"typescript": "^6.0.
|
|
57
|
-
"typescript-eslint": "^8.
|
|
55
|
+
"typescript": "^6.0.3",
|
|
56
|
+
"typescript-eslint": "^8.59.0"
|
|
58
57
|
},
|
|
59
58
|
"engines": {
|
|
60
59
|
"node": "^20.9.0 || ^22.11.0 || ^24.11.0"
|
package/dist/parse.js
DELETED
|
@@ -1,435 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* JSON5 parse β zero dependencies, ESM + TypeScript
|
|
3
|
-
*
|
|
4
|
-
* Suporta toda a spec JSON5:
|
|
5
|
-
* - Chaves sem aspas (identificadores ES5/Unicode)
|
|
6
|
-
* - Strings single ou double quoted com escapes e line continuation
|
|
7
|
-
* - ComentΓ‘rios // e /* ... * /
|
|
8
|
-
* - Trailing commas em objetos e arrays
|
|
9
|
-
* - NΓΊmeros: hex, Infinity, NaN, +/- leading/trailing decimal
|
|
10
|
-
* - Reviver opcional (mesmo comportamento do JSON.parse nativo)
|
|
11
|
-
*/
|
|
12
|
-
// βββ Helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
13
|
-
function err(ctx, msg) {
|
|
14
|
-
throw new SyntaxError(`JSON5: ${msg} at ${ctx.line}:${ctx.col}`);
|
|
15
|
-
}
|
|
16
|
-
function current(ctx) {
|
|
17
|
-
return ctx.src[ctx.pos] ?? '';
|
|
18
|
-
}
|
|
19
|
-
function peek(ctx, offset = 1) {
|
|
20
|
-
return ctx.src[ctx.pos + offset] ?? '';
|
|
21
|
-
}
|
|
22
|
-
function advance(ctx) {
|
|
23
|
-
const ch = ctx.src[ctx.pos++] ?? '';
|
|
24
|
-
if (ch === '\n') {
|
|
25
|
-
ctx.line++;
|
|
26
|
-
ctx.col = 1;
|
|
27
|
-
}
|
|
28
|
-
else {
|
|
29
|
-
ctx.col++;
|
|
30
|
-
}
|
|
31
|
-
return ch;
|
|
32
|
-
}
|
|
33
|
-
function startsWith(ctx, s) {
|
|
34
|
-
return ctx.src.startsWith(s, ctx.pos);
|
|
35
|
-
}
|
|
36
|
-
function consume(ctx, s) {
|
|
37
|
-
ctx.pos += s.length;
|
|
38
|
-
ctx.col += s.length;
|
|
39
|
-
}
|
|
40
|
-
// βββ Whitespace + Comments ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
41
|
-
const WHITESPACE = new Set([
|
|
42
|
-
' ',
|
|
43
|
-
'\t',
|
|
44
|
-
'\r',
|
|
45
|
-
'\n',
|
|
46
|
-
'\u00A0',
|
|
47
|
-
'\u2028',
|
|
48
|
-
'\u2029',
|
|
49
|
-
'\uFEFF'
|
|
50
|
-
]);
|
|
51
|
-
function skipWhitespaceAndComments(ctx) {
|
|
52
|
-
while (ctx.pos < ctx.src.length) {
|
|
53
|
-
const ch = current(ctx);
|
|
54
|
-
if (WHITESPACE.has(ch)) {
|
|
55
|
-
advance(ctx);
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
// ComentΓ‘rio de linha: //
|
|
59
|
-
if (ch === '/' && peek(ctx, 1) === '/') {
|
|
60
|
-
advance(ctx);
|
|
61
|
-
advance(ctx);
|
|
62
|
-
while (ctx.pos < ctx.src.length &&
|
|
63
|
-
current(ctx) !== '\n' &&
|
|
64
|
-
current(ctx) !== '\r' &&
|
|
65
|
-
current(ctx) !== '\u2028' &&
|
|
66
|
-
current(ctx) !== '\u2029')
|
|
67
|
-
advance(ctx);
|
|
68
|
-
continue;
|
|
69
|
-
}
|
|
70
|
-
// ComentΓ‘rio de bloco: /* ... */
|
|
71
|
-
if (ch === '/' && peek(ctx, 1) === '*') {
|
|
72
|
-
advance(ctx);
|
|
73
|
-
advance(ctx);
|
|
74
|
-
while (ctx.pos < ctx.src.length) {
|
|
75
|
-
if (current(ctx) === '*' && peek(ctx, 1) === '/') {
|
|
76
|
-
advance(ctx);
|
|
77
|
-
advance(ctx);
|
|
78
|
-
break;
|
|
79
|
-
}
|
|
80
|
-
advance(ctx);
|
|
81
|
-
}
|
|
82
|
-
continue;
|
|
83
|
-
}
|
|
84
|
-
break;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
// βββ String βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
88
|
-
function parseString(ctx) {
|
|
89
|
-
const quote = advance(ctx); // ' ou "
|
|
90
|
-
let result = '';
|
|
91
|
-
while (ctx.pos < ctx.src.length) {
|
|
92
|
-
const ch = current(ctx);
|
|
93
|
-
if (ch === quote) {
|
|
94
|
-
advance(ctx);
|
|
95
|
-
return result;
|
|
96
|
-
}
|
|
97
|
-
if (ch === '\\') {
|
|
98
|
-
advance(ctx);
|
|
99
|
-
const esc = advance(ctx);
|
|
100
|
-
switch (esc) {
|
|
101
|
-
case '"':
|
|
102
|
-
result += '"';
|
|
103
|
-
break;
|
|
104
|
-
case "'":
|
|
105
|
-
result += "'";
|
|
106
|
-
break;
|
|
107
|
-
case '\\':
|
|
108
|
-
result += '\\';
|
|
109
|
-
break;
|
|
110
|
-
case '/':
|
|
111
|
-
result += '/';
|
|
112
|
-
break;
|
|
113
|
-
case 'b':
|
|
114
|
-
result += '\b';
|
|
115
|
-
break;
|
|
116
|
-
case 'f':
|
|
117
|
-
result += '\f';
|
|
118
|
-
break;
|
|
119
|
-
case 'n':
|
|
120
|
-
result += '\n';
|
|
121
|
-
break;
|
|
122
|
-
case 'r':
|
|
123
|
-
result += '\r';
|
|
124
|
-
break;
|
|
125
|
-
case 't':
|
|
126
|
-
result += '\t';
|
|
127
|
-
break;
|
|
128
|
-
case 'v':
|
|
129
|
-
result += '\v';
|
|
130
|
-
break;
|
|
131
|
-
case '0':
|
|
132
|
-
if (/[0-9]/.test(current(ctx)))
|
|
133
|
-
err(ctx, 'Octal escapes are not allowed');
|
|
134
|
-
result += '\0';
|
|
135
|
-
break;
|
|
136
|
-
case 'x': {
|
|
137
|
-
const hex = ctx.src.slice(ctx.pos, ctx.pos + 2);
|
|
138
|
-
if (!/^[0-9a-fA-F]{2}$/.test(hex))
|
|
139
|
-
err(ctx, 'Invalid hex escape sequence');
|
|
140
|
-
result += String.fromCharCode(parseInt(hex, 16));
|
|
141
|
-
ctx.pos += 2;
|
|
142
|
-
ctx.col += 2;
|
|
143
|
-
break;
|
|
144
|
-
}
|
|
145
|
-
case 'u': {
|
|
146
|
-
if (current(ctx) === '{') {
|
|
147
|
-
// \u{XXXXXX}
|
|
148
|
-
advance(ctx);
|
|
149
|
-
let hexStr = '';
|
|
150
|
-
while (ctx.pos < ctx.src.length && current(ctx) !== '}')
|
|
151
|
-
hexStr += advance(ctx);
|
|
152
|
-
if (current(ctx) !== '}')
|
|
153
|
-
err(ctx, 'Unterminated unicode escape');
|
|
154
|
-
advance(ctx);
|
|
155
|
-
const cp = parseInt(hexStr, 16);
|
|
156
|
-
if (isNaN(cp) || cp > 0x10ffff)
|
|
157
|
-
err(ctx, `Invalid unicode code point: ${hexStr}`);
|
|
158
|
-
result += String.fromCodePoint(cp);
|
|
159
|
-
}
|
|
160
|
-
else {
|
|
161
|
-
// \uXXXX
|
|
162
|
-
const hex = ctx.src.slice(ctx.pos, ctx.pos + 4);
|
|
163
|
-
if (!/^[0-9a-fA-F]{4}$/.test(hex))
|
|
164
|
-
err(ctx, 'Invalid unicode escape sequence');
|
|
165
|
-
result += String.fromCharCode(parseInt(hex, 16));
|
|
166
|
-
ctx.pos += 4;
|
|
167
|
-
ctx.col += 4;
|
|
168
|
-
}
|
|
169
|
-
break;
|
|
170
|
-
}
|
|
171
|
-
// Line continuation
|
|
172
|
-
case '\n':
|
|
173
|
-
case '\u2028':
|
|
174
|
-
case '\u2029':
|
|
175
|
-
break;
|
|
176
|
-
case '\r':
|
|
177
|
-
if (current(ctx) === '\n')
|
|
178
|
-
advance(ctx);
|
|
179
|
-
break;
|
|
180
|
-
default:
|
|
181
|
-
result += esc;
|
|
182
|
-
}
|
|
183
|
-
continue;
|
|
184
|
-
}
|
|
185
|
-
if (ch === '\n' || ch === '\r' || ch === '\u2028' || ch === '\u2029') {
|
|
186
|
-
err(ctx, 'Unterminated string literal');
|
|
187
|
-
}
|
|
188
|
-
result += advance(ctx);
|
|
189
|
-
}
|
|
190
|
-
err(ctx, 'Unterminated string literal');
|
|
191
|
-
}
|
|
192
|
-
// βββ Identifier (chave sem aspas) βββββββββββββββββββββββββββββββββββββββββββββ
|
|
193
|
-
const IDENT_START = /[\p{L}\p{Nl}$_]/u;
|
|
194
|
-
const IDENT_PART = /[\p{L}\p{Nl}\p{Mn}\p{Mc}\p{Nd}\p{Pc}$_\u200C\u200D]/u;
|
|
195
|
-
function isIdentStart(ch) {
|
|
196
|
-
return IDENT_START.test(ch);
|
|
197
|
-
}
|
|
198
|
-
function isIdentPart(ch) {
|
|
199
|
-
return IDENT_PART.test(ch);
|
|
200
|
-
}
|
|
201
|
-
function parseIdentifier(ctx) {
|
|
202
|
-
const first = current(ctx);
|
|
203
|
-
if (!isIdentStart(first))
|
|
204
|
-
err(ctx, `Unexpected character: ${JSON.stringify(first)}`);
|
|
205
|
-
let name = advance(ctx);
|
|
206
|
-
while (ctx.pos < ctx.src.length && isIdentPart(current(ctx))) {
|
|
207
|
-
name += advance(ctx);
|
|
208
|
-
}
|
|
209
|
-
return name;
|
|
210
|
-
}
|
|
211
|
-
// βββ Number βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
212
|
-
function parseNumber(ctx) {
|
|
213
|
-
let numStr = '';
|
|
214
|
-
// Sinal opcional
|
|
215
|
-
if (current(ctx) === '+' || current(ctx) === '-') {
|
|
216
|
-
numStr += advance(ctx);
|
|
217
|
-
}
|
|
218
|
-
// Infinity
|
|
219
|
-
if (startsWith(ctx, 'Infinity')) {
|
|
220
|
-
consume(ctx, 'Infinity');
|
|
221
|
-
return numStr === '-' ? -Infinity : Infinity;
|
|
222
|
-
}
|
|
223
|
-
// NaN
|
|
224
|
-
if (startsWith(ctx, 'NaN')) {
|
|
225
|
-
consume(ctx, 'NaN');
|
|
226
|
-
return NaN;
|
|
227
|
-
}
|
|
228
|
-
// Hexadecimal: 0x / 0X
|
|
229
|
-
if (current(ctx) === '0' && (peek(ctx, 1) === 'x' || peek(ctx, 1) === 'X')) {
|
|
230
|
-
const sign = numStr; // pode ser '-' ou ''
|
|
231
|
-
numStr += advance(ctx); // '0'
|
|
232
|
-
numStr += advance(ctx); // 'x'
|
|
233
|
-
if (!/[0-9a-fA-F]/.test(current(ctx)))
|
|
234
|
-
err(ctx, 'Invalid hexadecimal number');
|
|
235
|
-
while (ctx.pos < ctx.src.length && /[0-9a-fA-F_]/.test(current(ctx))) {
|
|
236
|
-
const ch = advance(ctx);
|
|
237
|
-
if (ch !== '_')
|
|
238
|
-
numStr += ch;
|
|
239
|
-
}
|
|
240
|
-
const abs = parseInt(numStr, 16); // parseInt ignora o prefixo 0x corretamente
|
|
241
|
-
return sign === '-' ? -abs : abs;
|
|
242
|
-
}
|
|
243
|
-
// Parte inteira
|
|
244
|
-
while (ctx.pos < ctx.src.length && /[0-9_]/.test(current(ctx))) {
|
|
245
|
-
const ch = advance(ctx);
|
|
246
|
-
if (ch !== '_')
|
|
247
|
-
numStr += ch;
|
|
248
|
-
}
|
|
249
|
-
// Parte decimal
|
|
250
|
-
if (ctx.pos < ctx.src.length && current(ctx) === '.') {
|
|
251
|
-
numStr += advance(ctx);
|
|
252
|
-
while (ctx.pos < ctx.src.length && /[0-9_]/.test(current(ctx))) {
|
|
253
|
-
const ch = advance(ctx);
|
|
254
|
-
if (ch !== '_')
|
|
255
|
-
numStr += ch;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
// Expoente
|
|
259
|
-
if (ctx.pos < ctx.src.length &&
|
|
260
|
-
(current(ctx) === 'e' || current(ctx) === 'E')) {
|
|
261
|
-
numStr += advance(ctx);
|
|
262
|
-
if (current(ctx) === '+' || current(ctx) === '-')
|
|
263
|
-
numStr += advance(ctx);
|
|
264
|
-
while (ctx.pos < ctx.src.length && /[0-9_]/.test(current(ctx))) {
|
|
265
|
-
const ch = advance(ctx);
|
|
266
|
-
if (ch !== '_')
|
|
267
|
-
numStr += ch;
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
const n = Number(numStr);
|
|
271
|
-
if (isNaN(n))
|
|
272
|
-
err(ctx, `Invalid number: ${numStr}`);
|
|
273
|
-
return n;
|
|
274
|
-
}
|
|
275
|
-
// βββ Value ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
276
|
-
function parseValue(ctx) {
|
|
277
|
-
skipWhitespaceAndComments(ctx);
|
|
278
|
-
if (ctx.pos >= ctx.src.length)
|
|
279
|
-
err(ctx, 'Unexpected end of input');
|
|
280
|
-
const ch = current(ctx);
|
|
281
|
-
if (ch === '"' || ch === "'")
|
|
282
|
-
return parseString(ctx);
|
|
283
|
-
if (ch === '{')
|
|
284
|
-
return parseObject(ctx);
|
|
285
|
-
if (ch === '[')
|
|
286
|
-
return parseArray(ctx);
|
|
287
|
-
if (startsWith(ctx, 'true')) {
|
|
288
|
-
consume(ctx, 'true');
|
|
289
|
-
return true;
|
|
290
|
-
}
|
|
291
|
-
if (startsWith(ctx, 'false')) {
|
|
292
|
-
consume(ctx, 'false');
|
|
293
|
-
return false;
|
|
294
|
-
}
|
|
295
|
-
if (startsWith(ctx, 'null')) {
|
|
296
|
-
consume(ctx, 'null');
|
|
297
|
-
return null;
|
|
298
|
-
}
|
|
299
|
-
if (ch === '+' ||
|
|
300
|
-
ch === '-' ||
|
|
301
|
-
ch === '.' ||
|
|
302
|
-
(ch >= '0' && ch <= '9') ||
|
|
303
|
-
startsWith(ctx, 'Infinity') ||
|
|
304
|
-
startsWith(ctx, 'NaN'))
|
|
305
|
-
return parseNumber(ctx);
|
|
306
|
-
err(ctx, `Unexpected token: ${JSON.stringify(ch)}`);
|
|
307
|
-
}
|
|
308
|
-
// βββ Object βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
309
|
-
function parseObject(ctx) {
|
|
310
|
-
advance(ctx); // {
|
|
311
|
-
const obj = {};
|
|
312
|
-
skipWhitespaceAndComments(ctx);
|
|
313
|
-
if (current(ctx) === '}') {
|
|
314
|
-
advance(ctx);
|
|
315
|
-
return obj;
|
|
316
|
-
}
|
|
317
|
-
while (ctx.pos < ctx.src.length) {
|
|
318
|
-
skipWhitespaceAndComments(ctx);
|
|
319
|
-
if (ctx.pos >= ctx.src.length)
|
|
320
|
-
err(ctx, 'Unterminated object');
|
|
321
|
-
// Trailing comma
|
|
322
|
-
if (current(ctx) === '}') {
|
|
323
|
-
advance(ctx);
|
|
324
|
-
return obj;
|
|
325
|
-
}
|
|
326
|
-
// Chave: string ou identifier
|
|
327
|
-
const ch = current(ctx);
|
|
328
|
-
const key = ch === '"' || ch === "'" ? parseString(ctx) : parseIdentifier(ctx);
|
|
329
|
-
skipWhitespaceAndComments(ctx);
|
|
330
|
-
if (current(ctx) !== ':')
|
|
331
|
-
err(ctx, `Expected ':' after key ${JSON.stringify(key)}`);
|
|
332
|
-
advance(ctx); // :
|
|
333
|
-
const entry = [key, parseValue(ctx)];
|
|
334
|
-
obj[entry[0]] = entry[1];
|
|
335
|
-
skipWhitespaceAndComments(ctx);
|
|
336
|
-
if (current(ctx) === ',') {
|
|
337
|
-
advance(ctx);
|
|
338
|
-
continue;
|
|
339
|
-
}
|
|
340
|
-
if (current(ctx) === '}') {
|
|
341
|
-
advance(ctx);
|
|
342
|
-
return obj;
|
|
343
|
-
}
|
|
344
|
-
err(ctx, 'Expected "," or "}" in object');
|
|
345
|
-
}
|
|
346
|
-
err(ctx, 'Unterminated object');
|
|
347
|
-
}
|
|
348
|
-
// βββ Array ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
349
|
-
function parseArray(ctx) {
|
|
350
|
-
advance(ctx); // [
|
|
351
|
-
const arr = [];
|
|
352
|
-
skipWhitespaceAndComments(ctx);
|
|
353
|
-
if (current(ctx) === ']') {
|
|
354
|
-
advance(ctx);
|
|
355
|
-
return arr;
|
|
356
|
-
}
|
|
357
|
-
while (ctx.pos < ctx.src.length) {
|
|
358
|
-
skipWhitespaceAndComments(ctx);
|
|
359
|
-
if (ctx.pos >= ctx.src.length)
|
|
360
|
-
err(ctx, 'Unterminated array');
|
|
361
|
-
// Trailing comma
|
|
362
|
-
if (current(ctx) === ']') {
|
|
363
|
-
advance(ctx);
|
|
364
|
-
return arr;
|
|
365
|
-
}
|
|
366
|
-
arr.push(parseValue(ctx));
|
|
367
|
-
skipWhitespaceAndComments(ctx);
|
|
368
|
-
if (current(ctx) === ',') {
|
|
369
|
-
advance(ctx);
|
|
370
|
-
continue;
|
|
371
|
-
}
|
|
372
|
-
if (current(ctx) === ']') {
|
|
373
|
-
advance(ctx);
|
|
374
|
-
return arr;
|
|
375
|
-
}
|
|
376
|
-
err(ctx, 'Expected "," or "]" in array');
|
|
377
|
-
}
|
|
378
|
-
err(ctx, 'Unterminated array');
|
|
379
|
-
}
|
|
380
|
-
function applyReviver(reviver, holder, key) {
|
|
381
|
-
const val = holder[key];
|
|
382
|
-
if (val !== null && typeof val === 'object') {
|
|
383
|
-
const obj = val;
|
|
384
|
-
for (const k of Object.keys(obj)) {
|
|
385
|
-
const newVal = applyReviver(reviver, obj, k);
|
|
386
|
-
if (newVal === undefined)
|
|
387
|
-
delete obj[k];
|
|
388
|
-
else
|
|
389
|
-
obj[k] = newVal;
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
return reviver.call(holder, key, val);
|
|
393
|
-
}
|
|
394
|
-
// βββ API pΓΊblica ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
395
|
-
/**
|
|
396
|
-
* Faz o parse de uma string JSON5 e retorna o valor JavaScript correspondente.
|
|
397
|
-
*
|
|
398
|
-
* @param source - String JSON5 a ser parseada.
|
|
399
|
-
* @param reviver - FunΓ§Γ£o opcional chamada para cada par chave/valor (igual ao `JSON.parse`).
|
|
400
|
-
* @returns O valor JavaScript resultante tipado como `T`.
|
|
401
|
-
*
|
|
402
|
-
* @throws {SyntaxError} Se a string nΓ£o for JSON5 vΓ‘lido.
|
|
403
|
-
*
|
|
404
|
-
* @example
|
|
405
|
-
* ```ts
|
|
406
|
-
* // Sem generic β retorna Json5Value
|
|
407
|
-
* const raw = parse(`{ host: 'localhost' }`)
|
|
408
|
-
*
|
|
409
|
-
* // Com generic β cast para o tipo desejado
|
|
410
|
-
* interface TsConfig {
|
|
411
|
-
* compilerOptions: {
|
|
412
|
-
* paths: Record<string, string[]>
|
|
413
|
-
* baseUrl: string
|
|
414
|
-
* }
|
|
415
|
-
* }
|
|
416
|
-
* const { compilerOptions: { paths, baseUrl } } = parse<TsConfig>(data)
|
|
417
|
-
* ```
|
|
418
|
-
*/
|
|
419
|
-
export function parse(source, reviver) {
|
|
420
|
-
const ctx = {
|
|
421
|
-
src: String(source),
|
|
422
|
-
pos: 0,
|
|
423
|
-
line: 1,
|
|
424
|
-
col: 1
|
|
425
|
-
};
|
|
426
|
-
const result = parseValue(ctx);
|
|
427
|
-
skipWhitespaceAndComments(ctx);
|
|
428
|
-
if (ctx.pos < ctx.src.length) {
|
|
429
|
-
err(ctx, `Unexpected token after value: ${JSON.stringify(current(ctx))}`);
|
|
430
|
-
}
|
|
431
|
-
if (typeof reviver === 'function') {
|
|
432
|
-
return applyReviver(reviver, { '': result }, '');
|
|
433
|
-
}
|
|
434
|
-
return result;
|
|
435
|
-
}
|