eslint-plugin-wc
ESLint rules that catch Web Components anti-patterns before they ship.
Repository Health
Technical Analysis
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
.eslintrcand ESLint 9 flat-config exports (recommendedandbest-practicein each format) - A shared
elementBaseClassessetting so custom base classes (e.g. Lit’sLitElement) 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
attachShadowcalls made outside the constructor, which throw at runtime in strict implementations - Preventing DOM traversal inside
attributeChangedCallback/connectedCallbackbefore the element is guaranteed connected - Flagging event listeners added without a corresponding
disconnectedCallbackteardown 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.
Used by 3 apps in this directory
authentik
Authentication · Security
The self-hosted Identity Provider that replaces Okta, Auth0, and Entra ID with a unified SSO platform supporting SAML, OAuth2/OIDC, LDAP, RADIUS, and WebAuthn.
Gitea
Devops · Developer Tools · Project Management
Self-hosted DevOps in a single Go binary — Git hosting, GitHub Actions-compatible CI/CD, and 30+ package registries without any SaaS dependency.
Umbraco CMS
CMS
The friendly open-source .NET CMS that gives developers full control over content, structure, and delivery without lock-in.