binja 0.5.2 → 0.6.0
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/dist/cli.js +179 -65
- package/dist/debug/collector.d.ts +43 -0
- package/dist/debug/collector.d.ts.map +1 -1
- package/dist/debug/index.d.ts +2 -1
- package/dist/debug/index.d.ts.map +1 -1
- package/dist/debug/index.js +278 -1
- package/dist/debug/integrations.d.ts +104 -0
- package/dist/debug/integrations.d.ts.map +1 -0
- package/dist/errors/index.d.ts +65 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +438 -111
- package/dist/lexer/index.d.ts.map +1 -1
- package/dist/parser/index.d.ts +2 -1
- package/dist/parser/index.d.ts.map +1 -1
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -153,6 +153,159 @@ function tokenizeNative(source) {
|
|
|
153
153
|
return tokens;
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
// src/errors/index.ts
|
|
157
|
+
var colors = {
|
|
158
|
+
red: "\x1B[31m",
|
|
159
|
+
yellow: "\x1B[33m",
|
|
160
|
+
cyan: "\x1B[36m",
|
|
161
|
+
gray: "\x1B[90m",
|
|
162
|
+
bold: "\x1B[1m",
|
|
163
|
+
dim: "\x1B[2m",
|
|
164
|
+
reset: "\x1B[0m"
|
|
165
|
+
};
|
|
166
|
+
var useColors = process.stdout?.isTTY !== false;
|
|
167
|
+
function c(color, text) {
|
|
168
|
+
return useColors ? `${colors[color]}${text}${colors.reset}` : text;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
class TemplateError extends Error {
|
|
172
|
+
line;
|
|
173
|
+
column;
|
|
174
|
+
source;
|
|
175
|
+
templateName;
|
|
176
|
+
suggestion;
|
|
177
|
+
availableOptions;
|
|
178
|
+
constructor(message, options) {
|
|
179
|
+
const formatted = formatError("TemplateError", message, options);
|
|
180
|
+
super(formatted);
|
|
181
|
+
this.name = "TemplateError";
|
|
182
|
+
this.line = options.line;
|
|
183
|
+
this.column = options.column;
|
|
184
|
+
this.source = options.source;
|
|
185
|
+
this.templateName = options.templateName;
|
|
186
|
+
this.suggestion = options.suggestion;
|
|
187
|
+
this.availableOptions = options.availableOptions;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
class TemplateSyntaxError extends Error {
|
|
192
|
+
line;
|
|
193
|
+
column;
|
|
194
|
+
source;
|
|
195
|
+
templateName;
|
|
196
|
+
suggestion;
|
|
197
|
+
constructor(message, options) {
|
|
198
|
+
const formatted = formatError("TemplateSyntaxError", message, options);
|
|
199
|
+
super(formatted);
|
|
200
|
+
this.name = "TemplateSyntaxError";
|
|
201
|
+
this.line = options.line;
|
|
202
|
+
this.column = options.column;
|
|
203
|
+
this.source = options.source;
|
|
204
|
+
this.templateName = options.templateName;
|
|
205
|
+
this.suggestion = options.suggestion;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
class TemplateRuntimeError extends Error {
|
|
210
|
+
line;
|
|
211
|
+
column;
|
|
212
|
+
source;
|
|
213
|
+
templateName;
|
|
214
|
+
suggestion;
|
|
215
|
+
availableOptions;
|
|
216
|
+
constructor(message, options) {
|
|
217
|
+
const formatted = formatError("TemplateRuntimeError", message, options);
|
|
218
|
+
super(formatted);
|
|
219
|
+
this.name = "TemplateRuntimeError";
|
|
220
|
+
this.line = options.line;
|
|
221
|
+
this.column = options.column;
|
|
222
|
+
this.source = options.source;
|
|
223
|
+
this.templateName = options.templateName;
|
|
224
|
+
this.suggestion = options.suggestion;
|
|
225
|
+
this.availableOptions = options.availableOptions;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function formatError(type, message, options) {
|
|
229
|
+
const parts = [];
|
|
230
|
+
const location = options.templateName ? `${options.templateName}:${options.line}:${options.column}` : `line ${options.line}, column ${options.column}`;
|
|
231
|
+
parts.push(`${c("red", c("bold", type))}: ${message} at ${c("cyan", location)}`);
|
|
232
|
+
if (options.source) {
|
|
233
|
+
parts.push("");
|
|
234
|
+
parts.push(generateSnippet(options.source, options.line, options.column));
|
|
235
|
+
}
|
|
236
|
+
if (options.suggestion) {
|
|
237
|
+
parts.push("");
|
|
238
|
+
parts.push(`${c("yellow", "Did you mean")}: ${c("cyan", options.suggestion)}?`);
|
|
239
|
+
}
|
|
240
|
+
if (options.availableOptions && options.availableOptions.length > 0) {
|
|
241
|
+
parts.push("");
|
|
242
|
+
const truncated = options.availableOptions.slice(0, 8);
|
|
243
|
+
const more = options.availableOptions.length > 8 ? ` ${c("gray", `... and ${options.availableOptions.length - 8} more`)}` : "";
|
|
244
|
+
parts.push(`${c("gray", "Available")}: ${truncated.join(", ")}${more}`);
|
|
245
|
+
}
|
|
246
|
+
return parts.join(`
|
|
247
|
+
`);
|
|
248
|
+
}
|
|
249
|
+
function generateSnippet(source, errorLine, errorColumn) {
|
|
250
|
+
const lines = source.split(`
|
|
251
|
+
`);
|
|
252
|
+
const parts = [];
|
|
253
|
+
const startLine = Math.max(1, errorLine - 2);
|
|
254
|
+
const endLine = Math.min(lines.length, errorLine + 1);
|
|
255
|
+
const gutterWidth = String(endLine).length;
|
|
256
|
+
for (let i = startLine;i <= endLine; i++) {
|
|
257
|
+
const lineContent = lines[i - 1] || "";
|
|
258
|
+
const lineNum = String(i).padStart(gutterWidth, " ");
|
|
259
|
+
const isErrorLine = i === errorLine;
|
|
260
|
+
if (isErrorLine) {
|
|
261
|
+
parts.push(`${c("red", " \u2192")} ${c("gray", lineNum)} ${c("dim", "\u2502")} ${lineContent}`);
|
|
262
|
+
const caretPadding = " ".repeat(gutterWidth + 4 + Math.max(0, errorColumn - 1));
|
|
263
|
+
const caret = c("red", "^");
|
|
264
|
+
parts.push(`${caretPadding}${caret}`);
|
|
265
|
+
} else {
|
|
266
|
+
parts.push(` ${c("gray", lineNum)} ${c("dim", "\u2502")} ${c("gray", lineContent)}`);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return parts.join(`
|
|
270
|
+
`);
|
|
271
|
+
}
|
|
272
|
+
function findSimilar(input, candidates, maxDistance = 3) {
|
|
273
|
+
let bestMatch = null;
|
|
274
|
+
let bestDistance = maxDistance + 1;
|
|
275
|
+
const inputLower = input.toLowerCase();
|
|
276
|
+
for (const candidate of candidates) {
|
|
277
|
+
const distance = levenshteinDistance(inputLower, candidate.toLowerCase());
|
|
278
|
+
if (distance < bestDistance) {
|
|
279
|
+
bestDistance = distance;
|
|
280
|
+
bestMatch = candidate;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return bestDistance <= maxDistance ? bestMatch : null;
|
|
284
|
+
}
|
|
285
|
+
function levenshteinDistance(a, b) {
|
|
286
|
+
if (a.length === 0)
|
|
287
|
+
return b.length;
|
|
288
|
+
if (b.length === 0)
|
|
289
|
+
return a.length;
|
|
290
|
+
const matrix = [];
|
|
291
|
+
for (let i = 0;i <= b.length; i++) {
|
|
292
|
+
matrix[i] = [i];
|
|
293
|
+
}
|
|
294
|
+
for (let j = 0;j <= a.length; j++) {
|
|
295
|
+
matrix[0][j] = j;
|
|
296
|
+
}
|
|
297
|
+
for (let i = 1;i <= b.length; i++) {
|
|
298
|
+
for (let j = 1;j <= a.length; j++) {
|
|
299
|
+
if (b[i - 1] === a[j - 1]) {
|
|
300
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
301
|
+
} else {
|
|
302
|
+
matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return matrix[b.length][a.length];
|
|
307
|
+
}
|
|
308
|
+
|
|
156
309
|
// src/lexer/index.ts
|
|
157
310
|
class Lexer {
|
|
158
311
|
state;
|
|
@@ -240,7 +393,11 @@ class Lexer {
|
|
|
240
393
|
if (this.peek() === "-")
|
|
241
394
|
this.advance();
|
|
242
395
|
if (!this.match(this.blockEnd)) {
|
|
243
|
-
throw new
|
|
396
|
+
throw new TemplateSyntaxError(`Expected '${this.blockEnd}' after '${tagName}'`, {
|
|
397
|
+
line: this.state.line,
|
|
398
|
+
column: this.state.column,
|
|
399
|
+
source: this.state.source
|
|
400
|
+
});
|
|
244
401
|
}
|
|
245
402
|
const endTag = `end${tagName}`;
|
|
246
403
|
const contentStart = this.state.pos;
|
|
@@ -270,7 +427,11 @@ class Lexer {
|
|
|
270
427
|
if (this.peek() === "-")
|
|
271
428
|
this.advance();
|
|
272
429
|
if (!this.match(this.blockEnd)) {
|
|
273
|
-
throw new
|
|
430
|
+
throw new TemplateSyntaxError(`Expected '${this.blockEnd}' after '${endTag}'`, {
|
|
431
|
+
line: this.state.line,
|
|
432
|
+
column: this.state.column,
|
|
433
|
+
source: this.state.source
|
|
434
|
+
});
|
|
274
435
|
}
|
|
275
436
|
return;
|
|
276
437
|
}
|
|
@@ -285,7 +446,12 @@ class Lexer {
|
|
|
285
446
|
}
|
|
286
447
|
this.advance();
|
|
287
448
|
}
|
|
288
|
-
throw new
|
|
449
|
+
throw new TemplateSyntaxError(`Unclosed '${tagName}' block`, {
|
|
450
|
+
line: startLine,
|
|
451
|
+
column: startColumn,
|
|
452
|
+
source: this.state.source,
|
|
453
|
+
suggestion: `Add {% end${tagName} %} to close the block`
|
|
454
|
+
});
|
|
289
455
|
}
|
|
290
456
|
scanText() {
|
|
291
457
|
const start = this.state.pos;
|
|
@@ -325,22 +491,27 @@ class Lexer {
|
|
|
325
491
|
}
|
|
326
492
|
this.scanExpressionToken();
|
|
327
493
|
}
|
|
328
|
-
throw new
|
|
494
|
+
throw new TemplateSyntaxError(`Unclosed template tag`, {
|
|
495
|
+
line: this.state.line,
|
|
496
|
+
column: this.state.column,
|
|
497
|
+
source: this.state.source,
|
|
498
|
+
suggestion: `Add closing delimiter '${endDelimiter}'`
|
|
499
|
+
});
|
|
329
500
|
}
|
|
330
501
|
scanExpressionToken() {
|
|
331
502
|
this.skipWhitespace();
|
|
332
503
|
if (this.isAtEnd())
|
|
333
504
|
return;
|
|
334
|
-
const
|
|
335
|
-
if (
|
|
336
|
-
this.scanString(
|
|
505
|
+
const c2 = this.peek();
|
|
506
|
+
if (c2 === '"' || c2 === "'") {
|
|
507
|
+
this.scanString(c2);
|
|
337
508
|
return;
|
|
338
509
|
}
|
|
339
|
-
if (this.isDigit(
|
|
510
|
+
if (this.isDigit(c2)) {
|
|
340
511
|
this.scanNumber();
|
|
341
512
|
return;
|
|
342
513
|
}
|
|
343
|
-
if (this.isAlpha(
|
|
514
|
+
if (this.isAlpha(c2) || c2 === "_") {
|
|
344
515
|
this.scanIdentifier();
|
|
345
516
|
return;
|
|
346
517
|
}
|
|
@@ -361,7 +532,12 @@ class Lexer {
|
|
|
361
532
|
this.advance();
|
|
362
533
|
}
|
|
363
534
|
if (this.isAtEnd()) {
|
|
364
|
-
throw new
|
|
535
|
+
throw new TemplateSyntaxError(`Unterminated string literal`, {
|
|
536
|
+
line: this.state.line,
|
|
537
|
+
column: this.state.column,
|
|
538
|
+
source: this.state.source,
|
|
539
|
+
suggestion: `Add closing quote '${quote}'`
|
|
540
|
+
});
|
|
365
541
|
}
|
|
366
542
|
const value = this.state.source.slice(start, this.state.pos);
|
|
367
543
|
this.advance();
|
|
@@ -391,55 +567,55 @@ class Lexer {
|
|
|
391
567
|
this.addToken(type, value);
|
|
392
568
|
}
|
|
393
569
|
scanOperator() {
|
|
394
|
-
const
|
|
395
|
-
switch (
|
|
570
|
+
const c2 = this.advance();
|
|
571
|
+
switch (c2) {
|
|
396
572
|
case ".":
|
|
397
|
-
this.addToken("DOT" /* DOT */,
|
|
573
|
+
this.addToken("DOT" /* DOT */, c2);
|
|
398
574
|
break;
|
|
399
575
|
case ",":
|
|
400
|
-
this.addToken("COMMA" /* COMMA */,
|
|
576
|
+
this.addToken("COMMA" /* COMMA */, c2);
|
|
401
577
|
break;
|
|
402
578
|
case ":":
|
|
403
|
-
this.addToken("COLON" /* COLON */,
|
|
579
|
+
this.addToken("COLON" /* COLON */, c2);
|
|
404
580
|
break;
|
|
405
581
|
case "|":
|
|
406
|
-
this.addToken("PIPE" /* PIPE */,
|
|
582
|
+
this.addToken("PIPE" /* PIPE */, c2);
|
|
407
583
|
break;
|
|
408
584
|
case "(":
|
|
409
|
-
this.addToken("LPAREN" /* LPAREN */,
|
|
585
|
+
this.addToken("LPAREN" /* LPAREN */, c2);
|
|
410
586
|
break;
|
|
411
587
|
case ")":
|
|
412
|
-
this.addToken("RPAREN" /* RPAREN */,
|
|
588
|
+
this.addToken("RPAREN" /* RPAREN */, c2);
|
|
413
589
|
break;
|
|
414
590
|
case "[":
|
|
415
|
-
this.addToken("LBRACKET" /* LBRACKET */,
|
|
591
|
+
this.addToken("LBRACKET" /* LBRACKET */, c2);
|
|
416
592
|
break;
|
|
417
593
|
case "]":
|
|
418
|
-
this.addToken("RBRACKET" /* RBRACKET */,
|
|
594
|
+
this.addToken("RBRACKET" /* RBRACKET */, c2);
|
|
419
595
|
break;
|
|
420
596
|
case "{":
|
|
421
|
-
this.addToken("LBRACE" /* LBRACE */,
|
|
597
|
+
this.addToken("LBRACE" /* LBRACE */, c2);
|
|
422
598
|
break;
|
|
423
599
|
case "}":
|
|
424
|
-
this.addToken("RBRACE" /* RBRACE */,
|
|
600
|
+
this.addToken("RBRACE" /* RBRACE */, c2);
|
|
425
601
|
break;
|
|
426
602
|
case "+":
|
|
427
|
-
this.addToken("ADD" /* ADD */,
|
|
603
|
+
this.addToken("ADD" /* ADD */, c2);
|
|
428
604
|
break;
|
|
429
605
|
case "-":
|
|
430
|
-
this.addToken("SUB" /* SUB */,
|
|
606
|
+
this.addToken("SUB" /* SUB */, c2);
|
|
431
607
|
break;
|
|
432
608
|
case "*":
|
|
433
|
-
this.addToken("MUL" /* MUL */,
|
|
609
|
+
this.addToken("MUL" /* MUL */, c2);
|
|
434
610
|
break;
|
|
435
611
|
case "/":
|
|
436
|
-
this.addToken("DIV" /* DIV */,
|
|
612
|
+
this.addToken("DIV" /* DIV */, c2);
|
|
437
613
|
break;
|
|
438
614
|
case "%":
|
|
439
|
-
this.addToken("MOD" /* MOD */,
|
|
615
|
+
this.addToken("MOD" /* MOD */, c2);
|
|
440
616
|
break;
|
|
441
617
|
case "~":
|
|
442
|
-
this.addToken("TILDE" /* TILDE */,
|
|
618
|
+
this.addToken("TILDE" /* TILDE */, c2);
|
|
443
619
|
break;
|
|
444
620
|
case "=":
|
|
445
621
|
if (this.match("=")) {
|
|
@@ -452,7 +628,12 @@ class Lexer {
|
|
|
452
628
|
if (this.match("=")) {
|
|
453
629
|
this.addToken("NE" /* NE */, "!=");
|
|
454
630
|
} else {
|
|
455
|
-
throw new
|
|
631
|
+
throw new TemplateSyntaxError(`Unexpected character '!'`, {
|
|
632
|
+
line: this.state.line,
|
|
633
|
+
column: this.state.column - 1,
|
|
634
|
+
source: this.state.source,
|
|
635
|
+
suggestion: `Use '!=' for not-equal comparison or 'not' for negation`
|
|
636
|
+
});
|
|
456
637
|
}
|
|
457
638
|
break;
|
|
458
639
|
case "<":
|
|
@@ -470,8 +651,12 @@ class Lexer {
|
|
|
470
651
|
}
|
|
471
652
|
break;
|
|
472
653
|
default:
|
|
473
|
-
if (!this.isWhitespace(
|
|
474
|
-
throw new
|
|
654
|
+
if (!this.isWhitespace(c2)) {
|
|
655
|
+
throw new TemplateSyntaxError(`Unexpected character '${c2}'`, {
|
|
656
|
+
line: this.state.line,
|
|
657
|
+
column: this.state.column - 1,
|
|
658
|
+
source: this.state.source
|
|
659
|
+
});
|
|
475
660
|
}
|
|
476
661
|
}
|
|
477
662
|
}
|
|
@@ -502,10 +687,10 @@ class Lexer {
|
|
|
502
687
|
return this.state.source[this.state.pos + 1];
|
|
503
688
|
}
|
|
504
689
|
advance() {
|
|
505
|
-
const
|
|
690
|
+
const c2 = this.state.source[this.state.pos];
|
|
506
691
|
this.state.pos++;
|
|
507
692
|
this.state.column++;
|
|
508
|
-
return
|
|
693
|
+
return c2;
|
|
509
694
|
}
|
|
510
695
|
match(expected, offset = 0) {
|
|
511
696
|
const source = this.state.source;
|
|
@@ -545,20 +730,20 @@ class Lexer {
|
|
|
545
730
|
this.advance();
|
|
546
731
|
}
|
|
547
732
|
}
|
|
548
|
-
isWhitespace(
|
|
549
|
-
return
|
|
550
|
-
` ||
|
|
733
|
+
isWhitespace(c2) {
|
|
734
|
+
return c2 === " " || c2 === "\t" || c2 === `
|
|
735
|
+
` || c2 === "\r";
|
|
551
736
|
}
|
|
552
|
-
isDigit(
|
|
553
|
-
const code =
|
|
737
|
+
isDigit(c2) {
|
|
738
|
+
const code = c2.charCodeAt(0);
|
|
554
739
|
return code >= 48 && code <= 57;
|
|
555
740
|
}
|
|
556
|
-
isAlpha(
|
|
557
|
-
const code =
|
|
741
|
+
isAlpha(c2) {
|
|
742
|
+
const code = c2.charCodeAt(0);
|
|
558
743
|
return code >= 97 && code <= 122 || code >= 65 && code <= 90;
|
|
559
744
|
}
|
|
560
|
-
isAlphaNumeric(
|
|
561
|
-
const code =
|
|
745
|
+
isAlphaNumeric(c2) {
|
|
746
|
+
const code = c2.charCodeAt(0);
|
|
562
747
|
return code >= 48 && code <= 57 || code >= 97 && code <= 122 || code >= 65 && code <= 90;
|
|
563
748
|
}
|
|
564
749
|
addToken(type, value) {
|
|
@@ -575,8 +760,10 @@ class Lexer {
|
|
|
575
760
|
class Parser {
|
|
576
761
|
tokens;
|
|
577
762
|
current = 0;
|
|
578
|
-
|
|
763
|
+
source;
|
|
764
|
+
constructor(tokens, source) {
|
|
579
765
|
this.tokens = tokens;
|
|
766
|
+
this.source = source;
|
|
580
767
|
}
|
|
581
768
|
parse() {
|
|
582
769
|
const body = [];
|
|
@@ -1668,7 +1855,11 @@ class Parser {
|
|
|
1668
1855
|
}
|
|
1669
1856
|
error(message) {
|
|
1670
1857
|
const token = this.peek();
|
|
1671
|
-
return new
|
|
1858
|
+
return new TemplateSyntaxError(message, {
|
|
1859
|
+
line: token.line,
|
|
1860
|
+
column: token.column,
|
|
1861
|
+
source: this.source
|
|
1862
|
+
});
|
|
1672
1863
|
}
|
|
1673
1864
|
}
|
|
1674
1865
|
|
|
@@ -1830,7 +2021,7 @@ var capfirst = (value) => {
|
|
|
1830
2021
|
const str = String(value);
|
|
1831
2022
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
1832
2023
|
};
|
|
1833
|
-
var title = (value) => String(value).replace(TITLE_REGEX, (
|
|
2024
|
+
var title = (value) => String(value).replace(TITLE_REGEX, (c2) => c2.toUpperCase());
|
|
1834
2025
|
var trim = (value) => String(value).trim();
|
|
1835
2026
|
var striptags = (value) => String(value).replace(STRIPTAGS_REGEX, "");
|
|
1836
2027
|
var escape = (value) => {
|
|
@@ -2943,6 +3134,7 @@ class Runtime {
|
|
|
2943
3134
|
tests;
|
|
2944
3135
|
blocks = new Map;
|
|
2945
3136
|
parentTemplate = null;
|
|
3137
|
+
source;
|
|
2946
3138
|
constructor(options = {}) {
|
|
2947
3139
|
this.options = {
|
|
2948
3140
|
autoescape: options.autoescape ?? true,
|
|
@@ -3679,8 +3871,17 @@ class Runtime {
|
|
|
3679
3871
|
}
|
|
3680
3872
|
const kwargs = this.evalObjectSync(node.kwargs, ctx);
|
|
3681
3873
|
const filter = this.filters[node.filter];
|
|
3682
|
-
if (!filter)
|
|
3683
|
-
|
|
3874
|
+
if (!filter) {
|
|
3875
|
+
const available = Object.keys(this.filters);
|
|
3876
|
+
const suggestion = findSimilar(node.filter, available);
|
|
3877
|
+
throw new TemplateRuntimeError(`Unknown filter '${node.filter}'`, {
|
|
3878
|
+
line: node.line,
|
|
3879
|
+
column: node.column,
|
|
3880
|
+
source: this.source,
|
|
3881
|
+
suggestion: suggestion || undefined,
|
|
3882
|
+
availableOptions: available.slice(0, 15)
|
|
3883
|
+
});
|
|
3884
|
+
}
|
|
3684
3885
|
return filter(value, ...args, ...Object.values(kwargs));
|
|
3685
3886
|
}
|
|
3686
3887
|
evalBinaryOp(node, ctx) {
|
|
@@ -4117,6 +4318,9 @@ class Runtime {
|
|
|
4117
4318
|
addGlobal(name, value) {
|
|
4118
4319
|
this.options.globals[name] = value;
|
|
4119
4320
|
}
|
|
4321
|
+
setSource(source) {
|
|
4322
|
+
this.source = source;
|
|
4323
|
+
}
|
|
4120
4324
|
}
|
|
4121
4325
|
|
|
4122
4326
|
// src/compiler/index.ts
|
|
@@ -4877,6 +5081,14 @@ class DebugCollector {
|
|
|
4877
5081
|
testsUsed: new Map,
|
|
4878
5082
|
cacheHits: 0,
|
|
4879
5083
|
cacheMisses: 0,
|
|
5084
|
+
queries: [],
|
|
5085
|
+
queryStats: {
|
|
5086
|
+
count: 0,
|
|
5087
|
+
totalDuration: 0,
|
|
5088
|
+
slowCount: 0,
|
|
5089
|
+
n1Count: 0,
|
|
5090
|
+
queryCounts: new Map
|
|
5091
|
+
},
|
|
4880
5092
|
warnings: []
|
|
4881
5093
|
};
|
|
4882
5094
|
}
|
|
@@ -5017,6 +5229,33 @@ class DebugCollector {
|
|
|
5017
5229
|
addWarning(message) {
|
|
5018
5230
|
this.data.warnings.push(message);
|
|
5019
5231
|
}
|
|
5232
|
+
recordQuery(query) {
|
|
5233
|
+
const normalizedSql = this.normalizeQuery(query.sql);
|
|
5234
|
+
const currentCount = this.data.queryStats.queryCounts.get(normalizedSql) || 0;
|
|
5235
|
+
this.data.queryStats.queryCounts.set(normalizedSql, currentCount + 1);
|
|
5236
|
+
const isN1 = currentCount >= 2;
|
|
5237
|
+
const queryInfo = {
|
|
5238
|
+
...query,
|
|
5239
|
+
timestamp: performance.now(),
|
|
5240
|
+
isN1
|
|
5241
|
+
};
|
|
5242
|
+
this.data.queries.push(queryInfo);
|
|
5243
|
+
this.data.queryStats.count++;
|
|
5244
|
+
this.data.queryStats.totalDuration += query.duration;
|
|
5245
|
+
if (query.duration > 100) {
|
|
5246
|
+
this.data.queryStats.slowCount++;
|
|
5247
|
+
}
|
|
5248
|
+
if (isN1 && currentCount === 2) {
|
|
5249
|
+
this.data.queryStats.n1Count++;
|
|
5250
|
+
this.addWarning(`N+1 query detected: ${normalizedSql.slice(0, 50)}...`);
|
|
5251
|
+
}
|
|
5252
|
+
}
|
|
5253
|
+
normalizeQuery(sql) {
|
|
5254
|
+
return sql.replace(/\s+/g, " ").replace(/= \?/g, "= ?").replace(/= \$\d+/g, "= ?").replace(/= '\w+'/g, "= '?'").replace(/= \d+/g, "= ?").replace(/IN \([^)]+\)/gi, "IN (?)").trim();
|
|
5255
|
+
}
|
|
5256
|
+
getQueryStats() {
|
|
5257
|
+
return this.data.queryStats;
|
|
5258
|
+
}
|
|
5020
5259
|
getData() {
|
|
5021
5260
|
return this.data;
|
|
5022
5261
|
}
|
|
@@ -5053,13 +5292,13 @@ var DEFAULT_OPTIONS = {
|
|
|
5053
5292
|
function generateDebugPanel(data, options = {}) {
|
|
5054
5293
|
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
5055
5294
|
const id = `binja-debug-${Date.now()}`;
|
|
5056
|
-
const
|
|
5295
|
+
const colors2 = opts.dark ? darkTheme : lightTheme;
|
|
5057
5296
|
return `
|
|
5058
5297
|
<!-- Binja Debug Panel -->
|
|
5059
5298
|
<div id="${id}" class="binja-debugger" data-theme="${opts.dark ? "dark" : "light"}">
|
|
5060
|
-
<style>${generateStyles(id,
|
|
5061
|
-
${generateToggle(id, data,
|
|
5062
|
-
${generatePanel(id, data,
|
|
5299
|
+
<style>${generateStyles(id, colors2, opts)}</style>
|
|
5300
|
+
${generateToggle(id, data, colors2)}
|
|
5301
|
+
${generatePanel(id, data, colors2, opts)}
|
|
5063
5302
|
<script>${generateScript(id)}</script>
|
|
5064
5303
|
</div>
|
|
5065
5304
|
<!-- /Binja Debug Panel -->
|
|
@@ -5097,84 +5336,103 @@ var lightTheme = {
|
|
|
5097
5336
|
error: "#dc2626",
|
|
5098
5337
|
info: "#0891b2"
|
|
5099
5338
|
};
|
|
5100
|
-
function generateStyles(id,
|
|
5339
|
+
function generateStyles(id, c2, opts) {
|
|
5101
5340
|
const pos = getPosition(opts.position);
|
|
5102
5341
|
return `
|
|
5103
5342
|
#${id} { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', sans-serif; font-size: 13px; line-height: 1.5; position: fixed; ${pos} z-index: 2147483647; }
|
|
5104
5343
|
#${id} * { box-sizing: border-box; margin: 0; padding: 0; }
|
|
5105
|
-
#${id} .dbg-toggle { display: inline-flex; align-items: center; gap: 8px; padding: 8px 14px; background: ${
|
|
5106
|
-
#${id} .dbg-toggle:hover { border-color: ${
|
|
5344
|
+
#${id} .dbg-toggle { display: inline-flex; align-items: center; gap: 8px; padding: 8px 14px; background: ${c2.bg}; border: 1px solid ${c2.border}; border-radius: 8px; color: ${c2.text}; cursor: pointer; font-size: 12px; font-weight: 500; box-shadow: 0 4px 12px rgba(0,0,0,0.15); transition: all 0.2s ease; }
|
|
5345
|
+
#${id} .dbg-toggle:hover { border-color: ${c2.accent}; box-shadow: 0 4px 16px rgba(0,0,0,0.2); }
|
|
5107
5346
|
#${id} .dbg-toggle svg { width: 16px; height: 16px; }
|
|
5108
|
-
#${id} .dbg-toggle .dbg-time { font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; font-size: 11px; padding: 2px 8px; background: ${
|
|
5109
|
-
#${id} .dbg-panel { display: none; width: ${opts.width}px; max-height: 85vh; background: ${
|
|
5347
|
+
#${id} .dbg-toggle .dbg-time { font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; font-size: 11px; padding: 2px 8px; background: ${c2.bgTertiary}; border-radius: 4px; color: ${c2.success}; }
|
|
5348
|
+
#${id} .dbg-panel { display: none; width: ${opts.width}px; max-height: 85vh; background: ${c2.bg}; border: 1px solid ${c2.border}; border-radius: 10px; box-shadow: 0 8px 32px rgba(0,0,0,0.24); overflow: hidden; margin-top: 8px; }
|
|
5110
5349
|
#${id} .dbg-panel.open { display: block; }
|
|
5111
|
-
#${id} .dbg-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; background: ${
|
|
5112
|
-
#${id} .dbg-logo { display: flex; align-items: center; gap: 10px; font-weight: 600; color: ${
|
|
5113
|
-
#${id} .dbg-logo svg { width: 20px; height: 20px; color: ${
|
|
5350
|
+
#${id} .dbg-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; background: ${c2.bgSecondary}; border-bottom: 1px solid ${c2.border}; }
|
|
5351
|
+
#${id} .dbg-logo { display: flex; align-items: center; gap: 10px; font-weight: 600; color: ${c2.text}; }
|
|
5352
|
+
#${id} .dbg-logo svg { width: 20px; height: 20px; color: ${c2.accent}; }
|
|
5114
5353
|
#${id} .dbg-meta { display: flex; align-items: center; gap: 12px; }
|
|
5115
5354
|
#${id} .dbg-badge { font-family: 'SF Mono', Monaco, monospace; font-size: 11px; padding: 3px 10px; border-radius: 4px; font-weight: 500; }
|
|
5116
|
-
#${id} .dbg-badge.time { background: rgba(34,197,94,0.1); color: ${
|
|
5117
|
-
#${id} .dbg-badge.mode { background: rgba(59,130,246,0.1); color: ${
|
|
5118
|
-
#${id} .dbg-close { background: none; border: none; color: ${
|
|
5119
|
-
#${id} .dbg-close:hover { background: ${
|
|
5355
|
+
#${id} .dbg-badge.time { background: rgba(34,197,94,0.1); color: ${c2.success}; }
|
|
5356
|
+
#${id} .dbg-badge.mode { background: rgba(59,130,246,0.1); color: ${c2.accent}; }
|
|
5357
|
+
#${id} .dbg-close { background: none; border: none; color: ${c2.textMuted}; cursor: pointer; padding: 4px; border-radius: 4px; display: flex; }
|
|
5358
|
+
#${id} .dbg-close:hover { background: ${c2.bgTertiary}; color: ${c2.text}; }
|
|
5120
5359
|
#${id} .dbg-close svg { width: 18px; height: 18px; }
|
|
5121
5360
|
#${id} .dbg-body { max-height: calc(85vh - 52px); overflow-y: auto; }
|
|
5122
|
-
#${id} .dbg-section { border-bottom: 1px solid ${
|
|
5361
|
+
#${id} .dbg-section { border-bottom: 1px solid ${c2.border}; }
|
|
5123
5362
|
#${id} .dbg-section:last-child { border-bottom: none; }
|
|
5124
5363
|
#${id} .dbg-section-header { display: flex; align-items: center; justify-content: space-between; padding: 10px 16px; cursor: pointer; user-select: none; transition: background 0.15s; }
|
|
5125
|
-
#${id} .dbg-section-header:hover { background: ${
|
|
5126
|
-
#${id} .dbg-section-title { display: flex; align-items: center; gap: 8px; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; color: ${
|
|
5364
|
+
#${id} .dbg-section-header:hover { background: ${c2.bgSecondary}; }
|
|
5365
|
+
#${id} .dbg-section-title { display: flex; align-items: center; gap: 8px; font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; color: ${c2.textSecondary}; }
|
|
5127
5366
|
#${id} .dbg-section-title svg { width: 14px; height: 14px; opacity: 0.7; }
|
|
5128
|
-
#${id} .dbg-section-meta { font-size: 11px; color: ${
|
|
5129
|
-
#${id} .dbg-section-content { display: none; padding: 12px 16px; background: ${
|
|
5367
|
+
#${id} .dbg-section-meta { font-size: 11px; color: ${c2.textMuted}; font-family: 'SF Mono', Monaco, monospace; }
|
|
5368
|
+
#${id} .dbg-section-content { display: none; padding: 12px 16px; background: ${c2.bgSecondary}; }
|
|
5130
5369
|
#${id} .dbg-section.open .dbg-section-content { display: block; }
|
|
5131
|
-
#${id} .dbg-section .dbg-chevron { transition: transform 0.2s; color: ${
|
|
5370
|
+
#${id} .dbg-section .dbg-chevron { transition: transform 0.2s; color: ${c2.textMuted}; }
|
|
5132
5371
|
#${id} .dbg-section.open .dbg-chevron { transform: rotate(90deg); }
|
|
5133
|
-
#${id} .dbg-row { display: flex; justify-content: space-between; align-items: center; padding: 6px 0; border-bottom: 1px solid ${
|
|
5372
|
+
#${id} .dbg-row { display: flex; justify-content: space-between; align-items: center; padding: 6px 0; border-bottom: 1px solid ${c2.border}; }
|
|
5134
5373
|
#${id} .dbg-row:last-child { border-bottom: none; }
|
|
5135
|
-
#${id} .dbg-label { color: ${
|
|
5136
|
-
#${id} .dbg-value { color: ${
|
|
5137
|
-
#${id} .dbg-bar { height: 3px; background: ${
|
|
5374
|
+
#${id} .dbg-label { color: ${c2.textSecondary}; font-size: 12px; }
|
|
5375
|
+
#${id} .dbg-value { color: ${c2.text}; font-family: 'SF Mono', Monaco, monospace; font-size: 12px; text-align: right; max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
5376
|
+
#${id} .dbg-bar { height: 3px; background: ${c2.bgTertiary}; border-radius: 2px; margin-top: 4px; overflow: hidden; }
|
|
5138
5377
|
#${id} .dbg-bar-fill { height: 100%; border-radius: 2px; transition: width 0.3s ease; }
|
|
5139
|
-
#${id} .dbg-bar-fill.lexer { background: ${
|
|
5140
|
-
#${id} .dbg-bar-fill.parser { background: ${
|
|
5141
|
-
#${id} .dbg-bar-fill.render { background: ${
|
|
5378
|
+
#${id} .dbg-bar-fill.lexer { background: ${c2.info}; }
|
|
5379
|
+
#${id} .dbg-bar-fill.parser { background: ${c2.warning}; }
|
|
5380
|
+
#${id} .dbg-bar-fill.render { background: ${c2.success}; }
|
|
5142
5381
|
#${id} .dbg-templates { display: flex; flex-direction: column; gap: 6px; }
|
|
5143
|
-
#${id} .dbg-template { display: flex; align-items: center; gap: 8px; padding: 8px 10px; background: ${
|
|
5144
|
-
#${id} .dbg-template-icon { width: 16px; height: 16px; color: ${
|
|
5145
|
-
#${id} .dbg-template-name { color: ${
|
|
5382
|
+
#${id} .dbg-template { display: flex; align-items: center; gap: 8px; padding: 8px 10px; background: ${c2.bg}; border-radius: 6px; font-size: 12px; }
|
|
5383
|
+
#${id} .dbg-template-icon { width: 16px; height: 16px; color: ${c2.textMuted}; flex-shrink: 0; }
|
|
5384
|
+
#${id} .dbg-template-name { color: ${c2.text}; font-family: 'SF Mono', Monaco, monospace; }
|
|
5146
5385
|
#${id} .dbg-template-tag { font-size: 10px; padding: 2px 6px; border-radius: 3px; font-weight: 500; text-transform: uppercase; }
|
|
5147
|
-
#${id} .dbg-template-tag.root { background: rgba(59,130,246,0.15); color: ${
|
|
5386
|
+
#${id} .dbg-template-tag.root { background: rgba(59,130,246,0.15); color: ${c2.accent}; }
|
|
5148
5387
|
#${id} .dbg-template-tag.extends { background: rgba(168,85,247,0.15); color: #a855f7; }
|
|
5149
|
-
#${id} .dbg-template-tag.include { background: rgba(34,197,94,0.15); color: ${
|
|
5388
|
+
#${id} .dbg-template-tag.include { background: rgba(34,197,94,0.15); color: ${c2.success}; }
|
|
5150
5389
|
#${id} .dbg-ctx-grid { display: flex; flex-direction: column; gap: 4px; }
|
|
5151
|
-
#${id} .dbg-ctx-item { background: ${
|
|
5390
|
+
#${id} .dbg-ctx-item { background: ${c2.bg}; border-radius: 6px; overflow: hidden; }
|
|
5152
5391
|
#${id} .dbg-ctx-row { display: flex; align-items: center; justify-content: space-between; padding: 8px 10px; cursor: default; }
|
|
5153
5392
|
#${id} .dbg-ctx-row.expandable { cursor: pointer; }
|
|
5154
|
-
#${id} .dbg-ctx-row.expandable:hover { background: ${
|
|
5393
|
+
#${id} .dbg-ctx-row.expandable:hover { background: ${c2.bgTertiary}; }
|
|
5155
5394
|
#${id} .dbg-ctx-key { display: flex; align-items: center; gap: 6px; }
|
|
5156
|
-
#${id} .dbg-ctx-arrow { width: 12px; height: 12px; color: ${
|
|
5395
|
+
#${id} .dbg-ctx-arrow { width: 12px; height: 12px; color: ${c2.textMuted}; transition: transform 0.15s; flex-shrink: 0; }
|
|
5157
5396
|
#${id} .dbg-ctx-item.open > .dbg-ctx-row .dbg-ctx-arrow { transform: rotate(90deg); }
|
|
5158
|
-
#${id} .dbg-ctx-name { color: ${
|
|
5159
|
-
#${id} .dbg-ctx-type { font-size: 10px; color: ${
|
|
5160
|
-
#${id} .dbg-ctx-preview { color: ${
|
|
5161
|
-
#${id} .dbg-ctx-children { display: none; padding-left: 16px; border-left: 1px solid ${
|
|
5397
|
+
#${id} .dbg-ctx-name { color: ${c2.text}; font-family: 'SF Mono', Monaco, monospace; font-size: 12px; }
|
|
5398
|
+
#${id} .dbg-ctx-type { font-size: 10px; color: ${c2.accent}; background: rgba(59,130,246,0.1); padding: 1px 5px; border-radius: 3px; }
|
|
5399
|
+
#${id} .dbg-ctx-preview { color: ${c2.textSecondary}; font-family: 'SF Mono', Monaco, monospace; font-size: 11px; max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
5400
|
+
#${id} .dbg-ctx-children { display: none; padding-left: 16px; border-left: 1px solid ${c2.border}; margin-left: 10px; }
|
|
5162
5401
|
#${id} .dbg-ctx-item.open > .dbg-ctx-children { display: block; }
|
|
5163
5402
|
#${id} .dbg-ctx-children .dbg-ctx-item { background: transparent; }
|
|
5164
5403
|
#${id} .dbg-ctx-children .dbg-ctx-row { padding: 4px 8px; }
|
|
5165
5404
|
#${id} .dbg-filters { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
5166
|
-
#${id} .dbg-filter { display: inline-flex; align-items: center; gap: 4px; padding: 4px 10px; background: ${
|
|
5167
|
-
#${id} .dbg-filter-count { font-size: 10px; color: ${
|
|
5405
|
+
#${id} .dbg-filter { display: inline-flex; align-items: center; gap: 4px; padding: 4px 10px; background: ${c2.bg}; border-radius: 5px; font-size: 12px; font-family: 'SF Mono', Monaco, monospace; color: ${c2.text}; }
|
|
5406
|
+
#${id} .dbg-filter-count { font-size: 10px; color: ${c2.accent}; font-weight: 600; }
|
|
5168
5407
|
#${id} .dbg-cache { display: flex; gap: 16px; }
|
|
5169
|
-
#${id} .dbg-cache-stat { flex: 1; padding: 12px; background: ${
|
|
5408
|
+
#${id} .dbg-cache-stat { flex: 1; padding: 12px; background: ${c2.bg}; border-radius: 6px; text-align: center; }
|
|
5170
5409
|
#${id} .dbg-cache-num { font-size: 24px; font-weight: 600; font-family: 'SF Mono', Monaco, monospace; }
|
|
5171
|
-
#${id} .dbg-cache-num.hit { color: ${
|
|
5172
|
-
#${id} .dbg-cache-num.miss { color: ${
|
|
5173
|
-
#${id} .dbg-cache-label { font-size: 11px; color: ${
|
|
5410
|
+
#${id} .dbg-cache-num.hit { color: ${c2.success}; }
|
|
5411
|
+
#${id} .dbg-cache-num.miss { color: ${c2.error}; }
|
|
5412
|
+
#${id} .dbg-cache-label { font-size: 11px; color: ${c2.textMuted}; margin-top: 4px; }
|
|
5174
5413
|
#${id} .dbg-warnings { display: flex; flex-direction: column; gap: 6px; }
|
|
5175
|
-
#${id} .dbg-warning { display: flex; align-items: flex-start; gap: 8px; padding: 10px 12px; background: rgba(234,179,8,0.1); border-radius: 6px; border-left: 3px solid ${
|
|
5176
|
-
#${id} .dbg-warning-icon { color: ${
|
|
5177
|
-
#${id} .dbg-warning-text { color: ${
|
|
5414
|
+
#${id} .dbg-warning { display: flex; align-items: flex-start; gap: 8px; padding: 10px 12px; background: rgba(234,179,8,0.1); border-radius: 6px; border-left: 3px solid ${c2.warning}; }
|
|
5415
|
+
#${id} .dbg-warning-icon { color: ${c2.warning}; flex-shrink: 0; margin-top: 1px; }
|
|
5416
|
+
#${id} .dbg-warning-text { color: ${c2.text}; font-size: 12px; }
|
|
5417
|
+
#${id} .dbg-queries { display: flex; flex-direction: column; gap: 8px; }
|
|
5418
|
+
#${id} .dbg-query { background: ${c2.bg}; border-radius: 6px; overflow: hidden; }
|
|
5419
|
+
#${id} .dbg-query.n1 { border-left: 3px solid ${c2.error}; }
|
|
5420
|
+
#${id} .dbg-query.slow { border-left: 3px solid ${c2.warning}; }
|
|
5421
|
+
#${id} .dbg-query-header { display: flex; align-items: center; justify-content: space-between; padding: 8px 10px; gap: 8px; }
|
|
5422
|
+
#${id} .dbg-query-sql { flex: 1; font-family: 'SF Mono', Monaco, monospace; font-size: 11px; color: ${c2.text}; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
5423
|
+
#${id} .dbg-query-meta { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
|
|
5424
|
+
#${id} .dbg-query-time { font-family: 'SF Mono', Monaco, monospace; font-size: 10px; padding: 2px 6px; border-radius: 3px; background: ${c2.bgTertiary}; }
|
|
5425
|
+
#${id} .dbg-query-time.slow { background: rgba(234,179,8,0.15); color: ${c2.warning}; }
|
|
5426
|
+
#${id} .dbg-query-rows { font-size: 10px; color: ${c2.textMuted}; }
|
|
5427
|
+
#${id} .dbg-query-source { font-size: 9px; padding: 2px 5px; border-radius: 3px; background: rgba(59,130,246,0.1); color: ${c2.accent}; text-transform: uppercase; }
|
|
5428
|
+
#${id} .dbg-query-badge { font-size: 9px; padding: 2px 5px; border-radius: 3px; font-weight: 600; }
|
|
5429
|
+
#${id} .dbg-query-badge.n1 { background: rgba(239,68,68,0.15); color: ${c2.error}; }
|
|
5430
|
+
#${id} .dbg-query-stats { display: flex; gap: 12px; margin-bottom: 12px; padding: 10px; background: ${c2.bg}; border-radius: 6px; }
|
|
5431
|
+
#${id} .dbg-query-stat { flex: 1; text-align: center; }
|
|
5432
|
+
#${id} .dbg-query-stat-num { font-size: 18px; font-weight: 600; font-family: 'SF Mono', Monaco, monospace; color: ${c2.text}; }
|
|
5433
|
+
#${id} .dbg-query-stat-num.warning { color: ${c2.warning}; }
|
|
5434
|
+
#${id} .dbg-query-stat-num.error { color: ${c2.error}; }
|
|
5435
|
+
#${id} .dbg-query-stat-label { font-size: 10px; color: ${c2.textMuted}; margin-top: 2px; }
|
|
5178
5436
|
`;
|
|
5179
5437
|
}
|
|
5180
5438
|
function getPosition(pos) {
|
|
@@ -5199,9 +5457,10 @@ var icons = {
|
|
|
5199
5457
|
filter: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/></svg>`,
|
|
5200
5458
|
cache: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 2a10 10 0 1010 10H12V2z"/><path d="M12 2a10 10 0 00-8.66 15"/></svg>`,
|
|
5201
5459
|
warning: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>`,
|
|
5202
|
-
file: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V9z"/></svg
|
|
5460
|
+
file: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V9z"/></svg>`,
|
|
5461
|
+
database: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/></svg>`
|
|
5203
5462
|
};
|
|
5204
|
-
function generateToggle(id, data,
|
|
5463
|
+
function generateToggle(id, data, c2) {
|
|
5205
5464
|
const time2 = (data.totalTime || 0).toFixed(1);
|
|
5206
5465
|
return `
|
|
5207
5466
|
<button class="dbg-toggle" onclick="document.querySelector('#${id} .dbg-panel').classList.add('open');this.style.display='none'">
|
|
@@ -5210,7 +5469,7 @@ function generateToggle(id, data, c) {
|
|
|
5210
5469
|
<span class="dbg-time">${time2}ms</span>
|
|
5211
5470
|
</button>`;
|
|
5212
5471
|
}
|
|
5213
|
-
function generatePanel(id, data,
|
|
5472
|
+
function generatePanel(id, data, c2, opts) {
|
|
5214
5473
|
const time2 = (data.totalTime || 0).toFixed(2);
|
|
5215
5474
|
const mode = data.mode === "aot" ? "AOT" : "Runtime";
|
|
5216
5475
|
return `
|
|
@@ -5228,6 +5487,7 @@ function generatePanel(id, data, c, opts) {
|
|
|
5228
5487
|
${generateTemplatesSection(data)}
|
|
5229
5488
|
${generateContextSection(data)}
|
|
5230
5489
|
${generateFiltersSection(data)}
|
|
5490
|
+
${generateQueriesSection(data)}
|
|
5231
5491
|
${generateCacheSection(data)}
|
|
5232
5492
|
${generateWarningsSection(data)}
|
|
5233
5493
|
</div>
|
|
@@ -5391,6 +5651,70 @@ function generateWarningsSection(data) {
|
|
|
5391
5651
|
</div>
|
|
5392
5652
|
</div>`;
|
|
5393
5653
|
}
|
|
5654
|
+
function generateQueriesSection(data) {
|
|
5655
|
+
if (data.queries.length === 0)
|
|
5656
|
+
return "";
|
|
5657
|
+
const stats = data.queryStats;
|
|
5658
|
+
const hasIssues = stats.slowCount > 0 || stats.n1Count > 0;
|
|
5659
|
+
const statsHtml = `
|
|
5660
|
+
<div class="dbg-query-stats">
|
|
5661
|
+
<div class="dbg-query-stat">
|
|
5662
|
+
<div class="dbg-query-stat-num">${stats.count}</div>
|
|
5663
|
+
<div class="dbg-query-stat-label">Queries</div>
|
|
5664
|
+
</div>
|
|
5665
|
+
<div class="dbg-query-stat">
|
|
5666
|
+
<div class="dbg-query-stat-num">${stats.totalDuration.toFixed(1)}ms</div>
|
|
5667
|
+
<div class="dbg-query-stat-label">Total Time</div>
|
|
5668
|
+
</div>
|
|
5669
|
+
<div class="dbg-query-stat">
|
|
5670
|
+
<div class="dbg-query-stat-num ${stats.slowCount > 0 ? "warning" : ""}">${stats.slowCount}</div>
|
|
5671
|
+
<div class="dbg-query-stat-label">Slow (>100ms)</div>
|
|
5672
|
+
</div>
|
|
5673
|
+
<div class="dbg-query-stat">
|
|
5674
|
+
<div class="dbg-query-stat-num ${stats.n1Count > 0 ? "error" : ""}">${stats.n1Count}</div>
|
|
5675
|
+
<div class="dbg-query-stat-label">N+1</div>
|
|
5676
|
+
</div>
|
|
5677
|
+
</div>`;
|
|
5678
|
+
const queries = data.queries.map((q) => {
|
|
5679
|
+
const isSlow = q.duration > 100;
|
|
5680
|
+
const classes = [
|
|
5681
|
+
"dbg-query",
|
|
5682
|
+
q.isN1 ? "n1" : "",
|
|
5683
|
+
isSlow ? "slow" : ""
|
|
5684
|
+
].filter(Boolean).join(" ");
|
|
5685
|
+
const badges = [];
|
|
5686
|
+
if (q.isN1) {
|
|
5687
|
+
badges.push('<span class="dbg-query-badge n1">N+1</span>');
|
|
5688
|
+
}
|
|
5689
|
+
const rowsText = q.rows !== undefined ? `<span class="dbg-query-rows">${q.rows} rows</span>` : "";
|
|
5690
|
+
const sourceText = q.source ? `<span class="dbg-query-source">${escapeHtml(q.source)}</span>` : "";
|
|
5691
|
+
return `
|
|
5692
|
+
<div class="${classes}">
|
|
5693
|
+
<div class="dbg-query-header">
|
|
5694
|
+
<span class="dbg-query-sql" title="${escapeHtml(q.sql)}">${escapeHtml(q.sql)}</span>
|
|
5695
|
+
<div class="dbg-query-meta">
|
|
5696
|
+
${badges.join("")}
|
|
5697
|
+
${sourceText}
|
|
5698
|
+
${rowsText}
|
|
5699
|
+
<span class="dbg-query-time ${isSlow ? "slow" : ""}">${q.duration.toFixed(1)}ms</span>
|
|
5700
|
+
</div>
|
|
5701
|
+
</div>
|
|
5702
|
+
</div>`;
|
|
5703
|
+
}).join("");
|
|
5704
|
+
const metaColor = hasIssues ? 'style="color:#ef4444"' : "";
|
|
5705
|
+
return `
|
|
5706
|
+
<div class="dbg-section ${hasIssues ? "open" : ""}">
|
|
5707
|
+
<div class="dbg-section-header" onclick="this.parentElement.classList.toggle('open')">
|
|
5708
|
+
<span class="dbg-section-title">${icons.database} Queries</span>
|
|
5709
|
+
<span class="dbg-section-meta" ${metaColor}>${stats.count} (${stats.totalDuration.toFixed(1)}ms)</span>
|
|
5710
|
+
<span class="dbg-chevron">${icons.chevron}</span>
|
|
5711
|
+
</div>
|
|
5712
|
+
<div class="dbg-section-content">
|
|
5713
|
+
${statsHtml}
|
|
5714
|
+
<div class="dbg-queries">${queries}</div>
|
|
5715
|
+
</div>
|
|
5716
|
+
</div>`;
|
|
5717
|
+
}
|
|
5394
5718
|
function generateScript(id) {
|
|
5395
5719
|
return `
|
|
5396
5720
|
(function(){
|
|
@@ -5484,12 +5808,12 @@ function createDebugRenderer(env, options = {}) {
|
|
|
5484
5808
|
function debugMiddleware(env, options = {}) {
|
|
5485
5809
|
return {
|
|
5486
5810
|
hono() {
|
|
5487
|
-
return async (
|
|
5811
|
+
return async (c2, next) => {
|
|
5488
5812
|
await next();
|
|
5489
|
-
const contentType =
|
|
5813
|
+
const contentType = c2.res.headers.get("content-type") || "";
|
|
5490
5814
|
if (!contentType.includes("text/html"))
|
|
5491
5815
|
return;
|
|
5492
|
-
const body = await
|
|
5816
|
+
const body = await c2.res.text();
|
|
5493
5817
|
const collector = startDebugCollection();
|
|
5494
5818
|
collector.captureContext({});
|
|
5495
5819
|
collector.setMode("runtime");
|
|
@@ -5497,9 +5821,9 @@ function debugMiddleware(env, options = {}) {
|
|
|
5497
5821
|
const data = endDebugCollection();
|
|
5498
5822
|
const panel = generateDebugPanel(data, options.panel);
|
|
5499
5823
|
const newBody = body.includes("</body>") ? body.replace("</body>", `${panel}</body>`) : body + panel;
|
|
5500
|
-
|
|
5501
|
-
status:
|
|
5502
|
-
headers:
|
|
5824
|
+
c2.res = new Response(newBody, {
|
|
5825
|
+
status: c2.res.status,
|
|
5826
|
+
headers: c2.res.headers
|
|
5503
5827
|
});
|
|
5504
5828
|
};
|
|
5505
5829
|
},
|
|
@@ -5620,7 +5944,7 @@ class Environment {
|
|
|
5620
5944
|
compile(source) {
|
|
5621
5945
|
const lexer = new Lexer(source);
|
|
5622
5946
|
const tokens = lexer.tokenize();
|
|
5623
|
-
const parser = new Parser(tokens);
|
|
5947
|
+
const parser = new Parser(tokens, source);
|
|
5624
5948
|
return parser.parse();
|
|
5625
5949
|
}
|
|
5626
5950
|
async loadTemplate(templateName) {
|
|
@@ -5741,7 +6065,7 @@ function Template(source, options = {}) {
|
|
|
5741
6065
|
function compile(source, options = {}) {
|
|
5742
6066
|
const lexer = new Lexer(source);
|
|
5743
6067
|
const tokens = lexer.tokenize();
|
|
5744
|
-
const parser = new Parser(tokens);
|
|
6068
|
+
const parser = new Parser(tokens, source);
|
|
5745
6069
|
const ast = parser.parse();
|
|
5746
6070
|
return compileToFunction(ast, options);
|
|
5747
6071
|
}
|
|
@@ -5764,7 +6088,7 @@ async function compileWithInheritance(templateName, options) {
|
|
|
5764
6088
|
parse(source2) {
|
|
5765
6089
|
const lexer = new Lexer(source2);
|
|
5766
6090
|
const tokens = lexer.tokenize();
|
|
5767
|
-
const parser = new Parser(tokens);
|
|
6091
|
+
const parser = new Parser(tokens, source2);
|
|
5768
6092
|
return parser.parse();
|
|
5769
6093
|
}
|
|
5770
6094
|
};
|
|
@@ -5796,7 +6120,7 @@ async function compileWithInheritanceToCode(templateName, options) {
|
|
|
5796
6120
|
parse(source2) {
|
|
5797
6121
|
const lexer = new Lexer(source2);
|
|
5798
6122
|
const tokens = lexer.tokenize();
|
|
5799
|
-
const parser = new Parser(tokens);
|
|
6123
|
+
const parser = new Parser(tokens, source2);
|
|
5800
6124
|
return parser.parse();
|
|
5801
6125
|
}
|
|
5802
6126
|
};
|
|
@@ -5812,7 +6136,7 @@ async function compileWithInheritanceToCode(templateName, options) {
|
|
|
5812
6136
|
function compileToCode(source, options = {}) {
|
|
5813
6137
|
const lexer = new Lexer(source);
|
|
5814
6138
|
const tokens = lexer.tokenize();
|
|
5815
|
-
const parser = new Parser(tokens);
|
|
6139
|
+
const parser = new Parser(tokens, source);
|
|
5816
6140
|
const ast = parser.parse();
|
|
5817
6141
|
return compileToString(ast, options);
|
|
5818
6142
|
}
|
|
@@ -5832,6 +6156,9 @@ export {
|
|
|
5832
6156
|
builtinTests,
|
|
5833
6157
|
builtinFilters,
|
|
5834
6158
|
TokenType,
|
|
6159
|
+
TemplateSyntaxError,
|
|
6160
|
+
TemplateRuntimeError,
|
|
6161
|
+
TemplateError,
|
|
5835
6162
|
Template,
|
|
5836
6163
|
Runtime,
|
|
5837
6164
|
Parser,
|