lint

package
v1.0.49 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 8, 2026 License: MIT Imports: 6 Imported by: 0

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

View Source
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.

View Source
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

func EmptyReport(html string) Report

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

func Run(html string, opts Options) Report

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"
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL