@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
|
@@ -0,0 +1,2464 @@
|
|
|
1
|
+
/// Declaration extractors - extract specific declaration types from the scanner.
|
|
2
|
+
/// Port of scanner.ts high-level extraction functions (lines 590-2598).
|
|
3
|
+
const std = @import("std");
|
|
4
|
+
const ch = @import("char_utils.zig");
|
|
5
|
+
const types = @import("types.zig");
|
|
6
|
+
const type_inf = @import("type_inference.zig");
|
|
7
|
+
const Scanner = @import("scanner.zig").Scanner;
|
|
8
|
+
const Declaration = types.Declaration;
|
|
9
|
+
const DeclarationKind = types.DeclarationKind;
|
|
10
|
+
const Allocator = std.mem.Allocator;
|
|
11
|
+
|
|
12
|
+
// ========================================================================
|
|
13
|
+
// Comment extraction
|
|
14
|
+
// ========================================================================
|
|
15
|
+
|
|
16
|
+
/// Extract leading JSDoc/block/single-line comments before position
|
|
17
|
+
pub fn extractLeadingComments(s: *Scanner, decl_start: usize) ?[]const []const u8 {
|
|
18
|
+
if (!s.keep_comments) return null;
|
|
19
|
+
|
|
20
|
+
var p: isize = @as(isize, @intCast(decl_start)) - 1;
|
|
21
|
+
while (p >= 0 and ch.isWhitespace(s.source[@intCast(p)])) p -= 1;
|
|
22
|
+
if (p < 0) return null;
|
|
23
|
+
|
|
24
|
+
var comments = std.array_list.Managed([]const u8).init(s.allocator);
|
|
25
|
+
comments.ensureTotalCapacity(4) catch {};
|
|
26
|
+
var has_block_comment = false;
|
|
27
|
+
|
|
28
|
+
while (p >= 0) {
|
|
29
|
+
const pu: usize = @intCast(p);
|
|
30
|
+
// Check for block comment ending with */
|
|
31
|
+
if (p >= 1 and s.source[pu] == ch.CH_SLASH and s.source[pu - 1] == ch.CH_STAR) {
|
|
32
|
+
// Find matching /* or /**
|
|
33
|
+
var start: isize = p - 2;
|
|
34
|
+
while (start >= 1) {
|
|
35
|
+
const su: usize = @intCast(start);
|
|
36
|
+
if (s.source[su] == ch.CH_SLASH and s.source[su + 1] == ch.CH_STAR) break;
|
|
37
|
+
start -= 1;
|
|
38
|
+
}
|
|
39
|
+
if (start >= 0) {
|
|
40
|
+
const su: usize = @intCast(start);
|
|
41
|
+
if (s.source[su] == ch.CH_SLASH and s.source[su + 1] == ch.CH_STAR) {
|
|
42
|
+
comments.append(s.source[su .. pu + 1]) catch {};
|
|
43
|
+
has_block_comment = true;
|
|
44
|
+
p = start - 1;
|
|
45
|
+
while (p >= 0 and ch.isWhitespace(s.source[@intCast(p)])) p -= 1;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Check for single-line comments
|
|
53
|
+
var line_start: usize = pu;
|
|
54
|
+
while (line_start > 0 and s.source[line_start - 1] != ch.CH_LF) line_start -= 1;
|
|
55
|
+
const line_text = ch.sliceTrimmed(s.source, line_start, pu + 1);
|
|
56
|
+
|
|
57
|
+
if (line_text.len >= 2 and line_text[0] == '/' and line_text[1] == '/') {
|
|
58
|
+
if (has_block_comment) break;
|
|
59
|
+
|
|
60
|
+
var single_lines = std.array_list.Managed([]const u8).init(s.allocator);
|
|
61
|
+
single_lines.append(line_text) catch {};
|
|
62
|
+
p = @as(isize, @intCast(line_start)) - 1;
|
|
63
|
+
while (p >= 0 and (s.source[@intCast(p)] == ch.CH_LF or s.source[@intCast(p)] == ch.CH_CR)) p -= 1;
|
|
64
|
+
|
|
65
|
+
while (p >= 0) {
|
|
66
|
+
var ls: usize = @intCast(p);
|
|
67
|
+
while (ls > 0 and s.source[ls - 1] != ch.CH_LF) ls -= 1;
|
|
68
|
+
const lt = ch.sliceTrimmed(s.source, ls, @as(usize, @intCast(p)) + 1);
|
|
69
|
+
if (lt.len >= 2 and lt[0] == '/' and lt[1] == '/') {
|
|
70
|
+
single_lines.append(lt) catch {};
|
|
71
|
+
p = @as(isize, @intCast(ls)) - 1;
|
|
72
|
+
while (p >= 0 and (s.source[@intCast(p)] == ch.CH_LF or s.source[@intCast(p)] == ch.CH_CR)) p -= 1;
|
|
73
|
+
} else if (lt.len == 0) {
|
|
74
|
+
p = @as(isize, @intCast(ls)) - 1;
|
|
75
|
+
while (p >= 0 and (s.source[@intCast(p)] == ch.CH_LF or s.source[@intCast(p)] == ch.CH_CR)) p -= 1;
|
|
76
|
+
} else {
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Reverse and join with newlines
|
|
82
|
+
std.mem.reverse([]const u8, single_lines.items);
|
|
83
|
+
var total_len: usize = 0;
|
|
84
|
+
for (single_lines.items, 0..) |line, i| {
|
|
85
|
+
total_len += line.len;
|
|
86
|
+
if (i < single_lines.items.len - 1) total_len += 1;
|
|
87
|
+
}
|
|
88
|
+
const joined = s.allocator.alloc(u8, total_len) catch break;
|
|
89
|
+
var offset: usize = 0;
|
|
90
|
+
for (single_lines.items, 0..) |line, i| {
|
|
91
|
+
@memcpy(joined[offset .. offset + line.len], line);
|
|
92
|
+
offset += line.len;
|
|
93
|
+
if (i < single_lines.items.len - 1) {
|
|
94
|
+
joined[offset] = '\n';
|
|
95
|
+
offset += 1;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
comments.append(joined) catch {};
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (comments.items.len == 0) return null;
|
|
105
|
+
std.mem.reverse([]const u8, comments.items);
|
|
106
|
+
return comments.toOwnedSlice() catch null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ========================================================================
|
|
110
|
+
// Import extraction
|
|
111
|
+
// ========================================================================
|
|
112
|
+
|
|
113
|
+
/// Extract import statement text from current position
|
|
114
|
+
pub fn extractImport(s: *Scanner, start: usize) Declaration {
|
|
115
|
+
const stmt_start = start;
|
|
116
|
+
var found_quote = false;
|
|
117
|
+
while (s.pos < s.len) {
|
|
118
|
+
const c = s.source[s.pos];
|
|
119
|
+
if (c == ch.CH_SEMI) {
|
|
120
|
+
s.pos += 1;
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
if (c == ch.CH_SQUOTE or c == ch.CH_DQUOTE) {
|
|
124
|
+
s.skipString(c);
|
|
125
|
+
found_quote = true;
|
|
126
|
+
while (s.pos < s.len and (s.source[s.pos] == ch.CH_SPACE or s.source[s.pos] == ch.CH_TAB)) s.pos += 1;
|
|
127
|
+
if (s.pos < s.len and s.source[s.pos] == ch.CH_SEMI) s.pos += 1;
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
if (c == ch.CH_LF and found_quote) break;
|
|
131
|
+
s.pos += 1;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const text = s.sliceTrimmed(stmt_start, s.pos);
|
|
135
|
+
// Check for 'import type '
|
|
136
|
+
const is_type_only = text.len > 11 and text[7] == 't' and
|
|
137
|
+
(ch.startsWith(text, "import type ") or ch.startsWith(text, "import type{"));
|
|
138
|
+
|
|
139
|
+
// Detect side-effect imports
|
|
140
|
+
var is_side_effect = false;
|
|
141
|
+
{
|
|
142
|
+
var si: usize = 6; // skip 'import'
|
|
143
|
+
while (si < text.len and (text[si] == ch.CH_SPACE or text[si] == ch.CH_TAB)) si += 1;
|
|
144
|
+
if (si < text.len and text[si] == 't' and si + 4 <= text.len and std.mem.eql(u8, text[si .. si + 4], "type")) {
|
|
145
|
+
si += 4;
|
|
146
|
+
while (si < text.len and (text[si] == ch.CH_SPACE or text[si] == ch.CH_TAB)) si += 1;
|
|
147
|
+
}
|
|
148
|
+
if (si < text.len) {
|
|
149
|
+
const qc = text[si];
|
|
150
|
+
is_side_effect = qc == ch.CH_SQUOTE or qc == ch.CH_DQUOTE;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Extract source module
|
|
155
|
+
var module_src: []const u8 = "";
|
|
156
|
+
{
|
|
157
|
+
const from_idx = ch.indexOf(text, "from ", 0);
|
|
158
|
+
if (from_idx) |fi| {
|
|
159
|
+
var mi = fi + 5;
|
|
160
|
+
while (mi < text.len and (text[mi] == ch.CH_SPACE or text[mi] == ch.CH_TAB)) mi += 1;
|
|
161
|
+
if (mi < text.len) {
|
|
162
|
+
const q = text[mi];
|
|
163
|
+
if (q == ch.CH_SQUOTE or q == ch.CH_DQUOTE) {
|
|
164
|
+
const q_str: []const u8 = if (q == ch.CH_SQUOTE) "'" else "\"";
|
|
165
|
+
const end_idx = ch.indexOf(text, q_str, mi + 1);
|
|
166
|
+
if (end_idx) |ei| {
|
|
167
|
+
module_src = text[mi + 1 .. ei];
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
} else if (is_side_effect) {
|
|
172
|
+
var mi: usize = 6;
|
|
173
|
+
while (mi < text.len and text[mi] != ch.CH_SQUOTE and text[mi] != ch.CH_DQUOTE) mi += 1;
|
|
174
|
+
if (mi < text.len) {
|
|
175
|
+
const q = text[mi];
|
|
176
|
+
const q_str: []const u8 = if (q == ch.CH_SQUOTE) "'" else "\"";
|
|
177
|
+
const end_idx = ch.indexOf(text, q_str, mi + 1);
|
|
178
|
+
if (end_idx) |ei| {
|
|
179
|
+
module_src = text[mi + 1 .. ei];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const comments = extractLeadingComments(s, stmt_start);
|
|
186
|
+
|
|
187
|
+
// Parse import clause once (cached in Declaration to avoid re-parsing in emitter)
|
|
188
|
+
var parsed_import: ?types.ParsedImport = null;
|
|
189
|
+
if (!is_side_effect) pi: {
|
|
190
|
+
const from_idx = ch.indexOf(text, " from ", 0) orelse break :pi;
|
|
191
|
+
var import_part = ch.sliceTrimmed(text, 0, from_idx);
|
|
192
|
+
|
|
193
|
+
// Strip 'import' and optional 'type'
|
|
194
|
+
if (ch.startsWith(import_part, "import type ")) {
|
|
195
|
+
import_part = ch.sliceTrimmed(import_part, 12, import_part.len);
|
|
196
|
+
} else if (ch.startsWith(import_part, "import ")) {
|
|
197
|
+
import_part = ch.sliceTrimmed(import_part, 7, import_part.len);
|
|
198
|
+
}
|
|
199
|
+
if (ch.startsWith(import_part, "type ")) {
|
|
200
|
+
import_part = ch.sliceTrimmed(import_part, 5, import_part.len);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
var default_name: ?[]const u8 = null;
|
|
204
|
+
var named_items = std.array_list.Managed([]const u8).init(s.allocator);
|
|
205
|
+
named_items.ensureTotalCapacity(8) catch break :pi;
|
|
206
|
+
|
|
207
|
+
// Also collect "resolved" items (with type/as stripped) for filtering
|
|
208
|
+
var resolved_items = std.array_list.Managed([]const u8).init(s.allocator);
|
|
209
|
+
resolved_items.ensureTotalCapacity(8) catch break :pi;
|
|
210
|
+
|
|
211
|
+
var is_namespace = false;
|
|
212
|
+
var namespace_name: ?[]const u8 = null;
|
|
213
|
+
|
|
214
|
+
const brace_start = ch.indexOfChar(import_part, '{', 0);
|
|
215
|
+
const brace_end = std.mem.lastIndexOf(u8, import_part, "}");
|
|
216
|
+
|
|
217
|
+
// Check for namespace import: * as Name
|
|
218
|
+
if (ch.indexOf(import_part, "* as ", 0)) |star_idx| {
|
|
219
|
+
is_namespace = true;
|
|
220
|
+
const ns_name = ch.sliceTrimmed(import_part, star_idx + 5, if (brace_start) |bs| bs else import_part.len);
|
|
221
|
+
if (ns_name.len > 0) {
|
|
222
|
+
namespace_name = ns_name;
|
|
223
|
+
resolved_items.append(ns_name) catch {};
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (brace_start != null and brace_end != null) {
|
|
228
|
+
const bs = brace_start.?;
|
|
229
|
+
const be = brace_end.?;
|
|
230
|
+
|
|
231
|
+
// Default import before braces
|
|
232
|
+
if (bs > 0) {
|
|
233
|
+
var before = ch.sliceTrimmed(import_part, 0, bs);
|
|
234
|
+
if (before.len > 0 and before[before.len - 1] == ',') {
|
|
235
|
+
before = ch.sliceTrimmed(before, 0, before.len - 1);
|
|
236
|
+
}
|
|
237
|
+
if (before.len > 0 and !ch.contains(before, "*")) {
|
|
238
|
+
default_name = before;
|
|
239
|
+
resolved_items.append(before) catch {};
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Named imports
|
|
244
|
+
const named_part = import_part[bs + 1 .. be];
|
|
245
|
+
var iter = std.mem.splitSequence(u8, named_part, ",");
|
|
246
|
+
while (iter.next()) |raw_item| {
|
|
247
|
+
const trimmed = ch.sliceTrimmed(raw_item, 0, raw_item.len);
|
|
248
|
+
if (trimmed.len == 0) continue;
|
|
249
|
+
named_items.append(trimmed) catch {};
|
|
250
|
+
|
|
251
|
+
// Resolve: strip 'type ' prefix, use alias after ' as '
|
|
252
|
+
var resolved = trimmed;
|
|
253
|
+
if (ch.startsWith(resolved, "type ")) {
|
|
254
|
+
resolved = ch.sliceTrimmed(resolved, 5, resolved.len);
|
|
255
|
+
}
|
|
256
|
+
if (ch.indexOf(resolved, " as ", 0)) |as_idx| {
|
|
257
|
+
resolved = ch.sliceTrimmed(resolved, as_idx + 4, resolved.len);
|
|
258
|
+
}
|
|
259
|
+
if (resolved.len > 0) resolved_items.append(resolved) catch {};
|
|
260
|
+
}
|
|
261
|
+
} else if (!is_namespace) {
|
|
262
|
+
// Default import only
|
|
263
|
+
if (import_part.len > 0 and !ch.contains(import_part, "*")) {
|
|
264
|
+
default_name = import_part;
|
|
265
|
+
resolved_items.append(import_part) catch {};
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
parsed_import = .{
|
|
270
|
+
.default_name = default_name,
|
|
271
|
+
.named_items = named_items.items,
|
|
272
|
+
.source = module_src,
|
|
273
|
+
.is_type_only = is_type_only,
|
|
274
|
+
.is_namespace = is_namespace,
|
|
275
|
+
.namespace_name = namespace_name,
|
|
276
|
+
.resolved_items = resolved_items.items,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return .{
|
|
281
|
+
.kind = .import_decl,
|
|
282
|
+
.name = "",
|
|
283
|
+
.text = text,
|
|
284
|
+
.is_exported = false,
|
|
285
|
+
.is_type_only = is_type_only,
|
|
286
|
+
.is_side_effect = is_side_effect,
|
|
287
|
+
.source_module = module_src,
|
|
288
|
+
.leading_comments = comments,
|
|
289
|
+
.start = stmt_start,
|
|
290
|
+
.end = s.pos,
|
|
291
|
+
.parsed_import = parsed_import,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ========================================================================
|
|
296
|
+
// Generics, params, return type extraction
|
|
297
|
+
// ========================================================================
|
|
298
|
+
|
|
299
|
+
/// Extract type parameters <...> (normalized to single line)
|
|
300
|
+
pub fn extractGenerics(s: *Scanner) []const u8 {
|
|
301
|
+
if (s.pos >= s.len or s.source[s.pos] != ch.CH_LANGLE) return "";
|
|
302
|
+
const start = s.pos;
|
|
303
|
+
_ = s.findMatchingClose(ch.CH_LANGLE, ch.CH_RANGLE);
|
|
304
|
+
const raw = s.source[start..s.pos];
|
|
305
|
+
// Normalize multi-line generics to single line
|
|
306
|
+
if (ch.indexOf(raw, "\n", 0) != null) {
|
|
307
|
+
// Direct alloc: output ≤ input length (whitespace collapsed)
|
|
308
|
+
const buf = s.allocator.alloc(u8, raw.len) catch return raw;
|
|
309
|
+
var pos: usize = 0;
|
|
310
|
+
var prev_space = false;
|
|
311
|
+
for (raw) |c| {
|
|
312
|
+
if (c == ' ' or c == '\t' or c == '\n' or c == '\r') {
|
|
313
|
+
if (!prev_space and pos > 0 and buf[pos - 1] != '<') {
|
|
314
|
+
buf[pos] = ' ';
|
|
315
|
+
pos += 1;
|
|
316
|
+
prev_space = true;
|
|
317
|
+
}
|
|
318
|
+
} else {
|
|
319
|
+
if (c == '>' and prev_space and pos > 0 and buf[pos - 1] == ' ') {
|
|
320
|
+
pos -= 1;
|
|
321
|
+
}
|
|
322
|
+
buf[pos] = c;
|
|
323
|
+
pos += 1;
|
|
324
|
+
prev_space = false;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return buf[0..pos];
|
|
328
|
+
}
|
|
329
|
+
return raw;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/// Extract parameter list (...) as raw text
|
|
333
|
+
pub fn extractParamList(s: *Scanner) []const u8 {
|
|
334
|
+
if (s.pos >= s.len or s.source[s.pos] != ch.CH_LPAREN) return "()";
|
|
335
|
+
const start = s.pos;
|
|
336
|
+
_ = s.findMatchingClose(ch.CH_LPAREN, ch.CH_RPAREN);
|
|
337
|
+
return s.source[start..s.pos];
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/// Extract return type annotation `: ReturnType` after params
|
|
341
|
+
pub fn extractReturnType(s: *Scanner) []const u8 {
|
|
342
|
+
s.skipWhitespaceAndComments();
|
|
343
|
+
if (s.pos < s.len and s.source[s.pos] == ch.CH_COLON) {
|
|
344
|
+
s.pos += 1; // skip :
|
|
345
|
+
s.skipWhitespaceAndComments();
|
|
346
|
+
const start = s.pos;
|
|
347
|
+
var depth: isize = 0;
|
|
348
|
+
while (s.pos < s.len) {
|
|
349
|
+
if (s.skipNonCode()) continue;
|
|
350
|
+
const c = s.source[s.pos];
|
|
351
|
+
if (c == ch.CH_LPAREN or c == ch.CH_LBRACKET or c == ch.CH_LANGLE) {
|
|
352
|
+
depth += 1;
|
|
353
|
+
} else if (c == ch.CH_RPAREN or c == ch.CH_RBRACKET or (c == ch.CH_RANGLE and !s.isArrowGT())) {
|
|
354
|
+
depth -= 1;
|
|
355
|
+
} else if (c == ch.CH_LBRACE) {
|
|
356
|
+
if (depth > 0) {
|
|
357
|
+
depth += 1;
|
|
358
|
+
} else {
|
|
359
|
+
const text_so_far = ch.sliceTrimmed(s.source, start, s.pos);
|
|
360
|
+
const is_type_ctx = text_so_far.len == 0 or
|
|
361
|
+
ch.endsWith(text_so_far, "|") or
|
|
362
|
+
ch.endsWith(text_so_far, "&") or
|
|
363
|
+
endsWithWord(text_so_far, "is") or
|
|
364
|
+
endsWithWord(text_so_far, "extends");
|
|
365
|
+
if (is_type_ctx) {
|
|
366
|
+
depth += 1;
|
|
367
|
+
} else {
|
|
368
|
+
break; // function body
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
} else if (c == ch.CH_RBRACE) {
|
|
372
|
+
if (depth == 0) break;
|
|
373
|
+
depth -= 1;
|
|
374
|
+
} else if (depth == 0 and c == ch.CH_SEMI) {
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
if (depth == 0 and s.checkASIMember()) break;
|
|
378
|
+
s.pos += 1;
|
|
379
|
+
}
|
|
380
|
+
return ch.sliceTrimmed(s.source, start, s.pos);
|
|
381
|
+
}
|
|
382
|
+
return "";
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
fn endsWithWord(text: []const u8, word: []const u8) bool {
|
|
386
|
+
if (text.len < word.len) return false;
|
|
387
|
+
const idx = text.len - word.len;
|
|
388
|
+
if (!std.mem.eql(u8, text[idx..], word)) return false;
|
|
389
|
+
return idx == 0 or !ch.isIdentChar(text[idx - 1]);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// ========================================================================
|
|
393
|
+
// Parameter processing
|
|
394
|
+
// ========================================================================
|
|
395
|
+
|
|
396
|
+
/// Check if string is a numeric literal
|
|
397
|
+
pub fn isNumericLiteral(v: []const u8) bool {
|
|
398
|
+
var i: usize = 0;
|
|
399
|
+
if (i < v.len and v[i] == '-') i += 1;
|
|
400
|
+
if (i >= v.len) return false;
|
|
401
|
+
if (v[i] < '0' or v[i] > '9') return false;
|
|
402
|
+
while (i < v.len and v[i] >= '0' and v[i] <= '9') i += 1;
|
|
403
|
+
if (i < v.len and v[i] == '.') {
|
|
404
|
+
i += 1;
|
|
405
|
+
if (i >= v.len or v[i] < '0' or v[i] > '9') return false;
|
|
406
|
+
while (i < v.len and v[i] >= '0' and v[i] <= '9') i += 1;
|
|
407
|
+
}
|
|
408
|
+
return i == v.len;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/// Infer type from a default value expression (simple cases)
|
|
412
|
+
pub fn inferTypeFromDefault(value: []const u8) []const u8 {
|
|
413
|
+
const v = std.mem.trim(u8, value, " \t\r\n");
|
|
414
|
+
if (std.mem.eql(u8, v, "true") or std.mem.eql(u8, v, "false")) return "boolean";
|
|
415
|
+
if (isNumericLiteral(v)) return "number";
|
|
416
|
+
if (v.len >= 2 and ((v[0] == '\'' and v[v.len - 1] == '\'') or (v[0] == '"' and v[v.len - 1] == '"'))) return "string";
|
|
417
|
+
if (v.len > 0 and v[0] == '[') return "unknown[]";
|
|
418
|
+
if (v.len > 0 and v[0] == '{') return "Record<string, unknown>";
|
|
419
|
+
return "unknown";
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/// Infer literal type from initializer value (for const-like / static readonly)
|
|
423
|
+
pub fn inferLiteralType(value: []const u8) []const u8 {
|
|
424
|
+
const v = std.mem.trim(u8, value, " \t\r\n");
|
|
425
|
+
if (std.mem.eql(u8, v, "true") or std.mem.eql(u8, v, "false")) return v;
|
|
426
|
+
if (isNumericLiteral(v)) return v;
|
|
427
|
+
if (v.len >= 2 and ((v[0] == '\'' and v[v.len - 1] == '\'') or (v[0] == '"' and v[v.len - 1] == '"'))) return v;
|
|
428
|
+
return "unknown";
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/// Extract type from `as Type` assertion in initializer
|
|
432
|
+
pub fn extractAssertion(init_text: []const u8) ?[]const u8 {
|
|
433
|
+
if (ch.endsWith(init_text, "as const")) return null;
|
|
434
|
+
// Only find " as " at depth 0 (not inside nested brackets/braces/parens)
|
|
435
|
+
var last_as: ?usize = null;
|
|
436
|
+
var depth: isize = 0;
|
|
437
|
+
var in_str: u8 = 0;
|
|
438
|
+
var i: usize = 0;
|
|
439
|
+
while (i < init_text.len) {
|
|
440
|
+
const c = init_text[i];
|
|
441
|
+
if (in_str != 0) {
|
|
442
|
+
if (c == '\\') {
|
|
443
|
+
i += 2;
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
if (c == in_str) in_str = 0;
|
|
447
|
+
i += 1;
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
if (c == '\'' or c == '"' or c == '`') {
|
|
451
|
+
in_str = c;
|
|
452
|
+
} else if (c == '(' or c == '[' or c == '{' or c == '<') {
|
|
453
|
+
depth += 1;
|
|
454
|
+
} else if (c == ')' or c == ']' or c == '}' or c == '>') {
|
|
455
|
+
depth -= 1;
|
|
456
|
+
} else if (depth == 0 and i + 4 <= init_text.len and std.mem.eql(u8, init_text[i .. i + 4], " as ")) {
|
|
457
|
+
last_as = i;
|
|
458
|
+
}
|
|
459
|
+
i += 1;
|
|
460
|
+
}
|
|
461
|
+
if (last_as) |idx| {
|
|
462
|
+
const after = std.mem.trim(u8, init_text[idx + 4 ..], " \t\r\n");
|
|
463
|
+
if (after.len > 0 and !std.mem.eql(u8, after, "const")) return after;
|
|
464
|
+
}
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/// Build DTS-safe parameter text from raw parameter text
|
|
469
|
+
pub fn buildDtsParams(s: *Scanner, raw_params: []const u8) []const u8 {
|
|
470
|
+
if (raw_params.len < 2) return "()";
|
|
471
|
+
const inner = std.mem.trim(u8, raw_params[1 .. raw_params.len - 1], " \t\r\n");
|
|
472
|
+
if (inner.len == 0) return "()";
|
|
473
|
+
|
|
474
|
+
// Fast path: single-pass analysis — allow { and [ in type positions (after colon).
|
|
475
|
+
// Only reject destructuring ({ or [ before colon), defaults (=), and decorators (@).
|
|
476
|
+
if (ch.indexOfChar(raw_params, '\n', 0) == null and inner.len > 0) {
|
|
477
|
+
var depth: isize = 0;
|
|
478
|
+
var seen_colon = false;
|
|
479
|
+
var colons: usize = 0;
|
|
480
|
+
var commas: usize = 0;
|
|
481
|
+
var can_passthrough = true;
|
|
482
|
+
var fp_i: usize = 0;
|
|
483
|
+
while (fp_i < inner.len) : (fp_i += 1) {
|
|
484
|
+
const c = inner[fp_i];
|
|
485
|
+
if (c == ch.CH_LPAREN or c == ch.CH_LANGLE) {
|
|
486
|
+
depth += 1;
|
|
487
|
+
} else if (c == ch.CH_RPAREN or c == ch.CH_RANGLE) {
|
|
488
|
+
depth -= 1;
|
|
489
|
+
} else if (c == ch.CH_LBRACE or c == ch.CH_LBRACKET) {
|
|
490
|
+
if (depth == 0 and !seen_colon) { can_passthrough = false; break; }
|
|
491
|
+
depth += 1;
|
|
492
|
+
} else if (c == ch.CH_RBRACE or c == ch.CH_RBRACKET) {
|
|
493
|
+
depth -= 1;
|
|
494
|
+
} else if (depth == 0) {
|
|
495
|
+
if (c == ch.CH_COLON) { colons += 1; seen_colon = true; } else if (c == ch.CH_COMMA) { commas += 1; seen_colon = false; } else if (c == ch.CH_EQUAL and (fp_i + 1 >= inner.len or (inner[fp_i + 1] != ch.CH_RANGLE and inner[fp_i + 1] != ch.CH_EQUAL))) {
|
|
496
|
+
can_passthrough = false;
|
|
497
|
+
break;
|
|
498
|
+
} else if (c == ch.CH_AT) {
|
|
499
|
+
can_passthrough = false;
|
|
500
|
+
break;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
if (can_passthrough and colons >= commas + 1) {
|
|
505
|
+
// Check for parameter modifiers
|
|
506
|
+
var has_modifier = false;
|
|
507
|
+
for (types.PARAM_MODIFIERS) |mod| {
|
|
508
|
+
if (ch.indexOf(inner, mod, 0)) |mod_idx| {
|
|
509
|
+
const after_idx = mod_idx + mod.len;
|
|
510
|
+
const before_ok = mod_idx == 0 or !ch.isIdentChar(inner[mod_idx - 1]);
|
|
511
|
+
const after_ok = after_idx >= inner.len or !ch.isIdentChar(inner[after_idx]);
|
|
512
|
+
if (before_ok and after_ok) {
|
|
513
|
+
has_modifier = true;
|
|
514
|
+
break;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
if (!has_modifier) return raw_params;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
// Split parameters by comma at depth 0
|
|
523
|
+
var params = std.array_list.Managed([]const u8).init(s.allocator);
|
|
524
|
+
params.ensureTotalCapacity(8) catch {};
|
|
525
|
+
var param_start: usize = 0;
|
|
526
|
+
var depth: isize = 0;
|
|
527
|
+
var in_str = false;
|
|
528
|
+
var str_ch: u8 = 0;
|
|
529
|
+
var skip_next = false;
|
|
530
|
+
|
|
531
|
+
for (inner, 0..) |c, i| {
|
|
532
|
+
if (skip_next) {
|
|
533
|
+
skip_next = false;
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
if (in_str) {
|
|
537
|
+
if (c == ch.CH_BACKSLASH) {
|
|
538
|
+
skip_next = true; // skip the escaped character
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
541
|
+
if (c == str_ch) in_str = false;
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
if (c == ch.CH_SQUOTE or c == ch.CH_DQUOTE or c == ch.CH_BACKTICK) {
|
|
545
|
+
in_str = true;
|
|
546
|
+
str_ch = c;
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
if (c == ch.CH_LPAREN or c == ch.CH_LBRACE or c == ch.CH_LBRACKET or c == ch.CH_LANGLE) {
|
|
550
|
+
depth += 1;
|
|
551
|
+
} else if (c == ch.CH_RPAREN or c == ch.CH_RBRACE or c == ch.CH_RBRACKET or c == ch.CH_RANGLE) {
|
|
552
|
+
depth -= 1;
|
|
553
|
+
} else if (c == ch.CH_COMMA and depth == 0) {
|
|
554
|
+
params.append(std.mem.trim(u8, inner[param_start..i], " \t\r\n")) catch {};
|
|
555
|
+
param_start = i + 1;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
params.append(std.mem.trim(u8, inner[param_start..], " \t\r\n")) catch {};
|
|
559
|
+
|
|
560
|
+
// Build DTS params — direct alloc, output ≤ raw_params.len + extra for type annotations
|
|
561
|
+
const buf = s.allocator.alloc(u8, raw_params.len * 2 + 16) catch return "()";
|
|
562
|
+
var pos: usize = 0;
|
|
563
|
+
buf[pos] = '(';
|
|
564
|
+
pos += 1;
|
|
565
|
+
var first = true;
|
|
566
|
+
for (params.items) |param| {
|
|
567
|
+
if (param.len == 0) continue;
|
|
568
|
+
if (!first) {
|
|
569
|
+
@memcpy(buf[pos..][0..2], ", ");
|
|
570
|
+
pos += 2;
|
|
571
|
+
}
|
|
572
|
+
first = false;
|
|
573
|
+
const dts_param = buildSingleDtsParam(s, param);
|
|
574
|
+
@memcpy(buf[pos..][0..dts_param.len], dts_param);
|
|
575
|
+
pos += dts_param.len;
|
|
576
|
+
}
|
|
577
|
+
buf[pos] = ')';
|
|
578
|
+
pos += 1;
|
|
579
|
+
return buf[0..pos];
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/// Build a single DTS parameter from raw source text
|
|
583
|
+
pub fn buildSingleDtsParam(s: *Scanner, raw: []const u8) []const u8 {
|
|
584
|
+
var p = std.mem.trim(u8, raw, " \t\r\n");
|
|
585
|
+
|
|
586
|
+
// Handle rest parameter
|
|
587
|
+
const is_rest = ch.startsWith(p, "...");
|
|
588
|
+
if (is_rest) p = std.mem.trim(u8, p[3..], " \t\r\n");
|
|
589
|
+
|
|
590
|
+
// Handle decorators (skip @... before param)
|
|
591
|
+
while (p.len > 0 and p[0] == '@') {
|
|
592
|
+
var di: usize = 1;
|
|
593
|
+
while (di < p.len and ch.isIdentChar(p[di])) di += 1;
|
|
594
|
+
if (di < p.len and p[di] == ch.CH_LPAREN) {
|
|
595
|
+
var dd: isize = 1;
|
|
596
|
+
di += 1;
|
|
597
|
+
while (di < p.len and dd > 0) {
|
|
598
|
+
if (p[di] == ch.CH_LPAREN) dd += 1 else if (p[di] == ch.CH_RPAREN) dd -= 1;
|
|
599
|
+
di += 1;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
p = std.mem.trim(u8, p[di..], " \t\r\n");
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// Strip parameter modifiers
|
|
606
|
+
var stripped = true;
|
|
607
|
+
while (stripped) {
|
|
608
|
+
stripped = false;
|
|
609
|
+
for (types.PARAM_MODIFIERS) |mod| {
|
|
610
|
+
if (p.len > mod.len and ch.startsWith(p, mod) and !ch.isIdentChar(p[mod.len])) {
|
|
611
|
+
p = std.mem.trim(u8, p[mod.len..], " \t\r\n");
|
|
612
|
+
stripped = true;
|
|
613
|
+
break;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Find : and = at depth 0
|
|
619
|
+
var colon_idx: ?usize = null;
|
|
620
|
+
var equal_idx: ?usize = null;
|
|
621
|
+
var depth: isize = 0;
|
|
622
|
+
var in_str2 = false;
|
|
623
|
+
var str_ch2: u8 = 0;
|
|
624
|
+
var skip_next2 = false;
|
|
625
|
+
|
|
626
|
+
for (p, 0..) |c, i| {
|
|
627
|
+
if (skip_next2) {
|
|
628
|
+
skip_next2 = false;
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
if (in_str2) {
|
|
632
|
+
if (c == ch.CH_BACKSLASH) {
|
|
633
|
+
skip_next2 = true;
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
if (c == str_ch2) in_str2 = false;
|
|
637
|
+
continue;
|
|
638
|
+
}
|
|
639
|
+
if (c == ch.CH_SQUOTE or c == ch.CH_DQUOTE or c == ch.CH_BACKTICK) {
|
|
640
|
+
in_str2 = true;
|
|
641
|
+
str_ch2 = c;
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
if (c == ch.CH_LPAREN or c == ch.CH_LBRACE or c == ch.CH_LBRACKET or c == ch.CH_LANGLE) {
|
|
645
|
+
depth += 1;
|
|
646
|
+
} else if (c == ch.CH_RPAREN or c == ch.CH_RBRACE or c == ch.CH_RBRACKET or c == ch.CH_RANGLE) {
|
|
647
|
+
depth -= 1;
|
|
648
|
+
} else if (depth == 0) {
|
|
649
|
+
if (c == ch.CH_COLON and colon_idx == null) {
|
|
650
|
+
colon_idx = i;
|
|
651
|
+
} else if (c == ch.CH_EQUAL and equal_idx == null) {
|
|
652
|
+
// Check it's not == or =>
|
|
653
|
+
const not_double = i == 0 or p[i - 1] != ch.CH_EQUAL;
|
|
654
|
+
const not_arrow = i + 1 >= p.len or (p[i + 1] != ch.CH_EQUAL and p[i + 1] != ch.CH_RANGLE);
|
|
655
|
+
if (not_double and not_arrow) {
|
|
656
|
+
equal_idx = i;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
var name: []const u8 = undefined;
|
|
663
|
+
var param_type: []const u8 = undefined;
|
|
664
|
+
const has_default = equal_idx != null;
|
|
665
|
+
|
|
666
|
+
if (colon_idx) |ci| {
|
|
667
|
+
if (equal_idx == null or ci < equal_idx.?) {
|
|
668
|
+
name = std.mem.trim(u8, p[0..ci], " \t\r\n");
|
|
669
|
+
if (equal_idx) |ei| {
|
|
670
|
+
param_type = std.mem.trim(u8, p[ci + 1 .. ei], " \t\r\n");
|
|
671
|
+
} else {
|
|
672
|
+
param_type = std.mem.trim(u8, p[ci + 1 ..], " \t\r\n");
|
|
673
|
+
}
|
|
674
|
+
} else {
|
|
675
|
+
name = std.mem.trim(u8, p[0..equal_idx.?], " \t\r\n");
|
|
676
|
+
param_type = inferTypeFromDefault(std.mem.trim(u8, p[equal_idx.? + 1 ..], " \t\r\n"));
|
|
677
|
+
}
|
|
678
|
+
} else if (equal_idx) |ei| {
|
|
679
|
+
name = std.mem.trim(u8, p[0..ei], " \t\r\n");
|
|
680
|
+
param_type = inferTypeFromDefault(std.mem.trim(u8, p[ei + 1 ..], " \t\r\n"));
|
|
681
|
+
} else {
|
|
682
|
+
name = p;
|
|
683
|
+
param_type = "unknown";
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// Clean destructured patterns: strip defaults and rest operators
|
|
687
|
+
if (name.len > 0 and (name[0] == '{' or name[0] == '[')) {
|
|
688
|
+
name = cleanDestructuredPattern(s.allocator, name);
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
// Handle optional marker
|
|
692
|
+
const is_optional = (name.len > 0 and name[name.len - 1] == '?') or has_default;
|
|
693
|
+
if (name.len > 0 and name[name.len - 1] == '?') {
|
|
694
|
+
name = std.mem.trim(u8, name[0 .. name.len - 1], " \t\r\n");
|
|
695
|
+
}
|
|
696
|
+
const opt_marker: []const u8 = if (is_optional and !is_rest) "?" else "";
|
|
697
|
+
|
|
698
|
+
// Build result
|
|
699
|
+
var result = std.array_list.Managed(u8).init(s.allocator);
|
|
700
|
+
if (is_rest) result.appendSlice("...") catch {};
|
|
701
|
+
result.appendSlice(name) catch {};
|
|
702
|
+
result.appendSlice(opt_marker) catch {};
|
|
703
|
+
result.appendSlice(": ") catch {};
|
|
704
|
+
result.appendSlice(param_type) catch {};
|
|
705
|
+
return result.toOwnedSlice() catch "unknown: unknown";
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/// Clean a destructured pattern by stripping default values and rest operators.
|
|
709
|
+
/// E.g., "{ name, age = 0, ...props }" → "{ name, age, props }"
|
|
710
|
+
/// Also handles multiline patterns like:
|
|
711
|
+
/// "{\n name,\n headers = { ... },\n}" → "{\n name,\n headers,\n}"
|
|
712
|
+
fn cleanDestructuredPattern(alloc: std.mem.Allocator, pattern: []const u8) []const u8 {
|
|
713
|
+
var result = std.array_list.Managed(u8).init(alloc);
|
|
714
|
+
result.ensureTotalCapacity(pattern.len) catch {};
|
|
715
|
+
var i: usize = 0;
|
|
716
|
+
var depth: isize = 0;
|
|
717
|
+
var in_str = false;
|
|
718
|
+
var str_c: u8 = 0;
|
|
719
|
+
|
|
720
|
+
while (i < pattern.len) : (i += 1) {
|
|
721
|
+
const c = pattern[i];
|
|
722
|
+
|
|
723
|
+
// String tracking
|
|
724
|
+
if (!in_str and (c == '\'' or c == '"' or c == '`')) {
|
|
725
|
+
in_str = true;
|
|
726
|
+
str_c = c;
|
|
727
|
+
result.append(c) catch {};
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
if (in_str) {
|
|
731
|
+
result.append(c) catch {};
|
|
732
|
+
if (c == str_c and (i == 0 or pattern[i - 1] != '\\')) {
|
|
733
|
+
in_str = false;
|
|
734
|
+
}
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// Track depth
|
|
739
|
+
if (c == '{' or c == '[' or c == '(') depth += 1;
|
|
740
|
+
if (c == '}' or c == ']' or c == ')') depth -= 1;
|
|
741
|
+
|
|
742
|
+
// At depth 1 (inside the outermost braces), handle defaults and rest
|
|
743
|
+
if (depth == 1) {
|
|
744
|
+
// Skip "..." rest operator before identifiers
|
|
745
|
+
if (c == '.' and i + 2 < pattern.len and pattern[i + 1] == '.' and pattern[i + 2] == '.') {
|
|
746
|
+
i += 2; // skip 2 more dots (loop will advance 1 more)
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// Skip "= value" default values
|
|
751
|
+
if (c == '=' and i + 1 < pattern.len and pattern[i + 1] != '>' and (i == 0 or pattern[i - 1] != '!')) {
|
|
752
|
+
// Skip whitespace before '='
|
|
753
|
+
while (result.items.len > 0 and (result.items[result.items.len - 1] == ' ' or result.items[result.items.len - 1] == '\t')) {
|
|
754
|
+
_ = result.pop();
|
|
755
|
+
}
|
|
756
|
+
// Skip the default value: everything up to ',' or '}'/']' at this depth
|
|
757
|
+
i += 1; // skip '='
|
|
758
|
+
var inner_depth: isize = 0;
|
|
759
|
+
while (i < pattern.len) {
|
|
760
|
+
const dc = pattern[i];
|
|
761
|
+
if (!in_str and (dc == '\'' or dc == '"' or dc == '`')) {
|
|
762
|
+
in_str = true;
|
|
763
|
+
str_c = dc;
|
|
764
|
+
i += 1;
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
767
|
+
if (in_str) {
|
|
768
|
+
if (dc == str_c and (i == 0 or pattern[i - 1] != '\\')) {
|
|
769
|
+
in_str = false;
|
|
770
|
+
}
|
|
771
|
+
i += 1;
|
|
772
|
+
continue;
|
|
773
|
+
}
|
|
774
|
+
if (dc == '{' or dc == '[' or dc == '(') inner_depth += 1;
|
|
775
|
+
if (dc == '}' or dc == ']' or dc == ')') {
|
|
776
|
+
if (inner_depth == 0) break; // Hit the closing brace
|
|
777
|
+
inner_depth -= 1;
|
|
778
|
+
}
|
|
779
|
+
if (dc == ',' and inner_depth == 0) break;
|
|
780
|
+
i += 1;
|
|
781
|
+
}
|
|
782
|
+
// Don't advance i further — the loop's :i+=1 will handle it,
|
|
783
|
+
// but we need to emit the comma or closing brace
|
|
784
|
+
i -= 1; // compensate for the loop's += 1
|
|
785
|
+
continue;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
result.append(c) catch {};
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
const cleaned = result.toOwnedSlice() catch return pattern;
|
|
793
|
+
|
|
794
|
+
// If the pattern contains newlines, try to collapse to single line if short enough
|
|
795
|
+
if (ch.indexOf(cleaned, "\n", 0) != null) {
|
|
796
|
+
// Collapse newlines and extra whitespace to single spaces
|
|
797
|
+
var collapsed = std.array_list.Managed(u8).init(alloc);
|
|
798
|
+
collapsed.ensureTotalCapacity(cleaned.len) catch {};
|
|
799
|
+
var in_ws = false;
|
|
800
|
+
for (cleaned) |c2| {
|
|
801
|
+
if (c2 == '\n' or c2 == '\r' or c2 == ' ' or c2 == '\t') {
|
|
802
|
+
if (!in_ws) {
|
|
803
|
+
collapsed.append(' ') catch {};
|
|
804
|
+
in_ws = true;
|
|
805
|
+
}
|
|
806
|
+
} else {
|
|
807
|
+
collapsed.append(c2) catch {};
|
|
808
|
+
in_ws = false;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
const collapsed_str = std.mem.trim(u8, collapsed.items, " \t\r\n");
|
|
812
|
+
if (collapsed_str.len <= 40) {
|
|
813
|
+
return collapsed_str;
|
|
814
|
+
}
|
|
815
|
+
// Keep multiline but normalize indent
|
|
816
|
+
var normalized = std.array_list.Managed(u8).init(alloc);
|
|
817
|
+
normalized.ensureTotalCapacity(cleaned.len) catch {};
|
|
818
|
+
var line_start: usize = 0;
|
|
819
|
+
var ci: usize = 0;
|
|
820
|
+
while (ci <= cleaned.len) : (ci += 1) {
|
|
821
|
+
if (ci == cleaned.len or cleaned[ci] == '\n') {
|
|
822
|
+
const line = std.mem.trim(u8, cleaned[line_start..ci], " \t\r\n");
|
|
823
|
+
if (line.len > 0) {
|
|
824
|
+
if (normalized.items.len > 0) normalized.append('\n') catch {};
|
|
825
|
+
if (line[0] == '{' or line[0] == '}' or line[0] == '[' or line[0] == ']') {
|
|
826
|
+
normalized.appendSlice(line) catch {};
|
|
827
|
+
} else {
|
|
828
|
+
normalized.appendSlice(" ") catch {};
|
|
829
|
+
normalized.appendSlice(line) catch {};
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
line_start = ci + 1;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
return normalized.toOwnedSlice() catch cleaned;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
return cleaned;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// ========================================================================
|
|
842
|
+
// Function extraction
|
|
843
|
+
// ========================================================================
|
|
844
|
+
|
|
845
|
+
/// Extract a function declaration and build DTS text
|
|
846
|
+
pub fn extractFunction(s: *Scanner, decl_start: usize, is_exported: bool, is_async: bool, is_default: bool) ?Declaration {
|
|
847
|
+
s.pos += 8; // skip 'function'
|
|
848
|
+
s.skipWhitespaceAndComments();
|
|
849
|
+
|
|
850
|
+
const is_generator = s.pos < s.len and s.source[s.pos] == ch.CH_STAR;
|
|
851
|
+
if (is_generator) {
|
|
852
|
+
s.pos += 1;
|
|
853
|
+
s.skipWhitespaceAndComments();
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
const name = s.readIdent();
|
|
857
|
+
if (name.len == 0 and !is_default) return null;
|
|
858
|
+
s.skipWhitespaceAndComments();
|
|
859
|
+
|
|
860
|
+
const generics = extractGenerics(s);
|
|
861
|
+
s.skipWhitespaceAndComments();
|
|
862
|
+
|
|
863
|
+
const raw_params = extractParamList(s);
|
|
864
|
+
s.skipWhitespaceAndComments();
|
|
865
|
+
|
|
866
|
+
var return_type = extractReturnType(s);
|
|
867
|
+
if (return_type.len == 0) {
|
|
868
|
+
if (is_async and is_generator) {
|
|
869
|
+
return_type = "AsyncGenerator<unknown, void, unknown>";
|
|
870
|
+
} else if (is_generator) {
|
|
871
|
+
return_type = "Generator<unknown, void, unknown>";
|
|
872
|
+
} else if (is_async) {
|
|
873
|
+
return_type = "Promise<void>";
|
|
874
|
+
} else {
|
|
875
|
+
return_type = "void";
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
s.skipWhitespaceAndComments();
|
|
880
|
+
var has_body = false;
|
|
881
|
+
if (s.pos < s.len and s.source[s.pos] == ch.CH_LBRACE) {
|
|
882
|
+
has_body = true;
|
|
883
|
+
_ = s.findMatchingClose(ch.CH_LBRACE, ch.CH_RBRACE);
|
|
884
|
+
} else if (s.pos < s.len and s.source[s.pos] == ch.CH_SEMI) {
|
|
885
|
+
s.pos += 1;
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
const dts_params = buildDtsParams(s, raw_params);
|
|
889
|
+
const func_name = if (name.len > 0) name else "default";
|
|
890
|
+
|
|
891
|
+
// Build DTS text
|
|
892
|
+
var text = std.array_list.Managed(u8).init(s.allocator);
|
|
893
|
+
text.ensureTotalCapacity(128) catch {};
|
|
894
|
+
if (is_exported) text.appendSlice("export ") catch {};
|
|
895
|
+
text.appendSlice("declare function ") catch {};
|
|
896
|
+
text.appendSlice(func_name) catch {};
|
|
897
|
+
text.appendSlice(generics) catch {};
|
|
898
|
+
text.appendSlice(dts_params) catch {};
|
|
899
|
+
text.appendSlice(": ") catch {};
|
|
900
|
+
text.appendSlice(return_type) catch {};
|
|
901
|
+
text.append(';') catch {};
|
|
902
|
+
|
|
903
|
+
const dts_text = text.toOwnedSlice() catch "";
|
|
904
|
+
const comments = extractLeadingComments(s, decl_start);
|
|
905
|
+
|
|
906
|
+
if (has_body) {
|
|
907
|
+
s.func_body_indices.put(s.declarations.items.len, {}) catch {};
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
return .{
|
|
911
|
+
.kind = .function_decl,
|
|
912
|
+
.name = func_name,
|
|
913
|
+
.text = dts_text,
|
|
914
|
+
.is_exported = is_exported,
|
|
915
|
+
.is_default = is_default,
|
|
916
|
+
.is_async = is_async,
|
|
917
|
+
.is_generator = is_generator,
|
|
918
|
+
.generics = generics,
|
|
919
|
+
.leading_comments = comments,
|
|
920
|
+
.start = decl_start,
|
|
921
|
+
.end = s.pos,
|
|
922
|
+
.has_body = has_body,
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
// ========================================================================
|
|
927
|
+
// Variable extraction
|
|
928
|
+
// ========================================================================
|
|
929
|
+
|
|
930
|
+
/// Extract variable declaration(s)
|
|
931
|
+
pub fn extractVariable(s: *Scanner, decl_start: usize, kind: []const u8, is_exported: bool) []const Declaration {
|
|
932
|
+
s.pos += kind.len; // skip const/let/var
|
|
933
|
+
s.skipWhitespaceAndComments();
|
|
934
|
+
|
|
935
|
+
var results = std.array_list.Managed(Declaration).init(s.allocator);
|
|
936
|
+
results.ensureTotalCapacity(2) catch {};
|
|
937
|
+
|
|
938
|
+
if (s.pos >= s.len) return results.toOwnedSlice() catch &.{};
|
|
939
|
+
|
|
940
|
+
const c = s.source[s.pos];
|
|
941
|
+
// Skip destructuring patterns
|
|
942
|
+
if (c == ch.CH_LBRACE or c == ch.CH_LBRACKET) {
|
|
943
|
+
s.skipToStatementEnd();
|
|
944
|
+
return results.toOwnedSlice() catch &.{};
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
const name = s.readIdent();
|
|
948
|
+
if (name.len == 0) {
|
|
949
|
+
s.skipToStatementEnd();
|
|
950
|
+
return results.toOwnedSlice() catch &.{};
|
|
951
|
+
}
|
|
952
|
+
s.skipWhitespaceAndComments();
|
|
953
|
+
|
|
954
|
+
var type_annotation: []const u8 = "";
|
|
955
|
+
var initializer_text: []const u8 = "";
|
|
956
|
+
var is_as_const = false;
|
|
957
|
+
|
|
958
|
+
// Type annotation
|
|
959
|
+
if (s.pos < s.len and s.source[s.pos] == ch.CH_COLON) {
|
|
960
|
+
s.pos += 1;
|
|
961
|
+
s.skipWhitespaceAndComments();
|
|
962
|
+
const type_start = s.pos;
|
|
963
|
+
var depth: isize = 0;
|
|
964
|
+
while (s.pos < s.len) {
|
|
965
|
+
if (s.skipNonCode()) continue;
|
|
966
|
+
const tc = s.source[s.pos];
|
|
967
|
+
if (tc == ch.CH_LPAREN or tc == ch.CH_LBRACE or tc == ch.CH_LBRACKET or tc == ch.CH_LANGLE) {
|
|
968
|
+
depth += 1;
|
|
969
|
+
} else if (tc == ch.CH_RPAREN or tc == ch.CH_RBRACE or tc == ch.CH_RBRACKET or (tc == ch.CH_RANGLE and !s.isArrowGT())) {
|
|
970
|
+
depth -= 1;
|
|
971
|
+
} else if (depth == 0 and (tc == ch.CH_EQUAL or tc == ch.CH_SEMI or tc == ch.CH_COMMA)) {
|
|
972
|
+
break;
|
|
973
|
+
}
|
|
974
|
+
if (depth == 0 and s.checkASITopLevel()) break;
|
|
975
|
+
s.pos += 1;
|
|
976
|
+
}
|
|
977
|
+
type_annotation = s.sliceTrimmed(type_start, s.pos);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// Initializer
|
|
981
|
+
if (s.pos < s.len and s.source[s.pos] == ch.CH_EQUAL) {
|
|
982
|
+
if (s.isolated_declarations and type_annotation.len > 0 and !type_inf.isGenericType(type_annotation)) {
|
|
983
|
+
s.skipToStatementEnd();
|
|
984
|
+
} else {
|
|
985
|
+
s.pos += 1;
|
|
986
|
+
s.skipWhitespaceAndComments();
|
|
987
|
+
const init_start = s.pos;
|
|
988
|
+
var depth: isize = 0;
|
|
989
|
+
while (s.pos < s.len) {
|
|
990
|
+
if (s.skipNonCode()) continue;
|
|
991
|
+
const ic = s.source[s.pos];
|
|
992
|
+
if (ic == ch.CH_LPAREN or ic == ch.CH_LBRACE or ic == ch.CH_LBRACKET or ic == ch.CH_LANGLE) {
|
|
993
|
+
depth += 1;
|
|
994
|
+
} else if (ic == ch.CH_RPAREN or ic == ch.CH_RBRACE or ic == ch.CH_RBRACKET or (ic == ch.CH_RANGLE and !s.isArrowGT())) {
|
|
995
|
+
depth -= 1;
|
|
996
|
+
} else if (depth == 0 and (ic == ch.CH_SEMI or ic == ch.CH_COMMA)) {
|
|
997
|
+
break;
|
|
998
|
+
}
|
|
999
|
+
if (depth == 0 and s.checkASITopLevel()) break;
|
|
1000
|
+
s.pos += 1;
|
|
1001
|
+
}
|
|
1002
|
+
initializer_text = s.sliceTrimmed(init_start, s.pos);
|
|
1003
|
+
if (ch.endsWith(initializer_text, " as const") or std.mem.eql(u8, initializer_text, "const")) {
|
|
1004
|
+
is_as_const = true;
|
|
1005
|
+
if (type_annotation.len == 0) {
|
|
1006
|
+
const val = if (ch.endsWith(initializer_text, " as const"))
|
|
1007
|
+
std.mem.trim(u8, initializer_text[0 .. initializer_text.len - 9], " \t\r\n")
|
|
1008
|
+
else
|
|
1009
|
+
initializer_text;
|
|
1010
|
+
const lit = inferLiteralType(val);
|
|
1011
|
+
if (!std.mem.eql(u8, lit, "unknown")) {
|
|
1012
|
+
type_annotation = lit;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
} else if (type_annotation.len == 0) {
|
|
1016
|
+
const as_type = extractAssertion(initializer_text);
|
|
1017
|
+
if (as_type) |t| type_annotation = t;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
// Skip comma or semicolon
|
|
1023
|
+
if (s.pos < s.len) {
|
|
1024
|
+
const sc = s.source[s.pos];
|
|
1025
|
+
if (sc == ch.CH_SEMI) s.pos += 1;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
const comments = extractLeadingComments(s, decl_start);
|
|
1029
|
+
const final_type = if (type_annotation.len > 0) type_annotation else "unknown";
|
|
1030
|
+
|
|
1031
|
+
// Build DTS text
|
|
1032
|
+
var text = std.array_list.Managed(u8).init(s.allocator);
|
|
1033
|
+
text.ensureTotalCapacity(128) catch {};
|
|
1034
|
+
if (is_exported) text.appendSlice("export ") catch {};
|
|
1035
|
+
text.appendSlice("declare ") catch {};
|
|
1036
|
+
text.appendSlice(kind) catch {};
|
|
1037
|
+
text.append(' ') catch {};
|
|
1038
|
+
text.appendSlice(name) catch {};
|
|
1039
|
+
text.appendSlice(": ") catch {};
|
|
1040
|
+
text.appendSlice(final_type) catch {};
|
|
1041
|
+
text.append(';') catch {};
|
|
1042
|
+
|
|
1043
|
+
// Store the variable kind in modifiers
|
|
1044
|
+
const mods = s.allocator.alloc([]const u8, 1) catch null;
|
|
1045
|
+
if (mods) |m| {
|
|
1046
|
+
m[0] = kind;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
results.append(.{
|
|
1050
|
+
.kind = .variable_decl,
|
|
1051
|
+
.name = name,
|
|
1052
|
+
.text = text.toOwnedSlice() catch "",
|
|
1053
|
+
.is_exported = is_exported,
|
|
1054
|
+
.modifiers = mods,
|
|
1055
|
+
.type_annotation = type_annotation,
|
|
1056
|
+
.value = initializer_text,
|
|
1057
|
+
.leading_comments = comments,
|
|
1058
|
+
.start = decl_start,
|
|
1059
|
+
.end = s.pos,
|
|
1060
|
+
}) catch {};
|
|
1061
|
+
|
|
1062
|
+
return results.toOwnedSlice() catch &.{};
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
// ========================================================================
|
|
1066
|
+
// Interface extraction
|
|
1067
|
+
// ========================================================================
|
|
1068
|
+
|
|
1069
|
+
/// Extract interface declaration
|
|
1070
|
+
pub fn extractInterface(s: *Scanner, decl_start: usize, is_exported: bool) Declaration {
|
|
1071
|
+
s.pos += 9; // skip 'interface'
|
|
1072
|
+
s.skipWhitespaceAndComments();
|
|
1073
|
+
|
|
1074
|
+
const name = s.readIdent();
|
|
1075
|
+
s.skipWhitespaceAndComments();
|
|
1076
|
+
|
|
1077
|
+
const generics = extractGenerics(s);
|
|
1078
|
+
s.skipWhitespaceAndComments();
|
|
1079
|
+
|
|
1080
|
+
var extends_clause: []const u8 = "";
|
|
1081
|
+
if (s.matchWord("extends")) {
|
|
1082
|
+
s.pos += 7;
|
|
1083
|
+
s.skipWhitespaceAndComments();
|
|
1084
|
+
const ext_start = s.pos;
|
|
1085
|
+
var depth: isize = 0;
|
|
1086
|
+
while (s.pos < s.len) {
|
|
1087
|
+
if (s.skipNonCode()) continue;
|
|
1088
|
+
const c = s.source[s.pos];
|
|
1089
|
+
if (c == ch.CH_LANGLE) {
|
|
1090
|
+
depth += 1;
|
|
1091
|
+
} else if (c == ch.CH_RANGLE and !s.isArrowGT()) {
|
|
1092
|
+
depth -= 1;
|
|
1093
|
+
} else if (c == ch.CH_LBRACE and depth == 0) {
|
|
1094
|
+
break;
|
|
1095
|
+
}
|
|
1096
|
+
s.pos += 1;
|
|
1097
|
+
}
|
|
1098
|
+
extends_clause = s.sliceTrimmed(ext_start, s.pos);
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
s.skipWhitespaceAndComments();
|
|
1102
|
+
const raw_body = if (s.pos < s.len and s.source[s.pos] == ch.CH_LBRACE) s.extractBraceBlock() else "{}";
|
|
1103
|
+
const body = cleanBraceBlock(s, raw_body);
|
|
1104
|
+
|
|
1105
|
+
// Build DTS text
|
|
1106
|
+
var text = std.array_list.Managed(u8).init(s.allocator);
|
|
1107
|
+
text.ensureTotalCapacity(128) catch {};
|
|
1108
|
+
if (is_exported) text.appendSlice("export ") catch {};
|
|
1109
|
+
text.appendSlice("declare interface ") catch {};
|
|
1110
|
+
text.appendSlice(name) catch {};
|
|
1111
|
+
text.appendSlice(generics) catch {};
|
|
1112
|
+
if (extends_clause.len > 0) {
|
|
1113
|
+
text.appendSlice(" extends ") catch {};
|
|
1114
|
+
text.appendSlice(extends_clause) catch {};
|
|
1115
|
+
}
|
|
1116
|
+
text.append(' ') catch {};
|
|
1117
|
+
text.appendSlice(body) catch {};
|
|
1118
|
+
|
|
1119
|
+
const comments = extractLeadingComments(s, decl_start);
|
|
1120
|
+
|
|
1121
|
+
return .{
|
|
1122
|
+
.kind = .interface_decl,
|
|
1123
|
+
.name = name,
|
|
1124
|
+
.text = text.toOwnedSlice() catch "",
|
|
1125
|
+
.is_exported = is_exported,
|
|
1126
|
+
.extends_clause = extends_clause,
|
|
1127
|
+
.generics = generics,
|
|
1128
|
+
.leading_comments = comments,
|
|
1129
|
+
.start = decl_start,
|
|
1130
|
+
.end = s.pos,
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
// ========================================================================
|
|
1135
|
+
// Type alias extraction
|
|
1136
|
+
// ========================================================================
|
|
1137
|
+
|
|
1138
|
+
/// Extract type alias declaration
|
|
1139
|
+
pub fn extractTypeAlias(s: *Scanner, decl_start: usize, is_exported: bool) Declaration {
|
|
1140
|
+
s.pos += 4; // skip 'type'
|
|
1141
|
+
s.skipWhitespaceAndComments();
|
|
1142
|
+
|
|
1143
|
+
const name = s.readIdent();
|
|
1144
|
+
s.skipWhitespaceAndComments();
|
|
1145
|
+
|
|
1146
|
+
const generics = extractGenerics(s);
|
|
1147
|
+
s.skipWhitespaceAndComments();
|
|
1148
|
+
|
|
1149
|
+
if (s.pos < s.len and s.source[s.pos] == ch.CH_EQUAL) s.pos += 1;
|
|
1150
|
+
s.skipWhitespaceAndComments();
|
|
1151
|
+
|
|
1152
|
+
const type_start = s.pos;
|
|
1153
|
+
var depth: isize = 0;
|
|
1154
|
+
while (s.pos < s.len) {
|
|
1155
|
+
if (s.skipNonCode()) continue;
|
|
1156
|
+
const c = s.source[s.pos];
|
|
1157
|
+
if (c == ch.CH_LPAREN or c == ch.CH_LBRACE or c == ch.CH_LBRACKET or c == ch.CH_LANGLE) {
|
|
1158
|
+
depth += 1;
|
|
1159
|
+
} else if (c == ch.CH_RPAREN or c == ch.CH_RBRACE or c == ch.CH_RBRACKET or (c == ch.CH_RANGLE and !s.isArrowGT())) {
|
|
1160
|
+
depth -= 1;
|
|
1161
|
+
} else if (depth == 0 and c == ch.CH_SEMI) {
|
|
1162
|
+
break;
|
|
1163
|
+
}
|
|
1164
|
+
if (depth == 0 and s.checkASITopLevel()) break;
|
|
1165
|
+
s.pos += 1;
|
|
1166
|
+
}
|
|
1167
|
+
const type_body = s.sliceTrimmed(type_start, s.pos);
|
|
1168
|
+
if (s.pos < s.len and s.source[s.pos] == ch.CH_SEMI) s.pos += 1;
|
|
1169
|
+
|
|
1170
|
+
var text = std.array_list.Managed(u8).init(s.allocator);
|
|
1171
|
+
text.ensureTotalCapacity(128) catch {};
|
|
1172
|
+
if (is_exported) text.appendSlice("export ") catch {};
|
|
1173
|
+
text.appendSlice("type ") catch {};
|
|
1174
|
+
text.appendSlice(name) catch {};
|
|
1175
|
+
text.appendSlice(generics) catch {};
|
|
1176
|
+
text.appendSlice(" = ") catch {};
|
|
1177
|
+
text.appendSlice(type_body) catch {};
|
|
1178
|
+
|
|
1179
|
+
const comments = extractLeadingComments(s, decl_start);
|
|
1180
|
+
|
|
1181
|
+
return .{
|
|
1182
|
+
.kind = .type_decl,
|
|
1183
|
+
.name = name,
|
|
1184
|
+
.text = text.toOwnedSlice() catch "",
|
|
1185
|
+
.is_exported = is_exported,
|
|
1186
|
+
.generics = generics,
|
|
1187
|
+
.leading_comments = comments,
|
|
1188
|
+
.start = decl_start,
|
|
1189
|
+
.end = s.pos,
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
// ========================================================================
|
|
1194
|
+
// Enum extraction
|
|
1195
|
+
// ========================================================================
|
|
1196
|
+
|
|
1197
|
+
/// Extract enum declaration
|
|
1198
|
+
pub fn extractEnum(s: *Scanner, decl_start: usize, is_exported: bool, is_const: bool) Declaration {
|
|
1199
|
+
s.pos += 4; // skip 'enum'
|
|
1200
|
+
s.skipWhitespaceAndComments();
|
|
1201
|
+
|
|
1202
|
+
const name = s.readIdent();
|
|
1203
|
+
s.skipWhitespaceAndComments();
|
|
1204
|
+
|
|
1205
|
+
if (s.pos < s.len and s.source[s.pos] == ch.CH_LBRACE) {
|
|
1206
|
+
_ = s.findMatchingClose(ch.CH_LBRACE, ch.CH_RBRACE);
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
const raw_text = s.sliceTrimmed(decl_start, s.pos);
|
|
1210
|
+
const comments = extractLeadingComments(s, decl_start);
|
|
1211
|
+
|
|
1212
|
+
// Store const modifier
|
|
1213
|
+
const mods: ?[]const []const u8 = if (is_const) blk: {
|
|
1214
|
+
const mod_list = s.allocator.alloc([]const u8, 1) catch break :blk null;
|
|
1215
|
+
mod_list[0] = "const";
|
|
1216
|
+
break :blk mod_list;
|
|
1217
|
+
} else null;
|
|
1218
|
+
|
|
1219
|
+
return .{
|
|
1220
|
+
.kind = .enum_decl,
|
|
1221
|
+
.name = name,
|
|
1222
|
+
.text = raw_text,
|
|
1223
|
+
.is_exported = is_exported,
|
|
1224
|
+
.modifiers = mods,
|
|
1225
|
+
.leading_comments = comments,
|
|
1226
|
+
.start = decl_start,
|
|
1227
|
+
.end = s.pos,
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
// ========================================================================
|
|
1232
|
+
// Class extraction
|
|
1233
|
+
// ========================================================================
|
|
1234
|
+
|
|
1235
|
+
/// Extract class declaration and build DTS
|
|
1236
|
+
pub fn extractClass(s: *Scanner, decl_start: usize, is_exported: bool, is_abstract: bool) Declaration {
|
|
1237
|
+
s.pos += 5; // skip 'class'
|
|
1238
|
+
s.skipWhitespaceAndComments();
|
|
1239
|
+
|
|
1240
|
+
const name_raw = s.readIdent();
|
|
1241
|
+
const name = if (name_raw.len > 0) name_raw else "AnonymousClass";
|
|
1242
|
+
s.skipWhitespaceAndComments();
|
|
1243
|
+
|
|
1244
|
+
const generics = extractGenerics(s);
|
|
1245
|
+
s.skipWhitespaceAndComments();
|
|
1246
|
+
|
|
1247
|
+
var extends_clause: []const u8 = "";
|
|
1248
|
+
if (s.matchWord("extends")) {
|
|
1249
|
+
s.pos += 7;
|
|
1250
|
+
s.skipWhitespaceAndComments();
|
|
1251
|
+
const ext_start = s.pos;
|
|
1252
|
+
var depth: isize = 0;
|
|
1253
|
+
while (s.pos < s.len) {
|
|
1254
|
+
if (s.skipNonCode()) continue;
|
|
1255
|
+
const c = s.source[s.pos];
|
|
1256
|
+
if (c == ch.CH_LANGLE) {
|
|
1257
|
+
depth += 1;
|
|
1258
|
+
} else if (c == ch.CH_RANGLE and !s.isArrowGT()) {
|
|
1259
|
+
depth -= 1;
|
|
1260
|
+
} else if (depth == 0 and (c == ch.CH_LBRACE or s.matchWord("implements"))) {
|
|
1261
|
+
break;
|
|
1262
|
+
}
|
|
1263
|
+
s.pos += 1;
|
|
1264
|
+
}
|
|
1265
|
+
extends_clause = s.sliceTrimmed(ext_start, s.pos);
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
var implements_text: []const u8 = "";
|
|
1269
|
+
if (s.matchWord("implements")) {
|
|
1270
|
+
s.pos += 10;
|
|
1271
|
+
s.skipWhitespaceAndComments();
|
|
1272
|
+
const impl_start = s.pos;
|
|
1273
|
+
var depth: isize = 0;
|
|
1274
|
+
while (s.pos < s.len) {
|
|
1275
|
+
if (s.skipNonCode()) continue;
|
|
1276
|
+
const c = s.source[s.pos];
|
|
1277
|
+
if (c == ch.CH_LANGLE) {
|
|
1278
|
+
depth += 1;
|
|
1279
|
+
} else if (c == ch.CH_RANGLE and !s.isArrowGT()) {
|
|
1280
|
+
depth -= 1;
|
|
1281
|
+
} else if (depth == 0 and c == ch.CH_LBRACE) {
|
|
1282
|
+
break;
|
|
1283
|
+
}
|
|
1284
|
+
s.pos += 1;
|
|
1285
|
+
}
|
|
1286
|
+
implements_text = s.sliceTrimmed(impl_start, s.pos);
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
s.skipWhitespaceAndComments();
|
|
1290
|
+
const class_body = buildClassBodyDts(s);
|
|
1291
|
+
|
|
1292
|
+
// Build DTS text
|
|
1293
|
+
var text = std.array_list.Managed(u8).init(s.allocator);
|
|
1294
|
+
text.ensureTotalCapacity(128) catch {};
|
|
1295
|
+
if (is_exported) text.appendSlice("export ") catch {};
|
|
1296
|
+
text.appendSlice("declare ") catch {};
|
|
1297
|
+
if (is_abstract) text.appendSlice("abstract ") catch {};
|
|
1298
|
+
text.appendSlice("class ") catch {};
|
|
1299
|
+
text.appendSlice(name) catch {};
|
|
1300
|
+
text.appendSlice(generics) catch {};
|
|
1301
|
+
if (extends_clause.len > 0) {
|
|
1302
|
+
text.appendSlice(" extends ") catch {};
|
|
1303
|
+
text.appendSlice(extends_clause) catch {};
|
|
1304
|
+
}
|
|
1305
|
+
if (implements_text.len > 0) {
|
|
1306
|
+
text.appendSlice(" implements ") catch {};
|
|
1307
|
+
text.appendSlice(implements_text) catch {};
|
|
1308
|
+
}
|
|
1309
|
+
text.append(' ') catch {};
|
|
1310
|
+
text.appendSlice(class_body) catch {};
|
|
1311
|
+
|
|
1312
|
+
const comments = extractLeadingComments(s, decl_start);
|
|
1313
|
+
|
|
1314
|
+
return .{
|
|
1315
|
+
.kind = .class_decl,
|
|
1316
|
+
.name = name,
|
|
1317
|
+
.text = text.toOwnedSlice() catch "",
|
|
1318
|
+
.is_exported = is_exported,
|
|
1319
|
+
.extends_clause = extends_clause,
|
|
1320
|
+
.generics = generics,
|
|
1321
|
+
.leading_comments = comments,
|
|
1322
|
+
.start = decl_start,
|
|
1323
|
+
.end = s.pos,
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
/// Build class body DTS (members only, no implementations)
|
|
1328
|
+
fn buildClassBodyDts(s: *Scanner) []const u8 {
|
|
1329
|
+
if (s.pos >= s.len or s.source[s.pos] != ch.CH_LBRACE) return "{}";
|
|
1330
|
+
s.pos += 1; // skip {
|
|
1331
|
+
|
|
1332
|
+
var members = std.array_list.Managed([]const u8).init(s.allocator);
|
|
1333
|
+
members.ensureTotalCapacity(16) catch {};
|
|
1334
|
+
|
|
1335
|
+
while (s.pos < s.len) {
|
|
1336
|
+
s.skipWhitespaceAndComments();
|
|
1337
|
+
if (s.pos >= s.len) break;
|
|
1338
|
+
if (s.source[s.pos] == ch.CH_RBRACE) {
|
|
1339
|
+
s.pos += 1;
|
|
1340
|
+
break;
|
|
1341
|
+
}
|
|
1342
|
+
if (s.source[s.pos] == ch.CH_SEMI) {
|
|
1343
|
+
s.pos += 1;
|
|
1344
|
+
continue;
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
// Skip static blocks
|
|
1348
|
+
if (s.matchWord("static") and s.peekAfterWord("static") == ch.CH_LBRACE) {
|
|
1349
|
+
s.pos += 6;
|
|
1350
|
+
s.skipWhitespaceAndComments();
|
|
1351
|
+
_ = s.findMatchingClose(ch.CH_LBRACE, ch.CH_RBRACE);
|
|
1352
|
+
continue;
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
// Collect modifiers
|
|
1356
|
+
var is_private = false;
|
|
1357
|
+
var is_protected = false;
|
|
1358
|
+
var is_static = false;
|
|
1359
|
+
var is_abstract = false;
|
|
1360
|
+
var is_readonly = false;
|
|
1361
|
+
var is_async = false;
|
|
1362
|
+
|
|
1363
|
+
while (true) {
|
|
1364
|
+
s.skipWhitespaceAndComments();
|
|
1365
|
+
if (s.matchWord("private")) {
|
|
1366
|
+
is_private = true;
|
|
1367
|
+
s.pos += 7;
|
|
1368
|
+
} else if (s.matchWord("protected")) {
|
|
1369
|
+
is_protected = true;
|
|
1370
|
+
s.pos += 9;
|
|
1371
|
+
} else if (s.matchWord("public")) {
|
|
1372
|
+
s.pos += 6;
|
|
1373
|
+
} else if (s.matchWord("static")) {
|
|
1374
|
+
is_static = true;
|
|
1375
|
+
s.pos += 6;
|
|
1376
|
+
} else if (s.matchWord("abstract")) {
|
|
1377
|
+
is_abstract = true;
|
|
1378
|
+
s.pos += 8;
|
|
1379
|
+
} else if (s.matchWord("readonly")) {
|
|
1380
|
+
is_readonly = true;
|
|
1381
|
+
s.pos += 8;
|
|
1382
|
+
} else if (s.matchWord("override")) {
|
|
1383
|
+
s.pos += 8;
|
|
1384
|
+
} else if (s.matchWord("accessor")) {
|
|
1385
|
+
s.pos += 8;
|
|
1386
|
+
} else if (s.matchWord("async")) {
|
|
1387
|
+
is_async = true;
|
|
1388
|
+
s.pos += 5;
|
|
1389
|
+
} else if (s.matchWord("declare")) {
|
|
1390
|
+
s.pos += 7;
|
|
1391
|
+
} else break;
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
s.skipWhitespaceAndComments();
|
|
1395
|
+
if (s.pos >= s.len or s.source[s.pos] == ch.CH_RBRACE) break;
|
|
1396
|
+
|
|
1397
|
+
// Skip private # members
|
|
1398
|
+
if (s.source[s.pos] == ch.CH_HASH) is_private = true;
|
|
1399
|
+
|
|
1400
|
+
if (is_private) {
|
|
1401
|
+
s.skipClassMember();
|
|
1402
|
+
continue;
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
// Build modifier prefix
|
|
1406
|
+
var mod_prefix = std.array_list.Managed(u8).init(s.allocator);
|
|
1407
|
+
mod_prefix.appendSlice(" ") catch {};
|
|
1408
|
+
if (is_protected) mod_prefix.appendSlice("protected ") catch {};
|
|
1409
|
+
if (is_static) mod_prefix.appendSlice("static ") catch {};
|
|
1410
|
+
if (is_abstract) mod_prefix.appendSlice("abstract ") catch {};
|
|
1411
|
+
if (is_readonly) mod_prefix.appendSlice("readonly ") catch {};
|
|
1412
|
+
const prefix = mod_prefix.toOwnedSlice() catch " ";
|
|
1413
|
+
|
|
1414
|
+
// Detect member type
|
|
1415
|
+
if (s.matchWord("constructor")) {
|
|
1416
|
+
s.pos += 11;
|
|
1417
|
+
s.skipWhitespaceAndComments();
|
|
1418
|
+
const raw_params = extractParamList(s);
|
|
1419
|
+
s.skipWhitespaceAndComments();
|
|
1420
|
+
|
|
1421
|
+
// Extract parameter properties
|
|
1422
|
+
extractParamProperties(s, raw_params, &members);
|
|
1423
|
+
|
|
1424
|
+
// Skip constructor body
|
|
1425
|
+
if (s.pos < s.len and s.source[s.pos] == ch.CH_LBRACE) {
|
|
1426
|
+
_ = s.findMatchingClose(ch.CH_LBRACE, ch.CH_RBRACE);
|
|
1427
|
+
} else if (s.pos < s.len and s.source[s.pos] == ch.CH_SEMI) {
|
|
1428
|
+
s.pos += 1;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
const dts_params = buildDtsParams(s, raw_params);
|
|
1432
|
+
var member = std.array_list.Managed(u8).init(s.allocator);
|
|
1433
|
+
member.appendSlice(" constructor") catch {};
|
|
1434
|
+
member.appendSlice(dts_params) catch {};
|
|
1435
|
+
member.append(';') catch {};
|
|
1436
|
+
members.append(member.toOwnedSlice() catch "") catch {};
|
|
1437
|
+
} else if (s.matchWord("get") and isAccessorFollowed(s)) {
|
|
1438
|
+
s.pos += 3;
|
|
1439
|
+
s.skipWhitespaceAndComments();
|
|
1440
|
+
const member_name = s.readMemberName();
|
|
1441
|
+
if (ch.startsWith(member_name, "#")) {
|
|
1442
|
+
skipAccessorBody(s);
|
|
1443
|
+
continue;
|
|
1444
|
+
}
|
|
1445
|
+
s.skipWhitespaceAndComments();
|
|
1446
|
+
_ = extractParamList(s);
|
|
1447
|
+
s.skipWhitespaceAndComments();
|
|
1448
|
+
const ret_type_raw = extractReturnType(s);
|
|
1449
|
+
const ret_type = if (ret_type_raw.len > 0) ret_type_raw else "unknown";
|
|
1450
|
+
s.skipWhitespaceAndComments();
|
|
1451
|
+
if (s.pos < s.len and s.source[s.pos] == ch.CH_LBRACE) {
|
|
1452
|
+
_ = s.findMatchingClose(ch.CH_LBRACE, ch.CH_RBRACE);
|
|
1453
|
+
} else if (s.pos < s.len and s.source[s.pos] == ch.CH_SEMI) {
|
|
1454
|
+
s.pos += 1;
|
|
1455
|
+
}
|
|
1456
|
+
var member = std.array_list.Managed(u8).init(s.allocator);
|
|
1457
|
+
member.appendSlice(prefix) catch {};
|
|
1458
|
+
member.appendSlice("get ") catch {};
|
|
1459
|
+
member.appendSlice(member_name) catch {};
|
|
1460
|
+
member.appendSlice("(): ") catch {};
|
|
1461
|
+
member.appendSlice(ret_type) catch {};
|
|
1462
|
+
member.append(';') catch {};
|
|
1463
|
+
members.append(member.toOwnedSlice() catch "") catch {};
|
|
1464
|
+
} else if (s.matchWord("set") and isAccessorFollowed(s)) {
|
|
1465
|
+
s.pos += 3;
|
|
1466
|
+
s.skipWhitespaceAndComments();
|
|
1467
|
+
const member_name = s.readMemberName();
|
|
1468
|
+
if (ch.startsWith(member_name, "#")) {
|
|
1469
|
+
skipAccessorBody(s);
|
|
1470
|
+
continue;
|
|
1471
|
+
}
|
|
1472
|
+
s.skipWhitespaceAndComments();
|
|
1473
|
+
const raw_params = extractParamList(s);
|
|
1474
|
+
s.skipWhitespaceAndComments();
|
|
1475
|
+
if (s.pos < s.len and s.source[s.pos] == ch.CH_LBRACE) {
|
|
1476
|
+
_ = s.findMatchingClose(ch.CH_LBRACE, ch.CH_RBRACE);
|
|
1477
|
+
} else if (s.pos < s.len and s.source[s.pos] == ch.CH_SEMI) {
|
|
1478
|
+
s.pos += 1;
|
|
1479
|
+
}
|
|
1480
|
+
const dts_params = buildDtsParams(s, raw_params);
|
|
1481
|
+
var member = std.array_list.Managed(u8).init(s.allocator);
|
|
1482
|
+
member.appendSlice(prefix) catch {};
|
|
1483
|
+
member.appendSlice("set ") catch {};
|
|
1484
|
+
member.appendSlice(member_name) catch {};
|
|
1485
|
+
member.appendSlice(dts_params) catch {};
|
|
1486
|
+
member.append(';') catch {};
|
|
1487
|
+
members.append(member.toOwnedSlice() catch "") catch {};
|
|
1488
|
+
} else {
|
|
1489
|
+
// Regular method or property
|
|
1490
|
+
const is_generator = s.source[s.pos] == ch.CH_STAR;
|
|
1491
|
+
if (is_generator) {
|
|
1492
|
+
s.pos += 1;
|
|
1493
|
+
s.skipWhitespaceAndComments();
|
|
1494
|
+
}
|
|
1495
|
+
const member_name = s.readMemberName();
|
|
1496
|
+
if (member_name.len == 0) {
|
|
1497
|
+
s.skipClassMember();
|
|
1498
|
+
continue;
|
|
1499
|
+
}
|
|
1500
|
+
handleMethodOrPropertyAfterName(s, member_name, prefix, is_static, is_readonly, is_generator, is_abstract, is_async, &members);
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
if (members.items.len == 0) return "{}";
|
|
1505
|
+
|
|
1506
|
+
// Join members — pre-calculate total length
|
|
1507
|
+
var total_len: usize = 4; // "{\n" + "\n}"
|
|
1508
|
+
for (members.items) |m| total_len += m.len + 1;
|
|
1509
|
+
var result = std.array_list.Managed(u8).init(s.allocator);
|
|
1510
|
+
result.ensureTotalCapacity(total_len) catch {};
|
|
1511
|
+
result.appendSlice("{\n") catch {};
|
|
1512
|
+
for (members.items, 0..) |m, i| {
|
|
1513
|
+
result.appendSlice(m) catch {};
|
|
1514
|
+
if (i < members.items.len - 1) result.append('\n') catch {};
|
|
1515
|
+
}
|
|
1516
|
+
result.appendSlice("\n}") catch {};
|
|
1517
|
+
return result.toOwnedSlice() catch "{}";
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
fn isAccessorFollowed(s: *const Scanner) bool {
|
|
1521
|
+
const next_after = s.peekAfterWord(if (s.matchWord("get")) "get" else "set");
|
|
1522
|
+
return next_after != 0 and (ch.isIdentStart(next_after) or next_after == ch.CH_LBRACKET or next_after == ch.CH_HASH);
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
fn skipAccessorBody(s: *Scanner) void {
|
|
1526
|
+
s.skipWhitespaceAndComments();
|
|
1527
|
+
if (s.pos < s.len and s.source[s.pos] == ch.CH_LPAREN) _ = extractParamList(s);
|
|
1528
|
+
s.skipWhitespaceAndComments();
|
|
1529
|
+
_ = extractReturnType(s);
|
|
1530
|
+
s.skipWhitespaceAndComments();
|
|
1531
|
+
if (s.pos < s.len and s.source[s.pos] == ch.CH_LBRACE) {
|
|
1532
|
+
_ = s.findMatchingClose(ch.CH_LBRACE, ch.CH_RBRACE);
|
|
1533
|
+
} else if (s.pos < s.len and s.source[s.pos] == ch.CH_SEMI) {
|
|
1534
|
+
s.pos += 1;
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
/// Handle method or property after reading member name
|
|
1539
|
+
fn handleMethodOrPropertyAfterName(s: *Scanner, member_name: []const u8, mod_prefix: []const u8, is_static: bool, is_readonly: bool, is_generator: bool, is_abstract: bool, is_async: bool, members: *std.array_list.Managed([]const u8)) void {
|
|
1540
|
+
_ = is_abstract;
|
|
1541
|
+
s.skipWhitespaceAndComments();
|
|
1542
|
+
if (s.pos >= s.len) return;
|
|
1543
|
+
|
|
1544
|
+
var c = s.source[s.pos];
|
|
1545
|
+
var is_optional = false;
|
|
1546
|
+
if (c == ch.CH_QUESTION) {
|
|
1547
|
+
is_optional = true;
|
|
1548
|
+
s.pos += 1;
|
|
1549
|
+
s.skipWhitespaceAndComments();
|
|
1550
|
+
}
|
|
1551
|
+
// Definite assignment !
|
|
1552
|
+
if (s.pos < s.len and s.source[s.pos] == ch.CH_EXCL) {
|
|
1553
|
+
s.pos += 1;
|
|
1554
|
+
s.skipWhitespaceAndComments();
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
const next_ch: u8 = if (s.pos < s.len) s.source[s.pos] else 0;
|
|
1558
|
+
|
|
1559
|
+
if (next_ch == ch.CH_LPAREN or next_ch == ch.CH_LANGLE) {
|
|
1560
|
+
// Method
|
|
1561
|
+
const generics = if (next_ch == ch.CH_LANGLE) extractGenerics(s) else "";
|
|
1562
|
+
s.skipWhitespaceAndComments();
|
|
1563
|
+
const raw_params = extractParamList(s);
|
|
1564
|
+
s.skipWhitespaceAndComments();
|
|
1565
|
+
var ret_type = extractReturnType(s);
|
|
1566
|
+
if (ret_type.len == 0) {
|
|
1567
|
+
if (is_async and is_generator) {
|
|
1568
|
+
ret_type = "AsyncGenerator<unknown, void, unknown>";
|
|
1569
|
+
} else if (is_generator) {
|
|
1570
|
+
ret_type = "Generator<unknown, void, unknown>";
|
|
1571
|
+
} else if (is_async) {
|
|
1572
|
+
ret_type = "Promise<void>";
|
|
1573
|
+
} else {
|
|
1574
|
+
ret_type = "void";
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
s.skipWhitespaceAndComments();
|
|
1578
|
+
if (s.pos < s.len and s.source[s.pos] == ch.CH_LBRACE) {
|
|
1579
|
+
_ = s.findMatchingClose(ch.CH_LBRACE, ch.CH_RBRACE);
|
|
1580
|
+
} else if (s.pos < s.len and s.source[s.pos] == ch.CH_SEMI) {
|
|
1581
|
+
s.pos += 1;
|
|
1582
|
+
}
|
|
1583
|
+
const dts_params = buildDtsParams(s, raw_params);
|
|
1584
|
+
const opt_mark: []const u8 = if (is_optional) "?" else "";
|
|
1585
|
+
const gen_text: []const u8 = if (is_generator) "*" else "";
|
|
1586
|
+
|
|
1587
|
+
var member = std.array_list.Managed(u8).init(s.allocator);
|
|
1588
|
+
member.appendSlice(mod_prefix) catch {};
|
|
1589
|
+
member.appendSlice(gen_text) catch {};
|
|
1590
|
+
member.appendSlice(member_name) catch {};
|
|
1591
|
+
member.appendSlice(opt_mark) catch {};
|
|
1592
|
+
member.appendSlice(generics) catch {};
|
|
1593
|
+
member.appendSlice(dts_params) catch {};
|
|
1594
|
+
member.appendSlice(": ") catch {};
|
|
1595
|
+
member.appendSlice(ret_type) catch {};
|
|
1596
|
+
member.append(';') catch {};
|
|
1597
|
+
members.append(member.toOwnedSlice() catch "") catch {};
|
|
1598
|
+
} else if (next_ch == ch.CH_COLON or next_ch == ch.CH_EQUAL or next_ch == ch.CH_SEMI or next_ch == ch.CH_RBRACE or next_ch == ch.CH_LF or next_ch == ch.CH_CR) {
|
|
1599
|
+
// Property
|
|
1600
|
+
var prop_type: []const u8 = "";
|
|
1601
|
+
if (next_ch == ch.CH_COLON) {
|
|
1602
|
+
s.pos += 1;
|
|
1603
|
+
s.skipWhitespaceAndComments();
|
|
1604
|
+
const type_start = s.pos;
|
|
1605
|
+
var depth: isize = 0;
|
|
1606
|
+
while (s.pos < s.len) {
|
|
1607
|
+
if (s.skipNonCode()) continue;
|
|
1608
|
+
c = s.source[s.pos];
|
|
1609
|
+
if (c == ch.CH_LPAREN or c == ch.CH_LBRACE or c == ch.CH_LBRACKET or c == ch.CH_LANGLE) {
|
|
1610
|
+
depth += 1;
|
|
1611
|
+
} else if (c == ch.CH_RPAREN or c == ch.CH_RBRACE or c == ch.CH_RBRACKET or (c == ch.CH_RANGLE and !s.isArrowGT())) {
|
|
1612
|
+
if (depth == 0) break;
|
|
1613
|
+
depth -= 1;
|
|
1614
|
+
} else if (depth == 0 and (c == ch.CH_SEMI or c == ch.CH_EQUAL or c == ch.CH_COMMA)) {
|
|
1615
|
+
break;
|
|
1616
|
+
}
|
|
1617
|
+
if (depth == 0 and s.checkASIMember()) break;
|
|
1618
|
+
s.pos += 1;
|
|
1619
|
+
}
|
|
1620
|
+
prop_type = s.sliceTrimmed(type_start, s.pos);
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
// Capture initializer
|
|
1624
|
+
var init_text: []const u8 = "";
|
|
1625
|
+
if (s.pos < s.len and s.source[s.pos] == ch.CH_EQUAL) {
|
|
1626
|
+
s.pos += 1;
|
|
1627
|
+
s.skipWhitespaceAndComments();
|
|
1628
|
+
const init_start = s.pos;
|
|
1629
|
+
var depth: isize = 0;
|
|
1630
|
+
while (s.pos < s.len) {
|
|
1631
|
+
if (s.skipNonCode()) continue;
|
|
1632
|
+
const ic = s.source[s.pos];
|
|
1633
|
+
if (ic == ch.CH_LPAREN or ic == ch.CH_LBRACE or ic == ch.CH_LBRACKET) {
|
|
1634
|
+
depth += 1;
|
|
1635
|
+
} else if (ic == ch.CH_RPAREN or ic == ch.CH_RBRACE or ic == ch.CH_RBRACKET) {
|
|
1636
|
+
if (depth == 0 and ic == ch.CH_RBRACE) break;
|
|
1637
|
+
depth -= 1;
|
|
1638
|
+
} else if (depth == 0 and ic == ch.CH_SEMI) {
|
|
1639
|
+
break;
|
|
1640
|
+
}
|
|
1641
|
+
if (depth == 0 and s.checkASIMember()) break;
|
|
1642
|
+
s.pos += 1;
|
|
1643
|
+
}
|
|
1644
|
+
init_text = s.sliceTrimmed(init_start, s.pos);
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
if (s.pos < s.len and s.source[s.pos] == ch.CH_SEMI) s.pos += 1;
|
|
1648
|
+
|
|
1649
|
+
if (prop_type.len == 0) {
|
|
1650
|
+
if (init_text.len > 0) {
|
|
1651
|
+
const as_type = extractAssertion(init_text);
|
|
1652
|
+
if (as_type) |t| {
|
|
1653
|
+
prop_type = t;
|
|
1654
|
+
} else {
|
|
1655
|
+
const is_const_like = is_static and is_readonly;
|
|
1656
|
+
prop_type = if (is_const_like) inferLiteralType(init_text) else inferTypeFromDefault(init_text);
|
|
1657
|
+
}
|
|
1658
|
+
} else {
|
|
1659
|
+
prop_type = "unknown";
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
const opt_mark: []const u8 = if (is_optional) "?" else "";
|
|
1664
|
+
var member = std.array_list.Managed(u8).init(s.allocator);
|
|
1665
|
+
member.appendSlice(mod_prefix) catch {};
|
|
1666
|
+
member.appendSlice(member_name) catch {};
|
|
1667
|
+
member.appendSlice(opt_mark) catch {};
|
|
1668
|
+
member.appendSlice(": ") catch {};
|
|
1669
|
+
member.appendSlice(prop_type) catch {};
|
|
1670
|
+
member.append(';') catch {};
|
|
1671
|
+
members.append(member.toOwnedSlice() catch "") catch {};
|
|
1672
|
+
} else {
|
|
1673
|
+
s.skipClassMember();
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
|
|
1677
|
+
/// Extract parameter properties from constructor params
|
|
1678
|
+
fn extractParamProperties(s: *Scanner, raw_params: []const u8, members: *std.array_list.Managed([]const u8)) void {
|
|
1679
|
+
if (raw_params.len < 2) return;
|
|
1680
|
+
const inner = std.mem.trim(u8, raw_params[1 .. raw_params.len - 1], " \t\r\n");
|
|
1681
|
+
if (inner.len == 0) return;
|
|
1682
|
+
|
|
1683
|
+
// Split by comma at depth 0
|
|
1684
|
+
var params = std.array_list.Managed([]const u8).init(s.allocator);
|
|
1685
|
+
var start: usize = 0;
|
|
1686
|
+
var depth: isize = 0;
|
|
1687
|
+
var in_str = false;
|
|
1688
|
+
var str_ch_val: u8 = 0;
|
|
1689
|
+
var skip_next3 = false;
|
|
1690
|
+
for (inner, 0..) |c, i| {
|
|
1691
|
+
if (skip_next3) {
|
|
1692
|
+
skip_next3 = false;
|
|
1693
|
+
continue;
|
|
1694
|
+
}
|
|
1695
|
+
if (in_str) {
|
|
1696
|
+
if (c == ch.CH_BACKSLASH) {
|
|
1697
|
+
skip_next3 = true;
|
|
1698
|
+
continue;
|
|
1699
|
+
}
|
|
1700
|
+
if (c == str_ch_val) in_str = false;
|
|
1701
|
+
continue;
|
|
1702
|
+
}
|
|
1703
|
+
if (c == ch.CH_SQUOTE or c == ch.CH_DQUOTE or c == ch.CH_BACKTICK) {
|
|
1704
|
+
in_str = true;
|
|
1705
|
+
str_ch_val = c;
|
|
1706
|
+
continue;
|
|
1707
|
+
}
|
|
1708
|
+
if (c == ch.CH_LPAREN or c == ch.CH_LBRACE or c == ch.CH_LBRACKET or c == ch.CH_LANGLE) {
|
|
1709
|
+
depth += 1;
|
|
1710
|
+
} else if (c == ch.CH_RPAREN or c == ch.CH_RBRACE or c == ch.CH_RBRACKET or c == ch.CH_RANGLE) {
|
|
1711
|
+
depth -= 1;
|
|
1712
|
+
} else if (c == ch.CH_COMMA and depth == 0) {
|
|
1713
|
+
params.append(std.mem.trim(u8, inner[start..i], " \t\r\n")) catch {};
|
|
1714
|
+
start = i + 1;
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
params.append(std.mem.trim(u8, inner[start..], " \t\r\n")) catch {};
|
|
1718
|
+
|
|
1719
|
+
for (params.items) |param| {
|
|
1720
|
+
const has_public = ch.startsWith(param, "public ") or ch.startsWith(param, "public\t");
|
|
1721
|
+
const has_protected = ch.startsWith(param, "protected ") or ch.startsWith(param, "protected\t");
|
|
1722
|
+
const has_private = ch.startsWith(param, "private ") or ch.startsWith(param, "private\t");
|
|
1723
|
+
const has_readonly = ch.contains(param, "readonly ");
|
|
1724
|
+
|
|
1725
|
+
if (!has_public and !has_protected and !has_private and !has_readonly) continue;
|
|
1726
|
+
if (has_private) continue;
|
|
1727
|
+
|
|
1728
|
+
var p = param;
|
|
1729
|
+
var mods = std.array_list.Managed([]const u8).init(s.allocator);
|
|
1730
|
+
if (has_public) {
|
|
1731
|
+
var si: usize = 6;
|
|
1732
|
+
while (si < p.len and ch.isWhitespace(p[si])) si += 1;
|
|
1733
|
+
p = p[si..];
|
|
1734
|
+
mods.append("public") catch {};
|
|
1735
|
+
}
|
|
1736
|
+
if (has_protected) {
|
|
1737
|
+
var si: usize = 9;
|
|
1738
|
+
while (si < p.len and ch.isWhitespace(p[si])) si += 1;
|
|
1739
|
+
p = p[si..];
|
|
1740
|
+
mods.append("protected") catch {};
|
|
1741
|
+
}
|
|
1742
|
+
if (has_readonly) {
|
|
1743
|
+
if (ch.indexOf(p, "readonly ", 0)) |ri| {
|
|
1744
|
+
var si = ri + 8;
|
|
1745
|
+
while (si < p.len and ch.isWhitespace(p[si])) si += 1;
|
|
1746
|
+
// Reconstruct without 'readonly '
|
|
1747
|
+
const before = p[0..ri];
|
|
1748
|
+
const after = p[si..];
|
|
1749
|
+
const new_p = s.allocator.alloc(u8, before.len + after.len) catch continue;
|
|
1750
|
+
@memcpy(new_p[0..before.len], before);
|
|
1751
|
+
@memcpy(new_p[before.len..], after);
|
|
1752
|
+
p = new_p;
|
|
1753
|
+
}
|
|
1754
|
+
mods.append("readonly") catch {};
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
// Build mod text
|
|
1758
|
+
var mod_text = std.array_list.Managed(u8).init(s.allocator);
|
|
1759
|
+
for (mods.items, 0..) |m, i| {
|
|
1760
|
+
mod_text.appendSlice(m) catch {};
|
|
1761
|
+
if (i < mods.items.len - 1) mod_text.append(' ') catch {};
|
|
1762
|
+
}
|
|
1763
|
+
if (mods.items.len > 0) mod_text.append(' ') catch {};
|
|
1764
|
+
|
|
1765
|
+
const dts_param = buildSingleDtsParam(s, p);
|
|
1766
|
+
var member = std.array_list.Managed(u8).init(s.allocator);
|
|
1767
|
+
member.appendSlice(" ") catch {};
|
|
1768
|
+
member.appendSlice(mod_text.toOwnedSlice() catch "") catch {};
|
|
1769
|
+
member.appendSlice(dts_param) catch {};
|
|
1770
|
+
member.append(';') catch {};
|
|
1771
|
+
members.append(member.toOwnedSlice() catch "") catch {};
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
// ========================================================================
|
|
1776
|
+
// Module/Namespace extraction
|
|
1777
|
+
// ========================================================================
|
|
1778
|
+
|
|
1779
|
+
/// Extract module/namespace declaration
|
|
1780
|
+
pub fn extractModule(s: *Scanner, decl_start: usize, is_exported: bool, keyword: []const u8) Declaration {
|
|
1781
|
+
s.pos += keyword.len;
|
|
1782
|
+
s.skipWhitespaceAndComments();
|
|
1783
|
+
|
|
1784
|
+
var name: []const u8 = "";
|
|
1785
|
+
if (s.pos >= s.len) return .{
|
|
1786
|
+
.kind = .module_decl,
|
|
1787
|
+
.name = name,
|
|
1788
|
+
.text = "",
|
|
1789
|
+
.is_exported = is_exported,
|
|
1790
|
+
.start = decl_start,
|
|
1791
|
+
.end = s.pos,
|
|
1792
|
+
};
|
|
1793
|
+
const c = s.source[s.pos];
|
|
1794
|
+
if (c == ch.CH_SQUOTE or c == ch.CH_DQUOTE) {
|
|
1795
|
+
const quote_start = s.pos;
|
|
1796
|
+
s.skipString(c);
|
|
1797
|
+
name = s.source[quote_start..s.pos];
|
|
1798
|
+
} else {
|
|
1799
|
+
name = s.readIdent();
|
|
1800
|
+
while (s.pos < s.len and s.source[s.pos] == ch.CH_DOT) {
|
|
1801
|
+
s.pos += 1;
|
|
1802
|
+
const next_ident = s.readIdent();
|
|
1803
|
+
const new_name = s.allocator.alloc(u8, name.len + 1 + next_ident.len) catch break;
|
|
1804
|
+
@memcpy(new_name[0..name.len], name);
|
|
1805
|
+
new_name[name.len] = '.';
|
|
1806
|
+
@memcpy(new_name[name.len + 1 ..], next_ident);
|
|
1807
|
+
name = new_name;
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
s.skipWhitespaceAndComments();
|
|
1812
|
+
const body = if (s.pos < s.len and s.source[s.pos] == ch.CH_LBRACE)
|
|
1813
|
+
buildNamespaceBodyDts(s, " ")
|
|
1814
|
+
else
|
|
1815
|
+
"{}";
|
|
1816
|
+
|
|
1817
|
+
var text = std.array_list.Managed(u8).init(s.allocator);
|
|
1818
|
+
text.ensureTotalCapacity(128) catch {};
|
|
1819
|
+
if (is_exported) text.appendSlice("export ") catch {};
|
|
1820
|
+
text.appendSlice("declare ") catch {};
|
|
1821
|
+
text.appendSlice(keyword) catch {};
|
|
1822
|
+
text.append(' ') catch {};
|
|
1823
|
+
text.appendSlice(name) catch {};
|
|
1824
|
+
text.append(' ') catch {};
|
|
1825
|
+
text.appendSlice(body) catch {};
|
|
1826
|
+
|
|
1827
|
+
const comments = extractLeadingComments(s, decl_start);
|
|
1828
|
+
const is_ambient = name.len > 0 and (name[0] == '\'' or name[0] == '"');
|
|
1829
|
+
|
|
1830
|
+
return .{
|
|
1831
|
+
.kind = .module_decl,
|
|
1832
|
+
.name = name,
|
|
1833
|
+
.text = text.toOwnedSlice() catch "",
|
|
1834
|
+
.is_exported = is_exported,
|
|
1835
|
+
.source_module = if (is_ambient and name.len > 2) name[1 .. name.len - 1] else "",
|
|
1836
|
+
.leading_comments = comments,
|
|
1837
|
+
.start = decl_start,
|
|
1838
|
+
.end = s.pos,
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
/// Build DTS text for namespace/module body by processing inner declarations
|
|
1843
|
+
pub fn buildNamespaceBodyDts(s: *Scanner, indent: []const u8) []const u8 {
|
|
1844
|
+
if (s.pos >= s.len or s.source[s.pos] != ch.CH_LBRACE) return "{}";
|
|
1845
|
+
s.pos += 1; // skip {
|
|
1846
|
+
|
|
1847
|
+
var lines = std.array_list.Managed([]const u8).init(s.allocator);
|
|
1848
|
+
lines.ensureTotalCapacity(16) catch {};
|
|
1849
|
+
|
|
1850
|
+
while (s.pos < s.len) {
|
|
1851
|
+
s.skipWhitespaceAndComments();
|
|
1852
|
+
if (s.pos >= s.len) break;
|
|
1853
|
+
if (s.source[s.pos] == ch.CH_RBRACE) {
|
|
1854
|
+
s.pos += 1;
|
|
1855
|
+
break;
|
|
1856
|
+
}
|
|
1857
|
+
if (s.source[s.pos] == ch.CH_SEMI) {
|
|
1858
|
+
s.pos += 1;
|
|
1859
|
+
continue;
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
var has_export = false;
|
|
1863
|
+
if (s.matchWord("export")) {
|
|
1864
|
+
has_export = true;
|
|
1865
|
+
s.pos += 6;
|
|
1866
|
+
s.skipWhitespaceAndComments();
|
|
1867
|
+
}
|
|
1868
|
+
if (s.matchWord("declare")) {
|
|
1869
|
+
s.pos += 7;
|
|
1870
|
+
s.skipWhitespaceAndComments();
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
const prefix: []const u8 = if (has_export) "export " else "";
|
|
1874
|
+
|
|
1875
|
+
if (s.matchWord("function") or (s.matchWord("async") and s.peekAfterKeyword("async", "function"))) {
|
|
1876
|
+
var is_async = false;
|
|
1877
|
+
if (s.matchWord("async")) {
|
|
1878
|
+
is_async = true;
|
|
1879
|
+
s.pos += 5;
|
|
1880
|
+
s.skipWhitespaceAndComments();
|
|
1881
|
+
}
|
|
1882
|
+
s.pos += 8; // function
|
|
1883
|
+
s.skipWhitespaceAndComments();
|
|
1884
|
+
const is_gen = s.pos < s.len and s.source[s.pos] == ch.CH_STAR;
|
|
1885
|
+
if (is_gen) {
|
|
1886
|
+
s.pos += 1;
|
|
1887
|
+
s.skipWhitespaceAndComments();
|
|
1888
|
+
}
|
|
1889
|
+
const fname = s.readIdent();
|
|
1890
|
+
s.skipWhitespaceAndComments();
|
|
1891
|
+
const generics = extractGenerics(s);
|
|
1892
|
+
s.skipWhitespaceAndComments();
|
|
1893
|
+
const raw_params = extractParamList(s);
|
|
1894
|
+
s.skipWhitespaceAndComments();
|
|
1895
|
+
var ret_type = extractReturnType(s);
|
|
1896
|
+
if (ret_type.len == 0) ret_type = if (is_async) "Promise<void>" else "void";
|
|
1897
|
+
s.skipWhitespaceAndComments();
|
|
1898
|
+
if (s.pos < s.len and s.source[s.pos] == ch.CH_LBRACE) {
|
|
1899
|
+
_ = s.findMatchingClose(ch.CH_LBRACE, ch.CH_RBRACE);
|
|
1900
|
+
} else if (s.pos < s.len and s.source[s.pos] == ch.CH_SEMI) {
|
|
1901
|
+
s.pos += 1;
|
|
1902
|
+
}
|
|
1903
|
+
const dts_params = buildDtsParams(s, raw_params);
|
|
1904
|
+
var line = std.array_list.Managed(u8).init(s.allocator);
|
|
1905
|
+
line.appendSlice(indent) catch {};
|
|
1906
|
+
line.appendSlice(prefix) catch {};
|
|
1907
|
+
line.appendSlice("function ") catch {};
|
|
1908
|
+
line.appendSlice(fname) catch {};
|
|
1909
|
+
line.appendSlice(generics) catch {};
|
|
1910
|
+
line.appendSlice(dts_params) catch {};
|
|
1911
|
+
line.appendSlice(": ") catch {};
|
|
1912
|
+
line.appendSlice(ret_type) catch {};
|
|
1913
|
+
line.append(';') catch {};
|
|
1914
|
+
lines.append(line.toOwnedSlice() catch "") catch {};
|
|
1915
|
+
} else if (s.matchWord("const") or s.matchWord("let") or s.matchWord("var")) {
|
|
1916
|
+
const kw: []const u8 = if (s.matchWord("const")) "const" else if (s.matchWord("let")) "let" else "var";
|
|
1917
|
+
s.pos += kw.len;
|
|
1918
|
+
s.skipWhitespaceAndComments();
|
|
1919
|
+
|
|
1920
|
+
// Check for const enum
|
|
1921
|
+
if (std.mem.eql(u8, kw, "const") and s.matchWord("enum")) {
|
|
1922
|
+
s.pos += 4;
|
|
1923
|
+
s.skipWhitespaceAndComments();
|
|
1924
|
+
const ce_name = s.readIdent();
|
|
1925
|
+
s.skipWhitespaceAndComments();
|
|
1926
|
+
const ce_body = if (s.pos < s.len and s.source[s.pos] == ch.CH_LBRACE) s.extractBraceBlock() else "{}";
|
|
1927
|
+
var line = std.array_list.Managed(u8).init(s.allocator);
|
|
1928
|
+
line.appendSlice(indent) catch {};
|
|
1929
|
+
line.appendSlice(prefix) catch {};
|
|
1930
|
+
line.appendSlice("const enum ") catch {};
|
|
1931
|
+
line.appendSlice(ce_name) catch {};
|
|
1932
|
+
line.append(' ') catch {};
|
|
1933
|
+
line.appendSlice(ce_body) catch {};
|
|
1934
|
+
lines.append(line.toOwnedSlice() catch "") catch {};
|
|
1935
|
+
continue;
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
const vname = s.readIdent();
|
|
1939
|
+
if (vname.len == 0) {
|
|
1940
|
+
s.skipToStatementEnd();
|
|
1941
|
+
continue;
|
|
1942
|
+
}
|
|
1943
|
+
s.skipWhitespaceAndComments();
|
|
1944
|
+
var vtype: []const u8 = "";
|
|
1945
|
+
if (s.pos < s.len and s.source[s.pos] == ch.CH_COLON) {
|
|
1946
|
+
s.pos += 1;
|
|
1947
|
+
s.skipWhitespaceAndComments();
|
|
1948
|
+
const ts = s.pos;
|
|
1949
|
+
var depth: isize = 0;
|
|
1950
|
+
while (s.pos < s.len) {
|
|
1951
|
+
if (s.skipNonCode()) continue;
|
|
1952
|
+
const tc = s.source[s.pos];
|
|
1953
|
+
if (tc == ch.CH_LPAREN or tc == ch.CH_LBRACE or tc == ch.CH_LBRACKET or tc == ch.CH_LANGLE) {
|
|
1954
|
+
depth += 1;
|
|
1955
|
+
} else if (tc == ch.CH_RPAREN or tc == ch.CH_RBRACE or tc == ch.CH_RBRACKET or (tc == ch.CH_RANGLE and !s.isArrowGT())) {
|
|
1956
|
+
depth -= 1;
|
|
1957
|
+
} else if (depth == 0 and (tc == ch.CH_EQUAL or tc == ch.CH_SEMI or tc == ch.CH_COMMA)) {
|
|
1958
|
+
break;
|
|
1959
|
+
}
|
|
1960
|
+
if (depth == 0 and s.checkASITopLevel()) break;
|
|
1961
|
+
s.pos += 1;
|
|
1962
|
+
}
|
|
1963
|
+
vtype = s.sliceTrimmed(ts, s.pos);
|
|
1964
|
+
}
|
|
1965
|
+
var init_text: []const u8 = "";
|
|
1966
|
+
if (s.pos < s.len and s.source[s.pos] == ch.CH_EQUAL) {
|
|
1967
|
+
s.pos += 1;
|
|
1968
|
+
s.skipWhitespaceAndComments();
|
|
1969
|
+
const is2 = s.pos;
|
|
1970
|
+
var depth: isize = 0;
|
|
1971
|
+
while (s.pos < s.len) {
|
|
1972
|
+
if (s.skipNonCode()) continue;
|
|
1973
|
+
const ic = s.source[s.pos];
|
|
1974
|
+
if (ic == ch.CH_LPAREN or ic == ch.CH_LBRACE or ic == ch.CH_LBRACKET or ic == ch.CH_LANGLE) {
|
|
1975
|
+
depth += 1;
|
|
1976
|
+
} else if (ic == ch.CH_RPAREN or ic == ch.CH_RBRACE or ic == ch.CH_RBRACKET or (ic == ch.CH_RANGLE and !s.isArrowGT())) {
|
|
1977
|
+
depth -= 1;
|
|
1978
|
+
} else if (depth == 0 and (ic == ch.CH_SEMI or ic == ch.CH_COMMA)) {
|
|
1979
|
+
break;
|
|
1980
|
+
}
|
|
1981
|
+
if (depth == 0 and s.checkASITopLevel()) break;
|
|
1982
|
+
s.pos += 1;
|
|
1983
|
+
}
|
|
1984
|
+
init_text = s.sliceTrimmed(is2, s.pos);
|
|
1985
|
+
}
|
|
1986
|
+
if (s.pos < s.len and s.source[s.pos] == ch.CH_SEMI) s.pos += 1;
|
|
1987
|
+
|
|
1988
|
+
if (vtype.len == 0 and init_text.len > 0) {
|
|
1989
|
+
const as_type = extractAssertion(init_text);
|
|
1990
|
+
if (as_type) |t| {
|
|
1991
|
+
vtype = t;
|
|
1992
|
+
} else if (std.mem.eql(u8, kw, "const")) {
|
|
1993
|
+
vtype = inferLiteralType(init_text);
|
|
1994
|
+
} else {
|
|
1995
|
+
vtype = inferTypeFromDefault(init_text);
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
if (vtype.len == 0) vtype = "unknown";
|
|
1999
|
+
|
|
2000
|
+
var line = std.array_list.Managed(u8).init(s.allocator);
|
|
2001
|
+
line.appendSlice(indent) catch {};
|
|
2002
|
+
line.appendSlice(prefix) catch {};
|
|
2003
|
+
line.appendSlice(kw) catch {};
|
|
2004
|
+
line.append(' ') catch {};
|
|
2005
|
+
line.appendSlice(vname) catch {};
|
|
2006
|
+
line.appendSlice(": ") catch {};
|
|
2007
|
+
line.appendSlice(vtype) catch {};
|
|
2008
|
+
line.append(';') catch {};
|
|
2009
|
+
lines.append(line.toOwnedSlice() catch "") catch {};
|
|
2010
|
+
} else if (s.matchWord("interface")) {
|
|
2011
|
+
s.pos += 9;
|
|
2012
|
+
s.skipWhitespaceAndComments();
|
|
2013
|
+
const iname = s.readIdent();
|
|
2014
|
+
s.skipWhitespaceAndComments();
|
|
2015
|
+
const generics = extractGenerics(s);
|
|
2016
|
+
s.skipWhitespaceAndComments();
|
|
2017
|
+
var ext: []const u8 = "";
|
|
2018
|
+
if (s.matchWord("extends")) {
|
|
2019
|
+
const ext_start = s.pos;
|
|
2020
|
+
while (s.pos < s.len and s.source[s.pos] != ch.CH_LBRACE) {
|
|
2021
|
+
if (s.skipNonCode()) continue;
|
|
2022
|
+
s.pos += 1;
|
|
2023
|
+
}
|
|
2024
|
+
ext = s.source[ext_start..s.pos];
|
|
2025
|
+
}
|
|
2026
|
+
const body = cleanBraceBlock(s, s.extractBraceBlock());
|
|
2027
|
+
var line = std.array_list.Managed(u8).init(s.allocator);
|
|
2028
|
+
line.appendSlice(indent) catch {};
|
|
2029
|
+
line.appendSlice(prefix) catch {};
|
|
2030
|
+
line.appendSlice("interface ") catch {};
|
|
2031
|
+
line.appendSlice(iname) catch {};
|
|
2032
|
+
line.appendSlice(generics) catch {};
|
|
2033
|
+
line.appendSlice(ext) catch {};
|
|
2034
|
+
line.append(' ') catch {};
|
|
2035
|
+
line.appendSlice(body) catch {};
|
|
2036
|
+
lines.append(line.toOwnedSlice() catch "") catch {};
|
|
2037
|
+
} else if (s.matchWord("type")) {
|
|
2038
|
+
s.pos += 4;
|
|
2039
|
+
s.skipWhitespaceAndComments();
|
|
2040
|
+
const tname = s.readIdent();
|
|
2041
|
+
s.skipWhitespaceAndComments();
|
|
2042
|
+
const generics = extractGenerics(s);
|
|
2043
|
+
s.skipWhitespaceAndComments();
|
|
2044
|
+
if (s.pos < s.len and s.source[s.pos] == ch.CH_EQUAL) {
|
|
2045
|
+
s.pos += 1;
|
|
2046
|
+
s.skipWhitespaceAndComments();
|
|
2047
|
+
const ts = s.pos;
|
|
2048
|
+
var depth: isize = 0;
|
|
2049
|
+
while (s.pos < s.len) {
|
|
2050
|
+
if (s.skipNonCode()) continue;
|
|
2051
|
+
const tc = s.source[s.pos];
|
|
2052
|
+
if (tc == ch.CH_LPAREN or tc == ch.CH_LBRACE or tc == ch.CH_LBRACKET or tc == ch.CH_LANGLE) {
|
|
2053
|
+
depth += 1;
|
|
2054
|
+
} else if (tc == ch.CH_RPAREN or tc == ch.CH_RBRACE or tc == ch.CH_RBRACKET or (tc == ch.CH_RANGLE and !s.isArrowGT())) {
|
|
2055
|
+
depth -= 1;
|
|
2056
|
+
} else if (depth == 0 and tc == ch.CH_SEMI) {
|
|
2057
|
+
break;
|
|
2058
|
+
}
|
|
2059
|
+
if (depth == 0 and s.checkASITopLevel()) break;
|
|
2060
|
+
s.pos += 1;
|
|
2061
|
+
}
|
|
2062
|
+
const type_body = s.sliceTrimmed(ts, s.pos);
|
|
2063
|
+
if (s.pos < s.len and s.source[s.pos] == ch.CH_SEMI) s.pos += 1;
|
|
2064
|
+
var line = std.array_list.Managed(u8).init(s.allocator);
|
|
2065
|
+
line.appendSlice(indent) catch {};
|
|
2066
|
+
line.appendSlice(prefix) catch {};
|
|
2067
|
+
line.appendSlice("type ") catch {};
|
|
2068
|
+
line.appendSlice(tname) catch {};
|
|
2069
|
+
line.appendSlice(generics) catch {};
|
|
2070
|
+
line.appendSlice(" = ") catch {};
|
|
2071
|
+
line.appendSlice(type_body) catch {};
|
|
2072
|
+
lines.append(line.toOwnedSlice() catch "") catch {};
|
|
2073
|
+
}
|
|
2074
|
+
} else if (s.matchWord("enum")) {
|
|
2075
|
+
// Handle enum inside namespace
|
|
2076
|
+
s.pos += 4; // enum
|
|
2077
|
+
s.skipWhitespaceAndComments();
|
|
2078
|
+
const ename = s.readIdent();
|
|
2079
|
+
s.skipWhitespaceAndComments();
|
|
2080
|
+
const ebody = if (s.pos < s.len and s.source[s.pos] == ch.CH_LBRACE) s.extractBraceBlock() else "{}";
|
|
2081
|
+
var line = std.array_list.Managed(u8).init(s.allocator);
|
|
2082
|
+
line.appendSlice(indent) catch {};
|
|
2083
|
+
line.appendSlice(prefix) catch {};
|
|
2084
|
+
line.appendSlice("enum ") catch {};
|
|
2085
|
+
line.appendSlice(ename) catch {};
|
|
2086
|
+
line.append(' ') catch {};
|
|
2087
|
+
line.appendSlice(ebody) catch {};
|
|
2088
|
+
lines.append(line.toOwnedSlice() catch "") catch {};
|
|
2089
|
+
} else if (s.matchWord("namespace") or s.matchWord("module")) {
|
|
2090
|
+
// Handle nested namespace/module
|
|
2091
|
+
const ns_kw: []const u8 = if (s.matchWord("namespace")) "namespace" else "module";
|
|
2092
|
+
s.pos += ns_kw.len;
|
|
2093
|
+
s.skipWhitespaceAndComments();
|
|
2094
|
+
const ns_name = s.readIdent();
|
|
2095
|
+
s.skipWhitespaceAndComments();
|
|
2096
|
+
// Use same indent level for nested namespace body (matches TS behavior)
|
|
2097
|
+
const ns_body = if (s.pos < s.len and s.source[s.pos] == ch.CH_LBRACE) buildNamespaceBodyDts(s, indent) else "{}";
|
|
2098
|
+
var line = std.array_list.Managed(u8).init(s.allocator);
|
|
2099
|
+
line.appendSlice(indent) catch {};
|
|
2100
|
+
line.appendSlice(prefix) catch {};
|
|
2101
|
+
line.appendSlice(ns_kw) catch {};
|
|
2102
|
+
line.append(' ') catch {};
|
|
2103
|
+
line.appendSlice(ns_name) catch {};
|
|
2104
|
+
line.append(' ') catch {};
|
|
2105
|
+
line.appendSlice(ns_body) catch {};
|
|
2106
|
+
lines.append(line.toOwnedSlice() catch "") catch {};
|
|
2107
|
+
} else if (s.matchWord("class") or s.matchWord("abstract")) {
|
|
2108
|
+
// Handle class inside namespace
|
|
2109
|
+
var is_abs = false;
|
|
2110
|
+
if (s.matchWord("abstract")) {
|
|
2111
|
+
is_abs = true;
|
|
2112
|
+
s.pos += 8;
|
|
2113
|
+
s.skipWhitespaceAndComments();
|
|
2114
|
+
}
|
|
2115
|
+
if (s.matchWord("class")) {
|
|
2116
|
+
s.pos += 5;
|
|
2117
|
+
s.skipWhitespaceAndComments();
|
|
2118
|
+
const cname = s.readIdent();
|
|
2119
|
+
s.skipWhitespaceAndComments();
|
|
2120
|
+
const cgen = extractGenerics(s);
|
|
2121
|
+
s.skipWhitespaceAndComments();
|
|
2122
|
+
// Skip extends/implements
|
|
2123
|
+
while (s.matchWord("extends") or s.matchWord("implements")) {
|
|
2124
|
+
const kw_len: usize = if (s.matchWord("extends")) 7 else 10;
|
|
2125
|
+
s.pos += kw_len;
|
|
2126
|
+
s.skipWhitespaceAndComments();
|
|
2127
|
+
var depth: isize = 0;
|
|
2128
|
+
while (s.pos < s.len) {
|
|
2129
|
+
if (s.skipNonCode()) continue;
|
|
2130
|
+
const tc = s.source[s.pos];
|
|
2131
|
+
if (tc == ch.CH_LANGLE) depth += 1 else if (tc == ch.CH_RANGLE and !s.isArrowGT()) depth -= 1 else if (depth == 0 and (tc == ch.CH_LBRACE or s.matchWord("implements"))) break;
|
|
2132
|
+
s.pos += 1;
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
s.skipWhitespaceAndComments();
|
|
2136
|
+
const cbody = if (s.pos < s.len and s.source[s.pos] == ch.CH_LBRACE) buildClassBodyDts(s) else "{}";
|
|
2137
|
+
var line = std.array_list.Managed(u8).init(s.allocator);
|
|
2138
|
+
line.appendSlice(indent) catch {};
|
|
2139
|
+
line.appendSlice(prefix) catch {};
|
|
2140
|
+
if (is_abs) line.appendSlice("abstract ") catch {};
|
|
2141
|
+
line.appendSlice("class ") catch {};
|
|
2142
|
+
line.appendSlice(cname) catch {};
|
|
2143
|
+
line.appendSlice(cgen) catch {};
|
|
2144
|
+
line.append(' ') catch {};
|
|
2145
|
+
line.appendSlice(cbody) catch {};
|
|
2146
|
+
lines.append(line.toOwnedSlice() catch "") catch {};
|
|
2147
|
+
} else {
|
|
2148
|
+
s.skipToStatementEnd();
|
|
2149
|
+
}
|
|
2150
|
+
} else if (has_export and s.matchWord("default")) {
|
|
2151
|
+
s.pos += 7;
|
|
2152
|
+
s.skipWhitespaceAndComments();
|
|
2153
|
+
const def_start = s.pos;
|
|
2154
|
+
s.skipToStatementEnd();
|
|
2155
|
+
var def_text = s.sliceTrimmed(def_start, s.pos);
|
|
2156
|
+
if (def_text.len > 0 and def_text[def_text.len - 1] == ';') def_text = def_text[0 .. def_text.len - 1];
|
|
2157
|
+
if (def_text.len > 0) {
|
|
2158
|
+
var line = std.array_list.Managed(u8).init(s.allocator);
|
|
2159
|
+
line.appendSlice(indent) catch {};
|
|
2160
|
+
line.appendSlice("export default ") catch {};
|
|
2161
|
+
line.appendSlice(def_text) catch {};
|
|
2162
|
+
line.append(';') catch {};
|
|
2163
|
+
lines.append(line.toOwnedSlice() catch "") catch {};
|
|
2164
|
+
}
|
|
2165
|
+
} else {
|
|
2166
|
+
s.skipToStatementEnd();
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
if (lines.items.len == 0) return "{}";
|
|
2171
|
+
var result = std.array_list.Managed(u8).init(s.allocator);
|
|
2172
|
+
result.appendSlice("{\n") catch {};
|
|
2173
|
+
for (lines.items, 0..) |line, i| {
|
|
2174
|
+
result.appendSlice(line) catch {};
|
|
2175
|
+
if (i < lines.items.len - 1) result.append('\n') catch {};
|
|
2176
|
+
}
|
|
2177
|
+
result.appendSlice("\n}") catch {};
|
|
2178
|
+
return result.toOwnedSlice() catch "{}";
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
// ========================================================================
|
|
2182
|
+
// Clean brace block helper
|
|
2183
|
+
// ========================================================================
|
|
2184
|
+
|
|
2185
|
+
/// Strip trailing inline comments from a line (respecting strings)
|
|
2186
|
+
fn stripTrailingInlineComment(line: []const u8) []const u8 {
|
|
2187
|
+
var in_string: u8 = 0;
|
|
2188
|
+
var i: usize = 0;
|
|
2189
|
+
while (i < line.len) {
|
|
2190
|
+
const c = line[i];
|
|
2191
|
+
if (in_string != 0) {
|
|
2192
|
+
if (c == '\\') {
|
|
2193
|
+
i += 2;
|
|
2194
|
+
continue;
|
|
2195
|
+
}
|
|
2196
|
+
if (c == in_string) in_string = 0;
|
|
2197
|
+
i += 1;
|
|
2198
|
+
continue;
|
|
2199
|
+
}
|
|
2200
|
+
if (c == '\'' or c == '"' or c == '`') {
|
|
2201
|
+
in_string = c;
|
|
2202
|
+
i += 1;
|
|
2203
|
+
continue;
|
|
2204
|
+
}
|
|
2205
|
+
if (c == '/' and i + 1 < line.len and line[i + 1] == '/') {
|
|
2206
|
+
// Trim trailing whitespace before //
|
|
2207
|
+
var end = i;
|
|
2208
|
+
while (end > 0 and (line[end - 1] == ' ' or line[end - 1] == '\t')) end -= 1;
|
|
2209
|
+
return line[0..end];
|
|
2210
|
+
}
|
|
2211
|
+
i += 1;
|
|
2212
|
+
}
|
|
2213
|
+
// Trim trailing whitespace
|
|
2214
|
+
var end = line.len;
|
|
2215
|
+
while (end > 0 and (line[end - 1] == ' ' or line[end - 1] == '\t' or line[end - 1] == '\r')) end -= 1;
|
|
2216
|
+
return line[0..end];
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
/// Strip inline comments from a brace block and normalize indentation
|
|
2220
|
+
pub fn cleanBraceBlock(s: *Scanner, raw: []const u8) []const u8 {
|
|
2221
|
+
if (raw.len < 2) return raw;
|
|
2222
|
+
|
|
2223
|
+
// Fast path: no comment markers, no semicolons, and ≤2-space indent → body is already clean.
|
|
2224
|
+
// This avoids line-by-line processing for simple interface/namespace bodies.
|
|
2225
|
+
if (ch.indexOfChar(raw, '/', 0) == null and ch.indexOfChar(raw, ';', 0) == null) {
|
|
2226
|
+
// Verify first member line has ≤ 2-space indentation (else re-indent is needed)
|
|
2227
|
+
var needs_reindent = false;
|
|
2228
|
+
if (ch.indexOfChar(raw, '\n', 0)) |nl| {
|
|
2229
|
+
var after = nl + 1;
|
|
2230
|
+
var indent: usize = 0;
|
|
2231
|
+
while (after < raw.len and (raw[after] == ' ' or raw[after] == '\t')) {
|
|
2232
|
+
indent += 1;
|
|
2233
|
+
after += 1;
|
|
2234
|
+
}
|
|
2235
|
+
if (after < raw.len and raw[after] != '\n' and raw[after] != '\r' and
|
|
2236
|
+
raw[after] != '}' and raw[after] != ']' and indent > 2)
|
|
2237
|
+
{
|
|
2238
|
+
needs_reindent = true;
|
|
2239
|
+
}
|
|
2240
|
+
}
|
|
2241
|
+
if (!needs_reindent) return raw;
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
// Check if there are any comment markers
|
|
2245
|
+
const has_comments = ch.contains(raw, "//") or ch.contains(raw, "/*");
|
|
2246
|
+
|
|
2247
|
+
// Split raw text by newlines and process line by line
|
|
2248
|
+
const est_lines = @max(raw.len / 30, 4);
|
|
2249
|
+
var filtered = std.array_list.Managed([]const u8).init(s.allocator);
|
|
2250
|
+
filtered.ensureTotalCapacity(est_lines) catch {};
|
|
2251
|
+
var indent_cache = std.array_list.Managed(usize).init(s.allocator);
|
|
2252
|
+
indent_cache.ensureTotalCapacity(est_lines) catch {};
|
|
2253
|
+
var in_block_comment = false;
|
|
2254
|
+
var min_indent: usize = std.math.maxInt(usize);
|
|
2255
|
+
|
|
2256
|
+
var line_start: usize = 0;
|
|
2257
|
+
var li: usize = 0;
|
|
2258
|
+
while (li <= raw.len) : (li += 1) {
|
|
2259
|
+
if (li == raw.len or raw[li] == '\n') {
|
|
2260
|
+
const line = raw[line_start..li];
|
|
2261
|
+
line_start = li + 1;
|
|
2262
|
+
|
|
2263
|
+
if (has_comments) {
|
|
2264
|
+
if (in_block_comment) {
|
|
2265
|
+
if (ch.contains(line, "*/"))
|
|
2266
|
+
in_block_comment = false;
|
|
2267
|
+
continue;
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
const trimmed = ch.sliceTrimmed(line, 0, line.len);
|
|
2271
|
+
if (trimmed.len == 0) continue;
|
|
2272
|
+
|
|
2273
|
+
// Skip standalone comment lines
|
|
2274
|
+
if (trimmed[0] == '/') {
|
|
2275
|
+
if (trimmed.len > 1 and trimmed[1] == '/') continue; // //
|
|
2276
|
+
if (trimmed.len > 1 and trimmed[1] == '*') { // /* or /**
|
|
2277
|
+
if (!ch.contains(trimmed, "*/"))
|
|
2278
|
+
in_block_comment = true;
|
|
2279
|
+
continue;
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
if (trimmed[0] == '*') continue; // continuation of block comment
|
|
2283
|
+
|
|
2284
|
+
// Strip trailing inline comments and whitespace
|
|
2285
|
+
var cleaned_line = stripTrailingInlineComment(line);
|
|
2286
|
+
// Strip trailing semicolons (DTS convention for interfaces)
|
|
2287
|
+
if (cleaned_line.len > 0 and cleaned_line[cleaned_line.len - 1] == ';')
|
|
2288
|
+
cleaned_line = cleaned_line[0 .. cleaned_line.len - 1];
|
|
2289
|
+
const ct = ch.sliceTrimmed(cleaned_line, 0, cleaned_line.len);
|
|
2290
|
+
if (ct.len == 0) continue;
|
|
2291
|
+
|
|
2292
|
+
filtered.append(cleaned_line) catch {};
|
|
2293
|
+
|
|
2294
|
+
// Compute indent
|
|
2295
|
+
var iw: usize = 0;
|
|
2296
|
+
while (iw < cleaned_line.len and (cleaned_line[iw] == ' ' or cleaned_line[iw] == '\t')) iw += 1;
|
|
2297
|
+
if (!std.mem.eql(u8, ct, "{") and !std.mem.eql(u8, ct, "}")) {
|
|
2298
|
+
if (iw < min_indent) min_indent = iw;
|
|
2299
|
+
}
|
|
2300
|
+
indent_cache.append(iw) catch {};
|
|
2301
|
+
} else {
|
|
2302
|
+
// No comments — just trim trailing whitespace
|
|
2303
|
+
var end = line.len;
|
|
2304
|
+
while (end > 0 and (line[end - 1] == ' ' or line[end - 1] == '\t' or line[end - 1] == '\r')) end -= 1;
|
|
2305
|
+
if (end == 0) continue;
|
|
2306
|
+
var cleaned_line = line[0..end];
|
|
2307
|
+
// Strip trailing semicolons
|
|
2308
|
+
if (cleaned_line.len > 0 and cleaned_line[cleaned_line.len - 1] == ';')
|
|
2309
|
+
cleaned_line = cleaned_line[0 .. cleaned_line.len - 1];
|
|
2310
|
+
const ct = ch.sliceTrimmed(cleaned_line, 0, cleaned_line.len);
|
|
2311
|
+
if (ct.len == 0) continue;
|
|
2312
|
+
|
|
2313
|
+
filtered.append(cleaned_line) catch {};
|
|
2314
|
+
|
|
2315
|
+
var iw: usize = 0;
|
|
2316
|
+
while (iw < cleaned_line.len and (cleaned_line[iw] == ' ' or cleaned_line[iw] == '\t')) iw += 1;
|
|
2317
|
+
if (!std.mem.eql(u8, ct, "{") and !std.mem.eql(u8, ct, "}")) {
|
|
2318
|
+
if (iw < min_indent) min_indent = iw;
|
|
2319
|
+
}
|
|
2320
|
+
indent_cache.append(iw) catch {};
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
if (filtered.items.len == 0) return "{}";
|
|
2326
|
+
|
|
2327
|
+
// Direct alloc: output ≤ raw.len (we're only removing/shifting content)
|
|
2328
|
+
const buf = s.allocator.alloc(u8, raw.len) catch return raw;
|
|
2329
|
+
var pos: usize = 0;
|
|
2330
|
+
|
|
2331
|
+
// If minIndent <= 2, return filtered lines as-is
|
|
2332
|
+
if (min_indent == std.math.maxInt(usize) or min_indent <= 2) {
|
|
2333
|
+
for (filtered.items, 0..) |line, i| {
|
|
2334
|
+
if (i > 0) {
|
|
2335
|
+
buf[pos] = '\n';
|
|
2336
|
+
pos += 1;
|
|
2337
|
+
}
|
|
2338
|
+
@memcpy(buf[pos..][0..line.len], line);
|
|
2339
|
+
pos += line.len;
|
|
2340
|
+
}
|
|
2341
|
+
return buf[0..pos];
|
|
2342
|
+
}
|
|
2343
|
+
|
|
2344
|
+
// Re-indent: offset = minIndent - 2
|
|
2345
|
+
const offs = min_indent - 2;
|
|
2346
|
+
for (filtered.items, 0..) |line, i| {
|
|
2347
|
+
if (i > 0) {
|
|
2348
|
+
buf[pos] = '\n';
|
|
2349
|
+
pos += 1;
|
|
2350
|
+
}
|
|
2351
|
+
|
|
2352
|
+
const ct = ch.sliceTrimmed(line, 0, line.len);
|
|
2353
|
+
if (std.mem.eql(u8, ct, "{")) {
|
|
2354
|
+
@memcpy(buf[pos..][0..ct.len], ct);
|
|
2355
|
+
pos += ct.len;
|
|
2356
|
+
continue;
|
|
2357
|
+
}
|
|
2358
|
+
|
|
2359
|
+
const current_indent = indent_cache.items[i];
|
|
2360
|
+
if (current_indent > min_indent) {
|
|
2361
|
+
@memcpy(buf[pos..][0..line.len], line);
|
|
2362
|
+
pos += line.len;
|
|
2363
|
+
} else if (current_indent == min_indent and ct.len > 0 and (ct[0] == '}' or ct[0] == ']' or ct[0] == ')')) {
|
|
2364
|
+
@memcpy(buf[pos..][0..line.len], line);
|
|
2365
|
+
pos += line.len;
|
|
2366
|
+
} else {
|
|
2367
|
+
const new_indent = if (current_indent >= offs) current_indent - offs else 0;
|
|
2368
|
+
@memset(buf[pos..][0..new_indent], ' ');
|
|
2369
|
+
pos += new_indent;
|
|
2370
|
+
@memcpy(buf[pos..][0..ct.len], ct);
|
|
2371
|
+
pos += ct.len;
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2375
|
+
return buf[0..pos];
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
// ========================================================================
|
|
2379
|
+
// Declare handler
|
|
2380
|
+
// ========================================================================
|
|
2381
|
+
|
|
2382
|
+
/// Handle `declare ...` after `declare` keyword
|
|
2383
|
+
pub fn handleDeclare(s: *Scanner, stmt_start: usize, is_exported: bool) void {
|
|
2384
|
+
if (s.matchWord("function")) {
|
|
2385
|
+
const decl = extractFunction(s, stmt_start, is_exported, false, false);
|
|
2386
|
+
if (decl) |d| s.declarations.append(d) catch {};
|
|
2387
|
+
} else if (s.matchWord("async")) {
|
|
2388
|
+
s.pos += 5;
|
|
2389
|
+
s.skipWhitespaceAndComments();
|
|
2390
|
+
if (s.matchWord("function")) {
|
|
2391
|
+
const decl = extractFunction(s, stmt_start, is_exported, true, false);
|
|
2392
|
+
if (decl) |d| s.declarations.append(d) catch {};
|
|
2393
|
+
}
|
|
2394
|
+
} else if (s.matchWord("class")) {
|
|
2395
|
+
const decl = extractClass(s, stmt_start, is_exported, false);
|
|
2396
|
+
s.declarations.append(decl) catch {};
|
|
2397
|
+
} else if (s.matchWord("abstract")) {
|
|
2398
|
+
s.pos += 8;
|
|
2399
|
+
s.skipWhitespaceAndComments();
|
|
2400
|
+
if (s.matchWord("class")) {
|
|
2401
|
+
const decl = extractClass(s, stmt_start, is_exported, true);
|
|
2402
|
+
s.declarations.append(decl) catch {};
|
|
2403
|
+
}
|
|
2404
|
+
} else if (s.matchWord("interface")) {
|
|
2405
|
+
const decl = extractInterface(s, stmt_start, is_exported);
|
|
2406
|
+
s.declarations.append(decl) catch {};
|
|
2407
|
+
} else if (s.matchWord("type")) {
|
|
2408
|
+
const decl = extractTypeAlias(s, stmt_start, is_exported);
|
|
2409
|
+
s.declarations.append(decl) catch {};
|
|
2410
|
+
} else if (s.matchWord("enum")) {
|
|
2411
|
+
const decl = extractEnum(s, stmt_start, is_exported, false);
|
|
2412
|
+
s.declarations.append(decl) catch {};
|
|
2413
|
+
} else if (s.matchWord("const")) {
|
|
2414
|
+
const saved_pos = s.pos;
|
|
2415
|
+
s.pos += 5;
|
|
2416
|
+
s.skipWhitespaceAndComments();
|
|
2417
|
+
if (s.matchWord("enum")) {
|
|
2418
|
+
s.pos = saved_pos + 5;
|
|
2419
|
+
s.skipWhitespaceAndComments();
|
|
2420
|
+
const decl = extractEnum(s, stmt_start, is_exported, true);
|
|
2421
|
+
s.declarations.append(decl) catch {};
|
|
2422
|
+
} else if (is_exported) {
|
|
2423
|
+
s.pos = saved_pos;
|
|
2424
|
+
const decls = extractVariable(s, stmt_start, "const", true);
|
|
2425
|
+
for (decls) |d| s.declarations.append(d) catch {};
|
|
2426
|
+
} else {
|
|
2427
|
+
s.skipToStatementEnd();
|
|
2428
|
+
}
|
|
2429
|
+
} else if (s.matchWord("let") or s.matchWord("var")) {
|
|
2430
|
+
if (is_exported) {
|
|
2431
|
+
const kind: []const u8 = if (s.matchWord("let")) "let" else "var";
|
|
2432
|
+
const decls = extractVariable(s, stmt_start, kind, true);
|
|
2433
|
+
for (decls) |d| s.declarations.append(d) catch {};
|
|
2434
|
+
} else {
|
|
2435
|
+
s.skipToStatementEnd();
|
|
2436
|
+
}
|
|
2437
|
+
} else if (s.matchWord("module")) {
|
|
2438
|
+
const decl = extractModule(s, stmt_start, is_exported, "module");
|
|
2439
|
+
s.declarations.append(decl) catch {};
|
|
2440
|
+
} else if (s.matchWord("namespace")) {
|
|
2441
|
+
const decl = extractModule(s, stmt_start, is_exported, "namespace");
|
|
2442
|
+
s.declarations.append(decl) catch {};
|
|
2443
|
+
} else if (s.matchWord("global")) {
|
|
2444
|
+
s.pos += 6;
|
|
2445
|
+
s.skipWhitespaceAndComments();
|
|
2446
|
+
const body = if (s.pos < s.len and s.source[s.pos] == ch.CH_LBRACE) buildNamespaceBodyDts(s, " ") else "{}";
|
|
2447
|
+
var text = std.array_list.Managed(u8).init(s.allocator);
|
|
2448
|
+
text.ensureTotalCapacity(128) catch {};
|
|
2449
|
+
text.appendSlice("declare global ") catch {};
|
|
2450
|
+
text.appendSlice(body) catch {};
|
|
2451
|
+
const comments = extractLeadingComments(s, stmt_start);
|
|
2452
|
+
s.declarations.append(.{
|
|
2453
|
+
.kind = .module_decl,
|
|
2454
|
+
.name = "global",
|
|
2455
|
+
.text = text.toOwnedSlice() catch "",
|
|
2456
|
+
.is_exported = false,
|
|
2457
|
+
.leading_comments = comments,
|
|
2458
|
+
.start = stmt_start,
|
|
2459
|
+
.end = s.pos,
|
|
2460
|
+
}) catch {};
|
|
2461
|
+
} else {
|
|
2462
|
+
s.skipToStatementEnd();
|
|
2463
|
+
}
|
|
2464
|
+
}
|