diff --git a/REUSE.toml b/REUSE.toml index d8afb4e..38f3fb2 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -1,5 +1,6 @@ # SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors # SPDX-License-Identifier: AGPL-3.0-or-later + version = 1 SPDX-PackageName = "eslint-config" SPDX-PackageSupplier = "Nextcloud " @@ -12,7 +13,19 @@ SPDX-FileCopyrightText = "2019 Nextcloud GmbH and Nextcloud contributors" SPDX-License-Identifier = "AGPL-3.0-or-later" [[annotations]] -path = ["tsconfig.json"] +path = ["tsconfig.json", "tests/tsconfig.json"] precedence = "aggregate" SPDX-FileCopyrightText = "2025 Nextcloud GmbH and Nextcloud contributors" SPDX-License-Identifier = "CC0-1.0" + +# Test fixtures might not be able to have headers (as e.g. the header is tested) +[[annotations]] +path = [ + "tests/fixtures/codestyle/input/*.ts", + "tests/fixtures/codestyle/input/*.js", + "tests/fixtures/codestyle/output/*.ts", + "tests/fixtures/codestyle/output/*.js" +] +precedence = "aggregate" +SPDX-FileCopyrightText = "2025 Nextcloud GmbH and Nextcloud contributors" +SPDX-License-Identifier = "AGPL-3.0-or-later" diff --git a/eslint.config.js b/eslint.config.js index 4c7c06b..bd041a1 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -6,5 +6,10 @@ import { recommended } from './dist/index.mjs' export default [ + { + ignores: [ + 'tests/fixtures/', + ], + }, ...recommended, ] diff --git a/package.json b/package.json index 2972504..b68d7e2 100644 --- a/package.json +++ b/package.json @@ -35,9 +35,9 @@ ], "scripts": { "build": "vite --mode production build", - "lint": "eslint lib *.js *.ts", - "lint:fix": "eslint --fix lib *.js *.ts", - "test": "jest" + "lint": "eslint", + "lint:fix": "eslint --fix", + "test": "vitest" }, "dependencies": { "@eslint/json": "^0.9.0", @@ -52,7 +52,8 @@ "@nextcloud/vite-config": "^2.3.0", "@types/node": "^22.10.6", "eslint": "^9.18.0", - "vite": "^6.0.7" + "vite": "^6.0.7", + "vitest": "^2.1.8" }, "peerDependencies": { "eslint": "^9" diff --git a/tests/config-codestyle.test.ts b/tests/config-codestyle.test.ts new file mode 100644 index 0000000..368b6ff --- /dev/null +++ b/tests/config-codestyle.test.ts @@ -0,0 +1,35 @@ +/** + * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +import type { Linter } from 'eslint' + +import { ESLint } from 'eslint' +import { expect, test } from 'vitest' +import * as path from 'path' +import * as eslintConfig from '../lib/index.js' + +const eslint = new ESLint({ + fix: true, + overrideConfigFile: true, + overrideConfig: eslintConfig.recommended as Linter.Config, +}) + +const lintFile = async (file) => { + const real = path.resolve(path.join(__dirname, file)) + return await eslint.lintFiles(real) +} + +test.for([ + 'array', + 'arrow-function', + 'function', + 'indention', + 'objects', + 'quotes', + 'semicolons', +])('Code style', async (testCase: string) => { + const results = await lintFile(`fixtures/codestyle/input/${testCase}.{t,j}s`) + expect(results).toHaveLength(1) + expect(results[0].output).toMatchFileSnapshot(results[0].filePath.replace('input', 'output')) +}) diff --git a/tests/eslint-config.test.ts b/tests/config-javascript.test.ts similarity index 59% rename from tests/eslint-config.test.ts rename to tests/config-javascript.test.ts index 6a001e2..c4f90c3 100644 --- a/tests/eslint-config.test.ts +++ b/tests/config-javascript.test.ts @@ -2,14 +2,16 @@ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -import { ESLint } from "eslint" -import type { Linter } from "eslint" -import * as path from 'path' -import * as eslintConfig from '../index.js' +import type { Linter } from 'eslint' +import { ESLint } from 'eslint' +import { expect, test } from 'vitest' +import * as path from 'path' +import * as eslintConfig from '../lib/index.js' const eslint = new ESLint({ - baseConfig: eslintConfig as unknown as Linter.Config + overrideConfigFile: true, + overrideConfig: eslintConfig.recommendedJavascript as Linter.Config[], }) const lintFile = async (file) => { @@ -20,8 +22,12 @@ const lintFile = async (file) => { test('some basic issues should fail', async () => { const results = await lintFile('fixtures/example-fail.js') expect(results).toHaveIssueCount(3) - expect(results).toHaveIssue('spaced-comment') - expect(results).toHaveIssue({ ruleId: 'no-console', line: 3 }) + expect(results).toHaveIssue('@stylistic/spaced-comment') + expect(results).toHaveIssue('@stylistic/semi') + expect(results).toHaveIssue({ + ruleId: 'no-console', + line: 7, + }) }) test('TSX is linted', async () => { @@ -29,15 +35,20 @@ test('TSX is linted', async () => { expect(ignored).toBe(false) const results = await lintFile('fixtures/some.tsx') - expect(results).toHaveIssue({ruleId: 'jsdoc/check-tag-names', line: 5}) - expect(results).toHaveIssue({ruleId: '@typescript-eslint/no-unused-vars', line: 7}) + expect(results).toHaveIssue({ + ruleId: 'jsdoc/check-tag-names', + line: 10, + }) + expect(results).toHaveIssue({ + ruleId: '@typescript-eslint/no-unused-vars', + line: 12, + }) expect(results).toHaveIssueCount(2) }) test('ignore camelcase for webpack', async () => { const results = await lintFile('fixtures/webpack-nonce.js') - expect(results).toHaveIssueCount(1) - expect(results).toHaveIssue({ruleId: 'no-undef', line: 2 }) + expect(results).toHaveIssueCount(0) }) test('works with Vue Composition API', async () => { diff --git a/tests/eslint-typescript-config.test.ts b/tests/config-typescript.test.ts similarity index 65% rename from tests/eslint-typescript-config.test.ts rename to tests/config-typescript.test.ts index 021074f..f82a26d 100644 --- a/tests/eslint-typescript-config.test.ts +++ b/tests/config-typescript.test.ts @@ -2,13 +2,16 @@ * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors * SPDX-License-Identifier: AGPL-3.0-or-later */ -import { ESLint } from 'eslint' import type { Linter } from 'eslint' + +import { ESLint } from 'eslint' +import { expect, test } from 'vitest' import * as path from 'path' -import * as eslintConfig from '../typescript.js' +import * as eslintConfig from '../lib/index.js' const eslint = new ESLint({ - baseConfig: eslintConfig as unknown as Linter.Config, + overrideConfigFile: true, + overrideConfig: eslintConfig.recommended as Linter.Config, }) const lintFile = async (file) => { @@ -23,5 +26,10 @@ test('works with Typescript + Vue', async () => { test('Typescript overrides have higher priority than vue', async () => { const results = await lintFile('fixtures/typescript-vue-overrides.vue') - expect(results).toHaveIssueCount(0) + expect(results).toHaveIssueCount(1) + // Expect "'@type' is redundant when using a type system." + expect(results).toHaveIssue({ + ruleId: 'jsdoc/check-tag-names', + line: 15, + }) }) diff --git a/tests/eslint.d.ts b/tests/eslint.d.ts deleted file mode 100644 index 690cc6f..0000000 --- a/tests/eslint.d.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors - * SPDX-License-Identifier: AGPL-3.0-or-later - */ -export { } - -declare global { - namespace jest { - interface Matchers { - toPass(): R; - toHaveIssueCount(n: number): R; - toHaveIssue(issue: string | {ruleId: string, line?: number}): R; - } - } -} diff --git a/tests/fixtures/codestyle/input/array.js b/tests/fixtures/codestyle/input/array.js new file mode 100644 index 0000000..c07e05a --- /dev/null +++ b/tests/fixtures/codestyle/input/array.js @@ -0,0 +1,9 @@ +// this should be multi line +const arr = ['first', 'second', 'third', 'and', 'so', 'on'] + +// this has a missing trailing comma causing too much git diff +const arr2 = [ + 'first', + 'second', + 'third' +] diff --git a/tests/fixtures/codestyle/input/arrow-function.ts b/tests/fixtures/codestyle/input/arrow-function.ts new file mode 100644 index 0000000..c382033 --- /dev/null +++ b/tests/fixtures/codestyle/input/arrow-function.ts @@ -0,0 +1,10 @@ +/** + * An arrow function with the { on the wrong line + */ +const arrow = (name: string) => +{ + // +} + +// Arrow functions should always have parenthesis for readability +const arr = ['myArray'].map(item => item.length) diff --git a/tests/fixtures/codestyle/input/function.ts b/tests/fixtures/codestyle/input/function.ts new file mode 100644 index 0000000..95ada9b --- /dev/null +++ b/tests/fixtures/codestyle/input/function.ts @@ -0,0 +1,46 @@ +/** + * A function with the { on the wrong line + * + * @param name + */ +export function foo(name: string): boolean +{ + return true +} + +/** + * Also a function with the { on the wrong line + * + * @param firstName + * @param lastName + */ +export function bar( + firstName: string, + lastName: string, +): boolean +{ + return false +} + +/** + * Parameters should always be either on the same line or have consistent new lines + * + * @param num + * @param enable + */ +export function doSomething(num: number, + enable: boolean) { + // ... +} + +/** + * Same here: Parameters should always be either on the same line or have consistent new lines + * + * @param num + * @param enable + */ +export function doSomethingDifferent( + num: number, enable: boolean +) { + // ... +} \ No newline at end of file diff --git a/tests/fixtures/codestyle/input/indention.js b/tests/fixtures/codestyle/input/indention.js new file mode 100644 index 0000000..7246b5d --- /dev/null +++ b/tests/fixtures/codestyle/input/indention.js @@ -0,0 +1,7 @@ +const obj = { + foo: 'bar' +} + +class Foo { + a = 1 +} \ No newline at end of file diff --git a/tests/fixtures/codestyle/input/objects.js b/tests/fixtures/codestyle/input/objects.js new file mode 100644 index 0000000..324bdab --- /dev/null +++ b/tests/fixtures/codestyle/input/objects.js @@ -0,0 +1,24 @@ +// Only quote if needed +const obj = { + 'noQuotesNeeded': true, + 'quotes-needed': false, +} + +// Prefer shorthand property +const user = 'jdoe' +const obj2 = { + user: user, + id: 123, +} + +// Require spaces around braces +const obj3 = {first: 1} + +// Prefer multi line objects +const obj4 = { first: 1, second: 'two' } + +// Use trailing commas to be git diff friendly +const obj5 = { + first: 1, + second: 2 +} \ No newline at end of file diff --git a/tests/fixtures/codestyle/input/quotes.js b/tests/fixtures/codestyle/input/quotes.js new file mode 100644 index 0000000..426eb3b --- /dev/null +++ b/tests/fixtures/codestyle/input/quotes.js @@ -0,0 +1,5 @@ +const foo = "foo" +const bar = 'bar' +const baz = `baz` +const escape = `Escape 'the' single quote` +const escape2 = "Escape 'the' single quote" \ No newline at end of file diff --git a/tests/fixtures/codestyle/input/semicolons.js b/tests/fixtures/codestyle/input/semicolons.js new file mode 100644 index 0000000..882ae50 --- /dev/null +++ b/tests/fixtures/codestyle/input/semicolons.js @@ -0,0 +1,8 @@ +const text = 'foo'; +console.log(text); + +const other = 'foo' +;[ + '1', + '2', +].map(console.log) \ No newline at end of file diff --git a/tests/fixtures/codestyle/output/array.js b/tests/fixtures/codestyle/output/array.js new file mode 100644 index 0000000..7c752aa --- /dev/null +++ b/tests/fixtures/codestyle/output/array.js @@ -0,0 +1,16 @@ +// this should be multi line +const arr = [ + 'first', + 'second', + 'third', + 'and', + 'so', + 'on', +] + +// this has a missing trailing comma causing too much git diff +const arr2 = [ + 'first', + 'second', + 'third', +] diff --git a/tests/fixtures/codestyle/output/arrow-function.ts b/tests/fixtures/codestyle/output/arrow-function.ts new file mode 100644 index 0000000..0a03df7 --- /dev/null +++ b/tests/fixtures/codestyle/output/arrow-function.ts @@ -0,0 +1,11 @@ +/** + * An arrow function with the { on the wrong line + * + * @param name + */ +const arrow = (name: string) => { + // +} + +// Arrow functions should always have parenthesis for readability +const arr = ['myArray'].map((item) => item.length) diff --git a/tests/fixtures/codestyle/output/function.ts b/tests/fixtures/codestyle/output/function.ts new file mode 100644 index 0000000..911c860 --- /dev/null +++ b/tests/fixtures/codestyle/output/function.ts @@ -0,0 +1,44 @@ +/** + * A function with the { on the wrong line + * + * @param name + */ +export function foo(name: string): boolean { + return true +} + +/** + * Also a function with the { on the wrong line + * + * @param firstName + * @param lastName + */ +export function bar( + firstName: string, + lastName: string, +): boolean { + return false +} + +/** + * Parameters should always be either on the same line or have consistent new lines + * + * @param num + * @param enable + */ +export function doSomething( + num: number, + enable: boolean, +) { + // ... +} + +/** + * Same here: Parameters should always be either on the same line or have consistent new lines + * + * @param num + * @param enable + */ +export function doSomethingDifferent(num: number, enable: boolean) { + // ... +} diff --git a/tests/fixtures/codestyle/output/indention.js b/tests/fixtures/codestyle/output/indention.js new file mode 100644 index 0000000..f53fdc2 --- /dev/null +++ b/tests/fixtures/codestyle/output/indention.js @@ -0,0 +1,7 @@ +const obj = { + foo: 'bar', +} + +class Foo { + a = 1 +} diff --git a/tests/fixtures/codestyle/output/objects.js b/tests/fixtures/codestyle/output/objects.js new file mode 100644 index 0000000..ee438c9 --- /dev/null +++ b/tests/fixtures/codestyle/output/objects.js @@ -0,0 +1,27 @@ +// Only quote if needed +const obj = { + noQuotesNeeded: true, + 'quotes-needed': false, +} + +// Prefer shorthand property +const user = 'jdoe' +const obj2 = { + user, + id: 123, +} + +// Require spaces around braces +const obj3 = { first: 1 } + +// Prefer multi line objects +const obj4 = { + first: 1, + second: 'two', +} + +// Use trailing commas to be git diff friendly +const obj5 = { + first: 1, + second: 2, +} diff --git a/tests/fixtures/codestyle/output/quotes.js b/tests/fixtures/codestyle/output/quotes.js new file mode 100644 index 0000000..0c248e2 --- /dev/null +++ b/tests/fixtures/codestyle/output/quotes.js @@ -0,0 +1,5 @@ +const foo = 'foo' +const bar = 'bar' +const baz = 'baz' +const escape = 'Escape \'the\' single quote' +const escape2 = "Escape 'the' single quote" diff --git a/tests/fixtures/codestyle/output/semicolons.js b/tests/fixtures/codestyle/output/semicolons.js new file mode 100644 index 0000000..96c69bd --- /dev/null +++ b/tests/fixtures/codestyle/output/semicolons.js @@ -0,0 +1,8 @@ +const text = 'foo' +console.log(text) + +const other = 'foo' +;[ + '1', + '2', +].map(console.log) diff --git a/tests/fixtures/composition-test.vue b/tests/fixtures/composition-test.vue index 48976b7..45c2e29 100644 --- a/tests/fixtures/composition-test.vue +++ b/tests/fixtures/composition-test.vue @@ -1,7 +1,7 @@ +-->