tsnite 0.0.18 → 0.0.20
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/LICENSE +1 -1
- package/README.md +1 -1
- package/dist/cli.js +98 -30
- package/dist/loader.js +3 -3
- package/dist/metadata.js +4 -0
- package/dist/parse.js +435 -0
- package/package.json +6 -7
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -52,7 +52,7 @@ This will execute the specified TypeScript file, allowing you to quickly test an
|
|
|
52
52
|
{
|
|
53
53
|
"scripts": {
|
|
54
54
|
//...
|
|
55
|
-
"dev": "tsnite
|
|
55
|
+
"dev": "tsnite watch --include src --exclude uploads --ext js,ts,json src/index.ts"
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
```
|
package/dist/cli.js
CHANGED
|
@@ -1,22 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { program } from 'commander';
|
|
3
|
-
import { join } from 'node:path';
|
|
3
|
+
import { extname, join, resolve, relative } from 'node:path';
|
|
4
4
|
import { pathToFileURL } from 'node:url';
|
|
5
5
|
import { fork } from 'node:child_process';
|
|
6
6
|
import { watch } from 'chokidar';
|
|
7
|
-
import {
|
|
8
|
-
const
|
|
9
|
-
const
|
|
7
|
+
import { name, description, version } from './metadata.js';
|
|
8
|
+
const DEFAULT_INCLUDE_PATHS = ['.'];
|
|
9
|
+
const DEFAULT_EXCLUDE_PATHS = ['dist', 'build', 'coverage'];
|
|
10
|
+
const DEFAULT_WATCH_EXTENSIONS = ['ts', 'tsx', 'js', 'jsx', 'json'];
|
|
11
|
+
const INTERNAL_IGNORED_PATHS = ['node_modules', '.git'];
|
|
10
12
|
const pids = new Set();
|
|
11
13
|
program
|
|
12
14
|
.name(name)
|
|
13
15
|
.description(description)
|
|
14
16
|
.version(version, '-v, --version', 'Output the current version')
|
|
15
|
-
.option('--source-root <string>', 'Source Root', '.')
|
|
16
|
-
.option('--watch', 'Enables watch mode', false)
|
|
17
|
-
.option('--include-assets', 'Include static files in the build', false)
|
|
18
|
-
.argument('<string>', 'Entrypoint file')
|
|
19
|
-
.argument('[args...]', 'Specify the Node.js command and any additional arguments')
|
|
20
17
|
.showSuggestionAfterError();
|
|
21
18
|
function cleanup() {
|
|
22
19
|
for (const pid of pids.values()) {
|
|
@@ -24,17 +21,14 @@ function cleanup() {
|
|
|
24
21
|
process.kill(pid, 'SIGTERM');
|
|
25
22
|
}
|
|
26
23
|
catch {
|
|
27
|
-
//
|
|
24
|
+
//
|
|
28
25
|
}
|
|
29
26
|
}
|
|
30
27
|
process.exit(0);
|
|
31
28
|
}
|
|
32
29
|
process.on('SIGINT', cleanup);
|
|
33
30
|
process.on('SIGTERM', cleanup);
|
|
34
|
-
|
|
35
|
-
const options = program.opts();
|
|
36
|
-
const [entry, nodeArgs] = program.processedArgs;
|
|
37
|
-
process.stdout.write('\x1Bc');
|
|
31
|
+
function spawn(entry, nodeArgs) {
|
|
38
32
|
const { pid } = fork(join(process.cwd(), entry), {
|
|
39
33
|
stdio: 'inherit',
|
|
40
34
|
execArgv: [
|
|
@@ -46,12 +40,68 @@ async function handler() {
|
|
|
46
40
|
]
|
|
47
41
|
});
|
|
48
42
|
pids.add(pid);
|
|
49
|
-
|
|
43
|
+
}
|
|
44
|
+
function normalizePath(value) {
|
|
45
|
+
return value
|
|
46
|
+
.replace(/\\/g, '/')
|
|
47
|
+
.replace(/^\.\/+/, '')
|
|
48
|
+
.replace(/\/+$/, '');
|
|
49
|
+
}
|
|
50
|
+
function normalizeExt(value) {
|
|
51
|
+
return value.trim().replace(/^\./, '').toLowerCase();
|
|
52
|
+
}
|
|
53
|
+
function createWatchConfig(options) {
|
|
54
|
+
const sourceRoot = resolve(process.cwd(), options.sourceRoot);
|
|
55
|
+
const includePaths = options.include.map((value) => resolve(sourceRoot, value));
|
|
56
|
+
const excludePaths = options.exclude.map(normalizePath).filter(Boolean);
|
|
57
|
+
const excludeSet = new Set(excludePaths);
|
|
58
|
+
const allowedExts = new Set(options.ext.map(normalizeExt).filter(Boolean));
|
|
59
|
+
function ignored(filePath, stats) {
|
|
60
|
+
const rel = normalizePath(relative(sourceRoot, filePath));
|
|
61
|
+
// fora do sourceRoot
|
|
62
|
+
if (rel.startsWith('../')) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
// raiz do sourceRoot
|
|
66
|
+
if (rel === '') {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
const segments = rel.split('/');
|
|
70
|
+
// ignora por nome de pasta/arquivo em qualquer nível
|
|
71
|
+
if (segments.some((segment) => excludeSet.has(segment))) {
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
// ignora por caminho relativo completo
|
|
75
|
+
if (excludePaths.some((excluded) => rel === excluded || rel.startsWith(excluded + '/'))) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
// nunca filtra diretório por extensão
|
|
79
|
+
if (stats?.isDirectory()) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
const extension = extname(rel).slice(1).toLowerCase();
|
|
83
|
+
// se tiver lista de extensões, ignora arquivo fora dela
|
|
84
|
+
if (allowedExts.size > 0 && !allowedExts.has(extension)) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
sourceRoot,
|
|
91
|
+
paths: includePaths,
|
|
92
|
+
ignored
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
async function handler(entry, options, nodeArgs, isWatch) {
|
|
96
|
+
process.stdout.write('\x1Bc');
|
|
97
|
+
spawn(entry, nodeArgs);
|
|
98
|
+
if (!isWatch)
|
|
50
99
|
return;
|
|
51
|
-
const
|
|
100
|
+
const { ignored, paths } = createWatchConfig(options);
|
|
101
|
+
const watcher = watch(paths, {
|
|
52
102
|
atomic: true,
|
|
53
103
|
ignoreInitial: true,
|
|
54
|
-
ignored
|
|
104
|
+
ignored
|
|
55
105
|
});
|
|
56
106
|
watcher.on('change', async function () {
|
|
57
107
|
process.stdout.write('\x1Bc');
|
|
@@ -63,18 +113,36 @@ async function handler() {
|
|
|
63
113
|
//
|
|
64
114
|
}
|
|
65
115
|
}
|
|
66
|
-
|
|
67
|
-
stdio: 'inherit',
|
|
68
|
-
execArgv: [
|
|
69
|
-
'--enable-source-maps',
|
|
70
|
-
'--no-experimental-strip-types',
|
|
71
|
-
'--import',
|
|
72
|
-
pathToFileURL(join(import.meta.dirname, 'register.js')).href,
|
|
73
|
-
...nodeArgs
|
|
74
|
-
]
|
|
75
|
-
});
|
|
76
|
-
pids.add(pid);
|
|
116
|
+
spawn(entry, nodeArgs);
|
|
77
117
|
});
|
|
78
118
|
}
|
|
79
|
-
|
|
80
|
-
|
|
119
|
+
function parseCsv(value) {
|
|
120
|
+
return value
|
|
121
|
+
.split(',')
|
|
122
|
+
.map((item) => item.trim())
|
|
123
|
+
.filter(Boolean);
|
|
124
|
+
}
|
|
125
|
+
program
|
|
126
|
+
.argument('<string>', 'Entrypoint file')
|
|
127
|
+
.argument('[nodeArgs...]', 'Specify the Node.js command and any additional arguments')
|
|
128
|
+
.action(async function (entry, nodeArgs, options) {
|
|
129
|
+
await handler(entry, options, nodeArgs, false);
|
|
130
|
+
});
|
|
131
|
+
program
|
|
132
|
+
.command('watch')
|
|
133
|
+
.option('--include <string>', 'Paths to be watched', parseCsv, DEFAULT_INCLUDE_PATHS)
|
|
134
|
+
.option('--exclude <string>', 'Paths to be ignored by watched', parseCsv, [
|
|
135
|
+
...INTERNAL_IGNORED_PATHS,
|
|
136
|
+
...DEFAULT_EXCLUDE_PATHS
|
|
137
|
+
])
|
|
138
|
+
.option('--ext <string>', 'Extensions to be watched', parseCsv, DEFAULT_WATCH_EXTENSIONS)
|
|
139
|
+
.option('--source-root <string>', 'Source root to be watched', '.')
|
|
140
|
+
.argument('<string>', 'Entrypoint file')
|
|
141
|
+
.argument('[nodeArgs...]', 'Specify the Node.js command and any additional arguments')
|
|
142
|
+
.action(async function (entry, nodeArgs, options) {
|
|
143
|
+
await handler(entry, options, nodeArgs, true);
|
|
144
|
+
});
|
|
145
|
+
await program.parseAsync(process.argv).catch(function (err) {
|
|
146
|
+
console.error(err);
|
|
147
|
+
process.exit(1);
|
|
148
|
+
});
|
package/dist/loader.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { transformFile } from '@swc/core';
|
|
2
2
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
3
3
|
import { readFile } from 'node:fs/promises';
|
|
4
|
-
import json5 from 'json5';
|
|
5
4
|
import path from 'node:path';
|
|
5
|
+
import { parse } from './parse.js';
|
|
6
6
|
import { resolveCache, existsWithCache } from './cache.js';
|
|
7
7
|
const tsconfigCache = { paths: null, baseUrl: null };
|
|
8
8
|
async function loadTSConfig() {
|
|
@@ -11,9 +11,9 @@ async function loadTSConfig() {
|
|
|
11
11
|
}
|
|
12
12
|
try {
|
|
13
13
|
const data = await readFile(path.join(process.cwd(), 'tsconfig.json'), 'utf-8');
|
|
14
|
-
const { compilerOptions: { paths, baseUrl } } =
|
|
14
|
+
const { compilerOptions: { paths, baseUrl } } = parse(data);
|
|
15
15
|
tsconfigCache.paths = paths || null;
|
|
16
|
-
tsconfigCache.baseUrl = baseUrl || process.cwd();
|
|
16
|
+
tsconfigCache.baseUrl = path.join(process.cwd(), baseUrl) || process.cwd();
|
|
17
17
|
return tsconfigCache;
|
|
18
18
|
}
|
|
19
19
|
catch {
|
package/dist/metadata.js
ADDED
package/dist/parse.js
ADDED
|
@@ -0,0 +1,435 @@
|
|
|
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
|
+
}
|
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.0.
|
|
4
|
+
"version": "0.0.20",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|
|
7
7
|
"esm",
|
|
@@ -34,16 +34,15 @@
|
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@swc/core": "^1.15.18",
|
|
36
36
|
"chokidar": "^5.0.0",
|
|
37
|
-
"commander": "^14.0.3"
|
|
38
|
-
"json5": "^2.2.3"
|
|
37
|
+
"commander": "^14.0.3"
|
|
39
38
|
},
|
|
40
39
|
"devDependencies": {
|
|
41
|
-
"@commitlint/cli": "^20.4.
|
|
42
|
-
"@commitlint/config-conventional": "^20.4.
|
|
40
|
+
"@commitlint/cli": "^20.4.3",
|
|
41
|
+
"@commitlint/config-conventional": "^20.4.3",
|
|
43
42
|
"@eslint/js": "^10.0.1",
|
|
44
43
|
"@jest/globals": "^30.2.0",
|
|
45
44
|
"@swc/jest": "^0.2.39",
|
|
46
|
-
"eslint": "^10.0.
|
|
45
|
+
"eslint": "^10.0.3",
|
|
47
46
|
"eslint-plugin-jest": "^29.15.0",
|
|
48
47
|
"eslint-plugin-prettier": "^5.5.5",
|
|
49
48
|
"globals": "^17.4.0",
|
|
@@ -51,7 +50,7 @@
|
|
|
51
50
|
"jest": "^30.2.0",
|
|
52
51
|
"jest-environment-node": "^30.2.0",
|
|
53
52
|
"jest-mock-extended": "^4.0.0",
|
|
54
|
-
"lint-staged": "^16.3.
|
|
53
|
+
"lint-staged": "^16.3.2",
|
|
55
54
|
"prettier": "^3.8.1",
|
|
56
55
|
"rimraf": "^6.1.3",
|
|
57
56
|
"tsc-alias": "^1.8.16",
|