Can JS plugins lint .css files? (exposing a CSS AST to plugins)
#22926
Replies: 1 comment 1 reply
No. we have no plans to ship a core provided CSS AST + CSS linting for JS plugins
See previous comment, we have no plans to suport this.
This doesn't relaly fix into milestone 3. Please look a #21936 which will enable support (via JS plugins) for CSS file linting.
CSS rules will not be added to oxlint core. As an aside, the your original post reads like it was purely generated by AI, please try avoiding submitting issues that are soely AI as adds additional time to review and process what the AI has said - thanks. |
Uh oh!
There was an error while loading. Please reload this page.
Context
I maintain
oxlint-tailwindcss, a native oxlintplugin (built on
@oxlint/plugins/createOnce) with 23 rules for Tailwind CSS v4. It's used inproduction by projects including Expo's docs and currently sees ~124k npm downloads/month. I'm
opening this as a discussion first — per the contributing guidelines — rather than a PR, to align on
direction before anyone writes code.
This is deliberately scoped as a design question, not a feature drop.
The limitation
Today the JS plugin API only exposes JS/TS/JSX AST nodes. A plugin can lint Tailwind classes
wherever they appear in JavaScript (
className,cn(),tv(), tagged templates, …), but it isblind to the
.cssfile where the design system is actually defined.In Tailwind v4 the stylesheet is the configuration surface:
@theme,@utility,@custom-variant,@apply,@import "tailwindcss". Real things our users want flagged, and that we cannot flagtoday because we never see the CSS:
@applyreferencing a class that doesn't exist in the design system@custom-variant/@variant@themetokens defined but never used (dead tokens)@plugin/@config/@sourcepaths that don't resolve@import "tailwindcss"As a concrete example, the first rule I'd ship is
no-unknown-apply: flag@apply btn-primrywhenbtn-primryisn't a real utility in the loaded design system — the CSS-side mirror of theno-unknown-classesrule we already run on JS. Today we simply can't see that line.So we can lint half the Tailwind surface (classes in JS) but not the other half (the stylesheet
that defines them). The ecosystem already crosses into CSS for Tailwind — oxfmt sorts classes inside
@apply(#19801) — so the boundary is not theoretical.What I'm asking
One concrete thing: could the JS plugin API expose a CSS AST (with spans), so a plugin can
register visitors over
.cssfiles and emit precise diagnostics — reusing oxc's native Rustparser rather than each plugin shipping its own? The infrastructure prototyped in #21177
(
full_source_text, partial-loader for.css) looks like most of what a plugin would need; it wasclosed on process grounds, not on the idea, so I'm raising the direction here first.
The reason I'm asking for plugin access specifically — and not for Tailwind rules in the core — is
that domain semantics don't belong in core. Validating
@applyagainst a real design system needs@tailwindcss/node, v4 semantics, and Tailwind's version; baking that in would couple the core to afast-moving userland dependency. That's the "use JS plugins" guidance from #22537, and I agree with
it. This mirrors how JS already works: the core parses and exposes the AST, plugins add domain
semantics. I'm asking for the same shape for CSS.
(Whether oxc also wants its own language-level CSS correctness rules — duplicate properties, empty
blocks, etc. — is a separate, bigger question I'm happy to leave entirely to you. The plugin-access
ask above stands on its own regardless of how you answer it.)
Why core-provided, not
languageOptions.parserI know
languageOptions.parser(#20373) is on the table, and in the ESLint world that's how CSSgets linted (a JS parser like
css-tree). For oxc specifically I'd argue the native path isstronger: the core already has a fast CSS parser, so handing plugins that AST avoids every plugin
re-shipping a JS parser — keeping the "native, no translation layer" advantage that makes oxlint
plugins worth writing in the first place.
languageOptions.parsercould remain the escape hatch;the native AST would be the fast path.
Open questions for maintainers
infrastructure the right foundation, or was it closed for reasons beyond process?
LintPlugins::CSS-style flag, a visitor key, …), andwhere would
.cssfile routing live (the partial-loader extension)?at all, or stay JS/TS-only? Not what I'm here to push — just flagging it's adjacent.
Happy to write up a more detailed design, prototype a single proof-of-concept rule against whatever
API shape you prefer, or pair on this. I just want to start from a direction you're comfortable
with.
All reactions