@tommy-mitchell/eslint-config-xo 2.0.0-beta.1 → 2.0.0-beta.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,59 +1,59 @@
1
1
  {
2
2
  "name": "@tommy-mitchell/eslint-config-xo",
3
- "version": "2.0.0-beta.1",
3
+ "version": "2.0.0-beta.11",
4
4
  "description": "Personal config for XO.",
5
- "keywords": [
6
- "nodejs",
7
- "config",
8
- "xo"
9
- ],
10
- "repository": "tommy-mitchell/eslint-config-xo",
11
5
  "license": "MIT",
12
- "author": {
13
- "name": "Tommy Mitchell",
14
- "url": "https://tommymitchell.io"
15
- },
16
- "sideEffects": false,
6
+ "repository": "tommy-mitchell/eslint-config-xo",
7
+ "author": "Tommy Mitchell (https://tommymitchell.io)",
17
8
  "type": "module",
18
- "files": [
19
- "src"
20
- ],
21
9
  "exports": {
22
10
  "types": "./src/index.d.ts",
23
11
  "default": "./src/index.js"
24
12
  },
13
+ "sideEffects": false,
14
+ "engines": {
15
+ "node": "^22.22 || ^24.15 || >=26"
16
+ },
25
17
  "scripts": {
26
- "format": "dprint fmt && xo --fix",
27
18
  "lint": "xo",
19
+ "format": "dprint fmt && xo --fix",
28
20
  "test": "ava"
29
21
  },
22
+ "files": [
23
+ "src"
24
+ ],
25
+ "keywords": [
26
+ "nodejs",
27
+ "config",
28
+ "xo"
29
+ ],
30
30
  "dependencies": {
31
- "@eslint-react/eslint-plugin": "^3.0.0",
32
- "@next/eslint-plugin-next": "^16.0.5",
33
- "eslint-plugin-better-tailwindcss": "^4.3.2",
34
- "eslint-plugin-jsx-a11y": "^6.10.2",
35
- "eslint-plugin-perfectionist": "^5.7.0",
36
- "eslint-plugin-react-refresh": "^0.5.2",
37
- "eslint-plugin-simple-import-sort": "^12.1.1",
38
- "eslint-plugin-sort-react-dependency-arrays": "^1.0.0"
31
+ "@next/eslint-plugin-next": "^16.3.7",
32
+ "@tanstack/eslint-plugin-router": "^1.162.0",
33
+ "eslint-config-xo-react": "^0.32.0",
34
+ "eslint-plugin-better-tailwindcss": "^4.7.0",
35
+ "eslint-plugin-perfectionist": "^5.12.1",
36
+ "eslint-plugin-react-refresh": "^0.5.7",
37
+ "eslint-plugin-simple-import-sort": "^14.0.0"
39
38
  },
40
39
  "devDependencies": {
41
- "@stylistic/eslint-plugin": "^5.6.1",
42
- "@tommy-mitchell/dprint-config": "0.6.0",
43
- "ava": "^7.0.0",
40
+ "@stylistic/eslint-plugin": "^5.10.0",
41
+ "@tommy-mitchell/dprint-config": "^0.8.0",
42
+ "@tommy-mitchell/tsconfig": "^4.0.0",
43
+ "@types/node": "^22.20.4",
44
+ "@types/react": "^19.3.0",
45
+ "ava": "^8.0.1",
44
46
  "dprint": "0.x",
45
- "execa": "^9.6.0",
46
- "react": "^19.2.0",
47
- "tailwindcss": "^4.1.17",
48
- "tempy": "^3.1.0",
49
- "typescript": "6.0.2",
50
- "xo": "~2.0.2"
47
+ "react": "^19.3.0",
48
+ "tailwindcss": "^4.3.3",
49
+ "typescript": "~6.0.3",
50
+ "xo": "~5.0.1"
51
51
  },
52
52
  "peerDependencies": {
53
53
  "dprint": "0.x",
54
54
  "react": "19.x",
55
55
  "tailwindcss": "4.x",
56
- "xo": "2.x"
56
+ "xo": "5.x"
57
57
  },
58
58
  "peerDependenciesMeta": {
59
59
  "dprint": {
@@ -66,10 +66,10 @@
66
66
  "optional": true
67
67
  }
68
68
  },
69
- "engines": {
70
- "node": ">=22"
71
- },
72
69
  "ava": {
70
+ "extensions": [
71
+ "ts"
72
+ ],
73
73
  "timeout": "30s"
74
74
  }
75
75
  }
package/readme.md CHANGED
@@ -45,9 +45,9 @@ pnpm add --save-dev @tommy-mitchell/eslint-config-xo xo @tommy-mitchell/dprint-c
45
45
  ### Peer Dependencies
46
46
 
47
47
  - [xo](https://github.com/xojs/xo) - JavaScript/TypeScript linter (ESLint wrapper) with great defaults.
48
- - [dprint](https://github.com/dprint/dprint) (*Optional*) - Pluggable and configurable code formatting platform written in Rust.
49
- - [react](https://react.dev) (*Optional*) - The library for web and native user interfaces.
50
- - [tailwindcss](https://tailwindcss.com) (*Optional*) - A utility-first CSS framework for rapid UI development.
48
+ - [dprint](https://github.com/dprint/dprint) (_Optional_) - Pluggable and configurable code formatting platform written in Rust.
49
+ - [react](https://react.dev) (_Optional_) - The library for web and native user interfaces.
50
+ - [tailwindcss](https://tailwindcss.com) (_Optional_) - A utility-first CSS framework for rapid UI development.
51
51
 
52
52
  ## Usage (Flat Config)
53
53
 
@@ -58,12 +58,27 @@ import * as configs from "@tommy-mitchell/eslint-config-xo";
58
58
  /** @type {import('xo').FlatXoConfig} */
59
59
  export default [
60
60
  ...configs.xo,
61
- ...configs.react({ version: "19" }), // If using React
61
+ ...configs.react, // If using React
62
62
  ...configs.next, // If using Next.js
63
+ ...configs.tanstack, // If using TanStack Start
63
64
  ...configs.tailwind({ config: "src/tailwind.css" }), // If using TailwindCSS
64
65
  ...configs.dprint, // If using dprint (must be last to override stylistic rules)
65
66
  // Plus any overrides
66
- ]
67
+ ];
68
+ ```
69
+
70
+ ### TailwindCSS
71
+
72
+ Add the following to your `settings.json` to prevent duplicate lints:
73
+
74
+ ```jsonc
75
+ "tailwindCSS.lint.suggestCanonicalClasses": "ignore",
76
+ ```
77
+
78
+ If using TailwindCSS v3:
79
+
80
+ ```js
81
+ configs.tailwind({ config: "tailwind.config.ts", version: "3" }),
67
82
  ```
68
83
 
69
84
  ### VS Code
@@ -80,7 +95,7 @@ Add the following to your `settings.json`:
80
95
  If formatting with `dprint`:
81
96
 
82
97
  ```jsonc
83
- "[javascript][javascriptreact][typescript][typescriptreact]": {
98
+ "[javascript][javascriptreact][typescript][typescriptreact][json][jsonc][yaml][markdown]": {
84
99
  "editor.formatOnSave": true,
85
100
  "editor.defaultFormatter": "dprint.dprint",
86
101
  "editor.codeActionsOnSave": {
package/src/index.d.ts CHANGED
@@ -3,5 +3,6 @@ import type { FlatXoConfig } from "xo";
3
3
  export declare const dprint: FlatXoConfig;
4
4
  export declare const next: FlatXoConfig;
5
5
  export declare const react: FlatXoConfig;
6
- export declare const tailwind: ({ config }: { config: string; }) => FlatXoConfig;
6
+ export declare const tailwind: (options: { config: string; version?: "3" | "4"; }) => FlatXoConfig;
7
+ export declare const tanstack: FlatXoConfig;
7
8
  export declare const xo: FlatXoConfig;
package/src/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export { default as dprint } from "./dprint.js";
2
2
  export { default as next } from "./next.js";
3
3
  export { default as react } from "./react.js";
4
- // TODO: only load if tailwind is installed
5
4
  export { default as tailwind } from "./tailwind.js";
5
+ export { default as tanstack } from "./tanstack.js";
6
6
  export { default as xo } from "./xo.js";
package/src/react.js CHANGED
@@ -1,26 +1,48 @@
1
- import eslintReact from "@eslint-react/eslint-plugin";
2
- import jsxA11y from "eslint-plugin-jsx-a11y";
1
+ import eslintConfigXoReact from "eslint-config-xo-react";
3
2
  import { reactRefresh } from "eslint-plugin-react-refresh";
4
- import sortReactDependencyArrays from "eslint-plugin-sort-react-dependency-arrays";
5
3
 
6
- const configs = [
7
- eslintReact.configs["strict-type-checked"],
8
- jsxA11y.flatConfigs.recommended,
4
+ const customGroups = [
5
+ { elementNamePattern: "^on[A-Z].*", groupName: "callback" },
6
+ { elementNamePattern: "^(children|dangerouslySetInnerHTML|key|ref)$", groupName: "reserved" },
9
7
  ];
10
8
 
9
+ const reactDependencyHooks = [
10
+ "useCallback",
11
+ "useEffect",
12
+ "useImperativeHandle",
13
+ "useInsertionEffect",
14
+ "useLayoutEffect",
15
+ "useMemo",
16
+ ];
17
+
18
+ // useX()
19
+ const reactHookCallees = reactDependencyHooks.map(hook => ([
20
+ "[callee.type=\"Identifier\"]",
21
+ `[callee.name="${hook}"]`,
22
+ ].join(""))).join(", ");
23
+
24
+ // React.useX()
25
+ const reactHookMemberCallees = reactDependencyHooks.map(hook => ([
26
+ "[callee.type=\"MemberExpression\"]",
27
+ "[callee.object.type=\"Identifier\"]",
28
+ "[callee.object.name=\"React\"]",
29
+ "[callee.property.type=\"Identifier\"]",
30
+ `[callee.property.name="${hook}"]`,
31
+ ].join(""))).join(", ");
32
+
11
33
  /** @type {import('xo').FlatXoConfig} */
12
- export default [...configs, {
34
+ export default [...eslintConfigXoReact(), {
13
35
  plugins: {
14
36
  "react-refresh": reactRefresh.plugin,
15
- "sort-react-dependency-arrays": sortReactDependencyArrays,
16
37
  },
17
- // TODO: boolean-prop-naming
18
38
  rules: {
39
+ "perfectionist/sort-arrays": ["error", {
40
+ useConfigurationIf: {
41
+ matchesAstSelector: `CallExpression:is(${reactHookCallees}, ${reactHookMemberCallees}) > ArrayExpression`,
42
+ },
43
+ }],
19
44
  "perfectionist/sort-jsx-props": ["error", {
20
- customGroups: [
21
- { elementNamePattern: "^on[A-Z].*", groupName: "callback" },
22
- { elementNamePattern: "^(children|dangerouslySetInnerHTML|key|ref)$", groupName: "reserved" },
23
- ],
45
+ customGroups,
24
46
  groups: ["reserved", "shorthand-prop", "unknown", "callback"],
25
47
  }],
26
48
  "react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
@@ -36,7 +58,6 @@ export default [...configs, {
36
58
  String.raw`^.+\.s?css$`,
37
59
  ]],
38
60
  }],
39
- "sort-react-dependency-arrays/sort": "error",
40
61
  "unicorn/filename-case": ["error", {
41
62
  cases: {
42
63
  camelCase: true,
@@ -45,20 +66,15 @@ export default [...configs, {
45
66
  },
46
67
  }],
47
68
  },
48
- }, {
49
- files: ["src/constants/**/*.{ts,cts,mts,tsx}", "**/constants.{ts,cts,mts,tsx}"],
50
- rules: {
51
- "@typescript-eslint/naming-convention": "off",
52
- },
53
69
  }, {
54
70
  files: "**/*.{ts,cts,mts,tsx}",
55
71
  rules: {
56
72
  "perfectionist/sort-interfaces": ["error", {
57
- customGroups: [{ elementNamePattern: "^on[A-Z].*", groupName: "callback" }],
73
+ customGroups,
58
74
  groups: ["unknown", "callback"],
59
75
  }],
60
76
  "perfectionist/sort-object-types": ["error", {
61
- customGroups: [{ elementNamePattern: "^on[A-Z].*", groupName: "callback" }],
77
+ customGroups,
62
78
  groups: ["unknown", "callback"],
63
79
  }],
64
80
  },
package/src/tailwind.js CHANGED
@@ -1,22 +1,23 @@
1
1
  import eslintPluginBetterTailwindcss from "eslint-plugin-better-tailwindcss";
2
2
 
3
- /** @type {({config}: {config: string}) => import('xo').FlatXoConfig} */
4
- export default ({ config }) => [{
5
- plugins: {
6
- "better-tailwindcss": eslintPluginBetterTailwindcss,
7
- },
3
+ // eslint-disable-next-line jsdoc/require-description
4
+ /** @type {(options: {config: string; version?: "3" | "4"}) => import('xo').FlatXoConfig} */
5
+ export default ({ config, version = "4" }) => [{
6
+ ...eslintPluginBetterTailwindcss.configs.recommended,
8
7
  rules: {
9
- ...eslintPluginBetterTailwindcss.configs.recommended.rules,
10
8
  "better-tailwindcss/enforce-consistent-line-wrapping": ["warn", {
11
9
  indent: "tab",
12
10
  preferSingleLine: true,
13
11
  printWidth: 120,
12
+ tabWidth: 2,
14
13
  }],
14
+ "better-tailwindcss/enforce-logical-properties": ["warn"],
15
15
  },
16
16
  settings: {
17
17
  "better-tailwindcss": {
18
18
  callees: ["clsx", "cn", "cnx", "cva", "cx", "tv", "twJoin", "twMerge"],
19
- entryPoint: config, // v4 config path
19
+ detectComponentClasses: true,
20
+ ...(version === "3" ? { tailwindConfig: config } : { entryPoint: config }),
20
21
  },
21
22
  },
22
23
  }];
@@ -0,0 +1,38 @@
1
+ import pluginRouter from "@tanstack/eslint-plugin-router";
2
+ import { reactRefresh } from "eslint-plugin-react-refresh";
3
+
4
+ /** @type {import('xo').FlatXoConfig} */
5
+ export default [
6
+ ...pluginRouter.configs["flat/recommended"],
7
+ // https://github.com/ArnaudBarre/eslint-plugin-react-refresh/issues/102#issuecomment-3881982814
8
+ reactRefresh.configs.vite({
9
+ extraHOCs: [
10
+ "createFileRoute",
11
+ "createLazyFileRoute",
12
+ "createRootRoute",
13
+ "createRootRouteWithContext",
14
+ "createLink",
15
+ "createRoute",
16
+ "createLazyRoute",
17
+ ],
18
+ }),
19
+ {
20
+ files: "**/**.{ts,cts,mts,tsx}",
21
+ rules: {
22
+ "@typescript-eslint/only-throw-error": ["error", {
23
+ allow: [{
24
+ from: "package",
25
+ name: "Redirect",
26
+ package: "@tanstack/router-core",
27
+ }, {
28
+ from: "package",
29
+ name: "NotFoundError",
30
+ package: "@tanstack/router-core",
31
+ }],
32
+ // https://github.com/xojs/eslint-config-xo/blob/55b6ae18731cc30599057de401fd711ff9f5cb8c/source/typescript-rules.js#L476-L483
33
+ allowThrowingAny: false,
34
+ allowThrowingUnknown: true,
35
+ }],
36
+ },
37
+ },
38
+ ];
package/src/xo.js CHANGED
@@ -12,8 +12,11 @@ export default [{
12
12
  "capitalized-comments": "off",
13
13
  "import-x/extensions": "off",
14
14
  "import-x/order": "off",
15
+ "jsdoc/require-asterisk-prefix": "off",
15
16
  // https://github.com/xojs/eslint-config-xo/blob/v0.44.0/index.js#L186
16
17
  "no-warning-comments": ["warn", { decoration: ["/", "*"] }],
18
+ // TODO: submit PR upstream
19
+ "node-test/no-import-test-files": "off",
17
20
  "object-shorthand": "off",
18
21
  "perfectionist/sort-array-includes": "error",
19
22
  "perfectionist/sort-maps": "error",
@@ -28,6 +31,7 @@ export default [{
28
31
  }],
29
32
  "unicorn/prefer-regexp-test": "off",
30
33
  "unicorn/prevent-abbreviations": "off",
34
+ "unicorn/single-line-block-comment-style": ["error", "single-line"],
31
35
  },
32
36
  settings: {
33
37
  perfectionist: {
@@ -38,16 +42,31 @@ export default [{
38
42
  }, {
39
43
  files: "**/*.{ts,cts,mts,tsx}",
40
44
  rules: {
45
+ "@typescript-eslint/consistent-type-imports": ["error", {
46
+ disallowTypeAnnotations: false,
47
+ fixStyle: "inline-type-imports",
48
+ }],
41
49
  // https://github.com/xojs/eslint-config-xo-typescript/blob/v2.0.0/index.js#L384
42
- "@typescript-eslint/no-confusing-void-expression": ["error", { ignoreArrowShorthand: true }],
50
+ "@typescript-eslint/no-confusing-void-expression": ["error", {
51
+ ignoreArrowShorthand: true,
52
+ }],
43
53
  // https://github.com/xojs/eslint-config-xo-typescript/blob/v2.0.0/index.js#L707
44
54
  "@typescript-eslint/prefer-regexp-exec": "off",
55
+ "@typescript-eslint/strict-boolean-expressions": ["error", {
56
+ allowNullableBoolean: true,
57
+ allowNullableObject: true,
58
+ allowNullableString: true, // diff from xo
59
+ allowNumber: false,
60
+ allowString: true, // diff from xo
61
+ }],
45
62
  "perfectionist/sort-enums": "error",
46
63
  "perfectionist/sort-heritage-clauses": "error",
47
64
  "perfectionist/sort-interfaces": "error",
48
65
  "perfectionist/sort-intersection-types": "error",
49
66
  "perfectionist/sort-object-types": "error",
50
- "perfectionist/sort-union-types": ["error", { groups: ["unknown", "keyword", "nullish"] }],
67
+ "perfectionist/sort-union-types": ["error", {
68
+ groups: ["unknown", "keyword", "nullish"],
69
+ }],
51
70
  },
52
71
  settings: {
53
72
  n: { typescriptExtensionMap: [[".ts", ".ts"], [".tsx", ".tsx"]] },
@@ -69,4 +88,11 @@ export default [{
69
88
  rules: {
70
89
  "import-x/no-anonymous-default-export": "off",
71
90
  },
91
+ }, {
92
+ files: "package.json",
93
+ rules: {
94
+ "package-json/dependency-version-range": ["warn", {
95
+ exceptions: ["typescript"],
96
+ }],
97
+ },
72
98
  }];