Skip to content

Pug: classes not extracted after a text line with an unbalanced ( #20545

Description

@timmapme

What version of Tailwind CSS are you using?

v4.3.3 (@tailwindcss/oxide 4.3.3)

What build tool (or framework if it abstracts the build tool) are you using?

Vite 8 with @tailwindcss/vite, Vue 3 SFCs with <template lang="pug">; reproduced with the bare Oxide Scanner too.

Reproduction

broken.pug:

p
  | (
  span.underline.font-bold(title="x") km
  | )

working.pug (same, but the text lines contain no bracket):

p
  | x
  span.underline.font-bold(title="x") km
  | x
const {Scanner} = require('@tailwindcss/oxide')
for (const f of ['broken.pug', 'working.pug'])
  console.log(f, new Scanner({sources: [{base: __dirname, pattern: f, negated: false}]}).scan()
    .filter(c => ['underline', 'font-bold'].includes(c)))
broken.pug   []
working.pug  [ 'font-bold', 'underline' ]

Describe your issue

A Pug text line containing an unbalanced opening bracket (here | (, which renders a literal "(") stops the
class shorthand on the next tag from being extracted, so those utilities silently get no CSS. Pug itself
renders the template correctly. A real-world case is wrapping a button in parentheses: | (, then
v-btn.px-0.normal-case(...), then | ).

The cause appears to be the bracket tracking in crates/oxide/src/extractor/pre_processors/pug.rs: the
BracketStack is kept across the whole file, so the ( in the text line is pushed and never matched.
While the stack is non-empty, . is no longer replaced with a space, so span.underline.font-bold stays a
single, invalid candidate. The tag's own attribute parens don't help: its ) pops only its own (.

The same happens inside Vue SFCs (<template lang="pug">), where only some of the classes are extracted.

A comment line behaves the same way: //- a bare "(" here before the tag reproduces it too.

Workaround: write the bracket as an HTML entity (| &#40;) or keep brackets balanced on the text line.

Possible fix: reset the bracket stack at line boundaries unless the line is inside an attribute list
(Pug attribute lists may span lines, so a plain per-line reset is not enough), or skip piped text (| ...)
and comment (//, //-) lines in the pre-processor.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions