From 3846952d332c2398fe1b9a64f3c71b30e45cb78f Mon Sep 17 00:00:00 2001 From: owjs3901 Date: Sun, 15 Nov 2026 02:36:09 +0900 Subject: [PATCH] fix(eslint-plugin): lint @emotion/css like the build The build aliases `@emotion/css` by default and rejects a dynamic value in its `css`, but the lint rules did not know the package, so the editor stayed silent until the build failed. Its `css`, `keyframes` and `injectGlobal` now read as Devup UI's `css`, `keyframes` and `globalCss`, as the extractor maps them. Refs #684 Co-Authored-By: Claude Opus 5.5 --- .changepacks/changepack_log_eslint_emotion_css.json | 1 + .../css-utils-literal-only/__tests__/sources.test.ts | 10 ++++++---- .../src/utils/__tests__/import-sources.test.ts | 4 +++- .../src/utils/__tests__/style-sources.test.ts | 2 +- packages/eslint-plugin/src/utils/import-storage.ts | 3 +++ 5 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 .changepacks/changepack_log_eslint_emotion_css.json diff --git a/.changepacks/changepack_log_eslint_emotion_css.json b/.changepacks/changepack_log_eslint_emotion_css.json new file mode 100644 index 000000000..ad2154042 --- /dev/null +++ b/.changepacks/changepack_log_eslint_emotion_css.json @@ -0,0 +1 @@ +{"changes":{"packages/eslint-plugin/package.json":"Patch"},"note":"Lint `@emotion/css` like the build does: its `css`, `keyframes` and `injectGlobal` get the same rules as Devup UI's `css`, `keyframes` and `globalCss`, so a dynamic value the build rejects is reported in the editor too","date":"2026-11-15T00:00:00.000Z"} diff --git a/packages/eslint-plugin/src/rules/css-utils-literal-only/__tests__/sources.test.ts b/packages/eslint-plugin/src/rules/css-utils-literal-only/__tests__/sources.test.ts index 8f80cf6d0..d67ea7f01 100644 --- a/packages/eslint-plugin/src/rules/css-utils-literal-only/__tests__/sources.test.ts +++ b/packages/eslint-plugin/src/rules/css-utils-literal-only/__tests__/sources.test.ts @@ -15,6 +15,7 @@ describe('css-utils-literal-only rule: libraries the build reads as Devup UI', ( const styledComponents = `import styled, { css, keyframes, createGlobalStyle } from "styled-components";\n` const vanilla = `import { style, globalStyle, keyframes } from "@vanilla-extract/css";\n` const devup = `import { css, globalCss } from "@devup-ui/react";\n` + const emotionCss = `import { css, cx, injectGlobal } from "@emotion/css";\n` const error = { messageId: 'cssUtilsLiteralOnly' as const } const missing = (name: string) => ({ messageId: 'missingGlobal' as const, @@ -54,10 +55,9 @@ describe('css-utils-literal-only rule: libraries the build reads as Devup UI', ( [vanilla, 'style({ color: "red" })'], [vanilla, 'globalStyle("body", { color: "red" })'], [devup, 'globalCss({ body: { color: "red" } })'], - [ - `import { css } from "@emotion/css";\n`, - 'css({ color: window.name })', - ], + [emotionCss, 'css({ color: "red" })'], + [emotionCss, 'injectGlobal({ body: { color: "red" } })'], + [emotionCss, 'cx(window.name)'], [`import { css } from "other";\n`, 'css({ color: window.name })'], [ `import { jsx } from "@emotion/react";\n`, @@ -115,6 +115,8 @@ describe('css-utils-literal-only rule: libraries the build reads as Devup UI', ( [vanilla, 'let v = 1;\nglobalStyle("body", { color: v })'], [vanilla, 'let v = 1;\nkeyframes({ from: { opacity: v } })'], [devup, 'let v = 1;\nglobalCss({ body: { color: v } })'], + [emotionCss, 'css({ color: window.name })'], + [emotionCss, 'let v = 1;\ninjectGlobal({ body: { color: v } })'], ].map(([header, source]) => ({ code: header + source, filename, diff --git a/packages/eslint-plugin/src/utils/__tests__/import-sources.test.ts b/packages/eslint-plugin/src/utils/__tests__/import-sources.test.ts index c39c935ac..cbacf837f 100644 --- a/packages/eslint-plugin/src/utils/__tests__/import-sources.test.ts +++ b/packages/eslint-plugin/src/utils/__tests__/import-sources.test.ts @@ -72,7 +72,7 @@ describe('import sources', () => { 'import styled, { css as e, createGlobalStyle } from "styled-components";', 'import * as E from "@emotion/styled";', 'import { style, globalStyle, other } from "@vanilla-extract/css";', - 'import { css as skipped } from "@emotion/css";', + 'import { css as f, cx, injectGlobal } from "@emotion/css";', ].join('\n'), 'page.tsx', (storage) => { @@ -90,6 +90,8 @@ describe('import sources', () => { E: 'styled', style: 'css', globalStyle: 'globalCss', + f: 'css', + injectGlobal: 'globalCss', }) }, ) diff --git a/packages/eslint-plugin/src/utils/__tests__/style-sources.test.ts b/packages/eslint-plugin/src/utils/__tests__/style-sources.test.ts index 5782e45a4..f22a978b3 100644 --- a/packages/eslint-plugin/src/utils/__tests__/style-sources.test.ts +++ b/packages/eslint-plugin/src/utils/__tests__/style-sources.test.ts @@ -54,7 +54,7 @@ function elsewhere(source: string) { return [ { code: `import { jsx } from "@emotion/react";\n${source}`, filename }, { code: `import styled from "other-package";\n${source}`, filename }, - { code: `import { css } from "@emotion/css";\n${source}`, filename }, + { code: `import { cx } from "@emotion/css";\n${source}`, filename }, { code: `${HEADERS.vanilla}${source}`, filename: 'src/styles.css.ts' }, { code: `${HEADERS.devup}${source}`, filename: 'src/styles.css.js' }, ] diff --git a/packages/eslint-plugin/src/utils/import-storage.ts b/packages/eslint-plugin/src/utils/import-storage.ts index ea577b68d..86325e54a 100644 --- a/packages/eslint-plugin/src/utils/import-storage.ts +++ b/packages/eslint-plugin/src/utils/import-storage.ts @@ -12,6 +12,7 @@ const DEFAULT_IS_STYLED = new Set(['@emotion/styled', 'styled-components']) /** The packages whose named exports the build maps onto Devup UI's, as `DEFAULT_IMPORT_ALIASES` in `plugin-utils` lists them */ const NAMED_ALIASES = new Set([ '@emotion/react', + '@emotion/css', '@emotion/styled', 'styled-components', '@vanilla-extract/css', @@ -23,6 +24,8 @@ function devupEquivalent(source: string, imported: string): string | undefined { if (imported === 'style') return 'css' if (imported === 'globalStyle') return 'globalCss' } + if (source === '@emotion/css' && imported === 'injectGlobal') + return 'globalCss' switch (imported) { case 'css': case 'keyframes':