diff --git a/.changeset/css-prop-script-mode.md b/.changeset/css-prop-script-mode.md new file mode 100644 index 00000000..8269d8f2 --- /dev/null +++ b/.changeset/css-prop-script-mode.md @@ -0,0 +1,5 @@ +--- +'babel-plugin-styled-components': patch +--- + +Fix a crash when the `css` prop is used in a CommonJS file without a `styled-components` import. diff --git a/.changeset/faster-transforms.md b/.changeset/faster-transforms.md new file mode 100644 index 00000000..161c7972 --- /dev/null +++ b/.changeset/faster-transforms.md @@ -0,0 +1,5 @@ +--- +'babel-plugin-styled-components': patch +--- + +Faster transforms: the plugin's own time drops about 2–4× on typical files and up to ~28× on CommonJS files with many `css` props. diff --git a/.changeset/ignore-ambient-module-imports.md b/.changeset/ignore-ambient-module-imports.md new file mode 100644 index 00000000..6972681c --- /dev/null +++ b/.changeset/ignore-ambient-module-imports.md @@ -0,0 +1,5 @@ +--- +'babel-plugin-styled-components': patch +--- + +Fix components losing displayName and componentId when a `declare module` block also imports `styled-components`. diff --git a/.changeset/ignore-type-only-imports.md b/.changeset/ignore-type-only-imports.md new file mode 100644 index 00000000..bd33addc --- /dev/null +++ b/.changeset/ignore-type-only-imports.md @@ -0,0 +1,5 @@ +--- +'babel-plugin-styled-components': patch +--- + +Ignore `import type` from `styled-components` when finding the real `styled` import. diff --git a/.changeset/keep-namespace-import-recognized.md b/.changeset/keep-namespace-import-recognized.md new file mode 100644 index 00000000..558d6671 --- /dev/null +++ b/.changeset/keep-namespace-import-recognized.md @@ -0,0 +1,5 @@ +--- +'babel-plugin-styled-components': patch +--- + +Fix `styled.default.x` losing displayName and componentId with `import * as styled` alongside the css prop or a default import. diff --git a/.changeset/namespace-default-call-form.md b/.changeset/namespace-default-call-form.md new file mode 100644 index 00000000..3f35e9b2 --- /dev/null +++ b/.changeset/namespace-default-call-form.md @@ -0,0 +1,5 @@ +--- +'babel-plugin-styled-components': patch +--- + +Fix broken output for `styled.default(Component)` with `import * as styled` or `require`. diff --git a/.changeset/real-styled-binding-names.md b/.changeset/real-styled-binding-names.md new file mode 100644 index 00000000..103c9a35 --- /dev/null +++ b/.changeset/real-styled-binding-names.md @@ -0,0 +1,5 @@ +--- +'babel-plugin-styled-components': patch +--- + +Recognize `styled` under any local name, such as `import { styled as sc }` or `const sc = require('styled-components')`. diff --git a/.changeset/string-aware-minify.md b/.changeset/string-aware-minify.md new file mode 100644 index 00000000..34c97b20 --- /dev/null +++ b/.changeset/string-aware-minify.md @@ -0,0 +1,5 @@ +--- +'babel-plugin-styled-components': patch +--- + +Fix minification dropping rules after a commented line whose string contains an apostrophe, escaped quote, or parenthesis (e.g. `content: "it's"; // note`). diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..6313b56c --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4f5386ef..f6ee0545 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -10,6 +10,8 @@ concurrency: ${{ github.workflow }}-${{ github.ref }} jobs: release: runs-on: ubuntu-latest + env: + PNPM_CONFIG_VIRTUAL_STORE_TYPE: project permissions: contents: write pull-requests: write @@ -17,7 +19,7 @@ jobs: steps: - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v6.1.0 - uses: actions/setup-node@v4 with: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d127414f..c507f5e1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,14 +7,20 @@ on: jobs: build: - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} + env: + PNPM_CONFIG_VIRTUAL_STORE_TYPE: project strategy: matrix: + os: [ubuntu-latest] node: ["22", "24"] + include: + - os: windows-latest + node: "24" steps: - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 + - uses: pnpm/action-setup@v6.1.0 - name: Setup node uses: actions/setup-node@v4 diff --git a/AGENTS.md b/AGENTS.md index e099b44b..4ab3ea1f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,58 +1,115 @@ -Project notes for AI coding agents working on `babel-plugin-styled-components`. +Agent instructions for `babel-plugin-styled-components`, a Babel plugin (plain JavaScript) that annotates and minifies styled-components code. -Hard rules +Charter + +- This file holds only one-line rules that prevent a recurring defect class, everyday commands, and a map of every doc. Everything else goes to a named home in this repo (README, a test, or a new `docs/.md` listed in the map). Never narration, history, backlog, drifting figures, code trivia an agent can grep, or a second copy of a fact that has a home. A misplaced line moves home in the change that finds it, leaving the rule and a pointer. + +Commands + +- `pnpm install --frozen-lockfile`: install (the form CI uses) +- `pnpm test`: Jest over `test/` (fixture suite and unit tests) +- `pnpm exec jest -u`: regenerate fixture outputs, only after the snapshot-drift rule below is satisfied +- `pnpm exec jest --coverage`: prints live coverage +- `pnpm build`: compile `src/` to `lib/`; run it to verify every set of changes. Don't start a dev server unless asked +- `pnpm bench [--base ] [--config default|all|bare] [--rounds N] [--normalize-aliases]`: interleaved A/B of the plugin at `` (default `origin/main`) against the working tree over generated inputs; fails when outputs differ +- `pnpm bench:profile [--input small|medium|large|large-cjs]`: CPU profile with the plugin frames that drive Babel's work +- `pnpm style`: Prettier over `src/` +- `pnpm changeset`: add a release note + +Map + +- `README.md`: user-facing overview, requirements, highlighted options. The full option reference lives on the styled-components website +- `CHANGELOG.md`: written by changesets at release, never hand-edited +- `.changeset/`: pending release notes, one file per user-visible change +- `src/index.js`: plugin entry, wires the visitors; `src/utils/options.js`: option keys and defaults +- `src/visitors/`: one transform per file; `src/utils/detectors.js`: recognizes styled, css, keyframes, and helper call shapes; `src/minify/`: template-literal CSS minifier +- `test/fixtures//`: `code.js` (input), `.babelrc` (plugin config), and either `output.js` (expected) or `error.js` (expected throw) +- `test/minify/`: minifier unit tests +- Tracker: GitHub Issues + +Rules + +Release and attribution - Never bump the version in `package.json` or publish to npm. Releases ship via changesets + GitHub Actions. +- Add a `.changeset/.md` for every user-visible change: one sentence on what users see, with a short example when it helps. `patch` for fixes, `minor` for new behavior, `major` only when the change actually breaks working consumers. Declaring an implicit peer dependency that consumers must already have in order to use the plugin (for example adding `@babel/core` as an explicit peer) is not by itself a major; tightening an existing peer range to drop currently-supported versions is. - Never include AI attribution on commits, PRs, files, or output. -- External communications (issues, gists, comments) never leak client names or internals. Speak as the maintainer in first person, warmly toward contributors. Bug reports to third parties are redacted and minimal. -Voice +Prose and communication - Warm, concise, situationally humorous, grounded in data. American English. - No em-dashes. Use situationally-appropriate unicode punctuation. +- Plain words over terms of art; define jargon at first use. Lead with the recommendation or finding, reasoning after. No clincher sign-offs. - Machine-oriented markdown (AGENTS.md, internal notes, agent instructions): dashes for lists, no headers, no bold, no tables. This file is one of those. -- Human-facing markdown (README, PR descriptions, changesets, release notes): written for a human to read. Don't include internals or point-in-time numbers or quantities that go stale (test counts, coverage percentages, dependency counts). +- Human-facing markdown (README, PR descriptions, changesets, release notes): written for a human to read. Don't include internals or point-in-time numbers or quantities that go stale (test counts, coverage percentages, dependency counts). A changeset may state the measured speedup of its own release, since release notes are frozen once published. +- Public text (PR descriptions, changesets, issues, comments) leads with user-facing impact, assumes no familiarity with the code, and is warm toward contributors. +- External communications (issues, gists, comments) never leak client names or internals. Bug reports to third parties are redacted and minimal: mechanism, symptom, minimal repro. - Escape literal asterisks outside code blocks (process.env.\*). +- Commits follow Conventional Commits (`type(scope): subject`). -How to think +Method - Scientific method. Form a hypothesis, hold variables constant, test one at a time, update beliefs. -- Be meticulous. Observe, note, plan, act, then reconcile work against the plan. Keep a running to-do list and update it as work finishes or new requests land. Append new requests to the end unless told otherwise. +- Be meticulous. Observe, note, plan, act, then reconcile work against the plan. +- A reported defect names a class. Catalog every instance by search (source, tests, fixtures, docs) and fix them all in the same change, or list the ones left and why. +- Fix at the cause. Never update a snapshot to green, swallow an error, or disable a check to hide a symptom. - Be the gardener. Handle unobjectionable cleanup as you pass through. Sometimes substantial cuts are necessary to keep the system healthy. - Question complexity. Propose simpler alternatives. Prefer questioning assumptions over accepting overengineering. -- Don't silently work around issues. Log them in a backlog or in the PR body rather than deferring invisibly. +- Don't silently work around issues. Log them in a GitHub issue or in the PR body rather than deferring invisibly. - Never make assumptions about information or file content you have not read in the same session. Verify against fresh sources. Training data is stale by default. Research first-party docs and current code before deciding. +- Distinguish observed (file:line, command output, exact error) from inferred. +- When a behavior, name, or value changes, grep every place that records it (README, tests, fixtures, comments, CI, this file) and update each in the same change. After deleting a module, remove the comments, docs, tests, and CI steps it left behind. -How to plan +Planning - Ask clarifying multiple-choice questions until you can restate the ask in one paragraph the maintainer agrees with. -- Present 2-3 directions with research-backed opinions, not a single decided plan. +- Present 2-3 directions with research-backed opinions, ranked with the recommendation first. - Never give time estimates. -How to ship +Code -- Types are law. No `as any`, non-null assertions, or `@ts-expect-error` escape hatches, except as deliberate negative-behavior tests clearly labeled as such. -- Tests ship with features. Branch coverage above 80%. Cover happy paths, edge cases, and failure modes. Prefer inline snapshots seeded empty. Local tests run well under 30s. +- Source is plain JavaScript with no type checker, so read an AST node's fields only after a `@babel/types` predicate (`t.isX`) confirms its shape. +- Prettier enforces format (`.prettierrc`): no semicolons, single quotes, ES5 trailing commas, no parens around a lone arrow parameter. +- Backward compatibility applies only to public API surfaces. The plugin's public surface is the default export (a Babel plugin function), the option keys it accepts, and the runtime behavior of the code it emits. Emitted code may change shape (imports, alias names, helper output) when it behaves identically at runtime and `componentId`/`displayName` values stay stable; a shape change alone is not user-visible and needs no changeset. Internal modules in `src/utils`, `src/visitors`, and `src/minify` are rewritable when complexity warrants. +- Errors carry typed metadata for context but never leak internals to consumers. User-facing throws should use `path.buildCodeFrameError` (with a real `path`) so the error points at the offending source. The message names the problem and the fix. +- Comments explain non-obvious current behavior and are rare; never restate what the code says. Never narrate change history, reference an issue number, or describe what a future maintainer should do. +- Dry-run destructive or batch operations. Verify targeting before executing. + +Testing + +- Tests ship with features. Branch coverage above 90% (`pnpm exec jest --coverage`). Cover happy paths, edge cases, and failure modes. Prefer inline snapshots seeded empty. Local tests run well under 30s. +- Emitted code and minified CSS are asserted whole (a fixture `output.js`, `toBe`, or an inline snapshot), never with a regex or substring. Derive the expected value from what the code should do, never from observed output. Extend an existing suite or fixture family before creating a new one. - Red/green TDD is required for every fix. Add or adjust the failing test before the code change, confirm it fails (red) on the unmodified surrounding code, apply the fix, then confirm it passes (green). When the test is a fixture snapshot, generate it on the pre-fix tree so the diff captures the broken output, then update on the post-fix tree. A test that only ever passes alongside the fix is not a regression test. - Snapshot drift is suspect. When fixture `output.js` files change beyond cosmetic helper renames, isolate the cause before regenerating. Counter shifts (`componentId: "sc--N"`) in particular signal that visit order changed; verify the new order is intentional and source-order-stable before committing. -- Comments explain non-obvious current behavior. Never narrate change history, reference an issue number, or describe what a future maintainer should do. -- Hot-path code earns microbenchmarks before claiming it is optimized. Babel plugins run per file per build, so per-node allocation and repeated AST walks matter. -- Run the build (`pnpm build`) to verify changes. Don't start a dev server unless asked. -- Backward compatibility applies only to public API surfaces. The plugin's public surface is the default export (a Babel plugin function) and the option keys it accepts. Internal modules in `src/utils`, `src/visitors`, and `src/minify` are rewritable when complexity warrants. -- Errors carry typed metadata for context but never leak internals to consumers. User-facing throws should use `path.buildCodeFrameError` (with a real `path`) so the error points at the offending source. -- Dry-run destructive or batch operations. Verify targeting before executing. -- Always subscribe to pull request events and handle review comments and failing builds automatically. When the reviewer's logic is faulty, double-check, then gently leave a corrective comment. +- Fixture snapshots carry no machine paths on any OS: `error.js` holds the error message and code frame with the working directory as `` and forward slashes (babel-test is patched in `patches/` to do this), and a test in `test/index.test.js` fails on any absolute path. CI runs on Linux and Windows; `.gitattributes` keeps checkouts LF. +- A new css-prop fixture family includes one fixture without `@babel/preset-env`: its CommonJS transform folds repeated imports into one `require`, which hides import-shape bugs. +- Avoid snapshot churn. A change rewrites only the snapshots its behavior requires, and a hand-written expected output is changed only when the expectation itself was wrong, with the reason in the commit. +- Jest `roots` is `test/`, so agent worktrees under `.claude/worktrees/` stay out of the run. + +Performance -Project shape +- Hot-path code earns microbenchmarks before claiming it is optimized: A/B against the prior code at several input sizes (`pnpm bench`). Its run-to-run noise is large on big inputs, so repeat a run before trusting a single-digit difference. Babel plugins run per file per build, so per-node allocation and repeated AST walks matter. +- The happy path never pays for rare shapes; handle those behind a cheap presence check. +- Docs state a budget and the command that measures it, never a measured figure. -- Package manager: pnpm 11 via the `packageManager` field. Use `pnpm install --frozen-lockfile` in CI. -- Releases: changesets. Add a `.changeset/.md` for every user-visible change. `patch` for fixes, `minor` for new behavior, `major` only when the change actually breaks working consumers. Declaring an implicit peer dependency that consumers must already have in order to use the plugin (for example adding `@babel/core` as an explicit peer) is not by itself a major; tightening an existing peer range to drop currently-supported versions is. -- Tests: `pnpm test`. Fixture-based via `babel-test`. Each directory under `test/fixtures//` has `code.js` (input), `.babelrc` (plugin config), and either `output.js` (expected) or `error.js` (expected throw). To regenerate intentionally: `pnpm exec jest -u`. +Dependencies and git -Plugin internals +- Package manager: pnpm 12 via the `packageManager` field. Use `pnpm install --frozen-lockfile` in CI. Settings live in `pnpm-workspace.yaml`, including the global virtual store (`virtualStoreType: global`), which links every worktree's `node_modules` into one shared store. CI sets `PNPM_CONFIG_VIRTUAL_STORE_TYPE=project`: a cold CI store gains nothing from it, and on Windows the global layout fails install with "Access is denied". +- A dependency change commits its regenerated lockfile. Install scripts stay off; allow one only for demonstrated breakage, in `pnpm-workspace.yaml` `allowBuilds` with a one-line reason. +- `minimumReleaseAge` holds back releases younger than its window; add a deliberate exclusion rather than lowering it. +- Fetch origin before starting work or rebasing. Prefer fast-forward, then rebase; no merge commits. Never `git stash`; use a temporary commit. +- Watch every open PR for review comments and failing checks and handle them. After fixing a valid comment, resolve the thread. When the reviewer's logic is faulty, double-check, then gently leave a corrective comment. + +Babel plugin internals - Babel plugins run per file per build. Hot-path discipline: pre-curry visitor factories once at plugin init, don't re-curry per node. - Module-level mutable state leaks across files in long-running watchers (webpack-dev-server, jest watch, Next dev). Per-file caches live on `state.file.set/get`. - The css-prop transform injects new `VariableDeclaration` nodes at the end of `Program.body`, and Babel's outer traversal does not reliably re-visit them. The styled visitors are applied eagerly from inside `transpileCssProp` to compensate. When changing this area, verify on a file that mixes user-written `styled.div` and css-prop usages that both receive `displayName`/`componentId`. -- `componentId` is `sc--`. The counter is per-file, source-order-stable. If a change makes counter values shift unexpectedly across a snapshot, treat it as a regression until proven otherwise. +- `componentId` is `sc--`. The counter is per-file, source-order-stable. Components found by the css-prop pre-pass are numbered first; a component whose styled `require` sits below the first css prop is not found by it and takes its number in source order. If a change makes counter values shift unexpectedly across a snapshot, treat it as a regression until proven otherwise. - `isStyled` and the helper detectors in `src/utils/detectors.js` are recursive predicate chains. They short-circuit through nested CallExpression/SequenceExpression shapes (the `(0, x.default)(...)` form produced by `@babel/plugin-transform-modules-commonjs`). When adding a new arm, also reason about whether the recursive traversal will revisit children of the matched shape and re-match downstream (the `addConfig` mutation is not perfectly idempotent across all shapes). + +Not applicable + +- UI and accessibility: headless build-time library. +- Migrations and data lifecycle: no persisted data. +- User data and secrets: the plugin handles no user data, secrets, or network calls; supply-chain rules are under Dependencies and git. diff --git a/package.json b/package.json index 72e74ce4..8a836b8c 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,8 @@ "build": "babel src -d lib", "test": "jest", "test:watch": "pnpm test -- --watch", + "bench": "node scripts/bench/index.js", + "bench:profile": "node scripts/bench/profile.js", "prepublishOnly": "pnpm clean && pnpm build", "changeset": "changeset", "changeset-publish": "pnpm build && changeset publish" @@ -60,6 +62,9 @@ "displayName" ], "jest": { + "roots": [ + "/test" + ], "testEnvironment": "node", "snapshotSerializers": [ "/test/whitespaceTrimmingSerializer.js" @@ -68,5 +73,5 @@ "fixtures\\/[^/]+\\/(output|error)\\.js" ] }, - "packageManager": "pnpm@11.1.3" + "packageManager": "pnpm@12.14.0" } diff --git a/patches/babel-test@0.2.4.patch b/patches/babel-test@0.2.4.patch new file mode 100644 index 00000000..49cf3551 --- /dev/null +++ b/patches/babel-test@0.2.4.patch @@ -0,0 +1,33 @@ +diff --git a/src/index.js b/src/index.js +index cfe5f4f399845caba48b245b953194dc02d11a81..32edeacdb7dffca0fa5a2233df4f4291d6b5e842 100644 +--- a/src/index.js ++++ b/src/index.js +@@ -191,13 +191,22 @@ exports.create = function create(config) { + filename: error, + // Errors might have ansi colors, for example babel codeframe error + // Strip them so the error is more readable +- // Also replace the current working directory with a placeholder +- // This makes sure that the stacktraces are same across machines ++ // Snapshot the message (which carries Babel's code frame), not the ++ // stack: stack frames point into dependency installs outside the ++ // working directory and change with every dependency release. ++ // Also replace the current working directory with a placeholder, in ++ // either separator style and any drive-letter case, and write the ++ // rest of each such path with forward slashes so Windows matches + content: +- stripAnsi(e.stack).replace( +- new RegExp(escapeRegexp(process.cwd()), 'g'), +- '' +- ) + '\n', ++ stripAnsi(String(e)) ++ .replace( ++ new RegExp( ++ escapeRegexp(process.cwd()).replace(/\\\\|\//g, '[\\\\/]'), ++ 'gi' ++ ), ++ '' ++ ) ++ .replace(/[^\s:]*/g, (p) => p.replace(/\\/g, '/')) + '\n', + stack, + }; + } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ed3ab75f..9d21c5ac 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,9 +1,170 @@ +--- +lockfileVersion: '9.0' + +importers: + + .: + configDependencies: {} + packageManagerDependencies: + pnpm: + specifier: 12.14.0 + version: 12.14.0 + +packages: + + '@pnpm/exe.android-arm64@12.14.0': + resolution: {integrity: sha512-+iSh7x21C3A2hqc+Z0ZmbKTcn6YNlzrXM142PsnQH/dLj0DYxFzoBvc6XAnL/yfQ5nnH2ebBiRj/zVxr61fZbQ==} + cpu: [arm64] + os: [android] + + '@pnpm/exe.android-x64@12.14.0': + resolution: {integrity: sha512-qT+0LqEr2wBVaxcG2T8cX/R8SSRz2Rvpp8nRR4fh8tnGUZRch87gFmsHUAoSQh7w6j1h0qTjrA384j4xi8frvw==} + cpu: [x64] + os: [android] + + '@pnpm/exe.darwin-arm64@12.14.0': + resolution: {integrity: sha512-tnrdUB8ecJoehuKKqi1YHCBVl61xtJ9XVGcx5EviC6fWO4DeGI8GtXrjvJxDBM3nuVjJeWistgsUp/N142x4sw==} + cpu: [arm64] + os: [darwin] + + '@pnpm/exe.darwin-x64@12.14.0': + resolution: {integrity: sha512-vlBfGkruqQRsEHTW43cPwpYd7QlbIJ//cvpAdb3bVXkI8SgTPoXhCnc05m+ZNiPrrEkBsX/t7KHw9aXpkzPm3Q==} + cpu: [x64] + os: [darwin] + + '@pnpm/exe.freebsd-x64@12.14.0': + resolution: {integrity: sha512-Qg5rxBrGYl6Tfso+EyygPf84fY1Fzy55E/0or+iuYKtnf0vc0MlHJfVIlT7pBukMRntQHRik7hUi0Z4s7AlJsw==} + cpu: [x64] + os: [freebsd] + + '@pnpm/exe.linux-arm64-musl@12.14.0': + resolution: {integrity: sha512-DRIe4yQkXBGA/PhVJdzlbnllxBdAtnMMvTVLLcGpsK5FvJz9kxD2Hyxtn68ovmNkVX06f6p45cQwfm3wx2jn1A==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@pnpm/exe.linux-arm64@12.14.0': + resolution: {integrity: sha512-7joGShZ44Nk3WpNU+60JoOPjCXlgxhJAq11S2UiVtwCUDgTer1yTPPfxGW8pxaD6046actYLIYDbZNlubHWs0A==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-ppc64@12.14.0': + resolution: {integrity: sha512-gSoryn7ySdXd//gjgf3MvvA8ZerJayaSbuMZMtAWq4vb2/wlVEgpl0Kvqt54y58TRj533Qjq2txKgnxd4JyoAQ==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-riscv64@12.14.0': + resolution: {integrity: sha512-EMjew1H9WqQ7pL+tYqCPwiuB+0PeCiJRV7fyWeuFw0wwYUajFBjcaPO3IGAVu9sdmJDU+e+HYv/Zey6aKae0Vw==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-s390x@12.14.0': + resolution: {integrity: sha512-shYOj/SFoXD35fHe0pWr2JofwSXulC0BnEIXLj2QokDi0EF//AfKsui35yKIkTEl+s2VJybc3PO4iJNhKIj5Qw==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@pnpm/exe.linux-x64-musl@12.14.0': + resolution: {integrity: sha512-btZiZ2Ot0llKK1ndSfA5i3IRX7/S0VrmzseNH/BJ4c+Cady+S81H6vvU1zByggHMyqQhC8OgxO5PiIY7D9bH0Q==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@pnpm/exe.linux-x64@12.14.0': + resolution: {integrity: sha512-F6K8e/jWxiuEYJaQtRRUG9ynBSTEHa+t7Lf86MKuOO5UA7WVVMbi+oB8nY6iKW++ncWS2yG6T9faXZGxqNsfoQ==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@pnpm/exe.win32-arm64@12.14.0': + resolution: {integrity: sha512-DOjlkVB6TtTKeAAe0hNZXn5S7YqESmo1Z6SGbR4BZNQ4isRg31XVHjX2RFTbY8gnZq68IwzyLmPqlEjMmYxofg==} + cpu: [arm64] + os: [win32] + + '@pnpm/exe.win32-x64@12.14.0': + resolution: {integrity: sha512-/LSX6mew/sLf08XqcjemuklNKf1K/Ftpgj4//hK/HKzI2dKbmxDB27a/2OaQ5a0O3/3OFzlssXDziZsi+yVBwQ==} + cpu: [x64] + os: [win32] + + pnpm@12.14.0: + resolution: {integrity: sha512-wYum2HmT09Fky16JSPMrLAbuhUSH4GzY2GeBNC+z5btGNDZwNHEehoArqpw3vqkGzhkprQjXCITJfYAd8TOJpg==} + engines: {node: '>=18.*'} + hasBin: true + +snapshots: + + '@pnpm/exe.android-arm64@12.14.0': + optional: true + + '@pnpm/exe.android-x64@12.14.0': + optional: true + + '@pnpm/exe.darwin-arm64@12.14.0': + optional: true + + '@pnpm/exe.darwin-x64@12.14.0': + optional: true + + '@pnpm/exe.freebsd-x64@12.14.0': + optional: true + + '@pnpm/exe.linux-arm64-musl@12.14.0': + optional: true + + '@pnpm/exe.linux-arm64@12.14.0': + optional: true + + '@pnpm/exe.linux-ppc64@12.14.0': + optional: true + + '@pnpm/exe.linux-riscv64@12.14.0': + optional: true + + '@pnpm/exe.linux-s390x@12.14.0': + optional: true + + '@pnpm/exe.linux-x64-musl@12.14.0': + optional: true + + '@pnpm/exe.linux-x64@12.14.0': + optional: true + + '@pnpm/exe.win32-arm64@12.14.0': + optional: true + + '@pnpm/exe.win32-x64@12.14.0': + optional: true + + pnpm@12.14.0: + optionalDependencies: + '@pnpm/exe.android-arm64': 12.14.0 + '@pnpm/exe.android-x64': 12.14.0 + '@pnpm/exe.darwin-arm64': 12.14.0 + '@pnpm/exe.darwin-x64': 12.14.0 + '@pnpm/exe.freebsd-x64': 12.14.0 + '@pnpm/exe.linux-arm64': 12.14.0 + '@pnpm/exe.linux-arm64-musl': 12.14.0 + '@pnpm/exe.linux-ppc64': 12.14.0 + '@pnpm/exe.linux-riscv64': 12.14.0 + '@pnpm/exe.linux-s390x': 12.14.0 + '@pnpm/exe.linux-x64': 12.14.0 + '@pnpm/exe.linux-x64-musl': 12.14.0 + '@pnpm/exe.win32-arm64': 12.14.0 + '@pnpm/exe.win32-x64': 12.14.0 + +--- lockfileVersion: '9.0' settings: autoInstallPeers: true excludeLinksFromLockfile: false +patchedDependencies: + babel-test@0.2.4: 6b3cf0bcf4b9110049af53f6978b3834388ea424736399f5321452cba461e47e + importers: .: @@ -13,38 +174,38 @@ importers: version: 7.27.3 '@babel/helper-module-imports': specifier: ^7.25.9 - version: 7.28.6 + version: 7.28.6(supports-color@8.1.1) '@babel/plugin-syntax-jsx': specifier: ^7.25.9 - version: 7.28.6(@babel/core@7.29.0) + version: 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) picomatch: specifier: ^4.0.2 version: 4.0.4 devDependencies: '@babel/cli': specifier: ^7.26.4 - version: 7.28.6(@babel/core@7.29.0) + version: 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) '@babel/core': specifier: ^7.26.0 - version: 7.29.0 + version: 7.29.0(supports-color@8.1.1) '@babel/plugin-transform-class-properties': specifier: ^7.25.9 - version: 7.28.6(@babel/core@7.29.0) + version: 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) '@babel/plugin-transform-modules-commonjs': specifier: ^7.26.3 - version: 7.28.6(@babel/core@7.29.0) + version: 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) '@babel/preset-env': specifier: ^7.26.0 - version: 7.29.5(@babel/core@7.29.0) + version: 7.29.5(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) '@changesets/cli': specifier: 2.31.0 version: 2.31.0(@types/node@25.8.0) babel-test: specifier: ^0.2.4 - version: 0.2.4 + version: 0.2.4(patch_hash=6b3cf0bcf4b9110049af53f6978b3834388ea424736399f5321452cba461e47e) jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@25.8.0) + version: 29.7.0(@types/node@25.8.0)(supports-color@8.1.1) jest-file-snapshot: specifier: ^0.5.0 version: 0.5.0 @@ -2071,9 +2232,9 @@ packages: snapshots: - '@babel/cli@7.28.6(@babel/core@7.29.0)': + '@babel/cli@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@jridgewell/trace-mapping': 0.3.31 commander: 6.2.1 convert-source-map: 2.0.0 @@ -2093,20 +2254,20 @@ snapshots: '@babel/compat-data@7.29.3': {} - '@babel/core@7.29.0': + '@babel/core@7.29.0(supports-color@8.1.1)': dependencies: '@babel/code-frame': 7.29.0 '@babel/generator': 7.29.1 '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) '@babel/helpers': 7.29.2 '@babel/parser': 7.29.3 '@babel/template': 7.28.6 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.0(supports-color@8.1.1) '@babel/types': 7.29.0 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -2133,32 +2294,32 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.29.3(@babel/core@7.29.0)': + '@babel/helper-create-class-features-plugin@7.29.3(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-member-expression-to-functions': 7.28.5(supports-color@8.1.1) '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/traverse': 7.29.0 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1(supports-color@8.1.1) + '@babel/traverse': 7.29.0(supports-color@8.1.1) semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.29.0)': + '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-annotate-as-pure': 7.27.3 regexpu-core: 6.4.0 semver: 6.3.1 - '@babel/helper-define-polyfill-provider@0.6.8(@babel/core@7.29.0)': + '@babel/helper-define-polyfill-provider@0.6.8(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) lodash.debounce: 4.0.8 resolve: 1.22.12 transitivePeerDependencies: @@ -2166,26 +2327,26 @@ snapshots: '@babel/helper-globals@7.28.0': {} - '@babel/helper-member-expression-to-functions@7.28.5': + '@babel/helper-member-expression-to-functions@7.28.5(supports-color@8.1.1)': dependencies: - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.0(supports-color@8.1.1) '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helper-module-imports@7.28.6': + '@babel/helper-module-imports@7.28.6(supports-color@8.1.1)': dependencies: - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.0(supports-color@8.1.1) '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-imports': 7.28.6 + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-module-imports': 7.28.6(supports-color@8.1.1) '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -2195,27 +2356,27 @@ snapshots: '@babel/helper-plugin-utils@7.28.6': {} - '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.29.0)': + '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-wrap-function': 7.28.6 - '@babel/traverse': 7.29.0 + '@babel/helper-wrap-function': 7.28.6(supports-color@8.1.1) + '@babel/traverse': 7.29.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)': + '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-member-expression-to-functions': 7.28.5(supports-color@8.1.1) '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + '@babel/helper-skip-transparent-expression-wrappers@7.27.1(supports-color@8.1.1)': dependencies: - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.0(supports-color@8.1.1) '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color @@ -2226,10 +2387,10 @@ snapshots: '@babel/helper-validator-option@7.27.1': {} - '@babel/helper-wrap-function@7.28.6': + '@babel/helper-wrap-function@7.28.6(supports-color@8.1.1)': dependencies: '@babel/template': 7.28.6 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.0(supports-color@8.1.1) '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color @@ -2243,562 +2404,562 @@ snapshots: dependencies: '@babel/types': 7.29.0 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.29.0)': + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.3(@babel/core@7.29.0)': + '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.3(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1(supports-color@8.1.1) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0)': + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.0)': + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.0)': + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.0)': + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.0)': + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-import-assertions@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-syntax-import-assertions@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.0)': + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.0)': + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.0)': + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.0)': + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.0)': + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.0)': + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.0)': + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.0)': + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.0)': + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.0)': + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.29.0)': + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1)) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-async-generator-functions@7.29.0(@babel/core@7.29.0)': + '@babel/plugin-transform-async-generator-functions@7.29.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0) - '@babel/traverse': 7.29.0 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/traverse': 7.29.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-async-to-generator@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-async-to-generator@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-imports': 7.28.6 + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-module-imports': 7.28.6(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0) + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-classes@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-classes@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-globals': 7.28.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) - '@babel/traverse': 7.29.0 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/traverse': 7.29.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-computed-properties@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-computed-properties@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 '@babel/template': 7.28.6 - '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.29.0)': + '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-dotall-regex@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-dotall-regex@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1)) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)': + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1)) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-explicit-resource-management@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-explicit-resource-management@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-exponentiation-operator@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-exponentiation-operator@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-json-strings@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-json-strings@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-literals@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-literals@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-logical-assignment-operators@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-logical-assignment-operators@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-systemjs@7.29.4(@babel/core@7.29.0)': + '@babel/plugin-transform-modules-systemjs@7.29.4(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.29.0 + '@babel/traverse': 7.29.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)': + '@babel/plugin-transform-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1)) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-numeric-separator@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-numeric-separator@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-object-rest-spread@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-object-rest-spread@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) - '@babel/traverse': 7.29.0 + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/traverse': 7.29.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-optional-catch-binding@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-optional-catch-binding@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.29.0)': + '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-private-property-in-object@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-private-property-in-object@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-regenerator@7.29.0(@babel/core@7.29.0)': + '@babel/plugin-transform-regenerator@7.29.0(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-regexp-modifiers@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-regexp-modifiers@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1)) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-spread@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-spread@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1(supports-color@8.1.1) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-unicode-property-regex@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-unicode-property-regex@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1)) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.29.0)': + '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1)) '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-unicode-sets-regex@7.28.6(@babel/core@7.29.0)': + '@babel/plugin-transform-unicode-sets-regex@7.28.6(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1)) '@babel/helper-plugin-utils': 7.28.6 - '@babel/preset-env@7.29.5(@babel/core@7.29.0)': + '@babel/preset-env@7.29.5(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1)': dependencies: '@babel/compat-data': 7.29.3 - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.29.0) - '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array': 7.29.3(@babel/core@7.29.0) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0) - '@babel/plugin-syntax-import-assertions': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.29.0) - '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.29.0) - '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) - '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) - '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-explicit-resource-management': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-exponentiation-operator': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-json-strings': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-modules-systemjs': 7.29.4(@babel/core@7.29.0) - '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) - '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-numeric-separator': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) - '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.29.0) - '@babel/plugin-transform-regexp-modifiers': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-unicode-property-regex': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-unicode-sets-regex': 7.28.6(@babel/core@7.29.0) - '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.0) - babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.0) - babel-plugin-polyfill-corejs3: 0.14.2(@babel/core@7.29.0) - babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.0) + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array': 7.29.3(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-syntax-import-assertions': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-explicit-resource-management': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-exponentiation-operator': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-json-strings': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-modules-systemjs': 7.29.4(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-numeric-separator': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-regexp-modifiers': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-unicode-property-regex': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-transform-unicode-sets-regex': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.0(supports-color@8.1.1)) + babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + babel-plugin-polyfill-corejs3: 0.14.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) + babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) core-js-compat: 3.49.0 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.29.0)': + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.29.0(supports-color@8.1.1))': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/helper-plugin-utils': 7.28.6 '@babel/types': 7.29.0 esutils: 2.0.3 @@ -2811,7 +2972,7 @@ snapshots: '@babel/parser': 7.29.3 '@babel/types': 7.29.0 - '@babel/traverse@7.29.0': + '@babel/traverse@7.29.0(supports-color@8.1.1)': dependencies: '@babel/code-frame': 7.29.0 '@babel/generator': 7.29.1 @@ -2819,7 +2980,7 @@ snapshots: '@babel/parser': 7.29.3 '@babel/template': 7.28.6 '@babel/types': 7.29.0 - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -3005,12 +3166,12 @@ snapshots: jest-util: 29.7.0 slash: 3.0.0 - '@jest/core@29.7.0': + '@jest/core@29.7.0(supports-color@8.1.1)': dependencies: '@jest/console': 29.7.0 - '@jest/reporters': 29.7.0 + '@jest/reporters': 29.7.0(supports-color@8.1.1) '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 + '@jest/transform': 29.7.0(supports-color@8.1.1) '@jest/types': 29.6.3 '@types/node': 25.8.0 ansi-escapes: 4.3.2 @@ -3019,15 +3180,15 @@ snapshots: exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@25.8.0) + jest-config: 29.7.0(@types/node@25.8.0)(supports-color@8.1.1) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 jest-resolve: 29.7.0 - jest-resolve-dependencies: 29.7.0 - jest-runner: 29.7.0 - jest-runtime: 29.7.0 - jest-snapshot: 29.7.0 + jest-resolve-dependencies: 29.7.0(supports-color@8.1.1) + jest-runner: 29.7.0(supports-color@8.1.1) + jest-runtime: 29.7.0(supports-color@8.1.1) + jest-snapshot: 29.7.0(supports-color@8.1.1) jest-util: 29.7.0 jest-validate: 29.7.0 jest-watcher: 29.7.0 @@ -3051,10 +3212,10 @@ snapshots: dependencies: jest-get-type: 29.6.3 - '@jest/expect@29.7.0': + '@jest/expect@29.7.0(supports-color@8.1.1)': dependencies: expect: 29.7.0 - jest-snapshot: 29.7.0 + jest-snapshot: 29.7.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -3067,21 +3228,21 @@ snapshots: jest-mock: 29.7.0 jest-util: 29.7.0 - '@jest/globals@29.7.0': + '@jest/globals@29.7.0(supports-color@8.1.1)': dependencies: '@jest/environment': 29.7.0 - '@jest/expect': 29.7.0 + '@jest/expect': 29.7.0(supports-color@8.1.1) '@jest/types': 29.6.3 jest-mock: 29.7.0 transitivePeerDependencies: - supports-color - '@jest/reporters@29.7.0': + '@jest/reporters@29.7.0(supports-color@8.1.1)': dependencies: '@bcoe/v8-coverage': 0.2.3 '@jest/console': 29.7.0 '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 + '@jest/transform': 29.7.0(supports-color@8.1.1) '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.31 '@types/node': 25.8.0 @@ -3091,9 +3252,9 @@ snapshots: glob: 7.2.3 graceful-fs: 4.2.11 istanbul-lib-coverage: 3.2.2 - istanbul-lib-instrument: 6.0.3 + istanbul-lib-instrument: 6.0.3(supports-color@8.1.1) istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 4.0.1 + istanbul-lib-source-maps: 4.0.1(supports-color@8.1.1) istanbul-reports: 3.2.0 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -3129,12 +3290,12 @@ snapshots: jest-haste-map: 29.7.0 slash: 3.0.0 - '@jest/transform@29.7.0': + '@jest/transform@29.7.0(supports-color@8.1.1)': dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.31 - babel-plugin-istanbul: 6.1.1 + babel-plugin-istanbul: 6.1.1(supports-color@8.1.1) chalk: 4.1.2 convert-source-map: 2.0.0 fast-json-stable-stringify: 2.1.0 @@ -3306,25 +3467,25 @@ snapshots: array-union@2.1.0: {} - babel-jest@29.7.0(@babel/core@7.29.0): + babel-jest@29.7.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1): dependencies: - '@babel/core': 7.29.0 - '@jest/transform': 29.7.0 + '@babel/core': 7.29.0(supports-color@8.1.1) + '@jest/transform': 29.7.0(supports-color@8.1.1) '@types/babel__core': 7.20.5 - babel-plugin-istanbul: 6.1.1 - babel-preset-jest: 29.6.3(@babel/core@7.29.0) + babel-plugin-istanbul: 6.1.1(supports-color@8.1.1) + babel-preset-jest: 29.6.3(@babel/core@7.29.0(supports-color@8.1.1)) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 transitivePeerDependencies: - supports-color - babel-plugin-istanbul@6.1.1: + babel-plugin-istanbul@6.1.1(supports-color@8.1.1): dependencies: '@babel/helper-plugin-utils': 7.28.6 '@istanbuljs/load-nyc-config': 1.1.0 '@istanbuljs/schema': 0.1.6 - istanbul-lib-instrument: 5.2.1 + istanbul-lib-instrument: 5.2.1(supports-color@8.1.1) test-exclude: 6.0.0 transitivePeerDependencies: - supports-color @@ -3336,56 +3497,56 @@ snapshots: '@types/babel__core': 7.20.5 '@types/babel__traverse': 7.28.0 - babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.0): + babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1): dependencies: '@babel/compat-data': 7.29.3 - '@babel/core': 7.29.0 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) semver: 6.3.1 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-corejs3@0.14.2(@babel/core@7.29.0): + babel-plugin-polyfill-corejs3@0.14.2(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1): dependencies: - '@babel/core': 7.29.0 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) core-js-compat: 3.49.0 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-regenerator@0.6.8(@babel/core@7.29.0): + babel-plugin-polyfill-regenerator@0.6.8(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1): dependencies: - '@babel/core': 7.29.0 - '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) transitivePeerDependencies: - supports-color - babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.0): - dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.0) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.0) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.0) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.0) - '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.0) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.0) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.0) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.0) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.0) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.0) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.0) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.0) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.0) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.0) - - babel-preset-jest@29.6.3(@babel/core@7.29.0): - dependencies: - '@babel/core': 7.29.0 + babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.0(supports-color@8.1.1)): + dependencies: + '@babel/core': 7.29.0(supports-color@8.1.1) + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.0(supports-color@8.1.1)) + + babel-preset-jest@29.6.3(@babel/core@7.29.0(supports-color@8.1.1)): + dependencies: + '@babel/core': 7.29.0(supports-color@8.1.1) babel-plugin-jest-hoist: 29.6.3 - babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0(supports-color@8.1.1)) - babel-test@0.2.4: + babel-test@0.2.4(patch_hash=6b3cf0bcf4b9110049af53f6978b3834388ea424736399f5321452cba461e47e): dependencies: chalk: 4.1.2 error-stack-parser: 2.1.4 @@ -3492,13 +3653,13 @@ snapshots: dependencies: browserslist: 4.28.2 - create-jest@29.7.0(@types/node@25.8.0): + create-jest@29.7.0(@types/node@25.8.0)(supports-color@8.1.1): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@25.8.0) + jest-config: 29.7.0(@types/node@25.8.0)(supports-color@8.1.1) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -3515,9 +3676,11 @@ snapshots: csstype@3.2.3: {} - debug@4.4.3: + debug@4.4.3(supports-color@8.1.1): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 8.1.1 dedent@1.7.2: {} @@ -3753,9 +3916,9 @@ snapshots: istanbul-lib-coverage@3.2.2: {} - istanbul-lib-instrument@5.2.1: + istanbul-lib-instrument@5.2.1(supports-color@8.1.1): dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/parser': 7.29.3 '@istanbuljs/schema': 0.1.6 istanbul-lib-coverage: 3.2.2 @@ -3763,9 +3926,9 @@ snapshots: transitivePeerDependencies: - supports-color - istanbul-lib-instrument@6.0.3: + istanbul-lib-instrument@6.0.3(supports-color@8.1.1): dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/parser': 7.29.3 '@istanbuljs/schema': 0.1.6 istanbul-lib-coverage: 3.2.2 @@ -3779,9 +3942,9 @@ snapshots: make-dir: 4.0.0 supports-color: 7.2.0 - istanbul-lib-source-maps@4.0.1: + istanbul-lib-source-maps@4.0.1(supports-color@8.1.1): dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@8.1.1) istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: @@ -3798,10 +3961,10 @@ snapshots: jest-util: 29.7.0 p-limit: 3.1.0 - jest-circus@29.7.0: + jest-circus@29.7.0(supports-color@8.1.1): dependencies: '@jest/environment': 29.7.0 - '@jest/expect': 29.7.0 + '@jest/expect': 29.7.0(supports-color@8.1.1) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 '@types/node': 25.8.0 @@ -3812,8 +3975,8 @@ snapshots: jest-each: 29.7.0 jest-matcher-utils: 29.7.0 jest-message-util: 29.7.0 - jest-runtime: 29.7.0 - jest-snapshot: 29.7.0 + jest-runtime: 29.7.0(supports-color@8.1.1) + jest-snapshot: 29.7.0(supports-color@8.1.1) jest-util: 29.7.0 p-limit: 3.1.0 pretty-format: 29.7.0 @@ -3824,16 +3987,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@25.8.0): + jest-cli@29.7.0(@types/node@25.8.0)(supports-color@8.1.1): dependencies: - '@jest/core': 29.7.0 + '@jest/core': 29.7.0(supports-color@8.1.1) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@25.8.0) + create-jest: 29.7.0(@types/node@25.8.0)(supports-color@8.1.1) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@25.8.0) + jest-config: 29.7.0(@types/node@25.8.0)(supports-color@8.1.1) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -3843,23 +4006,23 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@25.8.0): + jest-config@29.7.0(@types/node@25.8.0)(supports-color@8.1.1): dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.29.0) + babel-jest: 29.7.0(@babel/core@7.29.0(supports-color@8.1.1))(supports-color@8.1.1) chalk: 4.1.2 ci-info: 3.9.0 deepmerge: 4.3.1 glob: 7.2.3 graceful-fs: 4.2.11 - jest-circus: 29.7.0 + jest-circus: 29.7.0(supports-color@8.1.1) jest-environment-node: 29.7.0 jest-get-type: 29.6.3 jest-regex-util: 29.6.3 jest-resolve: 29.7.0 - jest-runner: 29.7.0 + jest-runner: 29.7.0(supports-color@8.1.1) jest-util: 29.7.0 jest-validate: 29.7.0 micromatch: 4.0.8 @@ -3971,10 +4134,10 @@ snapshots: jest-regex-util@29.6.3: {} - jest-resolve-dependencies@29.7.0: + jest-resolve-dependencies@29.7.0(supports-color@8.1.1): dependencies: jest-regex-util: 29.6.3 - jest-snapshot: 29.7.0 + jest-snapshot: 29.7.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -3990,12 +4153,12 @@ snapshots: resolve.exports: 2.0.3 slash: 3.0.0 - jest-runner@29.7.0: + jest-runner@29.7.0(supports-color@8.1.1): dependencies: '@jest/console': 29.7.0 '@jest/environment': 29.7.0 '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 + '@jest/transform': 29.7.0(supports-color@8.1.1) '@jest/types': 29.6.3 '@types/node': 25.8.0 chalk: 4.1.2 @@ -4007,7 +4170,7 @@ snapshots: jest-leak-detector: 29.7.0 jest-message-util: 29.7.0 jest-resolve: 29.7.0 - jest-runtime: 29.7.0 + jest-runtime: 29.7.0(supports-color@8.1.1) jest-util: 29.7.0 jest-watcher: 29.7.0 jest-worker: 29.7.0 @@ -4016,14 +4179,14 @@ snapshots: transitivePeerDependencies: - supports-color - jest-runtime@29.7.0: + jest-runtime@29.7.0(supports-color@8.1.1): dependencies: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 - '@jest/globals': 29.7.0 + '@jest/globals': 29.7.0(supports-color@8.1.1) '@jest/source-map': 29.6.3 '@jest/test-result': 29.7.0 - '@jest/transform': 29.7.0 + '@jest/transform': 29.7.0(supports-color@8.1.1) '@jest/types': 29.6.3 '@types/node': 25.8.0 chalk: 4.1.2 @@ -4036,24 +4199,24 @@ snapshots: jest-mock: 29.7.0 jest-regex-util: 29.6.3 jest-resolve: 29.7.0 - jest-snapshot: 29.7.0 + jest-snapshot: 29.7.0(supports-color@8.1.1) jest-util: 29.7.0 slash: 3.0.0 strip-bom: 4.0.0 transitivePeerDependencies: - supports-color - jest-snapshot@29.7.0: + jest-snapshot@29.7.0(supports-color@8.1.1): dependencies: - '@babel/core': 7.29.0 + '@babel/core': 7.29.0(supports-color@8.1.1) '@babel/generator': 7.29.1 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0(supports-color@8.1.1)) '@babel/types': 7.29.0 '@jest/expect-utils': 29.7.0 - '@jest/transform': 29.7.0 + '@jest/transform': 29.7.0(supports-color@8.1.1) '@jest/types': 29.6.3 - babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0(supports-color@8.1.1)) chalk: 4.1.2 expect: 29.7.0 graceful-fs: 4.2.11 @@ -4104,12 +4267,12 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@25.8.0): + jest@29.7.0(@types/node@25.8.0)(supports-color@8.1.1): dependencies: - '@jest/core': 29.7.0 + '@jest/core': 29.7.0(supports-color@8.1.1) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@25.8.0) + jest-cli: 29.7.0(@types/node@25.8.0)(supports-color@8.1.1) transitivePeerDependencies: - '@types/node' - babel-plugin-macros diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 4ff9cc2c..707cee82 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1 +1,4 @@ minimumReleaseAge: 4320 +patchedDependencies: + babel-test@0.2.4: patches/babel-test@0.2.4.patch +virtualStoreType: global diff --git a/scripts/bench/build-ref.js b/scripts/bench/build-ref.js new file mode 100644 index 00000000..93ce049c --- /dev/null +++ b/scripts/bench/build-ref.js @@ -0,0 +1,72 @@ +const fs = require('fs') +const path = require('path') +const { spawnSync } = require('child_process') +const { ROOT } = require('./shared') + +const BABEL_BIN = path.join(ROOT, 'node_modules/.bin/babel') +const MAX_ARCHIVE_BYTES = 64 * 1024 * 1024 + +const fail = (message, result) => { + const detail = result.error + ? result.error.message + : String(result.stderr || '').trim() + throw new Error(`${message}\n${detail}`) +} + +// The built plugin requires its dependencies by bare name, and the temp dir has no node_modules of its own. +const linkDependencies = dir => + fs.symlinkSync( + path.join(ROOT, 'node_modules'), + path.join(dir, 'node_modules'), + 'dir' + ) + +// Compiles /src to /lib with the repo's .babelrc, which the temp dir cannot discover on its own. +const compile = dir => { + linkDependencies(dir) + const result = spawnSync( + BABEL_BIN, + [ + path.join(dir, 'src'), + '-d', + path.join(dir, 'lib'), + '--no-babelrc', + '--config-file', + path.join(ROOT, '.babelrc'), + ], + { cwd: ROOT, encoding: 'utf8' } + ) + if (result.status !== 0) + fail(`Babel failed to compile src for the bench build in ${dir}`, result) + const entry = path.join(dir, 'lib/index.js') + if (!fs.existsSync(entry)) throw new Error(`Babel produced no ${entry}`) + return entry +} + +// Builds the plugin as it stood at a git ref. Pass any ref git resolves locally (a branch, tag, or sha). +const buildRef = (ref, dir) => { + fs.mkdirSync(dir, { recursive: true }) + const archive = spawnSync('git', ['archive', ref, 'src'], { + cwd: ROOT, + maxBuffer: MAX_ARCHIVE_BYTES, + }) + if (archive.status !== 0) { + fail( + `git archive could not read src at ref "${ref}". Fetch it or pass a ref that exists locally with --base.`, + archive + ) + } + const extract = spawnSync('tar', ['-x', '-C', dir], { input: archive.stdout }) + if (extract.status !== 0) + fail(`tar could not extract src for ref "${ref}" into ${dir}`, extract) + return compile(dir) +} + +// Builds the plugin from the working tree's src. Copies src first so the repo's lib/ is never written. +const buildWorkingTree = dir => { + fs.mkdirSync(dir, { recursive: true }) + fs.cpSync(path.join(ROOT, 'src'), path.join(dir, 'src'), { recursive: true }) + return compile(dir) +} + +module.exports = { buildRef, buildWorkingTree } diff --git a/scripts/bench/gen.js b/scripts/bench/gen.js new file mode 100644 index 00000000..a9058cd1 --- /dev/null +++ b/scripts/bench/gen.js @@ -0,0 +1,117 @@ +const fs = require('fs') +const os = require('os') +const path = require('path') +const babel = require('@babel/core') +const jsxSyntax = require('@babel/plugin-syntax-jsx').default +const modulesCommonjs = + require('@babel/plugin-transform-modules-commonjs').default + +const css = i => ` + display: flex; /* layout */ + color: ${'${p => p.theme.c' + i + '}'}; + padding: ${i % 7}px ${'${gap}'}px; + // a line comment that should go + background: url("https://example.com/img${i}.png"); // trailing comment + &:hover { + opacity: 0.${i % 9}; + transform: translate(${i}px, calc(100% - ${'${off}'}px)); + } + @media (min-width: ${400 + i}px) { + grid-template-columns: repeat(auto-fill, minmax(${i}px, 1fr)); + } +` + +function file(nStyled, nCss, nOther) { + const lines = [ + `import React, { useState, useEffect } from 'react'`, + `import styled, { css, keyframes, createGlobalStyle, withTheme } from 'styled-components'`, + `import { format } from './util'`, + `const gap = 4, off = 2`, + 'const spin = keyframes`from { transform: rotate(0deg); } to { transform: rotate(360deg); }`', + 'const Global = createGlobalStyle`body { margin: 0; }`', + ] + for (let i = 0; i < nStyled; i++) { + switch (i % 5) { + case 0: + lines.push(`export const Box${i} = styled.div\`${css(i)}\``) + break + case 1: + lines.push( + `const Btn${i} = styled(Box${i - 1}).attrs({ role: 'button' })\`${css(i)}\`` + ) + break + case 2: + lines.push(`const mixin${i} = css\`${css(i)}\``) + break + case 3: + lines.push( + `const Obj${i} = styled.span({ color: 'red', padding: ${i} })` + ) + break + case 4: + lines.push( + `const Cfg${i} = styled.a.withConfig({ shouldForwardProp: p => p !== 'x' })\`${css(i)}\`` + ) + break + } + } + for (let i = 0; i < nOther; i++) { + lines.push(`function helper${i}(a, b) { + const [s, setS] = useState(a) + useEffect(() => { setS(format(b, a.map(x => x * ${i}).filter(Boolean).join(','))) }, [a, b]) + return Math.max(...[a.length, b.length, ${i}].map(Number)) + JSON.parse(JSON.stringify({ v: s })).v +}`) + } + for (let i = 0; i < nCss; i++) { + switch (i % 4) { + case 0: + lines.push(`const P${i} = p =>

A

`) + break + case 1: + lines.push( + `const T${i} = p =>
{p.children}
` + ) + break + case 2: + lines.push( + `const O${i} = p => x` + ) + break + case 3: + lines.push( + `const C${i} = p => y` + ) + break + } + } + lines.push( + `export default function App() { return <>${nStyled ? '' : ''} }` + ) + return lines.join('\n') + '\n' +} + +// Writes small.js, medium.js, large.js, and large-cjs.js (large after the commonjs transform) into dir. +// Returns the written file names. +function generateInputs(dir) { + const write = (name, code) => fs.writeFileSync(path.join(dir, name), code) + write('small.js', file(10, 2, 5)) + write('medium.js', file(60, 12, 40)) + const large = file(400, 80, 300) + write('large.js', large) + const cjs = babel.transformSync(large, { + babelrc: false, + configFile: false, + filename: 'large.js', + plugins: [jsxSyntax, modulesCommonjs], + }).code + write('large-cjs.js', cjs) + return fs.readdirSync(dir).sort() +} + +if (require.main === module) { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'styled-bench-inputs-')) + generateInputs(dir) + console.log(dir) +} + +module.exports = { generateInputs } diff --git a/scripts/bench/index.js b/scripts/bench/index.js new file mode 100644 index 00000000..1285ef1d --- /dev/null +++ b/scripts/bench/index.js @@ -0,0 +1,170 @@ +// A/B benchmark: transforms each generated input with the base ref's plugin and the working tree's plugin, +// interleaved, and reports each plugin's cost as its median time minus the median time with no plugin. +// Run with `pnpm bench`. Flags: --base , --config default|all|bare, --rounds , --normalize-aliases. +const fs = require('fs') +const path = require('path') +const { parseArgs } = require('util') +const { generateInputs } = require('./gen') +const { buildRef, buildWorkingTree } = require('./build-ref') +const { CONFIGS, transform, withWorkDir } = require('./shared') + +const parseOptions = argv => { + const { values } = parseArgs({ + args: argv, + strict: true, + options: { + base: { type: 'string', default: 'origin/main' }, + config: { type: 'string', default: 'default' }, + rounds: { type: 'string', default: '12' }, + 'normalize-aliases': { type: 'boolean', default: false }, + }, + }) + if (!Object.hasOwn(CONFIGS, values.config)) { + throw new Error( + `Unknown --config "${values.config}". Use one of: ${Object.keys(CONFIGS).join(', ')}.` + ) + } + if (!/^[1-9]\d*$/.test(values.rounds)) { + throw new Error( + `--rounds takes a positive whole number, got "${values.rounds}".` + ) + } + return { + base: values.base, + config: values.config, + rounds: Number(values.rounds), + normalizeAliases: values['normalize-aliases'], + } +} + +// Collapses the `_styledN` default-import aliases the plugin injects, so variants compare by shape. +const normalizeAliases = code => + code + .replace( + /import _styled(, \{[^}]*\})? from "styled-components";/, + 'import _styled from "styled-components";' + ) + .replace(/\b_styled\d+\(/g, '_styled(') + +const shuffle = xs => { + for (let i = xs.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)) + ;[xs[i], xs[j]] = [xs[j], xs[i]] + } + return xs +} + +const median = xs => { + const sorted = [...xs].sort((a, b) => a - b) + return sorted[Math.floor(sorted.length / 2)] +} + +const firstDifference = (left, right) => { + const a = left.split('\n') + const b = right.split('\n') + const index = a.findIndex((line, i) => line !== b[i]) + return { + line: index + 1, + left: a[index] ?? '(end of output)', + right: b[index] ?? '(end of output)', + } +} + +const timeTransform = (code, plugin, config) => { + const start = process.hrtime.bigint() + transform({ code, plugin, config, generate: false }) + return Number(process.hrtime.bigint() - start) / 1e6 +} + +const reportMismatch = ({ options, input, base, current, diff, aliasOnly }) => { + const hint = + aliasOnly && !options.normalizeAliases + ? ' The only difference is the _styledN alias names, so re-run with --normalize-aliases.' + : '' + console.error( + `${options.config} ${input}: ${current} output differs from ${base} at line ${diff.line}.${hint}\n` + + ` ${base}: ${diff.left}\n` + + ` ${current}: ${diff.right}` + ) + process.exitCode = 1 +} + +const benchInput = ({ options, input, code, variants }) => { + const config = CONFIGS[options.config] + const generated = variants.map( + variant => + transform({ code, plugin: variant.plugin, config, generate: true }).code + ) + const outputs = options.normalizeAliases + ? generated.map(normalizeAliases) + : generated + const mismatch = outputs.findIndex(output => output !== outputs[0]) + if (mismatch !== -1) { + reportMismatch({ + options, + input, + base: variants[0].name, + current: variants[mismatch].name, + diff: firstDifference(outputs[0], outputs[mismatch]), + aliasOnly: + normalizeAliases(generated[mismatch]) === + normalizeAliases(generated[0]), + }) + return false + } + + const iters = Math.max(3, Math.round(400000 / code.length)) + const arms = [{ name: 'no-plugin', plugin: null }, ...variants] + const samples = arms.map(() => []) + for (let w = 0; w < iters; w++) { + arms.forEach(arm => timeTransform(code, arm.plugin, config)) + } + for (let r = 0; r < options.rounds; r++) { + for (const i of shuffle(arms.map((_, index) => index))) { + for (let k = 0; k < iters; k++) { + samples[i].push(timeTransform(code, arms[i].plugin, config)) + } + } + } + + const base = median(samples[0]) + const line = arms + .map((arm, i) => { + const m = median(samples[i]) + return i === 0 + ? `${arm.name} ${m.toFixed(3)}ms` + : `${arm.name} +${(m - base).toFixed(3)}ms` + }) + .join(' | ') + console.log(`${options.config} ${input.padEnd(13)} ${line}`) + return true +} + +const main = () => + withWorkDir(async workDir => { + const options = parseOptions(process.argv.slice(2)) + const inputDir = path.join(workDir, 'inputs') + fs.mkdirSync(inputDir) + const inputs = generateInputs(inputDir) + const variants = [ + { + name: 'base', + plugin: require(buildRef(options.base, path.join(workDir, 'base'))) + .default, + }, + { + name: 'current', + plugin: require(buildWorkingTree(path.join(workDir, 'current'))) + .default, + }, + ] + for (const input of inputs) { + const code = fs.readFileSync(path.join(inputDir, input), 'utf8') + if (!benchInput({ options, input, code, variants })) return + } + }) + +main().catch(error => { + console.error(error.message) + process.exitCode = 1 +}) diff --git a/scripts/bench/profile.js b/scripts/bench/profile.js new file mode 100644 index 00000000..a0a4f329 --- /dev/null +++ b/scripts/bench/profile.js @@ -0,0 +1,193 @@ +// CPU profile of the working tree's plugin over one generated input. Run with `pnpm bench:profile`. +// Flags: --config default|all|bare, --input small|medium|large|large-cjs, --runs , --target . +// The .cpuprofile is kept in a temp dir that is printed, so it can be opened in Chrome DevTools. +const fs = require('fs') +const inspector = require('inspector') +const os = require('os') +const path = require('path') +const { pathToFileURL } = require('url') +const { parseArgs } = require('util') +const { generateInputs } = require('./gen') +const { buildWorkingTree } = require('./build-ref') +const { CONFIGS, transform, withWorkDir } = require('./shared') + +const INPUTS = ['small', 'medium', 'large', 'large-cjs'] +const SAMPLING_INTERVAL_US = 100 +const TOP_ROWS = 25 + +const parseOptions = argv => { + const { values } = parseArgs({ + args: argv, + strict: true, + options: { + config: { type: 'string', default: 'default' }, + input: { type: 'string', default: 'large' }, + runs: { type: 'string', default: '20' }, + target: { type: 'string', default: '@babel/traverse' }, + }, + }) + if (!Object.hasOwn(CONFIGS, values.config)) { + throw new Error( + `Unknown --config "${values.config}". Use one of: ${Object.keys(CONFIGS).join(', ')}.` + ) + } + if (!INPUTS.includes(values.input)) { + throw new Error( + `Unknown --input "${values.input}". Use one of: ${INPUTS.join(', ')}.` + ) + } + if (!/^[1-9]\d*$/.test(values.runs)) { + throw new Error( + `--runs takes a positive whole number, got "${values.runs}".` + ) + } + return { + config: values.config, + input: values.input, + runs: Number(values.runs), + target: values.target, + } +} + +const startSession = async () => { + const session = new inspector.Session() + session.connect() + const post = (method, params = {}) => + new Promise((resolve, reject) => { + session.post(method, params, (error, result) => + error ? reject(error) : resolve(result) + ) + }) + await post('Profiler.enable') + await post('Profiler.setSamplingInterval', { interval: SAMPLING_INTERVAL_US }) + return { post, disconnect: () => session.disconnect() } +} + +// Indexes the profile for parent walks and per-frame sample counts. +const indexProfile = profile => { + const byId = new Map(profile.nodes.map(node => [node.id, node])) + const parent = new Map() + profile.nodes.forEach(node => + (node.children || []).forEach(child => parent.set(child, node.id)) + ) + const hits = new Map() + profile.samples.forEach(id => hits.set(id, (hits.get(id) || 0) + 1)) + return { byId, parent, hits, total: profile.samples.length } +} + +// Frames inside the built plugin are labeled relative to it. Everything else is labeled by its package path. +const frameLabel = (node, pluginUrl) => { + const { functionName, url, lineNumber } = node.callFrame + const file = url.startsWith(pluginUrl) + ? `plugin/${url.slice(pluginUrl.length)}` + : url.replace(/^file:\/\//, '').replace(/^.*\/node_modules\//, '') + return `${functionName || '(anon)'} ${file}:${lineNumber + 1}` +} + +const addCount = (map, key, count) => map.set(key, (map.get(key) || 0) + count) + +const selfTimes = (index, label) => { + const totals = new Map() + for (const [id, count] of index.hits) + addCount(totals, label(index.byId.get(id)), count) + return totals +} + +const inclusiveTimes = (index, label) => { + const totals = new Map() + for (const [id, count] of index.hits) { + const seen = new Set() + for (let cur = id; cur !== undefined; cur = index.parent.get(cur)) { + const key = label(index.byId.get(cur)) + if (seen.has(key)) continue + seen.add(key) + addCount(totals, key, count) + } + } + return totals +} + +// Attributes each sample under a frame matching target to the nearest plugin frame above that frame. +const nearestPluginCallers = (index, { target, label, pluginUrl }) => { + const callers = new Map() + for (const [id, count] of index.hits) { + let hit = false + let caller = '(no plugin frame)' + for (let cur = id; cur !== undefined; cur = index.parent.get(cur)) { + const node = index.byId.get(cur) + if (!hit) { + hit = label(node).includes(target) + } else if (node.callFrame.url.startsWith(pluginUrl)) { + caller = label(node) + break + } + } + if (hit) addCount(callers, caller, count) + } + return callers +} + +const printRanked = (title, totals, total) => { + console.log(`\n== ${title} (of ${total} samples)`) + ;[...totals] + .sort((a, b) => b[1] - a[1]) + .slice(0, TOP_ROWS) + .forEach(([key, count]) => + console.log(`${((100 * count) / total).toFixed(1).padStart(5)}% ${key}`) + ) +} + +const summarize = (profile, { target, pluginUrl }) => { + const index = indexProfile(profile) + const label = node => frameLabel(node, pluginUrl) + printRanked('self time', selfTimes(index, label), index.total) + printRanked('inclusive time', inclusiveTimes(index, label), index.total) + printRanked( + `nearest plugin caller of "${target}"`, + nearestPluginCallers(index, { target, label, pluginUrl }), + index.total + ) +} + +const main = () => + withWorkDir(async workDir => { + const options = parseOptions(process.argv.slice(2)) + const inputDir = path.join(workDir, 'inputs') + fs.mkdirSync(inputDir) + generateInputs(inputDir) + const libEntry = buildWorkingTree(path.join(workDir, 'current')) + const plugin = require(libEntry).default + // The profiler reports realpaths, so the plugin prefix must be a realpath too (macOS tmp is a symlink). + const pluginUrl = pathToFileURL( + `${fs.realpathSync(path.dirname(libEntry))}${path.sep}` + ).href + const code = fs.readFileSync( + path.join(inputDir, `${options.input}.js`), + 'utf8' + ) + const config = CONFIGS[options.config] + const runOnce = () => transform({ code, plugin, config, generate: false }) + + runOnce() + const { post, disconnect } = await startSession() + await post('Profiler.start') + for (let i = 0; i < options.runs; i++) runOnce() + const { profile } = await post('Profiler.stop') + disconnect() + + const profileDir = fs.mkdtempSync( + path.join(os.tmpdir(), 'styled-bench-profile-') + ) + const profilePath = path.join( + profileDir, + `${options.config}-${options.input}.cpuprofile` + ) + fs.writeFileSync(profilePath, JSON.stringify(profile)) + console.log(`profile ${profilePath}`) + summarize(profile, { target: options.target, pluginUrl }) + }) + +main().catch(error => { + console.error(error.message) + process.exitCode = 1 +}) diff --git a/scripts/bench/shared.js b/scripts/bench/shared.js new file mode 100644 index 00000000..3b08af89 --- /dev/null +++ b/scripts/bench/shared.js @@ -0,0 +1,60 @@ +const fs = require('fs') +const os = require('os') +const path = require('path') +const babel = require('@babel/core') +const jsxSyntax = require('@babel/plugin-syntax-jsx').default + +const ROOT = path.resolve(__dirname, '../..') + +// Plugin options each named config passes to both variants. +const CONFIGS = { + default: {}, + all: { + pure: true, + topLevelImportPaths: ['@acme/styled', '@acme/*/css'], + namespace: 'ns', + }, + bare: { + ssr: false, + displayName: false, + minify: false, + transpileTemplateLiterals: false, + }, +} + +// Inputs are transformed as if they lived here, so path-derived output (file hashes, display names) stays stable. +const VIRTUAL_FILENAME = path.join(ROOT, 'bench-src', 'components', 'Large.js') + +// Runs one transform. A null plugin measures the parser and generator alone. +const transform = ({ code, plugin, config, generate }) => + babel.transformSync(code, { + babelrc: false, + configFile: false, + filename: VIRTUAL_FILENAME, + code: generate, + ast: false, + plugins: plugin ? [[plugin, config]] : [jsxSyntax], + }) + +const makeWorkDir = () => + fs.mkdtempSync(path.join(os.tmpdir(), 'styled-bench-')) + +const removeDir = dir => fs.rmSync(dir, { recursive: true, force: true }) + +// Runs body with a fresh work dir that is removed on return, on a thrown error, and on SIGINT or SIGTERM. +const withWorkDir = async body => { + const workDir = makeWorkDir() + const exitAfterCleanup = code => () => { + removeDir(workDir) + process.exit(code) + } + process.once('SIGINT', exitAfterCleanup(130)) + process.once('SIGTERM', exitAfterCleanup(143)) + try { + return await body(workDir) + } finally { + removeDir(workDir) + } +} + +module.exports = { CONFIGS, ROOT, transform, withWorkDir } diff --git a/src/index.js b/src/index.js index 53d97e48..78e77f18 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,10 @@ import syntax from '@babel/plugin-syntax-jsx' import assignStyledRequired from './visitors/assignStyledRequired' import transpileCssProp from './visitors/transpileCssProp' -import { processCallExpression, processTaggedTemplate } from './visitors/process' +import { + processCallExpression, + processTaggedTemplate, +} from './visitors/process' export default function ({ types: t }) { const transpileCssPropVisit = transpileCssProp(t) diff --git a/src/minify/index.js b/src/minify/index.js index d3d610b3..28606bbd 100644 --- a/src/minify/index.js +++ b/src/minify/index.js @@ -20,73 +20,83 @@ const injectUniquePlaceholders = strArr => { const makeMultilineCommentRegex = newlinePattern => new RegExp('\\/\\*[^!](.|' + newlinePattern + ')*?\\*\\/', 'g') -const lineCommentStart = /\/\//g const symbolRegex = /(\s*[;:{},]\s*)/g -// Counts occurences of substr inside str -const countOccurences = (str, substr) => str.split(substr).length - 1 - -// Counts parentheses left open inside str. A closing one without a match in str -// ends a value opened on a previous line (e.g. `);` after `minmax(`), so it's ignored -const countOpenParens = str => { - let depth = 0 - +// Returns the quote character still open at the end of str, given the one open +// at its start ('' when outside a string). A backslash escapes the next character +const quoteOpenAfter = (str, quote) => { for (let i = 0; i < str.length; i++) { - if (str[i] === '(') { - depth++ - } else if (str[i] === ')' && depth > 0) { - depth-- + const char = str[i] + + if (char === '\\') { + i++ + } else if (quote) { + if (char === quote) { + quote = '' + } + } else if (char === '"' || char === "'") { + quote = char } } - return depth + return quote } -// Joins substrings until predicate returns true -const reduceSubstr = (substrs, join, predicate) => { - const length = substrs.length - let res = substrs[0] - - if (length === 1) { - return res +// Cuts the line at the first `//` the styled-components runtime treats as a +// comment: outside strings and parentheses, and not directly after a colon +// (`https://`). A `)` without a match on the line closes a value opened on a +// previous line (e.g. `);` after `minmax(`), so it's ignored +export const stripLineComment = line => { + if (line.indexOf('//') === -1) { + return line } - for (let i = 1; i < length; i++) { - if (predicate(res)) { - break + let depth = 0 + let quote = '' + + for (let i = 0; i < line.length; i++) { + const char = line[i] + + if (char === '\\') { + i++ + } else if (quote) { + if (char === quote) { + quote = '' + } + } else if (char === '"' || char === "'") { + quote = char + } else if (char === '(') { + depth++ + } else if (char === ')') { + if (depth > 0) { + depth-- + } + } else if ( + char === '/' && + line[i + 1] === '/' && + depth === 0 && + line[i - 1] !== ':' + ) { + return line.slice(0, i) } - - res += join + substrs[i] } - return res + return line } -// Joins at comment starts when it's inside a string or parantheses -// effectively removing line comments -export const stripLineComment = line => - reduceSubstr( - line.split(lineCommentStart), - '//', - str => - !str.endsWith(':') && // NOTE: This is another guard against urls, if they're not inside strings or parantheses. - countOccurences(str, "'") % 2 === 0 && - countOccurences(str, '"') % 2 === 0 && - countOpenParens(str) === 0 - ) +export const compressSymbols = code => { + let quote = '' -export const compressSymbols = code => - code.split(symbolRegex).reduce((str, fragment, index) => { + return code.split(symbolRegex).reduce((str, fragment, index) => { // Even-indices are non-symbol fragments if (index % 2 === 0) { + quote = quoteOpenAfter(fragment, quote) + return str + fragment } // Only manipulate symbols outside of strings - if ( - countOccurences(str, "'") % 2 !== 0 || - countOccurences(str, '"') % 2 !== 0 - ) { + if (quote) { return str + fragment } @@ -97,6 +107,7 @@ export const compressSymbols = code => return str + fragment.trim() }, '') +} // Detects lines that are exclusively line comments const isLineComment = line => line.trim().startsWith('//') diff --git a/src/utils/detectors.js b/src/utils/detectors.js index b9105d81..219edf12 100644 --- a/src/utils/detectors.js +++ b/src/utils/detectors.js @@ -1,80 +1,126 @@ import getSequenceExpressionValue from './getSequenceExpressionValue' import { useTopLevelImportPathMatchers } from './options' -const VALID_TOP_LEVEL_IMPORT_PATH_MATCHERS = [ +const VALID_TOP_LEVEL_IMPORT_PATHS = new Set([ 'styled-components', 'styled-components/no-tags', 'styled-components/native', 'styled-components/primitives', -].map(literal => x => x === literal) +]) -export const isValidTopLevelImport = (x, state) => { - return [ - ...VALID_TOP_LEVEL_IMPORT_PATH_MATCHERS, - ...useTopLevelImportPathMatchers(state), - ].some(isMatch => isMatch(x)) -} +export const isValidTopLevelImport = (x, state) => + VALID_TOP_LEVEL_IMPORT_PATHS.has(x) || + useTopLevelImportPathMatchers(state).some(isMatch => isMatch(x)) + +// `import type` and `import typeof` (declaration or specifier) bind no value +const isTypeOnly = node => + node.importKind === 'type' || node.importKind === 'typeof' const IMPORT_NAMES_CACHE = 'styled-components-import-local-names' +const INJECTED_DEFAULT_NAME = 'styled-components-injected-default-name' -export const importLocalName = (name, state, options = {}) => { - const { bypassCache = false } = options +// The default binding the css-prop transform injected, which can be a +// `require` (script files) that no ImportDeclaration scan would find +export const injectedDefaultName = state => + state.file.get(INJECTED_DEFAULT_NAME) + +export const registerInjectedDefault = (state, name) => { + state.file.set(INJECTED_DEFAULT_NAME, name) + state.file.set(IMPORT_NAMES_CACHE, null) +} +export const importLocalName = (name, state) => { if (state.customImportName) return state.customImportName.name let cache = state.file.get(IMPORT_NAMES_CACHE) - if (!cache || bypassCache) { + if (!cache) { cache = {} state.file.set(IMPORT_NAMES_CACHE, cache) } - if (!bypassCache && name in cache) return cache[name] + if (name in cache) return cache[name] let localName = state.styledRequired ? name === 'default' - ? 'styled' + ? state.styledRequired : name : false - state.file.path.traverse({ - ImportDeclaration: { - exit(path) { - const { node } = path - if (!isValidTopLevelImport(node.source.value, state)) return - - for (const specifier of path.get('specifiers')) { - if ( - specifier.isImportSpecifier() && - specifier.node.imported.name === 'styled' - ) { - localName = 'styled' - } - - if (specifier.isImportDefaultSpecifier()) { - localName = specifier.node.local.name - } - - if ( - specifier.isImportSpecifier() && - specifier.node.imported.name === name - ) { - localName = specifier.node.local.name - } - - // The namespace binding isn't directly callable, so prefer any - // other specifier (default or named) discovered in this file. - if (specifier.isImportNamespaceSpecifier() && !localName) { - localName = name === 'default' ? specifier.node.local.name : name - } - } - }, - }, - }) + // Only top-level declarations are real bindings; imports inside TypeScript + // and Flow `declare module` or `declare namespace` bodies are ambient + for (const node of state.file.path.node.body) { + if (node.type !== 'ImportDeclaration') continue + if (isTypeOnly(node)) continue + if (!isValidTopLevelImport(node.source.value, state)) continue + + for (const specifier of node.specifiers) { + if (isTypeOnly(specifier)) continue + + if ( + name === 'default' && + specifier.type === 'ImportSpecifier' && + specifier.imported.name === 'styled' + ) { + localName = specifier.local.name + } + + if (specifier.type === 'ImportDefaultSpecifier') { + localName = specifier.local.name + } + + if ( + specifier.type === 'ImportSpecifier' && + specifier.imported.name === name + ) { + localName = specifier.local.name + } + + // The namespace binding isn't directly callable, so prefer any + // other specifier (default or named) discovered in this file. + if (specifier.type === 'ImportNamespaceSpecifier' && !localName) { + localName = name === 'default' ? specifier.local.name : name + } + } + } + + if (name === 'default') { + const injected = injectedDefaultName(state) + if (injected) localName = injected + } cache[name] = localName return localName } +const NAMESPACE_NAMES_CACHE = 'styled-components-import-namespace-names' +const NO_NAMESPACE_NAMES = [] + +// Local names bound by `import * as X from 'styled-components'`. Kept apart +// from `importLocalName('default')` so a default import injected later (the +// css-prop transform) does not displace the namespace binding. +const importNamespaceNames = state => { + if (state.customImportName) return NO_NAMESPACE_NAMES + + let names = state.file.get(NAMESPACE_NAMES_CACHE) + if (names) return names + + names = [] + for (const node of state.file.path.node.body) { + if (node.type !== 'ImportDeclaration') continue + if (isTypeOnly(node)) continue + if (!isValidTopLevelImport(node.source.value, state)) continue + + for (const specifier of node.specifiers) { + if (specifier.type === 'ImportNamespaceSpecifier') { + names.push(specifier.local.name) + } + } + } + + state.file.set(NAMESPACE_NAMES_CACHE, names) + return names +} + // Follow `const X = Y` (and TS `const X = Y as Type`) chains so a local // re-binding of the styled import still resolves to it. Lazy: only walks // the chain when the direct-name check misses, so the hot path is unchanged. @@ -106,6 +152,22 @@ const resolvesToDefaultLocal = (name, defaultLocal, state, t) => { } } +// With only a styled `require` in the file, the default name is the required +// variable. A global `styled` (a bundler-provided identifier) keeps resolving +// as the default too. +const globalStyledAlias = (state, defaultLocal) => + state.styledRequired && defaultLocal === state.styledRequired + ? 'styled' + : null + +// `ns.default`: the callable default export reached through a namespace +const isNamespaceDefault = (t, node, defaultLocal, state) => + t.isMemberExpression(node) && + node.property.name === 'default' && + (node.object.name === defaultLocal || + node.object.name === globalStyledAlias(state, defaultLocal) || + importNamespaceNames(state).includes(node.object.name)) + export const isStyled = t => (tag, state) => { if ( t.isCallExpression(tag) && @@ -122,10 +184,13 @@ export const isStyled = t => (tag, state) => { return isStyled(t)(getSequenceExpressionValue(tag.callee), state) } else { const defaultLocal = importLocalName('default', state) + const alias = globalStyledAlias(state, defaultLocal) const matchesDefault = name => - resolvesToDefaultLocal(name, defaultLocal, state, t) + resolvesToDefaultLocal(name, defaultLocal, state, t) || + resolvesToDefaultLocal(name, alias, state, t) return ( (t.isMemberExpression(tag) && + tag.property.name !== 'default' && matchesDefault(tag.object.name) && !isHelper(t)(tag.property, state)) || (t.isCallExpression(tag) && matchesDefault(tag.callee.name)) || @@ -150,11 +215,18 @@ export const isStyled = t => (tag, state) => { getSequenceExpressionValue(tag.callee).property.name === 'default' && getSequenceExpressionValue(tag.callee).object.name === state.styledRequired) || - (defaultLocal && - t.isMemberExpression(tag) && - t.isMemberExpression(tag.object) && - tag.object.property.name === 'default' && - tag.object.object.name === defaultLocal) + (t.isMemberExpression(tag) && + isNamespaceDefault(t, tag.object, defaultLocal, state)) || + (t.isCallExpression(tag) && + isNamespaceDefault(t, tag.callee, defaultLocal, state)) || + (t.isCallExpression(tag) && + t.isSequenceExpression(tag.callee) && + isNamespaceDefault( + t, + getSequenceExpressionValue(tag.callee), + defaultLocal, + state + )) ) } } diff --git a/src/utils/options.js b/src/utils/options.js index cd607a2b..ee338260 100644 --- a/src/utils/options.js +++ b/src/utils/options.js @@ -1,4 +1,4 @@ -import pm from 'picomatch'; +import pm from 'picomatch' function getOption({ opts }, name, defaultValue = true) { return opts[name] === undefined || opts[name] === null @@ -7,11 +7,27 @@ function getOption({ opts }, name, defaultValue = true) { } export const useDisplayName = state => getOption(state, 'displayName') -export const useTopLevelImportPathMatchers = state => - getOption(state, 'topLevelImportPaths', []).map(pattern => pm(pattern)) + +const NO_PATTERNS = [] +// Compiled matchers per topLevelImportPaths value, so each config compiles once +const compiledMatchers = new WeakMap() + +export const useTopLevelImportPathMatchers = state => { + const patterns = getOption(state, 'topLevelImportPaths', NO_PATTERNS) + let matchers = compiledMatchers.get(patterns) + + if (!matchers) { + matchers = patterns.map(pattern => pm(pattern)) + compiledMatchers.set(patterns, matchers) + } + + return matchers +} + export const useSSR = state => getOption(state, 'ssr', true) export const useFileName = state => getOption(state, 'fileName') -export const useMeaninglessFileNames = state => getOption(state, 'meaninglessFileNames', ['index']) +export const useMeaninglessFileNames = state => + getOption(state, 'meaninglessFileNames', ['index']) export const useMinify = state => getOption(state, 'minify') export const useTranspileTemplateLiterals = state => getOption(state, 'transpileTemplateLiterals') diff --git a/src/visitors/assignStyledRequired.js b/src/visitors/assignStyledRequired.js index c6bbcef7..2af37b40 100644 --- a/src/visitors/assignStyledRequired.js +++ b/src/visitors/assignStyledRequired.js @@ -1,7 +1,7 @@ import { isValidTopLevelImport } from '../utils/detectors' export default t => (path, state) => { - let init = path.node.init; + let init = path.node.init if ( t.isCallExpression(path.node.init) && @@ -13,7 +13,7 @@ export default t => (path, state) => { init.arguments && init.arguments[0] ) { - init = path.node.init.arguments[0]; + init = path.node.init.arguments[0] } if ( diff --git a/src/visitors/displayNameAndId.js b/src/visitors/displayNameAndId.js index 7f768655..8b59aeab 100644 --- a/src/visitors/displayNameAndId.js +++ b/src/visitors/displayNameAndId.js @@ -225,36 +225,39 @@ const taggedTagAlreadyConfigured = (t, path) => { return hasDisplayNameOrComponentId(t, firstArg.properties) } -export default t => (path, state) => { - if (taggedTagAlreadyConfigured(t, path)) return - if ( - path.node.tag - ? isStyled(t)(path.node.tag, state) - : /* styled()`` */ (isStyled(t)(path.node.callee, state) && - path.node.callee.property && - path.node.callee.property.name !== 'withConfig') || - // styled(x)({}) - (isStyled(t)(path.node.callee, state) && - !t.isMemberExpression(path.node.callee.callee)) || - // styled(x).attrs()({}) - (isStyled(t)(path.node.callee, state) && - t.isMemberExpression(path.node.callee.callee) && - path.node.callee.callee.property && - path.node.callee.callee.property.name && - path.node.callee.callee.property.name !== 'withConfig') || - // styled(x).withConfig({}) - (isStyled(t)(path.node.callee, state) && - t.isMemberExpression(path.node.callee.callee) && - path.node.callee.callee.property && - path.node.callee.callee.property.name && - path.node.callee.callee.property.name === 'withConfig' && - path.node.callee.arguments.length && - Array.isArray(path.node.callee.arguments[0].properties) && - !hasDisplayNameOrComponentId( - t, - path.node.callee.arguments[0].properties - )) - ) { +// Whether a styled callee still needs displayName/componentId +const calleeNeedsConfig = (t, callee) => { + // styled()`` + if (callee.property && callee.property.name !== 'withConfig') return true + // styled(x)({}) + if (!t.isMemberExpression(callee.callee)) return true + + const method = callee.callee.property && callee.callee.property.name + if (!method) return false + // styled(x).attrs()({}) + if (method !== 'withConfig') return true + + // styled(x).withConfig({}) + return ( + callee.arguments.length > 0 && + Array.isArray(callee.arguments[0].properties) && + !hasDisplayNameOrComponentId(t, callee.arguments[0].properties) + ) +} + +export default t => { + const isStyledNode = isStyled(t) + + return (path, state) => { + if (taggedTagAlreadyConfigured(t, path)) return + + const { tag, callee } = path.node + const needsConfig = tag + ? isStyledNode(tag, state) + : isStyledNode(callee, state) && calleeNeedsConfig(t, callee) + + if (!needsConfig) return + const displayName = useDisplayName(state) && getDisplayName(t)(path, useFileName(state) && state) diff --git a/src/visitors/transpileCssProp.js b/src/visitors/transpileCssProp.js index aeceaa6c..c34ac531 100644 --- a/src/visitors/transpileCssProp.js +++ b/src/visitors/transpileCssProp.js @@ -1,6 +1,10 @@ // Most of this code was taken from @satya164's babel-plugin-css-prop import { addDefault } from '@babel/helper-module-imports' -import { importLocalName } from '../utils/detectors' +import { + importLocalName, + injectedDefaultName, + registerInjectedDefault, +} from '../utils/detectors' import { useCssProp, useCssPropImportPath } from '../utils/options' import { processCallExpression, processTaggedTemplate } from './process' @@ -64,17 +68,27 @@ export default t => { importBinding && importBinding.path && importBinding.path.isImportNamespaceSpecifier() - // A namespace binding (`import * as styled from 'styled-components'`) is - // not directly callable, so treat it the same as "no default binding" and - // inject a fresh default import to use as the css-prop callee. - if (!importBinding || importBindingIsNamespace) { - addDefault(path, useCssPropImportPath(state), { - nameHint: 'styled', - }) + // A namespace binding (`import * as styled from 'styled-components'`) and a + // `require()` result are not directly callable, so treat them the same as + // "no default binding" and inject a fresh default import to use as the + // css-prop callee. + if ( + !importBinding || + importBindingIsNamespace || + importName === state.styledRequired + ) { + // The injected binding never becomes a program-scope binding, so later + // css props reuse the name resolved for the first injection + let injectedName = injectedDefaultName(state) + + if (!injectedName) { + injectedName = addDefault(path, useCssPropImportPath(state), { + nameHint: 'styled', + }).name + registerInjectedDefault(state, injectedName) + } - importName = t.identifier( - importLocalName('default', state, { bypassCache: true }) - ) + importName = t.identifier(injectedName) } if (!t.isIdentifier(importName)) importName = t.identifier(importName) diff --git a/test/fixtures/ambient-import-without-real-import/.babelrc b/test/fixtures/ambient-import-without-real-import/.babelrc new file mode 100644 index 00000000..b1823bfa --- /dev/null +++ b/test/fixtures/ambient-import-without-real-import/.babelrc @@ -0,0 +1,14 @@ +{ + "parserOpts": { + "plugins": ["typescript"] + }, + "plugins": [ + [ + "../../../src", + { + "transpileTemplateLiterals": false, + "ssr": true + } + ] + ] +} diff --git a/test/fixtures/ambient-import-without-real-import/code.js b/test/fixtures/ambient-import-without-real-import/code.js new file mode 100644 index 00000000..241edf1d --- /dev/null +++ b/test/fixtures/ambient-import-without-real-import/code.js @@ -0,0 +1,12 @@ +import { observer } from 'mobx-react' + +declare module 'my-theme' { + import styled from 'styled-components' + export const tag: typeof styled +} + +const styled = { div: observer } + +const Plain = styled.div` + color: red; +` diff --git a/test/fixtures/ambient-import-without-real-import/output.js b/test/fixtures/ambient-import-without-real-import/output.js new file mode 100644 index 00000000..b8d08107 --- /dev/null +++ b/test/fixtures/ambient-import-without-real-import/output.js @@ -0,0 +1,11 @@ +import { observer } from 'mobx-react'; +declare module 'my-theme' { + import styled from 'styled-components'; + export const tag: typeof styled; +} +const styled = { + div: observer +}; +const Plain = styled.div` + color: red; +`; diff --git a/test/fixtures/css-prop-on-namespaced-jsx-element/.babelrc b/test/fixtures/css-prop-on-namespaced-jsx-element/.babelrc new file mode 100644 index 00000000..90361e01 --- /dev/null +++ b/test/fixtures/css-prop-on-namespaced-jsx-element/.babelrc @@ -0,0 +1,14 @@ +{ + "plugins": [ + [ + "../../../src", + { + "ssr": false, + "displayName": false, + "transpileTemplateLiterals": false, + "minify": false, + "cssProp": true + } + ] + ] +} diff --git a/test/fixtures/css-prop-on-namespaced-jsx-element/code.js b/test/fixtures/css-prop-on-namespaced-jsx-element/code.js new file mode 100644 index 00000000..5b51759e --- /dev/null +++ b/test/fixtures/css-prop-on-namespaced-jsx-element/code.js @@ -0,0 +1 @@ +export default () => diff --git a/test/fixtures/css-prop-on-namespaced-jsx-element/error.js b/test/fixtures/css-prop-on-namespaced-jsx-element/error.js new file mode 100644 index 00000000..e9f955a6 --- /dev/null +++ b/test/fixtures/css-prop-on-namespaced-jsx-element/error.js @@ -0,0 +1,4 @@ +SyntaxError: /test/fixtures/css-prop-on-namespaced-jsx-element/code.js: Cannot infer name from node with type "JSXNamespacedName". Please submit an issue at https://github.com/styled-components/babel-plugin-styled-components with your code so we can take a look at your use case! +> 1 | export default () => + | ^^^^^^^^^^^^^^^^ + 2 | diff --git a/test/fixtures/css-prop-script-mode-interop-default-require/.babelrc b/test/fixtures/css-prop-script-mode-interop-default-require/.babelrc new file mode 100644 index 00000000..e7cd3222 --- /dev/null +++ b/test/fixtures/css-prop-script-mode-interop-default-require/.babelrc @@ -0,0 +1,15 @@ +{ + "sourceType": "script", + "plugins": [ + [ + "../../../src", + { + "displayName": true, + "fileName": true, + "ssr": true, + "transpileTemplateLiterals": true, + "cssProp": true + } + ] + ] +} diff --git a/test/fixtures/css-prop-script-mode-interop-default-require/code.js b/test/fixtures/css-prop-script-mode-interop-default-require/code.js new file mode 100644 index 00000000..f5e1fc47 --- /dev/null +++ b/test/fixtures/css-prop-script-mode-interop-default-require/code.js @@ -0,0 +1,5 @@ +var _sc = _interopRequireDefault(require('styled-components')) + +const T = _sc.default.h1`color: blue;` +const A = () =>
+const B = () => diff --git a/test/fixtures/css-prop-script-mode-interop-default-require/output.js b/test/fixtures/css-prop-script-mode-interop-default-require/output.js new file mode 100644 index 00000000..87b8b66e --- /dev/null +++ b/test/fixtures/css-prop-script-mode-interop-default-require/output.js @@ -0,0 +1,17 @@ +var _styled = _interopRequireDefault2(require("styled-components")).default; +function _interopRequireDefault2(e) { return e && e.__esModule ? e : { default: e }; } +var _sc = _interopRequireDefault(require('styled-components')); +const T = _sc.default.h1.withConfig({ + displayName: "code__T", + componentId: "sc-c1rs10-0" +})(["color:blue;"]); +const A = () => <_StyledDiv />; +const B = () => <_StyledSpan />; +var _StyledDiv = _styled("div").withConfig({ + displayName: "code___StyledDiv", + componentId: "sc-c1rs10-1" +})(["color:red;"]); +var _StyledSpan = _styled("span").withConfig({ + displayName: "code___StyledSpan", + componentId: "sc-c1rs10-2" +})(["color:green;"]); diff --git a/test/fixtures/css-prop-script-mode-require-after-css-prop/.babelrc b/test/fixtures/css-prop-script-mode-require-after-css-prop/.babelrc new file mode 100644 index 00000000..e7cd3222 --- /dev/null +++ b/test/fixtures/css-prop-script-mode-require-after-css-prop/.babelrc @@ -0,0 +1,15 @@ +{ + "sourceType": "script", + "plugins": [ + [ + "../../../src", + { + "displayName": true, + "fileName": true, + "ssr": true, + "transpileTemplateLiterals": true, + "cssProp": true + } + ] + ] +} diff --git a/test/fixtures/css-prop-script-mode-require-after-css-prop/code.js b/test/fixtures/css-prop-script-mode-require-after-css-prop/code.js new file mode 100644 index 00000000..58fd93e0 --- /dev/null +++ b/test/fixtures/css-prop-script-mode-require-after-css-prop/code.js @@ -0,0 +1,5 @@ +const A = () =>
+ +const styled = require('styled-components') + +const T = styled.default.h1`color: blue;` diff --git a/test/fixtures/css-prop-script-mode-require-after-css-prop/output.js b/test/fixtures/css-prop-script-mode-require-after-css-prop/output.js new file mode 100644 index 00000000..1d317d59 --- /dev/null +++ b/test/fixtures/css-prop-script-mode-require-after-css-prop/output.js @@ -0,0 +1,12 @@ +var _styled = _interopRequireDefault(require("styled-components")).default; +function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } +const A = () => <_StyledDiv />; +const styled = require('styled-components'); +const T = styled.default.h1.withConfig({ + displayName: "code__T", + componentId: "sc-626tbr-1" +})(["color:blue;"]); +var _StyledDiv = _styled("div").withConfig({ + displayName: "code___StyledDiv", + componentId: "sc-626tbr-0" +})(["color:red;"]); diff --git a/test/fixtures/css-prop-script-mode-require-default/.babelrc b/test/fixtures/css-prop-script-mode-require-default/.babelrc new file mode 100644 index 00000000..e7cd3222 --- /dev/null +++ b/test/fixtures/css-prop-script-mode-require-default/.babelrc @@ -0,0 +1,15 @@ +{ + "sourceType": "script", + "plugins": [ + [ + "../../../src", + { + "displayName": true, + "fileName": true, + "ssr": true, + "transpileTemplateLiterals": true, + "cssProp": true + } + ] + ] +} diff --git a/test/fixtures/css-prop-script-mode-require-default/code.js b/test/fixtures/css-prop-script-mode-require-default/code.js new file mode 100644 index 00000000..b88e1382 --- /dev/null +++ b/test/fixtures/css-prop-script-mode-require-default/code.js @@ -0,0 +1,4 @@ +const sc = require('styled-components') + +const T = sc.default.h1`color: blue;` +const A = () =>
diff --git a/test/fixtures/css-prop-script-mode-require-default/output.js b/test/fixtures/css-prop-script-mode-require-default/output.js new file mode 100644 index 00000000..6736d0a8 --- /dev/null +++ b/test/fixtures/css-prop-script-mode-require-default/output.js @@ -0,0 +1,12 @@ +var _styled = _interopRequireDefault(require("styled-components")).default; +function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } +const sc = require('styled-components'); +const T = sc.default.h1.withConfig({ + displayName: "code__T", + componentId: "sc-1u3hzwe-0" +})(["color:blue;"]); +const A = () => <_StyledDiv />; +var _StyledDiv = _styled("div").withConfig({ + displayName: "code___StyledDiv", + componentId: "sc-1u3hzwe-1" +})(["color:red;"]); diff --git a/test/fixtures/css-prop-script-mode-require-named-styled/.babelrc b/test/fixtures/css-prop-script-mode-require-named-styled/.babelrc new file mode 100644 index 00000000..e7cd3222 --- /dev/null +++ b/test/fixtures/css-prop-script-mode-require-named-styled/.babelrc @@ -0,0 +1,15 @@ +{ + "sourceType": "script", + "plugins": [ + [ + "../../../src", + { + "displayName": true, + "fileName": true, + "ssr": true, + "transpileTemplateLiterals": true, + "cssProp": true + } + ] + ] +} diff --git a/test/fixtures/css-prop-script-mode-require-named-styled/code.js b/test/fixtures/css-prop-script-mode-require-named-styled/code.js new file mode 100644 index 00000000..874dc6b2 --- /dev/null +++ b/test/fixtures/css-prop-script-mode-require-named-styled/code.js @@ -0,0 +1,4 @@ +const styled = require('styled-components') + +const T = styled.default.h1`color: blue;` +const A = () =>
diff --git a/test/fixtures/css-prop-script-mode-require-named-styled/output.js b/test/fixtures/css-prop-script-mode-require-named-styled/output.js new file mode 100644 index 00000000..99a7bd8d --- /dev/null +++ b/test/fixtures/css-prop-script-mode-require-named-styled/output.js @@ -0,0 +1,12 @@ +var _styled = _interopRequireDefault(require("styled-components")).default; +function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } +const styled = require('styled-components'); +const T = styled.default.h1.withConfig({ + displayName: "code__T", + componentId: "sc-74qzhj-0" +})(["color:blue;"]); +const A = () => <_StyledDiv />; +var _StyledDiv = _styled("div").withConfig({ + displayName: "code___StyledDiv", + componentId: "sc-74qzhj-1" +})(["color:red;"]); diff --git a/test/fixtures/css-prop-script-mode-without-require/.babelrc b/test/fixtures/css-prop-script-mode-without-require/.babelrc new file mode 100644 index 00000000..e7cd3222 --- /dev/null +++ b/test/fixtures/css-prop-script-mode-without-require/.babelrc @@ -0,0 +1,15 @@ +{ + "sourceType": "script", + "plugins": [ + [ + "../../../src", + { + "displayName": true, + "fileName": true, + "ssr": true, + "transpileTemplateLiterals": true, + "cssProp": true + } + ] + ] +} diff --git a/test/fixtures/css-prop-script-mode-without-require/code.js b/test/fixtures/css-prop-script-mode-without-require/code.js new file mode 100644 index 00000000..9e847d8c --- /dev/null +++ b/test/fixtures/css-prop-script-mode-without-require/code.js @@ -0,0 +1,2 @@ +const A = () =>
+const B = () => diff --git a/test/fixtures/css-prop-script-mode-without-require/output.js b/test/fixtures/css-prop-script-mode-without-require/output.js new file mode 100644 index 00000000..48555ab8 --- /dev/null +++ b/test/fixtures/css-prop-script-mode-without-require/output.js @@ -0,0 +1,12 @@ +var _styled = _interopRequireDefault(require("styled-components")).default; +function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } +const A = () => <_StyledDiv />; +const B = () => <_StyledSpan />; +var _StyledDiv = _styled("div").withConfig({ + displayName: "code___StyledDiv", + componentId: "sc-7lvfxg-0" +})(["color:red;"]); +var _StyledSpan = _styled("span").withConfig({ + displayName: "code___StyledSpan", + componentId: "sc-7lvfxg-1" +})(["color:blue;"]); diff --git a/test/fixtures/css-prop-with-aliased-namespace-import-chained/.babelrc b/test/fixtures/css-prop-with-aliased-namespace-import-chained/.babelrc new file mode 100644 index 00000000..0d275cd9 --- /dev/null +++ b/test/fixtures/css-prop-with-aliased-namespace-import-chained/.babelrc @@ -0,0 +1,14 @@ +{ + "plugins": [ + [ + "../../../src", + { + "displayName": true, + "fileName": true, + "ssr": true, + "transpileTemplateLiterals": true, + "cssProp": true + } + ] + ] +} diff --git a/test/fixtures/css-prop-with-aliased-namespace-import-chained/code.js b/test/fixtures/css-prop-with-aliased-namespace-import-chained/code.js new file mode 100644 index 00000000..cd315cc8 --- /dev/null +++ b/test/fixtures/css-prop-with-aliased-namespace-import-chained/code.js @@ -0,0 +1,11 @@ +import * as sc from 'styled-components' + +const Title = sc.default.h1.attrs({ role: 'heading' })` + color: blue; +` + +const Wrapper = sc.default.div` + margin: 0; +` + +const A = () =>
diff --git a/test/fixtures/css-prop-with-aliased-namespace-import-chained/output.js b/test/fixtures/css-prop-with-aliased-namespace-import-chained/output.js new file mode 100644 index 00000000..3fa30bbd --- /dev/null +++ b/test/fixtures/css-prop-with-aliased-namespace-import-chained/output.js @@ -0,0 +1,17 @@ +import _styled from "styled-components"; +import * as sc from 'styled-components'; +const Title = sc.default.h1.attrs({ + role: 'heading' +}).withConfig({ + displayName: "code__Title", + componentId: "sc-1ec91ov-0" +})(["color:blue;"]); +const Wrapper = sc.default.div.withConfig({ + displayName: "code__Wrapper", + componentId: "sc-1ec91ov-1" +})(["margin:0;"]); +const A = () => <_StyledDiv />; +var _StyledDiv = _styled("div").withConfig({ + displayName: "code___StyledDiv", + componentId: "sc-1ec91ov-2" +})(["color:red;"]); diff --git a/test/fixtures/css-prop-with-namespace-default-call-form/.babelrc b/test/fixtures/css-prop-with-namespace-default-call-form/.babelrc new file mode 100644 index 00000000..0d275cd9 --- /dev/null +++ b/test/fixtures/css-prop-with-namespace-default-call-form/.babelrc @@ -0,0 +1,14 @@ +{ + "plugins": [ + [ + "../../../src", + { + "displayName": true, + "fileName": true, + "ssr": true, + "transpileTemplateLiterals": true, + "cssProp": true + } + ] + ] +} diff --git a/test/fixtures/css-prop-with-namespace-default-call-form/code.js b/test/fixtures/css-prop-with-namespace-default-call-form/code.js new file mode 100644 index 00000000..acfbbbc0 --- /dev/null +++ b/test/fixtures/css-prop-with-namespace-default-call-form/code.js @@ -0,0 +1,6 @@ +import * as styled from 'styled-components' + +const Foo = () => null + +const Called = styled.default(Foo)`color: red;` +const App = () =>
diff --git a/test/fixtures/css-prop-with-namespace-default-call-form/output.js b/test/fixtures/css-prop-with-namespace-default-call-form/output.js new file mode 100644 index 00000000..cc7a3e65 --- /dev/null +++ b/test/fixtures/css-prop-with-namespace-default-call-form/output.js @@ -0,0 +1,12 @@ +import _styled from "styled-components"; +import * as styled from 'styled-components'; +const Foo = () => null; +const Called = styled.default(Foo).withConfig({ + displayName: "code__Called", + componentId: "sc-15447g-0" +})(["color:red;"]); +const App = () => <_StyledDiv />; +var _StyledDiv = _styled("div").withConfig({ + displayName: "code___StyledDiv", + componentId: "sc-15447g-1" +})(["color:blue;"]); diff --git a/test/fixtures/css-prop-with-namespace-import-multiple/.babelrc b/test/fixtures/css-prop-with-namespace-import-multiple/.babelrc new file mode 100644 index 00000000..0d275cd9 --- /dev/null +++ b/test/fixtures/css-prop-with-namespace-import-multiple/.babelrc @@ -0,0 +1,14 @@ +{ + "plugins": [ + [ + "../../../src", + { + "displayName": true, + "fileName": true, + "ssr": true, + "transpileTemplateLiterals": true, + "cssProp": true + } + ] + ] +} diff --git a/test/fixtures/css-prop-with-namespace-import-multiple/code.js b/test/fixtures/css-prop-with-namespace-import-multiple/code.js new file mode 100644 index 00000000..895f3573 --- /dev/null +++ b/test/fixtures/css-prop-with-namespace-import-multiple/code.js @@ -0,0 +1,9 @@ +import * as styled from 'styled-components' + +const Title = styled.default.h1` + color: blue; +` + +const A = () =>
+ +const B = () =>

diff --git a/test/fixtures/css-prop-with-namespace-import-multiple/output.js b/test/fixtures/css-prop-with-namespace-import-multiple/output.js new file mode 100644 index 00000000..e1667f1e --- /dev/null +++ b/test/fixtures/css-prop-with-namespace-import-multiple/output.js @@ -0,0 +1,16 @@ +import _styled from "styled-components"; +import * as styled from 'styled-components'; +const Title = styled.default.h1.withConfig({ + displayName: "code__Title", + componentId: "sc-1cx0gi5-0" +})(["color:blue;"]); +const A = () => <_StyledDiv />; +const B = () => <_StyledP />; +var _StyledDiv = _styled("div").withConfig({ + displayName: "code___StyledDiv", + componentId: "sc-1cx0gi5-1" +})(["color:red;"]); +var _StyledP = _styled("p").withConfig({ + displayName: "code___StyledP", + componentId: "sc-1cx0gi5-2" +})(["margin:0;"]); diff --git a/test/fixtures/imports-inside-ambient-module-declarations/.babelrc b/test/fixtures/imports-inside-ambient-module-declarations/.babelrc new file mode 100644 index 00000000..b1823bfa --- /dev/null +++ b/test/fixtures/imports-inside-ambient-module-declarations/.babelrc @@ -0,0 +1,14 @@ +{ + "parserOpts": { + "plugins": ["typescript"] + }, + "plugins": [ + [ + "../../../src", + { + "transpileTemplateLiterals": false, + "ssr": true + } + ] + ] +} diff --git a/test/fixtures/imports-inside-ambient-module-declarations/code.js b/test/fixtures/imports-inside-ambient-module-declarations/code.js new file mode 100644 index 00000000..bceee602 --- /dev/null +++ b/test/fixtures/imports-inside-ambient-module-declarations/code.js @@ -0,0 +1,10 @@ +import styled from 'styled-components' + +declare module 'my-theme' { + import sc from 'styled-components' + export const theme: typeof sc +} + +const Button = styled.div` + color: red; +` diff --git a/test/fixtures/imports-inside-ambient-module-declarations/output.js b/test/fixtures/imports-inside-ambient-module-declarations/output.js new file mode 100644 index 00000000..751b7413 --- /dev/null +++ b/test/fixtures/imports-inside-ambient-module-declarations/output.js @@ -0,0 +1,9 @@ +import styled from 'styled-components'; +declare module 'my-theme' { + import sc from 'styled-components'; + export const theme: typeof sc; +} +const Button = styled.div.withConfig({ + displayName: "code__Button", + componentId: "sc-134nhji-0" +})`color:red;`; diff --git a/test/fixtures/imports-inside-exported-ambient-namespace/.babelrc b/test/fixtures/imports-inside-exported-ambient-namespace/.babelrc new file mode 100644 index 00000000..b1823bfa --- /dev/null +++ b/test/fixtures/imports-inside-exported-ambient-namespace/.babelrc @@ -0,0 +1,14 @@ +{ + "parserOpts": { + "plugins": ["typescript"] + }, + "plugins": [ + [ + "../../../src", + { + "transpileTemplateLiterals": false, + "ssr": true + } + ] + ] +} diff --git a/test/fixtures/imports-inside-exported-ambient-namespace/code.js b/test/fixtures/imports-inside-exported-ambient-namespace/code.js new file mode 100644 index 00000000..e5fa48de --- /dev/null +++ b/test/fixtures/imports-inside-exported-ambient-namespace/code.js @@ -0,0 +1,16 @@ +import styled from 'styled-components' + +const theme = { color: 'red' } + +export { theme } + +export const spacing = 4 + +export declare namespace Tokens { + import sc from 'styled-components' + export const tag: typeof sc +} + +const Button = styled.div` + color: red; +` diff --git a/test/fixtures/imports-inside-exported-ambient-namespace/output.js b/test/fixtures/imports-inside-exported-ambient-namespace/output.js new file mode 100644 index 00000000..0eba05a7 --- /dev/null +++ b/test/fixtures/imports-inside-exported-ambient-namespace/output.js @@ -0,0 +1,14 @@ +import styled from 'styled-components'; +const theme = { + color: 'red' +}; +export { theme }; +export const spacing = 4; +export declare namespace Tokens { + import sc from 'styled-components'; + export const tag: typeof sc; +} +const Button = styled.div.withConfig({ + displayName: "code__Button", + componentId: "sc-k9gnbk-0" +})`color:red;`; diff --git a/test/fixtures/imports-inside-flow-declare-module/.babelrc b/test/fixtures/imports-inside-flow-declare-module/.babelrc new file mode 100644 index 00000000..6c70b5ce --- /dev/null +++ b/test/fixtures/imports-inside-flow-declare-module/.babelrc @@ -0,0 +1,14 @@ +{ + "parserOpts": { + "plugins": ["flow"] + }, + "plugins": [ + [ + "../../../src", + { + "transpileTemplateLiterals": false, + "ssr": true + } + ] + ] +} diff --git a/test/fixtures/imports-inside-flow-declare-module/code.js b/test/fixtures/imports-inside-flow-declare-module/code.js new file mode 100644 index 00000000..0f53601b --- /dev/null +++ b/test/fixtures/imports-inside-flow-declare-module/code.js @@ -0,0 +1,10 @@ +import styled from 'styled-components' + +declare module 'my-theme' { + import typeof sc from 'styled-components' + declare module.exports: sc +} + +const Button = styled.div` + color: red; +` diff --git a/test/fixtures/imports-inside-flow-declare-module/output.js b/test/fixtures/imports-inside-flow-declare-module/output.js new file mode 100644 index 00000000..e22955ce --- /dev/null +++ b/test/fixtures/imports-inside-flow-declare-module/output.js @@ -0,0 +1,9 @@ +import styled from 'styled-components'; +declare module 'my-theme' { + import typeof sc from 'styled-components'; + declare module.exports: sc +} +const Button = styled.div.withConfig({ + displayName: "code__Button", + componentId: "sc-mn515o-0" +})`color:red;`; diff --git a/test/fixtures/named-import-styled-with-alias/.babelrc b/test/fixtures/named-import-styled-with-alias/.babelrc new file mode 100644 index 00000000..94bf9ef5 --- /dev/null +++ b/test/fixtures/named-import-styled-with-alias/.babelrc @@ -0,0 +1,12 @@ +{ + "plugins": [ + [ + "../../../src", + { + "ssr": false, + "fileName": false, + "transpileTemplateLiterals": false + } + ] + ] +} diff --git a/test/fixtures/named-import-styled-with-alias/code.js b/test/fixtures/named-import-styled-with-alias/code.js new file mode 100644 index 00000000..aab10d96 --- /dev/null +++ b/test/fixtures/named-import-styled-with-alias/code.js @@ -0,0 +1,13 @@ +import { css, styled as sc } from 'styled-components' + +const Foo = sc.div` + color: red; +` + +const Bar = sc('span')` + color: blue; +` + +const mixin = css` + color: green; +` diff --git a/test/fixtures/named-import-styled-with-alias/output.js b/test/fixtures/named-import-styled-with-alias/output.js new file mode 100644 index 00000000..acc4c89f --- /dev/null +++ b/test/fixtures/named-import-styled-with-alias/output.js @@ -0,0 +1,8 @@ +import { css, styled as sc } from 'styled-components'; +const Foo = sc.div.withConfig({ + displayName: "Foo" +})`color:red;`; +const Bar = sc('span').withConfig({ + displayName: "Bar" +})`color:blue;`; +const mixin = css`color:green;`; diff --git a/test/fixtures/namespace-default-call-form/.babelrc b/test/fixtures/namespace-default-call-form/.babelrc new file mode 100644 index 00000000..4f8903d2 --- /dev/null +++ b/test/fixtures/namespace-default-call-form/.babelrc @@ -0,0 +1,14 @@ +{ + "plugins": [ + [ + "../../../src", + { + "displayName": true, + "fileName": true, + "ssr": true, + "transpileTemplateLiterals": true, + "minify": false + } + ] + ] +} diff --git a/test/fixtures/namespace-default-call-form/code.js b/test/fixtures/namespace-default-call-form/code.js new file mode 100644 index 00000000..01343e5e --- /dev/null +++ b/test/fixtures/namespace-default-call-form/code.js @@ -0,0 +1,9 @@ +import * as styled from 'styled-components' + +const Foo = () => null + +const Called = styled.default(Foo)`color: red;` +const CalledObject = styled.default(Foo)({ color: 'blue' }) +const CalledChained = styled.default(Foo).attrs({})`color: green;` +const CalledSequence = (0, styled.default)(Foo)`color: red;` +const Tagged = styled.default.div`color: red;` diff --git a/test/fixtures/namespace-default-call-form/output.js b/test/fixtures/namespace-default-call-form/output.js new file mode 100644 index 00000000..0f3dee69 --- /dev/null +++ b/test/fixtures/namespace-default-call-form/output.js @@ -0,0 +1,24 @@ +import * as styled from 'styled-components'; +const Foo = () => null; +const Called = styled.default(Foo).withConfig({ + displayName: "code__Called", + componentId: "sc-1rd8nhx-0" +})(["color: red;"]); +const CalledObject = styled.default(Foo).withConfig({ + displayName: "code__CalledObject", + componentId: "sc-1rd8nhx-1" +})({ + color: 'blue' +}); +const CalledChained = styled.default(Foo).attrs({}).withConfig({ + displayName: "code__CalledChained", + componentId: "sc-1rd8nhx-2" +})(["color: green;"]); +const CalledSequence = (0, styled.default)(Foo).withConfig({ + displayName: "code__CalledSequence", + componentId: "sc-1rd8nhx-3" +})(["color: red;"]); +const Tagged = styled.default.div.withConfig({ + displayName: "code__Tagged", + componentId: "sc-1rd8nhx-4" +})(["color: red;"]); diff --git a/test/fixtures/namespace-import-alongside-default-import/.babelrc b/test/fixtures/namespace-import-alongside-default-import/.babelrc new file mode 100644 index 00000000..b5e31b27 --- /dev/null +++ b/test/fixtures/namespace-import-alongside-default-import/.babelrc @@ -0,0 +1,13 @@ +{ + "plugins": [ + [ + "../../../src", + { + "displayName": true, + "fileName": true, + "ssr": true, + "transpileTemplateLiterals": true + } + ] + ] +} diff --git a/test/fixtures/namespace-import-alongside-default-import/code.js b/test/fixtures/namespace-import-alongside-default-import/code.js new file mode 100644 index 00000000..d06910a6 --- /dev/null +++ b/test/fixtures/namespace-import-alongside-default-import/code.js @@ -0,0 +1,9 @@ +import styled, * as sc from 'styled-components' + +const Plain = styled.div` + color: red; +` + +const Title = sc.default.h1` + color: blue; +` diff --git a/test/fixtures/namespace-import-alongside-default-import/output.js b/test/fixtures/namespace-import-alongside-default-import/output.js new file mode 100644 index 00000000..593d5b9c --- /dev/null +++ b/test/fixtures/namespace-import-alongside-default-import/output.js @@ -0,0 +1,9 @@ +import styled, * as sc from 'styled-components'; +const Plain = styled.div.withConfig({ + displayName: "code__Plain", + componentId: "sc-56xt2j-0" +})(["color:red;"]); +const Title = sc.default.h1.withConfig({ + displayName: "code__Title", + componentId: "sc-56xt2j-1" +})(["color:blue;"]); diff --git a/test/fixtures/require-default-call-form/.babelrc b/test/fixtures/require-default-call-form/.babelrc new file mode 100644 index 00000000..4f8903d2 --- /dev/null +++ b/test/fixtures/require-default-call-form/.babelrc @@ -0,0 +1,14 @@ +{ + "plugins": [ + [ + "../../../src", + { + "displayName": true, + "fileName": true, + "ssr": true, + "transpileTemplateLiterals": true, + "minify": false + } + ] + ] +} diff --git a/test/fixtures/require-default-call-form/code.js b/test/fixtures/require-default-call-form/code.js new file mode 100644 index 00000000..f8cacfcd --- /dev/null +++ b/test/fixtures/require-default-call-form/code.js @@ -0,0 +1,9 @@ +const styled = require('styled-components') + +const Foo = () => null + +const Called = styled.default(Foo)`color: red;` +const CalledObject = styled.default(Foo)({ color: 'blue' }) +const CalledChained = styled.default(Foo).attrs({})`color: green;` +const CalledSequence = (0, styled.default)(Foo)`color: red;` +const Tagged = styled.default.div`color: red;` diff --git a/test/fixtures/require-default-call-form/output.js b/test/fixtures/require-default-call-form/output.js new file mode 100644 index 00000000..6f52cf69 --- /dev/null +++ b/test/fixtures/require-default-call-form/output.js @@ -0,0 +1,24 @@ +const styled = require('styled-components'); +const Foo = () => null; +const Called = styled.default(Foo).withConfig({ + displayName: "code__Called", + componentId: "sc-1u83qaw-0" +})(["color: red;"]); +const CalledObject = styled.default(Foo).withConfig({ + displayName: "code__CalledObject", + componentId: "sc-1u83qaw-1" +})({ + color: 'blue' +}); +const CalledChained = styled.default(Foo).attrs({}).withConfig({ + displayName: "code__CalledChained", + componentId: "sc-1u83qaw-2" +})(["color: green;"]); +const CalledSequence = (0, styled.default)(Foo).withConfig({ + displayName: "code__CalledSequence", + componentId: "sc-1u83qaw-3" +})(["color: red;"]); +const Tagged = styled.default.div.withConfig({ + displayName: "code__Tagged", + componentId: "sc-1u83qaw-4" +})(["color: red;"]); diff --git a/test/fixtures/require-default-local-name/.babelrc b/test/fixtures/require-default-local-name/.babelrc new file mode 100644 index 00000000..4f8903d2 --- /dev/null +++ b/test/fixtures/require-default-local-name/.babelrc @@ -0,0 +1,14 @@ +{ + "plugins": [ + [ + "../../../src", + { + "displayName": true, + "fileName": true, + "ssr": true, + "transpileTemplateLiterals": true, + "minify": false + } + ] + ] +} diff --git a/test/fixtures/require-default-local-name/code.js b/test/fixtures/require-default-local-name/code.js new file mode 100644 index 00000000..dae42b0f --- /dev/null +++ b/test/fixtures/require-default-local-name/code.js @@ -0,0 +1,8 @@ +const sc = require('styled-components') + +const Foo = () => null + +const Global = styled(Foo)`color: red;` +const Member = sc.default.h1`color: blue;` +const Called = sc.default(Foo)`color: green;` +const Direct = sc(Foo)`color: black;` diff --git a/test/fixtures/require-default-local-name/output.js b/test/fixtures/require-default-local-name/output.js new file mode 100644 index 00000000..b3caaddf --- /dev/null +++ b/test/fixtures/require-default-local-name/output.js @@ -0,0 +1,18 @@ +const sc = require('styled-components'); +const Foo = () => null; +const Global = styled(Foo).withConfig({ + displayName: "code__Global", + componentId: "sc-fr2vfw-0" +})(["color: red;"]); +const Member = sc.default.h1.withConfig({ + displayName: "code__Member", + componentId: "sc-fr2vfw-1" +})(["color: blue;"]); +const Called = sc.default(Foo).withConfig({ + displayName: "code__Called", + componentId: "sc-fr2vfw-2" +})(["color: green;"]); +const Direct = sc(Foo).withConfig({ + displayName: "code__Direct", + componentId: "sc-fr2vfw-3" +})(["color: black;"]); diff --git a/test/fixtures/transpile-css-prop-inject-default-import-once/.babelrc b/test/fixtures/transpile-css-prop-inject-default-import-once/.babelrc new file mode 100644 index 00000000..0d275cd9 --- /dev/null +++ b/test/fixtures/transpile-css-prop-inject-default-import-once/.babelrc @@ -0,0 +1,14 @@ +{ + "plugins": [ + [ + "../../../src", + { + "displayName": true, + "fileName": true, + "ssr": true, + "transpileTemplateLiterals": true, + "cssProp": true + } + ] + ] +} diff --git a/test/fixtures/transpile-css-prop-inject-default-import-once/code.js b/test/fixtures/transpile-css-prop-inject-default-import-once/code.js new file mode 100644 index 00000000..177cf5de --- /dev/null +++ b/test/fixtures/transpile-css-prop-inject-default-import-once/code.js @@ -0,0 +1,16 @@ +import React from 'react' + +const gap = 4 + +const Static = () =>

+ +const Dynamic = p => ( +

+) + +const Obj = p => diff --git a/test/fixtures/transpile-css-prop-inject-default-import-once/output.js b/test/fixtures/transpile-css-prop-inject-default-import-once/output.js new file mode 100644 index 00000000..e4e20b2d --- /dev/null +++ b/test/fixtures/transpile-css-prop-inject-default-import-once/output.js @@ -0,0 +1,21 @@ +import _styled from "styled-components"; +import React from 'react'; +const gap = 4; +const Static = () => <_StyledP />; +const Dynamic = p => <_StyledDiv $_css={p.m} />; +const Obj = p => <_StyledSpan $_css2={p.color} />; +var _StyledP = _styled("p").withConfig({ + displayName: "code___StyledP", + componentId: "sc-fcxzrw-0" +})(["color:red;"]); +var _StyledDiv = _styled("div").withConfig({ + displayName: "code___StyledDiv", + componentId: "sc-fcxzrw-1" +})(["margin:", "px;padding:", "px;"], p => p.$_css, gap); +var _StyledSpan = _styled("span").withConfig({ + displayName: "code___StyledSpan", + componentId: "sc-fcxzrw-2" +})(p => ({ + color: p.$_css2, + padding: 1 +})); diff --git a/test/fixtures/type-only-imports-ignored-flow/.babelrc b/test/fixtures/type-only-imports-ignored-flow/.babelrc new file mode 100644 index 00000000..6c70b5ce --- /dev/null +++ b/test/fixtures/type-only-imports-ignored-flow/.babelrc @@ -0,0 +1,14 @@ +{ + "parserOpts": { + "plugins": ["flow"] + }, + "plugins": [ + [ + "../../../src", + { + "transpileTemplateLiterals": false, + "ssr": true + } + ] + ] +} diff --git a/test/fixtures/type-only-imports-ignored-flow/code.js b/test/fixtures/type-only-imports-ignored-flow/code.js new file mode 100644 index 00000000..092aec07 --- /dev/null +++ b/test/fixtures/type-only-imports-ignored-flow/code.js @@ -0,0 +1,21 @@ +import styled, { css, keyframes } from 'styled-components' +import typeof TypeofDefault from 'styled-components' +import type TypeOnlyDefault from 'styled-components' +import { type css as TypeOnlyCss, typeof keyframes as TypeofKeyframes } from 'styled-components' +import typeof * as TypeofNamespace from 'styled-components' + +const Button = styled.div` + color: red; +` + +const mixin = css` + color: blue; +` + +const fade = keyframes` + from { opacity: 0; } +` + +const make = (TypeofNamespace: any) => TypeofNamespace.default.div` + color: green; +` diff --git a/test/fixtures/type-only-imports-ignored-flow/output.js b/test/fixtures/type-only-imports-ignored-flow/output.js new file mode 100644 index 00000000..c5aaa1ef --- /dev/null +++ b/test/fixtures/type-only-imports-ignored-flow/output.js @@ -0,0 +1,14 @@ +import styled, { css, keyframes } from 'styled-components'; +import typeof TypeofDefault from 'styled-components'; +import type TypeOnlyDefault from 'styled-components'; +import { type css as TypeOnlyCss, typeof keyframes as TypeofKeyframes } from 'styled-components'; +import typeof * as TypeofNamespace from 'styled-components'; +const Button = styled.div.withConfig({ + displayName: "code__Button", + componentId: "sc-d98r4-0" +})`color:red;`; +const mixin = css`color:blue;`; +const fade = keyframes`from{opacity:0;}`; +const make = (TypeofNamespace: any) => TypeofNamespace.default.div` + color: green; +`; diff --git a/test/fixtures/type-only-imports-ignored-typescript/.babelrc b/test/fixtures/type-only-imports-ignored-typescript/.babelrc new file mode 100644 index 00000000..b1823bfa --- /dev/null +++ b/test/fixtures/type-only-imports-ignored-typescript/.babelrc @@ -0,0 +1,14 @@ +{ + "parserOpts": { + "plugins": ["typescript"] + }, + "plugins": [ + [ + "../../../src", + { + "transpileTemplateLiterals": false, + "ssr": true + } + ] + ] +} diff --git a/test/fixtures/type-only-imports-ignored-typescript/code.js b/test/fixtures/type-only-imports-ignored-typescript/code.js new file mode 100644 index 00000000..052d36cd --- /dev/null +++ b/test/fixtures/type-only-imports-ignored-typescript/code.js @@ -0,0 +1,21 @@ +import styled, { css, keyframes } from 'styled-components' +import type TypeOnlyDefault from 'styled-components' +import type { css as TypeOnlyCss } from 'styled-components' +import { type keyframes as TypeOnlyKeyframes } from 'styled-components' +import type * as TypeOnlyNamespace from 'styled-components' + +const Button = styled.div` + color: red; +` + +const mixin = css` + color: blue; +` + +const fade = keyframes` + from { opacity: 0; } +` + +const make = (TypeOnlyNamespace: any) => TypeOnlyNamespace.default.div` + color: green; +` diff --git a/test/fixtures/type-only-imports-ignored-typescript/output.js b/test/fixtures/type-only-imports-ignored-typescript/output.js new file mode 100644 index 00000000..5d563f6d --- /dev/null +++ b/test/fixtures/type-only-imports-ignored-typescript/output.js @@ -0,0 +1,14 @@ +import styled, { css, keyframes } from 'styled-components'; +import type TypeOnlyDefault from 'styled-components'; +import type { css as TypeOnlyCss } from 'styled-components'; +import { type keyframes as TypeOnlyKeyframes } from 'styled-components'; +import type * as TypeOnlyNamespace from 'styled-components'; +const Button = styled.div.withConfig({ + displayName: "code__Button", + componentId: "sc-1uq12u7-0" +})`color:red;`; +const mixin = css`color:blue;`; +const fade = keyframes`from{opacity:0;}`; +const make = (TypeOnlyNamespace: any) => TypeOnlyNamespace.default.div` + color: green; +`; diff --git a/test/index.test.js b/test/index.test.js index c4b7fe09..6e749c65 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -1,3 +1,4 @@ +import fs from 'fs' import path from 'path' import { create } from 'babel-test' import { toMatchFile } from 'jest-file-snapshot' @@ -6,4 +7,25 @@ expect.extend({ toMatchFile }) const { fixtures } = create({ babelrc: true }) -fixtures('babel-plugin-styled-components', path.join(__dirname, 'fixtures')) +const fixturesDir = path.join(__dirname, 'fixtures') + +fixtures('babel-plugin-styled-components', fixturesDir) + +describe('fixture snapshots', () => { + const absolutePath = + /(^|[\s('"])(\/(Users|home|private|var|tmp|opt)\/|[A-Za-z]:[\\/]|\\\\[^\\\s]+\\)/m + + it('contain no absolute machine paths', () => { + const leaks = fs + .readdirSync(fixturesDir) + .flatMap(name => + ['output.js', 'error.js'].map(file => path.join(name, file)) + ) + .filter(file => fs.existsSync(path.join(fixturesDir, file))) + .filter(file => + absolutePath.test(fs.readFileSync(path.join(fixturesDir, file), 'utf8')) + ) + + expect(leaks).toEqual([]) + }) +}) diff --git a/test/minify/index.test.js b/test/minify/index.test.js index aec0cc01..2ec575a7 100644 --- a/test/minify/index.test.js +++ b/test/minify/index.test.js @@ -34,6 +34,28 @@ describe('minify utils', () => { expect(stripLineComment(' ); // long comment')).toBe(' ); ') }) + it('strips comments after a string containing the other quote type', () => { + expect(stripLineComment('content: "it\'s"; // c')).toBe( + 'content: "it\'s"; ' + ) + expect(stripLineComment("content: 'say \"hi'; // c")).toBe( + "content: 'say \"hi'; " + ) + }) + + it('strips comments after a string containing an escaped quote', () => { + expect(stripLineComment('content: "\\""; // c')).toBe('content: "\\""; ') + expect(stripLineComment('content: "\\\\"; // c')).toBe( + 'content: "\\\\"; ' + ) + }) + + it('ignores parens inside strings when looking for comments', () => { + expect(stripLineComment('content: "("; // c')).toBe('content: "("; ') + expect(stripLineComment('content: ")("; // c')).toBe('content: ")("; ') + expect(stripLineComment('content: ")"; // c')).toBe('content: ")"; ') + }) + it('ignores comment markers inside parens that follow an unmatched closing paren', () => { expect(stripLineComment(' ), url(//cdn.example.com/x.png)')).toBe( ' ), url(//cdn.example.com/x.png)' @@ -123,6 +145,26 @@ describe('minify utils', () => { expect(minifyCooked(input)).toEqual([actual, []]) }) + it('Removes line comments after a multi-line selector or at-rule prelude', () => { + const input = + '&:is(\n .a,\n .b\n) { // c\n color: red;\n}\n@media (\n min-width: 1px\n) { // c\n color: blue;\n}\ncolor: green;' + const expected = + '&:is( .a,.b ){color:red;}@media ( min-width:1px ){color:blue;}color:green;' + const [actual] = minifyRaw(input) + + expect(actual).toBe(expected) + expect(actual).toBe(minifyCooked(input)[0]) + }) + + it('Removes line comments after a string and keeps the following declaration', () => { + const input = 'content: "it\'s"; // c\ncontent: "("; // c\ncolor: red;' + const expected = 'content:"it\'s";content:"(";color:red;' + const [actual] = minifyRaw(input) + + expect(actual).toBe(expected) + expect(actual).toBe(minifyCooked(input)[0]) + }) + it('Removes line comments inside a multi-line value', () => { const input = 'width: calc(\n 10px // first\n + 20px\n);\ncolor: blue;' const expected = 'width:calc( 10px + 20px );color:blue;' @@ -192,6 +234,13 @@ describe('minify utils', () => { expect(compressSymbols(input)).toBe(expected) }) + it('tracks strings containing the other quote type or an escaped quote', () => { + const input = `content: "it's"; content: ' a ; b '; content: "\\" ; "; color: red;` + const expected = `content:"it's";content:' a ; b ';content:"\\" ; ";color:red;` + + expect(compressSymbols(input)).toBe(expected) + }) + it('preserves whitespace preceding colons', () => { const input = '& :last-child { color: blue; }' const expected = '& :last-child{color:blue;}'