84 Commits

Author SHA1 Message Date
Robin Malfait
88a8234c8a
Mark .hdr and .exr as binary extensions (#18734)
This PR fixes an issue where `.hdr` files were scanned for candidates
even though it's a binary file.

As a workaround today, you could use:

```css
@source not "**/*.hdr";
```

To ignore `.hdr` files, but let's bake it in by default instead.

Fixes: #18733
2025-08-14 14:32:30 +02:00
Hugo Parente Lima
298ad08653
Add support for Slang template (#18565)
## Summary

Slang is basically a Slim template language for Crystal language, so the
very same Slim parser works fine.

Slim template: https://github.com/slim-template/slim
Slang template: https://github.com/jeromegn/slang

## Test plan

Create a simple slang file with some tailwind-css and check if the CSS
is being extracted:
```slim
doctype html
html
  head
    title This is a title
  body.min-h-screen
    header.stick.top-0.z-10
      section.max-w-4xl.mx-auto.p-4.flex.items-center.justify-between
        h1.text-3xl.font-medium This is a slang file
``` 
To test it, get any slim template, rename the extension to .slang 

Fixes #17851

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-07-17 14:10:19 -04:00
Jordan Pittman
8c8033017d
Detect classes inside Elixir charlist, word list, and string sigils (#18432)
Fixes #18431

The `~W(…)` syntax in Elixir is a _sigil_. It's basically another way to
write strings, arrays of strings, etc… The syntax lets you use a handful
of surrounding brackets like `~W(…)`, `~W[…]`, `~W{…}`, `~W"…"`, etc… to
let you write lists without necessarily having to escape characters.

In v3 our extractor was able to pick these up but in v4 Oxide does not.
I've added a preprocessor for Elixir files so we can modify the code
before our main extractor sees it.

Now things like this: `~W(text-white bg-gray-600)` will get turned into
` ~W text-white bg-gray-600 ` which can easily be processed by our
extractor.

The sigils we support are:
- `~s` / `~S` (strings)
- `~w` / `~W` (word lists)
- `~c` / `~C` (charlists)

We're specifically detecting the use of `(…)`, `[…]`, and `{…}` as using
quotes already works today.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-07-02 16:01:13 -04:00
Jordan Pittman
98158fd372 Fix failing test 2025-06-30 12:23:56 -04:00
Henrik Eneroth
05b65d59b5
Fix trailing ) from interfering with extraction in Clojure keywords (#18345)
## Summary

In a form like, 

```clojure 
(if condition :bg-white :bg-black)
```

`:bg-black` will fail to extract, while `:bg-white` is extracted as
expected. This PR fixes this case, implements more comprehensive
candidate filtering, and supersedes a previous PR.

Having recently submitted a PR for handling another special case with
Clojure keywords (the presence of `:` inside of keywords), I thought it
best to invert the previous strategy: Instead of handling special cases
one by one, consume keywords according to the Clojure reader spec.
Consume nothing else, other than strings.

Because of this, this PR is a tad more invasive rather than additive,
for which I apologize. The strategy is this:
- Strings begin with a `"` and ends with an unescaped `"`. Consume
everything between these delimiters (existing case).
- Keywords begin with `:`, and end with whitespace, or one out of a
small set of specific reserved characters. Everything else is a valid
character in a keyword. Consume everything between these delimiters, and
apply the class splitting previously contained in the outer loop. My
previous special case handling of `:` inside of keywords in #18338 is
now redundant (and is removed), as this is a more general solution.
- Discard _everything else_. 

I'm hoping that a strategy that is based on Clojure's definition of
strings and keywords will pre-empt any further issues with edge cases.

Closes #18344.

## Test plan
- Added failing tests.
- `cargo test` -> failure
- Added fix
- `cargo test` -> success

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-06-30 12:23:38 -04:00
Henrik Eneroth
f4a7eea6cc
Extract candidates with variants in Clojure/ClojureScript keywords (#18338)
## Summary

Taking a shot at fixing my own complaint from #18336. 

Added failing test and fix for extracting classes containing a
pseudo-class from Clojure keywords. Eg., `:hover:text` ->
`"hover:text"`. This would previously produce `["hover", "text"]`.

## Test plan

- Add a failing test. 
- Verify that it fails with `cargo test`. 
- Add fix. 
- Verify that `cargo test` has no further complaints. 

ATT: @RobinMalfait

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-06-18 12:38:27 -04:00
Jordan Pittman
58a6ad0e28
Ignore .pnpm-store in Oxide (#18163)
Fixes #18148
2025-05-27 13:02:10 -04:00
Robin Malfait
1d4c263c73
Fix Haml pre-processing crash when there is no \n at the end of the file (#18155)
This PR fixes a Haml pre-processing issue where a crash occurs if there
is no trailing `\n` at the end of the file and the code before it was
considered Ruby code.

This happens in situations where Ruby code was used. E.g.:

```
- index = 0
- index += 1
```

In this situation when we see the `-` on the second line, then we will
find the whole indented block and parse it as Ruby code instead. The
block ands at the last `\n`, but since we reach the end of the file,
there is no `\n`. Right now we incorrectly reset the internal cursor to
the last known `\n` position. This means that the `start` of the Ruby
block will be _after_ the `end` of the Ruby block and the Haml parser
will crash.

To solve this, once we reach the end of the file, we don't reset the
cursor to the wrong position.

Fixes:
https://github.com/tailwindlabs/tailwindcss/issues/17379#issuecomment-2910108646

## Test plan

1. Added a regression test that did fail before the fix, and doesn't
anymore
2025-05-26 18:52:44 +02:00
Álvaro Mondéjar Rubio
ed3cecd39d
Support Leptos class: attributes (#18093)
<!--

👋 Hey, thanks for your interest in contributing to Tailwind!

**Please ask first before starting work on any significant new
features.**

It's never a fun experience to have your pull request declined after
investing a lot of time and effort into a new feature. To avoid this
from happening, we request that contributors create a discussion to
first discuss any significant new features.

For more info, check out the contributing guide:


https://github.com/tailwindcss/tailwindcss/blob/main/.github/CONTRIBUTING.md

-->

Fixes #18092

## Summary

Using the Svelte preprocessor for Rust files we can support Leptos
`class:` attributes syntax.

## Test plan

```
pnpm t
```

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-05-23 10:15:33 +00:00
Robin Malfait
bf591febf3
Fix missing extracted classes containing . in Clojure (#18038)
This PR fixes an issue in the Clojure pre-processor where candidates
including `.` characters were not extracted correctly.

The solution here is to only replace the `.` with a ` ` when the `.` is
not surrounded by numbers. This means that:


```
:.foo.bar
```
Becomes
```
: foo bar
```

But
```
:.gap-1.5.flex
```

Becomes
```
: gap-1.5 flex
```

This way the `gap-1.5` is correctly extracted.

## Test plan

1. Added a test for this case
2. Tested this in the extractor tool as well. Notice how the `gap-1.5`
is correctly extracted here.

<img width="1247" alt="image"
src="https://github.com/user-attachments/assets/f5dd2600-5c5e-4ad8-88af-4e5be44340f5"
/>


Fixes: 17760
2025-05-15 14:50:03 +02:00
Robin Malfait
737994b7aa
Allow _ before numbers during candidate extraction (#17961)
This PR fixes a bug where a class like `header_1` wasn't properly
extracted because we didn't allow an `_` before a number.

This PR fixes that by allowing an `_` before a number.

Fixes: #17960


## Test plan

1. Added a test to verify this works
2. Existing tests work

Used the visualizer tool for this to verify that the `header_1` class is
being extracted:
<img width="1816" alt="image"
src="https://github.com/user-attachments/assets/fdc21602-0e2b-4e4e-92a1-19c4f4f5393f"
/>
2025-05-09 16:29:28 +00:00
Robin Malfait
2f6679abfe
Print sources when DEBUG=* is set (#17952)
This PR improves the debug logging by also adding the provided `@source`
to the log file. It will also print the optimized sources (the ones we
expand and mark as `Auto` or `Pattern`).


In the logs, this will look like this:

```
2025-05-09T11:03:32.906478Z  INFO tailwindcss_oxide::scanner: Provided sources:
2025-05-09T11:03:32.906544Z  INFO tailwindcss_oxide::scanner: Source: PublicSourceEntry { base: "/Users/robin/github.com/RobinMalfait/spreadsheet", pattern: "**/*", negated: false }
2025-05-09T11:03:32.906589Z  INFO tailwindcss_oxide::scanner: Optimized sources:
2025-05-09T11:03:32.906595Z  INFO tailwindcss_oxide::scanner: Source: Auto { base: "/Users/robin/github.com/RobinMalfait/spreadsheet" }
```

Or if you have more sources:

```
2025-05-09T11:06:54.767546Z  INFO tailwindcss_oxide::scanner: Provided sources:
2025-05-09T11:06:54.767660Z  INFO tailwindcss_oxide::scanner: Source: PublicSourceEntry { base: "/Users/robin/github.com/RobinMalfait/spreadsheet", pattern: "**/*", negated: false }
2025-05-09T11:06:54.767987Z  INFO tailwindcss_oxide::scanner: Source: PublicSourceEntry { base: "/Users/robin/github.com/RobinMalfait/spreadsheet/app", pattern: "./routes/*.{jsx,tsx}", negated: false }
2025-05-09T11:06:54.767992Z  INFO tailwindcss_oxide::scanner: Source: PublicSourceEntry { base: "/Users/robin/github.com/RobinMalfait/spreadsheet/app", pattern: "./utils/*.ts", negated: false }
2025-05-09T11:06:54.768450Z  INFO tailwindcss_oxide::scanner: Optimized sources:
2025-05-09T11:06:54.768455Z  INFO tailwindcss_oxide::scanner: Source: Auto { base: "/Users/robin/github.com/RobinMalfait/spreadsheet" }
2025-05-09T11:06:54.768459Z  INFO tailwindcss_oxide::scanner: Source: Pattern { base: "/Users/robin/github.com/RobinMalfait/spreadsheet/app/routes", pattern: "*.jsx" }
2025-05-09T11:06:54.768462Z  INFO tailwindcss_oxide::scanner: Source: Pattern { base: "/Users/robin/github.com/RobinMalfait/spreadsheet/app/routes", pattern: "*.tsx" }
2025-05-09T11:06:54.768466Z  INFO tailwindcss_oxide::scanner: Source: Pattern { base: "/Users/robin/github.com/RobinMalfait/spreadsheet/app/utils", pattern: "*.ts" }
```
2025-05-09 13:39:17 +02:00
Philipp Spiess
179e5ddd7c
Add more folders to the list of ignored content dirs (#17892)
Closes #15452

This PR adds more directories to the list of ignored content dirs. These
are now handled the same as `node_modules`:

- Contents of this directory are ignored by default to avoid scanning
dependency trees
- Explicit `@import`s inside these folders are now treated as
_external_, bypassing any `.gitignore` files.

The new extensions are:

- Version control systems: `.hg`, `.svn`
- Bundler caches: `.venv`, `venv`, `.yarn`
- Framework caches: `.next`, `.turbo`, `.parcel-cache`, `__pycache__`,
`.svelte-kit`

## Test plan

Verified with the repro of #15452 by renaming the ignored directory to
`.venv` and installing a local tarball:

<img width="1283" alt="Screenshot 2025-05-06 at 13 14 55"
src="https://github.com/user-attachments/assets/3265acbb-e121-47b3-ac6c-e174770f8234"
/>
2025-05-08 11:27:11 +02:00
Robin Malfait
d8c4df8001
Write to log file when using DEBUG=* (#17906)
This PR improves the debugability of the scanner when using `DEBUG=*` by
writing to a `tailwindcss-{pid}.log` file in the current working
directory.

It will include all the tracing information from the scanner. This PR
also introduces `Discovering {path}` and `Reading {path}` logs.

- `Discovering {path}` — this is logged when we are traversing the file
system looking for files. We use the `ignore` crate, and log this
information in the `filter_entry` callback. If a file was already
ignored by `.gitignore` files, this won't show up, but it also means
that we will not read it.
- `Reading {path}` — this is when we are actually reading the file so we
can start extracting potential Tailwind CSS classes.

These will give you some insights in what paths are being scanned, and
if we get stuck, where we get stuck.

Also, we are appending to the file. In the log below, you can already
see that a `tailwindcss-<number>.log` file exists already even though it
didn't exist before running the command. This should make it easier to
debug if we get stuck on a specific file/folder because the file will be
populated with information.

There are a few reasons for appending to a file:
1. There is a lot of output, so spamming the stdout/stderr is not ideal
2. If you run the same command again, after changing your `@source`
directives, you could diff the outputs. (although, the timestamps will
be different)
3. When using `DEBUG=*`, a lot of other tools also output debug
information, so writing to a file should make this better.

<details>

<summary>Example log</summary>

```log
2025-05-06T23:13:45.912292Z  INFO scan_sources: tailwindcss_oxide::scanner: enter
2025-05-06T23:13:45.912697Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/out.css"
2025-05-06T23:13:45.912716Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/tailwindcss-61347.log"
2025-05-06T23:13:45.912748Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app"
2025-05-06T23:13:45.912786Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/test"
2025-05-06T23:13:45.912814Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/test/utils.ts"
2025-05-06T23:13:45.912851Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/utils"
2025-05-06T23:13:45.912873Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/utils/flatten.ts"
2025-05-06T23:13:45.912884Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/utils/matrix.ts"
2025-05-06T23:13:45.912893Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/utils/default-map.ts"
2025-05-06T23:13:45.912904Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/tailwind.css"
2025-05-06T23:13:45.912914Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/root.tsx"
2025-05-06T23:13:45.912936Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain"
2025-05-06T23:13:45.912962Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/expression.ts"
2025-05-06T23:13:45.912972Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/walk-ast.ts"
2025-05-06T23:13:45.912995Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature"
2025-05-06T23:13:45.913019Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/parser.test.ts"
2025-05-06T23:13:45.913029Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/validate.test.ts"
2025-05-06T23:13:45.913039Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/tokenizer.ts"
2025-05-06T23:13:45.913048Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/validate.ts"
2025-05-06T23:13:45.913058Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/parser.ts"
2025-05-06T23:13:45.913067Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/tokenizer.test.ts"
2025-05-06T23:13:45.913077Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/ast.ts"
2025-05-06T23:13:45.913086Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/version-control.ts"
2025-05-06T23:13:45.913095Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/tokenizer.ts"
2025-05-06T23:13:45.913105Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/type-checker.test.ts"
2025-05-06T23:13:45.913121Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/evaluation-result.ts"
2025-05-06T23:13:45.913505Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/tmp.test.ts"
2025-05-06T23:13:45.913514Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/version-control.test.ts"
2025-05-06T23:13:45.913523Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/tokenizer.test.ts"
2025-05-06T23:13:45.913531Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/evaluation.ts"
2025-05-06T23:13:45.913554Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions"
2025-05-06T23:13:45.913583Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/sequence.test.ts"
2025-05-06T23:13:45.913592Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/date.ts"
2025-05-06T23:13:45.913601Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/lookup.test.ts"
2025-05-06T23:13:45.913613Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/statistics.ts"
2025-05-06T23:13:45.913622Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/intrinsics.test.ts"
2025-05-06T23:13:45.913631Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/math.ts"
2025-05-06T23:13:45.913640Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/engineering.ts"
2025-05-06T23:13:45.913648Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/types.test.ts"
2025-05-06T23:13:45.913656Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/text.ts"
2025-05-06T23:13:45.913665Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/text.test.ts"
2025-05-06T23:13:45.913673Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/intrinsics.ts"
2025-05-06T23:13:45.913681Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/logic.ts"
2025-05-06T23:13:45.913689Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/date.test.ts"
2025-05-06T23:13:45.913697Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/types.ts"
2025-05-06T23:13:45.913705Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/logic.test.ts"
2025-05-06T23:13:45.913713Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/lookup.ts"
2025-05-06T23:13:45.913720Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/sequence.ts"
2025-05-06T23:13:45.913728Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/engineering.test.ts"
2025-05-06T23:13:45.913741Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/index.ts"
2025-05-06T23:13:45.913749Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/math.test.ts"
2025-05-06T23:13:45.913757Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/statistics.test.ts"
2025-05-06T23:13:45.913783Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/__snapshots__"
2025-05-06T23:13:45.913817Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/spreadsheet.test.ts"
2025-05-06T23:13:45.913826Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/type-checker.ts"
2025-05-06T23:13:45.913833Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/function-utils.ts"
2025-05-06T23:13:45.913841Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/spreadsheet.ts"
2025-05-06T23:13:45.913849Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/expression.test.ts"
2025-05-06T23:13:45.913857Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/evaluation.test.ts"
2025-05-06T23:13:45.913879Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/routes"
2025-05-06T23:13:45.913896Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/app/routes/_index.tsx"
2025-05-06T23:13:45.914172Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/README.md"
2025-05-06T23:13:45.914197Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/public"
2025-05-06T23:13:45.914228Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/public/fonts"
2025-05-06T23:13:45.914268Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/package.json"
2025-05-06T23:13:45.914289Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/scripts"
2025-05-06T23:13:45.914310Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/scripts/generate-documentation.ts"
2025-05-06T23:13:45.914332Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/.github"
2025-05-06T23:13:45.914383Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/.github/workflows"
2025-05-06T23:13:45.914410Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/.github/workflows/ci.yml"
2025-05-06T23:13:45.914420Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/tsconfig.json"
2025-05-06T23:13:45.914455Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/vite.config.ts"
2025-05-06T23:13:45.914486Z  INFO scan_sources: tailwindcss_oxide::scanner: Discovering "/Users/robin/github.com/RobinMalfait/spreadsheet/biome.json"
2025-05-06T23:13:45.914512Z  INFO scan_sources: tailwindcss_oxide::scanner: exit
2025-05-06T23:13:45.914515Z  INFO extract_candidates: tailwindcss_oxide::scanner: enter
2025-05-06T23:13:45.914518Z  INFO extract_candidates:read_all_files: tailwindcss_oxide::scanner: enter
2025-05-06T23:13:45.914524Z  INFO extract_candidates:read_all_files: tailwindcss_oxide::scanner: Reading 58 file(s)
2025-05-06T23:13:45.914808Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/tailwindcss-61347.log"
2025-05-06T23:13:45.914990Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/routes/_index.tsx"
2025-05-06T23:13:45.915138Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/.github/workflows/ci.yml"
2025-05-06T23:13:45.915140Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/statistics.test.ts"
2025-05-06T23:13:45.915145Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/tokenizer.ts"
2025-05-06T23:13:45.915163Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/tsconfig.json"
2025-05-06T23:13:45.915153Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/engineering.ts"
2025-05-06T23:13:45.915226Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/utils/matrix.ts"
2025-05-06T23:13:45.915229Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/walk-ast.ts"
2025-05-06T23:13:45.915372Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/test/utils.ts"
2025-05-06T23:13:45.915578Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/ast.ts"
2025-05-06T23:13:45.915599Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/validate.ts"
2025-05-06T23:13:45.915637Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/version-control.ts"
2025-05-06T23:13:45.915647Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/parser.ts"
2025-05-06T23:13:45.915657Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/vite.config.ts"
2025-05-06T23:13:45.915680Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/types.test.ts"
2025-05-06T23:13:45.915681Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/utils/flatten.ts"
2025-05-06T23:13:45.915701Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/parser.test.ts"
2025-05-06T23:13:45.915706Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/README.md"
2025-05-06T23:13:45.915691Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/utils/default-map.ts"
2025-05-06T23:13:45.915701Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/spreadsheet.test.ts"
2025-05-06T23:13:45.915730Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/package.json"
2025-05-06T23:13:45.915753Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/tokenizer.ts"
2025-05-06T23:13:45.915731Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/biome.json"
2025-05-06T23:13:45.915787Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/validate.test.ts"
2025-05-06T23:13:45.915822Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/signature/tokenizer.test.ts"
2025-05-06T23:13:45.915826Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/text.ts"
2025-05-06T23:13:45.915885Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/root.tsx"
2025-05-06T23:13:45.915885Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/types.ts"
2025-05-06T23:13:45.915886Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/type-checker.ts"
2025-05-06T23:13:45.915990Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/type-checker.test.ts"
2025-05-06T23:13:45.915995Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/text.test.ts"
2025-05-06T23:13:45.915998Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/logic.ts"
2025-05-06T23:13:45.916000Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/intrinsics.ts"
2025-05-06T23:13:45.916024Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/sequence.ts"
2025-05-06T23:13:45.916056Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/function-utils.ts"
2025-05-06T23:13:45.916058Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/scripts/generate-documentation.ts"
2025-05-06T23:13:45.916070Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/expression.ts"
2025-05-06T23:13:45.916075Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/logic.test.ts"
2025-05-06T23:13:45.916063Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/date.test.ts"
2025-05-06T23:13:45.916119Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/evaluation-result.ts"
2025-05-06T23:13:45.916120Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/index.ts"
2025-05-06T23:13:45.916148Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/expression.test.ts"
2025-05-06T23:13:45.916152Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/engineering.test.ts"
2025-05-06T23:13:45.916193Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/lookup.ts"
2025-05-06T23:13:45.916219Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/tmp.test.ts"
2025-05-06T23:13:45.916228Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/tokenizer.test.ts"
2025-05-06T23:13:45.916245Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/evaluation.test.ts"
2025-05-06T23:13:45.916256Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/spreadsheet.ts"
2025-05-06T23:13:45.916253Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/lookup.test.ts"
2025-05-06T23:13:45.916267Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/sequence.test.ts"
2025-05-06T23:13:45.916287Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/math.test.ts"
2025-05-06T23:13:45.916286Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/version-control.test.ts"
2025-05-06T23:13:45.916317Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/intrinsics.test.ts"
2025-05-06T23:13:45.916323Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/evaluation.ts"
2025-05-06T23:13:45.916354Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/statistics.ts"
2025-05-06T23:13:45.916562Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/date.ts"
2025-05-06T23:13:45.916609Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/domain/functions/math.ts"
2025-05-06T23:13:45.916676Z  INFO extract_candidates:read_all_files: tailwindcss_oxide::scanner: exit
2025-05-06T23:13:45.916682Z  INFO extract_candidates:parse_all_blobs: tailwindcss_oxide::scanner: enter
2025-05-06T23:13:45.916688Z  INFO extract_candidates:parse_all_blobs:extract: tailwindcss_oxide::scanner: enter
2025-05-06T23:13:45.918271Z  INFO extract_candidates:parse_all_blobs:extract: tailwindcss_oxide::scanner: exit
2025-05-06T23:13:45.918282Z  INFO extract_candidates:parse_all_blobs: tailwindcss_oxide::scanner: exit
2025-05-06T23:13:45.918286Z  INFO extract_candidates:read_all_files: tailwindcss_oxide::scanner: enter
2025-05-06T23:13:45.918288Z  INFO extract_candidates:read_all_files: tailwindcss_oxide::scanner: Reading 2 file(s)
2025-05-06T23:13:45.918315Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/app/tailwind.css"
2025-05-06T23:13:45.918504Z  INFO tailwindcss_oxide::scanner: Reading "/Users/robin/github.com/RobinMalfait/spreadsheet/out.css"
2025-05-06T23:13:45.918512Z  INFO extract_candidates:read_all_files: tailwindcss_oxide::scanner: exit
2025-05-06T23:13:45.918519Z  INFO extract_candidates:extract_css_variables: tailwindcss_oxide::scanner: enter
2025-05-06T23:13:45.918522Z  INFO extract_candidates:extract_css_variables:extract: tailwindcss_oxide::scanner: enter
2025-05-06T23:13:45.918635Z  INFO extract_candidates:extract_css_variables:extract: tailwindcss_oxide::scanner: exit
2025-05-06T23:13:45.918640Z  INFO extract_candidates:extract_css_variables: tailwindcss_oxide::scanner: exit
2025-05-06T23:13:45.919059Z  INFO extract_candidates: tailwindcss_oxide::scanner: exit
```

</details>

We also output where we are writing the file to. This looks like this
when using the CLI:
<img width="1462" alt="image"
src="https://github.com/user-attachments/assets/79c2cc95-adea-4bbd-a4f1-101de45726f5"
/>

Last but not least, this also ignores `.log` files by default

## Test plan

Ran the CLI (but you can use any tool real, since this is implemented in
Oxide) with the `DEBUG=*` flag.
The file generated, looks like the example I shared above.
2025-05-07 10:19:54 -04:00
Robin Malfait
5c5ae04db6
Fix Windows CI build (#17878)
This PR fixes a Windows CI issue due to the recently merged #17846

There might be better ways to solve this, but essentially we want to
make sure we are always dealing with `\n` and not `\r\n`. This PR fixes
that by replacing all `\r\n` with `\n` in the tests.
2025-05-05 17:38:53 +02:00
Robin Malfait
d38554d110
Fix HAML extraction with embedded Ruby (#17846)
This PR fixes and improves the HAML extractor by ensuring that whenever
we detect lines or blocks of Ruby code (`-`, `=` and `~`) characters at
the beginning of the line, we treat them as Ruby code.

Fixes: #17813

## Test Plan

1. Existing tests pass
2. Changed 1 existing test which embedded Ruby syntax
3. Added a dedicated test to ensure the HAML file in the linked issue is
parsed correctly

Running this in the internal extractor tool you can see that the
`w-[12px]`, `w-[16px]`, `h-[12px]`, and `h-[16px]` are properly
extracted.

Note: the `mr-12px` is also extracted, but not highlighted because this
is not a valid Tailwind CSS class.

<img width="1816" alt="image"
src="https://github.com/user-attachments/assets/fc5929ca-bc71-47d2-b21b-7abeec86f54d"
/>
2025-05-05 10:26:17 -04:00
Robin Malfait
25ec6a3b7d
Ignore .db files by default (#17711)
This PR ignores `.db` files by default. We already ignored `.sqlite` and
`.sqlite3` files but we didn't ignore `.db` files which is a common
extension for database files as well.

Due to the binary nature of `.db` files, scanning these could result in
hard to debug errors such as:


![image](https://github.com/user-attachments/assets/52f779ac-cd5f-4f37-9615-2163bf852999)
2025-04-22 14:53:11 +00:00
Adam Wathan
8feb6a758a
Ignore .geojson files by default (#17700)
Resolves https://github.com/tailwindlabs/tailwindcss/issues/17699

GeoJSON files are giant JSON files for geographic data structures and
will never contain Tailwind classes, but because they are often huge
they will slow down the build a lot if scanned.

---------

Co-authored-by: Adam Wathan <4323180+adamwathan@users.noreply.github.com>
2025-04-18 10:46:33 +02:00
Robin Malfait
bbd916aaa0
Ignore binary extensions, except in folder names (#17595)
We generate a glob to ignore binary extensions that looks something like
this:
```
*.{mp4,pages,exe,…}
```

However, if you have a folder that happens to end in `.pages` for
example, then this folder will be ignored in its entirety.

To solve this, we added a new flag to the `Gitignore` struct so we can
register a bunch of ignore rules that _only_ apply to paths and not
folders.

Fixes: #17569

## Test plan

- Added a unit test

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-04-12 13:30:17 +02:00
Robin Malfait
7d317251f2
Handle Ruby %w syntax in Slim pre processing (#17557)
This PR fixes an issue where the Ruby `%w[…]` syntax causes utilities to
not be properly extracted.

This PR will now ensure that it does get extracted correctly.

Given this input:
```slim
div[
  class=%w[bg-blue-500 w-10 h-10]
]
div[
  class=%w[w-10 bg-green-500 h-10]
]
```

Before this PR, we extracted everything but the `bg-blue-500`. The
`w-10` was extracted but not because of the second div, but because of
the first one.

Fixes: #17542

## Test plan

1. Added a test to ensure it's working correctly.

Looking at the extractor tool, you can see that it now gets extracted
correctly. Top is before, bottom is with this change.

<img width="1199" alt="image"
src="https://github.com/user-attachments/assets/028d9abd-8917-438c-a423-88ba887b7f97"
/>
2025-04-04 17:21:35 +02:00
Philipp Spiess
4200a1ecc4
Fix slow incremental builds (especially on Windows) (#17511)
This PR fixes slow rebuilds on Windows where rebuilds go from ~2s to
~20ms.

Fixes: #16911
Fixes: #17522

## Test plan

1. Tested it on a reproduction with the following results:

Before:


https://github.com/user-attachments/assets/10c5e9e0-3c41-4e1d-95f6-ee8d856577ef

After:


https://github.com/user-attachments/assets/2c7597e9-3fff-4922-a2da-a8d06eab9047

Zooming in on the times, it looks like this:
<img width="674" alt="image"
src="https://github.com/user-attachments/assets/85eee69c-bbf6-4c28-8ce3-6dcdad74be9c"
/>

But with these changes:
<img width="719" alt="image"
src="https://github.com/user-attachments/assets/d89cefda-0711-4f84-bfaf-2bea11977bf7"
/>

We also tested this on Windows with the following results:
Before:
<img width="961" alt="image"
src="https://github.com/user-attachments/assets/3a42f822-f103-4598-9a91-e659ae09800c"
/>

After:
<img width="956" alt="image"
src="https://github.com/user-attachments/assets/05b6b6bc-d107-40d1-a207-3638aba3fc3a"
/>


[ci-all]

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-04-03 17:13:15 +02:00
Robin Malfait
53801091a0
Watch CSS module files for changes (#17467)
This PR is a follow-up PR for:
https://github.com/tailwindlabs/tailwindcss/pull/17433

In the other PR we allow scanning CSS files for extracting usages of CSS
variables. This is important for `.module.css` files that reference
these variables but aren't in the same big AST of the main CSS file.

This PR also makes sure to watch for changes in those registered CSS
files and re-extract the variables when they change.

This PR took a bit longer than expected because I was trying to make
sure that writing to `./dist/out.css` works without infinite-looping
(e.g.: we had issues with this in Tailwind CSS v3 with webpack).

But I couldn't reproduce the issue at all. I did had some code that
tried to detect if the CSS file contained license headers and skip in
(because then it's very likely an output CSS file) but even without it
the tests were fine.

I setup integration tests with `@tailwindcss/cli` itself, and with tools
that use webpack. Added a test for Next.js, and a dedicated webpack test
as well.

Even without tests, locally, I couldn't reproduce an infinite loop due
to changes in an output CSS file...

Eventually dropped the code that tries to detect output CSS files.

One thing to keep in mind is that if you change any of your "main" CSS
files, then we will trigger a full rebuild anyway, so this change is
only required for unrelated CSS files (like CSS module files) that use
CSS variables.

## Test plan

1. Added integration tests for the CLI and Next.js
2. Added new dedicated test for webpack
2025-03-31 18:44:06 +02:00
Robin Malfait
2af7c57983
Extract used CSS variables from .css files (#17433)
This PR fixes an issue where CSS variables could be used in CSS modules,
but where never emitted in your final CSS.

Some backstory, when Tailwind CSS v4 came out, we _always_ emitted all
CSS variables whether they were used or not.

Later, we added an optimization where we only emit the CSS variables
that were actually used. The definition of "used" in this case is:

1. Used in your CSS  file(s) — (we check the final CSS AST for this)
2. Used _somewhere_ in any of your source files (e.g.: a JavaScript file
accessing a variable)

The issue this PR tries to solve is with the very first point. If you
are using CSS modules, then every CSS file is processed separately. This
is not a choice Tailwind CSS made, but how other build tooling works
(like Vite for example).

To prevent emitting all of Tailwind's Preflight reset and all utilities
per CSS file, you can use the `@reference` directive instead of
repeating `@import "tailwindcss";`. This is explained here:
https://tailwindcss.com/docs/compatibility#explicit-context-sharing

But now we are just _referencing_ them, not emitting them. And since the
CSS module is not connected in any way to the main `index.css` file that
contains the `@import "tailwindcss";` directive, we don't even see the
CSS variables while processing the `index.css` file. (or wherever your
main CSS file is)

This is where point 2 from above comes in. This is a situation where we
rely on the extractor to find the used CSS variables so we can
internally mark them as used.

To finally get to the point of this PR, the extractor only scans
`.html`, `.js`, ... files but not `.css` files. So all the CSS variables
used inside of CSS modules will not be generated.

This PR changes that behavior to also scan `.css` files. But _only_ for
CSS variables (not any other type of class candidate). This is
important, otherwise all your custom `@utility foo {}` definitions would
always mark `foo` as a used class and include it in the CSS which is not
always the case.

On top extracting CSS variables, we will also make sure that the CSS
variables we find are in usage positions (e.g.: `var(--color-red-500)`)
and not in definition positions (e.g.: `--color-red-500: #ff0000;`).
This is important because we only want to emit the variables that are
actually used in the final CSS output.

One future improvement not implemented here, is that technically we will
also extract CSS variables that might not be used if defined in a
`@utility`.

```css
@utility never-used {
  color: var(--color-red-500); /* --color-red-500 will be emitted, even if it might not be used */
}
```

Fixes: #16904
Fixes: #17429

# Test plan

1. Added a test where CSS variables are defined in `.css` files (and
ignored)
2. Added a test where CSS variables are used in `.css` files (and
included)

Testing on the reproduction defined in #16904, the `.module.css` file
contains a reference to `var(--color-hot-pink)`, but generating a build
shows that the variable definition is not available:

<img width="1630" alt="image"
src="https://github.com/user-attachments/assets/a0d5c37e-6813-4cd5-a677-6c356b5a73d4"
/>

When you run the build again with the changes from this PR, then we _do_
see the definition of the `--color-hot-pink` in the root CSS file:
<img width="2876" alt="image"
src="https://github.com/user-attachments/assets/beab7c11-a31b-4ea4-8235-4849a8e92859"
/>
2025-03-28 17:53:15 +01:00
Robin Malfait
601f369374
Extract special @("@")md:… syntax in Razor files (#17427)
This PR fixes an extraction issue in Razor files where `@@md:bg-red-500`
can't always be extracted properly. We already convert `@@md:bg-red-500`
to ` @md:bg-red-500` but in certain situations Razor will emit the
double `@@` to the DOM.

A workaround in Razor land would be to write `@("@")md:bg-red-500`
instead. See: https://github.com/dotnet/aspnetcore/issues/38595 But then
we don't extract the `@md:bg-red-500` properly anymore.

This is where this PR comes in, essentially we will pre process the
Razor contents and apply the following replacement internally:

```diff
- @("@")md:bg-red-500
+      @md:bg-red-500
```
Notice that the `)` looks like it's replaced with `@`. This will have a
small side effect later when we get to the testing part.

But this way we properly see the `@md:bg-red-500` class during class
extraction.

> [!WARNING]
> There is technically a bug here because of the replacement with `@`,
because if you now run the `npx @tailwindcss/upgrade@latest` tool, then
we would replace `)md:bg-red-500` if changes are required, not the
`@("@")md:bg-red-500` part. We can try to fix that in this PR but it
seems unlikely that we will actually run into this issue realistically
speaking. I think fixing the actual extraction here is much more
important than the upgrade tooling that could fail _if_ we ever have to
migrate `@md:…` to something else.

Fixes: #17424

## Test plan

1. Added a test to verify the fix
2. Existing tests pass
3. Verified this in the extractor tool, but it looks a tiny bit funky.

Typically we remove characters by replacing it with a space ` `. But
this time, we replace it with some spaces _and_ an `@` character that
didn't exist at that specific position. If you look at the diff above,
you will notice that `)` was replaced with `@`.

That's why in the extractor tool it is highlighted that it could extract
it, but it's just funny looking because it highlights `)md:bg-red-500`

<img width="1816" alt="image"
src="https://github.com/user-attachments/assets/57e6a3ac-bfd5-4cad-a1ce-0039b4d7d9b5"
/>
2025-03-28 14:37:54 +01:00
Robin Malfait
eecb6f7476
A bit of cleanup (#17394)
Was working on another issue and noticed that I wanted these but they
aren't related to any of the issues. So opening a separate PR to do some
internal cleanup.
2025-03-26 12:08:53 -04:00
Robin Malfait
b0c48c3de9
Fix symlink issues when resolving @source directives (#17391)
This PR fixes some issues related to symlinks when using them in the
`@source` directive.

Fixes: #16765
Fixes: #16038

## Test plan

1. Added tests to prove this works
   - Added a recursive symlink test as well to make sure we don't hang
2. Existing tests still pass


[ci-all]
2025-03-26 08:11:55 -04:00
Jordan Pittman
3dcd615b02
Fix Ruby files causing the CLI to hang (#17383)
Fixes #17379

The preprocessor we added to detect embedded languages uses a back
reference and given a long enough file with certain byte / character
patterns it'll cause what appears to be an indefinite hang (might just
be catastrophically exponential backtracking but not sure)

This replaces the one regex w/ back references with two, anchored,
multi-line regexes

Now we search for all the starting & ending delimiters in the file. We
then loop over all the starting delimiters, find the paired ending one,
and preprocess the content inside

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-03-26 11:00:10 +01:00
Robin Malfait
1ef97759e3
Add @source not support (#17255)
This PR adds a new source detection feature: `@source not "…"`. It can
be used to exclude files specifically from your source configuration
without having to think about creating a rule that matches all but the
requested file:

```css
@import "tailwindcss";
@source not "../src/my-tailwind-js-plugin.js";
```

While working on this feature, we noticed that there are multiple places
with different heuristics we used to scan the file system. These are:

- Auto source detection (so the default configuration or an `@source
"./my-dir"`)
- Custom sources ( e.g. `@source "./**/*.bin"` — these contain file
extensions)
- The code to detect updates on the file system

Because of the different heuristics, we were able to construct failing
cases (e.g. when you create a new file into `my-dir` that would be
thrown out by auto-source detection, it'd would actually be scanned). We
were also leaving a lot of performance on the table as the file system
is traversed multiple times for certain problems.

To resolve these issues, we're now unifying all of these systems into
one `ignore` crate walker setup. We also implemented features like
auto-source-detection and the `not` flag as additional _gitignore_ rules
only, avoid the need for a lot of custom code needed to make decisions.

High level, this is what happens after the now:

- We collect all non-negative `@source` rules into a list of _roots_
(that is the source directory for this rule) and optional _globs_ (that
is the actual rules for files in this file). For custom sources (i.e
with a custom `glob`), we add an allowlist rule to the gitignore setup,
so that we can be sure these files are always included.
- For every negative `@source` rule, we create respective ignore rules.
- Furthermore we have a custom filter that ensures files are only read
if they have been changed since the last time they were read.

So, consider the following setup:

```css
/* packages/web/src/index.css */
@import "tailwindcss";
@source "../../lib/ui/**/*.bin";
@source not "../../lib/ui/expensive.bin";
```

This creates a git ignore file that (simplified) looks like this:

```gitignore
# Auto-source rules
*.{exe,node,bin,…}
*.{css,scss,sass,…}
{node_modules,git}/

# Custom sources can overwrite auto-source rules
!lib/ui/**/*.bin

# Negative rules
lib/ui/expensive.bin
```

We then use this information _on top of your existing `.gitignore`
setup_ to resolve files (i.e so if your `.gitignore` contains rules e.g.
`dist/` this line is going to be added _before_ any of the rules lined
out in the example above. This allows negative rules to allow-list your
`.gitignore` rules.

To implement this, we're rely on the `ignore` crate but we had to make
various changes, very specific, to it so we decided to fork the crate.
All changes are prefixed with a `// CHANGED:` block but here are the
most-important ones:

- We added a way to add custom ignore rules that _extend_ (rather than
overwrite) your existing `.gitignore` rules
- We updated the order in which files are resolved and made it so that
more-specific files can allow-list more generic ignore rules.
- We resolved various issues related to adding more than one base path
to the traversal and ensured it works consistent for Linux, macOS, and
Windows.

## Behavioral changes

1. Any custom glob defined via `@source` now wins over your `.gitignore`
file and the auto-content rules.
   - Resolves #16920
3. The `node_modules` and `.git` folders as well as the `.gitignore`
file are now ignored by default (but can be overridden by an explicit
`@source` rule).
   - Resolves #17318
   - Resolves #15882
4. Source paths into ignored-by-default folders (like `node_modules`)
now also win over your `.gitignore` configuration and auto-content
rules.
    -  Resolves #16669
5. Introduced `@source not "…"` to negate any previous rules.
   - Resolves #17058
6. Negative `content` rules in your legacy JavaScript configuration
(e.g. `content: ['!./src']`) now work with v4.
   - Resolves #15943 
7. The order of `@source` definitions matter now, because you can
technically include or negate previous rules. This is similar to your
`.gitingore` file.
9. Rebuilds in watch mode now take the `@source` configuration into
account
   - Resolves #15684

## Combining with other features

Note that the `not` flag is also already compatible with [`@source
inline(…)`](https://github.com/tailwindlabs/tailwindcss/pull/17147)
added in an earlier commit:

```css
@import "tailwindcss";
@source not inline("container");
```

## Test plan

- We added a bunch of oxide unit tests to ensure that the right files
are scanned
- We updated the existing integration tests with new `@source not "…"`
specific examples and updated the existing tests to match the subtle
behavior changes
- We also added a new special tag `[ci-all]` that, when added to the
description of a PR, causes the PR to run unit and integration tests on
all operating systems.

[ci-all]

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-03-25 15:54:41 +01:00
Robin Malfait
711b9cd0db
Pre process Slim templates embedded in Ruby files (#17336)
This PR fixes an issue where embedded Slim templates inside of Ruby
files are not pre processed because we pre process based on a file
extension.

This PR also handles embedded SLIM templates using the following syntax:

```rb
slim_template <<~SLIM
  .flex
    .flex-1
      h1 Hello World
    .flex-1
      p This is a test
SLIM
```

~~As far as I can tell, this is only a Slim template thing and not a
Haml template thing but I could be wrong here. See:
https://viewcomponent.org/guide/templates.html#interpolations~~

The ViewComponent package handles anything that looks like
`{lang}_template`, so the lang here will be used as the pre processing
language for now.

Fixes: #17334

# Test plan

1. Added test for this
2. Existing tests pass
3. Made sure that the snippet from the issue works as expected:

Added an example where we have a `slim_template` and a `svelte_template`
to prove that it embeds based on the language. I also added a
`html_template` with Svelte syntax to really make sure that that
_doesn't_ work.

<img width="1816" alt="image"
src="https://github.com/user-attachments/assets/35564a32-9c46-4b51-bb1f-e02f4ffe8b01"
/>
2025-03-24 11:58:37 +01:00
Robin Malfait
5426baf358
Fix class extraction followed by ( in Pug (#17320)
This PR fixes an issue where a class shorthand in Pug followed by a `(`
is not properly extracted.

```html
<template lang="pug">
.text-sky-600.bg-neutral-900(title="A tooltip") This div has an HTML attribute.
</template>
```

The `text-sky-600` is extracted, but the `bg-neutral-900` is not.

Fixes: #17313

# Test plan

1. Added test to cover this case
2. Existing tests pass (after a few small adjustments due to _more_
extracted candidates, but definitely not _less_)
3. Verified against the original issue (top is before, bottom is this
PR)
<img width="1307" alt="image"
src="https://github.com/user-attachments/assets/68a0529f-63ad-477d-a342-e3f91c5a1690"
/>

We had this exact same bug in Slim
(https://github.com/tailwindlabs/tailwindcss/pull/17278). Since Pug,
Slim and Haml are the only pre processors we have right now with this
dot-separated class notation I also double checked the Haml
pre-processor if this is an issue or not (and it's already covered
there).

<img width="1263" alt="image"
src="https://github.com/user-attachments/assets/c658168b-d124-46c9-9ec0-9697151a57bf"
/>
2025-03-21 15:43:37 +01:00
Robin Malfait
f369e22172
Fix class extraction followed by ( in Slim (#17278)
This PR fixes an issue where using the class shorthand in Slim
templates, followed by an `(` results in the last class being ignored.

E.g.:

```slim
body.border-t-4.p-8(class="#{body_classes}" data-hotwire-native="#{hotwire_native_app?}" data-controller="update-time-zone")
```

This is because we will eventually extract `p-8` but it's followed by an
invalid boundary character `(`.

To solve this, we make sure to replace the `(` with a space. We already
do a similar thing when the classes are followed by an `[`.

One caveat, we _can_ have `(` in our classes, like `bg-(--my-color)`.
But in my testing this is not something that can be used in the
shorthand version.

E.g.:
```slim
div.bg-(--my-color)
```

Compiles to:
```html
<div --my-color="" class="bg-"></div>
```

So I didn't add any special handling for this. Even when trying to
escape the `(`, `-` and `)` characters, it still doesn't work. E.g.:

```slim
div.bg-\(--my-color\)
```

Compiles to:
```html
<div class="bg-">\(--my-color\)</div>
```

# Test plan

1. Added test for the issue
2. Existing tests pass
3. Verified via the extractor tool:

| Before | After |
| --- | --- |
| <img width="958" alt="image"
src="https://github.com/user-attachments/assets/f72c420e-5429-424f-a01d-12f724062bf2"
/> | <img width="958" alt="image"
src="https://github.com/user-attachments/assets/b0cc8f2f-97a8-4fca-8813-3bb1da8d99a8"
/> |

---

Fixes: #17277
2025-03-19 09:50:30 -04:00
Robin Malfait
d7c81164da
Pre process <template lang="…"> in Vue files (#17252)
This PR fixes an issue where `<template lang="…">…</template>` in Vue
files should be handled as-if it's the language specified in the `lang`
attribute.

To do this, we added a new Vue pre processor and run the content through
the same pre processor logic as we do for other languages.

Fixes: #17211

# Test plan

1. Added a test to verify this works
2. Existing tests still work

Visually verified against the reproduction in the issue:

| Before | After |
| --- | --- |
| <img width="1273" alt="image"
src="https://github.com/user-attachments/assets/d1accdeb-97cf-48ef-83fb-978832b3e599"
/> | <img width="1273" alt="image"
src="https://github.com/user-attachments/assets/ab7ec19c-b6c4-43be-8845-096ff4e58808"
/> |

---------

Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2025-03-17 13:54:37 +00:00
Robin Malfait
221855b195
Ensure candidate extraction works as expected in Clojure/ClojureScript (#17087)
This PR adds a Clojure/ClojureScript pre processor to make sure that
candidate extraction works as expected.


| Before | After |
| --- | --- |
| <img width="908" alt="image"
src="https://github.com/user-attachments/assets/98aba8b6-0c44-47c6-b87c-ecf955e5e007"
/> | <img width="908" alt="image"
src="https://github.com/user-attachments/assets/7a5ec3eb-1630-4b60-80bd-c07bc2381d3b"
/> |

You can see that the classes preceded by `:` are not properly extracted
in the before case, but they are in the after case. We do extract a few
more cases now like `:class` and `:className` itself, but at least we
also retrieve all the `flex-*` classes.

We could also always ignore `:class` and `:className` literals:
<img width="908" alt="image"
src="https://github.com/user-attachments/assets/f5a67cae-25d6-4811-b777-f72fdb5ef450"
/>
2025-03-13 11:31:27 +01:00
Robin Malfait
cedd54fecf
Fix variants with <digit>.</digit> are extracted correctly (#17153)
This PR fixes an issue where if you use a number with a decimal in a
variant then it wasn't picked up correctly.

E.g.:
```
<div class="2xl:flex 1.5xl:flex"></div>
            ^^^^^^^^                        Picked up
                     ^^^^^^^^^^             Not picket up
```

This PR fixes that behavior by applying the same rules for utilities
where a `.` is valid if it is surrounded by numbers.

# Test plan

1. Added test to ensure this is picked up
2. Existing tests pass
3. Ran the extractor on a real example with the following results:

| Before | After |
| --- | --- |
| <img width="821" alt="image"
src="https://github.com/user-attachments/assets/a77ed5e4-6848-4fe3-8cbf-cf61ff8db41d"
/> | <img width="821" alt="image"
src="https://github.com/user-attachments/assets/61aca66a-e38d-4b61-bf86-e6286a89a3d9"
/> |

They are crossed out just because it's not a default value we know in
the system, but you can see that the `1.` part is also extracted now.

Fixes: #17148
2025-03-12 21:04:11 +00:00
Robin Malfait
ca408d0612
Do not extract candidates containing JS string interpolation pattern ${ (#17142)
This PR fixes an issue where often people run into issues where they try
to use string interpolation and it doesn't work. Even worse, it could
result in crashes because we will actually generate CSS. This fix only
filters out candidates with a pattern like `${`. If this occurs in a
string position it is fine.

Another solution would be to add a pre processor for JS/TS (and all
thousand file extension combinations) but the problem is that you can
also write JS in HTML files so we would have to pre process HTML as well
which would not be ideal.

# Test plan

1. Added tests to prove this works in arbitrary values, arbitrary
variables in both utilities and variants.
2. Existing tests pass.
3. Some screenshots with before / after situations:

Given this input:
```ts
let color = '#0088cc';
let opacity = 0.8;
let name = 'variable-name';
let classes = [
  // Arbitrary Properties
  `[color:${color}]`
  `[${property}:value]`,
  `[--img:url('https://example.com?q=${name}')]`, // WONT WORK BUT VALID CSS

  // Arbitrary Values
  `bg-[${color}]`,

  // Arbitrary Variables
  `bg-(--my-${color})`,
  `bg-(--my-color,${color})`,

  // Arbitrary Modifier
  `bg-red-500/[${opacity}]`,
  `bg-red-500/(--my-${name})`,
  `bg-red-500/(--my-opacity,${opacity})`,

  // Arbitrary Variant
  `data-[state=${name}]:flex`,
  `supports-(--my-${name}):flex`,
  `[@media(width>=${value})]:flex`,
];
```

This is the result:

| Before | After |
| --- | --- |
| <img width="908" alt="image"
src="https://github.com/user-attachments/assets/c64d1b16-d39d-48a6-a098-bc4477cb4b0a"
/> | <img width="908" alt="image"
src="https://github.com/user-attachments/assets/d71aaf62-5e13-4174-82bb-690eb81aaeaf"
/> |

Fixes: #17054
Fixes: #15853
2025-03-12 12:09:13 +01:00
Robin Malfait
37062928f9
Improve candidate extraction when candidates contain . characters (#17113)
This PR fixes an issue where some classes weren't properly extracted due
to some incorrect assumptions in the pre processors.

Templating languages such as Haml, Slim and Pug allow you to write
classes in a shorter way that are not properly contained inside of
strings. E.g.:
```slim
p.flex.px-2
```

These candidates are not properly extracted because there are no
bounding characters like quotes. To solve this, we pre-process it and
replace `.` with ` ` characters. This results in something like:
```
p flex px-2
```

However, this has some challenges on its own. Candidates like `px-2.5`
cannot be written in this shorthand form, instead they need to be in
strings. Now we _cannot_ replace the `.` because otherwise we would
change `px-2.5` to `px-2 5` which is wrong.

The next problem is that we need to know when they are in a "string".
This has another set of problems because these templating languages
allow you to write normal text that will eventually be the contents of
the HTML tags.

```haml
.text-red-500.text-3xl
  | This text can't should be red
                 ^ Wait, is this the start of a string now???
```

In this example, if we consider the `'` the start of a string, when it's
clearly not, how would we know it's for _sure_ not a string?

This ended up as a bit of a rabbit hole, but we came up with another
approach entirely if we think about the original problem we want to
solve which is when do we change `.` to ` ` characters.

One of the rules in our current extractor is that a `.` has to be
between 2 numbers. Which works great in a scenario like: `px-2.5`.
However, if you look at Haml or Slim syntax, this is also allowed:

```slim
p.bg-red-500.2xl:flex
           ^^^ Uh oh...
```

In this scenario, a `.` is surrounded by numbers so we shouldn't replace
it with a space. But as you can see, we clearly do... so we need another
heuristic in this case.

Luckily, one of the rules in Tailwind CSS is that a utility cannot start
with a number, but a variant _can_. This means that if we see a scenario
like `<digit>.<digit>` then we can just check if the value after the `.`
is a valid variant or not.

In this case it is a valid variant so we _do_ want to replace the `.`
with a ` ` even though we do have the `<digit>.<digit>` format.

🥴

# Test plan

1. Added additional tests.
2. Existing tests still pass

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2025-03-11 17:53:16 +01:00
Philipp Spiess
9d7f25316e
Don't extract links as arbitrary properties (#17129)
Closes #17128

This PR prevents extraction of links inside square brackets as valid
candidate:

```
[https://example/]
```

We do this by throwing out arbitrary properties when the value starts
with a slash (`/`).

## Test plan

- Added unit test

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2025-03-11 16:40:24 +00:00
Robin Malfait
9ddeb091d7
Add JSON pre-processor (#17125)
This PR adds a small JSON pre processor to improve parsing JSON files.

Due to the sheer amount of potential `[` and `]` brackets, it could be
that parsing JSON files are way slower than they need to be.

We saw this while debugging this issue:
https://github.com/tailwindlabs/tailwindcss/issues/17092


# Test plan

1. Added test to verify the pre processing works
2. Existing tests still pass
2025-03-11 12:15:48 +00:00
Philipp Spiess
8bc633bb17
Add .node and .wasm to known binary file list (#17123)
Closes #17092

After a lot of spelunking we found one specific reason for the very slow
builds in the repro from #17092: Turns our we are needlessly scanning
the binary `.node` extension for class names 😬. This PR adds `.wasm` and
`.node` to the list of known binary extensions.

## Test plan

 - Check out the repro from `#17092`
 - Delete the `.gitignore` file
 - Observe that builds are very slow (`527.79s`)
 - Add a _pnpm override_ to load local versions of Oxide
 - `pnpm build` now completes in ~50s
2025-03-11 13:05:51 +01:00
Robin Malfait
de145c5b06
Refactor: use compile time type state pattern (#17083)
This PR implements the state machines using the type state pattern at
compile time (via generic types) instead of a runtime state variable.
There is no runtime check to see what state we are in, instead we
transition to the new state when it's necessary.

This has some nice performance improvements for some of the state
machines, e.g.:

```diff
- ArbitraryVariableMachine: Throughput: 744.92 MB/s
+ ArbitraryVariableMachine: Throughput:   1.21 GB/s
```

We also don't have to store the current state because each machine runs
to completion. It's during execution that we can move to a new state if
necessary.


Unfortunately the diff is a tiny bit annoying to read, but essentially
this is what happened:

### The `enum` is split up in it's individual states as structs:
```rs
enum State {
  A,
  B,
  C,
}
```
Becomes:
```rs
struct A;
struct B;
struct C;
```

### Generics

The current machine will receive a generic `State` that we can default
to the `IdleState`. Then we use `PhantomData` to "use" the type because
the generic type is otherwise not used as a concrete value, it's just a
marker.

```rs
struct MyMachine {}
```
Becomes:
```rs
struct MyMachine<State = Idle> {
  _state: std::marker::PhantomData<State>
}
```

### Split 

Next, the `next` function used to match on the current state, but now
each match arm is moved to a dedicated implementation instead:
```rs
impl Machine for MyMachine {
  fn next(&mut self) -> MachineState {
    match self.state {
      State::A => { /* … */ },
      State::B => { /* … */ },
      State::C => { /* … */ },
    }
  }
}
``` 
Becomes:
```rs
impl Machine for MyMachine<A> {
  fn next(&mut self) -> MachineState {
    /* … */
  }
}
impl Machine for MyMachine<B> {
  fn next(&mut self) -> MachineState {
    /* … */
  }
}
impl Machine for MyMachine<C> {
  fn next(&mut self) -> MachineState {
    /* … */
  }
}
```

It's a bit more verbose, but now each state is implemented in its own
block. This also removes 2 levels of nesting which is a nice benefit.
2025-03-10 14:08:39 -04:00
Robin Malfait
cc3e852791
Treat starting single quote as verbatim text in Slim (#17085)
This PR fixes an issue in Slim templates where a single quote `'` at the
start of the line (excluding white space) is considered a line indicator
for verbatim text. It is not considered a string in this scenario.

So something like this:
```slim
div
  'Foo'
```

Will compile to:
```html
<div>Foo'</div>
```

Fixes: #17081
2025-03-10 11:45:26 +01:00
Robin Malfait
bc5a8c3683
Ensure classes between > and < are properly extracted (#17094)
This PR fixes an issue where candidates inside `>…<` were not always
correctly extracted. This happens in XML-like languages where the
classes are inside of these boundaries.

E.g.:
```html
<!-- Fluid template language -->
<f:variable name="bgStyle">
  <f:switch expression="{data.layout}">
    <f:case value="0">from-blue-900 to-cyan-200</f:case>
    <!--              ^^^^^^^^^^^^^^^^^^^^^^^^^      -->
    <f:case value="1">from-cyan-600 to-teal-200</f:case>
    <f:defaultCase>from-blue-300 to-cyan-100</f:defaultCase>
  </f:switch>
</f:variable>
```


Fixes: https://github.com/tailwindlabs/tailwindcss/issues/17088

# Test plan

1. Added a new test
2. Existing tests pass
2025-03-10 11:28:08 +01:00
Robin Malfait
7005ad7e00
Add Haml pre processor (#17051)
This PR ensures we extract candidates from Haml files.

Fixes: #17050
2025-03-07 22:32:15 +01:00
Robin Malfait
f498e4a97c
Ensure extracting candidates from JS embedded in a PHP string works as expected (#17031)
This PR fixes an issue where candidates are not properly extractor when
they end in `\`. This can happen if you embed a programming language
like JS inside another language like PHP where you need to escape some
strings.

Here is an example of Livewire flux:
```blade
@php
if ($sidebarIsStashable) {
    $attributes = $attributes->merge([
        'x-init' => '$el.classList.add(\'-translate-x-full\'); $el.classList.add(\'transition-transform\')',
        //                                                ^                                            ^
    ]);
}
@endphp

<div x-data {{ $attributes->class('border-r w-64 p-4 min-h-dvh max-h-dvh top-0 fixed left-0') }}>
    {{ $slot }}
</div>
```
Where the `\'` is causing some issues.

Another solution might be to add a custom pre processor for blade files
where we drop the escaped characters, but that felt overkill for now
because some escapes are still valid.

Fixes: #17023

# Test plan

1. Added a test to cover this case.
2. Existing tests still pass
2025-03-07 12:19:01 +01:00
Robin Malfait
d18fed1dca
Add razor/cshtml pre processing (#17027)
This PR fixes an issue in Razor template files where `@sm:flex` doesn't
work and `@@sm:flex` is required.

In Tailwind CSS v3, some people used a custom transform to replace `@@`
with just `@`. But in Tailwind CSS v4 we don't have this.

However, we can add a pre processor for `.cshtml` and `.razor` files.

Fixes: #17022
2025-03-07 12:02:07 +01:00
Robin Malfait
d0a97467f4
Improve boundary classification (#17005)
This PR cleans up the boundary character checking by using similar
classification techniques as we used for other classification problems.

For starters, this moves the boundary related items to its own file,
next we setup the classification enum.

Last but not least, we removed `}` as an _after_ boundary character, and
instead handle that situation in the Ruby pre processor where we need
it. This means the `%w{flex}` will still work in Ruby files.

---

This PR is a followup for
https://github.com/tailwindlabs/tailwindcss/pull/17001, the main goal is
to clean up some of the boundary character checking code. The other big
improvement is performance. Changing the boundary character checking to
use a classification instead results in:

Took the best score of 10 runs each:
```diff
- CandidateMachine: Throughput: 311.96 MB/s
+ CandidateMachine: Throughput: 333.52 MB/s
```

So a ~20MB/s improvement.

# Test plan

1. Existing tests should pass. Due to the removal of `}` as an after
boundary character, some tests are updated.
2. Added new tests to ensure the Ruby pre processor still works as
expected.

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2025-03-07 00:00:54 +00:00
Robin Malfait
57e91a671a
Ensure } and { are valid boundary characters (#17001) 2025-03-06 20:40:03 +01:00
Robin Malfait
85c6e04f44
Ensure strings in Pug and Slim templates are handled correctly (#17000)
This PR fixes an issue where strings in the Pug and Slim pre-processor
were handled using the `string_machine`. However, the `string_machine`
is not for strings inside of Tailwind CSS classes which means that
whitespace is invalid.

This means that parts of the code that _are_ inside strings will not be
inside strings and parts of the code that are not inside strings will be
part of a potential string. This is a bit confusing to wrap your head
around, but here is a visual representation of the problem:

```
.join(' ')
        ^  3. start of new string, which means that the `)` _could_ be part of a string if a new `'` occurs later.
       ^   2. whitespace is not allowed, stop string
      ^    1. start of string
```

Fixes: #16998

# Test plan

1. Added new test
2. Existing tests still pass
3. Added a simple test helper to make sure that we can extract the
correct candidates _after_ pre-processing
2025-03-06 17:24:00 +01:00
Philipp Spiess
bff387bf6b
Ensure arbitrary variables with data types are extracted correctly (#16986)
Closes #16983

This PR fixes an issue where the arbitrary variable machine did not
extract data type correctly.

## Test plan

- Added regression test
2025-03-06 12:05:33 +01:00
Robin Malfait
af132fbedb
Fix Slim attributes (#16985)
This PR fixes an issue in Slim templates where the start of attributes
causes some candidates to be missing.


```slim
.text-xl.text-red-600[
  data-foo="bar"
]
  | This line should be red
```

Because of the `[` attached to the `text-red-600`, the `text-red-600`
was not extracted because `[` is not a valid boundary character.

To solve this, we copied the Pug pre processor and created a dedicated
Slim pre processor. Next, we ensure that we replace `[` with ` ` in this
scenario (by also making sure that we don't replace `[` where it's
important).

Additionally, we noticed that `.` was also replaced inside of arbitrary
values such as URLs. This has been fixed for both Pug and Slim.

Fixes: #16975

# Test plan

1. Added failing tests
2. Existing tests still pass
2025-03-06 11:59:14 +01:00