mirror of
https://github.com/scratchfoundation/eslint-config-scratch.git
synced 2025-06-12 05:12:34 -04:00
refactor!: simplify and flatten configurations
BREAKING CHANGE: Configurations are now returned as objects, like most shared ESLint configurations. The `make*Config` functions are no more. Also, everything is now exported through one file.
This commit is contained in:
parent
329b4de61c
commit
a7dda101f2
8 changed files with 316 additions and 400 deletions
201
README.md
201
README.md
|
@ -12,26 +12,40 @@ Install the config along with its peer dependencies, `eslint` and `prettier`:
|
|||
npm install -D eslint-config-scratch eslint@^9 prettier@^3
|
||||
```
|
||||
|
||||
Add `eslint.config.mjs` to your project root (pick the `export` line appropriate for your project):
|
||||
Add `eslint.config.mjs` to your project root and, optionally, configure parser options for rules that require type
|
||||
information:
|
||||
|
||||
```js
|
||||
// myProjectRoot/eslint.config.mjs
|
||||
import { makeEslintConfig } from 'eslint-config-scratch'
|
||||
|
||||
// for a TypeScript project:
|
||||
export default makeEslintConfig({ globals: 'browser', tsconfigRootDir: import.meta.dirname })
|
||||
import { eslintConfigScratch } from 'eslint-config-scratch'
|
||||
|
||||
// for plain JavaScript:
|
||||
export default makeEslintConfig({ globals: 'browser' })
|
||||
export default eslintConfigScratch.recommended
|
||||
|
||||
// for a TypeScript project:
|
||||
export default eslintConfigScratch.config(
|
||||
eslintConfigScratch.recommended,
|
||||
{
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
The function `eslintConfigScratch.config` is a re-export of the `config` function from `typescript-eslint`, and helps
|
||||
with merging and extending configurations.
|
||||
|
||||
Add `prettier.config.mjs` to your project root as well:
|
||||
|
||||
```js
|
||||
// myProjectRoot/prettier.config.mjs
|
||||
import { makePrettierConfig } from 'eslint-config-scratch'
|
||||
import { prettierConfigScratch } from 'eslint-config-scratch'
|
||||
|
||||
export default makePrettierConfig()
|
||||
export default prettierConfigScratch.recommended
|
||||
```
|
||||
|
||||
Finally, add scripts like these to your `package.json`:
|
||||
|
@ -45,147 +59,88 @@ Finally, add scripts like these to your `package.json`:
|
|||
|
||||
## Basic Configuration
|
||||
|
||||
The `makeEslintConfig` function takes options to adjust the ESLint configuration object for your project. Most
|
||||
projects should start with something like this:
|
||||
The `eslintConfigScratch.config` is a re-export of the `config` function from `typescript-eslint`. Full documentation
|
||||
is available here: <https://typescript-eslint.io/packages/typescript-eslint#config>.
|
||||
|
||||
```mjs
|
||||
The `config` function can be used to add or override rules, plugins, and other configuration options. For example:
|
||||
|
||||
```js
|
||||
// myProjectRoot/eslint.config.mjs
|
||||
import { makeEslintConfig } from 'eslint-config-scratch'
|
||||
|
||||
export default makeEslintConfig({
|
||||
// Optional: specify global variables available in your environment
|
||||
globals: 'browser',
|
||||
|
||||
// Optional: enables rules that use type info, some of which work in JS too
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
})
|
||||
```
|
||||
|
||||
If you have no `tsconfig.json` (or `jsconfig.json`) in your project, you can skip the `tsconfigRootDir` option. Rules
|
||||
that require type information will be disabled or replaced with less strict alternatives that work without type info.
|
||||
|
||||
### Globals
|
||||
|
||||
The `globals` property is optional. If present, it can take several forms:
|
||||
|
||||
- a string, interpreted as a key in the `globals` object exported by the `globals` package.
|
||||
- Examples: `'browser'`, `'node'`, `'es2021'`, `'jest'`, etc.
|
||||
- an object, set up as described in the "Specifying Globals" section of the [ESLint documentation](https://eslint.org/docs/latest/use/configure/language-options#using-configuration-files)
|
||||
- Example: `{ myGlobal: 'readonly', anotherGlobal: 'writable' }`
|
||||
- an array of zero or more of any mixture of the above
|
||||
|
||||
```mjs
|
||||
// myProjectRoot/eslint.config.mjs
|
||||
import { makeEslintConfig } from 'eslint-config-scratch'
|
||||
|
||||
export default makeEslintConfig({
|
||||
// Optional: enables rules that use type info, some of which work in JS too
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
|
||||
// Optional: specify global variables available in your environment
|
||||
// Warning: this is a very silly configuration
|
||||
globals: [
|
||||
'shared-node-browser',
|
||||
{
|
||||
fun: 'readonly',
|
||||
thing: false,
|
||||
},
|
||||
'es2021',
|
||||
{
|
||||
whyNot: 'writable',
|
||||
},
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
### Further Customization
|
||||
|
||||
The first parameter to `makeEslintConfig` is covered above. Any further parameters passed to `makeEslintConfig` are
|
||||
appended to the resulting ESLint configuration array. This means you can customize your configuration further like
|
||||
this:
|
||||
|
||||
```mjs
|
||||
// myProjectRoot/eslint.config.mjs
|
||||
import { makeEslintConfig } from 'eslint-config-scratch'
|
||||
|
||||
export default makeEslintConfig(
|
||||
{
|
||||
// Optional: enables rules that use type info, some of which work in JS too
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
|
||||
// Optional: specify global variables available in your environment
|
||||
globals: 'browser',
|
||||
},
|
||||
// Add custom rules or overrides here
|
||||
{
|
||||
files: ['*.test.js'],
|
||||
rules: {
|
||||
'no-console': 'off', // Allow console logs in test files
|
||||
},
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
You could concatenate more configuration objects onto the array returned by `makeEslintConfig` with equivalent
|
||||
results, but this approach offers better editor hints for autocomplete and type checking.
|
||||
|
||||
All ESLint configuration options are available this way. You can use this to handle globals yourself if the simplified
|
||||
`globals` configuration from above doesn't meet your needs:
|
||||
|
||||
```mjs
|
||||
// myProjectRoot/eslint.config.mjs
|
||||
import { makeEslintConfig } from 'eslint-config-scratch'
|
||||
import { eslintConfigScratch } from 'eslint-config-scratch'
|
||||
import { globalIgnores } from 'eslint/config'
|
||||
import globals from 'globals'
|
||||
|
||||
export default makeEslintConfig(
|
||||
export default eslintConfigScratch.config(
|
||||
eslintConfigScratch.recommended,
|
||||
{
|
||||
// Optional: enables rules that use type info, some of which work in JS too
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
{
|
||||
files: ['src/main/**.js'],
|
||||
languageOptions: {
|
||||
globals: globals.node,
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['src/renderer/**.js'],
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node,
|
||||
MY_CUSTOM_GLOBAL: 'readonly',
|
||||
},
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
},
|
||||
// Ignore all files in the dist directory
|
||||
globalIgnores(['dist/**/*']),
|
||||
)
|
||||
```
|
||||
|
||||
Of course, another option would be to place a different `eslint.config.mjs` file in each subdirectory. If you have
|
||||
multiple `tsconfig.json` or `jsconfig.json` files in your project, it likely makes sense to have an
|
||||
`eslint.config.mjs` file beside each one.
|
||||
## Granular Configuration
|
||||
|
||||
The `eslintConfigScratch` object contains granular configurations as well:
|
||||
|
||||
- `recommendedTypeFree`: A configuration suitable for contexts without type information, such as a JavaScript project.
|
||||
- `recommendedTypeChecked`: A configuration suitable for contexts with type information, such as a TypeScript project.
|
||||
You must provide extra configuration to `parserOptions` to enable type checking. See here:
|
||||
<https://typescript-eslint.io/getting-started/typed-linting/>
|
||||
|
||||
The `recommended` configuration is a combination of the two, and should be suitable for most projects. Features
|
||||
requiring type information are enabled for TypeScript files, and features that don't require type information are
|
||||
enabled for all files.
|
||||
|
||||
## Legacy Styles
|
||||
|
||||
Scratch used very different styling rules in `eslint-config-scratch@^9` and below. If you need to use those rules, you
|
||||
can use the rule sets under `legacy/`:
|
||||
can use these legacy configurations:
|
||||
|
||||
- `eslint-config-scratch/legacy`: Legacy base configuration, not configured for any particular environment
|
||||
- `eslint-config-scratch/legacy/es6`: Legacy rules for targeting Scratch's supported web browsers
|
||||
- `eslint-config-scratch/legacy/node`: Legacy rules for targeting Node.js
|
||||
- `eslint-config-scratch/legacy/react`: Legacy rules for targeting Scratch's supported web browsers with React
|
||||
- `eslintConfigScratch.legacy.base`: Legacy base configuration, not configured for any particular environment
|
||||
- `eslintConfigScratch.legacy.es6`: Legacy rules for targeting Scratch's supported web browsers
|
||||
- `eslintConfigScratch.legacy.node`: Legacy rules for targeting Node.js
|
||||
- `eslintConfigScratch.legacy.react`: Legacy rules for targeting Scratch's supported web browsers with React
|
||||
|
||||
New projects should not use these rule sets. They may disappear in the future. Scratch did not use Prettier at this
|
||||
time, so there is no legacy Prettier configuration.
|
||||
|
||||
Use these rule sets by importing them directly:
|
||||
Legacy Scratch projects usually `extend` more than one of these at a time, and potentially a different set per
|
||||
subdirectory. To do that in this new flat configuration format:
|
||||
|
||||
```mjs
|
||||
// myProjectRoot/eslint.config.mjs
|
||||
import webConfig from 'eslint-config-scratch/legacy/es6'
|
||||
```js
|
||||
// scratch-gui/eslint.config.mjs
|
||||
import { eslintConfigScratch } from 'eslint-config-scratch'
|
||||
import { globalIgnores } from 'eslint/config'
|
||||
import globals from 'globals'
|
||||
|
||||
/** @returns {import('eslint').Linter.Config[]} */
|
||||
export default [...webConfig, globalIgnores(['dist/**/*'])]
|
||||
export default eslintConfigScratch.config(
|
||||
eslintConfigScratch.legacy.base,
|
||||
eslintConfigScratch.legacy.es6,
|
||||
{
|
||||
files: ['src/**/*.js', 'src/**/*.jsx'],
|
||||
extends: [eslintConfigScratch.legacy.react],
|
||||
languageOptions: {
|
||||
globals: globals.browser,
|
||||
},
|
||||
rules: {
|
||||
// ...customized rules for `src/`...
|
||||
},
|
||||
// ...other settings for `src/`...
|
||||
},
|
||||
// ...settings for `test/`, etc...
|
||||
globalIgnores(['dist/**/*']),
|
||||
)
|
||||
```
|
||||
|
||||
## Committing
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
import { makeEslintConfig } from './lib/index.mjs'
|
||||
import globals from 'globals'
|
||||
import { eslintConfigScratch } from './lib/index.mjs'
|
||||
|
||||
/** @type {import('typescript-eslint').ConfigArray} */
|
||||
export default makeEslintConfig({ globals: 'node' })
|
||||
export default eslintConfigScratch.config(eslintConfigScratch.recommended, {
|
||||
languageOptions: {
|
||||
globals: globals.node,
|
||||
},
|
||||
})
|
||||
|
|
479
lib/eslint.mjs
479
lib/eslint.mjs
|
@ -8,10 +8,20 @@ import jsxA11y from 'eslint-plugin-jsx-a11y'
|
|||
import markdown from 'eslint-plugin-markdown'
|
||||
import react from 'eslint-plugin-react'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import globals from 'globals'
|
||||
import tseslint from 'typescript-eslint'
|
||||
import eslintComments from '@eslint-community/eslint-plugin-eslint-comments/configs'
|
||||
import eslint from '@eslint/js'
|
||||
import legacyES6 from './legacy/es6.mjs'
|
||||
import legacyBase from './legacy/index.mjs'
|
||||
import legacyNode from './legacy/node.mjs'
|
||||
import legacyReact from './legacy/react.mjs'
|
||||
|
||||
const legacy = {
|
||||
base: legacyBase,
|
||||
es6: legacyES6,
|
||||
node: legacyNode,
|
||||
react: legacyReact,
|
||||
}
|
||||
|
||||
// See https://www.npmjs.com/package/eslint-plugin-html#user-content-settings
|
||||
const htmlExtensions = htmlSettings.getSettings({}).htmlExtensions
|
||||
|
@ -19,274 +29,233 @@ const htmlExtensions = htmlSettings.getSettings({}).htmlExtensions
|
|||
// '.html' => '**/*.html'
|
||||
const htmlGlobs = htmlExtensions.map(ext => `**/*${ext}`)
|
||||
|
||||
/**
|
||||
* @typedef {import('eslint').Linter.Globals} Globals
|
||||
* @typedef {keyof globals} GlobalsKey
|
||||
* @typedef {Globals | GlobalsKey} GlobalsObjOrKey
|
||||
*/
|
||||
const typeScriptExtensions = ['.ts', '.tsx', '.mts', '.cts']
|
||||
|
||||
/**
|
||||
* Flatten the globals passed to `makeScratchConfig` into an object suitable for ESLint's `globals` option.
|
||||
* @param {GlobalsObjOrKey | GlobalsObjOrKey[]} [globalsIn] The globals to flatten.
|
||||
* @returns {Globals|undefined} Flattened globals object for ESLint.
|
||||
* Base rules recommended when type information is not available.
|
||||
* These rules are also safe to use when type information is available.
|
||||
*/
|
||||
const flattenGlobals = globalsIn => {
|
||||
if (!globalsIn) return
|
||||
const typeFreeRules = tseslint.config(
|
||||
eslint.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
tseslint.configs.stylistic,
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Globals} globalsAcc Globals accumulator.
|
||||
* @param {GlobalsObjOrKey} objOrKey A globals object or key to add to the accumulator.
|
||||
* @returns {Globals} The accumulator after adding the current globals object or key.
|
||||
*/
|
||||
const globalsReducer = (globalsAcc, objOrKey) => {
|
||||
if (typeof objOrKey === 'string') {
|
||||
const globalsForKey = globals[objOrKey]
|
||||
if (!globalsForKey) {
|
||||
throw new Error(`Invalid globals name. Not a key from the globals package: ${objOrKey}`)
|
||||
}
|
||||
Object.assign(globalsAcc, globalsForKey)
|
||||
} else {
|
||||
Object.assign(globalsAcc, objOrKey)
|
||||
}
|
||||
// eslint-plugin-formatjs
|
||||
{
|
||||
plugins: {
|
||||
formatjs,
|
||||
},
|
||||
rules: {
|
||||
'formatjs/no-offset': ['error'],
|
||||
},
|
||||
},
|
||||
// eslint-plugin-html
|
||||
{
|
||||
files: htmlGlobs,
|
||||
plugins: { html },
|
||||
settings: {
|
||||
'html/html-extensions': htmlExtensions,
|
||||
},
|
||||
},
|
||||
// eslint-plugin-import
|
||||
{
|
||||
plugins: importPlugin.flatConfigs.recommended.plugins,
|
||||
rules: {
|
||||
'import/no-duplicates': 'error', // Forbid duplicate imports
|
||||
},
|
||||
},
|
||||
// eslint-plugin-jsdoc
|
||||
jsdoc.configs['flat/recommended-error'],
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'],
|
||||
extends: [jsdoc.configs['flat/recommended-typescript-error']],
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
// If JSDoc comments are present, they must be informative (non-trivial).
|
||||
// For example, the description "The foo." on a variable called "foo" is not informative.
|
||||
// https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/informative-docs.md
|
||||
'jsdoc/informative-docs': ['error'],
|
||||
|
||||
return globalsAcc
|
||||
}
|
||||
// Don't require JSDoc comments. Library authors should consider turning this on for external interfaces.
|
||||
// https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-jsdoc.md
|
||||
'jsdoc/require-jsdoc': ['off'],
|
||||
},
|
||||
},
|
||||
// eslint-plugin-jsx-a11y
|
||||
jsxA11y.flatConfigs.recommended,
|
||||
// eslint-plugin-markdown
|
||||
markdown.configs.recommended,
|
||||
// eslint-plugin-react
|
||||
{
|
||||
plugins: {
|
||||
react,
|
||||
},
|
||||
rules: {
|
||||
// https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-danger.md
|
||||
'react/no-danger': ['error'],
|
||||
|
||||
if (Array.isArray(globalsIn)) {
|
||||
return globalsIn.reduce(globalsReducer, {})
|
||||
}
|
||||
// https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/self-closing-comp.md
|
||||
'react/self-closing-comp': ['error'],
|
||||
|
||||
return globalsReducer({}, globalsIn)
|
||||
}
|
||||
// https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-boolean-value.md
|
||||
'react/jsx-boolean-value': ['error', 'never'],
|
||||
|
||||
// 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'],
|
||||
|
||||
// https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-curly-spacing.md
|
||||
'react/jsx-curly-spacing': ['error'],
|
||||
|
||||
// https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-equals-spacing.md
|
||||
'react/jsx-equals-spacing': ['error'],
|
||||
|
||||
// https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-filename-extension.md
|
||||
'react/jsx-filename-extension': ['error', { extensions: ['.jsx', '.tsx'] }],
|
||||
|
||||
// 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'],
|
||||
|
||||
// https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-handler-names.md
|
||||
'react/jsx-handler-names': ['error', { checkLocalVariables: true, eventHandlerPrefix: false }],
|
||||
|
||||
// https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-indent.md
|
||||
'react/jsx-indent': ['error'],
|
||||
|
||||
// https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md
|
||||
'react/jsx-no-bind': ['error', { ignoreRefs: true, allowArrowFunctions: true }],
|
||||
|
||||
// https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-pascal-case.md
|
||||
'react/jsx-pascal-case': ['error'],
|
||||
|
||||
// https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-tag-spacing.md
|
||||
'react/jsx-tag-spacing': ['error'],
|
||||
|
||||
// https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-wrap-multilines.md
|
||||
'react/jsx-wrap-multilines': ['error'],
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect',
|
||||
},
|
||||
},
|
||||
},
|
||||
// eslint-plugin-react-hooks
|
||||
{
|
||||
extends: [reactHooks.configs['recommended-latest']],
|
||||
rules: {
|
||||
// https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/README.md#advanced-configuration
|
||||
'react-hooks/exhaustive-deps': ['error', { additionalHooks: '^useAsync$' }],
|
||||
},
|
||||
},
|
||||
// typescript-eslint
|
||||
{
|
||||
rules: {
|
||||
// https://typescript-eslint.io/rules/no-non-null-asserted-nullish-coalescing/
|
||||
'@typescript-eslint/no-non-null-asserted-nullish-coalescing': ['error'],
|
||||
|
||||
// https://typescript-eslint.io/rules/no-useless-constructor/
|
||||
'@typescript-eslint/no-useless-constructor': ['error'],
|
||||
|
||||
// https://typescript-eslint.io/rules/no-non-null-assertion
|
||||
'@typescript-eslint/no-non-null-assertion': ['error'],
|
||||
},
|
||||
},
|
||||
// @eslint-community/eslint-plugin-eslint-comments
|
||||
{
|
||||
extends: [
|
||||
// @ts-expect-error This plugin's recommended rules don't quite match the type `tseslint.config` expects.
|
||||
eslintComments.recommended,
|
||||
],
|
||||
rules: {
|
||||
// require a description for eslint control comments other than `eslint-enable`
|
||||
'@eslint-community/eslint-comments/require-description': ['error', { ignore: ['eslint-enable'] }],
|
||||
},
|
||||
},
|
||||
// @eslint/js
|
||||
{
|
||||
rules: {
|
||||
// https://eslint.org/docs/latest/rules/arrow-body-style
|
||||
'arrow-body-style': ['error', 'as-needed'],
|
||||
|
||||
// https://eslint.org/docs/latest/rules/no-duplicate-imports
|
||||
'no-duplicate-imports': ['error'],
|
||||
|
||||
// https://eslint.org/docs/latest/rules/no-template-curly-in-string
|
||||
'no-template-curly-in-string': ['error'],
|
||||
|
||||
// https://eslint.org/docs/latest/rules/no-useless-computed-key
|
||||
'no-useless-computed-key': ['error'],
|
||||
|
||||
// https://eslint.org/docs/latest/rules/no-useless-rename
|
||||
'no-useless-rename': ['error'],
|
||||
|
||||
// https://eslint.org/docs/latest/rules/prefer-arrow-callback
|
||||
'prefer-arrow-callback': ['error'],
|
||||
|
||||
// https://eslint.org/docs/latest/rules/prefer-const#destructuring
|
||||
'prefer-const': ['error'],
|
||||
|
||||
// https://eslint.org/docs/latest/rules/prefer-spread
|
||||
'prefer-spread': ['error'],
|
||||
|
||||
// https://eslint.org/docs/latest/rules/require-atomic-updates
|
||||
'require-atomic-updates': ['error'],
|
||||
|
||||
// https://eslint.org/docs/latest/rules/symbol-description
|
||||
'symbol-description': ['error'],
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
/**
|
||||
* Create an ESLint configuration for Scratch style.
|
||||
* Supports JavaScript, TypeScript, and React (JSX/TSX) files.
|
||||
* Setting `tsconfigRootDir` enables type-aware rules, some of which apply even in JavaScript files.
|
||||
* @param {object} options Configuration options
|
||||
* @param {string} [options.tsconfigRootDir] Enable type checking by setting the root TypeScript config directory.
|
||||
* @param {GlobalsObjOrKey | GlobalsObjOrKey[]} [options.globals] Globals to provide to ESLint.
|
||||
* This can be expressed as:
|
||||
* - a single string, such as `'browser'`, corresponding to a key in the `globals` package.
|
||||
* - a single object as described in the "Specifying Globals" section of the ESLint documentation:
|
||||
* https://eslint.org/docs/latest/use/configure/language-options#using-configuration-files
|
||||
* - an array of zero or more elements, each of which can be either of the above
|
||||
* @param {import('typescript-eslint').InfiniteDepthConfigWithExtends[]} moreConfigs Additional ESLint configurations
|
||||
* to merge with the base configuration.
|
||||
* @example
|
||||
* // eslint.config.mjs
|
||||
* export default makeScratchConfig({tsconfigRootDir: import.meta.dirname, globals: 'node'})
|
||||
* @example
|
||||
* // eslint.config.mjs
|
||||
* export default [
|
||||
* ...makeScratchConfig({tsconfigRootDir: import.meta.dirname, globals: 'browser'}),
|
||||
* {
|
||||
* // customization
|
||||
* }
|
||||
* ]
|
||||
* @returns {import('typescript-eslint').ConfigArray} An ESLint configuration array.
|
||||
* Additional rules recommended when information is available.
|
||||
* These rules require additional configuration.
|
||||
* @see https://typescript-eslint.io/getting-started/typed-linting/
|
||||
*/
|
||||
const makeEslintConfig = ({ tsconfigRootDir, globals: globalsIn } = {}, ...moreConfigs) => {
|
||||
const flattenedGlobals = flattenGlobals(globalsIn)
|
||||
const typeCheckedRules = tseslint.config(
|
||||
tseslint.configs.recommendedTypeChecked,
|
||||
tseslint.configs.stylisticTypeChecked,
|
||||
{
|
||||
rules: {
|
||||
// https://typescript-eslint.io/rules/no-unnecessary-condition/
|
||||
'@typescript-eslint/no-unnecessary-condition': ['error'],
|
||||
|
||||
return tseslint.config(
|
||||
// Start with recommended rules from ESLint and TypeScript ESLint.
|
||||
{
|
||||
extends: [eslint.configs.recommended, tseslint.configs.recommended, tseslint.configs.stylistic],
|
||||
languageOptions: {
|
||||
...(globalsIn ? { globals: flattenedGlobals } : {}),
|
||||
},
|
||||
// https://typescript-eslint.io/rules/require-await/
|
||||
'@typescript-eslint/require-await': ['error'],
|
||||
},
|
||||
// eslint-plugin-formatjs
|
||||
{
|
||||
plugins: {
|
||||
formatjs,
|
||||
},
|
||||
rules: {
|
||||
'formatjs/no-offset': ['error'],
|
||||
},
|
||||
},
|
||||
// eslint-plugin-html
|
||||
{
|
||||
files: htmlGlobs,
|
||||
plugins: { html },
|
||||
settings: {
|
||||
'html/html-extensions': htmlExtensions,
|
||||
},
|
||||
},
|
||||
// eslint-plugin-import
|
||||
{
|
||||
plugins: importPlugin.flatConfigs.recommended.plugins,
|
||||
rules: {
|
||||
'import/no-duplicates': 'error', // Forbid duplicate imports
|
||||
},
|
||||
},
|
||||
// eslint-plugin-jsdoc
|
||||
jsdoc.configs['flat/recommended-error'],
|
||||
{
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'],
|
||||
extends: [jsdoc.configs['flat/recommended-typescript-error']],
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
// If JSDoc comments are present, they must be informative (non-trivial).
|
||||
// For example, the description "The foo." on a variable called "foo" is not informative.
|
||||
// https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/informative-docs.md
|
||||
'jsdoc/informative-docs': ['error'],
|
||||
},
|
||||
)
|
||||
|
||||
// Don't require JSDoc comments. Library authors should consider turning this on for external interfaces.
|
||||
// https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/require-jsdoc.md
|
||||
'jsdoc/require-jsdoc': ['off'],
|
||||
},
|
||||
},
|
||||
// eslint-plugin-jsx-a11y
|
||||
jsxA11y.flatConfigs.recommended,
|
||||
// eslint-plugin-markdown
|
||||
markdown.configs.recommended,
|
||||
// eslint-plugin-react
|
||||
{
|
||||
plugins: {
|
||||
react,
|
||||
},
|
||||
rules: {
|
||||
// https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-danger.md
|
||||
'react/no-danger': ['error'],
|
||||
/**
|
||||
* Scratch's recommended configuration when type information is not available.
|
||||
*/
|
||||
const recommendedTypeFree = tseslint.config(typeFreeRules, eslintConfigPrettier)
|
||||
|
||||
// https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/self-closing-comp.md
|
||||
'react/self-closing-comp': ['error'],
|
||||
/**
|
||||
* Scratch's recommended configuration when type information is available.
|
||||
* These rules require additional configuration.
|
||||
* @see https://typescript-eslint.io/getting-started/typed-linting/
|
||||
*/
|
||||
const recommendedTypeChecked = tseslint.config(typeFreeRules, typeCheckedRules, eslintConfigPrettier)
|
||||
|
||||
// https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-boolean-value.md
|
||||
'react/jsx-boolean-value': ['error', 'never'],
|
||||
/**
|
||||
* Scratch's recommended configuration for general use.
|
||||
* Type-checked rules are enabled for files with known TypeScript extensions.
|
||||
* If your project includes such files, you must include additional configuration.
|
||||
* @see https://typescript-eslint.io/getting-started/typed-linting/
|
||||
*/
|
||||
const recommended = tseslint.config(
|
||||
typeFreeRules,
|
||||
{
|
||||
files: typeScriptExtensions,
|
||||
extends: [typeCheckedRules],
|
||||
},
|
||||
eslintConfigPrettier,
|
||||
)
|
||||
|
||||
// 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'],
|
||||
// Helper to get type hints while conveniently merging and extending configurations
|
||||
export { config } from 'typescript-eslint'
|
||||
|
||||
// https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-curly-spacing.md
|
||||
'react/jsx-curly-spacing': ['error'],
|
||||
|
||||
// https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-equals-spacing.md
|
||||
'react/jsx-equals-spacing': ['error'],
|
||||
|
||||
// https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-filename-extension.md
|
||||
'react/jsx-filename-extension': ['error', { extensions: ['.jsx', '.tsx'] }],
|
||||
|
||||
// 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'],
|
||||
|
||||
// https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-handler-names.md
|
||||
'react/jsx-handler-names': ['error', { checkLocalVariables: true, eventHandlerPrefix: false }],
|
||||
|
||||
// https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-indent.md
|
||||
'react/jsx-indent': ['error'],
|
||||
|
||||
// https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md
|
||||
'react/jsx-no-bind': ['error', { ignoreRefs: true, allowArrowFunctions: true }],
|
||||
|
||||
// https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-pascal-case.md
|
||||
'react/jsx-pascal-case': ['error'],
|
||||
|
||||
// https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-tag-spacing.md
|
||||
'react/jsx-tag-spacing': ['error'],
|
||||
|
||||
// https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/jsx-wrap-multilines.md
|
||||
'react/jsx-wrap-multilines': ['error'],
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect',
|
||||
},
|
||||
},
|
||||
},
|
||||
// eslint-plugin-react-hooks
|
||||
{
|
||||
extends: [reactHooks.configs['recommended-latest']],
|
||||
rules: {
|
||||
// https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/README.md#advanced-configuration
|
||||
'react-hooks/exhaustive-deps': ['error', { additionalHooks: '^useAsync$' }],
|
||||
},
|
||||
},
|
||||
// typescript-eslint
|
||||
{
|
||||
rules: {
|
||||
// https://typescript-eslint.io/rules/no-non-null-asserted-nullish-coalescing/
|
||||
'@typescript-eslint/no-non-null-asserted-nullish-coalescing': ['error'],
|
||||
|
||||
// https://typescript-eslint.io/rules/no-useless-constructor/
|
||||
'@typescript-eslint/no-useless-constructor': ['error'],
|
||||
|
||||
// https://typescript-eslint.io/rules/no-non-null-assertion
|
||||
'@typescript-eslint/no-non-null-assertion': ['error'],
|
||||
},
|
||||
},
|
||||
tsconfigRootDir
|
||||
? {
|
||||
files: ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts'],
|
||||
extends: [tseslint.configs.recommendedTypeChecked, tseslint.configs.stylisticTypeChecked],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
tsconfigRootDir,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
// https://typescript-eslint.io/rules/no-unnecessary-condition/
|
||||
'@typescript-eslint/no-unnecessary-condition': ['error'],
|
||||
|
||||
// https://typescript-eslint.io/rules/require-await/
|
||||
'@typescript-eslint/require-await': ['error'],
|
||||
},
|
||||
}
|
||||
: {},
|
||||
// @eslint-community/eslint-plugin-eslint-comments
|
||||
{
|
||||
extends: [
|
||||
// @ts-expect-error This plugin's recommended rules don't quite match the type `tseslint.config` expects.
|
||||
eslintComments.recommended,
|
||||
],
|
||||
rules: {
|
||||
// require a description for eslint control comments other than `eslint-enable`
|
||||
'@eslint-community/eslint-comments/require-description': ['error', { ignore: ['eslint-enable'] }],
|
||||
},
|
||||
},
|
||||
// @eslint/js
|
||||
{
|
||||
rules: {
|
||||
// https://eslint.org/docs/latest/rules/arrow-body-style
|
||||
'arrow-body-style': ['error', 'as-needed'],
|
||||
|
||||
// https://eslint.org/docs/latest/rules/no-duplicate-imports
|
||||
'no-duplicate-imports': ['error'],
|
||||
|
||||
// https://eslint.org/docs/latest/rules/no-template-curly-in-string
|
||||
'no-template-curly-in-string': ['error'],
|
||||
|
||||
// https://eslint.org/docs/latest/rules/no-useless-computed-key
|
||||
'no-useless-computed-key': ['error'],
|
||||
|
||||
// https://eslint.org/docs/latest/rules/no-useless-rename
|
||||
'no-useless-rename': ['error'],
|
||||
|
||||
// https://eslint.org/docs/latest/rules/prefer-arrow-callback
|
||||
'prefer-arrow-callback': ['error'],
|
||||
|
||||
// https://eslint.org/docs/latest/rules/prefer-const#destructuring
|
||||
'prefer-const': ['error'],
|
||||
|
||||
// https://eslint.org/docs/latest/rules/prefer-spread
|
||||
'prefer-spread': ['error'],
|
||||
|
||||
// https://eslint.org/docs/latest/rules/require-atomic-updates
|
||||
'require-atomic-updates': ['error'],
|
||||
|
||||
// https://eslint.org/docs/latest/rules/symbol-description
|
||||
'symbol-description': ['error'],
|
||||
},
|
||||
},
|
||||
...moreConfigs,
|
||||
// Keep `eslintConfigPrettier` last to turn off rules that conflict with Prettier
|
||||
eslintConfigPrettier,
|
||||
)
|
||||
}
|
||||
|
||||
export { makeEslintConfig }
|
||||
// Our exported configurations
|
||||
export { recommended, recommendedTypeChecked, recommendedTypeFree, legacy }
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { makeEslintConfig } from './eslint.mjs'
|
||||
import { makePrettierConfig } from './prettier.mjs'
|
||||
import * as eslintConfigScratch from './eslint.mjs'
|
||||
import * as prettierConfigScratch from './prettier.mjs'
|
||||
|
||||
export { makeEslintConfig, makePrettierConfig }
|
||||
export { eslintConfigScratch, prettierConfigScratch }
|
||||
|
|
|
@ -4,7 +4,7 @@ import sortImports from '@trivago/prettier-plugin-sort-imports'
|
|||
* @see https://prettier.io/docs/configuration
|
||||
* @type {import("prettier").Config}
|
||||
*/
|
||||
const prettierConfig = {
|
||||
const recommended = {
|
||||
// #region Prettier
|
||||
arrowParens: 'avoid',
|
||||
bracketSameLine: false,
|
||||
|
@ -22,9 +22,4 @@ const prettierConfig = {
|
|||
// #endregion @trivago/prettier-plugin-sort-imports
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {import("prettier").Config} A Prettier configuration for Scratch style.
|
||||
*/
|
||||
const makePrettierConfig = () => prettierConfig
|
||||
|
||||
export { makePrettierConfig }
|
||||
export { recommended }
|
||||
|
|
|
@ -3,13 +3,6 @@
|
|||
"version": "10.0.12",
|
||||
"description": "Shareable ESLint config for Scratch",
|
||||
"main": "./lib/index.mjs",
|
||||
"exports": {
|
||||
".": "./lib/index.mjs",
|
||||
"./legacy": "./lib/legacy/index.mjs",
|
||||
"./legacy/es6": "./lib/legacy/web.mjs",
|
||||
"./legacy/node": "./lib/legacy/node.mjs",
|
||||
"./legacy/react": "./lib/legacy/react.mjs"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "husky install",
|
||||
"format": "prettier --write . && eslint --fix",
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import { makePrettierConfig } from './lib/prettier.mjs'
|
||||
import { prettierConfigScratch } from './lib/index.mjs'
|
||||
|
||||
export default makePrettierConfig()
|
||||
export default prettierConfigScratch.recommended
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue