Skip to content

feat(codemod-html-defaults): codemod removing redundant native HTML attribute defaults - #294

Open
smndhm wants to merge 3 commits into
mainfrom
claude/codemod-package-9v595i
Open

feat(codemod-html-defaults): codemod removing redundant native HTML attribute defaults#294
smndhm wants to merge 3 commits into
mainfrom
claude/codemod-package-9v595i

Conversation

@smndhm

@smndhm smndhm commented Jul 18, 2026

Copy link
Copy Markdown
Owner

New package @dume/codemod-html-defaults: a CLI + library that removes native HTML attributes whose literal value is already the WHATWG spec default — e.g. <input type="text"><input>, <form method="get"><form>, <script type="text/javascript"><script>.

Usage

npx codemod-html-defaults src/                    # dry-run (default): per-attribute report, writes nothing
npx codemod-html-defaults --write src/            # apply
npx codemod-html-defaults --ext html,njk --write . # opt template files in when walking directories

Runs are idempotent, and edits are byte-preserving outside the removed attribute ranges (parse5 + source locations), so HTML-shaped templates like the Nunjucks files in apps/articles are safe.

Where the defaults come from (nothing from memory)

  • Primary source: html-enumerated-attributes (MIT, unified collective), which encodes the WHATWG missing value defaults as state equivalence classes. A value is removable only when it belongs to the same state group as the missing value default.
  • A small spec-linked supplementary table in src/defaults.ts for the non-enumerated cases (script[type], style[type], link[type]), cross-checked with html-minifier-terser's redundant-attribute handling.
  • Empirical verification in tests against jsdom's WHATWG reflection (input.type === 'text', form.method === 'get', button.type === 'submit', …).

Conditional defaults — never applied flatly

  • button[type="submit"]: never removed in v1, only reported (behavior depends on form association; explicit types are often kept for readability).
  • target="_self" on a/area/form: removed only in a full document with no <base target>; fragments are always skipped (a partial may be included under a layout with <base>); target on <base> itself is never touched.
  • link[type="text/css"]: removed only when the element's static rel contains the stylesheet token.

Never touched

Dynamic values ({{ }}, {% %}, <%= %>, ${}) and binding attribute names (:type, v-bind:, ng-*), boolean attributes and empty values, custom elements, foreign (SVG/MathML) content, and start tags containing duplicate attributes.

Verification

  • 73 tests, 96.7% statement coverage: removal matrix, dynamic/boolean guards, conditional cases (button in/out of form written before any rule), normalization (case, quotes, no trimming), idempotence, duplicate-attribute safety, CLI dry-run/write.
  • Functional no-op proven on form submission behavior (method/enctype/type/required/elements identical before and after in jsdom) — not just snapshots.
  • Dry-run on this repo's own apps/: 7 files scanned, 0 changes (repo already clean; type="module" correctly untouched).
  • pnpm lint, manypkg check and the package's vite build all pass. Note: slides#build fails in turbo on main too (marp-cli browser discovery, environment issue) — unrelated to this change.

A changeset is included for the initial 1.0.0 release.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QjF61pCbakF8bMW4XaaFeW


Generated by Claude Code

…ML attribute defaults

New package @dume/codemod-html-defaults: a CLI + library that removes
native HTML attributes whose literal value is already the WHATWG spec
default (<input type="text">, <form method="get">,
<script type="text/javascript">, ...).

- Defaults sourced from html-enumerated-attributes (WHATWG missing value
  defaults as state equivalence classes) plus a spec-linked supplementary
  table for non-enumerated cases (script/style/link type), cross-checked
  against jsdom's WHATWG reflection in tests.
- Contextual defaults are never applied flatly: target/_self requires a
  full document without <base target>, link[type] requires
  rel="stylesheet", and button[type="submit"] is only reported, never
  removed.
- Never touches dynamic bindings/interpolations, boolean attributes,
  empty values, custom elements, foreign (SVG) content, or start tags
  with duplicate attributes; edits are byte-preserving outside removed
  ranges so HTML-shaped templates (Nunjucks) are safe.
- Dry-run by default with a per-attribute report, --write to apply,
  idempotent; functional no-op on form submission behavior proven in
  tests, not just snapshots.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjF61pCbakF8bMW4XaaFeW
@changeset-bot

changeset-bot Bot commented Jul 18, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 7893919

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@dume/codemod-html-defaults Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

claude added 2 commits July 22, 2026 15:41
Adopt the conventional codemod test layout: each
tests/fixtures/<name>.input.<ext> is transformed and compared byte for
byte to its <name>.expected.<ext> sibling, with an idempotence check on
every expected file. Fixtures cover a full document, a form, the
<base target> conditional, a Nunjucks template, and a no-changes corpus
(booleans, dynamic values, SVG, custom elements, duplicate attributes).
Fixtures are excluded from prettier so the pairs stay byte-exact.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjF61pCbakF8bMW4XaaFeW
Add @dume/codemod-html-defaults/codemod, a default-exported
transform(root) -> string | null step matching the codemod:ast-grep
workflow contract, so the package can slot into an existing rollout
alongside other workflow scripts. It reuses the parse5 engine untouched,
returns null for untouched files, logs one line per changed file and
reports conditional skips as info lines.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QjF61pCbakF8bMW4XaaFeW
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants