9 Commits

Author SHA1 Message Date
Jordan Pittman
00ccbdc937
Don’t detect arbitrary properties when preceded by an escape (#15456)
This is a targeted bug fix uncovered by the v4 docs.

Given this code:
```html
<!-- [!code word:group-has-\\[a\\]\\:block] -->
```

We'd pick up `[a\\]\\:block]` as a candidate which would then make it
far enough to get output to CSS and throw an error. This makes sure we
don't try to start an arbitrary property if the preceding character is a
`\`

cc @RobinMalfait this look okay?

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
2024-12-20 15:22:49 +01:00
Robin Malfait
4bdc724a22
Fix scanning classes delimited by tab characters (#15169)
This PR fixes an issue where multi-line candidates in Svelte files
couldn't be found as reported in #15148

After digging in, the real culprit seems to be that the reproduction
used tab `\t` characters instead of spaces and we only delimited
explicitly on spaces.

Initially I couldn't reproduce this in an integration test until we
(@thecrypticace and I) realised that `\t` was being used.

## Test plan:

This PR adds an integration test that fails before the fix happens. The
fix itself is easy in the sense that we just use all ascii whitespace
characters instead of just spaces.

Fixes: #15148
2024-11-26 18:22:18 +00:00
Jordan Pittman
8122837dde
Allow simple utilities with numbers in Oxide (#15110)
Fixes #15072

---------

Co-authored-by: Philipp Spiess <hello@philippspiess.com>
2024-11-22 11:05:24 -05:00
Robin Malfait
3cf5c2df79
Disallow empty arbitrary values (#15055)
This PR makes the candidate parser more strict by not allowing empty
arbitrary values.

Examples that are not allowed anymore:

- `bg-[]` — arbitrary value
- `bg-()` — arbitrary value, var shorthand
- `bg-[length:]` — arbitrary value, with typehint
- `bg-(length:)` — arbitrary value, with typehint, var shorthand
- `bg-red-500/[]` — arbitrary modifier
- `bg-red-500/()` — arbitrary modifier, var shorthand
- `data-[]:flex` — arbitrary value for variant
- `data-():flex` — arbitrary value for variant, var shorthand
- `group-visible/[]:flex` — arbitrary modifier for variant
- `group-visible/():flex` — arbitrary modifier for variant, var
shorthand

If you are trying to trick the parser by injecting some spaces like
this:

- `bg-[_]`

Then that is also not allowed.
2024-11-21 13:47:13 +01:00
Robin Malfait
3dc3bad781
Re-introduce automatic var injection shorthand (#15020)
This PR re-introduces the automatic var injection feature.

For some backstory, we used to support classes such as `bg-[--my-color]`
that resolved as-if you wrote `bg-[var(--my-color)]`.

The is issue is that some newer CSS properties accepts dashed-idents
(without the `var(…)`). This means that some properties accept
`view-timeline-name: --my-name;` (see:
https://developer.mozilla.org/en-US/docs/Web/CSS/view-timeline-name).

To make this a tiny bit worse, these properties _also_ accept
`var(--my-name-reference)` where the variable `--my-name-reference`
could reference a dashed-ident such as `--my-name`.

This makes the `bg-[--my-color]` ambiguous because we don't know if you
want `var(--my-color)` or `--my-color`.

With this PR, we bring back the automatic var injection feature as
syntactic sugar, but we use a different syntax to avoid the ambiguity.
Instead of `bg-[--my-color]`, you can now write `bg-(--my-color)` to get
the same effect as `bg-[var(--my-color)]`.

This also applies to modifiers, so `bg-red-500/[var(--my-opacity)]` can
be written as `bg-red-500/(--my-opacity)`. To go full circle, you can
rewrite `bg-[var(--my-color)]/[var(--my-opacity)]` as
`bg-(--my-color)/(--my-opacity)`.

---

This is implemented as syntactical sugar at the parsing stage and
handled when re-printing. Internally the system (and every plugin) still
see the proper `var(--my-color)` value.

Since this is also handled during printing of the candidate, codemods
don't need to be changed but they will provide the newly updated syntax.

E.g.: running this on the Catalyst codebase, you'll now see changes like
this:
<img width="542" alt="image"
src="https://github.com/user-attachments/assets/8f0e26f8-f4c9-4cdc-9f28-52307c38610e">

Whereas before we converted this to the much longer
`min-w-[var(--button-width)]`.

---

Additionally, this required some changes to the Oxide scanner to make
sure that `(` and `)` are valid characters for arbitrary-like values.

---------

Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
2024-11-18 15:47:48 +01:00
Robin Malfait
92007a5b23
Fix crash when using @source containing .. (#14831)
This PR fixes an issue where a `@source` crashes when the path
eventually resolves to a path ending in `..`.

We have to make sure that we canonicalize the path to make sure that we
are working with the real directory.

---------

Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-10-30 16:24:48 -04:00
Philipp Spiess
1467dab59e
Fix template migration issues (#14600)
This PR fixes two issues we found when testing the candidate codemodes:

1. Sometimes, core would emit the same candidate twice. This would
result into rewriting a range multiple times, without realizing that
this change might already be applied, causing it to swallow or duplicate
some bytes.
2. The codemods were mutating the `Candidate` object, however since the
`Candidate` parsing is _cached_ in core, it would sometimes return the
same instance. This is an issue especially since we monkey patch the
prefix to `null` when migrating prefixed candidates. This means that a
candidate would be cached that would be _invalid relative to the real
design system_. We fixed this by making sure the mutations would only be
applied to clones of the `Candidate` and I changed the `DesignSystem`
API to return `ReadOnly<T>` versions of these candidates. A better
solution would maybe be to disable the cache at all but this requires
broader changes in Core.
2024-10-08 18:06:43 +02:00
Philipp Spiess
732147a761
Add setup for template migrations (#14502)
This PR adds the initial setup and a first codemod for the template
migrations. These are a new set of migrations that operate on files
defined in the Tailwind v3 config as part of the `content` option (so
your HTML, JavaScript, TSX files etc.).

The migration for this is integrated in the new `@tailwindcss/upgrade`
package and will require pointing the migration to an input JavaScript
config file, like this:

```
npx @tailwindcss/upgrade --config tailwind.config.js
```

The idea of template migrations is to apply breaking changes from the v3
to v4 migration within your template files.

## Migrating !important syntax

The first migration that I’m adding with this PR is to ensure we use the
v4 important syntax that has the exclamation mark at the end of the
utility.

For example, this:

```html
<div class="!flex sm:!block"></div>
```

Will now turn into:

```html
<div class="flex! sm:block!"></div>
```

## Architecture considerations

Implementation wise, we make use of Oxide to scan the content files fast
and efficiently. By relying on the same scanner als Tailwind v4, we
guarantee that all candidates that are part of the v4 output will have
gone through a migration.

Migrations itself operate on the abstract `Candidate` type, similar to
the type we use in the v4 codebase. It will parse the candidate into its
parts so they can easily be introspected/modified. Migrations are typed
as:

```ts
type TemplateMigration = (candidate: Candidate) => Candidate | null
``` 

`null` should be returned if the `Candidate` does not need a migration. 

We currently use the v4 `parseCandidate` function to get an abstract
definition of the candidate rule that we can operate on. _This will
likely need to change in the future as we need to fork `parseCandidate`
for v3 specific syntax_.

Additionally, we're inlining a `printCandidate` function that can
stringify the abstract `Candidate` type. It is not guaranteed that this
is an identity function since some information can be lost during the
parse step. This is not a problem though, because migrations will only
run selectively and if none of the selectors trigger, the candidates are
not updated. h/t to @RobinMalfait for providing the printer.

So the overall flow of a migration looks like this:

- Scan the config file for `content` files
- Use Oxide to extract a list of candidate and their positions from
these `content` files
- Run a few migrations that operate on the `Candidate` abstract type.
- Print the updated `Candidate` back into the original `content` file.

---------

Co-authored-by: Robin Malfait <malfait.robin@gmail.com>
Co-authored-by: Jordan Pittman <jordan@cryptica.me>
2024-09-25 16:20:14 +02:00
Robin Malfait
1c48683a23
Hoist oxide/crates to just crates (#13333)
* move `oxide/crates` to `crates`

* ignore `target/` folder

* ensure pnpm points to `crates` instead of `oxide/crates`

* ensure all paths point to `crates` instead of `oxide/crates`

* update `oxide/crates` -> `crates` path in workflows

* use correct path in .prettierignore

* rename `crates/core` to `crates/oxide`

* remove oxide folder

* fix test script to run `cargo test` directly
2024-03-23 09:00:48 -04:00