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 @@ + + + + + + + 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 @@ + + + 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', {