mirror of
https://github.com/scratchfoundation/eslint-config-scratch.git
synced 2025-08-28 22:40:13 -04:00
commit
71edf06a48
22 changed files with 3612 additions and 533 deletions
203
README.md
203
README.md
|
@ -12,26 +12,43 @@ 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.
|
||||
|
||||
For a TypeScript project, you can add `languageOptions` to enable type checking:
|
||||
|
||||
```js
|
||||
// myProjectRoot/eslint.config.mjs
|
||||
import { makeEslintConfig } from 'eslint-config-scratch'
|
||||
import { eslintConfigScratch } from 'eslint-config-scratch'
|
||||
|
||||
// for a TypeScript project:
|
||||
export default makeEslintConfig({ globals: 'browser', tsconfigRootDir: import.meta.dirname })
|
||||
|
||||
// for plain JavaScript:
|
||||
export default makeEslintConfig({ globals: 'browser' })
|
||||
export default eslintConfigScratch.config(eslintConfigScratch.recommended, {
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
For a JavaScript project, it might look like this:
|
||||
|
||||
```js
|
||||
// myProjectRoot/eslint.config.mjs
|
||||
import { eslintConfigScratch } from 'eslint-config-scratch'
|
||||
|
||||
export default eslintConfigScratch.recommended
|
||||
```
|
||||
|
||||
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,134 +62,90 @@ 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 return value of the `makeEslintConfig` function is a standard 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
|
||||
},
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
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({
|
||||
// Optional: enables rules that use type info, some of which work in JS too
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
}),
|
||||
export default eslintConfigScratch.config(
|
||||
eslintConfigScratch.recommended,
|
||||
{
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
```js
|
||||
// scratch-gui/eslint.config.mjs
|
||||
import { eslintConfigScratch } from 'eslint-config-scratch'
|
||||
import { globalIgnores } from 'eslint/config'
|
||||
import globals from 'globals'
|
||||
|
||||
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
|
||||
|
||||
This project uses [semantic release](https://github.com/semantic-release/semantic-release)
|
||||
|
|
|
@ -1,4 +1,13 @@
|
|||
import { makeEslintConfig } from './lib/index.mjs'
|
||||
import { globalIgnores } from 'eslint/config'
|
||||
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,
|
||||
},
|
||||
},
|
||||
globalIgnores(['test/**/*.bad.*']),
|
||||
)
|
||||
|
|
584
lib/eslint.mjs
584
lib/eslint.mjs
|
@ -3,272 +3,360 @@ import formatjs from 'eslint-plugin-formatjs'
|
|||
import html from 'eslint-plugin-html'
|
||||
import htmlSettings from 'eslint-plugin-html/src/settings.js'
|
||||
import importPlugin from 'eslint-plugin-import'
|
||||
import jsdoc from 'eslint-plugin-jsdoc'
|
||||
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 markdown from '@eslint/markdown'
|
||||
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,
|
||||
}
|
||||
|
||||
// WARNING: eslint rules from `typescript-eslint`, even the "untyped" rules, assume that your code will be run through
|
||||
// `tsc` or equivalent for type checking. Using any rule set from `typescript-eslint` will, for example, turn off the
|
||||
// `no-undef` rule. That makes sense if you'll use TypeScript to catch undefined globals, but it could be dangerous
|
||||
// for plain JavaScript.
|
||||
// More information here: https://github.com/typescript-eslint/typescript-eslint/issues/8825#issuecomment-2033315610
|
||||
|
||||
/**
|
||||
* Convert an array of file extensions to an array of globs
|
||||
* @param {string[]} extArray - an array of file extensions, like `.foo`
|
||||
* @returns {string[]} an array of globs, like `** /*.foo` (without the space)
|
||||
*/
|
||||
const extArrayToGlobArray = extArray => extArray.map(ext => `**/*${ext}`)
|
||||
|
||||
// See https://www.npmjs.com/package/eslint-plugin-html#user-content-settings
|
||||
const htmlExtensions = htmlSettings.getSettings({}).htmlExtensions
|
||||
const htmlSettingsDefault = htmlSettings.getSettings({})
|
||||
|
||||
// '.html' => '**/*.html'
|
||||
const htmlGlobs = htmlExtensions.map(ext => `**/*${ext}`)
|
||||
const fileExtensions = (x => {
|
||||
x.allScript = [...x.javaScript, ...x.typeScript]
|
||||
return x
|
||||
})({
|
||||
html: /** @type {string[]} */ (htmlSettingsDefault.htmlExtensions),
|
||||
javaScript: ['.js', '.jsx', '.mjs', '.cjs'],
|
||||
markdown: ['.md'],
|
||||
typeScript: ['.ts', '.tsx', '.mts', '.cts'],
|
||||
react: ['.jsx', '.tsx'],
|
||||
xml: /** @type {string[]} */ (htmlSettingsDefault.xmlExtensions),
|
||||
})
|
||||
|
||||
/**
|
||||
* @typedef {import('eslint').Linter.Globals} Globals
|
||||
* @typedef {keyof globals} GlobalsKey
|
||||
* @typedef {Globals | GlobalsKey} GlobalsObjOrKey
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
const flattenGlobals = globalsIn => {
|
||||
if (!globalsIn) return
|
||||
|
||||
/**
|
||||
*
|
||||
* @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)
|
||||
}
|
||||
|
||||
return globalsAcc
|
||||
}
|
||||
|
||||
if (Array.isArray(globalsIn)) {
|
||||
return globalsIn.reduce(globalsReducer, {})
|
||||
}
|
||||
|
||||
return globalsReducer({}, globalsIn)
|
||||
// This explicitly lists each entry so that we can get unused warnings
|
||||
const fileGlobs = {
|
||||
allScript: extArrayToGlobArray(fileExtensions.allScript),
|
||||
html: extArrayToGlobArray(fileExtensions.html),
|
||||
javaScript: extArrayToGlobArray(fileExtensions.javaScript),
|
||||
markdown: extArrayToGlobArray(fileExtensions.markdown),
|
||||
react: extArrayToGlobArray(fileExtensions.react),
|
||||
typeScript: extArrayToGlobArray(fileExtensions.typeScript),
|
||||
xml: extArrayToGlobArray(fileExtensions.xml),
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @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.
|
||||
* Rules for specific file types outside of the core JS/TS rule sets.
|
||||
*/
|
||||
const makeEslintConfig = ({ tsconfigRootDir, globals: globalsIn } = {}) => {
|
||||
const flattenedGlobals = flattenGlobals(globalsIn)
|
||||
const miscFileRules = tseslint.config([
|
||||
// eslint-plugin-html
|
||||
{
|
||||
name: 'scratch/miscFileRules[eslint-plugin-html]',
|
||||
files: [...fileGlobs.html, ...fileGlobs.xml],
|
||||
plugins: { html },
|
||||
settings: {
|
||||
'html/html-extensions': fileExtensions.html,
|
||||
'xml/xml-extensions': fileExtensions.xml,
|
||||
},
|
||||
},
|
||||
// eslint-plugin-markdown
|
||||
{
|
||||
name: 'scratch/miscFileRules[eslint-plugin-markdown]',
|
||||
files: fileGlobs.markdown,
|
||||
extends: [markdown.configs.recommended],
|
||||
language: 'markdown/gfm', // Github Flavored Markdown
|
||||
},
|
||||
markdown.configs.processor, // Process script blocks inside Markdown files
|
||||
])
|
||||
|
||||
return tseslint.config(
|
||||
// Start with recommended rules from ESLint and TypeScript ESLint.
|
||||
{
|
||||
extends: [
|
||||
eslint.configs.recommended,
|
||||
tsconfigRootDir ? tseslint.configs.recommendedTypeChecked : tseslint.configs.recommended,
|
||||
tsconfigRootDir ? tseslint.configs.stylisticTypeChecked : tseslint.configs.stylistic,
|
||||
],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
...(tsconfigRootDir
|
||||
? {
|
||||
projectService: true,
|
||||
tsconfigRootDir,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
...(globalsIn ? { globals: flattenedGlobals } : {}),
|
||||
/**
|
||||
* Rules recommended for all script files, whether or not type information is available or checked.
|
||||
*/
|
||||
const allScriptRules = tseslint.config([
|
||||
// eslint-plugin-formatjs
|
||||
{
|
||||
name: 'scratch/allScriptRules[eslint-plugin-formatjs]',
|
||||
plugins: {
|
||||
formatjs,
|
||||
},
|
||||
rules: {
|
||||
'formatjs/no-offset': ['error'],
|
||||
},
|
||||
},
|
||||
// eslint-plugin-import
|
||||
{
|
||||
name: 'scratch/allScriptRules[eslint-plugin-import]',
|
||||
plugins: importPlugin.flatConfigs.recommended.plugins,
|
||||
rules: {
|
||||
'import/no-duplicates': 'error', // Forbid duplicate imports
|
||||
},
|
||||
},
|
||||
// eslint-plugin-jsx-a11y
|
||||
{
|
||||
name: 'scratch/allScriptRules[eslint-plugin-jsx-a11y]',
|
||||
files: fileGlobs.react,
|
||||
extends: [jsxA11y.flatConfigs.recommended],
|
||||
},
|
||||
// eslint-plugin-react
|
||||
{
|
||||
name: 'scratch/allScriptRules[eslint-plugin-react]',
|
||||
files: fileGlobs.react,
|
||||
plugins: {
|
||||
react,
|
||||
},
|
||||
rules: {
|
||||
// https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/no-danger.md
|
||||
'react/no-danger': ['error'],
|
||||
|
||||
// https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/self-closing-comp.md
|
||||
'react/self-closing-comp': ['error'],
|
||||
|
||||
// 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-formatjs
|
||||
{
|
||||
plugins: {
|
||||
formatjs,
|
||||
},
|
||||
rules: {
|
||||
'formatjs/no-offset': ['error'],
|
||||
},
|
||||
// eslint-plugin-react-hooks
|
||||
{
|
||||
name: 'scratch/allScriptRules[eslint-plugin-react-hooks]',
|
||||
files: fileGlobs.react,
|
||||
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$' }],
|
||||
},
|
||||
},
|
||||
// @eslint-community/eslint-plugin-eslint-comments
|
||||
{
|
||||
name: 'scratch/allScriptRules[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
|
||||
{
|
||||
name: 'scratch/allScriptRules[@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'],
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
/**
|
||||
* Additional rules recommended when type information is not available or checked.
|
||||
*/
|
||||
const typeFreeRules = tseslint.config([
|
||||
{
|
||||
name: 'scratch/typeFreeRules[base]',
|
||||
extends: [eslint.configs.recommended],
|
||||
},
|
||||
...allScriptRules,
|
||||
{
|
||||
name: 'scratch/typeFreeRules[eslint-plugin-jsdoc]',
|
||||
extends: [jsdoc.configs['flat/recommended-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'],
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
/**
|
||||
* Rules recommended when type information is available and checked. This configuration turns off some rules with the
|
||||
* assumption that other software, such as TypeScript, will flag those problems. For example, the `no-undef` rule is
|
||||
* disabled in this configuration. These rules include `allScriptRules`.
|
||||
* These rules require additional configuration.
|
||||
* @see https://typescript-eslint.io/getting-started/typed-linting/
|
||||
*/
|
||||
const typeCheckedRules = tseslint.config([
|
||||
{
|
||||
name: 'scratch/typeCheckedRules[base]',
|
||||
extends: [
|
||||
eslint.configs.recommended,
|
||||
tseslint.configs.recommendedTypeChecked,
|
||||
tseslint.configs.stylisticTypeChecked,
|
||||
],
|
||||
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'],
|
||||
},
|
||||
},
|
||||
...allScriptRules,
|
||||
{
|
||||
name: 'scratch/typeCheckedRules[eslint-plugin-jsdoc][1]',
|
||||
extends: [jsdoc.configs['flat/recommended-error']],
|
||||
},
|
||||
{
|
||||
name: 'scratch/typeCheckedRules[eslint-plugin-jsdoc][2]',
|
||||
extends: [jsdoc.configs['flat/recommended-typescript-error']],
|
||||
},
|
||||
{
|
||||
name: 'scratch/typeCheckedRules[eslint-plugin-jsdoc][3]',
|
||||
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'],
|
||||
},
|
||||
},
|
||||
// typescript-eslint
|
||||
{
|
||||
name: 'scratch/typeCheckedRules[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'],
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
/**
|
||||
* Scratch's recommended configuration when type information is not available.
|
||||
*/
|
||||
const recommendedTypeFree = tseslint.config(typeFreeRules, eslintConfigPrettier)
|
||||
|
||||
/**
|
||||
* Scratch's recommended configuration when type information is available.
|
||||
* These rules require additional configuration.
|
||||
* WARNING: These rules do not specify the `files` property.
|
||||
* @see https://typescript-eslint.io/getting-started/typed-linting/
|
||||
*/
|
||||
const recommendedTypeChecked = tseslint.config(typeCheckedRules, eslintConfigPrettier)
|
||||
|
||||
/**
|
||||
* 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(
|
||||
{
|
||||
name: 'scratch/recommended',
|
||||
},
|
||||
{
|
||||
files: fileGlobs.allScript,
|
||||
extends: [typeFreeRules],
|
||||
},
|
||||
{
|
||||
files: fileGlobs.typeScript,
|
||||
extends: [typeCheckedRules],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
},
|
||||
},
|
||||
// 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-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'],
|
||||
},
|
||||
miscFileRules,
|
||||
eslintConfigPrettier,
|
||||
)
|
||||
|
||||
// https://github.com/jsx-eslint/eslint-plugin-react/blob/master/docs/rules/self-closing-comp.md
|
||||
'react/self-closing-comp': ['error'],
|
||||
// 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-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'],
|
||||
|
||||
// Rules that require type information
|
||||
...(tsconfigRootDir
|
||||
? {
|
||||
// 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'],
|
||||
},
|
||||
},
|
||||
// Keep `eslintConfigPrettier` last to turn off rules that conflict with Prettier
|
||||
eslintConfigPrettier,
|
||||
)
|
||||
}
|
||||
|
||||
export { makeEslintConfig }
|
||||
// Our exported configurations
|
||||
export { recommended, recommendedTypeChecked, recommendedTypeFree, miscFileRules, 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 }
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/** @type {import('eslint').Linter.Config[]} */
|
||||
export default [
|
||||
{
|
||||
languageOptions: {
|
|
@ -14,6 +14,7 @@ const compat = new FlatCompat({
|
|||
allConfig: js.configs.all,
|
||||
})
|
||||
|
||||
/** @type {import('eslint').Linter.Config[]} */
|
||||
export default [
|
||||
...compat.extends('eslint:recommended'),
|
||||
jsdoc.configs['flat/recommended'],
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import globals from 'globals'
|
||||
|
||||
/** @type {import('eslint').Linter.Config[]} */
|
||||
export default [
|
||||
{
|
||||
languageOptions: {
|
||||
|
|
|
@ -12,6 +12,7 @@ const compat = new FlatCompat({
|
|||
allConfig: js.configs.all,
|
||||
})
|
||||
|
||||
/** @type {import('eslint').Linter.Config[]} */
|
||||
export default [
|
||||
...compat.extends('plugin:react/recommended'),
|
||||
{
|
||||
|
|
|
@ -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,10 +22,4 @@ const prettierConfig = {
|
|||
// #endregion @trivago/prettier-plugin-sort-imports
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a Prettier configuration for Scratch style.
|
||||
* @returns {import("prettier").Config}
|
||||
*/
|
||||
const makePrettierConfig = () => prettierConfig
|
||||
|
||||
export { makePrettierConfig }
|
||||
export { recommended }
|
||||
|
|
2982
package-lock.json
generated
2982
package-lock.json
generated
File diff suppressed because it is too large
Load diff
17
package.json
17
package.json
|
@ -3,18 +3,12 @@
|
|||
"version": "10.0.14",
|
||||
"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",
|
||||
"lint": "eslint && prettier --check .",
|
||||
"test": "npm run lint"
|
||||
"test:lint": "eslint && prettier --check .",
|
||||
"test:vitest": "vitest run",
|
||||
"test": "npm run test:lint && npm run test:vitest"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -40,6 +34,7 @@
|
|||
"@eslint-community/eslint-plugin-eslint-comments": "4.5.0",
|
||||
"@eslint/eslintrc": "3.3.1",
|
||||
"@eslint/js": "9.25.1",
|
||||
"@eslint/markdown": "6.4.0",
|
||||
"@trivago/prettier-plugin-sort-imports": "5.2.2",
|
||||
"eslint-config-prettier": "10.1.2",
|
||||
"eslint-plugin-formatjs": "5.3.1",
|
||||
|
@ -47,7 +42,6 @@
|
|||
"eslint-plugin-import": "2.31.0",
|
||||
"eslint-plugin-jsdoc": "50.6.11",
|
||||
"eslint-plugin-jsx-a11y": "6.10.2",
|
||||
"eslint-plugin-markdown": "5.1.0",
|
||||
"eslint-plugin-react": "7.37.5",
|
||||
"eslint-plugin-react-hooks": "5.2.0",
|
||||
"globals": "16.0.0",
|
||||
|
@ -61,7 +55,8 @@
|
|||
"eslint": "9.25.1",
|
||||
"husky": "8.0.3",
|
||||
"scratch-semantic-release-config": "3.0.0",
|
||||
"semantic-release": "24.2.3"
|
||||
"semantic-release": "24.2.3",
|
||||
"vitest": "^3.1.1"
|
||||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import { makePrettierConfig } from './lib/prettier.mjs'
|
||||
import { prettierConfigScratch } from './lib/index.mjs'
|
||||
|
||||
export default makePrettierConfig()
|
||||
export default prettierConfigScratch.recommended
|
||||
|
|
112
test/__snapshots__/eslint.test.mjs.snap
Normal file
112
test/__snapshots__/eslint.test.mjs.snap
Normal file
|
@ -0,0 +1,112 @@
|
|||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`'recommended' > Plain TS (bad) 1`] = `
|
||||
[
|
||||
{
|
||||
"column": 7,
|
||||
"endColumn": 25,
|
||||
"endLine": 5,
|
||||
"line": 5,
|
||||
"messageId": "noInferrableType",
|
||||
"nodeType": "VariableDeclarator",
|
||||
"ruleId": "@typescript-eslint/no-inferrable-types",
|
||||
},
|
||||
{
|
||||
"column": 7,
|
||||
"endColumn": 12,
|
||||
"endLine": 5,
|
||||
"line": 5,
|
||||
"messageId": "unusedVar",
|
||||
"nodeType": null,
|
||||
"ruleId": "@typescript-eslint/no-unused-vars",
|
||||
},
|
||||
{
|
||||
"column": 10,
|
||||
"endColumn": 13,
|
||||
"endLine": 8,
|
||||
"line": 8,
|
||||
"messageId": "unusedVar",
|
||||
"nodeType": null,
|
||||
"ruleId": "@typescript-eslint/no-unused-vars",
|
||||
},
|
||||
{
|
||||
"column": 14,
|
||||
"endColumn": 26,
|
||||
"endLine": 17,
|
||||
"line": 17,
|
||||
"messageId": "anyAssignment",
|
||||
"nodeType": "VariableDeclarator",
|
||||
"ruleId": "@typescript-eslint/no-unsafe-assignment",
|
||||
},
|
||||
{
|
||||
"column": 20,
|
||||
"endColumn": 24,
|
||||
"endLine": 17,
|
||||
"line": 17,
|
||||
"messageId": "unsafeCall",
|
||||
"nodeType": "Identifier",
|
||||
"ruleId": "@typescript-eslint/no-unsafe-call",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`'recommended' > Plain TS (good) 1`] = `[]`;
|
||||
|
||||
exports[`'recommended' > React JSX (bad) 1`] = `
|
||||
[
|
||||
{
|
||||
"column": 10,
|
||||
"endColumn": 13,
|
||||
"endLine": 4,
|
||||
"line": 4,
|
||||
"messageId": "unusedVar",
|
||||
"nodeType": "Identifier",
|
||||
"ruleId": "no-unused-vars",
|
||||
},
|
||||
{
|
||||
"column": 20,
|
||||
"endColumn": 24,
|
||||
"endLine": 14,
|
||||
"line": 14,
|
||||
"messageId": "undef",
|
||||
"nodeType": "Identifier",
|
||||
"ruleId": "no-undef",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`'recommended' > React JSX (good) 1`] = `[]`;
|
||||
|
||||
exports[`'recommended' > plain JS (bad) 1`] = `
|
||||
[
|
||||
{
|
||||
"column": 10,
|
||||
"endColumn": 13,
|
||||
"endLine": 4,
|
||||
"line": 4,
|
||||
"messageId": "unusedVar",
|
||||
"nodeType": "Identifier",
|
||||
"ruleId": "no-unused-vars",
|
||||
},
|
||||
{
|
||||
"column": 26,
|
||||
"endColumn": 46,
|
||||
"endLine": 12,
|
||||
"line": 12,
|
||||
"messageId": "noJSXWithExtension",
|
||||
"nodeType": "JSXElement",
|
||||
"ruleId": "react/jsx-filename-extension",
|
||||
},
|
||||
{
|
||||
"column": 20,
|
||||
"endColumn": 24,
|
||||
"endLine": 15,
|
||||
"line": 15,
|
||||
"messageId": "undef",
|
||||
"nodeType": "Identifier",
|
||||
"ruleId": "no-undef",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`'recommended' > plain JS (good) 1`] = `[]`;
|
110
test/eslint.test.mjs
Normal file
110
test/eslint.test.mjs
Normal file
|
@ -0,0 +1,110 @@
|
|||
import { ESLint } from 'eslint'
|
||||
import path from 'path'
|
||||
import util from 'util'
|
||||
import { beforeAll, describe, expect, test } from 'vitest'
|
||||
|
||||
/**
|
||||
* @typedef {object} EslintTestInfo
|
||||
* @property {string} name - the title/message for this test
|
||||
* @property {string} filePath - the path to the file to lint
|
||||
* @property {number} warningCount - the number of warnings to expect
|
||||
* @property {number} errorCount - the number of errors to expect
|
||||
*/
|
||||
|
||||
// TSX is omitted because TypeScript insists on fully knowing the React types,
|
||||
// and I would rather not add React as a dependency of eslint-config-scratch.
|
||||
/** @type {Record<string, EslintTestInfo[]>} */
|
||||
const testInfo = {
|
||||
recommended: [
|
||||
{
|
||||
name: 'plain JS (good)',
|
||||
filePath: 'plain.good.mjs',
|
||||
warningCount: 0,
|
||||
errorCount: 0,
|
||||
},
|
||||
{
|
||||
name: 'React JSX (good)',
|
||||
filePath: 'react.good.jsx',
|
||||
warningCount: 0,
|
||||
errorCount: 0,
|
||||
},
|
||||
{
|
||||
name: 'plain JS (bad)',
|
||||
filePath: 'plain.bad.mjs',
|
||||
warningCount: 0,
|
||||
errorCount: 3,
|
||||
},
|
||||
{
|
||||
name: 'React JSX (bad)',
|
||||
filePath: 'react.bad.jsx',
|
||||
warningCount: 0,
|
||||
errorCount: 2,
|
||||
},
|
||||
{
|
||||
name: 'Plain TS (good)',
|
||||
filePath: 'plain.good.ts',
|
||||
warningCount: 0,
|
||||
errorCount: 0,
|
||||
},
|
||||
{
|
||||
name: 'Plain TS (bad)',
|
||||
filePath: 'plain.bad.ts',
|
||||
warningCount: 0,
|
||||
errorCount: 5,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a snapshot of a lint message.
|
||||
* Excludes properties that may change without affecting correctness, such as human-readable text.
|
||||
* @param {ESLint.LintMessage} result - the lint message to filter
|
||||
* @returns {object} a filtered snapshot of the lint message
|
||||
*/
|
||||
const messageSnapshot = result =>
|
||||
Object.fromEntries(
|
||||
Object.entries(result).filter(([k]) =>
|
||||
['line', 'column', 'endLine', 'endColumn', 'messageId', 'nodeType', 'ruleId'].includes(k),
|
||||
),
|
||||
)
|
||||
|
||||
test('make sure eslint works at all', async () => {
|
||||
const source = 'foo(42)'
|
||||
const eslint = new ESLint({
|
||||
overrideConfigFile: true,
|
||||
})
|
||||
const results = await eslint.lintText(source)
|
||||
expect(results).toBeDefined()
|
||||
expect(results.length).toBe(1)
|
||||
expect(results[0].warningCount).toEqual(0)
|
||||
expect(results[0].errorCount).toEqual(0)
|
||||
})
|
||||
|
||||
describe.concurrent.for(Object.entries(testInfo))('$0', ([subdir, testList]) => {
|
||||
/**
|
||||
* @type {ESLint.LintResult[]}
|
||||
*/
|
||||
let results
|
||||
|
||||
// Linting one file at a time takes much longer
|
||||
beforeAll(async () => {
|
||||
const eslint = new ESLint({
|
||||
overrideConfigFile: path.resolve(import.meta.dirname, subdir, 'eslint.config.mjs'),
|
||||
})
|
||||
results = await eslint.lintFiles(testList.map(info => path.resolve(import.meta.dirname, subdir, info.filePath)))
|
||||
})
|
||||
|
||||
test('results container', () => {
|
||||
expect(results).toBeDefined()
|
||||
expect(results.length).toBe(testList.length)
|
||||
})
|
||||
|
||||
testList.forEach(({ name, filePath, warningCount, errorCount }, i) => {
|
||||
test(name, () => {
|
||||
expect(path.resolve(results[i].filePath)).toBe(path.resolve(import.meta.dirname, subdir, filePath))
|
||||
expect(results[i].warningCount, util.inspect(results[i])).toBe(warningCount)
|
||||
expect(results[i].errorCount, util.inspect(results[i])).toBe(errorCount)
|
||||
expect(results[i].messages.map(messageSnapshot), util.inspect(results[i].messages)).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
14
test/recommended/eslint.config.mjs
Normal file
14
test/recommended/eslint.config.mjs
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { eslintConfigScratch } from '../../lib/index.mjs'
|
||||
|
||||
const config = eslintConfigScratch.config(eslintConfigScratch.recommended, {
|
||||
files: ['**/*.ts', '**/*.tsx'],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
project: ['./tsconfig.json'],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
export default config
|
15
test/recommended/plain.bad.mjs
Normal file
15
test/recommended/plain.bad.mjs
Normal file
|
@ -0,0 +1,15 @@
|
|||
import ESLint from 'eslint'
|
||||
|
||||
// foo isn't used
|
||||
function foo() {
|
||||
const eslint = new ESLint({
|
||||
overrideConfigFile: true,
|
||||
})
|
||||
return eslint
|
||||
}
|
||||
|
||||
// React isn't allowed in plain JS
|
||||
export const myElement = <div>{'hello'}</div>
|
||||
|
||||
// foo2 isn't defined
|
||||
export const bar = foo2()
|
17
test/recommended/plain.bad.ts
Normal file
17
test/recommended/plain.bad.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { ESLint } from 'eslint'
|
||||
|
||||
// @typescript-eslint/no-inferrable-types
|
||||
// @typescript-eslint/no-unused-vars
|
||||
const forty: number = 40
|
||||
|
||||
// @typescript-eslint/no-unused-vars
|
||||
function foo(): ESLint {
|
||||
const eslint = new ESLint({
|
||||
overrideConfigFile: true,
|
||||
})
|
||||
return eslint
|
||||
}
|
||||
|
||||
// @typescript-eslint/no-unsafe-call (`foo2` is an error-typed value)
|
||||
// @typescript-eslint/no-unsafe-assignment (`foo2()` is an error-typed value)
|
||||
export const bar = foo2()
|
10
test/recommended/plain.good.mjs
Normal file
10
test/recommended/plain.good.mjs
Normal file
|
@ -0,0 +1,10 @@
|
|||
import ESLint from 'eslint'
|
||||
|
||||
function foo() {
|
||||
const eslint = new ESLint({
|
||||
overrideConfigFile: true,
|
||||
})
|
||||
return eslint
|
||||
}
|
||||
|
||||
export const bar = foo()
|
10
test/recommended/plain.good.ts
Normal file
10
test/recommended/plain.good.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { ESLint } from 'eslint'
|
||||
|
||||
function foo(): ESLint {
|
||||
const eslint = new ESLint({
|
||||
overrideConfigFile: true,
|
||||
})
|
||||
return eslint
|
||||
}
|
||||
|
||||
export const bar = foo()
|
14
test/recommended/react.bad.jsx
Normal file
14
test/recommended/react.bad.jsx
Normal file
|
@ -0,0 +1,14 @@
|
|||
import ESLint from 'eslint'
|
||||
|
||||
// foo isn't used
|
||||
function foo() {
|
||||
const eslint = new ESLint({
|
||||
overrideConfigFile: true,
|
||||
})
|
||||
return eslint
|
||||
}
|
||||
|
||||
export const myElement = <div>{'hello'}</div>
|
||||
|
||||
// foo2 isn't defined
|
||||
export const bar = foo2()
|
12
test/recommended/react.good.jsx
Normal file
12
test/recommended/react.good.jsx
Normal file
|
@ -0,0 +1,12 @@
|
|||
import ESLint from 'eslint'
|
||||
|
||||
function foo() {
|
||||
const eslint = new ESLint({
|
||||
overrideConfigFile: true,
|
||||
})
|
||||
return eslint
|
||||
}
|
||||
|
||||
export const myElement = <div>{'hello'}</div>
|
||||
|
||||
export const bar = foo()
|
6
test/recommended/tsconfig.json
Normal file
6
test/recommended/tsconfig.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"strict": true
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.tsx"]
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue