Run lints the given HTML body and returns a structured Report. Report.CleanedHTML contains the rewritten HTML (warnings rewritten + errors deleted) — the autofix is unconditional. IMPORTANT: when the input is empty or plain-text (no HTML markup detected by the cli's existing `bodyIsHTML` heuristic)
(html string, opts Options)
| 31 | // rendering will reproduce the original text — but the round-trip is wasteful |
| 32 | // and produces no findings. |
| 33 | func Run(html string, opts Options) Report { |
| 34 | if html == "" { |
| 35 | return EmptyReport("") |
| 36 | } |
| 37 | |
| 38 | rep := Report{ |
| 39 | Applied: []Finding{}, |
| 40 | Blocked: []Finding{}, |
| 41 | } |
| 42 | |
| 43 | // We use html.ParseFragment so users authoring fragment-style snippets |
| 44 | // (the canonical compose-5 input shape — `<div>...</div>` rather than a |
| 45 | // full document) don't get implicit <html><head><body> wrappers |
| 46 | // re-rendered. The "body" insertion mode matches what html.Parse would |
| 47 | // have done internally for a fragment but skips the structural wrappers |
| 48 | // at render time. |
| 49 | bodyContext := &xhtml.Node{Type: xhtml.ElementNode, DataAtom: atom.Body, Data: "body"} |
| 50 | nodes, err := xhtml.ParseFragment(strings.NewReader(html), bodyContext) |
| 51 | if err != nil { |
| 52 | // Parser failure is exceptional (the parser is permissive by design); |
| 53 | // fall back to the original input so we don't lose user content. |
| 54 | return EmptyReport(html) |
| 55 | } |
| 56 | |
| 57 | // Wrap fragment nodes in a synthetic root so the recursive walker has a |
| 58 | // uniform parent pointer to mutate. |
| 59 | root := &xhtml.Node{Type: xhtml.DocumentNode} |
| 60 | for _, n := range nodes { |
| 61 | root.AppendChild(n) |
| 62 | } |
| 63 | |
| 64 | walk(root, &rep) |
| 65 | // nativeCtx tracks per-Run() state so positional ids (e.g. data-ol-id) |
| 66 | // are deterministic across multiple Run() calls on the same input — |
| 67 | // keying off the document-traversal order rather than heap pointers, |
| 68 | // so cleaned_html is byte-stable and amenable to golden-file tests / CI |
| 69 | // diff / cache-key reuse. |
| 70 | nctx := &nativeCtx{olIDs: map[*xhtml.Node]string{}} |
| 71 | applyFeishuNativeStyles(root, &rep, nctx) |
| 72 | |
| 73 | rep.HasErrorFindings = len(rep.Blocked) > 0 |
| 74 | rep.HasWarningFindings = len(rep.Applied) > 0 |
| 75 | rep.CleanedHTML = renderFragment(root) |
| 76 | |
| 77 | return rep |
| 78 | } |
| 79 | |
| 80 | // walk visits every element node under parent, applying tag/attr/style |
| 81 | // classification. Children are iterated via the next-sibling pointer because |