eslint-plugin-wc

ESLint rules that catch Web Components anti-patterns before they ship.

Tool
npm
v3.1.0
113stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
62/100Good
Development Activity64
Maintenance36
Community68
Maturity60
Momentum20

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
77/100Good
Architecture78
Code Quality88
Innovation68
Learning Curve75

eslint-plugin-wc adds static-analysis rules for authoring native Web Components and custom elements, covering both plain HTMLElement subclasses and framework-flavored variants like Lit’s @customElement decorator. It flags issues that are easy to miss by eye but break at runtime: invalid or reserved custom element tag names, unsafe use of attachShadow outside the constructor, closed shadow roots, DOM traversal inside lifecycle callbacks before the element is connected, missing super() calls, and event listeners registered without a matching teardown.

The plugin ships both a legacy .eslintrc-style config and an ESLint 9 flat config, plus a recommended and a stricter best-practice preset, so teams can adopt it with a single extends line or hand-pick individual rules. A shared wc.elementBaseClasses setting lets every rule recognize custom base classes (beyond HTMLElement and Lit’s LitElement) so detection stays consistent across the whole rule set.

What You Get

  • 21 rules covering element naming, shadow DOM safety, lifecycle-callback correctness, and listener cleanup
  • Both legacy .eslintrc and ESLint 9 flat-config exports (recommended and best-practice in each format)
  • A shared elementBaseClasses setting so custom base classes (e.g. Lit’s LitElement) are recognized by every rule uniformly
  • Per-rule schema options for finer control, such as required tag-name prefixes/suffixes or namespace restrictions
  • Detailed per-rule documentation under docs/rules/ with rule rationale and pass/fail code examples

Common Use Cases

  • Enforcing valid, spec-compliant custom element tag names across a design-system or component library
  • Catching attachShadow calls made outside the constructor, which throw at runtime in strict implementations
  • Preventing DOM traversal inside attributeChangedCallback/connectedCallback before the element is guaranteed connected
  • Flagging event listeners added without a corresponding disconnectedCallback teardown to avoid memory leaks
  • Adopting a house style for element definitions (constructor shape, class/tag naming symmetry, file-naming conventions)

Under The Hood

Architecture Each rule is a self-contained ESLint Rule.RuleModule (a meta block plus a create visitor) living under src/rules/, registered by name in a central rules map in src/index.ts alongside four exported config shapes: two legacy string-based configs (src/configs/legacy-recommended.ts, legacy-best-practice.ts) and two flat-config factories (src/configs/recommended.ts, best-practice.ts) that take the built plugin object and return a Linter.FlatConfig. Shared detection logic lives in src/util.ts and src/util/{ast,customElements,dom,tag-names,text}.ts — most notably isCustomElement(), which recognizes a class as a custom-element definition via its superclass name, a @customElement-style JSDoc comment, or a decorator, and memoizes the result per-node in a WeakMap cache — so every rule reuses the same detection primitive instead of re-deriving it. Adding a new rule is purely additive: a file under src/rules/, an entry in the rules map, and optionally a slot in one of the four config exports.

Tech Stack Written in strict-mode TypeScript (noImplicitAny, strictNullChecks, noUnusedLocals, target es2021, module/moduleResolution set to node16) and compiled via tsc into lib/. Runtime dependencies are minimal and purpose-built: is-valid-element-name validates custom-element tag names against the HTML spec, and js-levenshtein-esm powers the fuzzy-matching behind the no-typos rule. It depends on ESLint’s own Rule/ESLint/Linter types directly (peer dependency eslint >=8.40.0) rather than wrapping them. Dev tooling includes ESLint 9 self-linting via typescript-eslint’s strict preset, Prettier for formatting, and Mocha/Chai/c8 for tests with HTML, lcov, and text-summary coverage reporting.

Code Quality The rule set has broad test coverage: a dedicated *_test.ts file per rule under src/test/rules/, plus tests for the shared utilities and for each config export. CI (GitHub Actions) runs lint and test across a Node 20/22/25 matrix on every push and pull request. TypeScript strict mode is enforced throughout, including noUnusedLocals, noUnusedParameters, and noImplicitReturns. Naming is consistent end to end — kebab-case rule IDs matching kebab-case rule filenames, camelCase internal identifiers. Error handling is minimal by design, appropriate for AST-analysis code with no I/O; the one intentional swallow (probing whether an optional module like lit is installed) is documented inline rather than silently discarded.

API Design The plugin’s core technical insight is that useful Web Components lint checks require recognizing “is this class a custom element” across multiple authoring styles — a plain HTMLElement subclass, Lit’s @customElement decorator, or JSDoc-annotated legacy code — rather than assuming one framework. isCustomElement() and getElementBaseClasses() unify that detection and are reused by nearly every rule. The public surface stays low-friction for consumers: two shareable presets (recommended, best-practice) in both legacy and flat-config form mean most teams need only a single extends line, with an escape hatch of per-rule schema options (required tag-name prefixes/suffixes, namespace restrictions) and one shared elementBaseClasses setting so custom base classes are recognized consistently across the whole rule set instead of being configured per rule.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search