diff --git a/.github/semantic.yml b/.github/semantic.yml deleted file mode 100644 index dc58211fc..000000000 --- a/.github/semantic.yml +++ /dev/null @@ -1,18 +0,0 @@ -# In order to let this correctly work, install https://github.com/apps/semantic-pull-requests - -titleOnly: true -types: - - "build" - - "chore" - - "ci" - - "deps" - - "docs" - - "feat" - - "fix" - - "perf" - - "refactor" - - "revert" - - "security" - - "style" - - "test" - - "translation" diff --git a/.github/workflows/allo-allo.yaml b/.github/workflows/allo-allo.yaml index 0f4dfb9eb..ba7831268 100644 --- a/.github/workflows/allo-allo.yaml +++ b/.github/workflows/allo-allo.yaml @@ -16,19 +16,3 @@ jobs: uses: "anolilab/workflows/.github/workflows/allo-allo.yml@main" with: target-repo: "anolilab/javascript-style-guide" - issue-welcome: > - It looks like this is your first issue. Welcome! 👋 - One of the project maintainers will be with you as soon as possible. We - appreciate your patience. To safeguard the health of the project, please - take a moment to read our [code of conduct](https://github.com/anolilab/javascript-style-guide/blob/main/.github/CODE_OF_CONDUCT.md). - pr-welcome: > - It looks like this is your first pull request. 🎉 - Thank you for your contribution! One of the project maintainers will triage - and assign the pull request for review. We appreciate your patience. To - safeguard the health of the project, please take a moment to read our - [code of conduct](https://github.com/anolilab/javascript-style-guide/blob/main/.github/CODE_OF_CONDUCT.md). - pr-merged: > - Congratulations on your first merged pull request! 🎉 - Thank you for your contribution! - We appreciate your patience. - We look forward to your next contribution. diff --git a/.github/workflows/cache-clear.yml b/.github/workflows/cache-clear.yml index cbac5f704..9a746c31c 100644 --- a/.github/workflows/cache-clear.yml +++ b/.github/workflows/cache-clear.yml @@ -7,6 +7,6 @@ on: # yamllint disable-line rule:truthy jobs: cleanup-branch-cache: - uses: "anolilab/workflows/.github/workflows/cleanup-branch-cache.yml@main" + uses: "anolilab/workflows/.github/workflows/cleanup-branch-cache.yaml@main" with: target-repo: "anolilab/javascript-style-guide" diff --git a/.github/workflows/comment-issue.yml b/.github/workflows/comment-issue.yml index ae28e3be2..7233c1882 100644 --- a/.github/workflows/comment-issue.yml +++ b/.github/workflows/comment-issue.yml @@ -47,7 +47,7 @@ jobs: We do this because: - There are plenty of frameworks/tools out there and we would like to ensure that every method can cover all or almost all of them. - - Every feature we add to Visulima has "costs" associated to it: + - Every feature we add to "javascript-style-guide" has "costs" associated to it: - initial costs: design, implementation, reviews, documentation - running costs: awareness of the feature itself, more complex module structure, increased bundle size, more work during refactors diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 524a6881b..8fe2c7e51 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -59,7 +59,7 @@ jobs: matrix: os: - "ubuntu-latest" - node_version: ["18", "20", "21", "22"] + node_version: ["22", "23", "24"] # On the other platforms, we only test the lts node version include: - os: "macos-latest" @@ -87,6 +87,10 @@ jobs: # Pulls all commits (needed for NX) fetch-depth: 0 + - name: "Derive appropriate SHAs for base and head for `nx affected` commands" + id: "setSHAs" + uses: "nrwl/nx-set-shas@76907e7e5d3cd17ddb5e2b123389f054bffcdd03" # v4 + - name: "Setup resources and environment" id: "setup" uses: "anolilab/workflows/step/setup@main" @@ -94,20 +98,31 @@ jobs: node-version: "${{ matrix.node_version }}" install-bun: false - - name: "Build" - run: "pnpm run build:packages" - - - name: "Run test for browserslist-config-anolilab" - run: "pnpm run test:coverage:browserslist-config-anolilab" + # Temporary solution until Nx solve this https://github.com/nrwl/nx/issues/22259 + - name: "Get changed files" + id: "files" + uses: "tj-actions/changed-files@c65cd883420fd2eb864698a825fc4162dd94482c" # v44.5.7 + with: + files_ignore_from_source_file: ".github/ignore-files-for-nx-affected.yml" + base_sha: "${{ steps.setSHAs.outputs.base }}" + separator: "," - - name: "Run test for stylelint-config" - run: "pnpm run test:coverage:stylelint-config" + - name: "Build" + shell: "bash" + run: | + files="${{ steps.files.outputs.all_changed_files }}"; + pnpm run build:affected:packages --files=${files//\\/\/} - - name: "Run stylelint config test" - run: "pnpm run test:stylelint" + - name: "Run tests" + shell: "bash" + run: | + files="${{ steps.files.outputs.all_changed_files }}"; - - name: "Run babel config test" - run: "pnpm run test:babel" + if [[ ${{ matrix.os }} == "ubuntu-latest" && ${{ matrix.node_version }} == 22 ]]; then + pnpm run test:affected:coverage --files=${files//\\/\/} + else + pnpm run test:affected --files=${files//\\/\/} + fi - name: "Prepare nx cache" shell: "bash" diff --git a/.husky/post-commit b/.husky/post-commit index a2499f0ee..a3c20e3e4 100755 --- a/.husky/post-commit +++ b/.husky/post-commit @@ -1,6 +1,5 @@ #!/bin/sh -. "$(dirname "$0")/_/husky.sh" . "$(dirname "$0")/common.sh" # The hook should exit with non-zero status after issuing diff --git a/.husky/pre-commit b/.husky/pre-commit index 7062c41f9..2dcff06dd 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,6 +1,5 @@ #!/bin/sh -. "$(dirname "$0")/_/husky.sh" . "$(dirname "$0")/common.sh" # The hook should exit with non-zero status after issuing diff --git a/.husky/prepare-commit-msg b/.husky/prepare-commit-msg index a5db2fc10..ba5fc2615 100755 --- a/.husky/prepare-commit-msg +++ b/.husky/prepare-commit-msg @@ -1,6 +1,5 @@ #!/bin/sh -. "$(dirname "$0")/_/husky.sh" . "$(dirname "$0")/common.sh" echo -------------------------------------------- diff --git a/packages/eslint-config/.gitignore b/packages/eslint-config/.gitignore new file mode 100644 index 000000000..0437ca66b --- /dev/null +++ b/packages/eslint-config/.gitignore @@ -0,0 +1,2 @@ +src/typegen.d.ts +src/utils/vitest-globals.ts \ No newline at end of file diff --git a/packages/eslint-config/.prettierignore b/packages/eslint-config/.prettierignore new file mode 100644 index 000000000..c2cfc0666 --- /dev/null +++ b/packages/eslint-config/.prettierignore @@ -0,0 +1,10 @@ +.gitkeep +.env* +*.ico +*.lock +dist +CHANGELOG.md +coverage +node_modules +.eslintcache +__fixtures__ \ No newline at end of file diff --git a/packages/eslint-config/.prettierrc.js b/packages/eslint-config/.prettierrc.js new file mode 100644 index 000000000..de1375efd --- /dev/null +++ b/packages/eslint-config/.prettierrc.js @@ -0,0 +1,6 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import config from "@anolilab/prettier-config"; + +export default { + ...config, +}; diff --git a/packages/eslint-config/README.md b/packages/eslint-config/README.md index 48f1f6440..10dc873e5 100644 --- a/packages/eslint-config/README.md +++ b/packages/eslint-config/README.md @@ -22,28 +22,28 @@ Our package serves as a valuable resource for JavaScript/Typescript-based projects, offering composable [ESLint](https://eslint.org/) configurations. It encompasses a range of features, including performance optimization and the flexibility to extend pre-defined base configurations. -- Tailored Configuration for Workspaces: With this package, each workspace within your monorepo gains the ability to have its own customized ESLint configuration. This ensures that individual projects can maintain their specific requirements while still adhering to the overall guidelines. +- Tailored Configuration for Workspaces: With this package, each workspace within your monorepo gains the ability to have its own customized ESLint configuration. This ensures that individual projects can maintain their specific requirements while still adhering to the overall guidelines. -- Configurability at Your Fingertips: Crafting your workspace's ESLint configuration is a breeze, thanks to the seamless composition of pre-defined base configurations. This empowers you to tailor the settings to suit your project's unique needs, without starting from scratch. +- Configurability at Your Fingertips: Crafting your workspace's ESLint configuration is a breeze, thanks to the seamless composition of pre-defined base configurations. This empowers you to tailor the settings to suit your project's unique needs, without starting from scratch. -- Streamlined Convenience: Say goodbye to the hassle of installing plugins for each workspace. Our package integrates [@rushstack/eslint-patch](https://www.npmjs.com/package/@rushstack/eslint-patch), eliminating the need for repetitive plugin installations. Enjoy peace of mind as you focus on your work, knowing that the necessary plugins are automatically included. +- Streamlined Convenience: Say goodbye to the hassle of installing plugins for each workspace. Our package integrates [@rushstack/eslint-patch](https://www.npmjs.com/package/@rushstack/eslint-patch), eliminating the need for repetitive plugin installations. Enjoy peace of mind as you focus on your work, knowing that the necessary plugins are automatically included. -- Enhanced Efficiency: We've optimized the package's performance by intelligently enabling plugins based on file naming conventions. This streamlined approach ensures that your ESLint checks run efficiently, targeting the relevant files and maximizing productivity. +- Enhanced Efficiency: We've optimized the package's performance by intelligently enabling plugins based on file naming conventions. This streamlined approach ensures that your ESLint checks run efficiently, targeting the relevant files and maximizing productivity. In summary, our package provides comprehensive and adaptable ESLint configurations for JavaScript and Typescript projects. It empowers you to achieve code quality while minimizing overhead and maximizing productivity throughout your workspaces. ## Highlights -- Zero-config, but configurable when needed. -- Enforces readable code, because you read more code than you write. -- No need to specify file paths to lint as it lints all JS/TS files except for commonly ignored paths. -- Config overrides per files/globs. -- TypeScript supported by default, if `typescript` was installed. -- Includes many useful ESLint plugins, like [unicorn](https://github.com/sindresorhus/eslint-plugin-unicorn), [import](https://github.com/benmosher/eslint-plugin-import) and [more](#plugins). -- Automatically enables rules based on the [engines](https://docs.npmjs.com/files/package.json#engines) field in your package.json. -- Specify indent and semicolon preferences easily without messing with the rule config. -- Disables rules that conflict with [Prettier](#let-prettier-handle-style-related-rules). -- Typesafe, because it's written in TypeScript and uses [eslint-define-config](https://github.com/Shinigami92/eslint-define-config) to define the config. +- Zero-config, but configurable when needed. +- Enforces readable code, because you read more code than you write. +- No need to specify file paths to lint as it lints all JS/TS files except for commonly ignored paths. +- Config overrides per files/globs. +- TypeScript supported by default, if `typescript` was installed. +- Includes many useful ESLint plugins, like [unicorn](https://github.com/sindresorhus/eslint-plugin-unicorn), [import](https://github.com/benmosher/eslint-plugin-import) and [more](#plugins). +- Automatically enables rules based on the [engines](https://docs.npmjs.com/files/package.json#engines) field in your package.json. +- Specify indent and semicolon preferences easily without messing with the rule config. +- Disables rules that conflict with [Prettier](#let-prettier-handle-style-related-rules). +- Typesafe, because it's written in TypeScript and uses [eslint-define-config](https://github.com/Shinigami92/eslint-define-config) to define the config. ## Install @@ -65,402 +65,29 @@ yarn add -D eslint @anolilab/eslint-config eslint-plugin-import@npm:eslint-plugi ## Usage -If you don’t have a `.eslintrc.js`, we will create the file for you after installing `@anolilab/eslint-config`. - -If you already have a `.eslintrc.js`, then you can extend the `.eslintrc.js`, with `@anolilab/eslint-config`. - -> Note: If the script detects an existing `.eslintrc.js` file, it will not overwrite it. - -> Note: It can happen that the postinstall script don't run, then you have to add the `.eslintrc.js` manually, or you will use bin command `./node_modules/bin/anolilab-eslint-config` to generate it. - > Note: Our default export contains all of our ESLint rules, including ECMAScript 6+. `@anolilab/eslint-config` use the `ecmaVersion`:`2021` as default. > > To change this configuration, change `env: { es2021: false, then active you needed env }` same for, `parserOptions: { "ecmaVersion": 2021 change the version }` -```js -/** @ts-check */ -const { defineConfig } = require('@anolilab/eslint-config/define-config'); - -/// -/// -/// -/// -/// - -module.exports = defineConfig({ - env: { - // Your environments (which contains several predefined global variables) - // - // browser: true, - // node: true, - // mocha: true, - // jest: true, - // jquery: true - }, - extends: ["@anolilab/eslint-config"], - globals: { - // Your global variables (setting to false means it's not allowed to be reassigned) - // - // myGlobal: false - }, - root: true, - rules: { - // Customize your rules - }, -}); -``` - -For more advanced use cases see the example configurations for Node, TypeScript, React or Prettier. - -> Note: `@anolilab/eslint-config` will handle the configuration for almost all eslint-plugins / eslint-configs automatically. -> With this you only need to install the needed plugins/configs for TypeScript or React and you done. - -### TypeScript - -```bash -npm install --save-dev typescript -``` - -Please extend the `.eslintrc.js` file with the correct `tsconfig.js` path if you have a custom path. - -```js -module.exports = { - parserOptions: { - project: "./tsconfig.eslint.json", - }, -}; -``` - -For projects that use TypeScript and want additional rules that require type information (rules using type information take longer to run). - -Extend the `.eslintrc.js` file: - -```js -/** @ts-check */ -const { defineConfig } = require('@anolilab/eslint-config/define-config'); - -/// -/// -/// -/// -/// - -module.exports = defineConfig({ - env: { - // Your environments (which contains several predefined global variables) - // - // browser: true, - // node: true, - // mocha: true, - // jest: true, - // jquery: true - }, - extends: ["@anolilab/eslint-config", "@anolilab/eslint-config/typescript-type-checking"], - globals: { - // Your global variables (setting to false means it's not allowed to be reassigned) - // - // myGlobal: false - }, - root: true, - rules: { - // Customize your rules - }, -}); -``` - -> Tip: Run eslint with the TIMING=1 to identify slow rules. -> -> `TIMING=1 eslint . --ext .ts,.tsx` -> -> This is useful to identify rules that are slow because they require type information. - -### React - -You need to have "react" and "react-dom" installed. - -```bash -npm install --save-dev eslint-plugin-react eslint-plugin-react-hooks - -yarn add -D eslint-plugin-react eslint-plugin-react-hooks - -pnpm add -D eslint-plugin-react eslint-plugin-react-hooks -``` - -Or for the use of `TypeScript` in react install "typescript" as a dev dependency. - -Please extend the `.eslintrc.js` file with the correct `tsconfig.js` path if you have a custom path. - -```js -module.exports = { - parserOptions: { - project: "./tsconfig.eslint.json", - }, -}; -``` - -Or for the use of `.jsx` files install "@babel/plugin-syntax-jsx" as a dev dependency. - -```bash -npm install --save-dev babel @babel/plugin-syntax-jsx - -yarn add -D babel @babel/plugin-syntax-jsx - -pnpm add -D babel @babel/plugin-syntax-jsx -``` - -In your `babel.config.js` file add the plugin. - -```js -const babelPluginSyntaxJSX = require("@babel/plugin-syntax-jsx"); - -module.exports = { - plugins: [ - [ - babelPluginSyntaxJSX, - { - pragma: "React.createElement", - pragmaFrag: "React.Fragment", - }, - ], - ], -}; -``` - -### MDX - -```bash -npm install --save-dev eslint eslint-plugin-mdx -``` - -For more information about `missing` or `optional` to install rules see the `eslint` console output. - -### Config - -You can configure `@anolilab/eslint-config` options with your `package.json` file. - -Add this property to your package.json: - -```json5 -{ - anolilab: { - "eslint-config": { - // options - }, - }, -} -``` - -#### indent - -Type: `number` - -Default: `4` - -It will throw an error if the value is not numeric. - -#### plugin - -Type: `object` -> key: `string` value: `boolean` - -Disable a plugin in your package.json config to turn it off globally in your project. - -Example using package.json: - -```json -{ - "anolilab": { - "eslint-config": { - "plugin": { - "unicorn": false - } - } - } -} -``` - -#### warn_on_unsupported_typescript_version - -Type: `boolean` - -Default: `undefined` - -To disable the warning, set the value to `false`. - -```json -{ - "anolilab": { - "eslint-config": { - "warn_on_unsupported_typescript_version": false - } - } -} -``` - -#### info_on_disabling_jsx_react_rule - -Type: `boolean` - -Default: `undefined` - -To disable the info, set the value to `false`. - -```json -{ - "anolilab": { - "eslint-config": { - "info_on_disabling_jsx_react_rule": false - } - } -} -``` - -#### info_on_disabling_prettier_conflict_rule - -Type: `boolean` - -Default: `undefined` - -To disable the info, set the value to `false`. - -```json -{ - "anolilab": { - "eslint-config": { - "info_on_disabling_prettier_conflict_rule": false - } - } -} -``` - -#### info_on_disabling_jsonc_sort_keys_rule - -Type: `boolean` - -Default: `undefined` - -To disable the info, set the value to `false`. - -```json -{ - "anolilab": { - "eslint-config": { - "info_on_disabling_jsonc_sort_keys_rule": false - } - } -} -``` - -#### info_on_disabling_etc_no_deprecated - -Type: `boolean` - -Default: `undefined` - -To disable the info, set the value to `false`. - -```json -{ - "anolilab": { - "eslint-config": { - "info_on_disabling_etc_no_deprecated": false - } - } -} -``` - -#### info_on_testing_library_framework - -Type: `boolean` - -Default: `undefined` - -To disable the info, set the value to `false`. - -```json -{ - "anolilab": { - "eslint-config": { - "info_on_testing_library_framework": false - } - } -} -``` - -#### info_on_found_react_version - -Type: `boolean` - -Default: `undefined` - -To disable the info, set the value to `false`. - -```json -{ - "anolilab": { - "eslint-config": { - "info_on_found_react_version": false - } - } -} -``` - -#### import_ignore_exports - -Type: `string[]` - -Default: `[]` - -An array with files/paths for which unused exports will not be reported (e.g module entry points in a published package). - -```json -{ - "anolilab": { - "eslint-config": { - "import_ignore_exports": [] - } - } -} -``` - -### Let [Prettier](https://prettier.io/) handle style-related rules - -Prettier is a code formatting tool that offers fewer options but is more professional than the style-related rules in ESLint. - -Now that Prettier has become a necessary tool in front end projects, `@anolilab/eslint-config` does not need to maintain the style-related rules in ESLint anymore, -so we completely removed all Prettier related rules, if `prettier` is found in your `package.json` and use ESLint to check logical errors which it’s good at. - -As for whether two spaces or four spaces are used for indentation and whether there is a semicolon at the end, you can configure it in the project’s `.prettierrc.js`. -Of course, we also provide a recommended Prettier [configuration](../prettier-config/README.md) for your reference. - -`@anolilab/eslint-config` does disable all included style-related rules, so there is no need to install [`eslint-config-prettier`](https://github.com/prettier/eslint-config-prettier). - -## Using experimental features with JavaScript - -If you are using experimental features such as class fields with JavaScript files you should install `@babel/eslint-parser`. - -```bash -npm install --save-dev @babel/core -``` - ## Plugins ### Code Quality This plugin provide a range of code quality rules: -- [eslint-plugin-unicorn](https://github.com/sindresorhus/eslint-plugin-unicorn) -- [eslint-plugin-antfu](https://github.com/antfu/eslint-config) +- [eslint-plugin-unicorn](https://github.com/sindresorhus/eslint-plugin-unicorn) +- [eslint-plugin-antfu](https://github.com/antfu/eslint-config) ### Languages The following plugins expand esLint to work with json files, and lint JavaScript contained in HTML, and MarkDown: -- [eslint-plugin-html](https://github.com/BenoitZugmeyer/eslint-plugin-html) -- [eslint-plugin-jsonc](https://github.com/ota-meshi/eslint-plugin-jsonc) -- [eslint-plugin-markdown](https://github.com/eslint/eslint-plugin-markdown) -- [eslint-plugin-mdx](https://github.com/mdx-js/eslint-mdx) -- [eslint-plugin-toml](https://github.com/ota-meshi/eslint-plugin-toml) -- [eslint-plugin-yml](https://github.com/ota-meshi/eslint-plugin-yml) +- [eslint-plugin-html](https://github.com/BenoitZugmeyer/eslint-plugin-html) +- [eslint-plugin-jsonc](https://github.com/ota-meshi/eslint-plugin-jsonc) +- [eslint-plugin-markdown](https://github.com/eslint/eslint-plugin-markdown) +- [eslint-plugin-mdx](https://github.com/mdx-js/eslint-mdx) +- [eslint-plugin-toml](https://github.com/ota-meshi/eslint-plugin-toml) +- [eslint-plugin-yml](https://github.com/ota-meshi/eslint-plugin-yml) When linting code snippets in Markdown files, a few [rules](src/config/plugins/markdown.ts#L3) relating to globals and unused vars are disabled. @@ -468,76 +95,65 @@ When linting code snippets in Markdown files, a few [rules](src/config/plugins/m If a supported library is part of your project then it’s related esLint plugins will be loaded. The following plugins are supported: -- [eslint-plugin-fsa](https://github.com/joseph-galindo/eslint-plugin-fsa) -- [eslint-plugin-lodash](https://github.com/wix/eslint-plugin-lodash) - - You need to install `eslint-plugin-lodash` and `lodash` to use this plugin. -- [eslint-plugin-lodash-fp](https://github.com/jfmengels/eslint-plugin-lodash-fp) - - You need to install `eslint-plugin-lodash-fp` and `lodash` to use this plugin. -- [eslint-plugin-react-redux](https://github.com/DianaSuvorova/eslint-plugin-react-redux#readme) - - You need to install `eslint-plugin-react-redux` and `react-redux` to use this plugin. -- [eslint-plugin-redux-saga](https://github.com/pke/eslint-plugin-redux-saga) - - You need to install `eslint-plugin-redux-saga` and `redux-saga` to use this plugin. +- [eslint-plugin-fsa](https://github.com/joseph-galindo/eslint-plugin-fsa) +- [eslint-plugin-lodash](https://github.com/wix/eslint-plugin-lodash) + - You need to install `eslint-plugin-lodash` and `lodash` to use this plugin. +- [eslint-plugin-lodash-fp](https://github.com/jfmengels/eslint-plugin-lodash-fp) + - You need to install `eslint-plugin-lodash-fp` and `lodash` to use this plugin. +- [eslint-plugin-react-redux](https://github.com/DianaSuvorova/eslint-plugin-react-redux#readme) + - You need to install `eslint-plugin-react-redux` and `react-redux` to use this plugin. +- [eslint-plugin-redux-saga](https://github.com/pke/eslint-plugin-redux-saga) + - You need to install `eslint-plugin-redux-saga` and `redux-saga` to use this plugin. ### Practices The following esLint plugins enforce good coding practices: -- [eslint-plugin-array-func](https://github.com/freaktechnik/eslint-plugin-array-func) -- [eslint-plugin-eslint-comments](https://github.com/mysticatea/eslint-plugin-eslint-comments) -- [eslint-plugin-promise](https://github.com/xjamundx/eslint-plugin-promise) -- [eslint-plugin-no-loops](https://github.com/buildo/eslint-plugin-no-loops) -- [eslint-plugin-simple-import-sort](https://github.com/lydell/eslint-plugin-simple-import-sort) -- [eslint-plugin-es-x](https://github.com/eslint-community/eslint-plugin-es-x) +- [eslint-plugin-array-func](https://github.com/freaktechnik/eslint-plugin-array-func) +- [eslint-plugin-eslint-comments](https://github.com/mysticatea/eslint-plugin-eslint-comments) +- [eslint-plugin-promise](https://github.com/xjamundx/eslint-plugin-promise) +- [eslint-plugin-simple-import-sort](https://github.com/lydell/eslint-plugin-simple-import-sort) +- [eslint-plugin-es-x](https://github.com/eslint-community/eslint-plugin-es-x) ### Security These plugins add code security rules to esLint: -- [eslint-plugin-no-secrets](https://github.com/nickdeis/eslint-plugin-no-secrets) -- [eslint-plugin-no-unsanitized](https://github.com/mozilla/eslint-plugin-no-unsanitized) -- [eslint-plugin-sonarjs](https://github.com/SonarSource/eslint-plugin-sonarjs) -- [eslint-plugin-security](https://github.com/eslint-community/eslint-plugin-security) -- [rushstack/eslint-plugin-security](https://www.npmjs.com/package/@rushstack/eslint-plugin-security) +- [eslint-plugin-no-secrets](https://github.com/nickdeis/eslint-plugin-no-secrets) +- [eslint-plugin-no-unsanitized](https://github.com/mozilla/eslint-plugin-no-unsanitized) +- [eslint-plugin-sonarjs](https://github.com/SonarSource/eslint-plugin-sonarjs) +- [eslint-plugin-security](https://github.com/eslint-community/eslint-plugin-security) +- [rushstack/eslint-plugin-security](https://www.npmjs.com/package/@rushstack/eslint-plugin-security) ### Test Libraries The following test plugins are supported: -- [eslint-plugin-ava](https://github.com/avajs/eslint-plugin-ava) - - You need to install `eslint-plugin-ava` and `ava` to use this plugin. -- [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) - - You need to install `eslint-plugin-jest` and `jest` to use this plugin. -- [eslint-plugin-jest-async](https://www.npmjs.com/package/eslint-plugin-jest-async) - - You need to install `eslint-plugin-jest-async` and `jest` to use this plugin. -- [eslint-plugin-cypress](https://github.com/cypress-io/eslint-plugin-cypress) - - You need to install `eslint-plugin-cypress` and `cypress` to use this plugin. +- [eslint-plugin-ava](https://github.com/avajs/eslint-plugin-ava) + - You need to install `@vitest/eslint-plugin` and `vitest` to use this plugin. ### List of used plugins -- eslint-plugin-security -- @rushstack/eslint-plugin-security -- @typescript-eslint/eslint-plugin -- eslint-plugin-antfu -- eslint-plugin-compat -- eslint-plugin-es-x -- eslint-plugin-eslint-comments -- eslint-plugin-html -- eslint-plugin-i -- eslint-plugin-jsonc -- eslint-plugin-markdown -- eslint-plugin-mdx -- eslint-plugin-no-loops -- eslint-plugin-no-only-tests -- eslint-plugin-no-secrets -- eslint-plugin-no-use-extend-native -- eslint-plugin-promise -- eslint-plugin-regexp -- eslint-plugin-simple-import-sort -- eslint-plugin-sonarjs -- eslint-plugin-toml -- eslint-plugin-typescript-sort-keys -- eslint-plugin-unicorn -- eslint-plugin-yml +- eslint-plugin-security +- @typescript-eslint/eslint-plugin +- eslint-plugin-antfu +- eslint-plugin-compat +- eslint-plugin-es-x +- eslint-plugin-eslint-comments +- eslint-plugin-html +- eslint-plugin-i +- eslint-plugin-jsonc +- eslint-plugin-markdown +- eslint-plugin-no-only-tests +- eslint-plugin-no-secrets +- eslint-plugin-promise +- eslint-plugin-regexp +- eslint-plugin-simple-import-sort +- eslint-plugin-sonarjs +- eslint-plugin-toml +- eslint-plugin-typescript-sort-keys +- eslint-plugin-unicorn +- eslint-plugin-yml ## Troubleshooting @@ -632,11 +248,11 @@ If you would like to help take a look at the [list of issues](https://github.com ## Credits -- [Daniel Bannert](https://github.com/prisis) -- [All Contributors](https://github.com/anolilab/javascript-style-guide/graphs/contributors) -- [eslint-config-airbnb](https://www.npmjs.com/package/eslint-config-airbnb) -- [eslint-config-alloy](https://github.com/AlloyTeam/eslint-config-alloy) -- [eslint-config-canonical](https://github.com/gajus/eslint-config-canonical) +- [Daniel Bannert](https://github.com/prisis) +- [All Contributors](https://github.com/anolilab/javascript-style-guide/graphs/contributors) +- [eslint-config-airbnb](https://www.npmjs.com/package/eslint-config-airbnb) +- [eslint-config-canonical](https://github.com/gajus/eslint-config-canonical) +- [](https://github.com/antfu/eslint-config) ## License diff --git a/packages/eslint-config/__fixtures__/LICENSE.md b/packages/eslint-config/__fixtures__/LICENSE.md new file mode 100644 index 000000000..b2501586b --- /dev/null +++ b/packages/eslint-config/__fixtures__/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019-PRESENT Anthony Fu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/eslint-config/__fixtures__/input/astro.astro b/packages/eslint-config/__fixtures__/input/astro.astro new file mode 100644 index 000000000..df4e8e5ef --- /dev/null +++ b/packages/eslint-config/__fixtures__/input/astro.astro @@ -0,0 +1,21 @@ +--- +const isJsx = true +const content = "hi!"; +--- + +
+
{content}
+
+ {isJsx && ( +

{content}

+ )} +
+
+ + + diff --git a/packages/eslint-config/__fixtures__/input/css.css b/packages/eslint-config/__fixtures__/input/css.css new file mode 100644 index 000000000..12a4724d6 --- /dev/null +++ b/packages/eslint-config/__fixtures__/input/css.css @@ -0,0 +1,10 @@ +@media (max-width: 480px) { + .bd-examples {margin-right: -.75rem;margin-left: -.75rem + } + + .bd-examples>[class^="col-"] { + padding-right: .75rem; + padding-left: .75rem; + + } +} diff --git a/packages/eslint-config/__fixtures__/input/html.html b/packages/eslint-config/__fixtures__/input/html.html new file mode 100644 index 000000000..1516dca6a --- /dev/null +++ b/packages/eslint-config/__fixtures__/input/html.html @@ -0,0 +1,17 @@ + + + + + My tITlE + + + +

Hello world!
This is HTML5 Boilerplate.

+ + + + diff --git a/packages/eslint-config/__fixtures__/input/javascript.js b/packages/eslint-config/__fixtures__/input/javascript.js new file mode 100644 index 000000000..ab9c71f97 --- /dev/null +++ b/packages/eslint-config/__fixtures__/input/javascript.js @@ -0,0 +1,72 @@ +// This file is generated by ChatGPT + +// eslint-disable-next-line no-console +var 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/packages/eslint-config/__fixtures__/input/jsx.jsx b/packages/eslint-config/__fixtures__/input/jsx.jsx new file mode 100644 index 000000000..f2d2a2563 --- /dev/null +++ b/packages/eslint-config/__fixtures__/input/jsx.jsx @@ -0,0 +1,22 @@ +export function HelloWorld({ + greeting = "hello", greeted = '"World"', silent = false, onMouseOver,}) { + + if(!greeting){ + return null}; + + // TODO: Don't use random in render + let num = Math + .floor (Math.random() * 1E+7).toString() + .replace(/\.\d+/ig, "") + + return
+ { greeting.slice( 0, 1 ).toUpperCase() + greeting.slice(1).toLowerCase() } + {greeting.endsWith(",") + ? " " : ", " } + + { greeted } + + { (silent)? ".": "!"} +
; + +} diff --git a/packages/eslint-config/__fixtures__/input/markdown.md b/packages/eslint-config/__fixtures__/input/markdown.md new file mode 100644 index 000000000..a7f61a305 --- /dev/null +++ b/packages/eslint-config/__fixtures__/input/markdown.md @@ -0,0 +1,35 @@ +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/packages/eslint-config/__fixtures__/input/svelte.svelte b/packages/eslint-config/__fixtures__/input/svelte.svelte new file mode 100644 index 000000000..bf114b923 --- /dev/null +++ b/packages/eslint-config/__fixtures__/input/svelte.svelte @@ -0,0 +1,8 @@ + + +
+ +
{@html content}
+
\ No newline at end of file diff --git a/packages/eslint-config/__fixtures__/input/svg.svg b/packages/eslint-config/__fixtures__/input/svg.svg new file mode 100644 index 000000000..f3e336599 --- /dev/null +++ b/packages/eslint-config/__fixtures__/input/svg.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/packages/eslint-config/__fixtures__/input/toml.toml b/packages/eslint-config/__fixtures__/input/toml.toml new file mode 100644 index 000000000..31e10f1eb --- /dev/null +++ b/packages/eslint-config/__fixtures__/input/toml.toml @@ -0,0 +1,24 @@ +comma = [ + 1 + ,2 + ,3, +] + +[foo] + b = 1 + c = "hello" + a = {answer = 42} + +"indent" = [ +1, + 2 +] + +['a-table'] +apple.type = "fruit" +orange.type = "fruit" +apple.skin = "thin" +orange.skin = "thick" + +apple.color = "red" +orange.color = "orange" diff --git a/packages/eslint-config/__fixtures__/input/tsconfig.json b/packages/eslint-config/__fixtures__/input/tsconfig.json new file mode 100644 index 000000000..66372fb65 --- /dev/null +++ b/packages/eslint-config/__fixtures__/input/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "skipDefaultLibCheck": true, + "skipLibCheck": true + } +} diff --git a/packages/eslint-config/__fixtures__/input/tsx.tsx b/packages/eslint-config/__fixtures__/input/tsx.tsx new file mode 100644 index 000000000..97ca0890b --- /dev/null +++ b/packages/eslint-config/__fixtures__/input/tsx.tsx @@ -0,0 +1,24 @@ +export function Component1() { + return
; +} + +export function jsx2() { + const props = {a:1, + b:2} + return < a foo= 'bar' bar={`foo` } > +
Inline Text
+ + Block Text + +
+ Mixed +
Foo
+ Text Bar +
+

+ foobarbaz +

+ +} diff --git a/packages/eslint-config/__fixtures__/input/typescript.ts b/packages/eslint-config/__fixtures__/input/typescript.ts new file mode 100644 index 000000000..213ff3db2 --- /dev/null +++ b/packages/eslint-config/__fixtures__/input/typescript.ts @@ -0,0 +1,90 @@ +// 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 +var 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(); + +var fn = (): string => { + return 'hello' + 1 +} + +log(car1, car2, favoriteFruit, numericValue, fn()) + +// Generator +export function* generator1() { + let id = 0; + while (id < 100) { + yield id++; + } +} +export function * generator2() { + yield* generator1() +} diff --git a/packages/eslint-config/__fixtures__/input/vue-ts.vue b/packages/eslint-config/__fixtures__/input/vue-ts.vue new file mode 100644 index 000000000..61ed8c6c8 --- /dev/null +++ b/packages/eslint-config/__fixtures__/input/vue-ts.vue @@ -0,0 +1,34 @@ + + + + + + + diff --git a/packages/eslint-config/__fixtures__/input/vue.vue b/packages/eslint-config/__fixtures__/input/vue.vue new file mode 100644 index 000000000..b540d06e6 --- /dev/null +++ b/packages/eslint-config/__fixtures__/input/vue.vue @@ -0,0 +1,24 @@ + + + diff --git a/packages/eslint-config/__fixtures__/input/xml.xml b/packages/eslint-config/__fixtures__/input/xml.xml new file mode 100644 index 000000000..8fbc57b9b --- /dev/null +++ b/packages/eslint-config/__fixtures__/input/xml.xml @@ -0,0 +1,9 @@ + +Effective Java45.00 + Bluetooth Speaker120.00 + Clean Code + 33.50 + + + + diff --git a/packages/eslint-config/__fixtures__/old-config/.eslintignore b/packages/eslint-config/__fixtures__/old-config/.eslintignore deleted file mode 100644 index 4f21ac90b..000000000 --- a/packages/eslint-config/__fixtures__/old-config/.eslintignore +++ /dev/null @@ -1,13 +0,0 @@ -packages/semantic-release-preset/commitlint.config.js -packages/semantic-release-preset/.czrc -packages/prettier-config/.prettierrc.js -packages/textlint-config/.textlintrc -packages/babel-preset/babel.config.js -packages/babel-preset/fixture/test.js -packages/babel-preset/fixture/test.ts -packages/stylelint-config/.stylelintrc.js -packages/lint-staged-config/.lintstagedrc.js -packages/eslint-config/src/global.d.ts -packages/eslint-config/src/types.d.ts - -tsup.config.ts diff --git a/packages/eslint-config/__fixtures__/old-config/.eslintrc.js b/packages/eslint-config/__fixtures__/old-config/.eslintrc.js deleted file mode 100644 index cd7f61bd7..000000000 --- a/packages/eslint-config/__fixtures__/old-config/.eslintrc.js +++ /dev/null @@ -1,22 +0,0 @@ -const config = require("@anolilab/eslint-config"); -const globals = require("@anolilab/eslint-config/globals"); - -module.exports = { - ...config, - extends: [...config.extends, "@anolilab/eslint-config/typescript-type-checking"], - globals: { - ...config?.globals, - ...globals.es2021, - }, - overrides: [ - ...config.overrides, - { - files: ["*.ts", "*.mts", "*.cts", "*.tsx", ".mdx"], - parserOptions: { - project: true, - tsconfigRootDir: __dirname, - }, - }, - ], - root: true, -}; diff --git a/packages/eslint-config/__fixtures__/old-config/index.js b/packages/eslint-config/__fixtures__/old-config/index.js deleted file mode 100644 index c36e70fbb..000000000 --- a/packages/eslint-config/__fixtures__/old-config/index.js +++ /dev/null @@ -1,2 +0,0 @@ - -console.log("hello world"); diff --git a/packages/eslint-config/__fixtures__/old-config/package.json b/packages/eslint-config/__fixtures__/old-config/package.json deleted file mode 100644 index 96df50e77..000000000 --- a/packages/eslint-config/__fixtures__/old-config/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "test-package", - "devDependencies": { - "@anolilab/eslint-config": "file:../../dist", - "eslint": "latest", - "eslint-plugin-import": "npm:eslint-plugin-i@2.28.1" - } -} diff --git a/packages/eslint-config/__fixtures__/old-config/pnpm-lock.yaml b/packages/eslint-config/__fixtures__/old-config/pnpm-lock.yaml deleted file mode 100644 index 54fe3d890..000000000 --- a/packages/eslint-config/__fixtures__/old-config/pnpm-lock.yaml +++ /dev/null @@ -1,813 +0,0 @@ -lockfileVersion: '6.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -devDependencies: - '@anolilab/eslint-config': - specifier: file:../../dist - version: file:../../dist - eslint: - specifier: latest - version: 8.50.0 - eslint-plugin-import: - specifier: npm:eslint-plugin-i@2.28.1 - version: /eslint-plugin-i@2.28.1(eslint@8.50.0) - -packages: - - /@aashutoshrathi/word-wrap@1.2.6: - resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} - engines: {node: '>=0.10.0'} - dev: true - - /@eslint-community/eslint-utils@4.4.0(eslint@8.50.0): - resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - dependencies: - eslint: 8.50.0 - eslint-visitor-keys: 3.4.3 - dev: true - - /@eslint-community/regexpp@4.8.2: - resolution: {integrity: sha512-0MGxAVt1m/ZK+LTJp/j0qF7Hz97D9O/FH9Ms3ltnyIdDD57cbb1ACIQTkbHvNXtWDv5TPq7w5Kq56+cNukbo7g==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - dev: true - - /@eslint/eslintrc@2.1.2: - resolution: {integrity: sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - ajv: 6.12.6 - debug: 4.3.4 - espree: 9.6.1 - globals: 13.22.0 - ignore: 5.2.4 - import-fresh: 3.3.0 - js-yaml: 4.1.0 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - dev: true - - /@eslint/js@8.50.0: - resolution: {integrity: sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true - - /@humanwhocodes/config-array@0.11.11: - resolution: {integrity: sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==} - engines: {node: '>=10.10.0'} - dependencies: - '@humanwhocodes/object-schema': 1.2.1 - debug: 4.3.4 - minimatch: 3.1.2 - transitivePeerDependencies: - - supports-color - dev: true - - /@humanwhocodes/module-importer@1.0.1: - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - dev: true - - /@humanwhocodes/object-schema@1.2.1: - resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} - dev: true - - /@nodelib/fs.scandir@2.1.5: - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - dev: true - - /@nodelib/fs.stat@2.0.5: - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - dev: true - - /@nodelib/fs.walk@1.2.8: - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.15.0 - dev: true - - /acorn-jsx@5.3.2(acorn@8.10.0): - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - dependencies: - acorn: 8.10.0 - dev: true - - /acorn@8.10.0: - resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==} - engines: {node: '>=0.4.0'} - hasBin: true - dev: true - - /ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - dev: true - - /ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - dev: true - - /ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - dependencies: - color-convert: 2.0.1 - dev: true - - /argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - dev: true - - /balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - dev: true - - /brace-expansion@1.1.11: - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - dev: true - - /callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - dev: true - - /chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - dev: true - - /color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - dependencies: - color-name: 1.1.4 - dev: true - - /color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - dev: true - - /concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - dev: true - - /cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} - engines: {node: '>= 8'} - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - dev: true - - /debug@3.2.7: - resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.3 - dev: true - - /debug@4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.2 - dev: true - - /deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - dev: true - - /doctrine@2.1.0: - resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} - engines: {node: '>=0.10.0'} - dependencies: - esutils: 2.0.3 - dev: true - - /doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} - dependencies: - esutils: 2.0.3 - dev: true - - /escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - dev: true - - /eslint-import-resolver-node@0.3.9: - resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} - dependencies: - debug: 3.2.7 - is-core-module: 2.13.0 - resolve: 1.22.6 - transitivePeerDependencies: - - supports-color - dev: true - - /eslint-module-utils@2.8.0(eslint-import-resolver-node@0.3.9)(eslint@8.50.0): - resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} - engines: {node: '>=4'} - peerDependencies: - '@typescript-eslint/parser': '*' - eslint: '*' - eslint-import-resolver-node: '*' - eslint-import-resolver-typescript: '*' - eslint-import-resolver-webpack: '*' - peerDependenciesMeta: - '@typescript-eslint/parser': - optional: true - eslint: - optional: true - eslint-import-resolver-node: - optional: true - eslint-import-resolver-typescript: - optional: true - eslint-import-resolver-webpack: - optional: true - dependencies: - debug: 3.2.7 - eslint: 8.50.0 - eslint-import-resolver-node: 0.3.9 - transitivePeerDependencies: - - supports-color - dev: true - - /eslint-plugin-i@2.28.1(eslint@8.50.0): - resolution: {integrity: sha512-a4oVt0j3ixNhGhvV4XF6NS7OWRFK2rrJ0Q5C4S2dSRb8FxZi31J0uUd5WJLL58wnVJ/OiQ1BxiXnFA4dWQO1Cg==} - engines: {node: '>=12'} - peerDependencies: - eslint: ^7.2.0 || ^8 - dependencies: - debug: 3.2.7 - doctrine: 2.1.0 - eslint: 8.50.0 - eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.8.0(eslint-import-resolver-node@0.3.9)(eslint@8.50.0) - get-tsconfig: 4.7.2 - is-glob: 4.0.3 - minimatch: 3.1.2 - resolve: 1.22.6 - semver: 7.5.4 - transitivePeerDependencies: - - '@typescript-eslint/parser' - - eslint-import-resolver-typescript - - eslint-import-resolver-webpack - - supports-color - dev: true - - /eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - dev: true - - /eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dev: true - - /eslint@8.50.0: - resolution: {integrity: sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - hasBin: true - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.50.0) - '@eslint-community/regexpp': 4.8.2 - '@eslint/eslintrc': 2.1.2 - '@eslint/js': 8.50.0 - '@humanwhocodes/config-array': 0.11.11 - '@humanwhocodes/module-importer': 1.0.1 - '@nodelib/fs.walk': 1.2.8 - ajv: 6.12.6 - chalk: 4.1.2 - cross-spawn: 7.0.3 - debug: 4.3.4 - doctrine: 3.0.0 - escape-string-regexp: 4.0.0 - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 - esquery: 1.5.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 - find-up: 5.0.0 - glob-parent: 6.0.2 - globals: 13.22.0 - graphemer: 1.4.0 - ignore: 5.2.4 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - is-path-inside: 3.0.3 - js-yaml: 4.1.0 - json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 - natural-compare: 1.4.0 - optionator: 0.9.3 - strip-ansi: 6.0.1 - text-table: 0.2.0 - transitivePeerDependencies: - - supports-color - dev: true - - /espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - dependencies: - acorn: 8.10.0 - acorn-jsx: 5.3.2(acorn@8.10.0) - eslint-visitor-keys: 3.4.3 - dev: true - - /esquery@1.5.0: - resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} - engines: {node: '>=0.10'} - dependencies: - estraverse: 5.3.0 - dev: true - - /esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} - dependencies: - estraverse: 5.3.0 - dev: true - - /estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - dev: true - - /esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - dev: true - - /fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - dev: true - - /fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - dev: true - - /fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - dev: true - - /fastq@1.15.0: - resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} - dependencies: - reusify: 1.0.4 - dev: true - - /file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} - dependencies: - flat-cache: 3.1.0 - dev: true - - /find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - dev: true - - /flat-cache@3.1.0: - resolution: {integrity: sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==} - engines: {node: '>=12.0.0'} - dependencies: - flatted: 3.2.9 - keyv: 4.5.3 - rimraf: 3.0.2 - dev: true - - /flatted@3.2.9: - resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} - dev: true - - /fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - dev: true - - /function-bind@1.1.1: - resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} - dev: true - - /get-tsconfig@4.7.2: - resolution: {integrity: sha512-wuMsz4leaj5hbGgg4IvDU0bqJagpftG5l5cXIAvo8uZrqn0NJqwtfupTN00VnkQJPcIRrxYrm1Ue24btpCha2A==} - dependencies: - resolve-pkg-maps: 1.0.0 - dev: true - - /glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} - dependencies: - is-glob: 4.0.3 - dev: true - - /glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 - dev: true - - /globals@13.22.0: - resolution: {integrity: sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==} - engines: {node: '>=8'} - dependencies: - type-fest: 0.20.2 - dev: true - - /graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - dev: true - - /has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - dev: true - - /has@1.0.3: - resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} - engines: {node: '>= 0.4.0'} - dependencies: - function-bind: 1.1.1 - dev: true - - /ignore@5.2.4: - resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} - engines: {node: '>= 4'} - dev: true - - /import-fresh@3.3.0: - resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} - engines: {node: '>=6'} - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - dev: true - - /imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - dev: true - - /inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - dev: true - - /inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - dev: true - - /is-core-module@2.13.0: - resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==} - dependencies: - has: 1.0.3 - dev: true - - /is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - dev: true - - /is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - dependencies: - is-extglob: 2.1.1 - dev: true - - /is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - dev: true - - /isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - dev: true - - /js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true - dependencies: - argparse: 2.0.1 - dev: true - - /json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - dev: true - - /json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - dev: true - - /json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - dev: true - - /keyv@4.5.3: - resolution: {integrity: sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==} - dependencies: - json-buffer: 3.0.1 - dev: true - - /levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 - dev: true - - /locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - dependencies: - p-locate: 5.0.0 - dev: true - - /lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - dev: true - - /lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} - dependencies: - yallist: 4.0.0 - dev: true - - /minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - dependencies: - brace-expansion: 1.1.11 - dev: true - - /ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - dev: true - - /ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - dev: true - - /natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - dev: true - - /once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - dependencies: - wrappy: 1.0.2 - dev: true - - /optionator@0.9.3: - resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} - engines: {node: '>= 0.8.0'} - dependencies: - '@aashutoshrathi/word-wrap': 1.2.6 - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.4.1 - prelude-ls: 1.2.1 - type-check: 0.4.0 - dev: true - - /p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - dependencies: - yocto-queue: 0.1.0 - dev: true - - /p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - dependencies: - p-limit: 3.1.0 - dev: true - - /parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - dependencies: - callsites: 3.1.0 - dev: true - - /path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - dev: true - - /path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - dev: true - - /path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - dev: true - - /path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - dev: true - - /prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - dev: true - - /punycode@2.3.0: - resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} - engines: {node: '>=6'} - dev: true - - /queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - dev: true - - /resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - dev: true - - /resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - dev: true - - /resolve@1.22.6: - resolution: {integrity: sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==} - hasBin: true - dependencies: - is-core-module: 2.13.0 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - dev: true - - /reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - dev: true - - /rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - hasBin: true - dependencies: - glob: 7.2.3 - dev: true - - /run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - dependencies: - queue-microtask: 1.2.3 - dev: true - - /semver@7.5.4: - resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} - engines: {node: '>=10'} - hasBin: true - dependencies: - lru-cache: 6.0.0 - dev: true - - /shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - dependencies: - shebang-regex: 3.0.0 - dev: true - - /shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - dev: true - - /strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - dependencies: - ansi-regex: 5.0.1 - dev: true - - /strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - dev: true - - /supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - dependencies: - has-flag: 4.0.0 - dev: true - - /supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - dev: true - - /text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - dev: true - - /type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - dependencies: - prelude-ls: 1.2.1 - dev: true - - /type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - dev: true - - /uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - dependencies: - punycode: 2.3.0 - dev: true - - /which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - dependencies: - isexe: 2.0.0 - dev: true - - /wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - dev: true - - /yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - dev: true - - /yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - dev: true - - file:../../dist: - resolution: {directory: ../../dist, type: directory} - name: dist - dev: true diff --git a/packages/eslint-config/__fixtures__/output/all/astro.astro b/packages/eslint-config/__fixtures__/output/all/astro.astro new file mode 100644 index 000000000..b67c6784e --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/all/astro.astro @@ -0,0 +1,21 @@ +--- +const isJsx = true +const content = 'hi!'; +--- + +
+
{content}
+
+ {isJsx && ( +

{content}

+ )} +
+
+ + + diff --git a/packages/eslint-config/__fixtures__/output/all/javascript.js b/packages/eslint-config/__fixtures__/output/all/javascript.js new file mode 100644 index 000000000..e0702598c --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/all/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/packages/eslint-config/__fixtures__/output/all/jsx.jsx b/packages/eslint-config/__fixtures__/output/all/jsx.jsx new file mode 100644 index 000000000..523af3782 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/all/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/packages/eslint-config/__fixtures__/output/all/markdown.md b/packages/eslint-config/__fixtures__/output/all/markdown.md new file mode 100644 index 000000000..a66c94c89 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/all/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/packages/eslint-config/__fixtures__/output/all/svelte.svelte b/packages/eslint-config/__fixtures__/output/all/svelte.svelte new file mode 100644 index 000000000..7cc629a47 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/all/svelte.svelte @@ -0,0 +1,8 @@ + + +
+ +
{@html content}
+
diff --git a/packages/eslint-config/__fixtures__/output/all/toml.toml b/packages/eslint-config/__fixtures__/output/all/toml.toml new file mode 100644 index 000000000..1f73d046b --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/all/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/packages/eslint-config/__fixtures__/output/all/tsx.tsx b/packages/eslint-config/__fixtures__/output/all/tsx.tsx new file mode 100644 index 000000000..ab640af67 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/all/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/packages/eslint-config/__fixtures__/output/all/typescript.ts b/packages/eslint-config/__fixtures__/output/all/typescript.ts new file mode 100644 index 000000000..c1d6575f7 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/all/typescript.ts @@ -0,0 +1,95 @@ +// 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(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()) + +// Generator +export function* generator1() { + let id = 0 + while (id < 100) { + yield id++ + } +} +export function* generator2() { + yield* generator1() +} diff --git a/packages/eslint-config/__fixtures__/output/all/vue-ts.vue b/packages/eslint-config/__fixtures__/output/all/vue-ts.vue new file mode 100644 index 000000000..ce35e66b0 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/all/vue-ts.vue @@ -0,0 +1,35 @@ + + + + + + + diff --git a/packages/eslint-config/__fixtures__/output/all/vue.vue b/packages/eslint-config/__fixtures__/output/all/vue.vue new file mode 100644 index 000000000..944cc4e56 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/all/vue.vue @@ -0,0 +1,27 @@ + + + diff --git a/packages/eslint-config/__fixtures__/output/js/javascript.js b/packages/eslint-config/__fixtures__/output/js/javascript.js new file mode 100644 index 000000000..e0702598c --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/js/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/packages/eslint-config/__fixtures__/output/js/jsx.jsx b/packages/eslint-config/__fixtures__/output/js/jsx.jsx new file mode 100644 index 000000000..523af3782 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/js/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/packages/eslint-config/__fixtures__/output/js/markdown.md b/packages/eslint-config/__fixtures__/output/js/markdown.md new file mode 100644 index 000000000..a66c94c89 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/js/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/packages/eslint-config/__fixtures__/output/js/toml.toml b/packages/eslint-config/__fixtures__/output/js/toml.toml new file mode 100644 index 000000000..1f73d046b --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/js/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/packages/eslint-config/__fixtures__/output/js/tsx.tsx b/packages/eslint-config/__fixtures__/output/js/tsx.tsx new file mode 100644 index 000000000..ab640af67 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/js/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/packages/eslint-config/__fixtures__/output/no-markdown-with-formatters/javascript.js b/packages/eslint-config/__fixtures__/output/no-markdown-with-formatters/javascript.js new file mode 100644 index 000000000..e0702598c --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/no-markdown-with-formatters/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/packages/eslint-config/__fixtures__/output/no-markdown-with-formatters/markdown.md b/packages/eslint-config/__fixtures__/output/no-markdown-with-formatters/markdown.md new file mode 100644 index 000000000..b049fd926 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/no-markdown-with-formatters/markdown.md @@ -0,0 +1,33 @@ +# 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/packages/eslint-config/__fixtures__/output/no-markdown-with-formatters/toml.toml b/packages/eslint-config/__fixtures__/output/no-markdown-with-formatters/toml.toml new file mode 100644 index 000000000..1f73d046b --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/no-markdown-with-formatters/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/packages/eslint-config/__fixtures__/output/no-markdown-with-formatters/tsx.tsx b/packages/eslint-config/__fixtures__/output/no-markdown-with-formatters/tsx.tsx new file mode 100644 index 000000000..efc80b97c --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/no-markdown-with-formatters/tsx.tsx @@ -0,0 +1,21 @@ +export function Component1() { + return
+} + +export function jsx2() { + const props = { a: 1, b: 2 } + return < a foo= 'bar' bar={`foo` } > +
Inline Text
+ + Block Text + +
+ Mixed +
Foo
+ Text Bar +
+

+ foobarbaz +

+ +} diff --git a/packages/eslint-config/__fixtures__/output/no-markdown-with-formatters/typescript.ts b/packages/eslint-config/__fixtures__/output/no-markdown-with-formatters/typescript.ts new file mode 100644 index 000000000..c1d6575f7 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/no-markdown-with-formatters/typescript.ts @@ -0,0 +1,95 @@ +// 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(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()) + +// Generator +export function* generator1() { + let id = 0 + while (id < 100) { + yield id++ + } +} +export function* generator2() { + yield* generator1() +} diff --git a/packages/eslint-config/__fixtures__/output/no-style/javascript.js b/packages/eslint-config/__fixtures__/output/no-style/javascript.js new file mode 100644 index 000000000..9ee4a7ca3 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/no-style/javascript.js @@ -0,0 +1,72 @@ +// 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/packages/eslint-config/__fixtures__/output/no-style/jsx.jsx b/packages/eslint-config/__fixtures__/output/no-style/jsx.jsx new file mode 100644 index 000000000..762fec4f0 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/no-style/jsx.jsx @@ -0,0 +1,22 @@ +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/packages/eslint-config/__fixtures__/output/no-style/toml.toml b/packages/eslint-config/__fixtures__/output/no-style/toml.toml new file mode 100644 index 000000000..a28003c42 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/no-style/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/packages/eslint-config/__fixtures__/output/no-style/typescript.ts b/packages/eslint-config/__fixtures__/output/no-style/typescript.ts new file mode 100644 index 000000000..52a7e6a92 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/no-style/typescript.ts @@ -0,0 +1,90 @@ +// 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(); + +const fn = (): string => { + return `hello${ 1}` +} + +log(car1, car2, favoriteFruit, numericValue, fn()) + +// Generator +export function* generator1() { + let id = 0; + while (id < 100) { + yield id++; + } +} +export function * generator2() { + yield* generator1() +} diff --git a/packages/eslint-config/__fixtures__/output/no-style/vue-ts.vue b/packages/eslint-config/__fixtures__/output/no-style/vue-ts.vue new file mode 100644 index 000000000..944abcf50 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/no-style/vue-ts.vue @@ -0,0 +1,35 @@ + + + + + + + diff --git a/packages/eslint-config/__fixtures__/output/no-style/vue.vue b/packages/eslint-config/__fixtures__/output/no-style/vue.vue new file mode 100644 index 000000000..36b2ff6d8 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/no-style/vue.vue @@ -0,0 +1,27 @@ + + + diff --git a/packages/eslint-config/__fixtures__/output/tab-double-quotes/javascript.js b/packages/eslint-config/__fixtures__/output/tab-double-quotes/javascript.js new file mode 100644 index 000000000..3918d1388 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/tab-double-quotes/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/packages/eslint-config/__fixtures__/output/tab-double-quotes/jsx.jsx b/packages/eslint-config/__fixtures__/output/tab-double-quotes/jsx.jsx new file mode 100644 index 000000000..08744a513 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/tab-double-quotes/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/packages/eslint-config/__fixtures__/output/tab-double-quotes/markdown.md b/packages/eslint-config/__fixtures__/output/tab-double-quotes/markdown.md new file mode 100644 index 000000000..c9616151a --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/tab-double-quotes/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/packages/eslint-config/__fixtures__/output/tab-double-quotes/toml.toml b/packages/eslint-config/__fixtures__/output/tab-double-quotes/toml.toml new file mode 100644 index 000000000..1f73d046b --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/tab-double-quotes/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/packages/eslint-config/__fixtures__/output/tab-double-quotes/tsconfig.json b/packages/eslint-config/__fixtures__/output/tab-double-quotes/tsconfig.json new file mode 100644 index 000000000..75dd2b29a --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/tab-double-quotes/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "skipDefaultLibCheck": true, + "skipLibCheck": true + } +} diff --git a/packages/eslint-config/__fixtures__/output/tab-double-quotes/tsx.tsx b/packages/eslint-config/__fixtures__/output/tab-double-quotes/tsx.tsx new file mode 100644 index 000000000..2f56c5c4a --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/tab-double-quotes/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/packages/eslint-config/__fixtures__/output/tab-double-quotes/typescript.ts b/packages/eslint-config/__fixtures__/output/tab-double-quotes/typescript.ts new file mode 100644 index 000000000..59ba5af14 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/tab-double-quotes/typescript.ts @@ -0,0 +1,95 @@ +// 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(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()) + +// Generator +export function* generator1() { + let id = 0 + while (id < 100) { + yield id++ + } +} +export function* generator2() { + yield* generator1() +} diff --git a/packages/eslint-config/__fixtures__/output/tab-double-quotes/vue-ts.vue b/packages/eslint-config/__fixtures__/output/tab-double-quotes/vue-ts.vue new file mode 100644 index 000000000..00e3aaeae --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/tab-double-quotes/vue-ts.vue @@ -0,0 +1,35 @@ + + + + + + + diff --git a/packages/eslint-config/__fixtures__/output/tab-double-quotes/vue.vue b/packages/eslint-config/__fixtures__/output/tab-double-quotes/vue.vue new file mode 100644 index 000000000..1356a70c6 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/tab-double-quotes/vue.vue @@ -0,0 +1,27 @@ + + + diff --git a/packages/eslint-config/__fixtures__/output/ts-override/javascript.js b/packages/eslint-config/__fixtures__/output/ts-override/javascript.js new file mode 100644 index 000000000..e0702598c --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/ts-override/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/packages/eslint-config/__fixtures__/output/ts-override/jsx.jsx b/packages/eslint-config/__fixtures__/output/ts-override/jsx.jsx new file mode 100644 index 000000000..523af3782 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/ts-override/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/packages/eslint-config/__fixtures__/output/ts-override/markdown.md b/packages/eslint-config/__fixtures__/output/ts-override/markdown.md new file mode 100644 index 000000000..a66c94c89 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/ts-override/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/packages/eslint-config/__fixtures__/output/ts-override/toml.toml b/packages/eslint-config/__fixtures__/output/ts-override/toml.toml new file mode 100644 index 000000000..1f73d046b --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/ts-override/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/packages/eslint-config/__fixtures__/output/ts-override/tsx.tsx b/packages/eslint-config/__fixtures__/output/ts-override/tsx.tsx new file mode 100644 index 000000000..ab640af67 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/ts-override/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/packages/eslint-config/__fixtures__/output/ts-override/typescript.ts b/packages/eslint-config/__fixtures__/output/ts-override/typescript.ts new file mode 100644 index 000000000..5d8e5f8d6 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/ts-override/typescript.ts @@ -0,0 +1,95 @@ +// Define a TypeScript interface +type 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(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 +type 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()) + +// Generator +export function* generator1() { + let id = 0 + while (id < 100) { + yield id++ + } +} +export function* generator2() { + yield* generator1() +} diff --git a/packages/eslint-config/__fixtures__/output/ts-override/vue-ts.vue b/packages/eslint-config/__fixtures__/output/ts-override/vue-ts.vue new file mode 100644 index 000000000..ce35e66b0 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/ts-override/vue-ts.vue @@ -0,0 +1,35 @@ + + + + + + + diff --git a/packages/eslint-config/__fixtures__/output/ts-override/vue.vue b/packages/eslint-config/__fixtures__/output/ts-override/vue.vue new file mode 100644 index 000000000..944cc4e56 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/ts-override/vue.vue @@ -0,0 +1,27 @@ + + + diff --git a/packages/eslint-config/__fixtures__/output/ts-strict-with-react/javascript.js b/packages/eslint-config/__fixtures__/output/ts-strict-with-react/javascript.js new file mode 100644 index 000000000..e0702598c --- /dev/null +++ b/packages/eslint-config/__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/packages/eslint-config/__fixtures__/output/ts-strict-with-react/jsx.jsx b/packages/eslint-config/__fixtures__/output/ts-strict-with-react/jsx.jsx new file mode 100644 index 000000000..523af3782 --- /dev/null +++ b/packages/eslint-config/__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/packages/eslint-config/__fixtures__/output/ts-strict-with-react/markdown.md b/packages/eslint-config/__fixtures__/output/ts-strict-with-react/markdown.md new file mode 100644 index 000000000..a66c94c89 --- /dev/null +++ b/packages/eslint-config/__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/packages/eslint-config/__fixtures__/output/ts-strict-with-react/toml.toml b/packages/eslint-config/__fixtures__/output/ts-strict-with-react/toml.toml new file mode 100644 index 000000000..1f73d046b --- /dev/null +++ b/packages/eslint-config/__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/packages/eslint-config/__fixtures__/output/ts-strict-with-react/tsx.tsx b/packages/eslint-config/__fixtures__/output/ts-strict-with-react/tsx.tsx new file mode 100644 index 000000000..ab640af67 --- /dev/null +++ b/packages/eslint-config/__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/packages/eslint-config/__fixtures__/output/ts-strict-with-react/typescript.ts b/packages/eslint-config/__fixtures__/output/ts-strict-with-react/typescript.ts new file mode 100644 index 000000000..c1d6575f7 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/ts-strict-with-react/typescript.ts @@ -0,0 +1,95 @@ +// 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(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()) + +// Generator +export function* generator1() { + let id = 0 + while (id < 100) { + yield id++ + } +} +export function* generator2() { + yield* generator1() +} diff --git a/packages/eslint-config/__fixtures__/output/ts-strict-with-react/vue-ts.vue b/packages/eslint-config/__fixtures__/output/ts-strict-with-react/vue-ts.vue new file mode 100644 index 000000000..ce35e66b0 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/ts-strict-with-react/vue-ts.vue @@ -0,0 +1,35 @@ + + + + + + + diff --git a/packages/eslint-config/__fixtures__/output/ts-strict-with-react/vue.vue b/packages/eslint-config/__fixtures__/output/ts-strict-with-react/vue.vue new file mode 100644 index 000000000..944cc4e56 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/ts-strict-with-react/vue.vue @@ -0,0 +1,27 @@ + + + diff --git a/packages/eslint-config/__fixtures__/output/ts-strict/javascript.js b/packages/eslint-config/__fixtures__/output/ts-strict/javascript.js new file mode 100644 index 000000000..e0702598c --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/ts-strict/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/packages/eslint-config/__fixtures__/output/ts-strict/jsx.jsx b/packages/eslint-config/__fixtures__/output/ts-strict/jsx.jsx new file mode 100644 index 000000000..523af3782 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/ts-strict/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/packages/eslint-config/__fixtures__/output/ts-strict/markdown.md b/packages/eslint-config/__fixtures__/output/ts-strict/markdown.md new file mode 100644 index 000000000..a66c94c89 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/ts-strict/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/packages/eslint-config/__fixtures__/output/ts-strict/toml.toml b/packages/eslint-config/__fixtures__/output/ts-strict/toml.toml new file mode 100644 index 000000000..1f73d046b --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/ts-strict/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/packages/eslint-config/__fixtures__/output/ts-strict/tsx.tsx b/packages/eslint-config/__fixtures__/output/ts-strict/tsx.tsx new file mode 100644 index 000000000..ab640af67 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/ts-strict/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/packages/eslint-config/__fixtures__/output/ts-strict/typescript.ts b/packages/eslint-config/__fixtures__/output/ts-strict/typescript.ts new file mode 100644 index 000000000..c1d6575f7 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/ts-strict/typescript.ts @@ -0,0 +1,95 @@ +// 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(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()) + +// Generator +export function* generator1() { + let id = 0 + while (id < 100) { + yield id++ + } +} +export function* generator2() { + yield* generator1() +} diff --git a/packages/eslint-config/__fixtures__/output/ts-strict/vue-ts.vue b/packages/eslint-config/__fixtures__/output/ts-strict/vue-ts.vue new file mode 100644 index 000000000..ce35e66b0 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/ts-strict/vue-ts.vue @@ -0,0 +1,35 @@ + + + + + + + diff --git a/packages/eslint-config/__fixtures__/output/ts-strict/vue.vue b/packages/eslint-config/__fixtures__/output/ts-strict/vue.vue new file mode 100644 index 000000000..944cc4e56 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/ts-strict/vue.vue @@ -0,0 +1,27 @@ + + + diff --git a/packages/eslint-config/__fixtures__/output/with-formatters/astro.astro b/packages/eslint-config/__fixtures__/output/with-formatters/astro.astro new file mode 100644 index 000000000..47e8620b5 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/with-formatters/astro.astro @@ -0,0 +1,18 @@ +--- +const isJsx = true +const content = 'hi!' +--- + +
+
{content}
+
+ {isJsx &&

{content}

} +
+
+ + diff --git a/packages/eslint-config/__fixtures__/output/with-formatters/css.css b/packages/eslint-config/__fixtures__/output/with-formatters/css.css new file mode 100644 index 000000000..ad88dbcfb --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/with-formatters/css.css @@ -0,0 +1,11 @@ +@media (max-width: 480px) { + .bd-examples { + margin-right: -0.75rem; + margin-left: -0.75rem; + } + + .bd-examples > [class^='col-'] { + padding-right: 0.75rem; + padding-left: 0.75rem; + } +} diff --git a/packages/eslint-config/__fixtures__/output/with-formatters/html.html b/packages/eslint-config/__fixtures__/output/with-formatters/html.html new file mode 100644 index 000000000..f4aec70fe --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/with-formatters/html.html @@ -0,0 +1,24 @@ + + + + + My tITlE + + + +

+ Hello world!
+ This is HTML5 Boilerplate. +

+ + + + diff --git a/packages/eslint-config/__fixtures__/output/with-formatters/javascript.js b/packages/eslint-config/__fixtures__/output/with-formatters/javascript.js new file mode 100644 index 000000000..e0702598c --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/with-formatters/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/packages/eslint-config/__fixtures__/output/with-formatters/jsx.jsx b/packages/eslint-config/__fixtures__/output/with-formatters/jsx.jsx new file mode 100644 index 000000000..523af3782 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/with-formatters/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/packages/eslint-config/__fixtures__/output/with-formatters/markdown.md b/packages/eslint-config/__fixtures__/output/with-formatters/markdown.md new file mode 100644 index 000000000..337663bd9 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/with-formatters/markdown.md @@ -0,0 +1,35 @@ +# 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/packages/eslint-config/__fixtures__/output/with-formatters/svg.svg b/packages/eslint-config/__fixtures__/output/with-formatters/svg.svg new file mode 100644 index 000000000..493e162e7 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/with-formatters/svg.svg @@ -0,0 +1,17 @@ + + + + + + + + diff --git a/packages/eslint-config/__fixtures__/output/with-formatters/toml.toml b/packages/eslint-config/__fixtures__/output/with-formatters/toml.toml new file mode 100644 index 000000000..1f73d046b --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/with-formatters/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/packages/eslint-config/__fixtures__/output/with-formatters/tsx.tsx b/packages/eslint-config/__fixtures__/output/with-formatters/tsx.tsx new file mode 100644 index 000000000..ab640af67 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/with-formatters/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/packages/eslint-config/__fixtures__/output/with-formatters/typescript.ts b/packages/eslint-config/__fixtures__/output/with-formatters/typescript.ts new file mode 100644 index 000000000..c1d6575f7 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/with-formatters/typescript.ts @@ -0,0 +1,95 @@ +// 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(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()) + +// Generator +export function* generator1() { + let id = 0 + while (id < 100) { + yield id++ + } +} +export function* generator2() { + yield* generator1() +} diff --git a/packages/eslint-config/__fixtures__/output/with-formatters/vue-ts.vue b/packages/eslint-config/__fixtures__/output/with-formatters/vue-ts.vue new file mode 100644 index 000000000..ea5c75ae9 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/with-formatters/vue-ts.vue @@ -0,0 +1,38 @@ + + + + + + + diff --git a/packages/eslint-config/__fixtures__/output/with-formatters/vue.vue b/packages/eslint-config/__fixtures__/output/with-formatters/vue.vue new file mode 100644 index 000000000..944cc4e56 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/with-formatters/vue.vue @@ -0,0 +1,27 @@ + + + diff --git a/packages/eslint-config/__fixtures__/output/with-formatters/xml.xml b/packages/eslint-config/__fixtures__/output/with-formatters/xml.xml new file mode 100644 index 000000000..88571e3d8 --- /dev/null +++ b/packages/eslint-config/__fixtures__/output/with-formatters/xml.xml @@ -0,0 +1,20 @@ + + + Effective Java + 45.00 + + + Bluetooth Speaker + 120.00 + + + Clean Code + 33.50 + + + + + + + + diff --git a/packages/eslint-config/__tests__/old-config-loading.test.ts b/packages/eslint-config/__tests__/old-config-loading.test.ts deleted file mode 100644 index 5c94e765f..000000000 --- a/packages/eslint-config/__tests__/old-config-loading.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { exec } from "node:child_process"; -import { rm } from "node:fs/promises"; -import { join } from "node:path"; -import { promisify } from "node:util"; - -import { afterEach, beforeEach, describe, expect, it } from "vitest"; - -const ONE_SECOND_IN_MS = 1000; - -const TEST_DIR = join(__dirname, "../__fixtures__", "old-config"); -const NODE_MODULES = join(TEST_DIR, "node_modules"); - -const execAsync = promisify(exec); - -describe("integration - old config", () => { - beforeEach(async () => { - await rm(NODE_MODULES, { force: true, recursive: true }); - }); - - afterEach(async () => { - await rm(NODE_MODULES, { recursive: true }); - }); - - it( - "installs & works", - async () => { - expect.assertions(2); - await execAsync("pnpm --ignore-workspace i", { cwd: TEST_DIR }); - - const { stderr, stdout } = await execAsync("pnpm exec eslint -c ./.eslintrc.js . || true", { - cwd: TEST_DIR, - env: { ...process.env, FORCE_COLOR: "0" }, - }); - - expect(stderr.replace(TEST_DIR, "mocked-root-dir")).toContain(""); - expect(stdout.replace(TEST_DIR, "mocked-root-dir")).toContain("4 problems (3 errors, 1 warning)"); - }, - 240 * ONE_SECOND_IN_MS, - ); -}); diff --git a/packages/eslint-config/__tests__/rules.test.ts b/packages/eslint-config/__tests__/rules.test.ts new file mode 100644 index 000000000..cc388135c --- /dev/null +++ b/packages/eslint-config/__tests__/rules.test.ts @@ -0,0 +1,228 @@ +import { existsSync } from "node:fs"; +import { + copyFile, + lstat, + mkdir, + readdir, + readFile, + rm, + writeFile, +} from "node:fs/promises"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +import { execa } from "execa"; +import { glob } from "tinyglobby"; +import { afterAll, beforeAll, it } from "vitest"; + +import type { OptionsConfig, TypedFlatConfigItem } from "../src/types"; + +const rootPath = dirname(fileURLToPath(import.meta.url)); +const fixturesPath = join(rootPath, "..", "__fixtures__"); + +const copyFolderRecursive = async (from: string, to: string) => { + await mkdir(to, { + recursive: true, + }); + + const files = await readdir(from, { + recursive: true, + }); + + for (const element of files) { + // eslint-disable-next-line unicorn/no-await-expression-member,no-await-in-loop + await ((await lstat(join(from, element))).isFile() + ? copyFile(join(from, element), join(to, element)) + : copyFolderRecursive(join(from, element), join(to, element))); + } +}; + +const temporaryDirectoryPath = join(rootPath, "..", "tmp_fixtures"); + +// eslint-disable-next-line vitest/require-top-level-describe +beforeAll(async () => { + await rm(temporaryDirectoryPath, { force: true, recursive: true }); +}); + +// eslint-disable-next-line vitest/require-top-level-describe +afterAll(async () => { + await rm(temporaryDirectoryPath, { force: true, recursive: true }); +}); + +const runWithConfig = (name: string, configs: OptionsConfig, ...items: TypedFlatConfigItem[]) => { + // eslint-disable-next-line vitest/prefer-expect-assertions,vitest/require-top-level-describe + it.concurrent( + // eslint-disable-next-line vitest/valid-title + name, + async ({ expect }) => { + const from = join(fixturesPath, "input"); + const output = join(fixturesPath, "output", name); + + const target = join(temporaryDirectoryPath, name); + + await copyFolderRecursive(from, target); + await writeFile( + join(target, "eslint.config.js"), + ` +// @eslint-disable +import { createConfig } from "../../dist/index.mjs"; + +export default createConfig( + ${JSON.stringify(configs)}, + ...(${JSON.stringify(items) ?? []}) +); + `, + ); + + await execa("npx", ["eslint", target, "--fix"], { + cwd: target, + stdio: "pipe", + }); + + const files = await glob("**/*", { + cwd: target, + ignore: ["node_modules", "eslint.config.js"], + }); + + await Promise.all( + files.map(async (file: string) => { + const content = await readFile(join(target, file), "utf8"); + const source = await readFile(join(from, file), "utf8"); + const outputPath = join(output, file); + + if (content === source) { + if (existsSync(outputPath)) { + await rm(outputPath); + } + + return; + } + + await expect.soft(content).toMatchFileSnapshot(join(output, file)); + }), + ); + }, + 30_000, + ); +}; + +// runWithConfig("no-config", { +// astro: false, +// formatters: false, +// gitignore: false, +// html: false, +// isInEditor: false, +// jsonc: false, +// jsx: false, +// lessOpinionated: false, +// lodash: false, +// markdown: false, +// playwright: false, +// react: false, +// regexp: false, +// storybook: false, +// stylistic: false, +// tailwindcss: false, +// tanstackQuery: false, +// tanstackRouter: false, +// testingLibrary: false, +// toml: false, +// tsdoc: false, +// typescript: false, +// unicorn: false, +// unocss: false, +// vitest: false, +// yaml: false, +// zod: false, +// }); +// runWithConfig("js", { +// typescript: false, +// vitest: false, +// }); +// runWithConfig("all", { +// astro: true, +// // svelte: true, +// typescript: true, +// // vue: true, +// }); +// runWithConfig("no-style", { +// stylistic: false, +// typescript: true, +// // vue: true, +// }); +// runWithConfig( +// "tab-double-quotes", +// { +// stylistic: { +// indent: "tab", +// quotes: "double", +// }, +// typescript: true, +// vue: true, +// }, +// { +// rules: { +// "style/no-mixed-spaces-and-tabs": "off", +// }, +// }, +// ); +// +// // https://github.com/antfu/eslint-config/issues/255 +// runWithConfig( +// "ts-override", +// { +// typescript: true, +// }, +// { +// rules: { +// "ts/consistent-type-definitions": ["error", "type"], +// }, +// }, +// ); +// +// // https://github.com/antfu/eslint-config/issues/255 +// runWithConfig( +// "ts-strict", +// { +// typescript: { +// tsconfigPath: "./tsconfig.json", +// }, +// }, +// { +// rules: { +// "ts/no-unsafe-return": ["off"], +// }, +// }, +// ); +// +// // https://github.com/antfu/eslint-config/issues/618 +// runWithConfig( +// "ts-strict-with-react", +// { +// react: true, +// typescript: { +// tsconfigPath: "./tsconfig.json", +// }, +// }, +// { +// rules: { +// "ts/no-unsafe-return": ["off"], +// }, +// }, +// ); +// +// runWithConfig("with-formatters", { +// astro: true, +// formatters: true, +// typescript: true, +// vue: true, +// }); +// +// runWithConfig("no-markdown-with-formatters", { +// formatters: { +// markdown: true, +// }, +// jsx: false, +// markdown: false, +// vue: false, +// }); diff --git a/packages/eslint-config/bin/generate-eslint-cofig.js b/packages/eslint-config/bin/generate-eslint-cofig.js deleted file mode 100755 index c08dbd81c..000000000 --- a/packages/eslint-config/bin/generate-eslint-cofig.js +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env node - -require("../dist/postinstall"); diff --git a/packages/eslint-config/debug-eslint.config.mjs b/packages/eslint-config/debug-eslint.config.mjs new file mode 100644 index 000000000..0881003b6 --- /dev/null +++ b/packages/eslint-config/debug-eslint.config.mjs @@ -0,0 +1,3 @@ +import { createConfig } from "./dist/index.mjs"; + +export default createConfig({}); diff --git a/packages/eslint-config/eslint.config.js b/packages/eslint-config/eslint.config.js new file mode 100644 index 000000000..00dc60c60 --- /dev/null +++ b/packages/eslint-config/eslint.config.js @@ -0,0 +1,41 @@ +import { createConfig } from "./dist/index.mjs"; + +export default createConfig( + { + ignores: ["./eslint.config.js", "./src/typegen.d.ts", "./src/utils/vitest-globals.ts", "./__fixtures__/**/*", "./__tests__/**/*"], + react: false, + playwright: false, + storybook: false, + tailwindcss: false, + tanstackQuery: false, + tanstackRouter: false, + testingLibrary: false, + tsdoc: false, + unocss: false, + zod: false, + lodash: false, + jsx: false, + html: false, + astro: false, + }, + { + files: ["**/*.ts"], + rules: { + "no-secrets/no-secrets": "off", + }, + }, + { + files: ["debug-eslint.config.mjs"], + rules: { + "antfu/no-import-dist": "off", + }, + }, + { + files: ["README.md"], + rules: { + "import/no-commonjs": "off", + "unicorn/prefer-module": "off", + "jsdoc/check-tag-names": "off", + }, + }, +); diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index 212f9f946..f2e5a6834 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -22,46 +22,34 @@ "eslint-plugin-antfu", "eslint-plugin-compat", "eslint-plugin-es", + "eslint-plugin-es-x", "eslint-plugin-eslint-comments", "eslint-plugin-html", "eslint-plugin-i", + "@tanstack/eslint-plugin-router", "eslint-plugin-jsonc", - "eslint-plugin-markdown", - "eslint-plugin-mdx", - "eslint-plugin-no-loops", + "eslint-markdown", "eslint-plugin-no-secrets", "eslint-plugin-no-use-extend-native", "eslint-plugin-promise", "eslint-plugin-regexp", - "eslint-plugin-simple-import-sort", "eslint-plugin-sonarjs", "eslint-plugin-toml", "eslint-plugin-unicorn", "eslint-plugin-yml", - "eslint-plugin-array-func", - "eslint-plugin-ava", - "eslint-plugin-babel", - "eslint-plugin-cypress", - "eslint-plugin-jest", - "eslint-plugin-jest-async", - "eslint-plugin-jest-dom", - "eslint-plugin-jest-formatting", "eslint-plugin-playwright", "eslint-plugin-jsdoc", "eslint-plugin-jsx-a11y", "eslint-plugin-n", "eslint-plugin-no-unsanitized", - "eslint-plugin-prefer-object-spread", "eslint-plugin-react", "eslint-plugin-react-hooks", - "eslint-plugin-react-redux", "eslint-plugin-storybook", "eslint-plugin-tailwindcss", "eslint-plugin-testing-library", "eslint-plugin-tsdoc", - "eslint-plugin-etc", - "eslint-plugin-you-dont-need-lodash-underscore", - "eslint-plugin-you-dont-need-momentjs" + "eslint-plugin-no-for-of-array", + "eslint-plugin-you-dont-need-lodash-underscore" ], "homepage": "https://anolilab.com/nodejs/packages/eslint-config", "repository": { @@ -84,206 +72,184 @@ "name": "Daniel Bannert", "email": "d.bannert@anolilab.de" }, - "type": "commonjs", + "sideEffects": false, + "type": "module", "exports": { ".": { - "types": "./dist/index.d.ts", - "require": "./dist/index.js" - }, - "./typescript-type-checking": { - "types": "./dist/typescript-type-checking.d.ts", - "require": "./dist/typescript-type-checking.js" - }, - "./globals": { - "types": "./dist/globals.d.ts", - "require": "./dist/globals.js" - }, - "./define-config": { - "types": "./dist/define-config.d.ts", - "require": "./dist/define-config.js" + "import": { + "types": "./dist/index.d.tjs", + "default": "./dist/index.mjs" + }, + "require": { + "types": "./dist/index.d.cts", + "default": "./dist/index.cjs" + } }, "./package.json": "./package.json" }, - "main": "dist/index.js", + "main": "dist/index.cjs", + "module": "dist/index.mjs", "types": "dist/index.d.ts", - "bin": { - "anolilab-eslint-config": "./bin/generate-eslint-cofig.js" + "typesVersions": { + ">=5.0": { + ".": [ + "./dist/index.d.ts" + ] + } }, "files": [ - "bin/generate-eslint-cofig.js", - "dist", - "skip.js", - "globals.js", - "typescript-type-checking.js", - "README.md", "CHANGELOG.md", - "LICENSE.md" + "LICENSE.md", + "README.md", + "dist" ], "scripts": { - "build": "cross-env NODE_ENV=development tsup", - "build:prod": "cross-env NODE_ENV=production tsup", + "build": "pnpm run build:global-vitest && pnpm run build:typegen && packem build --development", + "build:global-vitest": "tsx ./scripts/global-vitest.ts", + "build:prod": "pnpm run build:global-vitest && pnpm run build:typegen && packem build --production", + "build:typegen": "tsx ./scripts/typegen.ts", "clean": "rimraf node_modules dist", - "postinstall": "node ./skip.js || node ./dist/postinstall.js", - "test": "vitest --config ./vitest.config.ts", - "test:coverage": "vitest --config ./vitest.config.ts --run --coverage" + "debug:rules": "pnpm exec eslint-config-inspector --config ./debug-eslint.config.mjs", + "lint:attw": "attw --pack", + "lint:eslint": "eslint .", + "lint:eslint:fix": "eslint . --fix", + "lint:prettier": "prettier --config=.prettierrc.js --check .", + "lint:prettier:fix": "prettier --config=.prettierrc.js --write .", + "test": "vitest run || exit 0", + "test:coverage": "vitest run --coverage || exit 0", + "test:ui": "vitest --ui --coverage.enabled=true", + "test:watch": "vitest" }, "dependencies": { - "@anolilab/package-json-utils": "3.0.9", - "@babel/eslint-parser": "^7.22.15", - "@babel/plugin-syntax-import-assertions": "^7.22.5", - "@eslint/js": "^8.52.0", - "@html-eslint/eslint-plugin": "^0.20.0", - "@html-eslint/parser": "^0.20.0", - "@jsenv/eslint-import-resolver": ">=8.0.4", - "@rushstack/eslint-patch": "^1.5.1", - "@rushstack/eslint-plugin-security": "^0.7.1", - "@typescript-eslint/eslint-plugin": ">=6.9.1", - "@typescript-eslint/parser": "^6.9.1", + "@eslint-community/eslint-plugin-eslint-comments": "^4.5.0", + "@eslint/compat": "^1.2.9", + "@eslint/js": "^9.27.0", + "@eslint/markdown": "^6.4.0", + "@html-eslint/eslint-plugin": "^0.40.3", + "@html-eslint/parser": "^0.40.0", + "@stylistic/eslint-plugin": "^4.2.0", + "@stylistic/eslint-plugin-ts": "^4.2.0", + "@typescript-eslint/eslint-plugin": "^8.32.1", + "@typescript-eslint/parser": "^8.32.1", + "@visulima/package": "^3.5.4", + "@visulima/tsconfig": "^1.1.16", + "@vitest/eslint-plugin": "^1.2.0", "confusing-browser-globals": "^1.0.11", - "eslint-define-config": "^1.24.1", + "eslint-config-flat-gitignore": "^2.1.0", + "eslint-flat-config-utils": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", - "eslint-import-resolver-typescript": "^3.6.1", - "eslint-plugin-antfu": "^1.0.1", - "eslint-plugin-compat": "^4.2.0", - "eslint-plugin-es-x": "^7.2.0", - "eslint-plugin-eslint-comments": "^3.2.0", - "eslint-plugin-html": "^7.1.0", - "eslint-plugin-i": "^2.29.0", - "eslint-plugin-jsonc": "^2.10.0", - "eslint-plugin-markdown": "^3.0.1", - "eslint-plugin-mdx": "^2.2.0", - "eslint-plugin-n": "^16.2.0", - "eslint-plugin-no-loops": "^0.3.0", - "eslint-plugin-no-only-tests": "^3.1.0", - "eslint-plugin-no-secrets": "^0.8.9", - "eslint-plugin-no-use-extend-native": "^0.5.0", - "eslint-plugin-perfectionist": "^2.2.0", - "eslint-plugin-promise": "^6.1.1", - "eslint-plugin-regexp": "^2.1.1", - "eslint-plugin-security": "^1.7.1", - "eslint-plugin-simple-import-sort": "^10.0.0", - "eslint-plugin-sonarjs": "^0.22.0", - "eslint-plugin-toml": "^0.6.0", - "eslint-plugin-unicorn": "^49.0.0", - "eslint-plugin-yml": "^1.10.0", - "find-up": "5.0.0", - "globals": "^13.23.0", + "eslint-import-resolver-typescript": "^4.3.5", + "eslint-merge-processors": "^2.0.0", + "eslint-plugin-antfu": "^3.1.1", + "eslint-plugin-compat": "^6.0.2", + "eslint-plugin-es-x": "^8.6.2", + "eslint-plugin-html": "^8.1.3", + "eslint-plugin-import-x": "^4.12.2", + "eslint-plugin-jsdoc": "^50.6.17", + "eslint-plugin-jsonc": "^2.20.1", + "eslint-plugin-n": "^17.18.0", + "eslint-plugin-no-for-of-array": "^0.1.0", + "eslint-plugin-no-only-tests": "^3.3.0", + "eslint-plugin-no-secrets": "^2.2.1", + "eslint-plugin-no-unsanitized": "^4.1.2", + "eslint-plugin-perfectionist": "^4.13.0", + "eslint-plugin-promise": "^7.2.1", + "eslint-plugin-regexp": "^2.7.0", + "eslint-plugin-security": "^3.0.1", + "eslint-plugin-simple-import-sort": "^12.1.1", + "eslint-plugin-sonarjs": "^3.0.2", + "eslint-plugin-toml": "^0.12.0", + "eslint-plugin-unicorn": "^59.0.1", + "eslint-plugin-unused-imports": "^4.1.4", + "eslint-plugin-yml": "^1.18.0", + "globals": "^16.1.0", "jsonc-eslint-parser": "^2.4.0", - "read-pkg-up": "^7.0.1", - "semver": "^7.5.4", - "toml-eslint-parser": "^0.6.0", - "yaml-eslint-parser": "^1.2.2" + "parse-gitignore": "^2.0.0", + "semver": "^7.7.2", + "toml-eslint-parser": "^0.10.0", + "typescript-eslint": "^8.32.1", + "yaml-eslint-parser": "^1.3.0" }, "devDependencies": { - "@anolilab/semantic-release-preset": "8.0.3", - "@arthurgeron/eslint-plugin-react-usememo": "^2.2.1", - "@testing-library/dom": "^9.3.3", - "@total-typescript/ts-reset": "^0.5.1", + "@anolilab/prettier-config": "^5.0.14", + "@anolilab/semantic-release-preset": "10.0.5", + "@eslint-react/eslint-plugin": "^1.49.0", + "@eslint/config-inspector": "^1.0.2", + "@stylistic/eslint-plugin-migrate": "^4.2.0", + "@tanstack/eslint-plugin-query": "^5.74.7", + "@tanstack/eslint-plugin-router": "^1.115.0", + "@testing-library/dom": "^10.4.0", + "@total-typescript/ts-reset": "^0.6.1", "@types/confusing-browser-globals": "^1.0.3", - "@types/eslint": "^8.56.0", - "@types/semver": "^7.5.6", - "eslint": "^8.56.0", - "eslint-find-rules": "^4.1.0", - "eslint-plugin-babel": "^5.3.1", - "eslint-plugin-cypress": "^2.15.1", - "eslint-plugin-deprecation": "^2.0.0", - "eslint-plugin-editorconfig": "^4.0.3", - "eslint-plugin-etc": "^2.0.3", - "eslint-plugin-jest": "^27.6.0", - "eslint-plugin-jest-async": "^1.0.3", - "eslint-plugin-jest-dom": "^5.1.0", - "eslint-plugin-jest-formatting": "^3.1.0", - "eslint-plugin-jsdoc": "^46.9.1", - "eslint-plugin-jsx-a11y": "^6.8.0", - "eslint-plugin-no-unsanitized": "^4.0.2", - "eslint-plugin-prefer-object-spread": "^1.2.1", - "eslint-plugin-react": "^7.33.2", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-redux": "^4.1.0", - "eslint-plugin-ssr-friendly": "^1.3.0", - "eslint-plugin-storybook": "^0.6.15", - "eslint-plugin-tailwindcss": "^3.13.0", - "eslint-plugin-testing-library": "^6.2.0", + "@types/eslint": "^9.6.1", + "@types/eslint-plugin-jsx-a11y": "^6.10.0", + "@types/eslint-plugin-tailwindcss": "^3.17.0", + "@types/semver": "^7.7.0", + "@unocss/eslint-plugin": "^66.1.2", + "@visulima/packem": "^1.19.1", + "astro-eslint-parser": "^1.2.2", + "esbuild": "^0.25.4", + "eslint": "^9.27.0", + "eslint-plugin-astro": "^1.3.1", + "eslint-plugin-format": "^1.0.1", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.20", + "eslint-plugin-storybook": "^0.12.0", + "eslint-plugin-tailwindcss": "^3.18.0", + "eslint-plugin-testing-library": "^7.2.1", + "eslint-plugin-tsdoc": "^0.4.0", "eslint-plugin-validate-jsx-nesting": "^0.1.1", - "eslint-plugin-vitest": "^0.3.18", - "eslint-plugin-you-dont-need-lodash-underscore": "^6.13.0", - "eslint-plugin-you-dont-need-momentjs": "^1.6.0", + "eslint-plugin-vitest": "^0.5.4", + "eslint-plugin-you-dont-need-lodash-underscore": "^6.14.0", "eslint-plugin-zod": "^1.4.0", - "jest": "^29.7.0", - "react": "^18.2.0", - "rimraf": "^5.0.5", - "semantic-release": "^22.0.12", - "tsup": "^8.0.1", - "type-fest": "^4.8.3", - "typescript": "^5.3.3", - "vitest": "^1.1.0" + "eslint-typegen": "^2.2.0", + "execa": "^9.5.3", + "prettier": "^3.5.3", + "react": "^19.1.0", + "rimraf": "^6.0.1", + "semantic-release": "^24.2.4", + "tinyglobby": "^0.2.13", + "tsx": "^4.19.4", + "type-fest": "^4.41.0", + "typescript": "^5.8.3", + "vitest": "^3.1.4" }, "peerDependencies": { - "@arthurgeron/eslint-plugin-react-usememo": "^2.0.1", "@babel/core": "^7.22.20", - "@tanstack/eslint-plugin-query": "^4.34.1 || ^5.0.0", - "eslint": "^8.15.0", - "eslint-plugin-array-func": "^4.0.0", - "eslint-plugin-ava": "^14.0.0", - "eslint-plugin-babel": "^5.3.1", - "eslint-plugin-cypress": "^2.15.1", - "eslint-plugin-editorconfig": "^4.0.3", - "eslint-plugin-jest": "^27.4.0", - "eslint-plugin-jest-async": "^1.0.3", - "eslint-plugin-jest-dom": "^5.1.0", - "eslint-plugin-jest-formatting": "^3.1.0", - "eslint-plugin-jsdoc": "^46.8.2", + "@eslint-react/eslint-plugin": "^1.22.1", + "@tanstack/eslint-plugin-query": "^5.0.0", + "@tanstack/eslint-plugin-router": "^1.92.7", + "@unocss/eslint-plugin": "^0.65.3", + "astro-eslint-parser": "^1.1.0", + "eslint": "^9.10.0", + "eslint-plugin-astro": "^1.3.1", + "eslint-plugin-format": ">=0.1.0", "eslint-plugin-jsx-a11y": "^6.7.1", - "eslint-plugin-no-unsanitized": "^4.0.2", "eslint-plugin-playwright": "^0.16.0 || ^0.18.0", - "eslint-plugin-prefer-object-spread": "^1.2.1", - "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react": "^7.37.3", "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-redux": "^4.0.0", - "eslint-plugin-ssr-friendly": "^1.2.0", + "eslint-plugin-react-refresh": "^0.4.16", "eslint-plugin-storybook": "^0.6.14", "eslint-plugin-tailwindcss": "^3.13.0", "eslint-plugin-testing-library": "^6.0.1", "eslint-plugin-tsdoc": "^0.2.17", "eslint-plugin-validate-jsx-nesting": "^0.1.1", "eslint-plugin-you-dont-need-lodash-underscore": "^6.13.0", - "eslint-plugin-you-dont-need-momentjs": "^1.6.0" + "eslint-plugin-zod": "^1.4.0" }, "peerDependenciesMeta": { - "@arthurgeron/eslint-plugin-react-usememo": { + "@eslint-react/eslint-plugin": { "optional": true }, "@tanstack/eslint-plugin-query": { "optional": true }, - "eslint-plugin-array-func": { - "optional": true - }, - "eslint-plugin-ava": { - "optional": true - }, - "eslint-plugin-babel": { - "optional": true - }, - "eslint-plugin-cypress": { - "optional": true - }, - "eslint-plugin-editorconfig": { + "@tanstack/eslint-plugin-router": { "optional": true }, - "eslint-plugin-jest": { - "optional": true - }, - "eslint-plugin-jest-async": { - "optional": true - }, - "eslint-plugin-jest-dom": { - "optional": true - }, - "eslint-plugin-jest-formatting": { - "optional": true - }, - "eslint-plugin-jsdoc": { + "@unocss/eslint-plugin": { "optional": true }, "eslint-plugin-jsx-a11y": { @@ -292,27 +258,15 @@ "eslint-plugin-n": { "optional": true }, - "eslint-plugin-no-unsanitized": { - "optional": true - }, "eslint-plugin-playwright": { "optional": true }, - "eslint-plugin-prefer-object-spread": { - "optional": true - }, "eslint-plugin-react": { "optional": true }, "eslint-plugin-react-hooks": { "optional": true }, - "eslint-plugin-react-redux": { - "optional": true - }, - "eslint-plugin-ssr-friendly": { - "optional": true - }, "eslint-plugin-storybook": { "optional": true }, @@ -331,7 +285,7 @@ "eslint-plugin-you-dont-need-lodash-underscore": { "optional": true }, - "eslint-plugin-you-dont-need-momentjs": { + "eslint-plugin-zod": { "optional": true }, "typescript": { @@ -339,77 +293,10 @@ } }, "engines": { - "node": ">=18.* <=21.*" + "node": ">=18.* <=23.*" }, "publishConfig": { "access": "public", "provenance": true - }, - "sources": [ - "src/config/best-practices.ts", - "src/config/errors.ts", - "src/config/es6.ts", - "src/config/plugins/antfu.ts", - "src/config/plugins/array-func.ts", - "src/config/plugins/ava.ts", - "src/config/plugins/babel.ts", - "src/config/plugins/compat.ts", - "src/config/plugins/cypress.ts", - "src/config/plugins/deprecation.ts", - "src/config/plugins/es.ts", - "src/config/plugins/eslint-comments.ts", - "src/config/plugins/etc.ts", - "src/config/plugins/editorconfig.ts", - "src/config/plugins/html.ts", - "src/config/plugins/import.ts", - "src/config/plugins/jest-async.ts", - "src/config/plugins/jest-dom.ts", - "src/config/plugins/jest-formatting.ts", - "src/config/plugins/jest.ts", - "src/config/plugins/jsdoc.ts", - "src/config/plugins/jsonc.ts", - "src/config/plugins/jsx-a11y.ts", - "src/config/plugins/markdown.ts", - "src/config/plugins/mdx.ts", - "src/config/plugins/no-extend-native.ts", - "src/config/plugins/no-loops.ts", - "src/config/plugins/no-only-tests.ts", - "src/config/plugins/no-secrets.ts", - "src/config/plugins/no-unsanitized.ts", - "src/config/plugins/node.ts", - "src/config/plugins/perfectionist.ts", - "src/config/plugins/playwright.ts", - "src/config/plugins/promise.ts", - "src/config/plugins/regexp.ts", - "src/config/plugins/react.ts", - "src/config/plugins/react-hooks.ts", - "src/config/plugins/react-redux.ts", - "src/config/plugins/react-usememo.ts", - "src/config/plugins/security.ts", - "src/config/plugins/simple-import-sort.ts", - "src/config/plugins/sonarjs.ts", - "src/config/plugins/ssr-friendly.ts", - "src/config/plugins/storybook.ts", - "src/config/plugins/tailwindcss.ts", - "src/config/plugins/tanstack-query.ts", - "src/config/plugins/testing-library-dom.ts", - "src/config/plugins/testing-library-react.ts", - "src/config/plugins/toml.ts", - "src/config/plugins/tsdoc.ts", - "src/config/plugins/typescript.ts", - "src/config/plugins/unicorn.ts", - "src/config/plugins/validate-jsx-nesting.ts", - "src/config/plugins/vitest.ts", - "src/config/plugins/yml.ts", - "src/config/plugins/you-dont-need-lodash-underscore.ts", - "src/config/plugins/you-dont-need-momentjs.ts", - "src/config/plugins/zod.ts", - "src/config/style.ts", - "src/config/variables.ts", - "src/globals.ts", - "src/index.ts", - "src/postinstall.ts", - "src/typescript-type-checking.ts", - "src/define-config.ts" - ] + } } diff --git a/packages/eslint-config/packem.config.ts b/packages/eslint-config/packem.config.ts new file mode 100644 index 000000000..04577878b --- /dev/null +++ b/packages/eslint-config/packem.config.ts @@ -0,0 +1,21 @@ +import { defineConfig } from "@visulima/packem/config"; +import transformer from "@visulima/packem/transformer/esbuild"; + +export default defineConfig({ + declaration: false, + rollup: { + license: { + path: "./LICENSE.md", + }, + node10Compatibility: { + typeScriptVersion: ">=5.0", + writeToPackageJson: true, + }, + }, + transformer, + validation: { + packageJson: { + exports: false, + }, + }, +}); diff --git a/packages/eslint-config/project.json b/packages/eslint-config/project.json index 58a39ba7e..5ab52c637 100644 --- a/packages/eslint-config/project.json +++ b/packages/eslint-config/project.json @@ -1,4 +1,5 @@ { + "name": "eslint-config", "$schema": "../../node_modules/nx/schemas/project-schema.json", "sourceRoot": "packages/eslint-config/src", "projectType": "library", diff --git a/packages/eslint-config/scripts/global-vitest.ts b/packages/eslint-config/scripts/global-vitest.ts new file mode 100644 index 000000000..64faafbc6 --- /dev/null +++ b/packages/eslint-config/scripts/global-vitest.ts @@ -0,0 +1,92 @@ +/** + * This script is used to generate the globals used by vitest. + * + * A modified version of the original script from https://github.com/FRSOURCE/toolkit/blob/ac470422ba97837556c8a281e6ed4f73bcd0ba90/packages/globals-vitest + * + * MIT License + * Copyright (c) 2024 FRSOURCE - Let's shape your web + */ +import { writeFileSync } from "node:fs"; +import { createRequire } from "node:module"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +import type { ModuleBody, SourceFile } from "typescript"; +// eslint-disable-next-line import/no-extraneous-dependencies +import ts from "typescript"; + +const rootPath = dirname(fileURLToPath(import.meta.url)); + +const showMessageAndExit = (message: string, fail = true) => { + // eslint-disable-next-line no-console + console.log(message); + + // eslint-disable-next-line unicorn/no-process-exit + process.exit(fail ? 1 : 0); +}; + +const extract = (file: string) => { + const program = ts.createProgram([file], {}); + const sourceFile = program.getSourceFile(file) as SourceFile; + const globals: string[] = []; + + ts.forEachChild(sourceFile, (node) => { + if (ts.isModuleDeclaration(node)) { + ts.forEachChild(node.body as ModuleBody, (mNode) => { + if (ts.isVariableStatement(mNode)) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ts.forEachChild(mNode, (vNode: any) => { + if (ts.isVariableDeclarationList(vNode)) { + for (const declaration of vNode.declarations) { + const name = ts.getNameOfDeclaration(declaration); + + if (name && "escapedText" in name) { + globals.push(name.escapedText as string); + } + } + } + }); + } + }); + } + }); + + return globals; +}; + +const require = createRequire(import.meta.url); +const packagePath = require.resolve("vitest/package.json"); + +const { + default: { version: vitestVersion }, +} = await import(packagePath, { + with: { type: "json" }, +}); + +if (!vitestVersion) { + showMessageAndExit("Vitest version cannot be read."); +} + +const globalsPath = require.resolve("vitest/globals.d.ts"); + +const globalsArray = extract(globalsPath); +const globals: Record = {}; + +if (globalsArray.length === 0) { + showMessageAndExit("No globals! Check extractor implementation."); +} + +globalsArray.forEach((globalName) => { + globals[globalName] = true; +}); + +const moduleContent = `/** + * vitest version ${vitestVersion} + */ +export default /** @type {const} */ (${JSON.stringify(globals, undefined, 4)}); +`; + +writeFileSync(join(rootPath, "..", "src", "utils", "vitest-globals.ts"), moduleContent); + +// eslint-disable-next-line no-console +console.log("Finished generation with result:\n", moduleContent); diff --git a/packages/eslint-config/scripts/typegen.ts b/packages/eslint-config/scripts/typegen.ts new file mode 100644 index 000000000..610bc746d --- /dev/null +++ b/packages/eslint-config/scripts/typegen.ts @@ -0,0 +1,137 @@ +import fs from "node:fs/promises"; + +import JS from "@eslint/js"; +import type { NormalizedPackageJson } from "@visulima/package"; +import { flatConfigsToRulesDTS } from "eslint-typegen/core"; + +import bestPractices from "../src/config/best-practices"; +import errors from "../src/config/errors"; +import antfu from "../src/config/plugins/antfu"; +import astro from "../src/config/plugins/astro"; +import comments from "../src/config/plugins/comments"; +import compat from "../src/config/plugins/compat"; +import formatters from "../src/config/plugins/formatters"; +import html from "../src/config/plugins/html"; +import imports from "../src/config/plugins/imports"; +import javascript from "../src/config/plugins/javascript"; +import jsdoc from "../src/config/plugins/jsdoc"; +import jsonc from "../src/config/plugins/jsonc"; +import jsxA11y from "../src/config/plugins/jsx-a11y"; +import markdown from "../src/config/plugins/markdown"; +import noSecrets from "../src/config/plugins/no-secrets"; +import noUnsanitized from "../src/config/plugins/no-unsanitized"; +import node from "../src/config/plugins/node"; +import perfectionist from "../src/config/plugins/perfectionist"; +import playwright from "../src/config/plugins/playwright"; +import promise from "../src/config/plugins/promise"; +import react from "../src/config/plugins/react"; +import regexp from "../src/config/plugins/regexp"; +import simpleImportSort from "../src/config/plugins/simple-import-sort"; +import sonarjs from "../src/config/plugins/sonarjs"; +import storybook from "../src/config/plugins/storybook"; +import stylistic from "../src/config/plugins/stylistic"; +import tailwindcss from "../src/config/plugins/tailwindcss"; +import tanstackQuery from "../src/config/plugins/tanstack-query"; +import tanstackRouter from "../src/config/plugins/tanstack-router"; +import testingLibrary from "../src/config/plugins/testing-library"; +import toml from "../src/config/plugins/toml"; +import tsdoc from "../src/config/plugins/tsdoc"; +import typescript from "../src/config/plugins/typescript"; +import unicorn from "../src/config/plugins/unicorn"; +import unocss from "../src/config/plugins/unocss"; +import validateJsxNesting from "../src/config/plugins/validate-jsx-nesting"; +import vitest from "../src/config/plugins/vitest"; +import yaml from "../src/config/plugins/yml"; +import youDontNeedLodashUnderscore from "../src/config/plugins/you-dont-need-lodash-underscore"; +import zod from "../src/config/plugins/zod"; +import style from "../src/config/style"; +import variables from "../src/config/variables"; +import combine from "../src/utils/combine"; + +const fakePackageJson = {} as NormalizedPackageJson; + +const configs = await combine( + { + plugins: { + "": { + rules: JS.configs.all, + }, + }, + }, + antfu({ + packageJson: fakePackageJson, + }), + astro({}), + bestPractices({}), + compat({}), + errors({}), + html({}), + noSecrets({}), + noUnsanitized({}), + playwright({}), + promise({}), + simpleImportSort({}), + sonarjs({}), + storybook({}), + tailwindcss({}), + tanstackQuery({}), + tanstackRouter({}), + tsdoc({}), + validateJsxNesting({}), + variables({}), + style({}), + comments({}), + formatters({}, {}), + imports({ + cwd: "", + packageJson: fakePackageJson, + }), + javascript({ + packageJson: fakePackageJson, + }), + jsxA11y({}), + jsdoc({ + packageJson: fakePackageJson, + }), + jsonc({ + packageJson: fakePackageJson, + }), + markdown({}), + perfectionist({ + packageJson: fakePackageJson, + }), + react({ + packageJson: fakePackageJson, + }), + node({ + packageJson: fakePackageJson, + }), + youDontNeedLodashUnderscore({}), + stylistic({}), + vitest({}), + toml({}), + regexp({}), + typescript({}), + unicorn({ + packageJson: fakePackageJson, + }), + testingLibrary({ + packageJson: fakePackageJson, + }), + unocss({}), + yaml({}), + zod({}), +); + +const configNames = configs.map((index) => index.name).filter(Boolean) as string[]; + +let dts = await flatConfigsToRulesDTS(configs, { + includeAugmentation: false, +}); + +dts += ` +// Names of all the configs +export type ConfigNames = ${configNames.map((index) => `'${index}'`).join(" | ")} +`; + +await fs.writeFile("src/typegen.d.ts", dts); diff --git a/packages/eslint-config/skip.js b/packages/eslint-config/skip.js deleted file mode 100644 index d64d9b3c9..000000000 --- a/packages/eslint-config/skip.js +++ /dev/null @@ -1,7 +0,0 @@ -if (process.env.SKIP_BUILD) { - // eslint-disable-next-line unicorn/no-process-exit - process.exit(0); -} else { - // eslint-disable-next-line unicorn/no-process-exit - process.exit(1); -} diff --git a/packages/eslint-config/src/config.ts b/packages/eslint-config/src/config.ts deleted file mode 100644 index 0687d2cf4..000000000 --- a/packages/eslint-config/src/config.ts +++ /dev/null @@ -1,574 +0,0 @@ -import { hasDependency, hasDevDependency, hasFile, resolvePackage } from "@anolilab/package-json-utils"; - -import type { PackageRules } from "./types"; -import anolilabEslintConfig from "./utils/eslint-config"; - -const baseConfig = ["best-practices", "errors", "style", "es6", "variables"]; - -// eslint-disable-next-line import/exports-last -export const internalPluginConfig = [ - "compat", - "eslint-comments", - "import", - "promise", - "simple-import-sort", - "no-extend-native", - "node", - // Security Rules - "no-secrets", - "sonarjs", - "security", - "regexp", - // file rules - "jsonc", - "markdown", - "toml", - "yml", - "html", - "no-loops", - - // custom rules - "antfu", - "unicorn", - "es", - "perfectionist", -]; - -const pluginConfig: PackageRules = [ - { - configName: "array-func", - dependencies: ["eslint-plugin-array-func"], - }, - { - configName: "jsdoc", - dependencies: ["eslint-plugin-jsdoc"], - }, - { - configName: "tsdoc", - dependencies: ["eslint-plugin-tsdoc", "typescript"], - }, - { - configName: "you-dont-need-lodash-underscore", - dependencies: ["eslint-plugin-you-dont-need-lodash-underscore"], - oneOfDependency: [ - "lodash", - "underscore", - "lodash-es", - "@types/lodash", - - "lodash.chunk", - "lodash.compact", - "lodash.concat", - "lodash.difference", - "lodash.differenceby", - "lodash.differencewith", - "lodash.drop", - "lodash.dropright", - "lodash.droprightwhile", - "lodash.dropwhile", - "lodash.fill", - "lodash.findindex", - "lodash.findlastindex", - "lodash.flatten", - "lodash.flattendeep", - "lodash.flattendepth", - "lodash.frompairs", - "lodash.head", - "lodash.indexof", - "lodash.initial", - "lodash.intersection", - "lodash.intersectionby", - "lodash.intersectionwith", - "lodash.join", - "lodash.last", - "lodash.lastindexof", - "lodash.nth", - "lodash.pull", - "lodash.pullall", - "lodash.pullallby", - "lodash.pullallwith", - "lodash.pullat", - "lodash.remove", - "lodash.reverse", - "lodash.slice", - "lodash.sortedindex", - "lodash.sortedindexby", - "lodash.sortedindexof", - "lodash.sortedlastindex", - "lodash.sortedlastindexby", - "lodash.sortedlastindexof", - "lodash.sorteduniq", - "lodash.sorteduniqby", - "lodash.tail", - "lodash.take", - "lodash.takeright", - "lodash.takerightwhile", - "lodash.takewhile", - "lodash.union", - "lodash.unionby", - "lodash.unionwith", - "lodash.uniq", - "lodash.uniqby", - "lodash.uniqwith", - "lodash.unzip", - "lodash.unzipwith", - "lodash.without", - "lodash.xor", - "lodash.xorby", - "lodash.xorwith", - "lodash.zip", - "lodash.zipobject", - "lodash.zipobjectdeep", - "lodash.zipwith", - "lodash.countby", - "lodash.every", - "lodash.filter", - "lodash.find", - "lodash.findlast", - "lodash.flatmap", - "lodash.flatmapdeep", - "lodash.flatmapdepth", - "lodash.foreach", - "lodash.foreachright", - "lodash.groupby", - "lodash.includes", - "lodash.invokemap", - "lodash.keyby", - "lodash.map", - "lodash.orderby", - "lodash.partition", - "lodash.reduce", - "lodash.reduceright", - "lodash.reject", - "lodash.sample", - "lodash.samplesize", - "lodash.shuffle", - "lodash.size", - "lodash.some", - "lodash.sortby", - "lodash.now", - "lodash.after", - "lodash.ary", - "lodash.before", - "lodash.bind", - "lodash.bindkey", - "lodash.curry", - "lodash.curryright", - "lodash.debounce", - "lodash.defer", - "lodash.delay", - "lodash.flip", - "lodash.memoize", - "lodash.negate", - "lodash.once", - "lodash.overargs", - "lodash.partial", - "lodash.partialright", - "lodash.rearg", - "lodash.rest", - "lodash.spread", - "lodash.throttle", - "lodash.unary", - "lodash.wrap", - "lodash.castarray", - "lodash.clone", - "lodash.clonedeep", - "lodash.clonedeepwith", - "lodash.clonewith", - "lodash.conformsto", - "lodash.eq", - "lodash.gt", - "lodash.gte", - "lodash.isarguments", - "lodash.isarray", - "lodash.isarraybuffer", - "lodash.isarraylike", - "lodash.isarraylikeobject", - "lodash.isboolean", - "lodash.isbuffer", - "lodash.isdate", - "lodash.iselement", - "lodash.isempty", - "lodash.isequal", - "lodash.isequalwith", - "lodash.iserror", - "lodash.isfinite", - "lodash.isfunction", - "lodash.isinteger", - "lodash.islength", - "lodash.ismap", - "lodash.ismatch", - "lodash.ismatchwith", - "lodash.isnan", - "lodash.isnative", - "lodash.isnil", - "lodash.isnull", - "lodash.isnumber", - "lodash.isobject", - "lodash.isobjectlike", - "lodash.isplainobject", - "lodash.isregexp", - "lodash.issafeinteger", - "lodash.isset", - "lodash.isstring", - "lodash.issymbol", - "lodash.istypedarray", - "lodash.isundefined", - "lodash.isweakmap", - "lodash.isweakset", - "lodash.lt", - "lodash.lte", - "lodash.toarray", - "lodash.tofinite", - "lodash.tointeger", - "lodash.tolength", - "lodash.tonumber", - "lodash.toplainobject", - "lodash.tosafeinteger", - "lodash.tostring", - "lodash.add", - "lodash.ceil", - "lodash.divide", - "lodash.floor", - "lodash.max", - "lodash.maxby", - "lodash.mean", - "lodash.meanby", - "lodash.min", - "lodash.minby", - "lodash.multiply", - "lodash.round", - "lodash.subtract", - "lodash.sum", - "lodash.sumby", - "lodash.clamp", - "lodash.inrange", - "lodash.random", - "lodash.assign", - "lodash.assignin", - "lodash.assigninwith", - "lodash.assignwith", - "lodash.at", - "lodash.create", - "lodash.defaults", - "lodash.defaultsdeep", - "lodash.findkey", - "lodash.findlastkey", - "lodash.forin", - "lodash.forinright", - "lodash.forown", - "lodash.forownright", - "lodash.functions", - "lodash.functionsin", - "lodash.get", - "lodash.has", - "lodash.hasin", - "lodash.invert", - "lodash.invertby", - "lodash.invoke", - "lodash.keys", - "lodash.keysin", - "lodash.mapkeys", - "lodash.mapvalues", - "lodash.merge", - "lodash.mergewith", - "lodash.omit", - "lodash.omitby", - "lodash.pick", - "lodash.pickby", - "lodash.result", - "lodash.set", - "lodash.setwith", - "lodash.topairs", - "lodash.topairsin", - "lodash.transform", - "lodash.unset", - "lodash.update", - "lodash.updatewith", - "lodash.values", - "lodash.valuesin", - "lodash.chain", - "lodash.tap", - "lodash.thru", - "lodash.camelcase", - "lodash.capitalize", - "lodash.deburr", - "lodash.endswith", - "lodash.escape", - "lodash.escaperegexp", - "lodash.kebabcase", - "lodash.lowercase", - "lodash.lowerfirst", - "lodash.pad", - "lodash.padend", - "lodash.padstart", - "lodash.parseint", - "lodash.repeat", - "lodash.replace", - "lodash.snakecase", - "lodash.split", - "lodash.startcase", - "lodash.startswith", - "lodash.template", - "lodash.tolower", - "lodash.toupper", - "lodash.trim", - "lodash.trimend", - "lodash.trimstart", - "lodash.truncate", - "lodash.unescape", - "lodash.uppercase", - "lodash.upperfirst", - "lodash.words", - "lodash.attempt", - "lodash.bindall", - "lodash.cond", - "lodash.conforms", - "lodash.constant", - "lodash.defaultto", - "lodash.flow", - "lodash.flowright", - "lodash.identity", - "lodash.iteratee", - "lodash.matches", - "lodash.matchesproperty", - "lodash.method", - "lodash.methodof", - "lodash.mixin", - "lodash.noconflict", - "lodash.noop", - "lodash.ntharg", - "lodash.over", - "lodash.overevery", - "lodash.oversome", - "lodash.property", - "lodash.propertyof", - "lodash.range", - "lodash.rangeright", - "lodash.runincontext", - "lodash.stubarray", - "lodash.stubfalse", - "lodash.stubobject", - "lodash.stubstring", - "lodash.stubtrue", - "lodash.times", - "lodash.topath", - "lodash.uniqueid", - ], - }, - { - configName: "mdx", - dependencies: ["eslint-plugin-mdx"], - oneOfDependency: ["nextra", "docz", "@docusaurus/core", "gatsby-plugin-mdx"], - resolve: ["@mdx-js/mdx", "remark-mdx", "@mdx-js/loader", "@mdx-js/rollup", "@mdx-js/react"], - }, - { - configName: "no-unsanitized", - dependencies: ["eslint-plugin-no-unsanitized"], - }, - { - configName: "react", - dependencies: ["react", "react-dom", "eslint-plugin-react"], - }, - { - configName: "validate-jsx-nesting", - dependencies: ["eslint-plugin-validate-jsx-nesting"], - oneOfDependency: ["react", "react-dom", "preact", "preact/compat"], - }, - { - configName: "ssr-friendly", - dependencies: ["eslint-plugin-ssr-friendly"], - oneOfDependency: ["react", "react-dom", "preact", "preact/compat"], - }, - { - configName: "react-redux", - dependencies: ["eslint-plugin-react-redux"], - oneOfDependency: ["@reduxjs/toolkit", "redux"], - }, - { - configName: "jsx-a11y", - dependencies: ["eslint-plugin-jsx-a11y"], - oneOfDependency: ["react", "react-dom", "preact", "preact/compat"], - }, - { - configName: "react-hooks", - dependencies: ["eslint-plugin-react-hooks"], - oneOfDependency: ["react", "react-dom", "preact", "preact/compat"], - }, - { - configName: "react-usememo", - dependencies: ["@arthurgeron/eslint-plugin-react-usememo"], - oneOfDependency: ["react", "react-dom", "preact", "preact/compat"], - }, - { - configName: "you-dont-need-momentjs", - dependencies: ["moment", "eslint-plugin-you-dont-need-momentjs"], - }, - { - configName: "you-dont-need-momentjs", - dependencies: ["moment-timezone", "eslint-plugin-you-dont-need-momentjs"], - }, - { - configName: "tailwindcss", - dependencies: ["eslint-plugin-tailwindcss"], - oneOfDependency: ["tailwindcss", "@tailwindcss/typography", "@tailwindcss/forms", "@tailwindcss/aspect-ratio", "@tailwindcss/line-clamp"], - }, - { - configName: "cypress", - dependencies: ["cypress", "eslint-plugin-cypress"], - }, - { - configName: "jest", - dependencies: ["jest", "eslint-plugin-jest"], - }, - { - configName: "jest-dom", - dependencies: ["jest", "@testing-library/jest-dom", "eslint-plugin-jest-dom"], - }, - { - configName: "jest-async", - dependencies: ["jest", "eslint-plugin-jest-async"], - }, - { - configName: "jest-formatting", - dependencies: ["jest", "eslint-plugin-jest-formatting"], - }, - { - configName: "tailwindcss", - dependencies: ["tailwindcss"], - }, - { - configName: "testing-library-dom", - dependencies: ["@testing-library/dom", "eslint-plugin-testing-library"], - }, - { - configName: "testing-library-react", - dependencies: ["react", "@testing-library/react", "eslint-plugin-testing-library"], - }, - { - configName: "typescript", - dependencies: ["typescript"], - files: ["tsconfig.json", "tsconfig.eslint.json"], - }, - { - configName: "deprecation", - dependencies: ["eslint-plugin-deprecation", "typescript"], - files: ["tsconfig.json", "tsconfig.eslint.json"], - }, - { - configName: "no-only-tests", - dependencies: [], - oneOfDependency: ["jest", "mocha", "jasmine", "tape", "ava", "qunit", "cypress"], - }, - { - configName: "etc", - dependencies: ["typescript", "eslint-plugin-etc"], - }, - { - configName: "vitest", - dependencies: ["vitest", "eslint-plugin-vitest"], - }, - { - configName: "vitest", - dependencies: ["vitest", "eslint-plugin-vitest", "eslint-plugin-vitest-globals"], - }, - { - configName: "zod", - dependencies: ["zod", "eslint-plugin-zod"], - }, - { - configName: "ava", - dependencies: ["ava", "eslint-plugin-ava"], - }, - { - configName: "editorconfig", - dependencies: ["eslint-plugin-editorconfig"], - files: [".editorconfig"], - }, - { - configName: "storybook", - dependencies: ["storybook", "eslint-plugin-storybook"], - }, - { - configName: "playwright", - dependencies: ["playwright", "eslint-plugin-playwright"], - }, - { - configName: "tanstack-query", - dependencies: ["@tanstack/react-query", "@tanstack/eslint-plugin-query"], - }, -]; - -const loadedPlugins: string[] = [...internalPluginConfig]; -const possiblePlugins: Record> = {}; - -if (loadedPlugins.length === internalPluginConfig.length) { - // eslint-disable-next-line sonarjs/cognitive-complexity - pluginConfig.forEach((plugin) => { - const { configName, dependencies } = plugin; - - // eslint-disable-next-line security/detect-object-injection - if ((anolilabEslintConfig as unknown as Record>)["plugin"]?.[configName] !== false) { - const foundDependencies = []; - - dependencies.forEach((dependency) => { - if (hasDependency(dependency) || hasDevDependency(dependency)) { - foundDependencies.push(dependency); - } - }); - - // eslint-disable-next-line security/detect-object-injection - possiblePlugins[configName] = {}; - - if (foundDependencies.length === 0) { - if (plugin.oneOfDependency !== undefined) { - let foundOneOfDependency = false; - - plugin.oneOfDependency.forEach((dependency) => { - if (!foundOneOfDependency && (hasDependency(dependency) || hasDevDependency(dependency))) { - foundOneOfDependency = true; - - // eslint-disable-next-line security/detect-object-injection - (possiblePlugins[configName] as Record)[dependency] = true; - } - }); - } - - if (plugin.resolve !== undefined) { - plugin.resolve.forEach((rdependency) => { - if (resolvePackage(rdependency) !== undefined) { - // eslint-disable-next-line security/detect-object-injection - (possiblePlugins[configName] as Record)[rdependency] = true; - } - }); - } - - if (plugin.files !== undefined) { - plugin.files.forEach((file) => { - if (hasFile(file)) { - // eslint-disable-next-line security/detect-object-injection - (possiblePlugins[configName] as Record)[file] = true; - } - }); - } - } - - if (foundDependencies.length === dependencies.length) { - // eslint-disable-next-line security/detect-object-injection,@typescript-eslint/no-dynamic-delete - delete possiblePlugins[configName]; - - loadedPlugins.push(configName); - } else { - dependencies.forEach((dependency) => { - // eslint-disable-next-line security/detect-object-injection - (possiblePlugins[configName] as Record)[dependency] = hasDependency(dependency) || hasDevDependency(dependency); - }); - } - } - }); -} - -export const rules = baseConfig; -export const pluginRules = loadedPlugins; - -export const possiblePluginRules = possiblePlugins; diff --git a/packages/eslint-config/src/config/best-practices.ts b/packages/eslint-config/src/config/best-practices.ts index 6a5691c95..baa907abf 100644 --- a/packages/eslint-config/src/config/best-practices.ts +++ b/packages/eslint-config/src/config/best-practices.ts @@ -1,10 +1,57 @@ import type { Linter } from "eslint"; -import { createConfigs } from "../utils/create-config"; - -const config: Linter.Config = createConfigs([ - { - config: { +import type { OptionsFiles } from "../types"; +import { createConfig, getFilesGlobs } from "../utils/create-config"; + +export const bestPracticesRules: Partial = { + // Disable the "dot-notation" rule, as it can report incorrect errors on TypeScript code + "dot-notation": "off", + + // Require explicit return and argument types on exported functions' and classes' public class methods. + // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-empty-function.md + "no-empty-function": "off", + + // Disallow using to delete operator on computed key expressions. + // Disable the "no-implied-eval" and "no-new-func" rule, as it can report incorrect errors on TypeScript code + "no-implied-eval": "off", + + // Disallow extra non-null assertions. + // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-loop-func.md + "no-loop-func": "off", + + // Disallow void type outside of generic or return types. + // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-magic-numbers.md + "no-magic-numbers": "off", + + "no-new-func": "off", + + // Disallow non-null assertions after an optional chain expression. + // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-redeclare.md + "no-redeclare": "off", + // Disallow non-null assertions using the ! postfix operator. + // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/return-await.md + "no-return-await": "off", + + // Disallow type assertions that do not change the type of expression. + // Disable the "no-throw-literal" rule, as it can report incorrect errors on TypeScript code + "no-throw-literal": "off", + + // Disallow calling a value with type any. + // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unused-expressions.md + "no-unused-expressions": "off", + + // Disallow empty exports that don't change anything in a module file. + // Breaks @typescript-eslint/parser + strict: "off", +}; + +export default createConfig("all", async (config, oFiles) => { + const { files = oFiles } = config; + + return [ + { + files, + name: "anolilab/best-practices/rules", rules: { // enforces getter/setter pairs in objects "accessor-pairs": "off", @@ -323,7 +370,7 @@ const config: Linter.Config = createConfigs([ property: "parseInt", }, { - message: "Use `Object.getPrototypeOf` instead.", + message: "Use `Object.getPrototypeOf` or `Object.setPrototypeOf` instead.", property: "__proto__", }, { @@ -404,7 +451,7 @@ const config: Linter.Config = createConfigs([ // https://eslint.org/docs/rules/no-void "no-void": "error", - // disallow usage of configurable warning terms in comments: e.g. todo + // disallow usage of configurable warning terms in comments: e.g. "todo" "no-warning-comments": [ "off", { @@ -450,56 +497,10 @@ const config: Linter.Config = createConfigs([ yoda: "error", }, }, - type: "all", - }, - // The following rules are enabled in config, but are already checked (more thoroughly) by the TypeScript compiler - // Some rules also fail in TypeScript files, for example: https://github.com/typescript-eslint/typescript-eslint/issues/662#issuecomment-507081586 - { - config: { - rules: { - // Disallow non-null assertions using the ! postfix operator. - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/return-await.md - "no-return-await": "off", - - // Disallow calling a value with type any. - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unused-expressions.md - "no-unused-expressions": "off", - - // Disallow type assertions that do not change the type of expression. - // Disable the "no-throw-literal" rule, as it can report incorrect errors on TypeScript code - "no-throw-literal": "off", - - // Disallow empty exports that don't change anything in a module file. - // Breaks @typescript-eslint/parser - strict: "off", - - // Disable the "dot-notation" rule, as it can report incorrect errors on TypeScript code - "dot-notation": "off", - - // Require explicit return and argument types on exported functions' and classes' public class methods. - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-empty-function.md - "no-empty-function": "off", - - // Disallow using to delete operator on computed key expressions. - // Disable the "no-implied-eval" and "no-new-func" rule, as it can report incorrect errors on TypeScript code - "no-implied-eval": "off", - "no-new-func": "off", - - // Disallow extra non-null assertions. - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-loop-func.md - "no-loop-func": "off", - - // Disallow void type outside of generic or return types. - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-magic-numbers.md - "no-magic-numbers": "off", - - // Disallow non-null assertions after an optional chain expression. - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-redeclare.md - "no-redeclare": "off", - }, + { + files: getFilesGlobs("ts"), + name: "anolilab/best-practices/ts-rules", + rules: bestPracticesRules, }, - type: "typescript", - }, -]); - -export default config; + ]; +}); diff --git a/packages/eslint-config/src/config/errors.ts b/packages/eslint-config/src/config/errors.ts index a7e8dc28e..620e30195 100644 --- a/packages/eslint-config/src/config/errors.ts +++ b/packages/eslint-config/src/config/errors.ts @@ -1,199 +1,186 @@ import type { Linter } from "eslint"; -import { createConfigs } from "../utils/create-config"; +import type { OptionsFiles } from "../types"; +import { createConfig, getFilesGlobs } from "../utils/create-config"; -const config: Linter.Config = createConfigs([ - { - config: { - rules: { - // Enforce “for” loop update clause moving the counter in the right direction - // https://eslint.org/docs/rules/for-direction - "for-direction": "error", +export const errorsRules: Partial = { + // Enforce “for” loop update clause moving the counter in the right direction + // https://eslint.org/docs/rules/for-direction + "for-direction": "error", - // Enforces that a return statement is present in property getters - // https://eslint.org/docs/rules/getter-return - "getter-return": ["error", { allowImplicit: true }], + // Enforces that a return statement is present in property getters + // https://eslint.org/docs/rules/getter-return + "getter-return": ["error", { allowImplicit: true }], - // disallow using an async function as a Promise executor - // https://eslint.org/docs/rules/no-async-promise-executor - "no-async-promise-executor": "error", + // disallow using an async function as a Promise executor + // https://eslint.org/docs/rules/no-async-promise-executor + "no-async-promise-executor": "error", - // Disallow await inside of loops - // https://eslint.org/docs/rules/no-await-in-loop - "no-await-in-loop": "error", + // Disallow await inside of loops + // https://eslint.org/docs/rules/no-await-in-loop + "no-await-in-loop": "error", - // Disallow comparisons to negative zero - // https://eslint.org/docs/rules/no-compare-neg-zero - "no-compare-neg-zero": "error", + // Disallow comparisons to negative zero + // https://eslint.org/docs/rules/no-compare-neg-zero + "no-compare-neg-zero": "error", - // disallow assignment in conditional expressions - "no-cond-assign": ["error", "always"], + // disallow assignment in conditional expressions + "no-cond-assign": ["error", "always"], - // disallow use of console - "no-console": "warn", + // disallow use of console + "no-console": "warn", - // disallow use of constant expressions in conditions - "no-constant-condition": "warn", + // disallow use of constant expressions in conditions + "no-constant-condition": "warn", - // Disabled because of eslint-plugin-regexp - // disallow control characters in regular expressions - "no-control-regex": "off", + // Disabled because of eslint-plugin-regexp + // disallow control characters in regular expressions + "no-control-regex": "off", - // disallow use of debugger - "no-debugger": "error", + // disallow use of debugger + "no-debugger": "error", - // disallow duplicate arguments in functions - "no-dupe-args": "error", + // disallow duplicate arguments in functions + "no-dupe-args": "error", - // Disallow duplicate conditions in if-else-if chains - // https://eslint.org/docs/rules/no-dupe-else-if - "no-dupe-else-if": "error", + // Disallow duplicate conditions in if-else-if chains + // https://eslint.org/docs/rules/no-dupe-else-if + "no-dupe-else-if": "error", - // disallow duplicate keys when creating object literals - "no-dupe-keys": "error", + // disallow duplicate keys when creating object literals + "no-dupe-keys": "error", - // disallow a duplicate case label. - "no-duplicate-case": "error", + // disallow a duplicate case label. + "no-duplicate-case": "error", - // disallow empty statements - "no-empty": "error", + // disallow empty statements + "no-empty": "error", - // disallow the use of empty character classes in regular expressions - "no-empty-character-class": "error", + // disallow the use of empty character classes in regular expressions + "no-empty-character-class": "error", - // disallow assigning to the exception in a catch block - "no-ex-assign": "error", + // disallow assigning to the exception in a catch block + "no-ex-assign": "error", - // disallow double-negation boolean casts in a boolean context - // https://eslint.org/docs/rules/no-extra-boolean-cast - "no-extra-boolean-cast": "error", + // disallow double-negation boolean casts in a boolean context + // https://eslint.org/docs/rules/no-extra-boolean-cast + "no-extra-boolean-cast": "error", - // disallow unnecessary parentheses - // https://eslint.org/docs/rules/no-extra-parens - "no-extra-parens": [ - "error", - "all", - { - conditionalAssign: true, - enforceForArrowConditionals: false, - ignoreJSX: "all", // delegate to eslint-plugin-react - nestedBinaryExpressions: false, - returnAssign: false, - }, - ], + // disallow overwriting functions written as function declarations + "no-func-assign": "error", - // Disallow non-null assertion in locations that may be confusing. - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-extra-semi.md - "no-extra-semi": "error", + // https://eslint.org/docs/rules/no-import-assign + "no-import-assign": "error", - // disallow overwriting functions written as function declarations - "no-func-assign": "error", + // disallow function or variable declarations in nested blocks + "no-inner-declarations": "error", - // https://eslint.org/docs/rules/no-import-assign - "no-import-assign": "error", + // Disabled because of eslint-plugin-regexp + // disallow invalid regular expression strings in the RegExp constructor + "no-invalid-regexp": "off", - // disallow function or variable declarations in nested blocks - "no-inner-declarations": "error", + // disallow irregular whitespace outside of strings and comments + "no-irregular-whitespace": "error", - // Disabled because of eslint-plugin-regexp - // disallow invalid regular expression strings in the RegExp constructor - "no-invalid-regexp": "off", + // Disallow Number Literals That Lose Precision + // https://eslint.org/docs/rules/no-loss-of-precision + "no-loss-of-precision": "error", - // disallow irregular whitespace outside of strings and comments - "no-irregular-whitespace": "error", + // Disallow characters which are made with multiple code points in character class syntax + // https://eslint.org/docs/rules/no-misleading-character-class + "no-misleading-character-class": "error", - // Disallow Number Literals That Lose Precision - // https://eslint.org/docs/rules/no-loss-of-precision - "no-loss-of-precision": "error", + // deprecated in favor of no-unsafe-negation + "no-negated-in-lhs": "off", - // Disallow characters which are made with multiple code points in character class syntax - // https://eslint.org/docs/rules/no-misleading-character-class - "no-misleading-character-class": "error", + // Disallow new operators with global non-constructor functions + // https://eslint.org/docs/latest/rules/no-new-native-nonconstructor + "no-new-native-nonconstructor": "error", - // deprecated in favor of no-unsafe-negation - "no-negated-in-lhs": "off", + // Disallow returning values from Promise executor functions + // disallow the use of object properties of the global object (Math and JSON) as functions + "no-obj-calls": "error", - // Disallow returning values from Promise executor functions - // disallow the use of object properties of the global object (Math and JSON) as functions - "no-obj-calls": "error", + // disallow use of Object.prototypes builtins directly + // https://eslint.org/docs/rules/no-promise-executor-return + "no-promise-executor-return": "error", - // Disallow new operators with global non-constructor functions - // https://eslint.org/docs/latest/rules/no-new-native-nonconstructor - // TODO: semver-major, enable - "no-new-native-nonconstructor": "off", + // https://eslint.org/docs/rules/no-prototype-builtins + "no-prototype-builtins": "error", - // disallow use of Object.prototypes builtins directly - // https://eslint.org/docs/rules/no-promise-executor-return - "no-promise-executor-return": "error", + // Disabled because of eslint-plugin-regexp + // disallow multiple spaces in a regular expression literal + "no-regex-spaces": "off", - // https://eslint.org/docs/rules/no-prototype-builtins - "no-prototype-builtins": "error", + // https://eslint.org/docs/rules/no-setter-return + "no-setter-return": "error", - // Disabled because of eslint-plugin-regexp - // disallow multiple spaces in a regular expression literal - "no-regex-spaces": "off", + // Disallow template literal placeholder syntax in regular strings + // disallow sparse arrays + "no-sparse-arrays": "error", - // https://eslint.org/docs/rules/no-setter-return - "no-setter-return": "error", + // Avoid code that looks like two expressions but is actually one + // https://eslint.org/docs/rules/no-template-curly-in-string + "no-template-curly-in-string": "error", - // Disallow template literal placeholder syntax in regular strings - // disallow sparse arrays - "no-sparse-arrays": "error", + // https://eslint.org/docs/rules/no-unexpected-multiline + "no-unexpected-multiline": "error", - // Avoid code that looks like two expressions but is actually one - // https://eslint.org/docs/rules/no-template-curly-in-string - "no-template-curly-in-string": "error", + // Disallow loops with a body that allows only one iteration + // disallow unreachable statements after a return, throw, continue, or break statement + "no-unreachable": "error", - // https://eslint.org/docs/rules/no-unexpected-multiline - "no-unexpected-multiline": "error", + // disallow return/throw/break/continue inside finally blocks + // https://eslint.org/docs/rules/no-unreachable-loop + "no-unreachable-loop": "off", // error with typescript - // Disallow loops with a body that allows only one iteration - // disallow unreachable statements after a return, throw, continue, or break statement - "no-unreachable": "error", + // disallow negating the left operand of relational operators + // https://eslint.org/docs/rules/no-unsafe-finally + "no-unsafe-finally": "error", - // disallow return/throw/break/continue inside finally blocks - // https://eslint.org/docs/rules/no-unreachable-loop - "no-unreachable-loop": "off", // error with typescript + // disallow use of optional chaining in contexts where the undefined value is not allowed + // https://eslint.org/docs/rules/no-unsafe-negation + "no-unsafe-negation": "error", - // disallow negating the left operand of relational operators - // https://eslint.org/docs/rules/no-unsafe-finally - "no-unsafe-finally": "error", - - // disallow use of optional chaining in contexts where the undefined value is not allowed - // https://eslint.org/docs/rules/no-unsafe-negation - "no-unsafe-negation": "error", - - // Disallow useless backreferences in regular expressions - // https://eslint.org/docs/rules/no-unsafe-optional-chaining - "no-unsafe-optional-chaining": ["error", { disallowArithmeticOperators: true }], - - // disallow negation of the left operand of an in expression - // https://eslint.org/docs/rules/no-useless-backreference - "no-useless-backreference": "error", - - // Disallow assignments that can lead to race conditions due to usage of await or yield - // https://eslint.org/docs/rules/require-atomic-updates - // note: not enabled because it is very buggy - "require-atomic-updates": "off", + // Disallow useless backreferences in regular expressions + // https://eslint.org/docs/rules/no-unsafe-optional-chaining + "no-unsafe-optional-chaining": ["error", { disallowArithmeticOperators: true }], - // disallow comparisons with the value NaN - "use-isnan": "error", + // disallow negation of the left operand of an in expression + // https://eslint.org/docs/rules/no-useless-backreference + "no-useless-backreference": "error", - // ensure JSDoc comments are valid - // https://eslint.org/docs/rules/valid-jsdoc - "valid-jsdoc": "off", + // Disallow assignments that can lead to race conditions due to usage of await or yield + // https://eslint.org/docs/rules/require-atomic-updates + // note: not enabled because it is very buggy + "require-atomic-updates": "off", - // ensure that the results of typeof are compared against a valid string - // https://eslint.org/docs/rules/valid-typeof - "valid-typeof": ["error", { requireStringLiterals: true }], - }, + // disallow comparisons with the value NaN + "use-isnan": "error", + + // ensure JSDoc comments are valid + // https://eslint.org/docs/rules/valid-jsdoc + "valid-jsdoc": "off", + + // ensure that the results of typeof are compared against a valid string + // https://eslint.org/docs/rules/valid-typeof + "valid-typeof": ["error", { requireStringLiterals: true }], +}; + +export default createConfig("all", async (config, oFiles) => { + const { files = oFiles } = config; + + return [ + { + files, + name: "anolilab/errors/rules", + rules: errorsRules, }, - type: "all", - }, - // The following rules are enabled in config, but are already checked (more thoroughly) by the TypeScript compiler - // Some rules also fail in TypeScript files, for example: https://github.com/typescript-eslint/typescript-eslint/issues/662#issuecomment-507081586 - { - config: { + // The following rules are enabled in config, but are already checked (more thoroughly) by the TypeScript compiler + // Some rules also fail in TypeScript files, for example: https://github.com/typescript-eslint/typescript-eslint/issues/662#issuecomment-507081586 + { + files: getFilesGlobs("ts"), + name: "anolilab/errors/ts-rules", rules: { "getter-return": "off", @@ -207,6 +194,8 @@ const config: Linter.Config = createConfigs([ // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-extra-parens.md "no-extra-parens": "off", + "no-extra-semi": "off", + // Disallow duplicate enum member values. "no-func-assign": "off", @@ -224,12 +213,7 @@ const config: Linter.Config = createConfigs([ "space-infix-ops": "off", "valid-typeof": "off", - - "no-extra-semi": "off", }, }, - type: "typescript", - }, -]); - -export default config; + ]; +}); diff --git a/packages/eslint-config/src/config/es6.ts b/packages/eslint-config/src/config/es6.ts index fffbb2896..be0fae47a 100644 --- a/packages/eslint-config/src/config/es6.ts +++ b/packages/eslint-config/src/config/es6.ts @@ -1,388 +1,390 @@ import type { Linter } from "eslint"; -import { createConfigs } from "../utils/create-config"; - -const config: Linter.Config = createConfigs([ - { - config: { - env: { - es6: true, - }, - parserOptions: { - ecmaFeatures: { - generators: false, - objectLiteralDuplicateProperties: false, - }, - ecmaVersion: 6, - sourceType: "module", +import type { OptionsFiles, OptionsIsInEditor } from "../types"; +import { createConfig, getFilesGlobs } from "../utils/create-config"; + +export const es6Rules: (isInEditor: boolean) => Partial = (isInEditor: boolean) => { + return { + // enforces no braces where they can be omitted + // https://eslint.org/docs/rules/arrow-body-style + "arrow-body-style": [ + "error", + "as-needed", + { + requireReturnForObjectLiteral: true, }, - rules: { - // enforces no braces where they can be omitted - // https://eslint.org/docs/rules/arrow-body-style - "arrow-body-style": [ - "error", - "as-needed", - { - requireReturnForObjectLiteral: true, - }, - ], + ], - // require parens in arrow function arguments - // https://eslint.org/docs/rules/arrow-parens - "arrow-parens": ["error", "always"], + // require parens in arrow function arguments + // https://eslint.org/docs/rules/arrow-parens + "arrow-parens": ["error", "always"], - // require space before/after arrow function's arrow - // https://eslint.org/docs/rules/arrow-spacing - "arrow-spacing": ["error", { after: true, before: true }], + // require space before/after arrow function's arrow + // https://eslint.org/docs/rules/arrow-spacing + "arrow-spacing": ["error", { after: true, before: true }], - // verify super() callings in constructors - "constructor-super": "error", + // verify super() callings in constructors + "constructor-super": "error", - // enforce the spacing around the * in generator functions - // https://eslint.org/docs/rules/generator-star-spacing - "generator-star-spacing": ["error", { after: true, before: false }], + // enforce the spacing around the * in generator functions + // https://eslint.org/docs/rules/generator-star-spacing + "generator-star-spacing": ["error", { after: true, before: false }], - // disallow modifying variables of class declarations - // https://eslint.org/docs/rules/no-class-assign - "no-class-assign": "error", + // disallow modifying variables of class declarations + // https://eslint.org/docs/rules/no-class-assign + "no-class-assign": "error", - // disallow arrow functions where they could be confused with comparisons - // https://eslint.org/docs/rules/no-confusing-arrow - "no-confusing-arrow": [ - "error", + // disallow arrow functions where they could be confused with comparisons + // https://eslint.org/docs/rules/no-confusing-arrow + "no-confusing-arrow": [ + "error", + { + allowParens: true, + }, + ], + + // disallow modifying variables that are declared using const + "no-const-assign": "error", + + // disallow duplicate class members + // https://eslint.org/docs/rules/no-dupe-class-members + "no-dupe-class-members": "error", + + // disallow importing from the same path more than once + // https://eslint.org/docs/rules/no-duplicate-imports + // replaced by https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-duplicates.md + "no-duplicate-imports": "off", + + // disallow symbol constructor + // https://eslint.org/docs/rules/no-new-symbol + "no-new-symbol": "error", + + // Disallow specified names in exports + // https://eslint.org/docs/rules/no-restricted-exports + "no-restricted-exports": [ + "error", + { + // default export while still blocking other "default" exports. + // The 'default' entry in restrictedNamedExports must also be removed. + // See https://github.com/airbnb/javascript/issues/2500 and https://github.com/eslint/eslint/pull/16785 + restrictDefaultExports: { + defaultFrom: false, // permits `export { default } from 'foo';` declarations + direct: false, // permits `export default` declarations + named: true, // restricts `export { foo as default };` declarations + namedFrom: false, // permits `export { foo as default } from 'foo';` declarations + namespaceFrom: true, // restricts `export * as default from 'foo';` declarations + }, + // this will cause tons of confusion when your module is dynamically `import()`ed + restrictedNamedExports: ["then"], + }, + ], + + // disallow specific imports + // https://eslint.org/docs/rules/no-restricted-imports + "no-restricted-imports": [ + "error", + { + paths: [ { - allowParens: true, + message: "Lodash modularised (and lodash < 4.17.11) have CVE vulnerabilities. Please use tree-shakeable imports like lodash/xxx instead", + name: "lodash.isequal", }, - ], - - // disallow modifying variables that are declared using const - "no-const-assign": "error", - - // disallow duplicate class members - // https://eslint.org/docs/rules/no-dupe-class-members - "no-dupe-class-members": "error", - - // disallow importing from the same path more than once - // https://eslint.org/docs/rules/no-duplicate-imports - // replaced by https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-duplicates.md - "no-duplicate-imports": "off", - - // disallow symbol constructor - // https://eslint.org/docs/rules/no-new-symbol - "no-new-symbol": "error", - - // Disallow specified names in exports - // https://eslint.org/docs/rules/no-restricted-exports - "no-restricted-exports": [ - "error", - { - // default export while still blocking other "default" exports. - // The 'default' entry in restrictedNamedExports must also be removed. - // See https://github.com/airbnb/javascript/issues/2500 and https://github.com/eslint/eslint/pull/16785 - restrictDefaultExports: { - defaultFrom: false, // permits `export { default } from 'foo';` declarations - direct: false, // permits `export default` declarations - named: true, // restricts `export { foo as default };` declarations - namedFrom: false, // permits `export { foo as default } from 'foo';` declarations - namespaceFrom: true, // restricts `export * as default from 'foo';` declarations - }, - // this will cause tons of confusion when your module is dynamically `import()`ed - restrictedNamedExports: ["then"], + { + message: "Lodash modularised (and lodash < 4.17.11) have CVE vulnerabilities. Please use tree-shakeable imports like lodash/xxx instead", + name: "lodash.uniqueId", }, - ], - - // disallow specific imports - // https://eslint.org/docs/rules/no-restricted-imports - "no-restricted-imports": [ - "error", - { - paths: [ - { - message: - "Lodash modularised (and lodash < 4.17.11) have CVE vulnerabilities. Please use tree-shakeable imports like lodash/xxx instead", - name: "lodash.isequal", - }, - { - message: - "Lodash modularised (and lodash < 4.17.11) have CVE vulnerabilities. Please use tree-shakeable imports like lodash/xxx instead", - name: "lodash.uniqueId", - }, - { - message: - "Lodash modularised (and lodash < 4.17.11) have CVE vulnerabilities. Please use tree-shakeable imports like lodash/xxx instead", - name: "lodash.mergewith", - }, - { - message: - "Lodash modularised (and lodash < 4.17.11) have CVE vulnerabilities. Please use tree-shakeable imports like lodash/xxx instead", - name: "lodash.pick", - }, - { - name: "error", - }, - { - name: "domain", - }, - { - name: "freelist", - }, - { - name: "smalloc", - }, - { - name: "punycode", - }, - { - name: "sys", - }, - { - message: 'Is legacy, npm version got deprecated, migrate to URLSearchParams as recommended or try "qs" as a package', - name: "querystring", - }, - { - message: "Please use one of the following instead: chalk, kleur, ansi-colors, @colors/colors", - name: "colors", - }, - { - message: "node v10.12 mkdir supports recursive option", - name: "mkdirp", - }, - { - message: 'Please use "@faker-js/faker" as a replacement', - name: "faker", - }, - { - message: "Please use Object.assign or spread { ...obj }", - name: "xtend", - }, - { - message: "Please use Object.assign or spread { ...obj }", - name: "object-assign", - }, - { - message: "Please use Object.assign or spread { ...obj }", - name: "extend-shallow", - }, - { - message: "node supports recursive option now", - name: "rimraf", - }, - { - message: 'just use "".padStart() and "".padEnd()', - name: "pad-left", - }, - { - message: 'just use "".padStart() and "".padEnd()', - name: "pad-right", - }, - { - message: 'just use "".padStart() and "".padEnd()', - name: "left-pad", - }, - { - message: 'just use "".padStart() and "".padEnd()', - name: "right-pad", - }, - { - message: 'just use "".padStart() and "".padEnd()', - name: "pad", - }, - { - name: "safe-buffer", - }, - { - name: "safer-buffer", - }, - { - message: "just use [].flat() or some other polyfill", - name: "array-flatten", - }, - { - message: "Been deprecated", - name: "request", - }, - { - message: "use async/await instead", - name: "co", - }, - { - message: "Please use TextDecoder instead", - name: "windows-1252", - }, - { - message: "Please use TextDecoder instead", - name: "string_decoder", - }, - { - message: "Please use array.prototype.flatMap instead", - name: "concat-map", - }, - { - name: "buffer-alloc", - }, - ], - // catch-all for any lodash modularized. - // The CVE is listed against the entire family for lodash < 4.17.11 - patterns: ["lodash.*"], + { + message: "Lodash modularised (and lodash < 4.17.11) have CVE vulnerabilities. Please use tree-shakeable imports like lodash/xxx instead", + name: "lodash.mergewith", }, - ], - - // disallow to use this/super before super() calling in constructors. - // https://eslint.org/docs/rules/no-this-before-super - "no-this-before-super": "error", - - // disallow useless computed property keys - // https://eslint.org/docs/rules/no-useless-computed-key - "no-useless-computed-key": "error", - - // disallow unnecessary constructor - // https://eslint.org/docs/rules/no-useless-constructor - "no-useless-constructor": "error", - - // disallow renaming import, export, and destructured assignments to the same name - // https://eslint.org/docs/rules/no-useless-rename - "no-useless-rename": [ - "error", { - ignoreDestructuring: false, - ignoreExport: false, - ignoreImport: false, + message: "Lodash modularised (and lodash < 4.17.11) have CVE vulnerabilities. Please use tree-shakeable imports like lodash/xxx instead", + name: "lodash.pick", }, - ], - - // require let or const instead of var - "no-var": "error", - - // require method and property shorthand syntax for object literals - // https://eslint.org/docs/rules/object-shorthand - "object-shorthand": [ - "error", - "always", { - avoidQuotes: true, - ignoreConstructors: false, + name: "error", }, - ], - - // suggest using arrow functions as callbacks - "prefer-arrow-callback": [ - "error", { - allowNamedFunctions: false, - allowUnboundThis: true, + name: "domain", }, - ], - - // suggest using of const declaration for variables that are never modified after declared - "prefer-const": [ - "error", { - destructuring: "any", - ignoreReadBeforeAssign: true, + name: "freelist", }, - ], - - // Prefer destructuring from arrays and objects - // https://eslint.org/docs/rules/prefer-destructuring - "prefer-destructuring": [ - "error", { - AssignmentExpression: { - array: true, - object: false, - }, - VariableDeclarator: { - array: false, - object: true, - }, + name: "smalloc", }, { - enforceForRenamedProperties: false, + name: "punycode", + }, + { + name: "sys", + }, + { + message: "Is legacy, npm version got deprecated, migrate to URLSearchParams as recommended or try \"qs\" as a package", + name: "querystring", + }, + { + message: "Please use one of the following instead: chalk, kleur, ansi-colors, @colors/colors", + name: "colors", + }, + { + message: "node v10.12 mkdir supports recursive option", + name: "mkdirp", + }, + { + message: "Please use \"@faker-js/faker\" as a replacement", + name: "faker", }, - ], - - // disallow parseInt() in favor of binary, octal, and hexadecimal literals - // https://eslint.org/docs/rules/prefer-numeric-literals - "prefer-numeric-literals": "error", - - // suggest using Reflect methods where applicable - // https://eslint.org/docs/rules/prefer-reflect - "prefer-reflect": "off", - - // use rest parameters instead of arguments - // https://eslint.org/docs/rules/prefer-rest-params - "prefer-rest-params": "error", - - // suggest using the spread operator instead of .apply() - // https://eslint.org/docs/rules/prefer-spread - "prefer-spread": "error", - - // suggest using template literals instead of string concatenation - // https://eslint.org/docs/rules/prefer-template - "prefer-template": "error", - - // disallow generator functions that do not have yield - // https://eslint.org/docs/rules/require-yield - "require-yield": "error", - - // enforce spacing between object rest-spread - // https://eslint.org/docs/rules/rest-spread-spacing - "rest-spread-spacing": ["error", "never"], - - // import sorting - // https://eslint.org/docs/rules/sort-imports - "sort-imports": [ - "off", { - ignoreCase: false, - ignoreDeclarationSort: false, - ignoreMemberSort: false, - memberSyntaxSortOrder: ["none", "all", "multiple", "single"], + message: "Please use Object.assign or spread { ...obj }", + name: "xtend", + }, + { + message: "Please use Object.assign or spread { ...obj }", + name: "object-assign", + }, + { + message: "Please use Object.assign or spread { ...obj }", + name: "extend-shallow", + }, + { + message: "node supports recursive option now", + name: "rimraf", + }, + { + message: "just use \"\".padStart() and \"\".padEnd()", + name: "pad-left", + }, + { + message: "just use \"\".padStart() and \"\".padEnd()", + name: "pad-right", + }, + { + message: "just use \"\".padStart() and \"\".padEnd()", + name: "left-pad", + }, + { + message: "just use \"\".padStart() and \"\".padEnd()", + name: "right-pad", + }, + { + message: "just use \"\".padStart() and \"\".padEnd()", + name: "pad", + }, + { + name: "safe-buffer", + }, + { + name: "safer-buffer", + }, + { + message: "just use [].flat() or some other polyfill", + name: "array-flatten", + }, + { + message: "Been deprecated", + name: "request", + }, + { + message: "use async/await instead", + name: "co", + }, + { + message: "Please use TextDecoder instead", + name: "windows-1252", + }, + { + message: "Please use TextDecoder instead", + name: "string_decoder", + }, + { + message: "Please use array.prototype.flatMap instead", + name: "concat-map", + }, + { + name: "buffer-alloc", }, ], - - // require a Symbol description - // https://eslint.org/docs/rules/symbol-description - "symbol-description": "error", - - // enforce usage of spacing in template strings - // https://eslint.org/docs/rules/template-curly-spacing - "template-curly-spacing": "error", - - // enforce spacing around the * in yield* expressions - // https://eslint.org/docs/rules/yield-star-spacing - "yield-star-spacing": ["error", "after"], + // catch-all for any lodash modularized. + // The CVE is listed against the entire family for lodash < 4.17.11 + patterns: ["lodash.*"], + }, + ], + + // disallow to use this/super before super() calling in constructors. + // https://eslint.org/docs/rules/no-this-before-super + "no-this-before-super": "error", + + // disallow useless computed property keys + // https://eslint.org/docs/rules/no-useless-computed-key + "no-useless-computed-key": "error", + + // disallow unnecessary constructor + // https://eslint.org/docs/rules/no-useless-constructor + "no-useless-constructor": "error", + + // disallow renaming import, export, and destructured assignments to the same name + // https://eslint.org/docs/rules/no-useless-rename + "no-useless-rename": [ + "error", + { + ignoreDestructuring: false, + ignoreExport: false, + ignoreImport: false, + }, + ], + + // require let or const instead of var + "no-var": "error", + + // require method and property shorthand syntax for object literals + // https://eslint.org/docs/rules/object-shorthand + "object-shorthand": [ + "error", + "always", + { + avoidQuotes: true, + ignoreConstructors: false, + }, + ], + + // suggest using arrow functions as callbacks + "prefer-arrow-callback": [ + "error", + { + allowNamedFunctions: false, + allowUnboundThis: true, + }, + ], + + // suggest using of const declaration for variables that are never modified after declared + "prefer-const": isInEditor + ? "off" + : [ + "error", + { + destructuring: "any", + ignoreReadBeforeAssign: true, + }, + ], + + // Prefer destructuring from arrays and objects + // https://eslint.org/docs/rules/prefer-destructuring + "prefer-destructuring": [ + "error", + { + AssignmentExpression: { + array: true, + object: false, + }, + VariableDeclarator: { + array: false, + object: true, + }, + }, + { + enforceForRenamedProperties: false, + }, + ], + + // disallow parseInt() in favor of binary, octal, and hexadecimal literals + // https://eslint.org/docs/rules/prefer-numeric-literals + "prefer-numeric-literals": "error", + + // suggest using Reflect methods where applicable + // https://eslint.org/docs/rules/prefer-reflect + "prefer-reflect": "off", + + // use rest parameters instead of arguments + // https://eslint.org/docs/rules/prefer-rest-params + "prefer-rest-params": "error", + + // suggest using the spread operator instead of .apply() + // https://eslint.org/docs/rules/prefer-spread + "prefer-spread": "error", + + // suggest using template literals instead of string concatenation + // https://eslint.org/docs/rules/prefer-template + "prefer-template": "error", + + // disallow generator functions that do not have yield + // https://eslint.org/docs/rules/require-yield + "require-yield": "error", + + // enforce spacing between object rest-spread + // https://eslint.org/docs/rules/rest-spread-spacing + "rest-spread-spacing": ["error", "never"], + + // import sorting + // https://eslint.org/docs/rules/sort-imports + "sort-imports": [ + "off", + { + ignoreCase: false, + ignoreDeclarationSort: false, + ignoreMemberSort: false, + memberSyntaxSortOrder: ["none", "all", "multiple", "single"], + }, + ], + + // require a Symbol description + // https://eslint.org/docs/rules/symbol-description + "symbol-description": "error", + + // enforce usage of spacing in template strings + // https://eslint.org/docs/rules/template-curly-spacing + "template-curly-spacing": "error", + + // enforce spacing around the * in yield* expressions + // https://eslint.org/docs/rules/yield-star-spacing + "yield-star-spacing": ["error", "after"], + }; +}; + +export default createConfig("all", async (config, oFiles) => { + const { files = oFiles, isInEditor = false } = config; + + return [ + { + files, + languageOptions: { + parserOptions: { + ecmaFeatures: { + generators: false, + objectLiteralDuplicateProperties: false, + }, + ecmaVersion: 6, + sourceType: "module", + }, }, + name: "anolilab/es6/rules", + rules: es6Rules(isInEditor), }, - type: "all", - }, - // The following rules are enabled in config, but are already checked (more thoroughly) by the TypeScript compiler - // Some rules also fail in TypeScript files, for example: https://github.com/typescript-eslint/typescript-eslint/issues/662#issuecomment-507081586 - { - config: { + // The following rules are enabled in config, but are already checked (more thoroughly) by the TypeScript compiler + // Some rules also fail in TypeScript files, for example: https://github.com/typescript-eslint/typescript-eslint/issues/662#issuecomment-507081586 + { + files: getFilesGlobs("ts"), + name: "anolilab/es6/ts-rules", rules: { "constructor-super": "off", - // Disallow returning a value with type any from a function. - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-useless-constructor.md - "no-useless-constructor": "off", + // Enforce constituents of a type union/intersection to be sorted alphabetically. + "no-const-assign": "off", // Enforce specifying generic type arguments on constructor name of a constructor call. // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-dupe-class-members.md "no-dupe-class-members": "off", - // Enforce constituents of a type union/intersection to be sorted alphabetically. - "no-const-assign": "off", - // Disallow TypeScript namespaces. "no-new-symbol": "off", // Disallow aliasing this. "no-this-before-super": "off", + + // Disallow returning a value with type any from a function. + // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-useless-constructor.md + "no-useless-constructor": "off", }, }, - type: "typescript", - }, -]); - -export default config; + ]; +}); diff --git a/packages/eslint-config/src/config/ignores.ts b/packages/eslint-config/src/config/ignores.ts new file mode 100644 index 000000000..06317a261 --- /dev/null +++ b/packages/eslint-config/src/config/ignores.ts @@ -0,0 +1,46 @@ +import type { TypedFlatConfigItem } from "../types"; + +const ignores = async (userIgnores: string[] = []): Promise => [ + { + ignores: [ + "**/node_modules", + "**/dist", + "**/package-lock.json", + "**/yarn.lock", + "**/pnpm-lock.yaml", + "**/bun.lockb", + + "**/output", + "**/coverage", + "**/temp", + "**/.temp", + "**/tmp", + "**/.tmp", + "**/.history", + "**/.vitepress/cache", + "**/.nuxt", + "**/.next", + "**/.svelte-kit", + "**/.vercel", + "**/.changeset", + "**/.idea", + "**/.cache", + "**/.output", + "**/.vite-inspect", + "**/.yarn", + "**/vite.config.*.timestamp-*", + + "**/CHANGELOG*.md", + "**/*.min.*", + "**/LICENSE*", + "**/__snapshots__", + "**/auto-import?(s).d.ts", + "**/components.d.ts", + + ...userIgnores, + ], + name: "anolilab/ignores", + }, +]; + +export default ignores; diff --git a/packages/eslint-config/src/config/plugins/antfu.ts b/packages/eslint-config/src/config/plugins/antfu.ts index 486f15194..b66ed2a90 100644 --- a/packages/eslint-config/src/config/plugins/antfu.ts +++ b/packages/eslint-config/src/config/plugins/antfu.ts @@ -1,19 +1,57 @@ -import { hasTypescript, packageIsTypeModule } from "@anolilab/package-json-utils"; -import type { Linter } from "eslint"; +import { hasPackageJsonAnyDependency } from "@visulima/package"; +import type { OptionsFiles, OptionsOverrides, OptionsPackageJson } from "../../types"; import { createConfig } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; -const config: Linter.Config = createConfig("all", { - plugins: ["antfu"], - rules: { - "antfu/generic-spacing": "error", - "antfu/if-newline": "error", - "antfu/import-dedupe": "error", - "antfu/no-cjs-exports": packageIsTypeModule ? "error" : "off", - "antfu/no-ts-export-equal": hasTypescript ? "error" : "off", - "antfu/prefer-inline-type-import": "off", - "antfu/top-level-function": "off", - }, -}); +export default createConfig< + OptionsFiles & + OptionsOverrides & + OptionsPackageJson & { + lessOpinionated?: boolean; + } +>("all", async (config, oFiles) => { + const { + files = oFiles, + lessOpinionated = false, + overrides, + packageJson, + } = config; + + const antfuPlugin = await interopDefault(import("eslint-plugin-antfu")); + + return [ + { + files, + name: "anolilab/antfu", + plugins: { + antfu: antfuPlugin, + }, + rules: { + "antfu/consistent-chaining": "error", + "antfu/consistent-list-newline": "error", + "antfu/if-newline": "error", + + "antfu/import-dedupe": "error", -export default config; + "antfu/no-import-dist": "error", + "antfu/no-import-node-modules-by-path": "error", + "antfu/no-ts-export-equal": hasPackageJsonAnyDependency(packageJson, ["typescript"]) ? "error" : "off", + + "antfu/prefer-inline-type-import": "off", + "antfu/top-level-function": "off", + + ...lessOpinionated + ? { + curly: ["error", "all"], + } + : { + "antfu/curly": "error", + "antfu/if-newline": "error", + }, + + ...overrides, + }, + }, + ]; +}); diff --git a/packages/eslint-config/src/config/plugins/array-func.ts b/packages/eslint-config/src/config/plugins/array-func.ts deleted file mode 100644 index 0f86c31f4..000000000 --- a/packages/eslint-config/src/config/plugins/array-func.ts +++ /dev/null @@ -1,19 +0,0 @@ -// eslint-disable-next-line unicorn/prevent-abbreviations -import type { Linter } from "eslint"; - -import { createConfig } from "../../utils/create-config"; - -const config: Linter.Config = createConfig("all", { - extends: ["plugin:array-func/recommended"], - plugins: ["array-func"], - rules: { - // Rule disabled due to clash with Unicorn - "array-func/prefer-array-from": "off", - - // Rules not in recommended config - "array-func/prefer-flat": 0, - "array-func/prefer-flat-map": 0, - }, -}); - -export default config; diff --git a/packages/eslint-config/src/config/plugins/astro.ts b/packages/eslint-config/src/config/plugins/astro.ts new file mode 100644 index 000000000..6836382ec --- /dev/null +++ b/packages/eslint-config/src/config/plugins/astro.ts @@ -0,0 +1,69 @@ +import type { + OptionsFiles, + OptionsOverrides, + OptionsStylistic, + TypedFlatConfigItem, +} from "../../types"; +import { createConfig } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; + +export default createConfig("astro", async (config, oFiles): Promise => { + const { files = oFiles, overrides = {}, stylistic = true } = config; + + const [pluginAstro, parserAstro, parserTs] = await Promise.all([ + interopDefault(import("eslint-plugin-astro")), + interopDefault(import("astro-eslint-parser")), + interopDefault(import("@typescript-eslint/parser")), + ] as const); + + return [ + { + name: "anolilab/astro/setup", + plugins: { + astro: pluginAstro, + }, + }, + { + files, + languageOptions: { + globals: pluginAstro.environments.astro.globals, + parser: parserAstro, + parserOptions: { + extraFileExtensions: [".astro"], + parser: parserTs, + }, + sourceType: "module", + }, + name: "anolilab/astro/rules", + processor: "astro/client-side-ts", + rules: { + // Astro uses top level await for e.g. data fetching + // https://docs.astro.build/en/guides/data-fetching/#fetch-in-astro + "antfu/no-top-level-await": "off", + + // use recommended rules + "astro/missing-client-only-directive-value": "error", + "astro/no-conflict-set-directives": "error", + "astro/no-deprecated-astro-canonicalurl": "error", + "astro/no-deprecated-astro-fetchcontent": "error", + "astro/no-deprecated-astro-resolve": "error", + "astro/no-deprecated-getentrybyslug": "error", + "astro/no-set-html-directive": "off", + "astro/no-unused-define-vars-in-style": "error", + "astro/semi": "off", + "astro/valid-compile": "error", + + ...stylistic + ? { + "@stylistic/indent": "off", + "@stylistic/jsx-closing-tag-location": "off", + "@stylistic/jsx-one-expression-per-line": "off", + "@stylistic/no-multiple-empty-lines": "off", + } + : {}, + + ...overrides, + }, + }, + ]; +}); diff --git a/packages/eslint-config/src/config/plugins/ava.ts b/packages/eslint-config/src/config/plugins/ava.ts deleted file mode 100644 index c2357c775..000000000 --- a/packages/eslint-config/src/config/plugins/ava.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { Linter } from "eslint"; - -const config: Linter.Config = { - overrides: [ - { - env: { - es6: true, - }, - extends: "plugin:ava/recommended", - // Default ava test search patterns - files: [ - "test.js", - "src/test.js", - "source/test.js", - "**/test-*.js", - "**/*.spec.js", - "**/*.test.js", - "**/test/**/*.js", - "**/tests/**/*.js", - "**/__tests__/**/*.js", - ], - parserOptions: { - ecmaVersion: "latest", - sourceType: "module", - }, - plugins: ["ava"], - }, - ], -}; - -export default config; diff --git a/packages/eslint-config/src/config/plugins/babel.ts b/packages/eslint-config/src/config/plugins/babel.ts deleted file mode 100644 index 0dabc8d74..000000000 --- a/packages/eslint-config/src/config/plugins/babel.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { hasDependency, hasDevDependency } from "@anolilab/package-json-utils"; -import type { Linter } from "eslint"; - -import { createConfig } from "../../utils/create-config"; -import bestPracticesConfig from "../best-practices"; -import errorsConfig from "../errors"; -import styleConfig from "../style"; - -// @ts-expect-error TODO: find the correct type -const bestPracticesRules = bestPracticesConfig.overrides[0].rules as Linter.RulesRecord; -// @ts-expect-error TODO: find the correct type -const errorsRules = errorsConfig.overrides[0].rules as Linter.RulesRecord; -// @ts-expect-error TODO: find the correct type -const styleRules = styleConfig.overrides[0].rules as Linter.RulesRecord; - -if (global.anolilabEslintConfigBabelPrettierRules === undefined && (hasDependency("prettier") || hasDevDependency("prettier"))) { - global.anolilabEslintConfigBabelPrettierRules = { - "@babel/object-curly-spacing": "off", - - "@babel/semi": "off", - "babel/quotes": 0, - }; -} - -const config: Linter.Config = createConfig("all", { - plugins: ["babel"], - rules: { - // Deep clone to avoid object mutation weirdness - "babel/camelcase": [...(styleRules["camelcase"] as unknown[])] as Linter.RuleEntry, - "babel/new-cap": styleRules["new-cap"], - - "babel/no-invalid-this": bestPracticesRules["no-invalid-this"], - "babel/no-unused-expressions": bestPracticesRules["no-unused-expressions"], - - "babel/object-curly-spacing": styleRules["object-curly-spacing"], - "babel/quotes": styleRules["quotes"], - - "babel/semi": styleRules["semi"], - "babel/valid-typeof": errorsRules["valid-typeof"], - - camelcase: "off", - "new-cap": "off", - - "no-invalid-this": "off", - "no-unused-expressions": "off", - - "object-curly-spacing": "off", - quotes: "off", - - semi: "off", - "valid-typeof": "off", - - ...global.anolilabEslintConfigBabelPrettierRules, - }, -}); - -export default config; diff --git a/packages/eslint-config/src/config/plugins/comments.ts b/packages/eslint-config/src/config/plugins/comments.ts new file mode 100644 index 000000000..84d4066b1 --- /dev/null +++ b/packages/eslint-config/src/config/plugins/comments.ts @@ -0,0 +1,36 @@ +import type { OptionsFiles, OptionsOverrides } from "../../types"; +import { createConfig } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; + +export default createConfig("all", async (config, oFiles) => { + const { files = oFiles, overrides } = config; + + const pluginComments = await interopDefault(import("@eslint-community/eslint-plugin-eslint-comments")); + + return [ + { + files, + name: "anolilab/eslint-comments/rules", + plugins: { + "eslint-comments": pluginComments, + }, + rules: { + "eslint-comments/no-aggregating-enable": "error", + + "eslint-comments/no-duplicate-disable": "error", + // Rules are not in recommended config + "eslint-comments/no-restricted-disable": "off", + // Disabled as it's already covered by the `unicorn/no-abusive-eslint-disable` rule. + "eslint-comments/no-unlimited-disable": "off", + + "eslint-comments/no-unused-disable": "error", + "eslint-comments/no-unused-enable": "error", + + "eslint-comments/no-use": "off", + "eslint-comments/require-description": "off", + + ...overrides, + }, + }, + ]; +}); diff --git a/packages/eslint-config/src/config/plugins/compat.ts b/packages/eslint-config/src/config/plugins/compat.ts index b563d0d69..cee0c69ae 100644 --- a/packages/eslint-config/src/config/plugins/compat.ts +++ b/packages/eslint-config/src/config/plugins/compat.ts @@ -1,7 +1,15 @@ -import type { Linter } from "eslint"; +import type { OptionsFiles } from "../../types"; +import { createConfig } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; -const config: Linter.Config = { - extends: ["plugin:compat/recommended"], -}; +export default createConfig("all", async (config, oFiles) => { + const { files = oFiles } = config; -export default config; + const compatPlugin = await interopDefault(import("eslint-plugin-compat")); + + const fConfig = compatPlugin.configs["flat/recommended"]; + + fConfig.files = files; + + return [fConfig]; +}); diff --git a/packages/eslint-config/src/config/plugins/cypress.ts b/packages/eslint-config/src/config/plugins/cypress.ts deleted file mode 100644 index 335e93703..000000000 --- a/packages/eslint-config/src/config/plugins/cypress.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { Linter } from "eslint"; - -const config: Linter.Config = { - env: { - "cypress/globals": true, - }, - extends: ["plugin:cypress/recommended"], - plugins: ["cypress"], - rules: { - // Rules not in recommend config - "cypress/assertion-before-screenshot": 0, - "cypress/no-force": 0, - "cypress/require-data-selectors": 0, - }, -}; - -export default config; diff --git a/packages/eslint-config/src/config/plugins/deprecation.ts b/packages/eslint-config/src/config/plugins/deprecation.ts deleted file mode 100644 index 233c76c2d..000000000 --- a/packages/eslint-config/src/config/plugins/deprecation.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { Linter } from "eslint"; - -import { createConfig } from "../../utils/create-config"; - -// @see https://github.com/francoismassart/eslint-plugin-tailwindcss, -const config: Linter.Config = createConfig("typescript", { - plugins: ["deprecation"], - extends: ["plugin:deprecation/recommended"], -}); - -export default config; diff --git a/packages/eslint-config/src/config/plugins/editorconfig.ts b/packages/eslint-config/src/config/plugins/editorconfig.ts deleted file mode 100644 index a76065e4f..000000000 --- a/packages/eslint-config/src/config/plugins/editorconfig.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { Linter } from "eslint"; - -import { createConfig } from "../../utils/create-config"; - -const config: Linter.Config = createConfig("all", { - extends: ["plugin:editorconfig/all"], - plugins: ["editorconfig"], -}); - -export default config; diff --git a/packages/eslint-config/src/config/plugins/es.ts b/packages/eslint-config/src/config/plugins/es.ts index f817a9ec1..c0abf9066 100644 --- a/packages/eslint-config/src/config/plugins/es.ts +++ b/packages/eslint-config/src/config/plugins/es.ts @@ -1,12 +1,19 @@ -import type { Linter } from "eslint"; - +import type { OptionsFiles } from "../../types"; import { createConfig } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; -const config: Linter.Config = createConfig("all", { - plugins: ["es-x"], - settings: { - es: { aggressive: true }, - }, -}); +export default createConfig("all", async (config, oFiles) => { + const { files = oFiles } = config; -export default config; + const pluginES = await interopDefault(import("eslint-plugin-es-x")); + + return [ + { + files, + name: "anolilab/es/setup", + plugins: { + "es-x": pluginES, + }, + }, + ]; +}); diff --git a/packages/eslint-config/src/config/plugins/eslint-comments.ts b/packages/eslint-config/src/config/plugins/eslint-comments.ts deleted file mode 100644 index 71d4b7d46..000000000 --- a/packages/eslint-config/src/config/plugins/eslint-comments.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { Linter } from "eslint"; - -import { createConfig } from "../../utils/create-config"; - -const config: Linter.Config = createConfig("all", { - extends: ["plugin:eslint-comments/recommended"], - plugins: ["eslint-comments"], - rules: { - // Rules are not in recommended config - "eslint-comments/no-restricted-disable": "off", - - // Disabled as it's already covered by the `unicorn/no-abusive-eslint-disable` rule. - "eslint-comments/no-unlimited-disable": "off", - "eslint-comments/no-unused-disable": "error", - "eslint-comments/no-unused-enable": "error", - - "eslint-comments/no-use": "off", - "eslint-comments/require-description": "off", - }, -}); - -export default config; diff --git a/packages/eslint-config/src/config/plugins/etc.ts b/packages/eslint-config/src/config/plugins/etc.ts deleted file mode 100644 index cd858dcdd..000000000 --- a/packages/eslint-config/src/config/plugins/etc.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { env } from "node:process"; - -import { hasDependency, hasDevDependency } from "@anolilab/package-json-utils"; -import type { Linter } from "eslint"; - -import { createConfig } from "../../utils/create-config"; -import anolilabEslintConfig from "../../utils/eslint-config"; -import { consoleLog } from "../../utils/loggers"; - -if (!global.hasAnolilabEsLintConfigDeprecation && (hasDependency("eslint-plugin-deprecation") || hasDevDependency("eslint-plugin-deprecation"))) { - global.hasAnolilabEsLintConfigDeprecation = true; - let showLog: boolean = env["DISABLE_INFO_ON_DISABLING_ETC_NO_DEPRECATED"] !== "true"; - - if (showLog && anolilabEslintConfig["info_on_disabling_etc_no_deprecated"] !== undefined) { - showLog = anolilabEslintConfig["info_on_disabling_etc_no_deprecated"] as boolean; - } - - if (showLog) { - consoleLog(`\n@anolilab/eslint-config found "eslint-plugin-deprecation" package. \n - Following rules are disabled: etc/no-deprecated. \n`); - } -} - -const config: Linter.Config = createConfig("typescript", { - extends: ["plugin:etc/recommended"], - plugins: ["etc"], - rules: { - "etc/no-deprecated": global.hasAnolilabEsLintConfigDeprecation ? "off" : "error", - }, -}); - -export default config; diff --git a/packages/eslint-config/src/config/plugins/formatters.ts b/packages/eslint-config/src/config/plugins/formatters.ts new file mode 100644 index 000000000..9d53e1edd --- /dev/null +++ b/packages/eslint-config/src/config/plugins/formatters.ts @@ -0,0 +1,287 @@ +import type { OptionsFormatters, StylisticConfig, TypedFlatConfigItem } from "../../types"; +import { getFilesGlobs } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; +import parserPlain from "../../utils/parser-plain"; +import type { VendoredPrettierOptions, VendoredPrettierRuleOptions } from "../../vender/prettier-types"; +import { StylisticConfigDefaults } from "./stylistic"; + +const mergePrettierOptions = (options: VendoredPrettierOptions, overrides: VendoredPrettierRuleOptions = {}): VendoredPrettierRuleOptions => { + return { + ...options, + ...overrides, + plugins: [...overrides.plugins || [], ...options.plugins || []], + }; +}; + +// eslint-disable-next-line sonarjs/cognitive-complexity +const formatters = async (options: OptionsFormatters, stylistic: StylisticConfig): Promise => { + if (options.slidev && options.markdown !== true && options.markdown !== "prettier") { + throw new Error("`slidev` option only works when `markdown` is enabled with `prettier`"); + } + + const { indent, quotes, semi } = { + ...StylisticConfigDefaults, + ...stylistic, + }; + + const prettierOptions: VendoredPrettierOptions = Object.assign( + { + endOfLine: "auto", + printWidth: 120, + semi, + singleQuote: quotes === "single", + tabWidth: typeof indent === "number" ? indent : 2, + trailingComma: "all", + useTabs: indent === "tab", + } satisfies VendoredPrettierOptions, + options.prettierOptions || {}, + ); + + const prettierXmlOptions: VendoredPrettierOptions = { + xmlQuoteAttributes: "double", + xmlSelfClosingSpace: true, + xmlSortAttributesByKey: false, + xmlWhitespaceSensitivity: "ignore", + }; + + const dprintOptions = { + indentWidth: typeof indent === "number" ? indent : 2, + quoteStyle: quotes === "single" ? "preferSingle" : "preferDouble", + useTabs: indent === "tab", + ...options.dprintOptions, + }; + + const pluginFormat = await interopDefault(import("eslint-plugin-format")); + + const configs: TypedFlatConfigItem[] = [ + { + name: "anolilab/formatter/setup", + plugins: { + format: pluginFormat, + }, + }, + ]; + + if (options.css) { + configs.push( + { + files: [...getFilesGlobs("css"), ...getFilesGlobs("postcss")], + languageOptions: { + parser: parserPlain, + }, + name: "anolilab/formatter/css", + rules: { + "format/prettier": [ + "error", + mergePrettierOptions(prettierOptions, { + parser: "css", + }), + ], + }, + }, + { + files: getFilesGlobs("scss"), + languageOptions: { + parser: parserPlain, + }, + name: "anolilab/formatter/scss", + rules: { + "format/prettier": [ + "error", + mergePrettierOptions(prettierOptions, { + parser: "scss", + }), + ], + }, + }, + { + files: getFilesGlobs("less"), + languageOptions: { + parser: parserPlain, + }, + name: "anolilab/formatter/less", + rules: { + "format/prettier": [ + "error", + mergePrettierOptions(prettierOptions, { + parser: "less", + }), + ], + }, + }, + ); + } + + if (options.html) { + configs.push({ + files: getFilesGlobs("html"), + languageOptions: { + parser: parserPlain, + }, + name: "anolilab/formatter/html", + rules: { + "format/prettier": [ + "error", + mergePrettierOptions(prettierOptions, { + parser: "html", + }), + ], + }, + }); + } + + if (options.xml) { + configs.push({ + files: getFilesGlobs("xml"), + languageOptions: { + parser: parserPlain, + }, + name: "anolilab/formatter/xml", + rules: { + "format/prettier": [ + "error", + mergePrettierOptions( + { ...prettierXmlOptions, ...prettierOptions }, + { + parser: "xml", + plugins: ["@prettier/plugin-xml"], + }, + ), + ], + }, + }); + } + + if (options.svg) { + configs.push({ + files: getFilesGlobs("svg"), + languageOptions: { + parser: parserPlain, + }, + name: "anolilab/formatter/svg", + rules: { + "format/prettier": [ + "error", + mergePrettierOptions( + { ...prettierXmlOptions, ...prettierOptions }, + { + parser: "xml", + plugins: ["@prettier/plugin-xml"], + }, + ), + ], + }, + }); + } + + if (options.markdown) { + const formater = options.markdown === true ? "prettier" : options.markdown; + + let GLOB_SLIDEV: string[] = []; + + if (typeof options.slidev === "boolean" && options.slidev === true) { + GLOB_SLIDEV = ["**/slides.md"]; + } else if (typeof options.slidev === "object" && options.slidev.files) { + GLOB_SLIDEV = options.slidev.files; + } + + configs.push({ + files: getFilesGlobs("markdown"), + ignores: GLOB_SLIDEV, + languageOptions: { + parser: parserPlain, + }, + name: "anolilab/formatter/markdown", + rules: { + [`format/${formater}`]: [ + "error", + formater === "prettier" + ? mergePrettierOptions(prettierOptions, { + embeddedLanguageFormatting: "off", + parser: "markdown", + }) + : { + ...dprintOptions, + language: "markdown", + }, + ], + }, + }); + + if (options.slidev) { + configs.push({ + files: GLOB_SLIDEV, + languageOptions: { + parser: parserPlain, + }, + name: "anolilab/formatter/slidev", + rules: { + "format/prettier": [ + "error", + mergePrettierOptions(prettierOptions, { + embeddedLanguageFormatting: "off", + parser: "slidev", + plugins: ["prettier-plugin-slidev"], + }), + ], + }, + }); + } + } + + if (options.astro) { + configs.push( + { + files: getFilesGlobs("astro"), + languageOptions: { + parser: parserPlain, + }, + name: "anolilab/formatter/astro", + rules: { + "format/prettier": [ + "error", + mergePrettierOptions(prettierOptions, { + parser: "astro", + plugins: ["prettier-plugin-astro"], + }), + ], + }, + }, + { + files: [...getFilesGlobs("astro"), ...getFilesGlobs("astro_ts")], + name: "anolilab/formatter/astro/disables", + rules: { + "@stylistic/arrow-parens": "off", + "@stylistic/block-spacing": "off", + "@stylistic/comma-dangle": "off", + "@stylistic/indent": "off", + "@stylistic/no-multi-spaces": "off", + "@stylistic/quotes": "off", + "@stylistic/semi": "off", + }, + }, + ); + } + + if (options.graphql) { + configs.push({ + files: getFilesGlobs("graphql"), + languageOptions: { + parser: parserPlain, + }, + name: "anolilab/formatter/graphql", + rules: { + "format/prettier": [ + "error", + mergePrettierOptions(prettierOptions, { + parser: "graphql", + }), + ], + }, + }); + } + + return configs; +}; + +export default formatters; diff --git a/packages/eslint-config/src/config/plugins/html.ts b/packages/eslint-config/src/config/plugins/html.ts index 04d2bfe4f..2c122b7ac 100644 --- a/packages/eslint-config/src/config/plugins/html.ts +++ b/packages/eslint-config/src/config/plugins/html.ts @@ -1,74 +1,54 @@ -import { hasDependency, hasDevDependency } from "@anolilab/package-json-utils"; -import type { Linter } from "eslint"; - -import indent from "../../utils/indent"; - -if (!global.hasAnolilabEsLintConfigPrettier && (hasDependency("prettier") || hasDevDependency("prettier"))) { - global.hasAnolilabEsLintConfigPrettier = true; -} - -if (global.hasAnolilabEsLintConfigPrettier) { - global.anolilabEslintConfigHtmlPrettierRules = { - "@html-eslint/element-newline": "off", - "@html-eslint/indent": "off", - "@html-eslint/no-extra-spacing-attrs": "off", - "@html-eslint/quotes": "off", - }; - - global.anolilabEslintConfigHtmlPrettierSettings = { - "html/report-bad-indent": "off", - }; -} - -let settings: Linter.Config["settings"] = {}; - -if (!global.hasAnolilabEsLintConfigPrettier) { - settings = { - "html/indent": `+${indent}`, - }; -} - -const config: Linter.Config = { - overrides: [ +import type { + OptionsFiles, + OptionsHasPrettier, + OptionsOverrides, + OptionsStylistic, +} from "../../types"; +import { createConfig } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; + +export default createConfig("html", async (config, oFiles) => { + const { + files = oFiles, + overrides, + prettier, + stylistic = true, + } = config; + + const { indent = 4 } = typeof stylistic === "boolean" ? {} : stylistic; + + const htmlPlugin = await interopDefault(import("eslint-plugin-html")); + + return [ { - extends: ["plugin:@html-eslint/recommended"], - files: [ - "**/*.erb", - "**/*.handlebars", - "**/*.hbs", - "**/*.htm", - "**/*.html", - "**/*.mustache", - "**/*.nunjucks", - "**/*.php", - "**/*.tag", - "**/*.twig", - "**/*.we", - ], - globals: { - sourceCode: true, - }, - env: { - browser: true, - node: false, + files, + name: "anolilab/html/setup", + plugins: { + html: htmlPlugin, }, - parser: "@html-eslint/parser", - plugins: ["html", "@html-eslint"], rules: { "@html-eslint/indent": ["error", indent], "capitalized-comments": "off", // @see https://github.com/yeonjuan/html-eslint/issues/67 bug in html-eslint "spaced-comment": "off", - ...global.anolilabEslintConfigHtmlPrettierRules, + ...prettier + ? { + "@html-eslint/element-newline": "off", + "@html-eslint/indent": "off", + "@html-eslint/no-extra-spacing-attrs": "off", + "@html-eslint/quotes": "off", + } + : {}, + + ...overrides, }, + settings: { + "html/indent": `+${indent}`, "html/report-bad-indent": "error", - ...settings, - ...global.anolilabEslintConfigHtmlPrettierSettings, + ...prettier ? { "html/report-bad-indent": "off" } : {}, }, }, - ], -}; - -export default config; + ]; +}); diff --git a/packages/eslint-config/src/config/plugins/import.ts b/packages/eslint-config/src/config/plugins/import.ts deleted file mode 100644 index 819df56c8..000000000 --- a/packages/eslint-config/src/config/plugins/import.ts +++ /dev/null @@ -1,403 +0,0 @@ -import { fromRoot, packageIsTypeModule, projectPath } from "@anolilab/package-json-utils"; -import type { Linter } from "eslint"; - -import { createConfigs } from "../../utils/create-config"; -import anolilabEslintConfig from "../../utils/eslint-config"; - -if (global.anolilabEslintImportNoUnusedModulesConfig === undefined && anolilabEslintConfig["import_ignore_exports"]) { - if (!Array.isArray(anolilabEslintConfig["import_ignore_exports"])) { - throw new TypeError("import.ignore_exports must be a array"); - } - - global.anolilabEslintImportNoUnusedModulesConfig = anolilabEslintConfig["import_ignore_exports"] as string[]; -} - -const config: Linter.Config = createConfigs([ - { - config: { - env: { - es6: true, - }, - - parserOptions: { - ecmaVersion: 6, - sourceType: "module", - }, - plugins: ["import"], - rules: { - // enforce a consistent style for type specifiers (inline or top-level) - // https://github.com/un-es/eslint-plugin-i/blob/d5fc8b670dc8e6903dbb7b0894452f60c03089f5/docs/rules/consistent-type-specifier-style.md - "import/consistent-type-specifier-style": ["error", "prefer-top-level"], - - // ensure named imports coupled with named exports - // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/default.md#when-not-to-use-it - "import/default": "off", - - // dynamic imports require a leading comment with a webpackChunkName - // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/dynamic-import-chunkname.md - "import/dynamic-import-chunkname": [ - "off", - { - importFunctions: [], - webpackChunknameFormat: "[0-9a-zA-Z-_/.]+", - }, - ], - - // disallow invalid exports, e.g. multiple defaults - // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/export.md - "import/export": "error", - - // This rule enforces that all exports are declared at the bottom of the file. - // https://github.com/import-js/eslint-plugin-import/blob/98acd6afd04dcb6920b81330114e146dc8532ea4/docs/rules/exports-last.md - "import/exports-last": "error", - - // Ensure consistent use of file extension within the import path - // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/extensions.md - "import/extensions": [ - "error", - "ignorePackages", - packageIsTypeModule - ? { - cjs: "always", - js: "always", - jsx: "always", - mjs: "always", - json: "always", - } - : { - cjs: "never", - js: "never", - jsx: "never", - mjs: "never", - json: "always", - }, - ], - - // disallow non-import statements appearing before import statements - // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/first.md - "import/first": "error", - - // Reports when named exports are not grouped together in a single export declaration - // or when multiple assignments to CommonJS module.exports or exports object are present - // in a single file. - // https://github.com/import-js/eslint-plugin-import/blob/44a038c06487964394b1e15b64f3bd34e5d40cde/docs/rules/group-exports.md - "import/group-exports": "off", - - // disallow non-import statements appearing before import statements - // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/imports-first.md - // deprecated: use `import/first` - "import/imports-first": "off", - - // "import/max-dependencies" is not super useful - // Either you will disable the eslint rule because it's "normal" - // to have a lot of dependencies or feel compelled to reduce the number of imports. - // It's already visible that a file has many imports and that ideally they should be - // less imports, no need for ESLint, let's keep ESLint for more valuable things. - // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/max-dependencies.md - "import/max-dependencies": ["off", { max: 10 }], - - // disallow require() - // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/named.md#when-not-to-use-it - "import/named": "error", - - // disallow AMD require/define - // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/namespace.md - "import/namespace": "off", - - // Require a newline after the last import/require in a group - // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/newline-after-import.md - "import/newline-after-import": "error", - - // Forbid import of modules using absolute paths - // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-absolute-path.md - "import/no-absolute-path": "error", - - // disallow AMD require/define - // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-amd.md - "import/no-amd": "error", - - // Reports if a module's default export is unnamed - // https://github.com/import-js/eslint-plugin-import/blob/d9b712ac7fd1fddc391f7b234827925c160d956f/docs/rules/no-anonymous-default-export.md - "import/no-anonymous-default-export": [ - "off", - { - allowAnonymousClass: false, - allowAnonymousFunction: false, - allowArray: false, - allowArrowFunction: false, - allowLiteral: false, - allowObject: false, - }, - ], - - // disallow require() - // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-commonjs.md - "import/no-commonjs": packageIsTypeModule ? ["error", { allowPrimitiveModules: true }] : "off", - - // Forbid cyclical dependencies between modules - // https://medium.com/@steven-lemon182/are-typescript-barrel-files-an-anti-pattern-72a713004250 - "import/no-cycle": ["error", { maxDepth: "∞" }], - - // forbid default exports. this is a terrible rule, do not use it. - // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-default-export.md - "import/no-default-export": "off", - - // disallow use of jsdoc-marked-deprecated imports - // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-deprecated.md - "import/no-deprecated": "off", - - // disallow duplicate imports - // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-duplicates.md - "import/no-duplicates": "error", - - // Forbid require() calls with expressions - // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-dynamic-require.md - "import/no-dynamic-require": "error", - - // Reports the use of empty named import blocks. - // https://github.com/un-es/eslint-plugin-i/blob/d5fc8b670dc8e6903dbb7b0894452f60c03089f5/docs/rules/no-empty-named-blocks.md - "import/no-empty-named-blocks": "error", - - // Forbid the use of extraneous packages - // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-extraneous-dependencies.md - // paths are treated both as absolute paths, and relative to process.cwd() - "import/no-extraneous-dependencies": [ - "error", - { - devDependencies: [ - "test/**", // tape, common npm pattern - "tests/**", // also common npm pattern - "spec/**", // mocha, rspec-like pattern - "**/fixture/**", // jest pattern - "**/__mocks__/**", // jest pattern - "test.{js,jsx}", // repos with a single test file - "test-*.{js,jsx}", // repos with multiple top-level test files - "**/*{.,_}{test,spec}.{js,jsx}", // tests where the extension or filename suffix denotes that it is a test - "**/jest.config.cjs", // jest config - "**/jest.setup.js", // jest setup - "**/vue.config.cjs", // vue-cli config - "**/webpack.config.cjs", // webpack config - "**/webpack.config.*.js", // webpack config - "**/rollup.config.cjs", // rollup config - "**/rollup.config.*.js", // rollup config - "**/gulpfile.js", // gulp config - "**/gulpfile.*.js", // gulp config - "**/Gruntfile{,.js}", // grunt config - "**/protractor.conf.js", // protractor config - "**/protractor.conf.*.js", // protractor config - "**/karma.conf.js", // karma config - "**/.eslintrc.js", // eslint config - "**/.eslintrc.cjs", // eslint config - "**/.eslintrc.mjs", // eslint config - "**/eslint.config.js", // eslint flat config - "**/eslint.config.mjs", // eslint flat config - "**/eslint.config.cjs", // eslint flat config - "**/vite.config.js", // vite config - "**/vite.config.ts", // vite config - "**/vitest.config.js", // vitest config - "**/vitest.config.ts", // vitest config - "**/__tests__/**/*.?(c|m)[jt]s?(x)", // vitest config test include - "**/?(*.){test,spec}.?(c|m)[jt]s?(x)", // vitest config test include - ], - optionalDependencies: false, - }, - ], - - // prevent importing the submodules of other modules - // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-internal-modules.md - "import/no-internal-modules": [ - "off", - { - allow: [], - }, - ], - - // Forbid mutable exports - // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-mutable-exports.md - "import/no-mutable-exports": "error", - - // Warn if a module could be mistakenly parsed as a script by a consumer - // leveraging Unambiguous JavaScript Grammar - // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/unambiguous.md - // this should not be enabled until this proposal has at least been *presented* to TC39. - // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-named-as-default.md - "import/no-named-as-default": "error", - - // warn on accessing default export property names that are also named exports - // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-named-as-default-member.md - "import/no-named-as-default-member": "error", - - // Prevent importing the default as if it were named - // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-named-default.md - "import/no-named-default": "error", - - // Prohibit named exports. this is a terrible rule, do not use it. - // https://github.com/import-js/eslint-plugin-import/blob/1ec80fa35fa1819e2d35a70e68fb6a149fb57c5e/docs/rules/no-named-export.md - "import/no-named-export": "off", - - // disallow namespace imports - // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-namespace.md - "import/no-namespace": "error", - - // No Node.js builtin modules - // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-nodejs-modules.md - // TODO: enable? - "import/no-nodejs-modules": "off", - - // Use this rule to prevent imports to folders in relative parent paths. - // https://github.com/import-js/eslint-plugin-import/blob/c34f14f67f077acd5a61b3da9c0b0de298d20059/docs/rules/no-relative-parent-imports.md - "import/no-relative-parent-imports": "off", - - // Restrict which files can be imported in a given folder - // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-restricted-paths.md - "import/no-restricted-paths": "off", - - // Forbid a module from importing itself - // https://github.com/import-js/eslint-plugin-import/blob/44a038c06487964394b1e15b64f3bd34e5d40cde/docs/rules/no-self-import.md - "import/no-self-import": "error", - - // Forbid a module from importing itself - // importing for side effects is perfectly acceptable, if you need side effects. - "import/no-unassigned-import": "off", - - // ensure imports point to files/modules that can be resolved - // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-unresolved.md - "import/no-unresolved": ["error", { caseSensitive: true, commonjs: true }], - - // Reports modules without any exports, or with unused exports - // https://github.com/import-js/eslint-plugin-import/blob/f63dd261809de6883b13b6b5b960e6d7f42a7813/docs/rules/no-unused-modules.md - "import/no-unused-modules": [ - packageIsTypeModule ? "error" : "off", - { - ignoreExports: global.anolilabEslintImportNoUnusedModulesConfig ?? [], - missingExports: true, - unusedExports: true, - }, - ], - - // Reports the use of import declarations with CommonJS exports in any module except for the main module. - // https://github.com/import-js/eslint-plugin-import/blob/1012eb951767279ce3b540a4ec4f29236104bb5b/docs/rules/no-import-module-exports.md - "import/no-import-module-exports": [ - packageIsTypeModule ? "off" : "error", - { - exceptions: [], - }, - ], - - // Use this rule to prevent importing packages through relative paths. - // https://github.com/import-js/eslint-plugin-import/blob/1012eb951767279ce3b540a4ec4f29236104bb5b/docs/rules/no-relative-packages.md - "import/no-relative-packages": "error", - - // Ensures that there are no useless path segments - // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-useless-path-segments.md - "import/no-useless-path-segments": ["error", { commonjs: true, noUselessIndex: true }], - - // Forbid Webpack loader syntax in imports - // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-webpack-loader-syntax.md - "import/no-webpack-loader-syntax": "error", - - // ensure absolute imports are above relative imports and that unassigned imports are ignored - // https://github.com/import-js/eslint-plugin-import/blob/f63dd261809de6883b13b6b5b960e6d7f42a7813/docs/rules/no-unused-modules.md - // simple-import-sort does this better - "import/order": "off", - - // Require modules with a single export to use a default export - // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/prefer-default-export.md - "import/prefer-default-export": "error", - - // Warn if a module could be mistakenly parsed as a script by a consumer - // leveraging Unambiguous JavaScript Grammar - // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/unambiguous.md - // this should not be enabled until this proposal has at least been *presented* to TC39. - // At the moment, it's not a thing. - "import/unambiguous": "off", - }, - settings: { - "import/core-modules": [], - // https://github.com/un-es/eslint-plugin-i/blob/main/docs/rules/extensions.md - "import/extensions": [".js", ".cjs", ".mjs", ".jsx"], - // Ensure consistent use of file extension within the import path - "import/ignore": ["\\.(coffee|scss|css|less|hbs|svg|json)$"], - }, - }, - type: "all", - }, - { - config: { - settings: { - "import/resolver": { - "@jsenv/eslint-import-resolver": { - rootDirectoryUrl: projectPath, - packageConditions: ["node", "import"], - }, - }, - }, - }, - type: "javascript", - }, - { - config: { - extends: ["plugin:import/typescript"], - rules: { - // Does not work when the TS definition exports a default const. - "import/default": "off", - - // Disabled because of https://github.com/import-js/eslint-plugin-import/issues/1590 - "import/export": "off", - - // Disabled as it doesn't work with TypeScript. - "import/extensions": [ - "error", - "ignorePackages", - { - js: "never", - jsx: "never", - mjs: "never", - cjs: "never", - ts: "never", - tsx: "never", - json: "always", - svg: "always", - }, - ], - - // This issue and some others: https://github.com/import-js/eslint-plugin-import/issues/1341 - "import/named": "off", - - // ensure imports point to files/modules that can be resolved - "import/no-unresolved": "off", - }, - settings: { - // Append 'ts' extensions to 'import/extensions' setting - "import/extensions": [".js", ".mjs", ".jsx", ".ts", ".tsx", ".d.ts", ".cjs", ".cts", ".mts"], - - // Resolve type definition packages - "import/external-module-folders": ["node_modules", "node_modules/@types"], - - // Apply special parsing for TypeScript files - "import/parsers": { - "@typescript-eslint/parser": [".ts", ".cts", ".mts", ".tsx", ".d.ts"], - }, - - // Append 'ts' extensions to 'import/resolver' setting - "import/resolver": { - typescript: { - alwaysTryTypes: true, // always try to resolve types under `@types` directory even it doesn't contain any source code, like `@types/unist` - project: fromRoot("tsconfig.json"), - }, - }, - }, - }, - type: "typescript", - }, - { - config: { - rules: { - "import/no-duplicates": "off", - }, - }, - type: "d.ts", - }, -]); - -export default config; diff --git a/packages/eslint-config/src/config/plugins/imports.ts b/packages/eslint-config/src/config/plugins/imports.ts new file mode 100644 index 000000000..4aa2e9993 --- /dev/null +++ b/packages/eslint-config/src/config/plugins/imports.ts @@ -0,0 +1,418 @@ +import tsParser from "@typescript-eslint/parser"; +import { hasPackageJsonAnyDependency } from "@visulima/package"; + +import type { + OptionsCwd, + OptionsFiles, + OptionsOverrides, + OptionsPackageJson, + OptionsStylistic, + OptionsTypeScriptWithTypes, + TypedFlatConfigItem, +} from "../../types"; +import { createConfig, getFilesGlobs } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; + +export default createConfig( + "all", + async (config, oFiles) => { + const { + files = oFiles, + overrides, + packageJson, + stylistic, + tsconfigPath, + } = config; + + const importPlugin = await interopDefault(import("eslint-plugin-import-x")); + + const rules: TypedFlatConfigItem[] = [ + { + name: "anolilab/imports/setup", + plugins: { + import: importPlugin, + }, + }, + { + files, + name: "anolilab/imports/rules", + rules: { + // enforce a consistent style for type specifiers (inline or top-level) + // https://github.com/un-es/eslint-plugin-i/blob/d5fc8b670dc8e6903dbb7b0894452f60c03089f5/docs/rules/consistent-type-specifier-style.md + "import/consistent-type-specifier-style": ["error", "prefer-top-level"], + + // ensure named imports coupled with named exports + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/default.md#when-not-to-use-it + "import/default": "off", + + // dynamic imports require a leading comment with a webpackChunkName + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/dynamic-import-chunkname.md + "import/dynamic-import-chunkname": [ + "off", + { + importFunctions: [], + webpackChunknameFormat: "[0-9a-zA-Z-_/.]+", + }, + ], + + // disallow invalid exports, e.g. multiple defaults + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/export.md + "import/export": "error", + + // This rule enforces that all exports are declared at the bottom of the file. + // https://github.com/import-js/eslint-plugin-import/blob/98acd6afd04dcb6920b81330114e146dc8532ea4/docs/rules/exports-last.md + "import/exports-last": "error", + + // Ensure consistent use of file extension within the import path + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/extensions.md + "import/extensions": [ + "error", + "ignorePackages", + { + checkTypeImports: tsconfigPath !== undefined, + ignorePackages: true, + pattern: { + ...packageJson.type === "module" + ? { + cjs: "always", + js: "always", + json: "always", + jsx: "always", + mjs: "always", + } + : { + cjs: "never", + js: "never", + json: "always", + jsx: "never", + mjs: "never", + }, + }, + }, + ], + + // disallow non-import statements appearing before import statements + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/first.md + "import/first": "error", + + // Reports when named exports are not grouped together in a single export declaration + // or when multiple assignments to CommonJS module.exports or exports object are present + // in a single file. + // https://github.com/import-js/eslint-plugin-import/blob/44a038c06487964394b1e15b64f3bd34e5d40cde/docs/rules/group-exports.md + "import/group-exports": "off", + + // disallow non-import statements appearing before import statements + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/imports-first.md + // deprecated: use `import/first` + "import/imports-first": "off", + + // "import/max-dependencies" is not super useful + // Either you will disable the eslint rule because it's "normal" + // to have a lot of dependencies or feel compelled to reduce the number of imports. + // It's already visible that a file has many imports and that ideally they should be + // less imports, no need for ESLint, let's keep ESLint for more valuable things. + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/max-dependencies.md + "import/max-dependencies": ["off", { max: 10 }], + + // disallow require() + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/named.md#when-not-to-use-it + "import/named": "error", + + // disallow AMD require/define + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/namespace.md + "import/namespace": "off", + + // Require a newline after the last import/require in a group + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/newline-after-import.md + ...stylistic + ? { + "import/newline-after-import": ["error", { count: 1 }], + } + : {}, + + // Forbid import of modules using absolute paths + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-absolute-path.md + "import/no-absolute-path": "error", + + // disallow AMD require/define + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-amd.md + "import/no-amd": "error", + + // Reports if a module's default export is unnamed + + // https://github.com/import-js/eslint-plugin-import/blob/d9b712ac7fd1fddc391f7b234827925c160d956f/docs/rules/no-anonymous-default-export.md + "import/no-anonymous-default-export": [ + "off", + { + allowAnonymousClass: false, + allowAnonymousFunction: false, + allowArray: false, + allowArrowFunction: false, + allowLiteral: false, + allowObject: false, + }, + ], + + // disallow require() + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-commonjs.md + "import/no-commonjs": packageJson.type === "module" ? ["error", { allowPrimitiveModules: true }] : "off", + + // Forbid cyclical dependencies between modules + // https://medium.com/@steven-lemon182/are-typescript-barrel-files-an-anti-pattern-72a713004250 + "import/no-cycle": ["error", { maxDepth: "∞" }], + + // forbid default exports. this is a terrible rule, do not use it. + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-default-export.md + "import/no-default-export": "off", + + // disallow use of jsdoc-marked-deprecated imports + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-deprecated.md + "import/no-deprecated": "off", + + // disallow duplicate imports + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-duplicates.md + "import/no-duplicates": "error", + + // Forbid require() calls with expressions + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-dynamic-require.md + "import/no-dynamic-require": "error", + + // Reports the use of empty named import blocks. + + // https://github.com/un-es/eslint-plugin-i/blob/d5fc8b670dc8e6903dbb7b0894452f60c03089f5/docs/rules/no-empty-named-blocks.md + "import/no-empty-named-blocks": "error", + + // Forbid the use of extraneous packages + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-extraneous-dependencies.md + // paths are treated both as absolute paths, and relative to process.cwd() + "import/no-extraneous-dependencies": [ + "error", + { + devDependencies: [ + "test/**", // tape, common npm pattern + "tests/**", // also common npm pattern + "spec/**", // mocha, rspec-like pattern + "**/fixture/**", // jest pattern + "**/__mocks__/**", // jest pattern + "test.{js,jsx}", // repos with a single test file + "test-*.{js,jsx}", // repos with multiple top-level test files + "**/*{.,_}{test,spec}.{js,jsx}", // tests where the extension or filename suffix denotes that it is a test + "**/jest.config.cjs", // jest config + "**/jest.setup.js", // jest setup + "**/vue.config.cjs", // vue-cli config + "**/webpack.config.cjs", // webpack config + "**/webpack.config.*.js", // webpack config + "**/rollup.config.cjs", // rollup config + "**/rollup.config.*.js", // rollup config + "**/gulpfile.js", // gulp config + "**/gulpfile.*.js", // gulp config + "**/Gruntfile{,.js}", // grunt config + "**/protractor.conf.js", // protractor config + "**/protractor.conf.*.js", // protractor config + "**/karma.conf.js", // karma config + "**/.eslintrc.js", // eslint config + "**/.eslintrc.cjs", // eslint config + "**/.eslintrc.mjs", // eslint config + "**/eslint.config.js", // eslint flat config + "**/eslint.config.mjs", // eslint flat config + "**/eslint.config.cjs", // eslint flat config + "**/vite.config.js", // vite config + "**/vite.config.ts", // vite config + "**/vitest.config.js", // vitest config + "**/vitest.config.ts", // vitest config + "**/__tests__/**/*.?(c|m)[jt]s?(x)", // vitest config test include + "**/?(*.){test,spec}.?(c|m)[jt]s?(x)", // vitest config test include + ], + optionalDependencies: false, + }, + ], + + // Reports the use of import declarations with CommonJS exports in any module except for the main module. + // https://github.com/import-js/eslint-plugin-import/blob/1012eb951767279ce3b540a4ec4f29236104bb5b/docs/rules/no-import-module-exports.md + "import/no-import-module-exports": [ + packageJson.type === "module" ? "off" : "error", + { + exceptions: [], + }, + ], + + // prevent importing the submodules of other modules + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-internal-modules.md + "import/no-internal-modules": [ + "off", + { + allow: [], + }, + ], + + // Forbid mutable exports + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-mutable-exports.md + "import/no-mutable-exports": "error", + + // Warn if a module could be mistakenly parsed as a script by a consumer + // leveraging Unambiguous JavaScript Grammar + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/unambiguous.md + // this should not be enabled until this proposal has at least been *presented* to TC39. + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-named-as-default.md + "import/no-named-as-default": "error", + + // warn on accessing default export property names that are also named exports + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-named-as-default-member.md + "import/no-named-as-default-member": "error", + + // Prevent importing the default as if it were named + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-named-default.md + "import/no-named-default": "error", + + // Prohibit named exports. this is a terrible rule, do not use it. + // https://github.com/import-js/eslint-plugin-import/blob/1ec80fa35fa1819e2d35a70e68fb6a149fb57c5e/docs/rules/no-named-export.md + "import/no-named-export": "off", + + // disallow namespace imports + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-namespace.md + "import/no-namespace": "error", + + // No Node.js builtin modules + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-nodejs-modules.md + "import/no-nodejs-modules": "off", + + // Use this rule to prevent importing packages through relative paths. + // https://github.com/import-js/eslint-plugin-import/blob/1012eb951767279ce3b540a4ec4f29236104bb5b/docs/rules/no-relative-packages.md + "import/no-relative-packages": "error", + + // Use this rule to prevent imports to folders in relative parent paths. + // https://github.com/import-js/eslint-plugin-import/blob/c34f14f67f077acd5a61b3da9c0b0de298d20059/docs/rules/no-relative-parent-imports.md + "import/no-relative-parent-imports": "off", + + // Restrict which files can be imported in a given folder + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-restricted-paths.md + "import/no-restricted-paths": "off", + + // Forbid a module from importing itself + // https://github.com/import-js/eslint-plugin-import/blob/44a038c06487964394b1e15b64f3bd34e5d40cde/docs/rules/no-self-import.md + "import/no-self-import": "error", + + // @TODO: Enable this rule when it's fixed https://github.com/import-js/eslint-plugin-import/issues/2678 + // Reports modules without any exports, or with unused exports + // https://github.com/import-js/eslint-plugin-import/blob/f63dd261809de6883b13b6b5b960e6d7f42a7813/docs/rules/no-unused-modules.md + // "import/no-unused-modules": [ + // packageJson.type === "module" ? "error" : "off", + // { + // ignoreExports: importNoUnusedModules ?? [], + // missingExports: true, + // unusedExports: true, + // }, + // ], + + // Forbid a module from importing itself + // importing for side effects is perfectly acceptable, if you need side effects. + "import/no-unassigned-import": "off", + + // ensure imports point to files/modules that can be resolved + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-unresolved.md + "import/no-unresolved": ["error", { caseSensitive: true, commonjs: true }], + + // Ensures that there are no useless path segments + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-useless-path-segments.md + "import/no-useless-path-segments": ["error", { commonjs: true, noUselessIndex: false }], + + // Forbid Webpack loader syntax in imports + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-webpack-loader-syntax.md + "import/no-webpack-loader-syntax": "error", + + // ensure absolute imports are above relative imports and that unassigned imports are ignored + // https://github.com/import-js/eslint-plugin-import/blob/f63dd261809de6883b13b6b5b960e6d7f42a7813/docs/rules/no-unused-modules.md + // simple-import-sort does this better + "import/order": "off", + + // Require modules with a single export to use a default export + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/prefer-default-export.md + "import/prefer-default-export": "error", + + // Warn if a module could be mistakenly parsed as a script by a consumer + // leveraging Unambiguous JavaScript Grammar + // https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/unambiguous.md + // this should not be enabled until this proposal has at least been *presented* to TC39. + // At the moment, it's not a thing. + "import/unambiguous": "off", + + ...overrides, + }, + }, + { + files: getFilesGlobs("d.ts"), + name: "anolilab/imports/d.ts-rules", + rules: { + "import/no-duplicates": "off", + }, + }, + ]; + + if (hasPackageJsonAnyDependency(packageJson, ["react", "react-dom"])) { + rules.push(importPlugin.flatConfigs.react); + } + + if (hasPackageJsonAnyDependency(packageJson, ["typescript"])) { + rules.push({ + files: getFilesGlobs("ts"), + languageOptions: { + ecmaVersion: "latest", + parser: tsParser, + sourceType: "module", + }, + name: "anolilab/import/ts-rules", + rules: { + // Does not work when the TS definition exports a default const. + "import/default": "off", + + // Disabled because of https://github.com/import-js/eslint-plugin-import/issues/1590 + "import/export": "off", + + // Disabled as it doesn't work with TypeScript. + "import/extensions": "off", + + // This issue and some others: https://github.com/import-js/eslint-plugin-import/issues/1341 + "import/named": "off", + + // ensure imports point to files/modules that can be resolved + "import/no-unresolved": "off", + }, + settings: { + // Append 'ts' extensions to 'import/extensions' setting + "import/extensions": [...getFilesGlobs("js_and_ts"), ...getFilesGlobs("jsx_and_tsx")].map((extension) => extension.replace("**/*", "")), + + // Resolve type definition packages + "import/external-module-folders": ["node_modules", "node_modules/@types"], + + // Apply special parsing for TypeScript files + "import/parsers": { + "@typescript-eslint/parser": getFilesGlobs("ts").map((extension) => extension.replace("**/*", "")), + }, + + ...tsconfigPath + ? { + // Append 'ts' extensions to 'import/resolver' setting + "import/resolver": { + node: true, + typescript: { + // always try to resolve types under `@types` directory even it doesn't contain any source code, like `@types/unist` + alwaysTryTypes: true, + project: tsconfigPath, + }, + }, + } + : { + "import/resolver": { + node: true, + // You will also need to install and configure the TypeScript resolver + // See also https://github.com/import-js/eslint-import-resolver-typescript#configuration + typescript: true, + }, + }, + }, + }); + } + + return rules; + }, +); diff --git a/packages/eslint-config/src/config/plugins/javascript.ts b/packages/eslint-config/src/config/plugins/javascript.ts new file mode 100644 index 000000000..e75d995d8 --- /dev/null +++ b/packages/eslint-config/src/config/plugins/javascript.ts @@ -0,0 +1,76 @@ +import globals from "globals"; + +import type { OptionsPackageJson } from "../../types"; +import { createConfig } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; + +export default createConfig("js", async (config) => { + const { packageJson } = config; + + const eslintJs = await interopDefault(import("@eslint/js")); + + return [ + { + languageOptions: { + ecmaVersion: 2022, + globals: { + ...globals.browser, + ...globals.es2021, + ...globals.node, + document: "readonly", + navigator: "readonly", + window: "readonly", + ...packageJson.type === "module" + ? { + __dirname: "off", + __filename: "off", + exports: "off", + require: "off", + } + : { + __dirname: true, + __filename: true, + exports: true, + require: true, + }, + }, + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + ecmaVersion: 2022, + sourceType: "module", + }, + sourceType: "module", + }, + linterOptions: { + reportUnusedDisableDirectives: true, + }, + name: "anolilab/javascript/setup", + }, + eslintJs.configs.recommended, + { + files: ["**/*.cjs"], + languageOptions: { + // inside *.cjs files. restore commonJS "globals" + globals: { + __dirname: true, + __filename: true, + exports: true, + require: true, + }, + }, + }, + { + files: ["**/*.mjs"], + languageOptions: { + globals: { + __dirname: "off", + __filename: "off", + exports: "off", + require: "off", + }, + }, + }, + ]; +}); diff --git a/packages/eslint-config/src/config/plugins/jest-async.ts b/packages/eslint-config/src/config/plugins/jest-async.ts deleted file mode 100644 index 2bbb4e6f7..000000000 --- a/packages/eslint-config/src/config/plugins/jest-async.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { Linter } from "eslint"; - -const config: Linter.Config = { - plugins: ["jest-async"], - rules: { - "jest-async/expect-return": "error", - }, -}; - -export default config; diff --git a/packages/eslint-config/src/config/plugins/jest-dom.ts b/packages/eslint-config/src/config/plugins/jest-dom.ts deleted file mode 100644 index e6fcb91c0..000000000 --- a/packages/eslint-config/src/config/plugins/jest-dom.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Linter } from "eslint"; - -const config: Linter.Config = { - extends: ["plugin:jest-dom/recommended"], -}; - -export default config; diff --git a/packages/eslint-config/src/config/plugins/jest-formatting.ts b/packages/eslint-config/src/config/plugins/jest-formatting.ts deleted file mode 100644 index a0e44d73c..000000000 --- a/packages/eslint-config/src/config/plugins/jest-formatting.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Linter } from "eslint"; - -const config: Linter.Config = { - extends: ["plugin:jest-formatting/recommended"], -}; - -export default config; diff --git a/packages/eslint-config/src/config/plugins/jest.ts b/packages/eslint-config/src/config/plugins/jest.ts deleted file mode 100644 index 2ac47f2ba..000000000 --- a/packages/eslint-config/src/config/plugins/jest.ts +++ /dev/null @@ -1,67 +0,0 @@ -import type { Linter } from "eslint"; -import globals from "globals"; - -const config: Linter.Config = { - overrides: [ - { - files: ["setupJest.js"], - rules: { - "import/no-extraneous-dependencies": "off", - }, - }, - { - env: { - es6: true, - jest: true, - node: true, - }, - extends: ["plugin:jest/recommended", "plugin:jest/style"], - files: [ - // Test files - "**/*.spec.{js,ts,tsx}", - "**/*.test.{js,ts,tsx}", - "**/test/*.{js,ts,tsx}", - - // Facebook convention - "**/__mocks__/*.{js,ts,tsx}", - "**/__tests__/*.{js,ts,tsx}", - ], - globals: { - ...globals.jest, - }, - plugins: ["jest"], - rules: { - "@typescript-eslint/ban-ts-comment": "off", - - "@typescript-eslint/no-empty-function": "off", - - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/no-object-literal-type-assertion": "off", - // you should turn the original rule off *only* for test files - "@typescript-eslint/unbound-method": "off", - "import/default": "off", - // Relax rules that are known to be slow and less useful in a test context - "import/namespace": "off", - "import/no-duplicates": "off", - - // Relax rules that makes writing tests easier - "import/no-named-as-default-member": "off", - "jest/consistent-test-it": ["error", { fn: "it" }], - "jest/no-disabled-tests": "off", - "jest/no-duplicate-hooks": "error", - - "jest/no-test-return-statement": "error", - "jest/prefer-hooks-in-order": "error", - "jest/prefer-hooks-on-top": "error", - "jest/prefer-strict-equal": "error", - "jest/prefer-to-have-length": "error", - - // Disabled this rule because jest doc blocks clash with jsdoc/check-tag-names - "jsdoc/check-tag-names": "off", - }, - }, - ], -}; - -export default config; diff --git a/packages/eslint-config/src/config/plugins/jsdoc.ts b/packages/eslint-config/src/config/plugins/jsdoc.ts index 682aaa18f..37aead5d7 100644 --- a/packages/eslint-config/src/config/plugins/jsdoc.ts +++ b/packages/eslint-config/src/config/plugins/jsdoc.ts @@ -1,31 +1,97 @@ -import { hasDependency, hasDevDependency, hasTypescript } from "@anolilab/package-json-utils"; -import type { Linter } from "eslint"; - -import { consoleLog } from "../../utils/loggers"; - -if (global.anolilabEslintConfigJsDocRules === undefined && hasTypescript) { - if (hasDependency("eslint-plugin-tsdoc") || hasDevDependency("eslint-plugin-tsdoc")) { - consoleLog("\nFound eslint-plugin-tsdoc as dependency, disabling the jsdoc rules for *.ts and *.tsx files."); - } else { - global.anolilabEslintConfigJsDocRules = [ - { - extends: ["plugin:jsdoc/recommended-typescript-error"], - files: ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"], - plugins: ["jsdoc"], - }, - ]; +import { hasPackageJsonAnyDependency } from "@visulima/package"; + +import type { + OptionsFiles, + OptionsOverrides, + OptionsPackageJson, + OptionsSilentConsoleLogs, + OptionsStylistic, + OptionsTypescript, + TypedFlatConfigItem, +} from "../../types"; +import { createConfig, getFilesGlobs } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; + +export default createConfig("js", async (config, oFiles) => { + const { + files = oFiles, + jsx = false, + overrides = {}, + packageJson, + silent, + stylistic = true, + typescript, + } = config; + + const jsdocPlugin = await interopDefault(import("eslint-plugin-jsdoc")); + + const hasTsDocumentPlugin = hasPackageJsonAnyDependency(packageJson, ["eslint-plugin-tsdoc"]); + + if (hasTsDocumentPlugin && !silent) { + // eslint-disable-next-line no-console + console.info("\nFound eslint-plugin-tsdoc as dependency, disabling the jsdoc rules for *.ts and *.tsx files."); } -} -const config: Linter.Config = { - overrides: [ + const definedTags = ["remarks", "openapi"]; + const excludeTags = ["openapi"]; + + const rules: TypedFlatConfigItem[] = [ + { + name: "anolilab/jsdoc/setup", + plugins: { + jsdoc: jsdocPlugin, + }, + }, { - extends: ["plugin:jsdoc/recommended-error"], - files: ["**/*.js", "**/*.jsx", "**/*.mjs", "**/*.cjs"], - plugins: ["jsdoc"], + files, + name: "anolilab/jsdoc/js-rules", + rules: { + ...jsdocPlugin.configs["flat/recommended-error"].rules, + + "jsdoc/check-indentation": ["error", { excludeTags }], + "jsdoc/check-tag-names": ["error", { + definedTags, + jsxTags: jsx, + }], + + ...overrides, + + ...stylistic + ? { + "jsdoc/check-alignment": "warn", + "jsdoc/multiline-blocks": "warn", + } + : {}, + }, }, - ...(global.anolilabEslintConfigJsDocRules ?? []), - ], -}; + ]; + + if (typescript && !hasTsDocumentPlugin) { + rules.push({ + files: getFilesGlobs("ts"), + name: "anolilab/jsdoc/ts-rules", + rules: { + ...jsdocPlugin.configs["flat/contents-typescript-error"].rules, + ...jsdocPlugin.configs["flat/logical-typescript-error"].rules, + ...jsdocPlugin.configs["flat/stylistic-typescript-error"].rules, + + "jsdoc/check-indentation": ["error", { excludeTags }], + "jsdoc/check-tag-names": ["error", { + definedTags, + jsxTags: jsx, + }], + + ...overrides, + + ...stylistic + ? { + "jsdoc/check-alignment": "warn", + "jsdoc/multiline-blocks": "warn", + } + : {}, + }, + }); + } -export default config; + return rules; +}); diff --git a/packages/eslint-config/src/config/plugins/jsonc.ts b/packages/eslint-config/src/config/plugins/jsonc.ts index f17f7e755..f89c9f5c9 100644 --- a/packages/eslint-config/src/config/plugins/jsonc.ts +++ b/packages/eslint-config/src/config/plugins/jsonc.ts @@ -1,120 +1,368 @@ -import { env } from "node:process"; - -import { hasDependency, hasDevDependency } from "@anolilab/package-json-utils"; +import { hasPackageJsonAnyDependency } from "@visulima/package"; import type { Linter } from "eslint"; -import anolilabEslintConfig from "../../utils/eslint-config"; -import { consoleLog } from "../../utils/loggers"; - -const extendedPlugins: string[] = []; +import type { + OptionsHasPrettier, + OptionsOverrides, + OptionsPackageJson, + OptionsSilentConsoleLogs, + OptionsStylistic, + TypedFlatConfigItem, +} from "../../types"; +import interopDefault from "../../utils/interop-default"; -if (hasDependency("prettier") || hasDevDependency("prettier")) { - extendedPlugins.push("plugin:jsonc/prettier"); -} +const jsonc = async ( + config: OptionsHasPrettier & OptionsOverrides & OptionsPackageJson & OptionsSilentConsoleLogs & OptionsStylistic, +): Promise => { + const { + overrides, + packageJson, + prettier, + silent, + stylistic = true, + } = config; + const { indent = 4 } = typeof stylistic === "boolean" ? {} : stylistic; -if (!global.hasAnolilabEsLintConfigJsoncPackageJsonSort && (hasDependency("sort-package-json") || hasDevDependency("sort-package-json"))) { - global.hasAnolilabEsLintConfigJsoncPackageJsonSort = true; + const jsoncPlugin = await interopDefault(import("eslint-plugin-jsonc")); - let showLog: boolean = env["DISABLE_INFO_ON_DISABLING_JSONC_SORT_KEYS_RULE"] !== "true"; + const hasSortPackageJson = hasPackageJsonAnyDependency(packageJson, ["sort-package-json"]); - if (showLog && anolilabEslintConfig["info_on_disabling_jsonc_sort_keys_rule"] !== undefined) { - showLog = anolilabEslintConfig["info_on_disabling_jsonc_sort_keys_rule"] as boolean; - } - - if (showLog) { - consoleLog(`\n@anolilab/eslint-config found "sort-package-json" package. \n + if (hasSortPackageJson && !silent) { + // eslint-disable-next-line no-console + console.info(`\n@anolilab/eslint-config found "sort-package-json" package. \n Following rules are disabled: jsonc/sort-keys for all package.json files. \n`); } -} -const config: Linter.Config = { - overrides: [ - { - extends: extendedPlugins, - files: ["**/*.json", "**/*.json5", "**/*.jsonc"], - parser: "jsonc-eslint-parser", - }, + return [ + ...jsoncPlugin.configs["flat/base"], { - extends: ["plugin:jsonc/recommended-with-json5"], files: ["**/*.json5"], + name: "anolilab/jsonc/json5-rules", + rules: (jsoncPlugin.configs["recommended-with-json5"] as Linter.Config).rules, }, { - extends: ["plugin:jsonc/recommended-with-jsonc"], files: ["**/*.jsonc"], + name: "anolilab/jsonc/jsonc-rules", + rules: (jsoncPlugin.configs["recommended-with-jsonc"] as Linter.Config).rules, }, { - extends: ["plugin:jsonc/recommended-with-json"], files: ["**/*.json"], + name: "anolilab/jsonc/json-rules", + rules: (jsoncPlugin.configs["recommended-with-json"] as Linter.Config).rules, }, { - extends: ["plugin:jsonc/recommended-with-json"], - files: ["package.json"], + files: ["package.json", "**/package.json"], + name: "anolilab/jsonc/package.json-rules", rules: { - // When the package "sort-package-json" is installed, we disable the rule "jsonc/sort-keys" because, the package "sort-package-json" is responsible for sorting the keys. - "jsonc/sort-keys": global.hasAnolilabEsLintConfigJsoncPackageJsonSort + "jsonc/sort-array-values": hasSortPackageJson + ? "off" + : [ + "error", + { + order: { type: "asc" }, + pathPattern: "^files$", + }, + ], + + // When the package "sort-package-json" is installed, we disable the rule "jsonc/sort-keys" because, + // the package "sort-package-json" is responsible for sorting the keys. + "jsonc/sort-keys": hasSortPackageJson ? "off" : [ "error", { order: [ - "publisher", + "$schema", "name", "displayName", - "type", "version", "private", - "packageManager", "description", - "author", - "license", - "funding", + "categories", + "keywords", "homepage", - "repository", "bugs", - "keywords", - "categories", + "repository", + "funding", + "license", + "qna", + "author", + "maintainers", + "contributors", + "publisher", "sideEffects", + "type", + "imports", "exports", "main", - "module", - "unpkg", + "svelte", + "umd:main", "jsdelivr", + "unpkg", + "module", + "source", + "jsnext:main", + "browser", + "react-native", "types", "typesVersions", + "typings", + "style", + "example", + "examplestyle", + "assets", "bin", - "icon", + "man", + "directories", "files", - "engines", - "activationEvents", - "contributes", + "workspaces", + "binary", "scripts", - "peerDependencies", - "peerDependenciesMeta", - "dependencies", - "optionalDependencies", - "devDependencies", - "pnpm", - "overrides", - "resolutions", + "betterScripts", + "contributes", + "activationEvents", "husky", "simple-git-hooks", + "pre-commit", + "commitlint", "lint-staged", + "nano-staged", + "config", + "nodemonConfig", + "browserify", + "babel", + "browserslist", + "xo", + "prettier", "eslintConfig", + "eslintIgnore", + "npmpackagejsonlint", + "release", + "remarkConfig", + "stylelint", + "ava", + "jest", + "mocha", + "nyc", + "tap", + "oclif", + "resolutions", + "dependencies", + "devDependencies", + "dependenciesMeta", + "peerDependencies", + "peerDependenciesMeta", + "optionalDependencies", + "bundledDependencies", + "bundleDependencies", + "extensionPack", + "extensionDependencies", + "flat", + "packageManager", + "engines", + "engineStrict", + "volta", + "languageName", + "os", + "cpu", + "preferGlobal", + "publishConfig", + "icon", + "badges", + "galleryBanner", + "preview", + "markdown", + "pnpm", ], pathPattern: "^$", }, { order: { type: "asc" }, - pathPattern: "^(?:dev|peer|optional|bundled)?[Dd]ependencies$", + pathPattern: "^(?:dev|peer|optional|bundled)?[Dd]ependencies(Meta)?$", }, { - order: ["types", "require", "import"], + order: { type: "asc" }, + pathPattern: "^(?:resolutions|overrides|pnpm.overrides)$", + }, + { + order: ["types", "import", "require", "default"], pathPattern: "^exports.*$", }, + { + order: [ + "applypatch-msg", + "pre-applypatch", + "post-applypatch", + "pre-commit", + "pre-merge-commit", + "prepare-commit-msg", + "commit-msg", + "post-commit", + "pre-rebase", + "post-checkout", + "post-merge", + "pre-push", + "pre-receive", + "update", + "post-receive", + "post-update", + "push-to-checkout", + "pre-auto-gc", + "post-rewrite", + "sendemail-validate", + "fsmonitor-watchman", + "p4-pre-submit", + "post-index-chang", + ], + pathPattern: "^(?:gitHooks|husky|simple-git-hooks)$", + }, + { + order: ["build", "preinstall", "install", "postinstall", "lint", { order: { type: "asc" } }], + pathPattern: "^scripts$", + }, ], }, }, - ], + { + files: ["**/tsconfig.json", "**/tsconfig.*.json"], + name: "anolilab/jsonc/tsconfig-json", + rules: { + "jsonc/sort-keys": [ + "error", + { + order: ["extends", "compilerOptions", "references", "files", "include", "exclude"], + pathPattern: "^$", + }, + { + order: [ + /* Projects */ + "incremental", + "composite", + "tsBuildInfoFile", + "disableSourceOfProjectReferenceRedirect", + "disableSolutionSearching", + "disableReferencedProjectLoad", + /* Language and Environment */ + "target", + "jsx", + "jsxFactory", + "jsxFragmentFactory", + "jsxImportSource", + "lib", + "moduleDetection", + "noLib", + "reactNamespace", + "useDefineForClassFields", + "emitDecoratorMetadata", + "experimentalDecorators", + "libReplacement", + /* Modules */ + "baseUrl", + "rootDir", + "rootDirs", + "customConditions", + "module", + "moduleResolution", + "moduleSuffixes", + "noResolve", + "paths", + "resolveJsonModule", + "resolvePackageJsonExports", + "resolvePackageJsonImports", + "typeRoots", + "types", + "allowArbitraryExtensions", + "allowImportingTsExtensions", + "allowUmdGlobalAccess", + /* JavaScript Support */ + "allowJs", + "checkJs", + "maxNodeModuleJsDepth", + /* Type Checking */ + "strict", + "strictBindCallApply", + "strictFunctionTypes", + "strictNullChecks", + "strictPropertyInitialization", + "allowUnreachableCode", + "allowUnusedLabels", + "alwaysStrict", + "exactOptionalPropertyTypes", + "noFallthroughCasesInSwitch", + "noImplicitAny", + "noImplicitOverride", + "noImplicitReturns", + "noImplicitThis", + "noPropertyAccessFromIndexSignature", + "noUncheckedIndexedAccess", + "noUnusedLocals", + "noUnusedParameters", + "useUnknownInCatchVariables", + /* Emit */ + "declaration", + "declarationDir", + "declarationMap", + "downlevelIteration", + "emitBOM", + "emitDeclarationOnly", + "importHelpers", + "importsNotUsedAsValues", + "inlineSourceMap", + "inlineSources", + "mapRoot", + "newLine", + "noEmit", + "noEmitHelpers", + "noEmitOnError", + "outDir", + "outFile", + "preserveConstEnums", + "preserveValueImports", + "removeComments", + "sourceMap", + "sourceRoot", + "stripInternal", + /* Interop Constraints */ + "allowSyntheticDefaultImports", + "esModuleInterop", + "forceConsistentCasingInFileNames", + "isolatedDeclarations", + "isolatedModules", + "preserveSymlinks", + "verbatimModuleSyntax", + "erasableSyntaxOnly", + /* Completeness */ + "skipDefaultLibCheck", + "skipLibCheck", + ], + pathPattern: "^compilerOptions$", + }, + ], + }, + }, + ...prettier ? jsoncPlugin.configs["flat/prettier"] : [], + { + files: ["**/*.json", "**/*.jsonc", "**/*.json5"], + rules: { + ...stylistic + ? { + "jsonc/array-bracket-spacing": ["error", "never"], + "jsonc/comma-dangle": ["error", "never"], + "jsonc/comma-style": ["error", "last"], + "jsonc/indent": ["error", indent], + "jsonc/key-spacing": ["error", { afterColon: true, beforeColon: false }], + "jsonc/object-curly-newline": ["error", { consistent: true, multiline: true }], + "jsonc/object-curly-spacing": ["error", "always"], + "jsonc/object-property-newline": ["error", { allowMultiplePropertiesPerLine: true }], + "jsonc/quote-props": "error", + "jsonc/quotes": "error", + } + : {}, + + ...overrides, + }, + }, + ]; }; -export default config; +export default jsonc; diff --git a/packages/eslint-config/src/config/plugins/jsx-a11y.ts b/packages/eslint-config/src/config/plugins/jsx-a11y.ts index 4d5589b7e..00455dfb7 100644 --- a/packages/eslint-config/src/config/plugins/jsx-a11y.ts +++ b/packages/eslint-config/src/config/plugins/jsx-a11y.ts @@ -1,256 +1,268 @@ -import type { Linter } from "eslint"; - +import type { OptionsFiles, OptionsOverrides } from "../../types"; import { createConfig } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; -const config: Linter.Config = createConfig("jsx_and_tsx", { - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - }, - - plugins: ["jsx-a11y"], - - rules: { - // Enforce that anchors have content - // disabled; rule is deprecated - "jsx-a11y/accessible-emoji": "off", - - // Require ARIA roles to be valid and non-abstract - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/alt-text.md - "jsx-a11y/alt-text": [ - "error", - { - area: [], - elements: ["img", "object", "area", 'input[type="image"]'], - img: [], - 'input[type="image"]': [], - object: [], - }, - ], - - // Enforce all aria-* props are valid. - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/anchor-has-content.md - "jsx-a11y/anchor-has-content": ["error", { components: [] }], - - // Enforce ARIA state and property values are valid. - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/anchor-is-valid.md - "jsx-a11y/anchor-is-valid": [ - "error", - { - aspects: ["noHref", "invalidHref", "preferButton"], - components: ["A", "LinkTo", "Link"], - specialLink: ["to"], - }, - ], - - // Enforce that elements that do not support ARIA roles, states, and - // properties do not have those attributes. - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/aria-activedescendant-has-tabindex.md - "jsx-a11y/aria-activedescendant-has-tabindex": "error", - - // Enforce that all elements that require alternative text have meaningful information - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/aria-props.md - "jsx-a11y/aria-props": "error", - - // Prevent img alt text from containing redundant words like "image", "picture", or "photo" - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/aria-proptypes.md - "jsx-a11y/aria-proptypes": "error", - - // require that JSX labels use "htmlFor" - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/label-has-for.md - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/aria-role.md - "jsx-a11y/aria-role": ["error", { ignoreNonDOM: false }], - - // Enforce that a label tag has a text label and an associated control. - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/aria-unsupported-elements.md - "jsx-a11y/aria-unsupported-elements": "error", - - // Enforce that a control (an interactive element) has a text label. - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/29c68596b15c4ff0a40daae6d4a2670e36e37d35/docs/rules/autocomplete-valid.md - "jsx-a11y/autocomplete-valid": [ - "off", - { - inputComponents: [], - }, - ], - - // require that mouseover/out come with focus/blur, for keyboard-only users - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/click-events-have-key-events.md - "jsx-a11y/click-events-have-key-events": "error", - - // Prevent use of `accessKey` - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/control-has-associated-label.md - "jsx-a11y/control-has-associated-label": [ - "error", - { - controlComponents: [], - depth: 5, - ignoreElements: ["audio", "canvas", "embed", "input", "textarea", "tr", "video"], - ignoreRoles: ["grid", "listbox", "menu", "menubar", "radiogroup", "row", "tablist", "toolbar", "tree", "treegrid"], - labelAttributes: ["label"], - }, - ], - - // require onBlur instead of onChange - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/heading-has-content.md - "jsx-a11y/heading-has-content": ["error", { components: [""] }], - - // Elements with an interactive role and interaction handlers must be focusable - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/html-has-lang.md - "jsx-a11y/html-has-lang": "error", - - // Enforce that elements with ARIA roles must have all required attributes - // for that role. - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/iframe-has-title.md - "jsx-a11y/iframe-has-title": "error", - - // Enforce that elements with explicit or implicit roles defined contain - // only aria-* properties supported by that role. - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/img-redundant-alt.md - "jsx-a11y/img-redundant-alt": "error", - - // Enforce tabIndex value is not greater than zero. - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/interactive-supports-focus.md - "jsx-a11y/interactive-supports-focus": "error", - - // ensure tags have content and are not aria-hidden - // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/b800f40a2a69ad48015ae9226fbe879f946757ed/docs/rules/label-has-associated-control.md - "jsx-a11y/label-has-associated-control": [ - "error", - { - assert: "both", - controlComponents: [], - depth: 25, - labelAttributes: [], - labelComponents: [], - }, - ], - - // require HTML elements to have a "lang" prop - // deprecated: replaced by `label-has-associated-control` rule - "jsx-a11y/label-has-for": [ - "off", - { - allowChildren: false, - components: [], - required: { - every: ["nesting", "id"], +export default createConfig("jsx_and_tsx", async (config, oFiles) => { + const { files = oFiles, overrides } = config; + + const jsxA11yPlugin = await interopDefault(import("eslint-plugin-jsx-a11y")); + + return [ + { + files, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, }, }, - ], - - // require HTML element's lang prop to be valid - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/lang.md - "jsx-a11y/lang": "error", - - // prevent distracting elements, like and - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/media-has-caption.md - "jsx-a11y/media-has-caption": [ - "error", - { - audio: [], - track: [], - video: [], - }, - ], - - // only allow to have the "scope" attr - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/mouse-events-have-key-events.md - "jsx-a11y/mouse-events-have-key-events": "error", - - // require onClick be accompanied by onKeyUp/onKeyDown/onKeyPress - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-access-key.md - "jsx-a11y/no-access-key": "error", - - // Enforce that DOM elements without semantic behavior not have interaction handlers - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-autofocus.md - "jsx-a11y/no-autofocus": ["error", { ignoreNonDOM: true }], - - // A non-interactive element does not support event handlers (mouse and key handlers) - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-distracting-elements.md - "jsx-a11y/no-distracting-elements": [ - "error", - { - elements: ["marquee", "blink"], + name: "anolilab/jsx-a11y/setup", + plugins: { + "jsx-a11y": jsxA11yPlugin, }, - ], - - // ensure emoji are accessible - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/accessible-emoji.md - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-interactive-element-to-noninteractive-role.md - "jsx-a11y/no-interactive-element-to-noninteractive-role": [ - "error", - { - tr: ["none", "presentation"], + rules: { + // Enforce that anchors have content + // disabled; rule is deprecated + "jsx-a11y/accessible-emoji": "off", + + // Require ARIA roles to be valid and non-abstract + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/alt-text.md + "jsx-a11y/alt-text": [ + "error", + { + area: [], + elements: ["img", "object", "area", "input[type=\"image\"]"], + img: [], + "input[type=\"image\"]": [], + object: [], + }, + ], + + // Enforce all aria-* props are valid. + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/anchor-has-content.md + "jsx-a11y/anchor-has-content": ["error", { components: [] }], + + // Enforce ARIA state and property values are valid. + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/anchor-is-valid.md + "jsx-a11y/anchor-is-valid": [ + "error", + { + aspects: ["noHref", "invalidHref", "preferButton"], + components: ["A", "LinkTo", "Link"], + specialLink: ["to"], + }, + ], + + // Enforce that elements that do not support ARIA roles, states, and + // properties do not have those attributes. + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/aria-activedescendant-has-tabindex.md + "jsx-a11y/aria-activedescendant-has-tabindex": "error", + + // Enforce that all elements that require alternative text have meaningful information + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/aria-props.md + "jsx-a11y/aria-props": "error", + + // Prevent img alt text from containing redundant words like "image", "picture", or "photo" + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/aria-proptypes.md + "jsx-a11y/aria-proptypes": "error", + + // require that JSX labels use "htmlFor" + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/label-has-for.md + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/aria-role.md + "jsx-a11y/aria-role": ["error", { ignoreNonDOM: false }], + + // Enforce that a label tag has a text label and an associated control. + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/aria-unsupported-elements.md + "jsx-a11y/aria-unsupported-elements": "error", + + // Enforce that a control (an interactive element) has a text label. + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/29c68596b15c4ff0a40daae6d4a2670e36e37d35/docs/rules/autocomplete-valid.md + "jsx-a11y/autocomplete-valid": [ + "off", + { + inputComponents: [], + }, + ], + + // require that mouseover/out come with focus/blur, for keyboard-only users + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/click-events-have-key-events.md + "jsx-a11y/click-events-have-key-events": "error", + + // Prevent use of `accessKey` + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/control-has-associated-label.md + "jsx-a11y/control-has-associated-label": [ + "error", + { + controlComponents: [], + depth: 5, + ignoreElements: ["audio", "canvas", "embed", "input", "textarea", "tr", "video"], + ignoreRoles: ["grid", "listbox", "menu", "menubar", "radiogroup", "row", "tablist", "toolbar", "tree", "treegrid"], + labelAttributes: ["label"], + }, + ], + + // require onBlur instead of onChange + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/heading-has-content.md + "jsx-a11y/heading-has-content": ["error", { components: [""] }], + + // Elements with an interactive role and interaction handlers must be focusable + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/html-has-lang.md + "jsx-a11y/html-has-lang": "error", + + // Enforce that elements with ARIA roles must have all required attributes + // for that role. + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/iframe-has-title.md + "jsx-a11y/iframe-has-title": "error", + + // Enforce that elements with explicit or implicit roles defined contain + // only aria-* properties supported by that role. + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/img-redundant-alt.md + "jsx-a11y/img-redundant-alt": "error", + + // Enforce tabIndex value is not greater than zero. + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/interactive-supports-focus.md + "jsx-a11y/interactive-supports-focus": "error", + + // ensure tags have content and are not aria-hidden + // https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/b800f40a2a69ad48015ae9226fbe879f946757ed/docs/rules/label-has-associated-control.md + "jsx-a11y/label-has-associated-control": [ + "error", + { + assert: "both", + controlComponents: [], + depth: 25, + labelAttributes: [], + labelComponents: [], + }, + ], + + // require HTML elements to have a "lang" prop + // deprecated: replaced by `label-has-associated-control` rule + "jsx-a11y/label-has-for": [ + "off", + { + allowChildren: false, + components: [], + required: { + every: ["nesting", "id"], + }, + }, + ], + + // require HTML element's lang prop to be valid + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/lang.md + "jsx-a11y/lang": "error", + + // prevent distracting elements, like and + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/media-has-caption.md + "jsx-a11y/media-has-caption": [ + "error", + { + audio: [], + track: [], + video: [], + }, + ], + + // only allow to have the "scope" attr + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/mouse-events-have-key-events.md + "jsx-a11y/mouse-events-have-key-events": "error", + + // require onClick be accompanied by onKeyUp/onKeyDown/onKeyPress + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-access-key.md + "jsx-a11y/no-access-key": "error", + + // Enforce that DOM elements without semantic behavior not have interaction handlers + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-autofocus.md + "jsx-a11y/no-autofocus": ["error", { ignoreNonDOM: true }], + + // A non-interactive element does not support event handlers (mouse and key handlers) + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-distracting-elements.md + "jsx-a11y/no-distracting-elements": [ + "error", + { + elements: ["marquee", "blink"], + }, + ], + + // ensure emoji are accessible + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/accessible-emoji.md + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-interactive-element-to-noninteractive-role.md + "jsx-a11y/no-interactive-element-to-noninteractive-role": [ + "error", + { + tr: ["none", "presentation"], + }, + ], + + // elements with aria-activedescendant must be tabbable + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-noninteractive-element-interactions.md + "jsx-a11y/no-noninteractive-element-interactions": [ + "error", + { + handlers: ["onClick", "onMouseDown", "onMouseUp", "onKeyPress", "onKeyDown", "onKeyUp"], + }, + ], + + // ensure iframe elements have a unique title + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-noninteractive-element-to-interactive-role.md + "jsx-a11y/no-noninteractive-element-to-interactive-role": [ + "error", + { + li: ["menuitem", "option", "row", "tab", "treeitem"], + ol: ["listbox", "menu", "menubar", "radiogroup", "tablist", "tree", "treegrid"], + table: ["grid"], + td: ["gridcell"], + ul: ["listbox", "menu", "menubar", "radiogroup", "tablist", "tree", "treegrid"], + }, + ], + + // prohibit autoFocus prop + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-noninteractive-tabindex.md + "jsx-a11y/no-noninteractive-tabindex": [ + "error", + { + roles: ["tabpanel"], + tags: [], + }, + ], + + // ensure HTML elements do not specify redundant ARIA roles + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-onchange.md + "jsx-a11y/no-onchange": "off", + + // media elements must have captions + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-redundant-roles.md + "jsx-a11y/no-redundant-roles": "error", + + // WAI-ARIA roles should not be used to convert an interactive element to non-interactive + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-static-element-interactions.md + "jsx-a11y/no-static-element-interactions": [ + "error", + { + handlers: ["onClick", "onMouseDown", "onMouseUp", "onKeyPress", "onKeyDown", "onKeyUp"], + }, + ], + + // WAI-ARIA roles should not be used to convert a non-interactive element to interactive + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/role-has-required-aria-props.md + "jsx-a11y/role-has-required-aria-props": "error", + + // Tab key navigation should be limited to elements on the page that can be interacted with. + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/role-supports-aria-props.md + "jsx-a11y/role-supports-aria-props": "error", + + // ensure tags are valid + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/scope.md + "jsx-a11y/scope": "error", + + // Ensure the autocomplete attribute is correct and suitable for the form field it is used with + // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/tabindex-no-positive.md + "jsx-a11y/tabindex-no-positive": "error", + + ...overrides, }, - ], - - // elements with aria-activedescendant must be tabbable - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-noninteractive-element-interactions.md - "jsx-a11y/no-noninteractive-element-interactions": [ - "error", - { - handlers: ["onClick", "onMouseDown", "onMouseUp", "onKeyPress", "onKeyDown", "onKeyUp"], - }, - ], - - // ensure iframe elements have a unique title - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-noninteractive-element-to-interactive-role.md - "jsx-a11y/no-noninteractive-element-to-interactive-role": [ - "error", - { - li: ["menuitem", "option", "row", "tab", "treeitem"], - ol: ["listbox", "menu", "menubar", "radiogroup", "tablist", "tree", "treegrid"], - table: ["grid"], - td: ["gridcell"], - ul: ["listbox", "menu", "menubar", "radiogroup", "tablist", "tree", "treegrid"], - }, - ], - - // prohibit autoFocus prop - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-noninteractive-tabindex.md - "jsx-a11y/no-noninteractive-tabindex": [ - "error", - { - roles: ["tabpanel"], - tags: [], - }, - ], - - // ensure HTML elements do not specify redundant ARIA roles - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-onchange.md - "jsx-a11y/no-onchange": "off", - - // media elements must have captions - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-redundant-roles.md - "jsx-a11y/no-redundant-roles": "error", - - // WAI-ARIA roles should not be used to convert an interactive element to non-interactive - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/no-static-element-interactions.md - "jsx-a11y/no-static-element-interactions": [ - "error", - { - handlers: ["onClick", "onMouseDown", "onMouseUp", "onKeyPress", "onKeyDown", "onKeyUp"], - }, - ], - - // WAI-ARIA roles should not be used to convert a non-interactive element to interactive - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/role-has-required-aria-props.md - "jsx-a11y/role-has-required-aria-props": "error", - - // Tab key navigation should be limited to elements on the page that can be interacted with. - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/role-supports-aria-props.md - "jsx-a11y/role-supports-aria-props": "error", - - // ensure tags are valid - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/scope.md - "jsx-a11y/scope": "error", - - // Ensure the autocomplete attribute is correct and suitable for the form field it is used with - // https://github.com/jsx-eslint/eslint-plugin-jsx-a11y/blob/main/docs/rules/tabindex-no-positive.md - "jsx-a11y/tabindex-no-positive": "error", - }, + }, + ]; }); - -export default config; diff --git a/packages/eslint-config/src/config/plugins/markdown.ts b/packages/eslint-config/src/config/plugins/markdown.ts index 478df014b..b6b35bf5f 100644 --- a/packages/eslint-config/src/config/plugins/markdown.ts +++ b/packages/eslint-config/src/config/plugins/markdown.ts @@ -1,56 +1,90 @@ -import type { Linter } from "eslint"; +import { mergeProcessors, processorPassThrough } from "eslint-merge-processors"; -import { createConfigs } from "../../utils/create-config"; +import type { OptionsComponentExtensions, OptionsFiles, OptionsOverrides } from "../../types"; +import { createConfig, getFilesGlobs } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; +import parserPlain from "../../utils/parser-plain"; -const config: Linter.Config = createConfigs([ - { - config: { - extends: "plugin:markdown/recommended", - plugins: ["markdown"], - processor: "markdown/markdown", - rules: { - "no-cond-assign": "off", +export default createConfig("markdown", async (config, oFiles) => { + const { componentExts: componentExtensions = [], files = oFiles, overrides } = config; + + const markdown = await interopDefault(import("@eslint/markdown")); + + return [ + { + name: "anolilab/markdown/setup", + plugins: { + markdown, }, }, - type: "markdown", - }, - { - config: { - extends: "plugin:markdown/recommended", - parserOptions: { - ecmacFeatures: { - impliedStrict: true, + { + files, + ignores: getFilesGlobs("markdown_in_markdown"), + name: "anolilab/markdown/processor", + // `eslint-plugin-markdown` only creates virtual files for code blocks, + // but not the markdown file itself. We use `eslint-merge-processors` to + // add a pass-through processor for the markdown file itself. + processor: mergeProcessors([markdown.processors?.markdown, processorPassThrough]), + }, + { + files, + languageOptions: { + parser: parserPlain, + }, + name: "anolilab/markdown/parser", + rules: markdown.configs.recommended[0].rules, + }, + { + files: ["**/*.md/**/*.?([cm])[jt]s?(x)", ...componentExtensions.map((extension) => `**/*.md/**/*.${extension}`)], + languageOptions: { + parserOptions: { + ecmaFeatures: { + impliedStrict: true, + }, }, }, - plugins: ["markdown"], - processor: "markdown/markdown", + name: "anolilab/markdown/disables", rules: { - "@typescript-eslint/comma-dangle": "off", + "@stylistic/comma-dangle": "off", + "@stylistic/eol-last": "off", + "@stylistic/prefer-global/process": "off", + + "@typescript-eslint/consistent-type-imports": "off", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/no-namespace": "off", "@typescript-eslint/no-redeclare": "off", + "@typescript-eslint/no-require-imports": "off", + "@typescript-eslint/no-unused-expressions": "off", "@typescript-eslint/no-unused-vars": "off", "@typescript-eslint/no-use-before-define": "off", - "@typescript-eslint/no-var-requires": "off", - "global-require": "off", + "antfu/no-top-level-await": "off", - "import/no-unresolved": "off", - "import/order": "off", + "import/newline-after-import": "off", "no-alert": "off", "no-console": "off", - "no-restricted-imports": "off", + "no-labels": "off", + "no-lone-blocks": "off", + "no-restricted-syntax": "off", "no-undef": "off", + "no-unused-expressions": "off", + + "no-unused-labels": "off", "no-unused-vars": "off", - "prefer-reflect": "off", - "sonar/no-dead-store": "off", + "unicode-bom": "off", + + "unicorn/prefer-module": "off", + "unicorn/prefer-string-raw": "off", - strict: "off", + "unused-imports/no-unused-imports": "off", + + "unused-imports/no-unused-vars": "off", + + ...overrides, }, }, - type: "markdown_inline_js_jsx", - }, -]); - -export default config; + ]; +}); diff --git a/packages/eslint-config/src/config/plugins/mdx.ts b/packages/eslint-config/src/config/plugins/mdx.ts deleted file mode 100644 index a2f2f426c..000000000 --- a/packages/eslint-config/src/config/plugins/mdx.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type { Linter } from "eslint"; - -import { createConfig } from "../../utils/create-config"; - -// @see https://github.com/mdx-js/eslint-mdx/tree/master/packages/eslint-plugin-mdx -const config: Linter.Config = createConfig("mdx", { - extends: ["plugin:mdx/recommended"], - parser: "eslint-mdx", - parserOptions: { - ecmaVersion: "latest", - }, - processor: "mdx/remark", - rules: { - "@typescript-eslint/comma-dangle": "off", - "@typescript-eslint/no-redeclare": "off", - "@typescript-eslint/no-unused-vars": "off", - "@typescript-eslint/no-use-before-define": "off", - "@typescript-eslint/no-var-requires": "off", - - "global-require": "off", - - "import/namespace": "off", - - "import/no-extraneous-dependencies": "off", - "import/no-unresolved": "off", - "import/order": "off", - "no-alert": "off", - - "no-console": "off", - "no-restricted-imports": "off", - "no-undef": "off", - "no-unused-expressions": "off", - "no-unused-vars": "off", - "prefer-reflect": "off", - - "react/jsx-no-undef": "off", - "react-hooks/rules-of-hooks": "off", - - "sonar/no-dead-store": "off", - }, - settings: { - "mdx/code-blocks": true, - }, -}); - -export default config; diff --git a/packages/eslint-config/src/config/plugins/no-extend-native.ts b/packages/eslint-config/src/config/plugins/no-extend-native.ts deleted file mode 100644 index fa905eb68..000000000 --- a/packages/eslint-config/src/config/plugins/no-extend-native.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { Linter } from "eslint"; - -// @see https://github.com/dustinspecker/eslint-plugin-no-use-extend-native -const config: Linter.Config = { - plugins: ["no-use-extend-native"], - rules: { - "no-use-extend-native/no-use-extend-native": "error", - }, -}; - -export default config; diff --git a/packages/eslint-config/src/config/plugins/no-loops.ts b/packages/eslint-config/src/config/plugins/no-loops.ts deleted file mode 100644 index a9f9e8834..000000000 --- a/packages/eslint-config/src/config/plugins/no-loops.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { Linter } from "eslint"; - -// @see https://github.com/buildo/eslint-plugin-no-loops -const config: Linter.Config = { - plugins: ["no-loops"], - rules: { - "no-loops/no-loops": 2, - }, -}; - -export default config; diff --git a/packages/eslint-config/src/config/plugins/no-only-tests.ts b/packages/eslint-config/src/config/plugins/no-only-tests.ts deleted file mode 100644 index 277eab500..000000000 --- a/packages/eslint-config/src/config/plugins/no-only-tests.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { Linter } from "eslint"; - -import { createConfig } from "../../utils/create-config"; -import isInEditor from "../../utils/is-in-editor"; - -// @see https://github.com/francoismassart/eslint-plugin-tailwindcss, -const config: Linter.Config = createConfig("tests", { - plugins: ["no-only-tests"], - rules: { - "no-only-tests/no-only-tests": isInEditor ? "off" : "error", - }, -}); - -export default config; diff --git a/packages/eslint-config/src/config/plugins/no-secrets.ts b/packages/eslint-config/src/config/plugins/no-secrets.ts index d2df0f010..bf9389512 100644 --- a/packages/eslint-config/src/config/plugins/no-secrets.ts +++ b/packages/eslint-config/src/config/plugins/no-secrets.ts @@ -1,17 +1,28 @@ -import type { Linter } from "eslint"; +import type { OptionsOverrides, TypedFlatConfigItem } from "../../types"; +import interopDefault from "../../utils/interop-default"; // @see https://github.com/nickdeis/eslint-plugin-no-secrets -const config: Linter.Config = { - overrides: [ +const noSecrets = async (config: OptionsOverrides): Promise => { + const noSecretsPlugin = await interopDefault(import("eslint-plugin-no-secrets")); + + return [ { - excludedFiles: ["package.json", "**/package.json", "package-lock.json", "**/package-lock.json", "tsconfig.json", "**/tsconfig.json"], files: ["*", "*/**"], - plugins: ["no-secrets"], + ignores: ["package.json", "**/package.json", "package-lock.json", "**/package-lock.json", "tsconfig.json", "**/tsconfig.json"], + languageOptions: { + ecmaVersion: 6, + }, + name: "anolilab/no-secrets", + plugins: { + "no-secrets": noSecretsPlugin, + }, rules: { "no-secrets/no-secrets": "error", + + ...config.overrides, }, }, - ], + ]; }; -export default config; +export default noSecrets; diff --git a/packages/eslint-config/src/config/plugins/no-unsanitized.ts b/packages/eslint-config/src/config/plugins/no-unsanitized.ts index f91adc869..bb278663d 100644 --- a/packages/eslint-config/src/config/plugins/no-unsanitized.ts +++ b/packages/eslint-config/src/config/plugins/no-unsanitized.ts @@ -1,12 +1,26 @@ -import type { Linter } from "eslint"; +import type { OptionsFiles, OptionsOverrides } from "../../types"; +import { createConfig } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; // @see https://github.com/mozilla/eslint-plugin-no-unsanitized -const config: Linter.Config = { - plugins: ["no-unsanitized"], - rules: { - "no-unsanitized/method": "error", - "no-unsanitized/property": "error", - }, -}; - -export default config; +export default createConfig("js", async (config, oFiles) => { + const { files = oFiles, overrides } = config; + + const noUnsanitizedPlugin = await interopDefault(import("eslint-plugin-no-unsanitized")); + + return [ + { + files, + name: "anolilab/no-unsanitized/setup", + plugins: { + "no-unsanitized": noUnsanitizedPlugin, + }, + rules: { + "no-unsanitized/method": "error", + "no-unsanitized/property": "error", + + ...overrides, + }, + }, + ]; +}); diff --git a/packages/eslint-config/src/config/plugins/node.ts b/packages/eslint-config/src/config/plugins/node.ts index 8d73a38c6..497803111 100644 --- a/packages/eslint-config/src/config/plugins/node.ts +++ b/packages/eslint-config/src/config/plugins/node.ts @@ -1,93 +1,103 @@ -import { pkg } from "@anolilab/package-json-utils"; -import type { Linter } from "eslint"; +import type { OptionsFiles, OptionsOverrides, OptionsPackageJson } from "../../types"; +import { createConfig } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; -if (global.anolilabEslintConfigNodeRules === undefined && pkg?.engines?.["node"]) { - const version: string = pkg.engines["node"]; +// @see https://github.com/eslint-community/eslint-plugin-n +export default createConfig("all", async (config, oFiles) => { + const { files = oFiles, overrides, packageJson } = config; - global.anolilabEslintConfigNodeRules = { - "n/no-unsupported-features/es-builtins": ["error", { version }], - "n/no-unsupported-features/es-syntax": ["error", { ignores: ["modules"], version }], - "n/no-unsupported-features/node-builtins": ["error", { version }], - }; -} + const pluginNode = await interopDefault(import("eslint-plugin-n")); -// @see https://github.com/eslint-community/eslint-plugin-n -const config: Linter.Config = { - env: { - node: true, - }, - extends: ["plugin:n/recommended"], - parserOptions: { - ecmaVersion: 2021, - }, - plugins: ["n"], - rules: { - // https://eslint.org/docs/rules/global-require - "global-require": "error", - // enforce return after a callback - "n/callback-return": "off", - - // enforce the style of file extensions in import declarations - // This rule is buggy @see https://github.com/eslint-community/eslint-plugin-n/issues/21 - "n/file-extension-in-import": "off", - - // enforces error handling in callbacks (node environment) - "n/handle-callback-err": "off", - - // Redundant with `import/no-extraneous-dependencies`. - "n/no-extraneous-import": "off", - - // disallow require() expressions which import extraneous modules - "n/no-extraneous-require": "off", - - // require all requires be top-level - // Redundant with `import/no-unresolved`. - "n/no-missing-import": "off", // This rule is also buggy and doesn't support `node:`. - - // disallow require() expressions which import non-existence modules - "n/no-missing-require": "off", - - // disallow use of the Buffer() constructor - // disallow mixing regular variable and require declarations - "n/no-mixed-requires": [ - "error", - { - allowCall: true, - grouping: true, + const nodeVersion = packageJson?.engines?.["node"]; + + return [ + { + name: "anolilab/node/setup", + plugins: { + n: pluginNode, }, - ], + }, + { + files, + name: "anolilab/node/rules", + rules: { + ...pluginNode.configs.recommended.rules, + + // https://eslint.org/docs/rules/global-require + "global-require": "error", + + // enforce return after a callback + "n/callback-return": "off", + + // enforce the style of file extensions in import declarations + // This rule is buggy @see https://github.com/eslint-community/eslint-plugin-n/issues/21 + "n/file-extension-in-import": "off", + + // enforces error handling in callbacks (node environment) + "n/handle-callback-err": "off", - // disallow use of new operator with the require function - "n/no-new-require": "error", + // Redundant with `import/no-extraneous-dependencies`. + "n/no-extraneous-import": "off", - // disallow use of process.env - "n/no-process-env": "off", + // disallow require() expressions which import extraneous modules + "n/no-extraneous-require": "off", - // disallow string concatenation with __dirname and __filename - // unicorn/no-process-exit is enabled instead. - "n/no-process-exit": "off", + // require all requires be top-level + // Redundant with `import/no-unresolved`. + "n/no-missing-import": "off", // This rule is also buggy and doesn't support `node:`. - // restrict usage of specified node modules - "n/no-restricted-modules": "off", + // disallow require() expressions which import non-existence modules + "n/no-missing-require": "off", - // disallow process.exit() - // disallow use of synchronous methods (off by default) - "n/no-sync": "off", + // disallow use of the Buffer() constructor + // disallow mixing regular variable and require declarations + "n/no-mixed-requires": [ + "error", + { + allowCall: true, + grouping: true, + }, + ], - // disallow bin files that npm ignores - "n/no-unpublished-bin": "error", + // disallow use of new operator with the require function + "n/no-new-require": "error", - // require that process.exit() expressions use the same code path as throw - "n/process-exit-as-throw": "error", + // disallow use of process.env + "n/no-process-env": "off", - // https://eslint.org/docs/rules/no-buffer-constructor - "no-buffer-constructor": "error", + // disallow string concatenation with __dirname and __filename + // unicorn/no-process-exit is enabled instead. + "n/no-process-exit": "off", - // https://eslint.org/docs/rules/no-path-concat - "no-path-concat": "error", + // restrict usage of specified node modules + "n/no-restricted-modules": "off", - ...global.anolilabEslintConfigNodeRules, - }, -}; + // disallow process.exit() + // disallow use of synchronous methods (off by default) + "n/no-sync": "off", -export default config; + // disallow bin files that npm ignores + "n/no-unpublished-bin": "error", + + // require that process.exit() expressions use the same code path as throw + "n/process-exit-as-throw": "error", + + // https://eslint.org/docs/rules/no-buffer-constructor + "no-buffer-constructor": "error", + + // https://eslint.org/docs/rules/no-path-concat + "no-path-concat": "error", + + ...nodeVersion + ? { + "n/no-unsupported-features/es-builtins": ["error", { version: nodeVersion }], + "n/no-unsupported-features/es-syntax": ["error", { ignores: ["modules"], version: nodeVersion }], + "n/no-unsupported-features/node-builtins": ["error", { version: nodeVersion }], + } + : {}, + + ...overrides, + }, + }, + ]; +}); diff --git a/packages/eslint-config/src/config/plugins/perfectionist.ts b/packages/eslint-config/src/config/plugins/perfectionist.ts index 4a6b28631..df7407751 100644 --- a/packages/eslint-config/src/config/plugins/perfectionist.ts +++ b/packages/eslint-config/src/config/plugins/perfectionist.ts @@ -1,55 +1,60 @@ -import { hasDependency, hasDevDependency } from "@anolilab/package-json-utils"; -import type { Linter } from "eslint"; +import { hasPackageJsonAnyDependency } from "@visulima/package"; -import { createConfigs } from "../../utils/create-config"; -import { consoleLog } from "../../utils/loggers"; +import type { OptionsFiles, OptionsOverrides, OptionsPackageJson } from "../../types"; +import { createConfig, getFilesGlobs } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; -if ( - !global.hasAnolilabEsLintConfigPerfectionistTypescriptSortKeys && - (hasDependency("eslint-plugin-typescript-sort-keys") || hasDevDependency("eslint-plugin-typescript-sort-keys")) -) { - global.hasAnolilabEsLintConfigPerfectionistTypescriptSortKeys = true; +// @see https://github.com/azat-io/eslint-plugin-perfectionist +export default createConfig("all", async (config, oFiles) => { + const { files = oFiles, overrides, packageJson } = config; - consoleLog('\nPlease remove "eslint-plugin-typescript-sort-keys" from your package.json, it conflicts with "eslint-plugin-perfectionist".\n'); -} + const pluginPerfectionist = await interopDefault(import("eslint-plugin-perfectionist")); -// @see https://github.com/azat-io/eslint-plugin-perfectionist -const config: Linter.Config = createConfigs([ - { - config: { - extends: ["plugin:perfectionist/recommended-natural"], - plugins: ["perfectionist"], + if (hasPackageJsonAnyDependency(packageJson, ["eslint-plugin-typescript-sort-keys"])) { + // eslint-disable-next-line no-console + console.warn("\nPlease remove \"eslint-plugin-typescript-sort-keys\" from your package.json, it conflicts with \"eslint-plugin-perfectionist\".\n"); + } + + return [ + { + name: "anolilab/perfectionist/setup", + plugins: { + perfectionist: pluginPerfectionist, + }, + }, + { + files, + name: "anolilab/perfectionist/rules", rules: { + ...pluginPerfectionist.configs["recommended-natural"].rules, + // Disabled because of sort-imports "perfectionist/sort-imports": "off", - // Disabled because of @typescript-eslint/sort-type-constituents - "perfectionist/sort-union-types": "off", + // Disabled because of simple-import-sort/exports + "perfectionist/sort-named-exports": "off", // Disabled because of simple-import-sort/imports "perfectionist/sort-named-imports": "off", - // Disabled because of simple-import-sort/exports - "perfectionist/sort-named-exports": "off", + // Disabled because of @typescript-eslint/sort-type-constituents + "perfectionist/sort-union-types": "off", + + ...overrides, }, }, - type: "all", - }, - { - config: { + { + files: getFilesGlobs("ts"), + name: "anolilab/perfectionist/typescript", rules: { // Disabled because of @typescript-eslint/member-ordering "perfectionist/sort-classes": "off", }, }, - type: "typescript", - }, - { - config: { + { + files: getFilesGlobs("postcss"), + name: "anolilab/perfectionist/postcss", rules: { "perfectionist/sort-objects": "off", }, }, - type: "postcss", - }, -]); - -export default config; + ]; +}); diff --git a/packages/eslint-config/src/config/plugins/playwright.ts b/packages/eslint-config/src/config/plugins/playwright.ts index ebc042dd2..b118ea3a4 100644 --- a/packages/eslint-config/src/config/plugins/playwright.ts +++ b/packages/eslint-config/src/config/plugins/playwright.ts @@ -1,37 +1,22 @@ -import { hasDependency, hasDevDependency } from "@anolilab/package-json-utils"; -import type { Linter } from "eslint"; +import type { OptionsFiles, OptionsOverrides } from "../../types"; +import { createConfig } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; -if ( - !global.hasAnolilabEsLintConfigPlaywrightJest && - (hasDependency("jest") || - hasDevDependency("jest") || - hasDevDependency("eslint-plugin-jest") || - hasDevDependency("eslint-plugin-jest") || - hasDevDependency("@types/jest") || - hasDevDependency("@types/jest")) -) { - global.hasAnolilabEsLintConfigPlaywrightJest = true; -} +export default createConfig("e2e", async (config, oFiles) => { + const { files = oFiles, overrides } = config; -// @see https://github.com/playwright-community/eslint-plugin-playwright -const config: Linter.Config = { - env: { - browser: true, - es6: true, - node: true, - }, - overrides: [ + const pluginPlaywright = await interopDefault(import("eslint-plugin-playwright")); + + return [ { - extends: [global.hasAnolilabEsLintConfigPlaywrightJest ? "plugin:playwright/jest-playwright" : "plugin:playwright/recommended"], - // To ensure best performance enable only on e2e test files - files: ["**/e2e/**/*.test.{js,ts}"], + files, + plugins: { + playwright: pluginPlaywright, + }, rules: { - "@typescript-eslint/no-empty-function": "off", - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/no-object-literal-type-assertion": "off", + ...pluginPlaywright.configs["recommended"].rules, + ...overrides, }, }, - ], -}; - -export default config; + ]; +}); diff --git a/packages/eslint-config/src/config/plugins/promise.ts b/packages/eslint-config/src/config/plugins/promise.ts index 146d6eb72..77c5dd0da 100644 --- a/packages/eslint-config/src/config/plugins/promise.ts +++ b/packages/eslint-config/src/config/plugins/promise.ts @@ -1,13 +1,29 @@ -import type { Linter } from "eslint"; +import { fixupPluginRules } from "@eslint/compat"; + +import type { OptionsFiles, OptionsOverrides } from "../../types"; +import { createConfig } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; // @see https://github.com/xjamundx/eslint-plugin-promise#readme -const config: Linter.Config = { - extends: ["plugin:promise/recommended"], - plugins: ["promise"], - rules: { - "promise/prefer-await-to-callbacks": "off", - "promise/prefer-await-to-then": "off", - }, -}; - -export default config; +export default createConfig("all", async (config, oFiles) => { + const { files = oFiles, overrides } = config; + + const promisesPlugin = await interopDefault(import("eslint-plugin-promise")); + + return [ + { + files, + name: "anolilab/promise/rules", + plugins: { + promise: promisesPlugin, + }, + rules: { + ...fixupPluginRules(promisesPlugin.configs["flat/recommended"].rules), + + "promise/prefer-await-to-callbacks": "off", + "promise/prefer-await-to-then": "off", + ...overrides, + }, + }, + ]; +}); diff --git a/packages/eslint-config/src/config/plugins/react-hooks.ts b/packages/eslint-config/src/config/plugins/react-hooks.ts deleted file mode 100644 index eceda5867..000000000 --- a/packages/eslint-config/src/config/plugins/react-hooks.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { Linter } from "eslint"; - -import { createConfig } from "../../utils/create-config"; - -// @see https://github.com/yannickcr/eslint-plugin-react -const config: Linter.Config = createConfig("jsx_and_tsx", { - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - }, - plugins: ["react-hooks"], - rules: { - // Enforce Rules of Hooks - // https://github.com/facebook/react/blob/1204c789776cb01fbaf3e9f032e7e2ba85a44137/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js - "react-hooks/exhaustive-deps": "error", - - // Verify the list of the dependencies for Hooks like useEffect and similar - // https://github.com/facebook/react/blob/c11015ff4f610ac2924d1fc6d569a17657a404fd/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js - "react-hooks/rules-of-hooks": "error", - }, -}); - -export default config; diff --git a/packages/eslint-config/src/config/plugins/react-redux.ts b/packages/eslint-config/src/config/plugins/react-redux.ts deleted file mode 100644 index 311167c6b..000000000 --- a/packages/eslint-config/src/config/plugins/react-redux.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { Linter } from "eslint"; - -import { createConfig } from "../../utils/create-config"; - -// @see https://github.com/DianaSuvorova/eslint-plugin-react-redux#readme -const config: Linter.Config = createConfig("jsx_and_tsx", { - extends: ["plugin:react-redux/recommended"], - plugins: ["react-redux"], - rules: { - "react-redux/mapStateToProps-prefer-selectors": "off", - "react-redux/prefer-separate-component-file": "off", - }, -}); - -export default config; diff --git a/packages/eslint-config/src/config/plugins/react-usememo.ts b/packages/eslint-config/src/config/plugins/react-usememo.ts deleted file mode 100644 index 4bcf587fc..000000000 --- a/packages/eslint-config/src/config/plugins/react-usememo.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { Linter } from "eslint"; - -import { createConfig } from "../../utils/create-config"; - -// @see https://www.npmjs.com/package/@arthurgeron/eslint-plugin-react-usememo -const config: Linter.Config = createConfig("jsx_and_tsx", { - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - }, - plugins: ["@arthurgeron/react-usememo"], - rules: { - "@arthurgeron/react-usememo/require-usememo": "error", - }, -}); - -export default config; diff --git a/packages/eslint-config/src/config/plugins/react.ts b/packages/eslint-config/src/config/plugins/react.ts index ac50026f1..6fbf6d8cf 100644 --- a/packages/eslint-config/src/config/plugins/react.ts +++ b/packages/eslint-config/src/config/plugins/react.ts @@ -1,108 +1,133 @@ -// @see https://github.com/yannickcr/eslint-plugin-react -import { env } from "node:process"; - -import { getPackageSubProperty, hasDependency, hasDevDependency } from "@anolilab/package-json-utils"; -import type { Linter } from "eslint"; -import findUp from "find-up"; +import { hasPackageJsonAnyDependency } from "@visulima/package"; +import { readTsConfig } from "@visulima/tsconfig"; import { parse } from "semver"; -import anolilabEslintConfig from "../../utils/eslint-config"; -import indent from "../../utils/indent"; -import { consoleLog } from "../../utils/loggers"; -import styleConfig from "../style"; - -// @ts-expect-error TODO: find the correct type -const styleRules = styleConfig.overrides[0].rules as Linter.RulesRecord; -const dangleRules = styleRules["no-underscore-dangle"] as Linter.RuleEntry; - -if (!global.hasAnolilabEsLintConfigPrettier && (hasDependency("prettier") || hasDevDependency("prettier"))) { - global.hasAnolilabEsLintConfigPrettier = true; -} - -if (global.anolilabEslintConfigReactPrettierRules === undefined && global.hasAnolilabEsLintConfigPrettier) { - global.anolilabEslintConfigReactPrettierRules = { - "react/jsx-child-element-spacing": "off", - "react/jsx-closing-bracket-location": "off", - "react/jsx-closing-tag-location": "off", - "react/jsx-curly-newline": "off", - "react/jsx-curly-spacing": "off", - "react/jsx-equals-spacing": "off", - "react/jsx-first-prop-new-line": "off", - "react/jsx-indent": "off", - "react/jsx-indent-props": "off", - "react/jsx-max-props-per-line": "off", - "react/jsx-newline": "off", - "react/jsx-one-expression-per-line": "off", - "react/jsx-props-no-multi-spaces": "off", - "react/jsx-tag-spacing": "off", - "react/jsx-wrap-multilines": "off", +import type { + OptionsFiles, + OptionsHasPrettier, + OptionsOverrides, + OptionsPackageJson, + OptionsSilentConsoleLogs, + OptionsStylistic, + OptionsTypeScriptParserOptions, + OptionsTypeScriptWithTypes, + TypedFlatConfigItem, +} from "../../types"; +import { createConfig, getFilesGlobs } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; +import { noUnderscoreDangle } from "../style"; + +// react refresh +const ReactRefreshAllowConstantExportPackages = ["vite"]; +const RemixPackages = ["@remix-run/node", "@remix-run/react", "@remix-run/serve", "@remix-run/dev"]; +const NextJsPackages = ["next"]; +const ReactRouterPackages = ["@react-router/node", "@react-router/react", "@react-router/serve", "@react-router/dev"]; + +// @see https://github.com/jsx-eslint/eslint-plugin-react +export default createConfig< + OptionsFiles & + OptionsHasPrettier & + OptionsOverrides & + OptionsPackageJson & + OptionsSilentConsoleLogs & + OptionsStylistic & + OptionsTypeScriptParserOptions & + OptionsTypeScriptWithTypes + // eslint-disable-next-line sonarjs/cognitive-complexity +>("jsx_and_tsx", async (config, oFiles) => { + const { + files = oFiles, + filesTypeAware = getFilesGlobs("ts"), + ignoresTypeAware = [`**/*.md/**`, ...getFilesGlobs("astro")], + overrides, + packageJson, + prettier, + silent, + stylistic = true, + tsconfigPath, + } = config; + + const isTypeAware = tsconfigPath !== undefined; + + const { indent = 4 } = typeof stylistic === "boolean" ? {} : stylistic; + + const typeAwareRules: TypedFlatConfigItem["rules"] = { + "react-x/no-leaked-conditional-rendering": "error", }; -} - -const hasJsxRuntime = (() => { - // Workaround VS Code trying to run this file twice! - if (!global.hasAnolilabEsLintConfigReactRuntimePath) { - const reactPath = findUp.sync("node_modules/react/jsx-runtime.js"); - const isFile = typeof reactPath === "string"; - let showLog: boolean = env["DISABLE_INFO_ON_DISABLING_JSX_REACT_RULE"] !== "true"; + const [pluginReactX, pluginReact, pluginReactHooks, pluginReactRefresh] = await Promise.all([ + interopDefault(import("@eslint-react/eslint-plugin")), + interopDefault(import("eslint-plugin-react")), + interopDefault(import("eslint-plugin-react-hooks")), + interopDefault(import("eslint-plugin-react-refresh")), + ] as const); - if (showLog && anolilabEslintConfig["info_on_disabling_jsx_react_rule"] !== undefined) { - showLog = anolilabEslintConfig["info_on_disabling_jsx_react_rule"] as boolean; - } - - if (showLog && isFile) { - consoleLog(`\n@anolilab/eslint-config found react jsx-runtime. \n - Following rules are disabled: "react/jsx-uses-react" and "react/react-in-jsx-scope". - If you dont use the new react jsx-runtime in you project, please enable it manually.\n`); - } - - global.hasAnolilabEsLintConfigReactRuntimePath = isFile; - } + const isAllowConstantExport = hasPackageJsonAnyDependency(packageJson, ReactRefreshAllowConstantExportPackages); + const isUsingRemix = hasPackageJsonAnyDependency(packageJson, RemixPackages); + const isUsingNext = hasPackageJsonAnyDependency(packageJson, NextJsPackages); + const isUsingReactRouter = hasPackageJsonAnyDependency(packageJson, ReactRouterPackages); - return global.hasAnolilabEsLintConfigReactRuntimePath; -})(); + const { plugins } = pluginReactX.configs.all; -if (!global.anolilabEslintConfigReactVersion) { - let reactVersion = getPackageSubProperty("dependencies")("react"); - - if (reactVersion === undefined) { - reactVersion = getPackageSubProperty("devDependencies")("react"); - } + let reactVersion = packageJson?.["dependencies"]?.["react"] || packageJson?.["devDependencies"]?.["react"]; if (reactVersion !== undefined) { const parsedVersion = parse(reactVersion); if (parsedVersion !== null) { - global.anolilabEslintConfigReactVersion = `${parsedVersion.major}.${parsedVersion.minor}`; + reactVersion = `${parsedVersion.major}.${parsedVersion.minor}`; + + if (!silent) { + // eslint-disable-next-line no-console + console.info( + `\n@anolilab/eslint-config found the version ${reactVersion} of react in your dependencies, this version ${reactVersion} will be used to setup the "eslint-plugin-react"\n`, + ); + } } } -} -if (global.anolilabEslintConfigReactVersion !== undefined && anolilabEslintConfig["info_on_found_react_version"] !== false) { - consoleLog( - `\n@anolilab/eslint-config found the version ${global.anolilabEslintConfigReactVersion} of react in your dependencies, this version ${global.anolilabEslintConfigReactVersion} will be used to setup the "eslint-plugin-react"\n`, - ); -} + let hasJsxRuntime = false; -const config: Linter.Config = { - overrides: [ - { - env: { - browser: true, - }, + if (tsconfigPath !== undefined) { + const tsConfig = readTsConfig(tsconfigPath); - files: ["**/*.jsx", "**/*.tsx"], + if (tsConfig?.compilerOptions !== undefined && (tsConfig?.compilerOptions.jsx === "react-jsx" || tsConfig?.compilerOptions.jsx === "react-jsxdev")) { + hasJsxRuntime = true; - parserOptions: { - ecmaFeatures: { - jsx: true, + if (!silent) { + // eslint-disable-next-line no-console + console.info(`\n@anolilab/eslint-config found react jsx-runtime. \n + Following rules are disabled: "react/jsx-uses-react" and "react/react-in-jsx-scope". + If you dont use the new react jsx-runtime in you project, please enable it manually.\n`); + } + } + } + + return [ + { + name: "anolilab/react/setup", + plugins: { + react: pluginReact, + "react-dom": plugins["@eslint-react/dom"], + "react-hooks": pluginReactHooks, + "react-hooks-extra": plugins["@eslint-react/hooks-extra"], + "react-naming-convention": plugins["@eslint-react/naming-convention"], + "react-refresh": pluginReactRefresh, + "react-web-api": plugins["@eslint-react/web-api"], + "react-x": plugins["@eslint-react"], + }, + }, + { + files, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, }, }, - - plugins: ["react"], - - // https://github.com/yannickcr/eslint-plugin-react#list-of-supported-rules + name: "anolilab/react/rules", + // https://github.com/jsx-eslint/eslint-plugin-react#list-of-supported-rules rules: { "class-methods-use-this": [ "error", @@ -133,16 +158,335 @@ const config: Linter.Config = { "jsx-quotes": ["error", "prefer-double"], "no-underscore-dangle": [ - (dangleRules as unknown[])[0] as Linter.RuleLevel, + "error", + { + ...noUnderscoreDangle, + + allow: [...noUnderscoreDangle.allow, "__REDUX_DEVTOOLS_EXTENSION_COMPOSE__"], + }, + ], + + // Disallow direct calls to the set function of useState in useEffect + // https://eslint-react.xyz/docs/rules/no-direct-set-state-in-use-effect + "react-hooks-extra/no-direct-set-state-in-use-effect": "error", + + // Disallow unnecessary usage of useCallback + // https://eslint-react.xyz/docs/rules/hooks-extra-no-unnecessary-use-callback + "react-hooks-extra/no-unnecessary-use-callback": "error", + + // Disallow unnecessary usage of useMemo + // https://eslint-react.xyz/docs/rules/react-hooks-extra/no-unnecessary-use-memo + "react-hooks-extra/no-unnecessary-use-memo": "error", + + // Enforces that a function with the use prefix should use at least one hook inside of it + // https://eslint-react.xyz/docs/rules/no-unnecessary-use-prefix + "react-hooks-extra/no-unnecessary-use-prefix": "error", + + // Prefer lazy initialization for useState + // https://eslint-react.xyz/docs/rules/prefer-use-state-lazy-initialization + "react-hooks-extra/prefer-use-state-lazy-initialization": "error", + + // Enforce Rules of Hooks + // https://github.com/facebook/react/blob/1204c789776cb01fbaf3e9f032e7e2ba85a44137/packages/eslint-plugin-react-hooks/src/ExhaustiveDeps.js + "react-hooks/exhaustive-deps": "error", + + // Verify the list of the dependencies for Hooks like useEffect and similar + // https://github.com/facebook/react/blob/c11015ff4f610ac2924d1fc6d569a17657a404fd/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js + "react-hooks/rules-of-hooks": "error", + + // Enforces naming conventions for components + // https://eslint-react.xyz/docs/rules/naming-convention-component-name + "react-naming-convention/component-name": "error", + + // Enforces context name to be a valid component name with the suffix Context + // https://eslint-react.xyz/docs/rules/naming-convention-context-name + "react-naming-convention/context-name": "error", + + // Enforces consistent file naming conventions + // https://eslint-react.xyz/docs/rules/naming-convention-filename + "react-naming-convention/filename": "off", + + // Enforces naming conventions for useState + // https://eslint-react.xyz/docs/rules/naming-convention-use-state + "react-naming-convention/use-state": "error", + + // react refresh + "react-refresh/only-export-components": [ + "error", { - ...((dangleRules as unknown[])[1] as Linter.RuleLevelAndOptions[]), - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-explicit-any - allow: [...(dangleRules as any)[1].allow, "__REDUX_DEVTOOLS_EXTENSION_COMPOSE__"], + allowConstantExport: isAllowConstantExport, + allowExportNames: [ + ...isUsingNext + ? [ + "dynamic", + "dynamicParams", + "revalidate", + "fetchCache", + "runtime", + "preferredRegion", + "maxDuration", + "config", + "generateStaticParams", + "metadata", + "generateMetadata", + "viewport", + "generateViewport", + ] + : [], + ...isUsingRemix || isUsingReactRouter ? ["meta", "links", "headers", "loader", "action"] : [], + ], }, ], + // Prevents leaked addEventListener in a component or custom hook + // https://eslint-react.xyz/docs/rules/web-api-no-leaked-event-listener + "react-web-api/no-leaked-event-listener": "error", + + // Prevents leaked setInterval in a component or custom hook + // https://eslint-react.xyz/docs/rules/web-api-no-leaked-interval + "react-web-api/no-leaked-interval": "error", + + // Prevents leaked ResizeObserver in a component or custom hook + // https://eslint-react.xyz/docs/rules/web-api-no-leaked-resize-observer + "react-web-api/no-leaked-resize-observer": "error", + + // Prevents leaked setTimeout in a component or custom hook + // https://eslint-react.xyz/docs/rules/web-api-no-leaked-timeout + "react-web-api/no-leaked-timeout": "error", + + // React-X Rules + // https://eslint-react.xyz/docs/rules + + // Enforces explicit boolean values for boolean attributes + // https://eslint-react.xyz/docs/rules/avoid-shorthand-boolean + "react-x/avoid-shorthand-boolean": "off", + + // Enforces explicit components instead of the shorthand <> or syntax + // https://eslint-react.xyz/docs/rules/avoid-shorthand-fragment + "react-x/avoid-shorthand-fragment": "off", + + // Enforces that the key attribute is placed before the spread attribute in JSX elements + // https://eslint-react.xyz/docs/rules/jsx-key-before-spread + "react-x/jsx-key-before-spread": "error", + + // Disallow duplicate props in JSX elements + // https://eslint-react.xyz/docs/rules/jsx-no-duplicate-props + "react-x/jsx-no-duplicate-props": "error", + + // Disallow undefined variables in JSX elements + // https://eslint-react.xyz/docs/rules/jsx-no-undef + "react-x/jsx-no-undef": "off", + + // Marks React variables as used when JSX is used + // https://eslint-react.xyz/docs/rules/jsx-uses-react + "react-x/jsx-uses-react": hasJsxRuntime ? "off" : "error", + + // Marks variables used in JSX elements as used + // https://eslint-react.xyz/docs/rules/jsx-uses-vars + "react-x/jsx-uses-vars": "error", + + // Disallow accessing this.state inside setState calls + // https://eslint-react.xyz/docs/rules/no-access-state-in-setstate + "react-x/no-access-state-in-setstate": "error", + + // Disallow an item's index in the array as its key + // https://eslint-react.xyz/docs/rules/no-array-index-key + "react-x/no-array-index-key": "error", + + // Disallow Children.count + // https://eslint-react.xyz/docs/rules/no-children-count + "react-x/no-children-count": "error", + + // Disallow Children.forEach + // https://eslint-react.xyz/docs/rules/no-children-for-each + "react-x/no-children-for-each": "error", + + // Disallow Children.map + // https://eslint-react.xyz/docs/rules/no-children-map + "react-x/no-children-map": "error", + + // Disallow Children.only + // https://eslint-react.xyz/docs/rules/no-children-only + "react-x/no-children-only": "error", + + // Disallow passing children as a prop + // https://eslint-react.xyz/docs/rules/no-children-prop + "react-x/no-children-prop": "off", + + // Disallow Children.toArray + // https://eslint-react.xyz/docs/rules/no-children-to-array + "react-x/no-children-to-array": "error", + + // Disallow class components except for error boundaries + // https://eslint-react.xyz/docs/rules/no-class-component + "react-x/no-class-component": "off", + + // Disallow cloneElement + // https://eslint-react.xyz/docs/rules/no-clone-element + "react-x/no-clone-element": "error", + + // Prevents comments from being inserted as text nodes + // https://eslint-react.xyz/docs/rules/no-comment-textnodes + "react-x/no-comment-textnodes": "error", + + // Disallow complex conditional rendering in JSX expressions + // https://eslint-react.xyz/docs/rules/no-complex-conditional-rendering + "react-x/no-complex-conditional-rendering": "off", + + // Replaces usages of componentWillMount with UNSAFE_componentWillMount + // https://eslint-react.xyz/docs/rules/no-component-will-mount + "react-x/no-component-will-mount": "error", + + // Replaces usages of componentWillReceiveProps with UNSAFE_componentWillReceiveProps + // https://eslint-react.xyz/docs/rules/no-component-will-receive-props + "react-x/no-component-will-receive-props": "error", + + // Replaces usages of componentWillUpdate with UNSAFE_componentWillUpdate + // https://eslint-react.xyz/docs/rules/no-component-will-update + "react-x/no-component-will-update": "error", + + // Replaces usages of with + // https://eslint-react.xyz/docs/rules/no-context-provider + "react-x/no-context-provider": "error", + + // Disallow createRef in function components + // https://eslint-react.xyz/docs/rules/no-create-ref + "react-x/no-create-ref": "error", + + // Disallow defaultProps property in favor of ES6 default parameters + // https://eslint-react.xyz/docs/rules/no-default-props + "react-x/no-default-props": "error", + + // Disallow direct mutation of this.state + // https://eslint-react.xyz/docs/rules/no-direct-mutation-state + "react-x/no-direct-mutation-state": "error", + + // Disallow duplicate key on elements in the same array or a list of children + // https://eslint-react.xyz/docs/rules/no-duplicate-key + "react-x/no-duplicate-key": "error", + + // Replaces usages of forwardRef with passing ref as a prop + // https://eslint-react.xyz/docs/rules/no-forward-ref + "react-x/no-forward-ref": "error", + + // Prevents key from not being explicitly specified (e.g. spreading key from objects) + // https://eslint-react.xyz/docs/rules/no-implicit-key + "react-x/no-implicit-key": "error", + + // Prevents problematic leaked values from being rendered + // https://eslint-react.xyz/docs/rules/no-leaked-conditional-rendering + "react-x/no-leaked-conditional-rendering": "error", + + // Enforces that all components have a displayName which can be used in devtools + // https://eslint-react.xyz/docs/rules/no-missing-component-display-name + "react-x/no-missing-component-display-name": "off", + + // Enforces that all contexts have a displayName which can be used in devtools + // https://eslint-react.xyz/docs/rules/no-missing-context-display-name + "react-x/no-missing-context-display-name": "off", + + // Disallow missing key on items in list rendering + // https://eslint-react.xyz/docs/rules/no-missing-key + "react-x/no-missing-key": "error", + + // Prevents incorrect usage of captureOwnerStack + // https://eslint-react.xyz/docs/rules/no-misused-capture-owner-stack + "react-x/no-misused-capture-owner-stack": "off", + + // Disallow nesting component definitions inside other components + // https://eslint-react.xyz/docs/rules/no-nested-component-definitions + "react-x/no-nested-component-definitions": "error", + + // Disallow nesting lazy component declarations inside other components + // https://eslint-react.xyz/docs/rules/no-nested-lazy-component-declarations + "react-x/no-nested-lazy-component-declarations": "error", + + // Disallow propTypes in favor of TypeScript or another type-checking solution + // https://eslint-react.xyz/docs/rules/no-prop-types + "react-x/no-prop-types": "error", + + // Disallow shouldComponentUpdate when extending React.PureComponent + // https://eslint-react.xyz/docs/rules/no-redundant-should-component-update + "react-x/no-redundant-should-component-update": "error", + + // Disallow calling this.setState in componentDidMount outside of functions, such as callbacks + // https://eslint-react.xyz/docs/rules/no-set-state-in-component-did-mount + "react-x/no-set-state-in-component-did-mount": "error", + + // Disallow calling this.setState in componentDidUpdate outside of functions, such as callbacks + // https://eslint-react.xyz/docs/rules/no-set-state-in-component-did-update + "react-x/no-set-state-in-component-did-update": "error", + + // Disallow calling this.setState in componentWillUpdate outside of functions, such as callbacks + // https://eslint-react.xyz/docs/rules/no-set-state-in-component-will-update + "react-x/no-set-state-in-component-will-update": "error", + + // Replaces string refs with callback refs + // https://eslint-react.xyz/docs/rules/no-string-refs + "react-x/no-string-refs": "error", + + // Warns the usage of UNSAFE_componentWillMount in class components + // https://eslint-react.xyz/docs/rules/no-unsafe-component-will-mount + "react-x/no-unsafe-component-will-mount": "error", + + // Warns the usage of UNSAFE_componentWillReceiveProps in class components + // https://eslint-react.xyz/docs/rules/no-unsafe-component-will-receive-props + "react-x/no-unsafe-component-will-receive-props": "error", + + // Warns the usage of UNSAFE_componentWillUpdate in class components + // https://eslint-react.xyz/docs/rules/no-unsafe-component-will-update + "react-x/no-unsafe-component-will-update": "error", + + // Prevents non-stable values (i.e. object literals) from being used as a value for Context.Provider + // https://eslint-react.xyz/docs/rules/no-unstable-context-value + "react-x/no-unstable-context-value": "error", + + // Prevents using referential-type values as default props in object destructuring + // https://eslint-react.xyz/docs/rules/no-unstable-default-props + "react-x/no-unstable-default-props": "error", + + // Warns unused class component methods and properties + // https://eslint-react.xyz/docs/rules/no-unused-class-component-members + "react-x/no-unused-class-component-members": "error", + + // Warns unused class component state + // https://eslint-react.xyz/docs/rules/no-unused-state + "react-x/no-unused-state": "error", + + // Replaces usages of useContext with use + // https://eslint-react.xyz/docs/rules/no-use-context + "react-x/no-use-context": "error", + + // Disallow useless forwardRef calls on components that don't use refs + // https://eslint-react.xyz/docs/rules/no-useless-forward-ref + "react-x/no-useless-forward-ref": "error", + + // Disallow useless fragment elements + // https://eslint-react.xyz/docs/rules/no-useless-fragment + "react-x/no-useless-fragment": "off", + + // Enforces destructuring assignment for component props and context + // https://eslint-react.xyz/docs/rules/prefer-destructuring-assignment + "react-x/prefer-destructuring-assignment": "off", + + // Enforces React is imported via a namespace import + // https://eslint-react.xyz/docs/rules/prefer-react-namespace-import + "react-x/prefer-react-namespace-import": "off", + + // Enforces read-only props in components + // https://eslint-react.xyz/docs/rules/prefer-read-only-props + "react-x/prefer-read-only-props": "off", + + // Enforces shorthand syntax for boolean attributes + // https://eslint-react.xyz/docs/rules/prefer-shorthand-boolean + "react-x/prefer-shorthand-boolean": "off", + + // Enforces shorthand syntax for fragments + // https://eslint-react.xyz/docs/rules/prefer-shorthand-fragment + "react-x/prefer-shorthand-fragment": "off", + // Prevent missing displayName in a React component definition - // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/boolean-prop-naming.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/boolean-prop-naming.md "react/boolean-prop-naming": [ "off", { @@ -153,7 +497,7 @@ const config: Linter.Config = { ], // Forbid certain propTypes (any, array, object) - // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/button-has-type.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/button-has-type.md "react/button-has-type": [ "error", { @@ -164,35 +508,35 @@ const config: Linter.Config = { ], // Forbid certain props on DOM Nodes - // https://github.com/yannickcr/eslint-plugin-react/blob/9e13ae2c51e44872b45cc15bf1ac3a72105bdd0e/docs/rules/default-props-match-prop-types.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/9e13ae2c51e44872b45cc15bf1ac3a72105bdd0e/docs/rules/default-props-match-prop-types.md "react/default-props-match-prop-types": ["error", { allowRequiredDefaults: false }], // Enforce boolean attributes notation in JSX - // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/destructuring-assignment.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/destructuring-assignment.md "react/destructuring-assignment": ["error", "always"], // Validate closing bracket location in JSX - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/display-name.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/display-name.md "react/display-name": ["off", { ignoreTranspilerName: false }], // Enforce or disallow spaces inside curly braces in JSX attributes - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/forbid-component-props.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/forbid-component-props.md "react/forbid-component-props": ["off", { forbid: [] }], // Enforce event handler naming conventions in JSX - // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/forbid-dom-props.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/forbid-dom-props.md "react/forbid-dom-props": ["off", { forbid: [] }], // Validate props indentation in JSX - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/forbid-elements.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/forbid-elements.md "react/forbid-elements": ["off", { forbid: [] }], // Validate JSX has key prop when in array or iterator // but it's only critical if you're stripping propTypes in production. - "react/forbid-foreign-prop-types": ["warn", { allowInPropTypes: true }], + "react/forbid-foreign-prop-types": ["error", { allowInPropTypes: true }], // Limit maximum of props on a single line in JSX - // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/forbid-prop-types.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/forbid-prop-types.md "react/forbid-prop-types": [ "error", { @@ -203,7 +547,7 @@ const config: Linter.Config = { ], // Prevent usage of .bind() in JSX props - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/function-component-definition.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/function-component-definition.md "react/function-component-definition": [ "error", { @@ -213,7 +557,7 @@ const config: Linter.Config = { ], // Prevent duplicate props in JSX - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-boolean-value.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-boolean-value.md "react/jsx-boolean-value": ["error", "never", { always: [] }], // Prevent usage of unwrapped JSX strings @@ -221,15 +565,15 @@ const config: Linter.Config = { "react/jsx-child-element-spacing": "off", // Disallow undeclared variables in JSX - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-closing-bracket-location.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-closing-bracket-location.md "react/jsx-closing-bracket-location": ["error", "line-aligned"], // Enforce PascalCase for user-defined JSX components - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-curly-brace-presence.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-curly-brace-presence.md "react/jsx-curly-brace-presence": ["error", { children: "never", props: "never" }], // Enforce propTypes declarations alphabetical sorting - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-curly-newline.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-curly-newline.md "react/jsx-curly-newline": [ "error", { @@ -239,23 +583,23 @@ const config: Linter.Config = { ], // Enforce props alphabetical sorting - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-curly-spacing.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-curly-spacing.md "react/jsx-curly-spacing": ["error", "never", { allowMultiline: true }], // Prevent React to be incorrectly marked as unused - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-equals-spacing.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-equals-spacing.md "react/jsx-equals-spacing": ["error", "never"], // Prevent variables used in JSX to be incorrectly marked as unused - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-first-prop-new-line.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-first-prop-new-line.md "react/jsx-first-prop-new-line": ["error", "multiline-multiprop"], // Prevent usage of dangerous JSX properties - // https://github.com/yannickcr/eslint-plugin-react/blob/bc976b837abeab1dffd90ac6168b746a83fc83cc/docs/rules/jsx-fragments.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/bc976b837abeab1dffd90ac6168b746a83fc83cc/docs/rules/jsx-fragments.md "react/jsx-fragments": ["error", "syntax"], // Prevent usage of deprecated methods - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-handler-names.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-handler-names.md "react/jsx-handler-names": [ "off", { @@ -265,31 +609,31 @@ const config: Linter.Config = { ], // Prevent usage of setState in componentWillUpdate - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-indent.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-indent.md "react/jsx-indent": ["error", indent, { checkAttributes: true, indentLogicalExpressions: true }], // Prevent direct mutation of this.state - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-indent-props.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-indent-props.md "react/jsx-indent-props": ["error", indent], // Prevent usage of isMounted - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-key.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-key.md "react/jsx-key": "off", // Prevent multiple component definition per file - // https://github.com/yannickcr/eslint-plugin-react/blob/abe8381c0d6748047224c430ce47f02e40160ed0/docs/rules/jsx-max-depth.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/abe8381c0d6748047224c430ce47f02e40160ed0/docs/rules/jsx-max-depth.md "react/jsx-max-depth": "off", // Prevent usage of setState - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-max-props-per-line.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-max-props-per-line.md "react/jsx-max-props-per-line": ["error", { maximum: 1, when: "multiline" }], // Prevent using string references - // https://github.com/yannickcr/eslint-plugin-react/blob/e2eaadae316f9506d163812a09424eb42698470a/docs/rules/jsx-newline.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/e2eaadae316f9506d163812a09424eb42698470a/docs/rules/jsx-newline.md "react/jsx-newline": "off", // Prevent usage of unknown DOM property - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md "react/jsx-no-bind": [ "error", { @@ -302,23 +646,23 @@ const config: Linter.Config = { ], // Require ES6 class declarations over React.createClass - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-comment-textnodes.md - "react/jsx-no-comment-textnodes": "error", + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-no-comment-textnodes.md + "react/jsx-no-comment-textnodes": "off", // Require stateless functions when not using lifecycle methods, setState or ref - // https://github.com/yannickcr/eslint-plugin-react/blob/e2eaadae316f9506d163812a09424eb42698470a/docs/rules/jsx-no-constructed-context-values.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/e2eaadae316f9506d163812a09424eb42698470a/docs/rules/jsx-no-constructed-context-values.md "react/jsx-no-constructed-context-values": "error", // Prevent missing props validation in a React component definition - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-duplicate-props.md - "react/jsx-no-duplicate-props": ["error", { ignoreCase: true }], + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-no-duplicate-props.md + "react/jsx-no-duplicate-props": "off", // Prevent missing React when using JSX - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-literals.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-no-literals.md "react/jsx-no-literals": ["off", { noStrings: true }], // Require render() methods to return something - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-script-url.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-no-script-url.md "react/jsx-no-script-url": [ "error", [ @@ -330,23 +674,23 @@ const config: Linter.Config = { ], // Prevent extra closing tags for components without children - // https://github.com/yannickcr/eslint-plugin-react/blob/ac102885765be5ff37847a871f239c6703e1c7cc/docs/rules/jsx-no-target-blank.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/ac102885765be5ff37847a871f239c6703e1c7cc/docs/rules/jsx-no-target-blank.md "react/jsx-no-target-blank": ["error", { enforceDynamicLinks: "always" }], // Enforce defaultProps declarations alphabetical sorting - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-undef.md - "react/jsx-no-undef": "error", + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-no-undef.md + "react/jsx-no-undef": "off", // Enforce component methods order - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-useless-fragment.md - "react/jsx-no-useless-fragment": "error", + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-no-useless-fragment.md + "react/jsx-no-useless-fragment": "off", // Require that the first prop in a JSX element be on a new line when the element is multiline - // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/jsx-one-expression-per-line.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-one-expression-per-line.md "react/jsx-one-expression-per-line": ["error", { allow: "single-child" }], // Enforce spacing around jsx equals signs - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-pascal-case.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-pascal-case.md "react/jsx-pascal-case": [ "error", { @@ -356,12 +700,12 @@ const config: Linter.Config = { ], // Enforce JSX indentation - // https://github.com/yannickcr/eslint-plugin-react/blob/ac102885765be5ff37847a871f239c6703e1c7cc/docs/rules/jsx-props-no-multi-spaces.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/ac102885765be5ff37847a871f239c6703e1c7cc/docs/rules/jsx-props-no-multi-spaces.md "react/jsx-props-no-multi-spaces": "error", // Prevent usage of setState in componentDidMount - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-did-mount-set-state.md - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-props-no-spreading.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-did-mount-set-state.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-props-no-spreading.md "react/jsx-props-no-spreading": [ "error", { @@ -373,7 +717,7 @@ const config: Linter.Config = { ], // Prevent usage of setState in componentDidUpdate - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-sort-props.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-sort-props.md "react/jsx-sort-props": "off", // Disallow target="_blank" on links @@ -381,7 +725,7 @@ const config: Linter.Config = { "react/jsx-space-before-closing": ["off", "always"], // prevent accidental JS comments from being injected into JSX as text - // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/jsx-tag-spacing.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-tag-spacing.md "react/jsx-tag-spacing": [ "error", { @@ -393,39 +737,44 @@ const config: Linter.Config = { ], // disallow using React.render/ReactDOM.render's return value - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-react.md - "react/jsx-uses-react": [hasJsxRuntime ? "off" : "error"], + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-uses-react.md + // DISABLED: Handled by react-x/jsx-uses-react + "react/jsx-uses-react": "off", // require a shouldComponentUpdate method, or PureRenderMixin - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-vars.md - "react/jsx-uses-vars": "error", + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-uses-vars.md + // DISABLED: Handled by react-x/jsx-uses-vars + "react/jsx-uses-vars": "off", // warn against using findDOMNode() - // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/no-access-state-in-setstate.md - "react/no-access-state-in-setstate": "error", + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-access-state-in-setstate.md + // DISABLED: Handled by react-x/no-access-state-in-setstate + "react/no-access-state-in-setstate": "off", // Forbid certain props on Components - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-adjacent-inline-elements.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-adjacent-inline-elements.md "react/no-adjacent-inline-elements": "error", // Forbid certain elements - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-array-index-key.md - "react/no-array-index-key": "error", + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-array-index-key.md + // DISABLED: Handled by react-x/no-array-index-key + "react/no-array-index-key": "off", // Prevent problem with children and props.dangerouslySetInnerHTML - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-children-prop.md - "react/no-children-prop": "error", + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-children-prop.md + // DISABLED: Handled by react-x/no-children-prop + "react/no-children-prop": "off", // Prevent unused propType definitions - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-danger.md - "react/no-danger": "warn", + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-danger.md + "react/no-danger": "error", // Require style prop value be an object or var - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-danger-with-children.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-danger-with-children.md "react/no-danger-with-children": "error", // Prevent invalid characters from appearing in markup - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-deprecated.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-deprecated.md "react/no-deprecated": ["error"], // Prevent passing of children as props @@ -433,62 +782,65 @@ const config: Linter.Config = { "react/no-did-mount-set-state": "off", // Validate whitespace in and around the JSX opening and closing brackets - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-did-update-set-state.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-did-update-set-state.md "react/no-did-update-set-state": "error", // Enforce spaces before the closing bracket of self-closing JSX elements - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-space-before-closing.md - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-direct-mutation-state.md - "react/no-direct-mutation-state": "error", + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-space-before-closing.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-direct-mutation-state.md + // DISABLED: Handled by react-x/no-direct-mutation-state + "react/no-direct-mutation-state": "off", // Prevent usage of Array index in keys - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-find-dom-node.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-find-dom-node.md "react/no-find-dom-node": "error", // Enforce a defaultProps definition for every prop that is not a required prop - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-is-mounted.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-is-mounted.md "react/no-is-mounted": "error", // Forbids using non-exported propTypes - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/forbid-foreign-prop-types.md - // this is intentionally set to "warn". it would be "error", - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-multi-comp.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/forbid-foreign-prop-types.md + // this is intentionally set to "error". it would be "error", + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-multi-comp.md "react/no-multi-comp": "off", // Prevent void DOM elements from receiving children - // https://github.com/yannickcr/eslint-plugin-react/blob/9e13ae2c51e44872b45cc15bf1ac3a72105bdd0e/docs/rules/no-redundant-should-component-update.md - "react/no-redundant-should-component-update": "error", + // https://github.com/jsx-eslint/eslint-plugin-react/blob/9e13ae2c51e44872b45cc15bf1ac3a72105bdd0e/docs/rules/no-redundant-should-component-update.md + // DISABLED: Handled by react-x/no-redundant-should-component-update + "react/no-redundant-should-component-update": "off", // Enforce all defaultProps have a corresponding non-required PropType - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-render-return-value.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-render-return-value.md "react/no-render-return-value": "error", // Prevent usage of shouldComponentUpdate when extending React.PureComponent - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-set-state.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-set-state.md "react/no-set-state": "off", // Prevent unused state values - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-string-refs.md - "react/no-string-refs": "error", + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-string-refs.md + // DISABLED: Handled by react-x/no-string-refs + "react/no-string-refs": "off", // Enforces consistent naming for boolean props - // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/no-this-in-sfc.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-this-in-sfc.md "react/no-this-in-sfc": "error", // Enforce curly braces or disallow unnecessary curly braces in JSX props and/or children - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unescaped-entities.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-unescaped-entities.md "react/no-unescaped-entities": "error", // One JSX Element Per Line - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unknown-property.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-unknown-property.md "react/no-unknown-property": "error", // Enforce consistent usage of destructuring assignment of props, state, and context - // https://github.com/yannickcr/eslint-plugin-react/blob/157cc932be2cfaa56b3f5b45df6f6d4322a2f660/docs/rules/no-unsafe.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/157cc932be2cfaa56b3f5b45df6f6d4322a2f660/docs/rules/no-unsafe.md "react/no-unsafe": "off", // Prevent using this.state within a this.setState - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unused-prop-types.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-unused-prop-types.md "react/no-unused-prop-types": [ "error", { @@ -498,65 +850,53 @@ const config: Linter.Config = { ], // Prevent usage of button elements without an explicit type attribute - // https://github.com/yannickcr/eslint-plugin-react/pull/1103/ - "react/no-unused-state": "error", + // https://github.com/jsx-eslint/eslint-plugin-react/pull/1103/ + // DISABLED: Handled by react-x/no-unused-state + "react/no-unused-state": "off", - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-will-update-set-state.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-will-update-set-state.md "react/no-will-update-set-state": "error", // Prevent this from being used in stateless functional components - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prefer-es6-class.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/prefer-es6-class.md "react/prefer-es6-class": ["error", "always"], // Validate JSX maximum depth - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prefer-read-only-props.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/prefer-read-only-props.md "react/prefer-read-only-props": "off", // Disallow multiple spaces between inline JSX props - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prefer-stateless-function.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/prefer-stateless-function.md "react/prefer-stateless-function": ["error", { ignorePureComponents: true }], // Prevent usage of UNSAFE_ methods - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prop-types.md - "react/prop-types": [ - "error", - { - customValidators: [], - ignore: [], - skipUndeclared: false, - }, - ], + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/prop-types.md + "react/prop-types": "off", // Enforce shorthand or standard form for React fragments - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/react-in-jsx-scope.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/react-in-jsx-scope.md "react/react-in-jsx-scope": hasJsxRuntime ? "off" : "error", // Enforce a defaultProps definition for every prop that is not a required prop - // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/require-default-props.md - "react/require-default-props": [ - "error", - { - forbidDefaultForRequired: true, - functions: hasDependency("typescript") || hasDevDependency("typescript") ? "defaultArguments" : "defaultProps", - }, - ], + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/require-default-props.md + "react/require-default-props": "off", // Enforce state initialization style - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/state-in-constructor.md - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/require-optimization.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/state-in-constructor.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/require-optimization.md "react/require-optimization": ["off", { allowDecorators: [] }], // Enforces where React component static properties should be positioned - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/static-property-placement.md - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/require-render-return.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/static-property-placement.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/require-render-return.md "react/require-render-return": "error", // Disallow JSX props spreading - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/self-closing-comp.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/self-closing-comp.md "react/self-closing-comp": "error", // Enforce that props are read-only - // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/sort-comp.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/sort-comp.md "react/sort-comp": [ "error", { @@ -608,7 +948,7 @@ const config: Linter.Config = { ], // Prevent usage of `javascript:` URLs - // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/sort-default-props.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/sort-default-props.md "react/sort-default-props": [ "error", { @@ -617,7 +957,7 @@ const config: Linter.Config = { ], // Disallow unnecessary fragments - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/sort-prop-types.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/sort-prop-types.md "react/sort-prop-types": [ "off", { @@ -629,24 +969,41 @@ const config: Linter.Config = { ], // Prevent adjacent inline elements not separated by whitespace - // TODO: set to "never" once @anolilab/babel-preset supports public class fields - "react/state-in-constructor": ["error", "always"], + "react/state-in-constructor": ["error", "never"], // Enforce a specific function type for function components - // TODO: set to "static public field" once @anolilab/babel-preset supports public class fields - "react/static-property-placement": ["error", "property assignment"], - + "react/static-property-placement": ["error", "static public field"], // Enforce a new line after jsx elements and expressions - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/style-prop-object.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/style-prop-object.md "react/style-prop-object": "error", // Prevent react contexts from taking non-stable values - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/void-dom-elements-no-children.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/void-dom-elements-no-children.md "react/void-dom-elements-no-children": "error", - ...global.anolilabEslintConfigReactPrettierRules, + ...prettier + ? { + "react/jsx-child-element-spacing": "off", + "react/jsx-closing-bracket-location": "off", + "react/jsx-closing-tag-location": "off", + "react/jsx-curly-newline": "off", + "react/jsx-curly-spacing": "off", + "react/jsx-equals-spacing": "off", + "react/jsx-first-prop-new-line": "off", + "react/jsx-indent": "off", + "react/jsx-indent-props": "off", + "react/jsx-max-props-per-line": "off", + "react/jsx-newline": "off", + "react/jsx-one-expression-per-line": "off", + "react/jsx-props-no-multi-spaces": "off", + "react/jsx-tag-spacing": "off", + "react/jsx-wrap-multilines": "off", + } + : {}, + + // overrides + ...overrides, }, - // View link below for react rules documentation settings: { propWrapperFunctions: [ @@ -658,32 +1015,35 @@ const config: Linter.Config = { // The default value is "detect". Automatic detection works by loading the entire React library // into the linter's process, which is inefficient. It is recommended to specify the version // explicity. - version: global.anolilabEslintConfigReactVersion ?? "detect", + version: reactVersion ?? "detect", }, }, }, { files: ["**/*.jsx"], - parser: "@babel/eslint-parser", - parserOptions: { - ecmaFeatures: { - jsx: true, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, }, }, - settings: { - extensions: [".jsx"], - }, + name: "anolilab/react/jsx", rules: { + // Enforces consistent use of the JSX file extension. + // https://eslint-react.xyz/docs/rules/naming-convention-filename-extension + "react-x/naming-convention/filename-extension": ["error", "as-needed"], + // only .jsx files may have JSX - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-closing-tag-location.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-closing-tag-location.md "react/jsx-closing-tag-location": "error", // Prevents common casing typos - // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-filename-extension.md - "react/jsx-filename-extension": "error", + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-filename-extension.md + "react/jsx-filename-extension": "off", // Validate closing tag location in JSX - // https://github.com/yannickcr/eslint-plugin-react/blob/843d71a432baf0f01f598d7cf1eea75ad6896e4b/docs/rules/jsx-wrap-multilines.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-wrap-multilines.md "react/jsx-wrap-multilines": [ "error", { @@ -698,12 +1058,16 @@ const config: Linter.Config = { ], // Prevent missing parentheses around multilines JSX - // https://github.com/yannickcr/eslint-plugin-react/blob/73abadb697034b5ccb514d79fb4689836fe61f91/docs/rules/no-typos.md + // https://github.com/jsx-eslint/eslint-plugin-react/blob/73abadb697034b5ccb514d79fb4689836fe61f91/docs/rules/no-typos.md "react/no-typos": "error", }, + settings: { + extensions: [".jsx"], + }, }, { files: ["**/*.tsx"], + name: "anolilab/react/tsx", rules: { "react/default-props-match-prop-types": "off", // Disable JS specific rules @@ -714,12 +1078,23 @@ const config: Linter.Config = { }, { // For performance run storybook/recommended on test files, not regular code - files: ["**/*.stories.{ts,tsx,mdx}"], + files: getFilesGlobs("storybook"), + name: "anolilab/react/storybook", rules: { "react/jsx-props-no-spreading": "off", }, }, - ], -}; - -export default config; + ...isTypeAware + ? [ + { + files: filesTypeAware, + ignores: ignoresTypeAware, + name: "anolilab/react/type-aware-rules", + rules: { + ...typeAwareRules, + }, + }, + ] + : [], + ]; +}); diff --git a/packages/eslint-config/src/config/plugins/regexp.ts b/packages/eslint-config/src/config/plugins/regexp.ts index 5b2c7e8fb..81aaeeffb 100644 --- a/packages/eslint-config/src/config/plugins/regexp.ts +++ b/packages/eslint-config/src/config/plugins/regexp.ts @@ -1,14 +1,39 @@ -import type { Linter } from "eslint"; +import { configs } from "eslint-plugin-regexp"; +import type { + OptionsFiles, + OptionsOverrides, + OptionsRegExp, + TypedFlatConfigItem, +} from "../../types"; import { createConfig } from "../../utils/create-config"; -const config: Linter.Config = createConfig("all", { - extends: ["plugin:regexp/recommended"], - plugins: ["regexp"], - rules: { - // disallow control characters - "regexp/no-control-character": "error", - }, -}); +export default createConfig("all", async (config, oFiles) => { + const { files = oFiles, level, overrides } = config; + const recommended = configs["flat/recommended"] as TypedFlatConfigItem; + + const rules = { + ...recommended.rules, + }; -export default config; + if (level === "warn") { + // eslint-disable-next-line no-restricted-syntax + for (const key in rules) { + if (rules[key] === "error") { + rules[key] = "warn"; + } + } + } + + return [ + { + ...recommended, + files, + name: "anolilab/regexp/rules", + rules: { + ...rules, + ...overrides, + }, + }, + ]; +}); diff --git a/packages/eslint-config/src/config/plugins/security.ts b/packages/eslint-config/src/config/plugins/security.ts deleted file mode 100644 index 8c33f61a4..000000000 --- a/packages/eslint-config/src/config/plugins/security.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { Linter } from "eslint"; - -const config: Linter.Config = { - // @see https://github.com/eslint-community/eslint-plugin-security - extends: ["plugin:security/recommended"], - // @see https://www.npmjs.com/package/@rushstack/eslint-plugin-security - plugins: ["@rushstack/eslint-plugin-security"], - rules: { - // This is disabled for tools because, for example, it is a common and safe practice for a tool - // to read a RegExp from a config file and use it to filter files paths. - - "@rushstack/security/no-unsafe-regexp": process.env["TRUSTED_TOOL"] ? "off" : "warn", - }, -}; - -export default config; diff --git a/packages/eslint-config/src/config/plugins/simple-import-sort.ts b/packages/eslint-config/src/config/plugins/simple-import-sort.ts index 828349c4f..9dcd463cb 100644 --- a/packages/eslint-config/src/config/plugins/simple-import-sort.ts +++ b/packages/eslint-config/src/config/plugins/simple-import-sort.ts @@ -1,17 +1,25 @@ -import type { Linter } from "eslint"; - +import type { OptionsFiles, OptionsOverrides } from "../../types"; import { createConfig } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; -const config: Linter.Config = createConfig("all", { - env: { es6: true }, - parserOptions: { - sourceType: "module", - }, - plugins: ["simple-import-sort"], - rules: { - "simple-import-sort/exports": "error", - "simple-import-sort/imports": "error", - }, -}); +export default createConfig("all", async (config, oFiles) => { + const { files = oFiles, overrides } = config; + + const pluginSimpleImportSort = await interopDefault(import("eslint-plugin-simple-import-sort")); -export default config; + return [ + { + files, + name: "anolilab/simple-import-sort", + plugins: { + "simple-import-sort": pluginSimpleImportSort, + }, + rules: { + "simple-import-sort/exports": "error", + "simple-import-sort/imports": "error", + + ...overrides, + }, + }, + ]; +}); diff --git a/packages/eslint-config/src/config/plugins/sonarjs.ts b/packages/eslint-config/src/config/plugins/sonarjs.ts index 2b045ceb0..4fd2dea9e 100644 --- a/packages/eslint-config/src/config/plugins/sonarjs.ts +++ b/packages/eslint-config/src/config/plugins/sonarjs.ts @@ -1,21 +1,39 @@ -import type { Linter } from "eslint"; - -import { createConfigs } from "../../utils/create-config"; +import type { OptionsFiles, OptionsOverrides } from "../../types"; +import { createConfig, getFilesGlobs } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; // @see https://github.com/SonarSource/eslint-plugin-sonarjs -const config: Linter.Config = createConfigs([ - { - config: { - excludedFiles: ["**/?(*.)+(test).{js,jsx,ts,tsx}", "**/*.stories.{js,ts,jsx,tsx}"], - extends: ["plugin:sonarjs/recommended"], +export default createConfig("all", async (config, oFiles) => { + const { files = oFiles, overrides } = config; + + const sonarJsPlugin = await interopDefault(import("eslint-plugin-sonarjs")); + + return [ + { + name: "anolilab/sonarjs/plugin", + plugins: { + sonarjs: sonarJsPlugin, + }, + }, + { + files, + name: "anolilab/sonarjs/rules", rules: { + ...sonarJsPlugin.configs["recommended"].rules, + "sonarjs/file-name-differ-from-class": "error", + "sonarjs/no-collapsible-if": "error", "sonarjs/no-nested-template-literals": "off", + "sonarjs/no-tab": "error", + + // This rule does not work will with disable next line + "sonarjs/todo-tag": "off", + + ...overrides, }, }, - type: "all", - }, - { - config: { + { + files: getFilesGlobs("js_and_ts"), + name: "anolilab/sonarjs/js-and-ts-rules", rules: { // relax complexity for react code "sonarjs/cognitive-complexity": ["error", 15], @@ -23,21 +41,16 @@ const config: Linter.Config = createConfigs([ "sonarjs/no-duplicate-string": "off", }, }, - type: "js_and_ts", - }, - { - config: { - parser: "espree", - parserOptions: { + { + files: getFilesGlobs("js"), + languageOptions: { ecmaVersion: 2020, }, + name: "anolilab/sonarjs/js-rules", rules: { "sonarjs/no-all-duplicated-branches": "off", "sonarjs/no-duplicate-string": "off", }, }, - type: "javascript", - }, -]); - -export default config; + ]; +}); diff --git a/packages/eslint-config/src/config/plugins/ssr-friendly.ts b/packages/eslint-config/src/config/plugins/ssr-friendly.ts deleted file mode 100644 index 548fe095b..000000000 --- a/packages/eslint-config/src/config/plugins/ssr-friendly.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { Linter } from "eslint"; - -import { createConfig } from "../../utils/create-config"; - -// @see https://github.com/francoismassart/eslint-plugin-tailwindcss, -const config: Linter.Config = createConfig("jsx_and_tsx", { - plugins: ["ssr-friendly"], - extends: ["plugin:ssr-friendly/recommended"], -}); - -export default config; diff --git a/packages/eslint-config/src/config/plugins/storybook.ts b/packages/eslint-config/src/config/plugins/storybook.ts index 844be5bff..3f49dca82 100644 --- a/packages/eslint-config/src/config/plugins/storybook.ts +++ b/packages/eslint-config/src/config/plugins/storybook.ts @@ -1,19 +1,18 @@ -import type { Linter } from "eslint"; +import type { OptionsOverrides } from "../../types"; +import { createConfig } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; -// @see https://github.com/storybookjs/eslint-plugin-storybook -const config: Linter.Config = { - env: { - browser: true, - es6: true, - node: true, - }, - overrides: [ - { - extends: ["plugin:storybook/recommended"], - // For performance run storybook/recommended on test files, not regular code - files: ["**/*.stories.{ts,tsx,mdx}", ".storybook/**/*"], - }, - ], -}; +export default createConfig("storybook", async (config) => { + const { overrides } = config; -export default config; + const storybookPlugin = await interopDefault(import("eslint-plugin-storybook")); + + const options = [...storybookPlugin.configs["flat/recommended"]]; + + options[0].rules = { + ...options[0].rules, + ...overrides, + }; + + return options; +}); diff --git a/packages/eslint-config/src/config/plugins/stylistic.ts b/packages/eslint-config/src/config/plugins/stylistic.ts new file mode 100644 index 000000000..e4b7a31d4 --- /dev/null +++ b/packages/eslint-config/src/config/plugins/stylistic.ts @@ -0,0 +1,438 @@ +import type { + OptionsHasPrettier, + OptionsOverrides, + StylisticConfig, + TypedFlatConfigItem, +} from "../../types"; +import interopDefault from "../../utils/interop-default"; + +const specialRule = 0; + +const stylistic = async (options: OptionsHasPrettier & OptionsOverrides & StylisticConfig): Promise => { + const { + indent, + jsx, + overrides = {}, + prettier, + quotes, + semi, + } = { + // eslint-disable-next-line @typescript-eslint/no-use-before-define + ...StylisticConfigDefaults, + ...options, + }; + + const pluginStylistic = await interopDefault(import("@stylistic/eslint-plugin")); + + const config = pluginStylistic.configs.customize({ + indent, + jsx, + pluginName: "@stylistic", + quotes: prettier ? undefined : quotes, + semi: prettier ? false : semi, + }); + + return [ + { + name: "anolilab/stylistic/rules", + plugins: { + "@stylistic": pluginStylistic, + }, + rules: { + ...config.rules, + + // Disable arrow parens rule + // We are using the arrow-parens + "@stylistic/arrow-parens": "off", + + // enforce spacing inside single-line blocks + "@stylistic/block-spacing": ["error", "always"], + + // Replace 'brace-style' rule with '@stylistic' version + // enforce one true brace style + "@stylistic/brace-style": ["error", "1tbs", { allowSingleLine: true }], + + // Replace 'comma-dangle' rule with '@stylistic' version + // The TypeScript version also adds 3 new options, all of which should be set to the same value as the base config + "@stylistic/comma-dangle": [ + "error", + { + arrays: "always-multiline", + enums: "always-multiline", + exports: "always-multiline", + functions: "always-multiline", + generics: "always-multiline", + imports: "always-multiline", + objects: "always-multiline", + tuples: "always-multiline", + }, + ], + + // Replace 'comma-spacing' rule with '@stylistic' version + // enforce spacing before and after comma + "@stylistic/comma-spacing": ["error", { after: true, before: false }], + + // Replace 'func-call-spacing' rule with '@stylistic' version + "@stylistic/func-call-spacing": ["error", "never"], + + "@stylistic/generator-star-spacing": ["error", { after: true, before: false }], + + "@stylistic/indent": [ + "error", + indent, + { + ArrayExpression: 1, + CallExpression: { + arguments: 1, + }, + flatTernaryExpressions: false, + // MemberExpression: null, + FunctionDeclaration: { + body: 1, + parameters: 1, + }, + FunctionExpression: { + body: 1, + parameters: 1, + }, + ignoreComments: false, + // list derived from https://github.com/benjamn/ast-types/blob/HEAD/def/jsx.js + ignoredNodes: [ + "JSXElement", + "JSXElement > *", + "JSXAttribute", + "JSXIdentifier", + "JSXNamespacedName", + "JSXMemberExpression", + "JSXSpreadAttribute", + "JSXExpressionContainer", + "JSXOpeningElement", + "JSXClosingElement", + "JSXFragment", + "JSXOpeningFragment", + "JSXClosingFragment", + "JSXText", + "JSXEmptyExpression", + "JSXSpreadChild", + ], + ImportDeclaration: 1, + ObjectExpression: 1, + outerIIFEBody: 1, + SwitchCase: 1, + VariableDeclarator: 1, + }, + ], + + // enforces spacing between keys and values in object literal properties + "@stylistic/key-spacing": ["error", { afterColon: true, beforeColon: false }], + + // require a space before & after certain keywords + "@stylistic/keyword-spacing": [ + "error", + { + after: true, + before: true, + overrides: { + case: { after: true }, + return: { after: true }, + throw: { after: true }, + }, + }, + ], + + // require or disallow an empty line between class members + // enforces empty lines around comments + "@stylistic/lines-around-comment": "off", + + // require or disallow newlines around directives + // https://eslint.org/docs/rules/lines-between-class-members + "@stylistic/lines-between-class-members": ["error", "always", { exceptAfterSingleLine: false }], + + "@stylistic/member-delimiter-style": "error", + + // disallow unnecessary parentheses + // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-extra-parens.md + "@stylistic/no-extra-parens": [ + "error", + "all", + { + conditionalAssign: true, + enforceForArrowConditionals: false, + ignoreJSX: "all", // delegate to eslint-plugin-react + nestedBinaryExpressions: false, + returnAssign: false, + }, + ], + + // Disallow non-null assertion in locations that may be confusing. + // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-extra-semi.md + "@stylistic/no-extra-semi": "error", + + "@stylistic/padding-line-between-statements": [ + "error", + // Require blank lines after all directive prologues (e.g. 'use strict') + { + blankLine: "always", + next: "*", + prev: "directive", + }, + // Disallow blank lines between all directive prologues (e.g. 'use strict') + { + blankLine: "never", + next: "directive", + prev: "directive", + }, + // Require blank lines after every sequence of variable declarations + { + blankLine: "always", + next: "*", + prev: ["const", "let", "var"], + }, + // Blank lines could be between variable declarations + { + blankLine: "any", + next: ["const", "let", "var"], + prev: ["const", "let", "var"], + }, + // Require blank lines before all return statements + { + blankLine: "always", + next: "return", + prev: "*", + }, + // Require blank lines before and after all following statements + { + blankLine: "always", + next: ["for", "function", "if", "switch", "try"], + prev: "*", + }, + { + blankLine: "always", + next: "*", + prev: ["for", "function", "if", "switch", "try"], + }, + ], + + // require quotes around object literal property names + // https://eslint.org/docs/rules/quote-props.html + "@stylistic/quote-props": ["error", "as-needed", { keywords: false, numbers: false, unnecessary: true }], + + // require or disallow space before blocks + "@stylistic/space-before-blocks": "error", + + // require or disallow space before function opening parenthesis + "@stylistic/space-before-function-paren": [ + "error", + { + anonymous: "always", + asyncArrow: "always", + named: "never", + }, + ], + + // require spaces around operators + "@stylistic/space-infix-ops": "error", + + "@stylistic/type-annotation-spacing": "error", + + "@stylistic/yield-star-spacing": ["error", { after: true, before: false }], + + ...overrides, + + ...prettier + ? { + "@stylistic/array-bracket-newline": "off", + "@stylistic/array-bracket-spacing": "off", + "@stylistic/array-element-newline": "off", + "@stylistic/arrow-parens": "off", + "@stylistic/arrow-spacing": "off", + "@stylistic/block-spacing": "off", + "@stylistic/brace-style": "off", + "@stylistic/comma-dangle": "off", + "@stylistic/comma-spacing": "off", + "@stylistic/comma-style": "off", + "@stylistic/computed-property-spacing": "off", + "@stylistic/dot-location": "off", + "@stylistic/eol-last": "off", + "@stylistic/func-call-spacing": "off", + "@stylistic/function-call-argument-newline": "off", + "@stylistic/function-call-spacing": "off", + "@stylistic/function-paren-newline": "off", + "@stylistic/generator-star-spacing": "off", + "@stylistic/implicit-arrow-linebreak": "off", + "@stylistic/indent": "off", + "@stylistic/indent-binary-ops": "off", + "@stylistic/js/array-bracket-newline": "off", + "@stylistic/js/array-bracket-spacing": "off", + "@stylistic/js/array-element-newline": "off", + "@stylistic/js/arrow-parens": "off", + "@stylistic/js/arrow-spacing": "off", + "@stylistic/js/block-spacing": "off", + "@stylistic/js/brace-style": "off", + "@stylistic/js/comma-dangle": "off", + "@stylistic/js/comma-spacing": "off", + "@stylistic/js/comma-style": "off", + "@stylistic/js/computed-property-spacing": "off", + "@stylistic/js/dot-location": "off", + "@stylistic/js/eol-last": "off", + "@stylistic/js/func-call-spacing": "off", + "@stylistic/js/function-call-argument-newline": "off", + "@stylistic/js/function-call-spacing": "off", + "@stylistic/js/function-paren-newline": "off", + "@stylistic/js/generator-star-spacing": "off", + "@stylistic/js/implicit-arrow-linebreak": "off", + "@stylistic/js/indent": "off", + "@stylistic/js/jsx-quotes": "off", + "@stylistic/js/key-spacing": "off", + "@stylistic/js/keyword-spacing": "off", + "@stylistic/js/linebreak-style": "off", + "@stylistic/js/lines-around-comment": specialRule, + "@stylistic/js/max-len": specialRule, + "@stylistic/js/max-statements-per-line": "off", + "@stylistic/js/multiline-ternary": "off", + "@stylistic/js/new-parens": "off", + "@stylistic/js/newline-per-chained-call": "off", + "@stylistic/js/no-confusing-arrow": specialRule, + "@stylistic/js/no-extra-parens": "off", + "@stylistic/js/no-extra-semi": "off", + "@stylistic/js/no-floating-decimal": "off", + "@stylistic/js/no-mixed-operators": specialRule, + "@stylistic/js/no-mixed-spaces-and-tabs": "off", + "@stylistic/js/no-multi-spaces": "off", + "@stylistic/js/no-multiple-empty-lines": "off", + "@stylistic/js/no-tabs": specialRule, + "@stylistic/js/no-trailing-spaces": "off", + "@stylistic/js/no-whitespace-before-property": "off", + "@stylistic/js/nonblock-statement-body-position": "off", + "@stylistic/js/object-curly-newline": "off", + "@stylistic/js/object-curly-spacing": "off", + "@stylistic/js/object-property-newline": "off", + "@stylistic/js/one-var-declaration-per-line": "off", + "@stylistic/js/operator-linebreak": "off", + "@stylistic/js/padded-blocks": "off", + "@stylistic/js/quote-props": "off", + "@stylistic/js/quotes": specialRule, + "@stylistic/js/rest-spread-spacing": "off", + "@stylistic/js/semi": "off", + "@stylistic/js/semi-spacing": "off", + "@stylistic/js/semi-style": "off", + "@stylistic/js/space-before-blocks": "off", + "@stylistic/js/space-before-function-paren": "off", + "@stylistic/js/space-in-parens": "off", + "@stylistic/js/space-infix-ops": "off", + "@stylistic/js/space-unary-ops": "off", + "@stylistic/js/switch-colon-spacing": "off", + "@stylistic/js/template-curly-spacing": "off", + "@stylistic/js/template-tag-spacing": "off", + "@stylistic/js/wrap-iife": "off", + "@stylistic/js/wrap-regex": "off", + "@stylistic/js/yield-star-spacing": "off", + "@stylistic/jsx-child-element-spacing": "off", + "@stylistic/jsx-closing-bracket-location": "off", + "@stylistic/jsx-closing-tag-location": "off", + "@stylistic/jsx-curly-newline": "off", + "@stylistic/jsx-curly-spacing": "off", + "@stylistic/jsx-equals-spacing": "off", + "@stylistic/jsx-first-prop-new-line": "off", + "@stylistic/jsx-indent": "off", + "@stylistic/jsx-indent-props": "off", + "@stylistic/jsx-max-props-per-line": "off", + "@stylistic/jsx-newline": "off", + "@stylistic/jsx-one-expression-per-line": "off", + "@stylistic/jsx-props-no-multi-spaces": "off", + "@stylistic/jsx-quotes": "off", + "@stylistic/jsx-tag-spacing": "off", + "@stylistic/jsx-wrap-multilines": "off", + "@stylistic/jsx/jsx-child-element-spacing": "off", + "@stylistic/jsx/jsx-closing-bracket-location": "off", + "@stylistic/jsx/jsx-closing-tag-location": "off", + "@stylistic/jsx/jsx-curly-newline": "off", + "@stylistic/jsx/jsx-curly-spacing": "off", + "@stylistic/jsx/jsx-equals-spacing": "off", + "@stylistic/jsx/jsx-first-prop-new-line": "off", + "@stylistic/jsx/jsx-indent": "off", + "@stylistic/jsx/jsx-indent-props": "off", + "@stylistic/jsx/jsx-max-props-per-line": "off", + "@stylistic/key-spacing": "off", + "@stylistic/keyword-spacing": "off", + "@stylistic/linebreak-style": "off", + "@stylistic/lines-around-comment": specialRule, + "@stylistic/max-len": specialRule, + "@stylistic/max-statements-per-line": "off", + "@stylistic/member-delimiter-style": "off", + "@stylistic/multiline-ternary": "off", + "@stylistic/new-parens": "off", + "@stylistic/newline-per-chained-call": "off", + "@stylistic/no-confusing-arrow": specialRule, + "@stylistic/no-extra-parens": "off", + "@stylistic/no-extra-semi": "off", + "@stylistic/no-floating-decimal": "off", + "@stylistic/no-mixed-operators": specialRule, + "@stylistic/no-mixed-spaces-and-tabs": "off", + "@stylistic/no-multi-spaces": "off", + "@stylistic/no-multiple-empty-lines": "off", + "@stylistic/no-tabs": specialRule, + "@stylistic/no-trailing-spaces": "off", + "@stylistic/no-whitespace-before-property": "off", + "@stylistic/nonblock-statement-body-position": "off", + "@stylistic/object-curly-newline": "off", + "@stylistic/object-curly-spacing": "off", + "@stylistic/object-property-newline": "off", + "@stylistic/one-var-declaration-per-line": "off", + "@stylistic/operator-linebreak": "off", + "@stylistic/padded-blocks": "off", + "@stylistic/quote-props": "off", + "@stylistic/quotes": specialRule, + "@stylistic/rest-spread-spacing": "off", + "@stylistic/semi": "off", + "@stylistic/semi-spacing": "off", + "@stylistic/semi-style": "off", + "@stylistic/space-before-blocks": "off", + "@stylistic/space-before-function-paren": "off", + "@stylistic/space-in-parens": "off", + "@stylistic/space-infix-ops": "off", + "@stylistic/space-unary-ops": "off", + "@stylistic/switch-colon-spacing": "off", + "@stylistic/template-curly-spacing": "off", + "@stylistic/template-tag-spacing": "off", + "@stylistic/ts/block-spacing": "off", + "@stylistic/ts/brace-style": "off", + "@stylistic/ts/comma-dangle": "off", + "@stylistic/ts/comma-spacing": "off", + "@stylistic/ts/func-call-spacing": "off", + "@stylistic/ts/function-call-spacing": "off", + "@stylistic/ts/indent": "off", + "@stylistic/ts/key-spacing": "off", + "@stylistic/ts/keyword-spacing": "off", + "@stylistic/ts/lines-around-comment": specialRule, + "@stylistic/ts/member-delimiter-style": "off", + "@stylistic/ts/no-extra-parens": "off", + "@stylistic/ts/no-extra-semi": "off", + "@stylistic/ts/object-curly-spacing": "off", + "@stylistic/ts/quotes": specialRule, + "@stylistic/ts/semi": "off", + "@stylistic/ts/space-before-blocks": "off", + "@stylistic/ts/space-before-function-paren": "off", + "@stylistic/ts/space-infix-ops": "off", + "@stylistic/ts/type-annotation-spacing": "off", + "@stylistic/type-annotation-spacing": "off", + "@stylistic/type-generic-spacing": "off", + "@stylistic/type-named-tuple-spacing": "off", + "@stylistic/wrap-iife": "off", + "@stylistic/wrap-regex": "off", + "@stylistic/yield-star-spacing": "off", + } + : {}, + }, + }, + ]; +}; + +export default stylistic; + +export const StylisticConfigDefaults: StylisticConfig = { + indent: 4, + jsx: true, + quotes: "double", + semi: true, +}; diff --git a/packages/eslint-config/src/config/plugins/tailwindcss.ts b/packages/eslint-config/src/config/plugins/tailwindcss.ts index aadbc9771..b7a5ba890 100644 --- a/packages/eslint-config/src/config/plugins/tailwindcss.ts +++ b/packages/eslint-config/src/config/plugins/tailwindcss.ts @@ -1,19 +1,20 @@ -import type { Linter } from "eslint"; - +import type { OptionsFiles, OptionsOverrides } from "../../types"; import { createConfig } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; // @see https://github.com/francoismassart/eslint-plugin-tailwindcss, -const config: Linter.Config = createConfig( - "jsx_and_tsx", - { - extends: ["plugin:tailwindcss/recommended"], - plugins: ["tailwindcss"], - }, - { - browser: true, - es6: true, - node: true, - }, -); +export default createConfig("jsx_and_tsx", async (config, oFiles) => { + const { files = oFiles, overrides } = config; + + const validateJsxNestingPlugin = await interopDefault(import("eslint-plugin-tailwindcss")); + + const options = [...validateJsxNestingPlugin.configs["flat/recommended"]]; + + options[1].files = files; + options[1].rules = { + ...options[1].rules, + ...overrides, + }; -export default config; + return options; +}); diff --git a/packages/eslint-config/src/config/plugins/tanstack-query.ts b/packages/eslint-config/src/config/plugins/tanstack-query.ts index b6d7fb25e..209c2408c 100644 --- a/packages/eslint-config/src/config/plugins/tanstack-query.ts +++ b/packages/eslint-config/src/config/plugins/tanstack-query.ts @@ -1,10 +1,23 @@ -import type { Linter } from "eslint"; - +import type { OptionsFiles, OptionsOverrides } from "../../types"; import { createConfig } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; // @see https://tanstack.com/query/v4/docs/react/eslint/eslint-plugin-query -const config: Linter.Config = createConfig("all", { - extends: ["plugin:@tanstack/eslint-plugin-query/recommended"], -}); +export default createConfig("all", async (config, oFiles) => { + const { files = oFiles, overrides } = config; -export default config; + const pluginTanstackQuery = await interopDefault(import("@tanstack/eslint-plugin-query")); + + return [ + { + files, + plugins: { + "@tanstack/query": pluginTanstackQuery, + }, + rules: { + ...pluginTanstackQuery.configs["recommended"].rules, + ...overrides, + }, + }, + ]; +}); diff --git a/packages/eslint-config/src/config/plugins/tanstack-router.ts b/packages/eslint-config/src/config/plugins/tanstack-router.ts new file mode 100644 index 000000000..0a9c6d20f --- /dev/null +++ b/packages/eslint-config/src/config/plugins/tanstack-router.ts @@ -0,0 +1,22 @@ +import type { OptionsFiles, OptionsOverrides } from "../../types"; +import { createConfig } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; + +export default createConfig("all", async (config, oFiles) => { + const { files = oFiles, overrides } = config; + + const pluginTanstackRouter = await interopDefault(import("@tanstack/eslint-plugin-router")); + + return [ + { + files, + plugins: { + "@tanstack/router": pluginTanstackRouter, + }, + rules: { + ...pluginTanstackRouter.configs["recommended"].rules, + ...overrides, + }, + }, + ]; +}); diff --git a/packages/eslint-config/src/config/plugins/testing-library-dom.ts b/packages/eslint-config/src/config/plugins/testing-library-dom.ts deleted file mode 100644 index 43c43fa54..000000000 --- a/packages/eslint-config/src/config/plugins/testing-library-dom.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { Linter } from "eslint"; - -import { createConfig } from "../../utils/create-config"; -import anolilabEslintConfig from "../../utils/eslint-config"; -import { consolePlugin } from "../../utils/loggers"; - -// Workaround VS Code trying to run this file twice! -if (!global.hasAnolilabEsLintTestConfigLoaded) { - if (anolilabEslintConfig["info_on_testing_library_framework"] !== false) { - consolePlugin(`testing-library: loading "dom" ruleset`); - } - - global.hasAnolilabEsLintTestConfigLoaded = true; -} - -// For performance enable react-testing-library only on test files -const config: Linter.Config = createConfig( - "tests", - { - extends: [`plugin:testing-library/dom`], - }, - { - browser: true, - es6: true, - node: true, - }, -); - -export default config; diff --git a/packages/eslint-config/src/config/plugins/testing-library-react.ts b/packages/eslint-config/src/config/plugins/testing-library-react.ts deleted file mode 100644 index 0e15a8540..000000000 --- a/packages/eslint-config/src/config/plugins/testing-library-react.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { Linter } from "eslint"; - -import { createConfig } from "../../utils/create-config"; -import anolilabEslintConfig from "../../utils/eslint-config"; -import { consolePlugin } from "../../utils/loggers"; - -// Workaround VS Code trying to run this file twice! -if (!global.hasAnolilabEsLintTestConfigLoaded) { - if (anolilabEslintConfig["info_on_testing_library_framework"] !== false) { - consolePlugin(`testing-library: loading "react" ruleset`); - } - - global.hasAnolilabEsLintTestConfigLoaded = true; -} - -// For performance enable react-testing-library only on test files -const config: Linter.Config = createConfig( - "tests", - { - extends: [`plugin:testing-library/react`], - }, - { - browser: true, - es6: true, - node: true, - }, -); - -export default config; diff --git a/packages/eslint-config/src/config/plugins/testing-library.ts b/packages/eslint-config/src/config/plugins/testing-library.ts new file mode 100644 index 000000000..cdb80c49b --- /dev/null +++ b/packages/eslint-config/src/config/plugins/testing-library.ts @@ -0,0 +1,28 @@ +import { hasPackageJsonAnyDependency } from "@visulima/package"; + +import type { OptionsFiles, OptionsOverrides, OptionsPackageJson } from "../../types"; +import { createConfig } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; + +export default createConfig("vitest", async (config, oFiles) => { + const { files = oFiles, overrides, packageJson } = config; + + const testingLibraryPlugin = await interopDefault(import("eslint-plugin-testing-library")); + + const hasReact = hasPackageJsonAnyDependency(packageJson, ["react", "react-dom", "eslint-plugin-react"]); + + return [ + { + files, + plugins: { + "testing-library": testingLibraryPlugin, + }, + rules: { + ...testingLibraryPlugin.configs["flat/dom"].rules, + ...hasReact ? testingLibraryPlugin.configs["flat/react"].rules : {}, + + ...overrides, + }, + }, + ]; +}); diff --git a/packages/eslint-config/src/config/plugins/toml.ts b/packages/eslint-config/src/config/plugins/toml.ts index 7997348be..88e4bac60 100644 --- a/packages/eslint-config/src/config/plugins/toml.ts +++ b/packages/eslint-config/src/config/plugins/toml.ts @@ -1,13 +1,55 @@ -import type { Linter } from "eslint"; +import type { OptionsFiles, OptionsOverrides, OptionsStylistic } from "../../types"; +import { createConfig } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; -const config: Linter.Config = { - overrides: [ +export default createConfig("toml", async (config, oFiles) => { + const { files = oFiles, overrides = {}, stylistic = true } = config; + + const { indent = 2 } = typeof stylistic === "boolean" ? {} : stylistic; + + const [pluginToml, parserToml] = await Promise.all([interopDefault(import("eslint-plugin-toml")), interopDefault(import("toml-eslint-parser"))] as const); + + return [ { - extends: ["plugin:toml/standard"], - files: ["**/*.toml"], - parser: "toml-eslint-parser", - }, - ], -}; + files, + languageOptions: { + parser: parserToml, + }, + name: "anolilab/toml", + plugins: { + toml: pluginToml, + }, + rules: { + ...stylistic ? { "@stylistic/spaced-comment": "off" } : {}, -export default config; + "toml/comma-style": "error", + "toml/keys-order": "error", + "toml/no-space-dots": "error", + "toml/no-unreadable-number-separator": "error", + "toml/precision-of-fractional-seconds": "error", + "toml/precision-of-integer": "error", + "toml/tables-order": "error", + + "toml/vue-custom-block/no-parsing-error": "error", + + ...stylistic + ? { + "toml/array-bracket-newline": "error", + "toml/array-bracket-spacing": "error", + "toml/array-element-newline": "error", + "toml/indent": ["error", indent === "tab" ? 2 : indent], + "toml/inline-table-curly-spacing": "error", + "toml/key-spacing": "error", + "toml/padding-line-between-pairs": "error", + "toml/padding-line-between-tables": "error", + "toml/quoted-keys": "error", + "toml/spaced-comment": "error", + "toml/table-bracket-spacing": "error", + } + : {}, + + ...overrides, + }, + }, + ]; +}); diff --git a/packages/eslint-config/src/config/plugins/tsdoc.ts b/packages/eslint-config/src/config/plugins/tsdoc.ts index 75a2ac226..689d48adf 100644 --- a/packages/eslint-config/src/config/plugins/tsdoc.ts +++ b/packages/eslint-config/src/config/plugins/tsdoc.ts @@ -1,12 +1,23 @@ -import type { Linter } from "eslint"; - +import type { OptionsFiles, OptionsOverrides } from "../../types"; import { createConfig } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; -const config: Linter.Config = createConfig("typescript", { - plugins: ["eslint-plugin-tsdoc"], - rules: { - "tsdoc/syntax": "error", - }, -}); +export default createConfig("ts", async (config, oFiles) => { + const { files = oFiles, overrides } = config; + + const eslintPluginTsdoc = await interopDefault(import("eslint-plugin-tsdoc")); -export default config; + return [ + { + files, + plugins: { + tsdoc: eslintPluginTsdoc, + }, + rules: { + "tsdoc/syntax": "error", + + ...overrides, + }, + }, + ]; +}); diff --git a/packages/eslint-config/src/config/plugins/typescript.ts b/packages/eslint-config/src/config/plugins/typescript.ts index 5865fcec9..dc52efc4c 100644 --- a/packages/eslint-config/src/config/plugins/typescript.ts +++ b/packages/eslint-config/src/config/plugins/typescript.ts @@ -1,403 +1,449 @@ -import { env } from "node:process"; - -import { hasDependency, hasDevDependency } from "@anolilab/package-json-utils"; import type { Linter } from "eslint"; -import { createConfigs } from "../../utils/create-config"; -import anolilabEslintConfig from "../../utils/eslint-config"; -import bestPracticesConfig from "../best-practices"; -import errorsConfig from "../errors"; -// eslint-disable-next-line unicorn/prevent-abbreviations -import eS6Config from "../es6"; -import styleConfig from "../style"; -import variablesConfig from "../variables"; - -// @ts-expect-error TODO: find the correct type -const bestPracticesRules = bestPracticesConfig.overrides[0].rules as Linter.RulesRecord; -// @ts-expect-error TODO: find the correct type -const errorsRules = errorsConfig.overrides[0].rules as Linter.RulesRecord; -// @ts-expect-error TODO: find the correct type -const styleRules = styleConfig.overrides[0].rules as Linter.RulesRecord; -// @ts-expect-error TODO: find the correct type -// eslint-disable-next-line unicorn/prevent-abbreviations -const eS6Rules = eS6Config.overrides[0].rules as Linter.RulesRecord; -// @ts-expect-error TODO: find the correct type -const variablesRules = variablesConfig.overrides[0].rules as Linter.RulesRecord; - -const { indent, quotes, semi } = styleRules; - -if (global.anolilabEslintConfigTypescriptPrettierRules === undefined && (hasDependency("prettier") || hasDevDependency("prettier"))) { - global.anolilabEslintConfigTypescriptPrettierRules = { - "@typescript-eslint/block-spacing": "off", - "@typescript-eslint/brace-style": "off", - "@typescript-eslint/comma-dangle": "off", - "@typescript-eslint/comma-spacing": "off", - "@typescript-eslint/func-call-spacing": "off", - "@typescript-eslint/indent": "off", - "@typescript-eslint/key-spacing": "off", - "@typescript-eslint/keyword-spacing": "off", - "@typescript-eslint/lines-around-comment": 0, - "@typescript-eslint/member-delimiter-style": "off", - "@typescript-eslint/no-extra-parens": "off", - "@typescript-eslint/no-extra-semi": "off", - "@typescript-eslint/object-curly-spacing": "off", - "@typescript-eslint/quotes": 0, - "@typescript-eslint/semi": "off", - "@typescript-eslint/space-before-blocks": "off", - "@typescript-eslint/space-before-function-paren": "off", - "@typescript-eslint/space-infix-ops": "off", - "@typescript-eslint/type-annotation-spacing": "off", +import type { + OptionsComponentExtensions, + OptionsFiles, + OptionsHasPrettier, + OptionsIsInEditor, + OptionsOverrides, + OptionsStylistic, + OptionsTypeScriptParserOptions, + OptionsTypeScriptWithTypes, + TypedFlatConfigItem, +} from "../../types"; +import { createConfig, getFilesGlobs } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; +import { bestPracticesRules } from "../best-practices"; +import { es6Rules as es6RulesFunction } from "../es6"; +import { styleRules as styleRulesFunction } from "../style"; +import { variablesRules } from "../variables"; + +export default createConfig< + OptionsComponentExtensions & + OptionsFiles & + OptionsHasPrettier & + OptionsIsInEditor & + OptionsOverrides & + OptionsStylistic & + OptionsTypeScriptParserOptions & + OptionsTypeScriptWithTypes +>("ts", async (config, oFiles) => { + const { + componentExts: componentExtensions = [], + files = oFiles, + isInEditor = false, + overrides, + overridesTypeAware, + parserOptions, + prettier, + stylistic = true, + } = config; + + const styleRules = styleRulesFunction(stylistic); + const es6Rules = es6RulesFunction(isInEditor); + + const [pluginTs, parserTs, tseslint, noForOfArrayPlugin] = await Promise.all([ + interopDefault(import("@typescript-eslint/eslint-plugin")), + interopDefault(import("@typescript-eslint/parser")), + interopDefault(import("typescript-eslint")), + interopDefault(import("eslint-plugin-no-for-of-array")), + ] as const); + + const filesTypeAware = config.filesTypeAware ?? getFilesGlobs("ts"); + const ignoresTypeAware = config.ignoresTypeAware ?? [`**/*.md/**`, ...getFilesGlobs("astro")]; + const tsconfigPath = config?.tsconfigPath ?? undefined; + const isTypeAware = tsconfigPath !== undefined; + + const makeParser = (typeAware: boolean, pFiles: string[], ignores?: string[]): TypedFlatConfigItem => { + return { + files: [...pFiles, ...componentExtensions.map((extension) => `**/*.${extension}`)], + ...ignores ? { ignores } : {}, + languageOptions: { + parser: parserTs, + parserOptions: { + extraFileExtensions: componentExtensions.map((extension) => `.${extension}`), + sourceType: "module", + ...typeAware + ? { + projectService: { + allowDefaultProject: ["./*.js"], + defaultProject: tsconfigPath, + }, + tsconfigRootDir: process.cwd(), + } + : {}, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ...(parserOptions as any), + }, + }, + name: `anolilab/typescript/${typeAware ? "type-aware-parser" : "parser"}`, + }; }; -} -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const commaDangle = styleRules["comma-dangle"] as any[]; + const rules: TypedFlatConfigItem[] = [ + { + // Install the plugins without globs, so they can be configured separately. + name: "anolilab/typescript/setup", + plugins: { + "@typescript-eslint": pluginTs, + "no-for-of-array": noForOfArrayPlugin, + }, + }, + // assign type-aware parser for type-aware files and type-unaware parser for the rest + ...isTypeAware ? [makeParser(false, files), makeParser(true, filesTypeAware, ignoresTypeAware)] : [makeParser(false, files)], + ...(tseslint.configs.strict as TypedFlatConfigItem[]), + ]; + + if (isTypeAware) { + rules.push( + ...(tseslint.configs.strictTypeCheckedOnly as TypedFlatConfigItem[]), + { + files: [...filesTypeAware, ...componentExtensions.map((extension) => `**/*.${extension}`)], + name: "anolilab/typescript/rules-type-aware", + rules: { + // Disallow type assertions that do not change the type of expression. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-unnecessary-type-assertion.md + "@typescript-eslint/no-unnecessary-type-assertion": "error", + + // Disallow calling a function with a value with type any. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-unsafe-argument.md + "@typescript-eslint/no-unsafe-argument": "error", + + // Disallow assigning a value with type any to variables and properties. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-unsafe-assignment.md + "@typescript-eslint/no-unsafe-assignment": "error", + + // Disallow calling a value with type any. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-unsafe-call.md + "@typescript-eslint/no-unsafe-call": "error", + + // Disallow member access on a value with type any. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-unsafe-member-access.md + "@typescript-eslint/no-unsafe-member-access": "error", + + // Disallow returning a value with type any from a function. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-unsafe-return.md + "@typescript-eslint/no-unsafe-return": "error", + + // Enforce using the nullish coalescing operator instead of logical chaining. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md + "@typescript-eslint/prefer-nullish-coalescing": "error", + + // Enforce using concise optional chain expressions instead of chained logical ands, negated logical ors, or empty objects. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/prefer-optional-chain.md + "@typescript-eslint/prefer-optional-chain": "error", + + ...overridesTypeAware, + }, + }, + { + files: getFilesGlobs("all"), + name: "anolilab/typescript/no-for-of-array/rules", + rules: { + "no-for-of-array/no-for-of-array": "error", + }, + }, + ); + } + + if (stylistic) { + rules.push(...(tseslint.configs.stylistic as TypedFlatConfigItem[])); + + if (isTypeAware) { + rules.push(...(tseslint.configs.stylisticTypeCheckedOnly as TypedFlatConfigItem[])); + } + } + + rules.push({ + files, + name: "anolilab/typescript/rules", + rules: { + // Require that function overload signatures be consecutive. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/adjacent-overload-signatures.md + "@typescript-eslint/adjacent-overload-signatures": "error", + + // Requires using either T[] for arrays (array-type) + "@typescript-eslint/array-type": [ + "error", + { + default: "array", + readonly: "generic", + }, + ], + + "@typescript-eslint/ban-ts-comment": [ + "error", + { + minimumDescriptionLength: 3, + "ts-check": false, + "ts-expect-error": "allow-with-description", + "ts-ignore": "allow-with-description", + "ts-nocheck": true, + }, + ], + + // @TODO: Fix this rule + // Some built-in types have aliases, while some types are considered dangerous or harmful. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/ban-types.md + // Enforces that types will not to be used + // "@typescript-eslint/ban-types": [ + // "error", + // { + // types: { + // String: { message: "Use string instead", fixWith: "string" }, + // Boolean: { message: "Use boolean instead", fixWith: "boolean" }, + // Number: { message: "Use number instead", fixWith: "number" }, + // Object: { message: "Use object instead", fixWith: "object" }, + // Array: { message: "Provide a more specific type" }, + // }, + // }, + // ], + + // Enforce specifying generic type arguments on constructor name of a constructor call. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/consistent-generic-constructors.md + "@typescript-eslint/consistent-generic-constructors": "error", + + // Enforce consistent usage of type imports. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/consistent-type-imports.md + "@typescript-eslint/consistent-type-imports": [ + "error", + { + disallowTypeAnnotations: false, + fixStyle: "separate-type-imports", + prefer: "type-imports", + }, + ], + + // Require explicit accessibility modifiers on class properties and methods. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/explicit-member-accessibility.md + "@typescript-eslint/explicit-member-accessibility": "error", + + // Require explicit return and argument types on exported functions' and classes' public class methods. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/explicit-module-boundary-types.md + "@typescript-eslint/explicit-module-boundary-types": "error", + + // Enforce a standard member declaration order. (member-ordering from TSLint) + "@typescript-eslint/member-ordering": [ + "error", + { + default: [ + "public-static-field", + "protected-static-field", + "private-static-field", + "public-static-method", + "protected-static-method", + "private-static-method", + "public-instance-field", + "protected-instance-field", + "private-instance-field", + "constructor", + "public-instance-method", + "protected-instance-method", + "private-instance-method", + ], + }, + ], + + // Enforce using a particular method signature syntax. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/method-signature-style.md + "@typescript-eslint/method-signature-style": "error", + + // The `@typescript-eslint/naming-convention` rule allows `leadingUnderscore` and `trailingUnderscore` settings. + // However, the existing `no-underscore-dangle` rule already takes care of this. + "@typescript-eslint/naming-convention": [ + "error", + // Allow camelCase variables (23.2), PascalCase variables (23.8), and UPPER_CASE variables (23.10) + { + format: ["camelCase", "PascalCase", "UPPER_CASE"], + selector: "variable", + }, + // Allow camelCase functions (23.2), and PascalCase functions (23.8) + { + format: ["camelCase", "PascalCase"], + selector: "function", + }, + // recommends PascalCase for classes (23.3), and although it does not make TypeScript recommendations, + // we are assuming this rule would similarly apply to anything "type like", including interfaces, type aliases, and enums + { + format: ["PascalCase"], + selector: "typeLike", + }, + ], + + // Replace 'no-array-constructor' rule with '@typescript-eslint' version + // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-array-constructor.md + "@typescript-eslint/no-array-constructor": styleRules["no-array-constructor"] as Linter.RuleEntry<[]>, -let showUnsupportedTypeScriptVersionWarning: boolean = env["DISABLE_ESLINT_WARN_UNSUPPORTED_TYPESCRIPT_VERSION"] !== "true"; + // Disallow non-null assertion in locations that may be confusing. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-confusing-non-null-assertion.md + "@typescript-eslint/no-confusing-non-null-assertion": "error", -if (anolilabEslintConfig["warn_on_unsupported_typescript_version"] !== undefined) { - showUnsupportedTypeScriptVersionWarning = anolilabEslintConfig["warn_on_unsupported_typescript_version"] as boolean; -} + // Replace 'no-dupe-class-members' rule with '@typescript-eslint' version + // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-dupe-class-members.md + "@typescript-eslint/no-dupe-class-members": es6Rules["no-dupe-class-members"] as Linter.RuleEntry<[]>, -const config: Linter.Config = createConfigs([ - { - config: { - extends: ["plugin:@typescript-eslint/recommended", "plugin:@typescript-eslint/stylistic", "plugin:@typescript-eslint/strict"], - parser: "@typescript-eslint/parser", - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - sourceType: "module", - warnOnUnsupportedTypeScriptVersion: showUnsupportedTypeScriptVersionWarning, - }, - plugins: ["@typescript-eslint"], - rules: { - // Replace 'brace-style' rule with '@typescript-eslint' version - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/brace-style.md - "@typescript-eslint/brace-style": styleRules["brace-style"], - - // The `@typescript-eslint/naming-convention` rule allows `leadingUnderscore` and `trailingUnderscore` settings. However, the existing `no-underscore-dangle` rule already takes care of this. - "@typescript-eslint/naming-convention": [ - "error", - // Allow camelCase variables (23.2), PascalCase variables (23.8), and UPPER_CASE variables (23.10) - { - selector: "variable", - format: ["camelCase", "PascalCase", "UPPER_CASE"], - }, - // Allow camelCase functions (23.2), and PascalCase functions (23.8) - { - selector: "function", - format: ["camelCase", "PascalCase"], - }, - // recommends PascalCase for classes (23.3), and although it does not make TypeScript recommendations, - // we are assuming this rule would similarly apply to anything "type like", including interfaces, type aliases, and enums - { - selector: "typeLike", - format: ["PascalCase"], - }, - ], - - // Replace 'comma-dangle' rule with '@typescript-eslint' version - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/comma-dangle.md - // The TypeScript version also adds 3 new options, all of which should be set to the same value as the base config - "@typescript-eslint/comma-dangle": [ - commaDangle[0], - { - ...commaDangle[1], - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access - enums: commaDangle[1].arrays, - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access - generics: commaDangle[1].arrays, - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access - tuples: commaDangle[1].arrays, - }, - ], - - // Replace 'comma-spacing' rule with '@typescript-eslint' version - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/comma-spacing.md - "@typescript-eslint/comma-spacing": styleRules["comma-spacing"], - - // Replace 'func-call-spacing' rule with '@typescript-eslint' version - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/func-call-spacing.md - "@typescript-eslint/func-call-spacing": styleRules["func-call-spacing"], - - // Replace 'indent' rule with '@typescript-eslint' version - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/indent.md - "@typescript-eslint/indent": indent, - - // Replace 'keyword-spacing' rule with '@typescript-eslint' version - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/keyword-spacing.md - "@typescript-eslint/keyword-spacing": styleRules["keyword-spacing"], - - // Replace 'lines-between-class-members' rule with '@typescript-eslint' version - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/lines-between-class-members.md - "@typescript-eslint/lines-between-class-members": ["error", "always", { exceptAfterSingleLine: false }], - - // Replace 'no-array-constructor' rule with '@typescript-eslint' version - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-array-constructor.md - "@typescript-eslint/no-array-constructor": styleRules["no-array-constructor"], - - // Replace 'no-dupe-class-members' rule with '@typescript-eslint' version - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-dupe-class-members.md - "@typescript-eslint/no-dupe-class-members": eS6Rules["no-dupe-class-members"], - - // Replace 'no-empty-function' rule with '@typescript-eslint' version - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-empty-function.md - "@typescript-eslint/no-empty-function": bestPracticesRules["no-empty-function"], - - // Replace 'no-extra-parens' rule with '@typescript-eslint' version - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-extra-parens.md - "@typescript-eslint/no-extra-parens": errorsRules["no-extra-parens"], - - // Replace 'no-extra-semi' rule with '@typescript-eslint' version - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-extra-semi.md - "@typescript-eslint/no-extra-semi": errorsRules["no-extra-semi"], - - // Replace 'no-loop-func' rule with '@typescript-eslint' version - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-loop-func.md - "@typescript-eslint/no-loop-func": bestPracticesRules["no-loop-func"], - - // Replace 'no-magic-numbers' rule with '@typescript-eslint' version - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-magic-numbers.md - "@typescript-eslint/no-magic-numbers": bestPracticesRules["no-magic-numbers"], - - // Replace 'no-redeclare' rule with '@typescript-eslint' version - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-redeclare.md - "@typescript-eslint/no-redeclare": bestPracticesRules["no-redeclare"], - - // Replace 'no-shadow' rule with '@typescript-eslint' version - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-shadow.md - "@typescript-eslint/no-shadow": variablesRules["no-shadow"], - - // Replace 'no-unused-expressions' rule with '@typescript-eslint' version - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unused-expressions.md - "@typescript-eslint/no-unused-expressions": bestPracticesRules["no-unused-expressions"], - - // Replace 'no-unused-vars' rule with '@typescript-eslint' version - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unused-vars.md - "@typescript-eslint/no-unused-vars": variablesRules["no-unused-vars"], - - // Replace 'no-use-before-define' rule with '@typescript-eslint' version - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-use-before-define.md - "@typescript-eslint/no-use-before-define": variablesRules["no-use-before-define"], - - // Replace 'no-useless-constructor' rule with '@typescript-eslint' version - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-useless-constructor.md - "@typescript-eslint/no-useless-constructor": eS6Rules["no-useless-constructor"], - - // Replace 'quotes' rule with '@typescript-eslint' version - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/quotes.md - "@typescript-eslint/quotes": quotes, - - // Replace 'semi' rule with '@typescript-eslint' version - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/semi.md - "@typescript-eslint/semi": semi, - - // Replace 'space-before-function-paren' rule with '@typescript-eslint' version - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/space-before-function-paren.md - "@typescript-eslint/space-before-function-paren": styleRules["space-before-function-paren"], - - // Replace 'no-return-await' rule with '@typescript-eslint' version - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/return-await.md - "@typescript-eslint/return-await": bestPracticesRules["no-return-await"], - - // Replace 'space-infix-ops' rule with '@typescript-eslint' version - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/space-infix-ops.md - "@typescript-eslint/space-infix-ops": styleRules["space-infix-ops"], - - // Enforce consistent usage of type imports. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/consistent-type-imports.md - "@typescript-eslint/consistent-type-imports": "error", - - // Require that function overload signatures be consecutive. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/adjacent-overload-signatures.md - "@typescript-eslint/adjacent-overload-signatures": "error", - - // Enforce a standard member declaration order. (member-ordering from TSLint) - "@typescript-eslint/member-ordering": [ - "error", - { - default: [ - "public-static-field", - "protected-static-field", - "private-static-field", - "public-static-method", - "protected-static-method", - "private-static-method", - "public-instance-field", - "protected-instance-field", - "private-instance-field", - "constructor", - "public-instance-method", - "protected-instance-method", - "private-instance-method", - ], - }, - ], - - // Requires using either T[] for arrays (array-type) - "@typescript-eslint/array-type": [ - "error", - { - default: "array", - readonly: "generic", - }, - ], - - // Some built-in types have aliases, while some types are considered dangerous or harmful. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/ban-types.md - // Enforces that types will not to be used - "@typescript-eslint/ban-types": [ - "error", - { - types: { - String: { message: "Use string instead", fixWith: "string" }, - Boolean: { message: "Use boolean instead", fixWith: "boolean" }, - Number: { message: "Use number instead", fixWith: "number" }, - Object: { message: "Use object instead", fixWith: "object" }, - Array: { message: "Provide a more specific type" }, - }, - }, - ], - - // Enforce constituents of a type union/intersection to be sorted alphabetically. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/sort-type-constituents.md - "@typescript-eslint/sort-type-constituents": "error", - - // Enforce using @ts-expect-error over @ts-ignore. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/prefer-ts-expect-error.md - "@typescript-eslint/prefer-ts-expect-error": "error", - - // Enforce specifying generic type arguments on constructor name of a constructor call. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/consistent-generic-constructors.md - "@typescript-eslint/consistent-generic-constructors": "error", - - // Require explicit accessibility modifiers on class properties and methods. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/explicit-member-accessibility.md - "@typescript-eslint/explicit-member-accessibility": "error", - - // Require explicit return and argument types on exported functions' and classes' public class methods. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/explicit-module-boundary-types.md - "@typescript-eslint/explicit-module-boundary-types": "error", - - // Enforce using a particular method signature syntax. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/method-signature-style.md - "@typescript-eslint/method-signature-style": "error", - - // Disallow non-null assertion in locations that may be confusing. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-confusing-non-null-assertion.md - "@typescript-eslint/no-confusing-non-null-assertion": "error", - - // Disallow duplicate enum member values. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-duplicate-enum-values.md - "@typescript-eslint/no-duplicate-enum-values": "error", - - // Disallow using to delete operator on computed key expressions. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-dynamic-delete.md - "@typescript-eslint/no-dynamic-delete": "warn", - - // Disallow extra non-null assertions. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-extra-non-null-assertion.md - "@typescript-eslint/no-extra-non-null-assertion": "error", - - // Disallow void type outside of generic or return types. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-invalid-void-type.md - "@typescript-eslint/no-invalid-void-type": "warn", - - // Enforce valid definition of new and constructor. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-misused-new.md - "@typescript-eslint/no-misused-new": "error", - - // Disallow TypeScript namespaces. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-namespace.md - "@typescript-eslint/no-namespace": "error", - - // Disallow non-null assertions in the left operand of a nullish coalescing operator. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-non-null-asserted-nullish-coalescing.md - "@typescript-eslint/no-non-null-asserted-nullish-coalescing": "warn", - - // Disallow non-null assertions after an optional chain expression. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-non-null-asserted-optional-chain.md - "@typescript-eslint/no-non-null-asserted-optional-chain": "error", - - // Disallow non-null assertions using the ! postfix operator. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-non-null-assertion.md - "@typescript-eslint/no-non-null-assertion": "error", - - // Disallow invocation of require(). - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-require-imports.md - "@typescript-eslint/no-require-imports": "error", - - // Disallow aliasing this. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-this-alias.md - "@typescript-eslint/no-this-alias": "error", - - // Disallow type assertions that do not change the type of expression. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-unnecessary-type-assertion.md - "@typescript-eslint/no-unnecessary-type-assertion": "error", - - // Disallow unnecessary constraints on generic types. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-unnecessary-type-constraint.md - "@typescript-eslint/no-unnecessary-type-constraint": "error", - - // Disallow calling a function with a value with type any. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-unsafe-argument.md - "@typescript-eslint/no-unsafe-argument": "error", - - // Disallow assigning a value with type any to variables and properties. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-unsafe-assignment.md - "@typescript-eslint/no-unsafe-assignment": "error", - - // Disallow calling a value with type any. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-unsafe-call.md - "@typescript-eslint/no-unsafe-call": "error", - - // Disallow unsafe declaration merging. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-unsafe-declaration-merging.md - "@typescript-eslint/no-unsafe-declaration-merging": "error", + // Disallow duplicate enum member values. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-duplicate-enum-values.md + "@typescript-eslint/no-duplicate-enum-values": "error", - // Disallow member access on a value with type any. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-unsafe-member-access.md - "@typescript-eslint/no-unsafe-member-access": "error", + // Disallow using to delete operator on computed key expressions. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-dynamic-delete.md + "@typescript-eslint/no-dynamic-delete": "warn", - // Disallow returning a value with type any from a function. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-unsafe-return.md - "@typescript-eslint/no-unsafe-return": "error", + // Replace 'no-empty-function' rule with '@typescript-eslint' version + // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-empty-function.md + "@typescript-eslint/no-empty-function": bestPracticesRules["no-empty-function"] as Linter.RuleEntry<[]>, - // Disallow empty exports that don't change anything in a module file. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-useless-empty-export.md - "@typescript-eslint/no-useless-empty-export": "error", + // Disallow extra non-null assertions. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-extra-non-null-assertion.md + "@typescript-eslint/no-extra-non-null-assertion": "error", - // Enforce non-null assertions over explicit type casts. This rule is disabled by @typescript-eslint/no-non-null-assertion. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/non-nullable-type-assertion-style.md - "@typescript-eslint/non-nullable-type-assertion-style": "off", + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-import-type-side-effects.md + "@typescript-eslint/no-import-type-side-effects": "error", - // Require each enum member value to be explicitly initialized. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/prefer-enum-initializers.md - "@typescript-eslint/prefer-enum-initializers": "error", + // Disallow void type outside of generic or return types. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-invalid-void-type.md + "@typescript-eslint/no-invalid-void-type": "warn", - // Enforce using function types instead of interfaces with call signatures. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/prefer-function-type.md - "@typescript-eslint/prefer-function-type": "error", + // Replace 'no-loop-func' rule with '@typescript-eslint' version + // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-loop-func.md + "@typescript-eslint/no-loop-func": bestPracticesRules["no-loop-func"] as Linter.RuleEntry<[]>, - // Enforce using the nullish coalescing operator instead of logical chaining. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md - "@typescript-eslint/prefer-nullish-coalescing": "error", + // Replace 'no-magic-numbers' rule with '@typescript-eslint' version + // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-magic-numbers.md + "@typescript-eslint/no-magic-numbers": bestPracticesRules["no-magic-numbers"] as Linter.RuleEntry<[]>, - // Enforce using concise optional chain expressions instead of chained logical ands, negated logical ors, or empty objects. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/prefer-optional-chain.md - "@typescript-eslint/prefer-optional-chain": "error", + // Enforce valid definition of new and constructor. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-misused-new.md + "@typescript-eslint/no-misused-new": "error", - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-import-type-side-effects.md - "@typescript-eslint/no-import-type-side-effects": "error", + // Disallow TypeScript namespaces. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-namespace.md + "@typescript-eslint/no-namespace": "error", - // Disable rules that are handled by prettier - ...global.anolilabEslintConfigTypescriptPrettierRules, - }, + // Disallow non-null assertions in the left operand of a nullish coalescing operator. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-non-null-asserted-nullish-coalescing.md + "@typescript-eslint/no-non-null-asserted-nullish-coalescing": "warn", + + // Disallow non-null assertions after an optional chain expression. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-non-null-asserted-optional-chain.md + "@typescript-eslint/no-non-null-asserted-optional-chain": "error", + + // Disallow non-null assertions using the ! postfix operator. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-non-null-assertion.md + "@typescript-eslint/no-non-null-assertion": "error", + + // Replace 'no-redeclare' rule with '@typescript-eslint' version + // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-redeclare.md + "@typescript-eslint/no-redeclare": bestPracticesRules["no-redeclare"] as Linter.RuleEntry<[]>, + + // Disallow invocation of require(). + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-require-imports.md + "@typescript-eslint/no-require-imports": "error", + + // Replace 'no-shadow' rule with '@typescript-eslint' version + // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-shadow.md + "@typescript-eslint/no-shadow": variablesRules["no-shadow"] as Linter.RuleEntry<[]>, + + // Disallow aliasing this. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-this-alias.md + "@typescript-eslint/no-this-alias": "error", + + // Disallow unnecessary constraints on generic types. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-unnecessary-type-constraint.md + "@typescript-eslint/no-unnecessary-type-constraint": "error", + + // Disallow unsafe declaration merging. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-unsafe-declaration-merging.md + "@typescript-eslint/no-unsafe-declaration-merging": "error", + + // Replace 'no-unused-expressions' rule with '@typescript-eslint' version + // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unused-expressions.md + "@typescript-eslint/no-unused-expressions": bestPracticesRules["no-unused-expressions"] as Linter.RuleEntry<[]>, + + // Replace 'no-unused-vars' rule with '@typescript-eslint' version + // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unused-vars.md + "@typescript-eslint/no-unused-vars": variablesRules["no-unused-vars"] as Linter.RuleEntry<[]>, + + // Replace 'no-use-before-define' rule with '@typescript-eslint' version + // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-use-before-define.md + "@typescript-eslint/no-use-before-define": variablesRules["no-use-before-define"] as Linter.RuleEntry<[]>, + + // Replace 'no-useless-constructor' rule with '@typescript-eslint' version + // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-useless-constructor.md + "@typescript-eslint/no-useless-constructor": es6Rules["no-useless-constructor"] as Linter.RuleEntry<[]>, + + // Disallow empty exports that don't change anything in a module file. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-useless-empty-export.md + "@typescript-eslint/no-useless-empty-export": "error", + + // Enforce non-null assertions over explicit type casts. This rule is disabled by @typescript-eslint/no-non-null-assertion. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/non-nullable-type-assertion-style.md + "@typescript-eslint/non-nullable-type-assertion-style": "off", + + // Require each enum member value to be explicitly initialized. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/prefer-enum-initializers.md + "@typescript-eslint/prefer-enum-initializers": "error", + + // Enforce using function types instead of interfaces with call signatures. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/prefer-function-type.md + "@typescript-eslint/prefer-function-type": "error", + + // Disabled to use faster alternatives. + "@typescript-eslint/prefer-string-starts-ends-with": "off", + // Enforce using @ts-expect-error over @ts-ignore. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/prefer-ts-expect-error.md + // DEPRECATED: in favor of ban-ts-comment + "@typescript-eslint/prefer-ts-expect-error": "off", + + // Replace 'no-return-await' rule with '@typescript-eslint' version + // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/return-await.md + "@typescript-eslint/return-await": bestPracticesRules["no-return-await"] as Linter.RuleEntry<[]>, + + // Replace 'semi' rule with '@typescript-eslint' version + // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/semi.md + "@typescript-eslint/semi": styleRules["semi"] as Linter.RuleEntry<[]>, + + // Enforce constituents of a type union/intersection to be sorted alphabetically. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/sort-type-constituents.md + "@typescript-eslint/sort-type-constituents": "error", + + // Replace 'space-before-function-paren' rule with '@typescript-eslint' version + // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/space-before-function-paren.md + "@typescript-eslint/space-before-function-paren": styleRules["space-before-function-paren"] as Linter.RuleEntry<[]>, + + // Replace 'space-infix-ops' rule with '@typescript-eslint' version + // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/space-infix-ops.md + "@typescript-eslint/space-infix-ops": styleRules["space-infix-ops"] as Linter.RuleEntry<[]>, + + ...overrides, + + // Disable rules that are handled by prettier + ...prettier + ? { + "@typescript-eslint/block-spacing": "off", + "@typescript-eslint/brace-style": "off", + "@typescript-eslint/comma-dangle": "off", + "@typescript-eslint/comma-spacing": "off", + "@typescript-eslint/func-call-spacing": "off", + "@typescript-eslint/indent": "off", + "@typescript-eslint/key-spacing": "off", + "@typescript-eslint/keyword-spacing": "off", + "@typescript-eslint/lines-around-comment": 0, + "@typescript-eslint/member-delimiter-style": "off", + "@typescript-eslint/no-extra-parens": "off", + "@typescript-eslint/no-extra-semi": "off", + "@typescript-eslint/object-curly-spacing": "off", + "@typescript-eslint/quotes": 0, + "@typescript-eslint/semi": "off", + "@typescript-eslint/space-before-blocks": "off", + "@typescript-eslint/space-before-function-paren": "off", + "@typescript-eslint/space-infix-ops": "off", + "@typescript-eslint/type-annotation-spacing": "off", + } + : {}, }, - type: "typescript", - }, -]); + }); -export default config; + return rules; +}); diff --git a/packages/eslint-config/src/config/plugins/unicorn.ts b/packages/eslint-config/src/config/plugins/unicorn.ts index 85fd1e348..8bae748da 100644 --- a/packages/eslint-config/src/config/plugins/unicorn.ts +++ b/packages/eslint-config/src/config/plugins/unicorn.ts @@ -1,75 +1,106 @@ -import { hasDependency, hasDevDependency, packageIsTypeModule } from "@anolilab/package-json-utils"; -import type { Linter } from "eslint"; - -import indent from "../../utils/indent"; - -if (global.anolilabEslintConfigUnicornPrettierRules === undefined && (hasDependency("prettier") || hasDevDependency("prettier"))) { - global.anolilabEslintConfigUnicornPrettierRules = { - "unicorn/empty-brace-spaces": "off", - "unicorn/no-nested-ternary": "off", - "unicorn/number-literal-case": "off", - "unicorn/template-indent": "off", - }; -} - -// @see https://github.com/sindresorhus/eslint-plugin-unicorn -const config: Linter.Config = { - extends: ["plugin:unicorn/recommended"], - overrides: [ +import globals from "globals"; + +import type { + OptionsFiles, + OptionsHasPrettier, + OptionsOverrides, + OptionsPackageJson, + OptionsStylistic, +} from "../../types"; +import { createConfig } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; + +export default createConfig("all", async (config, oFiles) => { + const { + files = oFiles, + overrides, + packageJson, + prettier, + stylistic = true, + } = config; + + const { indent = 4 } = typeof stylistic === "boolean" ? {} : stylistic; + + const pluginUnicorn = await interopDefault(import("eslint-plugin-unicorn")); + + return [ { - files: ["tsconfig.dev.json", "tsconfig.prod.json"], - rules: { - "unicorn/prevent-abbreviations": "off", + languageOptions: { + globals: globals.builtin, + }, + name: "anolilab/unicorn/plugin", + plugins: { + unicorn: pluginUnicorn, }, }, { - files: ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"], + files, + name: "anolilab/unicorn/rules", rules: { - "unicorn/import-style": "off", - }, - }, - ], - plugins: ["unicorn"], - rules: { - // TODO: Temporarily disabled as the rule is buggy. - "function-call-argument-newline": "off", - // Disabled because of eslint-plugin-regexp - "unicorn/better-regex": "off", - // TODO: Disabled for now until it becomes more stable: https://github.com/sindresorhus/eslint-plugin-unicorn/search?q=consistent-destructuring+is:issue&state=open&type=issues - "unicorn/consistent-destructuring": "off", - // TODO: Remove this override when the rule is more stable. - "unicorn/consistent-function-scoping": "off", - - "unicorn/filename-case": [ - "error", - { - case: "kebabCase", - ignore: [/(FUNDING\.yml|README\.md|CHANGELOG\.md|CONTRIBUTING\.md|CODE_OF_CONDUCT\.md|SECURITY\.md|LICENSE)/u], - }, - ], + ...pluginUnicorn.configs.recommended.rules, + + // Disabled because of eslint-plugin-regexp + "unicorn/better-regex": "off", + // TODO: Disabled for now until it becomes more stable: https://github.com/sindresorhus/eslint-plugin-unicorn/search?q=consistent-destructuring+is:issue&state=open&type=issues + "unicorn/consistent-destructuring": "off", + // TODO: Remove this override when the rule is more stable. + "unicorn/consistent-function-scoping": "off", - "unicorn/no-array-for-each": "off", + "unicorn/filename-case": [ + "error", + { + case: "kebabCase", + ignore: [/(FUNDING\.yml|README\.md|CHANGELOG\.md|CONTRIBUTING\.md|CODE_OF_CONDUCT\.md|SECURITY\.md|LICENSE)/u], + }, + ], - // TODO: Disabled for now as I don't have time to deal with the backslash that might come from this. Try to enable this rule in 2024. - "unicorn/no-null": "off", + "unicorn/no-array-for-each": "off", - // TODO: Temporarily disabled until it becomes more mature. - "unicorn/no-useless-undefined": "off", + "unicorn/no-instanceof-builtins": "error", - // It will be disabled in the next version of eslint-plugin-unicorn. - "unicorn/prefer-json-parse-buffer": "off", + // TODO: Temporarily disabled until it becomes more mature. + "unicorn/no-useless-undefined": "off", - "unicorn/prefer-module": packageIsTypeModule ? "error" : "off", + // Disabled to use faster alternatives. + "unicorn/prefer-at": "off", - "unicorn/prefer-node-protocol": "error", + // It will be disabled in the next version of eslint-plugin-unicorn. + "unicorn/prefer-json-parse-buffer": "off", - // We only enforce it for single-line statements to not be too opinionated. - "unicorn/prefer-ternary": ["error", "only-single-line"], + "unicorn/prefer-module": packageJson.type === "module" ? "error" : "off", - "unicorn/template-indent": ["error", { indent }], + "unicorn/prefer-node-protocol": "error", - ...global.anolilabEslintConfigUnicornPrettierRules, - }, -}; + // We only enforce it for single-line statements to not be too opinionated. + "unicorn/prefer-ternary": ["error", "only-single-line"], -export default config; + ...prettier + ? { + "unicorn/empty-brace-spaces": "off", + "unicorn/no-nested-ternary": "off", + "unicorn/number-literal-case": "off", + "unicorn/template-indent": "off", + } + : { + "unicorn/template-indent": ["error", { indent }], + }, + + ...overrides, + }, + }, + { + files: ["tsconfig.dev.json", "tsconfig.prod.json"], + name: "anolilab/unicorn/tsconfig-overrides", + rules: { + "unicorn/prevent-abbreviations": "off", + }, + }, + { + files: ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"], + name: "anolilab/unicorn/ts-overrides", + rules: { + "unicorn/import-style": "off", + }, + }, + ]; +}); diff --git a/packages/eslint-config/src/config/plugins/unocss.ts b/packages/eslint-config/src/config/plugins/unocss.ts new file mode 100644 index 000000000..d64d21d03 --- /dev/null +++ b/packages/eslint-config/src/config/plugins/unocss.ts @@ -0,0 +1,32 @@ +import type { OptionsUnoCSS, TypedFlatConfigItem } from "../../types"; +import interopDefault from "../../utils/interop-default"; + +const unocss = async (options: OptionsUnoCSS): Promise => { + const { attributify = true, strict = false } = options; + + const pluginUnoCSS = await interopDefault(import("@unocss/eslint-plugin")); + + return [ + { + name: "anolilab/unocss", + plugins: { + unocss: pluginUnoCSS, + }, + rules: { + "unocss/order": "warn", + ...attributify + ? { + "unocss/order-attributify": "warn", + } + : {}, + ...strict + ? { + "unocss/blocklist": "error", + } + : {}, + }, + }, + ]; +}; + +export default unocss; diff --git a/packages/eslint-config/src/config/plugins/unused-imports.ts b/packages/eslint-config/src/config/plugins/unused-imports.ts new file mode 100644 index 000000000..3b4d1a3fb --- /dev/null +++ b/packages/eslint-config/src/config/plugins/unused-imports.ts @@ -0,0 +1,32 @@ +import type { OptionsFiles, OptionsIsInEditor } from "../../types"; +import { createConfig } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; + +export default createConfig("js", async (config, oFiles) => { + const { files = oFiles, isInEditor } = config; + + const pluginUnusedImports = await interopDefault(import("eslint-plugin-unused-imports")); + + return [ + { + files, + name: "anolilab/unused-imports/rules", + plugins: { + "unused-imports": pluginUnusedImports, + }, + rules: { + "unused-imports/no-unused-imports": isInEditor ? "off" : "error", + "unused-imports/no-unused-vars": [ + "error", + { + args: "after-used", + argsIgnorePattern: "^_", + ignoreRestSiblings: true, + vars: "all", + varsIgnorePattern: "^_", + }, + ], + }, + }, + ]; +}); diff --git a/packages/eslint-config/src/config/plugins/validate-jsx-nesting.ts b/packages/eslint-config/src/config/plugins/validate-jsx-nesting.ts index 9d037be12..8913816e1 100644 --- a/packages/eslint-config/src/config/plugins/validate-jsx-nesting.ts +++ b/packages/eslint-config/src/config/plugins/validate-jsx-nesting.ts @@ -1,13 +1,25 @@ -import type { Linter } from "eslint"; - +import type { OptionsFiles, OptionsOverrides } from "../../types"; import { createConfig } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; // @see https://github.com/francoismassart/eslint-plugin-tailwindcss, -const config: Linter.Config = createConfig("jsx_and_tsx", { - plugins: ["validate-jsx-nesting"], - rules: { - "validate-jsx-nesting/no-invalid-jsx-nesting": "error", - }, -}); +export default createConfig("jsx_and_tsx", async (config, oFiles) => { + const { files = oFiles, overrides } = config; + + const validateJsxNestingPlugin = await interopDefault(import("eslint-plugin-validate-jsx-nesting")); -export default config; + return [ + { + files, + name: "anolilab/validate-jsx-nesting/setup", + plugins: { + "validate-jsx-nesting": validateJsxNestingPlugin, + }, + rules: { + "validate-jsx-nesting/no-invalid-jsx-nesting": "error", + + ...overrides, + }, + }, + ]; +}); diff --git a/packages/eslint-config/src/config/plugins/vitest.ts b/packages/eslint-config/src/config/plugins/vitest.ts index 371c38077..3c883791b 100644 --- a/packages/eslint-config/src/config/plugins/vitest.ts +++ b/packages/eslint-config/src/config/plugins/vitest.ts @@ -1,51 +1,127 @@ -import { hasDependency, hasDevDependency } from "@anolilab/package-json-utils"; -import type { Linter } from "eslint"; - -if (!global.hasAnolilabEsLintVitestGlobalsPlugin) { - global.hasAnolilabEsLintVitestGlobalsPlugin = hasDependency("eslint-plugin-vitest-globals") || hasDevDependency("eslint-plugin-vitest-globals"); -} - -const plugins = ["plugin:vitest/recommended", "plugin:vitest/all"]; - -if (global.hasAnolilabEsLintVitestGlobalsPlugin) { - plugins.push("plugin:vitest-globals/recommended"); -} - -const config: Linter.Config = { - overrides: [ - { - extends: plugins, - files: ["**/__tests__/**/*.?(c|m)[jt]s?(x)", "**/?(*.){test,spec}.?(c|m)[jt]s?(x)"], - plugins: ["vitest"], - // TODO: transform all rules to error +import type { + OptionsFiles, + OptionsHasPrettier, + OptionsIsInEditor, + OptionsOverrides, + OptionsTypeScriptWithTypes, +} from "../../types"; +import { createConfig } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; +import vitestGlobals from "../../utils/vitest-globals"; + +// Hold the reference so we don't redeclare the plugin on each call +// eslint-disable-next-line @typescript-eslint/no-explicit-any +let pluginTest: any; + +export default createConfig( + "vitest", + async (config, oFiles) => { + const { + files = oFiles, + isInEditor = false, + overrides, + prettier, + tsconfigPath, + } = config; + + const [vitestPlugin, noOnlyTestsPlugin] = await Promise.all([ + interopDefault(import("@vitest/eslint-plugin")), + // @ts-expect-error missing types + interopDefault(import("eslint-plugin-no-only-tests")), + ] as const); + + pluginTest = pluginTest || { + ...vitestPlugin, rules: { - // Enforce a maximum number of expect per test - // https://github.com/veritem/eslint-plugin-vitest/blob/main/docs/rules/max-expects.md - // This rule should be set on the root config - "vitest/max-expects": "off", - - // Enforce valid expect() usage - // https://github.com/veritem/eslint-plugin-vitest/blob/main/docs/rules/no-hooks.md - "vitest/no-hooks": "off", - - // Disallow setup and teardown hooks - // https://github.com/veritem/eslint-plugin-vitest/blob/main/docs/rules/no-mocks-import.md - "vitest/no-mocks-import": "off", - - // Disallow importing from mocks directory - // https://github.com/veritem/eslint-plugin-vitest/blob/main/docs/rules/no-restricted-vi-methods.md - "vitest/no-restricted-vi-methods": "off", - - // Disallow specific vi. methods - // https://github.com/veritem/eslint-plugin-vitest/blob/main/docs/rules/no-standalone-expect.md - "vitest/no-standalone-expect": "error", - - // Disallow using expect outside of it or test blocks - // https://github.com/veritem/eslint-plugin-vitest/blob/main/docs/rules/valid-expect.md - "vitest/valid-expect": ["error", { alwaysAwait: true, maxArgs: 2, minArgs: 1 }], + ...vitestPlugin.rules, + // extend `test/no-only-tests` rule + ...noOnlyTestsPlugin.rules, + }, + }; + + return [ + { + name: "anolilab/vitest/setup", + plugins: { + vitest: pluginTest, + }, }, - }, - ], -}; + { + files, + ...tsconfigPath + ? { + ...vitestPlugin.configs.env, + settings: { + vitest: { + typecheck: true, + }, + }, + } + : {}, + languageOptions: { + globals: { + ...vitestGlobals, + }, + }, + name: "anolilab/vitest/rules", + rules: { + ...vitestPlugin.configs.all.rules, + ...vitestPlugin.configs.recommended.rules, + + "@typescript-eslint/explicit-function-return-type": "off", + + // Disables + "antfu/no-top-level-await": "off", + + "n/prefer-global/process": "off", + + "no-unused-expressions": "off", -export default config; + "vitest/consistent-test-it": ["error", { fn: "it", withinDescribe: "it" }], + + // Enforce a maximum number of expect per test + // https://github.com/veritem/eslint-plugin-vitest/blob/main/docs/rules/max-expects.md + // This rule should be set on the root config + "vitest/max-expects": "off", + + // Enforce valid expect() usage + // https://github.com/veritem/eslint-plugin-vitest/blob/main/docs/rules/no-hooks.md + "vitest/no-hooks": "off", + + // Disallow setup and teardown hooks + // https://github.com/veritem/eslint-plugin-vitest/blob/main/docs/rules/no-mocks-import.md + "vitest/no-mocks-import": "off", + + "vitest/no-only-tests": isInEditor ? "off" : "error", + + // Disallow importing from mocks directory + // https://github.com/veritem/eslint-plugin-vitest/blob/main/docs/rules/no-restricted-vi-methods.md + "vitest/no-restricted-vi-methods": "off", + + // Disallow specific vi. methods + // https://github.com/veritem/eslint-plugin-vitest/blob/main/docs/rules/no-standalone-expect.md + "vitest/no-standalone-expect": "error", + + // Disallow using expect outside of it or test blocks + // https://github.com/veritem/eslint-plugin-vitest/blob/main/docs/rules/valid-expect.md + "vitest/valid-expect": ["error", { alwaysAwait: true, maxArgs: 2, minArgs: 1 }], + + ...overrides, + + ...prettier + ? { + "vitest/padding-around-after-all-blocks": "off", + "vitest/padding-around-after-each-blocks": "off", + "vitest/padding-around-all": "off", + "vitest/padding-around-before-all-blocks": "off", + "vitest/padding-around-before-each-blocks": "off", + "vitest/padding-around-describe-blocks": "off", + "vitest/padding-around-expect-blocks": "off", + "vitest/padding-around-test-blocks": "off", + } + : {}, + }, + }, + ]; + }, +); diff --git a/packages/eslint-config/src/config/plugins/yml.ts b/packages/eslint-config/src/config/plugins/yml.ts index 220b9ee76..5c73a27cc 100644 --- a/packages/eslint-config/src/config/plugins/yml.ts +++ b/packages/eslint-config/src/config/plugins/yml.ts @@ -1,24 +1,98 @@ -import { hasDependency, hasDevDependency } from "@anolilab/package-json-utils"; -import type { Linter } from "eslint"; +import type { + OptionsFiles, + OptionsHasPrettier, + OptionsOverrides, + OptionsStylistic, +} from "../../types"; +import { createConfig } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; -import indent from "../../utils/indent"; +export default createConfig("yaml", async (options, oFiles) => { + const { + files = oFiles, + overrides = {}, + prettier, + stylistic = true, + } = options; -if (!global.hasAnolilabEsLintConfigPrettier && (hasDependency("prettier") || hasDevDependency("prettier"))) { - global.hasAnolilabEsLintConfigPrettier = true; -} + const { indent = 4, quotes = "double" } = typeof stylistic === "boolean" ? {} : stylistic; -const config: Linter.Config = { - overrides: [ + const [pluginYaml, parserYaml] = await Promise.all([interopDefault(import("eslint-plugin-yml")), interopDefault(import("yaml-eslint-parser"))] as const); + + return [ { - extends: ["plugin:yml/recommended", ...(global.hasAnolilabEsLintConfigPrettier ? ["plugin:yml/prettier"] : [])], - files: ["**/*.yaml", "**/*.yml"], - parser: "yaml-eslint-parser", + files, + languageOptions: { + parser: parserYaml, + }, + name: "anolilab/yaml", + plugins: { + yaml: pluginYaml, + }, rules: { - indent: [global.hasAnolilabEsLintConfigPrettier ? "off" : "error", indent], - "spaced-comment": "off", + "@stylistic/spaced-comment": "off", + + "yaml/block-mapping": "error", + "yaml/block-sequence": "error", + "yaml/no-empty-key": "error", + "yaml/no-empty-sequence-entry": "error", + "yaml/no-irregular-whitespace": "error", + "yaml/plain-scalar": "error", + + "yaml/vue-custom-block/no-parsing-error": "error", + + ...stylistic + ? { + "yaml/block-mapping-question-indicator-newline": "error", + "yaml/block-sequence-hyphen-indicator-newline": "error", + "yaml/flow-mapping-curly-newline": "error", + "yaml/flow-mapping-curly-spacing": "error", + "yaml/flow-sequence-bracket-newline": "error", + "yaml/flow-sequence-bracket-spacing": "error", + "yaml/indent": [prettier ? "off" : "error", indent === "tab" ? 2 : indent], + "yaml/key-spacing": "error", + "yaml/no-tab-indent": "error", + "yaml/quotes": ["error", { avoidEscape: true, prefer: quotes === "backtick" ? "single" : quotes }], + "yaml/spaced-comment": "error", + } + : {}, + + ...prettier ? pluginYaml.configs.prettier.rules : {}, + + ...overrides, }, }, - ], -}; + { + files: ["pnpm-workspace.yaml"], + name: "anolilab/yaml/pnpm-workspace", + rules: { + "yaml/sort-keys": [ + "error", + { + order: [ + "packages", + "overrides", + "patchedDependencies", + "hoistPattern", + "catalog", + "catalogs", -export default config; + "allowedDeprecatedVersions", + "allowNonAppliedPatches", + "configDependencies", + "ignoredBuiltDependencies", + "ignoredOptionalDependencies", + "neverBuiltDependencies", + "onlyBuiltDependencies", + "onlyBuiltDependenciesFile", + "packageExtensions", + "peerDependencyRules", + "supportedArchitectures", + ], + pathPattern: "^$", + }, + ], + }, + }, + ]; +}); diff --git a/packages/eslint-config/src/config/plugins/you-dont-need-lodash-underscore.ts b/packages/eslint-config/src/config/plugins/you-dont-need-lodash-underscore.ts index 7c90a6cc1..f5ec38558 100644 --- a/packages/eslint-config/src/config/plugins/you-dont-need-lodash-underscore.ts +++ b/packages/eslint-config/src/config/plugins/you-dont-need-lodash-underscore.ts @@ -1,9 +1,24 @@ -import type { Linter } from "eslint"; +import { fixupPluginRules } from "@eslint/compat"; +import type { OptionsFiles, OptionsOverrides } from "../../types"; import { createConfig } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; -const config: Linter.Config = createConfig("all", { - extends: ["plugin:you-dont-need-lodash-underscore/compatible"], -}); +export default createConfig("all", async (config, oFiles) => { + const { files = oFiles, overrides } = config; + + const pluginYouDontNeedLodashUnderscore = await interopDefault(import("eslint-plugin-you-dont-need-lodash-underscore")); -export default config; + return [ + { + files, + plugins: { + "you-dont-need-lodash-underscore": fixupPluginRules(pluginYouDontNeedLodashUnderscore), + }, + rules: { + ...pluginYouDontNeedLodashUnderscore.configs["all"].rules, + ...overrides, + }, + }, + ]; +}); diff --git a/packages/eslint-config/src/config/plugins/you-dont-need-momentjs.ts b/packages/eslint-config/src/config/plugins/you-dont-need-momentjs.ts deleted file mode 100644 index fdccbf70b..000000000 --- a/packages/eslint-config/src/config/plugins/you-dont-need-momentjs.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { Linter } from "eslint"; - -import { createConfig } from "../../utils/create-config"; - -const config: Linter.Config = createConfig("all", { - extends: ["plugin:you-dont-need-momentjs/recommended"], -}); - -export default config; diff --git a/packages/eslint-config/src/config/plugins/zod.ts b/packages/eslint-config/src/config/plugins/zod.ts index c9f91efcc..c57b824e9 100644 --- a/packages/eslint-config/src/config/plugins/zod.ts +++ b/packages/eslint-config/src/config/plugins/zod.ts @@ -1,13 +1,23 @@ -import type { Linter } from "eslint"; - +import type { OptionsFiles, OptionsOverrides } from "../../types"; import { createConfig } from "../../utils/create-config"; +import interopDefault from "../../utils/interop-default"; -const config: Linter.Config = createConfig("all", { - plugins: ["zod"], - rules: { - "zod/prefer-enum": "error", - "zod/require-strict": "error", - }, -}); +export default createConfig("all", async (config, oFiles) => { + const { files = oFiles, overrides } = config; -export default config; + const zodPlugin = await interopDefault(import("eslint-plugin-zod")); + + return [ + { + files, + plugins: { + zod: zodPlugin, + }, + rules: { + "zod/prefer-enum": "error", + "zod/require-strict": "error", + ...overrides, + }, + }, + ]; +}); diff --git a/packages/eslint-config/src/config/style.ts b/packages/eslint-config/src/config/style.ts index c9fd7a456..24da8b552 100644 --- a/packages/eslint-config/src/config/style.ts +++ b/packages/eslint-config/src/config/style.ts @@ -1,724 +1,600 @@ -import { hasDependency, hasDevDependency } from "@anolilab/package-json-utils"; import type { Linter } from "eslint"; -import { createConfigs } from "../utils/create-config"; -import indent from "../utils/indent"; - -if (!global.hasAnolilabEsLintConfigPrettier && (hasDependency("prettier") || hasDevDependency("prettier"))) { - global.hasAnolilabEsLintConfigPrettier = true; -} - -let prettierRules: Linter.Config["rules"] = {}; - -if (global.hasAnolilabEsLintConfigPrettier) { - prettierRules = { - // The rest are rules that you never need to enable when using Prettier. +import type { + OptionsFiles, + OptionsHasPrettier, + OptionsStylistic, + StylisticConfig, +} from "../types"; +import { createConfig, getFilesGlobs } from "../utils/create-config"; + +export const noUnderscoreDangle = { + allow: ["__DEV__", "__STORYBOOK_CLIENT_API__", "__STORYBOOK_ADDONS_CHANNEL__", "__STORYBOOK_STORY_STORE__"], + allowAfterSuper: false, + allowAfterThis: false, + enforceInMethodNames: true, +}; + +export const styleRules = (stylistic?: StylisticConfig | boolean): Partial => { + return { + // enforce line breaks after opening and before closing array brackets + // https://eslint.org/docs/rules/array-bracket-newline "array-bracket-newline": "off", - "array-bracket-spacing": "off", - "array-element-newline": "off", - "arrow-parens": "off", - "arrow-spacing": "off", - "block-spacing": "off", - "brace-style": "off", - "comma-dangle": "off", - - "comma-spacing": "off", - "comma-style": "off", - "computed-property-spacing": "off", - // script can distinguish them.) - curly: 0, - "dot-location": "off", - "eol-last": "off", - "func-call-spacing": "off", - "function-call-argument-newline": "off", - "function-paren-newline": "off", - "generator-star-spacing": "off", - "implicit-arrow-linebreak": "off", - indent: "off", - "jsx-quotes": "off", - "key-spacing": "off", - "keyword-spacing": "off", - "linebreak-style": "off", - "lines-around-comment": 0, - "max-len": 0, - "max-statements-per-line": "off", - "multiline-ternary": "off", - "new-parens": "off", - "newline-per-chained-call": "off", - "no-confusing-arrow": 0, - "no-extra-parens": "off", - "no-extra-semi": "off", - "no-floating-decimal": "off", - "no-mixed-operators": 0, - "no-mixed-spaces-and-tabs": "off", - "no-multi-spaces": "off", - "no-multiple-empty-lines": "off", - "no-tabs": 0, - "no-trailing-spaces": "off", - "no-unexpected-multiline": 0, - "no-whitespace-before-property": "off", - "nonblock-statement-body-position": "off", - "object-curly-newline": "off", - "object-curly-spacing": "off", - "object-property-newline": "off", - "one-var-declaration-per-line": "off", - "operator-linebreak": "off", - "padded-blocks": "off", - "quote-props": "off", - quotes: 0, - "rest-spread-spacing": "off", - semi: "off", - "semi-spacing": "off", - "semi-style": "off", - "space-before-blocks": "off", - "space-before-function-paren": "off", - "space-in-parens": "off", - "space-unary-ops": "off", - "switch-colon-spacing": "off", - "template-curly-spacing": "off", - "template-tag-spacing": "off", - "wrap-iife": "off", - "wrap-regex": "off", - "yield-star-spacing": "off", - }; -} - -const config: Linter.Config = createConfigs([ - { - config: { - rules: { - // enforce line breaks after opening and before closing array brackets - // https://eslint.org/docs/rules/array-bracket-newline - "array-bracket-newline": "off", - - // enforce line breaks between array elements - // enforce spacing inside array brackets - "array-bracket-spacing": ["error", "never"], - - // https://eslint.org/docs/rules/array-element-newline - "array-element-newline": "off", - - // enforce spacing inside single-line blocks - // https://eslint.org/docs/rules/block-spacing - "block-spacing": ["error", "always"], - - // enforce one true brace style - "brace-style": ["error", "1tbs", { allowSingleLine: true }], - - // require camel case names - camelcase: ["error", { ignoreDestructuring: false, properties: "never" }], - - // enforce or disallow capitalization of the first letter of a comment - // https://eslint.org/docs/rules/capitalized-comments - "capitalized-comments": [ - "off", - "never", - { - block: { - ignoreConsecutiveComments: true, - ignoreInlineComments: true, - ignorePattern: ".*", - }, - line: { - ignoreConsecutiveComments: true, - ignoreInlineComments: true, - ignorePattern: ".*", - }, - }, - ], - // require trailing commas in multiline object literals - "comma-dangle": [ - "error", - { - arrays: "always-multiline", - exports: "always-multiline", - functions: "always-multiline", - imports: "always-multiline", - objects: "always-multiline", - }, - ], - - // enforce spacing before and after comma - "comma-spacing": ["error", { after: true, before: false }], - - // enforce one true comma style - "comma-style": [ - "error", - "last", - { - exceptions: { - ArrayExpression: false, - ArrayPattern: false, - ArrowFunctionExpression: false, - CallExpression: false, - FunctionDeclaration: false, - FunctionExpression: false, - ImportDeclaration: false, - NewExpression: false, - ObjectExpression: false, - ObjectPattern: false, - VariableDeclaration: false, - }, - }, - ], + // enforce line breaks between array elements + // enforce spacing inside array brackets + "array-bracket-spacing": ["error", "never"], - // disallow padding inside computed properties - "computed-property-spacing": ["error", "never"], + // https://eslint.org/docs/rules/array-element-newline + "array-element-newline": "off", - // enforces consistent naming when capturing the current execution context - "consistent-this": "off", + // require camel case names + camelcase: ["error", { ignoreDestructuring: false, properties: "never" }], + + // enforce or disallow capitalization of the first letter of a comment + // https://eslint.org/docs/rules/capitalized-comments + "capitalized-comments": [ + "off", + "never", + { + block: { + ignoreConsecutiveComments: true, + ignoreInlineComments: true, + ignorePattern: ".*", + }, + line: { + ignoreConsecutiveComments: true, + ignoreInlineComments: true, + ignorePattern: ".*", + }, + }, + ], + + // enforce one true comma style + "comma-style": [ + "error", + "last", + { + exceptions: { + ArrayExpression: false, + ArrayPattern: false, + ArrowFunctionExpression: false, + CallExpression: false, + FunctionDeclaration: false, + FunctionExpression: false, + ImportDeclaration: false, + NewExpression: false, + ObjectExpression: false, + ObjectPattern: false, + VariableDeclaration: false, + }, + }, + ], - // enforce that default parameters should come last - "default-param-last": ["error"], + // disallow padding inside computed properties + "computed-property-spacing": ["error", "never"], - // enforce newline at the end of file, with no multiple empty lines - "eol-last": ["error", "always"], + // enforces consistent naming when capturing the current execution context + "consistent-this": "off", - // https://eslint.org/docs/rules/func-call-spacing - "func-call-spacing": ["error", "never"], + // enforce that default parameters should come last + "default-param-last": ["error"], - // enforce spacing between functions and their invocations - // https://eslint.org/docs/rules/func-name-matching - "func-name-matching": [ - "off", - "always", - { - considerPropertyDescriptor: true, - includeCommonJSModuleExports: false, - }, - ], + // enforce newline at the end of file, with no multiple empty lines + "eol-last": ["error", "always"], - // requires function names to match the name of the variable or property to which they are - // assigned - // https://eslint.org/docs/rules/func-names - "func-names": ["error", "as-needed"], - - // require function expressions to have a name - // https://eslint.org/docs/rules/func-style - "func-style": ["error", "expression"], - - // enforces use of function declarations or expressions - // https://eslint.org/docs/rules/function-call-argument-newline - "function-call-argument-newline": ["error", "consistent"], - - // enforce consistent line breaks inside function parentheses - // https://eslint.org/docs/rules/function-paren-newline - "function-paren-newline": ["error", "consistent"], - - // Blacklist certain identifiers to prevent them being used - // https://eslint.org/docs/rules/id-blacklist - "id-blacklist": "error", - - // disallow specified identifiers - // https://eslint.org/docs/rules/id-denylist - "id-denylist": "off", - - // this option enforces minimum and maximum identifier lengths - // (variable names, property names etc.) - "id-length": "off", - - // require identifiers to match the provided regular expression - "id-match": "off", - - // Enforce the location of arrow function bodies with implicit returns - // https://eslint.org/docs/rules/implicit-arrow-linebreak - "implicit-arrow-linebreak": ["error", "beside"], - - // this option sets a specific tab width for your code - // https://eslint.org/docs/rules/indent - indent: [ - "error", - indent, - { - ArrayExpression: 1, - CallExpression: { - arguments: 1, - }, - // MemberExpression: null, - FunctionDeclaration: { - body: 1, - parameters: 1, - }, - FunctionExpression: { - body: 1, - parameters: 1, - }, - ImportDeclaration: 1, - ObjectExpression: 1, - SwitchCase: 1, - VariableDeclarator: 1, - flatTernaryExpressions: false, - ignoreComments: false, - // list derived from https://github.com/benjamn/ast-types/blob/HEAD/def/jsx.js - ignoredNodes: [ - "JSXElement", - "JSXElement > *", - "JSXAttribute", - "JSXIdentifier", - "JSXNamespacedName", - "JSXMemberExpression", - "JSXSpreadAttribute", - "JSXExpressionContainer", - "JSXOpeningElement", - "JSXClosingElement", - "JSXFragment", - "JSXOpeningFragment", - "JSXClosingFragment", - "JSXText", - "JSXEmptyExpression", - "JSXSpreadChild", - ], - outerIIFEBody: 1, - }, - ], + // enforce spacing between functions and their invocations + // https://eslint.org/docs/rules/func-name-matching + "func-name-matching": [ + "off", + "always", + { + considerPropertyDescriptor: true, + includeCommonJSModuleExports: false, + }, + ], + + // requires function names to match the name of the variable or property to which they are + // assigned + // https://eslint.org/docs/rules/func-names + "func-names": ["error", "as-needed"], + + // require function expressions to have a name + // https://eslint.org/docs/rules/func-style + "func-style": ["error", "expression"], + + // enforces use of function declarations or expressions + // https://eslint.org/docs/rules/function-call-argument-newline + "function-call-argument-newline": ["error", "consistent"], + + // enforce consistent line breaks inside function parentheses + // https://eslint.org/docs/rules/function-paren-newline + "function-paren-newline": ["error", "consistent"], + + // Blacklist certain identifiers to prevent them being used + // https://eslint.org/docs/rules/id-blacklist + "id-blacklist": "error", + + // disallow specified identifiers + // https://eslint.org/docs/rules/id-denylist + "id-denylist": "off", + + // this option enforces minimum and maximum identifier lengths + // (variable names, property names etc.) + "id-length": "off", + + // require identifiers to match the provided regular expression + "id-match": "off", + + // Enforce the location of arrow function bodies with implicit returns + // https://eslint.org/docs/rules/implicit-arrow-linebreak + "implicit-arrow-linebreak": ["error", "beside"], + + // specify whether double or single quotes should be used in JSX attributes + // https://eslint.org/docs/rules/jsx-quotes + "jsx-quotes": ["off", "prefer-double"], + + // enforce position of line comments + // https://eslint.org/docs/rules/line-comment-position + "line-comment-position": "off", + + // disallow mixed 'LF' and 'CRLF' as linebreaks + // https://eslint.org/docs/rules/linebreak-style + "linebreak-style": ["error", "unix"], + + // https://eslint.org/docs/rules/lines-around-directive + "lines-around-directive": [ + "error", + { + after: "always", + before: "always", + }, + ], + + // specify the maximum depth that blocks can be nested + "max-depth": ["off", 4], + + // specify the maximum length of a line in your program + // https://eslint.org/docs/rules/max-len + "max-len": [ + "error", + 180, + 2, + { + ignoreComments: false, + ignoreRegExpLiterals: true, + ignoreStrings: true, + ignoreTemplateLiterals: true, + ignoreUrls: true, + }, + ], + + // specify the max number of lines in a file + // https://eslint.org/docs/rules/max-lines + "max-lines": [ + "off", + { + max: 300, + skipBlankLines: true, + skipComments: true, + }, + ], + + // enforce a maximum function length + // https://eslint.org/docs/rules/max-lines-per-function + "max-lines-per-function": [ + "off", + { + IIFEs: true, + max: 50, + skipBlankLines: true, + skipComments: true, + }, + ], - // specify whether double or single quotes should be used in JSX attributes - // https://eslint.org/docs/rules/jsx-quotes - "jsx-quotes": ["off", "prefer-double"], - - // enforces spacing between keys and values in object literal properties - "key-spacing": ["error", { afterColon: true, beforeColon: false }], - - // require a space before & after certain keywords - "keyword-spacing": [ - "error", - { - after: true, - before: true, - overrides: { - case: { after: true }, - return: { after: true }, - throw: { after: true }, - }, - }, - ], + // specify the maximum depth callbacks can be nested + "max-nested-callbacks": "off", - // enforce position of line comments - // https://eslint.org/docs/rules/line-comment-position - "line-comment-position": "off", - - // disallow mixed 'LF' and 'CRLF' as linebreaks - // https://eslint.org/docs/rules/linebreak-style - "linebreak-style": ["error", "unix"], - - // require or disallow an empty line between class members - // enforces empty lines around comments - "lines-around-comment": "off", - - // https://eslint.org/docs/rules/lines-around-directive - "lines-around-directive": [ - "error", - { - after: "always", - before: "always", - }, - ], + // limits the number of parameters that can be used in the function declaration. + "max-params": ["off", 3], - // specify the maximum depth that blocks can be nested - "max-depth": ["off", 4], - - // specify the maximum length of a line in your program - // https://eslint.org/docs/rules/max-len - "max-len": [ - "error", - 160, - 2, - { - ignoreComments: false, - ignoreRegExpLiterals: true, - ignoreStrings: true, - ignoreTemplateLiterals: true, - ignoreUrls: true, - }, - ], + // specify the maximum number of statement allowed in a function + "max-statements": ["off", 10], - // specify the max number of lines in a file - // https://eslint.org/docs/rules/max-lines - "max-lines": [ - "off", - { - max: 300, - skipBlankLines: true, - skipComments: true, - }, - ], + // restrict the number of statements per line + // https://eslint.org/docs/rules/max-statements-per-line + "max-statements-per-line": ["off", { max: 1 }], - // enforce a maximum function length - // https://eslint.org/docs/rules/max-lines-per-function - "max-lines-per-function": [ - "off", - { - IIFEs: true, - max: 50, - skipBlankLines: true, - skipComments: true, - }, - ], + // enforce a particular style for multiline comments + // https://eslint.org/docs/rules/multiline-comment-style + "multiline-comment-style": ["off", "starred-block"], - // specify the maximum depth callbacks can be nested - "max-nested-callbacks": "off", - - // limits the number of parameters that can be used in the function declaration. - "max-params": ["off", 3], - - // specify the maximum number of statement allowed in a function - "max-statements": ["off", 10], - - // restrict the number of statements per line - // https://eslint.org/docs/rules/max-statements-per-line - "max-statements-per-line": ["off", { max: 1 }], - - // enforce a particular style for multiline comments - // https://eslint.org/docs/rules/multiline-comment-style - "multiline-comment-style": ["off", "starred-block"], - - // require multiline ternary - // https://eslint.org/docs/rules/multiline-ternary - // TODO: enable? - "multiline-ternary": ["off", "never"], - - // require a capital letter for constructors - "new-cap": [ - "error", - { - capIsNew: false, - capIsNewExceptions: ["Immutable.Map", "Immutable.Set", "Immutable.List"], - newIsCap: true, - newIsCapExceptions: [], - }, - ], + // require multiline ternary + // https://eslint.org/docs/rules/multiline-ternary + "multiline-ternary": ["off", "never"], - // disallow the omission of parentheses when invoking a constructor with no arguments - // https://eslint.org/docs/rules/new-parens - "new-parens": "error", - - // allow/disallow an empty newline after var statement - "newline-after-var": "off", - - // https://eslint.org/docs/rules/newline-before-return - "newline-before-return": "off", - - // enforces new line after each method call in the chain to make it - // more readable and easy to maintain - // https://eslint.org/docs/rules/newline-per-chained-call - "newline-per-chained-call": ["error", { ignoreChainWithDepth: 4 }], - - // disallow use of the Array constructor - "no-array-constructor": "error", - - // disallow use of bitwise operators - // https://eslint.org/docs/rules/no-bitwise - "no-bitwise": "error", - - // disallow use of the continue statement - // https://eslint.org/docs/rules/no-continue - "no-continue": "error", - - // disallow comments inline after code - "no-inline-comments": "off", - - // disallow if as the only statement in an else block - // https://eslint.org/docs/rules/no-lonely-if - "no-lonely-if": "error", - - // disallow un-paren'd mixes of different operators - // https://eslint.org/docs/rules/no-mixed-operators - "no-mixed-operators": [ - "error", - { - // the list of arithmetic groups disallows mixing `%` and `**` - allowSamePrecedence: false, - // with other arithmetic operators. - groups: [ - ["%", "**"], - ["%", "+"], - ["%", "-"], - ["%", "*"], - ["%", "/"], - ["/", "*"], - ["&", "|", "<<", ">>", ">>>"], - ["==", "!=", "===", "!=="], - ["&&", "||"], - ["in", "instanceof"], - ], - }, + // require a capital letter for constructors + "new-cap": [ + "error", + { + capIsNew: false, + capIsNewExceptions: ["Immutable.Map", "Immutable.Set", "Immutable.List"], + newIsCap: true, + newIsCapExceptions: [], + }, + ], + + // disallow the omission of parentheses when invoking a constructor with no arguments + // https://eslint.org/docs/rules/new-parens + "new-parens": "error", + + // allow/disallow an empty newline after var statement + "newline-after-var": "off", + + // https://eslint.org/docs/rules/newline-before-return + "newline-before-return": "off", + + // enforces new line after each method call in the chain to make it + // more readable and easy to maintain + // https://eslint.org/docs/rules/newline-per-chained-call + "newline-per-chained-call": ["error", { ignoreChainWithDepth: 4 }], + + // disallow use of the Array constructor + "no-array-constructor": "error", + + // disallow use of bitwise operators + // https://eslint.org/docs/rules/no-bitwise + "no-bitwise": "error", + + // disallow use of the continue statement + // https://eslint.org/docs/rules/no-continue + "no-continue": "off", + + // disallow comments inline after code + "no-inline-comments": "off", + + // disallow if as the only statement in an else block + // https://eslint.org/docs/rules/no-lonely-if + "no-lonely-if": "error", + + // disallow un-paren'd mixes of different operators + // https://eslint.org/docs/rules/no-mixed-operators + "no-mixed-operators": [ + "error", + { + // the list of arithmetic groups disallows mixing `%` and `**` + allowSamePrecedence: false, + // with other arithmetic operators. + groups: [ + ["%", "**"], + ["%", "+"], + ["%", "-"], + ["%", "*"], + ["%", "/"], + ["/", "*"], + ["&", "|", "<<", ">>", ">>>"], + ["==", "!=", "===", "!=="], + ["&&", "||"], + ["in", "instanceof"], ], + }, + ], - // disallow mixed spaces and tabs for indentation - "no-mixed-spaces-and-tabs": "error", - - // disallow use of chained assignment expressions - // https://eslint.org/docs/rules/no-multi-assign - "no-multi-assign": ["error"], - - // disallow multiple empty lines, only one newline at the end, and no new lines at the beginning - // https://eslint.org/docs/rules/no-multiple-empty-lines - "no-multiple-empty-lines": ["error", { max: 1, maxBOF: 0, maxEOF: 0 }], - - // disallow negated conditions - // https://eslint.org/docs/rules/no-negated-condition - "no-negated-condition": "off", - - // disallow nested ternary expressions - "no-nested-ternary": "error", - - // disallow use of the Object constructor - "no-new-object": "error", - - // disallow use of unary operators, ++ and -- - // https://eslint.org/docs/rules/no-plusplus - "no-plusplus": "error", - - // disallow certain syntax forms - // https://eslint.org/docs/rules/no-restricted-syntax - "no-restricted-syntax": [ - "error", - { - message: - "for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array.", - selector: "ForInStatement", - }, - { - message: - "iterators/generators require regenerator-runtime, which is too heavyweight for this guide to allow them. Separately, loops should be avoided in favor of array iterations.", - selector: "ForOfStatement", - }, - { - message: "Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.", - selector: "LabeledStatement", - }, - { - message: "`with` is disallowed in strict mode because it makes code impossible to predict and optimize.", - selector: "WithStatement", - }, - { - message: "`useMemo` with an empty dependency array can't provide a stable reference, use `useRef` instead.", - selector: "CallExpression[callee.name=useMemo][arguments.1.type=ArrayExpression][arguments.1.elements.length=0]", - }, - { - message: "Use `.key` instead of `.keyCode`", - selector: "MemberExpression > .property[type=Identifier][name=keyCode]", - }, - ], + // disallow mixed spaces and tabs for indentation + "no-mixed-spaces-and-tabs": "error", - // disallow space between function identifier and application - // deprecated in favor of func-call-spacing - "no-spaced-func": "off", + // disallow use of chained assignment expressions + // https://eslint.org/docs/rules/no-multi-assign + "no-multi-assign": ["error"], - // disallow tab characters entirely - "no-tabs": "error", + // disallow multiple empty lines, only one newline at the end, and no new lines at the beginning + // https://eslint.org/docs/rules/no-multiple-empty-lines + "no-multiple-empty-lines": ["error", { max: 1, maxBOF: 0, maxEOF: 0 }], - // disallow the use of ternary operators - "no-ternary": "off", + // disallow negated conditions + // https://eslint.org/docs/rules/no-negated-condition + "no-negated-condition": "off", - // disallow trailing whitespace at the end of lines - "no-trailing-spaces": [ - "error", - { - ignoreComments: false, - skipBlankLines: false, - }, - ], + // disallow nested ternary expressions + "no-nested-ternary": "error", - // disallow dangling underscores in identifiers - // https://eslint.org/docs/rules/no-underscore-dangle - "no-underscore-dangle": [ - "error", - { - allow: ["__DEV__", "__STORYBOOK_CLIENT_API__", "__STORYBOOK_ADDONS_CHANNEL__", "__STORYBOOK_STORY_STORE__"], - allowAfterSuper: false, - allowAfterThis: false, - enforceInMethodNames: true, - }, - ], + // disallow use of the Object constructor + "no-new-object": "error", - // disallow the use of Boolean literals in conditional expressions - // also, prefer `a || b` over `a ? a : b` - // https://eslint.org/docs/rules/no-unneeded-ternary - "no-unneeded-ternary": ["error", { defaultAssignment: false }], - - // disallow whitespace before properties - // https://eslint.org/docs/rules/no-whitespace-before-property - "no-whitespace-before-property": "error", - - // enforce the location of single-line statements - // https://eslint.org/docs/rules/nonblock-statement-body-position - "nonblock-statement-body-position": ["error", "beside", { overrides: {} }], - - // https://eslint.org/docs/rules/object-curly-newline - "object-curly-newline": [ - "error", - { - ExportDeclaration: { consistent: true, minProperties: 4, multiline: true }, - ImportDeclaration: { consistent: true, minProperties: 4, multiline: true }, - ObjectExpression: { consistent: true, minProperties: 4, multiline: true }, - ObjectPattern: { consistent: true, minProperties: 4, multiline: true }, - }, - ], + // disallow use of unary operators, ++ and -- + // https://eslint.org/docs/rules/no-plusplus + "no-plusplus": "error", - // enforce line breaks between braces - // require padding inside curly braces - "object-curly-spacing": ["error", "always"], - - // enforce "same line" or "multiple line" on object properties. - // https://eslint.org/docs/rules/object-property-newline - "object-property-newline": [ - "error", - { - allowAllPropertiesOnSameLine: true, - }, - ], - - // allow just one var statement per function - "one-var": ["error", "never"], - - // require a newline around variable declaration - // https://eslint.org/docs/rules/one-var-declaration-per-line - "one-var-declaration-per-line": ["error", "always"], - - // require assignment operator shorthand where possible or prohibit it entirely - // https://eslint.org/docs/rules/operator-assignment - "operator-assignment": ["error", "always"], - - // Requires operator at the beginning of the line in multiline statements - // https://eslint.org/docs/rules/operator-linebreak - "operator-linebreak": ["error", "before", { overrides: { "=": "none" } }], - - // disallow padding within blocks - "padded-blocks": [ - "error", - { - blocks: "never", - classes: "never", - switches: "never", - }, - { - allowSingleLineBlocks: true, - }, - ], + // disallow certain syntax forms + // https://eslint.org/docs/rules/no-restricted-syntax + "no-restricted-syntax": [ + "error", + { + message: + "for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array.", + selector: "ForInStatement", + }, + { + message: "Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.", + selector: "LabeledStatement", + }, + { + message: "`with` is disallowed in strict mode because it makes code impossible to predict and optimize.", + selector: "WithStatement", + }, + { + message: "`useMemo` with an empty dependency array can't provide a stable reference, use `useRef` instead.", + selector: "CallExpression[callee.name=useMemo][arguments.1.type=ArrayExpression][arguments.1.elements.length=0]", + }, + { + message: "Use `.key` instead of `.keyCode`", + selector: "MemberExpression > .property[type=Identifier][name=keyCode]", + }, + { + message: "Do not use full-width tilde. Use wave dash instead.", + // eslint-disable-next-line no-restricted-syntax + selector: ":matches(Literal[value=/~/],TemplateElement[value.raw=/~/])", + }, + { + message: "Use `.toString()` instead of template literal if you want to convert a value to string.", + selector: "TemplateLiteral[quasis.0.value.raw=\"\"][quasis.1.tail=true][quasis.1.value.raw=\"\"]", + }, + ], - // Require or disallow padding lines between statements - // https://eslint.org/docs/rules/padding-line-between-statements - "padding-line-between-statements": "off", + // disallow space between function identifier and application + // deprecated in favor of func-call-spacing + "no-spaced-func": "off", - // Disallow the use of Math.pow in favor of the ** operator - // https://eslint.org/docs/rules/prefer-exponentiation-operator - "prefer-exponentiation-operator": "error", + // disallow tab characters entirelyp + "no-tabs": "error", - // Prefer use of an object spread over Object.assign - // https://eslint.org/docs/rules/prefer-object-spread - "prefer-object-spread": "error", + // disallow the use of ternary operators + "no-ternary": "off", - // require quotes around object literal property names - // https://eslint.org/docs/rules/quote-props.html - "quote-props": ["error", "as-needed", { keywords: false, numbers: false, unnecessary: true }], + // disallow trailing whitespace at the end of lines + "no-trailing-spaces": [ + "error", + { + ignoreComments: true, + skipBlankLines: false, + }, + ], + + // disallow dangling underscores in identifiers + // https://eslint.org/docs/rules/no-underscore-dangle + "no-underscore-dangle": ["error", noUnderscoreDangle], + + // disallow the use of Boolean literals in conditional expressions + // also, prefer `a || b` over `a ? a : b` + // https://eslint.org/docs/rules/no-unneeded-ternary + "no-unneeded-ternary": ["error", { defaultAssignment: false }], + + // disallow whitespace before properties + // https://eslint.org/docs/rules/no-whitespace-before-property + "no-whitespace-before-property": "error", + + // enforce the location of single-line statements + // https://eslint.org/docs/rules/nonblock-statement-body-position + "nonblock-statement-body-position": ["error", "beside", { overrides: {} }], + + // https://eslint.org/docs/rules/object-curly-newline + "object-curly-newline": [ + "error", + { + ExportDeclaration: { consistent: true, minProperties: 4, multiline: true }, + ImportDeclaration: { consistent: true, minProperties: 4, multiline: true }, + ObjectExpression: { consistent: true, minProperties: 4, multiline: true }, + ObjectPattern: { consistent: true, minProperties: 4, multiline: true }, + }, + ], + + // enforce line breaks between braces + // require padding inside curly braces + "object-curly-spacing": ["error", "always"], + + // enforce "same line" or "multiple line" on object properties. + // https://eslint.org/docs/rules/object-property-newline + "object-property-newline": [ + "error", + { + allowAllPropertiesOnSameLine: true, + }, + ], + + // allow just one var statement per function + "one-var": ["error", "never"], + + // require a newline around variable declaration + // https://eslint.org/docs/rules/one-var-declaration-per-line + "one-var-declaration-per-line": ["error", "always"], + + // require assignment operator shorthand where possible or prohibit it entirely + // https://eslint.org/docs/rules/operator-assignment + "operator-assignment": ["error", "always"], + + // Requires operator at the beginning of the line in multiline statements + // https://eslint.org/docs/rules/operator-linebreak + "operator-linebreak": stylistic ? "off" : ["error", "before", { overrides: { "=": "none" } }], + + // disallow padding within blocks + "padded-blocks": [ + "error", + { + blocks: "never", + classes: "never", + switches: "never", + }, + { + allowSingleLineBlocks: true, + }, + ], - // specify whether double or single quotes should be used - quotes: ["error", "double", { avoidEscape: true }], + // Require or disallow padding lines between statements + // https://eslint.org/docs/rules/padding-line-between-statements + "padding-line-between-statements": "off", - // do not require jsdoc - // https://eslint.org/docs/rules/require-jsdoc - "require-jsdoc": "off", + // Disallow the use of Math.pow in favor of the ** operator + // https://eslint.org/docs/rules/prefer-exponentiation-operator + "prefer-exponentiation-operator": "error", - // require or disallow use of semicolons instead of ASI - semi: ["error", "always"], + // Prefer use of an object spread over Object.assign + // https://eslint.org/docs/rules/prefer-object-spread + "prefer-object-spread": "error", - // enforce spacing before and after semicolons - "semi-spacing": ["error", { after: true, before: false }], + // do not require jsdoc + // https://eslint.org/docs/rules/require-jsdoc + "require-jsdoc": "off", - // Enforce location of semicolons - // https://eslint.org/docs/rules/semi-style - "semi-style": ["error", "last"], + // enforce spacing before and after semicolons + "semi-spacing": ["error", { after: true, before: false }], - // requires object keys to be sorted - // Disabled because of perfectionist/sort-objects - "sort-keys": "off", + // Enforce location of semicolons + // https://eslint.org/docs/rules/semi-style + "semi-style": ["error", "last"], - // sort variables within the same declaration block - "sort-vars": "off", + // requires object keys to be sorted + // Disabled because of perfectionist/sort-objects + "sort-keys": "off", - // require or disallow space before blocks - "space-before-blocks": "error", + // sort variables within the same declaration block + "sort-vars": "off", - // require or disallow space before function opening parenthesis - // https://eslint.org/docs/rules/space-before-function-paren - "space-before-function-paren": [ - "error", - { - anonymous: "always", - asyncArrow: "always", - named: "never", - }, - ], + // require or disallow spaces inside parentheses + "space-in-parens": ["error", "never"], - // require or disallow spaces inside parentheses - "space-in-parens": ["error", "never"], - - // require spaces around operators - "space-infix-ops": "error", - - // Require or disallow spaces before/after unary operators - // https://eslint.org/docs/rules/space-unary-ops - "space-unary-ops": [ - "error", - { - nonwords: false, - overrides: {}, - words: true, - }, - ], + // Require or disallow spaces before/after unary operators + // https://eslint.org/docs/rules/space-unary-ops + "space-unary-ops": [ + "error", + { + nonwords: false, + overrides: {}, + words: true, + }, + ], + + // require or disallow a space immediately following the // or /* in a comment + // https://eslint.org/docs/rules/spaced-comment + "spaced-comment": [ + "error", + "always", + { + block: { + balanced: true, + exceptions: ["-", "+"], + markers: ["=", "!", ":", "::"], // space here to support sprockets directives and flow comment types + }, + line: { + exceptions: ["-", "+", "*"], + markers: ["=", "!", "/"], // space here to support sprockets directives, slash for TS /// comments + }, + }, + ], - // require or disallow a space immediately following the // or /* in a comment - // https://eslint.org/docs/rules/spaced-comment - "spaced-comment": [ - "error", - "always", - { - block: { - balanced: true, - exceptions: ["-", "+"], - markers: ["=", "!", ":", "::"], // space here to support sprockets directives and flow comment types - }, - line: { - exceptions: ["-", "+", "*"], - markers: ["=", "!", "/"], // space here to support sprockets directives, slash for TS /// comments - }, - }, - ], + // Enforce spacing around colons of switch statements + // https://eslint.org/docs/rules/switch-colon-spacing + "switch-colon-spacing": ["error", { after: true, before: false }], - // Enforce spacing around colons of switch statements - // https://eslint.org/docs/rules/switch-colon-spacing - "switch-colon-spacing": ["error", { after: true, before: false }], + // Require or disallow spacing between template tags and their literals + // https://eslint.org/docs/rules/template-tag-spacing + "template-tag-spacing": ["error", "never"], - // Require or disallow spacing between template tags and their literals - // https://eslint.org/docs/rules/template-tag-spacing - "template-tag-spacing": ["error", "never"], + // require or disallow the Unicode Byte Order Mark + // https://eslint.org/docs/rules/unicode-bom + "unicode-bom": ["error", "never"], - // require or disallow the Unicode Byte Order Mark - // https://eslint.org/docs/rules/unicode-bom - "unicode-bom": ["error", "never"], + // require regex literals to be wrapped in parentheses + "wrap-regex": "off", + }; +}; - // require regex literals to be wrapped in parentheses - "wrap-regex": "off", +export default createConfig("all", async (config, oFiles) => { + const { files = oFiles, prettier, stylistic } = config; - ...prettierRules, - }, - }, - type: "all", - }, - { - config: { + return [ + { + files, + name: "anolilab/style/rules", rules: { - // require or disallow newlines around directives - // https://eslint.org/docs/rules/lines-between-class-members - "lines-between-class-members": ["error", "always", { exceptAfterSingleLine: false }], + ...styleRules(stylistic), + "quote-props": "off", + + ...prettier + ? { + // The rest are rules that you never need to enable when using Prettier. + "array-bracket-newline": "off", + "array-bracket-spacing": "off", + "array-element-newline": "off", + "arrow-parens": "off", + "arrow-spacing": "off", + "block-spacing": "off", + "brace-style": "off", + "comma-dangle": "off", + + "comma-spacing": "off", + "comma-style": "off", + "computed-property-spacing": "off", + // script can distinguish them. + curly: 0, + "dot-location": "off", + "eol-last": "off", + "func-call-spacing": "off", + "function-call-argument-newline": "off", + "function-paren-newline": "off", + "generator-star-spacing": "off", + "implicit-arrow-linebreak": "off", + indent: "off", + "jsx-quotes": "off", + "key-spacing": "off", + "keyword-spacing": "off", + "linebreak-style": "off", + "lines-around-comment": 0, + "max-len": 0, + "max-statements-per-line": "off", + "multiline-ternary": "off", + "new-parens": "off", + "newline-per-chained-call": "off", + "no-confusing-arrow": 0, + "no-extra-parens": "off", + "no-extra-semi": "off", + "no-floating-decimal": "off", + "no-mixed-operators": 0, + "no-mixed-spaces-and-tabs": "off", + "no-multi-spaces": "off", + "no-multiple-empty-lines": "off", + "no-tabs": 0, + "no-trailing-spaces": "off", + "no-unexpected-multiline": 0, + "no-whitespace-before-property": "off", + "nonblock-statement-body-position": "off", + "object-curly-newline": "off", + "object-curly-spacing": "off", + "object-property-newline": "off", + "one-var-declaration-per-line": "off", + "operator-linebreak": "off", + "padded-blocks": "off", + quotes: 0, + "rest-spread-spacing": "off", + semi: "off", + "semi-spacing": "off", + "semi-style": "off", + "space-before-blocks": "off", + "space-before-function-paren": "off", + "space-in-parens": "off", + "space-unary-ops": "off", + "switch-colon-spacing": "off", + "template-curly-spacing": "off", + "template-tag-spacing": "off", + "wrap-iife": "off", + "wrap-regex": "off", + "yield-star-spacing": "off", + } + : {}, }, }, - type: "javascript", - }, - { - config: { + { + files: getFilesGlobs("ts"), + name: "anolilab/style/ts-rules", rules: { // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/brace-style.md "brace-style": "off", @@ -743,6 +619,11 @@ const config: Linter.Config = createConfigs([ // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/lines-between-class-members.md "lines-between-class-members": "off", + // Some built-in types have aliases, while some types are considered dangerous or harmful. + // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/ban-types.md + // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-array-constructor.md + "no-array-constructor": "off", + // Enforce using concise optional chain expressions instead of chained logical ands, negated logical ors, or empty objects. // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/quotes.md quotes: "off", @@ -754,15 +635,7 @@ const config: Linter.Config = createConfigs([ // Enforce using function types instead of interfaces with call signatures. // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/space-before-function-paren.md "space-before-function-paren": "off", - - // Some built-in types have aliases, while some types are considered dangerous or harmful. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/ban-types.md - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-array-constructor.md - "no-array-constructor": "off", }, }, - type: "typescript", - }, -]); - -export default config; + ]; +}); diff --git a/packages/eslint-config/src/config/variables.ts b/packages/eslint-config/src/config/variables.ts index 47d4518f1..7854b2d87 100644 --- a/packages/eslint-config/src/config/variables.ts +++ b/packages/eslint-config/src/config/variables.ts @@ -1,90 +1,95 @@ import confusingBrowserGlobals from "confusing-browser-globals"; import type { Linter } from "eslint"; -import { createConfigs } from "../utils/create-config"; +import type { OptionsFiles } from "../types"; +import { createConfig, getFilesGlobs } from "../utils/create-config"; -const config: Linter.Config = createConfigs([ - { - config: { - rules: { - // enforce or disallow variable initializations at definition - "init-declarations": "off", - - // disallow the catch clause parameter name being the same as a variable in the outer scope - "no-catch-shadow": "off", - - // disallow deletion of variables - "no-delete-var": "error", - - // disallow labels that share a name with a variable - // https://eslint.org/docs/rules/no-label-var - "no-label-var": "error", - - // disallow specific globals - "no-restricted-globals": [ - "error", - { - message: "Use Number.isFinite instead https://github.com/airbnb/javascript#standard-library--isfinite", - name: "isFinite", - }, - { - message: "Use Number.isNaN instead https://github.com/airbnb/javascript#standard-library--isnan", - name: "isNaN", - }, - ...confusingBrowserGlobals.map((g) => { - return { - name: g, - message: `Use window.${g} instead. https://github.com/facebook/create-react-app/blob/HEAD/packages/confusing-browser-globals/README.md`, - }; - }), - ], - - // disallow declaration of variables already declared in the outer scope - "no-shadow": "error", - - // disallow shadowing of names such as arguments - "no-shadow-restricted-names": "error", - - // disallow use of undeclared variables unless mentioned in a /*global */ block - "no-undef": "error", - - // disallow use of undefined when initializing variables - "no-undef-init": "error", - - // allow use of undefined variable - // https://eslint.org/docs/rules/no-undefined - "no-undefined": "off", - - // disallow declaration of variables that are not used in the code - "no-unused-vars": ["error", { args: "after-used", ignoreRestSiblings: true, vars: "all" }], - - // disallow use of variables before they are defined - "no-use-before-define": ["error", { classes: true, functions: true, variables: true }], - }, - }, - type: "all", - }, - { - config: { - rules: { - // Disallow member access on a value with type any. - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-use-before-define.md - "no-use-before-define": "off", +export const variablesRules: Partial = { + // enforce or disallow variable initializations at definition + "init-declarations": "off", - // Disallow unsafe declaration merging. - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unused-vars.md - "no-unused-vars": "off", + // disallow the catch clause parameter name being the same as a variable in the outer scope + "no-catch-shadow": "off", + // disallow deletion of variables + "no-delete-var": "error", + + // disallow labels that share a name with a variable + // https://eslint.org/docs/rules/no-label-var + "no-label-var": "error", + + // disallow specific globals + "no-restricted-globals": [ + "error", + { + message: "Use Number.isFinite instead https://github.com/airbnb/javascript#standard-library--isfinite", + name: "isFinite", + }, + { + message: "Use Number.isNaN instead https://github.com/airbnb/javascript#standard-library--isnan", + name: "isNaN", + }, + ...confusingBrowserGlobals.map((g) => { + return { + message: `Use window.${g} instead. https://github.com/facebook/create-react-app/blob/HEAD/packages/confusing-browser-globals/README.md`, + name: g, + }; + }), + { message: "Use `globalThis` instead.", name: "global" }, + { message: "Use `globalThis` instead.", name: "self" }, + ], + + // disallow declaration of variables already declared in the outer scope + "no-shadow": "error", + + // disallow shadowing of names such as arguments + "no-shadow-restricted-names": "error", + + // disallow use of undeclared variables unless mentioned in a /*global */ block + "no-undef": "error", + + // disallow use of undefined when initializing variables + "no-undef-init": "error", + + // allow use of undefined variable + // https://eslint.org/docs/rules/no-undefined + "no-undefined": "off", + + // disallow declaration of variables that are not used in the code + "no-unused-vars": ["error", { args: "after-used", ignoreRestSiblings: true, vars: "all" }], + + // disallow use of variables before they are defined + "no-use-before-define": ["error", { classes: true, functions: true, variables: true }], +}; + +export default createConfig("all", async (config, oFiles) => { + const { files = oFiles } = config; + + return [ + { + files, + name: "anolilab/variables/rules", + rules: variablesRules, + }, + { + files: getFilesGlobs("ts"), + name: "anolilab/variables/ts-rules", + rules: { // Disallow invocation of require(). // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-shadow.md "no-shadow": "off", // Disallow unnecessary constraints on generic types. "no-undef": "off", + + // Disallow unsafe declaration merging. + // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unused-vars.md + "no-unused-vars": "off", + + // Disallow member access on a value with type any. + // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-use-before-define.md + "no-use-before-define": "off", }, }, - type: "typescript", - }, -]); - -export default config; + ]; +}); diff --git a/packages/eslint-config/src/define-config.ts b/packages/eslint-config/src/define-config.ts deleted file mode 100644 index c1a4b1bbc..000000000 --- a/packages/eslint-config/src/define-config.ts +++ /dev/null @@ -1 +0,0 @@ -export { defineConfig, defineFlatConfig } from "eslint-define-config"; diff --git a/packages/eslint-config/src/engine-node-overwrite.ts b/packages/eslint-config/src/engine-node-overwrite.ts deleted file mode 100644 index 35831a672..000000000 --- a/packages/eslint-config/src/engine-node-overwrite.ts +++ /dev/null @@ -1,73 +0,0 @@ -/** -Define the rules config that are overwritten only for specific version of Node.js based on `engines.node` in package.json or the `nodeVersion` option. - -The keys are rule names and the values are an Object with a valid semver (`4.0.0` is valid `4` is not) as keys and the rule configuration as values. - -Each entry define the rule config and the maximum Node.js version for which to set it. -The entry with the lowest version that is compliant with the `engines.node`/`nodeVersion` range will be used. - -@type {Object} - -@example -``` -{ - 'plugin/rule': { - '6.0.0': ['error', {prop: 'node-6-conf'}], - '8.0.0': ['error', {prop: 'node-8-conf'}] - } -} -``` - -With `engines.node` set to `>=4` the rule `plugin/rule` will not be used. -With `engines.node` set to `>=6` the rule `plugin/rule` will be used with the config `{prop: 'node-6-conf'}`. -With `engines.node` set to `>=8` the rule `plugin/rule` will be used with the config `{prop: 'node-8-conf'}`. -*/ -const engineRules = { - "n/prefer-global/text-decoder": { - "11.0.0": "off", - }, - "n/prefer-global/text-encoder": { - "11.0.0": "off", - }, - "n/prefer-global/url": { - "10.0.0": "off", - }, - "n/prefer-global/url-search-params": { - "10.0.0": "off", - }, - "n/prefer-promises/dns": { - "11.14.0": "off", - }, - "n/prefer-promises/fs": { - "11.14.0": "off", - }, - "no-useless-catch": { - "10.0.0": "off", - }, - "prefer-destructuring": { - "6.0.0": "off", - }, - "prefer-named-capture-group": { - "10.0.0": "off", - }, - "prefer-object-spread": { - "8.3.0": "off", - }, - "prefer-rest-params": { - "6.0.0": "off", - }, - "promise/prefer-await-to-then": { - "7.6.0": "off", - }, - "unicorn/no-new-buffer": { - "5.10.0": "off", - }, - "unicorn/prefer-flat-map": { - "11.0.0": "off", - }, - "unicorn/prefer-spread": { - "5.0.0": "off", - }, -}; - -export default engineRules; diff --git a/packages/eslint-config/src/global.d.ts b/packages/eslint-config/src/global.d.ts deleted file mode 100644 index e12d466fa..000000000 --- a/packages/eslint-config/src/global.d.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { Linter } from "eslint"; - -export {}; - -declare global { - var hasAnolilabEsLintConfigLoaded: undefined | boolean; - - var hasAnolilabEsLintConfigReactRuntimePath: undefined | boolean; - - var hasAnolilabEsLintTestConfigLoaded: undefined | boolean; - - var hasAnolilabEsLintConfigPrettier: undefined | boolean; - - var hasAnolilabEsLintVitestGlobalsPlugin: undefined | boolean; - - var hasAnolilabEsLintConfigJsyA11yStorybook: undefined | boolean; - - var hasAnolilabEsLintConfigPerfectionistTypescriptSortKeys: undefined | boolean; - - var hasAnolilabEsLintConfigPlaywrightJest: undefined | boolean; - - var hasAnolilabEsLintConfigJsoncPackageJsonSort: undefined | boolean; - - var hasAnolilabEsLintConfigDeprecation: undefined | boolean; - - var anolilabEslintIndent: undefined | number; - - var anolilabEslintPackageJsonConfig: undefined | { [key: string]: boolean | undefined }; - - var anolilabEslintConfigTypescriptPrettierRules: undefined | Linter.RulesRecord; - - var anolilabEslintConfigBabelPrettierRules: undefined | Linter.RulesRecord; - - var anolilabEslintConfigJsDocRules: undefined | Linter.Config["overrides"]; - - var anolilabEslintConfigNodeRules: undefined | Linter.RulesRecord; - - var anolilabEslintConfigReactPrettierRules: undefined | Linter.RulesRecord; - - var anolilabEslintConfigHtmlPrettierRules: undefined | Linter.RulesRecord; - var anolilabEslintConfigHtmlPrettierSettings: undefined | Linter.Config["settings"]; - - var anolilabEslintConfigReactVersion: undefined | string; - - var anolilabEslintConfigTestingLibraryRuleSet: undefined | string; - - var anolilabEslintConfigUnicornPrettierRules: undefined | Linter.RulesRecord; - - var anolilabEslintImportNoUnusedModulesConfig: undefined | string[]; -} diff --git a/packages/eslint-config/src/globals.ts b/packages/eslint-config/src/globals.ts deleted file mode 100644 index a34c5d98b..000000000 --- a/packages/eslint-config/src/globals.ts +++ /dev/null @@ -1,13 +0,0 @@ -import globals from "globals"; - -// eslint-disable-next-line prefer-destructuring -export const es2015: typeof globals.es2015 = globals.es2015; - -// eslint-disable-next-line prefer-destructuring -export const es2017: typeof globals.es2017 = globals.es2017; - -// eslint-disable-next-line prefer-destructuring -export const es2020: typeof globals.es2020 = globals.es2020; - -// eslint-disable-next-line prefer-destructuring -export const es2021: typeof globals.es2021 = globals.es2021; diff --git a/packages/eslint-config/src/index.ts b/packages/eslint-config/src/index.ts index 65d789ebd..cf07ac734 100644 --- a/packages/eslint-config/src/index.ts +++ b/packages/eslint-config/src/index.ts @@ -1,228 +1,949 @@ -/** - * rushstack eslint-patch is used to include plugins as dev - * dependencies instead of imposing them as peer dependencies - * - * {@link https://www.npmjs.com/package/@rushstack/eslint-patch} - * {@link https://stackoverflow.com/a/74478635/1392749} - * {@link https://github.com/eslint/eslint/issues/3458} - * {@link https://eslint.org/blog/2022/08/new-config-system-part-1/} - * {@link https://eslint.org/blog/2022/08/new-config-system-part-2/} - * {@link https://eslint.org/blog/2022/08/new-config-system-part-3/} - * {@link https://eslint.org/docs/latest/user-guide/configuring/configuration-files-new} - */ -import "@rushstack/eslint-patch/modern-module-resolution"; - +import { readFileSync } from "node:fs"; import { join } from "node:path"; -import { hasDependency, hasDevDependency, packageIsTypeModule, pkg } from "@anolilab/package-json-utils"; +import { ensurePackages, hasPackageJsonAnyDependency, parsePackageJson } from "@visulima/package"; import type { Linter } from "eslint"; -import globals from "globals"; -import { intersects, rcompare } from "semver"; +import { FlatConfigComposer } from "eslint-flat-config-utils"; + +import bestPractices from "./config/best-practices"; +import errors from "./config/errors"; +import es6 from "./config/es6"; +import ignores from "./config/ignores"; +import antfu from "./config/plugins/antfu"; +import astro from "./config/plugins/astro"; +import comments from "./config/plugins/comments"; +import compat from "./config/plugins/compat"; +import formatters from "./config/plugins/formatters"; +import html from "./config/plugins/html"; +import imports from "./config/plugins/imports"; +import javascript from "./config/plugins/javascript"; +import jsdoc from "./config/plugins/jsdoc"; +import jsonc from "./config/plugins/jsonc"; +import jsxA11y from "./config/plugins/jsx-a11y"; +import markdown from "./config/plugins/markdown"; +import noSecrets from "./config/plugins/no-secrets"; +import noUnsanitized from "./config/plugins/no-unsanitized"; +import node from "./config/plugins/node"; +import perfectionist from "./config/plugins/perfectionist"; +import playwright from "./config/plugins/playwright"; +import promise from "./config/plugins/promise"; +import react from "./config/plugins/react"; +import regexp from "./config/plugins/regexp"; +import simpleImportSort from "./config/plugins/simple-import-sort"; +import sonarjs from "./config/plugins/sonarjs"; +import storybook from "./config/plugins/storybook"; +import stylistic from "./config/plugins/stylistic"; +import tailwindcss from "./config/plugins/tailwindcss"; +import tanstackQuery from "./config/plugins/tanstack-query"; +import tanstackRouter from "./config/plugins/tanstack-router"; +import testingLibrary from "./config/plugins/testing-library"; +import toml from "./config/plugins/toml"; +import tsdoc from "./config/plugins/tsdoc"; +import typescript from "./config/plugins/typescript"; +import unicorn from "./config/plugins/unicorn"; +import unocss from "./config/plugins/unocss"; +import validateJsxNesting from "./config/plugins/validate-jsx-nesting"; +import vitest from "./config/plugins/vitest"; +import yaml from "./config/plugins/yml"; +import youDontNeedLodashUnderscore from "./config/plugins/you-dont-need-lodash-underscore"; +import zod from "./config/plugins/zod"; +import style from "./config/style"; +import variables from "./config/variables"; +import type { RuleOptions } from "./typegen"; +import type { + Awaitable, + ConfigNames, + OptionsConfig, + OptionsFiles, + OptionsOverrides, + StylisticConfig, + TypedFlatConfigItem, +} from "./types"; +import { getFilesGlobs } from "./utils/create-config"; +import interopDefault from "./utils/interop-default"; +import isInEditorEnvironment from "./utils/is-in-editor-environment"; + +const flatConfigProperties = ["name", "languageOptions", "linterOptions", "processor", "plugins", "rules", "settings"] satisfies (keyof TypedFlatConfigItem)[]; + +export type ResolvedOptions = T extends boolean ? never : NonNullable; + +export const resolveSubOptions = (options: OptionsConfig, key: K): ResolvedOptions => (typeof options[key] === "boolean" ? {} : options[key] || {}) as ResolvedOptions; +export const getOverrides = (options: OptionsConfig, key: K): Partial => { + const sub = resolveSubOptions(options, key); + + return { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ...(options.overrides as any)?.[key], + ..."overrides" in sub ? sub.overrides : {}, + }; +}; -import { internalPluginConfig, pluginRules, possiblePluginRules, rules } from "./config"; -import engineRules from "./engine-node-overwrite"; -import anolilabEslintConfig from "./utils/eslint-config"; -import { consoleLog, consolePlugin } from "./utils/loggers"; +export const getFiles = (options: OptionsConfig, key: K): string[] | undefined => { + const sub = resolveSubOptions(options, key); -// Workaround VS Code trying to run this file twice! -if (!global.hasAnolilabEsLintConfigLoaded) { - if (process.env["DEBUG"]) { - consoleLog("\n@anolilab/eslint-config loaded the following plugins:\n"); + if ("files" in sub) { + if (typeof sub.files === "string") { + return [sub.files]; + } - consoleLog(" @rushstack/eslint-plugin-security"); - internalPluginConfig - .map((name: string) => { - if (name === "import") { - return "i"; - } + return sub.files; + } - if (name === "node") { - return "n"; - } + return undefined; +}; - return name; - }) - .forEach((plugin) => consolePlugin(plugin)); +/** + * Construct an array of ESLint flat config items. + * @param {OptionsConfig & TypedFlatConfigItem} options + * The options for generating the ESLint configurations. + * @param {Awaitable[]} userConfigs + * The user configurations to be merged with the generated configurations. + * @returns {Promise} + * The merged ESLint configurations. + */ +export const createConfig = async ( + options: Omit & OptionsConfig = {}, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ...userConfigs: Awaitable | Linter.Config[] | TypedFlatConfigItem | TypedFlatConfigItem[]>[] + // eslint-disable-next-line sonarjs/cognitive-complexity +): Promise> => { + if ("files" in options) { + throw new Error( + "[@anolilab/eslint-config] The first argument should not contain the \"files\" property as the options are supposed to be global. Place it in the second or later config instead.", + ); } - let hasLogged = false; + const cwd = options.cwd ?? process.cwd(); + const packageJson = parsePackageJson(readFileSync(join(cwd, "package.json"), "utf8")); + + const enablePrettier = hasPackageJsonAnyDependency(packageJson, ["prettier"]); + const isCwdInScope = hasPackageJsonAnyDependency(packageJson, ["@anolilab/eslint-config"]); + + const hasReact = hasPackageJsonAnyDependency(packageJson, ["react", "react-dom"]); + + const { + astro: enableAstro = hasPackageJsonAnyDependency(packageJson, ["astro"]), + componentExts: componentExtensions = [], + gitignore: enableGitignore = true, + html: enableHtml = false, + jsx: enableJsx = hasPackageJsonAnyDependency(packageJson, ["eslint-plugin-jsx-a11y", "eslint-plugin-validate-jsx-nesting"]) || hasReact, + lodash: enableLodash = hasPackageJsonAnyDependency(packageJson, [ + "lodash", + "underscore", + "lodash-es", + "@types/lodash", + + "lodash.chunk", + "lodash.compact", + "lodash.concat", + "lodash.difference", + "lodash.differenceby", + "lodash.differencewith", + "lodash.drop", + "lodash.dropright", + "lodash.droprightwhile", + "lodash.dropwhile", + "lodash.fill", + "lodash.findindex", + "lodash.findlastindex", + "lodash.flatten", + "lodash.flattendeep", + "lodash.flattendepth", + "lodash.frompairs", + "lodash.head", + "lodash.indexof", + "lodash.initial", + "lodash.intersection", + "lodash.intersectionby", + "lodash.intersectionwith", + "lodash.join", + "lodash.last", + "lodash.lastindexof", + "lodash.nth", + "lodash.pull", + "lodash.pullall", + "lodash.pullallby", + "lodash.pullallwith", + "lodash.pullat", + "lodash.remove", + "lodash.reverse", + "lodash.slice", + "lodash.sortedindex", + "lodash.sortedindexby", + "lodash.sortedindexof", + "lodash.sortedlastindex", + "lodash.sortedlastindexby", + "lodash.sortedlastindexof", + "lodash.sorteduniq", + "lodash.sorteduniqby", + "lodash.tail", + "lodash.take", + "lodash.takeright", + "lodash.takerightwhile", + "lodash.takewhile", + "lodash.union", + "lodash.unionby", + "lodash.unionwith", + "lodash.uniq", + "lodash.uniqby", + "lodash.uniqwith", + "lodash.unzip", + "lodash.unzipwith", + "lodash.without", + "lodash.xor", + "lodash.xorby", + "lodash.xorwith", + "lodash.zip", + "lodash.zipobject", + "lodash.zipobjectdeep", + "lodash.zipwith", + "lodash.countby", + "lodash.every", + "lodash.filter", + "lodash.find", + "lodash.findlast", + "lodash.flatmap", + "lodash.flatmapdeep", + "lodash.flatmapdepth", + "lodash.foreach", + "lodash.foreachright", + "lodash.groupby", + "lodash.includes", + "lodash.invokemap", + "lodash.keyby", + "lodash.map", + "lodash.orderby", + "lodash.partition", + "lodash.reduce", + "lodash.reduceright", + "lodash.reject", + "lodash.sample", + "lodash.samplesize", + "lodash.shuffle", + "lodash.size", + "lodash.some", + "lodash.sortby", + "lodash.now", + "lodash.after", + "lodash.ary", + "lodash.before", + "lodash.bind", + "lodash.bindkey", + "lodash.curry", + "lodash.curryright", + "lodash.debounce", + "lodash.defer", + "lodash.delay", + "lodash.flip", + "lodash.memoize", + "lodash.negate", + "lodash.once", + "lodash.overargs", + "lodash.partial", + "lodash.partialright", + "lodash.rearg", + "lodash.rest", + "lodash.spread", + "lodash.throttle", + "lodash.unary", + "lodash.wrap", + "lodash.castarray", + "lodash.clone", + "lodash.clonedeep", + "lodash.clonedeepwith", + "lodash.clonewith", + "lodash.conformsto", + "lodash.eq", + "lodash.gt", + "lodash.gte", + "lodash.isarguments", + "lodash.isarray", + "lodash.isarraybuffer", + "lodash.isarraylike", + "lodash.isarraylikeobject", + "lodash.isboolean", + "lodash.isbuffer", + "lodash.isdate", + "lodash.iselement", + "lodash.isempty", + "lodash.isequal", + "lodash.isequalwith", + "lodash.iserror", + "lodash.isfinite", + "lodash.isfunction", + "lodash.isinteger", + "lodash.islength", + "lodash.ismap", + "lodash.ismatch", + "lodash.ismatchwith", + "lodash.isnan", + "lodash.isnative", + "lodash.isnil", + "lodash.isnull", + "lodash.isnumber", + "lodash.isobject", + "lodash.isobjectlike", + "lodash.isplainobject", + "lodash.isregexp", + "lodash.issafeinteger", + "lodash.isset", + "lodash.isstring", + "lodash.issymbol", + "lodash.istypedarray", + "lodash.isundefined", + "lodash.isweakmap", + "lodash.isweakset", + "lodash.lt", + "lodash.lte", + "lodash.toarray", + "lodash.tofinite", + "lodash.tointeger", + "lodash.tolength", + "lodash.tonumber", + "lodash.toplainobject", + "lodash.tosafeinteger", + "lodash.tostring", + "lodash.add", + "lodash.ceil", + "lodash.divide", + "lodash.floor", + "lodash.max", + "lodash.maxby", + "lodash.mean", + "lodash.meanby", + "lodash.min", + "lodash.minby", + "lodash.multiply", + "lodash.round", + "lodash.subtract", + "lodash.sum", + "lodash.sumby", + "lodash.clamp", + "lodash.inrange", + "lodash.random", + "lodash.assign", + "lodash.assignin", + "lodash.assigninwith", + "lodash.assignwith", + "lodash.at", + "lodash.create", + "lodash.defaults", + "lodash.defaultsdeep", + "lodash.findkey", + "lodash.findlastkey", + "lodash.forin", + "lodash.forinright", + "lodash.forown", + "lodash.forownright", + "lodash.functions", + "lodash.functionsin", + "lodash.get", + "lodash.has", + "lodash.hasin", + "lodash.invert", + "lodash.invertby", + "lodash.invoke", + "lodash.keys", + "lodash.keysin", + "lodash.mapkeys", + "lodash.mapvalues", + "lodash.merge", + "lodash.mergewith", + "lodash.omit", + "lodash.omitby", + "lodash.pick", + "lodash.pickby", + "lodash.result", + "lodash.set", + "lodash.setwith", + "lodash.topairs", + "lodash.topairsin", + "lodash.transform", + "lodash.unset", + "lodash.update", + "lodash.updatewith", + "lodash.values", + "lodash.valuesin", + "lodash.chain", + "lodash.tap", + "lodash.thru", + "lodash.camelcase", + "lodash.capitalize", + "lodash.deburr", + "lodash.endswith", + "lodash.escape", + "lodash.escaperegexp", + "lodash.kebabcase", + "lodash.lowercase", + "lodash.lowerfirst", + "lodash.pad", + "lodash.padend", + "lodash.padstart", + "lodash.parseint", + "lodash.repeat", + "lodash.replace", + "lodash.snakecase", + "lodash.split", + "lodash.startcase", + "lodash.startswith", + "lodash.template", + "lodash.tolower", + "lodash.toupper", + "lodash.trim", + "lodash.trimend", + "lodash.trimstart", + "lodash.truncate", + "lodash.unescape", + "lodash.uppercase", + "lodash.upperfirst", + "lodash.words", + "lodash.attempt", + "lodash.bindall", + "lodash.cond", + "lodash.conforms", + "lodash.constant", + "lodash.defaultto", + "lodash.flow", + "lodash.flowright", + "lodash.identity", + "lodash.iteratee", + "lodash.matches", + "lodash.matchesproperty", + "lodash.method", + "lodash.methodof", + "lodash.mixin", + "lodash.noconflict", + "lodash.noop", + "lodash.ntharg", + "lodash.over", + "lodash.overevery", + "lodash.oversome", + "lodash.property", + "lodash.propertyof", + "lodash.range", + "lodash.rangeright", + "lodash.runincontext", + "lodash.stubarray", + "lodash.stubfalse", + "lodash.stubobject", + "lodash.stubstring", + "lodash.stubtrue", + "lodash.times", + "lodash.topath", + "lodash.uniqueid", + ]), + playwright: enablePlaywright = hasPackageJsonAnyDependency(packageJson, ["playwright", "eslint-plugin-playwright"]), + react: enableReact = hasReact || hasPackageJsonAnyDependency(packageJson, [ + "eslint-plugin-react", + "eslint-plugin-react-hooks", + "eslint-plugin-react-refresh", + "@eslint-react/eslint-plugin", + ]), + regexp: enableRegexp = true, + silent = false, + storybook: enableStorybook = hasPackageJsonAnyDependency(packageJson, ["storybook", "eslint-plugin-storybook"]), + tailwindcss: enableTailwindCss = false, + tanstackQuery: enableTanstackQuery = hasPackageJsonAnyDependency(packageJson, ["@tanstack/react-query"]), + tanstackRouter: enableTanstackRouter = hasPackageJsonAnyDependency(packageJson, ["@tanstack/react-router"]), + testingLibrary: enableTestingLibrary = hasPackageJsonAnyDependency(packageJson, ["@testing-library/dom", "@testing-library/react"]), + tsdoc: enableTsdoc = false, + typescript: enableTypeScript = hasPackageJsonAnyDependency(packageJson, ["typescript"]), + unicorn: enableUnicorn = true, + unocss: enableUnoCSS = false, + vitest: enableVitest = hasPackageJsonAnyDependency(packageJson, ["vitest"]), + zod: enableZod = hasPackageJsonAnyDependency(packageJson, ["zod"]), + } = options; + + if (isCwdInScope) { + const packages = []; + + if (enableZod) { + packages.push("eslint-plugin-zod"); + } - Object.entries(possiblePluginRules).forEach(([plugin, dependencies]) => { - const hasOneDependency = Object.values(dependencies).some(Boolean); + if (enableUnoCSS) { + packages.push("@unocss/eslint-plugin"); + } - if (hasOneDependency) { - hasLogged = true; + if (enableTanstackQuery) { + packages.push("@tanstack/eslint-plugin-query"); + } - consoleLog( - `\nYour package.json container dependencies for the "${plugin}" eslint-plugin, please add the following dependencies with your chosen package manager to enable this plugin:`, - ); + if (enableTanstackRouter) { + packages.push("@tanstack/eslint-plugin-router"); + } - Object.entries(dependencies).forEach(([dependency, installed]) => { - if (!installed) { - consoleLog(` ${dependency}`); - } - }); + if (enableTailwindCss) { + packages.push("eslint-plugin-tailwindcss"); } - }); - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (hasLogged) { - consoleLog("\nTo disable this message, add the following to your package.json:"); - consoleLog(' "anolilab": { "eslint-config": { plugin: { "plugin-name": false } } }\n'); - } + if (enableStorybook) { + packages.push("eslint-plugin-storybook"); + } + + if (enableReact) { + packages.push("eslint-plugin-react", "@eslint-react/eslint-plugin", "eslint-plugin-react-hooks"); + } + + if (enableTestingLibrary) { + packages.push("eslint-plugin-testing-library"); + } - consoleLog('To disable all logging, add the following to your eslint command call "NO_LOGS=true eslint ..."'); + if (enableJsx) { + packages.push("eslint-plugin-jsx-a11y", "eslint-plugin-validate-jsx-nesting"); + } - global.hasAnolilabEsLintConfigLoaded = true; -} + if (enableLodash) { + packages.push("eslint-plugin-you-dont-need-lodash-underscore"); + } -const configRules: Linter.RulesRecord = {}; + if (enableTsdoc) { + packages.push("eslint-plugin-tsdoc"); + } -let nodeVersion: string | undefined; + if (options.formatters) { + packages.push("eslint-plugin-format"); + } -if (pkg?.engines?.["node"]) { - nodeVersion = pkg.engines["node"]; -} + if (enableAstro) { + packages.push("eslint-plugin-astro", "astro-eslint-parser", "@typescript-eslint/parser"); -Object.entries(engineRules).forEach(([rule, ruleConfig]) => { - Object.keys(ruleConfig) - .sort(rcompare) - .forEach((minVersion) => { - if (nodeVersion && intersects(nodeVersion, `<${minVersion}`)) { - // eslint-disable-next-line security/detect-object-injection - configRules[rule] = ruleConfig[minVersion as keyof typeof ruleConfig] as Linter.RuleEntry; + if (typeof options.formatters === "object" && options.formatters.astro) { + packages.push("prettier-plugin-astro"); } - }); -}); - -// Workaround VS Code trying to run this file twice! -if (!global.hasAnolilabEsLintConfigPrettier && (hasDependency("prettier") || hasDevDependency("prettier"))) { - global.hasAnolilabEsLintConfigPrettier = true; - - if (anolilabEslintConfig["info_on_disabling_prettier_conflict_rule"] !== false) { - consoleLog("\nFound prettier as dependency, disabling some rules to fix wrong behavior of the rule with eslint and prettier"); - } -} - -const config: Linter.Config = { - // After an .eslintrc.js file is loaded, ESLint will normally continue visiting all parent folders - // to look for other .eslintrc.js files, and also consult a personal file ~/.eslintrc.js. If any files - // are found, their options will be merged. This is difficult for humans to understand, and it will cause - // nondeterministic behavior if files are loaded from outside the Git working folder. - // - // Setting root=true causes ESLint to stop looking for other config files after the first .eslintrc.js - extends: [ - ...rules.map((plugin) => join(__dirname, `./config/${plugin}.js`)), - - ...pluginRules.map((plugin) => join(__dirname, `./config/plugins/${plugin}.js`)), - ], - globals: { - ...globals.browser, - ...globals.nodeBuiltin, - ...(packageIsTypeModule - ? { - __dirname: "off", - __filename: "off", - exports: "off", - require: "off", + } + + if (typeof options.formatters === "object") { + if (options.formatters?.markdown && options.formatters?.slidev) { + packages.push("prettier-plugin-slidev"); } - : { __dirname: true, __filename: true, exports: true, require: true }), - }, - ignorePatterns: [ - "!.*", - - ".git/", - "node_modules/", - "bower_components/", - "jspm_packages/", - ".npm/", - - "lib-cov/", - "coverage/", - ".nyc_output/", - ".cache/", - - "build/", - "dist/", - "tmp/", - - "**/*.min.*", - - // Manually authored .d.ts files are generally used to describe external APIs that are not expected - // to follow our coding conventions. Linting those files tends to produce a lot of spurious suppressions, - // so we simply ignore them. - "*.d.ts", - - "pnpm-lock.yaml", - ], - overrides: [ - { - files: ["**/migrations/*.{js,ts}"], - rules: { - "filenames/match-regex": "off", - }, - }, - { - files: [ - // Test files - "**/*.spec.{js,ts,tsx}", - "**/*.test.{js,ts,tsx}", - - // Facebook convention - "**/__mocks__/*.{js,ts,tsx}", - "**/__tests__/*.{js,ts,tsx}", - - // Microsoft convention - "**/test/*.{js,ts,tsx}", + + if (options.formatters?.xml || options.formatters?.svg) { + packages.push("@prettier/plugin-xml"); + } + } + + if (packages.length > 0) { + await ensurePackages(packageJson, packages, "devDependencies", { + confirm: { + message: (list: string[]) => `@anolilab/eslint-config requires the following ${list.length === 1 ? "package" : "packages"} to be installed: \n\n"${list.join("\"\n\"")}"\n\nfor the ESLint configurations to work correctly. Do you want to install ${packages.length === 1 ? "it" : "them"} now?`, + }, + }); + } + } + + let { isInEditor } = options; + + if (isInEditor === undefined || isInEditor === false) { + isInEditor = isInEditorEnvironment(); + + if (isInEditor) { + // eslint-disable-next-line no-console + console.log("[@anolilab/eslint-config] Detected running in editor, some rules are disabled."); + } + } + + let stylisticOptions: boolean | (OptionsFiles & OptionsOverrides & StylisticConfig) = {}; + + if (options.stylistic === false) { + stylisticOptions = false; + } else if (typeof options.stylistic === "object") { + stylisticOptions = options.stylistic; + } + + if (stylisticOptions && !("jsx" in stylisticOptions)) { + stylisticOptions.jsx = enableJsx; + } + + const configs: Awaitable[] = []; + + if (enableGitignore) { + if (typeof enableGitignore === "boolean") { + configs.push( + interopDefault(import("eslint-config-flat-gitignore")).then((r) => [ + r({ + name: "anolilab/gitignore", + strict: false, + }), + ]), + ); + } else { + configs.push( + interopDefault(import("eslint-config-flat-gitignore")).then((r) => [ + r({ + name: "anolilab/gitignore", + ...enableGitignore, + }), + ]), + ); + } + } + + const typescriptOptions = resolveSubOptions(options, "typescript"); + const tsconfigPath = "tsconfigPath" in typescriptOptions ? typescriptOptions.tsconfigPath : undefined; + + // Base configs + configs.push( + ignores(options.ignores), + javascript({ + packageJson, + }), + bestPractices({}), + errors({}), + style({ + stylistic: stylisticOptions, + }), + es6({ + isInEditor, + }), + variables({}), + comments({ + files: getFiles(options, "comments"), + overrides: getOverrides(options, "comments"), + }), + node({ + files: getFiles(options, "node"), + overrides: getOverrides(options, "node"), + packageJson, + }), + jsdoc({ + files: getFiles(options, "jsdoc"), + jsx: enableJsx, + overrides: getOverrides(options, "jsdoc"), + packageJson, + silent, + stylistic: stylisticOptions, + typescript: enableTypeScript, + }), + imports({ + cwd, + files: getFiles(options, "imports"), + overrides: getOverrides(options, "imports"), + packageJson, + stylistic: stylisticOptions, + tsconfigPath, + }), + simpleImportSort({ + files: getFiles(options, "simpleImportSort"), + overrides: getOverrides(options, "simpleImportSort"), + }), + antfu({ + files: getFiles(options, "antfu"), + lessOpinionated: options.lessOpinionated, + overrides: getOverrides(options, "antfu"), + packageJson, + }), + noSecrets({ + overrides: getOverrides(options, "noSecrets"), + }), + noUnsanitized({ + overrides: getOverrides(options, "noUnsanitized"), + }), + promise({ + files: getFiles(options, "promise"), + overrides: getOverrides(options, "promise"), + }), + sonarjs({ + files: getFiles(options, "sonarjs"), + overrides: getOverrides(options, "sonarjs"), + }), + perfectionist({ + files: getFiles(options, "perfectionist"), + overrides: getOverrides(options, "perfectionist"), + packageJson, + }), + ); + + if (packageJson["browserlist"] !== undefined) { + configs.push( + compat({ + files: getFiles(options, "compat"), + }), + ); + } + + if (enableUnicorn) { + configs.push( + unicorn({ + files: getFiles(options, "unicorn"), + overrides: getOverrides(options, "unicorn"), + packageJson, + prettier: enablePrettier, + stylistic: stylisticOptions, + }), + ); + } + + if (enableLodash) { + configs.push( + youDontNeedLodashUnderscore({ + files: getFiles(options, "lodash"), + overrides: getOverrides(options, "lodash"), + }), + ); + } + + if (enableJsx) { + configs.push( + [ + { + files: getFilesGlobs("jsx_and_tsx"), + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, + name: "anolilab/jsx/setup", + }, ], - rules: { - "no-magic-numbers": "off", - "sonarjs/no-duplicate-string": "off", - }, - }, - // Fixes https://github.com/eslint/eslint/discussions/15305 - { - files: packageIsTypeModule ? ["**/*.js", "**/*.mjs"] : ["**/*.mjs"], - parser: "@babel/eslint-parser", - parserOptions: { - babelOptions: { - plugins: ["@babel/plugin-syntax-import-assertions"], + jsxA11y({ + files: getFiles(options, "jsx-a11y"), + overrides: getOverrides(options, "jsx-a11y"), + }), + validateJsxNesting({ + files: getFiles(options, "validateJsxNesting"), + overrides: getOverrides(options, "validateJsxNesting"), + }), + ); + } + + if (enableTypeScript) { + configs.push( + typescript({ + ...typescriptOptions, + componentExts: componentExtensions, + overrides: getOverrides(options, "typescript"), + prettier: enablePrettier, + stylistic: stylisticOptions, + }), + ); + } + + if (stylisticOptions) { + configs.push( + stylistic({ + ...stylisticOptions, + overrides: getOverrides(options, "stylistic"), + }), + ); + } + + if (enableRegexp) { + configs.push(regexp(typeof enableRegexp === "boolean" ? {} : enableRegexp)); + } + + if (enableVitest) { + configs.push( + vitest({ + files: getFiles(options, "vitest"), + isInEditor, + overrides: getOverrides(options, "vitest"), + prettier: enablePrettier, + tsconfigPath, + }), + ); + } + + if (enableTestingLibrary) { + configs.push( + testingLibrary({ + files: getFiles(options, "testingLibrary"), + overrides: getOverrides(options, "testingLibrary"), + packageJson, + }), + ); + } + + if (enablePlaywright) { + configs.push( + playwright({ + files: getFiles(options, "playwright"), + overrides: getOverrides(options, "playwright"), + }), + ); + } + + if (enableStorybook) { + configs.push( + storybook({ + overrides: getOverrides(options, "storybook"), + }), + ); + } + + if (enableTailwindCss) { + configs.push( + tailwindcss({ + overrides: getOverrides(options, "tailwindcss"), + }), + ); + } + + if (enableTanstackQuery) { + configs.push( + tanstackQuery({ + files: getFiles(options, "tanstackQuery"), + overrides: getOverrides(options, "tanstackQuery"), + }), + ); + } + + if (enableTanstackRouter) { + configs.push( + tanstackRouter({ + files: getFiles(options, "tanstackRouter"), + overrides: getOverrides(options, "tanstackRouter"), + }), + ); + } + + if (enableHtml) { + configs.push( + html({ + files: getFiles(options, "html"), + overrides: getOverrides(options, "html"), + prettier: enablePrettier, + stylistic: stylisticOptions, + }), + ); + } + + if (enableZod) { + configs.push( + zod({ + files: getFiles(options, "zod"), + overrides: getOverrides(options, "zod"), + }), + ); + } + + if (enableTsdoc) { + configs.push( + tsdoc({ + files: getFiles(options, "tsdoc"), + overrides: getOverrides(options, "tsdoc"), + }), + ); + } + + if (enableReact) { + configs.push( + react({ + ...typescriptOptions, + overrides: getOverrides(options, "react"), + packageJson, + silent, + tsconfigPath, + }), + ); + } + + if (enableUnoCSS) { + configs.push( + unocss({ + ...resolveSubOptions(options, "unocss"), + overrides: getOverrides(options, "unocss"), + }), + ); + } + + if (enableAstro) { + configs.push( + astro({ + files: getFiles(options, "astro"), + overrides: getOverrides(options, "astro"), + stylistic: stylisticOptions, + }), + ); + } + + if (options.jsonc ?? true) { + configs.push( + jsonc({ + overrides: getOverrides(options, "jsonc"), + packageJson, + prettier: enablePrettier, + silent, + stylistic: stylisticOptions, + }), + ); + } + + if (options.toml ?? true) { + configs.push( + toml({ + files: getFiles(options, "toml"), + overrides: getOverrides(options, "toml"), + stylistic: stylisticOptions, + }), + ); + } + + if (options.markdown ?? true) { + configs.push( + markdown({ + componentExts: componentExtensions, + files: getFiles(options, "markdown"), + overrides: getOverrides(options, "markdown"), + }), + ); + } + + if (options.yaml ?? true) { + configs.push( + yaml({ + files: getFiles(options, "yaml"), + overrides: getOverrides(options, "yaml"), + prettier: enablePrettier, + stylistic: stylisticOptions, + }), + ); + } + + if (options.formatters) { + const isPrettierPluginXmlInScope = hasPackageJsonAnyDependency(packageJson, ["@prettier/plugin-xml"]); + + configs.push( + formatters( + { + astro: hasPackageJsonAnyDependency(packageJson, ["prettier-plugin-astro"]), + css: true, + graphql: true, + html: true, + markdown: true, + slidev: hasPackageJsonAnyDependency(packageJson, ["@slidev/cli"]), + svg: isPrettierPluginXmlInScope, + xml: isPrettierPluginXmlInScope, + + ...typeof options.formatters === "object" ? options.formatters : {}, }, - requireConfigFile: false, - }, - }, - { - env: { - commonjs: true, - }, - files: ["**/*.cjs"], - // inside *.cjs files. restore commonJS "globals" - globals: { - __dirname: true, - __filename: true, - exports: true, - require: true, - }, - }, - { - env: { - commonjs: false, - }, - files: ["**/*.mjs"], - globals: { - __dirname: "off", - __filename: "off", - exports: "off", - require: "off", - }, - }, - ], - // Disable the parser by default - parser: "", - // is loaded. - rules: { - ...configRules, - }, -}; + typeof stylisticOptions === "object" ? stylisticOptions : {}, + ), + ); + } + + // User can optionally pass a flat config item to the first argument + // We pick the known keys as ESLint would do schema validation + // eslint-disable-next-line unicorn/no-array-reduce + const fusedConfig = flatConfigProperties.reduce((accumulator, key) => { + if (key in options) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + accumulator[key] = options[key] as any; + } -export default config; + return accumulator; + }, {} as TypedFlatConfigItem); + + if (Object.keys(fusedConfig).length > 0) { + configs.push([fusedConfig]); + } + + let composer = new FlatConfigComposer(); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + composer = composer.append(...configs, ...(userConfigs as any)); + + return composer; +}; diff --git a/packages/eslint-config/src/postinstall.ts b/packages/eslint-config/src/postinstall.ts deleted file mode 100644 index 6f8387eed..000000000 --- a/packages/eslint-config/src/postinstall.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { env, exit } from "node:process"; - -import { projectPath } from "@anolilab/package-json-utils"; - -import writeEslintIgnore from "./postinstall/write-eslint-ignore"; -import writeEslintRc from "./postinstall/write-eslint-rc"; - -if (env["CI"]) { - exit(0); -} - -console.log("Configuring @anolilab/eslint-config", projectPath, "\n"); - -// eslint-disable-next-line unicorn/prefer-top-level-await -(async () => { - try { - // eslint-disable-next-line compat/compat - await Promise.all([writeEslintRc(), writeEslintIgnore()]); - - console.log("😎 Everything went well, have fun!"); - - exit(0); - } catch (error) { - console.log("😬 something went wrong:"); - console.error(error); - - exit(1); - } -})(); diff --git a/packages/eslint-config/src/postinstall/write-eslint-ignore.ts b/packages/eslint-config/src/postinstall/write-eslint-ignore.ts deleted file mode 100644 index b51642ebc..000000000 --- a/packages/eslint-config/src/postinstall/write-eslint-ignore.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { existsSync, writeFile } from "node:fs"; -import { join } from "node:path"; -import { promisify } from "node:util"; - -import { projectPath } from "@anolilab/package-json-utils"; - -const writeFileAsync = promisify(writeFile); - -const writeEslintIgnore = async (): Promise => { - const eslintIgnorePath = join(projectPath, ".eslintignore"); - - // eslint-disable-next-line security/detect-non-literal-fs-filename - if (existsSync(eslintIgnorePath)) { - console.warn("⚠️ .eslintignore already exists"); - - return; - } - - await writeFileAsync(eslintIgnorePath, "", "utf8"); -}; - -export default writeEslintIgnore; diff --git a/packages/eslint-config/src/postinstall/write-eslint-rc.ts b/packages/eslint-config/src/postinstall/write-eslint-rc.ts deleted file mode 100644 index f19870f2a..000000000 --- a/packages/eslint-config/src/postinstall/write-eslint-rc.ts +++ /dev/null @@ -1,125 +0,0 @@ -import { existsSync, readFileSync, writeFile } from "node:fs"; -import { join } from "node:path"; -import { promisify } from "node:util"; - -import { packageIsTypeModule, projectPath } from "@anolilab/package-json-utils"; -import type { TsConfigJson } from "type-fest"; - -const writeFileAsync = promisify(writeFile); - -console.log("Configuring @anolilab/eslint-config", projectPath, "\n"); - -const configFile = ".eslintrc"; - -// eslint-disable-next-line sonarjs/cognitive-complexity -const writeEslintRc = async (): Promise => { - // eslint-disable-next-line no-restricted-syntax,no-loops/no-loops - for (const filename of [configFile, `${configFile}.js`, `${configFile}.cjs`, `${configFile}.json`, `${configFile}.yaml`, `${configFile}.yml`]) { - // eslint-disable-next-line security/detect-non-literal-fs-filename - if (existsSync(join(projectPath, filename))) { - console.warn(`⚠️ ${filename} already exists; -Make sure that it includes the following for @anolilab/eslint-config' -to work as it should: { extends: ["@anolilab/eslint-config"] }.`); - - return; - } - } - - const eslintPath = join(projectPath, `.eslintrc.${packageIsTypeModule ? "c" : ""}js`); - - let pluginExtends = ""; - let parserOptions = ` - parserOptions: { - ecmaVersion: "latest", - sourceType: ${packageIsTypeModule ? '"module"' : '"commonjs"'}, - },`; - - const tsconfigPath = join(projectPath, "tsconfig.json"); - - let ecmaVersion = "latest"; - - // eslint-disable-next-line security/detect-non-literal-fs-filename - if (existsSync(tsconfigPath)) { - // eslint-disable-next-line security/detect-non-literal-fs-filename - const tsConfig = JSON.parse(readFileSync(tsconfigPath, "utf8")) as TsConfigJson; - - if (tsConfig.compilerOptions?.target) { - ecmaVersion = tsConfig.compilerOptions.target; - - ecmaVersion = - ecmaVersion.toLowerCase() === "es2022" || ecmaVersion.toLowerCase() === "esnext" ? "latest" : ecmaVersion.toLowerCase().replace("es", ""); - - if (ecmaVersion !== "latest" && ecmaVersion !== "2022" && ecmaVersion !== "2021" && ecmaVersion !== "6") { - pluginExtends = `, "plugin:es-x/restrict-to-es${ecmaVersion}"`; - } - } - - parserOptions = ` - parserOptions: { - project: true, - ecmaVersion: ${ecmaVersion === "latest" ? `"${ecmaVersion}"` : ecmaVersion}, - sourceType: ${packageIsTypeModule ? '"module"' : '"commonjs"'}, - },`; - } - - const content = `/** @ts-check */ -const { defineConfig } = require('@anolilab/eslint-config/define-config'); -${["es2015", "es2017", "es2020", "es2021", "latest"].includes(ecmaVersion) ? 'const { globals } = require("@anolilab/eslint-config/globals");' : ""} - -/// -/// -/// -/// -/// - -module.exports = defineConfig({ - root: true, - extends: ["@anolilab/eslint-config"${pluginExtends}], - ignorePatterns: ["!**/*"], - env: { - // Your environments (which contains several predefined global variables) - // Most environments are loaded automatically if our rules are added - },${parserOptions} - globals: {${ - ["es2015", "es2017", "es2020", "es2021", "latest"].includes(ecmaVersion) - ? `\n ...globals.${ecmaVersion === "latest" ? "es2021" : ecmaVersion},` - : "" -} - // Your global variables (setting to false means it's not allowed to be reassigned) - // myGlobal: false - }, - rules: { - // Customize your rules - }, - overrides: [ - { - files: [ - "**/*.ts", - "**/*.tsx", - "**/*.mts", - "**/*.cts", - "**/*.js", - "**/*.jsx", - ], - // Set parserOptions.project for the project to allow TypeScript to create the type-checker behind the scenes when we run linting - parserOptions: {}, - rules: {}, - }, - { - files: ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"], - // Set parserOptions.project for the project to allow TypeScript to create the type-checker behind the scenes when we run linting - parserOptions: {}, - rules: {}, - }, - { - files: ["**/*.js", "**/*.jsx"], - rules: {}, - }, - ], -}); -`; - - await writeFileAsync(eslintPath, content, "utf8"); -}; - -export default writeEslintRc; diff --git a/packages/eslint-config/src/reset.d.ts b/packages/eslint-config/src/reset.d.ts index e4d600ccb..c0dc8dfa5 100644 --- a/packages/eslint-config/src/reset.d.ts +++ b/packages/eslint-config/src/reset.d.ts @@ -1,2 +1,3 @@ // Do not add any other lines of code to this file! +// eslint-disable-next-line import/no-extraneous-dependencies import "@total-typescript/ts-reset"; diff --git a/packages/eslint-config/src/stub.d.ts b/packages/eslint-config/src/stub.d.ts new file mode 100644 index 000000000..49e0555be --- /dev/null +++ b/packages/eslint-config/src/stub.d.ts @@ -0,0 +1,4 @@ +declare module "eslint-plugin-react-hooks"; +declare module "eslint-plugin-react-refresh"; +declare module "eslint-plugin-you-dont-need-lodash-underscore"; +declare module "eslint-plugin-validate-jsx-nesting"; diff --git a/packages/eslint-config/src/types.d.ts b/packages/eslint-config/src/types.d.ts deleted file mode 100644 index 50343ddf9..000000000 --- a/packages/eslint-config/src/types.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type PackageRules = { - configName: string; - dependencies: string[]; - oneOfDependency?: string[]; - resolve?: string[]; - files?: string[]; -}[]; diff --git a/packages/eslint-config/src/types.ts b/packages/eslint-config/src/types.ts new file mode 100644 index 000000000..bd8ea2aca --- /dev/null +++ b/packages/eslint-config/src/types.ts @@ -0,0 +1,565 @@ +import type { StylisticCustomizeOptions } from "@stylistic/eslint-plugin"; +import type { ParserOptions } from "@typescript-eslint/parser"; +import type { NormalizedPackageJson } from "@visulima/package"; +import type { Linter } from "eslint"; +import type { FlatGitignoreOptions } from "eslint-config-flat-gitignore"; + +import type { RuleOptions } from "./typegen"; +import type { VendoredPrettierOptions } from "./vender/prettier-types"; + +export type Awaitable = Promise | T; + +export interface OptionsComponentExtensions { + /** + * Additional extensions for components. + * @example ['vue'] + * @default [] + */ + componentExts?: string[]; +} + +export type { ConfigNames } from "./typegen"; + +export interface OptionsConfig extends OptionsComponentExtensions, OptionsSilentConsoleLogs { + /** + * Override the `files` option to provide custom globs or disable some rules. + */ + antfu?: OptionsFiles & OptionsOverrides; + + /** + * Enable ASTRO support. + * + * Requires installing: + * - `eslint-plugin-astro` + * + * Requires installing for formatting .astro: + * - `prettier-plugin-astro` + * @default false + */ + astro?: boolean | (OptionsFiles & OptionsOverrides); + + /** + * Override the `files` option to provide custom globs or disable some rules. + */ + comments?: OptionsFiles & OptionsOverrides; + + /** + * Override the `files` option to provide custom globs. + */ + compat?: OptionsFiles; + + /** + * The working directory for the config. + * @default process.cwd() + */ + cwd?: string; + + /** + * Use external formatters to format files. + * + * Requires installing: + * - `eslint-plugin-format` + * + * When set to `true`, it will enable all formatters. + * @default false + */ + formatters?: OptionsFormatters | boolean; + + /** + * Enable gitignore support. + * + * Passing an object to configure the options. + * @see https://github.com/antfu/eslint-config-flat-gitignore + * @default true + */ + gitignore?: FlatGitignoreOptions | boolean; + + /** + * Enable HTML support. + * + * Requires installing: + * - `eslint-plugin-html` + * @default false + */ + html?: boolean | (OptionsFiles & OptionsOverrides); + + /** + * Override the `files` option to provide custom globs or disable some rules. + */ + imports?: OptionsFiles & OptionsOverrides; + + /** + * Control to disable some rules in editors. + * @default auto-detect based on the process.env + */ + isInEditor?: boolean; + + /** + * Core rules. Can't be disabled. + */ + javascript?: OptionsOverrides; + + /** + * Override the `files` option to provide custom globs or disable some rules. + */ + jsdoc?: OptionsFiles & OptionsOverrides; + + /** + * Enable JSONC support. + * @default true + */ + jsonc?: boolean | (OptionsFiles & OptionsOverrides); + + /** + * Enable JSX related rules. + * + * Currently only stylistic rules are included. + * @default true + */ + jsx?: boolean; + + /** + * Override the `files` option to provide custom globs or disable some rules. + */ + "jsx-a11y"?: OptionsFiles & OptionsOverrides; + + /** + * Disable some opinionated rules to Anolilab's preference. + * + * Including: + * - `antfu/top-level-function` + * - `antfu/if-newline` + * @default false + */ + lessOpinionated?: boolean; + + /** + * Enable lodash rules. + * + * Requires installing: + * - `eslint-plugin-lodash` + * @default false + */ + lodash?: boolean | (OptionsFiles & OptionsOverrides); + + /** + * Enable linting for **code snippets** in Markdown. + * + * For formatting Markdown content, enable also `formatters.markdown`. + * @default true + */ + markdown?: boolean | (OptionsFiles & OptionsOverrides); + + /** + * Override the `files` option to provide custom globs or disable some rules. + */ + node?: OptionsFiles & OptionsOverrides; + + /** + * Override some rules. + */ + noSecrets?: OptionsOverrides; + + /** + * Override some rules. + */ + noUnsanitized?: OptionsOverrides; + + /** + * Provide overrides for rules for each integration. + * @deprecated use `overrides` option in each integration key instead + */ + overrides?: { + html?: TypedFlatConfigItem["rules"]; + javascript?: TypedFlatConfigItem["rules"]; + jsonc?: TypedFlatConfigItem["rules"]; + "jsx-a11y"?: TypedFlatConfigItem["rules"]; + lodash?: TypedFlatConfigItem["rules"]; + markdown?: TypedFlatConfigItem["rules"]; + node?: TypedFlatConfigItem["rules"]; + noSecrets?: TypedFlatConfigItem["rules"]; + noUnsanitized?: TypedFlatConfigItem["rules"]; + perfectionist?: TypedFlatConfigItem["rules"]; + playwright?: TypedFlatConfigItem["rules"]; + promise?: TypedFlatConfigItem["rules"]; + react?: TypedFlatConfigItem["rules"]; + simpleImportSort?: TypedFlatConfigItem["rules"]; + sonarjs?: TypedFlatConfigItem["rules"]; + storybook?: TypedFlatConfigItem["rules"]; + stylistic?: TypedFlatConfigItem["rules"]; + svelte?: TypedFlatConfigItem["rules"]; + test?: TypedFlatConfigItem["rules"]; + toml?: TypedFlatConfigItem["rules"]; + typescript?: TypedFlatConfigItem["rules"]; + unocss?: TypedFlatConfigItem["rules"]; + validateJsxNesting?: TypedFlatConfigItem["rules"]; + yaml?: TypedFlatConfigItem["rules"]; + }; + + /** + * Override the `files` option to provide custom globs or disable some rules. + */ + perfectionist?: OptionsFiles & OptionsOverrides; + + /** + * Enable playwright rules. + * + * Requires installing: + * - `eslint-plugin-playwright` + * @default false + */ + playwright?: boolean | (OptionsFiles & OptionsOverrides); + + /** + * Override the `files` option to provide custom globs or disable some rules. + */ + promise?: OptionsFiles & OptionsOverrides; + + /** + * Enable react rules. + * + * Requires installing: + * - `@eslint-react/eslint-plugin` + * - `eslint-plugin-react-hooks` + * - `eslint-plugin-react-refresh` + * @default false + */ + react?: boolean | (OptionsFiles & OptionsOverrides); + + /** + * Enable regexp rules. + * @see https://ota-meshi.github.io/eslint-plugin-regexp/ + * @default true + */ + regexp?: boolean | (OptionsFiles & OptionsOverrides & OptionsRegExp); + + /** + * Override the `files` option to provide custom globs or disable some rules. + */ + simpleImportSort?: OptionsFiles & OptionsOverrides; + + /** + * Override the `files` option to provide custom globs or disable some rules. + */ + sonarjs?: OptionsFiles & OptionsOverrides; + + /** + * Enable Storybook rules. + * + * Requires installing: + * - `eslint-plugin-storybook` + * @default false + */ + storybook?: boolean | (OptionsFiles & OptionsOverrides); + + /** + * Enable stylistic rules. + * @see https://eslint.style/ + * @default true + */ + stylistic?: boolean | (OptionsFiles & OptionsOverrides & StylisticConfig); + + /** + * Enable tailwindcss support. + * + * Requires installing: + * - `eslint-plugin-tailwindcss` + * @default false + */ + tailwindcss?: boolean | (OptionsFiles & OptionsOverrides); + + /** + * Enable tanstack rules. + * + * Requires installing: + * - `@tanstack/eslint-plugin-query` + * @default false + */ + tanstackQuery?: boolean | (OptionsFiles & OptionsOverrides); + + /** + * Enable tanstack rules. + * + * Requires installing: + * - `@tanstack/eslint-plugin-router` + * @default false + */ + tanstackRouter?: boolean | (OptionsFiles & OptionsOverrides); + + /** + * Enable testing-library rules. + * + * Requires installing: + * - `eslint-plugin-testing-library` + * @default false + */ + testingLibrary?: boolean | (OptionsFiles & OptionsOverrides & OptionsPackageJson); + + /** + * Enable TOML support. + * @default true + */ + toml?: boolean | (OptionsFiles & OptionsOverrides); + + /** + * Enable tsdoc rules. + * + * Requires installing: + * - `eslint-plugin-tsdoc` + * @default false + */ + tsdoc?: boolean | (OptionsFiles & OptionsOverrides); + + /** + * Enable TypeScript support. + * + * Passing an object to enable TypeScript Language Server support. + * @default auto-detect based on the dependencies + */ + typescript?: OptionsTypescript | boolean; + + /** + * Options for eslint-plugin-unicorn. + * @default true + */ + unicorn?: OptionsUnicorn | boolean; + + /** + * Enable unocss rules. + * + * Requires installing: + * - `@unocss/eslint-plugin` + * @default false + */ + unocss?: OptionsUnoCSS | boolean; + + /** + * Override the `files` option to provide custom globs or disable some rules. + */ + validateJsxNesting?: OptionsFiles & OptionsOverrides; + + /** + * Enable vitest support. + * @default true + */ + vitest?: boolean | (OptionsFiles & OptionsOverrides); + + /** + * Enable YAML support. + * @default true + */ + yaml?: boolean | (OptionsFiles & OptionsOverrides); + + /** + * Enable Zod rules. + * + * Requires installing: + * - `eslint-plugin-zod` + * @default false + */ + zod?: boolean | (OptionsFiles & OptionsOverrides); +} + +export interface OptionsCwd { + /** + * The working directory for the config. + * @default process.cwd() + */ + cwd: string; +} + +export interface OptionsFiles { + /** + * Override the `files` option to provide custom globs. + */ + files?: string[]; +} + +export interface OptionsFormatters { + /** + * Enable formatting support for Astro. + * + * Currently only support Prettier. + */ + astro?: boolean | "prettier"; + + /** + * Enable formatting support for CSS, Less, Sass, and SCSS. + * + * Currently only support Prettier. + */ + css?: boolean | "prettier"; + + /** + * Custom options for dprint. + * + * By default it's controlled by our own config. + */ + dprintOptions?: { + indent?: "space" | "tab"; + indentWidth?: number; + quoteStyle?: "preferDouble" | "preferSingle"; + useTabs?: boolean; + }; + + /** + * Enable formatting support for GraphQL. + */ + graphql?: boolean | "prettier"; + + /** + * Enable formatting support for HTML. + * + * Currently only support Prettier. + */ + html?: boolean | "prettier"; + + /** + * Enable formatting support for Markdown. + * + * Support both Prettier and dprint. + * + * When set to `true`, it will use Prettier. + */ + markdown?: boolean | "dprint" | "prettier"; + + /** + * Custom options for Prettier. + * + * By default it's controlled by our own config. + */ + prettierOptions?: VendoredPrettierOptions; + + /** + * Install the prettier plugin for handle Slidev markdown + * + * Only works when `markdown` is enabled with `prettier`. + */ + slidev?: + | boolean + | { + files?: string[]; + }; + + /** + * Enable formatting support for SVG. + * + * Currently only support Prettier. + */ + svg?: boolean | "prettier"; + + /** + * Enable formatting support for XML. + * + * Currently only support Prettier. + */ + xml?: boolean | "prettier"; +} + +export interface OptionsHasPrettier { + prettier?: boolean; +} + +export interface OptionsIsInEditor { + isInEditor?: boolean; +} + +export interface OptionsOverrides { + overrides?: TypedFlatConfigItem["rules"]; +} + +export interface OptionsPackageJson { + /** + * The package.json object + */ + packageJson: NormalizedPackageJson; +} + +export interface OptionsRegExp { + /** + * Override rulelevels + */ + level?: "error" | "warn"; +} + +export interface OptionsSilentConsoleLogs { + /** + * This option is used to enable or disable to console log information. + */ + silent?: boolean; +} + +export interface OptionsStylistic { + stylistic?: StylisticConfig | boolean; +} + +export type OptionsTypescript = (OptionsOverrides & OptionsTypeScriptParserOptions) | (OptionsOverrides & OptionsTypeScriptWithTypes); + +export interface OptionsTypeScriptParserOptions { + /** + * Glob patterns for files that should be type aware. + * @default ['**\/*.{ts,tsx}'] + */ + filesTypeAware?: string[]; + + /** + * Glob patterns for files that should not be type aware. + * @default ['**\/*.md\/**', '**\/*.astro/*.ts'] + */ + ignoresTypeAware?: string[]; + + /** + * Additional parser options for TypeScript. + */ + parserOptions?: Partial; +} + +export interface OptionsTypeScriptWithTypes { + /** + * Override type aware rules. + */ + overridesTypeAware?: TypedFlatConfigItem["rules"]; + + /** + * When this options is provided, type aware rules will be enabled. + * @see https://typescript-eslint.io/linting/typed-linting/ + */ + tsconfigPath?: string; +} + +export interface OptionsUnicorn { + /** + * Include all rules recommended by `eslint-plugin-unicorn`, instead of only ones picked by Anthony. + * @default false + */ + allRecommended?: boolean; +} + +export interface OptionsUnoCSS extends OptionsOverrides { + /** + * Enable attributify support. + * @default true + */ + attributify?: boolean; + /** + * Enable strict mode by throwing errors about blocklisted classes. + * @default false + */ + strict?: boolean; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface Rules extends RuleOptions {} + +export type StylisticConfig = Pick; + +export type TypedFlatConfigItem = Omit, "plugins"> & { + // Relax plugins type limitation, as most of the plugins did not have correct type info yet. + /** + * An object containing a name-value mapping of plugin names to plugin objects. + * When `files` is specified, these plugins are only available to the matching files. + * @see [Using plugins in your configuration](https://eslint.org/docs/latest/user-guide/configuring/configuration-files-new#using-plugins-in-your-configuration) + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + plugins?: Record; +}; diff --git a/packages/eslint-config/src/typescript-type-checking.ts b/packages/eslint-config/src/typescript-type-checking.ts deleted file mode 100644 index 3a346a6dd..000000000 --- a/packages/eslint-config/src/typescript-type-checking.ts +++ /dev/null @@ -1,201 +0,0 @@ -import { env } from "node:process"; - -import type { Linter } from "eslint"; - -import bestPracticesConfig from "./config/best-practices"; -import { createConfig } from "./utils/create-config"; -import anolilabEslintConfig from "./utils/eslint-config"; - -// @ts-expect-error TODO: find the correct type -const bestPracticesRules = bestPracticesConfig.overrides[0].rules as Linter.RulesRecord; - -let showUnsupportedTypeScriptVersionWarning: boolean = env["DISABLE_ESLINT_WARN_UNSUPPORTED_TYPESCRIPT_VERSION"] !== "true"; - -if (anolilabEslintConfig["warn_on_unsupported_typescript_version"] !== undefined) { - showUnsupportedTypeScriptVersionWarning = anolilabEslintConfig["warn_on_unsupported_typescript_version"] as boolean; -} - -const config = createConfig("typescript", { - extends: [ - "plugin:@typescript-eslint/recommended-type-checked", - "plugin:@typescript-eslint/strict-type-checked", - "plugin:@typescript-eslint/stylistic-type-checked", - ], - parser: "@typescript-eslint/parser", - parserOptions: { - ecmaFeatures: { - jsx: true, - }, - sourceType: "module", - warnOnUnsupportedTypeScriptVersion: showUnsupportedTypeScriptVersionWarning, - }, - plugins: ["@typescript-eslint"], - rules: { - // Disallows awaiting a value that is not a Thenable - "@typescript-eslint/await-thenable": "error", - - "@typescript-eslint/consistent-type-exports": [ - "error", - { - fixMixedExportsWithInlineTypeSpecifier: true, - }, - ], - - // Replace 'dot-notation' rule with '@typescript-eslint' version - "@typescript-eslint/dot-notation": ["error", { allowKeywords: true }], - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-base-to-string.md - "@typescript-eslint/no-base-to-string": [ - "error", - { - ignoredTypeNames: ["RegExp"], - }, - ], - - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-confusing-void-expression.md - "@typescript-eslint/no-confusing-void-expression": [ - "error", - { - ignoreArrowShorthand: true, - ignoreVoidOperator: false, - }, - ], - - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-floating-promises.md - "@typescript-eslint/no-floating-promises": [ - "error", - { - ignoreIIFE: true, - ignoreVoid: true, - }, - ], - - // Disallow iterating over an array with a for-in loop - "@typescript-eslint/no-for-in-array": "error", - - "@typescript-eslint/no-implied-eval": bestPracticesRules["no-implied-eval"], - - // Replace 'no-throw-literal' rule with '@typescript-eslint' version - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-meaningless-void-operator.md - "@typescript-eslint/no-meaningless-void-operator": ["error", { checkNever: true }], - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-misused-promises.md - "@typescript-eslint/no-misused-promises": [ - "error", - { - checksConditionals: true, - checksVoidReturn: { - arguments: true, - attributes: false, - properties: true, - returns: true, - variables: true, - }, - }, - ], - - // Replace 'no-implied-eval' and 'no-new-func' rules with '@typescript-eslint' version - "@typescript-eslint/no-throw-literal": bestPracticesRules["no-throw-literal"], - - // Force array predicates to return something that could be either truthy or falsy. - // See https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unnecessary-condition.md - // See also https://github.com/typescript-eslint/typescript-eslint/issues/1038 - // See also https://github.com/microsoft/TypeScript/issues/19456 - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-unnecessary-condition.md - "@typescript-eslint/no-unnecessary-condition": "error", - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-unnecessary-qualifier.md - "@typescript-eslint/no-unnecessary-qualifier": "error", - - // Disallow the void operator except when used to discard a value. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/no-unnecessary-type-arguments.md - "@typescript-eslint/no-unnecessary-type-arguments": "error", - - // Disabling here because in most cases the explicitness is still valuable - "@typescript-eslint/no-unnecessary-type-assertion": "off", - - // Warns if a type assertion does not change the type of an expression - // See https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/non-nullable-type-assertion-style.md - "@typescript-eslint/non-nullable-type-assertion-style": "off", - - // See https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/docs/rules/method-signature-style.md - "@typescript-eslint/method-signature-style": "error", - - // Enforce includes method over indexOf method. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/prefer-includes.md - "@typescript-eslint/prefer-includes": "error", - - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md - "@typescript-eslint/prefer-nullish-coalescing": [ - "error", - { - ignoreConditionalTests: true, - ignoreMixedLogicalExpressions: true, - }, - ], - - // Enforce RegExp#exec over String#match if no global flag is provided. - // Requires that private members are marked as readonly if they're never modified outside of the constructor - "@typescript-eslint/prefer-readonly": ["error", { onlyInlineLambdas: false }], - - // Enforce that this is used when only this type is returned. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/prefer-reduce-type-parameter.md - "@typescript-eslint/prefer-reduce-type-parameter": "error", - - // Enforce using String#startsWith and String#endsWith over other equivalent methods of checking substrings. - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/prefer-regexp-exec.md - "@typescript-eslint/prefer-regexp-exec": "error", - - // Replace 'require-await' rule with '@typescript-eslint' version - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/prefer-return-this-type.md - "@typescript-eslint/prefer-return-this-type": "error", - // https://github.com/typescript-eslint/typescript-eslint/tree/main/packages/eslint-plugin/docs/rules/prefer-string-starts-ends-with.md - "@typescript-eslint/prefer-string-starts-ends-with": "error", - - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/promise-function-async.md - "@typescript-eslint/promise-function-async": "error", - - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/require-array-sort-compare.md - "@typescript-eslint/require-array-sort-compare": [ - "error", - { - ignoreStringArrays: false, - }, - ], - - "@typescript-eslint/require-await": bestPracticesRules["require-await"], - - // When adding two variables, operands must both be of type number or of type string - "@typescript-eslint/restrict-plus-operands": "error", - - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/return-await.md - "@typescript-eslint/return-await": ["error", "always"], - - // Disallow unnecessary namespace qualifiers. - // https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/switch-exhaustiveness-check.md - "@typescript-eslint/switch-exhaustiveness-check": "error", - - // Enforces unbound methods are called with their expected scope - "@typescript-eslint/unbound-method": ["error", { ignoreStatic: false }], - - // TODO: enable this rule when decision is made on - "@typescript-eslint/consistent-type-assertions": [ - "off", - { - assertionStyle: "never", - }, - ], - - // TODO: enable this rule when decision is made on - "@typescript-eslint/strict-boolean-expressions": [ - "off", - { - allowString: false, - allowNumber: false, - allowNullableObject: false, - }, - ], - - // Interfaces encourage OO, types encourage FP. - "@typescript-eslint/consistent-type-definitions": "off", - }, -}); - -export default config; diff --git a/packages/eslint-config/src/utils/combine.ts b/packages/eslint-config/src/utils/combine.ts new file mode 100644 index 000000000..677e24935 --- /dev/null +++ b/packages/eslint-config/src/utils/combine.ts @@ -0,0 +1,14 @@ +import type { Awaitable, TypedFlatConfigItem } from "../types"; + +/** + * Combine array and non-array configs into a single array. + * @param {TypedFlatConfigItem | TypedFlatConfigItem[]} configs + * @returns {Promise} + */ +const combine = async (...configs: Awaitable[]): Promise => { + const resolved = await Promise.all(configs); + + return resolved.flat(); +}; + +export default combine; diff --git a/packages/eslint-config/src/utils/create-config.ts b/packages/eslint-config/src/utils/create-config.ts index 57bda539b..39948b2b5 100644 --- a/packages/eslint-config/src/utils/create-config.ts +++ b/packages/eslint-config/src/utils/create-config.ts @@ -1,134 +1,129 @@ -import type { Linter } from "eslint"; +import type { TypedFlatConfigItem } from "../types"; type FileType = | "all" - | "ava" + | "astro_ts" + | "astro" + | "css" | "d.ts" - | "javascript" - | "jest" + | "e2e" + | "graphql" + | "html" | "js_and_ts" + | "js" | "jsx_and_tsx" + | "less" + | "markdown_in_markdown" | "markdown_inline_js_jsx" | "markdown" - | "mdx" | "postcss" - | "tests" - | "typescript" - | "vitest"; + | "scss" + | "storybook" + | "svg" + | "toml" + | "ts" + | "vitest" + | "xml" + | "yaml"; -const getType = (fileType: FileType) => { +// @see https://devblogs.microsoft.com/typescript/announcing-typescript-4-5-beta/#new-file-extensions +const dtsGlobal = ["**/*.d.ts", "**/*.d.cts", "**/*.d.mts"]; +const tsGlobal = ["**/*.ts", "**/*.cts", "**/*.mts"]; +const tsxGlobal = ["**/*.tsx", "**/*.mtsx", "**/*.ctsx"]; + +const jsGlobal = ["**/*.js", "**/*.mjs", "**/*.cjs"]; +const jsxGlobal = ["**/*.jsx", "**/*.mjsx", "**/*.cjsx"]; + +export const getFilesGlobs = (fileType: FileType): string[] => { switch (fileType) { - case "typescript": { - // @see https://devblogs.microsoft.com/typescript/announcing-typescript-4-5-beta/#new-file-extensions - return ["**/*.ts", "**/*.d.ts", "**/*.tsx", "**/*.mts", "**/*.cts"]; + case "all": { + return [...jsGlobal, ...dtsGlobal, ...tsGlobal, ...tsxGlobal, ...jsxGlobal]; } - case "jsx_and_tsx": { - return ["**/*.jsx", "**/*.tsx"]; + case "astro": { + return ["**/*.astro"]; + } + case "astro_ts": { + return ["**/*.astro/*.ts"]; + } + case "css": { + return ["**/*.css"]; + } + case "d.ts": { + return dtsGlobal; + } + case "e2e": { + return ["**/e2e/**/*.test.{js,ts,jsx,tsx}"]; + } + case "graphql": { + return ["**/*.{g,graph}ql"]; + } + case "html": { + return [ + "**/*.erb", + "**/*.handlebars", + "**/*.hbs", + "**/*.htm", + "**/*.html", + "**/*.mustache", + "**/*.nunjucks", + "**/*.php", + "**/*.tag", + "**/*.twig", + "**/*.we", + ]; + } + case "js": { + return jsGlobal; } case "js_and_ts": { - return ["**/*.js", "**/*.mjs", "**/*.cjs", "**/*.ts", "**/*.d.ts", "**/*.mts", "**/*.cts"]; + return [...jsGlobal, ...tsGlobal]; } - case "javascript": { - return ["**/*.js", "**/*.mjs", "**/*.cjs"]; + case "jsx_and_tsx": { + return [...jsxGlobal, ...tsxGlobal]; } - case "all": { - return ["**/*.js", "**/*.jsx", "**/*.mjs", "**/*.cjs", "**/*.ts", "**/*.d.ts", "**/*.tsx", "**/*.mts", "**/*.cts"]; + case "less": { + return ["**/*.less"]; } case "markdown": { return ["**/*.{md,mkdn,mdown,markdown}"]; } + case "markdown_in_markdown": { + return ["**/*.{md,mkdn,mdown,markdown}/*.{md,mkdn,mdown,markdown}"]; + } case "markdown_inline_js_jsx": { return ["**/*.{md,mkdn,mdown,markdown}/*.{js,javascript,jsx,node,json}"]; } - case "mdx": { - return ["**/*.mdx"]; + case "postcss": { + return ["**/postcss.config.js", "**/postcssrc.js", "**/postcss.config.cjs", "**/postcssrc.cjs"]; + } + case "scss": { + return ["**/*.scss"]; } - case "jest": { - return [ - // Test files - "**/*.spec.{js,ts,tsx}", - "**/*.test.{js,ts,tsx}", - "**/test/*.{js,ts,tsx}", - - // Facebook convention - "**/__mocks__/*.{js,ts,tsx}", - "**/__tests__/*.{js,ts,tsx}", - ]; + case "storybook": { + return ["**/*.stories.@(ts|tsx|js|jsx|mjs|cjs)", "**/*.story.@(ts|tsx|js|jsx|mjs|cjs)"]; } - case "ava": { - return [ - "test.js", - "src/test.js", - "source/test.js", - "**/test-*.js", - "**/*.spec.js", - "**/*.test.js", - "**/test/**/*.js", - "**/tests/**/*.js", - "**/__tests__/**/*.js", - ]; + case "svg": { + return ["**/*.svg"]; + } + case "toml": { + return ["**/*.toml"]; + } + case "ts": { + return [...tsGlobal, ...dtsGlobal, ...tsxGlobal]; } case "vitest": { return ["**/__tests__/**/*.?(c|m)[jt]s?(x)", "**/?(*.){test,spec}.?(c|m)[jt]s?(x)"]; } - case "tests": { - return [ - // ava - "test.js", - "src/test.js", - "source/test.js", - "**/test-*.js", - "**/*.spec.js", - "**/*.test.js", - "**/test/**/*.js", - "**/tests/**/*.js", - "**/__tests__/**/*.js", - - // jest - "**/*.spec.{js,ts,tsx}", - "**/*.test.{js,ts,tsx}", - "**/test/*.{js,ts,tsx}", - "**/__mocks__/*.{js,ts,tsx}", - "**/__tests__/*.{js,ts,tsx}", - "**/__tests__/**/*.?(c|m)[jt]s?(x)", - "**/?(*.){test,spec}.?(c|m)[jt]s?(x)", - ]; + case "xml": { + return ["**/*.xml"]; } - case "d.ts": { - return ["**/*.d.ts"]; - } - case "postcss": { - return ["**/postcss.config.js", "**/postcssrc.js", "**/postcss.config.cjs", "**/postcssrc.cjs"]; + case "yaml": { + return ["**/*.yaml", "**/*.yml"]; } default: { - throw new Error("Unknown type"); + throw new Error(`Unknown file type: ${fileType}`); } } }; -export const createConfig = (type: FileType, config: Omit, environment?: Record): Linter.Config => { - return { - env: environment, - overrides: [ - { - files: getType(type), - ...config, - }, - ], - }; -}; -export const createConfigs = ( - rules: { - config: Omit; - type: FileType; - }[], -): Linter.Config => { - return { - overrides: rules.map(({ config, type }) => { - return { - files: getType(type), - ...config, - }; - }), - }; -}; +export const createConfig = (type: FileType, rules: (options: O, files: string[]) => Promise[]>) => async (options: O): Promise => await rules(options, getFilesGlobs(type)); diff --git a/packages/eslint-config/src/utils/eslint-config.ts b/packages/eslint-config/src/utils/eslint-config.ts deleted file mode 100644 index e1e6ae8af..000000000 --- a/packages/eslint-config/src/utils/eslint-config.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { pkg } from "@anolilab/package-json-utils"; - -if (!global.anolilabEslintPackageJsonConfig && pkg) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment,@typescript-eslint/no-unsafe-member-access - global.anolilabEslintPackageJsonConfig = pkg["anolilab"]?.["eslint-config"]; -} - -const config: Record = global.anolilabEslintPackageJsonConfig ?? {}; - -export default config; diff --git a/packages/eslint-config/src/utils/indent.ts b/packages/eslint-config/src/utils/indent.ts deleted file mode 100644 index cf1e48e54..000000000 --- a/packages/eslint-config/src/utils/indent.ts +++ /dev/null @@ -1,15 +0,0 @@ -import anolilabEslintConfig from "./eslint-config"; - -if (global.anolilabEslintIndent === undefined && anolilabEslintConfig["indent"]) { - if (Number.isNaN(anolilabEslintConfig["indent"])) { - throw new TypeError("Indent must be a number"); - } - - global.anolilabEslintIndent = Number(anolilabEslintConfig["indent"]); -} else { - global.anolilabEslintIndent = 4; -} - -const indent = global.anolilabEslintIndent; - -export default indent; diff --git a/packages/eslint-config/src/utils/interop-default.ts b/packages/eslint-config/src/utils/interop-default.ts new file mode 100644 index 000000000..164668f49 --- /dev/null +++ b/packages/eslint-config/src/utils/interop-default.ts @@ -0,0 +1,10 @@ +import type { Awaitable } from "../types"; + +const interopDefault = async (m: Awaitable): Promise => { + const resolved = await m; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (resolved as any).default || resolved; +}; + +export default interopDefault; diff --git a/packages/eslint-config/src/utils/is-in-editor-environment.ts b/packages/eslint-config/src/utils/is-in-editor-environment.ts new file mode 100644 index 000000000..7c8f80915 --- /dev/null +++ b/packages/eslint-config/src/utils/is-in-editor-environment.ts @@ -0,0 +1,15 @@ +import isInGitHooksOrLintStaged from "./is-in-git-hooks-or-lint-staged"; + +const isInEditorEnvironment = (): boolean => { + if (process.env["CI"]) { + return false; + } + + if (isInGitHooksOrLintStaged()) { + return false; + } + + return !!(process.env["VSCODE_PID"] ?? process.env["VSCODE_CWD"] ?? process.env["JETBRAINS_IDE"] ?? process.env["VIM"] ?? process.env["NVIM"]); +}; + +export default isInEditorEnvironment; diff --git a/packages/eslint-config/src/utils/is-in-editor.ts b/packages/eslint-config/src/utils/is-in-editor.ts deleted file mode 100644 index 9d05ddae4..000000000 --- a/packages/eslint-config/src/utils/is-in-editor.ts +++ /dev/null @@ -1,3 +0,0 @@ -const isInEditor = (process.env["VSCODE_PID"] || process.env["JETBRAINS_IDE"]) && !process.env["CI"]; - -export default isInEditor; diff --git a/packages/eslint-config/src/utils/is-in-git-hooks-or-lint-staged.ts b/packages/eslint-config/src/utils/is-in-git-hooks-or-lint-staged.ts new file mode 100644 index 000000000..7b00ea42a --- /dev/null +++ b/packages/eslint-config/src/utils/is-in-git-hooks-or-lint-staged.ts @@ -0,0 +1,3 @@ +const isInGitHooksOrLintStaged = (): boolean => !!(process.env["GIT_PARAMS"] || process.env["VSCODE_GIT_COMMAND"] || process.env["npm_lifecycle_script"]?.startsWith("lint-staged")); + +export default isInGitHooksOrLintStaged; diff --git a/packages/eslint-config/src/utils/loggers.ts b/packages/eslint-config/src/utils/loggers.ts deleted file mode 100644 index ef92d8f26..000000000 --- a/packages/eslint-config/src/utils/loggers.ts +++ /dev/null @@ -1,6 +0,0 @@ -const noop = () => undefined; - -const consolePrefix = (prefix: string) => (process.env["NO_LOGS"] ? noop : (message: string) => console.log(`${prefix}${message}`)); - -export const consolePlugin = consolePrefix(" eslint-plugin-"); -export const consoleLog = consolePrefix(""); diff --git a/packages/eslint-config/src/utils/parser-plain.ts b/packages/eslint-config/src/utils/parser-plain.ts new file mode 100644 index 000000000..c914bcd0a --- /dev/null +++ b/packages/eslint-config/src/utils/parser-plain.ts @@ -0,0 +1,26 @@ +import type { Linter } from "eslint"; + +const parserPlain: Linter.Parser = { + meta: { + name: "parser-plain", + }, + parseForESLint: (code: string) => { + return { + ast: { + body: [], + comments: [], + loc: { end: code.length, start: 0 }, + range: [0, code.length], + tokens: [], + type: "Program", + }, + scopeManager: undefined, + services: { isPlain: true }, + visitorKeys: { + Program: [], + }, + }; + }, +}; + +export default parserPlain; diff --git a/packages/eslint-config/src/vender/prettier-types.ts b/packages/eslint-config/src/vender/prettier-types.ts new file mode 100644 index 000000000..9097f707b --- /dev/null +++ b/packages/eslint-config/src/vender/prettier-types.ts @@ -0,0 +1,164 @@ +/** + * Vendor types from Prettier so we don't rely on the dependency. + */ + +export type BuiltInParserName = + | "acorn" + | "angular" + | "babel-flow" + | "babel-ts" + | "babel" + | "css" + | "espree" + | "flow" + | "glimmer" + | "graphql" + | "html" + | "json-stringify" + | "json" + | "json5" + | "less" + | "lwc" + | "markdown" + | "mdx" + | "meriyah" + | "scss" + | "typescript" + | "vue" + | "xml" + | "yaml"; + +export type ExternalParserName = "astro" | "slidev"; + +// This utility is here to handle the case where you have an explicit union +// between string literals and the generic string type. It would normally +// resolve out to just the string type, but this generic LiteralUnion maintains +// the intellisense of the original union. +// +// It comes from this issue: microsoft/TypeScript#29729: +// https://github.com/microsoft/TypeScript/issues/29729#issuecomment-700527227 +export type LiteralUnion = T | (Pick & { _?: never | undefined }); + +export type VendoredPrettierOptions = Partial; + +export interface VendoredPrettierOptionsRequired { + /** + * Include parentheses around a sole arrow function parameter. + * @default "always" + */ + arrowParens: "always" | "avoid"; + /** + * Put the `>` of a multi-line HTML (HTML, XML, JSX, Vue, Angular) element at the end of the last line instead of being + * alone on the next line (does not apply to self closing elements). + */ + bracketSameLine: boolean; + /** + * Print spaces between brackets in object literals. + */ + bracketSpacing: boolean; + /** + * Which end of line characters to apply. + * @default "lf" + */ + endOfLine: "auto" | "cr" | "crlf" | "lf"; + /** + * How to handle whitespaces in HTML. + * @default "css" + */ + htmlWhitespaceSensitivity: "css" | "ignore" | "strict"; + /** + * Put the `>` of a multi-line JSX element at the end of the last line instead of being alone on the next line. + * @deprecated use bracketSameLine instead + */ + jsxBracketSameLine: boolean; + /** + * Use single quotes in JSX. + */ + jsxSingleQuote: boolean; + /** + * Provide ability to support new languages to prettier. + */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + plugins: (any | string)[]; + /** + * Specify the line length that the printer will wrap on. + * @default 120 + */ + printWidth: number; + /** + * By default, Prettier will wrap markdown text as-is since some services use a linebreak-sensitive renderer. + * In some cases you may want to rely on editor/viewer soft wrapping instead, so this option allows you to opt out. + * @default "preserve" + */ + proseWrap: "always" | "never" | "preserve"; + /** + * Change when properties in objects are quoted. + * @default "as-needed" + */ + quoteProps: "as-needed" | "consistent" | "preserve"; + /** + * Format only a segment of a file. + * @default Number.POSITIVE_INFINITY + */ + rangeEnd: number; + /** + * Format only a segment of a file. + */ + rangeStart: number; + /** + * Print semicolons at the ends of statements. + */ + semi: boolean; + /** + * Enforce single attribute per line in HTML, XML, Vue and JSX. + * @default false + */ + singleAttributePerLine: boolean; + /** + * Use single quotes instead of double quotes. + */ + singleQuote: boolean; + /** + * Specify the number of spaces per indentation-level. + */ + tabWidth: number; + /** + * Print trailing commas wherever possible. + */ + trailingComma: "all" | "es5" | "none"; + /** + * Indent lines with tabs instead of spaces + */ + useTabs?: boolean; + /** + * Whether or not to indent the code inside