diff --git a/fixtures/output/ts-strict-with-react/javascript.js b/fixtures/output/ts-strict-with-react/javascript.js
new file mode 100644
index 0000000000..e0702598c7
--- /dev/null
+++ b/fixtures/output/ts-strict-with-react/javascript.js
@@ -0,0 +1,73 @@
+// This file is generated by ChatGPT
+
+// eslint-disable-next-line no-console
+const log = console.log
+
+// Define a class using ES6 class syntax
+class Person {
+ constructor(name, age) {
+ this.name = name
+ this.age = age
+ }
+
+ // Define a method within the class
+ sayHello() {
+ log(`Hello, my name is ${this.name} and I am ${this.age} years old.`)
+ }
+}
+
+// Create an array of objects
+const people = [
+ new Person('Alice', 30),
+ new Person('Bob', 25),
+ new Person('Charlie', 35),
+]
+
+// Use the forEach method to iterate over the array
+people.forEach((person) => {
+ person.sayHello()
+})
+
+// Use a template literal to create a multiline string
+const multilineString = `
+ This is a multiline string
+ that spans multiple lines.
+`
+
+// Use destructuring assignment to extract values from an object
+const { name, age } = people[0]
+log(`First person in the array is ${name} and they are ${age} years old.`, multilineString)
+
+// Use the spread operator to create a new array
+const numbers = [1, 2, 3]
+const newNumbers = [...numbers, 4, 5]
+log(newNumbers)
+
+// Use a try-catch block for error handling
+try {
+ // Attempt to parse an invalid JSON string
+ JSON.parse('invalid JSON')
+}
+catch (error) {
+ console.error('Error parsing JSON:', error.message)
+}
+
+// Use a ternary conditional operator
+const isEven = num => num % 2 === 0
+const number = 7
+log(`${number} is ${isEven(number) ? 'even' : 'odd'}.`)
+
+// Use a callback function with setTimeout for asynchronous code
+setTimeout(() => {
+ log('This code runs after a delay of 2 seconds.')
+}, 2000)
+
+let a, b, c, d, foo
+
+if (a
+ || b
+ || c || d
+ || (d && b)
+) {
+ foo()
+}
diff --git a/fixtures/output/ts-strict-with-react/jsx.jsx b/fixtures/output/ts-strict-with-react/jsx.jsx
new file mode 100644
index 0000000000..523af37822
--- /dev/null
+++ b/fixtures/output/ts-strict-with-react/jsx.jsx
@@ -0,0 +1,29 @@
+export function HelloWorld({
+ greeting = 'hello',
+ greeted = '"World"',
+ silent = false,
+ onMouseOver,
+}) {
+ if (!greeting) {
+ return null
+ };
+
+ // TODO: Don't use random in render
+ const num = Math
+ .floor (Math.random() * 1e+7)
+ .toString()
+ .replace(/\.\d+/g, '')
+
+ return (
+
+ { greeting.slice(0, 1).toUpperCase() + greeting.slice(1).toLowerCase() }
+ {greeting.endsWith(',')
+ ? ' '
+ : ", " }
+
+ { greeted }
+
+ { (silent) ? '.' : '!'}
+
+ )
+}
diff --git a/fixtures/output/ts-strict-with-react/markdown.md b/fixtures/output/ts-strict-with-react/markdown.md
new file mode 100644
index 0000000000..a66c94c891
--- /dev/null
+++ b/fixtures/output/ts-strict-with-react/markdown.md
@@ -0,0 +1,34 @@
+Header
+======
+
+_Look,_ code blocks are formatted *too!*
+
+```js
+// This should be handled by ESLint instead of Prettier
+function identity(x) {
+ if (foo) {
+ console.log('bar')
+ }
+}
+```
+
+```css
+/* This should be handled by Prettier */
+.foo { color:red;}
+```
+
+Pilot|Airport|Hours
+--|:--:|--:
+John Doe|SKG|1338
+Jane Roe|JFK|314
+
+- - - - - - - - - - - - - - -
+
++ List
+ + with a [link] (/to/somewhere)
++ and [another one]
+
+ [another one]: http://example.com 'Example title'
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+Curabitur consectetur maximus risus, sed maximus tellus tincidunt et.
diff --git a/fixtures/output/ts-strict-with-react/toml.toml b/fixtures/output/ts-strict-with-react/toml.toml
new file mode 100644
index 0000000000..1f73d046bd
--- /dev/null
+++ b/fixtures/output/ts-strict-with-react/toml.toml
@@ -0,0 +1,23 @@
+comma = [
+ 1,
+ 2,
+ 3,
+]
+
+[foo]
+b = 1
+c = "hello"
+a = { answer = 42 }
+indent = [
+ 1,
+ 2
+]
+
+[a-table]
+apple.type = "fruit"
+apple.skin = "thin"
+apple.color = "red"
+
+orange.type = "fruit"
+orange.skin = "thick"
+orange.color = "orange"
diff --git a/fixtures/output/ts-strict-with-react/tsx.tsx b/fixtures/output/ts-strict-with-react/tsx.tsx
new file mode 100644
index 0000000000..ab640af672
--- /dev/null
+++ b/fixtures/output/ts-strict-with-react/tsx.tsx
@@ -0,0 +1,32 @@
+export function Component1() {
+ return
+}
+
+export function jsx2() {
+ const props = { a: 1, b: 2 }
+ return (
+
+
+ Inline Text
+
+
+ Block Text
+
+
+ Mixed
+
Foo
+ Text
+
Bar
+
+
+ foo
+ bar
+ baz
+
+
+ )
+}
diff --git a/fixtures/output/ts-strict-with-react/typescript.ts b/fixtures/output/ts-strict-with-react/typescript.ts
new file mode 100644
index 0000000000..29016e3855
--- /dev/null
+++ b/fixtures/output/ts-strict-with-react/typescript.ts
@@ -0,0 +1,84 @@
+// Define a TypeScript interface
+interface Person {
+ name: string
+ age: number
+}
+
+// Create an array of objects with the defined interface
+const people: Person[] = [
+ { name: 'Alice', age: 30 },
+ { name: 'Bob', age: 25 },
+ { name: 'Charlie', age: 35 },
+]
+
+// eslint-disable-next-line no-console
+const log = console.log
+
+// Use a for...of loop to iterate over the array
+for (const person of people) {
+ log(`Hello, my name is ${person.name} and I am ${person.age} years old.`)
+}
+
+// Define a generic function
+function identity< T >(arg: T): T {
+ return arg
+}
+
+// Use the generic function with type inference
+const result = identity(
+ 'TypeScript is awesome',
+)
+log(result)
+
+// Use optional properties in an interface
+interface Car {
+ make: string
+ model?: string
+}
+
+// Create objects using the interface
+const car1: Car = { make: 'Toyota' }
+const car2: Car = {
+ make: 'Ford',
+ model: 'Focus',
+}
+
+// Use union types
+type Fruit = 'apple' | 'banana' | 'orange'
+const favoriteFruit: Fruit = 'apple'
+
+// Use a type assertion to tell TypeScript about the type
+const inputValue: any = '42'
+const numericValue = inputValue as number
+
+// Define a class with access modifiers
+class Animal {
+ private name: string
+ constructor(name: string) {
+ this.name = name
+ }
+
+ protected makeSound(sound: string) {
+ log(`${this.name} says ${sound}`)
+ }
+}
+
+// Extend a class
+class Dog extends Animal {
+ constructor(private alias: string) {
+ super(alias)
+ }
+
+ bark() {
+ this.makeSound('Woof!')
+ }
+}
+
+const dog = new Dog('Buddy')
+dog.bark()
+
+function fn(): string {
+ return `hello${1}`
+}
+
+log(car1, car2, favoriteFruit, numericValue, fn())
diff --git a/fixtures/output/ts-strict-with-react/vue-ts.vue b/fixtures/output/ts-strict-with-react/vue-ts.vue
new file mode 100644
index 0000000000..ce35e66b0c
--- /dev/null
+++ b/fixtures/output/ts-strict-with-react/vue-ts.vue
@@ -0,0 +1,35 @@
+
+
+
+
+
{{ greeting }}
+
+ Click me!
+
+
Counter: {{ counter }}
+
+
+
+
+
+
diff --git a/fixtures/output/ts-strict-with-react/vue.vue b/fixtures/output/ts-strict-with-react/vue.vue
new file mode 100644
index 0000000000..944cc4e569
--- /dev/null
+++ b/fixtures/output/ts-strict-with-react/vue.vue
@@ -0,0 +1,27 @@
+
+
+
+
+
+ {{ greeting }}
+
+
+ Click me!
+
+
Counter: {{ counter }}
+
+
diff --git a/src/configs/react.ts b/src/configs/react.ts
index d6383183d0..7e081610a3 100644
--- a/src/configs/react.ts
+++ b/src/configs/react.ts
@@ -1,9 +1,9 @@
-import type { OptionsFiles, OptionsOverrides, OptionsTypeScriptWithTypes, TypedFlatConfigItem } from '../types'
+import type { OptionsFiles, OptionsOverrides, OptionsTypeScriptParserOptions, OptionsTypeScriptWithTypes, TypedFlatConfigItem } from '../types'
import { isPackageExists } from 'local-pkg'
-import { GLOB_SRC } from '../globs'
+import { GLOB_ASTRO_TS, GLOB_MARKDOWN, GLOB_SRC, GLOB_TS, GLOB_TSX } from '../globs'
-import { ensurePackages, interopDefault, toArray } from '../utils'
+import { ensurePackages, interopDefault } from '../utils'
// react refresh
const ReactRefreshAllowConstantExportPackages = [
@@ -20,11 +20,17 @@ const NextJsPackages = [
]
export async function react(
- options: OptionsTypeScriptWithTypes & OptionsOverrides & OptionsFiles = {},
+ options: OptionsTypeScriptParserOptions & OptionsTypeScriptWithTypes & OptionsOverrides & OptionsFiles = {},
): Promise {
const {
files = [GLOB_SRC],
+ filesTypeAware = [GLOB_TS, GLOB_TSX],
+ ignoresTypeAware = [
+ `${GLOB_MARKDOWN}/**`,
+ GLOB_ASTRO_TS,
+ ],
overrides = {},
+ tsconfigPath,
} = options
await ensurePackages([
@@ -33,21 +39,20 @@ export async function react(
'eslint-plugin-react-refresh',
])
- const tsconfigPath = options?.tsconfigPath
- ? toArray(options.tsconfigPath)
- : undefined
const isTypeAware = !!tsconfigPath
+ const typeAwareRules: TypedFlatConfigItem['rules'] = {
+ 'react/no-leaked-conditional-rendering': 'warn',
+ }
+
const [
pluginReact,
pluginReactHooks,
pluginReactRefresh,
- parserTs,
] = await Promise.all([
interopDefault(import('@eslint-react/eslint-plugin')),
interopDefault(import('eslint-plugin-react-hooks')),
interopDefault(import('eslint-plugin-react-refresh')),
- interopDefault(import('@typescript-eslint/parser')),
] as const)
const isAllowConstantExport = ReactRefreshAllowConstantExportPackages.some(i => isPackageExists(i))
@@ -71,12 +76,10 @@ export async function react(
{
files,
languageOptions: {
- parser: parserTs,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
- ...isTypeAware ? { project: tsconfigPath } : {},
},
sourceType: 'module',
},
@@ -173,15 +176,19 @@ export async function react(
'react/prefer-shorthand-boolean': 'warn',
'react/prefer-shorthand-fragment': 'warn',
- ...isTypeAware
- ? {
- 'react/no-leaked-conditional-rendering': 'warn',
- }
- : {},
-
// overrides
...overrides,
},
},
+ ...isTypeAware
+ ? [{
+ files: filesTypeAware,
+ ignores: ignoresTypeAware,
+ name: 'antfu/react/type-aware-rules',
+ rules: {
+ ...typeAwareRules,
+ },
+ }]
+ : [],
]
}
diff --git a/src/factory.ts b/src/factory.ts
index 87de63fd7c..96e9b7a71e 100644
--- a/src/factory.ts
+++ b/src/factory.ts
@@ -207,6 +207,7 @@ export function antfu(
if (enableReact) {
configs.push(react({
+ ...typescriptOptions,
overrides: getOverrides(options, 'react'),
tsconfigPath,
}))
diff --git a/test/fixtures.test.ts b/test/fixtures.test.ts
index 0d4f42f471..55880251b5 100644
--- a/test/fixtures.test.ts
+++ b/test/fixtures.test.ts
@@ -74,6 +74,22 @@ runWithConfig(
},
)
+// https://github.com/antfu/eslint-config/issues/618
+runWithConfig(
+ 'ts-strict-with-react',
+ {
+ typescript: {
+ tsconfigPath: './tsconfig.json',
+ },
+ react: true,
+ },
+ {
+ rules: {
+ 'ts/no-unsafe-return': ['off'],
+ },
+ },
+)
+
runWithConfig(
'with-formatters',
{