@stacksjs/zig-dtsx 0.9.17 → 0.9.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/package.json +2 -2
- package/src/extractors.zig +77 -4
- package/test/zig-dtsx.test.ts +27 -2
- package/zig-out/bin/zig-dtsx +0 -0
- package/zig-out/bin/zig-dtsx.exe +0 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacksjs/zig-dtsx",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.9.
|
|
4
|
+
"version": "0.9.20",
|
|
5
5
|
"description": "High-performance DTS emitter written in Zig",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
@@ -18,6 +18,6 @@
|
|
|
18
18
|
"benchmark": "bun run test/benchmark.ts"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@stacksjs/dtsx": "0.9.
|
|
21
|
+
"@stacksjs/dtsx": "0.9.20"
|
|
22
22
|
}
|
|
23
23
|
}
|
package/src/extractors.zig
CHANGED
|
@@ -555,11 +555,21 @@ pub fn buildDtsParams(s: *Scanner, raw_params: []const u8) []const u8 {
|
|
|
555
555
|
var str_ch: u8 = 0;
|
|
556
556
|
var skip_next = false;
|
|
557
557
|
|
|
558
|
+
var in_block_comment = false;
|
|
559
|
+
var skip_to_eol = false;
|
|
558
560
|
for (inner, 0..) |c, i| {
|
|
559
561
|
if (skip_next) {
|
|
560
562
|
skip_next = false;
|
|
561
563
|
continue;
|
|
562
564
|
}
|
|
565
|
+
if (skip_to_eol) {
|
|
566
|
+
if (c == '\n') skip_to_eol = false;
|
|
567
|
+
continue;
|
|
568
|
+
}
|
|
569
|
+
if (in_block_comment) {
|
|
570
|
+
if (c == '/' and i > 0 and inner[i - 1] == '*') in_block_comment = false;
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
563
573
|
if (in_str) {
|
|
564
574
|
if (c == ch.CH_BACKSLASH) {
|
|
565
575
|
skip_next = true; // skip the escaped character
|
|
@@ -568,6 +578,20 @@ pub fn buildDtsParams(s: *Scanner, raw_params: []const u8) []const u8 {
|
|
|
568
578
|
if (c == str_ch) in_str = false;
|
|
569
579
|
continue;
|
|
570
580
|
}
|
|
581
|
+
// Skip block and line comments — JSDoc prose can contain unmatched
|
|
582
|
+
// quote chars (e.g. apostrophe in "error's") that would otherwise
|
|
583
|
+
// trip the scanner into a string-literal mode it never escapes.
|
|
584
|
+
if (c == '/' and i + 1 < inner.len) {
|
|
585
|
+
const nc = inner[i + 1];
|
|
586
|
+
if (nc == '*') {
|
|
587
|
+
in_block_comment = true;
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
if (nc == '/') {
|
|
591
|
+
skip_to_eol = true;
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
571
595
|
if (c == ch.CH_SQUOTE or c == ch.CH_DQUOTE or c == ch.CH_BACKTICK) {
|
|
572
596
|
in_str = true;
|
|
573
597
|
str_ch = c;
|
|
@@ -655,11 +679,21 @@ pub fn buildSingleDtsParam(s: *Scanner, raw: []const u8) []const u8 {
|
|
|
655
679
|
var str_ch2: u8 = 0;
|
|
656
680
|
var skip_next2 = false;
|
|
657
681
|
|
|
682
|
+
var in_block_comment2 = false;
|
|
683
|
+
var skip_to_eol2 = false;
|
|
658
684
|
for (p, 0..) |c, i| {
|
|
659
685
|
if (skip_next2) {
|
|
660
686
|
skip_next2 = false;
|
|
661
687
|
continue;
|
|
662
688
|
}
|
|
689
|
+
if (skip_to_eol2) {
|
|
690
|
+
if (c == '\n') skip_to_eol2 = false;
|
|
691
|
+
continue;
|
|
692
|
+
}
|
|
693
|
+
if (in_block_comment2) {
|
|
694
|
+
if (c == '/' and i > 0 and p[i - 1] == '*') in_block_comment2 = false;
|
|
695
|
+
continue;
|
|
696
|
+
}
|
|
663
697
|
if (in_str2) {
|
|
664
698
|
if (c == ch.CH_BACKSLASH) {
|
|
665
699
|
skip_next2 = true;
|
|
@@ -668,6 +702,19 @@ pub fn buildSingleDtsParam(s: *Scanner, raw: []const u8) []const u8 {
|
|
|
668
702
|
if (c == str_ch2) in_str2 = false;
|
|
669
703
|
continue;
|
|
670
704
|
}
|
|
705
|
+
// Skip block and line comments before string-mode detection so JSDoc
|
|
706
|
+
// apostrophes (e.g. "error's") don't trigger an unclosed string.
|
|
707
|
+
if (c == '/' and i + 1 < p.len) {
|
|
708
|
+
const nc = p[i + 1];
|
|
709
|
+
if (nc == '*') {
|
|
710
|
+
in_block_comment2 = true;
|
|
711
|
+
continue;
|
|
712
|
+
}
|
|
713
|
+
if (nc == '/') {
|
|
714
|
+
skip_to_eol2 = true;
|
|
715
|
+
continue;
|
|
716
|
+
}
|
|
717
|
+
}
|
|
671
718
|
if (c == ch.CH_SQUOTE or c == ch.CH_DQUOTE or c == ch.CH_BACKTICK) {
|
|
672
719
|
in_str2 = true;
|
|
673
720
|
str_ch2 = c;
|
|
@@ -763,6 +810,30 @@ fn cleanDestructuredPattern(alloc: std.mem.Allocator, pattern: []const u8) []con
|
|
|
763
810
|
while (i < pattern.len) : (i += 1) {
|
|
764
811
|
const c = pattern[i];
|
|
765
812
|
|
|
813
|
+
// Preserve block and line comments verbatim (useful JSDoc) but skip
|
|
814
|
+
// parsing inside them so apostrophes in prose (e.g. "error's") don't
|
|
815
|
+
// put us in an inescapable string mode.
|
|
816
|
+
if (!in_str and c == '/' and i + 1 < pattern.len) {
|
|
817
|
+
const nc = pattern[i + 1];
|
|
818
|
+
if (nc == '*') {
|
|
819
|
+
const start = i;
|
|
820
|
+
var j = i + 2;
|
|
821
|
+
while (j + 1 < pattern.len and !(pattern[j] == '*' and pattern[j + 1] == '/')) : (j += 1) {}
|
|
822
|
+
const end = if (j + 1 < pattern.len) j + 2 else pattern.len;
|
|
823
|
+
result.appendSlice(pattern[start..end]) catch {};
|
|
824
|
+
i = end - 1; // loop's += 1 advances past
|
|
825
|
+
continue;
|
|
826
|
+
}
|
|
827
|
+
if (nc == '/') {
|
|
828
|
+
const start = i;
|
|
829
|
+
var j = i;
|
|
830
|
+
while (j < pattern.len and pattern[j] != '\n') : (j += 1) {}
|
|
831
|
+
result.appendSlice(pattern[start..j]) catch {};
|
|
832
|
+
i = if (j == 0) 0 else j - 1; // loop's += 1 lands on the newline
|
|
833
|
+
continue;
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
766
837
|
// String tracking
|
|
767
838
|
if (!in_str and (c == '\'' or c == '"' or c == '`')) {
|
|
768
839
|
in_str = true;
|
|
@@ -934,16 +1005,18 @@ pub fn extractFunction(s: *Scanner, decl_start: usize, is_exported: bool, is_asy
|
|
|
934
1005
|
|
|
935
1006
|
// Build DTS text — single alloc + memcpy is cheaper than ArrayList init +
|
|
936
1007
|
// multiple appendSlice + toOwnedSlice for the fixed-shape function header.
|
|
937
|
-
|
|
938
|
-
|
|
1008
|
+
// `export default function foo()` keeps its `default` keyword in DTS:
|
|
1009
|
+
// `declare` is not allowed alongside `default`, and emitting
|
|
1010
|
+
// `export declare function ...` would silently drop the default-export
|
|
1011
|
+
// signal that consumers (and the dtsx TS reference impl) preserve.
|
|
1012
|
+
const export_prefix: []const u8 = if (is_default) "export default function " else if (is_exported) "export declare function " else "declare function ";
|
|
939
1013
|
const colon_sep = ": ";
|
|
940
|
-
const total = export_prefix.len +
|
|
1014
|
+
const total = export_prefix.len + func_name.len + generics.len +
|
|
941
1015
|
dts_params.len + colon_sep.len + return_type.len + 1; // +1 for ';'
|
|
942
1016
|
const dts_text = blk: {
|
|
943
1017
|
const buf = s.allocator.alloc(u8, total) catch break :blk @as([]const u8, "");
|
|
944
1018
|
var tp: usize = 0;
|
|
945
1019
|
@memcpy(buf[tp..][0..export_prefix.len], export_prefix); tp += export_prefix.len;
|
|
946
|
-
@memcpy(buf[tp..][0..declare_kw.len], declare_kw); tp += declare_kw.len;
|
|
947
1020
|
@memcpy(buf[tp..][0..func_name.len], func_name); tp += func_name.len;
|
|
948
1021
|
@memcpy(buf[tp..][0..generics.len], generics); tp += generics.len;
|
|
949
1022
|
@memcpy(buf[tp..][0..dts_params.len], dts_params); tp += dts_params.len;
|
package/test/zig-dtsx.test.ts
CHANGED
|
@@ -963,7 +963,9 @@ export function useFoo(f: Foo): void {}
|
|
|
963
963
|
|
|
964
964
|
test('default export function', () => {
|
|
965
965
|
const result = dts(`export default function main(): void {}`)
|
|
966
|
-
|
|
966
|
+
// `export default function` keeps the `default` keyword in DTS —
|
|
967
|
+
// `export declare function` would lose the default-export signal.
|
|
968
|
+
expect(result).toContain('export default function main(): void')
|
|
967
969
|
})
|
|
968
970
|
|
|
969
971
|
test('default export class', () => {
|
|
@@ -977,7 +979,7 @@ export const version = '1.0.0'
|
|
|
977
979
|
export default function init(): void {}
|
|
978
980
|
`)
|
|
979
981
|
expect(result).toContain('declare const version')
|
|
980
|
-
expect(result).toContain('
|
|
982
|
+
expect(result).toContain('export default function init')
|
|
981
983
|
})
|
|
982
984
|
})
|
|
983
985
|
|
|
@@ -1173,6 +1175,29 @@ export function merge<T extends Record<string, unknown>, U extends Record<string
|
|
|
1173
1175
|
expect(result).toContain('declare function parse')
|
|
1174
1176
|
})
|
|
1175
1177
|
|
|
1178
|
+
test('issue 3095 — destructured param with JSDoc apostrophe', () => {
|
|
1179
|
+
// JSDoc prose containing an apostrophe (e.g. "error's") used to put the
|
|
1180
|
+
// param scanner into an inescapable string-literal mode, producing
|
|
1181
|
+
// `,: unknown):` that TypeScript rejects with TS1138.
|
|
1182
|
+
const result = dts(`export class CLI {
|
|
1183
|
+
async parse(
|
|
1184
|
+
argv: string[] = [],
|
|
1185
|
+
{
|
|
1186
|
+
/** Whether to run the action for matched command */
|
|
1187
|
+
run = true,
|
|
1188
|
+
/**
|
|
1189
|
+
* Defaults to \`false\`. When set, render the error's message and exit.
|
|
1190
|
+
*/
|
|
1191
|
+
exitOnError = false,
|
|
1192
|
+
}: { run?: boolean, exitOnError?: boolean } = {},
|
|
1193
|
+
): Promise<void> {}
|
|
1194
|
+
}`)
|
|
1195
|
+
expect(result).not.toMatch(/,\s*:\s*unknown\)/)
|
|
1196
|
+
expect(result).not.toContain('= {}')
|
|
1197
|
+
expect(result).toContain('run?: boolean')
|
|
1198
|
+
expect(result).toContain('exitOnError?: boolean')
|
|
1199
|
+
})
|
|
1200
|
+
|
|
1176
1201
|
test('export declare (already declared)', () => {
|
|
1177
1202
|
const result = dts(`export declare const VERSION: string`)
|
|
1178
1203
|
expect(result).toContain('export declare const VERSION: string')
|
package/zig-out/bin/zig-dtsx
CHANGED
|
Binary file
|
package/zig-out/bin/zig-dtsx.exe
CHANGED
|
Binary file
|