diff --git a/.github/workflows/test-js.yml b/.github/workflows/test-js.yml index 7e49a1f43..f52699af9 100644 --- a/.github/workflows/test-js.yml +++ b/.github/workflows/test-js.yml @@ -18,42 +18,29 @@ jobs: - "20" - "latest" ts_version: - - "next" + # - "next" - "latest" - - "4.7.4" + # - "4.7.4" # - "JS" runs-on: ${{ matrix.os }} continue-on-error: ${{ matrix.ts_version == 'next' }} - env: - REPORT_COVERAGE: ${{ fromJSON('["false", "true"]')[matrix.node_version == 'latest' && matrix.os == 'ubuntu-latest'] }} steps: - uses: actions/checkout@v4 - uses: ./.github/actions/prepare - - name: Build - run: pnpm run build:node - - - name: Compile Tests - run: pnpm run build-tests - - name: Setup NodeJs ${{ matrix.node_version }} for testing uses: actions/setup-node@v4 with: node-version: ${{ matrix.node_version }} - - name: Remove Dev TypeScript - run: pnpm remove typescript - - - name: Add TypeScript "${{ matrix.ts_version }}" - if: matrix.ts_version != 'JS' - run: pnpm add -D typescript@"${{ matrix.ts_version }}" + # - name: Add TypeScript "${{ matrix.ts_version }}" for testing + # run: pnpm add -D typescript@"${{ matrix.ts_version }}" - name: Run Tests - run: pnpm test-compiled + run: pnpm test:js-run - name: Report coverage uses: codecov/codecov-action@v4.1.1 - if: env.REPORT_COVERAGE == 'true' with: file: coverage/lcov.info flags: ${{ matrix.ts_version }} diff --git a/knip.jsonc b/knip.jsonc index 2bfc0f6cc..97a70ab7f 100644 --- a/knip.jsonc +++ b/knip.jsonc @@ -4,12 +4,17 @@ "project": ["src/**/*.ts!", "tests/**/*.{js,ts}"], "ignore": ["tests/fixture/file.ts"], "ignoreDependencies": [ + // Unknown reason for issue. + "@vitest/coverage-v8", + + // Lint staged + "tsc-files", + + // Eslint "@stylistic/eslint-plugin", "@rebeccastevens/eslint-config", "@types/eslint", "@typescript-eslint/eslint-plugin", - "@vitest/coverage-istanbul", - "@vitest/coverage-v8", "eslint-config-prettier", "eslint-flat-config-utils", "eslint-import-resolver-typescript", @@ -31,8 +36,7 @@ "eslint-plugin-vitest", "eslint-plugin-yml", "jsonc-eslint-parser", - "prettier", - "tsc-files", "yaml-eslint-parser", + "prettier", ], } diff --git a/package.json b/package.json index 3a30f6aa0..eb9b700af 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,6 @@ "files": ["lib/", "package.json", "LICENSE", "README.md"], "scripts": { "build": "pnpm run build:node && pnpm run build:docs", - "build-tests": "rimraf tests-compiled && tsc -p tsconfig.tests-compiled.json && tsc-alias -p tsconfig.tests-compiled.json", "build:docs": "eslint-doc-generator", "build:node": "rimraf lib && rollup -c rollup.config.ts --configPlugin rollup-plugin-ts", "lint": "eslint && pnpm run lint:md && pnpm lint:eslint-docs && pnpm run lint:spelling && pnpm run lint:knip && pnpm run lint:packages", @@ -63,11 +62,11 @@ "prepare": "husky", "release": "semantic-release", "test": "pnpm run test:js", - "test-compiled": "USE_COMPILED_TESTS=1 vitest run --coverage", "test-work": "vitest", "test:js": "vitest --coverage", + "test:js-run": "vitest run --coverage", "typecheck": "tsc -p tsconfig.build.json --noEmit", - "verify": "pnpm run lint && pnpm run typecheck && pnpm run build-tests && pnpm run test-compiled" + "verify": "pnpm run lint && pnpm run typecheck && pnpm run test:js-run" }, "overrides": { "eslint-plugin-functional": "link:." @@ -92,12 +91,9 @@ "@stylistic/eslint-plugin": "2.6.1", "@types/dedent": "0.7.2", "@types/eslint": "9.6.0", - "@types/espree": "10.1.0", "@types/node": "18.18.0", "@typescript-eslint/eslint-plugin": "8.0.0", "@typescript-eslint/parser": "8.0.0", - "@typescript-eslint/rule-tester": "8.0.0", - "@vitest/coverage-istanbul": "2.0.5", "@vitest/coverage-v8": "2.0.5", "cspell": "8.13.1", "deassert": "1.0.2", @@ -125,7 +121,7 @@ "eslint-plugin-unicorn": "55.0.0", "eslint-plugin-vitest": "0.5.4", "eslint-plugin-yml": "1.14.0", - "espree": "10.1.0", + "eslint-vitest-rule-tester": "0.3.3", "fast-glob": "3.3.2", "husky": "9.1.4", "jsonc-eslint-parser": "2.4.0", @@ -139,7 +135,6 @@ "rollup-plugin-ts": "3.4.5", "semantic-release": "24.0.0", "semantic-release-replace-plugin": "1.2.7", - "tsc-alias": "1.8.10", "tsc-files": "1.1.4", "tsx": "4.16.5", "typescript": "5.5.4", @@ -159,5 +154,10 @@ "packageManager": "pnpm@9.6.0", "engines": { "node": ">=v18.18.0" + }, + "pnpm": { + "patchedDependencies": { + "eslint-vitest-rule-tester@0.3.3": "patches/eslint-vitest-rule-tester@0.3.3.patch" + } } } diff --git a/patches/eslint-vitest-rule-tester@0.3.3.patch b/patches/eslint-vitest-rule-tester@0.3.3.patch new file mode 100644 index 000000000..9ceece7f5 --- /dev/null +++ b/patches/eslint-vitest-rule-tester@0.3.3.patch @@ -0,0 +1,209 @@ +diff --git a/dist/index.d.mts b/dist/index.d.mts +index f982c4d7c06d00d8f1e9f7515951582be0cc1f59..7245940c64de19f60428239bcccca0658bb64a7b 100644 +--- a/dist/index.d.mts ++++ b/dist/index.d.mts +@@ -93,6 +93,12 @@ interface RuleTesterBehaviorOptions { + */ + verifyFixChanges?: boolean; + } ++interface DefaultFilenames { ++ js: string; ++ jsx: string; ++ ts: string; ++ tsx: string; ++} + interface RuleTesterInitOptions extends CompatConfigOptions, RuleTesterBehaviorOptions { + /** + * The rule to test +@@ -106,6 +112,11 @@ interface RuleTesterInitOptions extends CompatConfigOptions, RuleTesterBehaviorO + * Additional flat configs to be merged with the rule config + */ + configs?: Linter.FlatConfig | Linter.FlatConfig[]; ++ /** ++ * The default filenames to use for type-aware tests. ++ * @default { js: 'file.js', jsx: 'react.jsx', ts: 'file.ts', tsx: 'react.tsx' } ++ */ ++ defaultFilenames?: Partial; + } + interface TestCasesOptions { + valid?: (ValidTestCase | string)[]; +@@ -116,8 +127,10 @@ interface TestCasesOptions { + onResult?: (_case: NormalizedTestCase, result: Linter.FixReport) => void | Promise; + } + +-declare function normalizeTestCase(c: TestCase, type?: 'valid' | 'invalid'): NormalizedTestCase; ++declare function normalizeTestCase(c: TestCase, languageOptions: Linter.FlatConfig['languageOptions'], defaultFilenames: DefaultFilenames, type?: 'valid' | 'invalid'): NormalizedTestCase; + declare function normalizeCaseError(error: TestCaseError | string, rule?: RuleModule): Partial; ++declare function isUsingTypeScriptParser(languageOptions: Linter.FlatConfig['languageOptions']): boolean; ++declare function isUsingTypeScriptTypings(languageOptions: Linter.FlatConfig['languageOptions']): any; + + declare function createRuleTester(options: RuleTesterInitOptions): RuleTester; + +@@ -132,4 +145,4 @@ declare function runClassic(ruleName: string, rule: RuleModule, cases: TestCases + + declare function pickFlatConfigFromOptions(options: CompatConfigOptions): Linter.FlatConfig | undefined; + +-export { type CompatConfigOptions, type InvalidTestCase, type InvalidTestCaseBase, type NormalizedTestCase, type RuleModule, type RuleTester, type RuleTesterBehaviorOptions, type RuleTesterInitOptions, type TestCase, type TestCaseError, type TestCasesOptions, type TestExecutionResult, type ValidTestCase, type ValidTestCaseBase, createRuleTester, normalizeCaseError, normalizeTestCase, pickFlatConfigFromOptions, run, runClassic }; ++export { type CompatConfigOptions, type DefaultFilenames, type InvalidTestCase, type InvalidTestCaseBase, type NormalizedTestCase, type RuleModule, type RuleTester, type RuleTesterBehaviorOptions, type RuleTesterInitOptions, type TestCase, type TestCaseError, type TestCasesOptions, type TestExecutionResult, type ValidTestCase, type ValidTestCaseBase, createRuleTester, isUsingTypeScriptParser, isUsingTypeScriptTypings, normalizeCaseError, normalizeTestCase, pickFlatConfigFromOptions, run, runClassic }; +diff --git a/dist/index.d.ts b/dist/index.d.ts +index f982c4d7c06d00d8f1e9f7515951582be0cc1f59..7245940c64de19f60428239bcccca0658bb64a7b 100644 +--- a/dist/index.d.ts ++++ b/dist/index.d.ts +@@ -93,6 +93,12 @@ interface RuleTesterBehaviorOptions { + */ + verifyFixChanges?: boolean; + } ++interface DefaultFilenames { ++ js: string; ++ jsx: string; ++ ts: string; ++ tsx: string; ++} + interface RuleTesterInitOptions extends CompatConfigOptions, RuleTesterBehaviorOptions { + /** + * The rule to test +@@ -106,6 +112,11 @@ interface RuleTesterInitOptions extends CompatConfigOptions, RuleTesterBehaviorO + * Additional flat configs to be merged with the rule config + */ + configs?: Linter.FlatConfig | Linter.FlatConfig[]; ++ /** ++ * The default filenames to use for type-aware tests. ++ * @default { js: 'file.js', jsx: 'react.jsx', ts: 'file.ts', tsx: 'react.tsx' } ++ */ ++ defaultFilenames?: Partial; + } + interface TestCasesOptions { + valid?: (ValidTestCase | string)[]; +@@ -116,8 +127,10 @@ interface TestCasesOptions { + onResult?: (_case: NormalizedTestCase, result: Linter.FixReport) => void | Promise; + } + +-declare function normalizeTestCase(c: TestCase, type?: 'valid' | 'invalid'): NormalizedTestCase; ++declare function normalizeTestCase(c: TestCase, languageOptions: Linter.FlatConfig['languageOptions'], defaultFilenames: DefaultFilenames, type?: 'valid' | 'invalid'): NormalizedTestCase; + declare function normalizeCaseError(error: TestCaseError | string, rule?: RuleModule): Partial; ++declare function isUsingTypeScriptParser(languageOptions: Linter.FlatConfig['languageOptions']): boolean; ++declare function isUsingTypeScriptTypings(languageOptions: Linter.FlatConfig['languageOptions']): any; + + declare function createRuleTester(options: RuleTesterInitOptions): RuleTester; + +@@ -132,4 +145,4 @@ declare function runClassic(ruleName: string, rule: RuleModule, cases: TestCases + + declare function pickFlatConfigFromOptions(options: CompatConfigOptions): Linter.FlatConfig | undefined; + +-export { type CompatConfigOptions, type InvalidTestCase, type InvalidTestCaseBase, type NormalizedTestCase, type RuleModule, type RuleTester, type RuleTesterBehaviorOptions, type RuleTesterInitOptions, type TestCase, type TestCaseError, type TestCasesOptions, type TestExecutionResult, type ValidTestCase, type ValidTestCaseBase, createRuleTester, normalizeCaseError, normalizeTestCase, pickFlatConfigFromOptions, run, runClassic }; ++export { type CompatConfigOptions, type DefaultFilenames, type InvalidTestCase, type InvalidTestCaseBase, type NormalizedTestCase, type RuleModule, type RuleTester, type RuleTesterBehaviorOptions, type RuleTesterInitOptions, type TestCase, type TestCaseError, type TestCasesOptions, type TestExecutionResult, type ValidTestCase, type ValidTestCaseBase, createRuleTester, isUsingTypeScriptParser, isUsingTypeScriptTypings, normalizeCaseError, normalizeTestCase, pickFlatConfigFromOptions, run, runClassic }; +diff --git a/dist/index.mjs b/dist/index.mjs +index b10c991352ad54b17a05f8e41fe6d4b5e1f77ea1..b426a2540622b4bfd31d70598f971a280fe53ec5 100644 +--- a/dist/index.mjs ++++ b/dist/index.mjs +@@ -1,4 +1,6 @@ +-import { objectPick, toArray } from '@antfu/utils'; ++import path from 'node:path'; ++import process from 'node:process'; ++import { objectPick, deepMerge, toArray } from '@antfu/utils'; + export { unindent as $, unindent } from '@antfu/utils'; + import { Linter } from 'eslint'; + import { expect, describe, it } from 'vitest'; +@@ -17,10 +19,21 @@ function interpolate(text, data) { + ); + } + +-function normalizeTestCase(c, type) { ++function normalizeTestCase(c, languageOptions, defaultFilenames, type) { + const obj = typeof c === "string" ? { code: c } : { ...c }; + const normalized = obj; + normalized.type || (normalized.type = type || ("errors" in obj || "output" in obj ? "invalid" : "valid")); ++ if (isUsingTypeScriptParser(languageOptions)) { ++ normalized.filename || (normalized.filename = getDefaultTypeScriptFilename(languageOptions, defaultFilenames)); ++ normalized.parserOptions = { ++ ecmaVersion: "latest", ++ sourceType: "module", ++ disallowAutomaticSingleRunInference: true, ++ ...normalized.parserOptions ++ }; ++ } else { ++ normalized.filename || (normalized.filename = getDefaultJavaScriptFilename(languageOptions, defaultFilenames)); ++ } + return normalized; + } + function normalizeCaseError(error, rule) { +@@ -46,6 +59,20 @@ function normalizeCaseError(error, rule) { + } + return clone; + } ++function getDefaultJavaScriptFilename(languageOptions, defaultFilenames) { ++ return languageOptions?.parserOptions?.ecmaFeatures?.jsx ? defaultFilenames.jsx : defaultFilenames.js; ++} ++function getDefaultTypeScriptFilename(languageOptions, defaultFilenames) { ++ const rootPath = (isUsingTypeScriptTypings(languageOptions) ? languageOptions?.parserOptions?.tsconfigRootDir : void 0) ?? process.cwd(); ++ const filename = languageOptions?.parserOptions?.ecmaFeatures?.jsx ? defaultFilenames.tsx : defaultFilenames.ts; ++ return path.join(rootPath, filename); ++} ++function isUsingTypeScriptParser(languageOptions) { ++ return languageOptions?.parser?.meta?.name === "typescript-eslint/parser"; ++} ++function isUsingTypeScriptTypings(languageOptions) { ++ return languageOptions?.parserOptions?.program || languageOptions?.parserOptions?.project || languageOptions?.parserOptions?.projectService; ++} + + const BOM = "\uFEFF"; + function compareMessagesByFixRange(a, b) { +@@ -139,15 +166,32 @@ function pickFlatConfigFromOptions(options) { + } + + function createRuleTester(options) { +- const linter = new Linter({ configType: "flat" }); ++ const languageOptions = deepMerge( ++ options.languageOptions ?? { ++ parser: options.parser, ++ parserOptions: options.parserOptions ++ }, ++ ...toArray(options.configs).map((c) => c.languageOptions).filter((c) => c !== void 0) ++ ); ++ const linter = new Linter({ ++ configType: "flat", ++ cwd: isUsingTypeScriptParser(options) ? languageOptions?.parserOptions?.tsconfigRootDir : void 0 ++ }); + const defaultConfigs = toArray(options.configs); + { + const inlineConfig = pickFlatConfigFromOptions(options); + if (inlineConfig) + defaultConfigs.unshift(inlineConfig); + } ++ const defaultFilenames = { ++ js: "file.js", ++ ts: "file.ts", ++ jsx: "react.jsx", ++ tsx: "react.tsx", ++ ...options.defaultFilenames ++ }; + function each(c) { +- const testcase = normalizeTestCase(c); ++ const testcase = normalizeTestCase(c, languageOptions, defaultFilenames); + const { + recursive = 10, + verifyAfterFix = true, +@@ -260,7 +304,7 @@ ${result.output} + if (cases.valid?.length) { + describe("valid", () => { + for (const c of cases.valid) { +- const _case = normalizeTestCase(c, "valid"); ++ const _case = normalizeTestCase(c, languageOptions, defaultFilenames, "valid"); + let run2 = it; + if (_case.only) + run2 = it.only; +@@ -276,7 +320,7 @@ ${result.output} + if (cases.invalid?.length) { + describe("invalid", () => { + for (const c of cases.invalid) { +- const _case = normalizeTestCase(c, "invalid"); ++ const _case = normalizeTestCase(c, languageOptions, defaultFilenames, "invalid"); + let run2 = it; + if (_case.only) + run2 = it.only; +@@ -312,4 +356,4 @@ function runClassic(ruleName, rule, cases, options) { + return tester.run(cases); + } + +-export { createRuleTester, normalizeCaseError, normalizeTestCase, pickFlatConfigFromOptions, run, runClassic }; ++export { createRuleTester, isUsingTypeScriptParser, isUsingTypeScriptTypings, normalizeCaseError, normalizeTestCase, pickFlatConfigFromOptions, run, runClassic }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8934637ec..f66fde825 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,11 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +patchedDependencies: + eslint-vitest-rule-tester@0.3.3: + hash: wjur6dtc3pi5ag5ibkjvm56goy + path: patches/eslint-vitest-rule-tester@0.3.3.patch + devDependencies: '@babel/eslint-parser': specifier: 7.25.1 @@ -242,9 +247,6 @@ importers: '@types/eslint': specifier: 9.6.0 version: 9.6.0 - '@types/espree': - specifier: 10.1.0 - version: 10.1.0 '@types/node': specifier: 18.18.0 version: 18.18.0 @@ -254,12 +256,6 @@ importers: '@typescript-eslint/parser': specifier: 8.0.0 version: 8.0.0(eslint@9.8.0)(typescript@5.5.4) - '@typescript-eslint/rule-tester': - specifier: 8.0.0 - version: 8.0.0(@eslint/eslintrc@3.1.0)(eslint@9.8.0)(typescript@5.5.4) - '@vitest/coverage-istanbul': - specifier: 2.0.5 - version: 2.0.5(vitest@2.0.5(@types/node@18.18.0)) '@vitest/coverage-v8': specifier: 2.0.5 version: 2.0.5(vitest@2.0.5(@types/node@18.18.0)) @@ -341,9 +337,9 @@ importers: eslint-plugin-yml: specifier: 1.14.0 version: 1.14.0(eslint@9.8.0) - espree: - specifier: 10.1.0 - version: 10.1.0 + eslint-vitest-rule-tester: + specifier: 0.3.3 + version: 0.3.3(patch_hash=wjur6dtc3pi5ag5ibkjvm56goy)(eslint@9.8.0)(typescript@5.5.4)(vitest@2.0.5(@types/node@18.18.0)) fast-glob: specifier: 3.3.2 version: 3.3.2 @@ -383,9 +379,6 @@ importers: semantic-release-replace-plugin: specifier: 1.2.7 version: 1.2.7(semantic-release@24.0.0(typescript@5.5.4)) - tsc-alias: - specifier: 1.8.10 - version: 1.8.10 tsc-files: specifier: 1.1.4 version: 1.1.4(typescript@5.5.4) @@ -414,6 +407,9 @@ packages: '@antfu/install-pkg@0.3.3': resolution: {integrity: sha512-nHHsk3NXQ6xkCfiRRC8Nfrg8pU5kkr3P3Y9s9dKqiuRmBD0Yap7fymNDjGFKeWhZQHqqbCS5CfeMy9wtExM24w==} + '@antfu/utils@0.7.10': + resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==} + '@babel/code-frame@7.24.7': resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} engines: {node: '>=6.9.0'} @@ -1307,12 +1303,12 @@ packages: '@types/dedent@0.7.2': resolution: {integrity: sha512-kRiitIeUg1mPV9yH4VUJ/1uk2XjyANfeL8/7rH1tsjvHeO9PJLBHJIYsFWmAvmGj5u8rj+1TZx7PZzW2qLw3Lw==} + '@types/eslint@8.56.11': + resolution: {integrity: sha512-sVBpJMf7UPo/wGecYOpk2aQya2VUGeHhe38WG7/mN5FufNSubf5VT9Uh9Uyp8/eLJpu1/tuhJ/qTo4mhSB4V4Q==} + '@types/eslint@9.6.0': resolution: {integrity: sha512-gi6WQJ7cHRgZxtkQEoyHMppPjq9Kxo5Tjn2prSKDSmZrCz8TZ3jSRCeTJm+WoM+oB0WG37bRqLzaaU3q7JypGg==} - '@types/espree@10.1.0': - resolution: {integrity: sha512-uPQZdoUWWMuO6WS8/dwX1stZH/vOBa/wAniGnYEFI0IuU9RmLx6PLmo+VGfNOlbRc5I7hBsQc8H0zcdVI37kxg==} - '@types/estree@1.0.5': resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} @@ -1367,13 +1363,6 @@ packages: typescript: optional: true - '@typescript-eslint/rule-tester@8.0.0': - resolution: {integrity: sha512-mYINoxt2DnRDl+X0Er134e6lxTrpb6enfKkea4RIjucd+YjsLzTSSkN40hiU4CB5kOjM17xJVm25TiZJLZMRMw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - '@eslint/eslintrc': '>=2' - eslint: ^8.57.0 || ^9.0.0 - '@typescript-eslint/scope-manager@5.62.0': resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1474,11 +1463,6 @@ packages: resolution: {integrity: sha512-oN0K4nkHuOyF3PVMyETbpP5zp6wfyOvm7tWhTMfoqxSSsPmJIh6JNASuZDlODE8eE+0EB9uar+6+vxr9DBTYOA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@vitest/coverage-istanbul@2.0.5': - resolution: {integrity: sha512-BvjWKtp7fiMAeYUD0mO5cuADzn1gmjTm54jm5qUEnh/O08riczun8rI4EtQlg3bWoRo2lT3FO8DmjPDX9ZthPw==} - peerDependencies: - vitest: 2.0.5 - '@vitest/coverage-v8@2.0.5': resolution: {integrity: sha512-qeFcySCg5FLO2bHHSa0tAZAOnAUbp4L6/A5JDuj9+bt53JREl8hpLjLHEWF0e/gWc8INVpJaqA7+Ene2rclpZg==} peerDependencies: @@ -1573,10 +1557,6 @@ packages: any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - are-docs-informative@0.0.2: resolution: {integrity: sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==} engines: {node: '>=14'} @@ -1635,10 +1615,6 @@ packages: before-after-hook@3.0.2: resolution: {integrity: sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==} - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} - boolean@3.2.0: resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} @@ -1723,10 +1699,6 @@ packages: resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} engines: {node: '>= 16'} - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} - ci-info@4.0.0: resolution: {integrity: sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==} engines: {node: '>=8'} @@ -1803,10 +1775,6 @@ packages: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} - commander@9.5.0: - resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} - engines: {node: ^12.20.0 || >=14} - comment-json@4.2.4: resolution: {integrity: sha512-E5AjpSW+O+N5T2GsOQMHLLsJvrYw6G/AFt9GvU6NguEAfzKShh7hRiLtVo6S9KbRpFMGqE5ojo0/hE+sdteWvQ==} engines: {node: '>= 6'} @@ -2338,6 +2306,12 @@ packages: resolution: {integrity: sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-vitest-rule-tester@0.3.3: + resolution: {integrity: sha512-JVKpK8RaPhE0AYOHIUY9GVb/XKTT7oEHWLY4CLZ2fyI8IQXs/qesqCTkLwC42txOxvndZjrp8HW1lShpRPEKww==} + peerDependencies: + eslint: ^9.0.0 + vitest: ^1.0.0 || ^2.0.0 + eslint@9.8.0: resolution: {integrity: sha512-K8qnZ/QJzT2dLKdZJVX6W4XOwBzutMYmt0lqUS+JdXgd+HTYFlonFgkJ8s44d/zMPPCnOOk0kMWCApCPhiOy9A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2793,10 +2767,6 @@ packages: is-bigint@1.0.4: resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - is-boolean-object@1.1.2: resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} engines: {node: '>= 0.4'} @@ -2937,10 +2907,6 @@ packages: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} engines: {node: '>=8'} - istanbul-lib-instrument@6.0.3: - resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} - engines: {node: '>=10'} - istanbul-lib-report@3.0.1: resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} engines: {node: '>=10'} @@ -3265,10 +3231,6 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - mylas@2.1.13: - resolution: {integrity: sha512-+MrqnJRtxdF+xngFfUUkIMQrUUL0KsxbADUkn23Z/4ibGg192Q+z+CQyiYwvWTsYjJygmMR8+w3ZDa98Zh6ESg==} - engines: {node: '>=12.0.0'} - mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} @@ -3303,10 +3265,6 @@ packages: resolution: {integrity: sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==} engines: {node: ^16.14.0 || >=18.0.0} - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - normalize-url@8.0.1: resolution: {integrity: sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==} engines: {node: '>=14.16'} @@ -3616,10 +3574,6 @@ packages: pkg-types@1.1.3: resolution: {integrity: sha512-+JrgthZG6m3ckicaOB74TwQ+tBWsFl3qVQg7mN8ulwSOElJ7gBhKzj2VkCPnZ4NlF6kEquYU+RIYNVAvzd54UA==} - plimit-lit@1.6.1: - resolution: {integrity: sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==} - engines: {node: '>=12'} - pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} @@ -3675,10 +3629,6 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - queue-lit@1.5.2: - resolution: {integrity: sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==} - engines: {node: '>=12'} - queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -3708,10 +3658,6 @@ packages: readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - refa@0.12.1: resolution: {integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} @@ -4162,10 +4108,6 @@ packages: peerDependencies: typescript: '>=4.0.0' - tsc-alias@1.8.10: - resolution: {integrity: sha512-Ibv4KAWfFkFdKJxnWfVtdOmB0Zi1RJVxcbPGiCDsFpCQSsmpWyuzHG3rQyI5YkobWwxFPEyQfu1hdo4qLG2zPw==} - hasBin: true - tsc-files@1.1.4: resolution: {integrity: sha512-RePsRsOLru3BPpnf237y1Xe1oCGta8rmSYzM76kYo5tLGsv5R2r3s64yapYorGTPuuLyfS9NVbh9ydzmvNie2w==} hasBin: true @@ -4500,6 +4442,8 @@ snapshots: dependencies: '@jsdevtools/ez-spawn': 3.0.4 + '@antfu/utils@0.7.10': {} + '@babel/code-frame@7.24.7': dependencies: '@babel/highlight': 7.24.7 @@ -5324,15 +5268,15 @@ snapshots: '@types/dedent@0.7.2': {} - '@types/eslint@9.6.0': + '@types/eslint@8.56.11': dependencies: '@types/estree': 1.0.5 '@types/json-schema': 7.0.15 - '@types/espree@10.1.0': + '@types/eslint@9.6.0': dependencies: - acorn: 8.12.1 - eslint-visitor-keys: 4.0.0 + '@types/estree': 1.0.5 + '@types/json-schema': 7.0.15 '@types/estree@1.0.5': {} @@ -5389,20 +5333,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/rule-tester@8.0.0(@eslint/eslintrc@3.1.0)(eslint@9.8.0)(typescript@5.5.4)': - dependencies: - '@eslint/eslintrc': 3.1.0 - '@typescript-eslint/typescript-estree': 8.0.0(typescript@5.5.4) - '@typescript-eslint/utils': 8.0.0(eslint@9.8.0)(typescript@5.5.4) - ajv: 6.12.6 - eslint: 9.8.0 - json-stable-stringify-without-jsonify: 1.0.1 - lodash.merge: 4.6.2 - semver: 7.6.3 - transitivePeerDependencies: - - supports-color - - typescript - '@typescript-eslint/scope-manager@5.62.0': dependencies: '@typescript-eslint/types': 5.62.0 @@ -5544,22 +5474,6 @@ snapshots: '@typescript-eslint/types': 8.0.0 eslint-visitor-keys: 3.4.3 - '@vitest/coverage-istanbul@2.0.5(vitest@2.0.5(@types/node@18.18.0))': - dependencies: - '@istanbuljs/schema': 0.1.3 - debug: 4.3.6 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-instrument: 6.0.3 - istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 5.0.6 - istanbul-reports: 3.1.7 - magicast: 0.3.4 - test-exclude: 7.0.1 - tinyrainbow: 1.2.0 - vitest: 2.0.5(@types/node@18.18.0) - transitivePeerDependencies: - - supports-color - '@vitest/coverage-v8@2.0.5(vitest@2.0.5(@types/node@18.18.0))': dependencies: '@ampproject/remapping': 2.3.0 @@ -5677,11 +5591,6 @@ snapshots: any-promise@1.3.0: {} - anymatch@3.1.3: - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - are-docs-informative@0.0.2: {} argparse@2.0.1: {} @@ -5752,8 +5661,6 @@ snapshots: before-after-hook@3.0.2: {} - binary-extensions@2.3.0: {} - boolean@3.2.0: {} bottleneck@2.19.5: {} @@ -5844,18 +5751,6 @@ snapshots: check-error@2.1.1: {} - chokidar@3.6.0: - dependencies: - anymatch: 3.1.3 - braces: 3.0.3 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 - ci-info@4.0.0: {} clean-regexp@1.0.0: @@ -5932,8 +5827,6 @@ snapshots: commander@4.1.1: {} - commander@9.5.0: {} - comment-json@4.2.4: dependencies: array-timsort: 1.0.3 @@ -6641,6 +6534,17 @@ snapshots: eslint-visitor-keys@4.0.0: {} + eslint-vitest-rule-tester@0.3.3(patch_hash=wjur6dtc3pi5ag5ibkjvm56goy)(eslint@9.8.0)(typescript@5.5.4)(vitest@2.0.5(@types/node@18.18.0)): + dependencies: + '@antfu/utils': 0.7.10 + '@types/eslint': 8.56.11 + '@typescript-eslint/utils': 7.18.0(eslint@9.8.0)(typescript@5.5.4) + eslint: 9.8.0 + vitest: 2.0.5(@types/node@18.18.0) + transitivePeerDependencies: + - supports-color + - typescript + eslint@9.8.0: dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@9.8.0) @@ -7151,10 +7055,6 @@ snapshots: dependencies: has-bigints: 1.0.2 - is-binary-path@2.1.0: - dependencies: - binary-extensions: 2.3.0 - is-boolean-object@1.1.2: dependencies: call-bind: 1.0.7 @@ -7271,16 +7171,6 @@ snapshots: istanbul-lib-coverage@3.2.2: {} - istanbul-lib-instrument@6.0.3: - dependencies: - '@babel/core': 7.25.2 - '@babel/parser': 7.25.3 - '@istanbuljs/schema': 0.1.3 - istanbul-lib-coverage: 3.2.2 - semver: 7.6.3 - transitivePeerDependencies: - - supports-color - istanbul-lib-report@3.0.1: dependencies: istanbul-lib-coverage: 3.2.2 @@ -7634,8 +7524,6 @@ snapshots: ms@2.1.3: {} - mylas@2.1.13: {} - mz@2.7.0: dependencies: any-promise: 1.3.0 @@ -7677,8 +7565,6 @@ snapshots: semver: 7.6.3 validate-npm-package-license: 3.0.4 - normalize-path@3.0.0: {} - normalize-url@8.0.1: {} npm-run-path@4.0.1: @@ -7897,10 +7783,6 @@ snapshots: mlly: 1.7.1 pathe: 1.1.2 - plimit-lit@1.6.1: - dependencies: - queue-lit: 1.5.2 - pluralize@8.0.0: {} possible-typed-array-names@1.0.0: {} @@ -7944,8 +7826,6 @@ snapshots: punycode@2.3.1: {} - queue-lit@1.5.2: {} - queue-microtask@1.2.3: {} rc@1.2.8: @@ -7994,10 +7874,6 @@ snapshots: string_decoder: 1.1.1 util-deprecate: 1.0.2 - readdirp@3.6.0: - dependencies: - picomatch: 2.3.1 - refa@0.12.1: dependencies: '@eslint-community/regexpp': 4.11.0 @@ -8466,15 +8342,6 @@ snapshots: minimatch: 10.0.1 typescript: 5.5.4 - tsc-alias@1.8.10: - dependencies: - chokidar: 3.6.0 - commander: 9.5.0 - globby: 11.1.0 - mylas: 2.1.13 - normalize-path: 3.0.0 - plimit-lit: 1.6.1 - tsc-files@1.1.4(typescript@5.5.4): dependencies: typescript: 5.5.4 diff --git a/project-dictionary.txt b/project-dictionary.txt index e2fd461f0..d1a67c6ac 100644 --- a/project-dictionary.txt +++ b/project-dictionary.txt @@ -3,7 +3,6 @@ deassert declarator declarators deepmerge -espree IIFE jonaskello Kello @@ -19,3 +18,4 @@ rulesets sonarjs treeshake TSES +uncompiled diff --git a/tests/common/ignore-options.test.ts b/tests/common/ignore-options.test.ts deleted file mode 100644 index 7e86b5699..000000000 --- a/tests/common/ignore-options.test.ts +++ /dev/null @@ -1,305 +0,0 @@ -import { - type InvalidTestCase, - type ValidTestCase, -} from "@typescript-eslint/rule-tester"; -import dedent from "dedent"; - -import { - type IgnoreAccessorPatternOption, - type IgnoreCodePatternOption, - type IgnoreIdentifierPatternOption, - shouldIgnorePattern, -} from "#/options"; -import { getRuleTester } from "#/tests/helpers/RuleTester"; -import { configs, filename } from "#/tests/helpers/configs"; -import { addFilename, createDummyRule } from "#/tests/helpers/util"; - -/** - * Create a dummy rule that operates on AssignmentExpression nodes. - */ -function createDummyRuleFor(nodeType: string) { - return createDummyRule((context) => { - const [allowed, options] = context.options; - return { - [nodeType]: (node) => ({ - context, - descriptors: - shouldIgnorePattern( - node, - context, - options.ignoreIdentifierPattern, - options.ignoreAccessorPattern, - options.ignoreCodePattern, - ) === allowed - ? [] - : [{ node, messageId: "generic" }], - }), - }; - }); -} - -const validTests: Array> = - [ - // Exact match. - { - code: dedent` - mutable = 0; - `, - options: [true, { ignoreAccessorPattern: "mutable" }], - }, - { - code: dedent` - mutable.foo = 0; - `, - options: [true, { ignoreAccessorPattern: "mutable.foo" }], - }, - { - code: dedent` - x = 0; - xxx_mutable_xxx = 0; - mutable.foo.bar = 0; - mutable.foo[0] = 0; - mutable.foo["foo-bar"] = 0; - `, - options: [false, { ignoreAccessorPattern: "mutable" }], - }, - // Prefix match. - { - code: dedent` - mutable_ = 0; - mutable_xxx = 0; - `, - options: [true, { ignoreAccessorPattern: "mutable_*" }], - }, - { - code: dedent` - x = 0; - xxx_mutable_xxx = 0; - mutable_xxx.foo = 0; - mutable_xxx[0] = 0; - mutable_xxx["foo-bar"] = 0; - `, - options: [false, { ignoreAccessorPattern: "mutable_*" }], - }, - // Suffix match. - { - code: dedent` - _mutable = 0; - xxx_mutable = 0; - `, - options: [true, { ignoreAccessorPattern: "*_mutable" }], - }, - { - code: dedent` - x = 0; - xxx_mutable_xxx = 0; - xxx_mutable.foo = 0; - xxx_mutable[0] = 0; - xxx_mutable["foo-bar"] = 0; - `, - options: [false, { ignoreAccessorPattern: "*_mutable" }], - }, - // Middle match. - { - code: dedent` - xxx_mutable_xxx = 0; - `, - options: [true, { ignoreAccessorPattern: "*_mutable_*" }], - }, - { - code: dedent` - x = 0; - xxx_mutable_xxx.foo = 0; - xxx_mutable_xxx[0] = 0; - xxx_mutable_xxx["foo-bar"] = 0; - `, - options: [false, { ignoreAccessorPattern: "*_mutable_*" }], - }, - // Mutable properties. - { - code: dedent` - mutable_xxx.foo = 0; - mutable_xxx[0] = 0; - mutable_xxx["foo-bar"] = 0; - `, - options: [true, { ignoreAccessorPattern: "mutable_*.*" }], - }, - { - code: dedent` - mutable_xxx = 0; - mutable_xxx.foo.bar = 0; - mutable_xxx.foo[0] = 0; - mutable_xxx.foo["foo-bar"] = 0; - `, - options: [false, { ignoreAccessorPattern: "mutable_*.*" }], - }, - // Mutable deep properties. - { - code: dedent` - mutable_xxx.foo.bar[0] = 0; - mutable_xxx.foo.bar["foo-bar"] = 0; - mutable_xxx.foo.bar = [0, 1, 2]; - mutable_xxx.foo = 0; - mutable_xxx[0] = 0; - mutable_xxx["foo-bar"] = 0; - `, - options: [true, { ignoreAccessorPattern: "mutable_*.*.**" }], - }, - { - code: dedent` - mutable_xxx = 0; - `, - options: [false, { ignoreAccessorPattern: "mutable_*.*.**" }], - }, - // Mutable deep properties and container. - { - code: dedent` - mutable_xxx.foo.bar[0] = 0; - mutable_xxx.foo.bar["foo-bar"] = 0; - mutable_xxx.foo.bar = [0, 1, 2]; - mutable_xxx.foo = 0; - mutable_xxx[0] = 0; - mutable_xxx["foo-bar"] = 0; - mutable_xxx = 0; - `, - options: [true, { ignoreAccessorPattern: "mutable_*.**" }], - }, - ]; - -const invalidTests: Array< - InvalidTestCase<"generic", [boolean, IgnoreAccessorPatternOption]> -> = [ - // Exact match. - { - code: dedent` - immutable = 0; - `, - options: [true, { ignoreAccessorPattern: "mutable" }], - errors: [ - { - messageId: "generic", - }, - ], - }, -]; - -getRuleTester(configs.esLatest).run( - "ignoreAccessorPattern", - createDummyRuleFor("AssignmentExpression") as any, - addFilename(filename, { - valid: validTests, - invalid: invalidTests, - }), -); - -const assignmentExpressionValidTests: Array< - ValidTestCase<[boolean, IgnoreIdentifierPatternOption]> -> = [ - // Prefix match. - { - code: dedent` - mutable_ = 0; - mutable_xxx = 0; - mutable_xxx.foo = 0; - mutable_xxx[0] = 0; - `, - options: [true, { ignoreIdentifierPattern: "^mutable_" }], - }, - // Suffix match. - { - code: dedent` - _mutable = 0; - xxx_mutable = 0; - foo.xxx_mutable = 0; - `, - options: [true, { ignoreIdentifierPattern: "_mutable$" }], - }, - // Middle match. - { - code: dedent` - mutable = 0; - `, - options: [true, { ignoreIdentifierPattern: "^mutable$" }], - }, - { - code: dedent` - mutable.foo.bar = 0; - mutable.bar[0] = 0; - `, - options: [false, { ignoreIdentifierPattern: "^mutable$" }], - }, -]; - -const assignmentExpressionInvalidTests: Array< - InvalidTestCase<"generic", [boolean, IgnoreIdentifierPatternOption]> -> = [ - // Exact match. - { - code: dedent` - immutable_xxx = 0; - `, - options: [true, { ignoreIdentifierPattern: "^mutable_" }], - errors: [ - { - messageId: "generic", - }, - ], - }, -]; - -getRuleTester(configs.esLatest).run( - "ignoreIdentifierPattern", - createDummyRuleFor("AssignmentExpression") as any, - addFilename(filename, { - valid: assignmentExpressionValidTests, - invalid: assignmentExpressionInvalidTests, - }), -); - -const expressionStatementValidTests: Array< - ValidTestCase<[boolean, IgnoreCodePatternOption]> -> = [ - { - code: dedent` - const x = 0; - `, - options: [true, { ignoreCodePattern: "^const x" }], - }, - { - code: dedent` - const x = 0; - `, - options: [true, { ignoreCodePattern: "= 0;$" }], - }, - { - code: dedent` - const x = 0; - `, - options: [true, { ignoreCodePattern: "^const x = 0;$" }], - }, -]; - -const expressionStatementInvalidTests: Array< - InvalidTestCase<"generic", [boolean, IgnoreCodePatternOption]> -> = [ - { - code: dedent` - const x = 0; - `, - options: [true, { ignoreCodePattern: "^const y" }], - errors: [ - { - messageId: "generic", - }, - ], - }, -]; - -getRuleTester(configs.esLatest).run( - "ignoreCodePattern", - createDummyRuleFor("VariableDeclaration") as any, - addFilename(filename, { - valid: expressionStatementValidTests, - invalid: expressionStatementInvalidTests, - }), -); diff --git a/tests/fixture/file.ts b/tests/fixture/file.ts index e69de29bb..3eb049fe6 100644 --- a/tests/fixture/file.ts +++ b/tests/fixture/file.ts @@ -0,0 +1 @@ +// File needs to exist. diff --git a/tests/fixture/tsconfig.json b/tests/fixture/tsconfig.json index 2ce472f6b..27fd308e2 100644 --- a/tests/fixture/tsconfig.json +++ b/tests/fixture/tsconfig.json @@ -1,6 +1,10 @@ { "compilerOptions": { - "strict": true + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "Bundler", + "strict": true, + "skipLibCheck": true }, - "include": ["file.ts"] + "include": ["**/*"] } diff --git a/tests/helpers/RuleTester.ts b/tests/helpers/RuleTester.ts deleted file mode 100644 index 79c6fcbee..000000000 --- a/tests/helpers/RuleTester.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { - RuleTester, - type RuleTesterConfig, -} from "@typescript-eslint/rule-tester"; -import { afterAll, beforeAll, describe, it } from "vitest"; - -/* eslint-disable ts/naming-convention */ -class VitestRuleTester extends RuleTester { - public static afterAll: typeof afterAll = afterAll; - public static beforeAll: typeof beforeAll = beforeAll; - public static it: typeof it = it; - public static itOnly: typeof it.only = it.only; - public static itSkip: typeof it.skip = it.skip; - public static describe: typeof describe = describe; - public static describeOnly: typeof describe.only = describe.only; - public static describeSkip: typeof describe.skip = describe.skip; -} -/* eslint-enable ts/naming-convention */ - -export function getRuleTester(config: RuleTesterConfig): VitestRuleTester { - return new VitestRuleTester(config); -} diff --git a/tests/helpers/configs.ts b/tests/helpers/configs.ts deleted file mode 100644 index c4c4cffa9..000000000 --- a/tests/helpers/configs.ts +++ /dev/null @@ -1,170 +0,0 @@ -import path from "node:path"; - -// @ts-expect-error - Untyped. -import babelParser from "@babel/eslint-parser"; -import typescriptParser from "@typescript-eslint/parser"; -import { type RuleTesterConfig } from "@typescript-eslint/rule-tester"; -import espreeParser from "espree"; - -const fixturePath = path.join(process.cwd(), "tests/fixture"); -export const filename: string = path.join(fixturePath, "file.ts"); - -export const configs = { - typescript: { - languageOptions: { - parser: typescriptParser, - parserOptions: { - sourceType: "module", - tsconfigRootDir: fixturePath, - projectService: true, - }, - }, - dependencyConstraints: { - typescript: "4.7.4", - }, - } satisfies RuleTesterConfig as RuleTesterConfig, - - esLatest: { - languageOptions: { - parser: babelParser, - parserOptions: { - ecmaVersion: "latest", - requireConfigFile: false, - babelOptions: { - babelrc: false, - configFile: false, - }, - }, - }, - } satisfies RuleTesterConfig as RuleTesterConfig, - - es2022: { - languageOptions: { - parser: babelParser, - parserOptions: { - ecmaVersion: 2022, - requireConfigFile: false, - babelOptions: { - babelrc: false, - configFile: false, - }, - }, - }, - } satisfies RuleTesterConfig as RuleTesterConfig, - - es2021: { - languageOptions: { - parser: babelParser, - parserOptions: { - ecmaVersion: 2021, - requireConfigFile: false, - babelOptions: { - babelrc: false, - configFile: false, - }, - }, - }, - } satisfies RuleTesterConfig as RuleTesterConfig, - - es2020: { - languageOptions: { - parser: babelParser, - parserOptions: { - ecmaVersion: 2020, - requireConfigFile: false, - babelOptions: { - babelrc: false, - configFile: false, - }, - }, - }, - } satisfies RuleTesterConfig as RuleTesterConfig, - - es2019: { - languageOptions: { - parser: babelParser, - parserOptions: { - ecmaVersion: 2019, - requireConfigFile: false, - babelOptions: { - babelrc: false, - configFile: false, - }, - }, - }, - } satisfies RuleTesterConfig as RuleTesterConfig, - - es2018: { - languageOptions: { - parser: babelParser, - parserOptions: { - ecmaVersion: 2018, - requireConfigFile: false, - babelOptions: { - babelrc: false, - configFile: false, - }, - }, - }, - } satisfies RuleTesterConfig as RuleTesterConfig, - - es2017: { - languageOptions: { - parser: babelParser, - parserOptions: { - ecmaVersion: 2017, - requireConfigFile: false, - babelOptions: { - babelrc: false, - configFile: false, - }, - }, - }, - } satisfies RuleTesterConfig as RuleTesterConfig, - - es2016: { - languageOptions: { - parser: babelParser, - parserOptions: { - ecmaVersion: 2016, - requireConfigFile: false, - babelOptions: { - babelrc: false, - configFile: false, - }, - }, - }, - } satisfies RuleTesterConfig as RuleTesterConfig, - - es2015: { - languageOptions: { - parser: babelParser, - parserOptions: { - ecmaVersion: 2015, - requireConfigFile: false, - babelOptions: { - babelrc: false, - configFile: false, - }, - }, - }, - } satisfies RuleTesterConfig as RuleTesterConfig, - - es5: { - languageOptions: { - parser: espreeParser, - parserOptions: { - ecmaVersion: 5, - }, - }, - } satisfies RuleTesterConfig as RuleTesterConfig, - - es3: { - languageOptions: { - parser: espreeParser, - parserOptions: { - ecmaVersion: 3, - }, - }, - } satisfies RuleTesterConfig as RuleTesterConfig, -}; diff --git a/tests/helpers/testers.ts b/tests/helpers/testers.ts deleted file mode 100644 index 1ee101e8d..000000000 --- a/tests/helpers/testers.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { type Rule } from "#/utils/rule"; - -import { getRuleTester } from "./RuleTester"; -import { configs } from "./configs"; -import { - type InvalidTestCaseSet, - type ValidTestCaseSet, - processInvalidTestCase, - processValidTestCase, -} from "./util"; - -type TestFunction< - TMessageIds extends string, - TOptions extends Readonly, -> = (tests: { - valid: Array>; - invalid: Array>; -}) => void; - -export function testRule< - TMessageIds extends string, - TOptions extends Readonly, ->(ruleName: string, rule: Rule) { - return Object.fromEntries( - [...Object.entries(configs)].map( - ([configName, config]): [ - keyof typeof configs, - TestFunction, - ] => [ - configName as keyof typeof configs, - ({ valid, invalid }) => { - const ruleTester = getRuleTester(config); - - ruleTester.run(ruleName, rule as any, { - valid: processValidTestCase(valid), - invalid: processInvalidTestCase(invalid), - }); - }, - ], - ), - ) as Record>; -} diff --git a/tests/helpers/util.ts b/tests/helpers/util.ts deleted file mode 100644 index 7ab4d3109..000000000 --- a/tests/helpers/util.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { - type InvalidTestCase, - type RunTests, - type ValidTestCase, -} from "@typescript-eslint/rule-tester"; -import { - type SharedConfigurationSettings, - type TSESLint, -} from "@typescript-eslint/utils"; -import { - type NamedCreateRuleMeta, - type RuleModule, -} from "@typescript-eslint/utils/eslint-utils"; - -import { type RuleFunctionsMap, createRuleUsingFunction } from "#/utils/rule"; - -import { filename as dummyFilename } from "./configs"; - -type OptionsSets = { - /** - * The set of options this test case should pass for. - */ - optionsSet: ReadonlyArray; - - /** - * The set of settings this test case should pass for. - */ - - settingsSet?: ReadonlyArray>; -}; - -export type ValidTestCaseSet> = Omit< - ValidTestCase, - "options" | "settings" -> & - OptionsSets; - -export type InvalidTestCaseSet< - TMessageIds extends string, - TOptions extends Readonly, -> = Omit, "options" | "settings"> & - OptionsSets; - -/** - * Convert our test cases into ones eslint test runner is expecting. - */ -export function processInvalidTestCase< - TMessageIds extends string, - TOptions extends Readonly, ->( - testCases: Array>, -): Array> { - return testCases.flatMap((testCase) => - testCase.optionsSet.flatMap((options) => { - const { optionsSet, settingsSet, ...eslintTestCase } = testCase; - - return (settingsSet ?? [{}]).map( - (settings): InvalidTestCase => ({ - filename: dummyFilename, - ...eslintTestCase, - options, - settings, - }), - ); - }), - ); -} - -/** - * Convert our test cases into ones eslint test runner is expecting. - */ -export function processValidTestCase>( - testCases: Array>, -): Array> { - // Ideally these two functions should be merged into 1 but I haven't been able - // to get the typing information right - so for now they are two functions. - - return processInvalidTestCase(testCases as any); -} - -/** - * Create a dummy rule for testing. - */ -export function createDummyRule( - create: ( - context: Readonly>, - ) => RuleFunctionsMap, -): RuleModule { - const meta: NamedCreateRuleMeta<"generic", {}> = { - type: "suggestion", - docs: { - description: "rule used in testing", - }, - messages: { - generic: "Error.", - }, - schema: { - oneOf: [ - { - type: "object", - }, - { - type: "array", - }, - ], - }, - }; - - return createRuleUsingFunction( - "dummy-rule", - meta as any, - [true, {}], - create, - ) as any; -} - -/** - * Adds filenames to the tests (needed for typescript to work when parserOptions.project has been set). - */ -export function addFilename< - TMessageIds extends string, - TOptions extends Readonly, ->( - filename: string, - tests: RunTests, -): RunTests { - const { valid, invalid } = tests; - return { - invalid: - invalid.map((test) => ({ - ...test, - filename, - })) ?? [], - valid: - valid.map((test) => - typeof test === "string" - ? { code: test, filename } - : { ...test, filename }, - ) ?? [], - }; -} - -export type MessagesOf>> = - T extends RuleModule> - ? Messages - : never; - -export type OptionsOf>> = - T extends RuleModule ? Options : never; diff --git a/tests/rules/__snapshots__/functional-parameters.test.ts.snap b/tests/rules/__snapshots__/functional-parameters.test.ts.snap new file mode 100644 index 000000000..42dc916f9 --- /dev/null +++ b/tests/rules/__snapshots__/functional-parameters.test.ts.snap @@ -0,0 +1,129 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`functional-parameters > javascript - es latest > options > enforceParameterCount > atLeastOne 1`] = ` +[ + { + "column": 1, + "endColumn": 2, + "endLine": 3, + "line": 1, + "message": "Functions must have at least one parameter.", + "messageId": "paramCountAtLeastOne", + "nodeType": "FunctionDeclaration", + "ruleId": "functional-parameters", + "severity": 2, + }, +] +`; + +exports[`functional-parameters > javascript - es latest > options > enforceParameterCount > exactlyOne 1`] = ` +[ + { + "column": 1, + "endColumn": 2, + "endLine": 3, + "line": 1, + "message": "Functions must have exactly one parameter.", + "messageId": "paramCountExactlyOne", + "nodeType": "FunctionDeclaration", + "ruleId": "functional-parameters", + "severity": 2, + }, +] +`; + +exports[`functional-parameters > javascript - es latest > options > enforceParameterCount > exactlyOne 2`] = ` +[ + { + "column": 1, + "endColumn": 2, + "endLine": 3, + "line": 1, + "message": "Functions must have exactly one parameter.", + "messageId": "paramCountExactlyOne", + "nodeType": "FunctionDeclaration", + "ruleId": "functional-parameters", + "severity": 2, + }, +] +`; + +exports[`functional-parameters > javascript - es latest > options > enforceParameterCount > ignoreIIFE 1`] = ` +[ + { + "column": 2, + "endColumn": 2, + "endLine": 3, + "line": 1, + "message": "Functions must have at least one parameter.", + "messageId": "paramCountAtLeastOne", + "nodeType": "ArrowFunctionExpression", + "ruleId": "functional-parameters", + "severity": 2, + }, +] +`; + +exports[`functional-parameters > javascript - es latest > options > enforceParameterCount > ignoreLambdaExpression 1`] = ` +[ + { + "column": 1, + "endColumn": 18, + "endLine": 1, + "line": 1, + "message": "Functions must have at least one parameter.", + "messageId": "paramCountAtLeastOne", + "nodeType": "FunctionDeclaration", + "ruleId": "functional-parameters", + "severity": 2, + }, +] +`; + +exports[`functional-parameters > javascript - es latest > options > ignoreIdentifierPattern 1`] = ` +[ + { + "column": 14, + "endColumn": 20, + "endLine": 1, + "line": 1, + "message": "Unexpected rest parameter. Use a regular parameter of type array instead.", + "messageId": "restParam", + "nodeType": "RestElement", + "ruleId": "functional-parameters", + "severity": 2, + }, +] +`; + +exports[`functional-parameters > javascript - es latest > reports arguments keyword violations 1`] = ` +[ + { + "column": 15, + "endColumn": 24, + "endLine": 2, + "line": 2, + "message": "Unexpected use of \`arguments\`. Use regular function arguments instead.", + "messageId": "arguments", + "nodeType": "Identifier", + "ruleId": "functional-parameters", + "severity": 2, + }, +] +`; + +exports[`functional-parameters > javascript - es latest > reports rest parameter violations 1`] = ` +[ + { + "column": 14, + "endColumn": 20, + "endLine": 1, + "line": 1, + "message": "Unexpected rest parameter. Use a regular parameter of type array instead.", + "messageId": "restParam", + "nodeType": "RestElement", + "ruleId": "functional-parameters", + "severity": 2, + }, +] +`; diff --git a/tests/rules/__snapshots__/no-classes.test.ts.snap b/tests/rules/__snapshots__/no-classes.test.ts.snap new file mode 100644 index 000000000..b4d296a36 --- /dev/null +++ b/tests/rules/__snapshots__/no-classes.test.ts.snap @@ -0,0 +1,33 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`no-classes > javascript - es latest > reports class declarations 1`] = ` +[ + { + "column": 1, + "endColumn": 13, + "endLine": 1, + "line": 1, + "message": "Unexpected class, use functions not classes.", + "messageId": "generic", + "nodeType": "ClassDeclaration", + "ruleId": "no-classes", + "severity": 2, + }, +] +`; + +exports[`no-classes > javascript - es latest > reports class declarations 2`] = ` +[ + { + "column": 15, + "endColumn": 23, + "endLine": 1, + "line": 1, + "message": "Unexpected class, use functions not classes.", + "messageId": "generic", + "nodeType": "ClassExpression", + "ruleId": "no-classes", + "severity": 2, + }, +] +`; diff --git a/tests/rules/__snapshots__/no-conditional-statements.test.ts.snap b/tests/rules/__snapshots__/no-conditional-statements.test.ts.snap new file mode 100644 index 000000000..e856609aa --- /dev/null +++ b/tests/rules/__snapshots__/no-conditional-statements.test.ts.snap @@ -0,0 +1,65 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`no-conditional-statements > typescript > if statements > options > allowReturningBranches > ifExhaustive > else required 1`] = ` +[ + { + "column": 3, + "endColumn": 4, + "endLine": 4, + "line": 2, + "message": "Incomplete if, it must have an else statement and every branch must contain a return statement.", + "messageId": "incompleteIf", + "nodeType": "IfStatement", + "ruleId": "no-conditional-statements", + "severity": 2, + }, +] +`; + +exports[`no-conditional-statements > typescript > if statements > reports if statements 1`] = ` +[ + { + "column": 1, + "endColumn": 2, + "endLine": 3, + "line": 1, + "message": "Unexpected if, use a conditional expression (ternary operator) instead.", + "messageId": "unexpectedIf", + "nodeType": "IfStatement", + "ruleId": "no-conditional-statements", + "severity": 2, + }, +] +`; + +exports[`no-conditional-statements > typescript > switch statements > options > allowReturningBranches > ifExhaustive > requires default case 1`] = ` +[ + { + "column": 3, + "endColumn": 4, + "endLine": 7, + "line": 2, + "message": "Incomplete switch, it must be exhaustive or have an default case and every case must contain a return statement.", + "messageId": "incompleteSwitch", + "nodeType": "SwitchStatement", + "ruleId": "no-conditional-statements", + "severity": 2, + }, +] +`; + +exports[`no-conditional-statements > typescript > switch statements > reports switch statements 1`] = ` +[ + { + "column": 1, + "endColumn": 2, + "endLine": 7, + "line": 1, + "message": "Unexpected switch, use a conditional expression (ternary operator) instead.", + "messageId": "unexpectedSwitch", + "nodeType": "SwitchStatement", + "ruleId": "no-conditional-statements", + "severity": 2, + }, +] +`; diff --git a/tests/rules/__snapshots__/no-expression-statements.test.ts.snap b/tests/rules/__snapshots__/no-expression-statements.test.ts.snap new file mode 100644 index 000000000..acb52a278 --- /dev/null +++ b/tests/rules/__snapshots__/no-expression-statements.test.ts.snap @@ -0,0 +1,49 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`no-expression-statements > javascript - es latest > options > ignoreCodePattern 1`] = ` +[ + { + "column": 1, + "endColumn": 17, + "endLine": 1, + "line": 1, + "message": "Using expressions to cause side-effects not allowed.", + "messageId": "generic", + "nodeType": "ExpressionStatement", + "ruleId": "no-expression-statements", + "severity": 2, + }, +] +`; + +exports[`no-expression-statements > javascript - es latest > reports expression statements 1`] = ` +[ + { + "column": 1, + "endColumn": 11, + "endLine": 2, + "line": 2, + "message": "Using expressions to cause side-effects not allowed.", + "messageId": "generic", + "nodeType": "ExpressionStatement", + "ruleId": "no-expression-statements", + "severity": 2, + }, +] +`; + +exports[`no-expression-statements > typescript > options > ignoreSelfReturning 1`] = ` +[ + { + "column": 1, + "endColumn": 7, + "endLine": 2, + "line": 2, + "message": "Using expressions to cause side-effects not allowed.", + "messageId": "generic", + "nodeType": "ExpressionStatement", + "ruleId": "no-expression-statements", + "severity": 2, + }, +] +`; diff --git a/tests/rules/__snapshots__/no-let.test.ts.snap b/tests/rules/__snapshots__/no-let.test.ts.snap new file mode 100644 index 000000000..deba1dc39 --- /dev/null +++ b/tests/rules/__snapshots__/no-let.test.ts.snap @@ -0,0 +1,147 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`no-let > javascript - es latest > options > allowInForLoopInit > should not report let declarations in for loop init 1`] = ` +[ + { + "column": 1, + "endColumn": 7, + "endLine": 1, + "line": 1, + "message": "Unexpected let, use const instead.", + "messageId": "generic", + "nodeType": "VariableDeclaration", + "ruleId": "no-let", + "severity": 2, + }, + { + "column": 1, + "endColumn": 11, + "endLine": 2, + "line": 2, + "message": "Unexpected let, use const instead.", + "messageId": "generic", + "nodeType": "VariableDeclaration", + "ruleId": "no-let", + "severity": 2, + }, +] +`; + +exports[`no-let > javascript - es latest > options > allowInFunctions > should not report let declarations in arrow function declarations 1`] = ` +[ + { + "column": 1, + "endColumn": 7, + "endLine": 1, + "line": 1, + "message": "Unexpected let, use const instead.", + "messageId": "generic", + "nodeType": "VariableDeclaration", + "ruleId": "no-let", + "severity": 2, + }, + { + "column": 1, + "endColumn": 11, + "endLine": 2, + "line": 2, + "message": "Unexpected let, use const instead.", + "messageId": "generic", + "nodeType": "VariableDeclaration", + "ruleId": "no-let", + "severity": 2, + }, +] +`; + +exports[`no-let > javascript - es latest > options > allowInFunctions > should not report let declarations in function declarations 1`] = ` +[ + { + "column": 1, + "endColumn": 7, + "endLine": 1, + "line": 1, + "message": "Unexpected let, use const instead.", + "messageId": "generic", + "nodeType": "VariableDeclaration", + "ruleId": "no-let", + "severity": 2, + }, + { + "column": 1, + "endColumn": 11, + "endLine": 2, + "line": 2, + "message": "Unexpected let, use const instead.", + "messageId": "generic", + "nodeType": "VariableDeclaration", + "ruleId": "no-let", + "severity": 2, + }, +] +`; + +exports[`no-let > javascript - es latest > options > allowInFunctions > should not report let declarations in method declarations 1`] = ` +[ + { + "column": 1, + "endColumn": 7, + "endLine": 1, + "line": 1, + "message": "Unexpected let, use const instead.", + "messageId": "generic", + "nodeType": "VariableDeclaration", + "ruleId": "no-let", + "severity": 2, + }, + { + "column": 1, + "endColumn": 11, + "endLine": 2, + "line": 2, + "message": "Unexpected let, use const instead.", + "messageId": "generic", + "nodeType": "VariableDeclaration", + "ruleId": "no-let", + "severity": 2, + }, +] +`; + +exports[`no-let > javascript - es latest > should report let declarations 1`] = ` +[ + { + "column": 1, + "endColumn": 7, + "endLine": 1, + "line": 1, + "message": "Unexpected let, use const instead.", + "messageId": "generic", + "nodeType": "VariableDeclaration", + "ruleId": "no-let", + "severity": 2, + }, + { + "column": 3, + "endColumn": 9, + "endLine": 4, + "line": 4, + "message": "Unexpected let, use const instead.", + "messageId": "generic", + "nodeType": "VariableDeclaration", + "ruleId": "no-let", + "severity": 2, + }, + { + "column": 3, + "endColumn": 13, + "endLine": 5, + "line": 5, + "message": "Unexpected let, use const instead.", + "messageId": "generic", + "nodeType": "VariableDeclaration", + "ruleId": "no-let", + "severity": 2, + }, +] +`; diff --git a/tests/rules/__snapshots__/no-loop-statements.test.ts.snap b/tests/rules/__snapshots__/no-loop-statements.test.ts.snap new file mode 100644 index 000000000..4dfa61841 --- /dev/null +++ b/tests/rules/__snapshots__/no-loop-statements.test.ts.snap @@ -0,0 +1,97 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`no-loop-statements > javascript - es latest > reports do while loop statements 1`] = ` +[ + { + "column": 1, + "endColumn": 16, + "endLine": 3, + "line": 1, + "message": "Unexpected loop, use map or reduce instead.", + "messageId": "generic", + "nodeType": "DoWhileStatement", + "ruleId": "no-loop-statements", + "severity": 2, + }, +] +`; + +exports[`no-loop-statements > javascript - es latest > reports for await loop statements 1`] = ` +[ + { + "column": 1, + "endColumn": 2, + "endLine": 3, + "line": 1, + "message": "Unexpected loop, use map or reduce instead.", + "messageId": "generic", + "nodeType": "ForOfStatement", + "ruleId": "no-loop-statements", + "severity": 2, + }, +] +`; + +exports[`no-loop-statements > javascript - es latest > reports for in loop statements 1`] = ` +[ + { + "column": 1, + "endColumn": 2, + "endLine": 3, + "line": 1, + "message": "Unexpected loop, use map or reduce instead.", + "messageId": "generic", + "nodeType": "ForInStatement", + "ruleId": "no-loop-statements", + "severity": 2, + }, +] +`; + +exports[`no-loop-statements > javascript - es latest > reports for loop statements 1`] = ` +[ + { + "column": 1, + "endColumn": 2, + "endLine": 3, + "line": 1, + "message": "Unexpected loop, use map or reduce instead.", + "messageId": "generic", + "nodeType": "ForStatement", + "ruleId": "no-loop-statements", + "severity": 2, + }, +] +`; + +exports[`no-loop-statements > javascript - es latest > reports for of loop statements 1`] = ` +[ + { + "column": 1, + "endColumn": 2, + "endLine": 3, + "line": 1, + "message": "Unexpected loop, use map or reduce instead.", + "messageId": "generic", + "nodeType": "ForOfStatement", + "ruleId": "no-loop-statements", + "severity": 2, + }, +] +`; + +exports[`no-loop-statements > javascript - es latest > reports while loop statements 1`] = ` +[ + { + "column": 1, + "endColumn": 2, + "endLine": 3, + "line": 1, + "message": "Unexpected loop, use map or reduce instead.", + "messageId": "generic", + "nodeType": "WhileStatement", + "ruleId": "no-loop-statements", + "severity": 2, + }, +] +`; diff --git a/tests/rules/__snapshots__/no-mixed-types.test.ts.snap b/tests/rules/__snapshots__/no-mixed-types.test.ts.snap new file mode 100644 index 000000000..4ece8e029 --- /dev/null +++ b/tests/rules/__snapshots__/no-mixed-types.test.ts.snap @@ -0,0 +1,65 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`no-mixed-types > typescript > options > checkInterfaces > should report mixed types in interfaces when enabled 1`] = ` +[ + { + "column": 1, + "endColumn": 46, + "endLine": 1, + "line": 1, + "message": "Only the same kind of members allowed in types.", + "messageId": "generic", + "nodeType": "TSInterfaceDeclaration", + "ruleId": "no-mixed-types", + "severity": 2, + }, +] +`; + +exports[`no-mixed-types > typescript > options > checkTypeLiterals > should report mixed types in type literals when enabled 1`] = ` +[ + { + "column": 1, + "endColumn": 3, + "endLine": 4, + "line": 1, + "message": "Only the same kind of members allowed in types.", + "messageId": "generic", + "nodeType": "TSTypeAliasDeclaration", + "ruleId": "no-mixed-types", + "severity": 2, + }, +] +`; + +exports[`no-mixed-types > typescript > reports mixed types in interfaces 1`] = ` +[ + { + "column": 1, + "endColumn": 2, + "endLine": 4, + "line": 1, + "message": "Only the same kind of members allowed in types.", + "messageId": "generic", + "nodeType": "TSInterfaceDeclaration", + "ruleId": "no-mixed-types", + "severity": 2, + }, +] +`; + +exports[`no-mixed-types > typescript > reports mixed types in type literals 1`] = ` +[ + { + "column": 1, + "endColumn": 3, + "endLine": 4, + "line": 1, + "message": "Only the same kind of members allowed in types.", + "messageId": "generic", + "nodeType": "TSTypeAliasDeclaration", + "ruleId": "no-mixed-types", + "severity": 2, + }, +] +`; diff --git a/tests/rules/__snapshots__/no-promise-reject.test.ts.snap b/tests/rules/__snapshots__/no-promise-reject.test.ts.snap new file mode 100644 index 000000000..1dd6d8fc1 --- /dev/null +++ b/tests/rules/__snapshots__/no-promise-reject.test.ts.snap @@ -0,0 +1,65 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`no-promise-reject > javascript - es latest > reports Promise.reject 1`] = ` +[ + { + "column": 10, + "endColumn": 39, + "endLine": 2, + "line": 2, + "message": "Unexpected rejection, resolve an error instead.", + "messageId": "generic", + "nodeType": "CallExpression", + "ruleId": "no-promise-reject", + "severity": 2, + }, +] +`; + +exports[`no-promise-reject > javascript - es latest > reports new Promise(reject) 1`] = ` +[ + { + "column": 32, + "endColumn": 38, + "endLine": 2, + "line": 2, + "message": "Unexpected rejection, resolve an error instead.", + "messageId": "generic", + "nodeType": "Identifier", + "ruleId": "no-promise-reject", + "severity": 2, + }, +] +`; + +exports[`no-promise-reject > javascript - es latest > reports throw in async functions 1`] = ` +[ + { + "column": 3, + "endColumn": 34, + "endLine": 2, + "line": 2, + "message": "Unexpected rejection, resolve an error instead.", + "messageId": "generic", + "nodeType": "ThrowStatement", + "ruleId": "no-promise-reject", + "severity": 2, + }, +] +`; + +exports[`no-promise-reject > javascript - es latest > reports throw in try without catch in async functions 1`] = ` +[ + { + "column": 5, + "endColumn": 30, + "endLine": 3, + "line": 3, + "message": "Unexpected rejection, resolve an error instead.", + "messageId": "generic", + "nodeType": "ThrowStatement", + "ruleId": "no-promise-reject", + "severity": 2, + }, +] +`; diff --git a/tests/rules/__snapshots__/no-return-void.test.ts.snap b/tests/rules/__snapshots__/no-return-void.test.ts.snap new file mode 100644 index 000000000..062b8f3d1 --- /dev/null +++ b/tests/rules/__snapshots__/no-return-void.test.ts.snap @@ -0,0 +1,97 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`no-return-void > typescript > options > allowNull > reports null returning functions when disallowed 1`] = ` +[ + { + "column": 26, + "endColumn": 32, + "endLine": 1, + "line": 1, + "message": "Function must return a value.", + "messageId": "generic", + "nodeType": "TSTypeAnnotation", + "ruleId": "no-return-void", + "severity": 2, + }, +] +`; + +exports[`no-return-void > typescript > options > allowNull > reports null returning functions with inferred return type when disallowed 1`] = ` +[ + { + "column": 1, + "endColumn": 2, + "endLine": 3, + "line": 1, + "message": "Function must return a value.", + "messageId": "generic", + "nodeType": "FunctionDeclaration", + "ruleId": "no-return-void", + "severity": 2, + }, +] +`; + +exports[`no-return-void > typescript > options > allowUndefined > reports undefined returning functions when disallowed 1`] = ` +[ + { + "column": 26, + "endColumn": 37, + "endLine": 1, + "line": 1, + "message": "Function must return a value.", + "messageId": "generic", + "nodeType": "TSTypeAnnotation", + "ruleId": "no-return-void", + "severity": 2, + }, +] +`; + +exports[`no-return-void > typescript > options > allowUndefined > reports undefined returning functions with inferred return type when disallowed 1`] = ` +[ + { + "column": 1, + "endColumn": 2, + "endLine": 3, + "line": 1, + "message": "Function must return a value.", + "messageId": "generic", + "nodeType": "FunctionDeclaration", + "ruleId": "no-return-void", + "severity": 2, + }, +] +`; + +exports[`no-return-void > typescript > reports void returning functions 1`] = ` +[ + { + "column": 26, + "endColumn": 32, + "endLine": 1, + "line": 1, + "message": "Function must return a value.", + "messageId": "generic", + "nodeType": "TSTypeAnnotation", + "ruleId": "no-return-void", + "severity": 2, + }, +] +`; + +exports[`no-return-void > typescript > reports void returning functions with inferred return type 1`] = ` +[ + { + "column": 1, + "endColumn": 2, + "endLine": 3, + "line": 1, + "message": "Function must return a value.", + "messageId": "generic", + "nodeType": "FunctionDeclaration", + "ruleId": "no-return-void", + "severity": 2, + }, +] +`; diff --git a/tests/rules/__snapshots__/no-this-expressions.test.ts.snap b/tests/rules/__snapshots__/no-this-expressions.test.ts.snap new file mode 100644 index 000000000..3011b0ce7 --- /dev/null +++ b/tests/rules/__snapshots__/no-this-expressions.test.ts.snap @@ -0,0 +1,44 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`no-this-expressions > javascript - es latest > reports this expressions 1`] = ` +{ + "fixed": false, + "messages": [ + { + "column": 3, + "endColumn": 7, + "endLine": 2, + "line": 2, + "message": "Unexpected this, use functions not classes.", + "messageId": "generic", + "nodeType": "ThisExpression", + "ruleId": "no-this-expressions", + "severity": 2, + }, + ], + "output": "function foo() { + this.bar(); +}", + "steps": [ + { + "fixed": false, + "messages": [ + { + "column": 3, + "endColumn": 7, + "endLine": 2, + "line": 2, + "message": "Unexpected this, use functions not classes.", + "messageId": "generic", + "nodeType": "ThisExpression", + "ruleId": "rule-to-test/no-this-expressions", + "severity": 2, + }, + ], + "output": "function foo() { + this.bar(); +}", + }, + ], +} +`; diff --git a/tests/rules/__snapshots__/no-throw-statements.test.ts.snap b/tests/rules/__snapshots__/no-throw-statements.test.ts.snap new file mode 100644 index 000000000..9eba6a0b4 --- /dev/null +++ b/tests/rules/__snapshots__/no-throw-statements.test.ts.snap @@ -0,0 +1,81 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`no-throw-statements > javascript - es latest > options > allowToRejectPromises > reports throw statements in functions nested in async functions 1`] = ` +[ + { + "column": 5, + "endColumn": 23, + "endLine": 3, + "line": 3, + "message": "Unexpected throw, throwing exceptions is not functional.", + "messageId": "generic", + "nodeType": "ThrowStatement", + "ruleId": "no-throw-statements", + "severity": 2, + }, +] +`; + +exports[`no-throw-statements > javascript - es latest > options > allowToRejectPromises > reports throw statements in try with catch in async functions 1`] = ` +[ + { + "column": 5, + "endColumn": 36, + "endLine": 3, + "line": 3, + "message": "Unexpected throw, throwing exceptions is not functional.", + "messageId": "generic", + "nodeType": "ThrowStatement", + "ruleId": "no-throw-statements", + "severity": 2, + }, +] +`; + +exports[`no-throw-statements > javascript - es latest > reports throw statements in async functions 1`] = ` +[ + { + "column": 3, + "endColumn": 21, + "endLine": 2, + "line": 2, + "message": "Unexpected throw, throwing exceptions is not functional.", + "messageId": "generic", + "nodeType": "ThrowStatement", + "ruleId": "no-throw-statements", + "severity": 2, + }, +] +`; + +exports[`no-throw-statements > javascript - es latest > reports throw statements of Errors 1`] = ` +[ + { + "column": 3, + "endColumn": 21, + "endLine": 2, + "line": 2, + "message": "Unexpected throw, throwing exceptions is not functional.", + "messageId": "generic", + "nodeType": "ThrowStatement", + "ruleId": "no-throw-statements", + "severity": 2, + }, +] +`; + +exports[`no-throw-statements > javascript - es latest > reports throw statements of strings 1`] = ` +[ + { + "column": 3, + "endColumn": 17, + "endLine": 2, + "line": 2, + "message": "Unexpected throw, throwing exceptions is not functional.", + "messageId": "generic", + "nodeType": "ThrowStatement", + "ruleId": "no-throw-statements", + "severity": 2, + }, +] +`; diff --git a/tests/rules/__snapshots__/no-try-statements.test.ts.snap b/tests/rules/__snapshots__/no-try-statements.test.ts.snap new file mode 100644 index 000000000..5c778fd71 --- /dev/null +++ b/tests/rules/__snapshots__/no-try-statements.test.ts.snap @@ -0,0 +1,49 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`no-try-statements > javascript - es latest > options > allowCatch > reports try statements with catch and finally 1`] = ` +[ + { + "column": 1, + "endColumn": 2, + "endLine": 7, + "line": 1, + "message": "Unexpected try-finally, this pattern is not functional.", + "messageId": "finally", + "nodeType": "TryStatement", + "ruleId": "no-try-statements", + "severity": 2, + }, +] +`; + +exports[`no-try-statements > javascript - es latest > options > allowFinally > reports try statements with catch and finally 1`] = ` +[ + { + "column": 1, + "endColumn": 2, + "endLine": 7, + "line": 1, + "message": "Unexpected try-catch, this pattern is not functional.", + "messageId": "catch", + "nodeType": "TryStatement", + "ruleId": "no-try-statements", + "severity": 2, + }, +] +`; + +exports[`no-try-statements > javascript - es latest > reports try statements 1`] = ` +[ + { + "column": 1, + "endColumn": 2, + "endLine": 5, + "line": 1, + "message": "Unexpected try-catch, this pattern is not functional.", + "messageId": "catch", + "nodeType": "TryStatement", + "ruleId": "no-try-statements", + "severity": 2, + }, +] +`; diff --git a/tests/rules/__snapshots__/prefer-property-signatures.test.ts.snap b/tests/rules/__snapshots__/prefer-property-signatures.test.ts.snap new file mode 100644 index 000000000..0a9771212 --- /dev/null +++ b/tests/rules/__snapshots__/prefer-property-signatures.test.ts.snap @@ -0,0 +1,33 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`prefer-property-signatures > typescript > reports method signatures in interfaces 1`] = ` +[ + { + "column": 3, + "endColumn": 37, + "endLine": 2, + "line": 2, + "message": "Use a property signature instead of a method signature", + "messageId": "generic", + "nodeType": "TSMethodSignature", + "ruleId": "prefer-property-signatures", + "severity": 2, + }, +] +`; + +exports[`prefer-property-signatures > typescript > reports method signatures in type literals 1`] = ` +[ + { + "column": 3, + "endColumn": 37, + "endLine": 2, + "line": 2, + "message": "Use a property signature instead of a method signature", + "messageId": "generic", + "nodeType": "TSMethodSignature", + "ruleId": "prefer-property-signatures", + "severity": 2, + }, +] +`; diff --git a/tests/rules/__snapshots__/prefer-readonly-type.test.ts.snap b/tests/rules/__snapshots__/prefer-readonly-type.test.ts.snap new file mode 100644 index 000000000..f39db760b --- /dev/null +++ b/tests/rules/__snapshots__/prefer-readonly-type.test.ts.snap @@ -0,0 +1,668 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`prefer-readonly-type > typescript > options > ignoreClass > reports non-field issues in classes - fieldsOnly 1`] = ` +[ + { + "column": 7, + "endColumn": 19, + "endLine": 9, + "fix": { + "range": [ + 136, + 136, + ], + "text": "readonly ", + }, + "line": 9, + "message": "A readonly modifier is required.", + "messageId": "property", + "nodeType": "TSPropertySignature", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, +] +`; + +exports[`prefer-readonly-type > typescript > reports issue inside interfaces 1`] = ` +[ + { + "column": 17, + "endColumn": 30, + "endLine": 2, + "fix": { + "range": [ + 32, + 37, + ], + "text": "ReadonlyArray", + }, + "line": 2, + "message": "Only readonly types allowed.", + "messageId": "type", + "nodeType": "TSTypeReference", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, + { + "column": 25, + "endColumn": 38, + "endLine": 3, + "fix": { + "range": [ + 71, + 76, + ], + "text": "ReadonlyArray", + }, + "line": 3, + "message": "Only readonly types allowed.", + "messageId": "type", + "nodeType": "TSTypeReference", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, +] +`; + +exports[`prefer-readonly-type > typescript > reports issues with index signatures 1`] = ` +[ + { + "column": 17, + "endColumn": 30, + "endLine": 3, + "fix": { + "range": [ + 60, + 65, + ], + "text": "ReadonlyArray", + }, + "line": 3, + "message": "Only readonly types allowed.", + "messageId": "type", + "nodeType": "TSTypeReference", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, + { + "column": 25, + "endColumn": 38, + "endLine": 4, + "fix": { + "range": [ + 99, + 104, + ], + "text": "ReadonlyArray", + }, + "line": 4, + "message": "Only readonly types allowed.", + "messageId": "type", + "nodeType": "TSTypeReference", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, + { + "column": 3, + "endColumn": 24, + "endLine": 8, + "fix": { + "range": [ + 140, + 140, + ], + "text": "readonly ", + }, + "line": 8, + "message": "A readonly modifier is required.", + "messageId": "property", + "nodeType": "TSIndexSignature", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, + { + "column": 3, + "endColumn": 34, + "endLine": 11, + "fix": { + "range": [ + 182, + 182, + ], + "text": "readonly ", + }, + "line": 11, + "message": "A readonly modifier is required.", + "messageId": "property", + "nodeType": "TSIndexSignature", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, + { + "column": 20, + "endColumn": 32, + "endLine": 11, + "fix": { + "range": [ + 199, + 199, + ], + "text": "readonly ", + }, + "line": 11, + "message": "A readonly modifier is required.", + "messageId": "property", + "nodeType": "TSPropertySignature", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, +] +`; + +exports[`prefer-readonly-type > typescript > reports issues with mapped types 1`] = ` +[ + { + "column": 18, + "endColumn": 45, + "endLine": 1, + "fix": { + "range": [ + 18, + 18, + ], + "text": " readonly", + }, + "line": 1, + "message": "A readonly modifier is required.", + "messageId": "property", + "nodeType": "TSMappedType", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, +] +`; + +exports[`prefer-readonly-type > typescript > reports literals without readonly modifiers 1`] = ` +[ + { + "column": 3, + "endColumn": 13, + "endLine": 2, + "fix": { + "range": [ + 13, + 13, + ], + "text": "readonly ", + }, + "line": 2, + "message": "A readonly modifier is required.", + "messageId": "property", + "nodeType": "TSPropertySignature", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, + { + "column": 3, + "endColumn": 28, + "endLine": 3, + "fix": { + "range": [ + 26, + 26, + ], + "text": "readonly ", + }, + "line": 3, + "message": "A readonly modifier is required.", + "messageId": "property", + "nodeType": "TSPropertySignature", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, + { + "column": 3, + "endColumn": 19, + "endLine": 4, + "fix": { + "range": [ + 54, + 54, + ], + "text": "readonly ", + }, + "line": 4, + "message": "A readonly modifier is required.", + "messageId": "property", + "nodeType": "TSPropertySignature", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, + { + "column": 3, + "endColumn": 41, + "endLine": 5, + "fix": { + "range": [ + 73, + 73, + ], + "text": "readonly ", + }, + "line": 5, + "message": "A readonly modifier is required.", + "messageId": "property", + "nodeType": "TSPropertySignature", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, + { + "column": 3, + "endColumn": 25, + "endLine": 6, + "fix": { + "range": [ + 114, + 114, + ], + "text": "readonly ", + }, + "line": 6, + "message": "A readonly modifier is required.", + "messageId": "property", + "nodeType": "TSIndexSignature", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, + { + "column": 5, + "endColumn": 15, + "endLine": 8, + "fix": { + "range": [ + 157, + 157, + ], + "text": "readonly ", + }, + "line": 8, + "message": "A readonly modifier is required.", + "messageId": "property", + "nodeType": "TSPropertySignature", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, + { + "column": 5, + "endColumn": 30, + "endLine": 9, + "fix": { + "range": [ + 172, + 172, + ], + "text": "readonly ", + }, + "line": 9, + "message": "A readonly modifier is required.", + "messageId": "property", + "nodeType": "TSPropertySignature", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, + { + "column": 5, + "endColumn": 21, + "endLine": 10, + "fix": { + "range": [ + 202, + 202, + ], + "text": "readonly ", + }, + "line": 10, + "message": "A readonly modifier is required.", + "messageId": "property", + "nodeType": "TSPropertySignature", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, + { + "column": 5, + "endColumn": 43, + "endLine": 11, + "fix": { + "range": [ + 223, + 223, + ], + "text": "readonly ", + }, + "line": 11, + "message": "A readonly modifier is required.", + "messageId": "property", + "nodeType": "TSPropertySignature", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, + { + "column": 5, + "endColumn": 27, + "endLine": 12, + "fix": { + "range": [ + 266, + 266, + ], + "text": "readonly ", + }, + "line": 12, + "message": "A readonly modifier is required.", + "messageId": "property", + "nodeType": "TSIndexSignature", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, +] +`; + +exports[`prefer-readonly-type > typescript > reports mutable array (non-generic) 1`] = ` +[ + { + "column": 17, + "endColumn": 25, + "endLine": 1, + "fix": { + "range": [ + 16, + 16, + ], + "text": "readonly ", + }, + "line": 1, + "message": "Only readonly arrays allowed.", + "messageId": "array", + "nodeType": "TSArrayType", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, + { + "column": 38, + "endColumn": 46, + "endLine": 1, + "fix": { + "range": [ + 37, + 37, + ], + "text": "readonly ", + }, + "line": 1, + "message": "Only readonly arrays allowed.", + "messageId": "array", + "nodeType": "TSArrayType", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, +] +`; + +exports[`prefer-readonly-type > typescript > reports mutable arrays (generic) 1`] = ` +[ + { + "column": 17, + "endColumn": 30, + "endLine": 1, + "fix": { + "range": [ + 16, + 21, + ], + "text": "ReadonlyArray", + }, + "line": 1, + "message": "Only readonly types allowed.", + "messageId": "type", + "nodeType": "TSTypeReference", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, + { + "column": 43, + "endColumn": 56, + "endLine": 1, + "fix": { + "range": [ + 42, + 47, + ], + "text": "ReadonlyArray", + }, + "line": 1, + "message": "Only readonly types allowed.", + "messageId": "type", + "nodeType": "TSTypeReference", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, +] +`; + +exports[`prefer-readonly-type > typescript > reports mutable maps 1`] = ` +[ + { + "column": 17, + "endColumn": 28, + "endLine": 1, + "fix": { + "range": [ + 16, + 19, + ], + "text": "ReadonlyMap", + }, + "line": 1, + "message": "Only readonly types allowed.", + "messageId": "type", + "nodeType": "TSTypeReference", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, + { + "column": 41, + "endColumn": 52, + "endLine": 1, + "fix": { + "range": [ + 40, + 43, + ], + "text": "ReadonlyMap", + }, + "line": 1, + "message": "Only readonly types allowed.", + "messageId": "type", + "nodeType": "TSTypeReference", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, +] +`; + +exports[`prefer-readonly-type > typescript > reports mutable sets 1`] = ` +[ + { + "column": 17, + "endColumn": 28, + "endLine": 1, + "fix": { + "range": [ + 16, + 19, + ], + "text": "ReadonlySet", + }, + "line": 1, + "message": "Only readonly types allowed.", + "messageId": "type", + "nodeType": "TSTypeReference", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, + { + "column": 41, + "endColumn": 52, + "endLine": 1, + "fix": { + "range": [ + 40, + 43, + ], + "text": "ReadonlySet", + }, + "line": 1, + "message": "Only readonly types allowed.", + "messageId": "type", + "nodeType": "TSTypeReference", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, +] +`; + +exports[`prefer-readonly-type > typescript > reports non-readonly class parameter properties 1`] = ` +[ + { + "column": 5, + "endColumn": 30, + "endLine": 3, + "fix": { + "range": [ + 41, + 41, + ], + "text": "readonly ", + }, + "line": 3, + "message": "A readonly modifier is required.", + "messageId": "property", + "nodeType": "TSParameterProperty", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, + { + "column": 5, + "endColumn": 36, + "endLine": 4, + "fix": { + "range": [ + 75, + 75, + ], + "text": "readonly ", + }, + "line": 4, + "message": "A readonly modifier is required.", + "messageId": "property", + "nodeType": "TSParameterProperty", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, + { + "column": 5, + "endColumn": 32, + "endLine": 5, + "fix": { + "range": [ + 110, + 110, + ], + "text": "readonly ", + }, + "line": 5, + "message": "A readonly modifier is required.", + "messageId": "property", + "nodeType": "TSParameterProperty", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, +] +`; + +exports[`prefer-readonly-type > typescript > reports non-readonly class properties 1`] = ` +[ + { + "column": 3, + "endColumn": 15, + "endLine": 2, + "fix": { + "range": [ + 16, + 16, + ], + "text": "readonly ", + }, + "line": 2, + "message": "A readonly modifier is required.", + "messageId": "property", + "nodeType": "PropertyDefinition", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, + { + "column": 3, + "endColumn": 23, + "endLine": 3, + "fix": { + "range": [ + 39, + 39, + ], + "text": "readonly ", + }, + "line": 3, + "message": "A readonly modifier is required.", + "messageId": "property", + "nodeType": "PropertyDefinition", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, + { + "column": 3, + "endColumn": 22, + "endLine": 4, + "fix": { + "range": [ + 61, + 61, + ], + "text": "readonly ", + }, + "line": 4, + "message": "A readonly modifier is required.", + "messageId": "property", + "nodeType": "PropertyDefinition", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, + { + "column": 3, + "endColumn": 30, + "endLine": 5, + "fix": { + "range": [ + 91, + 91, + ], + "text": "readonly ", + }, + "line": 5, + "message": "A readonly modifier is required.", + "messageId": "property", + "nodeType": "PropertyDefinition", + "ruleId": "prefer-readonly-type", + "severity": 2, + }, +] +`; diff --git a/tests/rules/__snapshots__/prefer-tacit.test.ts.snap b/tests/rules/__snapshots__/prefer-tacit.test.ts.snap new file mode 100644 index 000000000..f04d9d2c9 --- /dev/null +++ b/tests/rules/__snapshots__/prefer-tacit.test.ts.snap @@ -0,0 +1,146 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`prefer-tacit > typescript > options > checkMemberExpressions > report member expressions when enabled 1`] = ` +[ + { + "column": 1, + "endColumn": 35, + "endLine": 2, + "line": 2, + "message": "Potentially unnecessary function wrapper.", + "messageId": "generic", + "nodeType": "FunctionDeclaration", + "ruleId": "prefer-tacit", + "severity": 2, + "suggestions": [ + { + "desc": "Potentially unnecessary function wrapper.", + "fix": { + "range": [ + 46, + 80, + ], + "text": "const foo = a.b.bind(a);", + }, + "messageId": "generic", + }, + ], + }, +] +`; + +exports[`prefer-tacit > typescript > options > checkMemberExpressions > report member expressions when enabled 2`] = ` +[ + { + "column": 13, + "endColumn": 33, + "endLine": 1, + "line": 1, + "message": "Potentially unnecessary function wrapper.", + "messageId": "generic", + "nodeType": "ArrowFunctionExpression", + "ruleId": "prefer-tacit", + "severity": 2, + "suggestions": [ + { + "desc": "Potentially unnecessary function wrapper.", + "fix": { + "range": [ + 12, + 32, + ], + "text": "/a/.test.bind(/a/)", + }, + "messageId": "generic", + }, + ], + }, +] +`; + +exports[`prefer-tacit > typescript > reports functions that are just instantiations 1`] = ` +[ + { + "column": 1, + "endColumn": 41, + "endLine": 2, + "line": 2, + "message": "Potentially unnecessary function wrapper.", + "messageId": "generic", + "nodeType": "FunctionDeclaration", + "ruleId": "prefer-tacit", + "severity": 2, + "suggestions": [ + { + "desc": "Potentially unnecessary function wrapper.", + "fix": { + "range": [ + 26, + 66, + ], + "text": "const foo = f;", + }, + "messageId": "generic", + }, + ], + }, +] +`; + +exports[`prefer-tacit > typescript > reports functions that can "safely" be changed 1`] = ` +[ + { + "column": 13, + "endColumn": 22, + "endLine": 2, + "line": 2, + "message": "Potentially unnecessary function wrapper.", + "messageId": "generic", + "nodeType": "ArrowFunctionExpression", + "ruleId": "prefer-tacit", + "severity": 2, + "suggestions": [ + { + "desc": "Potentially unnecessary function wrapper.", + "fix": { + "range": [ + 29, + 38, + ], + "text": "f", + }, + "messageId": "generic", + }, + ], + }, +] +`; + +exports[`prefer-tacit > typescript > reports functions that can "safely" be changed 2`] = ` +[ + { + "column": 27, + "endColumn": 42, + "endLine": 1, + "line": 1, + "message": "Potentially unnecessary function wrapper.", + "messageId": "generic", + "nodeType": "ArrowFunctionExpression", + "ruleId": "prefer-tacit", + "severity": 2, + "suggestions": [ + { + "desc": "Potentially unnecessary function wrapper.", + "fix": { + "range": [ + 26, + 41, + ], + "text": "Boolean", + }, + "messageId": "generic", + }, + ], + }, +] +`; diff --git a/tests/rules/__snapshots__/type-declaration-immutability.test.ts.snap b/tests/rules/__snapshots__/type-declaration-immutability.test.ts.snap new file mode 100644 index 000000000..71a2ee95e --- /dev/null +++ b/tests/rules/__snapshots__/type-declaration-immutability.test.ts.snap @@ -0,0 +1,273 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`type-declaration-immutability > typescript > reports invalid deep arrays 1`] = ` +[ + { + "column": 6, + "endColumn": 25, + "endLine": 1, + "line": 1, + "message": "This type is declare to have an immutability of at least "ReadonlyDeep" (actual: "ReadonlyShallow").", + "messageId": "AtLeast", + "nodeType": "Identifier", + "ruleId": "type-declaration-immutability", + "severity": 2, + }, +] +`; + +exports[`type-declaration-immutability > typescript > reports invalid deep maps 1`] = ` +[ + { + "column": 6, + "endColumn": 23, + "endLine": 1, + "line": 1, + "message": "This type is declare to have an immutability of at least "ReadonlyDeep" (actual: "ReadonlyShallow").", + "messageId": "AtLeast", + "nodeType": "Identifier", + "ruleId": "type-declaration-immutability", + "severity": 2, + }, +] +`; + +exports[`type-declaration-immutability > typescript > reports invalid deep sets 1`] = ` +[ + { + "column": 6, + "endColumn": 23, + "endLine": 1, + "line": 1, + "message": "This type is declare to have an immutability of at least "ReadonlyDeep" (actual: "ReadonlyShallow").", + "messageId": "AtLeast", + "nodeType": "Identifier", + "ruleId": "type-declaration-immutability", + "severity": 2, + }, +] +`; + +exports[`type-declaration-immutability > typescript > reports invalid immutable arrays 1`] = ` +[ + { + "column": 6, + "endColumn": 22, + "endLine": 1, + "line": 1, + "message": "This type is declare to have an immutability of at least "Immutable" (actual: "ReadonlyDeep").", + "messageId": "AtLeast", + "nodeType": "Identifier", + "ruleId": "type-declaration-immutability", + "severity": 2, + }, +] +`; + +exports[`type-declaration-immutability > typescript > reports invalid immutable maps 1`] = ` +[ + { + "column": 6, + "endColumn": 20, + "endLine": 1, + "line": 1, + "message": "This type is declare to have an immutability of at least "Immutable" (actual: "ReadonlyDeep").", + "messageId": "AtLeast", + "nodeType": "Identifier", + "ruleId": "type-declaration-immutability", + "severity": 2, + }, +] +`; + +exports[`type-declaration-immutability > typescript > reports invalid immutable sets 1`] = ` +[ + { + "column": 6, + "endColumn": 20, + "endLine": 1, + "line": 1, + "message": "This type is declare to have an immutability of at least "Immutable" (actual: "ReadonlyDeep").", + "messageId": "AtLeast", + "nodeType": "Identifier", + "ruleId": "type-declaration-immutability", + "severity": 2, + }, +] +`; + +exports[`type-declaration-immutability > typescript > reports invalid immutable/deep records 1`] = ` +[ + { + "column": 6, + "endColumn": 21, + "endLine": 1, + "line": 1, + "message": "This type is declare to have an immutability of at least "ReadonlyDeep" (actual: "ReadonlyShallow").", + "messageId": "AtLeast", + "nodeType": "Identifier", + "ruleId": "type-declaration-immutability", + "severity": 2, + }, +] +`; + +exports[`type-declaration-immutability > typescript > reports invalid immutable/deep records 2`] = ` +[ + { + "column": 6, + "endColumn": 18, + "endLine": 1, + "line": 1, + "message": "This type is declare to have an immutability of at least "Immutable" (actual: "ReadonlyShallow").", + "messageId": "AtLeast", + "nodeType": "Identifier", + "ruleId": "type-declaration-immutability", + "severity": 2, + }, +] +`; + +exports[`type-declaration-immutability > typescript > reports invalid shallow arrays 1`] = ` +[ + { + "column": 6, + "endColumn": 21, + "endLine": 1, + "line": 1, + "message": "This type is declare to have an immutability of at least "ReadonlyShallow" (actual: "Mutable").", + "messageId": "AtLeast", + "nodeType": "Identifier", + "ruleId": "type-declaration-immutability", + "severity": 2, + }, +] +`; + +exports[`type-declaration-immutability > typescript > reports invalid shallow maps 1`] = ` +[ + { + "column": 6, + "endColumn": 19, + "endLine": 1, + "line": 1, + "message": "This type is declare to have an immutability of at least "ReadonlyShallow" (actual: "Mutable").", + "messageId": "AtLeast", + "nodeType": "Identifier", + "ruleId": "type-declaration-immutability", + "severity": 2, + }, +] +`; + +exports[`type-declaration-immutability > typescript > reports invalid shallow records 1`] = ` +[ + { + "column": 6, + "endColumn": 17, + "endLine": 1, + "line": 1, + "message": "This type is declare to have an immutability of at least "ReadonlyShallow" (actual: "Mutable").", + "messageId": "AtLeast", + "nodeType": "Identifier", + "ruleId": "type-declaration-immutability", + "severity": 2, + }, +] +`; + +exports[`type-declaration-immutability > typescript > reports invalid shallow records 2`] = ` +[ + { + "column": 6, + "endColumn": 17, + "endLine": 1, + "line": 1, + "message": "This type is declare to have an immutability of at least "ReadonlyShallow" (actual: "Mutable").", + "messageId": "AtLeast", + "nodeType": "Identifier", + "ruleId": "type-declaration-immutability", + "severity": 2, + }, +] +`; + +exports[`type-declaration-immutability > typescript > reports invalid shallow sets 1`] = ` +[ + { + "column": 6, + "endColumn": 19, + "endLine": 1, + "line": 1, + "message": "This type is declare to have an immutability of at least "ReadonlyShallow" (actual: "Mutable").", + "messageId": "AtLeast", + "nodeType": "Identifier", + "ruleId": "type-declaration-immutability", + "severity": 2, + }, +] +`; + +exports[`type-declaration-immutability > typescript > reports non-mutable primitives 1`] = ` +[ + { + "column": 6, + "endColumn": 19, + "endLine": 1, + "line": 1, + "message": "This type is declare to have an immutability of at most "Mutable" (actual: "Immutable").", + "messageId": "AtMost", + "nodeType": "Identifier", + "ruleId": "type-declaration-immutability", + "severity": 2, + }, +] +`; + +exports[`type-declaration-immutability > typescript > reports non-mutable records 1`] = ` +[ + { + "column": 6, + "endColumn": 16, + "endLine": 1, + "line": 1, + "message": "This type is declare to have an immutability of at most "Mutable" (actual: "Immutable").", + "messageId": "AtMost", + "nodeType": "Identifier", + "ruleId": "type-declaration-immutability", + "severity": 2, + }, +] +`; + +exports[`type-declaration-immutability > typescript > reports non-mutable records 2`] = ` +[ + { + "column": 6, + "endColumn": 16, + "endLine": 1, + "line": 1, + "message": "This type is declare to have an immutability of at most "Mutable" (actual: "Immutable").", + "messageId": "AtMost", + "nodeType": "Identifier", + "ruleId": "type-declaration-immutability", + "severity": 2, + }, +] +`; + +exports[`type-declaration-immutability > typescript > respects override settings 1`] = ` +[ + { + "column": 6, + "endColumn": 16, + "endLine": 1, + "line": 1, + "message": "This type is declare to have an immutability of at most "Mutable" (actual: "Immutable").", + "messageId": "AtMost", + "nodeType": "Identifier", + "ruleId": "type-declaration-immutability", + "severity": 2, + }, +] +`; diff --git a/tests/rules/functional-parameters.test.ts b/tests/rules/functional-parameters.test.ts new file mode 100644 index 000000000..533d87789 --- /dev/null +++ b/tests/rules/functional-parameters.test.ts @@ -0,0 +1,438 @@ +import dedent from "dedent"; +import { createRuleTester } from "eslint-vitest-rule-tester"; +import { describe, expect, it } from "vitest"; + +import { name, rule } from "#/rules/functional-parameters"; + +import { esLatestConfig } from "../utils/configs"; + +describe(name, () => { + describe("javascript - es latest", () => { + const { valid, invalid } = createRuleTester({ + name, + rule, + configs: esLatestConfig, + }); + + it("doesn't report non-issues", () => { + valid(dedent` + var foo = { + arguments: 2 + }; + foo.arguments = 3 + `); + + valid(dedent` + (function() { + console.log("hello world"); + })(); + `); + + valid(dedent` + (() => { + console.log("hello world"); + })(); + `); + + valid(dedent` + function foo([bar, ...baz]) { + console.log(bar, baz); + } + `); + }); + + it("reports rest parameter violations", () => { + const code = dedent` + function foo(...bar) { + console.log(bar); + } + `; + + const result = invalid({ + code, + errors: ["restParam"], + }); + + expect(result.messages).toMatchSnapshot(); + }); + + it("reports arguments keyword violations", () => { + const code = dedent` + function foo(bar) { + console.log(arguments); + } + `; + + const result = invalid({ + code, + errors: ["arguments"], + }); + + expect(result.messages).toMatchSnapshot(); + }); + + describe("options", () => { + describe("enforceParameterCount", () => { + it("atLeastOne", () => { + valid({ + code: dedent` + function foo(bar) { + console.log(bar); + } + `, + options: [{ enforceParameterCount: "atLeastOne" }], + }); + + valid({ + code: dedent` + function foo(bar, baz) { + console.log(bar, baz); + } + `, + options: [{ enforceParameterCount: "atLeastOne" }], + }); + + valid({ + code: dedent` + const foo = { + get bar() { + return "baz"; + } + } + `, + options: [ + { + enforceParameterCount: { + count: "atLeastOne", + ignoreGettersAndSetters: true, + }, + }, + ], + }); + + valid({ + code: dedent` + const foo = { + set bar(baz) { + this.baz = baz; + } + } + `, + options: [ + { + enforceParameterCount: { + count: "atLeastOne", + ignoreGettersAndSetters: true, + }, + }, + ], + }); + + const invalidResult = invalid({ + code: dedent` + function foo() { + console.log("hello world"); + } + `, + options: [{ enforceParameterCount: "atLeastOne" }], + errors: ["paramCountAtLeastOne"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("exactlyOne", () => { + valid({ + code: dedent` + function foo(bar) { + console.log(bar); + } + `, + options: [{ enforceParameterCount: "exactlyOne" }], + }); + + valid({ + code: dedent` + const foo = { + get bar() { + return "baz"; + } + } + `, + options: [ + { + enforceParameterCount: { + count: "exactlyOne", + ignoreGettersAndSetters: true, + }, + }, + ], + }); + + valid({ + code: dedent` + const foo = { + set bar(baz) { + this.baz = baz; + } + } + `, + options: [ + { + enforceParameterCount: { + count: "exactlyOne", + ignoreGettersAndSetters: true, + }, + }, + ], + }); + + const invalidResult1 = invalid({ + code: dedent` + function foo(bar, baz) { + console.log(bar, baz); + } + `, + options: [{ enforceParameterCount: "exactlyOne" }], + errors: ["paramCountExactlyOne"], + }); + expect(invalidResult1.messages).toMatchSnapshot(); + + const invalidResult2 = invalid({ + code: dedent` + function foo() { + console.log("hello world"); + } + `, + options: [{ enforceParameterCount: "exactlyOne" }], + errors: ["paramCountExactlyOne"], + }); + expect(invalidResult2.messages).toMatchSnapshot(); + }); + + it("ignoreLambdaExpression", () => { + valid({ + code: dedent` + function foo(param) {} + foo(function () {}); + `, + options: [ + { enforceParameterCount: { ignoreLambdaExpression: true } }, + ], + }); + + valid({ + code: dedent` + function foo(param) {} + foo(() => 1); + `, + options: [ + { enforceParameterCount: { ignoreLambdaExpression: true } }, + ], + }); + + const invalidResult = invalid({ + code: dedent` + function foo() {} + `, + options: [ + { + enforceParameterCount: { + count: "atLeastOne", + ignoreLambdaExpression: true, + }, + }, + ], + errors: ["paramCountAtLeastOne"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("ignoreIIFE", () => { + valid({ + code: dedent` + (() => { + console.log("hello world"); + })(); + `, + options: [ + { + enforceParameterCount: { + count: "atLeastOne", + ignoreIIFE: true, + }, + }, + ], + }); + + const invalidResult = invalid({ + code: dedent` + (() => { + console.log("hello world"); + })(); + `, + options: [ + { + enforceParameterCount: { + count: "atLeastOne", + ignoreIIFE: false, + }, + }, + ], + errors: ["paramCountAtLeastOne"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + }); + + it("ignoreIdentifierPattern", () => { + valid({ + code: dedent` + function foo(...bar) { + console.log(bar); + } + `, + options: [{ ignoreIdentifierPattern: "^foo" }], + }); + + valid({ + code: dedent` + const baz = { + foo(...bar) { + console.log(bar); + } + } + `, + options: [{ ignoreIdentifierPattern: "^foo" }], + }); + + const invalidResult = invalid({ + code: dedent` + function foo(...bar) { + console.log(bar); + } + `, + options: [{ ignoreIdentifierPattern: "^bar" }], + errors: ["restParam"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("ignorePrefixSelector", () => { + valid({ + code: dedent` + [1, 2, 3].reduce( + function(carry, current) { + return carry + current; + }, + 0 + ); + `, + options: [ + { + ignorePrefixSelector: + "CallExpression[callee.property.name='reduce']", + enforceParameterCount: "exactlyOne", + }, + ], + }); + + valid({ + code: dedent` + [1, 2, 3].map( + function(element, index) { + return element + index; + }, + 0 + ); + `, + options: [ + { + enforceParameterCount: "exactlyOne", + ignorePrefixSelector: + "CallExpression[callee.property.name='map']", + }, + ], + }); + + valid({ + code: dedent` + [1, 2, 3] + .map( + function(element, index) { + return element + index; + } + ) + .reduce( + function(carry, current) { + return carry + current; + }, + 0 + ); + `, + options: [ + { + enforceParameterCount: "exactlyOne", + ignorePrefixSelector: [ + "CallExpression[callee.property.name='reduce']", + "CallExpression[callee.property.name='map']", + ], + }, + ], + }); + + valid({ + code: dedent` + [1, 2, 3].reduce( + (carry, current) => carry + current, + 0 + ); + `, + options: [ + { + enforceParameterCount: "exactlyOne", + ignorePrefixSelector: + "CallExpression[callee.property.name='reduce']", + }, + ], + }); + + valid({ + code: dedent` + [1, 2, 3].map( + (element, index) => element + index, + 0 + ); + `, + options: [ + { + enforceParameterCount: "exactlyOne", + ignorePrefixSelector: + "CallExpression[callee.property.name='map']", + }, + ], + }); + + valid({ + code: dedent` + [1, 2, 3] + .map( + (element, index) => element + index + ) + .reduce( + (carry, current) => carry + current, 0 + ); + `, + options: [ + { + enforceParameterCount: "exactlyOne", + ignorePrefixSelector: [ + "CallExpression[callee.property.name='reduce']", + "CallExpression[callee.property.name='map']", + ], + }, + ], + }); + }); + }); + }); +}); diff --git a/tests/rules/functional-parameters/es2015/index.test.ts b/tests/rules/functional-parameters/es2015/index.test.ts deleted file mode 100644 index 3888ca50c..000000000 --- a/tests/rules/functional-parameters/es2015/index.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { name, rule } from "#/rules/functional-parameters"; -import { testRule } from "#/tests/helpers/testers"; - -import es3Invalid from "../es3/invalid"; -import es3Valid from "../es3/valid"; - -import invalid from "./invalid"; -import valid from "./valid"; - -const tests = { - valid: [...es3Valid, ...valid], - invalid: [...es3Invalid, ...invalid], -}; - -const tester = testRule(name, rule); - -tester.typescript(tests); -tester.esLatest(tests); -tester.es2015(tests); diff --git a/tests/rules/functional-parameters/es2015/invalid.ts b/tests/rules/functional-parameters/es2015/invalid.ts deleted file mode 100644 index 4e7120550..000000000 --- a/tests/rules/functional-parameters/es2015/invalid.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { AST_NODE_TYPES } from "@typescript-eslint/utils"; -import dedent from "dedent"; - -import { type rule } from "#/rules/functional-parameters"; -import { - type InvalidTestCaseSet, - type MessagesOf, - type OptionsOf, -} from "#/tests/helpers/util"; - -const tests: Array< - InvalidTestCaseSet, OptionsOf> -> = [ - { - code: dedent` - (() => { - console.log("hello world"); - })(); - `, - optionsSet: [[{ enforceParameterCount: { ignoreIIFE: false } }]], - errors: [ - { - messageId: "paramCountAtLeastOne", - type: AST_NODE_TYPES.ArrowFunctionExpression, - line: 1, - column: 2, - }, - ], - }, - { - code: dedent` - function foo(...bar) { - console.log(bar); - } - `, - optionsSet: [[]], - errors: [ - { - messageId: "restParam", - type: AST_NODE_TYPES.RestElement, - line: 1, - column: 14, - }, - ], - }, - { - code: dedent` - const obj = { - foo(...params) { - console.log(params); - }, - bar(...params) { - console.log(params); - }, - } - `, - optionsSet: [[{ ignoreIdentifierPattern: "^foo" }]], - errors: [ - { - messageId: "restParam", - type: AST_NODE_TYPES.RestElement, - line: 5, - column: 7, - }, - ], - }, - { - code: dedent` - function foo(param) {} - foo(() => 1); - `, - optionsSet: [[]], - errors: [ - { - messageId: "paramCountAtLeastOne", - type: AST_NODE_TYPES.ArrowFunctionExpression, - line: 2, - column: 5, - }, - ], - }, -]; - -export default tests; diff --git a/tests/rules/functional-parameters/es2015/valid.ts b/tests/rules/functional-parameters/es2015/valid.ts deleted file mode 100644 index 0ee2ce7a4..000000000 --- a/tests/rules/functional-parameters/es2015/valid.ts +++ /dev/null @@ -1,114 +0,0 @@ -import dedent from "dedent"; - -import { type rule } from "#/rules/functional-parameters"; -import { type OptionsOf, type ValidTestCaseSet } from "#/tests/helpers/util"; - -const tests: Array>> = [ - { - code: dedent` - (() => { - console.log("hello world"); - })(); - `, - optionsSet: [[]], - }, - { - code: dedent` - function foo([bar, ...baz]) { - console.log(bar, baz); - } - `, - optionsSet: [[]], - }, - { - code: dedent` - function foo(...bar) { - console.log(bar); - } - `, - optionsSet: [[{ ignoreIdentifierPattern: "^foo" }]], - }, - { - code: dedent` - const baz = { - foo(...bar) { - console.log(bar); - } - } - `, - optionsSet: [[{ ignoreIdentifierPattern: "^foo" }]], - }, - { - code: dedent` - [1, 2, 3].reduce( - (carry, current) => carry + current, - 0 - ); - `, - optionsSet: [ - [ - { - enforceParameterCount: "exactlyOne", - ignorePrefixSelector: "CallExpression[callee.property.name='reduce']", - }, - ], - ], - }, - { - code: dedent` - [1, 2, 3].map( - (element, index) => element + index, - 0 - ); - `, - optionsSet: [ - [ - { - enforceParameterCount: "exactlyOne", - ignorePrefixSelector: "CallExpression[callee.property.name='map']", - }, - ], - ], - }, - { - code: dedent` - [1, 2, 3] - .map( - (element, index) => element + index - ) - .reduce( - (carry, current) => carry + current, 0 - ); - `, - optionsSet: [ - [ - { - enforceParameterCount: "exactlyOne", - ignorePrefixSelector: [ - "CallExpression[callee.property.name='reduce']", - "CallExpression[callee.property.name='map']", - ], - }, - ], - ], - }, - { - code: dedent` - function foo(param) {} - foo(() => 1); - `, - optionsSet: [[{ enforceParameterCount: { ignoreLambdaExpression: true } }]], - }, - { - code: dedent` - const foo = { - get bar() { - return "baz"; - } - } - `, - optionsSet: [[{ enforceParameterCount: {} }]], - }, -]; - -export default tests; diff --git a/tests/rules/functional-parameters/es3/index.test.ts b/tests/rules/functional-parameters/es3/index.test.ts deleted file mode 100644 index a68f3c232..000000000 --- a/tests/rules/functional-parameters/es3/index.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { name, rule } from "#/rules/functional-parameters"; -import { testRule } from "#/tests/helpers/testers"; - -import invalid from "./invalid"; -import valid from "./valid"; - -const tests = { - valid, - invalid, -}; - -const tester = testRule(name, rule); - -tester.typescript(tests); -tester.esLatest(tests); -tester.es3(tests); diff --git a/tests/rules/functional-parameters/es3/invalid.ts b/tests/rules/functional-parameters/es3/invalid.ts deleted file mode 100644 index 4ab29505a..000000000 --- a/tests/rules/functional-parameters/es3/invalid.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { AST_NODE_TYPES } from "@typescript-eslint/utils"; -import dedent from "dedent"; - -import { type rule } from "#/rules/functional-parameters"; -import { - type InvalidTestCaseSet, - type MessagesOf, - type OptionsOf, -} from "#/tests/helpers/util"; - -const tests: Array< - InvalidTestCaseSet, OptionsOf> -> = [ - { - code: dedent` - function foo() { - console.log("hello world"); - } - `, - optionsSet: [[]], - errors: [ - { - messageId: "paramCountAtLeastOne", - type: AST_NODE_TYPES.FunctionDeclaration, - line: 1, - column: 1, - }, - ], - }, - { - code: dedent` - (function() { - console.log("hello world"); - })(); - `, - optionsSet: [[{ enforceParameterCount: { ignoreIIFE: false } }]], - errors: [ - { - messageId: "paramCountAtLeastOne", - type: AST_NODE_TYPES.FunctionExpression, - line: 1, - column: 2, - }, - ], - }, - { - code: dedent` - function foo(bar) { - console.log(arguments); - } - `, - optionsSet: [[]], - errors: [ - { - messageId: "arguments", - type: AST_NODE_TYPES.Identifier, - line: 2, - column: 15, - }, - ], - }, - { - code: dedent` - function foo() { - console.log("bar"); - } - `, - optionsSet: [[{ enforceParameterCount: "atLeastOne" }]], - errors: [ - { - messageId: "paramCountAtLeastOne", - type: AST_NODE_TYPES.FunctionDeclaration, - line: 1, - column: 1, - }, - ], - }, - { - code: dedent` - function foo() { - console.log("bar"); - } - `, - optionsSet: [[{ enforceParameterCount: "exactlyOne" }]], - errors: [ - { - messageId: "paramCountExactlyOne", - type: AST_NODE_TYPES.FunctionDeclaration, - line: 1, - column: 1, - }, - ], - }, - { - code: dedent` - function foo(bar, baz) { - console.log(bar, baz); - } - `, - optionsSet: [[{ enforceParameterCount: "exactlyOne" }]], - errors: [ - { - messageId: "paramCountExactlyOne", - type: AST_NODE_TYPES.FunctionDeclaration, - line: 1, - column: 1, - }, - ], - }, - { - code: dedent` - [1, 2, 3] - .map( - function(element, index) { - return element + index; - } - ) - .reduce( - function(carry, current) { - return carry + current; - }, - 0 - ); - `, - optionsSet: [[{ enforceParameterCount: "exactlyOne" }]], - errors: [ - { - messageId: "paramCountExactlyOne", - type: AST_NODE_TYPES.FunctionExpression, - line: 3, - column: 5, - }, - { - messageId: "paramCountExactlyOne", - type: AST_NODE_TYPES.FunctionExpression, - line: 8, - column: 5, - }, - ], - }, - { - code: dedent` - function foo(param) {} - foo(function () {}); - `, - optionsSet: [[]], - errors: [ - { - messageId: "paramCountAtLeastOne", - type: AST_NODE_TYPES.FunctionExpression, - line: 2, - column: 5, - }, - ], - }, -]; - -export default tests; diff --git a/tests/rules/functional-parameters/es3/valid.ts b/tests/rules/functional-parameters/es3/valid.ts deleted file mode 100644 index 1ce78b56e..000000000 --- a/tests/rules/functional-parameters/es3/valid.ts +++ /dev/null @@ -1,123 +0,0 @@ -import dedent from "dedent"; - -import { type rule } from "#/rules/functional-parameters"; -import { type OptionsOf, type ValidTestCaseSet } from "#/tests/helpers/util"; - -const tests: Array>> = [ - { - code: dedent` - var foo = { - arguments: 2 - }; - foo.arguments = 3 - `, - optionsSet: [[]], - }, - { - code: dedent` - (function() { - console.log("hello world"); - })(); - `, - optionsSet: [[]], - }, - { - code: dedent` - function foo(bar) { - console.log(bar); - } - `, - optionsSet: [ - [{ enforceParameterCount: "atLeastOne" }], - [{ enforceParameterCount: "exactlyOne" }], - ], - }, - { - code: dedent` - function foo(bar, baz) { - console.log(bar, baz); - } - `, - optionsSet: [ - [{ enforceParameterCount: "atLeastOne" }], - [ - { - ignoreIdentifierPattern: "^foo", - enforceParameterCount: "exactlyOne", - }, - ], - ], - }, - { - code: dedent` - [1, 2, 3].reduce( - function(carry, current) { - return carry + current; - }, - 0 - ); - `, - optionsSet: [ - [ - { - ignorePrefixSelector: "CallExpression[callee.property.name='reduce']", - enforceParameterCount: "exactlyOne", - }, - ], - ], - }, - { - code: dedent` - [1, 2, 3].map( - function(element, index) { - return element + index; - }, - 0 - ); - `, - optionsSet: [ - [ - { - enforceParameterCount: "exactlyOne", - ignorePrefixSelector: "CallExpression[callee.property.name='map']", - }, - ], - ], - }, - { - code: dedent` - [1, 2, 3] - .map( - function(element, index) { - return element + index; - } - ) - .reduce( - function(carry, current) { - return carry + current; - }, - 0 - ); - `, - optionsSet: [ - [ - { - enforceParameterCount: "exactlyOne", - ignorePrefixSelector: [ - "CallExpression[callee.property.name='reduce']", - "CallExpression[callee.property.name='map']", - ], - }, - ], - ], - }, - { - code: dedent` - function foo(param) {} - foo(function () {}); - `, - optionsSet: [[{ enforceParameterCount: { ignoreLambdaExpression: true } }]], - }, -]; - -export default tests; diff --git a/tests/rules/immutable-data/__snapshots__/array.test.ts.snap b/tests/rules/immutable-data/__snapshots__/array.test.ts.snap new file mode 100644 index 000000000..d085a4411 --- /dev/null +++ b/tests/rules/immutable-data/__snapshots__/array.test.ts.snap @@ -0,0 +1,549 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`immutable-data > typescript > options > ignoreImmediateMutation > reports immediately mutation when disabled 1`] = ` +[ + { + "column": 1, + "endColumn": 17, + "endLine": 1, + "line": 1, + "message": "Modifying an array is not allowed.", + "messageId": "array", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 20, + "endLine": 2, + "line": 2, + "message": "Modifying an array is not allowed.", + "messageId": "array", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 25, + "endLine": 3, + "line": 3, + "message": "Modifying an array is not allowed.", + "messageId": "array", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 34, + "endLine": 4, + "line": 4, + "message": "Modifying an array is not allowed.", + "messageId": "array", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, +] +`; + +exports[`immutable-data > typescript > options > ignoreNonConstDeclarations > reports variables declared as const 1`] = ` +[ + { + "column": 1, + "endColumn": 12, + "endLine": 2, + "line": 2, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 9, + "endLine": 3, + "line": 3, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UpdateExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 14, + "endLine": 4, + "line": 4, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UnaryExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 24, + "endLine": 5, + "line": 5, + "message": "Modifying an array is not allowed.", + "messageId": "array", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 12, + "endLine": 6, + "line": 6, + "message": "Modifying an array is not allowed.", + "messageId": "array", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 10, + "endLine": 7, + "line": 7, + "message": "Modifying an array is not allowed.", + "messageId": "array", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 12, + "endLine": 8, + "line": 8, + "message": "Modifying an array is not allowed.", + "messageId": "array", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 14, + "endLine": 9, + "line": 9, + "message": "Modifying an array is not allowed.", + "messageId": "array", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 12, + "endLine": 10, + "line": 10, + "message": "Modifying an array is not allowed.", + "messageId": "array", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 11, + "endLine": 11, + "line": 11, + "message": "Modifying an array is not allowed.", + "messageId": "array", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 20, + "endLine": 12, + "line": 12, + "message": "Modifying an array is not allowed.", + "messageId": "array", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 15, + "endLine": 13, + "line": 13, + "message": "Modifying an array is not allowed.", + "messageId": "array", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, +] +`; + +exports[`immutable-data > typescript > report mutating array methods 1`] = ` +[ + { + "column": 1, + "endColumn": 22, + "endLine": 2, + "line": 2, + "message": "Modifying an array is not allowed.", + "messageId": "array", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 10, + "endLine": 3, + "line": 3, + "message": "Modifying an array is not allowed.", + "messageId": "array", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 8, + "endLine": 4, + "line": 4, + "message": "Modifying an array is not allowed.", + "messageId": "array", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 10, + "endLine": 5, + "line": 5, + "message": "Modifying an array is not allowed.", + "messageId": "array", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 12, + "endLine": 6, + "line": 6, + "message": "Modifying an array is not allowed.", + "messageId": "array", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 10, + "endLine": 7, + "line": 7, + "message": "Modifying an array is not allowed.", + "messageId": "array", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 9, + "endLine": 8, + "line": 8, + "message": "Modifying an array is not allowed.", + "messageId": "array", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 18, + "endLine": 9, + "line": 9, + "message": "Modifying an array is not allowed.", + "messageId": "array", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 13, + "endLine": 10, + "line": 10, + "message": "Modifying an array is not allowed.", + "messageId": "array", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 27, + "endLine": 12, + "line": 12, + "message": "Modifying an array is not allowed.", + "messageId": "array", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 15, + "endLine": 13, + "line": 13, + "message": "Modifying an array is not allowed.", + "messageId": "array", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 13, + "endLine": 14, + "line": 14, + "message": "Modifying an array is not allowed.", + "messageId": "array", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 15, + "endLine": 15, + "line": 15, + "message": "Modifying an array is not allowed.", + "messageId": "array", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 17, + "endLine": 16, + "line": 16, + "message": "Modifying an array is not allowed.", + "messageId": "array", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 15, + "endLine": 17, + "line": 17, + "message": "Modifying an array is not allowed.", + "messageId": "array", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 14, + "endLine": 18, + "line": 18, + "message": "Modifying an array is not allowed.", + "messageId": "array", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 23, + "endLine": 19, + "line": 19, + "message": "Modifying an array is not allowed.", + "messageId": "array", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 18, + "endLine": 20, + "line": 20, + "message": "Modifying an array is not allowed.", + "messageId": "array", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, +] +`; + +exports[`immutable-data > typescript > reports array mutations 1`] = ` +[ + { + "column": 1, + "endColumn": 9, + "endLine": 3, + "line": 3, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 14, + "endLine": 4, + "line": 4, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 10, + "endLine": 5, + "line": 5, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 15, + "endLine": 6, + "line": 6, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 12, + "endLine": 7, + "line": 7, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UnaryExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 17, + "endLine": 8, + "line": 8, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UnaryExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 7, + "endLine": 9, + "line": 9, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UpdateExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 12, + "endLine": 10, + "line": 10, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UpdateExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 7, + "endLine": 11, + "line": 11, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UpdateExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 12, + "endLine": 12, + "line": 12, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UpdateExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 5, + "endColumn": 13, + "endLine": 13, + "line": 13, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 5, + "endColumn": 18, + "endLine": 14, + "line": 14, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 13, + "endLine": 15, + "line": 15, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 18, + "endLine": 16, + "line": 16, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, +] +`; diff --git a/tests/rules/immutable-data/__snapshots__/object.test.ts.snap b/tests/rules/immutable-data/__snapshots__/object.test.ts.snap new file mode 100644 index 000000000..7abd8b67c --- /dev/null +++ b/tests/rules/immutable-data/__snapshots__/object.test.ts.snap @@ -0,0 +1,1328 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`immutable-data > typescript > ignoreNonConstDeclarations > report parameters 1`] = ` +[ + { + "column": 3, + "endColumn": 16, + "endLine": 2, + "line": 2, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 19, + "endLine": 3, + "line": 3, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 11, + "endLine": 4, + "line": 4, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 11, + "endLine": 5, + "line": 5, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 11, + "endLine": 6, + "line": 6, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 11, + "endLine": 7, + "line": 7, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 11, + "endLine": 8, + "line": 8, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 12, + "endLine": 9, + "line": 9, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 12, + "endLine": 10, + "line": 10, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 13, + "endLine": 11, + "line": 11, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 11, + "endLine": 12, + "line": 12, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 11, + "endLine": 13, + "line": 13, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 11, + "endLine": 14, + "line": 14, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 12, + "endLine": 15, + "line": 15, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 13, + "endLine": 16, + "line": 16, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UnaryExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 16, + "endLine": 17, + "line": 17, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UnaryExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 8, + "endLine": 18, + "line": 18, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UpdateExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 8, + "endLine": 19, + "line": 19, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UpdateExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 8, + "endLine": 20, + "line": 20, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UpdateExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 8, + "endLine": 21, + "line": 21, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UpdateExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 7, + "endColumn": 14, + "endLine": 22, + "line": 22, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 7, + "endColumn": 12, + "endLine": 23, + "line": 23, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UpdateExpression", + "ruleId": "immutable-data", + "severity": 2, + }, +] +`; + +exports[`immutable-data > typescript > ignoreNonConstDeclarations > report variables declared as const 1`] = ` +[ + { + "column": 1, + "endColumn": 14, + "endLine": 2, + "line": 2, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 17, + "endLine": 3, + "line": 3, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 9, + "endLine": 4, + "line": 4, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 9, + "endLine": 5, + "line": 5, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 9, + "endLine": 6, + "line": 6, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 9, + "endLine": 7, + "line": 7, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 9, + "endLine": 8, + "line": 8, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 10, + "endLine": 9, + "line": 9, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 10, + "endLine": 10, + "line": 10, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 11, + "endLine": 11, + "line": 11, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 9, + "endLine": 12, + "line": 12, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 9, + "endLine": 13, + "line": 13, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 9, + "endLine": 14, + "line": 14, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 10, + "endLine": 15, + "line": 15, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 11, + "endLine": 16, + "line": 16, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UnaryExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 14, + "endLine": 17, + "line": 17, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UnaryExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 6, + "endLine": 18, + "line": 18, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UpdateExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 6, + "endLine": 19, + "line": 19, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UpdateExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 6, + "endLine": 20, + "line": 20, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UpdateExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 6, + "endLine": 21, + "line": 21, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UpdateExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 5, + "endColumn": 12, + "endLine": 22, + "line": 22, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 5, + "endColumn": 10, + "endLine": 23, + "line": 23, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UpdateExpression", + "ruleId": "immutable-data", + "severity": 2, + }, +] +`; + +exports[`immutable-data > typescript > options > ignoreNonConstDeclarations > report parameters 1`] = ` +[ + { + "column": 3, + "endColumn": 16, + "endLine": 2, + "line": 2, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 19, + "endLine": 3, + "line": 3, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 11, + "endLine": 4, + "line": 4, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 11, + "endLine": 5, + "line": 5, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 11, + "endLine": 6, + "line": 6, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 11, + "endLine": 7, + "line": 7, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 11, + "endLine": 8, + "line": 8, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 12, + "endLine": 9, + "line": 9, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 12, + "endLine": 10, + "line": 10, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 13, + "endLine": 11, + "line": 11, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 11, + "endLine": 12, + "line": 12, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 11, + "endLine": 13, + "line": 13, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 11, + "endLine": 14, + "line": 14, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 12, + "endLine": 15, + "line": 15, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 13, + "endLine": 16, + "line": 16, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UnaryExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 16, + "endLine": 17, + "line": 17, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UnaryExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 8, + "endLine": 18, + "line": 18, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UpdateExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 8, + "endLine": 19, + "line": 19, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UpdateExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 8, + "endLine": 20, + "line": 20, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UpdateExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 3, + "endColumn": 8, + "endLine": 21, + "line": 21, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UpdateExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 7, + "endColumn": 14, + "endLine": 22, + "line": 22, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 7, + "endColumn": 12, + "endLine": 23, + "line": 23, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UpdateExpression", + "ruleId": "immutable-data", + "severity": 2, + }, +] +`; + +exports[`immutable-data > typescript > options > ignoreNonConstDeclarations > report variables declared as const 1`] = ` +[ + { + "column": 1, + "endColumn": 14, + "endLine": 2, + "line": 2, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 17, + "endLine": 3, + "line": 3, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 9, + "endLine": 4, + "line": 4, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 9, + "endLine": 5, + "line": 5, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 9, + "endLine": 6, + "line": 6, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 9, + "endLine": 7, + "line": 7, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 9, + "endLine": 8, + "line": 8, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 10, + "endLine": 9, + "line": 9, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 10, + "endLine": 10, + "line": 10, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 11, + "endLine": 11, + "line": 11, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 9, + "endLine": 12, + "line": 12, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 9, + "endLine": 13, + "line": 13, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 9, + "endLine": 14, + "line": 14, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 10, + "endLine": 15, + "line": 15, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 11, + "endLine": 16, + "line": 16, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UnaryExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 14, + "endLine": 17, + "line": 17, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UnaryExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 6, + "endLine": 18, + "line": 18, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UpdateExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 6, + "endLine": 19, + "line": 19, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UpdateExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 6, + "endLine": 20, + "line": 20, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UpdateExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 6, + "endLine": 21, + "line": 21, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UpdateExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 5, + "endColumn": 12, + "endLine": 22, + "line": 22, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 5, + "endColumn": 10, + "endLine": 23, + "line": 23, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UpdateExpression", + "ruleId": "immutable-data", + "severity": 2, + }, +] +`; + +exports[`immutable-data > typescript > report object mutating patterns 1`] = ` +[ + { + "column": 1, + "endColumn": 14, + "endLine": 2, + "line": 2, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 17, + "endLine": 3, + "line": 3, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 9, + "endLine": 4, + "line": 4, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 9, + "endLine": 5, + "line": 5, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 9, + "endLine": 6, + "line": 6, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 9, + "endLine": 7, + "line": 7, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 9, + "endLine": 8, + "line": 8, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 10, + "endLine": 9, + "line": 9, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 10, + "endLine": 10, + "line": 10, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 11, + "endLine": 11, + "line": 11, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 9, + "endLine": 12, + "line": 12, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 9, + "endLine": 13, + "line": 13, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 9, + "endLine": 14, + "line": 14, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 10, + "endLine": 15, + "line": 15, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 11, + "endLine": 16, + "line": 16, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UnaryExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 14, + "endLine": 17, + "line": 17, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UnaryExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 6, + "endLine": 18, + "line": 18, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UpdateExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 6, + "endLine": 19, + "line": 19, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UpdateExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 6, + "endLine": 20, + "line": 20, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UpdateExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 6, + "endLine": 21, + "line": 21, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UpdateExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 5, + "endColumn": 12, + "endLine": 22, + "line": 22, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 5, + "endColumn": 10, + "endLine": 23, + "line": 23, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "UpdateExpression", + "ruleId": "immutable-data", + "severity": 2, + }, +] +`; + +exports[`immutable-data > typescript > reports Object.assign() on identifiers 1`] = ` +[ + { + "column": 9, + "endColumn": 44, + "endLine": 3, + "line": 3, + "message": "Modifying properties of existing object not allowed.", + "messageId": "object", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 9, + "endColumn": 48, + "endLine": 4, + "line": 4, + "message": "Modifying properties of existing object not allowed.", + "messageId": "object", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, +] +`; + +exports[`immutable-data > typescript > reports field mutation in class methods 1`] = ` +[ + { + "column": 5, + "endColumn": 17, + "endLine": 9, + "line": 9, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 5, + "endColumn": 17, + "endLine": 10, + "line": 10, + "message": "Modifying an existing object/array is not allowed.", + "messageId": "generic", + "nodeType": "AssignmentExpression", + "ruleId": "immutable-data", + "severity": 2, + }, +] +`; + +exports[`immutable-data > typescript > reports object mutating methods 1`] = ` +[ + { + "column": 1, + "endColumn": 66, + "endLine": 2, + "line": 2, + "message": "Modifying properties of existing object not allowed.", + "messageId": "object", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 63, + "endLine": 3, + "line": 3, + "message": "Modifying properties of existing object not allowed.", + "messageId": "object", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, + { + "column": 1, + "endColumn": 33, + "endLine": 4, + "line": 4, + "message": "Modifying properties of existing object not allowed.", + "messageId": "object", + "nodeType": "CallExpression", + "ruleId": "immutable-data", + "severity": 2, + }, +] +`; diff --git a/tests/rules/immutable-data/array.test.ts b/tests/rules/immutable-data/array.test.ts new file mode 100644 index 000000000..7da695984 --- /dev/null +++ b/tests/rules/immutable-data/array.test.ts @@ -0,0 +1,439 @@ +import dedent from "dedent"; +import { createRuleTester } from "eslint-vitest-rule-tester"; +import { describe, expect, it } from "vitest"; + +import { name, rule } from "#/rules/immutable-data"; + +import { typescriptConfig } from "../../utils/configs"; + +describe(name, () => { + describe("typescript", () => { + const { valid, invalid } = createRuleTester({ + name, + rule, + configs: typescriptConfig, + }); + + it("reports array mutations", () => { + const invalidResult = invalid({ + code: dedent` + var x = [5, 6]; + var y = [{ z: [3, 7] }]; + x[0] = 4; + y[0].z[0] = 4; + x[0] += 1; + y[0].z[0] += 1; + delete x[0]; + delete y[0].z[0]; + x[0]++; + y[0].z[0]++; + --x[0]; + --y[0].z[0]; + if (x[0] = 2) {} + if (y[0].z[0] = 2) {} + x.length = 5; + y[0].z.length = 1; + `, + options: [], + errors: [ + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + ], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("report mutating array methods", () => { + const invalidResult = invalid({ + code: dedent` + var x = [5, 6]; + x.copyWithin(0, 1, 2); + x.fill(3); + x.pop(); + x.push(3); + x.reverse(); + x.shift(); + x.sort(); + x.splice(0, 1, 9); + x.unshift(6); + var y = [{ z: [3, 7] }]; + y[0].z.copyWithin(0, 1, 2); + y[0].z.fill(3); + y[0].z.pop(); + y[0].z.push(3); + y[0].z.reverse(); + y[0].z.shift(); + y[0].z.sort(); + y[0].z.splice(0, 1, 9); + y[0].z.unshift(6); + `, + errors: [ + "array", + "array", + "array", + "array", + "array", + "array", + "array", + "array", + "array", + "array", + "array", + "array", + "array", + "array", + "array", + "array", + "array", + "array", + ], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("doesn't report non-mutating array methods", () => { + valid(dedent` + var x = [5, 6]; + var y = [{ z: [3, 7] }]; + + x.concat([3, 4]); + x.includes(2); + x.indexOf(1); + x.join(', '); + x.lastIndexOf(0); + x.slice(1, 2); + x.toString(); + x.toLocaleString("en", {timeZone: "UTC"}); + + y[0].z.concat([3, 4]); + y[0].z.includes(2); + y[0].z.indexOf(1); + y[0].z.join(', '); + y[0].z.lastIndexOf(0); + y[0].z.slice(1, 2); + y[0].z.toString(); + y[0].z.toLocaleString("en", {timeZone: "UTC"}); + `); + }); + + it("allows mutating array methods to be chained to array accessor/iteration methods", () => { + valid(dedent` + x.slice().copyWithin(0, 1, 2); + x.slice().fill(3); + x.slice().pop(); + x.slice().push(3); + x.slice().reverse(); + x.slice().shift(); + x.slice().sort(); + x.slice().splice(0, 1, 9); + x.slice().unshift(6); + + x.concat([1, 2, 3]).copyWithin(0, 1, 2); + x.concat([1, 2, 3]).fill(3); + x.concat([1, 2, 3]).pop(); + x.concat([1, 2, 3]).push(3); + x.concat([1, 2, 3]).reverse(); + x.concat([1, 2, 3]).shift(); + x.concat([1, 2, 3]).sort(); + x.concat([1, 2, 3]).splice(0, 1, 9); + x.concat([1, 2, 3]).unshift(6); + + x.filter(function (v) { return v > 1; }).copyWithin(0, 1, 2); + x.filter(function (v) { return v > 1; }).fill(3); + x.filter(function (v) { return v > 1; }).pop(); + x.filter(function (v) { return v > 1; }).push(3); + x.filter(function (v) { return v > 1; }).reverse(); + x.filter(function (v) { return v > 1; }).shift(); + x.filter(function (v) { return v > 1; }).sort(); + x.filter(function (v) { return v > 1; }).splice(0, 1, 9); + x.filter(function (v) { return v > 1; }).unshift(6); + + x.map(function (v) { return v * 2; }).copyWithin(0, 1, 2); + x.map(function (v) { return v * 2; }).fill(3); + x.map(function (v) { return v * 2; }).pop(); + x.map(function (v) { return v * 2; }).push(3); + x.map(function (v) { return v * 2; }).reverse(); + x.map(function (v) { return v * 2; }).shift(); + x.map(function (v) { return v * 2; }).sort(); + x.map(function (v) { return v * 2; }).splice(0, 1, 9); + x.map(function (v) { return v * 2; }).unshift(6); + + x.reduce(function (r, v) { return r.concat([v + 1]); }, []).copyWithin(0, 1, 2); + x.reduce(function (r, v) { return r.concat([v + 1]); }, []).fill(3); + x.reduce(function (r, v) { return r.concat([v + 1]); }, []).pop(); + x.reduce(function (r, v) { return r.concat([v + 1]); }, []).push(3); + x.reduce(function (r, v) { return r.concat([v + 1]); }, []).reverse(); + x.reduce(function (r, v) { return r.concat([v + 1]); }, []).shift(); + x.reduce(function (r, v) { return r.concat([v + 1]); }, []).sort(); + x.reduce(function (r, v) { return r.concat([v + 1]); }, []).splice(0, 1, 9); + x.reduce(function (r, v) { return r.concat([v + 1]); }, []).unshift(6); + + x.reduceRight(function (r, v) { return r.concat([v + 1]); }, []).copyWithin(0, 1, 2); + x.reduceRight(function (r, v) { return r.concat([v + 1]); }, []).fill(3); + x.reduceRight(function (r, v) { return r.concat([v + 1]); }, []).pop(); + x.reduceRight(function (r, v) { return r.concat([v + 1]); }, []).push(3); + x.reduceRight(function (r, v) { return r.concat([v + 1]); }, []).reverse(); + x.reduceRight(function (r, v) { return r.concat([v + 1]); }, []).shift(); + x.reduceRight(function (r, v) { return r.concat([v + 1]); }, []).sort(); + x.reduceRight(function (r, v) { return r.concat([v + 1]); }, []).splice(0, 1, 9); + x.reduceRight(function (r, v) { return r.concat([v + 1]); }, []).unshift(6); + `); + }); + + it("doesn't report mutating array methods on non-array objects", () => { + valid(dedent` + var z = { + copyWithin: function () {}, + fill: function () {}, + pop: function () {}, + push: function () {}, + reverse: function () {}, + shift: function () {}, + sort: function () {}, + splice: function () {}, + unshift: function () {} + }; + + z.copyWithin(); + z.fill(); + z.pop(); + z.push(); + z.reverse(); + z.shift(); + z.sort(); + z.splice(); + z.unshift(); + `); + }); + + describe("options", () => { + describe("ignoreNonConstDeclarations", () => { + it("reports variables declared as const", () => { + const invalidResult = invalid({ + code: dedent` + const arr = [0, 1]; + arr[0] += 1; + arr[1]++; + delete arr[0]; + arr.copyWithin(0, 1, 2); + arr.fill(3); + arr.pop(); + arr.push(3); + arr.reverse(); + arr.shift(); + arr.sort(); + arr.splice(0, 1, 9); + arr.unshift(6); + `, + options: [ + { + ignoreNonConstDeclarations: true, + }, + ], + errors: [ + "generic", + "generic", + "generic", + "array", + "array", + "array", + "array", + "array", + "array", + "array", + "array", + "array", + ], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("doesn't report variables not declared as const", () => { + valid({ + code: dedent` + let arr = [0, 1]; + arr[0] += 1; + arr[1]++; + delete arr[0]; + arr.copyWithin(0, 1, 2); + arr.fill(3); + arr.pop(); + arr.push(3); + arr.reverse(); + arr.shift(); + arr.sort(); + arr.splice(0, 1, 9); + arr.unshift(6); + `, + options: [ + { + ignoreNonConstDeclarations: true, + }, + ], + }); + + valid({ + code: dedent` + let arr = [0]; + for (const x of [1, 2]) { + for (const y of [3, 4]) { + arr[x][y] += 1; + arr[x][y]++; + delete arr[x][y]; + arr[x].copyWithin(x, 1, 2); + arr[x].fill(x); + arr[x].pop(); + arr[x].push(x); + arr[x].reverse(); + arr[x].shift(); + arr[x].sort(); + arr[x].splice(x, 1, 9); + arr[x].unshift(x); + } + } + `, + options: [ + { + ignoreNonConstDeclarations: true, + }, + ], + }); + + valid({ + code: dedent` + const constArr = [[0, 1], [2, 3]]; + let arr = constArr[0]; + arr[0] += 1; + arr[1]++; + delete arr[0]; + arr.copyWithin(0, 1, 2); + arr.fill(3); + arr.pop(); + arr.push(3); + arr.reverse(); + arr.shift(); + arr.sort(); + arr.splice(0, 1, 9); + arr.unshift(6); + `, + options: [ + { + ignoreNonConstDeclarations: true, + }, + ], + }); + }); + }); + + describe("ignoreImmediateMutation", () => { + it("doesn't report immediately mutation when enabled", () => { + valid({ + code: dedent` + [0, 1, 2].copyWithin(0, 1, 2); + [0, 1, 2].fill(3); + [0, 1, 2].pop(); + [0, 1, 2].push(3); + [0, 1, 2].reverse(); + [0, 1, 2].shift(); + [0, 1, 2].sort(); + [0, 1, 2].splice(0, 1, 9); + [0, 1, 2].unshift(6); + + new Array(5).copyWithin(0, 1, 2); + new Array(5).fill(3); + new Array(5).pop(); + new Array(5).push(3); + new Array(5).reverse(); + new Array(5).shift(); + new Array(5).sort(); + new Array(5).splice(0, 1, 9); + new Array(5).unshift(6); + + Array.of(0, 1, 2).copyWithin(0, 1, 2); + Array.of(0, 1, 2).fill(3); + Array.of(0, 1, 2).pop(); + Array.of(0, 1, 2).push(3); + Array.of(0, 1, 2).reverse(); + Array.of(0, 1, 2).shift(); + Array.of(0, 1, 2).sort(); + Array.of(0, 1, 2).splice(0, 1, 9); + Array.of(0, 1, 2).unshift(6); + + Array.from({ length: 10 }).copyWithin(0, 1, 2); + Array.from({ length: 10 }).fill(3); + Array.from({ length: 10 }).pop(); + Array.from({ length: 10 }).push(3); + Array.from({ length: 10 }).reverse(); + Array.from({ length: 10 }).shift(); + Array.from({ length: 10 }).sort(); + Array.from({ length: 10 }).splice(0, 1, 9); + Array.from({ length: 10 }).unshift(6); + `, + options: [ + { + ignoreImmediateMutation: true, + }, + ], + }); + + valid({ + code: dedent` + Object.entries({}).sort(); + Object.keys({}).sort(); + Object.values({}).sort(); + `, + options: [ + { + ignoreImmediateMutation: true, + }, + ], + }); + + valid({ + code: dedent` + "foo".split("").sort(); + const bar = "bar"; + bar.split("").sort(); + ([a, b, c] as string[]).sort(); + `, + options: [ + { + ignoreImmediateMutation: true, + }, + ], + }); + }); + + it("reports immediately mutation when disabled", () => { + const invalidResult = invalid({ + code: dedent` + [0, 1, 2].sort(); + new Array(5).sort(); + Array.of(0, 1, 2).sort(); + Array.from({ length: 10 }).sort(); + `, + options: [ + { + ignoreImmediateMutation: false, + }, + ], + errors: ["array", "array", "array", "array"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + }); + }); + }); +}); diff --git a/tests/rules/immutable-data/object.test.ts b/tests/rules/immutable-data/object.test.ts new file mode 100644 index 000000000..226824bb7 --- /dev/null +++ b/tests/rules/immutable-data/object.test.ts @@ -0,0 +1,455 @@ +import dedent from "dedent"; +import { createRuleTester } from "eslint-vitest-rule-tester"; +import { describe, expect, it } from "vitest"; + +import { name, rule } from "#/rules/immutable-data"; + +import { typescriptConfig } from "../../utils/configs"; + +describe(name, () => { + describe("typescript", () => { + const { valid, invalid } = createRuleTester({ + name, + rule, + configs: typescriptConfig, + }); + + it("report object mutating patterns", () => { + const invalidResult = invalid({ + code: dedent` + var x = {a: 1}; + x.foo = "bar"; + x["foo"] = "bar"; + x.a += 1; + x.a -= 1; + x.a *= 1; + x.a /= 1; + x.a %= 1; + x.a <<= 1; + x.a >>= 1; + x.a >>>= 1; + x.a &= 1; + x.a |= 1; + x.a ^= 1; + x.a **= 1; + delete x.a; + delete x["a"]; + x.a++; + x.a--; + ++x.a; + --x.a; + if (x.a = 2) {} + if (x.a++) {} + var y = x.a; + var z = x["a"]; + if (x.a && y.a) {} + var w = ~x.a; + if (!x.a) {} + `, + errors: [ + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + ], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports Object.assign() on identifiers", () => { + const invalidResult = invalid({ + code: dedent` + var x = { msg1: "hello", obj: { a: 1, b: 2} }; + + var a = Object.assign(x, { msg2: "world" }); + var b = Object.assign(x.obj, { msg2: "world" }); + `, + errors: ["object", "object"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports object mutating methods", () => { + const invalidResult = invalid({ + code: dedent` + var foo = { a: 1 }; + Object.defineProperties(foo, { b: { value: 2, writable: false }}); + Object.defineProperty(foo, "c", { value: 3, writable: false }); + Object.setPrototypeOf(foo, null); + `, + errors: ["object", "object", "object"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports field mutation in class methods", () => { + const invalidResult = invalid({ + code: dedent` + class Klass { + bar = 1; + + constructor() { + this.baz = "hello"; + } + + zoo() { + this.bar = 2; + this.baz = 3; + } + } + `, + errors: ["generic", "generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("doesn't report non-object mutating patterns", () => { + valid(dedent` + var foo = function () {}; + var bar = { + x: 1, + y: foo + }; + var x = 0; + x = 4; + x += 1; + x -= 1; + x++; + x--; + ++x; + --x; + if (x = 2) {} + if (x++) {} + var y = x.a; + var z = x["a"]; + if (x.a && y.a) {} + var w = ~x.a; + if (!x.a) {} + `); + }); + + it("doesn't report Object.assign() on non-identifiers", () => { + valid(dedent` + var x = { msg1: "hello", obj: { a: 1, b: 2}, func: function() {} }; + var bar = function(a, b, c) { return { a: a, b: b, c: c }; }; + + var a = Object.assign({}, { msg: "hello world" }); + var b = Object.assign(bar(1, 2, 3), { d: 4 }); + var c = Object.assign(x.func(), { d: 4 }); + `); + }); + + it("doesn't report initialization of class members in constructor", () => { + valid(dedent` + class Klass { + bar = 1; + constructor() { + this.baz = "hello"; + } + } + `); + }); + + describe("options", () => { + describe("ignoreNonConstDeclarations", () => { + it("report variables declared as const", () => { + const invalidResult = invalid({ + code: dedent` + const x = {a: 1, b:{}}; + x.foo = "bar"; + x["foo"] = "bar"; + x.a += 1; + x.a -= 1; + x.a *= 1; + x.a /= 1; + x.a %= 1; + x.a <<= 1; + x.a >>= 1; + x.a >>>= 1; + x.a &= 1; + x.a |= 1; + x.a ^= 1; + x.a **= 1; + delete x.a; + delete x["a"]; + x.a++; + x.a--; + ++x.a; + --x.a; + if (x.a = 2) {} + if (x.a++) {} + Object.assign(x, { c: "world" }); + Object.assign(x.b, { c: "world" }); + Object.defineProperties(x, { d: { value: 2, writable: false }}); + Object.defineProperty(x, "e", { value: 3, writable: false }); + Object.setPrototypeOf(x, null); + `, + options: [{ ignoreNonConstDeclarations: true }], + errors: [ + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + ], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("report parameters", () => { + const invalidResult = invalid({ + code: dedent` + function y(x: {a: 1, b: object}) { + x.foo = "bar"; + x["foo"] = "bar"; + x.a += 1; + x.a -= 1; + x.a *= 1; + x.a /= 1; + x.a %= 1; + x.a <<= 1; + x.a >>= 1; + x.a >>>= 1; + x.a &= 1; + x.a |= 1; + x.a ^= 1; + x.a **= 1; + delete x.a; + delete x["a"]; + x.a++; + x.a--; + ++x.a; + --x.a; + if (x.a = 2) {} + if (x.a++) {} + Object.assign(x, { c: "world" }); + Object.assign(x.b, { c: "world" }); + Object.defineProperties(x, { d: { value: 2, writable: false }}); + Object.defineProperty(x, "e", { value: 3, writable: false }); + Object.setPrototypeOf(x, null); + } + `, + options: [ + { + ignoreNonConstDeclarations: { + treatParametersAsConst: true, + }, + }, + ], + errors: [ + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + "generic", + ], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("doesn't report variables not declared as const", () => { + valid({ + code: dedent` + let x = {a: 1, b:{}}; + x.foo = "bar"; + x["foo"] = "bar"; + x.a += 1; + x.a -= 1; + x.a *= 1; + x.a /= 1; + x.a %= 1; + x.a <<= 1; + x.a >>= 1; + x.a >>>= 1; + x.a &= 1; + x.a |= 1; + x.a ^= 1; + x.a **= 1; + delete x.a; + delete x["a"]; + x.a++; + x.a--; + ++x.a; + --x.a; + if (x.a = 2) {} + if (x.a++) {} + Object.assign(x, { c: "world" }); + Object.assign(x.b, { c: "world" }); + Object.defineProperties(x, { d: { value: 2, writable: false }}); + Object.defineProperty(x, "e", { value: 3, writable: false }); + Object.setPrototypeOf(x, null); + `, + options: [{ ignoreNonConstDeclarations: true }], + }); + }); + + it("doesn't report parameters", () => { + valid({ + code: dedent` + function y(x: {a: 1, b: object}) { + x.foo = "bar"; + x["foo"] = "bar"; + x.a += 1; + x.a -= 1; + x.a *= 1; + x.a /= 1; + x.a %= 1; + x.a <<= 1; + x.a >>= 1; + x.a >>>= 1; + x.a &= 1; + x.a |= 1; + x.a ^= 1; + x.a **= 1; + delete x.a; + delete x["a"]; + x.a++; + x.a--; + ++x.a; + --x.a; + if (x.a = 2) {} + if (x.a++) {} + Object.assign(x, { c: "world" }); + Object.assign(x.b, { c: "world" }); + Object.defineProperties(x, { d: { value: 2, writable: false }}); + Object.defineProperty(x, "e", { value: 3, writable: false }); + Object.setPrototypeOf(x, null); + } + `, + options: [ + { + ignoreNonConstDeclarations: { + treatParametersAsConst: false, + }, + }, + ], + }); + }); + }); + + describe("ignoreIdentifierPattern", () => { + it("ignores variables declared as mutable", () => { + valid({ + code: dedent` + var mutableVar = { a: 1 }; + delete mutableVar.a; + Object.assign(mutableVar, { b: 2 }); + `, + options: [{ ignoreIdentifierPattern: ["^mutable"] }], + }); + }); + }); + + describe("ignoreAccessorPattern", () => { + it("ignores variables declared as mutable", () => { + valid({ + code: dedent` + function y(mutable_x: {a: 1, b: object}) { + mutable_x.foo = "bar"; + mutable_x["foo"] = "bar"; + mutable_x.a += 1; + mutable_x.a -= 1; + mutable_x.a *= 1; + mutable_x.a /= 1; + mutable_x.a %= 1; + mutable_x.a <<= 1; + mutable_x.a >>= 1; + mutable_x.a >>>= 1; + mutable_x.a &= 1; + mutable_x.a |= 1; + mutable_x.a ^= 1; + mutable_x.a **= 1; + delete mutable_x.a; + delete mutable_x["a"]; + mutable_x.a++; + mutable_x.a--; + ++mutable_x.a; + --mutable_x.a; + if (mutable_x.a = 2) {} + if (mutable_x.a++) {} + Object.assign(mutable_x, { c: "world" }); + Object.assign(mutable_x.b, { c: "world" }); + Object.defineProperties(mutable_x, { d: { value: 2, writable: false }}); + Object.defineProperty(mutable_x, "e", { value: 3, writable: false }); + Object.setPrototypeOf(mutable_x, null); + (mutable_x as Bar).foo = "bar"; + mutable_x!.foo = "bar"; + } + `, + options: [ + { + ignoreNonConstDeclarations: { treatParametersAsConst: true }, + ignoreAccessorPattern: "mutable*.**", + }, + ], + }); + }); + + it("ignores class fields", () => { + valid({ + code: dedent` + class Klass { + mutate() { + this.mutableField = 0; + } + } + `, + options: [{ ignoreAccessorPattern: ["this.*.**"] }], + }); + }); + }); + }); + }); +}); diff --git a/tests/rules/immutable-data/ts/array/index.test.ts b/tests/rules/immutable-data/ts/array/index.test.ts deleted file mode 100644 index 90cfe0f04..000000000 --- a/tests/rules/immutable-data/ts/array/index.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { name, rule } from "#/rules/immutable-data"; -import { testRule } from "#/tests/helpers/testers"; - -import invalid from "./invalid"; -import valid from "./valid"; - -const tests = { - valid, - invalid, -}; - -const tester = testRule(name, rule); - -tester.typescript(tests); diff --git a/tests/rules/immutable-data/ts/array/invalid.ts b/tests/rules/immutable-data/ts/array/invalid.ts deleted file mode 100644 index 00b42684f..000000000 --- a/tests/rules/immutable-data/ts/array/invalid.ts +++ /dev/null @@ -1,814 +0,0 @@ -import { AST_NODE_TYPES } from "@typescript-eslint/utils"; -import dedent from "dedent"; - -import { type rule } from "#/rules/immutable-data"; -import { - type InvalidTestCaseSet, - type MessagesOf, - type OptionsOf, -} from "#/tests/helpers/util"; - -const tests: Array< - InvalidTestCaseSet, OptionsOf> -> = [ - { - code: dedent` - var x = [5, 6]; - var y = [{ z: [3, 7] }]; - x[0] = 4; - y[0].z[0] = 4; - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 3, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 4, - column: 1, - }, - ], - }, - { - code: dedent` - var x = [5, 6]; - var y = [{ z: [3, 7] }]; - x[0] += 1; - y[0].z[0] += 1; - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 3, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 4, - column: 1, - }, - ], - }, - { - code: dedent` - var x = [5, 6]; - var y = [{ z: [3, 7] }]; - x[0] -= 1; - y[0].z[0] -= 1; - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 3, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 4, - column: 1, - }, - ], - }, - { - code: dedent` - var x = [5, 6]; - var y = [{ z: [3, 7] }]; - delete x[0]; - delete y[0].z[0]; - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.UnaryExpression, - line: 3, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.UnaryExpression, - line: 4, - column: 1, - }, - ], - }, - { - code: dedent` - var x = [5, 6]; - var y = [{ z: [3, 7] }]; - x[0]++; - y[0].z[0]++; - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.UpdateExpression, - line: 3, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.UpdateExpression, - line: 4, - column: 1, - }, - ], - }, - { - code: dedent` - var x = [5, 6]; - var y = [{ z: [3, 7] }]; - x[0]--; - y[0].z[0]--; - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.UpdateExpression, - line: 3, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.UpdateExpression, - line: 4, - column: 1, - }, - ], - }, - { - code: dedent` - var x = [5, 6]; - var y = [{ z: [3, 7] }]; - ++x[0]; - ++y[0].z[0]; - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.UpdateExpression, - line: 3, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.UpdateExpression, - line: 4, - column: 1, - }, - ], - }, - { - code: dedent` - var x = [5, 6]; - var y = [{ z: [3, 7] }]; - --x[0]; - --y[0].z[0]; - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.UpdateExpression, - line: 3, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.UpdateExpression, - line: 4, - column: 1, - }, - ], - }, - { - code: dedent` - var x = [5, 6]; - var y = [{ z: [3, 7] }]; - if (x[0] = 2) {} - if (y[0].z[0] = 2) {} - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 3, - column: 5, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 4, - column: 5, - }, - ], - }, - { - code: dedent` - var x = [5, 6]; - var y = [{ z: [3, 7] }]; - if (x[0]++) {} - if (y[0].z[0]++) {} - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.UpdateExpression, - line: 3, - column: 5, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.UpdateExpression, - line: 4, - column: 5, - }, - ], - }, - { - code: dedent` - var x = [5, 6]; - var y = [{ z: [3, 7] }]; - x.length = 5; - y[0].z.length = 1; - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 3, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 4, - column: 1, - }, - ], - }, - // Disallowed array mutation methods. - { - code: dedent` - var x = [5, 6]; - x.copyWithin(0, 1, 2); - x.fill(3); - x.pop(); - x.push(3); - x.reverse(); - x.shift(); - x.sort(); - x.splice(0, 1, 9); - x.unshift(6); - var y = [{ z: [3, 7] }]; - y[0].z.copyWithin(0, 1, 2); - y[0].z.fill(3); - y[0].z.pop(); - y[0].z.push(3); - y[0].z.reverse(); - y[0].z.shift(); - y[0].z.sort(); - y[0].z.splice(0, 1, 9); - y[0].z.unshift(6); - `, - optionsSet: [[]], - errors: [ - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 2, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 3, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 4, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 5, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 6, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 7, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 8, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 9, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 10, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 12, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 13, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 14, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 15, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 16, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 17, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 18, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 19, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 20, - column: 1, - }, - ], - }, - // Disallowed array mutation methods to be chained to the creation of an array - // if `ignoreImmediateMutation` is false. - { - code: dedent` - [0, 1, 2].copyWithin(0, 1, 2); - [0, 1, 2].fill(3); - [0, 1, 2].pop(); - [0, 1, 2].push(3); - [0, 1, 2].reverse(); - [0, 1, 2].shift(); - [0, 1, 2].sort(); - [0, 1, 2].splice(0, 1, 9); - [0, 1, 2].unshift(6); - `, - optionsSet: [[{ ignoreImmediateMutation: false }]], - errors: [ - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 1, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 2, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 3, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 4, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 5, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 6, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 7, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 8, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 9, - column: 1, - }, - ], - }, - { - code: dedent` - const x = [5, 6]; - const y = [{ z: [3, 7] }]; - x[0] *= 2; - y[0].z[0] *= 2; - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 3, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 4, - column: 1, - }, - ], - }, - { - code: dedent` - const x = [5, 6]; - const y = [{ z: [3, 7] }]; - x[0] **= 2; - y[0].z[0] **= 2; - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 3, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 4, - column: 1, - }, - ], - }, - { - code: dedent` - const x = [5, 6]; - const y = [{ z: [3, 7] }]; - x[0] /= 1; - y[0].z[0] /= 1; - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 3, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 4, - column: 1, - }, - ], - }, - { - code: dedent` - const x = [5, 6]; - const y = [{ z: [3, 7] }]; - x[0] %= 1; - y[0].z[0] %= 1; - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 3, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 4, - column: 1, - }, - ], - }, - { - code: dedent` - const x = [5, 6]; - const y = [{ z: [3, 7] }]; - x[0] <<= 1; - y[0].z[0] <<= 1; - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 3, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 4, - column: 1, - }, - ], - }, - { - code: dedent` - const x = [5, 6]; - const y = [{ z: [3, 7] }]; - x[0] >>= 1; - y[0].z[0] >>= 1; - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 3, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 4, - column: 1, - }, - ], - }, - { - code: dedent` - const x = [5, 6]; - const y = [{ z: [3, 7] }]; - x[0] >>>= 1; - y[0].z[0] >>>= 1; - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 3, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 4, - column: 1, - }, - ], - }, - { - code: dedent` - const x = [5, 6]; - const y = [{ z: [3, 7] }]; - x[0] &= 1; - y[0].z[0] &= 1; - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 3, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 4, - column: 1, - }, - ], - }, - { - code: dedent` - const x = [5, 6]; - const y = [{ z: [3, 7] }]; - x[0] |= 1; - y[0].z[0] |= 1; - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 3, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 4, - column: 1, - }, - ], - }, - { - code: dedent` - const x = [5, 6]; - const y = [{ z: [3, 7] }]; - x[0] ^= 1; - y[0].z[0] ^= 1; - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 3, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 4, - column: 1, - }, - ], - }, - // ignoreNonConstDeclarations. - { - code: dedent` - const arr = [0, 1]; - arr[0] += 1; - arr[1]++; - delete arr[0]; - arr.copyWithin(0, 1, 2); - arr.fill(3); - arr.pop(); - arr.push(3); - arr.reverse(); - arr.shift(); - arr.sort(); - arr.splice(0, 1, 9); - arr.unshift(6); - `, - optionsSet: [[{ ignoreNonConstDeclarations: true }]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 2, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.UpdateExpression, - line: 3, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.UnaryExpression, - line: 4, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 5, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 6, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 7, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 8, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 9, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 10, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 11, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 12, - column: 1, - }, - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 13, - column: 1, - }, - ], - }, - { - code: dedent` - (mutable_foo as string[]).sort(); - `, - optionsSet: [[]], - errors: [ - { - messageId: "array", - type: AST_NODE_TYPES.CallExpression, - line: 1, - column: 1, - }, - ], - }, -]; - -export default tests; diff --git a/tests/rules/immutable-data/ts/array/valid.ts b/tests/rules/immutable-data/ts/array/valid.ts deleted file mode 100644 index e6f05b243..000000000 --- a/tests/rules/immutable-data/ts/array/valid.ts +++ /dev/null @@ -1,352 +0,0 @@ -import dedent from "dedent"; - -import { type rule } from "#/rules/immutable-data"; -import { type OptionsOf, type ValidTestCaseSet } from "#/tests/helpers/util"; - -const tests: Array>> = [ - // Allowed non-array mutation patterns. - { - code: dedent` - var foo = function () {}; - var bar = { - x: 1, - y: foo - }; - var x = 0; - x = 4; - x += 1; - x -= 1; - x++; - x--; - ++x; - --x; - if (x = 2) {} - if (x++) {} - `, - optionsSet: [[]], - }, - // Allow array non-mutation methods - { - code: dedent` - var x = [5, 6]; - var y = [{ z: [3, 7] }]; - - x.concat([3, 4]); - x.includes(2); - x.indexOf(1); - x.join(', '); - x.lastIndexOf(0); - x.slice(1, 2); - x.toString(); - x.toLocaleString("en", {timeZone: "UTC"}); - - y[0].z.concat([3, 4]); - y[0].z.includes(2); - y[0].z.indexOf(1); - y[0].z.join(', '); - y[0].z.lastIndexOf(0); - y[0].z.slice(1, 2); - y[0].z.toString(); - y[0].z.toLocaleString("en", {timeZone: "UTC"}); - `, - optionsSet: [[]], - }, - // Allowed array mutation methods to be chained to the creation of an array. - { - code: dedent` - [0, 1, 2].copyWithin(0, 1, 2); - [0, 1, 2].fill(3); - [0, 1, 2].pop(); - [0, 1, 2].push(3); - [0, 1, 2].reverse(); - [0, 1, 2].shift(); - [0, 1, 2].sort(); - [0, 1, 2].splice(0, 1, 9); - [0, 1, 2].unshift(6); - - new Array(5).copyWithin(0, 1, 2); - new Array(5).fill(3); - new Array(5).pop(); - new Array(5).push(3); - new Array(5).reverse(); - new Array(5).shift(); - new Array(5).sort(); - new Array(5).splice(0, 1, 9); - new Array(5).unshift(6); - `, - optionsSet: [[]], - }, - // Allowed array mutation methods to be chained to array constructor functions. - { - code: dedent` - Array.of(0, 1, 2).copyWithin(0, 1, 2); - Array.of(0, 1, 2).fill(3); - Array.of(0, 1, 2).pop(); - Array.of(0, 1, 2).push(3); - Array.of(0, 1, 2).reverse(); - Array.of(0, 1, 2).shift(); - Array.of(0, 1, 2).sort(); - Array.of(0, 1, 2).splice(0, 1, 9); - Array.of(0, 1, 2).unshift(6); - - Array.from({ length: 10 }).copyWithin(0, 1, 2); - Array.from({ length: 10 }).fill(3); - Array.from({ length: 10 }).pop(); - Array.from({ length: 10 }).push(3); - Array.from({ length: 10 }).reverse(); - Array.from({ length: 10 }).shift(); - Array.from({ length: 10 }).sort(); - Array.from({ length: 10 }).splice(0, 1, 9); - Array.from({ length: 10 }).unshift(6); - `, - optionsSet: [[]], - }, - // Allowed array mutation methods to be chained to array accessor/iteration methods. - { - code: dedent` - x.slice().copyWithin(0, 1, 2); - x.slice().fill(3); - x.slice().pop(); - x.slice().push(3); - x.slice().reverse(); - x.slice().shift(); - x.slice().sort(); - x.slice().splice(0, 1, 9); - x.slice().unshift(6); - - x.concat([1, 2, 3]).copyWithin(0, 1, 2); - x.concat([1, 2, 3]).fill(3); - x.concat([1, 2, 3]).pop(); - x.concat([1, 2, 3]).push(3); - x.concat([1, 2, 3]).reverse(); - x.concat([1, 2, 3]).shift(); - x.concat([1, 2, 3]).sort(); - x.concat([1, 2, 3]).splice(0, 1, 9); - x.concat([1, 2, 3]).unshift(6); - - x.filter(function (v) { return v > 1; }).copyWithin(0, 1, 2); - x.filter(function (v) { return v > 1; }).fill(3); - x.filter(function (v) { return v > 1; }).pop(); - x.filter(function (v) { return v > 1; }).push(3); - x.filter(function (v) { return v > 1; }).reverse(); - x.filter(function (v) { return v > 1; }).shift(); - x.filter(function (v) { return v > 1; }).sort(); - x.filter(function (v) { return v > 1; }).splice(0, 1, 9); - x.filter(function (v) { return v > 1; }).unshift(6); - - x.map(function (v) { return v * 2; }).copyWithin(0, 1, 2); - x.map(function (v) { return v * 2; }).fill(3); - x.map(function (v) { return v * 2; }).pop(); - x.map(function (v) { return v * 2; }).push(3); - x.map(function (v) { return v * 2; }).reverse(); - x.map(function (v) { return v * 2; }).shift(); - x.map(function (v) { return v * 2; }).sort(); - x.map(function (v) { return v * 2; }).splice(0, 1, 9); - x.map(function (v) { return v * 2; }).unshift(6); - - x.reduce(function (r, v) { return r.concat([v + 1]); }, []).copyWithin(0, 1, 2); - x.reduce(function (r, v) { return r.concat([v + 1]); }, []).fill(3); - x.reduce(function (r, v) { return r.concat([v + 1]); }, []).pop(); - x.reduce(function (r, v) { return r.concat([v + 1]); }, []).push(3); - x.reduce(function (r, v) { return r.concat([v + 1]); }, []).reverse(); - x.reduce(function (r, v) { return r.concat([v + 1]); }, []).shift(); - x.reduce(function (r, v) { return r.concat([v + 1]); }, []).sort(); - x.reduce(function (r, v) { return r.concat([v + 1]); }, []).splice(0, 1, 9); - x.reduce(function (r, v) { return r.concat([v + 1]); }, []).unshift(6); - - x.reduceRight(function (r, v) { return r.concat([v + 1]); }, []).copyWithin(0, 1, 2); - x.reduceRight(function (r, v) { return r.concat([v + 1]); }, []).fill(3); - x.reduceRight(function (r, v) { return r.concat([v + 1]); }, []).pop(); - x.reduceRight(function (r, v) { return r.concat([v + 1]); }, []).push(3); - x.reduceRight(function (r, v) { return r.concat([v + 1]); }, []).reverse(); - x.reduceRight(function (r, v) { return r.concat([v + 1]); }, []).shift(); - x.reduceRight(function (r, v) { return r.concat([v + 1]); }, []).sort(); - x.reduceRight(function (r, v) { return r.concat([v + 1]); }, []).splice(0, 1, 9); - x.reduceRight(function (r, v) { return r.concat([v + 1]); }, []).unshift(6); - `, - optionsSet: [[]], - }, - // Don't catch calls of array mutation methods on non-array objects. - { - code: dedent` - var z = { - copyWithin: function () {}, - fill: function () {}, - pop: function () {}, - push: function () {}, - reverse: function () {}, - shift: function () {}, - sort: function () {}, - splice: function () {}, - unshift: function () {} - }; - - z.copyWithin(); - z.fill(); - z.pop(); - z.push(); - z.reverse(); - z.shift(); - z.sort(); - z.splice(); - z.unshift(); - `, - optionsSet: [[]], - }, - { - code: dedent` - var mutableX = [0, 1]; - mutableX.copyWithin(0, 1, 2); - mutableX.fill(3); - mutableX.pop(); - mutableX.push(3); - mutableX.reverse(); - mutableX.shift(); - mutableX.sort(); - mutableX.splice(0, 1, 9); - mutableX.unshift(6); - `, - optionsSet: [ - [{ ignoreIdentifierPattern: "^mutable" }], - [{ ignoreAccessorPattern: "mutable*" }], - ], - }, - { - code: dedent` - const x = 0; - x *= 1; - x **= 1; - x /= 1; - x %= 1; - x <<= 1; - x >>= 1; - x >>>= 1; - x &= 1; - x |= 1; - x ^= 1; - `, - optionsSet: [[]], - }, - // ignoreNonConstDeclarations. - { - code: dedent` - var arr = [0, 1]; - arr[0] += 1; - arr[1]++; - delete arr[0]; - arr.copyWithin(0, 1, 2); - arr.fill(3); - arr.pop(); - arr.push(3); - arr.reverse(); - arr.shift(); - arr.sort(); - arr.splice(0, 1, 9); - arr.unshift(6); - `, - optionsSet: [[{ ignoreNonConstDeclarations: true }]], - }, - { - code: dedent` - let arr = [0, 1]; - arr[0] += 1; - arr[1]++; - delete arr[0]; - arr.copyWithin(0, 1, 2); - arr.fill(3); - arr.pop(); - arr.push(3); - arr.reverse(); - arr.shift(); - arr.sort(); - arr.splice(0, 1, 9); - arr.unshift(6); - `, - optionsSet: [[{ ignoreNonConstDeclarations: true }]], - }, - { - code: dedent` - let arr = [0]; - for (const x of [1, 2]) { - for (const y of [3, 4]) { - arr[x][y] += 1; - arr[x][y]++; - delete arr[x][y]; - arr[x].copyWithin(x, 1, 2); - arr[x].fill(x); - arr[x].pop(); - arr[x].push(x); - arr[x].reverse(); - arr[x].shift(); - arr[x].sort(); - arr[x].splice(x, 1, 9); - arr[x].unshift(x); - } - } - `, - optionsSet: [[{ ignoreNonConstDeclarations: true }]], - }, - { - code: dedent` - const constArr = [[0, 1], [2, 3]]; - let arr = constArr[0]; - arr[0] += 1; - arr[1]++; - delete arr[0]; - arr.copyWithin(0, 1, 2); - arr.fill(3); - arr.pop(); - arr.push(3); - arr.reverse(); - arr.shift(); - arr.sort(); - arr.splice(0, 1, 9); - arr.unshift(6); - `, - optionsSet: [[{ ignoreNonConstDeclarations: true }]], - }, - { - code: dedent` - [0, 1, 2].copyWithin(0, 1, 2); - [0, 1, 2].fill(3); - [0, 1, 2].pop(); - [0, 1, 2].push(3); - [0, 1, 2].reverse(); - [0, 1, 2].shift(); - [0, 1, 2].sort(); - [0, 1, 2].splice(0, 1, 9); - [0, 1, 2].unshift(6); - `, - optionsSet: [[{ ignoreImmediateMutation: true }]], - }, - { - code: dedent` - Object.entries({}).sort(); - Object.keys({}).sort(); - Object.values({}).sort(); - `, - optionsSet: [[{ ignoreImmediateMutation: true }]], - }, - { - code: dedent` - "foo".split("").sort(); - const bar = "bar"; - bar.split("").sort(); - `, - optionsSet: [[{ ignoreImmediateMutation: true }]], - }, - { - code: dedent` - (mutable_foo as string[]).sort(); - `, - optionsSet: [[{ ignoreAccessorPattern: "mutable*" }]], - }, - { - code: dedent` - ([a, b, c] as string[]).sort(); - `, - optionsSet: [[{ ignoreImmediateMutation: true }]], - }, -]; - -export default tests; diff --git a/tests/rules/immutable-data/ts/object/index.test.ts b/tests/rules/immutable-data/ts/object/index.test.ts deleted file mode 100644 index 90cfe0f04..000000000 --- a/tests/rules/immutable-data/ts/object/index.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { name, rule } from "#/rules/immutable-data"; -import { testRule } from "#/tests/helpers/testers"; - -import invalid from "./invalid"; -import valid from "./valid"; - -const tests = { - valid, - invalid, -}; - -const tester = testRule(name, rule); - -tester.typescript(tests); diff --git a/tests/rules/immutable-data/ts/object/invalid.ts b/tests/rules/immutable-data/ts/object/invalid.ts deleted file mode 100644 index e302c07db..000000000 --- a/tests/rules/immutable-data/ts/object/invalid.ts +++ /dev/null @@ -1,700 +0,0 @@ -import { AST_NODE_TYPES } from "@typescript-eslint/utils"; -import dedent from "dedent"; - -import { type rule } from "#/rules/immutable-data"; -import { - type InvalidTestCaseSet, - type MessagesOf, - type OptionsOf, -} from "#/tests/helpers/util"; - -const tests: Array< - InvalidTestCaseSet, OptionsOf> -> = [ - // Disallowed object mutation patterns. - { - code: dedent` - var x = {a: 1}; - x.foo = "bar"; - x["foo"] = "bar"; - x.a += 1; - x.a -= 1; - x.a *= 1; - x.a /= 1; - x.a %= 1; - x.a <<= 1; - x.a >>= 1; - x.a >>>= 1; - x.a &= 1; - x.a |= 1; - x.a ^= 1; - x.a **= 1; - delete x.a; - delete x["a"]; - x.a++; - x.a--; - ++x.a; - --x.a; - if (x.a = 2) {} - if (x.a++) {} - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 2, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 3, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 4, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 5, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 6, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 7, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 8, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 9, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 10, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 11, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 12, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 13, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 14, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 15, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.UnaryExpression, - line: 16, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.UnaryExpression, - line: 17, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.UpdateExpression, - line: 18, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.UpdateExpression, - line: 19, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.UpdateExpression, - line: 20, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.UpdateExpression, - line: 21, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 22, - column: 5, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.UpdateExpression, - line: 23, - column: 5, - }, - ], - }, - // Disallow Object.assign on identifiers. - { - code: dedent` - var x = { msg1: "hello", obj: { a: 1, b: 2} }; - - var a = Object.assign(x, { msg2: "world" }); - var b = Object.assign(x.obj, { msg2: "world" }); - `, - optionsSet: [[]], - errors: [ - { - messageId: "object", - type: AST_NODE_TYPES.CallExpression, - line: 3, - column: 9, - }, - { - messageId: "object", - type: AST_NODE_TYPES.CallExpression, - line: 4, - column: 9, - }, - ], - }, - // Disallow other object mutation methods. - { - code: dedent` - var foo = { a: 1 }; - Object.defineProperties(foo, { b: { value: 2, writable: false }}); - Object.defineProperty(foo, "c", { value: 3, writable: false }); - Object.setPrototypeOf(foo, null); - `, - optionsSet: [[]], - errors: [ - { - messageId: "object", - type: AST_NODE_TYPES.CallExpression, - line: 2, - column: 1, - }, - { - messageId: "object", - type: AST_NODE_TYPES.CallExpression, - line: 3, - column: 1, - }, - { - messageId: "object", - type: AST_NODE_TYPES.CallExpression, - line: 4, - column: 1, - }, - ], - }, - // No mutation in class methods. - { - code: dedent` - class Klass { - bar = 1; - - constructor() { - this.baz = "hello"; - } - - zoo() { - this.bar = 2; - this.baz = 3; - } - } - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 9, - column: 5, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 10, - column: 5, - }, - ], - }, - // Catch non-field mutation in classes. - { - code: dedent` - class Klass { - mutate() { - let data = { prop: 0 }; - data.prop = 1; - } - } - `, - optionsSet: [[{ ignoreClasses: "fieldsOnly" }]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 4, - column: 5, - }, - ], - }, - { - code: dedent` - class Klass { - bar = 1; - baz: string; - - constructor() { - this.baz = "hello"; - } - - zoo() { - this.bar = 2; - this.baz = 3; - } - } - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 10, - column: 5, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 11, - column: 5, - }, - ], - }, - // ignoreNonConstDeclarations. - { - code: dedent` - const mutableVar = { a: 1 }; - mutableVar.a++; - `, - optionsSet: [[{ ignoreNonConstDeclarations: true }]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.UpdateExpression, - line: 2, - column: 1, - }, - ], - }, - { - code: dedent` - const mutableVar = { a: 1 }; - mutableVar.a = 0; - `, - optionsSet: [[{ ignoreNonConstDeclarations: true }]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 2, - column: 1, - }, - ], - }, - // ignoreNonConstDeclarations. - { - code: dedent` - const x = {a: 1, b:{}}; - x.foo = "bar"; - x["foo"] = "bar"; - x.a += 1; - x.a -= 1; - x.a *= 1; - x.a /= 1; - x.a %= 1; - x.a <<= 1; - x.a >>= 1; - x.a >>>= 1; - x.a &= 1; - x.a |= 1; - x.a ^= 1; - x.a **= 1; - delete x.a; - delete x["a"]; - x.a++; - x.a--; - ++x.a; - --x.a; - if (x.a = 2) {} - if (x.a++) {} - Object.assign(x, { c: "world" }); - Object.assign(x.b, { c: "world" }); - Object.defineProperties(x, { d: { value: 2, writable: false }}); - Object.defineProperty(x, "e", { value: 3, writable: false }); - Object.setPrototypeOf(x, null); - `, - optionsSet: [[{ ignoreNonConstDeclarations: true }]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 2, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 3, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 4, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 5, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 6, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 7, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 8, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 9, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 10, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 11, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 12, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 13, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 14, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 15, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.UnaryExpression, - line: 16, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.UnaryExpression, - line: 17, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.UpdateExpression, - line: 18, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.UpdateExpression, - line: 19, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.UpdateExpression, - line: 20, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.UpdateExpression, - line: 21, - column: 1, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 22, - column: 5, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.UpdateExpression, - line: 23, - column: 5, - }, - ], - }, - { - code: dedent` - function y(x: {a: 1, b: object}) { - x.foo = "bar"; - x["foo"] = "bar"; - x.a += 1; - x.a -= 1; - x.a *= 1; - x.a /= 1; - x.a %= 1; - x.a <<= 1; - x.a >>= 1; - x.a >>>= 1; - x.a &= 1; - x.a |= 1; - x.a ^= 1; - x.a **= 1; - delete x.a; - delete x["a"]; - x.a++; - x.a--; - ++x.a; - --x.a; - if (x.a = 2) {} - if (x.a++) {} - Object.assign(x, { c: "world" }); - Object.assign(x.b, { c: "world" }); - Object.defineProperties(x, { d: { value: 2, writable: false }}); - Object.defineProperty(x, "e", { value: 3, writable: false }); - Object.setPrototypeOf(x, null); - } - `, - optionsSet: [ - [{ ignoreNonConstDeclarations: { treatParametersAsConst: true } }], - ], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 2, - column: 3, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 3, - column: 3, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 4, - column: 3, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 5, - column: 3, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 6, - column: 3, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 7, - column: 3, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 8, - column: 3, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 9, - column: 3, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 10, - column: 3, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 11, - column: 3, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 12, - column: 3, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 13, - column: 3, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 14, - column: 3, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 15, - column: 3, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.UnaryExpression, - line: 16, - column: 3, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.UnaryExpression, - line: 17, - column: 3, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.UpdateExpression, - line: 18, - column: 3, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.UpdateExpression, - line: 19, - column: 3, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.UpdateExpression, - line: 20, - column: 3, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.UpdateExpression, - line: 21, - column: 3, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 22, - column: 7, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.UpdateExpression, - line: 23, - column: 7, - }, - ], - }, - { - code: dedent` - (mutable_foo as Bar).baz = "hello world"; - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.AssignmentExpression, - line: 1, - column: 1, - }, - ], - }, -]; - -export default tests; diff --git a/tests/rules/immutable-data/ts/object/valid.ts b/tests/rules/immutable-data/ts/object/valid.ts deleted file mode 100644 index 1510c1df2..000000000 --- a/tests/rules/immutable-data/ts/object/valid.ts +++ /dev/null @@ -1,293 +0,0 @@ -import dedent from "dedent"; - -import { type rule } from "#/rules/immutable-data"; -import { type OptionsOf, type ValidTestCaseSet } from "#/tests/helpers/util"; - -const tests: Array>> = [ - // Allowed non-object mutation patterns. - { - code: dedent` - var y = x.a; - var z = x["a"]; - if (x.a && y.a) {} - var w = ~x.a; - if (!x.a) {} - `, - optionsSet: [[]], - }, - // Allow Object.assign() on non identifiers. - { - code: dedent` - var x = { msg1: "hello", obj: { a: 1, b: 2}, func: function() {} }; - var bar = function(a, b, c) { return { a: a, b: b, c: c }; }; - - var a = Object.assign({}, { msg: "hello world" }); - var b = Object.assign(bar(1, 2, 3), { d: 4 }); - var c = Object.assign(x.func(), { d: 4 }); - `, - optionsSet: [[]], - }, - // ignoreIdentifierPattern - objects. - { - code: dedent` - var mutableVar = { a: 1 }; - delete mutableVar.a; - `, - optionsSet: [[{ ignoreIdentifierPattern: ["^mutable"] }]], - }, - { - code: dedent` - var mutableVar = { a: 1 }; - Object.assign(mutableVar, { b: 2 }); - `, - optionsSet: [[{ ignoreIdentifierPattern: ["^mutable"] }]], - }, - // IgnoreAccessorPattern - objects. - { - code: dedent` - var mutableVar = { a: 1 }; - mutableVar.a = 0; - mutableVar.a++; - `, - optionsSet: [ - [{ ignoreAccessorPattern: ["**.mutable*.a"] }], - [{ ignoreAccessorPattern: ["**.mutable*.*"] }], - [{ ignoreAccessorPattern: ["**.mutable*.*.**"] }], - [{ ignoreAccessorPattern: ["**.mutable*.**"] }], - ], - }, - // ignoreNonConstDeclarations. - { - code: dedent` - var mutableVar = { a: 1 }; - mutableVar.a = 0; - mutableVar.a++; - `, - optionsSet: [[{ ignoreNonConstDeclarations: true }]], - }, - { - code: dedent` - let mutableVar = { a: 1 }; - mutableVar.a = 0; - mutableVar.a++; - `, - optionsSet: [[{ ignoreNonConstDeclarations: true }]], - }, - // Allow initialization of class members in constructor - { - code: dedent` - class Klass { - bar = 1; - constructor() { - this.baz = "hello"; - } - } - `, - optionsSet: [[]], - }, - // IgnoreAccessorPattern - classes. - { - code: dedent` - class Klass { - mutate() { - this.mutableField = 0; - } - } - `, - optionsSet: [ - [{ ignoreAccessorPattern: ["this.*.**"] }], - [{ ignoreAccessorPattern: ["**.mutable*"] }], - [{ ignoreAccessorPattern: ["**.mutable*.**"] }], - ], - }, - // Ignore class - { - code: dedent` - class Klass { - mutate() { - this.baz = "hello"; - } - } - `, - optionsSet: [[{ ignoreClasses: true }], [{ ignoreClasses: "fieldsOnly" }]], - }, - // Allow initialization of class members in constructor - { - code: dedent` - class Klass { - bar = 1; - baz: string; - constructor() { - this.baz = "hello"; - } - } - `, - optionsSet: [[]], - }, - // ignoreNonConstDeclarations. - { - code: dedent` - let x = {a: 1, b:{}}; - x.foo = "bar"; - x["foo"] = "bar"; - x.a += 1; - x.a -= 1; - x.a *= 1; - x.a /= 1; - x.a %= 1; - x.a <<= 1; - x.a >>= 1; - x.a >>>= 1; - x.a &= 1; - x.a |= 1; - x.a ^= 1; - x.a **= 1; - delete x.a; - delete x["a"]; - x.a++; - x.a--; - ++x.a; - --x.a; - if (x.a = 2) {} - if (x.a++) {} - Object.assign(x, { c: "world" }); - Object.assign(x.b, { c: "world" }); - Object.defineProperties(x, { d: { value: 2, writable: false }}); - Object.defineProperty(x, "e", { value: 3, writable: false }); - Object.setPrototypeOf(x, null); - `, - optionsSet: [[{ ignoreNonConstDeclarations: true }]], - }, - { - code: dedent` - const y = {x: {a: 1, b:{}}}; - let x = y.x; - x.foo = "bar"; - x["foo"] = "bar"; - x.a += 1; - x.a -= 1; - x.a *= 1; - x.a /= 1; - x.a %= 1; - x.a <<= 1; - x.a >>= 1; - x.a >>>= 1; - x.a &= 1; - x.a |= 1; - x.a ^= 1; - x.a **= 1; - delete x.a; - delete x["a"]; - x.a++; - x.a--; - ++x.a; - --x.a; - if (x.a = 2) {} - if (x.a++) {} - Object.assign(x, { c: "world" }); - Object.assign(x.b, { c: "world" }); - Object.defineProperties(x, { d: { value: 2, writable: false }}); - Object.defineProperty(x, "e", { value: 3, writable: false }); - Object.setPrototypeOf(x, null); - `, - optionsSet: [[{ ignoreNonConstDeclarations: true }]], - }, - { - code: dedent` - function y(x: {a: 1, b: object}) { - x.foo = "bar"; - x["foo"] = "bar"; - x.a += 1; - x.a -= 1; - x.a *= 1; - x.a /= 1; - x.a %= 1; - x.a <<= 1; - x.a >>= 1; - x.a >>>= 1; - x.a &= 1; - x.a |= 1; - x.a ^= 1; - x.a **= 1; - delete x.a; - delete x["a"]; - x.a++; - x.a--; - ++x.a; - --x.a; - if (x.a = 2) {} - if (x.a++) {} - Object.assign(x, { c: "world" }); - Object.assign(x.b, { c: "world" }); - Object.defineProperties(x, { d: { value: 2, writable: false }}); - Object.defineProperty(x, "e", { value: 3, writable: false }); - Object.setPrototypeOf(x, null); - } - `, - optionsSet: [ - [{ ignoreNonConstDeclarations: true }], - [ - { - ignoreNonConstDeclarations: { - treatParametersAsConst: false, - }, - }, - ], - ], - }, - { - code: dedent` - function y(mutable_x: {a: 1, b: object}) { - mutable_x.foo = "bar"; - mutable_x["foo"] = "bar"; - mutable_x.a += 1; - mutable_x.a -= 1; - mutable_x.a *= 1; - mutable_x.a /= 1; - mutable_x.a %= 1; - mutable_x.a <<= 1; - mutable_x.a >>= 1; - mutable_x.a >>>= 1; - mutable_x.a &= 1; - mutable_x.a |= 1; - mutable_x.a ^= 1; - mutable_x.a **= 1; - delete mutable_x.a; - delete mutable_x["a"]; - mutable_x.a++; - mutable_x.a--; - ++mutable_x.a; - --mutable_x.a; - if (mutable_x.a = 2) {} - if (mutable_x.a++) {} - Object.assign(mutable_x, { c: "world" }); - Object.assign(mutable_x.b, { c: "world" }); - Object.defineProperties(mutable_x, { d: { value: 2, writable: false }}); - Object.defineProperty(mutable_x, "e", { value: 3, writable: false }); - Object.setPrototypeOf(mutable_x, null); - } - `, - optionsSet: [ - [ - { - ignoreNonConstDeclarations: { treatParametersAsConst: true }, - ignoreAccessorPattern: "mutable*", - }, - ], - ], - }, - { - code: dedent` - (mutable_foo as Bar).baz = "hello world"; - `, - optionsSet: [[{ ignoreAccessorPattern: "mutable*.*" }]], - }, - { - code: dedent` - mutable_foo!.baz = "hello world"; - `, - optionsSet: [[{ ignoreAccessorPattern: "mutable*.*" }]], - }, -]; - -export default tests; diff --git a/tests/rules/index.test.ts b/tests/rules/index.test.ts index 11cdaf304..272e6c825 100644 --- a/tests/rules/index.test.ts +++ b/tests/rules/index.test.ts @@ -5,7 +5,7 @@ import { describe, expect, it } from "vitest"; import { rules } from "#/rules"; describe("rules index", () => { - it("to import all rule files", () => { + it("import all the rule files", () => { const rulesNames: string[] = Object.keys(rules); const files: string[] = fs .readdirSync("./src/rules") diff --git a/tests/rules/no-class/es2015/index.test.ts b/tests/rules/no-class/es2015/index.test.ts deleted file mode 100644 index 98b29c17a..000000000 --- a/tests/rules/no-class/es2015/index.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { name, rule } from "#/rules/no-classes"; -import { testRule } from "#/tests/helpers/testers"; - -import invalid from "./invalid"; -import valid from "./valid"; - -const tests = { - valid, - invalid, -}; - -const tester = testRule(name, rule); - -tester.typescript(tests); -tester.esLatest(tests); -tester.es2015(tests); diff --git a/tests/rules/no-class/es2015/invalid.ts b/tests/rules/no-class/es2015/invalid.ts deleted file mode 100644 index ea53f88a6..000000000 --- a/tests/rules/no-class/es2015/invalid.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { AST_NODE_TYPES } from "@typescript-eslint/utils"; - -import { type rule } from "#/rules/no-classes"; -import { - type InvalidTestCaseSet, - type MessagesOf, - type OptionsOf, -} from "#/tests/helpers/util"; - -const tests: Array< - InvalidTestCaseSet, OptionsOf> -> = [ - { - code: "class Foo {}", - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.ClassDeclaration, - line: 1, - column: 1, - }, - ], - }, - { - code: "const klass = class {}", - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.ClassExpression, - line: 1, - column: 15, - }, - ], - }, -]; - -export default tests; diff --git a/tests/rules/no-class/es2015/valid.ts b/tests/rules/no-class/es2015/valid.ts deleted file mode 100644 index 47f69625e..000000000 --- a/tests/rules/no-class/es2015/valid.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { type rule } from "#/rules/no-classes"; -import { type OptionsOf, type ValidTestCaseSet } from "#/tests/helpers/util"; - -const tests: Array>> = [ - { - code: "function Foo() {}", - optionsSet: [[]], - }, -]; - -export default tests; diff --git a/tests/rules/no-classes.test.ts b/tests/rules/no-classes.test.ts new file mode 100644 index 000000000..05346b6c0 --- /dev/null +++ b/tests/rules/no-classes.test.ts @@ -0,0 +1,34 @@ +import { createRuleTester } from "eslint-vitest-rule-tester"; +import { describe, expect, it } from "vitest"; + +import { name, rule } from "#/rules/no-classes"; + +import { esLatestConfig } from "../utils/configs"; + +describe(name, () => { + describe("javascript - es latest", () => { + const { valid, invalid } = createRuleTester({ + name, + rule, + configs: esLatestConfig, + }); + + it("doesn't report non-issues", () => { + valid("function Foo() {}"); + }); + + it("reports class declarations", () => { + const invalidResult1 = invalid({ + code: "class Foo {}", + errors: ["generic"], + }); + expect(invalidResult1.messages).toMatchSnapshot(); + + const invalidResult2 = invalid({ + code: "const klass = class {}", + errors: ["generic"], + }); + expect(invalidResult2.messages).toMatchSnapshot(); + }); + }); +}); diff --git a/tests/rules/no-conditional-statement/ts/index.test.ts b/tests/rules/no-conditional-statement/ts/index.test.ts deleted file mode 100644 index 55dd79a11..000000000 --- a/tests/rules/no-conditional-statement/ts/index.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { name, rule } from "#/rules/no-conditional-statements"; -import { testRule } from "#/tests/helpers/testers"; - -import invalid from "./invalid"; -import valid from "./valid"; - -const tests = { - valid, - invalid, -}; - -const tester = testRule(name, rule); - -tester.typescript(tests); diff --git a/tests/rules/no-conditional-statement/ts/invalid.ts b/tests/rules/no-conditional-statement/ts/invalid.ts deleted file mode 100644 index c655ed880..000000000 --- a/tests/rules/no-conditional-statement/ts/invalid.ts +++ /dev/null @@ -1,254 +0,0 @@ -import { AST_NODE_TYPES } from "@typescript-eslint/utils"; -import dedent from "dedent"; - -import { type rule } from "#/rules/no-conditional-statements"; -import { - type InvalidTestCaseSet, - type MessagesOf, - type OptionsOf, -} from "#/tests/helpers/util"; - -const tests: Array< - InvalidTestCaseSet, OptionsOf> -> = [ - { - code: dedent` - if (i === 1) { - x = 2; - } - `, - optionsSet: [[]], - errors: [ - { - messageId: "unexpectedIf", - type: AST_NODE_TYPES.IfStatement, - line: 1, - column: 1, - }, - ], - }, - { - code: dedent` - var x = "c"; - var y = ""; - switch(x) { - case "a": - y = 1; - break; - case "b": - y = 2; - break; - default: - y = 3; - break; - } - `, - optionsSet: [[]], - errors: [ - { - messageId: "unexpectedSwitch", - type: AST_NODE_TYPES.SwitchStatement, - line: 3, - column: 1, - }, - ], - }, - { - code: dedent` - function foo(i) { - if (i === 1) { - return 1; - } - return 0; - } - `, - optionsSet: [[]], - errors: [ - { - messageId: "unexpectedIf", - type: AST_NODE_TYPES.IfStatement, - line: 2, - column: 3, - }, - ], - }, - { - code: dedent` - function foo(i) { - switch(i) { - case "a": - return 1; - case "b": - return 2; - default: - return 3; - } - } - `, - optionsSet: [[]], - errors: [ - { - messageId: "unexpectedSwitch", - type: AST_NODE_TYPES.SwitchStatement, - line: 2, - column: 3, - }, - ], - }, - { - code: dedent` - function foo(i) { - if (i === 1) { - console.log("bar"); - } - if (i === 2) console.log("baz"); - else return 3; - return 0; - } - `, - optionsSet: [[{ allowReturningBranches: true }]], - errors: [ - { - messageId: "incompleteBranch", - type: AST_NODE_TYPES.BlockStatement, - line: 2, - column: 16, - }, - { - messageId: "incompleteBranch", - type: AST_NODE_TYPES.ExpressionStatement, - line: 5, - column: 16, - }, - ], - }, - { - code: dedent` - function foo(i) { - switch(i) { - case "a": - return 1; - case "b": - return 2; - default: - break; - } - } - `, - optionsSet: [[{ allowReturningBranches: true }]], - errors: [ - { - messageId: "incompleteBranch", - type: AST_NODE_TYPES.SwitchCase, - line: 7, - column: 5, - }, - ], - }, - { - code: dedent` - function foo(x, y) { - if (x > 0) { - if (y < 100) { - return 1; - } else { - console.log("bar"); - } - } - } - `, - optionsSet: [[{ allowReturningBranches: true }]], - errors: [ - { - messageId: "incompleteBranch", - type: AST_NODE_TYPES.BlockStatement, - line: 5, - column: 12, - }, - ], - }, - { - code: dedent` - function foo(i) { - if (i === 1) { - return 1; - } - } - `, - optionsSet: [[{ allowReturningBranches: "ifExhaustive" }]], - errors: [ - { - messageId: "incompleteIf", - type: AST_NODE_TYPES.IfStatement, - line: 2, - column: 3, - }, - ], - }, - { - code: dedent` - function foo(i) { - if (i === 1) { - return 1; - } else { - console.log(1); - } - } - `, - optionsSet: [[{ allowReturningBranches: "ifExhaustive" }]], - errors: [ - { - messageId: "incompleteBranch", - type: AST_NODE_TYPES.BlockStatement, - line: 4, - column: 10, - }, - ], - }, - { - code: dedent` - function foo(i) { - switch(i) { - case "a": - return 1; - case "b": - return 2; - } - } - `, - optionsSet: [[{ allowReturningBranches: "ifExhaustive" }]], - errors: [ - { - messageId: "incompleteSwitch", - type: AST_NODE_TYPES.SwitchStatement, - line: 2, - column: 3, - }, - ], - }, - { - code: dedent` - function foo(i) { - switch(i) { - case "a": - return 1; - case "b": - return 2; - default: - break; - } - } - `, - optionsSet: [[{ allowReturningBranches: "ifExhaustive" }]], - errors: [ - { - messageId: "incompleteBranch", - type: AST_NODE_TYPES.SwitchCase, - line: 7, - column: 5, - }, - ], - }, -]; - -export default tests; diff --git a/tests/rules/no-conditional-statement/ts/valid.ts b/tests/rules/no-conditional-statement/ts/valid.ts deleted file mode 100644 index 2c4c306a2..000000000 --- a/tests/rules/no-conditional-statement/ts/valid.ts +++ /dev/null @@ -1,230 +0,0 @@ -import dedent from "dedent"; - -import { type rule } from "#/rules/no-conditional-statements"; -import { type OptionsOf, type ValidTestCaseSet } from "#/tests/helpers/util"; - -const tests: Array>> = [ - { - code: dedent` - function foo(i) { - if (i === 1) { - return 1; - } - } - `, - optionsSet: [[{ allowReturningBranches: true }]], - }, - { - code: dedent` - function foo(i) { - if (i === 1) { - return 1; - } else { - return 0; - } - } - `, - optionsSet: [ - [{ allowReturningBranches: true }], - [{ allowReturningBranches: "ifExhaustive" }], - ], - }, - { - code: dedent` - function foo(i) { - switch(i) { - case "a": - return 1; - case "b": - case "c": - return 2; - } - } - `, - optionsSet: [[{ allowReturningBranches: true }]], - }, - { - code: dedent` - function foo(i) { - switch(i) { - case "a": - return 1; - case "b": - return 2; - default: - return 3; - } - } - `, - optionsSet: [ - [{ allowReturningBranches: true }], - [{ allowReturningBranches: "ifExhaustive" }], - ], - }, - // Check Break and Continue - { - code: dedent` - for(var i = 0; i < j; i++) { - if (e === 1) { - break; - } - } - `, - optionsSet: [[{ allowReturningBranches: true }]], - }, - { - code: dedent` - for(var i = 0; i < j; i++) { - if (e === 1) { - break; - } else { - continue; - } - } - `, - optionsSet: [ - [{ allowReturningBranches: true }], - [{ allowReturningBranches: "ifExhaustive" }], - ], - }, - // Exhaustive type test. - { - code: dedent` - type T = "a" | "b"; - function foo(i: T) { - switch(i) { - case "a": - return 1; - case "b": - return 2; - } - } - `, - optionsSet: [ - [{ allowReturningBranches: true }], - [{ allowReturningBranches: "ifExhaustive" }], - ], - }, - // Check throws - { - code: dedent` - function foo(i) { - if (i === 1) { - throw 1; - } - } - `, - optionsSet: [[{ allowReturningBranches: true }]], - }, - { - code: dedent` - function foo(i) { - if (i === 1) { - throw 1; - } else { - throw 2; - } - } - `, - optionsSet: [ - [{ allowReturningBranches: true }], - [{ allowReturningBranches: "ifExhaustive" }], - ], - }, - { - code: dedent` - function foo(i) { - switch(i) { - case "a": - throw 1; - case "b": - case "c": - throw 2; - } - } - `, - optionsSet: [[{ allowReturningBranches: true }]], - }, - { - code: dedent` - function foo(i) { - switch(i) { - case "a": - throw 1; - case "b": - throw 2; - default: - throw 3; - } - } - `, - optionsSet: [ - [{ allowReturningBranches: true }], - [{ allowReturningBranches: "ifExhaustive" }], - ], - }, - // Check never - { - code: dedent` - declare function neverReturn(): never; - function foo(i) { - if (i === 1) { - neverReturn(); - } - } - `, - optionsSet: [[{ allowReturningBranches: true }]], - }, - { - code: dedent` - declare function neverReturn(): never; - function foo(i) { - if (i === 1) { - neverReturn(); - } else { - neverReturn(); - } - } - `, - optionsSet: [ - [{ allowReturningBranches: true }], - [{ allowReturningBranches: "ifExhaustive" }], - ], - }, - { - code: dedent` - declare function neverReturn(): never; - function foo(i) { - switch(i) { - case "a": - neverReturn(); - case "b": - case "c": - neverReturn(); - } - } - `, - optionsSet: [[{ allowReturningBranches: true }]], - }, - { - code: dedent` - declare function neverReturn(): never; - function foo(i) { - switch(i) { - case "a": - neverReturn(); - case "b": - neverReturn(); - default: - neverReturn(); - } - } - `, - optionsSet: [ - [{ allowReturningBranches: true }], - [{ allowReturningBranches: "ifExhaustive" }], - ], - }, -]; - -export default tests; diff --git a/tests/rules/no-conditional-statements.test.ts b/tests/rules/no-conditional-statements.test.ts new file mode 100644 index 000000000..10d84c1b7 --- /dev/null +++ b/tests/rules/no-conditional-statements.test.ts @@ -0,0 +1,282 @@ +import dedent from "dedent"; +import { createRuleTester } from "eslint-vitest-rule-tester"; +import { describe, expect, it } from "vitest"; + +import { name, rule } from "#/rules/no-conditional-statements"; + +import { typescriptConfig } from "../utils/configs"; + +describe(name, () => { + describe("typescript", () => { + const { valid, invalid } = createRuleTester({ + name, + rule, + configs: typescriptConfig, + }); + + describe("if statements", () => { + it("reports if statements", () => { + const invalidResult = invalid({ + code: dedent` + if (true) { + console.log("hello world"); + } + `, + errors: ["unexpectedIf"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + describe("options", () => { + describe("allowReturningBranches", () => { + describe("true", () => { + it("allows return", () => { + valid({ + code: dedent` + function foo(i) { + if (i === 1) { + return 1; + } + } + `, + options: [{ allowReturningBranches: true }], + }); + }); + + it("supports break and continue as returning", () => { + valid({ + code: dedent` + for(var i = 0; i < j; i++) { + if (e === 1) { + break; + } + if (e === 2) { + continue; + } + } + `, + options: [{ allowReturningBranches: true }], + }); + }); + + it("supports throw as returning", () => { + valid({ + code: dedent` + function foo(i) { + if (i === 1) { + throw 1; + } + } + `, + options: [{ allowReturningBranches: true }], + }); + }); + + it("supports never as returning", () => { + valid({ + code: dedent` + declare function neverReturn(): never; + function foo(i) { + if (i === 1) { + neverReturn(); + } + } + `, + options: [{ allowReturningBranches: true }], + }); + }); + }); + + describe("ifExhaustive", () => { + it("else required", () => { + valid({ + code: dedent` + function foo(i) { + if (i === 1) { + return 1; + } else { + return 0; + } + } + `, + options: [{ allowReturningBranches: "ifExhaustive" }], + }); + + const invalidResult = invalid({ + code: dedent` + function foo(i) { + if (i === 1) { + return 1; + } + } + `, + options: [{ allowReturningBranches: "ifExhaustive" }], + errors: ["incompleteIf"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("supports break and continue as returning", () => { + valid({ + code: dedent` + for(var i = 0; i < j; i++) { + if (e === 1) { + break; + } else { + continue; + } + } + `, + options: [{ allowReturningBranches: "ifExhaustive" }], + }); + }); + }); + }); + }); + }); + + describe("switch statements", () => { + it("reports switch statements", () => { + const invalidResult = invalid({ + code: dedent` + switch(i) { + case "a": + console.log("hello"); + case "b": + case "c": + console.log("world"); + } + `, + errors: ["unexpectedSwitch"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + describe("options", () => { + describe("allowReturningBranches", () => { + describe("true", () => { + it("allows return", () => { + valid({ + code: dedent` + function foo(i) { + switch(i) { + case "a": + return 1; + case "b": + return 2; + default: + return 3; + } + } + `, + options: [{ allowReturningBranches: true }], + }); + }); + + it("supports break and continue as returning", () => { + valid({ + code: dedent` + label: for(var i = 0; i < j; i++) { + switch(i) { + case "a": + break label; + case "b": + continue; + } + } + `, + options: [{ allowReturningBranches: true }], + }); + }); + + it("supports throw as returning", () => { + valid({ + code: dedent` + function foo(i) { + switch(i) { + case "a": + throw 1; + case "b": + throw 2; + } + } + `, + options: [{ allowReturningBranches: true }], + }); + }); + + it("supports never as returning", () => { + valid({ + code: dedent` + declare function neverReturn(): never; + function foo(i) { + switch(i) { + case "a": + neverReturn(); + case "b": + neverReturn(); + } + } + `, + options: [{ allowReturningBranches: true }], + }); + }); + }); + + describe("ifExhaustive", () => { + it("requires default case", () => { + valid({ + code: dedent` + function foo(i) { + switch(i) { + case "a": + return 1; + case "b": + return 2; + default: + return 3; + } + } + `, + options: [{ allowReturningBranches: "ifExhaustive" }], + }); + + const invalidResult = invalid({ + code: dedent` + function foo(i) { + switch(i) { + case "a": + return 1; + case "b": + return 2; + } + } + `, + options: [{ allowReturningBranches: "ifExhaustive" }], + errors: ["incompleteSwitch"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("supports exhaustive type testing", () => { + valid({ + code: dedent` + type T = "a" | "b"; + function foo(i: T) { + switch(i) { + case "a": + return 1; + case "b": + return 2; + } + } + `, + options: [{ allowReturningBranches: "ifExhaustive" }], + }); + }); + }); + }); + }); + }); + }); +}); diff --git a/tests/rules/no-expression-statement/ts/index.test.ts b/tests/rules/no-expression-statement/ts/index.test.ts deleted file mode 100644 index 16d607b81..000000000 --- a/tests/rules/no-expression-statement/ts/index.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { name, rule } from "#/rules/no-expression-statements"; -import { testRule } from "#/tests/helpers/testers"; - -import invalid from "./invalid"; -import valid from "./valid"; - -const tests = { - valid, - invalid, -}; - -const tester = testRule(name, rule); - -tester.typescript(tests); diff --git a/tests/rules/no-expression-statement/ts/invalid.ts b/tests/rules/no-expression-statement/ts/invalid.ts deleted file mode 100644 index 99584b720..000000000 --- a/tests/rules/no-expression-statement/ts/invalid.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { AST_NODE_TYPES } from "@typescript-eslint/utils"; -import dedent from "dedent"; - -import { type rule } from "#/rules/no-expression-statements"; -import { - type InvalidTestCaseSet, - type MessagesOf, - type OptionsOf, -} from "#/tests/helpers/util"; - -const tests: Array< - InvalidTestCaseSet, OptionsOf> -> = [ - { - code: dedent` - var x = []; - x.push(1); - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.ExpressionStatement, - line: 2, - column: 1, - }, - ], - }, - // Non-allowed expressions should cause failures. - { - code: `console.trace();`, - optionsSet: [[{ ignoreCodePattern: "^console\\.log" }]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.ExpressionStatement, - line: 1, - column: 1, - }, - ], - }, -]; - -export default tests; diff --git a/tests/rules/no-expression-statement/ts/valid.ts b/tests/rules/no-expression-statement/ts/valid.ts deleted file mode 100644 index 3cbd537a9..000000000 --- a/tests/rules/no-expression-statement/ts/valid.ts +++ /dev/null @@ -1,74 +0,0 @@ -import dedent from "dedent"; - -import { type rule } from "#/rules/no-expression-statements"; -import { type OptionsOf, type ValidTestCaseSet } from "#/tests/helpers/util"; - -const tests: Array>> = [ - // Defining variable should still be allowed. - { - code: `var x = [];`, - optionsSet: [[]], - }, - // Allowed expressions should not cause failures. - { - code: dedent` - console.log("yo"); - console.error("yo"); - `, - optionsSet: [[{ ignoreCodePattern: "^console\\." }]], - }, - { - code: dedent` - assert(1 !== 2); - `, - optionsSet: [[{ ignoreCodePattern: "^assert" }]], - }, - // Allow specifying directive prologues. - { - code: `"use strict"`, - optionsSet: [[]], - }, - // Allow yield. - { - code: dedent` - export function* foo() { - yield "hello"; - return "world"; - } - `, - optionsSet: [[]], - }, - // Allowed ignoring void expressions. - { - code: dedent` - console.log("yo"); - console.error("yo"); - `, - optionsSet: [[{ ignoreVoid: true }]], - }, - // Allowed ignoring self returning expressions. - { - code: dedent` - function foo() { return this; } - foo(); - `, - optionsSet: [[{ ignoreSelfReturning: true }]], - }, - { - code: dedent` - const foo = { bar() { return this; }}; - foo.bar(); - `, - optionsSet: [[{ ignoreSelfReturning: true }]], - }, - { - code: dedent` - class Foo { bar() { return this; }}; - const foo = new Foo(); - foo.bar(); - `, - optionsSet: [[{ ignoreSelfReturning: true }]], - }, -]; - -export default tests; diff --git a/tests/rules/no-expression-statements.test.ts b/tests/rules/no-expression-statements.test.ts new file mode 100644 index 000000000..0fab658e3 --- /dev/null +++ b/tests/rules/no-expression-statements.test.ts @@ -0,0 +1,136 @@ +import dedent from "dedent"; +import { createRuleTester } from "eslint-vitest-rule-tester"; +import { describe, expect, it } from "vitest"; + +import { name, rule } from "#/rules/no-expression-statements"; + +import { esLatestConfig, typescriptConfig } from "../utils/configs"; + +describe(name, () => { + describe("javascript - es latest", () => { + const { valid, invalid } = createRuleTester({ + name, + rule, + configs: esLatestConfig, + }); + + it("reports expression statements", () => { + const invalidResult = invalid({ + code: dedent` + var x = []; + x.push(1); + `, + errors: ["generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("doesn't report variable declarations", () => { + valid(dedent` + var x = []; + let y = []; + const z = []; + `); + }); + + it("doesn't report directive prologues", () => { + valid(dedent` + "use strict"; + "use server"; + "use client"; + `); + }); + + it("doesn't report yield", () => { + valid(dedent` + export function* foo() { + yield "hello"; + return "world"; + } + `); + }); + + describe("options", () => { + it("ignoreCodePattern", () => { + valid({ + code: dedent` + console.log("yo"); + console.error("yo"); + `, + options: [{ ignoreCodePattern: "^console\\." }], + }); + + valid({ + code: dedent` + assert(1 !== 2); + `, + options: [{ ignoreCodePattern: "^assert" }], + }); + + const invalidResult = invalid({ + code: `console.trace();`, + options: [{ ignoreCodePattern: "^console\\.log" }], + errors: ["generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + }); + }); + + describe("typescript", () => { + const { valid, invalid } = createRuleTester({ + name, + rule, + configs: typescriptConfig, + }); + + describe("options", () => { + it("ignoreVoid", () => { + valid({ + code: dedent` + console.log("yo"); + console.error("yo"); + `, + options: [{ ignoreVoid: true }], + }); + }); + + it("ignoreSelfReturning", () => { + valid({ + code: dedent` + function foo() { return this; } + foo(); + `, + options: [{ ignoreSelfReturning: true }], + }); + + valid({ + code: dedent` + const foo = { bar() { return this; }}; + foo.bar(); + `, + options: [{ ignoreSelfReturning: true }], + }); + + valid({ + code: dedent` + class Foo { bar() { return this; }}; + const foo = new Foo(); + foo.bar(); + `, + options: [{ ignoreSelfReturning: true }], + }); + + const invalidResult = invalid({ + code: dedent` + const foo = () => { return this; }; + foo(); + `, + options: [{ ignoreSelfReturning: true }], + errors: ["generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + }); + }); +}); diff --git a/tests/rules/no-let.test.ts b/tests/rules/no-let.test.ts new file mode 100644 index 000000000..067476160 --- /dev/null +++ b/tests/rules/no-let.test.ts @@ -0,0 +1,381 @@ +import dedent from "dedent"; +import { createRuleTester } from "eslint-vitest-rule-tester"; +import { describe, expect, it } from "vitest"; + +import { name, rule } from "#/rules/no-let"; + +import { esLatestConfig } from "../utils/configs"; + +describe(name, () => { + describe("javascript - es latest", () => { + const { valid, invalid } = createRuleTester({ + name, + rule, + configs: esLatestConfig, + }); + + it("should report let declarations", () => { + const invalidResult = invalid({ + code: dedent` + let x; + + function foo() { + let y; + let z = 0; + } + `, + errors: ["generic", "generic", "generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + describe("options", () => { + describe("allowInFunctions", () => { + it("should not report let declarations in function declarations", () => { + valid({ + code: dedent` + function foo() { + let x; + let y = 0; + } + `, + options: [{ allowInFunctions: true }], + }); + + const invalidResult = invalid({ + code: dedent` + let x; + let y = 0; + `, + options: [{ allowInFunctions: true }], + errors: ["generic", "generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("should not report let declarations in arrow function declarations", () => { + valid({ + code: dedent` + const foo = () => { + let x; + let y = 0; + }; + `, + options: [{ allowInFunctions: true }], + }); + + const invalidResult = invalid({ + code: dedent` + let x; + let y = 0; + `, + options: [{ allowInFunctions: true }], + errors: ["generic", "generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("should not report let declarations in method declarations", () => { + valid({ + code: dedent` + class Foo { + foo() { + let x; + let y = 0; + } + } + `, + options: [{ allowInFunctions: true }], + }); + + const invalidResult = invalid({ + code: dedent` + let x; + let y = 0; + `, + options: [{ allowInFunctions: true }], + errors: ["generic", "generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + }); + + describe("allowInForLoopInit", () => { + it("should not report let declarations in for loop init", () => { + valid({ + code: `for (let x = 0; x < 1; x++);`, + options: [{ allowInForLoopInit: true }], + }); + + const invalidResult = invalid({ + code: dedent` + let x; + let y = 0; + `, + options: [{ allowInFunctions: true }], + errors: ["generic", "generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + }); + + describe("ignoreIdentifierPattern", () => { + it("should not report let declarations with matching identifiers", () => { + valid({ + code: dedent` + let mutable; + let mutableX + `, + options: [{ ignoreIdentifierPattern: "^mutable" }], + }); + + valid({ + code: dedent` + let mutable = 0; + let mutableX = 0 + `, + options: [{ ignoreIdentifierPattern: "^mutable" }], + }); + + valid({ + code: `for (let mutableX = 0; x < 1; x++);`, + options: [{ ignoreIdentifierPattern: "^mutable" }], + }); + + valid({ + code: `for (let mutableX in {});`, + options: [{ ignoreIdentifierPattern: "^mutable" }], + }); + + valid({ + code: `for (let mutableX of []);`, + options: [{ ignoreIdentifierPattern: "^mutable" }], + }); + + valid({ + code: dedent` + function foo() { + let mutableX; + let mutableY = 0; + } + `, + options: [{ ignoreIdentifierPattern: "^mutable" }], + }); + + valid({ + code: dedent` + const foo = () => { + let mutableX; + let mutableY = 0; + } + `, + options: [{ ignoreIdentifierPattern: "^mutable" }], + }); + + valid({ + code: dedent` + class Foo { + foo() { + let mutableX; + let mutableY = 0; + } + } + `, + options: [{ ignoreIdentifierPattern: "^mutable" }], + }); + + valid({ + code: dedent` + let Mutable; + let xMutable; + `, + options: [{ ignoreIdentifierPattern: "Mutable$" }], + }); + + valid({ + code: dedent` + let Mutable = 0; + let xMutable = 0; + `, + options: [{ ignoreIdentifierPattern: "Mutable$" }], + }); + + valid({ + code: `for (let xMutable = 0; xMutable < 1; xMutable++);`, + options: [{ ignoreIdentifierPattern: "Mutable$" }], + }); + + valid({ + code: `for (let xMutable in {});`, + options: [{ ignoreIdentifierPattern: "Mutable$" }], + }); + + valid({ + code: `for (let xMutable of []);`, + options: [{ ignoreIdentifierPattern: "Mutable$" }], + }); + + valid({ + code: dedent` + function foo() { + let xMutable; + let yMutable = 0; + } + `, + options: [{ ignoreIdentifierPattern: "Mutable$" }], + }); + + valid({ + code: dedent` + const foo = () => { + let xMutable; + let yMutable = 0; + } + `, + options: [{ ignoreIdentifierPattern: "Mutable$" }], + }); + + valid({ + code: dedent` + class Foo { + foo() { + let xMutable; + let yMutable = 0; + } + } + `, + options: [{ ignoreIdentifierPattern: "Mutable$" }], + }); + }); + + it("should report non ignored let declarations", () => { + invalid({ + code: dedent` + let immutable; + let immutableX; + `, + options: [{ ignoreIdentifierPattern: "^mutable" }], + }); + + invalid({ + code: dedent` + let immutable = 0; + let immutableX = 0; + `, + options: [{ ignoreIdentifierPattern: "^mutable" }], + }); + + invalid({ + code: `for (let immutableX = 0; immutableX < 1; immutableX++);`, + options: [{ ignoreIdentifierPattern: "^mutable" }], + }); + + invalid({ + code: `for (let immutableX in {});`, + options: [{ ignoreIdentifierPattern: "^mutable" }], + }); + + invalid({ + code: `for (let immutableX of []);`, + options: [{ ignoreIdentifierPattern: "^mutable" }], + }); + + invalid({ + code: dedent` + function foo() { + let immutableX; + let immutableY = 0; + } + `, + options: [{ ignoreIdentifierPattern: "^mutable" }], + }); + + invalid({ + code: dedent` + const foo = () => { + let immutableX; + let immutableY = 0; + } + `, + options: [{ ignoreIdentifierPattern: "^mutable" }], + }); + + invalid({ + code: dedent` + class Foo { + foo() { + let immutableX; + let immutableY = 0; + } + } + `, + options: [{ ignoreIdentifierPattern: "^mutable" }], + }); + + invalid({ + code: dedent` + let Immutable; + let xImmutable; + `, + options: [{ ignoreIdentifierPattern: "Mutable$" }], + }); + + invalid({ + code: dedent` + let Immutable = 0; + let xImmutable = 0; + `, + options: [{ ignoreIdentifierPattern: "Mutable$" }], + }); + + invalid({ + code: `for (let xImmutable = 0; x < 1; x++);`, + options: [{ ignoreIdentifierPattern: "Mutable$" }], + }); + + invalid({ + code: `for (let xImmutable in {});`, + options: [{ ignoreIdentifierPattern: "Mutable$" }], + }); + + invalid({ + code: `for (let xImmutable of []);`, + options: [{ ignoreIdentifierPattern: "Mutable$" }], + }); + + invalid({ + code: dedent` + function foo() { + let xImmutable; + let yImmutable = 0; + } + `, + options: [{ ignoreIdentifierPattern: "Mutable$" }], + }); + + invalid({ + code: dedent` + const foo = () => { + let xImmutable; + let yImmutable = 0; + } + `, + options: [{ ignoreIdentifierPattern: "Mutable$" }], + }); + + invalid({ + code: dedent` + class Foo { + foo() { + let xImmutable; + let yImmutable = 0; + } + } + `, + options: [{ ignoreIdentifierPattern: "Mutable$" }], + }); + }); + }); + }); + }); +}); diff --git a/tests/rules/no-let/es2015/index.test.ts b/tests/rules/no-let/es2015/index.test.ts deleted file mode 100644 index be5909c52..000000000 --- a/tests/rules/no-let/es2015/index.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { name, rule } from "#/rules/no-let"; -import { testRule } from "#/tests/helpers/testers"; - -import invalid from "./invalid"; -import valid from "./valid"; - -const tests = { - valid, - invalid, -}; - -const tester = testRule(name, rule); - -tester.typescript(tests); -tester.esLatest(tests); -tester.es2015(tests); diff --git a/tests/rules/no-let/es2015/invalid.ts b/tests/rules/no-let/es2015/invalid.ts deleted file mode 100644 index 640930abf..000000000 --- a/tests/rules/no-let/es2015/invalid.ts +++ /dev/null @@ -1,183 +0,0 @@ -import { AST_NODE_TYPES } from "@typescript-eslint/utils"; -import dedent from "dedent"; - -import { type rule } from "#/rules/no-let"; -import { - type InvalidTestCaseSet, - type MessagesOf, - type OptionsOf, -} from "#/tests/helpers/util"; - -const tests: Array< - InvalidTestCaseSet, OptionsOf> -> = [ - { - code: `let x;`, - optionsSet: [ - [], - [{ allowInFunctions: true }], - [{ ignoreIdentifierPattern: "^mutable" }], - ], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.VariableDeclaration, - line: 1, - column: 1, - }, - ], - }, - { - code: `let x = 0;`, - optionsSet: [ - [], - [{ allowInFunctions: true }], - [{ ignoreIdentifierPattern: "^mutable" }], - ], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.VariableDeclaration, - line: 1, - column: 1, - }, - ], - }, - { - code: `for (let x = 0; x < 1; x++);`, - optionsSet: [ - [], - [{ allowInFunctions: true }], - [{ ignoreIdentifierPattern: "^mutable" }], - ], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.VariableDeclaration, - line: 1, - column: 6, - }, - ], - }, - { - code: `for (let x = 0, y = 0; x < 1; x++);`, - optionsSet: [ - [], - [{ allowInFunctions: true }], - [{ ignoreIdentifierPattern: "^mutable" }], - ], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.VariableDeclaration, - line: 1, - column: 6, - }, - ], - }, - { - code: `for (let x in {});`, - optionsSet: [ - [], - [{ allowInFunctions: true }], - [{ ignoreIdentifierPattern: "^mutable" }], - ], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.VariableDeclaration, - line: 1, - column: 6, - }, - ], - }, - { - code: `for (let x of []);`, - optionsSet: [ - [], - [{ allowInFunctions: true }], - [{ ignoreIdentifierPattern: "^mutable" }], - ], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.VariableDeclaration, - line: 1, - column: 6, - }, - ], - }, - { - code: dedent` - function foo() { - let x; - let y = 0; - } - `, - optionsSet: [[], [{ ignoreIdentifierPattern: "^mutable" }]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.VariableDeclaration, - line: 2, - column: 3, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.VariableDeclaration, - line: 3, - column: 3, - }, - ], - }, - { - code: dedent` - const foo = () => { - let x; - let y = 0; - } - `, - optionsSet: [[], [{ ignoreIdentifierPattern: "^mutable" }]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.VariableDeclaration, - line: 2, - column: 3, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.VariableDeclaration, - line: 3, - column: 3, - }, - ], - }, - { - code: dedent` - class Foo { - foo() { - let x; - let y = 0; - } - } - `, - optionsSet: [[], [{ ignoreIdentifierPattern: "^mutable" }]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.VariableDeclaration, - line: 3, - column: 5, - }, - { - messageId: "generic", - type: AST_NODE_TYPES.VariableDeclaration, - line: 4, - column: 5, - }, - ], - }, -]; - -export default tests; diff --git a/tests/rules/no-let/es2015/valid.ts b/tests/rules/no-let/es2015/valid.ts deleted file mode 100644 index 38b247c96..000000000 --- a/tests/rules/no-let/es2015/valid.ts +++ /dev/null @@ -1,152 +0,0 @@ -import dedent from "dedent"; - -import { type rule } from "#/rules/no-let"; -import { type OptionsOf, type ValidTestCaseSet } from "#/tests/helpers/util"; - -const tests: Array>> = [ - { - code: dedent` - function foo() { - let x; - let y = 0; - } - `, - optionsSet: [[{ allowInFunctions: true }]], - }, - { - code: dedent` - const foo = () => { - let x; - let y = 0; - } - `, - optionsSet: [[{ allowInFunctions: true }]], - }, - { - code: dedent` - class Foo { - foo() { - let x; - let y = 0; - } - } - `, - optionsSet: [[{ allowInFunctions: true }]], - }, - { - code: dedent` - let mutable; - let mutableX - `, - optionsSet: [[{ ignoreIdentifierPattern: "^mutable" }]], - }, - { - code: dedent` - let mutable = 0; - let mutableX = 0 - `, - optionsSet: [[{ ignoreIdentifierPattern: "^mutable" }]], - }, - { - code: `for (let mutableX = 0; x < 1; x++);`, - optionsSet: [[{ ignoreIdentifierPattern: "^mutable" }]], - }, - { - code: `for (let mutableX in {});`, - optionsSet: [[{ ignoreIdentifierPattern: "^mutable" }]], - }, - { - code: `for (let mutableX of []);`, - optionsSet: [[{ ignoreIdentifierPattern: "^mutable" }]], - }, - { - code: dedent` - function foo() { - let mutableX; - let mutableY = 0; - } - `, - optionsSet: [[{ ignoreIdentifierPattern: "^mutable" }]], - }, - { - code: dedent` - const foo = () => { - let mutableX; - let mutableY = 0; - } - `, - optionsSet: [[{ ignoreIdentifierPattern: "^mutable" }]], - }, - { - code: dedent` - class Foo { - foo() { - let mutableX; - let mutableY = 0; - } - } - `, - optionsSet: [[{ ignoreIdentifierPattern: "^mutable" }]], - }, - { - code: dedent` - let Mutable; - let xMutable - `, - optionsSet: [[{ ignoreIdentifierPattern: "Mutable$" }]], - }, - { - code: dedent` - let Mutable = 0; - let xMutable = 0 - `, - optionsSet: [[{ ignoreIdentifierPattern: "Mutable$" }]], - }, - { - code: `for (let xMutable = 0; x < 1; x++);`, - optionsSet: [[{ ignoreIdentifierPattern: "Mutable$" }]], - }, - { - code: `for (let xMutable in {});`, - optionsSet: [[{ ignoreIdentifierPattern: "Mutable$" }]], - }, - { - code: `for (let xMutable of []);`, - optionsSet: [[{ ignoreIdentifierPattern: "Mutable$" }]], - }, - { - code: dedent` - function foo() { - let xMutable; - let yMutable = 0; - } - `, - optionsSet: [[{ ignoreIdentifierPattern: "Mutable$" }]], - }, - { - code: dedent` - const foo = () => { - let xMutable; - let yMutable = 0; - } - `, - optionsSet: [[{ ignoreIdentifierPattern: "Mutable$" }]], - }, - { - code: dedent` - class Foo { - foo() { - let xMutable; - let yMutable = 0; - } - } - `, - optionsSet: [[{ ignoreIdentifierPattern: "Mutable$" }]], - }, - { - code: `for (let x = 0; x < 1; x++);`, - optionsSet: [[{ allowInForLoopInit: true }]], - }, -]; - -export default tests; diff --git a/tests/rules/no-loop-statement/es2015/index.test.ts b/tests/rules/no-loop-statement/es2015/index.test.ts deleted file mode 100644 index 5e8a59484..000000000 --- a/tests/rules/no-loop-statement/es2015/index.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { name, rule } from "#/rules/no-loop-statements"; -import { testRule } from "#/tests/helpers/testers"; - -import es3Invalid from "../es3/invalid"; -import es3Valid from "../es3/valid"; - -import invalid from "./invalid"; -import valid from "./valid"; - -const tests = { - valid: [...es3Valid, ...valid], - invalid: [...es3Invalid, ...invalid], -}; - -const tester = testRule(name, rule); - -tester.typescript(tests); -tester.esLatest(tests); -tester.es2015(tests); diff --git a/tests/rules/no-loop-statement/es2015/invalid.ts b/tests/rules/no-loop-statement/es2015/invalid.ts deleted file mode 100644 index d5ce7b2ed..000000000 --- a/tests/rules/no-loop-statement/es2015/invalid.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { AST_NODE_TYPES } from "@typescript-eslint/utils"; - -import { type rule } from "#/rules/no-loop-statements"; -import { - type InvalidTestCaseSet, - type MessagesOf, - type OptionsOf, -} from "#/tests/helpers/util"; - -const tests: Array< - InvalidTestCaseSet, OptionsOf> -> = [ - { - code: `for (const x = 0; x < 10; x++) { console.log(x); }`, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.ForStatement, - line: 1, - column: 1, - }, - ], - }, - { - code: `for (const x in y) { console.log(x); }`, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.ForInStatement, - line: 1, - column: 1, - }, - ], - }, - { - code: `for (const x of y) { console.log(x); }`, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.ForOfStatement, - line: 1, - column: 1, - }, - ], - }, -]; - -export default tests; diff --git a/tests/rules/no-loop-statement/es2015/valid.ts b/tests/rules/no-loop-statement/es2015/valid.ts deleted file mode 100644 index 779391f74..000000000 --- a/tests/rules/no-loop-statement/es2015/valid.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { type rule } from "#/rules/no-loop-statements"; -import { type OptionsOf, type ValidTestCaseSet } from "#/tests/helpers/util"; - -const tests: Array>> = []; - -export default tests; diff --git a/tests/rules/no-loop-statement/es3/index.test.ts b/tests/rules/no-loop-statement/es3/index.test.ts deleted file mode 100644 index 181d2036c..000000000 --- a/tests/rules/no-loop-statement/es3/index.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { name, rule } from "#/rules/no-loop-statements"; -import { testRule } from "#/tests/helpers/testers"; - -import invalid from "./invalid"; -import valid from "./valid"; - -const tests = { - valid, - invalid, -}; - -const tester = testRule(name, rule); - -tester.typescript(tests); -tester.esLatest(tests); -tester.es2015(tests); diff --git a/tests/rules/no-loop-statement/es3/invalid.ts b/tests/rules/no-loop-statement/es3/invalid.ts deleted file mode 100644 index f5d6eab3b..000000000 --- a/tests/rules/no-loop-statement/es3/invalid.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { AST_NODE_TYPES } from "@typescript-eslint/utils"; - -import { type rule } from "#/rules/no-loop-statements"; -import { - type InvalidTestCaseSet, - type MessagesOf, - type OptionsOf, -} from "#/tests/helpers/util"; - -const tests: Array< - InvalidTestCaseSet, OptionsOf> -> = [ - { - code: `for (var x = 0; x < 10; x++) { console.log(x); }`, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.ForStatement, - line: 1, - column: 1, - }, - ], - }, - { - code: `for (var x in y) { console.log(x); }`, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.ForInStatement, - line: 1, - column: 1, - }, - ], - }, - { - code: `while (true) { console.log("a"); }`, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.WhileStatement, - line: 1, - column: 1, - }, - ], - }, - { - code: `do { console.log("a"); } while (true)`, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.DoWhileStatement, - line: 1, - column: 1, - }, - ], - }, -]; - -export default tests; diff --git a/tests/rules/no-loop-statement/es3/valid.ts b/tests/rules/no-loop-statement/es3/valid.ts deleted file mode 100644 index 2b32aa6b3..000000000 --- a/tests/rules/no-loop-statement/es3/valid.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { type rule } from "#/rules/no-loop-statements"; -import { type OptionsOf, type ValidTestCaseSet } from "#/tests/helpers/util"; - -const tests: Array>> = [ - { - code: `if (true) { console.log(); }`, - optionsSet: [[]], - }, -]; - -export default tests; diff --git a/tests/rules/no-loop-statements.test.ts b/tests/rules/no-loop-statements.test.ts new file mode 100644 index 000000000..0de32769a --- /dev/null +++ b/tests/rules/no-loop-statements.test.ts @@ -0,0 +1,99 @@ +import dedent from "dedent"; +import { createRuleTester } from "eslint-vitest-rule-tester"; +import { describe, expect, it } from "vitest"; + +import { name, rule } from "#/rules/no-loop-statements"; + +import { esLatestConfig } from "../utils/configs"; + +describe(name, () => { + describe("javascript - es latest", () => { + const { valid, invalid } = createRuleTester({ + name, + rule, + configs: esLatestConfig, + }); + + it("doesn't report non-issues", () => { + valid({ + code: dedent` + if (true) { + console.log("hello world"); + } + `, + }); + }); + + it("reports while loop statements", () => { + const invalidResult = invalid({ + code: dedent` + while (true) { + console.log("hello world"); + } + `, + errors: ["generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports do while loop statements", () => { + const invalidResult = invalid({ + code: dedent` + do { + console.log("hello world"); + } while (true); + `, + errors: ["generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports for loop statements", () => { + const invalidResult = invalid({ + code: dedent` + for (let i = 0; i < 10; i++) { + console.log("hello world"); + } + `, + errors: ["generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports for in loop statements", () => { + const invalidResult = invalid({ + code: dedent` + for (let i in []) { + console.log("hello world"); + } + `, + errors: ["generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports for of loop statements", () => { + const invalidResult = invalid({ + code: dedent` + for (let i of []) { + console.log("hello world"); + } + `, + errors: ["generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports for await loop statements", () => { + const invalidResult = invalid({ + code: dedent` + for await (let i of []) { + console.log("hello world"); + } + `, + errors: ["generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + }); +}); diff --git a/tests/rules/no-mixed-type/ts/index.test.ts b/tests/rules/no-mixed-type/ts/index.test.ts deleted file mode 100644 index 7e59c4468..000000000 --- a/tests/rules/no-mixed-type/ts/index.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { name, rule } from "#/rules/no-mixed-types"; -import { testRule } from "#/tests/helpers/testers"; - -import invalid from "./invalid"; -import valid from "./valid"; - -const tests = { - valid, - invalid, -}; - -const tester = testRule(name, rule); - -tester.typescript(tests); diff --git a/tests/rules/no-mixed-type/ts/invalid.ts b/tests/rules/no-mixed-type/ts/invalid.ts deleted file mode 100644 index 290984e56..000000000 --- a/tests/rules/no-mixed-type/ts/invalid.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { type rule } from "#/rules/no-mixed-types"; -import { - type InvalidTestCaseSet, - type MessagesOf, - type OptionsOf, -} from "#/tests/helpers/util"; - -const tests: Array< - InvalidTestCaseSet, OptionsOf> -> = [ - // // Mixing properties and methods (MethodSignature) should produce failures. - // { - // code: dedent` - // type Foo = { - // bar: string; - // zoo(): number; - // }; - // `, - // optionsSet: [[], [{ checkInterfaces: false }]], - // errors: [ - // { - // messageId: "generic", - // type: AST_NODE_TYPES.TSTypeAliasDeclaration, - // line: 1, - // column: 1, - // }, - // ], - // }, - // { - // code: dedent` - // type Foo = Readonly<{ - // bar: string; - // zoo(): number; - // }>; - // `, - // optionsSet: [[], [{ checkInterfaces: false }]], - // errors: [ - // { - // messageId: "generic", - // type: AST_NODE_TYPES.TSTypeAliasDeclaration, - // line: 1, - // column: 1, - // }, - // ], - // }, - // { - // code: dedent` - // interface Foo { - // bar: string; - // zoo(): number; - // } - // `, - // optionsSet: [[], [{ checkTypeLiterals: false }]], - // errors: [ - // { - // messageId: "generic", - // type: AST_NODE_TYPES.TSInterfaceDeclaration, - // line: 1, - // column: 1, - // }, - // ], - // }, - // // Mixing properties and functions (PropertySignature) should produce failures. - // { - // code: dedent` - // type Foo = { - // bar: string; - // zoo: () => number; - // }; - // `, - // optionsSet: [[], [{ checkInterfaces: false }]], - // errors: [ - // { - // messageId: "generic", - // type: AST_NODE_TYPES.TSTypeAliasDeclaration, - // line: 1, - // column: 1, - // }, - // ], - // }, - // { - // code: dedent` - // type Foo = Readonly<{ - // bar: string; - // zoo: () => number; - // }>; - // `, - // optionsSet: [[], [{ checkInterfaces: false }]], - // errors: [ - // { - // messageId: "generic", - // type: AST_NODE_TYPES.TSTypeAliasDeclaration, - // line: 1, - // column: 1, - // }, - // ], - // }, - // { - // code: dedent` - // interface Foo { - // bar: string; - // zoo: () => number; - // } - // `, - // optionsSet: [[], [{ checkTypeLiterals: false }]], - // errors: [ - // { - // messageId: "generic", - // type: AST_NODE_TYPES.TSInterfaceDeclaration, - // line: 1, - // column: 1, - // }, - // ], - // }, -]; - -export default tests; diff --git a/tests/rules/no-mixed-type/ts/valid.ts b/tests/rules/no-mixed-type/ts/valid.ts deleted file mode 100644 index 5395762f4..000000000 --- a/tests/rules/no-mixed-type/ts/valid.ts +++ /dev/null @@ -1,113 +0,0 @@ -import dedent from "dedent"; - -import { type rule } from "#/rules/no-mixed-types"; -import { type OptionsOf, type ValidTestCaseSet } from "#/tests/helpers/util"; - -const tests: Array>> = [ - // // Only properties should not produce failures. - // { - // code: dedent` - // type Foo = { - // bar: string; - // zoo: number; - // }; - // `, - // optionsSet: [[], [{ checkInterfaces: false }]], - // }, - // { - // code: dedent` - // interface Foo { - // bar: string; - // zoo: number; - // } - // `, - // optionsSet: [[], [{ checkTypeLiterals: false }]], - // }, - // // Only functions should not produce failures - // { - // code: dedent` - // type Foo = { - // bar: string; - // zoo: number; - // }; - // `, - // optionsSet: [[], [{ checkInterfaces: false }]], - // }, - // { - // code: dedent` - // interface Foo { - // bar: string; - // zoo: number; - // } - // `, - // optionsSet: [[], [{ checkTypeLiterals: false }]], - // }, - // // Only indexer should not produce failures - // { - // code: dedent` - // type Foo = { - // [key: string]: string; - // }; - // `, - // optionsSet: [[], [{ checkInterfaces: false }]], - // }, - // { - // code: dedent` - // interface Foo { - // [key: string]: string; - // } - // `, - // optionsSet: [[], [{ checkTypeLiterals: false }]], - // }, - // // Check Off. - // { - // code: dedent` - // type Foo = { - // bar: string; - // zoo(): number; - // }; - // `, - // optionsSet: [[{ checkTypeLiterals: false }]], - // }, - // { - // code: dedent` - // interface Foo { - // bar: string; - // zoo(): number; - // } - // `, - // optionsSet: [[{ checkInterfaces: false }]], - // }, - // // Mixing properties and functions (PropertySignature) should produce failures. - // { - // code: dedent` - // type Foo = { - // bar: string; - // zoo: () => number; - // }; - // `, - // optionsSet: [[{ checkTypeLiterals: false }]], - // }, - // { - // code: dedent` - // interface Foo { - // bar: string; - // zoo: () => number; - // } - // `, - // optionsSet: [[{ checkInterfaces: false }]], - // }, - { - code: dedent` - const func = (v: any): any => null; - type Foo = typeof func; - type FooBar = Readonly<{ - foo1: (v: any) => null; - foo2: Foo; - }>; - `, - optionsSet: [[]], - }, -]; - -export default tests; diff --git a/tests/rules/no-mixed-types.test.ts b/tests/rules/no-mixed-types.test.ts new file mode 100644 index 000000000..da2a46bc4 --- /dev/null +++ b/tests/rules/no-mixed-types.test.ts @@ -0,0 +1,136 @@ +import dedent from "dedent"; +import { createRuleTester } from "eslint-vitest-rule-tester"; +import { describe, expect, it } from "vitest"; + +import { name, rule } from "#/rules/no-mixed-types"; + +import { typescriptConfig } from "../utils/configs"; + +describe(name, () => { + describe("typescript", () => { + const { valid, invalid } = createRuleTester({ + name, + rule, + configs: typescriptConfig, + }); + + it("doesn't report non-issues", () => { + valid({ + code: dedent` + type Foo = { + bar: string; + baz: number; + }; + `, + }); + + valid({ + code: dedent` + interface Foo { + bar: string; + baz: number; + } + `, + }); + + valid({ + code: dedent` + type Foo = { + bar: () =>string; + baz(): number; + }; + `, + }); + + valid({ + code: dedent` + interface Foo { + bar: () => string; + baz(): number; + } + `, + }); + }); + + it("reports mixed types in interfaces", () => { + const invalidResult = invalid({ + code: dedent` + interface Foo { + bar: string; + baz(): number; + } + `, + errors: ["generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports mixed types in type literals", () => { + const invalidResult = invalid({ + code: dedent` + type Foo = { + bar: string; + baz(): number; + }; + `, + errors: ["generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + describe("options", () => { + describe("checkTypeLiterals", () => { + it("should report mixed types in type literals when enabled", () => { + const invalidResult = invalid({ + code: dedent` + type Foo = { + bar: string; + baz(): number; + }; + `, + options: [{ checkTypeLiterals: true }], + errors: ["generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("should not report mixed types in type literals when disabled", () => { + valid({ + code: dedent` + type Foo = { + bar: string; + baz(): number; + }; + `, + options: [{ checkTypeLiterals: false }], + }); + }); + }); + + describe("checkInterfaces", () => { + it("should report mixed types in interfaces when enabled", () => { + const invalidResult = invalid({ + code: dedent` + interface Foo { bar: string; baz(): number; } + `, + options: [{ checkInterfaces: true }], + errors: ["generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("should not report mixed types in interfaces when disabled", () => { + valid({ + code: dedent` + interface Foo { + bar: string; + baz(): number; + } + `, + options: [{ checkInterfaces: false }], + }); + }); + }); + }); + }); +}); diff --git a/tests/rules/no-promise-reject.test.ts b/tests/rules/no-promise-reject.test.ts new file mode 100644 index 000000000..b0f1da825 --- /dev/null +++ b/tests/rules/no-promise-reject.test.ts @@ -0,0 +1,107 @@ +import dedent from "dedent"; +import { createRuleTester } from "eslint-vitest-rule-tester"; +import { describe, expect, it } from "vitest"; + +import { name, rule } from "#/rules/no-promise-reject"; + +import { esLatestConfig } from "../utils/configs"; + +describe(name, () => { + describe("javascript - es latest", () => { + const { valid, invalid } = createRuleTester({ + name, + rule, + configs: esLatestConfig, + }); + + it("reports Promise.reject", () => { + const invalidResult = invalid({ + code: dedent` + function foo() { + return Promise.reject("hello world"); + } + `, + errors: ["generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports new Promise(reject)", () => { + const invalidResult = invalid({ + code: dedent` + function foo() { + return new Promise((resolve, reject) => { + reject("hello world"); + }); + } + `, + errors: ["generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports throw in async functions", () => { + const invalidResult = invalid({ + code: dedent` + async function foo() { + throw new Error("hello world"); + } + `, + errors: ["generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports throw in try without catch in async functions", () => { + const invalidResult = invalid({ + code: dedent` + async function foo() { + try { + throw new Error("hello"); + } finally { + console.log("world"); + } + } + `, + errors: ["generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("doesn't report Promise.resolve", () => { + valid({ + code: dedent` + function foo() { + return Promise.resolve("hello world"); + } + `, + }); + }); + + it("doesn't report new Promise(resolve)", () => { + valid({ + code: dedent` + function foo() { + return new Promise((resolve) => { + resolve("hello world"); + }); + } + `, + }); + }); + + it("doesn't report throw in try in async functions", () => { + valid({ + code: dedent` + async function foo() { + try { + throw new Error("hello world"); + } catch (e) { + console.log(e); + } + } + `, + }); + }); + }); +}); diff --git a/tests/rules/no-promise-reject/es2015/index.test.ts b/tests/rules/no-promise-reject/es2015/index.test.ts deleted file mode 100644 index ec1ea3c69..000000000 --- a/tests/rules/no-promise-reject/es2015/index.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { name, rule } from "#/rules/no-promise-reject"; -import { testRule } from "#/tests/helpers/testers"; - -import invalid from "./invalid"; -import valid from "./valid"; - -const tests = { - valid, - invalid, -}; - -const tester = testRule(name, rule); - -tester.typescript(tests); -tester.esLatest(tests); -tester.es2015(tests); diff --git a/tests/rules/no-promise-reject/es2015/invalid.ts b/tests/rules/no-promise-reject/es2015/invalid.ts deleted file mode 100644 index 9ccb1964c..000000000 --- a/tests/rules/no-promise-reject/es2015/invalid.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { AST_NODE_TYPES } from "@typescript-eslint/utils"; -import dedent from "dedent"; - -import { type rule } from "#/rules/no-promise-reject"; -import { - type InvalidTestCaseSet, - type MessagesOf, - type OptionsOf, -} from "#/tests/helpers/util"; - -const tests: Array< - InvalidTestCaseSet, OptionsOf> -> = [ - { - code: dedent` - function foo() { - if (Math.random() > 0.5) { - return Promise.reject(new Error("bar")) - } - return Promise.resolve(10) - } - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.CallExpression, - line: 3, - column: 14, - }, - ], - }, -]; - -export default tests; diff --git a/tests/rules/no-promise-reject/es2015/valid.ts b/tests/rules/no-promise-reject/es2015/valid.ts deleted file mode 100644 index b9d12dc89..000000000 --- a/tests/rules/no-promise-reject/es2015/valid.ts +++ /dev/null @@ -1,20 +0,0 @@ -import dedent from "dedent"; - -import { type rule } from "#/rules/no-promise-reject"; -import { type OptionsOf, type ValidTestCaseSet } from "#/tests/helpers/util"; - -const tests: Array>> = [ - { - code: dedent` - function bar() { - if (Math.random() > 0.5) { - return Promise.resolve(new Error("foo")) - } - return Promise.resolve(10) - } - `, - optionsSet: [[]], - }, -]; - -export default tests; diff --git a/tests/rules/no-return-void.test.ts b/tests/rules/no-return-void.test.ts new file mode 100644 index 000000000..7434b16f6 --- /dev/null +++ b/tests/rules/no-return-void.test.ts @@ -0,0 +1,198 @@ +import dedent from "dedent"; +import { createRuleTester } from "eslint-vitest-rule-tester"; +import { describe, expect, it } from "vitest"; + +import { name, rule } from "#/rules/no-return-void"; + +import { typescriptConfig } from "../utils/configs"; + +describe(name, () => { + describe("typescript", () => { + const { valid, invalid } = createRuleTester({ + name, + rule, + configs: typescriptConfig, + }); + + it("doesn't report non-void returning functions", () => { + valid({ + code: dedent` + function foo(bar: number): number { + return bar + 1; + } + `, + }); + }); + + it("doesn't report non-void returning functions with inferred return type", () => { + valid({ + code: dedent` + function foo(bar: number) { + return bar + 1; + } + `, + }); + }); + + it("reports void returning functions", () => { + const invalidResult = invalid({ + code: dedent` + function foo(bar: number): void { + console.log(bar); + } + `, + errors: ["generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports void returning functions with inferred return type", () => { + const invalidResult = invalid({ + code: dedent` + function foo(bar: number) { + console.log(bar); + } + `, + errors: ["generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + describe("options", () => { + describe("allowNull", () => { + it("doesn't report null returning functions when allowed", () => { + valid({ + code: dedent` + function foo(bar: number): null { + return null; + } + `, + options: [{ allowNull: true }], + }); + }); + + it("doesn't report null returning functions with inferred return type when allowed", () => { + valid({ + code: dedent` + function foo(bar: number) { + return null; + } + `, + options: [{ allowNull: true }], + }); + }); + + it("reports null returning functions when disallowed", () => { + const invalidResult = invalid({ + code: dedent` + function foo(bar: number): null { + return null; + } + `, + options: [{ allowNull: false }], + errors: ["generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports null returning functions with inferred return type when disallowed", () => { + const invalidResult = invalid({ + code: dedent` + function foo(bar: number) { + return null; + } + `, + options: [{ allowNull: false }], + errors: ["generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + }); + + describe("allowUndefined", () => { + it("doesn't report undefined returning functions when allowed", () => { + valid({ + code: dedent` + function foo(bar: number): undefined { + return undefined; + } + `, + options: [{ allowUndefined: true }], + }); + }); + + it("doesn't report undefined returning functions with inferred return type when allowed", () => { + valid({ + code: dedent` + function foo(bar: number) { + return undefined; + } + `, + options: [{ allowUndefined: true }], + }); + }); + + it("reports undefined returning functions when disallowed", () => { + const invalidResult = invalid({ + code: dedent` + function foo(bar: number): undefined { + return undefined; + } + `, + options: [{ allowUndefined: false }], + errors: ["generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports undefined returning functions with inferred return type when disallowed", () => { + const invalidResult = invalid({ + code: dedent` + function foo(bar: number) { + return undefined; + } + `, + options: [{ allowUndefined: false }], + errors: ["generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + }); + + describe("ignoreInferredTypes", () => { + it("doesn't report inferred void return type", () => { + valid({ + code: dedent` + function foo(bar: number) { + console.log(bar); + } + `, + options: [{ ignoreInferredTypes: true }], + }); + }); + + it("doesn't report inferred null return type", () => { + valid({ + code: dedent` + function foo(bar: number) { + return null; + } + `, + options: [{ ignoreInferredTypes: true, allowNull: false }], + }); + }); + + it("doesn't report inferred undefined return type", () => { + valid({ + code: dedent` + function foo(bar: number) { + return undefined; + } + `, + options: [{ ignoreInferredTypes: true, allowUndefined: false }], + }); + }); + }); + }); + }); +}); diff --git a/tests/rules/no-return-void/ts/index.test.ts b/tests/rules/no-return-void/ts/index.test.ts deleted file mode 100644 index 22bd874c4..000000000 --- a/tests/rules/no-return-void/ts/index.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { name, rule } from "#/rules/no-return-void"; -import { testRule } from "#/tests/helpers/testers"; - -import invalid from "./invalid"; -import valid from "./valid"; - -const tests = { - valid, - invalid, -}; - -const tester = testRule(name, rule); - -tester.typescript(tests); diff --git a/tests/rules/no-return-void/ts/invalid.ts b/tests/rules/no-return-void/ts/invalid.ts deleted file mode 100644 index 67e980098..000000000 --- a/tests/rules/no-return-void/ts/invalid.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { AST_NODE_TYPES } from "@typescript-eslint/utils"; -import dedent from "dedent"; - -import { type rule } from "#/rules/no-return-void"; -import { - type InvalidTestCaseSet, - type MessagesOf, - type OptionsOf, -} from "#/tests/helpers/util"; - -const tests: Array< - InvalidTestCaseSet, OptionsOf> -> = [ - // Disallow void. - { - code: dedent` - function foo(bar: number): void { - console.log(bar); - } - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.TSTypeAnnotation, - line: 1, - column: 26, - }, - ], - }, - // Disallow undefined. - { - code: dedent` - function foo(bar: number): undefined { - console.log(bar); - return undefined; - } - `, - optionsSet: [[{ allowUndefined: false }]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.TSTypeAnnotation, - line: 1, - column: 26, - }, - ], - }, - // Disallow null. - { - code: dedent` - function foo(bar: number): null { - console.log(bar); - return null; - } - `, - optionsSet: [[{ allowNull: false }]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.TSTypeAnnotation, - line: 1, - column: 26, - }, - ], - }, - // Disallow higher-order function void. - { - code: dedent` - function foo(bar: number): (baz: number) => void { - return baz => { console.log(bar, baz); } - } - `, - optionsSet: [[{ ignoreInferredTypes: true }]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.TSTypeAnnotation, - line: 1, - column: 42, - }, - ], - }, - // Disallow implicit return type. - { - code: dedent` - function foo(bar) { - console.log(bar); - } - `, - optionsSet: [ - [{ ignoreInferredTypes: false }], - [{ ignoreInferredTypes: false, allowNull: false }], - [{ ignoreInferredTypes: false, allowUndefined: false }], - ], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.FunctionDeclaration, - line: 1, - column: 1, - }, - ], - }, -]; - -export default tests; diff --git a/tests/rules/no-return-void/ts/valid.ts b/tests/rules/no-return-void/ts/valid.ts deleted file mode 100644 index e9985bce3..000000000 --- a/tests/rules/no-return-void/ts/valid.ts +++ /dev/null @@ -1,56 +0,0 @@ -import dedent from "dedent"; - -import { type rule } from "#/rules/no-return-void"; -import { type OptionsOf, type ValidTestCaseSet } from "#/tests/helpers/util"; - -const tests: Array>> = [ - { - code: dedent` - function foo(bar) { - return bar + 1; - } - `, - optionsSet: [[], [{ allowNull: false }], [{ allowUndefined: false }]], - }, - { - code: dedent` - function foo(bar: number): number { - return bar + 1; - } - `, - optionsSet: [[], [{ allowNull: false }], [{ allowUndefined: false }]], - }, - // Ignore implicit return types. - { - code: dedent` - function foo(bar) { - console.log(bar); - } - `, - optionsSet: [ - [{ ignoreInferredTypes: true }], - [{ ignoreInferredTypes: true, allowNull: false }], - [{ ignoreInferredTypes: true, allowUndefined: false }], - ], - }, - // Allow null. - { - code: dedent` - function foo(): null { - return null; - } - `, - optionsSet: [[], [{ allowNull: true }], [{ allowUndefined: false }]], - }, - // Allow undefined. - { - code: dedent` - function foo(): undefined { - return undefined; - } - `, - optionsSet: [[], [{ allowNull: false }], [{ allowUndefined: true }]], - }, -]; - -export default tests; diff --git a/tests/rules/no-this-expression/es3/index.test.ts b/tests/rules/no-this-expression/es3/index.test.ts deleted file mode 100644 index acc5b08cc..000000000 --- a/tests/rules/no-this-expression/es3/index.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { name, rule } from "#/rules/no-this-expressions"; -import { testRule } from "#/tests/helpers/testers"; - -import invalid from "./invalid"; -import valid from "./valid"; - -const tests = { - valid, - invalid, -}; - -const tester = testRule(name, rule); - -tester.typescript(tests); diff --git a/tests/rules/no-this-expression/es3/invalid.ts b/tests/rules/no-this-expression/es3/invalid.ts deleted file mode 100644 index 980f21f76..000000000 --- a/tests/rules/no-this-expression/es3/invalid.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { AST_NODE_TYPES } from "@typescript-eslint/utils"; - -import { type rule } from "#/rules/no-this-expressions"; -import { - type InvalidTestCaseSet, - type MessagesOf, - type OptionsOf, -} from "#/tests/helpers/util"; - -const tests: Array< - InvalidTestCaseSet, OptionsOf> -> = [ - { - code: `this.x = 0;`, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.ThisExpression, - line: 1, - column: 1, - }, - ], - }, -]; - -export default tests; diff --git a/tests/rules/no-this-expression/es3/valid.ts b/tests/rules/no-this-expression/es3/valid.ts deleted file mode 100644 index 80dfd1e8c..000000000 --- a/tests/rules/no-this-expression/es3/valid.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { type rule } from "#/rules/no-this-expressions"; -import { type OptionsOf, type ValidTestCaseSet } from "#/tests/helpers/util"; - -const tests: Array>> = [ - { - code: `var x = 0;`, - optionsSet: [[]], - }, -]; - -export default tests; diff --git a/tests/rules/no-this-expressions.test.ts b/tests/rules/no-this-expressions.test.ts new file mode 100644 index 000000000..3d5ab9de8 --- /dev/null +++ b/tests/rules/no-this-expressions.test.ts @@ -0,0 +1,35 @@ +import dedent from "dedent"; +import { createRuleTester } from "eslint-vitest-rule-tester"; +import { describe, expect, it } from "vitest"; + +import { name, rule } from "#/rules/no-this-expressions"; + +import { esLatestConfig } from "../utils/configs"; + +describe(name, () => { + describe("javascript - es latest", () => { + const { valid, invalid } = createRuleTester({ + name, + rule, + configs: esLatestConfig, + }); + + it("reports this expressions", () => { + const code = dedent` + function foo() { + this.bar(); + } + `; + + expect(invalid(code)).toMatchSnapshot(); + }); + + it("doesn't report non-issues", () => { + valid(dedent` + function foo() { + bar(); + } + `); + }); + }); +}); diff --git a/tests/rules/no-throw-statement/es2016/index.test.ts b/tests/rules/no-throw-statement/es2016/index.test.ts deleted file mode 100644 index d839b0cb8..000000000 --- a/tests/rules/no-throw-statement/es2016/index.test.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { name, rule } from "#/rules/no-throw-statements"; -import { testRule } from "#/tests/helpers/testers"; - -import es3Invalid from "../es3/invalid"; -import es3Valid from "../es3/valid"; - -import invalid from "./invalid"; -import valid from "./valid"; - -const tests = { - valid: [...es3Valid, ...valid], - invalid: [...es3Invalid, ...invalid], -}; - -const tester = testRule(name, rule); - -tester.typescript(tests); -tester.esLatest(tests); -tester.es2016(tests); diff --git a/tests/rules/no-throw-statement/es2016/invalid.ts b/tests/rules/no-throw-statement/es2016/invalid.ts deleted file mode 100644 index 99e511d7a..000000000 --- a/tests/rules/no-throw-statement/es2016/invalid.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { AST_NODE_TYPES } from "@typescript-eslint/utils"; -import dedent from "dedent"; - -import { type rule } from "#/rules/no-throw-statements"; -import { - type InvalidTestCaseSet, - type MessagesOf, - type OptionsOf, -} from "#/tests/helpers/util"; - -const tests: Array< - InvalidTestCaseSet, OptionsOf> -> = [ - { - code: dedent` - async function foo() { - throw new Error(); - } - `, - optionsSet: [ - [ - { - allowInAsyncFunctions: false, - }, - ], - ], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.ThrowStatement, - line: 2, - column: 3, - }, - ], - }, - { - code: dedent` - async function foo() { - function bar() { - throw new Error(); - } - } - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.ThrowStatement, - line: 3, - column: 5, - }, - ], - }, -]; - -export default tests; diff --git a/tests/rules/no-throw-statement/es2016/valid.ts b/tests/rules/no-throw-statement/es2016/valid.ts deleted file mode 100644 index e3968e883..000000000 --- a/tests/rules/no-throw-statement/es2016/valid.ts +++ /dev/null @@ -1,23 +0,0 @@ -import dedent from "dedent"; - -import { type rule } from "#/rules/no-throw-statements"; -import { type OptionsOf, type ValidTestCaseSet } from "#/tests/helpers/util"; - -const tests: Array>> = [ - { - code: dedent` - async function foo() { - throw new Error(); - } - `, - optionsSet: [ - [ - { - allowInAsyncFunctions: true, - }, - ], - ], - }, -]; - -export default tests; diff --git a/tests/rules/no-throw-statement/es3/index.test.ts b/tests/rules/no-throw-statement/es3/index.test.ts deleted file mode 100644 index 548e95dfd..000000000 --- a/tests/rules/no-throw-statement/es3/index.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { name, rule } from "#/rules/no-throw-statements"; -import { testRule } from "#/tests/helpers/testers"; - -import invalid from "./invalid"; -import valid from "./valid"; - -const tests = { - valid, - invalid, -}; - -const tester = testRule(name, rule); - -tester.typescript(tests); -tester.esLatest(tests); -tester.es3(tests); diff --git a/tests/rules/no-throw-statement/es3/invalid.ts b/tests/rules/no-throw-statement/es3/invalid.ts deleted file mode 100644 index 1d131a73f..000000000 --- a/tests/rules/no-throw-statement/es3/invalid.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { AST_NODE_TYPES } from "@typescript-eslint/utils"; -import dedent from "dedent"; - -import { type rule } from "#/rules/no-throw-statements"; -import { - type InvalidTestCaseSet, - type MessagesOf, - type OptionsOf, -} from "#/tests/helpers/util"; - -const tests: Array< - InvalidTestCaseSet, OptionsOf> -> = [ - { - code: `throw 'error';`, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.ThrowStatement, - line: 1, - column: 1, - }, - ], - }, - { - code: `throw new Error();`, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - line: 1, - column: 1, - }, - ], - }, - { - code: dedent` - var error = new Error(); - throw error; - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - line: 2, - column: 1, - }, - ], - }, -]; - -export default tests; diff --git a/tests/rules/no-throw-statement/es3/valid.ts b/tests/rules/no-throw-statement/es3/valid.ts deleted file mode 100644 index 2f2ab2309..000000000 --- a/tests/rules/no-throw-statement/es3/valid.ts +++ /dev/null @@ -1,17 +0,0 @@ -import dedent from "dedent"; - -import { type rule } from "#/rules/no-throw-statements"; -import { type OptionsOf, type ValidTestCaseSet } from "#/tests/helpers/util"; - -const tests: Array>> = [ - { - code: dedent` - function foo() { - console.error("boop"); - } - `, - optionsSet: [[]], - }, -]; - -export default tests; diff --git a/tests/rules/no-throw-statements.test.ts b/tests/rules/no-throw-statements.test.ts new file mode 100644 index 000000000..9b102927a --- /dev/null +++ b/tests/rules/no-throw-statements.test.ts @@ -0,0 +1,61 @@ +import dedent from "dedent"; +import { createRuleTester } from "eslint-vitest-rule-tester"; +import { describe, expect, it } from "vitest"; + +import { name, rule } from "#/rules/no-throw-statements"; + +import { esLatestConfig } from "../utils/configs"; + +describe(name, () => { + describe("javascript - es latest", () => { + const { valid, invalid } = createRuleTester({ + name, + rule, + configs: esLatestConfig, + }); + + it("doesn't report non-issues", () => { + valid(dedent` + function foo() { + bar(); + } + `); + }); + + it("reports throw statements of strings", () => { + const invalidResult = invalid({ + code: dedent` + function foo() { + throw 'error'; + } + `, + errors: ["generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports throw statements of Errors", () => { + const invalidResult = invalid({ + code: dedent` + function foo() { + throw new Error(); + } + `, + errors: ["generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports throw statements in async functions", () => { + const invalidResult = invalid({ + code: dedent` + async function foo() { + throw new Error(); + } + `, + errors: ["generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + }); +}); diff --git a/tests/rules/no-try-statement/es3/index.test.ts b/tests/rules/no-try-statement/es3/index.test.ts deleted file mode 100644 index d9e71d35f..000000000 --- a/tests/rules/no-try-statement/es3/index.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { name, rule } from "#/rules/no-try-statements"; -import { testRule } from "#/tests/helpers/testers"; - -import invalid from "./invalid"; -import valid from "./valid"; - -const tests = { - valid, - invalid, -}; - -const tester = testRule(name, rule); - -tester.typescript(tests); -tester.esLatest(tests); -tester.es3(tests); diff --git a/tests/rules/no-try-statement/es3/invalid.ts b/tests/rules/no-try-statement/es3/invalid.ts deleted file mode 100644 index 1a27be863..000000000 --- a/tests/rules/no-try-statement/es3/invalid.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { AST_NODE_TYPES } from "@typescript-eslint/utils"; - -import { type rule } from "#/rules/no-try-statements"; -import { - type InvalidTestCaseSet, - type MessagesOf, - type OptionsOf, -} from "#/tests/helpers/util"; - -const tests: Array< - InvalidTestCaseSet, OptionsOf> -> = [ - { - code: `try {} catch (e) {}`, - optionsSet: [[]], - errors: [ - { - messageId: "catch", - type: AST_NODE_TYPES.TryStatement, - line: 1, - column: 1, - }, - ], - }, - { - code: `try {} catch (e) {} finally {}`, - optionsSet: [[]], - errors: [ - { - messageId: "catch", - type: AST_NODE_TYPES.TryStatement, - line: 1, - column: 1, - }, - ], - }, - { - code: `try {} catch (e) {} finally {}`, - optionsSet: [[{ allowCatch: true }]], - errors: [ - { - messageId: "finally", - type: AST_NODE_TYPES.TryStatement, - line: 1, - column: 1, - }, - ], - }, - { - code: `try {} catch (e) {} finally {}`, - optionsSet: [[{ allowFinally: true }]], - errors: [ - { - messageId: "catch", - type: AST_NODE_TYPES.TryStatement, - line: 1, - column: 1, - }, - ], - }, - { - code: `try {} finally {}`, - optionsSet: [[]], - errors: [ - { - messageId: "finally", - type: AST_NODE_TYPES.TryStatement, - line: 1, - column: 1, - }, - ], - }, -]; - -export default tests; diff --git a/tests/rules/no-try-statement/es3/valid.ts b/tests/rules/no-try-statement/es3/valid.ts deleted file mode 100644 index ed0b97114..000000000 --- a/tests/rules/no-try-statement/es3/valid.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { type rule } from "#/rules/no-try-statements"; -import { type OptionsOf, type ValidTestCaseSet } from "#/tests/helpers/util"; - -const tests: Array>> = [ - { - code: `var x = 0;`, - optionsSet: [[]], - }, - { - code: `try {} catch (e) {}`, - optionsSet: [[{ allowCatch: true }]], - }, - { - code: `try {} finally {}`, - optionsSet: [[{ allowFinally: true }]], - }, -]; - -export default tests; diff --git a/tests/rules/no-try-statements.test.ts b/tests/rules/no-try-statements.test.ts new file mode 100644 index 000000000..0178f94f4 --- /dev/null +++ b/tests/rules/no-try-statements.test.ts @@ -0,0 +1,118 @@ +import dedent from "dedent"; +import { createRuleTester } from "eslint-vitest-rule-tester"; +import { describe, expect, it } from "vitest"; + +import { name, rule } from "#/rules/no-try-statements"; + +import { esLatestConfig } from "../utils/configs"; + +describe(name, () => { + describe("javascript - es latest", () => { + const { valid, invalid } = createRuleTester({ + name, + rule, + configs: esLatestConfig, + }); + + it("doesn't report non-issues", () => { + valid(dedent` + foo(); + `); + }); + + it("reports try statements", () => { + const invalidResult = invalid({ + code: dedent` + try { + foo(); + } catch (e) { + console.log(e); + } + `, + errors: ["catch"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + describe("options", () => { + describe("allowCatch", () => { + it("doesn't report try statements with catch", () => { + valid({ + code: dedent` + try { + foo(); + } catch (e) { + console.log(e); + } + `, + options: [{ allowCatch: true }], + }); + }); + + it("reports try statements with catch and finally", () => { + const invalidResult = invalid({ + code: dedent` + try { + foo(); + } catch (e) { + console.log(e); + } finally { + console.log("world"); + } + `, + errors: ["finally"], + options: [{ allowCatch: true, allowFinally: false }], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + }); + + describe("allowFinally", () => { + it("doesn't report try statements with finally", () => { + valid({ + code: dedent` + try { + foo(); + } finally { + console.log("world"); + } + `, + options: [{ allowFinally: true }], + }); + }); + + it("reports try statements with catch and finally", () => { + const invalidResult = invalid({ + code: dedent` + try { + foo(); + } catch (e) { + console.log(e); + } finally { + console.log("world"); + } + `, + errors: ["catch"], + options: [{ allowCatch: false, allowFinally: true }], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + }); + + it("doesn't report try statements with catch and finally if both are allowed", () => { + valid({ + code: dedent` + try { + foo(); + } catch (e) { + console.log(e); + } finally { + console.log("world"); + } + `, + options: [{ allowCatch: true, allowFinally: true }], + }); + }); + }); + }); +}); diff --git a/tests/rules/prefer-immutable-types/__snapshots__/parameters.test.ts.snap b/tests/rules/prefer-immutable-types/__snapshots__/parameters.test.ts.snap new file mode 100644 index 000000000..d7d03b8ad --- /dev/null +++ b/tests/rules/prefer-immutable-types/__snapshots__/parameters.test.ts.snap @@ -0,0 +1,627 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`prefer-immutable-types > typescript > allows for user fixes 1`] = ` +[ + { + "column": 14, + "endColumn": 32, + "endLine": 1, + "line": 1, + "message": "Parameter should have an immutability of at least "Immutable" (actual: "Mutable").", + "messageId": "parameter", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, + { + "column": 14, + "endColumn": 27, + "endLine": 2, + "line": 2, + "message": "Parameter should have an immutability of at least "Immutable" (actual: "Mutable").", + "messageId": "parameter", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, + { + "column": 14, + "endColumn": 30, + "endLine": 3, + "fix": { + "range": [ + 85, + 96, + ], + "text": "Readonly>", + }, + "line": 3, + "message": "Parameter should have an immutability of at least "Immutable" (actual: "Mutable").", + "messageId": "parameter", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, + { + "column": 14, + "endColumn": 38, + "endLine": 4, + "fix": { + "range": [ + 119, + 138, + ], + "text": "Readonly>", + }, + "line": 4, + "message": "Parameter should have an immutability of at least "Immutable" (actual: "Mutable").", + "messageId": "parameter", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, + { + "column": 14, + "endColumn": 40, + "endLine": 5, + "line": 5, + "message": "Parameter should have an immutability of at least "Immutable" (actual: "ReadonlyDeep").", + "messageId": "parameter", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, + { + "column": 14, + "endColumn": 36, + "endLine": 6, + "line": 6, + "message": "Parameter should have an immutability of at least "Immutable" (actual: "ReadonlyDeep").", + "messageId": "parameter", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, + { + "column": 14, + "endColumn": 38, + "endLine": 7, + "fix": { + "range": [ + 245, + 264, + ], + "text": "Readonly>", + }, + "line": 7, + "message": "Parameter should have an immutability of at least "Immutable" (actual: "ReadonlyDeep").", + "messageId": "parameter", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, + { + "column": 14, + "endColumn": 46, + "endLine": 8, + "fix": { + "range": [ + 287, + 314, + ], + "text": "Readonly>", + }, + "line": 8, + "message": "Parameter should have an immutability of at least "Immutable" (actual: "ReadonlyDeep").", + "messageId": "parameter", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, +] +`; + +exports[`prefer-immutable-types > typescript > allows for user suggestions 1`] = ` +[ + { + "column": 14, + "endColumn": 35, + "endLine": 1, + "line": 1, + "message": "Parameter should have an immutability of at least "ReadonlyDeep" (actual: "Mutable").", + "messageId": "parameter", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + "suggestions": [ + { + "data": { + "message": "Replace with: ReadonlyDeep<{ foo: string }>", + }, + "desc": "Replace with: ReadonlyDeep<{ foo: string }>", + "fix": { + "range": [ + 19, + 34, + ], + "text": "ReadonlyDeep<{ foo: string }>", + }, + "messageId": "userDefined", + }, + ], + }, +] +`; + +exports[`prefer-immutable-types > typescript > doesn't report type assertion parameters 1`] = ` +[ + { + "column": 46, + "endColumn": 76, + "endLine": 1, + "line": 1, + "message": "Parameter should have an immutability of at least "ReadonlyShallow" (actual: "Mutable").", + "messageId": "parameter", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + "suggestions": [ + { + "data": { + "message": "Surround with Readonly.", + }, + "desc": "Surround with Readonly.", + "fix": { + "range": [ + 51, + 75, + ], + "text": "Readonly<{ foo: string | number }>", + }, + "messageId": "userDefined", + }, + ], + }, +] +`; + +exports[`prefer-immutable-types > typescript > reports mutable class parameter properties 1`] = ` +[ + { + "column": 5, + "endColumn": 30, + "endLine": 3, + "line": 3, + "message": "Property should have a readonly modifier.", + "messageId": "propertyModifier", + "nodeType": "TSParameterProperty", + "ruleId": "prefer-immutable-types", + "severity": 2, + "suggestions": [ + { + "desc": "Add readonly modifier.", + "fix": { + "range": [ + 41, + 41, + ], + "text": "readonly ", + }, + "messageId": "propertyModifierSuggestion", + }, + ], + }, + { + "column": 5, + "endColumn": 36, + "endLine": 4, + "line": 4, + "message": "Property should have a readonly modifier.", + "messageId": "propertyModifier", + "nodeType": "TSParameterProperty", + "ruleId": "prefer-immutable-types", + "severity": 2, + "suggestions": [ + { + "desc": "Add readonly modifier.", + "fix": { + "range": [ + 75, + 75, + ], + "text": "readonly ", + }, + "messageId": "propertyModifierSuggestion", + }, + ], + }, + { + "column": 5, + "endColumn": 32, + "endLine": 5, + "line": 5, + "message": "Property should have a readonly modifier.", + "messageId": "propertyModifier", + "nodeType": "TSParameterProperty", + "ruleId": "prefer-immutable-types", + "severity": 2, + "suggestions": [ + { + "desc": "Add readonly modifier.", + "fix": { + "range": [ + 110, + 110, + ], + "text": "readonly ", + }, + "messageId": "propertyModifierSuggestion", + }, + ], + }, +] +`; + +exports[`prefer-immutable-types > typescript > reports mutable class parameter properties and suggests a fix for ReadonlyShallow 1`] = ` +[ + { + "column": 5, + "endColumn": 30, + "endLine": 3, + "line": 3, + "message": "Property should have a readonly modifier.", + "messageId": "propertyModifier", + "nodeType": "TSParameterProperty", + "ruleId": "prefer-immutable-types", + "severity": 2, + "suggestions": [ + { + "desc": "Add readonly modifier.", + "fix": { + "range": [ + 41, + 41, + ], + "text": "readonly ", + }, + "messageId": "propertyModifierSuggestion", + }, + ], + }, + { + "column": 5, + "endColumn": 36, + "endLine": 4, + "line": 4, + "message": "Property should have a readonly modifier.", + "messageId": "propertyModifier", + "nodeType": "TSParameterProperty", + "ruleId": "prefer-immutable-types", + "severity": 2, + "suggestions": [ + { + "desc": "Add readonly modifier.", + "fix": { + "range": [ + 75, + 75, + ], + "text": "readonly ", + }, + "messageId": "propertyModifierSuggestion", + }, + ], + }, + { + "column": 5, + "endColumn": 32, + "endLine": 5, + "line": 5, + "message": "Property should have a readonly modifier.", + "messageId": "propertyModifier", + "nodeType": "TSParameterProperty", + "ruleId": "prefer-immutable-types", + "severity": 2, + "suggestions": [ + { + "desc": "Add readonly modifier.", + "fix": { + "range": [ + 110, + 110, + ], + "text": "readonly ", + }, + "messageId": "propertyModifierSuggestion", + }, + ], + }, +] +`; + +exports[`prefer-immutable-types > typescript > reports mutable records parameters 1`] = ` +[ + { + "column": 14, + "endColumn": 35, + "endLine": 1, + "line": 1, + "message": "Parameter should have an immutability of at least "Immutable" (actual: "Mutable").", + "messageId": "parameter", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, + { + "column": 37, + "endColumn": 58, + "endLine": 1, + "line": 1, + "message": "Parameter should have an immutability of at least "Immutable" (actual: "Mutable").", + "messageId": "parameter", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, +] +`; + +exports[`prefer-immutable-types > typescript > reports mutable records parameters and suggests a fix for ReadonlyShallow 1`] = ` +[ + { + "column": 14, + "endColumn": 35, + "endLine": 1, + "line": 1, + "message": "Parameter should have an immutability of at least "ReadonlyShallow" (actual: "Mutable").", + "messageId": "parameter", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + "suggestions": [ + { + "data": { + "message": "Surround with Readonly.", + }, + "desc": "Surround with Readonly.", + "fix": { + "range": [ + 19, + 34, + ], + "text": "Readonly<{ foo: string }>", + }, + "messageId": "userDefined", + }, + ], + }, + { + "column": 37, + "endColumn": 58, + "endLine": 1, + "line": 1, + "message": "Parameter should have an immutability of at least "ReadonlyShallow" (actual: "Mutable").", + "messageId": "parameter", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + "suggestions": [ + { + "data": { + "message": "Surround with Readonly.", + }, + "desc": "Surround with Readonly.", + "fix": { + "range": [ + 42, + 57, + ], + "text": "Readonly<{ foo: number }>", + }, + "messageId": "userDefined", + }, + ], + }, +] +`; + +exports[`prefer-immutable-types > typescript > reports non-immutable map parameters 1`] = ` +[ + { + "column": 14, + "endColumn": 46, + "endLine": 1, + "line": 1, + "message": "Parameter should have an immutability of at least "Immutable" (actual: "ReadonlyDeep").", + "messageId": "parameter", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, +] +`; + +exports[`prefer-immutable-types > typescript > reports non-immutable set parameters 1`] = ` +[ + { + "column": 14, + "endColumn": 38, + "endLine": 1, + "line": 1, + "message": "Parameter should have an immutability of at least "Immutable" (actual: "ReadonlyDeep").", + "messageId": "parameter", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, +] +`; + +exports[`prefer-immutable-types > typescript > suggest multiple fixes for collections for ReadonlyShallow 1`] = ` +[ + { + "column": 14, + "endColumn": 32, + "endLine": 1, + "line": 1, + "message": "Parameter should have an immutability of at least "ReadonlyShallow" (actual: "Mutable").", + "messageId": "parameter", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + "suggestions": [ + { + "data": { + "message": "Use ReadonlyArray instead of Array.", + }, + "desc": "Use ReadonlyArray instead of Array.", + "fix": { + "range": [ + 18, + 31, + ], + "text": "ReadonlyArray", + }, + "messageId": "userDefined", + }, + { + "data": { + "message": "Surround with Readonly.", + }, + "desc": "Surround with Readonly.", + "fix": { + "range": [ + 18, + 31, + ], + "text": "Readonly>", + }, + "messageId": "userDefined", + }, + ], + }, + { + "column": 14, + "endColumn": 27, + "endLine": 2, + "line": 2, + "message": "Parameter should have an immutability of at least "ReadonlyShallow" (actual: "Mutable").", + "messageId": "parameter", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + "suggestions": [ + { + "data": { + "message": "Prepend with readonly.", + }, + "desc": "Prepend with readonly.", + "fix": { + "range": [ + 54, + 62, + ], + "text": "readonly string[]", + }, + "messageId": "userDefined", + }, + { + "data": { + "message": "Surround with Readonly.", + }, + "desc": "Surround with Readonly.", + "fix": { + "range": [ + 54, + 62, + ], + "text": "Readonly", + }, + "messageId": "userDefined", + }, + ], + }, + { + "column": 14, + "endColumn": 30, + "endLine": 3, + "line": 3, + "message": "Parameter should have an immutability of at least "ReadonlyShallow" (actual: "Mutable").", + "messageId": "parameter", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + "suggestions": [ + { + "data": { + "message": "Use ReadonlySet instead of Set.", + }, + "desc": "Use ReadonlySet instead of Set.", + "fix": { + "range": [ + 85, + 96, + ], + "text": "ReadonlySet", + }, + "messageId": "userDefined", + }, + { + "data": { + "message": "Surround with Readonly.", + }, + "desc": "Surround with Readonly.", + "fix": { + "range": [ + 85, + 96, + ], + "text": "Readonly>", + }, + "messageId": "userDefined", + }, + ], + }, + { + "column": 14, + "endColumn": 38, + "endLine": 4, + "line": 4, + "message": "Parameter should have an immutability of at least "ReadonlyShallow" (actual: "Mutable").", + "messageId": "parameter", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + "suggestions": [ + { + "data": { + "message": "Use ReadonlyMap instead of Map.", + }, + "desc": "Use ReadonlyMap instead of Map.", + "fix": { + "range": [ + 119, + 138, + ], + "text": "ReadonlyMap", + }, + "messageId": "userDefined", + }, + { + "data": { + "message": "Surround with Readonly.", + }, + "desc": "Surround with Readonly.", + "fix": { + "range": [ + 119, + 138, + ], + "text": "Readonly>", + }, + "messageId": "userDefined", + }, + ], + }, +] +`; diff --git a/tests/rules/prefer-immutable-types/__snapshots__/return-types.test.ts.snap b/tests/rules/prefer-immutable-types/__snapshots__/return-types.test.ts.snap new file mode 100644 index 000000000..3bbf5e213 --- /dev/null +++ b/tests/rules/prefer-immutable-types/__snapshots__/return-types.test.ts.snap @@ -0,0 +1,403 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`prefer-immutable-types > typescript > allows for user fixes 1`] = ` +[ + { + "column": 15, + "endColumn": 30, + "endLine": 1, + "line": 1, + "message": "Return type should have an immutability of at least "Immutable" (actual: "Mutable").", + "messageId": "returnType", + "nodeType": "TSTypeAnnotation", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, + { + "column": 15, + "endColumn": 25, + "endLine": 2, + "line": 2, + "message": "Return type should have an immutability of at least "Immutable" (actual: "Mutable").", + "messageId": "returnType", + "nodeType": "TSTypeAnnotation", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, + { + "column": 15, + "endColumn": 28, + "endLine": 3, + "fix": { + "range": [ + 77, + 88, + ], + "text": "Readonly>", + }, + "line": 3, + "message": "Return type should have an immutability of at least "Immutable" (actual: "Mutable").", + "messageId": "returnType", + "nodeType": "TSTypeAnnotation", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, + { + "column": 15, + "endColumn": 36, + "endLine": 4, + "fix": { + "range": [ + 108, + 127, + ], + "text": "Readonly>", + }, + "line": 4, + "message": "Return type should have an immutability of at least "Immutable" (actual: "Mutable").", + "messageId": "returnType", + "nodeType": "TSTypeAnnotation", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, + { + "column": 15, + "endColumn": 38, + "endLine": 5, + "line": 5, + "message": "Return type should have an immutability of at least "Immutable" (actual: "ReadonlyDeep").", + "messageId": "returnType", + "nodeType": "TSTypeAnnotation", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, + { + "column": 15, + "endColumn": 34, + "endLine": 6, + "line": 6, + "message": "Return type should have an immutability of at least "Immutable" (actual: "ReadonlyDeep").", + "messageId": "returnType", + "nodeType": "TSTypeAnnotation", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, + { + "column": 15, + "endColumn": 36, + "endLine": 7, + "fix": { + "range": [ + 225, + 244, + ], + "text": "Readonly>", + }, + "line": 7, + "message": "Return type should have an immutability of at least "Immutable" (actual: "ReadonlyDeep").", + "messageId": "returnType", + "nodeType": "TSTypeAnnotation", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, + { + "column": 15, + "endColumn": 44, + "endLine": 8, + "fix": { + "range": [ + 264, + 291, + ], + "text": "Readonly>", + }, + "line": 8, + "message": "Return type should have an immutability of at least "Immutable" (actual: "ReadonlyDeep").", + "messageId": "returnType", + "nodeType": "TSTypeAnnotation", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, +] +`; + +exports[`prefer-immutable-types > typescript > allows for user suggestions 1`] = ` +[ + { + "column": 15, + "endColumn": 32, + "endLine": 1, + "line": 1, + "message": "Return type should have an immutability of at least "ReadonlyDeep" (actual: "Mutable").", + "messageId": "returnType", + "nodeType": "TSTypeAnnotation", + "ruleId": "prefer-immutable-types", + "severity": 2, + "suggestions": [ + { + "data": { + "message": "Replace with: ReadonlyDeep<{ foo: string }>", + }, + "desc": "Replace with: ReadonlyDeep<{ foo: string }>", + "fix": { + "range": [ + 16, + 31, + ], + "text": "ReadonlyDeep<{ foo: string }>", + }, + "messageId": "userDefined", + }, + ], + }, +] +`; + +exports[`prefer-immutable-types > typescript > reports mutable records return types 1`] = ` +[ + { + "column": 15, + "endColumn": 32, + "endLine": 1, + "line": 1, + "message": "Return type should have an immutability of at least "Immutable" (actual: "Mutable").", + "messageId": "returnType", + "nodeType": "TSTypeAnnotation", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, +] +`; + +exports[`prefer-immutable-types > typescript > reports mutable records return types and suggests a fix for ReadonlyShallow 1`] = ` +[ + { + "column": 15, + "endColumn": 32, + "endLine": 1, + "line": 1, + "message": "Return type should have an immutability of at least "ReadonlyShallow" (actual: "Mutable").", + "messageId": "returnType", + "nodeType": "TSTypeAnnotation", + "ruleId": "prefer-immutable-types", + "severity": 2, + "suggestions": [ + { + "data": { + "message": "Surround with Readonly.", + }, + "desc": "Surround with Readonly.", + "fix": { + "range": [ + 16, + 31, + ], + "text": "Readonly<{ foo: string }>", + }, + "messageId": "userDefined", + }, + ], + }, +] +`; + +exports[`prefer-immutable-types > typescript > reports non-immutable map return types 1`] = ` +[ + { + "column": 15, + "endColumn": 44, + "endLine": 1, + "line": 1, + "message": "Return type should have an immutability of at least "Immutable" (actual: "ReadonlyDeep").", + "messageId": "returnType", + "nodeType": "TSTypeAnnotation", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, +] +`; + +exports[`prefer-immutable-types > typescript > reports non-immutable set return types 1`] = ` +[ + { + "column": 15, + "endColumn": 36, + "endLine": 1, + "line": 1, + "message": "Return type should have an immutability of at least "Immutable" (actual: "ReadonlyDeep").", + "messageId": "returnType", + "nodeType": "TSTypeAnnotation", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, +] +`; + +exports[`prefer-immutable-types > typescript > suggest multiple fixes for collections for ReadonlyShallow 1`] = ` +[ + { + "column": 15, + "endColumn": 30, + "endLine": 1, + "line": 1, + "message": "Return type should have an immutability of at least "ReadonlyShallow" (actual: "Mutable").", + "messageId": "returnType", + "nodeType": "TSTypeAnnotation", + "ruleId": "prefer-immutable-types", + "severity": 2, + "suggestions": [ + { + "data": { + "message": "Use ReadonlyArray instead of Array.", + }, + "desc": "Use ReadonlyArray instead of Array.", + "fix": { + "range": [ + 16, + 29, + ], + "text": "ReadonlyArray", + }, + "messageId": "userDefined", + }, + { + "data": { + "message": "Surround with Readonly.", + }, + "desc": "Surround with Readonly.", + "fix": { + "range": [ + 16, + 29, + ], + "text": "Readonly>", + }, + "messageId": "userDefined", + }, + ], + }, + { + "column": 15, + "endColumn": 25, + "endLine": 2, + "line": 2, + "message": "Return type should have an immutability of at least "ReadonlyShallow" (actual: "Mutable").", + "messageId": "returnType", + "nodeType": "TSTypeAnnotation", + "ruleId": "prefer-immutable-types", + "severity": 2, + "suggestions": [ + { + "data": { + "message": "Prepend with readonly.", + }, + "desc": "Prepend with readonly.", + "fix": { + "range": [ + 49, + 57, + ], + "text": "readonly string[]", + }, + "messageId": "userDefined", + }, + { + "data": { + "message": "Surround with Readonly.", + }, + "desc": "Surround with Readonly.", + "fix": { + "range": [ + 49, + 57, + ], + "text": "Readonly", + }, + "messageId": "userDefined", + }, + ], + }, + { + "column": 15, + "endColumn": 28, + "endLine": 3, + "line": 3, + "message": "Return type should have an immutability of at least "ReadonlyShallow" (actual: "Mutable").", + "messageId": "returnType", + "nodeType": "TSTypeAnnotation", + "ruleId": "prefer-immutable-types", + "severity": 2, + "suggestions": [ + { + "data": { + "message": "Use ReadonlySet instead of Set.", + }, + "desc": "Use ReadonlySet instead of Set.", + "fix": { + "range": [ + 77, + 88, + ], + "text": "ReadonlySet", + }, + "messageId": "userDefined", + }, + { + "data": { + "message": "Surround with Readonly.", + }, + "desc": "Surround with Readonly.", + "fix": { + "range": [ + 77, + 88, + ], + "text": "Readonly>", + }, + "messageId": "userDefined", + }, + ], + }, + { + "column": 15, + "endColumn": 36, + "endLine": 4, + "line": 4, + "message": "Return type should have an immutability of at least "ReadonlyShallow" (actual: "Mutable").", + "messageId": "returnType", + "nodeType": "TSTypeAnnotation", + "ruleId": "prefer-immutable-types", + "severity": 2, + "suggestions": [ + { + "data": { + "message": "Use ReadonlyMap instead of Map.", + }, + "desc": "Use ReadonlyMap instead of Map.", + "fix": { + "range": [ + 108, + 127, + ], + "text": "ReadonlyMap", + }, + "messageId": "userDefined", + }, + { + "data": { + "message": "Surround with Readonly.", + }, + "desc": "Surround with Readonly.", + "fix": { + "range": [ + 108, + 127, + ], + "text": "Readonly>", + }, + "messageId": "userDefined", + }, + ], + }, +] +`; diff --git a/tests/rules/prefer-immutable-types/__snapshots__/variables.test.ts.snap b/tests/rules/prefer-immutable-types/__snapshots__/variables.test.ts.snap new file mode 100644 index 000000000..7a5f65e87 --- /dev/null +++ b/tests/rules/prefer-immutable-types/__snapshots__/variables.test.ts.snap @@ -0,0 +1,452 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`prefer-immutable-types > typescript > allows for user fixes 1`] = ` +[ + { + "column": 7, + "endColumn": 25, + "endLine": 1, + "line": 1, + "message": "Variable should have an immutability of at least "Immutable" (actual: "Mutable").", + "messageId": "variable", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, + { + "column": 7, + "endColumn": 20, + "endLine": 2, + "line": 2, + "message": "Variable should have an immutability of at least "Immutable" (actual: "Mutable").", + "messageId": "variable", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, + { + "column": 7, + "endColumn": 23, + "endLine": 3, + "fix": { + "range": [ + 82, + 93, + ], + "text": "Readonly>", + }, + "line": 3, + "message": "Variable should have an immutability of at least "Immutable" (actual: "Mutable").", + "messageId": "variable", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, + { + "column": 7, + "endColumn": 31, + "endLine": 4, + "fix": { + "range": [ + 118, + 137, + ], + "text": "Readonly>", + }, + "line": 4, + "message": "Variable should have an immutability of at least "Immutable" (actual: "Mutable").", + "messageId": "variable", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, + { + "column": 7, + "endColumn": 33, + "endLine": 5, + "line": 5, + "message": "Variable should have an immutability of at least "Immutable" (actual: "ReadonlyDeep").", + "messageId": "variable", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, + { + "column": 7, + "endColumn": 29, + "endLine": 6, + "line": 6, + "message": "Variable should have an immutability of at least "Immutable" (actual: "ReadonlyDeep").", + "messageId": "variable", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, + { + "column": 7, + "endColumn": 31, + "endLine": 7, + "fix": { + "range": [ + 250, + 269, + ], + "text": "Readonly>", + }, + "line": 7, + "message": "Variable should have an immutability of at least "Immutable" (actual: "ReadonlyDeep").", + "messageId": "variable", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, + { + "column": 7, + "endColumn": 39, + "endLine": 8, + "fix": { + "range": [ + 294, + 321, + ], + "text": "Readonly>", + }, + "line": 8, + "message": "Variable should have an immutability of at least "Immutable" (actual: "ReadonlyDeep").", + "messageId": "variable", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, +] +`; + +exports[`prefer-immutable-types > typescript > allows for user suggestions 1`] = ` +[ + { + "column": 7, + "endColumn": 27, + "endLine": 1, + "line": 1, + "message": "Variable should have an immutability of at least "ReadonlyDeep" (actual: "Mutable").", + "messageId": "variable", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + "suggestions": [ + { + "data": { + "message": "Replace with: ReadonlyDeep<{ foo: string }>", + }, + "desc": "Replace with: ReadonlyDeep<{ foo: string }>", + "fix": { + "range": [ + 11, + 26, + ], + "text": "ReadonlyDeep<{ foo: string }>", + }, + "messageId": "userDefined", + }, + ], + }, +] +`; + +exports[`prefer-immutable-types > typescript > reports mutable class parameter properties 1`] = ` +[ + { + "column": 3, + "endColumn": 33, + "endLine": 2, + "line": 2, + "message": "Property should have an immutability of at least "Immutable" (actual: "Mutable").", + "messageId": "propertyImmutability", + "nodeType": "PropertyDefinition", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, + { + "column": 3, + "endColumn": 41, + "endLine": 3, + "line": 3, + "message": "Property should have an immutability of at least "Immutable" (actual: "Mutable").", + "messageId": "propertyImmutability", + "nodeType": "PropertyDefinition", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, + { + "column": 3, + "endColumn": 40, + "endLine": 4, + "line": 4, + "message": "Property should have an immutability of at least "Immutable" (actual: "Mutable").", + "messageId": "propertyImmutability", + "nodeType": "PropertyDefinition", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, + { + "column": 3, + "endColumn": 48, + "endLine": 5, + "line": 5, + "message": "Property should have an immutability of at least "Immutable" (actual: "Mutable").", + "messageId": "propertyImmutability", + "nodeType": "PropertyDefinition", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, +] +`; + +exports[`prefer-immutable-types > typescript > reports mutable records variables 1`] = ` +[ + { + "column": 7, + "endColumn": 27, + "endLine": 1, + "line": 1, + "message": "Variable should have an immutability of at least "Immutable" (actual: "Mutable").", + "messageId": "variable", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, +] +`; + +exports[`prefer-immutable-types > typescript > reports mutable records variables and suggests a fix for ReadonlyShallow 1`] = ` +[ + { + "column": 7, + "endColumn": 27, + "endLine": 1, + "line": 1, + "message": "Variable should have an immutability of at least "ReadonlyShallow" (actual: "Mutable").", + "messageId": "variable", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + "suggestions": [ + { + "data": { + "message": "Surround with Readonly.", + }, + "desc": "Surround with Readonly.", + "fix": { + "range": [ + 11, + 26, + ], + "text": "Readonly<{ foo: string }>", + }, + "messageId": "userDefined", + }, + ], + }, +] +`; + +exports[`prefer-immutable-types > typescript > reports non-immutable map variables 1`] = ` +[ + { + "column": 7, + "endColumn": 39, + "endLine": 1, + "line": 1, + "message": "Variable should have an immutability of at least "Immutable" (actual: "ReadonlyDeep").", + "messageId": "variable", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, +] +`; + +exports[`prefer-immutable-types > typescript > reports non-immutable set variables 1`] = ` +[ + { + "column": 7, + "endColumn": 31, + "endLine": 1, + "line": 1, + "message": "Variable should have an immutability of at least "Immutable" (actual: "ReadonlyDeep").", + "messageId": "variable", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + }, +] +`; + +exports[`prefer-immutable-types > typescript > suggest multiple fixes for collections for ReadonlyShallow 1`] = ` +[ + { + "column": 7, + "endColumn": 25, + "endLine": 1, + "line": 1, + "message": "Variable should have an immutability of at least "ReadonlyShallow" (actual: "Mutable").", + "messageId": "variable", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + "suggestions": [ + { + "data": { + "message": "Use ReadonlyArray instead of Array.", + }, + "desc": "Use ReadonlyArray instead of Array.", + "fix": { + "range": [ + 11, + 24, + ], + "text": "ReadonlyArray", + }, + "messageId": "userDefined", + }, + { + "data": { + "message": "Surround with Readonly.", + }, + "desc": "Surround with Readonly.", + "fix": { + "range": [ + 11, + 24, + ], + "text": "Readonly>", + }, + "messageId": "userDefined", + }, + ], + }, + { + "column": 7, + "endColumn": 20, + "endLine": 2, + "line": 2, + "message": "Variable should have an immutability of at least "ReadonlyShallow" (actual: "Mutable").", + "messageId": "variable", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + "suggestions": [ + { + "data": { + "message": "Prepend with readonly.", + }, + "desc": "Prepend with readonly.", + "fix": { + "range": [ + 49, + 57, + ], + "text": "readonly string[]", + }, + "messageId": "userDefined", + }, + { + "data": { + "message": "Surround with Readonly.", + }, + "desc": "Surround with Readonly.", + "fix": { + "range": [ + 49, + 57, + ], + "text": "Readonly", + }, + "messageId": "userDefined", + }, + ], + }, + { + "column": 7, + "endColumn": 23, + "endLine": 3, + "line": 3, + "message": "Variable should have an immutability of at least "ReadonlyShallow" (actual: "Mutable").", + "messageId": "variable", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + "suggestions": [ + { + "data": { + "message": "Use ReadonlySet instead of Set.", + }, + "desc": "Use ReadonlySet instead of Set.", + "fix": { + "range": [ + 82, + 93, + ], + "text": "ReadonlySet", + }, + "messageId": "userDefined", + }, + { + "data": { + "message": "Surround with Readonly.", + }, + "desc": "Surround with Readonly.", + "fix": { + "range": [ + 82, + 93, + ], + "text": "Readonly>", + }, + "messageId": "userDefined", + }, + ], + }, + { + "column": 7, + "endColumn": 31, + "endLine": 4, + "line": 4, + "message": "Variable should have an immutability of at least "ReadonlyShallow" (actual: "Mutable").", + "messageId": "variable", + "nodeType": "Identifier", + "ruleId": "prefer-immutable-types", + "severity": 2, + "suggestions": [ + { + "data": { + "message": "Use ReadonlyMap instead of Map.", + }, + "desc": "Use ReadonlyMap instead of Map.", + "fix": { + "range": [ + 118, + 137, + ], + "text": "ReadonlyMap", + }, + "messageId": "userDefined", + }, + { + "data": { + "message": "Surround with Readonly.", + }, + "desc": "Surround with Readonly.", + "fix": { + "range": [ + 118, + 137, + ], + "text": "Readonly>", + }, + "messageId": "userDefined", + }, + ], + }, +] +`; diff --git a/tests/rules/prefer-immutable-types/parameters.test.ts b/tests/rules/prefer-immutable-types/parameters.test.ts new file mode 100644 index 000000000..39a5b046c --- /dev/null +++ b/tests/rules/prefer-immutable-types/parameters.test.ts @@ -0,0 +1,309 @@ +import dedent from "dedent"; +import { createRuleTester } from "eslint-vitest-rule-tester"; +import { describe, expect, it } from "vitest"; + +import { name, rule } from "#/rules/prefer-immutable-types"; + +import { typescriptConfig } from "../../utils/configs"; + +describe(name, () => { + describe("typescript", () => { + const { valid, invalid } = createRuleTester({ + name, + rule, + configs: typescriptConfig, + }); + + it("reports non-immutable set parameters", () => { + const invalidResult = invalid({ + code: "function foo(arg: ReadonlySet) {}", + errors: ["parameter"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports non-immutable map parameters", () => { + const invalidResult = invalid({ + code: "function foo(arg: ReadonlyMap) {}", + errors: ["parameter"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports mutable records parameters", () => { + const invalidResult = invalid({ + code: "function foo(arg1: { foo: string }, arg2: { foo: number }) {}", + errors: ["parameter", "parameter"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports mutable records parameters and suggests a fix for ReadonlyShallow", () => { + const invalidResult = invalid({ + code: "function foo(arg1: { foo: string }, arg2: { foo: number }) {}", + options: [{ parameters: "ReadonlyShallow" }], + errors: ["parameter", "parameter"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + for (const message of invalidResult.messages) { + expect(message.suggestions).toHaveLength(1); + } + }); + + it("reports mutable class parameter properties", () => { + const invalidResult = invalid({ + code: dedent` + class Klass { + constructor ( + public publicProp: string, + protected protectedProp: string, + private privateProp: string, + ) { } + } + `, + errors: ["propertyModifier", "propertyModifier", "propertyModifier"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports mutable class parameter properties and suggests a fix for ReadonlyShallow", () => { + const invalidResult = invalid({ + code: dedent` + class Klass { + constructor ( + public publicProp: string, + protected protectedProp: string, + private privateProp: string, + ) { } + } + `, + options: [{ parameters: "ReadonlyShallow" }], + errors: ["propertyModifier", "propertyModifier", "propertyModifier"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + for (const message of invalidResult.messages) { + expect(message.suggestions).toHaveLength(1); + } + }); + + it("doesn't report type assertion parameters", () => { + const invalidResult = invalid({ + code: "function foo(arg0: { foo: string | number }, arg1: { foo: string | number }): arg0 is { foo: number } {}", + options: [{ parameters: "ReadonlyShallow" }], + errors: ["parameter"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + for (const message of invalidResult.messages) { + expect(message.suggestions).toHaveLength(1); + } + }); + + it("suggest multiple fixes for collections for ReadonlyShallow", () => { + const invalidResult = invalid({ + code: dedent` + function foo(arg: Array) {} + function foo(arg: string[]) {} + function foo(arg: Set) {} + function foo(arg: Map) {} + function foo(arg: ReadonlyArray) {} + function foo(arg: readonly string[]) {} + function foo(arg: ReadonlySet) {} + function foo(arg: ReadonlyMap) {} + `, + options: [{ parameters: "ReadonlyShallow" }], + errors: ["parameter", "parameter", "parameter", "parameter"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + for (const message of invalidResult.messages) { + expect(message.suggestions).toHaveLength(2); + } + }); + + it("allows for user suggestions", () => { + const invalidResult = invalid({ + code: "function foo(arg1: { foo: string }) {}", + options: [ + { + parameters: "ReadonlyDeep", + suggestions: { + ReadonlyDeep: [ + [ + { + pattern: "^(?!ReadonlyDeep)(?:Readonly<(.+)>|(.+))$", + replace: "ReadonlyDeep<$1$2>", + }, + ], + ], + }, + }, + ], + errors: ["parameter"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + for (const message of invalidResult.messages) { + expect(message.suggestions).toHaveLength(1); + } + }); + + it("allows for user fixes", () => { + const invalidResult = invalid({ + code: dedent` + function foo(arg: Array) {} + function foo(arg: string[]) {} + function foo(arg: Set) {} + function foo(arg: Map) {} + function foo(arg: ReadonlyArray) {} + function foo(arg: readonly string[]) {} + function foo(arg: ReadonlySet) {} + function foo(arg: ReadonlyMap) {} + `, + options: [ + { + parameters: "Immutable", + fixer: { + Immutable: [ + { + pattern: "^(?:Readonly)?(Set|Map)<(.+)>$", + replace: "Readonly>", + }, + ], + }, + }, + ], + errors: [ + "parameter", + "parameter", + "parameter", + "parameter", + "parameter", + "parameter", + "parameter", + "parameter", + ], + verifyAfterFix: false, // "fix" doesn't fix arrays so they will still report. + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it.each([ + [[{ parameters: "ReadonlyShallow" }]], + [[{ parameters: "ReadonlyDeep" }]], + [[{ parameters: "Immutable" }]], + ])("doesn't reports valid parameters", (options) => { + valid({ + code: "function foo(arg: boolean) {}", + options, + }); + valid({ + code: "function foo(arg: true) {}", + options, + }); + valid({ + code: "function foo(arg: string) {}", + options, + }); + valid({ + code: "function foo(arg: 'bar') {}", + options, + }); + valid({ + code: "function foo(arg: undefined) {}", + options, + }); + valid({ + code: "function foo(arg: readonly string[]) {}", + options, + settings: { + immutability: { + overrides: [ + { + type: { from: "lib", name: "ReadonlyArray" }, + to: "Immutable", + }, + ], + }, + }, + }); + valid({ + code: "function foo(arg: { readonly foo: string }) {}", + options, + }); + valid({ + code: "function foo(arg: { readonly foo: { readonly bar: number } }) {}", + options, + }); + valid({ + code: "function foo(arg: Readonly>) {}", + options, + }); + valid({ + code: "function foo(arg: Readonly>) {}", + options, + }); + valid({ + code: "function foo(arg: { foo: string | number }): arg is { foo: number } {}", + options, + }); + + if (options[0]!.parameters !== "Immutable") { + valid({ + code: "function foo(arg: { foo(): void }) {}", + options, + }); + valid({ + code: "function foo(arg: ReadonlyArray) {}", + options, + }); + valid({ + code: "function foo(arg: readonly [string, number]) {}", + options, + }); + valid({ + code: "function foo(arg: Readonly<[string, number]>) {}", + options, + }); + valid({ + code: "function foo(arg: { foo: () => void }) {}", + options, + }); + valid({ + code: "function foo(arg: ReadonlySet) {}", + options, + }); + valid({ + code: "function foo(arg: ReadonlyMap) {}", + options, + }); + valid({ + code: dedent` + class Foo { + constructor( + private readonly arg1: readonly string[], + public readonly arg2: readonly string[], + protected readonly arg3: readonly string[], + readonly arg4: readonly string[], + ) {} + } + `, + options, + }); + valid({ + code: dedent` + interface Foo { + (arg: readonly string[]): void; + } + `, + options, + }); + valid({ + code: dedent` + interface Foo { + new (arg: readonly string[]): void; + } + `, + options, + }); + } + }); + }); +}); diff --git a/tests/rules/prefer-immutable-types/return-types.test.ts b/tests/rules/prefer-immutable-types/return-types.test.ts new file mode 100644 index 000000000..59359d196 --- /dev/null +++ b/tests/rules/prefer-immutable-types/return-types.test.ts @@ -0,0 +1,260 @@ +import dedent from "dedent"; +import { createRuleTester } from "eslint-vitest-rule-tester"; +import { describe, expect, it } from "vitest"; + +import { name, rule } from "#/rules/prefer-immutable-types"; + +import { typescriptConfig } from "../../utils/configs"; + +describe(name, () => { + describe("typescript", () => { + const { valid, invalid } = createRuleTester({ + name, + rule, + configs: typescriptConfig, + }); + + it("reports non-immutable set return types", () => { + const invalidResult = invalid({ + code: "function foo(): ReadonlySet {}", + errors: ["returnType"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports non-immutable map return types", () => { + const invalidResult = invalid({ + code: "function foo(): ReadonlyMap {}", + errors: ["returnType"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports mutable records return types", () => { + const invalidResult = invalid({ + code: "function foo(): { foo: string } {}", + errors: ["returnType"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports mutable records return types and suggests a fix for ReadonlyShallow", () => { + const invalidResult = invalid({ + code: "function foo(): { foo: string } {}", + options: [{ returnTypes: "ReadonlyShallow" }], + errors: ["returnType"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + for (const message of invalidResult.messages) { + expect(message.suggestions).toHaveLength(1); + } + }); + + it("doesn't report type assertion return types", () => { + valid({ + code: "function foo(arg0: { foo: string | number }, arg1: { foo: string | number }): arg0 is { foo: number } {}", + options: [ + { + parameters: false, + returnTypes: "ReadonlyShallow", + }, + ], + }); + }); + + it("suggest multiple fixes for collections for ReadonlyShallow", () => { + const invalidResult = invalid({ + code: dedent` + function foo(): Array {} + function foo(): string[] {} + function foo(): Set {} + function foo(): Map {} + function foo(): ReadonlyArray {} + function foo(): readonly string[] {} + function foo(): ReadonlySet {} + function foo(): ReadonlyMap {} + `, + options: [{ returnTypes: "ReadonlyShallow" }], + errors: ["returnType", "returnType", "returnType", "returnType"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + for (const message of invalidResult.messages) { + expect(message.suggestions).toHaveLength(2); + } + }); + + it("allows for user suggestions", () => { + const invalidResult = invalid({ + code: "function foo(): { foo: string } {}", + options: [ + { + returnTypes: "ReadonlyDeep", + suggestions: { + ReadonlyDeep: [ + [ + { + pattern: "^(?!ReadonlyDeep)(?:Readonly<(.+)>|(.+))$", + replace: "ReadonlyDeep<$1$2>", + }, + ], + ], + }, + }, + ], + errors: ["returnType"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + for (const message of invalidResult.messages) { + expect(message.suggestions).toHaveLength(1); + } + }); + + it("allows for user fixes", () => { + const invalidResult = invalid({ + code: dedent` + function foo(): Array {} + function foo(): string[] {} + function foo(): Set {} + function foo(): Map {} + function foo(): ReadonlyArray {} + function foo(): readonly string[] {} + function foo(): ReadonlySet {} + function foo(): ReadonlyMap {} + `, + options: [ + { + returnTypes: "Immutable", + fixer: { + Immutable: [ + { + pattern: "^(?:Readonly)?(Set|Map)<(.+)>$", + replace: "Readonly>", + }, + ], + }, + }, + ], + errors: [ + "returnType", + "returnType", + "returnType", + "returnType", + "returnType", + "returnType", + "returnType", + "returnType", + ], + verifyAfterFix: false, // "fix" doesn't fix arrays so they will still report. + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it.each([ + [[{ returnTypes: "ReadonlyShallow" }]], + [[{ returnTypes: "ReadonlyDeep" }]], + [[{ returnTypes: "Immutable" }]], + ])("doesn't reports valid return types", (options) => { + valid({ + code: "function foo(): boolean {}", + options, + }); + valid({ + code: "function foo(): true {}", + options, + }); + valid({ + code: "function foo(): string {}", + options, + }); + valid({ + code: "function foo(): 'bar' {}", + options, + }); + valid({ + code: "function foo(): undefined {}", + options, + }); + valid({ + code: "function foo(): readonly string[] {}", + options, + settings: { + immutability: { + overrides: [ + { + type: { from: "lib", name: "ReadonlyArray" }, + to: "Immutable", + }, + ], + }, + }, + }); + valid({ + code: "function foo(): { readonly foo: string } {}", + options, + }); + valid({ + code: "function foo(): { readonly foo: { readonly bar: number } } {}", + options, + }); + valid({ + code: "function foo(): Readonly> {}", + options, + }); + valid({ + code: "function foo(): Readonly> {}", + options, + }); + valid({ + code: "function foo(arg: { foo: string | number }): arg is { foo: number } {}", + options, + }); + + if (options[0]!.returnTypes !== "Immutable") { + valid({ + code: "function foo(): { foo(): void } {}", + options, + }); + valid({ + code: "function foo(): ReadonlyArray {}", + options, + }); + valid({ + code: "function foo(): readonly [string, number] {}", + options, + }); + valid({ + code: "function foo(): Readonly<[string, number]> {}", + options, + }); + valid({ + code: "function foo(): { foo: () => void } {}", + options, + }); + valid({ + code: "function foo(): ReadonlySet {}", + options, + }); + valid({ + code: "function foo(): ReadonlyMap {}", + options, + }); + valid({ + code: dedent` + interface Foo { + (): readonly string[]; + } + `, + options, + }); + valid({ + code: dedent` + interface Foo { + new (): readonly string[]; + } + `, + options, + }); + } + }); + }); +}); diff --git a/tests/rules/prefer-immutable-types/ts/parameters/index.test.ts b/tests/rules/prefer-immutable-types/ts/parameters/index.test.ts deleted file mode 100644 index d2429ecf5..000000000 --- a/tests/rules/prefer-immutable-types/ts/parameters/index.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { name, rule } from "#/rules/prefer-immutable-types"; -import { testRule } from "#/tests/helpers/testers"; - -import invalid from "./invalid"; -import valid from "./valid"; - -const tests = { - valid, - invalid, -}; - -const tester = testRule(name, rule); - -tester.typescript(tests); diff --git a/tests/rules/prefer-immutable-types/ts/parameters/invalid.ts b/tests/rules/prefer-immutable-types/ts/parameters/invalid.ts deleted file mode 100644 index 275caeb41..000000000 --- a/tests/rules/prefer-immutable-types/ts/parameters/invalid.ts +++ /dev/null @@ -1,700 +0,0 @@ -import { AST_NODE_TYPES } from "@typescript-eslint/utils"; -import dedent from "dedent"; - -import { type rule } from "#/rules/prefer-immutable-types"; -import { - type InvalidTestCaseSet, - type MessagesOf, - type OptionsOf, -} from "#/tests/helpers/util"; - -const tests: Array< - InvalidTestCaseSet, OptionsOf> -> = [ - { - code: "function foo(arg: ReadonlySet) {}", - optionsSet: [[{ parameters: "Immutable" }]], - errors: [ - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 14, - }, - ], - }, - { - code: "function foo(arg: ReadonlyMap) {}", - optionsSet: [[{ parameters: "Immutable" }]], - errors: [ - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 14, - }, - ], - }, - { - code: "function foo(arg1: { foo: string }, arg2: { foo: number }) {}", - optionsSet: [[{ parameters: "ReadonlyShallow" }]], - errors: [ - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 14, - suggestions: [ - { - messageId: "userDefined", - data: { message: "Surround with Readonly." }, - output: - "function foo(arg1: Readonly<{ foo: string }>, arg2: { foo: number }) {}", - }, - ], - }, - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 37, - suggestions: [ - { - messageId: "userDefined", - data: { message: "Surround with Readonly." }, - output: - "function foo(arg1: { foo: string }, arg2: Readonly<{ foo: number }>) {}", - }, - ], - }, - ], - }, - { - code: "function foo(arg1: { foo: string }, arg2: { foo: number }) {}", - optionsSet: [ - [{ parameters: "ReadonlyDeep" }], - [{ parameters: "Immutable" }], - ], - errors: [ - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 14, - }, - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 37, - }, - ], - }, - { - code: dedent` - class Foo { - constructor( - private readonly arg1: readonly string[], - public readonly arg2: readonly string[], - protected readonly arg3: readonly string[], - readonly arg4: readonly string[], - ) {} - } - `, - optionsSet: [[{ parameters: "Immutable" }]], - errors: [ - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 3, - column: 22, - }, - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 4, - column: 21, - }, - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 5, - column: 24, - }, - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 6, - column: 14, - }, - ], - }, - { - code: dedent` - interface Foo { - (arg: readonly string[]): void; - } - `, - optionsSet: [[{ parameters: "Immutable" }]], - errors: [ - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 2, - column: 4, - }, - ], - }, - { - code: dedent` - interface Foo { - new (arg: readonly string[]): void; - } - `, - optionsSet: [[{ parameters: "Immutable" }]], - errors: [ - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 2, - column: 8, - }, - ], - }, - // Class Parameter Properties. - { - code: dedent` - class Klass { - constructor ( - public publicProp: string, - protected protectedProp: string, - private privateProp: string, - ) { } - } - `, - optionsSet: [[]], - errors: [ - { - messageId: "propertyModifier", - type: AST_NODE_TYPES.TSParameterProperty, - line: 3, - column: 5, - suggestions: [ - { - messageId: "propertyModifierSuggestion", - output: dedent` - class Klass { - constructor ( - public readonly publicProp: string, - protected protectedProp: string, - private privateProp: string, - ) { } - } - `, - }, - ], - }, - { - messageId: "propertyModifier", - type: AST_NODE_TYPES.TSParameterProperty, - line: 4, - column: 5, - suggestions: [ - { - messageId: "propertyModifierSuggestion", - output: dedent` - class Klass { - constructor ( - public publicProp: string, - protected readonly protectedProp: string, - private privateProp: string, - ) { } - } - `, - }, - ], - }, - { - messageId: "propertyModifier", - type: AST_NODE_TYPES.TSParameterProperty, - line: 5, - column: 5, - suggestions: [ - { - messageId: "propertyModifierSuggestion", - output: dedent` - class Klass { - constructor ( - public publicProp: string, - protected protectedProp: string, - private readonly privateProp: string, - ) { } - } - `, - }, - ], - }, - ], - }, - { - code: "function foo(arg0: { foo: string | number }, arg1: { foo: string | number }): arg0 is { foo: number } {}", - optionsSet: [[{ parameters: "ReadonlyShallow" }]], - errors: [ - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 46, - suggestions: [ - { - messageId: "userDefined", - data: { message: "Surround with Readonly." }, - output: - "function foo(arg0: { foo: string | number }, arg1: Readonly<{ foo: string | number }>): arg0 is { foo: number } {}", - }, - ], - }, - ], - }, - { - code: "function foo(arg0: { foo: string | number }, arg1: { foo: string | number }): arg0 is { foo: number } {}", - optionsSet: [ - [{ parameters: "ReadonlyDeep" }], - [{ parameters: "Immutable" }], - ], - errors: [ - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 46, - }, - ], - }, - { - code: "function foo(arg1: { foo: string }) {}", - optionsSet: [ - [ - { - parameters: "ReadonlyDeep", - suggestions: { - ReadonlyDeep: [ - [ - { - pattern: "^(.+)$", - replace: "ReadonlyDeep<$1>", - }, - ], - ], - }, - }, - ], - ], - errors: [ - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 14, - suggestions: [ - { - messageId: "userDefined", - data: { message: "Replace with: ReadonlyDeep<{ foo: string }>" }, - output: "function foo(arg1: ReadonlyDeep<{ foo: string }>) {}", - }, - ], - }, - ], - }, - { - code: "function foo(arg1: { foo: { bar: string } }) {}", - optionsSet: [ - [ - { - parameters: "ReadonlyDeep", - suggestions: { - ReadonlyDeep: [ - [ - { - pattern: "^(?!ReadonlyDeep)(?:Readonly<(.+)>|(.+))$", - replace: "ReadonlyDeep<$1$2>", - }, - ], - ], - }, - }, - ], - ], - errors: [ - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 14, - suggestions: [ - { - messageId: "userDefined", - data: { - message: "Replace with: ReadonlyDeep<{ foo: { bar: string } }>", - }, - output: - "function foo(arg1: ReadonlyDeep<{ foo: { bar: string } }>) {}", - }, - ], - }, - ], - }, - { - code: dedent` - function foo(arg: Array) {} - function foo(arg: string[]) {} - function foo(arg: Set) {} - function foo(arg: Map) {} - function foo(arg: ReadonlyArray) {} - function foo(arg: readonly string[]) {} - function foo(arg: ReadonlySet) {} - function foo(arg: ReadonlyMap) {} - `, - optionsSet: [[{ parameters: "ReadonlyShallow" }]], - errors: [ - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 14, - suggestions: [ - { - messageId: "userDefined", - data: { - message: "Use ReadonlyArray instead of Array.", - }, - output: dedent` - function foo(arg: ReadonlyArray) {} - function foo(arg: string[]) {} - function foo(arg: Set) {} - function foo(arg: Map) {} - function foo(arg: ReadonlyArray) {} - function foo(arg: readonly string[]) {} - function foo(arg: ReadonlySet) {} - function foo(arg: ReadonlyMap) {} - `, - }, - { - messageId: "userDefined", - data: { - message: "Surround with Readonly.", - }, - output: dedent` - function foo(arg: Readonly>) {} - function foo(arg: string[]) {} - function foo(arg: Set) {} - function foo(arg: Map) {} - function foo(arg: ReadonlyArray) {} - function foo(arg: readonly string[]) {} - function foo(arg: ReadonlySet) {} - function foo(arg: ReadonlyMap) {} - `, - }, - ], - }, - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 2, - column: 14, - suggestions: [ - { - messageId: "userDefined", - data: { - message: "Prepend with readonly.", - }, - output: dedent` - function foo(arg: Array) {} - function foo(arg: readonly string[]) {} - function foo(arg: Set) {} - function foo(arg: Map) {} - function foo(arg: ReadonlyArray) {} - function foo(arg: readonly string[]) {} - function foo(arg: ReadonlySet) {} - function foo(arg: ReadonlyMap) {} - `, - }, - { - messageId: "userDefined", - data: { - message: "Surround with Readonly.", - }, - output: dedent` - function foo(arg: Array) {} - function foo(arg: Readonly) {} - function foo(arg: Set) {} - function foo(arg: Map) {} - function foo(arg: ReadonlyArray) {} - function foo(arg: readonly string[]) {} - function foo(arg: ReadonlySet) {} - function foo(arg: ReadonlyMap) {} - `, - }, - ], - }, - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 3, - column: 14, - suggestions: [ - { - messageId: "userDefined", - data: { - message: "Use ReadonlySet instead of Set.", - }, - output: dedent` - function foo(arg: Array) {} - function foo(arg: string[]) {} - function foo(arg: ReadonlySet) {} - function foo(arg: Map) {} - function foo(arg: ReadonlyArray) {} - function foo(arg: readonly string[]) {} - function foo(arg: ReadonlySet) {} - function foo(arg: ReadonlyMap) {} - `, - }, - { - messageId: "userDefined", - data: { - message: "Surround with Readonly.", - }, - output: dedent` - function foo(arg: Array) {} - function foo(arg: string[]) {} - function foo(arg: Readonly>) {} - function foo(arg: Map) {} - function foo(arg: ReadonlyArray) {} - function foo(arg: readonly string[]) {} - function foo(arg: ReadonlySet) {} - function foo(arg: ReadonlyMap) {} - `, - }, - ], - }, - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 4, - column: 14, - suggestions: [ - { - messageId: "userDefined", - data: { - message: "Use ReadonlyMap instead of Map.", - }, - output: dedent` - function foo(arg: Array) {} - function foo(arg: string[]) {} - function foo(arg: Set) {} - function foo(arg: ReadonlyMap) {} - function foo(arg: ReadonlyArray) {} - function foo(arg: readonly string[]) {} - function foo(arg: ReadonlySet) {} - function foo(arg: ReadonlyMap) {} - `, - }, - { - messageId: "userDefined", - data: { - message: "Surround with Readonly.", - }, - output: dedent` - function foo(arg: Array) {} - function foo(arg: string[]) {} - function foo(arg: Set) {} - function foo(arg: Readonly>) {} - function foo(arg: ReadonlyArray) {} - function foo(arg: readonly string[]) {} - function foo(arg: ReadonlySet) {} - function foo(arg: ReadonlyMap) {} - `, - }, - ], - }, - ], - }, - { - code: dedent` - function foo(arg: Array<{foo: string}>) {} - function foo(arg: Set<{foo: string}>) {} - function foo(arg: Map<{foo: string}, {foo: string}>) {} - function foo(arg: ReadonlyArray<{foo: string}>) {} - function foo(arg: ReadonlySet<{foo: string}>) {} - function foo(arg: ReadonlyMap<{foo: string}, {foo: string}>) {} - function foo(arg: {foo: string}[]) {} - function foo(arg: readonly {foo: string}[]) {} - type ReadonlyDeep = T; - `, - optionsSet: [ - [ - { - parameters: "ReadonlyDeep", - fixer: { - ReadonlyDeep: [ - { - pattern: "^(?!ReadonlyDeep)(?:Readonly<(.+)>|(.+))$", - replace: "ReadonlyDeep<$1$2>", - }, - ], - }, - }, - ], - ], - output: dedent` - function foo(arg: ReadonlyDeep>) {} - function foo(arg: ReadonlyDeep>) {} - function foo(arg: ReadonlyDeep>) {} - function foo(arg: ReadonlyDeep>) {} - function foo(arg: ReadonlyDeep>) {} - function foo(arg: ReadonlyDeep>) {} - function foo(arg: ReadonlyDeep<{foo: string}[]>) {} - function foo(arg: ReadonlyDeep) {} - type ReadonlyDeep = T; - `, - errors: [ - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 14, - }, - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 2, - column: 14, - }, - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 3, - column: 14, - }, - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 4, - column: 14, - }, - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 5, - column: 14, - }, - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 6, - column: 14, - }, - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 7, - column: 14, - }, - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 8, - column: 14, - }, - ], - }, - { - code: dedent` - function foo(arg: Array) {} - function foo(arg: string[]) {} - function foo(arg: Set) {} - function foo(arg: Map) {} - function foo(arg: ReadonlyArray) {} - function foo(arg: readonly string[]) {} - function foo(arg: ReadonlySet) {} - function foo(arg: ReadonlyMap) {} - `, - optionsSet: [ - [ - { - parameters: "Immutable", - fixer: { - Immutable: [ - { - pattern: "^(?:Readonly)?(Set|Map)<(.+)>$", - replace: "Readonly>", - }, - ], - }, - }, - ], - ], - output: dedent` - function foo(arg: Array) {} - function foo(arg: string[]) {} - function foo(arg: Readonly>) {} - function foo(arg: Readonly>) {} - function foo(arg: ReadonlyArray) {} - function foo(arg: readonly string[]) {} - function foo(arg: Readonly>) {} - function foo(arg: Readonly>) {} - `, - errors: [ - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 14, - }, - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 2, - column: 14, - }, - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 3, - column: 14, - }, - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 4, - column: 14, - }, - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 5, - column: 14, - }, - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 6, - column: 14, - }, - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 7, - column: 14, - }, - { - messageId: "parameter", - type: AST_NODE_TYPES.Identifier, - line: 8, - column: 14, - }, - ], - }, -]; - -export default tests; diff --git a/tests/rules/prefer-immutable-types/ts/parameters/valid.ts b/tests/rules/prefer-immutable-types/ts/parameters/valid.ts deleted file mode 100644 index 3bf8c0766..000000000 --- a/tests/rules/prefer-immutable-types/ts/parameters/valid.ts +++ /dev/null @@ -1,266 +0,0 @@ -import dedent from "dedent"; - -import { type rule } from "#/rules/prefer-immutable-types"; -import { type OptionsOf, type ValidTestCaseSet } from "#/tests/helpers/util"; - -const tests: Array>> = [ - { - code: "function foo(arg: boolean) {}", - optionsSet: [ - [{ parameters: "ReadonlyShallow" }], - [{ parameters: "ReadonlyDeep" }], - [{ parameters: "Immutable" }], - ], - }, - { - code: "function foo(arg: true) {}", - optionsSet: [ - [{ parameters: "ReadonlyShallow" }], - [{ parameters: "ReadonlyDeep" }], - [{ parameters: "Immutable" }], - ], - }, - { - code: "function foo(arg: string) {}", - optionsSet: [ - [{ parameters: "ReadonlyShallow" }], - [{ parameters: "ReadonlyDeep" }], - [{ parameters: "Immutable" }], - ], - }, - { - code: "function foo(arg: 'bar') {}", - optionsSet: [ - [{ parameters: "ReadonlyShallow" }], - [{ parameters: "ReadonlyDeep" }], - [{ parameters: "Immutable" }], - ], - }, - { - code: "function foo(arg: 'undefined') {}", - optionsSet: [ - [{ parameters: "ReadonlyShallow" }], - [{ parameters: "ReadonlyDeep" }], - [{ parameters: "Immutable" }], - ], - }, - { - code: "function foo(arg: readonly string[]) {}", - optionsSet: [ - [{ parameters: "ReadonlyShallow" }], - [{ parameters: "ReadonlyDeep" }], - [{ parameters: "Immutable" }], - ], - settingsSet: [ - { - immutability: { - overrides: [ - { - type: { from: "lib", name: "ReadonlyArray" }, - to: "Immutable", - }, - ], - }, - }, - ], - }, - { - code: "function foo(arg: ReadonlyArray) {}", - optionsSet: [ - [{ parameters: "ReadonlyShallow" }], - [{ parameters: "ReadonlyDeep" }], - ], - }, - { - code: "function foo(arg: readonly [string, number]) {}", - optionsSet: [ - [{ parameters: "ReadonlyShallow" }], - [{ parameters: "ReadonlyDeep" }], - ], - }, - { - code: "function foo(arg: Readonly<[string, number]>) {}", - optionsSet: [ - [{ parameters: "ReadonlyShallow" }], - [{ parameters: "ReadonlyDeep" }], - ], - }, - { - code: "function foo(arg: { readonly foo: string }) {}", - optionsSet: [ - [{ parameters: "ReadonlyShallow" }], - [{ parameters: "ReadonlyDeep" }], - [{ parameters: "Immutable" }], - ], - }, - { - code: "function foo(arg: { readonly foo: { readonly bar: number } }) {}", - optionsSet: [ - [{ parameters: "ReadonlyShallow" }], - [{ parameters: "ReadonlyDeep" }], - [{ parameters: "Immutable" }], - ], - }, - { - code: "function foo(arg: { foo(): void }) {}", - optionsSet: [ - [{ parameters: "ReadonlyShallow" }], - [{ parameters: "ReadonlyDeep" }], - ], - }, - { - code: "function foo(arg: { foo: () => void }) {}", - optionsSet: [ - [{ parameters: "ReadonlyShallow" }], - [{ parameters: "ReadonlyDeep" }], - ], - }, - { - code: "function foo(arg: ReadonlySet) {}", - optionsSet: [ - [{ parameters: "ReadonlyShallow" }], - [{ parameters: "ReadonlyDeep" }], - ], - }, - { - code: "function foo(arg: ReadonlyMap) {}", - optionsSet: [ - [{ parameters: "ReadonlyShallow" }], - [{ parameters: "ReadonlyDeep" }], - ], - }, - { - code: "function foo(arg: Readonly>) {}", - optionsSet: [ - [{ parameters: "ReadonlyShallow" }], - [{ parameters: "ReadonlyDeep" }], - [{ parameters: "Immutable" }], - ], - }, - { - code: "function foo(arg: Readonly>) {}", - optionsSet: [ - [{ parameters: "ReadonlyShallow" }], - [{ parameters: "ReadonlyDeep" }], - [{ parameters: "Immutable" }], - ], - }, - { - code: dedent` - class Foo { - constructor( - private readonly arg1: readonly string[], - public readonly arg2: readonly string[], - protected readonly arg3: readonly string[], - readonly arg4: readonly string[], - ) {} - } - `, - optionsSet: [ - [{ parameters: "ReadonlyShallow" }], - [{ parameters: "ReadonlyDeep" }], - ], - }, - { - code: dedent` - interface Foo { - (arg: readonly string[]): void; - } - `, - optionsSet: [ - [{ parameters: "ReadonlyShallow" }], - [{ parameters: "ReadonlyDeep" }], - ], - }, - { - code: dedent` - interface Foo { - new (arg: readonly string[]): void; - } - `, - optionsSet: [ - [{ parameters: "ReadonlyShallow" }], - [{ parameters: "ReadonlyDeep" }], - ], - }, - { - code: dedent` - type Callback = (options: T) => void; - declare const acceptsCallback: (callback: Callback) => void; - interface CallbackOptions { - prop: string; - } - acceptsCallback(options => {}); - `, - optionsSet: [ - [ - { - parameters: { - enforcement: "ReadonlyShallow", - ignoreInferredTypes: true, - }, - }, - ], - [ - { - parameters: { - enforcement: "ReadonlyDeep", - ignoreInferredTypes: true, - }, - }, - ], - [ - { - parameters: { enforcement: "Immutable", ignoreInferredTypes: true }, - }, - ], - ], - }, - { - code: "function foo(arg: { foo: string | number }): arg is { foo: number } {}", - optionsSet: [ - [{ parameters: "ReadonlyShallow" }], - [{ parameters: "ReadonlyDeep" }], - [{ parameters: "Immutable" }], - ], - }, - // Ignore Name Prefix. - { - code: dedent` - function foo(mutableArg: string[]) {} - `, - optionsSet: [[{ ignoreNamePattern: "^mutable" }]], - }, - // Inherit Ignore Name Prefix. - { - code: dedent` - function foo(mutableArg: string[]) {} - `, - optionsSet: [ - [ - { - ignoreNamePattern: "^mutable", - parameters: { - enforcement: "Immutable", - }, - }, - ], - ], - }, - // Ignore Name Suffix. - { - code: dedent` - function foo(argMutable: string[]) {} - `, - optionsSet: [[{ ignoreNamePattern: "Mutable$" }]], - }, - // Ignore Type. - { - code: dedent` - function foo(arg: Readonly) {} - `, - optionsSet: [[{ ignoreTypePattern: "^Readonly<.+>$" }]], - }, -]; - -export default tests; diff --git a/tests/rules/prefer-immutable-types/ts/return-types/index.test.ts b/tests/rules/prefer-immutable-types/ts/return-types/index.test.ts deleted file mode 100644 index d2429ecf5..000000000 --- a/tests/rules/prefer-immutable-types/ts/return-types/index.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { name, rule } from "#/rules/prefer-immutable-types"; -import { testRule } from "#/tests/helpers/testers"; - -import invalid from "./invalid"; -import valid from "./valid"; - -const tests = { - valid, - invalid, -}; - -const tester = testRule(name, rule); - -tester.typescript(tests); diff --git a/tests/rules/prefer-immutable-types/ts/return-types/invalid.ts b/tests/rules/prefer-immutable-types/ts/return-types/invalid.ts deleted file mode 100644 index 114710584..000000000 --- a/tests/rules/prefer-immutable-types/ts/return-types/invalid.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { AST_NODE_TYPES } from "@typescript-eslint/utils"; -import dedent from "dedent"; - -import { type rule } from "#/rules/prefer-immutable-types"; -import { - type InvalidTestCaseSet, - type MessagesOf, - type OptionsOf, -} from "#/tests/helpers/util"; - -const tests: Array< - InvalidTestCaseSet, OptionsOf> -> = [ - { - code: "function foo(): ReadonlySet {}", - optionsSet: [[{ returnTypes: "Immutable" }]], - errors: [ - { - messageId: "returnType", - type: AST_NODE_TYPES.TSTypeAnnotation, - line: 1, - column: 15, - }, - ], - }, - { - code: "function foo(): ReadonlyMap {}", - optionsSet: [[{ returnTypes: "Immutable" }]], - errors: [ - { - messageId: "returnType", - type: AST_NODE_TYPES.TSTypeAnnotation, - line: 1, - column: 15, - }, - ], - }, - { - code: "function foo() { return { foo: 'bar' }; }", - optionsSet: [ - [{ returnTypes: "ReadonlyShallow" }], - [{ returnTypes: "ReadonlyDeep" }], - [{ returnTypes: "Immutable" }], - ], - errors: [ - { - messageId: "returnType", - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 10, - }, - ], - }, - { - code: dedent` - function foo(arg: number): { foo: string }; - function foo(arg: string): Readonly<{ foo: number }>; - function foo(arg: unknown): { foo: number }; - function foo(arg: unknown) {} - `, - optionsSet: [[{ returnTypes: "ReadonlyShallow" }]], - - errors: [ - { - messageId: "returnType", - type: AST_NODE_TYPES.TSTypeAnnotation, - line: 1, - column: 26, - suggestions: [ - { - messageId: "userDefined", - data: { - message: "Surround with Readonly.", - }, - output: dedent` - function foo(arg: number): Readonly<{ foo: string }>; - function foo(arg: string): Readonly<{ foo: number }>; - function foo(arg: unknown): { foo: number }; - function foo(arg: unknown) {} - `, - }, - ], - }, - { - messageId: "returnType", - type: AST_NODE_TYPES.TSTypeAnnotation, - line: 3, - column: 27, - suggestions: [ - { - messageId: "userDefined", - data: { - message: "Surround with Readonly.", - }, - output: dedent` - function foo(arg: number): { foo: string }; - function foo(arg: string): Readonly<{ foo: number }>; - function foo(arg: unknown): Readonly<{ foo: number }>; - function foo(arg: unknown) {} - `, - }, - ], - }, - ], - }, - { - code: dedent` - function foo(arg: number): { foo: string }; - function foo(arg: string): Readonly<{ foo: number }>; - function foo(arg: unknown): { foo: number }; - function foo(arg: unknown) {} - `, - optionsSet: [ - [{ returnTypes: "ReadonlyDeep" }], - [{ returnTypes: "Immutable" }], - ], - errors: [ - { - messageId: "returnType", - type: AST_NODE_TYPES.TSTypeAnnotation, - line: 1, - column: 26, - }, - { - messageId: "returnType", - type: AST_NODE_TYPES.TSTypeAnnotation, - line: 3, - column: 27, - }, - ], - }, - { - code: dedent` - function foo(arg: number): { foo: string }; - function foo(arg: string): Readonly<{ foo: number }>; - function foo(arg: number | string) {} - `, - optionsSet: [ - [ - { - returnTypes: { enforcement: "Immutable", ignoreInferredTypes: true }, - }, - ], - ], - errors: [ - { - messageId: "returnType", - type: AST_NODE_TYPES.TSTypeAnnotation, - line: 1, - column: 26, - }, - ], - }, - { - code: dedent` - interface Foo { - (arg: string): readonly string[]; - } - `, - optionsSet: [[{ returnTypes: "Immutable" }]], - errors: [ - { - messageId: "returnType", - type: AST_NODE_TYPES.TSTypeAnnotation, - line: 2, - column: 16, - }, - ], - }, - { - code: dedent` - interface Foo { - new (arg: string): readonly string[]; - } - `, - optionsSet: [[{ returnTypes: "Immutable" }]], - errors: [ - { - messageId: "returnType", - type: AST_NODE_TYPES.TSTypeAnnotation, - line: 2, - column: 20, - }, - ], - }, -]; - -export default tests; diff --git a/tests/rules/prefer-immutable-types/ts/return-types/valid.ts b/tests/rules/prefer-immutable-types/ts/return-types/valid.ts deleted file mode 100644 index 1d64e9d5e..000000000 --- a/tests/rules/prefer-immutable-types/ts/return-types/valid.ts +++ /dev/null @@ -1,219 +0,0 @@ -import dedent from "dedent"; - -import { type rule } from "#/rules/prefer-immutable-types"; -import { type OptionsOf, type ValidTestCaseSet } from "#/tests/helpers/util"; - -const tests: Array>> = [ - { - code: "function foo(): boolean {}", - optionsSet: [ - [{ returnTypes: "ReadonlyShallow" }], - [{ returnTypes: "ReadonlyDeep" }], - [{ returnTypes: "Immutable" }], - ], - }, - { - code: "function foo(): true {}", - optionsSet: [ - [{ returnTypes: "ReadonlyShallow" }], - [{ returnTypes: "ReadonlyDeep" }], - [{ returnTypes: "Immutable" }], - ], - }, - { - code: "function foo(): string {}", - optionsSet: [ - [{ returnTypes: "ReadonlyShallow" }], - [{ returnTypes: "ReadonlyDeep" }], - [{ returnTypes: "Immutable" }], - ], - }, - { - code: "function foo(): 'bar' {}", - optionsSet: [ - [{ returnTypes: "ReadonlyShallow" }], - [{ returnTypes: "ReadonlyDeep" }], - [{ returnTypes: "Immutable" }], - ], - }, - { - code: "function foo(): 'undefined' {}", - optionsSet: [ - [{ returnTypes: "ReadonlyShallow" }], - [{ returnTypes: "ReadonlyDeep" }], - [{ returnTypes: "Immutable" }], - ], - }, - { - code: "function foo(): readonly string[] {}", - optionsSet: [ - [{ returnTypes: "ReadonlyShallow" }], - [{ returnTypes: "ReadonlyDeep" }], - [{ returnTypes: "Immutable" }], - ], - settingsSet: [ - { - immutability: { - overrides: [ - { - type: { from: "lib", name: "ReadonlyArray" }, - to: "Immutable", - }, - ], - }, - }, - ], - }, - { - code: "function foo(): ReadonlyArray {}", - optionsSet: [ - [{ returnTypes: "ReadonlyShallow" }], - [{ returnTypes: "ReadonlyDeep" }], - ], - }, - { - code: "function foo(): readonly [string, number] {}", - optionsSet: [ - [{ returnTypes: "ReadonlyShallow" }], - [{ returnTypes: "ReadonlyDeep" }], - ], - }, - { - code: "function foo(): Readonly<[string, number]> {}", - optionsSet: [ - [{ returnTypes: "ReadonlyShallow" }], - [{ returnTypes: "ReadonlyDeep" }], - ], - }, - { - code: "function foo(): { readonly foo: string } {}", - optionsSet: [ - [{ returnTypes: "ReadonlyShallow" }], - [{ returnTypes: "ReadonlyDeep" }], - [{ returnTypes: "Immutable" }], - ], - }, - { - code: "function foo(): { readonly foo: { readonly bar: number } } {}", - optionsSet: [ - [{ returnTypes: "ReadonlyShallow" }], - [{ returnTypes: "ReadonlyDeep" }], - [{ returnTypes: "Immutable" }], - ], - }, - { - code: "function foo(): { foo(): void } {}", - optionsSet: [ - [{ returnTypes: "ReadonlyShallow" }], - [{ returnTypes: "ReadonlyDeep" }], - ], - }, - { - code: "function foo(): { foo: () => void } {}", - optionsSet: [ - [{ returnTypes: "ReadonlyShallow" }], - [{ returnTypes: "ReadonlyDeep" }], - ], - }, - { - code: "function foo(): ReadonlySet {}", - optionsSet: [ - [{ returnTypes: "ReadonlyShallow" }], - [{ returnTypes: "ReadonlyDeep" }], - ], - }, - { - code: "function foo(): ReadonlyMap {}", - optionsSet: [ - [{ returnTypes: "ReadonlyShallow" }], - [{ returnTypes: "ReadonlyDeep" }], - ], - }, - { - code: "function foo(): Readonly> {}", - optionsSet: [ - [{ returnTypes: "ReadonlyShallow" }], - [{ returnTypes: "ReadonlyDeep" }], - [{ returnTypes: "Immutable" }], - ], - }, - { - code: "function foo(): Readonly> {}", - optionsSet: [ - [{ returnTypes: "ReadonlyShallow" }], - [{ returnTypes: "ReadonlyDeep" }], - [{ returnTypes: "Immutable" }], - ], - }, - { - code: dedent` - interface Foo { - (): readonly string[]; - } - `, - optionsSet: [ - [{ returnTypes: "ReadonlyShallow" }], - [{ returnTypes: "ReadonlyDeep" }], - ], - }, - { - code: dedent` - interface Foo { - new (): readonly string[]; - } - `, - optionsSet: [ - [{ returnTypes: "ReadonlyShallow" }], - [{ returnTypes: "ReadonlyDeep" }], - ], - }, - { - code: "function foo() { return { foo: 'bar' }; }", - optionsSet: [ - [ - { - returnTypes: { - enforcement: "ReadonlyShallow", - ignoreInferredTypes: true, - }, - }, - ], - [ - { - returnTypes: { - enforcement: "ReadonlyDeep", - ignoreInferredTypes: true, - }, - }, - ], - [ - { - returnTypes: { enforcement: "Immutable", ignoreInferredTypes: true }, - }, - ], - ], - }, - // Ignore Name Prefix. - { - code: dedent` - function mutableFoo(): string[] {} - `, - optionsSet: [[{ ignoreNamePattern: "^mutable" }]], - }, - // Ignore Name Suffix. - { - code: dedent` - function fooMutable(): string[] {} - `, - optionsSet: [[{ ignoreNamePattern: "Mutable$" }]], - }, - // Ignore Type. - { - code: dedent` - function foo(): Readonly {} - `, - optionsSet: [[{ ignoreTypePattern: "^Readonly<.+>$" }]], - }, -]; - -export default tests; diff --git a/tests/rules/prefer-immutable-types/ts/variables/index.test.ts b/tests/rules/prefer-immutable-types/ts/variables/index.test.ts deleted file mode 100644 index d2429ecf5..000000000 --- a/tests/rules/prefer-immutable-types/ts/variables/index.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { name, rule } from "#/rules/prefer-immutable-types"; -import { testRule } from "#/tests/helpers/testers"; - -import invalid from "./invalid"; -import valid from "./valid"; - -const tests = { - valid, - invalid, -}; - -const tester = testRule(name, rule); - -tester.typescript(tests); diff --git a/tests/rules/prefer-immutable-types/ts/variables/invalid.ts b/tests/rules/prefer-immutable-types/ts/variables/invalid.ts deleted file mode 100644 index 44ef78f82..000000000 --- a/tests/rules/prefer-immutable-types/ts/variables/invalid.ts +++ /dev/null @@ -1,284 +0,0 @@ -import { AST_NODE_TYPES } from "@typescript-eslint/utils"; -import dedent from "dedent"; - -import { type rule } from "#/rules/prefer-immutable-types"; -import { - type InvalidTestCaseSet, - type MessagesOf, - type OptionsOf, -} from "#/tests/helpers/util"; - -const tests: Array< - InvalidTestCaseSet, OptionsOf> -> = [ - { - code: "const foo: ReadonlySet = {} as any", - optionsSet: [[{ variables: "Immutable" }]], - errors: [ - { - messageId: "variable", - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 7, - }, - ], - }, - { - code: "const foo: ReadonlyMap = {} as any", - optionsSet: [[{ variables: "Immutable" }]], - errors: [ - { - messageId: "variable", - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 7, - }, - ], - }, - { - code: "const foo = { foo: 'bar' };", - optionsSet: [ - [{ variables: "ReadonlyShallow" }], - [{ variables: "ReadonlyDeep" }], - [{ variables: "Immutable" }], - ], - errors: [ - { - messageId: "variable", - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 7, - }, - ], - }, - { - code: dedent` - const foo: Readonly<{ foo: string }> = {} as any, - bar: { foo: number } = {} as any; - `, - optionsSet: [[{ variables: "ReadonlyShallow" }]], - errors: [ - { - messageId: "variable", - type: AST_NODE_TYPES.Identifier, - line: 2, - column: 7, - suggestions: [ - { - messageId: "userDefined", - data: { - message: "Surround with Readonly.", - }, - output: dedent` - const foo: Readonly<{ foo: string }> = {} as any, - bar: Readonly<{ foo: number }> = {} as any; - `, - }, - ], - }, - ], - }, - { - code: dedent` - const foo: Readonly<{ foo: string }> = {} as any, - bar: { foo: number } = {} as any; - `, - optionsSet: [[{ variables: "ReadonlyDeep" }], [{ variables: "Immutable" }]], - errors: [ - { - messageId: "variable", - type: AST_NODE_TYPES.Identifier, - line: 2, - column: 7, - }, - ], - }, - // Destructuring array. - { - code: dedent` - const [a, ...rest] = [1, 2]; - `, - optionsSet: [[{ variables: "Immutable" }]], - errors: [ - { - messageId: "variable", - type: AST_NODE_TYPES.RestElement, - line: 1, - column: 11, - }, - ], - }, - // Destructuring object. - { - code: dedent` - const { a, ...rest } = { a: 1, b: 2 }; - `, - optionsSet: [[{ variables: "Immutable" }]], - errors: [ - { - messageId: "variable", - type: AST_NODE_TYPES.RestElement, - line: 1, - column: 12, - }, - ], - }, - // Local. - { - code: dedent` - function foo() { - let foo: { - a: { foo: number }, - b: string[], - c: () => string[], - d: { [key: string]: string[] }, - [key: string]: any, - } - }; - `, - optionsSet: [[]], - errors: [ - { - messageId: "variable", - type: AST_NODE_TYPES.Identifier, - line: 2, - column: 7, - }, - { - messageId: "returnType", - type: AST_NODE_TYPES.TSTypeAnnotation, - line: 5, - column: 11, - }, - ], - }, - // Class Property Signatures. - { - code: dedent` - class Klass { - foo: number; - private bar: number; - static baz: number; - private static qux: number; - } - `, - optionsSet: [[]], - errors: [ - { - messageId: "propertyModifier", - type: AST_NODE_TYPES.PropertyDefinition, - line: 2, - column: 3, - suggestions: [ - { - messageId: "propertyModifierSuggestion", - output: dedent` - class Klass { - readonly foo: number; - private bar: number; - static baz: number; - private static qux: number; - } - `, - }, - ], - }, - { - messageId: "propertyModifier", - type: AST_NODE_TYPES.PropertyDefinition, - line: 3, - column: 3, - suggestions: [ - { - messageId: "propertyModifierSuggestion", - output: dedent` - class Klass { - foo: number; - private readonly bar: number; - static baz: number; - private static qux: number; - } - `, - }, - ], - }, - { - messageId: "propertyModifier", - type: AST_NODE_TYPES.PropertyDefinition, - line: 4, - column: 3, - suggestions: [ - { - messageId: "propertyModifierSuggestion", - output: dedent` - class Klass { - foo: number; - private bar: number; - static readonly baz: number; - private static qux: number; - } - `, - }, - ], - }, - { - messageId: "propertyModifier", - type: AST_NODE_TYPES.PropertyDefinition, - line: 5, - column: 3, - suggestions: [ - { - messageId: "propertyModifierSuggestion", - output: dedent` - class Klass { - foo: number; - private bar: number; - static baz: number; - private static readonly qux: number; - } - `, - }, - ], - }, - ], - }, - { - code: dedent` - class Klass { - readonly foo: { foo: number }; - private readonly bar: { foo: number }; - static readonly baz: { foo: number }; - private static readonly qux: { foo: number }; - } - `, - optionsSet: [[]], - errors: [ - { - messageId: "propertyImmutability", - type: AST_NODE_TYPES.PropertyDefinition, - line: 2, - column: 3, - }, - { - messageId: "propertyImmutability", - type: AST_NODE_TYPES.PropertyDefinition, - line: 3, - column: 3, - }, - { - messageId: "propertyImmutability", - type: AST_NODE_TYPES.PropertyDefinition, - line: 4, - column: 3, - }, - { - messageId: "propertyImmutability", - type: AST_NODE_TYPES.PropertyDefinition, - line: 5, - column: 3, - }, - ], - }, -]; - -export default tests; diff --git a/tests/rules/prefer-immutable-types/ts/variables/valid.ts b/tests/rules/prefer-immutable-types/ts/variables/valid.ts deleted file mode 100644 index 811e58f88..000000000 --- a/tests/rules/prefer-immutable-types/ts/variables/valid.ts +++ /dev/null @@ -1,277 +0,0 @@ -import dedent from "dedent"; - -import { type rule } from "#/rules/prefer-immutable-types"; -import { type OptionsOf, type ValidTestCaseSet } from "#/tests/helpers/util"; - -const tests: Array>> = [ - { - code: "const foo: boolean = {} as any", - optionsSet: [ - [{ variables: "ReadonlyShallow" }], - [{ variables: "ReadonlyDeep" }], - [{ variables: "Immutable" }], - ], - }, - { - code: "const foo: true = {} as any", - optionsSet: [ - [{ variables: "ReadonlyShallow" }], - [{ variables: "ReadonlyDeep" }], - [{ variables: "Immutable" }], - ], - }, - { - code: "const foo: string = {} as any", - optionsSet: [ - [{ variables: "ReadonlyShallow" }], - [{ variables: "ReadonlyDeep" }], - [{ variables: "Immutable" }], - ], - }, - { - code: "const foo: 'bar' = {} as any", - optionsSet: [ - [{ variables: "ReadonlyShallow" }], - [{ variables: "ReadonlyDeep" }], - [{ variables: "Immutable" }], - ], - }, - { - code: "const foo: 'undefined' = {} as any", - optionsSet: [ - [{ variables: "ReadonlyShallow" }], - [{ variables: "ReadonlyDeep" }], - [{ variables: "Immutable" }], - ], - }, - { - code: "const foo: readonly string[] = {} as any", - optionsSet: [ - [{ variables: "ReadonlyShallow" }], - [{ variables: "ReadonlyDeep" }], - [{ variables: "Immutable" }], - ], - settingsSet: [ - { - immutability: { - overrides: [ - { - type: { from: "lib", name: "ReadonlyArray" }, - to: "Immutable", - }, - ], - }, - }, - ], - }, - { - code: "const foo: ReadonlyArray = {} as any", - optionsSet: [ - [{ variables: "ReadonlyShallow" }], - [{ variables: "ReadonlyDeep" }], - ], - }, - { - code: "const foo: readonly [string, number] = {} as any", - optionsSet: [ - [{ variables: "ReadonlyShallow" }], - [{ variables: "ReadonlyDeep" }], - ], - }, - { - code: "const foo: Readonly<[string, number]> = {} as any", - optionsSet: [ - [{ variables: "ReadonlyShallow" }], - [{ variables: "ReadonlyDeep" }], - ], - }, - { - code: "const foo: { readonly foo: string } = {} as any", - optionsSet: [ - [{ variables: "ReadonlyShallow" }], - [{ variables: "ReadonlyDeep" }], - [{ variables: "Immutable" }], - ], - }, - { - code: "const foo: { readonly foo: { readonly bar: number } } = {} as any", - optionsSet: [ - [{ variables: "ReadonlyShallow" }], - [{ variables: "ReadonlyDeep" }], - [{ variables: "Immutable" }], - ], - }, - { - code: "const foo: { foo(): void } = {} as any", - optionsSet: [ - [{ variables: "ReadonlyShallow" }], - [{ variables: "ReadonlyDeep" }], - ], - }, - { - code: "const foo: { foo: () => void } = {} as any", - optionsSet: [ - [{ variables: "ReadonlyShallow" }], - [{ variables: "ReadonlyDeep" }], - ], - }, - { - code: "const foo: ReadonlySet = {} as any", - optionsSet: [ - [{ variables: "ReadonlyShallow" }], - [{ variables: "ReadonlyDeep" }], - ], - }, - { - code: "const foo: ReadonlyMap = {} as any", - optionsSet: [ - [{ variables: "ReadonlyShallow" }], - [{ variables: "ReadonlyDeep" }], - ], - }, - { - code: "const foo: Readonly> = {} as any", - optionsSet: [ - [{ variables: "ReadonlyShallow" }], - [{ variables: "ReadonlyDeep" }], - [{ variables: "Immutable" }], - ], - }, - { - code: "const foo: Readonly> = {} as any", - optionsSet: [ - [{ variables: "ReadonlyShallow" }], - [{ variables: "ReadonlyDeep" }], - [{ variables: "Immutable" }], - ], - }, - { - code: "const foo = { foo: 'bar' };", - optionsSet: [ - [ - { - variables: { - enforcement: "ReadonlyShallow", - ignoreInferredTypes: true, - }, - }, - ], - [ - { - variables: { - enforcement: "ReadonlyDeep", - ignoreInferredTypes: true, - }, - }, - ], - [ - { - variables: { enforcement: "Immutable", ignoreInferredTypes: true }, - }, - ], - ], - }, - // Destructuring array. - { - code: dedent` - const [a, b] = [1, 2]; - `, - optionsSet: [[{ variables: "Immutable" }]], - }, - // Destructuring object. - { - code: dedent` - const { a, b } = { a: 1, b: 2 }; - `, - optionsSet: [[{ variables: "Immutable" }]], - }, - { - code: dedent` - const foo: Readonly> = {} as any; - - interface Foo

{ - (param: P): string; - baz: string; - } - `, - optionsSet: [[{ variables: "ReadonlyShallow" }]], - }, - // Ignore Classes. - { - code: dedent` - class Klass { - foo: number; - private bar: number; - static baz: number; - private static qux: number; - } - `, - optionsSet: [[{ ignoreClasses: true }]], - }, - // Allow Local. - { - code: dedent` - function foo() { - let foo: { - a: { foo: number }, - b: string[], - c: { [key: string]: string[] }, - [key: string]: any, - } - }; - `, - optionsSet: [[{ variables: { ignoreInFunctions: true } }]], - }, - // Ignore Name Prefix. - { - code: dedent` - let mutableFoo: string[] = []; - `, - optionsSet: [[{ ignoreNamePattern: "^mutable" }]], - }, - { - code: dedent` - class Klass { - mutableA: number; - private mutableB: number; - #mutableC: number; - } - `, - optionsSet: [[{ ignoreNamePattern: "^mutable" }]], - }, - // Ignore Name Suffix. - { - code: dedent` - let fooMutable: string[] = []; - `, - optionsSet: [[{ ignoreNamePattern: "Mutable$" }]], - }, - { - code: dedent` - class Klass { - AMutable: number; - private BMutable: number; - #CMutable: number; - } - `, - optionsSet: [[{ ignoreNamePattern: "Mutable$" }]], - }, - // Ignore Type. - { - code: dedent` - let fooMutable: Readonly = []; - `, - optionsSet: [[{ ignoreTypePattern: "^Readonly<.+>$" }]], - }, - // Ignore Type (multiline). - { - code: dedent` - let fooMutable: Readonly< - string[] - > = []; - `, - optionsSet: [[{ ignoreTypePattern: "^Readonly<.+>$" }]], - }, -]; - -export default tests; diff --git a/tests/rules/prefer-immutable-types/variables.test.ts b/tests/rules/prefer-immutable-types/variables.test.ts new file mode 100644 index 000000000..363fb00d5 --- /dev/null +++ b/tests/rules/prefer-immutable-types/variables.test.ts @@ -0,0 +1,330 @@ +import dedent from "dedent"; +import { createRuleTester } from "eslint-vitest-rule-tester"; +import { describe, expect, it } from "vitest"; + +import { name, rule } from "#/rules/prefer-immutable-types"; + +import { typescriptConfig } from "../../utils/configs"; + +describe(name, () => { + describe("typescript", () => { + const { valid, invalid } = createRuleTester({ + name, + rule, + configs: typescriptConfig, + }); + + it("reports non-immutable set variables", () => { + const invalidResult = invalid({ + code: "const foo: ReadonlySet = {} as any;", + errors: ["variable"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports non-immutable map variables", () => { + const invalidResult = invalid({ + code: "const foo: ReadonlyMap = {} as any;", + errors: ["variable"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports mutable records variables", () => { + const invalidResult = invalid({ + code: "const foo: { foo: string } = {} as any;", + errors: ["variable"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports mutable records variables and suggests a fix for ReadonlyShallow", () => { + const invalidResult = invalid({ + code: "const foo: { foo: string } = {} as any;", + options: [{ variables: "ReadonlyShallow" }], + errors: ["variable"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + for (const message of invalidResult.messages) { + expect(message.suggestions).toHaveLength(1); + } + }); + + it("doesn't report type assertion variables", () => { + valid({ + code: "function foo(arg0: { foo: string | number }, arg1: { foo: string | number }): arg0 is { foo: number } {}", + options: [ + { + parameters: false, + variables: "ReadonlyShallow", + }, + ], + }); + }); + + it("suggest multiple fixes for collections for ReadonlyShallow", () => { + const invalidResult = invalid({ + code: dedent` + const foo: Array = {} as any; + const foo: string[] = {} as any; + const foo: Set = {} as any; + const foo: Map = {} as any; + const foo: ReadonlyArray = {} as any; + const foo: readonly string[] = {} as any; + const foo: ReadonlySet = {} as any; + const foo: ReadonlyMap = {} as any; + `, + options: [{ variables: "ReadonlyShallow" }], + errors: ["variable", "variable", "variable", "variable"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + for (const message of invalidResult.messages) { + expect(message.suggestions).toHaveLength(2); + } + }); + + it("reports mutable class parameter properties", () => { + const invalidResult = invalid({ + code: dedent` + class Klass { + readonly foo: { foo: number }; + private readonly bar: { foo: number }; + static readonly baz: { foo: number }; + private static readonly qux: { foo: number }; + } + `, + errors: [ + "propertyImmutability", + "propertyImmutability", + "propertyImmutability", + "propertyImmutability", + ], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("allows for user suggestions", () => { + const invalidResult = invalid({ + code: "const foo: { foo: string } = {} as any;", + options: [ + { + variables: "ReadonlyDeep", + suggestions: { + ReadonlyDeep: [ + [ + { + pattern: "^(?!ReadonlyDeep)(?:Readonly<(.+)>|(.+))$", + replace: "ReadonlyDeep<$1$2>", + }, + ], + ], + }, + }, + ], + errors: ["variable"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + for (const message of invalidResult.messages) { + expect(message.suggestions).toHaveLength(1); + } + }); + + it("allows for user fixes", () => { + const invalidResult = invalid({ + code: dedent` + const foo: Array = {} as any; + const foo: string[] = {} as any; + const foo: Set = {} as any; + const foo: Map = {} as any; + const foo: ReadonlyArray = {} as any; + const foo: readonly string[] = {} as any; + const foo: ReadonlySet = {} as any; + const foo: ReadonlyMap = {} as any; + `, + options: [ + { + variables: "Immutable", + fixer: { + Immutable: [ + { + pattern: "^(?:Readonly)?(Set|Map)<(.+)>$", + replace: "Readonly>", + }, + ], + }, + }, + ], + errors: [ + "variable", + "variable", + "variable", + "variable", + "variable", + "variable", + "variable", + "variable", + ], + verifyAfterFix: false, // "fix" doesn't fix arrays so they will still report. + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it.each([ + [[{ variables: "ReadonlyShallow" }]], + [[{ variables: "ReadonlyDeep" }]], + [[{ variables: "Immutable" }]], + ])("doesn't reports valid variables", (options) => { + valid({ + code: "const foo: boolean = {} as any;", + options, + }); + valid({ + code: "const foo: true = {} as any;", + options, + }); + valid({ + code: "const foo: string = {} as any;", + options, + }); + valid({ + code: "const foo: 'bar' = {} as any;", + options, + }); + valid({ + code: "const foo: undefined = {} as any;", + options, + }); + valid({ + code: "const foo: readonly string[] = {} as any;", + options, + settings: { + immutability: { + overrides: [ + { + type: { from: "lib", name: "ReadonlyArray" }, + to: "Immutable", + }, + ], + }, + }, + }); + valid({ + code: "const foo: { readonly foo: string } = {} as any;", + options, + }); + valid({ + code: "const foo: { readonly foo: { readonly bar: number } } = {} as any;", + options, + }); + valid({ + code: "const foo: Readonly> = {} as any;", + options, + }); + valid({ + code: "const foo: Readonly> = {} as any;", + options, + }); + valid({ + code: "function foo(arg: { foo: string | number }): arg is { foo: number } {}", + options, + }); + valid({ + code: "const [a, b] = [1, 2];", + options, + }); + valid({ + code: "const { a, b } = { a: 1, b: 2 };", + options, + }); + + if (options[0]!.variables !== "Immutable") { + valid({ + code: "const foo: { foo(): void } = {} as any;", + options, + }); + valid({ + code: "const foo: ReadonlyArray = {} as any;", + options, + }); + valid({ + code: "const foo: readonly [string, number] = {} as any;", + options, + }); + valid({ + code: "const foo: Readonly<[string, number]> = {} as any;", + options, + }); + valid({ + code: "const foo: { foo: () => void } = {} as any;", + options, + }); + valid({ + code: "const foo: ReadonlySet = {} as any;", + options, + }); + valid({ + code: "const foo: ReadonlyMap = {} as any;", + options, + }); + } + }); + + describe("options", () => { + describe("ignoreClasses", () => { + it("doesn't report class fields", () => { + valid({ + code: dedent` + class Klass { + foo: number; + private bar: number; + static baz: number; + private static qux: number; + } + `, + options: [{ ignoreClasses: true }], + }); + }); + }); + + describe("ignoreInFunctions", () => { + it("doesn't report variables in functions", () => { + valid({ + code: dedent` + function foo() { + let foo: { + a: { foo: number }, + b: string[], + c: { [key: string]: string[] }, + [key: string]: any, + } + }; + `, + options: [{ variables: { ignoreInFunctions: true } }], + }); + }); + }); + + describe("ignoreNamePattern", () => { + it("doesn't report ignored names", () => { + valid({ + code: "let mutableFoo: string[] = [];", + options: [{ ignoreNamePattern: "^mutable" }], + }); + }); + }); + + describe("ignoreTypePattern", () => { + it("doesn't report ignored types", () => { + valid({ + code: dedent` + let fooMutable: Readonly< + string[] + > = []; + `, + options: [{ ignoreTypePattern: "^Readonly<.+>$" }], + }); + }); + }); + }); + }); +}); diff --git a/tests/rules/prefer-property-signatures.test.ts b/tests/rules/prefer-property-signatures.test.ts new file mode 100644 index 000000000..129bc52ed --- /dev/null +++ b/tests/rules/prefer-property-signatures.test.ts @@ -0,0 +1,122 @@ +import dedent from "dedent"; +import { createRuleTester } from "eslint-vitest-rule-tester"; +import { describe, expect, it } from "vitest"; + +import { name, rule } from "#/rules/prefer-property-signatures"; + +import { typescriptConfig } from "../utils/configs"; + +describe(name, () => { + describe("typescript", () => { + const { valid, invalid } = createRuleTester({ + name, + rule, + configs: typescriptConfig, + }); + + it("doesn't report property signatures in interfaces", () => { + valid(dedent` + interface Foo { + bar: (a: number, b: string) => number; + } + `); + }); + + it("doesn't report property signatures in type literals", () => { + valid(dedent` + type Foo = { + bar: (a: number, b: string) => number; + } + `); + }); + + it("reports method signatures in interfaces", () => { + const invalidResult = invalid({ + code: dedent` + interface Foo { + bar(a: number, b: string): number; + } + `, + errors: ["generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports method signatures in type literals", () => { + const invalidResult = invalid({ + code: dedent` + type Foo = { + bar(a: number, b: string): number; + } + `, + errors: ["generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + describe("options", () => { + describe("ignoreIfReadonlyWrapped", () => { + it("doesn't report method signatures wrapped in Readonly in interfaces", () => { + valid({ + code: dedent` + interface Foo extends Readonly<{ + methodSignature(): void + }>{} + `, + options: [{ ignoreIfReadonlyWrapped: true }], + }); + }); + + it("doesn't report method signatures wrapped in Readonly in type literals", () => { + valid({ + code: dedent` + type Foo = Readonly<{ + methodSignature(): void + }> + `, + options: [{ ignoreIfReadonlyWrapped: true }], + }); + }); + + it("doesn't report method signatures wrapped in Readonly that are intersepted", () => { + valid({ + code: dedent` + type Foo = Bar & Readonly + `, + options: [{ ignoreIfReadonlyWrapped: true }], + }); + }); + + it("doesn't report method signatures wrapped in Readonly that are intersepted and nested", () => { + valid({ + code: dedent` + type Foo = Bar & Readonly + }> + `, + options: [{ ignoreIfReadonlyWrapped: true }], + }); + }); + + it("doesn't report method signatures wrapped in Readonly that are intersepted and deeply nested", () => { + valid({ + code: dedent` + interface Foo extends Bar, Readonly + } + }>{} + `, + options: [{ ignoreIfReadonlyWrapped: true }], + }); + }); + }); + }); + }); +}); diff --git a/tests/rules/prefer-property-signatures/ts/index.test.ts b/tests/rules/prefer-property-signatures/ts/index.test.ts deleted file mode 100644 index dfc32f129..000000000 --- a/tests/rules/prefer-property-signatures/ts/index.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { name, rule } from "#/rules/prefer-property-signatures"; -import { testRule } from "#/tests/helpers/testers"; - -import invalid from "./invalid"; -import valid from "./valid"; - -const tests = { - valid, - invalid, -}; - -const tester = testRule(name, rule); - -tester.typescript(tests); diff --git a/tests/rules/prefer-property-signatures/ts/invalid.ts b/tests/rules/prefer-property-signatures/ts/invalid.ts deleted file mode 100644 index 7c5cc46cd..000000000 --- a/tests/rules/prefer-property-signatures/ts/invalid.ts +++ /dev/null @@ -1,122 +0,0 @@ -import { AST_NODE_TYPES } from "@typescript-eslint/utils"; -import dedent from "dedent"; - -import { type rule } from "#/rules/prefer-property-signatures"; -import { - type InvalidTestCaseSet, - type MessagesOf, - type OptionsOf, -} from "#/tests/helpers/util"; - -const tests: Array< - InvalidTestCaseSet, OptionsOf> -> = [ - { - code: dedent` - interface Foo { - bar(a: number, b: string): number; - } - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.TSMethodSignature, - line: 2, - column: 3, - }, - ], - }, - { - code: dedent` - type Foo2 = { - bar(a: number, b: string): number - } - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.TSMethodSignature, - line: 2, - column: 3, - }, - ], - }, - { - code: dedent` - type Foo = Bar & Readonly & { - methodSignature(): void - } - `, - optionsSet: [[{ ignoreIfReadonlyWrapped: true }]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.TSMethodSignature, - line: 2, - column: 3, - }, - ], - }, - { - code: dedent` - type Foo = Bar & Readonly - `, - optionsSet: [[{ ignoreIfReadonlyWrapped: true }]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.TSMethodSignature, - line: 3, - column: 5, - }, - ], - }, - { - code: dedent` - interface Foo extends Bar, Readonly - }>{} - `, - optionsSet: [[{ ignoreIfReadonlyWrapped: true }]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.TSMethodSignature, - line: 4, - column: 7, - }, - ], - }, - { - code: dedent` - interface Foo extends Bar, Readonly - } - }>{} - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.TSMethodSignature, - line: 4, - column: 7, - }, - ], - }, -]; - -export default tests; diff --git a/tests/rules/prefer-property-signatures/ts/valid.ts b/tests/rules/prefer-property-signatures/ts/valid.ts deleted file mode 100644 index c25235b57..000000000 --- a/tests/rules/prefer-property-signatures/ts/valid.ts +++ /dev/null @@ -1,79 +0,0 @@ -import dedent from "dedent"; - -import { type rule } from "#/rules/prefer-property-signatures"; -import { type OptionsOf, type ValidTestCaseSet } from "#/tests/helpers/util"; - -const tests: Array>> = [ - { - code: dedent` - interface Foo { - bar: (a: number, b: string) => number; - } - `, - optionsSet: [[]], - }, - { - code: dedent` - type Foo2 = { - bar: (a: number, b: string) => number - } - `, - optionsSet: [[]], - }, - { - code: dedent` - interface Foo extends Readonly<{ - methodSignature(): void - }>{} - `, - optionsSet: [[{ ignoreIfReadonlyWrapped: true }]], - }, - { - code: dedent` - interface Foo extends Bar, Readonly{} - `, - optionsSet: [[{ ignoreIfReadonlyWrapped: true }]], - }, - { - code: dedent` - type Foo = Readonly<{ - methodSignature(): void - }> - `, - optionsSet: [[{ ignoreIfReadonlyWrapped: true }]], - }, - { - code: dedent` - type Foo = Bar & Readonly - `, - optionsSet: [[{ ignoreIfReadonlyWrapped: true }]], - }, - { - code: dedent` - type Foo = Bar & Readonly - }> - `, - optionsSet: [[{ ignoreIfReadonlyWrapped: true }]], - }, - { - code: dedent` - interface Foo extends Bar, Readonly - } - }>{} - `, - optionsSet: [[{ ignoreIfReadonlyWrapped: true }]], - }, -]; - -export default tests; diff --git a/tests/rules/prefer-readonly-type.test.ts b/tests/rules/prefer-readonly-type.test.ts new file mode 100644 index 000000000..577701e18 --- /dev/null +++ b/tests/rules/prefer-readonly-type.test.ts @@ -0,0 +1,620 @@ +import dedent from "dedent"; +import { createRuleTester } from "eslint-vitest-rule-tester"; +import { describe, expect, it } from "vitest"; + +import { name, rule } from "#/rules/prefer-readonly-type"; + +import { typescriptConfig } from "../utils/configs"; + +describe(name, () => { + describe("typescript", () => { + const { valid, invalid } = createRuleTester({ + name, + rule, + configs: typescriptConfig, + }); + + it("reports mutable array (non-generic)", () => { + const invalidResult = invalid({ + code: dedent` + function foo(a: number[], b: Promise) { + console.log(a, b); + } + `, + errors: ["array", "array"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports mutable arrays (generic)", () => { + const invalidResult = invalid({ + code: dedent` + function foo(a: Array, b: Promise>) { + console.log(a, b); + } + `, + errors: ["type", "type"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports mutable sets", () => { + const invalidResult = invalid({ + code: dedent` + function foo(a: Set, b: Promise>) { + console.log(a, b); + } + `, + errors: ["type", "type"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports mutable maps", () => { + const invalidResult = invalid({ + code: dedent` + function foo(a: Map, b: Promise>) { + console.log(a, b); + } + `, + errors: ["type", "type"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports issue inside interfaces", () => { + const invalidResult = invalid({ + code: dedent` + interface Foo { + readonly bar: Array; + readonly baz: Promise>; + } + `, + errors: ["type", "type"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports issues with index signatures", () => { + const invalidResult = invalid({ + code: dedent` + interface Foo { + readonly [key: string]: { + readonly a: Array; + readonly b: Promise>; + }; + } + interface Bar { + [key: string]: string + } + interface Baz { + [key: string]: { prop: string } + } + `, + errors: ["type", "type", "property", "property", "property"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports non-readonly class properties", () => { + const invalidResult = invalid({ + code: dedent` + class Klass { + foo: number; + private bar: number; + static baz: number; + private static qux: number; + } + `, + errors: ["property", "property", "property", "property"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports non-readonly class parameter properties", () => { + const invalidResult = invalid({ + code: dedent` + class Klass { + constructor ( + public publicProp: string, + protected protectedProp: string, + private privateProp: string, + ) { } + } + `, + errors: ["property", "property", "property"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports literals without readonly modifiers", () => { + const invalidResult = invalid({ + code: dedent` + let foo: { + a: number, + b: ReadonlyArray, + c: () => string, + d: { readonly [key: string]: string }, + [key: string]: string, + readonly e: { + a: number, + b: ReadonlyArray, + c: () => string, + d: { readonly [key: string]: string }, + [key: string]: string, + } + }; + `, + errors: [ + "property", + "property", + "property", + "property", + "property", + "property", + "property", + "property", + "property", + "property", + ], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports issues with mapped types", () => { + const invalidResult = invalid({ + code: dedent` + const func = (x: { [key in string]: number }) => {} + `, + errors: ["property"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("doesn't report explicit readonly parameter types", () => { + valid(dedent` + function foo(...a: ReadonlyArray) { + console.log(a); + } + `); + + valid(dedent` + const foo = (...a: readonly number[]) => { + console.log(a); + } + `); + + valid(dedent` + const foo = (tuple: readonly [number, string, readonly [number, string]]) => { + console.log(a); + } + `); + }); + + it("doesn't report explicit readonly return types", () => { + valid(dedent` + function foo(): ReadonlyArray { + return [1, 2, 3]; + } + `); + + valid(dedent` + const foo = (): readonly number[] => { + return [1, 2, 3]; + } + `); + }); + + it("doesn't report implicit readonly arrays", () => { + valid({ + code: dedent` + const foo = [1, 2, 3]; + function bar(param = [1, 2, 3]) {} + `, + }); + }); + + it("doesn't report readonly local types", () => { + valid(dedent` + function foo() { + type Foo = ReadonlyArray; + } + `); + }); + + it("doesn't report readonly variable declarations", () => { + valid(dedent` + const foo: ReadonlyArray = []; + `); + }); + + it("doesn't report interfaces with readonly modifiers", () => { + valid(dedent` + interface Foo { + readonly a: number, + readonly b: ReadonlyArray, + readonly c: () => string, + readonly d: { readonly [key: string]: string }, + readonly [key: string]: string, + } + `); + + valid(dedent` + interface Foo { + readonly a: number, + readonly b: ReadonlyArray, + readonly c: () => string, + readonly d: { readonly [key: string]: string }, + readonly [key: string]: string, + readonly e: { + readonly a: number, + readonly b: ReadonlyArray, + readonly c: () => string, + readonly d: { readonly [key: string]: string }, + readonly [key: string]: string, + } + } + `); + }); + + it("doesn't report call signatures and method signatures", () => { + valid(dedent` + interface Foo { + (): void + foo(): void + } + `); + }); + + it("doesn't report literal with readonly index signature", () => { + valid(dedent` + let foo: { readonly [key: string]: number }; + `); + }); + + it("doesn't report type literals elements with a readonly modifer in an array", () => { + valid(dedent` + type foo = ReadonlyArray<{ readonly type: string, readonly code: string }>; + `); + }); + + it("doesn't report type literals with readonly on members", () => { + valid(dedent` + let foo: { + readonly a: number, + readonly b: ReadonlyArray, + readonly c: () => string, + readonly d: { readonly [key: string]: string } + readonly [key: string]: string + }; + `); + }); + + it("doesn't report class parameter properties", () => { + valid(dedent` + class Klass { + constructor ( + nonParameterProp: string, + readonly readonlyProp: string, + public readonly publicReadonlyProp: string, + protected readonly protectedReadonlyProp: string, + private readonly privateReadonlyProp: string, + ) { } + } + `); + }); + + it("doesn't report mapped types with readonly on members", () => { + valid(dedent` + const func = (x: { readonly [key in string]: number }) => {} + `); + }); + + describe("options", () => { + describe("allowMutableReturnType", () => { + it("doesn't report mutable return types", () => { + valid({ + code: dedent` + function foo(...numbers: ReadonlyArray): Array {} + function bar(...numbers: readonly number[]): number[] {} + `, + options: [{ allowMutableReturnType: true }], + }); + + valid({ + code: dedent` + const foo = (...numbers: ReadonlyArray): Array => {} + const bar = (...numbers: readonly number[]): number[] => {} + `, + options: [{ allowMutableReturnType: true }], + }); + + valid({ + code: dedent` + class Foo { + foo(...numbers: ReadonlyArray): Array { + } + } + class Bar { + foo(...numbers: readonly number[]): number[] { + } + } + `, + options: [{ allowMutableReturnType: true }], + }); + + valid({ + code: dedent` + function foo(...numbers: ReadonlyArray): Promise> {} + function foo(...numbers: ReadonlyArray): Promise {} + `, + options: [{ allowMutableReturnType: true }], + }); + + valid({ + code: dedent` + function foo(...numbers: ReadonlyArray): Promise>> {} + function foo(...numbers: ReadonlyArray): Promise> {} + `, + options: [{ allowMutableReturnType: true }], + }); + + valid({ + code: dedent` + function foo(...numbers: ReadonlyArray): { readonly a: Array } | { readonly b: string[] } {} + `, + options: [{ allowMutableReturnType: true }], + }); + + valid({ + code: dedent` + type Foo = { readonly x: T; }; + function foo(...numbers: ReadonlyArray): Promise>> {} + function foo(...numbers: ReadonlyArray): Promise> {} + `, + options: [{ allowMutableReturnType: true }], + }); + + valid({ + code: dedent` + function foo(...numbers: ReadonlyArray): { readonly a: Array } & { readonly b: string[] } {} + `, + options: [{ allowMutableReturnType: true }], + }); + + valid({ + code: dedent` + function foo(x: T): T extends Array ? string : number[] {} + `, + options: [{ allowMutableReturnType: true }], + }); + + valid({ + code: dedent` + function foo(bar: string): { baz: number } { + return 1 as any; + } + `, + options: [{ allowMutableReturnType: true }], + }); + }); + }); + + describe("checkImplicit", () => { + it("doesn't report implicit readonly array variables", () => { + valid({ + code: dedent` + const numbers = [1, 2, 3] as const; + `, + options: [{ checkImplicit: true }], + }); + }); + + it("doesn't report implicit readonly return types", () => { + valid({ + code: dedent` + function foo() { + return [1, 2, 3] as const; + } + `, + options: [{ checkImplicit: true }], + }); + + valid({ + code: dedent` + const foo = () => { + return [1, 2, 3] as const; + }; + `, + options: [{ checkImplicit: true }], + }); + }); + }); + + describe("ignoreClass", () => { + it("doesn't report classes", () => { + valid({ + code: dedent` + class Klass { + foo: number; + private bar: number; + static baz: number; + private static qux: number; + } + `, + options: [{ ignoreClass: true }], + }); + }); + + it("doesn't report classes - fieldsOnly", () => { + valid({ + code: dedent` + class Klass { + foo: number; + private bar: number; + static baz: number; + private static qux: number; + } + `, + options: [{ ignoreClass: "fieldsOnly" }], + }); + }); + + it("reports non-field issues in classes - fieldsOnly", () => { + const invalidResult = invalid({ + code: dedent` + class Klass { + foo: number; + private bar: number; + static baz: number; + private static qux: number; + + foo() { + let bar: { + foo: number; + }; + } + } + `, + options: [{ ignoreClass: "fieldsOnly" }], + errors: ["property"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + }); + + describe("ignoreInterface", () => { + it("doesn't report interfaces", () => { + valid({ + code: dedent` + interface Foo { + foo: number, + bar: ReadonlyArray, + baz: () => string, + qux: { [key: string]: string } + } + `, + options: [{ ignoreInterface: true }], + }); + }); + }); + + describe("ignorePattern", () => { + it("doesn't report matching identifiers", () => { + valid({ + code: dedent` + let mutableFoo: string[] = []; + `, + options: [{ ignorePattern: "^mutable" }], + }); + }); + }); + + describe("allowLocalMutation", () => { + it("doesn't report mutable local variables", () => { + valid({ + code: dedent` + function foo() { + let foo: { + a: number, + b: ReadonlyArray, + c: () => string, + d: { [key: string]: string }, + [key: string]: string, + readonly d: { + a: number, + b: ReadonlyArray, + c: () => string, + d: { [key: string]: string }, + [key: string]: string, + } + } + }; + `, + options: [{ allowLocalMutation: true }], + }); + }); + }); + + describe("ignoreCollections", () => { + it("doesn't report mutable arrays", () => { + valid({ + code: dedent` + type Foo = Array; + `, + options: [{ ignoreCollections: true }], + }); + + valid({ + code: dedent` + const foo: number[] = []; + `, + options: [{ ignoreCollections: true }], + }); + + valid({ + code: dedent` + const foo = []; + `, + options: [{ ignoreCollections: true, checkImplicit: true }], + }); + }); + + it("doesn't report mutable tuples", () => { + valid({ + code: dedent` + type Foo = [string, string]; + `, + options: [{ ignoreCollections: true }], + }); + + valid({ + code: dedent` + const foo: [string, string] = ['foo', 'bar']; + `, + options: [{ ignoreCollections: true }], + }); + + valid({ + code: dedent` + const foo = ['foo', 'bar']; + `, + options: [{ ignoreCollections: true, checkImplicit: true }], + }); + }); + + it("doesn't report mutable sets", () => { + valid({ + code: dedent` + type Foo = Set; + `, + options: [{ ignoreCollections: true }], + }); + + valid({ + code: dedent` + const foo: Set = new Set(); + `, + options: [{ ignoreCollections: true }], + }); + }); + + it("doesn't report mutable maps", () => { + valid({ + code: dedent` + type Foo = Map; + `, + options: [{ ignoreCollections: true }], + }); + + valid({ + code: dedent` + const foo: Map = new Map(); + `, + options: [{ ignoreCollections: true }], + }); + }); + }); + }); + }); +}); diff --git a/tests/rules/prefer-readonly-type/ts/index.test.ts b/tests/rules/prefer-readonly-type/ts/index.test.ts deleted file mode 100644 index 45256beaa..000000000 --- a/tests/rules/prefer-readonly-type/ts/index.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { name, rule } from "#/rules/prefer-readonly-type"; -import { testRule } from "#/tests/helpers/testers"; - -import invalid from "./invalid"; -import valid from "./valid"; - -const tests = { - valid, - invalid, -}; - -const tester = testRule(name, rule); - -tester.typescript(tests); diff --git a/tests/rules/prefer-readonly-type/ts/invalid.ts b/tests/rules/prefer-readonly-type/ts/invalid.ts deleted file mode 100644 index 5f0402980..000000000 --- a/tests/rules/prefer-readonly-type/ts/invalid.ts +++ /dev/null @@ -1,969 +0,0 @@ -import { AST_NODE_TYPES } from "@typescript-eslint/utils"; -import dedent from "dedent"; - -import { type rule } from "#/rules/prefer-readonly-type"; -import { - type InvalidTestCaseSet, - type MessagesOf, - type OptionsOf, -} from "#/tests/helpers/util"; - -const tests: Array< - InvalidTestCaseSet, OptionsOf> -> = [ - { - code: dedent` - function foo(...numbers: number[]) { - } - `, - optionsSet: [[]], - output: dedent` - function foo(...numbers: readonly number[]) { - } - `, - errors: [ - { - messageId: "array", - type: AST_NODE_TYPES.TSArrayType, - line: 1, - column: 26, - }, - ], - }, - { - code: dedent` - function foo(...numbers: Array) { - } - `, - optionsSet: [[]], - output: dedent` - function foo(...numbers: ReadonlyArray) { - } - `, - errors: [ - { - messageId: "type", - type: AST_NODE_TYPES.TSTypeReference, - line: 1, - column: 26, - }, - ], - }, - { - code: dedent` - function foo(numbers: Set) { - } - `, - optionsSet: [[]], - output: dedent` - function foo(numbers: ReadonlySet) { - } - `, - errors: [ - { - messageId: "type", - type: AST_NODE_TYPES.TSTypeReference, - line: 1, - column: 23, - }, - ], - }, - { - code: dedent` - function foo(numbers: Map) { - } - `, - optionsSet: [[]], - output: dedent` - function foo(numbers: ReadonlyMap) { - } - `, - errors: [ - { - messageId: "type", - type: AST_NODE_TYPES.TSTypeReference, - line: 1, - column: 23, - }, - ], - }, - // Should fail on Array type in interface. - { - code: dedent` - interface Foo { - readonly bar: Array - } - `, - optionsSet: [[]], - output: dedent` - interface Foo { - readonly bar: ReadonlyArray - } - `, - errors: [ - { - messageId: "type", - type: AST_NODE_TYPES.TSTypeReference, - line: 2, - column: 17, - }, - ], - }, - // Should fail on Array type in index interface. - { - code: dedent` - interface Foo { - readonly [key: string]: { - readonly groups: Array - } - } - `, - optionsSet: [[]], - output: dedent` - interface Foo { - readonly [key: string]: { - readonly groups: ReadonlyArray - } - } - `, - errors: [ - { - messageId: "type", - type: AST_NODE_TYPES.TSTypeReference, - line: 3, - column: 22, - }, - ], - }, - // Should fail on Array type as function return type and in local interface. - { - code: dedent` - function foo(): Array { - interface Foo { - readonly bar: Array - } - } - `, - optionsSet: [[]], - output: dedent` - function foo(): ReadonlyArray { - interface Foo { - readonly bar: ReadonlyArray - } - } - `, - errors: [ - { - messageId: "type", - type: AST_NODE_TYPES.TSTypeReference, - line: 1, - column: 17, - }, - { - messageId: "type", - type: AST_NODE_TYPES.TSTypeReference, - line: 3, - column: 19, - }, - ], - }, - // Should fail on Array type as function return type and in local interface. - { - code: dedent` - const foo = (): Array => { - interface Foo { - readonly bar: Array - } - } - `, - optionsSet: [[]], - output: dedent` - const foo = (): ReadonlyArray => { - interface Foo { - readonly bar: ReadonlyArray - } - } - `, - errors: [ - { - messageId: "type", - type: AST_NODE_TYPES.TSTypeReference, - line: 1, - column: 17, - }, - { - messageId: "type", - type: AST_NODE_TYPES.TSTypeReference, - line: 3, - column: 19, - }, - ], - }, - // Should fail on shorthand syntax Array type as return type. - { - code: dedent` - function foo(): number[] { - } - `, - optionsSet: [[]], - output: dedent` - function foo(): readonly number[] { - } - `, - errors: [ - { - messageId: "array", - type: AST_NODE_TYPES.TSArrayType, - line: 1, - column: 17, - }, - ], - }, - // Should fail on shorthand syntax Array type as return type. - { - code: `const foo = (): number[] => {}`, - optionsSet: [[]], - output: `const foo = (): readonly number[] => {}`, - errors: [ - { - messageId: "array", - type: AST_NODE_TYPES.TSArrayType, - line: 1, - column: 17, - }, - ], - }, - // Should fail inside function. - { - code: dedent` - const foo = function (): string { - let bar: Array; - }; - `, - optionsSet: [[]], - output: dedent` - const foo = function (): string { - let bar: ReadonlyArray; - }; - `, - errors: [ - { - messageId: "type", - type: AST_NODE_TYPES.TSTypeReference, - line: 2, - column: 12, - }, - ], - }, - // Tuples. - { - code: dedent` - function foo(tuple: [number, string]) { - } - `, - optionsSet: [[]], - output: dedent` - function foo(tuple: readonly [number, string]) { - } - `, - errors: [ - { - messageId: "tuple", - type: AST_NODE_TYPES.TSTupleType, - line: 1, - column: 21, - }, - ], - }, - { - code: dedent` - function foo(tuple: [number, string, [number, string]]) { - } - `, - optionsSet: [[]], - output: dedent` - function foo(tuple: readonly [number, string, readonly [number, string]]) { - } - `, - errors: [ - { - messageId: "tuple", - type: AST_NODE_TYPES.TSTupleType, - line: 1, - column: 21, - }, - { - messageId: "tuple", - type: AST_NODE_TYPES.TSTupleType, - line: 1, - column: 38, - }, - ], - }, - { - code: dedent` - function foo(tuple: readonly [number, string, [number, string]]) { - } - `, - optionsSet: [[]], - output: dedent` - function foo(tuple: readonly [number, string, readonly [number, string]]) { - } - `, - errors: [ - { - messageId: "tuple", - type: AST_NODE_TYPES.TSTupleType, - line: 1, - column: 47, - }, - ], - }, - { - code: dedent` - function foo(tuple: [number, string, readonly [number, string]]) { - } - `, - optionsSet: [[]], - output: dedent` - function foo(tuple: readonly [number, string, readonly [number, string]]) { - } - `, - errors: [ - { - messageId: "tuple", - type: AST_NODE_TYPES.TSTupleType, - line: 1, - column: 21, - }, - ], - }, - // Should fail on Array as type literal member as function parameter. - { - code: dedent` - function foo( - param1: { - readonly bar: Array, - readonly baz: ReadonlyArray - } - ): { - readonly bar: Array, - readonly baz: ReadonlyArray - } { - let foo: { - readonly bar: Array, - readonly baz: ReadonlyArray - } = { - bar: ["hello"], - baz: ["world"] - }; - return foo; - } - `, - optionsSet: [[]], - output: dedent` - function foo( - param1: { - readonly bar: ReadonlyArray, - readonly baz: ReadonlyArray - } - ): { - readonly bar: ReadonlyArray, - readonly baz: ReadonlyArray - } { - let foo: { - readonly bar: ReadonlyArray, - readonly baz: ReadonlyArray - } = { - bar: ["hello"], - baz: ["world"] - }; - return foo; - } - `, - errors: [ - { - messageId: "type", - type: AST_NODE_TYPES.TSTypeReference, - line: 3, - column: 19, - }, - { - messageId: "type", - type: AST_NODE_TYPES.TSTypeReference, - line: 7, - column: 17, - }, - { - messageId: "type", - type: AST_NODE_TYPES.TSTypeReference, - line: 11, - column: 19, - }, - ], - }, - // Should fail on Array type alias. - { - code: `type Foo = Array;`, - optionsSet: [[]], - output: `type Foo = ReadonlyArray;`, - errors: [ - { - messageId: "type", - type: AST_NODE_TYPES.TSTypeReference, - line: 1, - column: 12, - }, - ], - }, - // Should fail on Array as type member. - { - code: dedent` - type Foo = { - readonly bar: Array - } - `, - optionsSet: [[]], - output: dedent` - type Foo = { - readonly bar: ReadonlyArray - } - `, - errors: [ - { - messageId: "type", - type: AST_NODE_TYPES.TSTypeReference, - line: 2, - column: 17, - }, - ], - }, - // Should fail on Array type alias in local type. - { - code: dedent` - function foo() { - type Foo = Array; - } - `, - optionsSet: [[]], - output: dedent` - function foo() { - type Foo = ReadonlyArray; - } - `, - errors: [ - { - messageId: "type", - type: AST_NODE_TYPES.TSTypeReference, - line: 2, - column: 14, - }, - ], - }, - // Should fail on Array as type member in local type. - { - code: dedent` - function foo() { - type Foo = { - readonly bar: Array - } - } - `, - optionsSet: [[]], - output: dedent` - function foo() { - type Foo = { - readonly bar: ReadonlyArray - } - } - `, - errors: [ - { - messageId: "type", - type: AST_NODE_TYPES.TSTypeReference, - line: 3, - column: 19, - }, - ], - }, - // Should fail on Array type in variable declaration. - { - code: `const foo: Array = [];`, - optionsSet: [[]], - output: `const foo: ReadonlyArray = [];`, - errors: [ - { - messageId: "type", - type: AST_NODE_TYPES.TSTypeReference, - line: 1, - column: 12, - }, - ], - }, - // Should fail on shorthand Array syntax. - { - code: `const foo: number[] = [1, 2, 3];`, - optionsSet: [[]], - output: `const foo: readonly number[] = [1, 2, 3];`, - errors: [ - { - messageId: "array", - type: AST_NODE_TYPES.TSArrayType, - line: 1, - column: 12, - }, - ], - }, - // Should fail on Array type being used as template param. - { - code: `let x: Foo>;`, - optionsSet: [[]], - output: `let x: Foo>;`, - errors: [ - { - messageId: "type", - type: AST_NODE_TYPES.TSTypeReference, - line: 1, - column: 12, - }, - ], - }, - // Should fail on nested shorthand arrays. - { - code: `let x: readonly string[][];`, - optionsSet: [[]], - output: `let x: readonly (readonly string[])[];`, - errors: [ - { - messageId: "array", - type: AST_NODE_TYPES.TSArrayType, - line: 1, - column: 17, - }, - ], - }, - // Should fail on implicit Array type in variable declaration. - { - code: dedent` - const foo = [1, 2, 3] - function bar(param = [1, 2, 3]) {} - `, - optionsSet: [[{ checkImplicit: true }]], - output: dedent` - const foo: readonly unknown[] = [1, 2, 3] - function bar(param: readonly unknown[] = [1, 2, 3]) {} - `, - errors: [ - { - messageId: "implicit", - type: AST_NODE_TYPES.VariableDeclarator, - line: 1, - column: 7, - }, - { - messageId: "implicit", - type: AST_NODE_TYPES.AssignmentPattern, - line: 2, - column: 14, - }, - ], - }, - // Class Property Signatures. - { - code: dedent` - class Klass { - foo: number; - private bar: number; - static baz: number; - private static qux: number; - } - `, - optionsSet: [[]], - output: dedent` - class Klass { - readonly foo: number; - private readonly bar: number; - static readonly baz: number; - private static readonly qux: number; - } - `, - errors: [ - { - messageId: "property", - type: AST_NODE_TYPES.PropertyDefinition, - line: 2, - column: 3, - }, - { - messageId: "property", - type: AST_NODE_TYPES.PropertyDefinition, - line: 3, - column: 3, - }, - { - messageId: "property", - type: AST_NODE_TYPES.PropertyDefinition, - line: 4, - column: 3, - }, - { - messageId: "property", - type: AST_NODE_TYPES.PropertyDefinition, - line: 5, - column: 3, - }, - ], - }, - // Class Parameter Properties. - { - code: dedent` - class Klass { - constructor ( - public publicProp: string, - protected protectedProp: string, - private privateProp: string, - ) { } - } - `, - optionsSet: [[]], - output: dedent` - class Klass { - constructor ( - public readonly publicProp: string, - protected readonly protectedProp: string, - private readonly privateProp: string, - ) { } - } - `, - errors: [ - { - messageId: "property", - type: AST_NODE_TYPES.TSParameterProperty, - line: 3, - column: 5, - }, - { - messageId: "property", - type: AST_NODE_TYPES.TSParameterProperty, - line: 4, - column: 5, - }, - { - messageId: "property", - type: AST_NODE_TYPES.TSParameterProperty, - line: 5, - column: 5, - }, - ], - }, - // Interface Index Signatures. - { - code: dedent` - interface Foo { - [key: string]: string - } - interface Bar { - [key: string]: { prop: string } - } - `, - optionsSet: [[]], - output: dedent` - interface Foo { - readonly [key: string]: string - } - interface Bar { - readonly [key: string]: { readonly prop: string } - } - `, - errors: [ - { - messageId: "property", - type: AST_NODE_TYPES.TSIndexSignature, - line: 2, - column: 3, - }, - { - messageId: "property", - type: AST_NODE_TYPES.TSIndexSignature, - line: 5, - column: 3, - }, - { - messageId: "property", - type: AST_NODE_TYPES.TSPropertySignature, - line: 5, - column: 20, - }, - ], - }, - // Function Index Signatures. - { - code: dedent` - function foo(): { [source: string]: string } { - return undefined; - } - function bar(param: { [source: string]: string }): void { - return undefined; - } - `, - optionsSet: [[]], - output: dedent` - function foo(): { readonly [source: string]: string } { - return undefined; - } - function bar(param: { readonly [source: string]: string }): void { - return undefined; - } - `, - errors: [ - { - messageId: "property", - type: AST_NODE_TYPES.TSIndexSignature, - line: 1, - column: 19, - }, - { - messageId: "property", - type: AST_NODE_TYPES.TSIndexSignature, - line: 4, - column: 23, - }, - ], - }, - // Type literal with indexer without readonly modifier should produce failures. - { - code: `let foo: { [key: string]: number };`, - optionsSet: [[]], - output: `let foo: { readonly [key: string]: number };`, - errors: [ - { - messageId: "property", - type: AST_NODE_TYPES.TSIndexSignature, - line: 1, - column: 12, - }, - ], - }, - // Type literal in property template parameter without readonly should produce failures. - { - code: dedent` - type foo = ReadonlyArray<{ - type: string, - code: string, - }>; - `, - optionsSet: [[]], - output: dedent` - type foo = ReadonlyArray<{ - readonly type: string, - readonly code: string, - }>; - `, - errors: [ - { - messageId: "property", - type: AST_NODE_TYPES.TSPropertySignature, - line: 2, - column: 3, - }, - { - messageId: "property", - type: AST_NODE_TYPES.TSPropertySignature, - line: 3, - column: 3, - }, - ], - }, - // Type literal without readonly on members should produce failures. - // Also verify that nested members are checked. - { - code: dedent` - let foo: { - a: number, - b: ReadonlyArray, - c: () => string, - d: { readonly [key: string]: string }, - [key: string]: string, - readonly e: { - a: number, - b: ReadonlyArray, - c: () => string, - d: { readonly [key: string]: string }, - [key: string]: string, - } - }; - `, - optionsSet: [[]], - output: dedent` - let foo: { - readonly a: number, - readonly b: ReadonlyArray, - readonly c: () => string, - readonly d: { readonly [key: string]: string }, - readonly [key: string]: string, - readonly e: { - readonly a: number, - readonly b: ReadonlyArray, - readonly c: () => string, - readonly d: { readonly [key: string]: string }, - readonly [key: string]: string, - } - }; - `, - errors: [ - { - messageId: "property", - type: AST_NODE_TYPES.TSPropertySignature, - line: 2, - column: 3, - }, - { - messageId: "property", - type: AST_NODE_TYPES.TSPropertySignature, - line: 3, - column: 3, - }, - { - messageId: "property", - type: AST_NODE_TYPES.TSPropertySignature, - line: 4, - column: 3, - }, - { - messageId: "property", - type: AST_NODE_TYPES.TSPropertySignature, - line: 5, - column: 3, - }, - { - messageId: "property", - type: AST_NODE_TYPES.TSIndexSignature, - line: 6, - column: 3, - }, - { - messageId: "property", - type: AST_NODE_TYPES.TSPropertySignature, - line: 8, - column: 5, - }, - { - messageId: "property", - type: AST_NODE_TYPES.TSPropertySignature, - line: 9, - column: 5, - }, - { - messageId: "property", - type: AST_NODE_TYPES.TSPropertySignature, - line: 10, - column: 5, - }, - { - messageId: "property", - type: AST_NODE_TYPES.TSPropertySignature, - line: 11, - column: 5, - }, - { - messageId: "property", - type: AST_NODE_TYPES.TSIndexSignature, - line: 12, - column: 5, - }, - ], - }, - { - code: dedent` - function foo(bar: { x: number }) { - }; - `, - optionsSet: [[{ allowLocalMutation: true }]], - output: dedent` - function foo(bar: { readonly x: number }) { - }; - `, - errors: [ - { - messageId: "property", - type: AST_NODE_TYPES.TSPropertySignature, - line: 1, - column: 21, - }, - ], - }, - // Mapped type without readonly. - { - code: dedent` - const func = (x: { [key in string]: number }) => {} - `, - optionsSet: [[]], - output: dedent` - const func = (x: { readonly [key in string]: number }) => {} - `, - errors: [ - { - messageId: "property", - type: AST_NODE_TYPES.TSMappedType, - line: 1, - column: 18, - }, - ], - }, - // Flag non class fields. - { - code: dedent` - class Klass { - foo() { - let bar: { - foo: number; - }; - } - } - `, - optionsSet: [[{ ignoreClass: "fieldsOnly" }]], - output: dedent` - class Klass { - foo() { - let bar: { - readonly foo: number; - }; - } - } - `, - errors: [ - { - messageId: "property", - type: AST_NODE_TYPES.TSPropertySignature, - line: 4, - column: 7, - }, - ], - }, - // Computed properties. - { - code: dedent` - const propertyName = 'myProperty'; - type Foo = { - [propertyName]: string; - }; - `, - optionsSet: [[]], - output: dedent` - const propertyName = 'myProperty'; - type Foo = { - readonly [propertyName]: string; - }; - `, - errors: [ - { - messageId: "property", - type: AST_NODE_TYPES.TSPropertySignature, - line: 3, - column: 3, - }, - ], - }, -]; - -export default tests; diff --git a/tests/rules/prefer-readonly-type/ts/valid.ts b/tests/rules/prefer-readonly-type/ts/valid.ts deleted file mode 100644 index 70412b09b..000000000 --- a/tests/rules/prefer-readonly-type/ts/valid.ts +++ /dev/null @@ -1,428 +0,0 @@ -import dedent from "dedent"; - -import { type rule } from "#/rules/prefer-readonly-type"; -import { type OptionsOf, type ValidTestCaseSet } from "#/tests/helpers/util"; - -const tests: Array>> = [ - // Should not fail on explicit ReadonlyArray parameter. - { - code: dedent` - function foo(...numbers: ReadonlyArray) { - } - `, - optionsSet: [[]], - }, - { - code: dedent` - function foo(...numbers: readonly number[]) { - } - `, - optionsSet: [[]], - }, - // Should not fail on explicit ReadonlyArray return type. - { - code: dedent` - function foo(): ReadonlyArray { - return [1, 2, 3]; - } - `, - optionsSet: [[]], - }, - { - code: dedent` - const foo = (): ReadonlyArray => { - return [1, 2, 3]; - } - `, - optionsSet: [[]], - }, - // ReadonlyArray Tuple. - { - code: dedent` - function foo(tuple: readonly [number, string, readonly [number, string]]) { - } - `, - optionsSet: [[]], - }, - // Should not fail on ReadonlyArray type alias. - { - code: `type Foo = ReadonlyArray;`, - optionsSet: [[]], - }, - // Should not fail on ReadonlyArray type alias in local type. - { - code: dedent` - function foo() { - type Foo = ReadonlyArray; - } - `, - optionsSet: [[]], - }, - // Should not fail on ReadonlyArray in variable declaration. - { - code: `const foo: ReadonlyArray = [];`, - optionsSet: [[]], - }, - // Allow return type. - { - code: dedent` - function foo(...numbers: ReadonlyArray): Array {} - function bar(...numbers: readonly number[]): number[] {} - `, - optionsSet: [[{ allowMutableReturnType: true }]], - }, - // Allow return type. - { - code: dedent` - const foo = function(...numbers: ReadonlyArray): Array {} - const bar = function(...numbers: readonly number[]): number[] {} - `, - optionsSet: [[{ allowMutableReturnType: true }]], - }, - // Allow return type. - { - code: dedent` - const foo = (...numbers: ReadonlyArray): Array => {} - const bar = (...numbers: readonly number[]): number[] => {} - `, - optionsSet: [[{ allowMutableReturnType: true }]], - }, - // Allow return type. - { - code: dedent` - class Foo { - foo(...numbers: ReadonlyArray): Array { - } - } - class Bar { - foo(...numbers: readonly number[]): number[] { - } - } - `, - optionsSet: [[{ allowMutableReturnType: true }]], - }, - // Allow return type with Type Arguments. - { - code: dedent` - function foo(...numbers: ReadonlyArray): Promise> {} - function foo(...numbers: ReadonlyArray): Promise {} - `, - optionsSet: [[{ allowMutableReturnType: true }]], - }, - // Allow return type with deep Type Arguments. - { - code: dedent` - type Foo = { readonly x: T; }; - function foo(...numbers: ReadonlyArray): Promise>> {} - function foo(...numbers: ReadonlyArray): Promise> {} - `, - optionsSet: [[{ allowMutableReturnType: true }]], - }, - // Allow return type with Type Arguments in a tuple. - { - code: dedent` - function foo(...numbers: ReadonlyArray): readonly [number, Array, number] {} - function foo(...numbers: ReadonlyArray): readonly [number, number[], number] {} - `, - optionsSet: [[{ allowMutableReturnType: true }]], - }, - // Allow return type with Type Arguments Union. - { - code: dedent` - function foo(...numbers: ReadonlyArray): { readonly a: Array } | { readonly b: string[] } {} - `, - optionsSet: [[{ allowMutableReturnType: true }]], - }, - // Allow return type with Type Arguments Intersection. - { - code: dedent` - function foo(...numbers: ReadonlyArray): { readonly a: Array } & { readonly b: string[] } {} - `, - optionsSet: [[{ allowMutableReturnType: true }]], - }, - // Allow return type with Type Arguments Conditional. - { - code: dedent` - function foo(x: T): T extends Array ? string : number[] {} - `, - optionsSet: [[{ allowMutableReturnType: true }]], - }, - // Allow inline mutable return type. - { - code: dedent` - function foo(bar: string): { baz: number } { - return 1 as any; - } - `, - optionsSet: [[{ allowMutableReturnType: true }]], - }, - // Should not fail on implicit ReadonlyArray type in variable declaration. - { - code: dedent` - const foo = [1, 2, 3] as const - `, - optionsSet: [[{ checkImplicit: true }]], - }, - // Should not fail on implicit Array. - { - code: dedent` - const foo = [1, 2, 3] - function bar(param = [1, 2, 3]) {} - `, - optionsSet: [[]], - }, - // Interface with readonly modifiers should not produce failures. - { - code: dedent` - interface Foo { - readonly a: number, - readonly b: ReadonlyArray, - readonly c: () => string, - readonly d: { readonly [key: string]: string }, - readonly [key: string]: string, - } - `, - optionsSet: [[]], - }, - // PropertySignature and IndexSignature members without readonly modifier - // should produce failures. Also verify that nested members are checked. - { - code: dedent` - interface Foo { - readonly a: number, - readonly b: ReadonlyArray, - readonly c: () => string, - readonly d: { readonly [key: string]: string }, - readonly [key: string]: string, - readonly e: { - readonly a: number, - readonly b: ReadonlyArray, - readonly c: () => string, - readonly d: { readonly [key: string]: string }, - readonly [key: string]: string, - } - } - `, - optionsSet: [[]], - }, - // Class with parameter properties. - { - code: dedent` - class Klass { - constructor ( - nonParameterProp: string, - readonly readonlyProp: string, - public readonly publicReadonlyProp: string, - protected readonly protectedReadonlyProp: string, - private readonly privateReadonlyProp: string, - ) { } - } - `, - optionsSet: [[]], - }, - // CallSignature and MethodSignature cannot have readonly modifiers and should - // not produce failures. - { - code: dedent` - interface Foo { - (): void - foo(): void - } - `, - optionsSet: [[]], - }, - // The literal with indexer with readonly modifier should not produce failures. - { - code: `let foo: { readonly [key: string]: number };`, - optionsSet: [[]], - }, - // Type literal in array template parameter with readonly should not produce failures. - { - code: `type foo = ReadonlyArray<{ readonly type: string, readonly code: string }>;`, - optionsSet: [[]], - }, - // Type literal with readonly on members should not produce failures. - { - code: dedent` - let foo: { - readonly a: number, - readonly b: ReadonlyArray, - readonly c: () => string, - readonly d: { readonly [key: string]: string } - readonly [key: string]: string - }; - `, - optionsSet: [[]], - }, - // Mapped types with readonly on members should not produce failures. - { - code: dedent` - const func = (x: { readonly [key in string]: number }) => {} - `, - optionsSet: [[]], - }, - // Ignore Classes. - { - code: dedent` - class Klass { - foo: number; - private bar: number; - static baz: number; - private static qux: number; - } - `, - optionsSet: [[{ ignoreClass: true }]], - }, - // Ignore Interfaces. - { - code: dedent` - interface Foo { - foo: number, - bar: ReadonlyArray, - baz: () => string, - qux: { [key: string]: string } - } - `, - optionsSet: [[{ ignoreInterface: true }]], - }, - // Allow Local. - { - code: dedent` - function foo() { - let foo: { - a: number, - b: ReadonlyArray, - c: () => string, - d: { [key: string]: string }, - [key: string]: string, - readonly d: { - a: number, - b: ReadonlyArray, - c: () => string, - d: { [key: string]: string }, - [key: string]: string, - } - } - }; - `, - optionsSet: [[{ allowLocalMutation: true }]], - }, - // Ignore Prefix. - { - code: dedent` - let mutableFoo: string[] = []; - `, - optionsSet: [[{ ignorePattern: "^mutable" }]], - }, - { - code: dedent` - let foo: { - mutableA: number, - mutableB: ReadonlyArray, - mutableC: () => string, - mutableD: { readonly [key: string]: string }, - mutableE: { - mutableA: number, - mutableB: ReadonlyArray, - mutableC: () => string, - mutableD: { readonly [key: string]: string }, - } - }; - `, - optionsSet: [[{ ignorePattern: "^mutable" }]], - }, - { - code: dedent` - class Klass { - mutableA: string; - private mutableB: string; - } - `, - optionsSet: [[{ ignorePattern: "^mutable" }]], - }, - // Ignore Suffix. - { - code: dedent` - let fooMutable: string[] = []; - `, - optionsSet: [[{ ignorePattern: "Mutable$" }]], - }, - { - code: dedent` - let foo: { - aMutable: number, - bMutable: ReadonlyArray, - cMutable: () => string, - dMutable: { readonly [key: string]: string }, - eMutable: { - aMutable: number, - bMutable: ReadonlyArray, - cMutable: () => string, - dMutable: { readonly [key: string]: string }, - } - }; - `, - optionsSet: [[{ ignorePattern: "Mutable$" }]], - }, - { - code: dedent` - class Klass { - AMutable: string; - private BMutable: string; - } - `, - optionsSet: [[{ ignorePattern: "Mutable$" }]], - }, - // Allow mutable TSIndexSignature. - { - code: dedent` - const mutableResult: { - [key: string]: string - } = {}; - `, - optionsSet: [[{ ignorePattern: "^mutable" }]], - }, - // Ignore Mutable Collections (Array, Tuple, Set, Map) - { - code: dedent`type Foo = Array;`, - optionsSet: [[{ ignoreCollections: true }]], - }, - { - code: dedent`const Foo: number[] = [];`, - optionsSet: [[{ ignoreCollections: true }]], - }, - { - code: dedent`const Foo = []`, - optionsSet: [[{ ignoreCollections: true, checkImplicit: true }]], - }, - { - code: dedent`type Foo = [string, string];`, - optionsSet: [[{ ignoreCollections: true }]], - }, - { - code: dedent`const Foo: [string, string] = ['foo', 'bar'];`, - optionsSet: [[{ ignoreCollections: true }]], - }, - { - code: dedent`const Foo = ['foo', 'bar'];`, - optionsSet: [[{ ignoreCollections: true, checkImplicit: true }]], - }, - { - code: dedent`type Foo = Set;`, - optionsSet: [[{ ignoreCollections: true }]], - }, - { - code: dedent`const Foo: Set = new Set();`, - optionsSet: [[{ ignoreCollections: true }]], - }, - { - code: dedent`type Foo = Map;`, - optionsSet: [[{ ignoreCollections: true }]], - }, - { - code: dedent`const Foo: Map = new Map();`, - optionsSet: [[{ ignoreCollections: true }]], - }, -]; - -export default tests; diff --git a/tests/rules/prefer-tacit.test.ts b/tests/rules/prefer-tacit.test.ts new file mode 100644 index 000000000..802f747a1 --- /dev/null +++ b/tests/rules/prefer-tacit.test.ts @@ -0,0 +1,142 @@ +import dedent from "dedent"; +import { createRuleTester } from "eslint-vitest-rule-tester"; +import { describe, expect, it } from "vitest"; + +import { name, rule } from "#/rules/prefer-tacit"; + +import { typescriptConfig } from "../utils/configs"; + +describe(name, () => { + describe("typescript", () => { + const { valid, invalid } = createRuleTester({ + name, + rule, + configs: typescriptConfig, + }); + + it('reports functions that can "safely" be changed', () => { + const invalidResult1 = invalid({ + code: dedent` + function f(x) {} + const foo = x => f(x); + `, + errors: ["generic"], + }); + expect(invalidResult1.messages).toMatchSnapshot(); + + const invalidResult2 = invalid({ + code: dedent` + const foo = [1, 2, 3].map(x => Boolean(x)); + `, + errors: ["generic"], + }); + expect(invalidResult2.messages).toMatchSnapshot(); + }); + + it("reports functions that are just instantiations", () => { + const invalidResult = invalid({ + code: dedent` + function f(x: T): T {} + function foo(x) { return f(x); } + `, + errors: ["generic"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("doesn't report functions without type defs", () => { + valid(dedent` + function foo(x) { + f(x); + } + `); + + valid(dedent` + const foo = function (x) { + f(x); + }; + `); + + valid(dedent` + const foo = (x) => { + f(x); + }; + `); + }); + + it("doesn't report functions using a different number of parameters", () => { + valid(dedent` + function f(x, y) {} + const foo = x => f(x); + `); + + valid(dedent` + const a = ['1', '2']; + a.map((x) => Number.parseInt(x)); + `); + }); + + it("doesn't report functions using default parameters", () => { + valid(dedent` + function f(x, y = 1) {} + const foo = x => f(x); + `); + }); + + it("doesn't report functions with optional parameters", () => { + valid(dedent` + function f(x: number, y?: nunber) {} + const foo = x => f(x); + `); + }); + + it("doesn't report instantiation expressions", () => { + valid(dedent` + function f(x) {} + const foo = f; + `); + }); + + describe("options", () => { + describe("checkMemberExpressions", () => { + it("doesn't report member expressions when disabled", () => { + valid({ + code: dedent` + declare const a: { b(arg: string): string; }; + function foo(x) { return a.b(x); } + `, + options: [{ checkMemberExpressions: false }], + }); + + valid({ + code: dedent` + [''].filter(str => /a/.test(str)) + `, + options: [{ checkMemberExpressions: false }], + }); + }); + + it("report member expressions when enabled", () => { + const invalidResult1 = invalid({ + code: dedent` + declare const a: { b(arg: string): string; }; + function foo(x) { return a.b(x); } + `, + options: [{ checkMemberExpressions: true }], + errors: ["generic"], + }); + expect(invalidResult1.messages).toMatchSnapshot(); + + const invalidResult2 = invalid({ + code: dedent` + [''].filter(str => /a/.test(str)) + `, + options: [{ checkMemberExpressions: true }], + errors: ["generic"], + }); + expect(invalidResult2.messages).toMatchSnapshot(); + }); + }); + }); + }); +}); diff --git a/tests/rules/prefer-tacit/ts/index.test.ts b/tests/rules/prefer-tacit/ts/index.test.ts deleted file mode 100644 index fca4ac933..000000000 --- a/tests/rules/prefer-tacit/ts/index.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { name, rule } from "#/rules/prefer-tacit"; -import { testRule } from "#/tests/helpers/testers"; - -import invalid from "./invalid"; -import valid from "./valid"; - -const tests = { - valid, - invalid, -}; - -const tester = testRule(name, rule); - -tester.typescript(tests); diff --git a/tests/rules/prefer-tacit/ts/invalid.ts b/tests/rules/prefer-tacit/ts/invalid.ts deleted file mode 100644 index a8464b058..000000000 --- a/tests/rules/prefer-tacit/ts/invalid.ts +++ /dev/null @@ -1,345 +0,0 @@ -import { AST_NODE_TYPES } from "@typescript-eslint/utils"; -import dedent from "dedent"; - -import { type rule } from "#/rules/prefer-tacit"; -import { - type InvalidTestCaseSet, - type MessagesOf, - type OptionsOf, -} from "#/tests/helpers/util"; - -const tests: Array< - InvalidTestCaseSet, OptionsOf> -> = [ - // FunctionDeclaration. - { - code: dedent` - function f(x) {} - const foo = x => f(x); - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.ArrowFunctionExpression, - line: 2, - column: 13, - suggestions: [ - { - messageId: "generic", - output: dedent` - function f(x) {} - const foo = f; - `, - }, - ], - }, - ], - }, - { - code: dedent` - function f(x) {} - function foo(x) { return f(x); } - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.FunctionDeclaration, - line: 2, - column: 1, - suggestions: [ - { - messageId: "generic", - output: dedent` - function f(x) {} - const foo = f; - `, - }, - ], - }, - ], - }, - { - code: dedent` - function f(x) {} - export default function (x) { return f(x); } - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.FunctionDeclaration, - line: 2, - column: 16, - suggestions: [ - { - messageId: "generic", - output: dedent` - function f(x) {} - export default f - `, - }, - ], - }, - ], - }, - // FunctionExpression. - { - code: dedent` - const f = function(x) {} - const foo = x => f(x); - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.ArrowFunctionExpression, - line: 2, - column: 13, - suggestions: [ - { - messageId: "generic", - output: dedent` - const f = function(x) {} - const foo = f; - `, - }, - ], - }, - ], - }, - // ArrowFunction. - { - code: dedent` - const f = x => {} - const foo = x => f(x); - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.ArrowFunctionExpression, - line: 2, - column: 13, - suggestions: [ - { - messageId: "generic", - output: dedent` - const f = x => {} - const foo = f; - `, - }, - ], - }, - ], - }, - // TypeAlias. - { - code: dedent` - type F = (x) => {}; - const f = undefined as unknown as F; - const foo = x => f(x); - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.ArrowFunctionExpression, - line: 3, - column: 13, - suggestions: [ - { - messageId: "generic", - output: dedent` - type F = (x) => {}; - const f = undefined as unknown as F; - const foo = f; - `, - }, - ], - }, - ], - }, - // Instantiation Expression - { - code: dedent` - function f(x: T): T {} - const foo = x => f(x); - `, - dependencyConstraints: { typescript: "4.7.0" }, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.ArrowFunctionExpression, - line: 2, - column: 13, - suggestions: [ - { - messageId: "generic", - output: dedent` - function f(x: T): T {} - const foo = f; - `, - }, - ], - }, - ], - }, - { - code: dedent` - function f(x: T): T {} - function foo(x) { return f(x); } - `, - dependencyConstraints: { typescript: "4.7.0" }, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.FunctionDeclaration, - line: 2, - column: 1, - suggestions: [ - { - messageId: "generic", - output: dedent` - function f(x: T): T {} - const foo = f; - `, - }, - ], - }, - ], - }, - { - code: dedent` - function f(x: T): T {} - export default function (x) { return f(x); } - `, - dependencyConstraints: { typescript: "4.7.0" }, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.FunctionDeclaration, - line: 2, - column: 16, - suggestions: [ - { - messageId: "generic", - output: dedent` - function f(x: T): T {} - export default f - `, - }, - ], - }, - ], - }, - // Boolean constructor - { - code: dedent` - const foo = [1, 2, 3].map(x => Boolean(x)); - `, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.ArrowFunctionExpression, - line: 1, - column: 27, - suggestions: [ - { - messageId: "generic", - output: dedent` - const foo = [1, 2, 3].map(Boolean); - `, - }, - ], - }, - ], - }, - // Instantiation Expression not supported. - { - code: dedent` - function f(x: T): T {} - function foo(x) { return f(x); } - `, - dependencyConstraints: { typescript: { range: "<4.7.0" } }, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.FunctionDeclaration, - line: 2, - column: 1, - }, - ], - }, - { - code: dedent` - function f(x: T): T {} - export default function (x) { return f(x); } - `, - dependencyConstraints: { typescript: { range: "<4.7.0" } }, - optionsSet: [[]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.FunctionDeclaration, - line: 2, - column: 16, - }, - ], - }, - // Member Call Expression - { - code: dedent` - [''].filter(str => /a/.test(str)); - `, - optionsSet: [[{ checkMemberExpressions: true }]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.ArrowFunctionExpression, - line: 1, - column: 13, - suggestions: [ - { - messageId: "generic", - output: dedent` - [''].filter(/a/.test.bind(/a/)); - `, - }, - ], - }, - ], - }, - { - code: dedent` - declare const a: { b(arg: string): string; }; - function foo(x) { return a.b(x); } - `, - optionsSet: [[{ checkMemberExpressions: true }]], - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.FunctionDeclaration, - line: 2, - column: 1, - suggestions: [ - { - messageId: "generic", - output: dedent` - declare const a: { b(arg: string): string; }; - const foo = a.b.bind(a); - `, - }, - ], - }, - ], - }, -]; - -export default tests; diff --git a/tests/rules/prefer-tacit/ts/valid.ts b/tests/rules/prefer-tacit/ts/valid.ts deleted file mode 100644 index 3d65647b2..000000000 --- a/tests/rules/prefer-tacit/ts/valid.ts +++ /dev/null @@ -1,102 +0,0 @@ -import dedent from "dedent"; - -import { type rule } from "#/rules/prefer-tacit"; -import { type OptionsOf, type ValidTestCaseSet } from "#/tests/helpers/util"; - -const tests: Array>> = [ - { - // No typedef for `f` therefore no error (when not assuming types). - code: "function foo(x) { f(x); }", - optionsSet: [[]], - }, - { - // No typedef for `f` therefore no error (when not assuming types). - code: "var foo = function(x) { f(x); }", - optionsSet: [[]], - }, - { - // No typedef for `f` therefore no error (when not assuming types). - code: `const foo = x => f(x);`, - optionsSet: [[]], - }, - // Default parameters. - { - code: dedent` - function f(x, y = 10) {} - const foo = x => f(x); - `, - optionsSet: [[]], - }, - // FunctionDeclaration. - { - code: dedent` - function f(x, y) {} - const foo = x => f(x); - `, - optionsSet: [[]], - }, - // FunctionExpression. - { - code: dedent` - const f = function(x, y) {} - const foo = x => f(x); - `, - optionsSet: [[]], - }, - // ArrowFunction. - { - code: dedent` - const f = (x, y) => {} - const foo = x => f(x); - `, - optionsSet: [[]], - }, - // TypeAlias. - { - code: dedent` - type F = (x, y) => {}; - const f = undefined as unknown as F; - const foo = x => f(x); - `, - optionsSet: [[]], - }, - // Optional parameters. - { - code: dedent` - function f(x: number, y?: number) {} - const foo = x => f(x); - `, - optionsSet: [[]], - }, - { - code: dedent` - const a = ['1', '2']; - a.map((x) => Number.parseInt(x)); - `, - optionsSet: [[]], - }, - // Instantiation Expression - { - code: dedent` - const foo = f; - `, - dependencyConstraints: { typescript: "4.7.0" }, - optionsSet: [[]], - }, - // Member Call Expression - { - code: dedent` - [''].filter(str => /a/.test(str)) - `, - optionsSet: [[{ checkMemberExpressions: false }]], - }, - { - code: dedent` - declare const a: { b(arg: string): string; }; - function foo(x) { return a.b(x); } - `, - optionsSet: [[{ checkMemberExpressions: false }]], - }, -]; - -export default tests; diff --git a/tests/rules/readonly-type/ts/index.test.ts b/tests/rules/readonly-type/ts/index.test.ts deleted file mode 100644 index 5e21e2474..000000000 --- a/tests/rules/readonly-type/ts/index.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { name, rule } from "#/rules/readonly-type"; -import { testRule } from "#/tests/helpers/testers"; - -import invalid from "./invalid"; -import valid from "./valid"; - -const tests = { - valid, - invalid, -}; - -const tester = testRule(name, rule); - -tester.typescript(tests); diff --git a/tests/rules/readonly-type/ts/invalid.ts b/tests/rules/readonly-type/ts/invalid.ts deleted file mode 100644 index 8f5678ac3..000000000 --- a/tests/rules/readonly-type/ts/invalid.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { AST_NODE_TYPES } from "@typescript-eslint/utils"; -import dedent from "dedent"; - -import { type rule } from "#/rules/readonly-type"; -import { - type InvalidTestCaseSet, - type MessagesOf, - type OptionsOf, -} from "#/tests/helpers/util"; - -const tests: Array< - InvalidTestCaseSet, OptionsOf> -> = [ - { - code: dedent` - type Foo = Readonly<{ - bar: string; - }>; - `, - optionsSet: [["keyword"]], - output: dedent` - type Foo = { - readonly bar: string; - }; - `, - errors: [ - { - messageId: "keyword", - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 12, - }, - ], - }, - { - code: dedent` - type Foo = { - readonly bar: string; - }; - `, - optionsSet: [["generic"]], - output: dedent` - type Foo = Readonly<{ - bar: string; - }>; - `, - errors: [ - { - messageId: "generic", - type: AST_NODE_TYPES.TSTypeLiteral, - line: 1, - column: 12, - }, - ], - }, -]; - -export default tests; diff --git a/tests/rules/readonly-type/ts/valid.ts b/tests/rules/readonly-type/ts/valid.ts deleted file mode 100644 index ae0112316..000000000 --- a/tests/rules/readonly-type/ts/valid.ts +++ /dev/null @@ -1,82 +0,0 @@ -import dedent from "dedent"; - -import { type rule } from "#/rules/readonly-type"; -import { type OptionsOf, type ValidTestCaseSet } from "#/tests/helpers/util"; - -const tests: Array>> = [ - // Not readonly - { - code: dedent` - type Foo = { - bar: string; - baz: number; - }; - `, - optionsSet: [["generic"], ["keyword"]], - }, - // Not readonly - { - code: dedent` - type Foo = { - readonly bar: string; - baz: { - qux: number; - }; - }; - `, - optionsSet: [["generic"], ["keyword"]], - }, - // Readonly generic shallow - { - code: dedent` - type Foo = Readonly<{ - bar: string; - baz: number; - }>; - `, - optionsSet: [["generic"]], - }, - // Readonly generic deep - { - code: dedent` - type Foo = Readonly<{ - bar: string; - baz: Readonly<{ - qux: number; - }>; - }>; - `, - optionsSet: [["generic"]], - }, - // Readonly keyword shallow - { - code: dedent` - type Foo = { - readonly bar: string; - readonly baz: number; - }; - `, - optionsSet: [["keyword"]], - }, - // Readonly keyword deep - { - code: dedent` - type Foo = { - readonly bar: string; - readonly baz: { - readonly qux: number; - }; - }; - `, - optionsSet: [["keyword"]], - }, - // Empty should not cause issues - { - code: dedent` - type Foo = {}; - `, - optionsSet: [["keyword"], ["generic"]], - }, -]; - -export default tests; diff --git a/tests/rules/type-declaration-immutability.test.ts b/tests/rules/type-declaration-immutability.test.ts new file mode 100644 index 000000000..74273a6e0 --- /dev/null +++ b/tests/rules/type-declaration-immutability.test.ts @@ -0,0 +1,383 @@ +import dedent from "dedent"; +import { createRuleTester } from "eslint-vitest-rule-tester"; +import { Immutability } from "is-immutable-type"; +import { describe, expect, it } from "vitest"; + +import { name, rule } from "#/rules/type-declaration-immutability"; + +import { typescriptConfig } from "../utils/configs"; + +describe(name, () => { + describe("typescript", () => { + const options = [ + { + rules: [ + { + identifiers: ["^I?Immutable.+"], + immutability: Immutability.Immutable, + comparator: "AtLeast", + }, + { + identifiers: ["^I?ReadonlyDeep.+"], + immutability: Immutability.ReadonlyDeep, + comparator: "AtLeast", + }, + { + identifiers: ["^I?Readonly.+"], + immutability: Immutability.ReadonlyShallow, + comparator: "AtLeast", + }, + { + identifiers: ["^I?Mutable.+"], + immutability: Immutability.Mutable, + comparator: "AtMost", + }, + ], + }, + ]; + + const { valid, invalid } = createRuleTester({ + name, + rule, + configs: typescriptConfig, + }); + + it("reports invalid shallow records", () => { + const invalidResult1 = invalid({ + code: dedent` + type ReadonlyFoo = { + foo: number; + }; + `, + options, + errors: ["AtLeast"], + }); + expect(invalidResult1.messages).toMatchSnapshot(); + + const invalidResult2 = invalid({ + code: dedent` + type ReadonlyFoo = { + readonly foo: number; + bar: { + baz: string; + }; + } + `, + options, + errors: ["AtLeast"], + }); + expect(invalidResult2.messages).toMatchSnapshot(); + }); + + it("reports invalid immutable/deep records", () => { + const invalidResult1 = invalid({ + code: dedent` + type ReadonlyDeepFoo = { + readonly foo: number; + readonly bar: { + baz: string; + }; + }; + `, + options, + errors: ["AtLeast"], + }); + expect(invalidResult1.messages).toMatchSnapshot(); + + const invalidResult2 = invalid({ + code: dedent` + type ImmutableFoo = { + readonly foo: number; + readonly bar: { + baz: string; + }; + }; + `, + options, + errors: ["AtLeast"], + }); + expect(invalidResult2.messages).toMatchSnapshot(); + }); + + it("reports invalid shallow arrays", () => { + const invalidResult = invalid({ + code: `type ReadonlyMyArray = Array;`, + options, + errors: ["AtLeast"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports invalid deep arrays", () => { + const invalidResult = invalid({ + code: `type ReadonlyDeepMyArray = ReadonlyArray<{ foo: string; }>;`, + options, + errors: ["AtLeast"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports invalid immutable arrays", () => { + const invalidResult = invalid({ + code: `type ImmutableMyArray = ReadonlyArray<{ readonly foo: string; }>;`, + options, + errors: ["AtLeast"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports invalid shallow sets", () => { + const invalidResult = invalid({ + code: `type ReadonlyMySet = Set;`, + options, + errors: ["AtLeast"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports invalid deep sets", () => { + const invalidResult = invalid({ + code: `type ReadonlyDeepMySet = ReadonlySet<{ foo: string; }>;`, + options, + errors: ["AtLeast"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports invalid immutable sets", () => { + const invalidResult = invalid({ + code: `type ImmutableMySet = ReadonlySet<{ readonly foo: string; }>;`, + options, + errors: ["AtLeast"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports invalid shallow maps", () => { + const invalidResult = invalid({ + code: `type ReadonlyMyMap = Map;`, + options, + errors: ["AtLeast"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports invalid deep maps", () => { + const invalidResult = invalid({ + code: `type ReadonlyDeepMyMap = ReadonlyMap;`, + options, + errors: ["AtLeast"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports invalid immutable maps", () => { + const invalidResult = invalid({ + code: `type ImmutableMyMap = ReadonlyMap;`, + options, + errors: ["AtLeast"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports non-mutable primitives", () => { + const invalidResult = invalid({ + code: "type MutableString = string;", + options, + errors: ["AtMost"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + + it("reports non-mutable records", () => { + const invalidResult1 = invalid({ + code: dedent` + type MutableFoo = { + readonly foo: number; + }; + `, + options, + errors: ["AtMost"], + }); + expect(invalidResult1.messages).toMatchSnapshot(); + + const invalidResult2 = invalid({ + code: dedent` + type MutableFoo = Readonly<{ + foo: number; + }>; + `, + options, + errors: ["AtMost"], + }); + expect(invalidResult2.messages).toMatchSnapshot(); + }); + + it("doesn't report mutable sets", () => { + valid({ + code: `type MutableSet = Set;`, + options, + }); + }); + + it("doesn't report mutable map", () => { + valid({ + code: `type MutableMap = Map;`, + options, + }); + }); + + it("doesn't report valid types", () => { + valid({ + code: "type ReadonlyString = string;", + options, + }); + valid({ + code: "type ReadonlyFoo = { readonly foo: number };", + options, + }); + valid({ + code: "type ReadonlyFoo = Readonly<{ foo: number }>;", + options, + }); + valid({ + code: "type ReadonlyFoo = { readonly foo: number; readonly bar: { baz: string; }; };", + options, + }); + valid({ + code: "type ReadonlySet = ReadonlySet;", + options, + }); + valid({ + code: "type ReadonlyMap = ReadonlyMap;", + options, + }); + valid({ + code: "type ReadonlyDeepString = string;", + options, + }); + valid({ + code: "type ReadonlyDeepFoo = { readonly foo: number; readonly bar: { readonly baz: string; }; };", + options, + }); + valid({ + code: "type ReadonlyDeepSet = ReadonlySet;", + options, + }); + valid({ + code: "type ReadonlyDeepMap = ReadonlyMap;", + options, + }); + valid({ + code: "type ReadonlyDeepSet = ReadonlySet<{ readonly foo: string; }>;", + options, + }); + valid({ + code: "type ReadonlyDeepMap = ReadonlyMap;", + options, + }); + valid({ + code: "type ImmutableString = string;", + options, + }); + valid({ + code: "type ImmutableFoo = { readonly foo: number; readonly bar: { readonly baz: string; }; };", + options, + }); + valid({ + code: "type ImmutableSet = Readonly>;", + options, + }); + valid({ + code: "type ImmutableMap = Readonly>;", + options, + }); + valid({ + code: "type MutableFoo = { foo: number };", + options, + }); + valid({ + code: "type MutableFoo = { readonly foo: number; bar: { readonly baz: string; }; };", + options, + }); + valid({ + code: "type MutableSet = Set<{ readonly foo: string; }>;", + options, + }); + valid({ + code: "type MutableMap = Map;", + options, + }); + }); + + describe("ignoreInterfaces", () => { + it("doesn't report interfaces when enabled", () => { + valid({ + code: dedent` + interface ReadonlyFoo { + foo: number; + } + `, + options: [{ ignoreInterfaces: true }], + }); + }); + }); + + describe("ignoreIdentifierPattern", () => { + it("doesn't report interfaces when enabled", () => { + valid({ + code: dedent` + type ReadonlyFoo = { + foo: number; + }; + `, + options: [ + { + ignoreIdentifierPattern: "Foo", + }, + ], + }); + }); + }); + + it("respects override settings", () => { + valid({ + code: dedent` + type ReadonlyDeepFoo = ReadonlyDeep<{ foo: { bar: string; }; }>; + type ReadonlyDeep = T; + `, + options, + settings: { + immutability: { + overrides: [ + { + type: "ReadonlyDeep", + to: Immutability.ReadonlyDeep, + }, + ], + }, + }, + }); + + const invalidResult = invalid({ + code: `type MutableSet = Set;`, + options, + settings: { + immutability: { + overrides: { + keepDefault: false, + values: [ + { + type: { from: "lib", name: "Set" }, + to: Immutability.Immutable, + }, + ], + }, + }, + }, + errors: ["AtMost"], + }); + expect(invalidResult.messages).toMatchSnapshot(); + }); + }); +}); diff --git a/tests/rules/type-declaration-immutability/ts/index.test.ts b/tests/rules/type-declaration-immutability/ts/index.test.ts deleted file mode 100644 index f591363e3..000000000 --- a/tests/rules/type-declaration-immutability/ts/index.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { name, rule } from "#/rules/type-declaration-immutability"; -import { testRule } from "#/tests/helpers/testers"; - -import invalid from "./invalid"; -import valid from "./valid"; - -const tests = { - valid, - invalid, -}; - -const tester = testRule(name, rule); - -tester.typescript(tests); diff --git a/tests/rules/type-declaration-immutability/ts/invalid.ts b/tests/rules/type-declaration-immutability/ts/invalid.ts deleted file mode 100644 index 657585859..000000000 --- a/tests/rules/type-declaration-immutability/ts/invalid.ts +++ /dev/null @@ -1,482 +0,0 @@ -import { AST_NODE_TYPES } from "@typescript-eslint/utils"; -import dedent from "dedent"; -import { Immutability } from "is-immutable-type"; - -import { type rule } from "#/rules/type-declaration-immutability"; -import { - type InvalidTestCaseSet, - type MessagesOf, - type OptionsOf, -} from "#/tests/helpers/util"; - -const recommended = { - rules: [ - { - identifiers: ["^I?Immutable.+"], - immutability: Immutability.Immutable, - comparator: "AtLeast", - }, - { - identifiers: ["^I?ReadonlyDeep.+"], - immutability: Immutability.ReadonlyDeep, - comparator: "AtLeast", - }, - { - identifiers: ["^I?Readonly.+"], - immutability: Immutability.ReadonlyShallow, - comparator: "AtLeast", - fixer: [ - { - pattern: "^(Array|Map|Set)<(.+)>$", - replace: "Readonly$1<$2>", - }, - { - pattern: "^(.+)$", - replace: "Readonly<$1>", - }, - ], - }, - { - identifiers: ["^I?Mutable.+"], - immutability: Immutability.Mutable, - comparator: "AtMost", - fixer: [ - { - pattern: "^Readonly(Array|Map|Set)<(.+)>$", - replace: "$1<$2>", - }, - { - pattern: "^Readonly<(.+)>$", - replace: "$1", - }, - ], - }, - ], -}; - -const tests: Array< - InvalidTestCaseSet, OptionsOf> -> = [ - { - code: dedent` - type ReadonlyFoo = { foo: number } - `, - optionsSet: [[recommended]], - output: dedent` - type ReadonlyFoo = Readonly<{ foo: number }> - `, - errors: [ - { - messageId: "AtLeast", - data: { - expected: Immutability[Immutability.ReadonlyShallow], - actual: Immutability[Immutability.Mutable], - }, - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 6, - }, - ], - }, - { - code: dedent` - type ReadonlyFoo = { - readonly foo: number; - bar: { - baz: string; - }; - } - `, - optionsSet: [[recommended]], - output: dedent` - type ReadonlyFoo = Readonly<{ - readonly foo: number; - bar: { - baz: string; - }; - }> - `, - errors: [ - { - messageId: "AtLeast", - data: { - expected: Immutability[Immutability.ReadonlyShallow], - actual: Immutability[Immutability.Mutable], - }, - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 6, - }, - ], - }, - { - code: dedent` - type ReadonlyMySet = Set; - `, - optionsSet: [[recommended]], - output: dedent` - type ReadonlyMySet = ReadonlySet; - `, - errors: [ - { - messageId: "AtLeast", - data: { - expected: Immutability[Immutability.ReadonlyShallow], - actual: Immutability[Immutability.Mutable], - }, - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 6, - }, - ], - }, - { - code: dedent` - type ReadonlyMyMap = Map; - `, - output: dedent` - type ReadonlyMyMap = ReadonlyMap; - `, - optionsSet: [[recommended]], - errors: [ - { - messageId: "AtLeast", - data: { - expected: Immutability[Immutability.ReadonlyShallow], - actual: Immutability[Immutability.Mutable], - }, - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 6, - }, - ], - }, - { - code: dedent` - type ReadonlyDeepFoo = { - readonly foo: number; - readonly bar: { baz: string; }; - } - `, - optionsSet: [[recommended]], - errors: [ - { - messageId: "AtLeast", - data: { - expected: Immutability[Immutability.ReadonlyDeep], - actual: Immutability[Immutability.ReadonlyShallow], - }, - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 6, - }, - ], - }, - { - code: dedent` - type ReadonlyDeepSet = ReadonlySet<{ foo: string; }>; - `, - optionsSet: [[recommended]], - errors: [ - { - messageId: "AtLeast", - data: { - expected: Immutability[Immutability.ReadonlyDeep], - actual: Immutability[Immutability.ReadonlyShallow], - }, - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 6, - }, - ], - }, - { - code: dedent` - type ReadonlyDeepMap = ReadonlyMap; - `, - optionsSet: [[recommended]], - errors: [ - { - messageId: "AtLeast", - data: { - expected: Immutability[Immutability.ReadonlyDeep], - actual: Immutability[Immutability.ReadonlyShallow], - }, - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 6, - }, - ], - }, - { - code: dedent` - type ImmutableFoo = { - readonly foo: number; - readonly bar: { baz: string; }; - } - `, - optionsSet: [[recommended]], - errors: [ - { - messageId: "AtLeast", - data: { - expected: Immutability[Immutability.Immutable], - actual: Immutability[Immutability.ReadonlyShallow], - }, - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 6, - }, - ], - }, - { - code: dedent` - type ImmutableSet = ReadonlySet<{ readonly foo: string; }>; - `, - optionsSet: [[recommended]], - errors: [ - { - messageId: "AtLeast", - data: { - expected: Immutability[Immutability.Immutable], - actual: Immutability[Immutability.ReadonlyDeep], - }, - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 6, - }, - ], - }, - { - code: dedent` - type ImmutableMap = ReadonlyMap; - `, - optionsSet: [[recommended]], - errors: [ - { - messageId: "AtLeast", - data: { - expected: Immutability[Immutability.Immutable], - actual: Immutability[Immutability.ReadonlyDeep], - }, - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 6, - }, - ], - }, - { - code: dedent` - type MutableString = string - `, - optionsSet: [[recommended]], - errors: [ - { - messageId: "AtMost", - data: { - expected: Immutability[Immutability.Mutable], - actual: Immutability[Immutability.Immutable], - }, - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 6, - }, - ], - }, - { - code: dedent` - type MutableFoo = { readonly foo: number } - `, - optionsSet: [[recommended]], - errors: [ - { - messageId: "AtMost", - data: { - expected: Immutability[Immutability.Mutable], - actual: Immutability[Immutability.Immutable], - }, - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 6, - }, - ], - }, - { - code: dedent` - type MutableFoo = Readonly<{ foo: number }> - `, - optionsSet: [[recommended]], - output: dedent` - type MutableFoo = { foo: number } - `, - errors: [ - { - messageId: "AtMost", - data: { - expected: Immutability[Immutability.Mutable], - actual: Immutability[Immutability.Immutable], - }, - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 6, - }, - ], - }, - { - code: dedent` - type MutableFoo = { - readonly foo: number; - readonly bar: { baz: string; }; - } - `, - optionsSet: [[recommended]], - errors: [ - { - messageId: "AtMost", - data: { - expected: Immutability[Immutability.Mutable], - actual: Immutability[Immutability.ReadonlyShallow], - }, - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 6, - }, - ], - }, - { - code: dedent` - type Foo = { foo: number } - `, - optionsSet: [ - [ - { - rules: [ - { - identifiers: "Foo", - immutability: "ReadonlyDeep", - }, - ], - }, - ], - ], - errors: [ - { - messageId: "AtLeast", - data: { - expected: Immutability[Immutability.ReadonlyDeep], - actual: Immutability[Immutability.Mutable], - }, - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 6, - }, - ], - }, - { - code: dedent` - type ReadonlyFoo = { - readonly foo: number; - readonly bar: { baz: string; }; - }; - `, - optionsSet: [ - [ - { - rules: [ - { - identifiers: "^I?Readonly.+", - immutability: "ReadonlyDeep", - }, - ], - }, - ], - ], - errors: [ - { - messageId: "AtLeast", - data: { - expected: Immutability[Immutability.ReadonlyDeep], - actual: Immutability[Immutability.ReadonlyShallow], - }, - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 6, - }, - ], - }, - { - code: dedent` - type MutableSet = Set; - `, - optionsSet: [[recommended]], - settingsSet: [ - { - immutability: { - overrides: { - keepDefault: false, - }, - }, - }, - ], - errors: [ - { - messageId: "AtMost", - data: { - expected: Immutability[Immutability.Mutable], - actual: Immutability[Immutability.ReadonlyDeep], - }, - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 6, - }, - ], - }, - { - code: dedent` - type MutableSet = Set; - `, - optionsSet: [[recommended]], - settingsSet: [ - { - immutability: { - overrides: { - keepDefault: false, - values: [ - { - type: { from: "lib", name: "Set" }, - to: Immutability.Immutable, - }, - ], - }, - }, - }, - { - immutability: { - overrides: { - keepDefault: false, - values: [ - { - type: { from: "lib", name: "Set" }, - to: "Immutable", - }, - ], - }, - }, - }, - ], - errors: [ - { - messageId: "AtMost", - data: { - expected: Immutability[Immutability.Mutable], - actual: Immutability[Immutability.Immutable], - }, - type: AST_NODE_TYPES.Identifier, - line: 1, - column: 6, - }, - ], - }, -]; - -export default tests; diff --git a/tests/rules/type-declaration-immutability/ts/valid.ts b/tests/rules/type-declaration-immutability/ts/valid.ts deleted file mode 100644 index 196d9d2a1..000000000 --- a/tests/rules/type-declaration-immutability/ts/valid.ts +++ /dev/null @@ -1,189 +0,0 @@ -import dedent from "dedent"; -import { Immutability } from "is-immutable-type"; - -import { type rule } from "#/rules/type-declaration-immutability"; -import { type OptionsOf, type ValidTestCaseSet } from "#/tests/helpers/util"; - -const recommended = { - rules: [ - { - identifiers: ["^I?Immutable.+"], - immutability: Immutability.Immutable, - comparator: "AtLeast", - }, - { - identifiers: ["^I?ReadonlyDeep.+"], - immutability: Immutability.ReadonlyDeep, - comparator: "AtLeast", - }, - { - identifiers: ["^I?Readonly.+"], - immutability: Immutability.ReadonlyShallow, - comparator: "AtLeast", - }, - { - identifiers: ["^I?Mutable.+"], - immutability: Immutability.Mutable, - comparator: "AtMost", - }, - ], -}; - -const tests: Array>> = [ - { - code: "type ReadonlyString = string;", - optionsSet: [[recommended]], - }, - { - code: "type ReadonlyFoo = { readonly foo: number };", - optionsSet: [[recommended]], - }, - { - code: "type ReadonlyFoo = Readonly<{ foo: number }>;", - optionsSet: [[recommended]], - }, - { - code: "type ReadonlyFoo = { readonly foo: number; readonly bar: { baz: string; }; };", - optionsSet: [[recommended]], - }, - { - code: "type ReadonlySet = ReadonlySet;", - optionsSet: [[recommended]], - }, - { - code: "type ReadonlyMap = ReadonlyMap;", - optionsSet: [[recommended]], - }, - { - code: "type ReadonlyDeepString = string;", - optionsSet: [[recommended]], - }, - { - code: "type ReadonlyDeepFoo = { readonly foo: number; readonly bar: { readonly baz: string; }; };", - optionsSet: [[recommended]], - }, - { - code: "type ReadonlyDeepSet = ReadonlySet;", - optionsSet: [[recommended]], - }, - { - code: "type ReadonlyDeepMap = ReadonlyMap;", - optionsSet: [[recommended]], - }, - { - code: "type ReadonlyDeepSet = ReadonlySet<{ readonly foo: string; }>;", - optionsSet: [[recommended]], - }, - { - code: "type ReadonlyDeepMap = ReadonlyMap;", - optionsSet: [[recommended]], - }, - { - code: "type ImmutableString = string;", - optionsSet: [[recommended]], - }, - { - code: "type ImmutableFoo = { readonly foo: number; readonly bar: { readonly baz: string; }; };", - optionsSet: [[recommended]], - }, - { - code: "type ImmutableSet = Readonly>;", - optionsSet: [[recommended]], - }, - { - code: "type ImmutableMap = Readonly>;", - optionsSet: [[recommended]], - }, - { - code: "type MutableFoo = { foo: number };", - optionsSet: [[recommended]], - }, - { - code: "type MutableFoo = { readonly foo: number; bar: { readonly baz: string; }; };", - optionsSet: [[recommended]], - }, - { - code: "type MutableSet = Set<{ readonly foo: string; }>;", - optionsSet: [[recommended]], - }, - { - code: "type MutableMap = Map;", - optionsSet: [[recommended]], - }, - { - code: "type ReadonlyFoo = { foo: number };", - optionsSet: [ - [ - { - ignoreIdentifierPattern: "Foo", - }, - ], - ], - }, - { - code: "interface ReadonlyFoo { foo: number };", - optionsSet: [ - [ - { - ignoreIdentifierPattern: "Foo", - }, - ], - [ - { - ignoreInterfaces: true, - }, - ], - ], - }, - { - code: "type Foo = { readonly foo: number };", - optionsSet: [ - [ - { - rules: [ - { - identifiers: "Foo", - immutability: "ReadonlyDeep", - }, - ], - }, - ], - ], - }, - { - code: "type ReadonlyFoo = { readonly foo: number; readonly bar: { readonly baz: string; }; };", - optionsSet: [ - [ - { - rules: [ - { - identifiers: "^I?Readonly.+", - immutability: "ReadonlyDeep", - }, - ], - }, - ], - ], - }, - { - code: dedent` - type ReadonlyDeepFoo = ReadonlyDeep<{ foo: { bar: string; }; }>; - type ReadonlyDeep = T; - `, - optionsSet: [[recommended]], - settingsSet: [ - { - immutability: { - overrides: [ - { - type: "ReadonlyDeep", - to: Immutability.ReadonlyDeep, - }, - ], - }, - }, - ], - }, -]; - -export default tests; diff --git a/tests/utils/configs.ts b/tests/utils/configs.ts new file mode 100644 index 000000000..d68668bf8 --- /dev/null +++ b/tests/utils/configs.ts @@ -0,0 +1,37 @@ +import path from "node:path"; + +// @ts-expect-error - Not typed. +import babelParser from "@babel/eslint-parser"; +import typescriptParser, { + type ParserOptions, +} from "@typescript-eslint/parser"; +import { type Linter } from "eslint"; + +export const typescriptConfig = { + languageOptions: { + parser: typescriptParser, + parserOptions: { + tsconfigRootDir: path.join(import.meta.dirname, "../fixture"), + projectService: { + // Ensure we're not using the default project + maximumDefaultProjectFileMatchCount_THIS_WILL_SLOW_DOWN_LINTING: 0, + }, + }, + }, +} satisfies Linter.Config & { + languageOptions: { parserOptions: ParserOptions }; +}; + +export const esLatestConfig = { + languageOptions: { + parser: babelParser, + parserOptions: { + ecmaVersion: "latest", + requireConfigFile: false, + babelOptions: { + babelrc: false, + configFile: false, + }, + }, + }, +} satisfies Linter.Config; diff --git a/vitest.config.ts b/vitest.config.ts index d323d3ba2..207d3a308 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,33 +1,14 @@ import tsconfigPaths from "vite-tsconfig-paths"; import { defineConfig } from "vitest/config"; -/** - * Get the intended boolean value from the given string. - */ -function getBoolean(value: string | undefined) { - if (value === undefined) { - return false; - } - const asNumber = Number(value); - return Number.isNaN(asNumber) - ? Boolean(String(value).toLowerCase().replace("false", "")) - : Boolean(asNumber); -} - -const useCompiledTests = getBoolean(process.env["USE_COMPILED_TESTS"]); - -const testFilePattern = `${ - useCompiledTests ? "tests-compiled" : "." -}/**/*.test.${useCompiledTests ? "js" : "ts"}`; - export default defineConfig({ plugins: [tsconfigPaths()], test: { - include: [testFilePattern], + include: ["./**/*.test.ts"], exclude: ["lib", "node_modules"], coverage: { - provider: useCompiledTests ? "istanbul" : "v8", + provider: "v8", include: ["src"], reporter: ["lcov", "text"], watermarks: {