@stacksjs/zig-dtsx 0.9.10
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.md +21 -0
- package/README.md +73 -0
- package/build.zig +79 -0
- package/build.zig.zon +11 -0
- package/package.json +23 -0
- package/src/char_utils.zig +158 -0
- package/src/emitter.zig +1045 -0
- package/src/extractors.zig +2464 -0
- package/src/index.ts +222 -0
- package/src/lib.zig +254 -0
- package/src/main.zig +532 -0
- package/src/scan_loop.zig +330 -0
- package/src/scanner.zig +908 -0
- package/src/type_inference.zig +1564 -0
- package/src/types.zig +105 -0
- package/test/benchmark.ts +343 -0
- package/test/fixtures/output/variable.d.ts +157 -0
- package/test/zig-dtsx.test.ts +1386 -0
- package/zig-out/bin/zig-dtsx +0 -0
- package/zig-out/bin/zig-dtsx.exe +0 -0
package/src/types.zig
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
const std = @import("std");
|
|
2
|
+
|
|
3
|
+
/// Declaration kinds matching the TypeScript scanner output
|
|
4
|
+
pub const DeclarationKind = enum {
|
|
5
|
+
import_decl,
|
|
6
|
+
export_decl,
|
|
7
|
+
function_decl,
|
|
8
|
+
variable_decl,
|
|
9
|
+
interface_decl,
|
|
10
|
+
type_decl,
|
|
11
|
+
class_decl,
|
|
12
|
+
enum_decl,
|
|
13
|
+
module_decl,
|
|
14
|
+
namespace_decl,
|
|
15
|
+
unknown_decl,
|
|
16
|
+
|
|
17
|
+
pub fn toString(self: DeclarationKind) []const u8 {
|
|
18
|
+
return switch (self) {
|
|
19
|
+
.import_decl => "import",
|
|
20
|
+
.export_decl => "export",
|
|
21
|
+
.function_decl => "function",
|
|
22
|
+
.variable_decl => "variable",
|
|
23
|
+
.interface_decl => "interface",
|
|
24
|
+
.type_decl => "type",
|
|
25
|
+
.class_decl => "class",
|
|
26
|
+
.enum_decl => "enum",
|
|
27
|
+
.module_decl => "module",
|
|
28
|
+
.namespace_decl => "namespace",
|
|
29
|
+
.unknown_decl => "unknown",
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/// A single declaration extracted from TypeScript source.
|
|
35
|
+
/// `text` contains the pre-built DTS output for this declaration.
|
|
36
|
+
/// `name_slice` and other slices point into the original source buffer.
|
|
37
|
+
pub const Declaration = struct {
|
|
38
|
+
kind: DeclarationKind,
|
|
39
|
+
/// Name of the declaration (slice into source or allocated)
|
|
40
|
+
name: []const u8 = "",
|
|
41
|
+
/// Pre-built DTS text for this declaration (owned, allocated)
|
|
42
|
+
text: []const u8 = "",
|
|
43
|
+
/// Whether this declaration is exported
|
|
44
|
+
is_exported: bool = false,
|
|
45
|
+
/// Whether this is a default export
|
|
46
|
+
is_default: bool = false,
|
|
47
|
+
/// Whether this is type-only (for imports/exports)
|
|
48
|
+
is_type_only: bool = false,
|
|
49
|
+
/// Whether this is a side-effect import
|
|
50
|
+
is_side_effect: bool = false,
|
|
51
|
+
/// Module source for imports (e.g., 'node:fs')
|
|
52
|
+
source_module: []const u8 = "",
|
|
53
|
+
/// Leading comments (JSDoc, block, single-line)
|
|
54
|
+
leading_comments: ?[]const []const u8 = null,
|
|
55
|
+
/// Start position in source
|
|
56
|
+
start: usize = 0,
|
|
57
|
+
/// End position in source
|
|
58
|
+
end: usize = 0,
|
|
59
|
+
/// Whether this function had a body (for overload detection)
|
|
60
|
+
has_body: bool = false,
|
|
61
|
+
/// Initializer text for variable declarations
|
|
62
|
+
value: []const u8 = "",
|
|
63
|
+
/// Type annotation text
|
|
64
|
+
type_annotation: []const u8 = "",
|
|
65
|
+
/// Whether this is async
|
|
66
|
+
is_async: bool = false,
|
|
67
|
+
/// Whether this is a generator
|
|
68
|
+
is_generator: bool = false,
|
|
69
|
+
/// Modifiers like ['const', 'const assertion'], ['abstract']
|
|
70
|
+
modifiers: ?[]const []const u8 = null,
|
|
71
|
+
/// Generics text
|
|
72
|
+
generics: []const u8 = "",
|
|
73
|
+
/// Extends clause
|
|
74
|
+
extends_clause: []const u8 = "",
|
|
75
|
+
/// Implements list
|
|
76
|
+
implements_list: ?[]const []const u8 = null,
|
|
77
|
+
/// Cached parsed import (populated during scan, avoids re-parsing in emitter)
|
|
78
|
+
parsed_import: ?ParsedImport = null,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/// Parsed import statement for filtering/sorting
|
|
82
|
+
pub const ParsedImport = struct {
|
|
83
|
+
default_name: ?[]const u8 = null,
|
|
84
|
+
named_items: []const []const u8 = &.{},
|
|
85
|
+
source: []const u8 = "",
|
|
86
|
+
is_type_only: bool = false,
|
|
87
|
+
is_namespace: bool = false,
|
|
88
|
+
namespace_name: ?[]const u8 = null,
|
|
89
|
+
/// Pre-resolved item names (type/as stripped) for fast filtering
|
|
90
|
+
resolved_items: []const []const u8 = &.{},
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/// Constructor parameter modifiers (hoisted to avoid per-call allocation)
|
|
94
|
+
pub const PARAM_MODIFIERS = [_][]const u8{
|
|
95
|
+
"public",
|
|
96
|
+
"protected",
|
|
97
|
+
"private",
|
|
98
|
+
"readonly",
|
|
99
|
+
"override",
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
test "DeclarationKind toString" {
|
|
103
|
+
try std.testing.expectEqualStrings("function", DeclarationKind.function_decl.toString());
|
|
104
|
+
try std.testing.expectEqualStrings("import", DeclarationKind.import_decl.toString());
|
|
105
|
+
}
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Benchmark: Zig DTS (CLI + FFI) vs TypeScript DTS emitter
|
|
3
|
+
*
|
|
4
|
+
* Three runners:
|
|
5
|
+
* 1. Zig CLI — spawns zig-out/bin/zig-dtsx, pipes via stdin
|
|
6
|
+
* 2. Zig FFI — calls the shared library in-process via Bun FFI
|
|
7
|
+
* 3. TS — calls processSource() from @stacksjs/dtsx in-process
|
|
8
|
+
*
|
|
9
|
+
* Two benchmark modes:
|
|
10
|
+
* A. Single-file — each fixture processed individually
|
|
11
|
+
* B. Multi-file project — all fixtures processed as a batch
|
|
12
|
+
*
|
|
13
|
+
* Usage: bun run test/benchmark.ts [--quick] [--cli-only]
|
|
14
|
+
*/
|
|
15
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs'
|
|
16
|
+
import { join, resolve } from 'node:path'
|
|
17
|
+
import { spawnSync } from 'node:child_process'
|
|
18
|
+
|
|
19
|
+
const fixturesDir = resolve(import.meta.dir, '../../dtsx/test/fixtures')
|
|
20
|
+
const zigBinary = resolve(import.meta.dir, '..', 'zig-out', 'bin', 'zig-dtsx')
|
|
21
|
+
const args = process.argv.slice(2)
|
|
22
|
+
const quick = args.includes('--quick')
|
|
23
|
+
const cliOnly = args.includes('--cli-only')
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Runners
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
function zigCli(input: string): string {
|
|
30
|
+
const result = spawnSync(zigBinary, [], {
|
|
31
|
+
input,
|
|
32
|
+
encoding: 'utf-8',
|
|
33
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
34
|
+
})
|
|
35
|
+
if (result.error) throw result.error
|
|
36
|
+
return result.stdout
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let zigFfi: ((_input: string) => string) | null = null
|
|
40
|
+
let tsProcess: ((_input: string) => string) | null = null
|
|
41
|
+
|
|
42
|
+
async function loadRunners(): Promise<void> {
|
|
43
|
+
// Zig FFI
|
|
44
|
+
if (!cliOnly) {
|
|
45
|
+
try {
|
|
46
|
+
const { processSource } = await import('../src/index')
|
|
47
|
+
zigFfi = (source: string) => processSource(source, true)
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
console.log(' Zig FFI: not available (run "zig build" first)')
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// TS
|
|
55
|
+
try {
|
|
56
|
+
const mod = await import('@stacksjs/dtsx')
|
|
57
|
+
const fn = (mod as any).processSource ?? (mod as any).default?.processSource
|
|
58
|
+
if (fn) {
|
|
59
|
+
tsProcess = (source: string) => fn(source, 'bench.ts', true)
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
console.log(' TS: processSource not found in @stacksjs/dtsx')
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
console.log(' TS: could not import @stacksjs/dtsx')
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// Helpers
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
|
|
74
|
+
function formatBytes(bytes: number): string {
|
|
75
|
+
if (bytes >= 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(1)}MB`
|
|
76
|
+
if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)}KB`
|
|
77
|
+
return `${bytes}B`
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
interface SingleResult {
|
|
81
|
+
name: string
|
|
82
|
+
inputSize: number
|
|
83
|
+
zigCliMs: number
|
|
84
|
+
zigFfiMs: number | null
|
|
85
|
+
tsMs: number | null
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function bench(fn: (input: string) => string, input: string, iterations: number): number {
|
|
89
|
+
// warmup
|
|
90
|
+
for (let i = 0; i < 3; i++) fn(input)
|
|
91
|
+
const start = performance.now()
|
|
92
|
+
for (let i = 0; i < iterations; i++) fn(input)
|
|
93
|
+
return (performance.now() - start) / iterations
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// A) Single-file benchmark
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
async function singleFileBenchmark(): Promise<SingleResult[]> {
|
|
101
|
+
const fixtures = [
|
|
102
|
+
'imports', 'exports', 'function', 'function-types', 'variable',
|
|
103
|
+
'interface', 'class', 'enum', 'type', 'namespace',
|
|
104
|
+
'generics', 'advanced-types', 'edge-cases', 'complex-class', 'module',
|
|
105
|
+
'type-only-imports', 'comments',
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
const iterations = quick ? 20 : 100
|
|
109
|
+
const results: SingleResult[] = []
|
|
110
|
+
|
|
111
|
+
for (const fixture of fixtures) {
|
|
112
|
+
const inputPath = join(fixturesDir, 'input', `${fixture}.ts`)
|
|
113
|
+
if (!existsSync(inputPath)) continue
|
|
114
|
+
|
|
115
|
+
const input = readFileSync(inputPath, 'utf-8')
|
|
116
|
+
const inputSize = Buffer.byteLength(input, 'utf-8')
|
|
117
|
+
|
|
118
|
+
const zigCliMs = bench(zigCli, input, iterations)
|
|
119
|
+
const zigFfiMs = zigFfi ? bench(zigFfi, input, iterations) : null
|
|
120
|
+
const tsMs = tsProcess ? bench(tsProcess, input, iterations) : null
|
|
121
|
+
|
|
122
|
+
results.push({ name: fixture, inputSize, zigCliMs, zigFfiMs, tsMs })
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Large file — fewer iterations
|
|
126
|
+
const checkerPath = join(fixturesDir, 'input', 'checker.ts')
|
|
127
|
+
if (existsSync(checkerPath)) {
|
|
128
|
+
const input = readFileSync(checkerPath, 'utf-8')
|
|
129
|
+
const inputSize = Buffer.byteLength(input, 'utf-8')
|
|
130
|
+
const iters = quick ? 2 : 5
|
|
131
|
+
const zigCliMs = bench(zigCli, input, iters)
|
|
132
|
+
const zigFfiMs = zigFfi ? bench(zigFfi, input, iters) : null
|
|
133
|
+
const tsMs = tsProcess ? bench(tsProcess, input, iters) : null
|
|
134
|
+
results.push({ name: 'checker.ts (large)', inputSize, zigCliMs, zigFfiMs, tsMs })
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Real-world fixtures
|
|
138
|
+
for (const rw of ['lodash-like', 'react-like']) {
|
|
139
|
+
const rwPath = join(fixturesDir, 'input', 'real-world', `${rw}.ts`)
|
|
140
|
+
if (!existsSync(rwPath)) continue
|
|
141
|
+
const input = readFileSync(rwPath, 'utf-8')
|
|
142
|
+
const inputSize = Buffer.byteLength(input, 'utf-8')
|
|
143
|
+
const iters = quick ? 10 : 50
|
|
144
|
+
const zigCliMs = bench(zigCli, input, iters)
|
|
145
|
+
const zigFfiMs = zigFfi ? bench(zigFfi, input, iters) : null
|
|
146
|
+
const tsMs = tsProcess ? bench(tsProcess, input, iters) : null
|
|
147
|
+
results.push({ name: rw, inputSize, zigCliMs, zigFfiMs, tsMs })
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return results
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
// B) Multi-file project benchmark
|
|
155
|
+
// ---------------------------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
interface ProjectResult {
|
|
158
|
+
name: string
|
|
159
|
+
fileCount: number
|
|
160
|
+
totalSize: number
|
|
161
|
+
zigCliMs: number
|
|
162
|
+
zigFfiMs: number | null
|
|
163
|
+
tsMs: number | null
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function collectInputFiles(dir: string): { name: string, content: string }[] {
|
|
167
|
+
const files: { name: string, content: string }[] = []
|
|
168
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
169
|
+
if (entry.isFile() && entry.name.endsWith('.ts')) {
|
|
170
|
+
files.push({ name: entry.name, content: readFileSync(join(dir, entry.name), 'utf-8') })
|
|
171
|
+
}
|
|
172
|
+
else if (entry.isDirectory()) {
|
|
173
|
+
for (const sub of readdirSync(join(dir, entry.name), { withFileTypes: true })) {
|
|
174
|
+
if (sub.isFile() && sub.name.endsWith('.ts')) {
|
|
175
|
+
files.push({
|
|
176
|
+
name: `${entry.name}/${sub.name}`,
|
|
177
|
+
content: readFileSync(join(dir, entry.name, sub.name), 'utf-8'),
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return files
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function benchProject(
|
|
187
|
+
runner: (input: string) => string,
|
|
188
|
+
files: { content: string }[],
|
|
189
|
+
iterations: number,
|
|
190
|
+
): number {
|
|
191
|
+
// warmup
|
|
192
|
+
for (let w = 0; w < 2; w++) {
|
|
193
|
+
for (const f of files) runner(f.content)
|
|
194
|
+
}
|
|
195
|
+
const start = performance.now()
|
|
196
|
+
for (let i = 0; i < iterations; i++) {
|
|
197
|
+
for (const f of files) runner(f.content)
|
|
198
|
+
}
|
|
199
|
+
return (performance.now() - start) / iterations
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function multiFileBenchmark(): Promise<ProjectResult[]> {
|
|
203
|
+
const results: ProjectResult[] = []
|
|
204
|
+
const inputDir = join(fixturesDir, 'input')
|
|
205
|
+
const allFiles = collectInputFiles(inputDir)
|
|
206
|
+
|
|
207
|
+
// Small project: just the basic fixtures (no checker.ts)
|
|
208
|
+
const smallFiles = allFiles.filter(f => !f.name.includes('checker'))
|
|
209
|
+
const smallSize = smallFiles.reduce((s, f) => s + Buffer.byteLength(f.content, 'utf-8'), 0)
|
|
210
|
+
const smallIters = quick ? 5 : 20
|
|
211
|
+
|
|
212
|
+
{
|
|
213
|
+
const zigCliMs = benchProject(zigCli, smallFiles, smallIters)
|
|
214
|
+
const zigFfiMs = zigFfi ? benchProject(zigFfi, smallFiles, smallIters) : null
|
|
215
|
+
const tsMs = tsProcess ? benchProject(tsProcess, smallFiles, smallIters) : null
|
|
216
|
+
results.push({
|
|
217
|
+
name: 'All fixtures (excl. checker)',
|
|
218
|
+
fileCount: smallFiles.length,
|
|
219
|
+
totalSize: smallSize,
|
|
220
|
+
zigCliMs,
|
|
221
|
+
zigFfiMs,
|
|
222
|
+
tsMs,
|
|
223
|
+
})
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Full project: everything including checker.ts
|
|
227
|
+
const fullSize = allFiles.reduce((s, f) => s + Buffer.byteLength(f.content, 'utf-8'), 0)
|
|
228
|
+
const fullIters = quick ? 2 : 5
|
|
229
|
+
|
|
230
|
+
{
|
|
231
|
+
const zigCliMs = benchProject(zigCli, allFiles, fullIters)
|
|
232
|
+
const zigFfiMs = zigFfi ? benchProject(zigFfi, allFiles, fullIters) : null
|
|
233
|
+
const tsMs = tsProcess ? benchProject(tsProcess, allFiles, fullIters) : null
|
|
234
|
+
results.push({
|
|
235
|
+
name: 'Full project (all files)',
|
|
236
|
+
fileCount: allFiles.length,
|
|
237
|
+
totalSize: fullSize,
|
|
238
|
+
zigCliMs,
|
|
239
|
+
zigFfiMs,
|
|
240
|
+
tsMs,
|
|
241
|
+
})
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return results
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
// Output formatting
|
|
249
|
+
// ---------------------------------------------------------------------------
|
|
250
|
+
|
|
251
|
+
function printSingleResults(results: SingleResult[]): void {
|
|
252
|
+
const hasFFI = results.some(r => r.zigFfiMs !== null)
|
|
253
|
+
const hasTS = results.some(r => r.tsMs !== null)
|
|
254
|
+
|
|
255
|
+
// Header
|
|
256
|
+
let header = `${'Fixture'.padEnd(22)} ${'Size'.padStart(8)}`
|
|
257
|
+
header += ` ${'Zig CLI'.padStart(10)}`
|
|
258
|
+
if (hasFFI) header += ` ${'Zig FFI'.padStart(10)}`
|
|
259
|
+
if (hasTS) header += ` ${'TS'.padStart(10)}`
|
|
260
|
+
if (hasFFI) header += ` ${'FFI/TS'.padStart(8)}`
|
|
261
|
+
console.log(header)
|
|
262
|
+
console.log('-'.repeat(header.length))
|
|
263
|
+
|
|
264
|
+
for (const r of results) {
|
|
265
|
+
let row = `${r.name.padEnd(22)} ${formatBytes(r.inputSize).padStart(8)}`
|
|
266
|
+
row += ` ${(r.zigCliMs.toFixed(2) + 'ms').padStart(10)}`
|
|
267
|
+
if (hasFFI) row += ` ${r.zigFfiMs !== null ? (r.zigFfiMs.toFixed(2) + 'ms').padStart(10) : 'n/a'.padStart(10)}`
|
|
268
|
+
if (hasTS) row += ` ${r.tsMs !== null ? (r.tsMs.toFixed(2) + 'ms').padStart(10) : 'n/a'.padStart(10)}`
|
|
269
|
+
if (hasFFI && r.zigFfiMs !== null && r.tsMs !== null) {
|
|
270
|
+
const speedup = r.tsMs / r.zigFfiMs
|
|
271
|
+
row += ` ${(speedup.toFixed(1) + 'x').padStart(8)}`
|
|
272
|
+
}
|
|
273
|
+
console.log(row)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Summary
|
|
277
|
+
if (hasFFI && hasTS) {
|
|
278
|
+
const withBoth = results.filter(r => r.zigFfiMs !== null && r.tsMs !== null)
|
|
279
|
+
if (withBoth.length > 0) {
|
|
280
|
+
const avgFFI = withBoth.reduce((s, r) => s + r.zigFfiMs!, 0) / withBoth.length
|
|
281
|
+
const avgTS = withBoth.reduce((s, r) => s + r.tsMs!, 0) / withBoth.length
|
|
282
|
+
console.log(`\n Zig FFI avg: ${avgFFI.toFixed(3)}ms — TS avg: ${avgTS.toFixed(3)}ms — FFI is ${(avgTS / avgFFI).toFixed(1)}x faster`)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function printProjectResults(results: ProjectResult[]): void {
|
|
288
|
+
const hasFFI = results.some(r => r.zigFfiMs !== null)
|
|
289
|
+
const hasTS = results.some(r => r.tsMs !== null)
|
|
290
|
+
|
|
291
|
+
let header = `${'Project'.padEnd(32)} ${'Files'.padStart(6)} ${'Size'.padStart(8)}`
|
|
292
|
+
header += ` ${'Zig CLI'.padStart(10)}`
|
|
293
|
+
if (hasFFI) header += ` ${'Zig FFI'.padStart(10)}`
|
|
294
|
+
if (hasTS) header += ` ${'TS'.padStart(10)}`
|
|
295
|
+
if (hasFFI) header += ` ${'FFI/TS'.padStart(8)}`
|
|
296
|
+
console.log(header)
|
|
297
|
+
console.log('-'.repeat(header.length))
|
|
298
|
+
|
|
299
|
+
for (const r of results) {
|
|
300
|
+
let row = `${r.name.padEnd(32)} ${String(r.fileCount).padStart(6)} ${formatBytes(r.totalSize).padStart(8)}`
|
|
301
|
+
row += ` ${(r.zigCliMs.toFixed(1) + 'ms').padStart(10)}`
|
|
302
|
+
if (hasFFI) row += ` ${r.zigFfiMs !== null ? (r.zigFfiMs.toFixed(1) + 'ms').padStart(10) : 'n/a'.padStart(10)}`
|
|
303
|
+
if (hasTS) row += ` ${r.tsMs !== null ? (r.tsMs.toFixed(1) + 'ms').padStart(10) : 'n/a'.padStart(10)}`
|
|
304
|
+
if (hasFFI && r.zigFfiMs !== null && r.tsMs !== null) {
|
|
305
|
+
const speedup = r.tsMs / r.zigFfiMs
|
|
306
|
+
row += ` ${(speedup.toFixed(1) + 'x').padStart(8)}`
|
|
307
|
+
}
|
|
308
|
+
console.log(row)
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// ---------------------------------------------------------------------------
|
|
313
|
+
// Main
|
|
314
|
+
// ---------------------------------------------------------------------------
|
|
315
|
+
|
|
316
|
+
async function main() {
|
|
317
|
+
if (!existsSync(zigBinary)) {
|
|
318
|
+
console.error(`Zig binary not found at: ${zigBinary}`)
|
|
319
|
+
console.error('Run "zig build -Doptimize=ReleaseFast" first.')
|
|
320
|
+
process.exit(1)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
console.log('\n=== zig-dtsx Benchmark ===\n')
|
|
324
|
+
console.log(`Zig CLI binary: ${zigBinary}`)
|
|
325
|
+
console.log(`Mode: ${quick ? 'quick' : 'full'}${cliOnly ? ' (CLI only)' : ''}\n`)
|
|
326
|
+
|
|
327
|
+
console.log('Loading runners...')
|
|
328
|
+
await loadRunners()
|
|
329
|
+
|
|
330
|
+
// A) Single-file benchmark
|
|
331
|
+
console.log('\n--- Single-File Benchmark ---\n')
|
|
332
|
+
const singleResults = await singleFileBenchmark()
|
|
333
|
+
printSingleResults(singleResults)
|
|
334
|
+
|
|
335
|
+
// B) Multi-file project benchmark
|
|
336
|
+
console.log('\n\n--- Multi-File Project Benchmark ---\n')
|
|
337
|
+
const projectResults = await multiFileBenchmark()
|
|
338
|
+
printProjectResults(projectResults)
|
|
339
|
+
|
|
340
|
+
console.log('\n')
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
main().catch(console.error)
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example of const declaration
|
|
3
|
+
*/
|
|
4
|
+
export declare const conf: {
|
|
5
|
+
apiUrl: 'https://api.stacksjs.org';
|
|
6
|
+
timeout: '5000'
|
|
7
|
+
};
|
|
8
|
+
/** @defaultValue 'test' */
|
|
9
|
+
export declare let test: string;
|
|
10
|
+
/** @defaultValue 'Hello World' */
|
|
11
|
+
export declare var helloWorld: string;
|
|
12
|
+
/**
|
|
13
|
+
* @defaultValue
|
|
14
|
+
* ```ts
|
|
15
|
+
* {
|
|
16
|
+
* someString: 'Stacks',
|
|
17
|
+
* someNumber: 1000,
|
|
18
|
+
* someBoolean: true,
|
|
19
|
+
* someFalse: false,
|
|
20
|
+
* someFunction: () => unknown,
|
|
21
|
+
* anotherOne: () => unknown,
|
|
22
|
+
* someArray: [1, 2, 3],
|
|
23
|
+
* someNestedArray: [ [1, 2, 3], [4, 5, 6, 7, 8, 9, 10], ],
|
|
24
|
+
* someNestedArray2: [ [1, 2, 3], [4, 5, 6, 7, 8, 9, 10], 'dummy value', ],
|
|
25
|
+
* someNestedArray3: [ [1, 2, 3], [4, 5, 6, 7, 8, 9, 10], 'dummy value', [11, 12, 13], ],
|
|
26
|
+
* someObject: { key: 'value' },
|
|
27
|
+
* someNestedObject: { key: { nestedKey: 'value' }, otherKey: { nestedKey2: () => unknown } },
|
|
28
|
+
* someNestedObjectArray: [ { key: 'value' }, { key2: 'value2' }, ]
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare const someObject: {
|
|
33
|
+
/** @defaultValue 'Stacks' */
|
|
34
|
+
someString: string;
|
|
35
|
+
/** @defaultValue 1000 */
|
|
36
|
+
someNumber: number;
|
|
37
|
+
/** @defaultValue true */
|
|
38
|
+
someBoolean: boolean;
|
|
39
|
+
/** @defaultValue false */
|
|
40
|
+
someFalse: boolean;
|
|
41
|
+
someFunction: () => unknown;
|
|
42
|
+
anotherOne: () => unknown;
|
|
43
|
+
someArray: number[];
|
|
44
|
+
someNestedArray: number[][];
|
|
45
|
+
someNestedArray2: (number[] | string)[];
|
|
46
|
+
someNestedArray3: (number[] | string)[];
|
|
47
|
+
someOtherNestedArray: ((string | number | unknown | (() => unknown))[] | number[])[];
|
|
48
|
+
someComplexArray: ({
|
|
49
|
+
/** @defaultValue 'value' */
|
|
50
|
+
key: string
|
|
51
|
+
}[] | ({
|
|
52
|
+
/** @defaultValue 'value2' */
|
|
53
|
+
key2: string
|
|
54
|
+
} | string | number)[] | (string | unknown)[])[];
|
|
55
|
+
someObject: {
|
|
56
|
+
/** @defaultValue 'value' */
|
|
57
|
+
key: string
|
|
58
|
+
};
|
|
59
|
+
someNestedObject: { key: { /** @defaultValue 'value' */ nestedKey: string }; otherKey: { nestedKey: unknown; nestedKey2: () => unknown } };
|
|
60
|
+
someNestedObjectArray: ({
|
|
61
|
+
/** @defaultValue 'value' */
|
|
62
|
+
key: string
|
|
63
|
+
} | {
|
|
64
|
+
/** @defaultValue 'value2' */
|
|
65
|
+
key2: string
|
|
66
|
+
})[];
|
|
67
|
+
someOtherObject: unknown;
|
|
68
|
+
someInlineCall2: unknown;
|
|
69
|
+
someInlineCall3: unknown
|
|
70
|
+
};
|
|
71
|
+
/** @defaultValue `{ 'Content-Type': 'application/json' }` */
|
|
72
|
+
export declare const defaultHeaders: {
|
|
73
|
+
/** @defaultValue 'application/json' */
|
|
74
|
+
'Content-Type': string
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Complex Arrays and Tuples
|
|
78
|
+
* @defaultValue
|
|
79
|
+
* ```ts
|
|
80
|
+
* {
|
|
81
|
+
* matrix: [ [1, 2, [3, 4, [5, 6]]], ['a', 'b', ['c', 'd']], [true, [false, [true]]], ],
|
|
82
|
+
* tuples: [ [1, 'string', true] as const, ['literal', 42, false] as const, ]
|
|
83
|
+
* }
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
export declare const complexArrays: {
|
|
87
|
+
matrix: ((number | (number | number[])[])[] | (string | string[])[] | (boolean | (boolean | boolean[])[])[])[];
|
|
88
|
+
tuples: readonly [
|
|
89
|
+
readonly [1, 'string', true] |
|
|
90
|
+
readonly ['literal', 42, false]
|
|
91
|
+
];
|
|
92
|
+
mixedArrays: (Date | Promise<string> | (() => unknown) | (() => Generator<any, any, any>))[]
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Nested Object Types with Methods
|
|
96
|
+
* @defaultValue
|
|
97
|
+
* ```ts
|
|
98
|
+
* {
|
|
99
|
+
* handlers: {
|
|
100
|
+
* onSuccess<T>: (data: T) => unknown,
|
|
101
|
+
* onError: (error: Error & { code?: number }) => unknown,
|
|
102
|
+
* someOtherMethod: () => unknown
|
|
103
|
+
* },
|
|
104
|
+
* utils: {
|
|
105
|
+
* formatters: {
|
|
106
|
+
* date: (input: Date) => unknown,
|
|
107
|
+
* currency: (amount: number, currency?) => unknown
|
|
108
|
+
* }
|
|
109
|
+
* }
|
|
110
|
+
* }
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export declare const complexObject: {
|
|
114
|
+
handlers: { onSuccess<T>: (data: T) => unknown; onError: (error: Error & { code?: number }) => unknown; someOtherMethod: () => unknown };
|
|
115
|
+
utils: { formatters: { date: (input: Date) => unknown; currency: (amount: number, currency?) => unknown } }
|
|
116
|
+
};
|
|
117
|
+
// Method Decorators and Metadata (declares as unknown, because it should rely on explicit type)
|
|
118
|
+
export declare const methodDecorator: (
|
|
119
|
+
target: any,
|
|
120
|
+
propertyKey: string,
|
|
121
|
+
descriptor: PropertyDescriptor
|
|
122
|
+
) => unknown;
|
|
123
|
+
// declares as SomeType
|
|
124
|
+
export declare const methodDecoratorWithExplicitType: (
|
|
125
|
+
target: any,
|
|
126
|
+
propertyKey: string,
|
|
127
|
+
descriptor: PropertyDescriptor
|
|
128
|
+
) => SomeType;
|
|
129
|
+
// Complex Constants with Type Inference
|
|
130
|
+
export declare const CONFIG_MAP: {
|
|
131
|
+
development: {
|
|
132
|
+
features: {
|
|
133
|
+
auth: {
|
|
134
|
+
providers: readonly ['google', 'github'];
|
|
135
|
+
settings: {
|
|
136
|
+
timeout: 5000;
|
|
137
|
+
retries: 3
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
production: {
|
|
143
|
+
features: {
|
|
144
|
+
auth: {
|
|
145
|
+
providers: readonly ['google', 'github', 'microsoft'];
|
|
146
|
+
settings: {
|
|
147
|
+
timeout: 3000;
|
|
148
|
+
retries: 5
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
export declare const command: {
|
|
155
|
+
run: unknown;
|
|
156
|
+
runSync: unknown
|
|
157
|
+
};
|