Skip to content

Commit d7d768e

Browse files
authored
Implement automatic adding of jsxImportSource pragma definition (#2353)
1 parent 685bbec commit d7d768e

File tree

3 files changed

+190
-1
lines changed

3 files changed

+190
-1
lines changed

.changeset/sweet-plums-behave.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@emotion/eslint-plugin': patch
3+
---
4+
5+
implement automatic adding of jsxImportSource pragma definition

packages/eslint-plugin/src/rules/jsx-import.js

+80-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
const JSX_ANNOTATION_REGEX = /\*?\s*@jsx\s+([^\s]+)/
2+
const JSX_IMPORT_SOURCE_REGEX = /\*?\s*@jsxImportSource\s+([^\s]+)/
23

34
// TODO: handling this case
45
// <div css={`color:hotpink;`} />
@@ -7,9 +8,87 @@ const JSX_ANNOTATION_REGEX = /\*?\s*@jsx\s+([^\s]+)/
78

89
export default {
910
meta: {
10-
fixable: 'code'
11+
fixable: 'code',
12+
schema: {
13+
type: 'array',
14+
items: {
15+
oneOf: [
16+
{
17+
type: 'string'
18+
},
19+
{
20+
type: 'object',
21+
properties: {
22+
runtime: { type: 'string' },
23+
importSource: { type: 'string' }
24+
},
25+
required: ['runtime'],
26+
additionalProperties: false
27+
}
28+
]
29+
},
30+
uniqueItems: true,
31+
minItems: 0
32+
}
1133
},
1234
create(context) {
35+
const jsxRuntimeMode = context.options.find(
36+
option => option && option.runtime === 'automatic'
37+
)
38+
39+
if (jsxRuntimeMode) {
40+
return {
41+
JSXAttribute(node) {
42+
if (node.name.name !== 'css') {
43+
return
44+
}
45+
const importSource =
46+
(jsxRuntimeMode || {}).importSource || '@emotion/react'
47+
let jsxImportSourcePragmaNode
48+
let jsxImportSourceMatch
49+
let validJsxImportSource = false
50+
let sourceCode = context.getSourceCode()
51+
let pragma = sourceCode.getAllComments().find(node => {
52+
if (JSX_IMPORT_SOURCE_REGEX.test(node.value)) {
53+
jsxImportSourcePragmaNode = node
54+
return true
55+
}
56+
})
57+
jsxImportSourceMatch =
58+
pragma && pragma.value.match(JSX_IMPORT_SOURCE_REGEX)
59+
if (
60+
jsxImportSourceMatch &&
61+
jsxImportSourceMatch[1] === importSource
62+
) {
63+
validJsxImportSource = true
64+
}
65+
if (!jsxImportSourceMatch) {
66+
context.report({
67+
node,
68+
message: `The css prop can only be used if jsxImportSource is set to ${importSource}`,
69+
fix(fixer) {
70+
return fixer.insertTextBefore(
71+
sourceCode.ast.body[0],
72+
`/** @jsxImportSource ${importSource} */\n`
73+
)
74+
}
75+
})
76+
} else if (!validJsxImportSource && jsxImportSourcePragmaNode) {
77+
context.report({
78+
node,
79+
message: `The css prop can only be used if jsxImportSource is set to ${importSource}`,
80+
fix(fixer) {
81+
return fixer.replaceText(
82+
jsxImportSourcePragmaNode,
83+
`/** @jsxImportSource ${importSource} */`
84+
)
85+
}
86+
})
87+
}
88+
}
89+
}
90+
}
91+
1392
return {
1493
JSXAttribute(node) {
1594
if (node.name.name !== 'css') {

packages/eslint-plugin/test/rules/jsx-import.test.js

+105
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,45 @@ ruleTester.run('emotion jsx', rule, {
2929
let ele = <div css={{}} />
3030
`
3131
},
32+
{
33+
options: [{ runtime: 'classic' }],
34+
code: `
35+
// @jsx jsx
36+
import { jsx } from '@emotion/react'
37+
let ele = <div css={{}} />
38+
`
39+
},
40+
{
41+
options: [{ runtime: 'invalidRuntime' }],
42+
code: `
43+
// @jsx jsx
44+
import { jsx } from '@emotion/react'
45+
let ele = <div css={{}} />
46+
`
47+
},
48+
{
49+
options: [{ runtime: 'automatic' }],
50+
code: `
51+
/** @jsxImportSource @emotion/react */
52+
53+
let ele = <div css={{}} />
54+
`
55+
},
56+
{
57+
options: [{ runtime: 'automatic' }],
58+
code: `
59+
// no css prop usage for test coverage
60+
let ele = <div nonecss={{}} />
61+
`
62+
},
63+
{
64+
options: [{ runtime: 'automatic', importSource: '@emotion/react' }],
65+
code: `
66+
/** @jsxImportSource @emotion/react */
67+
68+
let ele = <div css={{}} />
69+
`
70+
},
3271
{
3372
code: `
3473
@@ -52,6 +91,72 @@ let ele = <div css={{}} />
5291
output: `
5392
// @jsx jsx
5493
import { jsx } from '@emotion/react'
94+
let ele = <div css={{}} />
95+
`.trim()
96+
},
97+
{
98+
options: [{ runtime: 'automatic' }],
99+
code: `
100+
let ele = <div css={{}} />
101+
`.trim(),
102+
errors: [
103+
{
104+
message:
105+
'The css prop can only be used if jsxImportSource is set to @emotion/react'
106+
}
107+
],
108+
output: `
109+
/** @jsxImportSource @emotion/react */
110+
let ele = <div css={{}} />
111+
`.trim()
112+
},
113+
{
114+
options: [{ runtime: 'automatic', importSource: '@iChenLei/react' }],
115+
code: `
116+
let ele = <div css={{}} />
117+
`.trim(),
118+
errors: [
119+
{
120+
message:
121+
'The css prop can only be used if jsxImportSource is set to @iChenLei/react'
122+
}
123+
],
124+
output: `
125+
/** @jsxImportSource @iChenLei/react */
126+
let ele = <div css={{}} />
127+
`.trim()
128+
},
129+
{
130+
options: [{ runtime: 'automatic', importSource: '@iChenLei/react' }],
131+
code: `
132+
/** @jsxImportSource invalid-react */
133+
let ele = <div css={{}} />
134+
`.trim(),
135+
errors: [
136+
{
137+
message:
138+
'The css prop can only be used if jsxImportSource is set to @iChenLei/react'
139+
}
140+
],
141+
output: `
142+
/** @jsxImportSource @iChenLei/react */
143+
let ele = <div css={{}} />
144+
`.trim()
145+
},
146+
{
147+
options: [{ runtime: 'classic' }],
148+
code: `
149+
let ele = <div css={{}} />
150+
`.trim(),
151+
errors: [
152+
{
153+
message:
154+
'The css prop can only be used if jsx from @emotion/react is imported and it is set as the jsx pragma'
155+
}
156+
],
157+
output: `
158+
/** @jsx jsx */
159+
import { jsx } from '@emotion/react'
55160
let ele = <div css={{}} />
56161
`.trim()
57162
},

0 commit comments

Comments
 (0)