@emailens/engine 0.10.0 → 0.10.1

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
@@ -10375,351 +10375,25 @@ function transformForAllClients(html, framework) {
10375
10375
  }
10376
10376
 
10377
10377
  // src/analyze.ts
10378
- import * as cheerio3 from "cheerio";
10379
- import * as csstree3 from "css-tree";
10380
- var HTML_ELEMENT_SELECTORS = {
10381
- "<style>": "style",
10382
- "<link>": "link[rel='stylesheet']",
10383
- "<svg>": "svg",
10384
- "<video>": "video",
10385
- "<form>": "form, input, button[type='submit']",
10386
- "<audio>": "audio",
10387
- "<picture>": "picture",
10388
- "<dialog>": "dialog",
10389
- "<meter>": "meter",
10390
- "<progress>": "progress",
10391
- "<select>": "select",
10392
- "<textarea>": "textarea",
10393
- "<marquee>": "marquee",
10394
- "<object>": "object",
10395
- "<base>": "base"
10396
- };
10397
- var HTML_ELEMENT_SEVERITY = {
10398
- "<style>": "error",
10399
- "<link>": "error",
10400
- "<svg>": "error",
10401
- "<form>": "error",
10402
- "<video>": "warning",
10403
- "<audio>": "warning",
10404
- "<picture>": "warning",
10405
- "<dialog>": "warning",
10406
- "<marquee>": "warning",
10407
- "<meter>": "warning",
10408
- "<progress>": "warning",
10409
- "<select>": "warning",
10410
- "<textarea>": "warning",
10411
- "<object>": "warning",
10412
- "<base>": "warning"
10413
- };
10414
- var HTML_ELEMENT_MESSAGES = {
10415
- "<style>": (n) => `${n} strips <style> blocks. Styles must be inlined.`,
10416
- "<link>": (n) => `${n} does not support external stylesheets.`,
10417
- "<svg>": (n) => `${n} does not support inline SVG.`,
10418
- "<video>": (n) => `${n} does not support <video> elements.`,
10419
- "<form>": (n) => `${n} strips form elements.`
10420
- };
10421
- var COMPOUND_DETECTORS = [
10422
- { key: "display:flex", property: "display", valueIncludes: "flex" },
10423
- { key: "display:grid", property: "display", valueIncludes: "grid" },
10424
- { key: "display:none", property: "display", valueIncludes: "none" }
10425
- ];
10426
- var CSS_FUNCTION_DETECTORS = CSS_FUNCTION_FEATURES.map((fn) => ({
10427
- key: fn,
10428
- pattern: `${fn}(`
10429
- // require opening paren — matches "min(" but not "Minion"
10430
- }));
10431
- function analyzeEmailFromDom($, framework) {
10432
- const warnings = [];
10433
- const seenWarnings = /* @__PURE__ */ new Set();
10434
- function addWarning(w) {
10435
- const key = `${w.client}:${w.property}:${w.severity}:${w.selector || ""}`;
10436
- if (!seenWarnings.has(key)) {
10437
- seenWarnings.add(key);
10438
- warnings.push(w);
10439
- }
10440
- }
10441
- function describeSelector(el) {
10442
- var _a;
10443
- const $el = $(el);
10444
- const tag = ((_a = el.tagName) == null ? void 0 : _a.toLowerCase()) || "";
10445
- const cls = $el.attr("class");
10446
- const id = $el.attr("id");
10447
- if (id) return `${tag}#${id}`;
10448
- if (cls) return `${tag}.${cls.split(/\s+/)[0]}`;
10449
- const href = $el.attr("href");
10450
- if (href) return `${tag}[href]`;
10451
- return tag;
10452
- }
10453
- for (const feature of HTML_ELEMENT_FEATURES) {
10454
- const selector = HTML_ELEMENT_SELECTORS[feature];
10455
- if (!selector) continue;
10456
- if ($(selector).length === 0) continue;
10457
- const supportData = CSS_SUPPORT[feature];
10458
- if (!supportData) continue;
10459
- const baseSeverity = HTML_ELEMENT_SEVERITY[feature] || "warning";
10460
- for (const client of EMAIL_CLIENTS) {
10461
- const support = supportData[client.id];
10462
- if (support === "unsupported") {
10463
- const msgFn = HTML_ELEMENT_MESSAGES[feature];
10464
- const message = msgFn ? msgFn(client.name) : `${client.name} does not support ${feature}.`;
10465
- const sug = getSuggestion(feature, client.id, framework);
10466
- const fix = getCodeFix(feature, client.id, framework);
10467
- addWarning(__spreadValues({
10468
- severity: baseSeverity,
10469
- client: client.id,
10470
- property: feature,
10471
- message,
10472
- suggestion: sug.text,
10473
- fix,
10474
- fixType: getFixType(feature)
10475
- }, framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback(feature, client.id, framework)) ? { fixIsGenericFallback: true } : {}));
10476
- } else if (support === "partial" && feature === "<style>") {
10477
- const sug = getSuggestion("<style>:partial", client.id, framework);
10478
- const fix = getCodeFix("<style>", client.id, framework);
10479
- addWarning(__spreadValues({
10480
- severity: "warning",
10481
- client: client.id,
10482
- property: "<style>",
10483
- message: `${client.name} has partial <style> support (head only, with limitations). Inline styles recommended.`,
10484
- suggestion: sug.text,
10485
- fix,
10486
- fixType: getFixType("<style>")
10487
- }, framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback("<style>", client.id, framework)) ? { fixIsGenericFallback: true } : {}));
10488
- }
10489
- }
10490
- }
10491
- const parsedAtRules = /* @__PURE__ */ new Set();
10492
- const parsedProperties = /* @__PURE__ */ new Set();
10493
- const propertyLines = /* @__PURE__ */ new Map();
10494
- const propertyValues = /* @__PURE__ */ new Map();
10495
- const detectedCssFunctions = /* @__PURE__ */ new Set();
10496
- const detectedPseudoClasses = /* @__PURE__ */ new Set();
10497
- const detectedPseudoElements = /* @__PURE__ */ new Set();
10498
- $("style").each((_, el) => {
10499
- const cssText = $(el).text();
10500
- try {
10501
- const ast = csstree3.parse(cssText, { parseCustomProperty: true, positions: true });
10502
- csstree3.walk(ast, {
10503
- enter(node) {
10504
- if (node.type === "Atrule") {
10505
- parsedAtRules.add(`@${node.name}`);
10506
- }
10507
- if (node.type === "PseudoClassSelector") {
10508
- detectedPseudoClasses.add(`:${node.name}`);
10509
- }
10510
- if (node.type === "PseudoElementSelector") {
10511
- detectedPseudoElements.add(`::${node.name}`);
10512
- }
10513
- if (node.type === "Declaration") {
10514
- const prop = node.property.toLowerCase();
10515
- parsedProperties.add(prop);
10516
- if (node.loc && !propertyLines.has(prop)) {
10517
- propertyLines.set(prop, node.loc.start.line);
10518
- }
10519
- const valueStr = csstree3.generate(node.value);
10520
- const seenValues = propertyValues.get(prop);
10521
- if (seenValues) seenValues.push(valueStr);
10522
- else propertyValues.set(prop, [valueStr]);
10523
- for (const det of COMPOUND_DETECTORS) {
10524
- if (prop === det.property && valueStr.includes(det.valueIncludes)) {
10525
- parsedProperties.add(det.key);
10526
- if (node.loc && !propertyLines.has(det.key)) {
10527
- propertyLines.set(det.key, node.loc.start.line);
10528
- }
10529
- }
10530
- }
10531
- for (const fn of CSS_FUNCTION_DETECTORS) {
10532
- if (valueStr.includes(fn.pattern)) {
10533
- detectedCssFunctions.add(fn.key);
10534
- if (node.loc && !propertyLines.has(fn.key)) {
10535
- propertyLines.set(fn.key, node.loc.start.line);
10536
- }
10537
- }
10538
- }
10539
- }
10540
- }
10541
- });
10542
- } catch (e) {
10543
- }
10544
- });
10545
- for (const atRule of AT_RULE_FEATURES) {
10546
- if (!parsedAtRules.has(atRule)) continue;
10547
- checkPropertySupport(atRule, addWarning, framework);
10548
- }
10549
- const cssPropertiesToCheck = Object.keys(CSS_SUPPORT).filter(
10550
- (k) => !k.startsWith("<") && !k.startsWith("@")
10551
- );
10552
- $("[style]").each((_, el) => {
10553
- var _a;
10554
- const style = $(el).attr("style") || "";
10555
- const props = parseStyleProperties(style);
10556
- const selector = describeSelector(el);
10557
- for (const prop of props) {
10558
- for (const det of COMPOUND_DETECTORS) {
10559
- if (prop === det.property) {
10560
- const value2 = getStyleValue(style, prop);
10561
- if (value2 == null ? void 0 : value2.includes(det.valueIncludes)) {
10562
- checkPropertySupport(det.key, addWarning, framework, selector);
10563
- }
10564
- }
10565
- }
10566
- if (cssPropertiesToCheck.includes(prop)) {
10567
- checkPropertySupport(prop, addWarning, framework, selector, void 0, (_a = getStyleValue(style, prop)) != null ? _a : void 0);
10568
- }
10569
- const value = getStyleValue(style, prop);
10570
- if (value) {
10571
- for (const fn of CSS_FUNCTION_DETECTORS) {
10572
- if (value.includes(fn.pattern)) {
10573
- checkPropertySupport(fn.key, addWarning, framework, selector);
10574
- }
10575
- }
10576
- }
10577
- }
10578
- });
10579
- for (const prop of parsedProperties) {
10580
- if (prop.includes(":")) continue;
10581
- if (!cssPropertiesToCheck.includes(prop)) continue;
10582
- const values = propertyValues.get(prop);
10583
- checkPropertySupport(
10584
- prop,
10585
- addWarning,
10586
- framework,
10587
- void 0,
10588
- propertyLines.get(prop),
10589
- values ? values.join(" ") : void 0
10590
- );
10591
- }
10592
- for (const compound of COMPOUND_VALUE_FEATURES) {
10593
- if (compound.startsWith(":") || compound.startsWith("::")) continue;
10594
- if (parsedProperties.has(compound)) {
10595
- checkPropertySupport(compound, addWarning, framework, void 0, propertyLines.get(compound));
10596
- }
10597
- }
10598
- for (const pseudo of detectedPseudoClasses) {
10599
- if (CSS_SUPPORT[pseudo]) {
10600
- checkPropertySupport(pseudo, addWarning, framework);
10601
- }
10602
- }
10603
- for (const pseudo of detectedPseudoElements) {
10604
- if (CSS_SUPPORT[pseudo]) {
10605
- checkPropertySupport(pseudo, addWarning, framework);
10606
- }
10607
- }
10608
- for (const fn of detectedCssFunctions) {
10609
- checkPropertySupport(fn, addWarning, framework, void 0, propertyLines.get(fn));
10610
- }
10611
- const severityOrder = { error: 0, warning: 1, info: 2 };
10612
- warnings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
10613
- return warnings;
10614
- }
10615
- function analyzeEmail(html, framework) {
10616
- if (!html || !html.trim()) {
10617
- return [];
10618
- }
10619
- if (html.length > MAX_HTML_SIZE) {
10620
- throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
10621
- }
10622
- const $ = cheerio3.load(html);
10623
- return analyzeEmailFromDom($, framework);
10624
- }
10625
- function getFixType(prop) {
10626
- return STRUCTURAL_FIX_PROPERTIES.has(prop) ? "structural" : "css";
10627
- }
10628
- var VALUE_CAVEAT_PROPS = /* @__PURE__ */ new Set(["margin", "position", "overflow"]);
10629
- var POSITION_KEYWORDS = ["relative", "absolute", "fixed", "sticky"];
10630
- function valueTriggersCaveat(prop, value, notes) {
10631
- const note = (notes != null ? notes : []).join(" ");
10632
- const noteLc = note.toLowerCase();
10633
- if (prop === "margin") {
10634
- if (/(?:^|[\s:(])-\.?\d/.test(value) && noteLc.includes("negative")) return true;
10635
- if (/\bauto\b/.test(value) && noteLc.includes("auto")) return true;
10636
- return false;
10637
- }
10638
- if (prop === "position") {
10639
- const used = POSITION_KEYWORDS.find((k) => new RegExp(`\\b${k}\\b`).test(value));
10640
- if (!used) return false;
10641
- const m = note.match(/supports\s+.+?\s+but not\s+([^.]+)/i);
10642
- if (m) return m[1].toLowerCase().includes(used);
10643
- return used === "fixed" || used === "sticky";
10644
- }
10645
- if (prop === "overflow") {
10646
- if (!/\b(?:auto|scroll)\b/.test(value)) return false;
10647
- return noteLc.includes("cannot scroll");
10648
- }
10649
- return true;
10650
- }
10651
- function noteSuffix(notes) {
10652
- if (!(notes == null ? void 0 : notes.length)) return "";
10653
- const cleaned = notes.map((n) => n.replace(/^(?:Partial|Buggy|Not supported)\.\s*/i, "").trim()).filter(Boolean);
10654
- return cleaned.length ? ` ${cleaned.join(" ")}` : "";
10655
- }
10656
- function checkPropertySupport(prop, addWarning, framework, selector, line, value) {
10657
- var _a;
10658
- const supportData = CSS_SUPPORT[prop];
10659
- if (!supportData) return;
10660
- const fixType = getFixType(prop);
10661
- const valueGated = VALUE_CAVEAT_PROPS.has(prop);
10662
- for (const client of EMAIL_CLIENTS) {
10663
- const support = supportData[client.id] || "unknown";
10664
- const notes = (_a = CSS_SUPPORT_NOTES[prop]) == null ? void 0 : _a[client.id];
10665
- if (support === "unsupported") {
10666
- const sug = getSuggestion(prop, client.id, framework);
10667
- const fix = getCodeFix(prop, client.id, framework);
10668
- addWarning(__spreadValues(__spreadValues(__spreadValues({
10669
- severity: "warning",
10670
- client: client.id,
10671
- property: prop,
10672
- message: `${client.name} does not support "${prop}".${noteSuffix(notes)}`,
10673
- suggestion: sug.text,
10674
- fix,
10675
- fixType
10676
- }, selector ? { selector } : {}), line !== void 0 ? { line } : {}), framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback(prop, client.id, framework)) ? { fixIsGenericFallback: true } : {}));
10677
- } else if (support === "partial") {
10678
- if (valueGated && value !== void 0 && !valueTriggersCaveat(prop, value, notes)) continue;
10679
- const sug = getSuggestion(prop, client.id, framework);
10680
- const fix = getCodeFix(prop, client.id, framework);
10681
- addWarning(__spreadValues(__spreadValues(__spreadValues({
10682
- severity: "info",
10683
- client: client.id,
10684
- property: prop,
10685
- message: `${client.name} has partial support for "${prop}".${noteSuffix(notes)}`,
10686
- suggestion: sug.text,
10687
- fix,
10688
- fixType
10689
- }, selector ? { selector } : {}), line !== void 0 ? { line } : {}), framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback(prop, client.id, framework)) ? { fixIsGenericFallback: true } : {}));
10690
- }
10691
- }
10692
- }
10693
- function generateCompatibilityScore(warnings) {
10694
- const result = {};
10695
- for (const client of EMAIL_CLIENTS) {
10696
- const clientWarnings = warnings.filter((w) => w.client === client.id);
10697
- const errorProps = new Set(clientWarnings.filter((w) => w.severity === "error").map((w) => w.property));
10698
- const warnProps = new Set(clientWarnings.filter((w) => w.severity === "warning").map((w) => w.property));
10699
- const infoProps = new Set(clientWarnings.filter((w) => w.severity === "info").map((w) => w.property));
10700
- const errors = errorProps.size;
10701
- const warns = warnProps.size;
10702
- const info = infoProps.size;
10703
- const score = Math.max(0, Math.min(100, 100 - errors * 10 - warns * 3));
10704
- result[client.id] = { score, errors, warnings: warns, info };
10705
- }
10706
- return result;
10707
- }
10708
- function warningsForClient(warnings, clientId) {
10709
- return warnings.filter((w) => w.client === clientId);
10710
- }
10711
- function errorWarnings(warnings) {
10712
- return warnings.filter((w) => w.severity === "error");
10713
- }
10714
- function structuralWarnings(warnings) {
10715
- return warnings.filter((w) => w.fixType === "structural");
10716
- }
10717
-
10718
- // src/dark-mode.ts
10719
10378
  import * as cheerio4 from "cheerio";
10379
+ import * as csstree5 from "css-tree";
10380
+
10381
+ // src/dark-mode-checker.ts
10720
10382
  import * as csstree4 from "css-tree";
10383
+
10384
+ // src/dark-mode.ts
10385
+ import * as cheerio3 from "cheerio";
10386
+ import * as csstree3 from "css-tree";
10721
10387
  var LIGHT_THRESHOLD = 0.7;
10722
10388
  var DARK_THRESHOLD = 0.15;
10389
+ var PREFERS_COLOR_SCHEME_CLIENTS = [
10390
+ "apple-mail-macos",
10391
+ "apple-mail-ios",
10392
+ "samsung-mail",
10393
+ "thunderbird",
10394
+ "hey-mail",
10395
+ "superhuman"
10396
+ ];
10723
10397
  function simulateDarkMode(html, clientId) {
10724
10398
  var _a, _b;
10725
10399
  if (!html || !html.trim()) {
@@ -10728,7 +10402,7 @@ function simulateDarkMode(html, clientId) {
10728
10402
  if (html.length > MAX_HTML_SIZE) {
10729
10403
  throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
10730
10404
  }
10731
- const $ = cheerio4.load(html);
10405
+ const $ = cheerio3.load(html);
10732
10406
  const warnings = [];
10733
10407
  $("img").each((_, el) => {
10734
10408
  const src = $(el).attr("src") || "";
@@ -10930,54 +10604,531 @@ function applyColorInversion($, mode) {
10930
10604
  }
10931
10605
  }
10932
10606
  }
10933
- });
10934
- if (changed) {
10935
- $(el).attr("style", serializeStyle(props));
10607
+ });
10608
+ if (changed) {
10609
+ $(el).attr("style", serializeStyle(props));
10610
+ }
10611
+ });
10612
+ $("style").each((_, el) => {
10613
+ const cssText = $(el).text();
10614
+ try {
10615
+ const ast = csstree3.parse(cssText, { parseCustomProperty: true });
10616
+ let modified = false;
10617
+ csstree3.walk(ast, {
10618
+ enter(node) {
10619
+ if (node.type !== "Declaration") return;
10620
+ const prop = node.property.toLowerCase();
10621
+ if (!COLOR_PROPS.has(prop) && prop !== "background") return;
10622
+ const valueStr = csstree3.generate(node.value);
10623
+ if (prop === "background") {
10624
+ const bgColor = extractBackgroundColor(valueStr);
10625
+ if (bgColor) {
10626
+ const inverted = invertColor(bgColor, mode);
10627
+ if (inverted) {
10628
+ const newValue = valueStr.replace(bgColor, inverted);
10629
+ node.value = csstree3.parse(newValue, { context: "value" });
10630
+ modified = true;
10631
+ }
10632
+ }
10633
+ } else {
10634
+ const inverted = invertColor(valueStr, mode);
10635
+ if (inverted) {
10636
+ node.value = csstree3.parse(inverted, { context: "value" });
10637
+ modified = true;
10638
+ }
10639
+ }
10640
+ }
10641
+ });
10642
+ if (modified) {
10643
+ $(el).text(csstree3.generate(ast));
10644
+ }
10645
+ } catch (e) {
10646
+ }
10647
+ });
10648
+ $("[bgcolor]").each((_, el) => {
10649
+ const bgcolor = $(el).attr("bgcolor") || "";
10650
+ const inverted = invertColor(bgcolor, mode);
10651
+ if (inverted) {
10652
+ $(el).attr("bgcolor", inverted);
10653
+ }
10654
+ });
10655
+ }
10656
+
10657
+ // src/dark-mode-checker.ts
10658
+ var DARK_MEDIA_RE = /\(\s*prefers-color-scheme\s*:\s*dark\s*\)/i;
10659
+ var MAX_UNCOVERED_ELEMENTS = 3;
10660
+ function backgroundShorthandColor(value) {
10661
+ if (!value) return null;
10662
+ const trimmed = value.trim();
10663
+ if (parseColor(trimmed)) return trimmed;
10664
+ for (const token of trimmed.split(/\s+/)) {
10665
+ if (token.includes("(")) continue;
10666
+ if (parseColor(token)) return token;
10667
+ }
10668
+ return null;
10669
+ }
10670
+ function isLight(value) {
10671
+ const c = parseColor(value);
10672
+ if (!c || c.a < 0.5) return false;
10673
+ return relativeLuminance(c.r, c.g, c.b) > LIGHT_THRESHOLD;
10674
+ }
10675
+ function collectDarkBlocks($) {
10676
+ const block = { any: [], important: [], rules: 0 };
10677
+ let found = false;
10678
+ $("style").each((_, el) => {
10679
+ const cssText = $(el).text();
10680
+ if (!DARK_MEDIA_RE.test(cssText)) return;
10681
+ let ast;
10682
+ try {
10683
+ ast = csstree4.parse(cssText);
10684
+ } catch (e) {
10685
+ found = true;
10686
+ return;
10687
+ }
10688
+ csstree4.walk(ast, {
10689
+ visit: "Atrule",
10690
+ enter(node) {
10691
+ if (node.type !== "Atrule" || node.name.toLowerCase() !== "media") return;
10692
+ if (!node.prelude || !DARK_MEDIA_RE.test(csstree4.generate(node.prelude))) return;
10693
+ found = true;
10694
+ if (!node.block) return;
10695
+ csstree4.walk(node.block, {
10696
+ visit: "Rule",
10697
+ enter(rule) {
10698
+ if (rule.type !== "Rule") return;
10699
+ block.rules++;
10700
+ let setsBackground = false;
10701
+ let important = false;
10702
+ rule.block.children.forEach((child) => {
10703
+ if (child.type !== "Declaration") return;
10704
+ const prop = child.property.toLowerCase();
10705
+ if (prop !== "background" && prop !== "background-color") return;
10706
+ setsBackground = true;
10707
+ if (child.important) important = true;
10708
+ });
10709
+ if (!setsBackground) return;
10710
+ const selector = csstree4.generate(rule.prelude).trim();
10711
+ if (!selector) return;
10712
+ block.any.push(selector);
10713
+ if (important) block.important.push(selector);
10714
+ }
10715
+ });
10716
+ }
10717
+ });
10718
+ });
10719
+ return found ? block : null;
10720
+ }
10721
+ function matchedElements($, selectors) {
10722
+ const matched = /* @__PURE__ */ new Set();
10723
+ for (const selector of selectors) {
10724
+ try {
10725
+ $(selector).each((_, el) => {
10726
+ matched.add(el);
10727
+ });
10728
+ } catch (e) {
10729
+ }
10730
+ }
10731
+ return matched;
10732
+ }
10733
+ function describeSelector($, el) {
10734
+ var _a;
10735
+ const $el = $(el);
10736
+ const tag = ((_a = el.tagName) == null ? void 0 : _a.toLowerCase()) || "element";
10737
+ const id = $el.attr("id");
10738
+ if (id) return `${tag}#${id}`;
10739
+ const cls = $el.attr("class");
10740
+ if (cls) return `${tag}.${cls.split(/\s+/)[0]}`;
10741
+ return tag;
10742
+ }
10743
+ function checkDarkModeFromDom($) {
10744
+ var _a, _b;
10745
+ const darkBlock = collectDarkBlocks($);
10746
+ if (!darkBlock) return [];
10747
+ const warnings = [];
10748
+ const hasOptIn = $("meta").toArray().some((el) => {
10749
+ const name = ($(el).attr("name") || "").trim().toLowerCase();
10750
+ return name === "color-scheme" || name === "supported-color-schemes";
10751
+ });
10752
+ if (!hasOptIn) {
10753
+ for (const clientId of PREFERS_COLOR_SCHEME_CLIENTS) {
10754
+ warnings.push({
10755
+ severity: "warning",
10756
+ client: clientId,
10757
+ property: "dark-mode-opt-in",
10758
+ message: `The email has @media (prefers-color-scheme: dark) styles but no dark-mode opt-in meta tag. ${(_b = (_a = getClient(clientId)) == null ? void 0 : _a.name) != null ? _b : clientId} may keep the email in light mode, so the dark styles never activate.`,
10759
+ suggestion: 'Add both opt-in tags to <head>: <meta name="color-scheme" content="light dark"> and <meta name="supported-color-schemes" content="light dark">.',
10760
+ fixType: "structural"
10761
+ });
10762
+ }
10763
+ }
10764
+ if (darkBlock.rules === 0) return warnings;
10765
+ const coveredByImportant = matchedElements($, darkBlock.important);
10766
+ const coveredByAny = matchedElements($, darkBlock.any);
10767
+ let uncovered = 0;
10768
+ $("[bgcolor], [style]").each((_, el) => {
10769
+ var _a2;
10770
+ if (uncovered >= MAX_UNCOVERED_ELEMENTS) return false;
10771
+ const $el = $(el);
10772
+ const style = parseInlineStyle($el.attr("style") || "");
10773
+ const inline = (_a2 = style.get("background-color")) != null ? _a2 : backgroundShorthandColor(style.get("background"));
10774
+ const color = inline != null ? inline : $el.attr("bgcolor");
10775
+ if (!color || !isLight(color)) return;
10776
+ if (inline ? coveredByImportant.has(el) : coveredByAny.has(el)) return;
10777
+ uncovered++;
10778
+ const selector = describeSelector($, el);
10779
+ for (const clientId of PREFERS_COLOR_SCHEME_CLIENTS) {
10780
+ warnings.push({
10781
+ severity: "warning",
10782
+ client: clientId,
10783
+ property: "dark-mode-coverage",
10784
+ message: `<${selector}> keeps its hardcoded light background (${color}) in dark mode \u2014 the dark block never overrides it. The rest of the email inverts around it, producing a half-inverted render (e.g. light text left on a still-white background).`,
10785
+ suggestion: inline ? `Override it inside @media (prefers-color-scheme: dark) with a dark background-color and !important (an inline style beats a plain rule), or move the colour onto a class the dark block already targets.` : `Override it inside @media (prefers-color-scheme: dark) with a dark background-color, or give the element a class the dark block already targets.`,
10786
+ fixType: "css",
10787
+ selector
10788
+ });
10789
+ }
10790
+ });
10791
+ return warnings;
10792
+ }
10793
+
10794
+ // src/analyze.ts
10795
+ var HTML_ELEMENT_SELECTORS = {
10796
+ "<style>": "style",
10797
+ "<link>": "link[rel='stylesheet']",
10798
+ "<svg>": "svg",
10799
+ "<video>": "video",
10800
+ "<form>": "form, input, button[type='submit']",
10801
+ "<audio>": "audio",
10802
+ "<picture>": "picture",
10803
+ "<dialog>": "dialog",
10804
+ "<meter>": "meter",
10805
+ "<progress>": "progress",
10806
+ "<select>": "select",
10807
+ "<textarea>": "textarea",
10808
+ "<marquee>": "marquee",
10809
+ "<object>": "object",
10810
+ "<base>": "base"
10811
+ };
10812
+ var HTML_ELEMENT_SEVERITY = {
10813
+ "<style>": "error",
10814
+ "<link>": "error",
10815
+ "<svg>": "error",
10816
+ "<form>": "error",
10817
+ "<video>": "warning",
10818
+ "<audio>": "warning",
10819
+ "<picture>": "warning",
10820
+ "<dialog>": "warning",
10821
+ "<marquee>": "warning",
10822
+ "<meter>": "warning",
10823
+ "<progress>": "warning",
10824
+ "<select>": "warning",
10825
+ "<textarea>": "warning",
10826
+ "<object>": "warning",
10827
+ "<base>": "warning"
10828
+ };
10829
+ var HTML_ELEMENT_MESSAGES = {
10830
+ "<style>": (n) => `${n} strips <style> blocks. Styles must be inlined.`,
10831
+ "<link>": (n) => `${n} does not support external stylesheets.`,
10832
+ "<svg>": (n) => `${n} does not support inline SVG.`,
10833
+ "<video>": (n) => `${n} does not support <video> elements.`,
10834
+ "<form>": (n) => `${n} strips form elements.`
10835
+ };
10836
+ var COMPOUND_DETECTORS = [
10837
+ { key: "display:flex", property: "display", valueIncludes: "flex" },
10838
+ { key: "display:grid", property: "display", valueIncludes: "grid" },
10839
+ { key: "display:none", property: "display", valueIncludes: "none" }
10840
+ ];
10841
+ var CSS_FUNCTION_DETECTORS = CSS_FUNCTION_FEATURES.map((fn) => ({
10842
+ key: fn,
10843
+ pattern: `${fn}(`
10844
+ // require opening paren — matches "min(" but not "Minion"
10845
+ }));
10846
+ function analyzeEmailFromDom($, framework) {
10847
+ const warnings = [];
10848
+ const seenWarnings = /* @__PURE__ */ new Set();
10849
+ function addWarning(w) {
10850
+ const key = `${w.client}:${w.property}:${w.severity}:${w.selector || ""}`;
10851
+ if (!seenWarnings.has(key)) {
10852
+ seenWarnings.add(key);
10853
+ warnings.push(w);
10854
+ }
10855
+ }
10856
+ function describeSelector2(el) {
10857
+ var _a;
10858
+ const $el = $(el);
10859
+ const tag = ((_a = el.tagName) == null ? void 0 : _a.toLowerCase()) || "";
10860
+ const cls = $el.attr("class");
10861
+ const id = $el.attr("id");
10862
+ if (id) return `${tag}#${id}`;
10863
+ if (cls) return `${tag}.${cls.split(/\s+/)[0]}`;
10864
+ const href = $el.attr("href");
10865
+ if (href) return `${tag}[href]`;
10866
+ return tag;
10867
+ }
10868
+ for (const feature of HTML_ELEMENT_FEATURES) {
10869
+ const selector = HTML_ELEMENT_SELECTORS[feature];
10870
+ if (!selector) continue;
10871
+ if ($(selector).length === 0) continue;
10872
+ const supportData = CSS_SUPPORT[feature];
10873
+ if (!supportData) continue;
10874
+ const baseSeverity = HTML_ELEMENT_SEVERITY[feature] || "warning";
10875
+ for (const client of EMAIL_CLIENTS) {
10876
+ const support = supportData[client.id];
10877
+ if (support === "unsupported") {
10878
+ const msgFn = HTML_ELEMENT_MESSAGES[feature];
10879
+ const message = msgFn ? msgFn(client.name) : `${client.name} does not support ${feature}.`;
10880
+ const sug = getSuggestion(feature, client.id, framework);
10881
+ const fix = getCodeFix(feature, client.id, framework);
10882
+ addWarning(__spreadValues({
10883
+ severity: baseSeverity,
10884
+ client: client.id,
10885
+ property: feature,
10886
+ message,
10887
+ suggestion: sug.text,
10888
+ fix,
10889
+ fixType: getFixType(feature)
10890
+ }, framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback(feature, client.id, framework)) ? { fixIsGenericFallback: true } : {}));
10891
+ } else if (support === "partial" && feature === "<style>") {
10892
+ const sug = getSuggestion("<style>:partial", client.id, framework);
10893
+ const fix = getCodeFix("<style>", client.id, framework);
10894
+ addWarning(__spreadValues({
10895
+ severity: "warning",
10896
+ client: client.id,
10897
+ property: "<style>",
10898
+ message: `${client.name} has partial <style> support (head only, with limitations). Inline styles recommended.`,
10899
+ suggestion: sug.text,
10900
+ fix,
10901
+ fixType: getFixType("<style>")
10902
+ }, framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback("<style>", client.id, framework)) ? { fixIsGenericFallback: true } : {}));
10903
+ }
10936
10904
  }
10937
- });
10905
+ }
10906
+ const parsedAtRules = /* @__PURE__ */ new Set();
10907
+ const parsedProperties = /* @__PURE__ */ new Set();
10908
+ const propertyLines = /* @__PURE__ */ new Map();
10909
+ const propertyValues = /* @__PURE__ */ new Map();
10910
+ const detectedCssFunctions = /* @__PURE__ */ new Set();
10911
+ const detectedPseudoClasses = /* @__PURE__ */ new Set();
10912
+ const detectedPseudoElements = /* @__PURE__ */ new Set();
10938
10913
  $("style").each((_, el) => {
10939
10914
  const cssText = $(el).text();
10940
10915
  try {
10941
- const ast = csstree4.parse(cssText, { parseCustomProperty: true });
10942
- let modified = false;
10943
- csstree4.walk(ast, {
10916
+ const ast = csstree5.parse(cssText, { parseCustomProperty: true, positions: true });
10917
+ csstree5.walk(ast, {
10944
10918
  enter(node) {
10945
- if (node.type !== "Declaration") return;
10946
- const prop = node.property.toLowerCase();
10947
- if (!COLOR_PROPS.has(prop) && prop !== "background") return;
10948
- const valueStr = csstree4.generate(node.value);
10949
- if (prop === "background") {
10950
- const bgColor = extractBackgroundColor(valueStr);
10951
- if (bgColor) {
10952
- const inverted = invertColor(bgColor, mode);
10953
- if (inverted) {
10954
- const newValue = valueStr.replace(bgColor, inverted);
10955
- node.value = csstree4.parse(newValue, { context: "value" });
10956
- modified = true;
10919
+ if (node.type === "Atrule") {
10920
+ parsedAtRules.add(`@${node.name}`);
10921
+ }
10922
+ if (node.type === "PseudoClassSelector") {
10923
+ detectedPseudoClasses.add(`:${node.name}`);
10924
+ }
10925
+ if (node.type === "PseudoElementSelector") {
10926
+ detectedPseudoElements.add(`::${node.name}`);
10927
+ }
10928
+ if (node.type === "Declaration") {
10929
+ const prop = node.property.toLowerCase();
10930
+ parsedProperties.add(prop);
10931
+ if (node.loc && !propertyLines.has(prop)) {
10932
+ propertyLines.set(prop, node.loc.start.line);
10933
+ }
10934
+ const valueStr = csstree5.generate(node.value);
10935
+ const seenValues = propertyValues.get(prop);
10936
+ if (seenValues) seenValues.push(valueStr);
10937
+ else propertyValues.set(prop, [valueStr]);
10938
+ for (const det of COMPOUND_DETECTORS) {
10939
+ if (prop === det.property && valueStr.includes(det.valueIncludes)) {
10940
+ parsedProperties.add(det.key);
10941
+ if (node.loc && !propertyLines.has(det.key)) {
10942
+ propertyLines.set(det.key, node.loc.start.line);
10943
+ }
10957
10944
  }
10958
10945
  }
10959
- } else {
10960
- const inverted = invertColor(valueStr, mode);
10961
- if (inverted) {
10962
- node.value = csstree4.parse(inverted, { context: "value" });
10963
- modified = true;
10946
+ for (const fn of CSS_FUNCTION_DETECTORS) {
10947
+ if (valueStr.includes(fn.pattern)) {
10948
+ detectedCssFunctions.add(fn.key);
10949
+ if (node.loc && !propertyLines.has(fn.key)) {
10950
+ propertyLines.set(fn.key, node.loc.start.line);
10951
+ }
10952
+ }
10964
10953
  }
10965
10954
  }
10966
10955
  }
10967
10956
  });
10968
- if (modified) {
10969
- $(el).text(csstree4.generate(ast));
10970
- }
10971
10957
  } catch (e) {
10972
10958
  }
10973
10959
  });
10974
- $("[bgcolor]").each((_, el) => {
10975
- const bgcolor = $(el).attr("bgcolor") || "";
10976
- const inverted = invertColor(bgcolor, mode);
10977
- if (inverted) {
10978
- $(el).attr("bgcolor", inverted);
10960
+ for (const atRule of AT_RULE_FEATURES) {
10961
+ if (!parsedAtRules.has(atRule)) continue;
10962
+ checkPropertySupport(atRule, addWarning, framework);
10963
+ }
10964
+ const cssPropertiesToCheck = Object.keys(CSS_SUPPORT).filter(
10965
+ (k) => !k.startsWith("<") && !k.startsWith("@")
10966
+ );
10967
+ $("[style]").each((_, el) => {
10968
+ var _a;
10969
+ const style = $(el).attr("style") || "";
10970
+ const props = parseStyleProperties(style);
10971
+ const selector = describeSelector2(el);
10972
+ for (const prop of props) {
10973
+ for (const det of COMPOUND_DETECTORS) {
10974
+ if (prop === det.property) {
10975
+ const value2 = getStyleValue(style, prop);
10976
+ if (value2 == null ? void 0 : value2.includes(det.valueIncludes)) {
10977
+ checkPropertySupport(det.key, addWarning, framework, selector);
10978
+ }
10979
+ }
10980
+ }
10981
+ if (cssPropertiesToCheck.includes(prop)) {
10982
+ checkPropertySupport(prop, addWarning, framework, selector, void 0, (_a = getStyleValue(style, prop)) != null ? _a : void 0);
10983
+ }
10984
+ const value = getStyleValue(style, prop);
10985
+ if (value) {
10986
+ for (const fn of CSS_FUNCTION_DETECTORS) {
10987
+ if (value.includes(fn.pattern)) {
10988
+ checkPropertySupport(fn.key, addWarning, framework, selector);
10989
+ }
10990
+ }
10991
+ }
10979
10992
  }
10980
10993
  });
10994
+ for (const prop of parsedProperties) {
10995
+ if (prop.includes(":")) continue;
10996
+ if (!cssPropertiesToCheck.includes(prop)) continue;
10997
+ const values = propertyValues.get(prop);
10998
+ checkPropertySupport(
10999
+ prop,
11000
+ addWarning,
11001
+ framework,
11002
+ void 0,
11003
+ propertyLines.get(prop),
11004
+ values ? values.join(" ") : void 0
11005
+ );
11006
+ }
11007
+ for (const compound of COMPOUND_VALUE_FEATURES) {
11008
+ if (compound.startsWith(":") || compound.startsWith("::")) continue;
11009
+ if (parsedProperties.has(compound)) {
11010
+ checkPropertySupport(compound, addWarning, framework, void 0, propertyLines.get(compound));
11011
+ }
11012
+ }
11013
+ for (const pseudo of detectedPseudoClasses) {
11014
+ if (CSS_SUPPORT[pseudo]) {
11015
+ checkPropertySupport(pseudo, addWarning, framework);
11016
+ }
11017
+ }
11018
+ for (const pseudo of detectedPseudoElements) {
11019
+ if (CSS_SUPPORT[pseudo]) {
11020
+ checkPropertySupport(pseudo, addWarning, framework);
11021
+ }
11022
+ }
11023
+ for (const fn of detectedCssFunctions) {
11024
+ checkPropertySupport(fn, addWarning, framework, void 0, propertyLines.get(fn));
11025
+ }
11026
+ for (const w of checkDarkModeFromDom($)) addWarning(w);
11027
+ const severityOrder = { error: 0, warning: 1, info: 2 };
11028
+ warnings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
11029
+ return warnings;
11030
+ }
11031
+ function analyzeEmail(html, framework) {
11032
+ if (!html || !html.trim()) {
11033
+ return [];
11034
+ }
11035
+ if (html.length > MAX_HTML_SIZE) {
11036
+ throw new Error(`HTML input exceeds ${MAX_HTML_SIZE / 1024}KB limit.`);
11037
+ }
11038
+ const $ = cheerio4.load(html);
11039
+ return analyzeEmailFromDom($, framework);
11040
+ }
11041
+ function getFixType(prop) {
11042
+ return STRUCTURAL_FIX_PROPERTIES.has(prop) ? "structural" : "css";
11043
+ }
11044
+ var VALUE_CAVEAT_PROPS = /* @__PURE__ */ new Set(["margin", "position", "overflow"]);
11045
+ var POSITION_KEYWORDS = ["relative", "absolute", "fixed", "sticky"];
11046
+ function valueTriggersCaveat(prop, value, notes) {
11047
+ const note = (notes != null ? notes : []).join(" ");
11048
+ const noteLc = note.toLowerCase();
11049
+ if (prop === "margin") {
11050
+ if (/(?:^|[\s:(])-\.?\d/.test(value) && noteLc.includes("negative")) return true;
11051
+ if (/\bauto\b/.test(value) && noteLc.includes("auto")) return true;
11052
+ return false;
11053
+ }
11054
+ if (prop === "position") {
11055
+ const used = POSITION_KEYWORDS.find((k) => new RegExp(`\\b${k}\\b`).test(value));
11056
+ if (!used) return false;
11057
+ const m = note.match(/supports\s+.+?\s+but not\s+([^.]+)/i);
11058
+ if (m) return m[1].toLowerCase().includes(used);
11059
+ return used === "fixed" || used === "sticky";
11060
+ }
11061
+ if (prop === "overflow") {
11062
+ if (!/\b(?:auto|scroll)\b/.test(value)) return false;
11063
+ return noteLc.includes("cannot scroll");
11064
+ }
11065
+ return true;
11066
+ }
11067
+ function noteSuffix(notes) {
11068
+ if (!(notes == null ? void 0 : notes.length)) return "";
11069
+ const cleaned = notes.map((n) => n.replace(/^(?:Partial|Buggy|Not supported)\.\s*/i, "").trim()).filter(Boolean);
11070
+ return cleaned.length ? ` ${cleaned.join(" ")}` : "";
11071
+ }
11072
+ function checkPropertySupport(prop, addWarning, framework, selector, line, value) {
11073
+ var _a;
11074
+ const supportData = CSS_SUPPORT[prop];
11075
+ if (!supportData) return;
11076
+ const fixType = getFixType(prop);
11077
+ const valueGated = VALUE_CAVEAT_PROPS.has(prop);
11078
+ for (const client of EMAIL_CLIENTS) {
11079
+ const support = supportData[client.id] || "unknown";
11080
+ const notes = (_a = CSS_SUPPORT_NOTES[prop]) == null ? void 0 : _a[client.id];
11081
+ if (support === "unsupported") {
11082
+ const sug = getSuggestion(prop, client.id, framework);
11083
+ const fix = getCodeFix(prop, client.id, framework);
11084
+ addWarning(__spreadValues(__spreadValues(__spreadValues({
11085
+ severity: "warning",
11086
+ client: client.id,
11087
+ property: prop,
11088
+ message: `${client.name} does not support "${prop}".${noteSuffix(notes)}`,
11089
+ suggestion: sug.text,
11090
+ fix,
11091
+ fixType
11092
+ }, selector ? { selector } : {}), line !== void 0 ? { line } : {}), framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback(prop, client.id, framework)) ? { fixIsGenericFallback: true } : {}));
11093
+ } else if (support === "partial") {
11094
+ if (valueGated && value !== void 0 && !valueTriggersCaveat(prop, value, notes)) continue;
11095
+ const sug = getSuggestion(prop, client.id, framework);
11096
+ const fix = getCodeFix(prop, client.id, framework);
11097
+ addWarning(__spreadValues(__spreadValues(__spreadValues({
11098
+ severity: "info",
11099
+ client: client.id,
11100
+ property: prop,
11101
+ message: `${client.name} has partial support for "${prop}".${noteSuffix(notes)}`,
11102
+ suggestion: sug.text,
11103
+ fix,
11104
+ fixType
11105
+ }, selector ? { selector } : {}), line !== void 0 ? { line } : {}), framework && (sug.isGenericFallback || fix && isCodeFixGenericFallback(prop, client.id, framework)) ? { fixIsGenericFallback: true } : {}));
11106
+ }
11107
+ }
11108
+ }
11109
+ function generateCompatibilityScore(warnings) {
11110
+ const result = {};
11111
+ for (const client of EMAIL_CLIENTS) {
11112
+ const clientWarnings = warnings.filter((w) => w.client === client.id);
11113
+ const errorProps = new Set(clientWarnings.filter((w) => w.severity === "error").map((w) => w.property));
11114
+ const warnProps = new Set(clientWarnings.filter((w) => w.severity === "warning").map((w) => w.property));
11115
+ const infoProps = new Set(clientWarnings.filter((w) => w.severity === "info").map((w) => w.property));
11116
+ const errors = errorProps.size;
11117
+ const warns = warnProps.size;
11118
+ const info = infoProps.size;
11119
+ const score = Math.max(0, Math.min(100, 100 - errors * 10 - warns * 3));
11120
+ result[client.id] = { score, errors, warnings: warns, info };
11121
+ }
11122
+ return result;
11123
+ }
11124
+ function warningsForClient(warnings, clientId) {
11125
+ return warnings.filter((w) => w.client === clientId);
11126
+ }
11127
+ function errorWarnings(warnings) {
11128
+ return warnings.filter((w) => w.severity === "error");
11129
+ }
11130
+ function structuralWarnings(warnings) {
11131
+ return warnings.filter((w) => w.fixType === "structural");
10981
11132
  }
10982
11133
 
10983
11134
  // src/diff.ts
@@ -12679,7 +12830,7 @@ function checkTemplateVariables(html) {
12679
12830
  }
12680
12831
 
12681
12832
  // src/overflow-checker.ts
12682
- import * as csstree5 from "css-tree";
12833
+ import * as csstree6 from "css-tree";
12683
12834
  function fixedPxWidth($el) {
12684
12835
  const style = $el.attr("style") || "";
12685
12836
  const styleMatch = style.match(/(?:^|[;\s])width\s*:\s*(\d+)px/i);
@@ -12716,11 +12867,11 @@ function checkOverflowFromDom($) {
12716
12867
  $("style").each((_, el) => {
12717
12868
  let ast;
12718
12869
  try {
12719
- ast = csstree5.parse($(el).text());
12870
+ ast = csstree6.parse($(el).text());
12720
12871
  } catch (e) {
12721
12872
  return;
12722
12873
  }
12723
- csstree5.walk(ast, {
12874
+ csstree6.walk(ast, {
12724
12875
  visit: "Rule",
12725
12876
  enter(node) {
12726
12877
  if (node.type !== "Rule") return;
@@ -12729,7 +12880,7 @@ function checkOverflowFromDom($) {
12729
12880
  node.block.children.forEach((child) => {
12730
12881
  if (child.type !== "Declaration") return;
12731
12882
  const prop = child.property.toLowerCase();
12732
- const val = csstree5.generate(child.value);
12883
+ const val = csstree6.generate(child.value);
12733
12884
  if (prop === "width") {
12734
12885
  const m = val.match(/^(\d+)px$/);
12735
12886
  if (m) widthPx = parseInt(m[1], 10);
@@ -12739,7 +12890,7 @@ function checkOverflowFromDom($) {
12739
12890
  }
12740
12891
  });
12741
12892
  if (widthPx !== null && widthPx > EMAIL_MAX_WIDTH && !fluid) {
12742
- const selector = csstree5.generate(node.prelude).trim().slice(0, 40);
12893
+ const selector = csstree6.generate(node.prelude).trim().slice(0, 40);
12743
12894
  addWidthIssue(widthPx, selector || "rule", issues, seen);
12744
12895
  }
12745
12896
  }
@@ -12773,7 +12924,7 @@ function checkOverflow(html) {
12773
12924
  }
12774
12925
 
12775
12926
  // src/visual-checker.ts
12776
- import * as csstree6 from "css-tree";
12927
+ import * as csstree7 from "css-tree";
12777
12928
  var CSS_WIDE_KEYWORDS = /* @__PURE__ */ new Set(["inherit", "initial", "unset", "revert", "revert-layer"]);
12778
12929
  var GRADIENT_RE = /(?:linear|radial|conic)-gradient\(/i;
12779
12930
  function isSolidColor(value) {
@@ -12860,7 +13011,7 @@ function ruleToMap(node) {
12860
13011
  const map = /* @__PURE__ */ new Map();
12861
13012
  node.block.children.forEach((child) => {
12862
13013
  if (child.type === "Declaration") {
12863
- map.set(child.property.toLowerCase(), csstree6.generate(child.value));
13014
+ map.set(child.property.toLowerCase(), csstree7.generate(child.value));
12864
13015
  }
12865
13016
  });
12866
13017
  return map;
@@ -12874,11 +13025,11 @@ function checkVisualFromDom($) {
12874
13025
  $("style").each((_, el) => {
12875
13026
  let ast;
12876
13027
  try {
12877
- ast = csstree6.parse($(el).text());
13028
+ ast = csstree7.parse($(el).text());
12878
13029
  } catch (e) {
12879
13030
  return;
12880
13031
  }
12881
- csstree6.walk(ast, {
13032
+ csstree7.walk(ast, {
12882
13033
  visit: "Rule",
12883
13034
  enter(node) {
12884
13035
  if (node.type === "Rule") inspectDeclarations(ruleToMap(node), issues, seen);
@@ -12962,7 +13113,7 @@ function toPlainText(html) {
12962
13113
  if (trimmed) lines.push(trimmed);
12963
13114
  currentLine = "";
12964
13115
  }
12965
- function walk7(node) {
13116
+ function walk8(node) {
12966
13117
  var _a;
12967
13118
  if (node.type === "text") {
12968
13119
  const text = node.data.replace(/\s+/g, " ");
@@ -13011,7 +13162,7 @@ function toPlainText(html) {
13011
13162
  currentLine = "- ";
13012
13163
  }
13013
13164
  for (const child of el.children) {
13014
- walk7(child);
13165
+ walk8(child);
13015
13166
  }
13016
13167
  if (isBlock) flushLine();
13017
13168
  }
@@ -13019,7 +13170,7 @@ function toPlainText(html) {
13019
13170
  const root = body.length ? body[0] : $.root()[0];
13020
13171
  if (root && "children" in root) {
13021
13172
  for (const child of root.children) {
13022
- walk7(child);
13173
+ walk8(child);
13023
13174
  }
13024
13175
  }
13025
13176
  flushLine();