Skip to content

Commit

Permalink
emotion-jsGH-3211 Support ESLint 9 for eslint-plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
soren121 committed Aug 27, 2024
1 parent ad630e3 commit 9951777
Show file tree
Hide file tree
Showing 13 changed files with 442 additions and 164 deletions.
11 changes: 6 additions & 5 deletions packages/eslint-plugin/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@
"license": "MIT",
"repository": "https://github.com/emotion-js/emotion/tree/main/packages/eslint-plugin",
"peerDependencies": {
"eslint": "6 || 7 || 8"
"eslint": "^6 || ^7 || ^8 || ^9"
},
"dependencies": {
"@typescript-eslint/utils": "^5.25.0"
"@typescript-eslint/utils": "^8.3.0"
},
"devDependencies": {
"@types/eslint": "^7.0.0",
"eslint": "^8.57.0",
"resolve-from": "^5.0.0"
"@types/eslint": "^9.6.1",
"@typescript-eslint/rule-tester": "^8.3.0",
"eslint": "^9.9.1",
"espree": "^10.1.0"
}
}
12 changes: 12 additions & 0 deletions packages/eslint-plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import styledImport from './rules/styled-import'
import jsxImport from './rules/jsx-import'
import pkgRenaming from './rules/pkg-renaming'

const { name, version } = require('../package.json')

export const rules = {
'import-from-emotion': importFromEmotion,
'no-vanilla': noVanilla,
Expand All @@ -13,3 +15,13 @@ export const rules = {
'jsx-import': jsxImport,
'pkg-renaming': pkgRenaming
}

const plugin = {
meta: {
name,
version
},
rules
}

export default plugin
54 changes: 37 additions & 17 deletions packages/eslint-plugin/src/rules/jsx-import.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,22 @@ const JSX_IMPORT_SOURCE_REGEX = /\*?\s*@jsxImportSource\s+([^\s]+)/
// to
// <div css={css`color:hotpink;`} /> + import { css }

declare module '@typescript-eslint/utils/dist/ts-eslint/Rule' {
export interface SharedConfigurationSettings {
react?: { pragma?: string }
}
}

type JSXConfig = {
runtime: string
importSource?: string
}

type RuleOptions = [(JSXConfig | string)?]

interface ReactConfigurationSettings {
pragma?: string
}

const isReactSettings = (
settings: unknown
): settings is ReactConfigurationSettings =>
typeof settings === 'object' && settings !== null && 'pragma' in settings

const messages = {
cssProp: `The css prop can only be used if jsxImportSource is set to {{ importSource }}`,
cssPropWithPragma: `The css prop can only be used if jsx from @emotion/react is imported and it is set as the jsx pragma`,
Expand Down Expand Up @@ -62,6 +65,8 @@ export default createRule<RuleOptions, keyof typeof messages>({
},
defaultOptions: [],
create(context) {
const filename = context.filename ?? context.getFilename()
const sourceCode = context.sourceCode ?? context.getSourceCode()
const jsxRuntimeMode = context.options.find(
(option): option is JSXConfig =>
typeof option === 'object' && option.runtime === 'automatic'
Expand All @@ -77,7 +82,6 @@ export default createRule<RuleOptions, keyof typeof messages>({
let jsxImportSourcePragmaComment: TSESTree.Comment | null = null
let jsxImportSourceMatch
let validJsxImportSource = false
let sourceCode = context.getSourceCode()
let pragma = sourceCode.getAllComments().find(comment => {
if (JSX_IMPORT_SOURCE_REGEX.test(comment.value)) {
jsxImportSourcePragmaComment = comment
Expand Down Expand Up @@ -113,7 +117,7 @@ export default createRule<RuleOptions, keyof typeof messages>({
/* istanbul ignore if */
if (jsxImportSourcePragmaComment === null) {
throw new Error(
`Unexpected null when attempting to fix ${context.getFilename()} - please file a github issue at ${REPO_URL}`
`Unexpected null when attempting to fix ${filename} - please file a github issue at ${REPO_URL}`
)
}

Expand All @@ -136,7 +140,6 @@ export default createRule<RuleOptions, keyof typeof messages>({
let hasJsxImport = false
let emotionCoreNode = null as TSESTree.ImportDeclaration | null
let local: string | null = null
let sourceCode = context.getSourceCode()
sourceCode.ast.body.forEach(x => {
if (
x.type === AST_NODE_TYPES.ImportDeclaration &&
Expand Down Expand Up @@ -164,10 +167,9 @@ export default createRule<RuleOptions, keyof typeof messages>({
}
}
})
let hasSetPragma = false
if (context.settings.react && context.settings.react.pragma === 'jsx') {
hasSetPragma = true
}
let hasSetPragma =
isReactSettings(context.settings.react) &&
context.settings.react.pragma === 'jsx'
let pragma = sourceCode
.getAllComments()
.find(node => JSX_ANNOTATION_REGEX.test(node.value))
Expand All @@ -185,10 +187,14 @@ export default createRule<RuleOptions, keyof typeof messages>({
/* istanbul ignore if */
if (emotionCoreNode === null) {
throw new Error(
`Unexpected null when attempting to fix ${context.getFilename()} - please file a github issue at ${REPO_URL}`
`Unexpected null when attempting to fix ${filename} - please file a github issue at ${REPO_URL}`
)
}

if (!hasSetPragma && pragma) {
return fixer.replaceText(pragma, `/** @jsx ${local} */`)
}

return fixer.insertTextBefore(
emotionCoreNode,
`/** @jsx ${local} */\n`
Expand Down Expand Up @@ -227,7 +233,7 @@ export default createRule<RuleOptions, keyof typeof messages>({
/* istanbul ignore if */
if (emotionCoreNode === null) {
throw new Error(
`Unexpected null when attempting to fix ${context.getFilename()} - please file a github issue at ${REPO_URL}`
`Unexpected null when attempting to fix ${filename} - please file a github issue at ${REPO_URL}`
)
}

Expand All @@ -239,24 +245,38 @@ export default createRule<RuleOptions, keyof typeof messages>({
value.type === AST_NODE_TYPES.JSXExpressionContainer &&
value.expression.type === AST_NODE_TYPES.TemplateLiteral
) {
let namespaceSpecifier = specifiers.find(
x => x.type === AST_NODE_TYPES.ImportNamespaceSpecifier
)
let cssSpecifier = specifiers.find(
x =>
x.type === AST_NODE_TYPES.ImportSpecifier &&
x.imported.name === 'css'
)

context.report({
node,
messageId: 'templateLiterals',
fix(fixer) {
if (namespaceSpecifier) {
return fixer.insertTextBefore(
value.expression,
namespaceSpecifier.local.name + '.css'
)
}
if (cssSpecifier) {
return fixer.insertTextBefore(
value.expression,
cssSpecifier.local.name
)
}
let lastSpecifier = specifiers[specifiers.length - 1]

if (context.getScope().variables.some(x => x.name === 'css')) {
const lastSpecifier = specifiers[specifiers.length - 1]
const scope = sourceCode.getScope
? sourceCode.getScope(node)
: context.getScope()

if (scope.variables.some(x => x.name === 'css')) {
return [
fixer.insertTextAfter(lastSpecifier, `, css as _css`),
fixer.insertTextBefore(value.expression, '_css')
Expand Down
17 changes: 11 additions & 6 deletions packages/eslint-plugin/src/rules/syntax-preference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,11 @@ const checkExpressionPreferringObject = (
) => {
switch (node.type) {
case AST_NODE_TYPES.ArrayExpression:
node.elements.forEach(element =>
checkExpressionPreferringObject(context, element)
)
node.elements.forEach(element => {
if (element !== null) {
checkExpressionPreferringObject(context, element)
}
})
return
case AST_NODE_TYPES.TemplateLiteral:
context.report({
Expand Down Expand Up @@ -154,9 +156,11 @@ const checkExpressionPreferringString = (
) => {
switch (node.type) {
case 'ArrayExpression':
node.elements.forEach(element =>
checkExpressionPreferringString(context, element)
)
node.elements.forEach(element => {
if (element !== null) {
checkExpressionPreferringString(context, element)
}
})
return
case 'ObjectExpression':
context.report({
Expand Down Expand Up @@ -242,6 +246,7 @@ export default createRule<RuleOptions, MessageId>({
},
schema: [
{
type: 'string',
enum: ['string', 'object']
}
],
Expand Down
14 changes: 10 additions & 4 deletions packages/eslint-plugin/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@ const { version } = require('../package.json')

export const REPO_URL = 'https://github.com/emotion-js/emotion'

export const createRule = ESLintUtils.RuleCreator(name => {
const ruleName = parsePath(name).name
export interface EmotionESLintPluginDocs {
recommended: boolean
}

return `${REPO_URL}/blob/@emotion/eslint-plugin@${version}/packages/eslint-plugin/docs/rules/${ruleName}.md`
})
export const createRule = ESLintUtils.RuleCreator<EmotionESLintPluginDocs>(
name => {
const ruleName = parsePath(name).name

return `${REPO_URL}/blob/@emotion/eslint-plugin@${version}/packages/eslint-plugin/docs/rules/${ruleName}.md`
}
)
16 changes: 8 additions & 8 deletions packages/eslint-plugin/test/rules/import-from-emotion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
* @jest-environment node
*/

import { TSESLint } from '@typescript-eslint/utils'
import { RuleTester } from '@typescript-eslint/rule-tester'
import rule from '../../src/rules/import-from-emotion'
import { espreeParser } from '../test-utils'

const ruleTester = new TSESLint.RuleTester({
parser: espreeParser,
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
ecmaFeatures: {
jsx: true
const ruleTester = new RuleTester({
languageOptions: {
parser: espreeParser,
parserOptions: {
ecmaFeatures: {
jsx: true
}
}
}
})
Expand Down
38 changes: 27 additions & 11 deletions packages/eslint-plugin/test/rules/jsx-import.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
* @jest-environment node
*/

import { TSESLint } from '@typescript-eslint/utils'
import { RuleTester } from '@typescript-eslint/rule-tester'
import rule from '../../src/rules/jsx-import'
import { espreeParser } from '../test-utils'

const ruleTester = new TSESLint.RuleTester({
parser: espreeParser,
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
ecmaFeatures: {
jsx: true
const ruleTester = new RuleTester({
languageOptions: {
parser: espreeParser,
parserOptions: {
ecmaFeatures: {
jsx: true
}
}
}
})
Expand Down Expand Up @@ -233,18 +233,17 @@ let ele = <div css={{}} />
code: `
/** @jsx jsx */
import * as emotion from '@emotion/react'
let ele = <div css={\`color:hotpink;\`} />
let ele = <div css={{}} />
`.trim(),
errors: [
{
messageId: 'cssPropWithPragma'
}
],
output: `
/** @jsx jsx */
/** @jsx emotion.jsx */
import * as emotion from '@emotion/react'
let ele = <div css={\`color:hotpink;\`} />
let ele = <div css={{}} />
`.trim()
},
{
Expand Down Expand Up @@ -337,6 +336,23 @@ let ele2 = <div css={{}} />
import {jsx, css} from '@emotion/react'
let ele = <div css={css\`color:hotpink;\`} />
`.trim()
},
{
code: `
/** @jsx emotion.jsx */
import * as emotion from '@emotion/react'
let ele = <div css={\`color:hotpink;\`} />
`.trim(),
errors: [
{
messageId: 'templateLiterals'
}
],
output: `
/** @jsx emotion.jsx */
import * as emotion from '@emotion/react'
let ele = <div css={emotion.css\`color:hotpink;\`} />
`.trim()
}
]
})
16 changes: 8 additions & 8 deletions packages/eslint-plugin/test/rules/no-vanilla.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
* @jest-environment node
*/

import { TSESLint } from '@typescript-eslint/utils'
import { RuleTester } from '@typescript-eslint/rule-tester'
import rule from '../../src/rules/no-vanilla'
import { espreeParser } from '../test-utils'

const ruleTester = new TSESLint.RuleTester({
parser: espreeParser,
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
ecmaFeatures: {
jsx: true
const ruleTester = new RuleTester({
languageOptions: {
parser: espreeParser,
parserOptions: {
ecmaFeatures: {
jsx: true
}
}
}
})
Expand Down
16 changes: 8 additions & 8 deletions packages/eslint-plugin/test/rules/pkg-renaming.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
* @jest-environment node
*/

import { TSESLint } from '@typescript-eslint/utils'
import { RuleTester } from '@typescript-eslint/rule-tester'
import rule from '../../src/rules/pkg-renaming'
import { espreeParser } from '../test-utils'

const ruleTester = new TSESLint.RuleTester({
parser: espreeParser,
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
ecmaFeatures: {
jsx: true
const ruleTester = new RuleTester({
languageOptions: {
parser: espreeParser,
parserOptions: {
ecmaFeatures: {
jsx: true
}
}
}
})
Expand Down
Loading

0 comments on commit 9951777

Please sign in to comment.