Compare commits

..

18 Commits

Author SHA1 Message Date
c38449c759 Bump 2025-06-12 21:59:23 -04:00
d7e9b986c7 Bump 2025-06-07 20:32:51 -04:00
d06cd4937c Bump deps 2025-05-24 17:38:09 -04:00
a746e2fbc8 Bump version 2025-05-10 19:13:52 -04:00
9b4a6cb498 Bump deps 2025-04-27 01:01:23 -04:00
d7990e5cf1 Update 2025-02-26 14:00:41 -05:00
cddee19340 Bump 2025-02-05 18:55:12 -05:00
c2d582dea0 Update 2025-01-25 19:44:09 -05:00
8824c2166d Bump 2024-12-28 18:34:57 -05:00
51df8a9a2c Bump 2024-12-15 21:38:43 -05:00
1e56001e78 New version 2024-12-15 02:30:34 -05:00
2b4c3038b3 Update 2024-12-12 19:48:39 -05:00
eb5f72a049 Bump 2024-12-08 19:14:11 -05:00
952914699b Bump version and fix glob 2024-12-01 17:51:52 -05:00
78ed00fd21 Bump version 2024-11-28 23:27:52 -05:00
43d9cc294e add default 2024-11-23 17:04:14 -05:00
235cf4101b Bump 2024-11-23 16:50:52 -05:00
f36e47f144 Update 2024-11-17 19:58:37 -05:00
33 changed files with 3396 additions and 2521 deletions

1
.gitignore vendored
View File

@ -8,6 +8,7 @@ src/types/rules
dist2 dist2
dist/**/*.js dist/**/*.js
dist/**/*.js.map dist/**/*.js.map
!dist/default.js
# Logs # Logs
logs logs

View File

@ -7,6 +7,35 @@ export type Middleware =
| (() => Promise<MiddlewareResult>) | (() => Promise<MiddlewareResult>)
| (() => Promise<{ default: MiddlewareResult }>); | (() => Promise<{ default: MiddlewareResult }>);
export type Environment =
| 'jsdoc'
| 'lingui'
| 'react'
| 'reactQuery'
| 'reactRefresh'
| 'storybook'
| 'tailwind'
| 'testingLibrary'
| 'vitest';
export interface NormalizedExtendConfigOptions {
auto?: boolean;
middlewares?: Middleware[];
configs: FlatESLintConfig[];
/**
* Use `.gitignore` file to exclude files from ESLint.
*/
gitignore?: boolean;
env?: {
[key in Environment]?: boolean;
};
}
export type ExtendConfigOptions =
| FlatESLintConfig
| FlatESLintConfig[]
| NormalizedExtendConfigOptions;
/** /**
* Returns a ESLint config object. * Returns a ESLint config object.
* *
@ -28,19 +57,7 @@ export type Middleware =
* *
* @returns ESLint configuration object. * @returns ESLint configuration object.
*/ */
export function extendConfig( export function extendConfig(options?: ExtendConfigOptions): Promise<FlatESLintConfig[]>;
options:
| FlatESLintConfig[]
| {
auto?: boolean;
middlewares?: Middleware[];
configs: FlatESLintConfig[];
/**
* Use `.gitignore` file to exclude files from ESLint.
*/
gitignore?: boolean;
},
): Promise<FlatESLintConfig[]>;
export const error = 'error'; export const error = 'error';
export const warn = 'warn'; export const warn = 'warn';

5
dist/default.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
import type { FlatESLintConfig } from '@aet/eslint-define-config';
declare const _default: FlatESLintConfig[];
export default _default;

3
dist/default.js vendored Normal file
View File

@ -0,0 +1,3 @@
import { extendConfig } from './config/index.js';
export default await extendConfig();

2
dist/eslint-init.sh vendored Normal file
View File

@ -0,0 +1,2 @@
#!/bin/bash
echo 'export { default } from "@aet/eslint-rules/default";' > eslint.config.js

69
dist/package.json vendored
View File

@ -1,61 +1,68 @@
{ {
"name": "@aet/eslint-rules", "name": "@aet/eslint-rules",
"version": "2.0.6", "version": "2.0.51",
"license": "UNLICENSED", "license": "UNLICENSED",
"type": "module", "type": "module",
"bin": { "bin": {
"eslint-init": "eslint-init.sh",
"eslint-install": "install.js", "eslint-install": "install.js",
"eslint-print": "print-config.sh" "eslint-print": "print-config.sh"
}, },
"main": "./config/index.js",
"peerDependencies": { "peerDependencies": {
"eslint": "^9.14.0", "eslint": "^9.15.0",
"typescript": "^5.6.3" "typescript": "^5.7.2"
},
"exports": {
".": "./config/index.js",
"./default": "./default.js",
"./prettier": "./prettier.js",
"./tsconfig": "./tsconfig.json",
"./types": "./types.js"
}, },
"optionalDependencies": { "optionalDependencies": {
"@tanstack/eslint-plugin-query": "^5.59.7" "@tanstack/eslint-plugin-query": "^5.62.1"
}, },
"dependencies": { "dependencies": {
"@antfu/install-pkg": "^0.4.1", "@aet/eslint-define-config": "^0.1.15",
"@antfu/install-pkg": "^1.1.0",
"@eslint-community/eslint-utils": "^4.7.0",
"@eslint-react/eslint-plugin": "1.51.3",
"@eslint/js": "^9.28.0",
"@nolyfill/is-core-module": "^1.0.39", "@nolyfill/is-core-module": "^1.0.39",
"@aet/eslint-define-config": "^0.1.0-beta.33", "@stylistic/eslint-plugin": "^4.4.1",
"@eslint/js": "^9.14.0", "@typescript-eslint/eslint-plugin": "^8.34.0",
"@eslint-community/eslint-utils": "^4.4.1", "@typescript-eslint/parser": "^8.34.0",
"@types/eslint": "^9.6.1", "@typescript-eslint/type-utils": "^8.34.0",
"@typescript-eslint/eslint-plugin": "^8.13.0", "@typescript-eslint/utils": "^8.34.0",
"@typescript-eslint/parser": "^8.13.0",
"@eslint-react/eslint-plugin": "1.15.2",
"@stylistic/eslint-plugin": "^2.10.1",
"@typescript-eslint/type-utils": "^8.13.0",
"@typescript-eslint/utils": "^8.13.0",
"aria-query": "^5.3.2", "aria-query": "^5.3.2",
"axe-core": "^4.10.2", "axe-core": "^4.10.3",
"axobject-query": "4.1.0", "axobject-query": "4.1.0",
"damerau-levenshtein": "1.0.8", "damerau-levenshtein": "1.0.8",
"debug": "^4.3.7", "debug": "^4.4.1",
"doctrine": "^3.0.0", "doctrine": "^3.0.0",
"emoji-regex": "^10.4.0", "emoji-regex": "^10.4.0",
"enhanced-resolve": "^5.17.1", "enhanced-resolve": "^5.18.1",
"typescript-eslint": "^8.13.0", "eslint-config-prettier": "^10.1.5",
"eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-node": "^0.3.9", "eslint-import-resolver-node": "^0.3.9",
"eslint-import-resolver-typescript": "^3.6.3", "eslint-import-resolver-typescript": "^4.4.3",
"eslint-module-utils": "^2.12.0", "eslint-module-utils": "^2.12.0",
"eslint-plugin-import-x": "^4.4.0", "eslint-plugin-import-x": "^4.15.1",
"eslint-plugin-unicorn": "^56.0.0", "eslint-plugin-regexp": "^2.9.0",
"eslint-plugin-unicorn": "^59.0.1",
"esprima": "^4.0.1", "esprima": "^4.0.1",
"esquery": "^1.6.0", "esquery": "^1.6.0",
"estraverse": "^5.3.0", "estraverse": "^5.3.0",
"fast-glob": "^3.3.2", "fast-glob": "^3.3.3",
"get-tsconfig": "^4.8.1", "get-tsconfig": "^4.10.1",
"globals": "^15.12.0", "globals": "^16.2.0",
"ignore": "^6.0.2", "ignore": "^7.0.5",
"is-bun-module": "^1.2.1", "is-bun-module": "^2.0.0",
"is-glob": "^4.0.3", "is-glob": "^4.0.3",
"language-tags": "^1.0.9", "language-tags": "^2.1.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"minimatch": "^10.0.1", "minimatch": "^10.0.1",
"semver": "^7.6.3" "semver": "^7.7.2",
"typescript-eslint": "^8.34.0"
}, },
"pnpm": { "pnpm": {
"overrides": { "overrides": {

19
dist/tsconfig.json vendored Normal file
View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"allowArbitraryExtensions": true,
"allowImportingTsExtensions": true,
"jsx": "react-jsx",
"module": "Preserve",
"moduleResolution": "Bundler",
"noEmit": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"resolveJsonModule": true,
"strict": true,
"stripInternal": true,
"target": "ESNext",
"verbatimModuleSyntax": true
}
}

View File

@ -6,71 +6,93 @@
"build": "./scripts/build.ts", "build": "./scripts/build.ts",
"check-import": "./scripts/check-imports.ts", "check-import": "./scripts/check-imports.ts",
"define": "/usr/local/bin/codium ./packages/eslint-define-config", "define": "/usr/local/bin/codium ./packages/eslint-define-config",
"do": "yarn build; (cd dist && ver bump && npm publish && ver unpub)" "sync": "./scripts/sync-deps.ts",
"do": "(cd dist && ver bump && npm publish && ver unpub)"
}, },
"private": true, "private": true,
"devDependencies": { "dependencies": {
"@aet/eslint-define-config": "^0.1.0-beta.33", "@aet/eslint-define-config": "^0.1.16",
"@antfu/install-pkg": "^0.4.1", "@antfu/install-pkg": "^1.1.0",
"@babel/core": "^7.26.0", "@eslint-community/eslint-utils": "^4.7.0",
"@babel/plugin-transform-flow-strip-types": "^7.25.9", "@eslint-react/eslint-plugin": "1.52.2",
"@babel/preset-env": "^7.26.0", "@eslint/js": "^9.28.0",
"@eslint-react/eslint-plugin": "^1.15.2", "@nolyfill/is-core-module": "^1.0.39",
"@eslint/js": "^9.14.0", "@stylistic/eslint-plugin": "^4.4.1",
"@graphql-eslint/eslint-plugin": "^3.20.1", "@typescript-eslint/eslint-plugin": "^8.34.0",
"@stylistic/eslint-plugin": "^2.10.1", "@typescript-eslint/parser": "^8.34.0",
"@swc-node/register": "^1.10.9", "@typescript-eslint/type-utils": "^8.34.0",
"@tanstack/eslint-plugin-query": "^5.60.1", "@typescript-eslint/utils": "^8.34.0",
"@types/babel-plugin-macros": "^3.1.3", "aria-query": "^5.3.2",
"@types/babel__core": "^7.20.5", "axe-core": "^4.10.3",
"@types/eslint": "^9.6.1", "axobject-query": "4.1.0",
"@types/eslint-config-prettier": "^6.11.3", "damerau-levenshtein": "1.0.8",
"@types/eslint-plugin-tailwindcss": "^3.17.0", "debug": "^4.4.1",
"@types/eslint__js": "^8.42.3", "doctrine": "^3.0.0",
"@types/esprima": "^4.0.6", "emoji-regex": "^10.4.0",
"@types/esquery": "^1.5.4", "enhanced-resolve": "^5.18.1",
"@types/estree": "^1.0.6", "eslint-config-prettier": "^10.1.5",
"@types/estree-jsx": "^1.0.5", "eslint-import-resolver-node": "^0.3.9",
"@types/lodash-es": "^4.17.12", "eslint-import-resolver-typescript": "^4.4.3",
"@types/node": "^22.9.0", "eslint-module-utils": "^2.12.0",
"@types/react-refresh": "^0.14.6", "eslint-plugin-import-x": "^4.15.2",
"@typescript-eslint/eslint-plugin": "^8.13.0", "eslint-plugin-regexp": "^2.9.0",
"@typescript-eslint/parser": "^8.13.0", "eslint-plugin-unicorn": "^59.0.1",
"@typescript-eslint/type-utils": "^8.13.0",
"@typescript-eslint/types": "^8.13.0",
"@typescript-eslint/typescript-estree": "^8.13.0",
"@typescript-eslint/utils": "^8.13.0",
"babel-plugin-macros": "^3.1.0",
"dts-bundle-generator": "9.5.1",
"esbuild": "0.24.0",
"esbuild-plugin-alias": "^0.2.1",
"eslint": "9.14.0",
"eslint-config-prettier": "^9.1.0",
"eslint-import-resolver-typescript": "^3.6.3",
"eslint-plugin-import-x": "^4.4.0",
"eslint-plugin-jsdoc": "^50.4.3",
"eslint-plugin-react-refresh": "^0.4.14",
"eslint-plugin-storybook": "canary",
"eslint-plugin-testing-library": "^6.4.0",
"eslint-plugin-unicorn": "^56.0.0",
"eslint-plugin-vitest": "^0.5.4",
"esprima": "^4.0.1", "esprima": "^4.0.1",
"esquery": "^1.6.0", "esquery": "^1.6.0",
"fast-glob": "^3.3.2", "estraverse": "^5.3.0",
"find-cache-dir": "^5.0.0", "fast-glob": "^3.3.3",
"globals": "^15.12.0", "get-tsconfig": "^4.10.1",
"graphql": "^16.9.0", "globals": "^16.2.0",
"json-schema-to-ts": "^3.1.1", "ignore": "^7.0.5",
"is-bun-module": "^2.0.0",
"is-glob": "^4.0.3",
"language-tags": "^2.1.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"nolyfill": "^1.0.42", "minimatch": "^10.0.3",
"semver": "^7.7.2",
"typescript-eslint": "^8.34.0"
},
"devDependencies": {
"@babel/core": "^7.27.4",
"@babel/plugin-transform-flow-strip-types": "^7.27.1",
"@babel/preset-env": "^7.27.2",
"@graphql-eslint/eslint-plugin": "^4.4.0",
"@swc-node/register": "^1.10.10",
"@tanstack/eslint-plugin-query": "^5.78.0",
"@types/babel-plugin-macros": "^3.1.3",
"@types/babel__core": "^7.20.5",
"@types/eslint-config-prettier": "^6.11.3",
"@types/eslint-plugin-tailwindcss": "^3.17.0",
"@types/esprima": "^4.0.6",
"@types/esquery": "^1.5.4",
"@types/estree": "^1.0.8",
"@types/estree-jsx": "^1.0.5",
"@types/lodash-es": "^4.17.12",
"@types/node": "^22.15.30",
"@types/react-refresh": "^0.14.6",
"@typescript-eslint/types": "^8.34.0",
"@typescript-eslint/typescript-estree": "^8.34.0",
"@vitest/eslint-plugin": "^1.2.4",
"babel-plugin-macros": "^3.1.0",
"dts-bundle-generator": "9.5.1",
"esbuild": "0.25.5",
"esbuild-plugin-alias": "^0.2.1",
"eslint": "^9.28.0",
"eslint-plugin-jsdoc": "^50.7.1",
"eslint-plugin-lingui": "^0.10.1",
"eslint-plugin-react-refresh": "^0.4.20",
"eslint-plugin-storybook": "^9.0.9",
"eslint-plugin-testing-library": "^7.5.2",
"graphql": "^16.11.0",
"json-schema-to-ts": "^3.1.1",
"nolyfill": "^1.0.44",
"patch-package": "^8.0.0", "patch-package": "^8.0.0",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
"prettier": "^3.3.3", "prettier": "^3.5.3",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"terser": "^5.36.0", "terser": "^5.42.0",
"type-fest": "^4.26.1", "type-fest": "^4.41.0",
"typescript": "^5.6.3", "typescript": "^5.8.3"
"typescript-eslint": "^8.13.0"
}, },
"prettier": { "prettier": {
"arrowParens": "avoid", "arrowParens": "avoid",

Submodule packages/eslint-import-resolver-typescript deleted from 3dfad602a0

Submodule packages/eslint-plugin-jsx-a11y updated: cca288b73a...a7d1a12a61

Submodule packages/eslint-plugin-n updated: 6744257b43...42464abe64

Submodule packages/jsx-ast-utils updated: 5943318eaf...a8ca8f7033

View File

@ -1,5 +1,5 @@
diff --git a/src/index.js b/src/index.js diff --git a/src/index.js b/src/index.js
index 2fa185f..3cf8018 100644 index 980081e..3cf8018 100644
--- a/src/index.js --- a/src/index.js
+++ b/src/index.js +++ b/src/index.js
@@ -1,48 +1,90 @@ @@ -1,48 +1,90 @@
@ -160,7 +160,7 @@ index 2fa185f..3cf8018 100644
strict: createConfig(strictRules, 'strict'), strict: createConfig(strictRules, 'strict'),
}; };
-module.exports = { ...jsxA11y, configs, flatConfigs }; -module.exports = Object.assign(jsxA11y, { configs, flatConfigs });
+export default { ...jsxA11y, configs, flatConfigs }; +export default { ...jsxA11y, configs, flatConfigs };
diff --git a/src/rules/autocomplete-valid.js b/src/rules/autocomplete-valid.js diff --git a/src/rules/autocomplete-valid.js b/src/rules/autocomplete-valid.js
index df7b6b8..c4d0da1 100644 index df7b6b8..c4d0da1 100644
@ -206,7 +206,7 @@ index df7b6b8..c4d0da1 100644
attributes: { attributes: {
autocomplete, autocomplete,
diff --git a/src/rules/label-has-associated-control.js b/src/rules/label-has-associated-control.js diff --git a/src/rules/label-has-associated-control.js b/src/rules/label-has-associated-control.js
index dd6b199..184199e 100644 index d65abe9..22ecee7 100644
--- a/src/rules/label-has-associated-control.js --- a/src/rules/label-has-associated-control.js
+++ b/src/rules/label-has-associated-control.js +++ b/src/rules/label-has-associated-control.js
@@ -11,7 +11,7 @@ @@ -11,7 +11,7 @@

5215
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -86,7 +86,6 @@ async function bundle(
plugins, plugins,
define: {}, define: {},
alias: {}, alias: {},
external: ['find-cache-dir'],
format: 'esm', format: 'esm',
banner: { banner: {
js: '/* eslint-disable */', js: '/* eslint-disable */',

View File

@ -20,7 +20,6 @@ pull() {
echo echo
} }
pull import-js eslint-import-resolver-typescript
pull jsx-eslint eslint-plugin-jsx-a11y pull jsx-eslint eslint-plugin-jsx-a11y
pull eslint-community eslint-plugin-n pull eslint-community eslint-plugin-n
pull jsx-eslint jsx-ast-utils pull jsx-eslint jsx-ast-utils

14
scripts/sync-deps.ts Executable file
View File

@ -0,0 +1,14 @@
#!/usr/bin/env node
import fs from 'node:fs';
const up = JSON.parse(
fs.readFileSync('package.json', 'utf8'),
) as typeof import('../package.json');
const down = JSON.parse(
fs.readFileSync('dist/package.json', 'utf8'),
) as typeof import('../dist/package.json');
down.dependencies = up.dependencies;
fs.writeFileSync('dist/package.json', JSON.stringify(down, null, 2));

View File

@ -5,23 +5,17 @@
"committer": "Jordan Harband", "committer": "Jordan Harband",
"subject": "[meta] add `repository.directory` field" "subject": "[meta] add `repository.directory` field"
}, },
"eslint-import-resolver-typescript": {
"hash": "42e7cc3eb413dda56683c1b2b2483e4756e0bd62",
"date": "2024-11-01T01:52:08+00:00",
"committer": "GitHub",
"subject": "chore(deps): update dependency @types/node to ^18.19.63 (#320)"
},
"eslint-plugin-jsx-a11y": { "eslint-plugin-jsx-a11y": {
"hash": "743168b1ba15196ec7001c7c1f368f5efbe78f0d", "hash": "a7d1a12a6198d546c4a06477b385b4fde03b762e",
"date": "2024-10-23T13:27:41+10:00", "date": "2025-06-05T12:28:53-07:00",
"committer": "Jordan Harband", "committer": "Jordan Harband",
"subject": "[New] `label-has-associated-control`: allow `labelComponents` to contain globs" "subject": "[Tests] fix linting errors introduced in 2d9ad55"
}, },
"eslint-plugin-n": { "eslint-plugin-n": {
"hash": "c4d15512b24a8c7c3ba4bf8b598e66eafd1baeec", "hash": "42464abe64c5cefb709e8e0a9072b6bb1cd7fcdc",
"date": "2024-11-07T20:14:17+08:00", "date": "2025-06-13T01:37:54+08:00",
"committer": "GitHub", "committer": "GitHub",
"subject": "chore(master): release 17.13.1 (#381)" "subject": "chore(master): release 17.20.0 (#448)"
}, },
"eslint-plugin-react": { "eslint-plugin-react": {
"hash": "983b88dd3cb5e07919517d3fde4085f60883ded7", "hash": "983b88dd3cb5e07919517d3fde4085f60883ded7",
@ -30,9 +24,9 @@
"subject": "[Tests] `no-array-index-key`: actually run valid tests" "subject": "[Tests] `no-array-index-key`: actually run valid tests"
}, },
"jsx-ast-utils": { "jsx-ast-utils": {
"hash": "5943318eaf23764eec3ff397ebb969613d728a95", "hash": "a8ca8f70331b02db537b0b5cf72ea10e3d6c9377",
"date": "2023-07-28T18:34:04-07:00", "date": "2025-02-20T08:51:06-08:00",
"committer": "Jordan Harband", "committer": "Jordan Harband",
"subject": "v3.3.5" "subject": "[Dev Deps] pin `psl` due to breaking change in a minor version"
} }
} }

43
src/config.d.ts vendored
View File

@ -7,6 +7,35 @@ export type Middleware =
| (() => Promise<MiddlewareResult>) | (() => Promise<MiddlewareResult>)
| (() => Promise<{ default: MiddlewareResult }>); | (() => Promise<{ default: MiddlewareResult }>);
export type Environment =
| 'jsdoc'
| 'lingui'
| 'react'
| 'reactQuery'
| 'reactRefresh'
| 'storybook'
| 'tailwind'
| 'testingLibrary'
| 'vitest';
export interface NormalizedExtendConfigOptions {
auto?: boolean;
middlewares?: Middleware[];
configs: FlatESLintConfig[];
/**
* Use `.gitignore` file to exclude files from ESLint.
*/
gitignore?: boolean;
env?: {
[key in Environment]?: boolean;
};
}
export type ExtendConfigOptions =
| FlatESLintConfig
| FlatESLintConfig[]
| NormalizedExtendConfigOptions;
/** /**
* Returns a ESLint config object. * Returns a ESLint config object.
* *
@ -28,19 +57,7 @@ export type Middleware =
* *
* @returns ESLint configuration object. * @returns ESLint configuration object.
*/ */
export function extendConfig( export function extendConfig(options?: ExtendConfigOptions): Promise<FlatESLintConfig[]>;
options:
| FlatESLintConfig[]
| {
auto?: boolean;
middlewares?: Middleware[];
configs: FlatESLintConfig[];
/**
* Use `.gitignore` file to exclude files from ESLint.
*/
gitignore?: boolean;
},
): Promise<FlatESLintConfig[]>;
export const error = 'error'; export const error = 'error';
export const warn = 'warn'; export const warn = 'warn';

View File

@ -3,6 +3,7 @@ import type { ESLint } from 'eslint';
import noEmptyObjectLiteral from './no-empty-object-literal'; import noEmptyObjectLiteral from './no-empty-object-literal';
import noImportDot from './no-import-dot'; import noImportDot from './no-import-dot';
import noUselessImportAlias from './no-useless-import-alias'; import noUselessImportAlias from './no-useless-import-alias';
import requireImportAttribute from './require-import-attribute';
import restrictTemplateExpressions from './restrict-template-expressions'; import restrictTemplateExpressions from './restrict-template-expressions';
type RuleLevel = 'error' | 'warn' | 'off' | 0 | 1 | 2; type RuleLevel = 'error' | 'warn' | 'off' | 0 | 1 | 2;
@ -10,29 +11,32 @@ type RuleEntry<Options> = RuleLevel | [RuleLevel, Partial<Options>];
export interface LocalRuleOptions { export interface LocalRuleOptions {
/** Bans import from the specifier '.' and '..' and replaces it with '.+/index' */ /** Bans import from the specifier '.' and '..' and replaces it with '.+/index' */
'custom/no-import-dot': RuleEntry<unknown>; 'aet/no-import-dot': RuleEntry<unknown>;
/** /**
* Enforce template literal expressions to be of `string` type * Enforce template literal expressions to be of `string` type
* @see [restrict-template-expressions](https://typescript-eslint.io/rules/restrict-template-expressions) * @see [restrict-template-expressions](https://typescript-eslint.io/rules/restrict-template-expressions)
*/ */
'typed-custom/restrict-template-expressions': RuleEntry<{ allow: string[] }>; 'typed-aet/restrict-template-expressions': RuleEntry<{ allow: string[] }>;
/** Ban assignment of empty object literals `{}` and replace them with `Object.create(null)` */ /** Ban assignment of empty object literals `{}` and replace them with `Object.create(null)` */
'custom/no-empty-object-literal': RuleEntry<unknown>; 'aet/no-empty-object-literal': RuleEntry<unknown>;
/** Ban useless import alias */ /** Ban useless import alias */
'custom/no-useless-import-alias': RuleEntry<unknown>; 'aet/no-useless-import-alias': RuleEntry<unknown>;
/** Require the use of `{ type: "json" }` for JSON imports. */
'aet/require-import-attribute': RuleEntry<unknown>;
} }
export const plugin: ESLint.Plugin = { export const plugin: ESLint.Plugin = {
name: 'custom', name: 'aet',
rules: { rules: {
'no-empty-object-literal': noEmptyObjectLiteral, 'no-empty-object-literal': noEmptyObjectLiteral,
'no-import-dot': noImportDot, 'no-import-dot': noImportDot,
'no-useless-import-alias': noUselessImportAlias, 'no-useless-import-alias': noUselessImportAlias,
'require-import-attribute': requireImportAttribute,
}, },
}; };
export const typedPlugin: ESLint.Plugin = { export const typedPlugin: ESLint.Plugin = {
name: 'typed-custom', name: 'typed-aet',
rules: { rules: {
// @ts-expect-error type mismatch // @ts-expect-error type mismatch
'restrict-template-expressions': restrictTemplateExpressions, 'restrict-template-expressions': restrictTemplateExpressions,

View File

@ -0,0 +1,61 @@
import type { Rule } from 'eslint';
import type { Identifier, SimpleLiteral } from 'estree';
interface ImportAttribute {
key: Identifier | SimpleLiteral;
value: SimpleLiteral;
}
const requireImportAttribute: Rule.RuleModule = {
meta: {
type: 'problem',
docs: {
description:
'Requires JSON imports to have a "with { type: \'json\' }" import attribute.',
category: 'Possible Errors',
recommended: false,
},
fixable: 'code',
schema: [],
messages: {
missingJsonTypeAttribute:
'JSON imports must have a "with { type: \'json\' }" import attribute.',
},
},
create(context) {
return {
ImportDeclaration(node) {
// Only check modules that end with ".json"
if (
typeof node.source.value === 'string' &&
node.source.value.endsWith('.json')
) {
// Cast the node to our extended interface to access the assertions.
const importNode = node;
const assertions = (
importNode as {
assertions?: ImportAttribute[];
}
).assertions;
const hasJsonType = assertions?.some(attr => {
const keyName =
(attr.key as Identifier).name ?? (attr.key as SimpleLiteral).value;
return keyName === 'type' && attr.value.value === 'json';
});
if (!hasJsonType) {
context.report({
node,
messageId: 'missingJsonTypeAttribute',
fix: fixer =>
fixer.insertTextAfterRange(node.source.range!, ' with { type: "json" }'),
});
}
}
},
};
},
};
export default requireImportAttribute;

View File

@ -1,63 +1,76 @@
import fs from 'node:fs'; import fs from 'node:fs';
import path from 'node:path'; import path from 'node:path';
import type { Environment, NormalizedExtendConfigOptions } from './config';
import type { Middleware } from './middleware'; import type { Middleware } from './middleware';
import { reactQuery, storybook, vitest } from './presets/misc'; import { reactQuery, storybook, vitest } from './presets/misc';
import { react, reactRefresh } from './presets/react'; import { react, reactRefresh } from './presets/react';
const jsdoc = () => import('./presets/jsdoc'); type Pkg = typeof import('../package.json');
const tailwind = () => import('./presets/tailwind'); type Dependency = keyof Pkg['dependencies'] | RemoveType<keyof Pkg['devDependencies']>;
const testingLibrary = () => import('./presets/testing-library');
const middlewares = { type RemoveType<T extends string> = T extends `@types/${infer U}__${infer V}`
? `@${U}/${V}`
: T extends `@types/${infer U}`
? U
: T;
export const middlewares = {
react, react,
reactRefresh, reactRefresh,
tailwind, tailwind: () => import('./presets/tailwind'),
storybook, storybook,
reactQuery, reactQuery,
testingLibrary, testingLibrary: () => import('./presets/testing-library'),
jsdoc, jsdoc: () => import('./presets/jsdoc'),
vitest, vitest,
lingui: () => import('./presets/lingui'),
} satisfies { } satisfies {
[key: string]: Middleware; [key in Environment]: Middleware;
}; };
export const envs: { export const envs: {
dependency: string; dependency: string[];
eslintPlugin?: string; eslintPlugin?: Dependency;
middleware: keyof typeof middlewares; middleware: keyof typeof middlewares;
}[] = [ }[] = [
{ {
dependency: 'react', dependency: ['react'],
middleware: 'react', middleware: 'react',
}, },
{ {
dependency: '@vitejs/plugin-react', dependency: ['@vitejs/plugin-react', '@vitejs/plugin-react-swc'],
eslintPlugin: 'eslint-plugin-react-refresh',
middleware: 'reactRefresh', middleware: 'reactRefresh',
}, },
{ {
dependency: 'tailwindcss', dependency: ['tailwindcss'],
eslintPlugin: 'eslint-plugin-tailwindcss', eslintPlugin: 'eslint-plugin-tailwindcss',
middleware: 'tailwind', middleware: 'tailwind',
}, },
{ {
dependency: 'storybook', dependency: ['storybook'],
eslintPlugin: 'eslint-plugin-storybook', eslintPlugin: 'eslint-plugin-storybook',
middleware: 'storybook', middleware: 'storybook',
}, },
{ {
dependency: '@tanstack/react-query', dependency: ['@lingui/core', '@lingui/react'],
eslintPlugin: 'eslint-plugin-lingui',
middleware: 'lingui',
},
{
dependency: ['@tanstack/react-query'],
eslintPlugin: '@tanstack/eslint-plugin-query', eslintPlugin: '@tanstack/eslint-plugin-query',
middleware: 'reactQuery', middleware: 'reactQuery',
}, },
{ {
dependency: '@testing-library/react', dependency: ['@testing-library/react'],
eslintPlugin: 'eslint-plugin-testing-library', eslintPlugin: 'eslint-plugin-testing-library',
middleware: 'testingLibrary', middleware: 'testingLibrary',
}, },
{ {
dependency: 'vitest', dependency: ['vitest'],
eslintPlugin: 'eslint-plugin-vitest', eslintPlugin: '@vitest/eslint-plugin',
middleware: 'vitest', middleware: 'vitest',
}, },
]; ];
@ -87,8 +100,19 @@ export function* checkEnv(): Generator<Middleware> {
const deps = getProjectDependencies(); const deps = getProjectDependencies();
for (const { dependency, eslintPlugin, middleware } of envs) { for (const { dependency, eslintPlugin, middleware } of envs) {
if (deps.has(dependency) && (!eslintPlugin || deps.has(eslintPlugin))) { if (
dependency.some(dep => deps.has(dep)) &&
(!eslintPlugin || deps.has(eslintPlugin))
) {
yield middlewares[middleware]; yield middlewares[middleware];
} }
} }
} }
export function* fromEnvironments(envs: NormalizedExtendConfigOptions['env']) {
for (const [env, enabled] of Object.entries(envs ?? {}) as [Environment, boolean][]) {
if (enabled) {
yield middlewares[env];
}
}
}

View File

@ -1,15 +1,18 @@
/* eslint-disable import-x/no-named-as-default-member */
import fs from 'node:fs'; import fs from 'node:fs';
import type { FlatESLintConfig } from '@aet/eslint-define-config'; import type { FlatESLintConfig } from '@aet/eslint-define-config';
import * as tsParser from '@typescript-eslint/parser'; import * as tsParser from '@typescript-eslint/parser';
import prettier from 'eslint-config-prettier'; import prettier from 'eslint-config-prettier';
import importPlugin from 'eslint-plugin-import-x'; import importPlugin from 'eslint-plugin-import-x';
import * as regexpPlugin from 'eslint-plugin-regexp';
import { uniq } from 'lodash-es'; import { uniq } from 'lodash-es';
import tseslint from 'typescript-eslint'; import tseslint from 'typescript-eslint';
import type { ExtendConfigOptions, NormalizedExtendConfigOptions } from './config';
import { off } from './constants'; import { off } from './constants';
import { checkEnv } from './environment'; import { checkEnv, fromEnvironments } from './environment';
import { Middleware } from './middleware'; import type { Middleware } from './middleware';
import { eslintRules } from './presets/eslint'; import { eslintRules } from './presets/eslint';
import stylistic from './presets/stylistic'; import stylistic from './presets/stylistic';
import { importRules, typescriptRules } from './presets/typescript'; import { importRules, typescriptRules } from './presets/typescript';
@ -17,29 +20,49 @@ import unicorn from './presets/unicorn';
export { error, warn, off } from './constants'; export { error, warn, off } from './constants';
export async function extendConfig( function normalizeExtendConfig(
options: options: ExtendConfigOptions,
| FlatESLintConfig[] ): NormalizedExtendConfigOptions {
| { if (Array.isArray(options)) {
auto?: boolean; options = { configs: options };
middlewares?: Middleware[]; } else if ('rules' in options) {
configs: FlatESLintConfig[]; options = { configs: [options] };
gitignore?: boolean; }
} = [],
): Promise<FlatESLintConfig[]> {
const { const {
auto = true, auto = true,
middlewares: addMiddlewares = [], middlewares = [],
configs = [], configs = [],
gitignore = true, gitignore = true,
} = Array.isArray(options) ? { configs: options } : options; env,
} = options as NormalizedExtendConfigOptions;
return {
auto,
middlewares,
configs,
gitignore,
env,
};
}
export async function extendConfig(
options: ExtendConfigOptions = [],
): Promise<FlatESLintConfig[]> {
const {
auto,
middlewares: addMiddlewares = [],
configs,
gitignore,
env,
} = normalizeExtendConfig(options);
const middlewares: Middleware[] = uniq([ const middlewares: Middleware[] = uniq([
() => import('./presets/custom'), () => import('./presets/custom'),
...(auto ? checkEnv() : []), ...(auto ? checkEnv() : []),
...fromEnvironments(env),
...addMiddlewares, ...addMiddlewares,
]); ]);
const result: FlatESLintConfig[] = [ const result: FlatESLintConfig[] = [
{ {
name: 'eslint-rules/eslint', name: 'eslint-rules/eslint',
@ -51,6 +74,17 @@ export async function extendConfig(
importPlugin.flatConfigs.typescript, importPlugin.flatConfigs.typescript,
...unicorn, ...unicorn,
stylistic, stylistic,
{
plugins: { regexp: regexpPlugin },
rules: {
...regexpPlugin.configs['flat/recommended'].rules,
// https://github.com/ota-meshi/eslint-plugin-regexp/issues/445
'regexp/strict': off,
'regexp/match-any': off,
// https://github.com/ota-meshi/eslint-plugin-regexp/issues/743
'regexp/letter-case': off,
},
},
{ {
name: 'eslint-rules/typescript-and-import-x', name: 'eslint-rules/typescript-and-import-x',
files: ['**/*.{js,mjs,cjs,jsx,ts,tsx,mts,cts}'], files: ['**/*.{js,mjs,cjs,jsx,ts,tsx,mts,cts}'],
@ -81,7 +115,7 @@ export async function extendConfig(
}, },
{ {
name: 'eslint-rules: Disable type checking', name: 'eslint-rules: Disable type checking',
files: ['*.js', '*.mjs', '*.cjs', '*.jsx'], files: ['**/*.js', '**/*.mjs', '**/*.cjs', '**/*.jsx'],
...tseslint.configs.disableTypeChecked, ...tseslint.configs.disableTypeChecked,
rules: { rules: {
'import-x/no-commonjs': off, 'import-x/no-commonjs': off,
@ -93,7 +127,7 @@ export async function extendConfig(
}, },
{ {
name: 'eslint-rules/.d.ts-files', name: 'eslint-rules/.d.ts-files',
files: ['*.d.ts'], files: ['**/*.d.ts'],
rules: { rules: {
'@typescript-eslint/consistent-type-imports': off, '@typescript-eslint/consistent-type-imports': off,
'import-x/unambiguous': off, 'import-x/unambiguous': off,
@ -113,7 +147,7 @@ export async function extendConfig(
} }
} }
if (configs) { if (configs?.length) {
result.push(...configs); result.push(...configs);
} }

View File

@ -6,7 +6,12 @@ import { envs, getProjectDependencies } from './environment';
const deps = getProjectDependencies(); const deps = getProjectDependencies();
const packages = uniq( const packages = uniq(
envs envs
.filter(_ => deps.has(_.dependency) && _.eslintPlugin && !deps.has(_.eslintPlugin)) .filter(
_ =>
_.dependency.some(dep => deps.has(dep)) &&
_.eslintPlugin &&
!deps.has(_.eslintPlugin),
)
.map(_ => _.eslintPlugin!), .map(_ => _.eslintPlugin!),
); );

View File

@ -6,5 +6,4 @@ export type Middleware =
| (() => Promise<MiddlewareResult>) | (() => Promise<MiddlewareResult>)
| (() => Promise<{ default: MiddlewareResult }>); | (() => Promise<{ default: MiddlewareResult }>);
// eslint-disable-next-line unicorn/prevent-abbreviations
export const def = <T>(module: { default: T }): T => module.default; export const def = <T>(module: { default: T }): T => module.default;

View File

@ -4,20 +4,21 @@ import { defineConfig } from '../types';
export default defineConfig([ export default defineConfig([
{ {
name: 'eslint-rules/custom', name: 'eslint-rules/aet',
plugins: { custom: plugin }, plugins: { aet: plugin },
rules: { rules: {
'custom/no-import-dot': error, 'aet/no-import-dot': error,
'custom/no-useless-import-alias': error, 'aet/no-useless-import-alias': error,
'aet/require-import-attribute': error,
} satisfies Partial<LocalRuleOptions>, } satisfies Partial<LocalRuleOptions>,
}, },
{ {
name: 'eslint-rules/typed-custom', name: 'eslint-rules/typed-aet',
plugins: { 'typed-custom': typedPlugin }, plugins: { 'typed-aet': typedPlugin },
files: ['*.ts'], files: ['**/*.ts'],
ignores: ['*.d.ts'], ignores: ['**/*.d.ts'],
rules: { rules: {
'typed-custom/restrict-template-expressions': error, 'typed-aet/restrict-template-expressions': error,
} satisfies Partial<LocalRuleOptions>, } satisfies Partial<LocalRuleOptions>,
}, },
]); ]);

12
src/presets/lingui.ts Normal file
View File

@ -0,0 +1,12 @@
import type { LinguiRulesObject } from '@aet/eslint-define-config/src/rules/lingui';
import * as pluginLingui from 'eslint-plugin-lingui';
import { defineConfig } from '../types';
// https://the-guild.dev/graphql/eslint/rules
const linguiRules: Partial<LinguiRulesObject> = {};
export default defineConfig([
pluginLingui.configs['flat/recommended'],
{ rules: linguiRules },
]);

View File

@ -12,6 +12,19 @@ export async function reactQuery() {
} }
export async function vitest() { export async function vitest() {
const { configs } = def(await import('eslint-plugin-vitest')); const { configs } = def(await import('@vitest/eslint-plugin'));
return defineConfig([configs.recommended]); return defineConfig([
configs.recommended,
{
rules: {
'vitest/expect-expect': [
'error',
{
assertFunctionNames: ['expect*', 'request.**.expect'],
additionalTestBlockFunctions: ['describe*'],
},
],
},
},
]);
} }

View File

@ -10,6 +10,8 @@ const reactRules: Partial<ReactRulesObject> = {
'@eslint-react/no-missing-component-display-name': off, '@eslint-react/no-missing-component-display-name': off,
'@eslint-react/no-children-prop': error, '@eslint-react/no-children-prop': error,
'@eslint-react/no-leaked-conditional-rendering': error, '@eslint-react/no-leaked-conditional-rendering': error,
'@eslint-react/prefer-read-only-props': off,
'@eslint-react/hooks-extra/no-direct-set-state-in-use-effect': off,
}; };
export async function react() { export async function react() {
@ -23,12 +25,12 @@ export async function react() {
a11y.flatConfigs.recommended, a11y.flatConfigs.recommended,
{ {
name: 'eslint-rules/react', name: 'eslint-rules/react',
files: ['*.tsx'], files: ['**/*.tsx'],
rules: reactRules, rules: reactRules,
}, },
{ {
name: 'eslint-rules/react/test-files', name: 'eslint-rules/react/test-files',
files: ['*.test.tsx'], files: ['**/*.test.tsx'],
rules: { rules: {
'@eslint-react/no-clone-element': off, '@eslint-react/no-clone-element': off,
'@eslint-react/no-create-ref': off, '@eslint-react/no-create-ref': off,

View File

@ -16,7 +16,7 @@ export default defineConfig([
settings: { settings: {
tailwindcss: { tailwindcss: {
callees: ['classnames', 'clsx', 'tw', 'twx'], callees: ['classnames', 'clsx', 'tw', 'twx'],
classRegex: '^(css|class(Name)?)$', classRegex: /^(css|class(Name)?)$/.source,
}, },
}, },
}, },

View File

@ -40,6 +40,14 @@ export const typescriptRules: Partial<TypeScriptRulesObject> = {
'@typescript-eslint/no-empty-object-type': off, '@typescript-eslint/no-empty-object-type': off,
'@typescript-eslint/no-empty-interface': [error, { allowSingleExtends: true }], '@typescript-eslint/no-empty-interface': [error, { allowSingleExtends: true }],
'@typescript-eslint/no-explicit-any': off, '@typescript-eslint/no-explicit-any': off,
'@typescript-eslint/no-floating-promises': [
'warn',
{
allowForKnownSafeCalls: [
{ from: 'package', name: ['it', 'describe', 'test'], package: 'node:test' },
],
},
],
'@typescript-eslint/no-misused-promises': [error, { checksVoidReturn: false }], '@typescript-eslint/no-misused-promises': [error, { checksVoidReturn: false }],
'@typescript-eslint/no-namespace': off, '@typescript-eslint/no-namespace': off,
'@typescript-eslint/no-unnecessary-type-assertion': error, '@typescript-eslint/no-unnecessary-type-assertion': error,

View File

@ -7,10 +7,12 @@ import { defineConfig } from '../types';
const suggest = (suggest: string) => ({ suggest, fix: false }); const suggest = (suggest: string) => ({ suggest, fix: false });
// https://github.com/sindresorhus/eslint-plugin-unicorn/tree/28e7498ad06679bb92343db53bb40a7b5ba2990a // https://github.com/sindresorhus/eslint-plugin-unicorn/tree/1774135a5ddbded2c89f82952e37a3e3bb01cdfa
const unicornRules: Partial<UnicornRulesObject> = { const unicornRules: Partial<UnicornRulesObject> = {
'unicorn/better-regex': error, 'unicorn/better-regex': error,
'unicorn/consistent-destructuring': warn, 'unicorn/consistent-destructuring': warn,
'unicorn/consistent-empty-array-spread': error,
'unicorn/consistent-existence-index-check': error,
'unicorn/consistent-function-scoping': warn, 'unicorn/consistent-function-scoping': warn,
'unicorn/escape-case': error, 'unicorn/escape-case': error,
'unicorn/no-array-for-each': warn, 'unicorn/no-array-for-each': warn,
@ -22,6 +24,7 @@ const unicornRules: Partial<UnicornRulesObject> = {
'unicorn/no-instanceof-array': error, 'unicorn/no-instanceof-array': error,
'unicorn/no-invalid-fetch-options': error, 'unicorn/no-invalid-fetch-options': error,
'unicorn/no-invalid-remove-event-listener': error, 'unicorn/no-invalid-remove-event-listener': error,
'unicorn/no-length-as-slice-end': error,
'unicorn/no-lonely-if': warn, 'unicorn/no-lonely-if': warn,
'unicorn/no-negation-in-equality-check': error, 'unicorn/no-negation-in-equality-check': error,
'unicorn/no-new-buffer': error, 'unicorn/no-new-buffer': error,
@ -38,6 +41,7 @@ const unicornRules: Partial<UnicornRulesObject> = {
'unicorn/no-useless-undefined': error, 'unicorn/no-useless-undefined': error,
'unicorn/no-zero-fractions': error, 'unicorn/no-zero-fractions': error,
'unicorn/number-literal-case': error, 'unicorn/number-literal-case': error,
'unicorn/prefer-array-index-of': error,
'unicorn/prefer-array-find': error, 'unicorn/prefer-array-find': error,
'unicorn/prefer-array-flat': error, 'unicorn/prefer-array-flat': error,
'unicorn/prefer-array-flat-map': error, 'unicorn/prefer-array-flat-map': error,
@ -54,6 +58,7 @@ const unicornRules: Partial<UnicornRulesObject> = {
'unicorn/prefer-json-parse-buffer': warn, 'unicorn/prefer-json-parse-buffer': warn,
'unicorn/prefer-keyboard-event-key': warn, 'unicorn/prefer-keyboard-event-key': warn,
'unicorn/prefer-logical-operator-over-ternary': warn, 'unicorn/prefer-logical-operator-over-ternary': warn,
'unicorn/prefer-math-min-max': error,
'unicorn/prefer-math-trunc': warn, 'unicorn/prefer-math-trunc': warn,
'unicorn/prefer-modern-dom-apis': error, 'unicorn/prefer-modern-dom-apis': error,
'unicorn/prefer-modern-math-apis': error, 'unicorn/prefer-modern-math-apis': error,
@ -66,14 +71,16 @@ const unicornRules: Partial<UnicornRulesObject> = {
'unicorn/prefer-regexp-test': error, 'unicorn/prefer-regexp-test': error,
'unicorn/prefer-set-has': warn, 'unicorn/prefer-set-has': warn,
'unicorn/prefer-set-size': error, 'unicorn/prefer-set-size': error,
'unicorn/prefer-string-raw': error, 'unicorn/prefer-string-raw': warn,
'unicorn/prefer-string-slice': error, 'unicorn/prefer-string-slice': error,
'unicorn/prefer-string-starts-ends-with': warn, 'unicorn/prefer-string-starts-ends-with': warn,
'unicorn/prefer-string-trim-start-end': error, 'unicorn/prefer-string-trim-start-end': error,
'unicorn/prefer-switch': warn, 'unicorn/prefer-switch': warn,
'unicorn/prefer-ternary': warn, 'unicorn/prefer-ternary': warn,
'unicorn/prefer-type-error': warn,
'unicorn/relative-url-style': warn, 'unicorn/relative-url-style': warn,
'unicorn/require-number-to-fixed-digits-argument': error, 'unicorn/require-number-to-fixed-digits-argument': error,
'unicorn/require-post-message-target-origin': warn,
'unicorn/string-content': [ 'unicorn/string-content': [
warn, warn,
{ {
@ -100,17 +107,6 @@ const unicornRules: Partial<UnicornRulesObject> = {
'unicorn/template-indent': warn, 'unicorn/template-indent': warn,
}; };
// export const unicorn = defineMiddleware((config, { addRules }) => {
// config.plugins.push('unicorn');
// addRules(unicornRules);
// config.overrides.push({
// files: ['*.test.ts', '*.test.tsx'],
// rules: {
// 'unicorn/no-useless-undefined': off,
// },
// });
// });
export default defineConfig([ export default defineConfig([
{ {
name: 'eslint-rules/unicorn', name: 'eslint-rules/unicorn',
@ -124,7 +120,7 @@ export default defineConfig([
}, },
{ {
name: 'eslint-rules/unicorn/tests', name: 'eslint-rules/unicorn/tests',
files: ['*.test.ts', '*.test.tsx'], files: ['**/*.test.ts', '**/*.test.tsx'],
rules: { rules: {
'unicorn/no-useless-undefined': off, 'unicorn/no-useless-undefined': off,
}, },

View File

@ -10,28 +10,15 @@ const prettier: Config = {
plugins: [], plugins: [],
}; };
export default function defineConfig({ export default function defineConfig(
tailwind, config: Partial<Config> & {
...config
}: Partial<Config> & {
tailwind?: boolean; tailwind?: boolean;
}) { },
) {
const result: Config = { const result: Config = {
...prettier, ...prettier,
...config, ...config,
}; };
if (tailwind) {
ensureHas(result.plugins!, 'prettier-plugin-tailwindcss');
result.tailwindAttributes ??= ['css'];
result.tailwindFunctions ??= ['tw'];
}
return result; return result;
} }
function ensureHas<T>(list: T[], item: T) {
if (!list.includes(item)) {
list.push(item);
}
}