@gitruck/cli 0.2.15 → 0.2.17

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/index.js CHANGED
@@ -9318,6 +9318,724 @@ function namedFnBody(html, name) {
9318
9318
  return null;
9319
9319
  }
9320
9320
  var NOT_A_CALL = new Set(["if", "for", "while", "switch", "catch", "return", "function", "typeof", "new", "delete", "void", "in", "of", "do", "else"]);
9321
+ var MERGEABLE_PRIMITIVES = new Set(["line", "rect", "path", "polyline", "polygon"]);
9322
+ var PRIMITIVE_MERGE_MIN = 8;
9323
+ function maskHtmlComments(src) {
9324
+ const out = src.split("");
9325
+ for (let at = src.indexOf("<!--");at >= 0; at = src.indexOf("<!--", at)) {
9326
+ const close = src.indexOf("-->", at + 4);
9327
+ const end = close < 0 ? src.length : close + 3;
9328
+ for (let i = at;i < end; i++)
9329
+ if (out[i] !== "\r" && out[i] !== `
9330
+ `)
9331
+ out[i] = " ";
9332
+ at = end;
9333
+ }
9334
+ return out.join("");
9335
+ }
9336
+ function scriptBodiesOnly(src) {
9337
+ const openRe = /<script\b[^>]*>/gi;
9338
+ const first = openRe.exec(src);
9339
+ if (!first)
9340
+ return src;
9341
+ const out = src.split("").map((c3) => c3 === "\r" || c3 === `
9342
+ ` ? c3 : " ");
9343
+ let open2 = first;
9344
+ while (open2) {
9345
+ const bodyStart = open2.index + open2[0].length;
9346
+ const closeRe = /<\/script\s*>/gi;
9347
+ closeRe.lastIndex = bodyStart;
9348
+ const close = closeRe.exec(src);
9349
+ const bodyEnd = close?.index ?? src.length;
9350
+ for (let i = bodyStart;i < bodyEnd; i++)
9351
+ out[i] = src[i];
9352
+ openRe.lastIndex = close ? close.index + close[0].length : src.length;
9353
+ open2 = openRe.exec(src);
9354
+ }
9355
+ return out.join("");
9356
+ }
9357
+ function maskJsComments(src) {
9358
+ const out = src.split("");
9359
+ const blank = (from, to) => {
9360
+ for (let i = from;i < to; i++)
9361
+ if (out[i] !== "\r" && out[i] !== `
9362
+ `)
9363
+ out[i] = " ";
9364
+ };
9365
+ const canStartRegexAfter = new Set(["(", "[", "{", "=", ":", ",", ";", "!", "?", "&", "|", "+", "-", "*", "%", "^", "~", "<", ">"]);
9366
+ const keywordBeforeRegex = new Set(["return", "case", "throw", "else", "do", "yield", "await"]);
9367
+ const regexStartsAt = (at) => {
9368
+ let prev = at - 1;
9369
+ while (prev >= 0 && /\s/.test(out[prev]))
9370
+ prev--;
9371
+ if (prev < 0 || canStartRegexAfter.has(out[prev]))
9372
+ return true;
9373
+ if (!/[\w$]/.test(out[prev]))
9374
+ return false;
9375
+ let begin = prev;
9376
+ while (begin > 0 && /[\w$]/.test(out[begin - 1]))
9377
+ begin--;
9378
+ return keywordBeforeRegex.has(out.slice(begin, prev + 1).join(""));
9379
+ };
9380
+ for (let i = 0;i < src.length; i++) {
9381
+ const c3 = src[i];
9382
+ if (c3 === '"' || c3 === "'" || c3 === "`") {
9383
+ i = skipString(src, i);
9384
+ continue;
9385
+ }
9386
+ if (src.startsWith("<!--", i)) {
9387
+ const end2 = src.indexOf("-->", i + 4);
9388
+ const to = end2 < 0 ? src.length : end2 + 3;
9389
+ blank(i, to);
9390
+ i = to - 1;
9391
+ continue;
9392
+ }
9393
+ if (c3 === "/" && src[i + 1] === "/") {
9394
+ const end2 = src.indexOf(`
9395
+ `, i + 2);
9396
+ const to = end2 < 0 ? src.length : end2;
9397
+ blank(i, to);
9398
+ i = to - 1;
9399
+ continue;
9400
+ }
9401
+ if (c3 === "/" && src[i + 1] === "*") {
9402
+ const end2 = src.indexOf("*/", i + 2);
9403
+ const to = end2 < 0 ? src.length : end2 + 2;
9404
+ blank(i, to);
9405
+ i = to - 1;
9406
+ continue;
9407
+ }
9408
+ if (c3 !== "/" || src[i + 1] === "=" || !regexStartsAt(i))
9409
+ continue;
9410
+ let inClass = false;
9411
+ let end = -1;
9412
+ for (let j = i + 1;j < src.length; j++) {
9413
+ if (src[j] === "\\") {
9414
+ j++;
9415
+ continue;
9416
+ }
9417
+ if (src[j] === "\r" || src[j] === `
9418
+ `)
9419
+ break;
9420
+ if (src[j] === "[")
9421
+ inClass = true;
9422
+ else if (src[j] === "]")
9423
+ inClass = false;
9424
+ else if (src[j] === "/" && !inClass) {
9425
+ end = j + 1;
9426
+ while (end < src.length && /[A-Za-z]/.test(src[end]))
9427
+ end++;
9428
+ break;
9429
+ }
9430
+ }
9431
+ if (end < 0)
9432
+ continue;
9433
+ blank(i, end);
9434
+ i = end - 1;
9435
+ }
9436
+ return out.join("");
9437
+ }
9438
+ function maskJsStrings(src) {
9439
+ const out = src.split("");
9440
+ for (let i = 0;i < src.length; i++) {
9441
+ const c3 = src[i];
9442
+ if (c3 !== '"' && c3 !== "'" && c3 !== "`")
9443
+ continue;
9444
+ const end = skipString(src, i);
9445
+ for (let j = i;j <= end && j < out.length; j++)
9446
+ if (out[j] !== "\r" && out[j] !== `
9447
+ `)
9448
+ out[j] = " ";
9449
+ i = end;
9450
+ }
9451
+ return out.join("");
9452
+ }
9453
+ function closeParen(src, open2) {
9454
+ let depth = 0;
9455
+ for (let i = open2;i < src.length; i++) {
9456
+ const c3 = src[i];
9457
+ if (c3 === '"' || c3 === "'" || c3 === "`") {
9458
+ i = skipString(src, i);
9459
+ continue;
9460
+ }
9461
+ if (c3 === "/" && src[i + 1] === "/") {
9462
+ const nl = src.indexOf(`
9463
+ `, i);
9464
+ i = nl < 0 ? src.length : nl;
9465
+ continue;
9466
+ }
9467
+ if (c3 === "/" && src[i + 1] === "*") {
9468
+ const e = src.indexOf("*/", i + 2);
9469
+ i = e < 0 ? src.length : e + 1;
9470
+ continue;
9471
+ }
9472
+ if (c3 === "(")
9473
+ depth++;
9474
+ else if (c3 === ")" && --depth === 0)
9475
+ return i;
9476
+ }
9477
+ return -1;
9478
+ }
9479
+ function skipSpace(src, at) {
9480
+ while (at < src.length && /\s/.test(src[at]))
9481
+ at++;
9482
+ return at;
9483
+ }
9484
+ function braceStatementEnd(src, open2) {
9485
+ const body = braceBlock(src, open2);
9486
+ const close = open2 + 1 + body.length;
9487
+ return src[close] === "}" ? close + 1 : src.length;
9488
+ }
9489
+ function statementEnd(src, start) {
9490
+ const at = skipSpace(src, start);
9491
+ if (src[at] === "{")
9492
+ return braceStatementEnd(src, at);
9493
+ const keyword = /^([A-Za-z_$][\w$]*)\b/.exec(src.slice(at))?.[1] ?? "";
9494
+ if (keyword === "if" || keyword === "for" || keyword === "while" || keyword === "with" || keyword === "switch") {
9495
+ const open2 = src.indexOf("(", at + keyword.length);
9496
+ const close = open2 < 0 ? -1 : closeParen(src, open2);
9497
+ if (close < 0)
9498
+ return src.length;
9499
+ if (keyword === "switch") {
9500
+ const block = skipSpace(src, close + 1);
9501
+ return src[block] === "{" ? braceStatementEnd(src, block) : statementEnd(src, block);
9502
+ }
9503
+ const bodyEnd = statementEnd(src, close + 1);
9504
+ if (keyword !== "if")
9505
+ return bodyEnd;
9506
+ const next = skipSpace(src, bodyEnd);
9507
+ return /^else\b/.test(src.slice(next)) ? statementEnd(src, next + 4) : bodyEnd;
9508
+ }
9509
+ if (keyword === "do") {
9510
+ const bodyEnd = statementEnd(src, at + 2);
9511
+ const trailer = skipSpace(src, bodyEnd);
9512
+ if (!/^while\b/.test(src.slice(trailer)))
9513
+ return bodyEnd;
9514
+ const open2 = src.indexOf("(", trailer + 5);
9515
+ const close = open2 < 0 ? -1 : closeParen(src, open2);
9516
+ if (close < 0)
9517
+ return src.length;
9518
+ const semi = skipSpace(src, close + 1);
9519
+ return src[semi] === ";" ? semi + 1 : close + 1;
9520
+ }
9521
+ if (keyword === "try") {
9522
+ let end = statementEnd(src, at + 3);
9523
+ for (;; ) {
9524
+ const next = skipSpace(src, end);
9525
+ if (/^catch\b/.test(src.slice(next))) {
9526
+ let body = skipSpace(src, next + 5);
9527
+ if (src[body] === "(") {
9528
+ const close = closeParen(src, body);
9529
+ if (close < 0)
9530
+ return src.length;
9531
+ body = skipSpace(src, close + 1);
9532
+ }
9533
+ end = statementEnd(src, body);
9534
+ continue;
9535
+ }
9536
+ if (/^finally\b/.test(src.slice(next))) {
9537
+ end = statementEnd(src, next + 7);
9538
+ continue;
9539
+ }
9540
+ return end;
9541
+ }
9542
+ }
9543
+ let parens = 0;
9544
+ let brackets = 0;
9545
+ let braces = 0;
9546
+ for (let i = at;i < src.length; i++) {
9547
+ const c3 = src[i];
9548
+ if (c3 === '"' || c3 === "'" || c3 === "`") {
9549
+ i = skipString(src, i);
9550
+ continue;
9551
+ }
9552
+ if (c3 === "(")
9553
+ parens++;
9554
+ else if (c3 === ")")
9555
+ parens = Math.max(0, parens - 1);
9556
+ else if (c3 === "[")
9557
+ brackets++;
9558
+ else if (c3 === "]")
9559
+ brackets = Math.max(0, brackets - 1);
9560
+ else if (c3 === "{")
9561
+ braces++;
9562
+ else if (c3 === "}") {
9563
+ if (braces === 0 && parens === 0 && brackets === 0)
9564
+ return i;
9565
+ braces = Math.max(0, braces - 1);
9566
+ } else if (c3 === ";" && parens === 0 && brackets === 0 && braces === 0)
9567
+ return i + 1;
9568
+ else if ((c3 === "\r" || c3 === `
9569
+ `) && parens === 0 && brackets === 0 && braces === 0) {
9570
+ let prev = i - 1;
9571
+ while (prev >= at && /\s/.test(src[prev]))
9572
+ prev--;
9573
+ let next = skipSpace(src, i + 1);
9574
+ if (prev >= at && /[\w$)\]'"`}]/.test(src[prev]) && next < src.length && !/[.(\[`?+\-*/%&|^<>=,:]/.test(src[next]))
9575
+ return i;
9576
+ }
9577
+ }
9578
+ return src.length;
9579
+ }
9580
+ function numericForCount(header) {
9581
+ const parts = header.split(";");
9582
+ if (parts.length !== 3)
9583
+ return null;
9584
+ const init = /^(?:(?:var|let|const)\s+)?([A-Za-z_$][\w$]*)\s*=\s*(-?\d+(?:\.\d+)?)\s*$/.exec(parts[0].trim());
9585
+ if (!init)
9586
+ return null;
9587
+ const name = init[1];
9588
+ const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9589
+ const cond = new RegExp(`^${escaped}\\s*(<=|<|>=|>)\\s*(-?\\d+(?:\\.\\d+)?)\\s*$`).exec(parts[1].trim());
9590
+ if (!cond)
9591
+ return null;
9592
+ const updateRaw = parts[2].trim();
9593
+ let step = null;
9594
+ if (new RegExp(`^(?:${escaped}\\s*\\+\\+|\\+\\+\\s*${escaped})$`).test(updateRaw))
9595
+ step = 1;
9596
+ else if (new RegExp(`^(?:${escaped}\\s*--|--\\s*${escaped})$`).test(updateRaw))
9597
+ step = -1;
9598
+ else {
9599
+ const by = new RegExp(`^${escaped}\\s*([+-])=\\s*(-?\\d+(?:\\.\\d+)?)$`).exec(updateRaw);
9600
+ if (by)
9601
+ step = (by[1] === "+" ? 1 : -1) * Number(by[2]);
9602
+ }
9603
+ if (step === null || !Number.isFinite(step) || step === 0)
9604
+ return null;
9605
+ const from = Number(init[2]);
9606
+ const to = Number(cond[2]);
9607
+ const op = cond[1];
9608
+ if (!Number.isFinite(from) || !Number.isFinite(to))
9609
+ return null;
9610
+ const compare = (value2) => op === "<" ? value2 < to : op === "<=" ? value2 <= to : op === ">" ? value2 > to : value2 >= to;
9611
+ if (!compare(from))
9612
+ return 0;
9613
+ if (step > 0 && op.startsWith(">") || step < 0 && op.startsWith("<"))
9614
+ return null;
9615
+ if (Number.isSafeInteger(from) && Number.isSafeInteger(to) && Number.isSafeInteger(step)) {
9616
+ const distance = step > 0 ? to - from : from - to;
9617
+ if (!Number.isSafeInteger(distance) || distance < 0)
9618
+ return null;
9619
+ const stride = Math.abs(step);
9620
+ const count2 = op === "<" || op === ">" ? Math.ceil(distance / stride) : Math.floor(distance / stride) + 1;
9621
+ return Number.isSafeInteger(count2) ? count2 : null;
9622
+ }
9623
+ let value = from;
9624
+ let count = 0;
9625
+ while (compare(value)) {
9626
+ if (++count > 1e6) {
9627
+ const distance = step > 0 ? to - from : from - to;
9628
+ const stride = Math.abs(step);
9629
+ const estimated = op === "<" || op === ">" ? Math.ceil(distance / stride) : Math.floor(distance / stride) + 1;
9630
+ return Number.isSafeInteger(estimated) && estimated >= 0 ? estimated : null;
9631
+ }
9632
+ const next = value + step;
9633
+ if (Object.is(next, value) || !Number.isFinite(next))
9634
+ return null;
9635
+ value = next;
9636
+ }
9637
+ return count;
9638
+ }
9639
+ function loopSites(html) {
9640
+ const out = [];
9641
+ const doWhileTrailers = new Set;
9642
+ const doRe = /\bdo\b/g;
9643
+ let dm;
9644
+ while (dm = doRe.exec(html)) {
9645
+ const bodyEnd = statementEnd(html, dm.index + dm[0].length);
9646
+ const trailer = skipSpace(html, bodyEnd);
9647
+ if (/^while\b/.test(html.slice(trailer)))
9648
+ doWhileTrailers.add(trailer);
9649
+ }
9650
+ const re = /\b(for|while)\s*\(/g;
9651
+ let m;
9652
+ while (m = re.exec(html)) {
9653
+ if (m[1] === "while" && doWhileTrailers.has(m.index))
9654
+ continue;
9655
+ const open2 = m.index + m[0].lastIndexOf("(");
9656
+ const close = closeParen(html, open2);
9657
+ if (close < 0)
9658
+ continue;
9659
+ let at = close + 1;
9660
+ while (at < html.length && /\s/.test(html[at]))
9661
+ at++;
9662
+ let bodyStart = at;
9663
+ let bodyEnd;
9664
+ if (html[at] === "{") {
9665
+ const body = braceBlock(html, at);
9666
+ bodyStart = at + 1;
9667
+ bodyEnd = bodyStart + body.length;
9668
+ } else
9669
+ bodyEnd = statementEnd(html, at);
9670
+ const kind = m[1];
9671
+ const header = html.slice(open2 + 1, close);
9672
+ out.push({
9673
+ kind,
9674
+ bodyStart,
9675
+ bodyEnd,
9676
+ count: kind === "for" ? numericForCount(header) : null,
9677
+ header
9678
+ });
9679
+ }
9680
+ return out;
9681
+ }
9682
+ function namedFunctionSites(code) {
9683
+ const out = [];
9684
+ const invocationAfter = (declStart, bodyEnd, expression) => {
9685
+ let before = declStart - 1;
9686
+ while (before >= 0 && /\s/.test(code[before]))
9687
+ before--;
9688
+ const expressionContext = expression || before >= 0 && /[(!=,:+\-~?]/.test(code[before]);
9689
+ let after = bodyEnd + 1;
9690
+ while (after < code.length && /\s/.test(code[after]))
9691
+ after++;
9692
+ if (code[after] === "(")
9693
+ return expressionContext;
9694
+ if (code[before] !== "(")
9695
+ return false;
9696
+ let groupingCount = 0;
9697
+ let beforeOpen = before;
9698
+ while (beforeOpen >= 0 && code[beforeOpen] === "(") {
9699
+ groupingCount++;
9700
+ beforeOpen--;
9701
+ while (beforeOpen >= 0 && /\s/.test(code[beforeOpen]))
9702
+ beforeOpen--;
9703
+ }
9704
+ if (beforeOpen >= 0 && /[\w$)\]]/.test(code[beforeOpen]))
9705
+ return false;
9706
+ for (let i = 0;i < groupingCount; i++) {
9707
+ if (code[after] !== ")")
9708
+ return false;
9709
+ after = skipSpace(code, after + 1);
9710
+ }
9711
+ return expressionContext && code[after] === "(";
9712
+ };
9713
+ const patterns = [
9714
+ {
9715
+ re: /\b(?:var|let|const)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:(?:async\s+)?function\s*\*?\s*[\w$]*\s*\([^)]*\)|(?:async\s*)?(?:\([^)]*\)|[A-Za-z_$][\w$]*)\s*=>)\s*\{/g,
9716
+ nameGroup: 1,
9717
+ expression: true
9718
+ },
9719
+ {
9720
+ re: /\b(?:async\s+)?function\s*\*?\s+([A-Za-z_$][\w$]*)\s*\([^)]*\)\s*\{/g,
9721
+ nameGroup: 1,
9722
+ expression: false
9723
+ },
9724
+ {
9725
+ re: /\b(?:async\s+)?function\s*\*?\s*\([^)]*\)\s*\{/g,
9726
+ nameGroup: null,
9727
+ expression: true
9728
+ },
9729
+ {
9730
+ re: /(?:async\s*)?(?:\([^)]*\)|[A-Za-z_$][\w$]*)\s*=>\s*\{/g,
9731
+ nameGroup: null,
9732
+ expression: true
9733
+ }
9734
+ ];
9735
+ for (const { re, nameGroup, expression } of patterns) {
9736
+ let m;
9737
+ while (m = re.exec(code)) {
9738
+ const open2 = m.index + m[0].lastIndexOf("{");
9739
+ const paired = braceBlock(code, open2);
9740
+ const bodyStart = open2 + 1;
9741
+ const bodyEnd = bodyStart + paired.length;
9742
+ if (out.some((fn) => fn.bodyStart === bodyStart))
9743
+ continue;
9744
+ const name = nameGroup === null ? null : m[nameGroup];
9745
+ const signature = m[0].slice(0, m[0].lastIndexOf("{"));
9746
+ const parenGroups = [...signature.matchAll(/\(([^()]*)\)/g)];
9747
+ const singleArrow = /=\s*([A-Za-z_$][\w$]*)\s*=>\s*$/.exec(signature);
9748
+ const rawParams = parenGroups.at(-1)?.[1] ?? singleArrow?.[1] ?? "";
9749
+ const params = new Set(rawParams.split(",").map((part) => /^([A-Za-z_$][\w$]*)/.exec(part.trim())?.[1]).filter((name2) => Boolean(name2)));
9750
+ out.push({
9751
+ name,
9752
+ declStart: m.index,
9753
+ bodyStart,
9754
+ bodyEnd,
9755
+ body: paired,
9756
+ params,
9757
+ immediatelyInvoked: invocationAfter(m.index, bodyEnd, expression)
9758
+ });
9759
+ }
9760
+ }
9761
+ const functionRe = /\b(?:async\s+)?function\s*\*?\s*([A-Za-z_$][\w$]*)?\s*\(/g;
9762
+ let fm;
9763
+ while (fm = functionRe.exec(code)) {
9764
+ const open2 = fm.index + fm[0].lastIndexOf("(");
9765
+ const close = closeParen(code, open2);
9766
+ if (close < 0)
9767
+ continue;
9768
+ let brace = close + 1;
9769
+ while (brace < code.length && /\s/.test(code[brace]))
9770
+ brace++;
9771
+ if (code[brace] !== "{")
9772
+ continue;
9773
+ const paired = braceBlock(code, brace);
9774
+ const bodyStart = brace + 1;
9775
+ const bodyEnd = bodyStart + paired.length;
9776
+ if (out.some((fn) => fn.bodyStart === bodyStart))
9777
+ continue;
9778
+ const prefix = code.slice(Math.max(0, fm.index - 160), fm.index);
9779
+ const assigned = /\b(?:var|let|const)\s+([A-Za-z_$][\w$]*)\s*=\s*$/.exec(prefix);
9780
+ const name = assigned?.[1] ?? fm[1] ?? null;
9781
+ const params = new Set(code.slice(open2 + 1, close).split(",").map((part) => /^([A-Za-z_$][\w$]*)/.exec(part.trim())?.[1]).filter((param) => Boolean(param)));
9782
+ out.push({
9783
+ name,
9784
+ declStart: fm.index,
9785
+ bodyStart,
9786
+ bodyEnd,
9787
+ body: paired,
9788
+ params,
9789
+ immediatelyInvoked: invocationAfter(fm.index, bodyEnd, assigned !== null || fm[1] === undefined)
9790
+ });
9791
+ }
9792
+ const arrowRe = /\b(?:var|let|const)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s*)?\(/g;
9793
+ let am;
9794
+ while (am = arrowRe.exec(code)) {
9795
+ const open2 = am.index + am[0].lastIndexOf("(");
9796
+ const close = closeParen(code, open2);
9797
+ if (close < 0)
9798
+ continue;
9799
+ let arrow = skipSpace(code, close + 1);
9800
+ if (!code.startsWith("=>", arrow))
9801
+ continue;
9802
+ const brace = skipSpace(code, arrow + 2);
9803
+ if (code[brace] !== "{")
9804
+ continue;
9805
+ const paired = braceBlock(code, brace);
9806
+ const bodyStart = brace + 1;
9807
+ const bodyEnd = bodyStart + paired.length;
9808
+ if (out.some((fn) => fn.bodyStart === bodyStart))
9809
+ continue;
9810
+ const params = new Set(code.slice(open2 + 1, close).split(",").map((part) => /^([A-Za-z_$][\w$]*)/.exec(part.trim())?.[1]).filter((param) => Boolean(param)));
9811
+ out.push({
9812
+ name: am[1],
9813
+ declStart: am.index,
9814
+ bodyStart,
9815
+ bodyEnd,
9816
+ body: paired,
9817
+ params,
9818
+ immediatelyInvoked: invocationAfter(am.index, bodyEnd, true)
9819
+ });
9820
+ }
9821
+ return out.sort((a, b) => a.declStart - b.declStart);
9822
+ }
9823
+ var CREATE_PRIMITIVE = /\b(?:(?:var|let|const)\s+)?([A-Za-z_$][\w$]*)\s*=(?!=)\s*document\s*\.\s*createElementNS\s*\(\s*[^,]+,\s*["']([A-Za-z][\w-]*)["']\s*\)/gi;
9824
+ function escapedIdent(name) {
9825
+ return name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9826
+ }
9827
+ function appendedParent(body, elVar, baseAt, sameExecutionScope) {
9828
+ const el = escapedIdent(elVar);
9829
+ const appendRe = new RegExp(`\\b([A-Za-z_$][\\w$]*)\\s*\\.\\s*appendChild\\s*\\(\\s*${el}\\s*\\)`, "g");
9830
+ let append;
9831
+ while ((append = appendRe.exec(body)) && !sameExecutionScope(baseAt + append.index))
9832
+ ;
9833
+ if (!append)
9834
+ return null;
9835
+ const reassignRe = new RegExp(`\\b(?:(?:var|let|const)\\s+)?${el}\\s*=(?!=)`, "g");
9836
+ let reassign;
9837
+ while (reassign = reassignRe.exec(body)) {
9838
+ if (reassign.index >= append.index)
9839
+ break;
9840
+ if (body[reassign.index - 1] === ".")
9841
+ continue;
9842
+ if (sameExecutionScope(baseAt + reassign.index))
9843
+ return null;
9844
+ }
9845
+ return append[1];
9846
+ }
9847
+ function perElementDriven(html, body, elVar) {
9848
+ const el = escapedIdent(elVar);
9849
+ if (new RegExp(`\\bgsap\\s*\\.\\s*set\\s*\\(\\s*${el}\\b`).test(body))
9850
+ return true;
9851
+ if (new RegExp(`\\b[A-Za-z_$][\\w$]*\\s*\\.\\s*(?:to|from|fromTo)\\s*\\(\\s*${el}\\b`).test(body))
9852
+ return true;
9853
+ const pushes = new Set;
9854
+ for (const pm of body.matchAll(new RegExp(`\\b([A-Za-z_$][\\w$]*)(?:\\s*\\[[^\\]]+\\])?\\s*\\.\\s*push\\s*\\(\\s*${el}\\s*\\)`, "g")))
9855
+ pushes.add(pm[1]);
9856
+ for (const arr of pushes) {
9857
+ const a = escapedIdent(arr);
9858
+ if (new RegExp(`\\b[A-Za-z_$][\\w$]*\\s*\\.\\s*(?:to|from|fromTo)\\s*\\(\\s*${a}(?:\\s*\\[[^\\]]+\\])?\\s*,`).test(html))
9859
+ return true;
9860
+ }
9861
+ return false;
9862
+ }
9863
+ function detectPrimitiveLoops(html) {
9864
+ const source = maskJsComments(scriptBodiesOnly(maskHtmlComments(html)));
9865
+ const code = maskJsStrings(source);
9866
+ const loops = loopSites(code);
9867
+ const functions = namedFunctionSites(code);
9868
+ const containingFunction = (at) => functions.filter((fn) => at >= fn.bodyStart && at < fn.bodyEnd).sort((a, b) => a.bodyEnd - a.bodyStart - (b.bodyEnd - b.bodyStart))[0] ?? null;
9869
+ const functionOwner = (fn) => containingFunction(fn.declStart);
9870
+ const hasOwnVariableBinding = (scope, name) => {
9871
+ if (scope.params.has(name))
9872
+ return true;
9873
+ const re = new RegExp(`\\b(?:var|let|const|class)\\s+${escapedIdent(name)}\\b`, "g");
9874
+ re.lastIndex = scope.bodyStart;
9875
+ let binding;
9876
+ while ((binding = re.exec(code)) && binding.index < scope.bodyEnd)
9877
+ if (containingFunction(binding.index) === scope)
9878
+ return true;
9879
+ return false;
9880
+ };
9881
+ const resolveFunction = (name, at) => {
9882
+ let scope = containingFunction(at);
9883
+ while (scope) {
9884
+ const local = functions.filter((fn) => fn.name === name && functionOwner(fn) === scope).sort((a, b) => b.declStart - a.declStart)[0];
9885
+ if (local)
9886
+ return local;
9887
+ if (scope.name === name)
9888
+ return scope;
9889
+ if (hasOwnVariableBinding(scope, name))
9890
+ return null;
9891
+ scope = functionOwner(scope);
9892
+ }
9893
+ return functions.filter((fn) => fn.name === name && functionOwner(fn) === null).sort((a, b) => b.declStart - a.declStart)[0] ?? null;
9894
+ };
9895
+ const parentBindingScope = (fn, parent) => {
9896
+ let scope = fn;
9897
+ while (scope) {
9898
+ if (hasOwnVariableBinding(scope, parent))
9899
+ return scope;
9900
+ scope = functionOwner(scope);
9901
+ }
9902
+ return null;
9903
+ };
9904
+ const freshParentDeclaration = (parent, before, scope) => {
9905
+ const ident = escapedIdent(parent);
9906
+ const re = new RegExp(`\\b(?:var|let|const)\\s+${ident}\\s*=\\s*(?:document\\s*\\.\\s*createElement(?:NS)?\\s*\\(|new\\b)`, "g");
9907
+ re.lastIndex = scope?.bodyStart ?? 0;
9908
+ let found = null;
9909
+ let m;
9910
+ while ((m = re.exec(code)) && m.index < before && (!scope || m.index < scope.bodyEnd))
9911
+ if (containingFunction(m.index) === scope)
9912
+ found = m.index;
9913
+ return found;
9914
+ };
9915
+ const loopsForOneParent = (owned, freshAt) => freshAt === null ? owned : owned.filter((loop) => freshAt < loop.bodyStart || freshAt >= loop.bodyEnd);
9916
+ const executionLoopsAt = (at) => {
9917
+ const fn = containingFunction(at);
9918
+ return loops.filter((loop) => {
9919
+ if (at < loop.bodyStart || at >= loop.bodyEnd)
9920
+ return false;
9921
+ if (fn)
9922
+ return loop.bodyStart >= fn.bodyStart && loop.bodyEnd <= fn.bodyEnd;
9923
+ return containingFunction(loop.bodyStart) === null;
9924
+ });
9925
+ };
9926
+ const loopProduct = (owned) => {
9927
+ if (owned.some((loop) => loop.count === null))
9928
+ return null;
9929
+ let product = 1;
9930
+ for (const loop of owned) {
9931
+ product *= loop.count;
9932
+ if (!Number.isSafeInteger(product))
9933
+ return null;
9934
+ }
9935
+ return product;
9936
+ };
9937
+ const innermostLoopBody = (at, owned = executionLoopsAt(at)) => {
9938
+ const loop = owned.sort((a, b) => a.bodyEnd - a.bodyStart - (b.bodyEnd - b.bodyStart))[0];
9939
+ return loop ? code.slice(loop.bodyStart, loop.bodyEnd) : "";
9940
+ };
9941
+ const grouped = new Map;
9942
+ const add = (tag, parent, count, driven, site, scope) => {
9943
+ const key = `${scope}\x00${tag}\x00${parent}`;
9944
+ const batch = grouped.get(key) ?? { tag, parent, count: 0, unknown: false, perElementDriven: false, sites: new Set };
9945
+ if (count === null)
9946
+ batch.unknown = true;
9947
+ else {
9948
+ batch.count += count;
9949
+ if (!Number.isSafeInteger(batch.count))
9950
+ batch.unknown = true;
9951
+ }
9952
+ batch.perElementDriven ||= driven;
9953
+ batch.sites.add(site);
9954
+ grouped.set(key, batch);
9955
+ };
9956
+ const creates = [];
9957
+ CREATE_PRIMITIVE.lastIndex = 0;
9958
+ let cm;
9959
+ while (cm = CREATE_PRIMITIVE.exec(source)) {
9960
+ if (!code[cm.index] || /\s/.test(code[cm.index]))
9961
+ continue;
9962
+ const tag = cm[2].toLowerCase();
9963
+ if (!MERGEABLE_PRIMITIVES.has(tag))
9964
+ continue;
9965
+ const fn = containingFunction(cm.index);
9966
+ const owned = executionLoopsAt(cm.index);
9967
+ const nearest = owned.slice().sort((a, b) => a.bodyEnd - a.bodyStart - (b.bodyEnd - b.bodyStart))[0];
9968
+ const bodyStart = nearest?.bodyStart ?? fn?.bodyStart ?? cm.index;
9969
+ const body = nearest ? code.slice(nearest.bodyStart, nearest.bodyEnd) : fn?.body ?? "";
9970
+ const tailStart = Math.max(0, cm.index - bodyStart) + cm[0].length;
9971
+ const parent = appendedParent(body.slice(tailStart), cm[1], bodyStart + tailStart, (at) => containingFunction(at) === fn);
9972
+ if (!parent)
9973
+ continue;
9974
+ creates.push({ at: cm.index, tag, elVar: cm[1], parent, fn, loops: owned });
9975
+ }
9976
+ const callsByFunction = new Map;
9977
+ const pushCall = (fn, at) => {
9978
+ const list = callsByFunction.get(fn.declStart) ?? [];
9979
+ list.push({ at, loops: executionLoopsAt(at) });
9980
+ callsByFunction.set(fn.declStart, list);
9981
+ };
9982
+ const functionNames = new Set(functions.map((fn) => fn.name).filter((name) => name !== null));
9983
+ for (const call of code.matchAll(/\b([A-Za-z_$][\w$]*)\s*\(/g)) {
9984
+ const name = call[1];
9985
+ const at = call.index;
9986
+ let before = at - 1;
9987
+ while (before >= 0 && /\s/.test(code[before]))
9988
+ before--;
9989
+ if (NOT_A_CALL.has(name) || !functionNames.has(name) || code[before] === ".")
9990
+ continue;
9991
+ if (functions.some((fn) => fn.name === name && at >= fn.declStart && at < fn.bodyStart))
9992
+ continue;
9993
+ const target = resolveFunction(name, at);
9994
+ if (target)
9995
+ pushCall(target, at);
9996
+ }
9997
+ for (const fn of functions)
9998
+ if (fn.immediatelyInvoked)
9999
+ pushCall(fn, fn.bodyEnd);
10000
+ for (const create of creates) {
10001
+ if (!create.fn) {
10002
+ if (create.loops.length === 0)
10003
+ continue;
10004
+ const nearest = create.loops.slice().sort((a, b) => a.bodyEnd - a.bodyStart - (b.bodyEnd - b.bodyStart))[0];
10005
+ const body = innermostLoopBody(create.at, create.loops);
10006
+ const freshAt = freshParentDeclaration(create.parent, create.at, null);
10007
+ add(create.tag, create.parent, loopProduct(loopsForOneParent(create.loops, freshAt)), perElementDriven(code, body, create.elVar), `${nearest.kind} (${nearest.header.trim()})`, freshAt === null ? "shared-parent" : `fresh-parent:${freshAt}`);
10008
+ continue;
10009
+ }
10010
+ const fnCalls = callsByFunction.get(create.fn.declStart) ?? [];
10011
+ if (fnCalls.length === 0 && create.loops.length > 0) {
10012
+ const nearest = create.loops.slice().sort((a, b) => a.bodyEnd - a.bodyStart - (b.bodyEnd - b.bodyStart))[0];
10013
+ const boundParent = parentBindingScope(create.fn, create.parent);
10014
+ const freshAt = freshParentDeclaration(create.parent, create.at, boundParent);
10015
+ add(create.tag, create.parent, loopProduct(loopsForOneParent(create.loops, freshAt)), perElementDriven(code, innermostLoopBody(create.at, create.loops), create.elVar), `${nearest.kind} (${nearest.header.trim()})`, freshAt !== null ? `fresh-parent:${freshAt}` : boundParent ? `function:${boundParent.declStart}` : "shared-parent");
10016
+ }
10017
+ for (const call of fnCalls) {
10018
+ if (create.loops.length === 0 && call.loops.length === 0)
10019
+ continue;
10020
+ const callerBody = innermostLoopBody(call.at, call.loops);
10021
+ const boundParent = parentBindingScope(create.fn, create.parent);
10022
+ const freshAt = freshParentDeclaration(create.parent, create.at, boundParent);
10023
+ const oneParentInside = loopProduct(loopsForOneParent(create.loops, freshAt));
10024
+ const oneParentOutside = loopProduct(loopsForOneParent(call.loops, freshAt));
10025
+ const freshOwnedByFactory = freshAt !== null && boundParent === create.fn;
10026
+ const count = freshOwnedByFactory ? oneParentInside : oneParentInside === null || oneParentOutside === null ? null : oneParentInside * oneParentOutside;
10027
+ add(create.tag, create.parent, count, perElementDriven(code, `${create.fn.body}
10028
+ ${callerBody}`, create.elVar), create.fn.name ? `factory ${create.fn.name}` : `IIFE @${create.fn.declStart}`, freshAt !== null ? `fresh-parent:${freshAt}` : boundParent ? `function:${boundParent.declStart}` : "shared-parent");
10029
+ }
10030
+ }
10031
+ return [...grouped.values()].map((batch) => ({
10032
+ tag: batch.tag,
10033
+ parent: batch.parent,
10034
+ count: batch.unknown ? null : batch.count,
10035
+ perElementDriven: batch.perElementDriven,
10036
+ site: [...batch.sites].join(" + ")
10037
+ }));
10038
+ }
9321
10039
  function bodyWritesDom(html, body, hops, seen) {
9322
10040
  if (DOM_WRITE.test(body))
9323
10041
  return true;
@@ -9466,6 +10184,12 @@ function lintParticle(html, opts = {}) {
9466
10184
  else if (Number.isFinite(est) && est < slot)
9467
10185
  push("7-fill-slot", false, `时间线静态估长 ~${est}s,短于槽位包络 ${slot}s(铁律⑦:颗粒应占满坑位并终态驻留)——静态估算是**下界**(${skipped} 条调用因无法静态解析被跳过),仅供参考,最终以真渲染引擎逐帧为准`);
9468
10186
  }
10187
+ for (const batch of detectPrimitiveLoops(html)) {
10188
+ if (batch.perElementDriven || batch.count !== null && batch.count < PRIMITIVE_MERGE_MIN)
10189
+ continue;
10190
+ const amount = batch.count === null ? "条数未知(循环边界非数字字面量)" : `静态估算 ${batch.count} 个`;
10191
+ push("8-primitive-merge", false, `${batch.site} 向同一父节点「${batch.parent}」循环生成 ${amount} <${batch.tag}>。` + "这里有一批可无损合并的重复图元:请按相同 stroke/fill 分档合并成单个 <path> 的多子路径;" + "合并后画面逐像素不变,属零成本改法。本项只提示写法形态,最终画面仍以真渲染出片为准");
10192
+ }
9469
10193
  return { ok: !v.some((x) => x.fatal), violations: v, opaque, compositionId: cid };
9470
10194
  }
9471
10195