typenative 0.2.0 → 0.3.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/.github/workflows/ci.yml +38 -1
- package/.github/workflows/npm-publish-github-packages.yml +6 -1
- package/CHANGELOG.md +60 -0
- package/CONTRIBUTION.md +7 -5
- package/README.md +11 -7
- package/bin/cli.js +4 -0
- package/bin/index.js +53 -6
- package/bin/main.js +8 -4
- package/bin/node/system_extra.js +54 -1
- package/bin/node/timers.js +14 -1
- package/bin/node/tls.js +3 -1
- package/bin/parse-native.js +10 -0
- package/bin/parse-node.js +10 -0
- package/bin/transpiler.js +243 -51
- package/bin/typecheck-node.js +78 -0
- package/bin/version.js +1 -1
- package/conformance.md +2 -1
- package/package.json +2 -1
- package/tsparser/check.go +157 -0
- package/tsparser/main.go +30 -1
- package/types/typenative-npm.d.ts +7 -4
package/bin/transpiler.js
CHANGED
|
@@ -285,10 +285,16 @@ const SyntaxKind = {
|
|
|
285
285
|
// Local replacement for nanoid's customAlphabet — must stay transpilable by
|
|
286
286
|
// TypeNative itself (no dependencies), only using mapped String methods.
|
|
287
287
|
const GO_SAFE_ALPHABET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
288
|
+
// Ids come from a Park–Miller generator reset for every transpilation, so the
|
|
289
|
+
// same source always gives the same Go (reproducible builds; the self-hosted
|
|
290
|
+
// compiler's output can be compared with the Node CLI's). The products stay
|
|
291
|
+
// below 2^46, exact in a float64 in both runtimes.
|
|
292
|
+
let goSafeIdSeed = 1;
|
|
288
293
|
function goSafeId() {
|
|
289
294
|
let id = '';
|
|
290
295
|
for (let i = 0; i < 8; i++) {
|
|
291
|
-
|
|
296
|
+
goSafeIdSeed = (goSafeIdSeed * 16807) % 2147483647;
|
|
297
|
+
id += GO_SAFE_ALPHABET.charAt(goSafeIdSeed % GO_SAFE_ALPHABET.length);
|
|
292
298
|
}
|
|
293
299
|
return id;
|
|
294
300
|
}
|
|
@@ -398,6 +404,13 @@ const classStaticProps = new Set();
|
|
|
398
404
|
let fileResolver = null;
|
|
399
405
|
// Directory of the file currently being processed (null = main entry file)
|
|
400
406
|
let currentFileDir = null;
|
|
407
|
+
// Name of the file currently being parsed, as diagnostics and line directives show it
|
|
408
|
+
let currentFileName = '';
|
|
409
|
+
// Names of the parsed source files, indexed by the fileIndex stamped on each
|
|
410
|
+
// SourceFile node (line markers and diagnostics refer to them)
|
|
411
|
+
const sourceFileNames = [];
|
|
412
|
+
// Errors found while transpiling (unsupported syntax, unresolved imports)
|
|
413
|
+
const diagnostics = [];
|
|
401
414
|
// Tracks already-included files by a stable key to prevent duplicates/cycles
|
|
402
415
|
const includedLocalImports = new Set();
|
|
403
416
|
// Collects Go source files generated from local TS imports (filename → content)
|
|
@@ -454,6 +467,8 @@ function parseSource(code) {
|
|
|
454
467
|
lowerWeakCollections(sourceFile);
|
|
455
468
|
lowerGlobalThis(sourceFile);
|
|
456
469
|
linkParents(sourceFile, undefined);
|
|
470
|
+
sourceFile.fileIndex = sourceFileNames.length;
|
|
471
|
+
sourceFileNames.push(currentFileName);
|
|
457
472
|
for (const stmt of sourceFile.statements ?? []) {
|
|
458
473
|
if (!stmt.name || !isIdentifier(stmt.name))
|
|
459
474
|
continue;
|
|
@@ -472,6 +487,115 @@ function parseSource(code) {
|
|
|
472
487
|
}
|
|
473
488
|
return sourceFile;
|
|
474
489
|
}
|
|
490
|
+
// Index (into sourceFileNames) of the file a node was parsed from, or -1
|
|
491
|
+
function sourceFileIndexOf(node) {
|
|
492
|
+
let current = node;
|
|
493
|
+
while (current && current.kind !== 'SourceFile')
|
|
494
|
+
current = current.parent;
|
|
495
|
+
return current && typeof current.fileIndex === 'number' ? current.fileIndex : -1;
|
|
496
|
+
}
|
|
497
|
+
// Line markers: generated statements and declarations start with
|
|
498
|
+
// /*tn@<file>:<line>:<column>*/, which applyLineDirectives turns into Go
|
|
499
|
+
// /*line file.ts:line:column*/ directives once the code is complete, so Go
|
|
500
|
+
// compile errors and stack traces point at the TypeScript source. The marker
|
|
501
|
+
// carries no file name until then: names like strings.ts would otherwise look
|
|
502
|
+
// like uses of a Go package to the import pruning.
|
|
503
|
+
// A line holding only this sentinel becomes //line <go file>:<next line>,
|
|
504
|
+
// which returns the following generated code (helpers) to its real Go position
|
|
505
|
+
const LINE_RESET = '/*tn@' + 'reset*/';
|
|
506
|
+
function lineMarker(node) {
|
|
507
|
+
if (!node?.line)
|
|
508
|
+
return '';
|
|
509
|
+
const fileIndex = sourceFileIndexOf(node);
|
|
510
|
+
if (fileIndex < 0)
|
|
511
|
+
return '';
|
|
512
|
+
return `/*tn@${fileIndex}:${node.line}:${node.column}*/`;
|
|
513
|
+
}
|
|
514
|
+
// Prefixes generated code with its node's line marker (after any leading
|
|
515
|
+
// whitespace, so the position applies to the code's first token)
|
|
516
|
+
function withLineMarker(node, code) {
|
|
517
|
+
if (!code.trim())
|
|
518
|
+
return code;
|
|
519
|
+
const marker = lineMarker(node);
|
|
520
|
+
if (!marker)
|
|
521
|
+
return code;
|
|
522
|
+
const indent = code.length - code.trimStart().length;
|
|
523
|
+
return code.slice(0, indent) + marker + code.slice(indent);
|
|
524
|
+
}
|
|
525
|
+
// Replaces line markers with Go line directives and resets with //line
|
|
526
|
+
// directives back to the generated file (goFile, as Go should report it).
|
|
527
|
+
// One statement often spans several Go lines (assert → if + panic): every
|
|
528
|
+
// unmarked line after a marker repeats its directive, so those lines report
|
|
529
|
+
// the statement's line instead of counting on from it.
|
|
530
|
+
function applyLineDirectives(code, goFile) {
|
|
531
|
+
const lines = code.split('\n');
|
|
532
|
+
let pin = '';
|
|
533
|
+
// Inside a multi-line raw string or block comment, where nothing may be inserted
|
|
534
|
+
let state = '';
|
|
535
|
+
for (let index = 0; index < lines.length; index++) {
|
|
536
|
+
const text = lines[index];
|
|
537
|
+
if (text === LINE_RESET) {
|
|
538
|
+
lines[index] = `//line ${goFile}:${index + 2}`;
|
|
539
|
+
pin = '';
|
|
540
|
+
continue;
|
|
541
|
+
}
|
|
542
|
+
const startState = state;
|
|
543
|
+
if (text.includes('`') || text.includes('/*') || text.includes('*/'))
|
|
544
|
+
state = goLineEndState(text, state);
|
|
545
|
+
if (text.includes('/*tn@')) {
|
|
546
|
+
// (regex literals inline: a /g regex held in a variable compiles to a
|
|
547
|
+
// replace-first in the self-hosted compiler). Later lines repeat the
|
|
548
|
+
// line's last directive; text like "/*tn@" in a string matches nothing
|
|
549
|
+
lines[index] = text.replace(/\/\*tn@(\d+):(\d+):(\d+)\*\//g, (_match, file, line, column) => {
|
|
550
|
+
pin = `/*line ${sourceFileNames[Number(file)]}:${line}:${column}*/`;
|
|
551
|
+
return pin;
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
else if (pin && startState === '' && text.trim()) {
|
|
555
|
+
const indent = text.length - text.trimStart().length;
|
|
556
|
+
lines[index] = text.slice(0, indent) + pin + text.slice(indent);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
return lines.join('\n');
|
|
560
|
+
}
|
|
561
|
+
// The multi-line context after a Go line that starts in state: '' (code),
|
|
562
|
+
// '`' (inside a raw string) or '*' (inside a block comment)
|
|
563
|
+
function goLineEndState(line, state) {
|
|
564
|
+
let rest = line;
|
|
565
|
+
if (state !== '') {
|
|
566
|
+
const close = state === '`' ? '`' : '*/';
|
|
567
|
+
const end = rest.indexOf(close);
|
|
568
|
+
if (end < 0)
|
|
569
|
+
return state;
|
|
570
|
+
rest = rest.slice(end + close.length);
|
|
571
|
+
}
|
|
572
|
+
// Go literals and comments that end on this line, matched leftmost-first
|
|
573
|
+
// like Go's scanner (a quote inside a raw string is not a string): what's
|
|
574
|
+
// left after them is an unclosed raw string or comment
|
|
575
|
+
rest = rest.replace(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`[^`]*`|\/\*.*?\*\/|\/\/.*/g, ' ');
|
|
576
|
+
const rawString = rest.indexOf('`');
|
|
577
|
+
const comment = rest.indexOf('/*');
|
|
578
|
+
if (rawString < 0 && comment < 0)
|
|
579
|
+
return '';
|
|
580
|
+
if (comment < 0 || (rawString >= 0 && rawString < comment))
|
|
581
|
+
return '`';
|
|
582
|
+
return '*';
|
|
583
|
+
}
|
|
584
|
+
// Records a transpile error at the node (or its nearest positioned ancestor)
|
|
585
|
+
function reportError(node, message) {
|
|
586
|
+
let positioned = node;
|
|
587
|
+
while (positioned && !positioned.line)
|
|
588
|
+
positioned = positioned.parent;
|
|
589
|
+
const fileIndex = sourceFileIndexOf(node);
|
|
590
|
+
const line = positioned ? positioned.line : 0;
|
|
591
|
+
const column = positioned ? positioned.column : 0;
|
|
592
|
+
diagnostics.push({
|
|
593
|
+
file: fileIndex >= 0 ? sourceFileNames[fileIndex] : currentFileName,
|
|
594
|
+
line: line,
|
|
595
|
+
column: column,
|
|
596
|
+
message: message
|
|
597
|
+
});
|
|
598
|
+
}
|
|
475
599
|
// `this: T` parameters only type `this`; they are not real parameters
|
|
476
600
|
function stripThisParameters(node) {
|
|
477
601
|
if (Array.isArray(node.parameters)) {
|
|
@@ -834,12 +958,20 @@ const usedHelpers = new Set();
|
|
|
834
958
|
// per-file import lists can exclude them (helpers are emitted once, in the
|
|
835
959
|
// main file, which carries these imports instead).
|
|
836
960
|
const helperProvidedPackages = new Set();
|
|
961
|
+
// GOOS of the current transpilation (see platformHelper)
|
|
962
|
+
let targetGoos = 'linux';
|
|
837
963
|
// Whether the file being parsed is .tsx
|
|
838
964
|
let currentFileIsTsx = false;
|
|
839
965
|
export function transpileToNative(code, options) {
|
|
840
966
|
fileResolver = options?.readFile ?? null;
|
|
841
967
|
parseFunction = options?.parse ?? defaultParseFunction;
|
|
842
968
|
currentFileIsTsx = !!options?.tsx;
|
|
969
|
+
currentFileName = options?.fileName ?? (currentFileIsTsx ? 'main.tsx' : 'main.ts');
|
|
970
|
+
const goFile = options?.goFile ?? 'code.go';
|
|
971
|
+
targetGoos = options?.goos ?? 'linux';
|
|
972
|
+
sourceFileNames.length = 0;
|
|
973
|
+
diagnostics.length = 0;
|
|
974
|
+
goSafeIdSeed = 1;
|
|
843
975
|
consoleGroupsUsed = code.includes('console.group');
|
|
844
976
|
declaredFunctions.clear();
|
|
845
977
|
declaredInterfaces.clear();
|
|
@@ -890,7 +1022,7 @@ export function transpileToNative(code, options) {
|
|
|
890
1022
|
helperProvidedPackages.clear();
|
|
891
1023
|
mainPackageVariables.length = 0;
|
|
892
1024
|
const transpiledCode = visit(sourceFile, { addFunctionOutside: true });
|
|
893
|
-
const transpiledCodeOutside = outsideNodes.map((n) => visit(n, { isOutside: true })).join('\n');
|
|
1025
|
+
const transpiledCodeOutside = outsideNodes.map((n) => withLineMarker(n, visit(n, { isOutside: true }))).join('\n');
|
|
894
1026
|
// Keep the process alive until detached async work and timers settle
|
|
895
1027
|
const drains = [];
|
|
896
1028
|
// The event loop runs until no timers, async work or open handles remain
|
|
@@ -899,7 +1031,7 @@ export function transpileToNative(code, options) {
|
|
|
899
1031
|
// process.exitCode
|
|
900
1032
|
if (usedHelpers.has('nodeProcess'))
|
|
901
1033
|
drains.push('TnProcessExit()');
|
|
902
|
-
const drainCode = drains.length > 0 ? `\n\t${drains.join('\n\t')}` : '';
|
|
1034
|
+
const drainCode = drains.length > 0 ? `\n${LINE_RESET}\n\t${drains.join('\n\t')}` : '';
|
|
903
1035
|
// Go bridge types are qualified by path internally (net/http.Cookie)
|
|
904
1036
|
const mainCode = localizeGoPaths(transpiledCode);
|
|
905
1037
|
const outsideCode = localizeGoPaths(transpiledCodeOutside);
|
|
@@ -929,14 +1061,19 @@ export function transpileToNative(code, options) {
|
|
|
929
1061
|
${mainImports.sort().map((pkg) => importLine(pkg)).join('\n')}
|
|
930
1062
|
|
|
931
1063
|
${packageVariables.join('\n')}
|
|
932
|
-
|
|
1064
|
+
${LINE_RESET}
|
|
933
1065
|
func main() {
|
|
934
1066
|
${body}${drainCode}
|
|
935
1067
|
}
|
|
936
1068
|
|
|
937
1069
|
${outsideCode.trim()}
|
|
1070
|
+
${LINE_RESET}
|
|
938
1071
|
${helpersCode}`.trimEnd();
|
|
939
|
-
return {
|
|
1072
|
+
return {
|
|
1073
|
+
main: applyLineDirectives(main, goFile),
|
|
1074
|
+
files: localImportFiles,
|
|
1075
|
+
diagnostics: diagnostics.slice()
|
|
1076
|
+
};
|
|
940
1077
|
}
|
|
941
1078
|
export // Property map of an interface, including inherited properties (flattened —
|
|
942
1079
|
function collectInterfaceProperties(iface, into, seen = new Set()) {
|
|
@@ -975,7 +1112,7 @@ function visit(node, options = {}) {
|
|
|
975
1112
|
}
|
|
976
1113
|
if (isSourceFile(node)) {
|
|
977
1114
|
return (node.statements ?? [])
|
|
978
|
-
.map((n) => visit(n, { addFunctionOutside: true }))
|
|
1115
|
+
.map((n) => withLineMarker(n, visit(n, { addFunctionOutside: true })))
|
|
979
1116
|
.filter((n) => !!n)
|
|
980
1117
|
.join(options.inline ? '' : '\n\t');
|
|
981
1118
|
}
|
|
@@ -2038,13 +2175,13 @@ function visit(node, options = {}) {
|
|
|
2038
2175
|
const thenTerm = node.elseStatement ? '' : ';';
|
|
2039
2176
|
const thenCode = withNarrowing(getNarrowedNames(node.expression, true), () => isBlock(node.thenStatement)
|
|
2040
2177
|
? visit(node.thenStatement, { inline: !!node.elseStatement })
|
|
2041
|
-
: `{\n${visit(node.thenStatement)}\n}${thenTerm}`);
|
|
2178
|
+
: `{\n${withLineMarker(node.thenStatement, visit(node.thenStatement))}\n}${thenTerm}`);
|
|
2042
2179
|
const condition = `if ${toGoCondition(node.expression)} ${thenCode}`;
|
|
2043
2180
|
if (node.elseStatement) {
|
|
2044
2181
|
// else-if chains stay chained; other single statements get a block
|
|
2045
2182
|
const elseCode = withNarrowing(getNarrowedNames(node.expression, false), () => isBlock(node.elseStatement) || isIfStatement(node.elseStatement)
|
|
2046
2183
|
? visit(node.elseStatement)
|
|
2047
|
-
: `{\n${visit(node.elseStatement)}\n};\n\t`);
|
|
2184
|
+
: `{\n${withLineMarker(node.elseStatement, visit(node.elseStatement))}\n};\n\t`);
|
|
2048
2185
|
return `${condition} else ${elseCode}`;
|
|
2049
2186
|
}
|
|
2050
2187
|
return condition;
|
|
@@ -2512,7 +2649,7 @@ function visit(node, options = {}) {
|
|
|
2512
2649
|
}
|
|
2513
2650
|
const methodParameterInfo = getFunctionParametersInfo(member.parameters ?? []);
|
|
2514
2651
|
const returnType = member.type ? ` ${getType(member.type)}` : '';
|
|
2515
|
-
copiedMethods.push(
|
|
2652
|
+
copiedMethods.push(`${lineMarker(member)}func (self *${name}${typeParamNames}) ${methodName}(${methodParameterInfo.signature})${returnType} ${visit(member.body, { prefixBlockContent: methodParameterInfo.prefixBlockContent })}\n`);
|
|
2516
2653
|
}
|
|
2517
2654
|
ancestorIndex--;
|
|
2518
2655
|
}
|
|
@@ -2543,7 +2680,7 @@ function visit(node, options = {}) {
|
|
|
2543
2680
|
const genBody = visit(member.body, { prefixBlockContent: methodParameterInfo.prefixBlockContent });
|
|
2544
2681
|
asyncChannelVar = prevChannel;
|
|
2545
2682
|
asyncFunctionNode = prevFn;
|
|
2546
|
-
result +=
|
|
2683
|
+
result += `${lineMarker(member)}func (self *${name}${typeParamNames}) ${genName}(${methodParameterInfo.signature}) chan ${yieldType} {\n\t__ch := make(chan ${yieldType})\n\tgo func() {\n\t\tdefer close(__ch)\n\t\t${genBody.trim()}\n\t}()\n\treturn __ch\n}\n\n`;
|
|
2547
2684
|
}
|
|
2548
2685
|
continue;
|
|
2549
2686
|
}
|
|
@@ -2561,23 +2698,23 @@ function visit(node, options = {}) {
|
|
|
2561
2698
|
}
|
|
2562
2699
|
else if (isStatic) {
|
|
2563
2700
|
// Static methods become package-level functions named ClassName_methodName
|
|
2564
|
-
result +=
|
|
2701
|
+
result += `${lineMarker(member)}func ${name}_${methodName}(${methodParameterInfo.signature})${returnType} ${visit(member.body, { prefixBlockContent: methodParameterInfo.prefixBlockContent })}\n\n`;
|
|
2565
2702
|
}
|
|
2566
2703
|
else {
|
|
2567
|
-
result +=
|
|
2704
|
+
result += `${lineMarker(member)}func (self *${name}${typeParamNames}) ${methodName}(${methodParameterInfo.signature})${returnType} ${visit(member.body, { prefixBlockContent: methodParameterInfo.prefixBlockContent })}\n\n`;
|
|
2568
2705
|
}
|
|
2569
2706
|
}
|
|
2570
2707
|
else if (isGetAccessor(member)) {
|
|
2571
2708
|
// Getter: get prop() { ... } → func (self *T) Get_prop() RetType { ... }
|
|
2572
2709
|
const getterName = visit(member.name);
|
|
2573
2710
|
const returnType = member.type ? ` ${getType(member.type)}` : ' interface{}';
|
|
2574
|
-
result +=
|
|
2711
|
+
result += `${lineMarker(member)}func (self *${name}${typeParamNames}) Get_${getterName}()${returnType} ${visit(member.body)}\n\n`;
|
|
2575
2712
|
}
|
|
2576
2713
|
else if (isSetAccessor(member)) {
|
|
2577
2714
|
// Setter: set prop(val) { ... } → func (self *T) Set_prop(val ValType) { ... }
|
|
2578
2715
|
const setterName = visit(member.name);
|
|
2579
2716
|
const parameterInfo = getFunctionParametersInfo(member.parameters ?? []);
|
|
2580
|
-
result +=
|
|
2717
|
+
result += `${lineMarker(member)}func (self *${name}${typeParamNames}) Set_${setterName}(${parameterInfo.signature}) ${visit(member.body, { prefixBlockContent: parameterInfo.prefixBlockContent })}\n\n`;
|
|
2581
2718
|
}
|
|
2582
2719
|
}
|
|
2583
2720
|
result += copiedMethods.join('\n');
|
|
@@ -2760,10 +2897,16 @@ function visit(node, options = {}) {
|
|
|
2760
2897
|
else if (isExportDeclaration(node) || isExportAssignment(node)) {
|
|
2761
2898
|
return '';
|
|
2762
2899
|
}
|
|
2900
|
+
else if (node.kind === 'EmptyStatement' || node.kind === 'DebuggerStatement') {
|
|
2901
|
+
// `;` and `debugger` do nothing in a native binary
|
|
2902
|
+
return '';
|
|
2903
|
+
}
|
|
2763
2904
|
const syntaxKind = node.kind;
|
|
2764
2905
|
if (!['VariableStatement', 'EndOfFileToken'].includes(syntaxKind)) {
|
|
2765
|
-
|
|
2766
|
-
|
|
2906
|
+
// The Go output would be incomplete: an error, not a guess (its children
|
|
2907
|
+
// are not visited, which would only report follow-on errors)
|
|
2908
|
+
reportError(node, `Unsupported syntax: ${syntaxKind}`);
|
|
2909
|
+
return '';
|
|
2767
2910
|
}
|
|
2768
2911
|
for (const child of childNodes(node)) {
|
|
2769
2912
|
code += visit(child);
|
|
@@ -3006,10 +3149,10 @@ function visitBlockStatements(statements) {
|
|
|
3006
3149
|
for (let index = 0; index < statements.length; index++) {
|
|
3007
3150
|
const statement = statements[index];
|
|
3008
3151
|
if (isUsingDeclaration(statement)) {
|
|
3009
|
-
parts.push(visitUsingDeclaration(statement, statements.slice(index + 1)));
|
|
3152
|
+
parts.push(withLineMarker(statement, visitUsingDeclaration(statement, statements.slice(index + 1))));
|
|
3010
3153
|
break;
|
|
3011
3154
|
}
|
|
3012
|
-
parts.push(visit(statement));
|
|
3155
|
+
parts.push(withLineMarker(statement, visit(statement)));
|
|
3013
3156
|
if (isIfStatement(statement) && !statement.elseStatement && alwaysExits(statement.thenStatement)) {
|
|
3014
3157
|
for (const name of getNarrowedNames(statement.expression, false)) {
|
|
3015
3158
|
if (!narrowedVariables.has(name)) {
|
|
@@ -8057,11 +8200,13 @@ function normalizeCjsToEsm(code) {
|
|
|
8057
8200
|
code = code.replace(/(?:module\.exports|exports)\.(\w+)\s*=\s*function\s*\w*\s*\(/g, 'export function $1(');
|
|
8058
8201
|
return code;
|
|
8059
8202
|
}
|
|
8060
|
-
function includeLocalImport(code, dir, goFileName, tsx = false) {
|
|
8203
|
+
function includeLocalImport(code, dir, goFileName, tsx = false, fileName = '') {
|
|
8061
8204
|
const prevDir = currentFileDir;
|
|
8062
8205
|
const prevTsx = currentFileIsTsx;
|
|
8206
|
+
const prevFileName = currentFileName;
|
|
8063
8207
|
currentFileDir = dir;
|
|
8064
8208
|
currentFileIsTsx = tsx;
|
|
8209
|
+
currentFileName = fileName;
|
|
8065
8210
|
// Normalize CommonJS modules to ES module syntax before parsing
|
|
8066
8211
|
if (!code.includes('export ') && (code.includes('module.exports') || code.includes('exports.'))) {
|
|
8067
8212
|
code = normalizeCjsToEsm(code);
|
|
@@ -8074,6 +8219,7 @@ function includeLocalImport(code, dir, goFileName, tsx = false) {
|
|
|
8074
8219
|
}
|
|
8075
8220
|
currentFileDir = prevDir;
|
|
8076
8221
|
currentFileIsTsx = prevTsx;
|
|
8222
|
+
currentFileName = prevFileName;
|
|
8077
8223
|
return;
|
|
8078
8224
|
}
|
|
8079
8225
|
// Separate-file mode (local TS imports): transpile to its own Go file.
|
|
@@ -8088,19 +8234,23 @@ function includeLocalImport(code, dir, goFileName, tsx = false) {
|
|
|
8088
8234
|
importedPackages.clear();
|
|
8089
8235
|
helperProvidedPackages.clear();
|
|
8090
8236
|
const sf = parseSource(code);
|
|
8091
|
-
const
|
|
8237
|
+
const isDeclarationLine = (line) => /^(var|const|type|func|import)\b/.test(line.trim());
|
|
8238
|
+
const declarationLines = [];
|
|
8239
|
+
const statementLines = [];
|
|
8092
8240
|
const prevEmittingModuleFile = emittingModuleFile;
|
|
8093
8241
|
emittingModuleFile = true;
|
|
8094
8242
|
for (const stmt of sf.statements) {
|
|
8095
8243
|
const result = visit(stmt, { addFunctionOutside: true });
|
|
8096
|
-
if (result.trim())
|
|
8097
|
-
|
|
8244
|
+
if (!result.trim())
|
|
8245
|
+
continue;
|
|
8246
|
+
const line = withLineMarker(stmt, localizeGoPaths(result));
|
|
8247
|
+
if (isDeclarationLine(result))
|
|
8248
|
+
declarationLines.push(line);
|
|
8249
|
+
else
|
|
8250
|
+
statementLines.push(line);
|
|
8098
8251
|
}
|
|
8099
8252
|
emittingModuleFile = prevEmittingModuleFile;
|
|
8100
|
-
const fileOutside = localizeGoPaths(outsideNodes.map((n) => visit(n, { isOutside: true })).join('\n'));
|
|
8101
|
-
const isDeclarationLine = (line) => /^(var|const|type|func|import)\b/.test(line.trim());
|
|
8102
|
-
const declarationLines = inlineLines.filter(isDeclarationLine);
|
|
8103
|
-
const statementLines = inlineLines.filter((line) => !isDeclarationLine(line));
|
|
8253
|
+
const fileOutside = localizeGoPaths(outsideNodes.map((n) => withLineMarker(n, visit(n, { isOutside: true }))).join('\n'));
|
|
8104
8254
|
const fileInline = [
|
|
8105
8255
|
...declarationLines,
|
|
8106
8256
|
...(statementLines.length > 0 ? [`func init() {\n\t${statementLines.join('\n\t')}\n}`] : [])
|
|
@@ -8128,7 +8278,7 @@ function includeLocalImport(code, dir, goFileName, tsx = false) {
|
|
|
8128
8278
|
parts.push(fileInline.trim());
|
|
8129
8279
|
if (fileOutside.trim())
|
|
8130
8280
|
parts.push(fileOutside.trim());
|
|
8131
|
-
localImportFiles.set(goFileName, parts.join('\n\n'));
|
|
8281
|
+
localImportFiles.set(goFileName, applyLineDirectives(parts.join('\n\n'), goFileName));
|
|
8132
8282
|
// Restore main-file state; helper-provided packages carry over so the main
|
|
8133
8283
|
// file (which carries the helper sources) imports them.
|
|
8134
8284
|
outsideNodes = savedOutsideNodes;
|
|
@@ -8141,6 +8291,7 @@ function includeLocalImport(code, dir, goFileName, tsx = false) {
|
|
|
8141
8291
|
for (const p of helperProvidedPackages)
|
|
8142
8292
|
importedPackages.add(p);
|
|
8143
8293
|
currentFileDir = prevDir;
|
|
8294
|
+
currentFileName = prevFileName;
|
|
8144
8295
|
}
|
|
8145
8296
|
// Go source without its string literals (for detecting real package use)
|
|
8146
8297
|
function stripGoStrings(code) {
|
|
@@ -8263,6 +8414,16 @@ const helperRequires = {
|
|
|
8263
8414
|
asyncWait: ['eventLoop'],
|
|
8264
8415
|
eventLoop: []
|
|
8265
8416
|
};
|
|
8417
|
+
// Helpers with OS-specific variants (Windows APIs that would not compile
|
|
8418
|
+
// elsewhere) are required as '<id>@os': the target OS's variant
|
|
8419
|
+
// ('<id>@windows') when there is one, else '<id>@other'
|
|
8420
|
+
function platformHelper(id) {
|
|
8421
|
+
if (!id.endsWith('@os'))
|
|
8422
|
+
return id;
|
|
8423
|
+
const base = id.slice(0, id.length - 3);
|
|
8424
|
+
const specific = `${base}@${targetGoos}`;
|
|
8425
|
+
return specific in goHelpers || specific in nodeRegistry.goHelpers ? specific : `${base}@other`;
|
|
8426
|
+
}
|
|
8266
8427
|
function useHelper(id) {
|
|
8267
8428
|
if (usedHelpers.has(id))
|
|
8268
8429
|
return;
|
|
@@ -8272,7 +8433,7 @@ function useHelper(id) {
|
|
|
8272
8433
|
interfacePropertyTypes.set('TnMatch', new Map([['groups', '*TnMap[string, string]']]));
|
|
8273
8434
|
}
|
|
8274
8435
|
for (const dep of helperRequires[id] ?? nodeRegistry.helperRequires[id] ?? [])
|
|
8275
|
-
useHelper(dep);
|
|
8436
|
+
useHelper(platformHelper(dep));
|
|
8276
8437
|
for (const pkg of helperPackages[id] ?? nodeRegistry.helperPackages[id] ?? []) {
|
|
8277
8438
|
importedPackages.add(pkg);
|
|
8278
8439
|
helperProvidedPackages.add(pkg);
|
|
@@ -8368,7 +8529,7 @@ const helperPackages = {
|
|
|
8368
8529
|
namedGroups: ['regexp'],
|
|
8369
8530
|
moduleUrl: ['os'],
|
|
8370
8531
|
asyncWait: [],
|
|
8371
|
-
timerWait: ['sync', 'sync/atomic', 'time'],
|
|
8532
|
+
timerWait: ['sort', 'sync', 'sync/atomic', 'time'],
|
|
8372
8533
|
clock: ['time'],
|
|
8373
8534
|
eventLoop: ['sync', 'sync/atomic', 'time'],
|
|
8374
8535
|
uri: ['strings', 'fmt', 'net/url'],
|
|
@@ -10614,7 +10775,46 @@ type TnTimeout struct {
|
|
|
10614
10775
|
fn func()
|
|
10615
10776
|
refd bool
|
|
10616
10777
|
active bool
|
|
10617
|
-
|
|
10778
|
+
due time.Time
|
|
10779
|
+
seq int
|
|
10780
|
+
}
|
|
10781
|
+
|
|
10782
|
+
// Armed timers, owned by the baton holder. An expired timer's goroutine only
|
|
10783
|
+
// wakes the loop, which then runs every due timer in deadline order: timer
|
|
10784
|
+
// goroutines that expire together (a starved loop, a coarse OS clock) may post
|
|
10785
|
+
// in any order, but Node fires timers in deadline order.
|
|
10786
|
+
var __tnPendingTimers = map[*TnTimeout]struct{}{}
|
|
10787
|
+
var __tnTimerSeq int
|
|
10788
|
+
|
|
10789
|
+
func tnRunDueTimers() {
|
|
10790
|
+
now := time.Now()
|
|
10791
|
+
var due []*TnTimeout
|
|
10792
|
+
for t := range __tnPendingTimers {
|
|
10793
|
+
if !t.active {
|
|
10794
|
+
delete(__tnPendingTimers, t)
|
|
10795
|
+
} else if !t.due.After(now) {
|
|
10796
|
+
due = append(due, t)
|
|
10797
|
+
}
|
|
10798
|
+
}
|
|
10799
|
+
sort.Slice(due, func(i, j int) bool {
|
|
10800
|
+
if !due[i].due.Equal(due[j].due) {
|
|
10801
|
+
return due[i].due.Before(due[j].due)
|
|
10802
|
+
}
|
|
10803
|
+
return due[i].seq < due[j].seq
|
|
10804
|
+
})
|
|
10805
|
+
for _, t := range due {
|
|
10806
|
+
// An earlier callback may have cleared or refreshed it
|
|
10807
|
+
if !t.active || t.due.After(now) {
|
|
10808
|
+
continue
|
|
10809
|
+
}
|
|
10810
|
+
delete(__tnPendingTimers, t)
|
|
10811
|
+
t.active = false
|
|
10812
|
+
if t.refd {
|
|
10813
|
+
t.refd = false
|
|
10814
|
+
__tnTimers.Done()
|
|
10815
|
+
}
|
|
10816
|
+
t.fn()
|
|
10817
|
+
}
|
|
10618
10818
|
}
|
|
10619
10819
|
|
|
10620
10820
|
// node:test mock timers: when set (and the API is mocked) it takes over a timer;
|
|
@@ -10643,21 +10843,12 @@ func tnSetTimeout(kind string, ms float64, fn func()) *TnTimeout {
|
|
|
10643
10843
|
}
|
|
10644
10844
|
|
|
10645
10845
|
func (t *TnTimeout) arm() {
|
|
10646
|
-
|
|
10647
|
-
|
|
10648
|
-
|
|
10649
|
-
|
|
10650
|
-
|
|
10651
|
-
|
|
10652
|
-
}
|
|
10653
|
-
t.active = false
|
|
10654
|
-
if t.refd {
|
|
10655
|
-
t.refd = false
|
|
10656
|
-
__tnTimers.Done()
|
|
10657
|
-
}
|
|
10658
|
-
t.fn()
|
|
10659
|
-
})
|
|
10660
|
-
})
|
|
10846
|
+
__tnTimerSeq++
|
|
10847
|
+
t.seq = __tnTimerSeq
|
|
10848
|
+
d := time.Duration(t.ms * float64(time.Millisecond))
|
|
10849
|
+
t.due = time.Now().Add(d)
|
|
10850
|
+
__tnPendingTimers[t] = struct{}{}
|
|
10851
|
+
t.timer = time.AfterFunc(d, func() { TnLoopPost(tnRunDueTimers) })
|
|
10661
10852
|
}
|
|
10662
10853
|
|
|
10663
10854
|
func (t *TnTimeout) ref() *TnTimeout {
|
|
@@ -10797,6 +10988,7 @@ func (t *TnInterval) close() *TnInterval {
|
|
|
10797
10988
|
func TnClearTimeout(t *TnTimeout) {
|
|
10798
10989
|
if t != nil && t.active {
|
|
10799
10990
|
t.active = false
|
|
10991
|
+
delete(__tnPendingTimers, t)
|
|
10800
10992
|
if t.timer != nil {
|
|
10801
10993
|
t.timer.Stop()
|
|
10802
10994
|
}
|
|
@@ -11547,8 +11739,6 @@ function nodeModuleName(spec) {
|
|
|
11547
11739
|
name = nodeRegistry.aliases[name];
|
|
11548
11740
|
if (nodeRegistry.modules[name])
|
|
11549
11741
|
return name;
|
|
11550
|
-
if (prefixed)
|
|
11551
|
-
console.warn(`[TypeNative] Unsupported Node.js module: ${spec}`);
|
|
11552
11742
|
return '';
|
|
11553
11743
|
}
|
|
11554
11744
|
// Binds the import's local names to registry keys
|
|
@@ -12117,7 +12307,7 @@ function visitImportDeclaration(node) {
|
|
|
12117
12307
|
if (resolved && !includedLocalImports.has(fileKey)) {
|
|
12118
12308
|
includedLocalImports.add(fileKey);
|
|
12119
12309
|
const goFileName = specifierToGoFileName(moduleSpec);
|
|
12120
|
-
includeLocalImport(resolved.content, resolved.dir, goFileName, !!resolved.tsx);
|
|
12310
|
+
includeLocalImport(resolved.content, resolved.dir, goFileName, !!resolved.tsx, resolved.path ?? moduleSpec);
|
|
12121
12311
|
}
|
|
12122
12312
|
}
|
|
12123
12313
|
// Renamed bindings refer to the original declaration (one Go package)
|
|
@@ -12143,11 +12333,13 @@ function visitImportDeclaration(node) {
|
|
|
12143
12333
|
setupNodeModuleImport(node, nodeModule);
|
|
12144
12334
|
return '';
|
|
12145
12335
|
}
|
|
12146
|
-
if (moduleSpec.startsWith('node:'))
|
|
12147
|
-
return '';
|
|
12148
12336
|
// Type-only imports contribute nothing to runtime
|
|
12149
12337
|
if (node.importClause?.isTypeOnly)
|
|
12150
12338
|
return '';
|
|
12339
|
+
if (moduleSpec.startsWith('node:')) {
|
|
12340
|
+
reportError(node, `Unsupported Node.js module: ${moduleSpec} (see conformance.md#node-builtins-node)`);
|
|
12341
|
+
return '';
|
|
12342
|
+
}
|
|
12151
12343
|
// Go standard library: `import { Println } from 'go:fmt'` → import "fmt"
|
|
12152
12344
|
if (moduleSpec.startsWith('go:')) {
|
|
12153
12345
|
const goPkg = moduleSpec.slice(3);
|
|
@@ -12172,7 +12364,7 @@ function visitImportDeclaration(node) {
|
|
|
12172
12364
|
const resolved = fileResolver(moduleSpec, currentFileDir);
|
|
12173
12365
|
if (resolved) {
|
|
12174
12366
|
const goFileName = specifierToGoFileName(moduleSpec);
|
|
12175
|
-
includeLocalImport(resolved.content, resolved.dir, goFileName);
|
|
12367
|
+
includeLocalImport(resolved.content, resolved.dir, goFileName, !!resolved.tsx, resolved.path ?? moduleSpec);
|
|
12176
12368
|
}
|
|
12177
12369
|
}
|
|
12178
12370
|
// Register default/namespace import name as a "stripped" namespace
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// Node-side type-checking: builds a TypeScript program for the entry file with
|
|
2
|
+
// the `typescript` package and returns its errors. The self-hosted binary runs
|
|
3
|
+
// the same check with typescript-go (`tsparser --check`, tsparser/check.go).
|
|
4
|
+
//
|
|
5
|
+
// Node-only module: injected into run() by the Node CLI wrapper (src/cli.ts).
|
|
6
|
+
import ts from 'typescript';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
// The bundled declarations that replace the default lib (types/ in the package)
|
|
9
|
+
const TYPENATIVE_TYPES = ['typenative.d.ts', 'typenative-go.d.ts', 'typenative-npm.d.ts'];
|
|
10
|
+
// TS18003: "No inputs were found in config file" (only the entry is checked)
|
|
11
|
+
const NO_INPUTS_FOUND = 18003;
|
|
12
|
+
// Type-checks entry with the nearest tsconfig.json's options (or TypeNative's
|
|
13
|
+
// defaults, typed by typesDir). The program's roots are the entry plus the
|
|
14
|
+
// config's .d.ts files, so other entry points in the same project don't
|
|
15
|
+
// redeclare each other's globals.
|
|
16
|
+
export function typeCheckNode(entry, typesDir) {
|
|
17
|
+
const entryPath = path.resolve(entry);
|
|
18
|
+
const roots = [entryPath];
|
|
19
|
+
const out = [];
|
|
20
|
+
let options;
|
|
21
|
+
const configPath = ts.findConfigFile(path.dirname(entryPath), ts.sys.fileExists);
|
|
22
|
+
if (configPath) {
|
|
23
|
+
const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, {
|
|
24
|
+
...ts.sys,
|
|
25
|
+
onUnRecoverableConfigFileDiagnostic: (d) => out.push(toDiagnostic(d))
|
|
26
|
+
});
|
|
27
|
+
if (!parsed)
|
|
28
|
+
return out;
|
|
29
|
+
for (const d of parsed.errors) {
|
|
30
|
+
if (d.code !== NO_INPUTS_FOUND)
|
|
31
|
+
out.push(toDiagnostic(d));
|
|
32
|
+
}
|
|
33
|
+
options = parsed.options;
|
|
34
|
+
for (const name of parsed.fileNames) {
|
|
35
|
+
if (name.endsWith('.d.ts'))
|
|
36
|
+
roots.push(name);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
options = {
|
|
41
|
+
target: ts.ScriptTarget.ES2020,
|
|
42
|
+
module: ts.ModuleKind.ESNext,
|
|
43
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
44
|
+
lib: [],
|
|
45
|
+
types: [],
|
|
46
|
+
strict: true,
|
|
47
|
+
noImplicitAny: true,
|
|
48
|
+
allowSyntheticDefaultImports: true,
|
|
49
|
+
allowImportingTsExtensions: true,
|
|
50
|
+
jsx: ts.JsxEmit.Preserve,
|
|
51
|
+
skipLibCheck: true
|
|
52
|
+
};
|
|
53
|
+
for (const name of TYPENATIVE_TYPES)
|
|
54
|
+
roots.push(path.join(typesDir, name));
|
|
55
|
+
}
|
|
56
|
+
const program = ts.createProgram(roots, { ...options, noEmit: true });
|
|
57
|
+
for (const d of ts.getPreEmitDiagnostics(program)) {
|
|
58
|
+
if (d.category === ts.DiagnosticCategory.Error)
|
|
59
|
+
out.push(toDiagnostic(d));
|
|
60
|
+
}
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
function toDiagnostic(d) {
|
|
64
|
+
const out = {
|
|
65
|
+
file: '',
|
|
66
|
+
line: 0,
|
|
67
|
+
column: 0,
|
|
68
|
+
code: d.code,
|
|
69
|
+
message: ts.flattenDiagnosticMessageText(d.messageText, '\n')
|
|
70
|
+
};
|
|
71
|
+
if (d.file && d.start !== undefined) {
|
|
72
|
+
const start = d.file.getLineAndCharacterOfPosition(d.start);
|
|
73
|
+
out.file = d.file.fileName;
|
|
74
|
+
out.line = start.line + 1;
|
|
75
|
+
out.column = start.character + 1;
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
package/bin/version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Generated by scripts/gen-version.mjs — do not edit.
|
|
2
|
-
export const VERSION = '0.
|
|
2
|
+
export const VERSION = '0.3.1';
|
package/conformance.md
CHANGED
|
@@ -537,7 +537,7 @@ async function runs synchronously until its first `await`.
|
|
|
537
537
|
| -------------------------------------------------------------------------------------------- | :----: | -------------------------------------------- | ----------------------------------------------------------------------- |
|
|
538
538
|
| `platform` / `arch` / `type` / `machine` / `EOL` / `devNull` / `endianness` | ✅ | [lib-node-os.ts](conformance/lib-node-os.ts) | Node's names (`win32`, `x64`, `Windows_NT`, `x86_64`) |
|
|
539
539
|
| `hostname` / `homedir` / `tmpdir` / `userInfo` / `cpus` / `availableParallelism` / `networkInterfaces` / `constants.signals` | ✅ | [lib-node-os.ts](conformance/lib-node-os.ts) | `cpus()` has one entry per logical CPU (model / speed / times are zero) |
|
|
540
|
-
| `totalmem` / `freemem` / `uptime` / `loadavg` / `release` / `version` | ✅ | [lib-node-os-mem.ts](conformance/lib-node-os-mem.ts) | `/proc` on Linux, `sysctl` on macOS / BSD
|
|
540
|
+
| `totalmem` / `freemem` / `uptime` / `loadavg` / `release` / `version` | ✅ | [lib-node-os-mem.ts](conformance/lib-node-os-mem.ts) | `/proc` on Linux, `sysctl` on macOS / BSD, `GlobalMemoryStatusEx` / `GetTickCount64` on Windows (`loadavg` is zeros there, as in Node) |
|
|
541
541
|
|
|
542
542
|
#### `node:process`
|
|
543
543
|
|
|
@@ -569,6 +569,7 @@ async function runs synchronously until its first `await`.
|
|
|
569
569
|
| `promisify` / `callbackify` | ✅ | [lib-node-util-promisify.ts](conformance/lib-node-util-promisify.ts) | Node functions map to their promise versions at compile time (`fs.readFile`, `setTimeout`, `exec`); other functions are wrapped |
|
|
570
570
|
| `parseArgs` (short / long / grouped flags, `multiple`, `default`, `strict`, `allowPositionals`, `allowNegative`, `tokens`, Node's errors), `getSystemErrorName` / `getSystemErrorMessage` / `getSystemErrorMap`, `MIMEType` / `MIMEParams`, `parseEnv` (+ `process.loadEnvFile`), `aborted`, `debuglog` (`NODE_DEBUG`), `toUSVString` | ✅ | [lib-node-util-parse-args.ts](conformance/lib-node-util-parse-args.ts) | |
|
|
571
571
|
| `inherits` (legacy), `inspect.custom` (symbols), `getCallSites` (V8 stack frames) | ❌ | — | Legacy or engine-specific |
|
|
572
|
+
| Node's errno numbers on Windows (libuv's: `ENOENT` is `-4058`) | ❌ | — | errno values are the OS's (Go's `syscall` numbers): the same as Node's on Linux and macOS, but `ENOENT` is `-2` on Windows. `getSystemErrorName(err.errno)` still names any error TypeNative raises |
|
|
572
573
|
|
|
573
574
|
#### `node:assert`
|
|
574
575
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "typenative",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Build native applications using Typescript.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"check:go-types": "tsc -p conformance/tsconfig.go-check.json",
|
|
19
19
|
"conformance": "node scripts/conformance.mjs",
|
|
20
20
|
"conformance:parallel": "node scripts/conformance-parallel.mjs",
|
|
21
|
+
"bootstrap:check": "node scripts/bootstrap-check.mjs",
|
|
21
22
|
"test": "npm run conformance",
|
|
22
23
|
"release": "npm publish"
|
|
23
24
|
},
|