Documentation
¶
Overview ¶
Package lint implements the mail-domain HTML lint lib used by `+lint-html` and the writing-path internals of the compose 5 shortcuts (`+send`, `+draft-create`, `+reply`, `+reply-all`, `+forward`) and `+draft-edit` body ops. The lib classifies HTML tags / attributes / inline styles into three tiers (pass / warn-and-autofix / error-delete) following the three-tier tag classification. `<style>` is passed through verbatim; `<script>` / `<iframe>` / external `<link>` / on*-handlers / `javascript:` URLs are removed outright.
The lib is deliberately decoupled from the cobra runtime so that it can be re-used as a pure-CPU pass before `bld.HTMLBody(...)` (compose 5) / `draftpkg.Apply(...)` (draft-edit) without taking a runtime dependency.
Index ¶
Constants ¶
const ( // Tag-level rules. RuleTagFontToSpan = "TAG_FONT_TO_SPAN" RuleTagCenterToDiv = "TAG_CENTER_TO_DIV" RuleTagMarqueeToText = "TAG_MARQUEE_TO_TEXT" RuleTagBlinkToText = "TAG_BLINK_TO_TEXT" RuleTagScriptBlocked = "TAG_SCRIPT_BLOCKED" RuleTagIframeBlocked = "TAG_IFRAME_BLOCKED" RuleTagObjectBlocked = "TAG_OBJECT_BLOCKED" RuleTagEmbedBlocked = "TAG_EMBED_BLOCKED" RuleTagFormBlocked = "TAG_FORM_BLOCKED" RuleTagInputBlocked = "TAG_INPUT_BLOCKED" RuleTagLinkBlocked = "TAG_LINK_BLOCKED" RuleTagMetaBlocked = "TAG_META_BLOCKED" RuleTagBaseBlocked = "TAG_BASE_BLOCKED" RuleTagUnknownStripped = "TAG_UNKNOWN_STRIPPED" // Attribute-level rules. RuleAttrEventHandlerBlocked = "ATTR_EVENT_HANDLER_BLOCKED" RuleAttrJSURLBlocked = "ATTR_JS_URL_BLOCKED" RuleAttrUnsafeSchemeBlocked = "ATTR_UNSAFE_SCHEME_BLOCKED" // Style-level rules. RuleStylePropertyDropped = "STYLE_PROPERTY_DROPPED" // Feishu-native autofix rules. These autofix the inline style / // class / nesting shape of common elements so AI-authored HTML // matches what Feishu mail-editor itself emits, fixing the visual // "extra blank line between blocks", "list bullets/numbers missing", // "link color wrong" etc. classes of issues. The rewrite is purely // additive — user-supplied inline styles take precedence; the lib // only fills the missing properties. RuleStyleListNative = "STYLE_LIST_NATIVE_INLINE_APPLIED" RuleStyleListItemNative = "STYLE_LIST_ITEM_NATIVE_INLINE_APPLIED" RuleStyleBlockquoteNative = "STYLE_BLOCKQUOTE_NATIVE_INLINE_APPLIED" RuleStyleLinkNative = "STYLE_LINK_NATIVE_INLINE_APPLIED" RuleStyleParaWrapper = "STYLE_PARA_WRAPPER_REWRITTEN" // RuleListDirectChildNonLI fires when a <ul> or <ol> has a non-<li> // element child (e.g. nested <ul><ul>). HTML spec requires list children // to be <li>; browsers silently hoist the nested list out and the visual // nesting falls apart. The lib autofixes by wrapping the offending child // in a synthetic <li>. RuleListDirectChildNonLI = "LIST_DIRECT_CHILD_NON_LI" )
Rule IDs surfaced through Finding.RuleID. UPPER_SNAKE_CASE naming is the contract for the stdout envelope. New rules MUST keep this naming convention so AI / test consumers can pattern-match reliably.
const MaxExcerptBytes = 200
MaxExcerptBytes caps the raw-HTML excerpt embedded in a Finding.Excerpt so a single offending tag with megabyte content can't bloat the envelope JSON. Lint operates on bytes only, but the excerpt representation must not be size-amplifying.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Finding ¶
type Finding struct {
RuleID string `json:"rule_id"`
Severity Severity `json:"severity"`
TagOrAttr string `json:"tag_or_attr"`
Excerpt string `json:"excerpt"`
Hint string `json:"hint"`
}
Finding describes a single lint observation. The stdout-envelope shape is: rule_id / severity / tag_or_attr / excerpt / hint, all UTF-8 strings.
type Options ¶
type Options struct{}
Options control a single Run invocation. The lib always autofixes warnings and removes errors — there is no opt-out (`--no-lint` is not provided). The struct is retained for forward compatibility but currently exposes no behavioural switches.
type Report ¶
type Report struct {
// Applied surfaces warning-tier findings that the lib rewrote in place
// (e.g. <font> -> <span style>). Each entry corresponds to a single rule
// firing on a single tag / attribute / style property.
Applied []Finding `json:"lint_applied"`
// Blocked surfaces error-tier findings that the lib removed
// unconditionally (writing-path safety floor: <script> / on* /
// javascript: URLs always go).
Blocked []Finding `json:"original_blocked"`
// CleanedHTML is the rewritten HTML produced by Run (warnings rewritten
// + errors deleted). When the input is plain text (bodyIsHTML == false)
// the field equals the input verbatim.
CleanedHTML string `json:"cleaned_html,omitempty"`
// HasErrorFindings reports whether any SeverityError finding was emitted.
HasErrorFindings bool `json:"-"`
// HasWarningFindings reports whether any SeverityWarning finding was emitted.
HasWarningFindings bool `json:"-"`
}
Report is the structured output of a single Run invocation.
Both Applied and Blocked are always non-nil slices (possibly empty). The stdout envelope contract requires `lint_applied` and `original_blocked` to always be present arrays — the JSON encoder must render `[]` rather than `null` so AI / test consumers can rely on `data.lint_applied[]` / `data.original_blocked[]` unconditionally.
func EmptyReport ¶
EmptyReport returns a Report with the contract-required empty (non-nil) arrays and CleanedHTML equal to the input. Compose 5 / +draft-edit call this when the body is plain-text or empty so the stdout envelope's `lint_applied` / `original_blocked` fields are always present arrays.
func Run ¶
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), callers should short-circuit with EmptyReport(html) instead of paying the parse cost. Run still handles this gracefully — html.Parse on plain text wraps the input in <html><head></head><body>...</body></html>, and the lib's pass-through rendering will reproduce the original text — but the round-trip is wasteful and produces no findings.
type Severity ¶
type Severity string
Severity denotes the severity of a lint finding.
const ( // SeverityWarning is emitted for tags / attrs / styles that have a // safe Feishu-native replacement (e.g. <font> -> <span style>). The // lib always applies the replacement and surfaces the finding in // `Applied` — unsafe tags are removed at lint time and the rewrite is // not opt-out. SeverityWarning Severity = "warning" // SeverityError is emitted for tags / attrs / styles that would cause // obvious rendering / safety issues (<script>, <iframe>, on*-handlers, // javascript:/vbscript: URLs, ...) and may be stripped or cause // obvious rendering issues downstream. The lib always removes these to // match the writing-path safety contract. SeverityError Severity = "error" )