mirror of
https://github.com/scratchfoundation/eslint-config-scratch.git
synced 2025-06-12 05:12:34 -04:00
test: add tests for recommended rule set
This commit is contained in:
parent
d01085335c
commit
6b37df906c
14 changed files with 1629 additions and 24 deletions
35
README.md
35
README.md
|
@ -12,28 +12,31 @@ 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 and, optionally, configure parser options for rules that require type
|
||||
information:
|
||||
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 { eslintConfigScratch } from 'eslint-config-scratch'
|
||||
|
||||
// for plain JavaScript:
|
||||
export default eslintConfigScratch.recommended
|
||||
|
||||
// for a TypeScript project:
|
||||
export default eslintConfigScratch.config(
|
||||
eslintConfigScratch.recommended,
|
||||
{
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
projectService: true,
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
}
|
||||
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
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
import { globalIgnores } from 'eslint/config'
|
||||
import globals from 'globals'
|
||||
import { eslintConfigScratch } from './lib/index.mjs'
|
||||
|
||||
export default eslintConfigScratch.config(eslintConfigScratch.recommended, {
|
||||
languageOptions: {
|
||||
globals: globals.node,
|
||||
export default eslintConfigScratch.config(
|
||||
eslintConfigScratch.recommended,
|
||||
{
|
||||
languageOptions: {
|
||||
globals: globals.node,
|
||||
},
|
||||
},
|
||||
})
|
||||
globalIgnores(['test/**/*.bad.*']),
|
||||
)
|
||||
|
|
1277
package-lock.json
generated
1277
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -6,8 +6,9 @@
|
|||
"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",
|
||||
|
@ -54,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": {
|
||||
|
|
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