diff --git a/.cspell.json b/.cspell.json index c65ff294c24..9df7d65a1b5 100644 --- a/.cspell.json +++ b/.cspell.json @@ -11,7 +11,8 @@ "**/**/ROADMAP.md", "**/*.{json,snap}", ".cspell.json", - "yarn.lock" + "yarn.lock", + ".github/workflows/**" ], "dictionaries": [ "typescript", @@ -34,45 +35,47 @@ "\\(#.+?\\)" ], "words": [ - "ASTs", "Airbnb", "Airbnb's", - "Codecov", - "Crockford", - "errored", - "IDE's", - "IIFE", - "IIFEs", - "OOM", - "OOMs", - "Premade", - "ROADMAP", + "ASTs", "autofix", "autofixers", "backticks", "bigint", + "bivariant", "blockless", "codebases", + "Codecov", + "contravariant", + "Crockford", "declarators", "destructure", "destructured", + "errored", "erroring", "ESLint", "ESLint's", "espree", "estree", + "IDE's", + "IIFE", + "IIFEs", "linebreaks", "necroing", "nocheck", "nullish", + "OOM", + "OOMs", "parameterised", "performant", "pluggable", "postprocess", + "Premade", "prettier's", "recurse", "reimplement", "resync", + "ROADMAP", "ruleset", "rulesets", "superset", diff --git a/.eslintrc.js b/.eslintrc.js index 39ca61de516..cee2ec901cc 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -174,7 +174,7 @@ module.exports = { '@typescript-eslint/internal/no-typescript-estree-import': 'error', }, }, - // rule source files + // plugin rule source files { files: [ 'packages/eslint-plugin-internal/src/rules/**/*.ts', @@ -187,6 +187,17 @@ module.exports = { 'import/no-default-export': 'off', }, }, + // plugin rule tests + { + files: [ + 'packages/eslint-plugin-internal/tests/rules/**/*.test.ts', + 'packages/eslint-plugin-tslint/tests/rules/**/*.test.ts', + 'packages/eslint-plugin/tests/rules/**/*.test.ts', + ], + rules: { + '@typescript-eslint/internal/plugin-test-formatting': 'error', + }, + }, // tools and tests { files: ['**/tools/**/*.ts', '**/tests/**/*.ts'], diff --git a/.github/ISSUE_TEMPLATE/eslint-plugin-tslint.md b/.github/ISSUE_TEMPLATE/eslint-plugin-tslint.md index b94f19fb5b8..e999830e4f1 100644 --- a/.github/ISSUE_TEMPLATE/eslint-plugin-tslint.md +++ b/.github/ISSUE_TEMPLATE/eslint-plugin-tslint.md @@ -15,7 +15,7 @@ The more relevant information you can include, the faster we can find the issue **Repro** diff --git a/.github/ISSUE_TEMPLATE/eslint-plugin-typescript.md b/.github/ISSUE_TEMPLATE/eslint-plugin-typescript.md index 3c0fb548f13..b9ab13f9249 100644 --- a/.github/ISSUE_TEMPLATE/eslint-plugin-typescript.md +++ b/.github/ISSUE_TEMPLATE/eslint-plugin-typescript.md @@ -28,7 +28,7 @@ Are you opening an issue because the rule you're trying to use is not found? **Repro** diff --git a/.github/ISSUE_TEMPLATE/typescript-eslint-parser.md b/.github/ISSUE_TEMPLATE/typescript-eslint-parser.md index aaa4bfd356c..ca8d245b263 100644 --- a/.github/ISSUE_TEMPLATE/typescript-eslint-parser.md +++ b/.github/ISSUE_TEMPLATE/typescript-eslint-parser.md @@ -15,7 +15,7 @@ The more relevant information you can include, the faster we can find the issue **Repro** diff --git a/.github/ISSUE_TEMPLATE/typescript-eslint-utils.md b/.github/ISSUE_TEMPLATE/typescript-eslint-utils.md index 5b365a85ea8..48bb39e6fe0 100644 --- a/.github/ISSUE_TEMPLATE/typescript-eslint-utils.md +++ b/.github/ISSUE_TEMPLATE/typescript-eslint-utils.md @@ -15,7 +15,7 @@ The more relevant information you can include, the faster we can find the issue **Repro** diff --git a/.github/ISSUE_TEMPLATE/typescript-estree.md b/.github/ISSUE_TEMPLATE/typescript-estree.md index 21f4c800d54..f4a72b75b0e 100644 --- a/.github/ISSUE_TEMPLATE/typescript-estree.md +++ b/.github/ISSUE_TEMPLATE/typescript-estree.md @@ -15,7 +15,7 @@ The more relevant information you can include, the faster we can find the issue **Repro** diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 933b4895837..3e2d0e9dd51 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ env: jobs: primary_code_validation_and_tests: - name: Primary code validation and tests + name: Typecheck and tests runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -49,15 +49,6 @@ jobs: - name: Typecheck all packages run: yarn typecheck - - name: Check code formatting - run: yarn format-check - - - name: Run linting - run: yarn lint - - - name: Validate spelling - run: yarn check:spelling - - name: Run unit tests run: yarn test env: @@ -71,6 +62,46 @@ jobs: flags: unittest name: codecov + linting_and_style: + name: Code style and lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ env.PRIMARY_NODE_VERSION }} + uses: actions/setup-node@v1 + with: + node-version: ${{ env.PRIMARY_NODE_VERSION }} + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "::set-output name=dir::$(yarn cache dir)" + + - uses: actions/cache@v1 + id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`) + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn- + + # This also runs a build as part of the postinstall bootstrap + - name: Install dependencies and build + run: | + yarn --ignore-engines --frozen-lockfile + yarn check-clean-workspace-after-install + + - name: Check code formatting + run: yarn format-check + + - name: Lint code + run: yarn lint + + - name: Lint markdown + run: yarn lint:markdown + + - name: Check spelling + run: yarn check:spelling + integration_tests: name: Run integration tests on primary Node.js version runs-on: ubuntu-latest @@ -143,7 +174,7 @@ jobs: publish_canary_version: name: Publish the latest code as a canary version runs-on: ubuntu-latest - needs: [primary_code_validation_and_tests, unit_tests_on_other_node_versions, integration_tests] + needs: [primary_code_validation_and_tests, unit_tests_on_other_node_versions, linting_and_style, integration_tests] if: github.ref == 'refs/heads/master' steps: - uses: actions/checkout@v2 diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 00000000000..77f01355394 --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,95 @@ +{ + "default": false, + + // MD001/heading-increment/header-increment - Heading levels should only increment by one level at a time + "MD001": true, + // MD002/first-heading-h1/first-header-h1 - First heading should be a top level heading + "MD002": false, + // MD003/heading-style/header-style - Heading style + "MD003": false, + // MD004/ul-style - Unordered list style + "MD004": false, + // MD005/list-indent - Inconsistent indentation for list items at the same level + "MD005": false, + // MD006/ul-start-left - Consider starting bulleted lists at the beginning of the line + "MD006": false, + // MD007/ul-indent - Unordered list indentation + "MD007": false, + // MD009/no-trailing-spaces - Trailing spaces + "MD009": false, + // MD010/no-hard-tabs - Hard tabs + "MD010": false, + // MD011/no-reversed-links - Reversed link syntax + "MD011": true, + // MD012/no-multiple-blanks - Multiple consecutive blank lines + "MD012": false, + // MD013/line-length - Line length + "MD013": { "line_length": 99999 }, // no line length + // MD014/commands-show-output - Dollar signs used before commands without showing output + "MD014": false, + // MD018/no-missing-space-atx - No space after hash on atx style heading + "MD018": true, + // MD019/no-multiple-space-atx - Multiple spaces after hash on atx style heading + "MD019": false, + // MD020/no-missing-space-closed-atx - No space inside hashes on closed atx style heading + "MD020": false, + // MD021/no-multiple-space-closed-atx - Multiple spaces inside hashes on closed atx style heading + "MD021": false, + // MD022/blanks-around-headings/blanks-around-headers - Headings should be surrounded by blank lines + "MD022": true, + // MD023/heading-start-left/header-start-left - Headings must start at the beginning of the line + "MD023": false, + // MD024/no-duplicate-heading/no-duplicate-header - Multiple headings with the same content + "MD024": false, + // MD025/single-title/single-h1 - Multiple top level headings in the same document + "MD025": true, + // MD026/no-trailing-punctuation - Trailing punctuation in heading + "MD026": { "punctuation": ".,;:!。,;:!?" }, // specifically allow "?" + // MD027/no-multiple-space-blockquote - Multiple spaces after blockquote symbol + "MD027": false, + // MD028/no-blanks-blockquote - Blank line inside blockquote + "MD028": true, + // MD029/ol-prefix - Ordered list item prefix + "MD029": false, + // MD030/list-marker-space - Spaces after list markers + "MD030": true, + // MD031/blanks-around-fences - Fenced code blocks should be surrounded by blank lines + "MD031": false, + // MD032/blanks-around-lists - Lists should be surrounded by blank lines + "MD032": false, + // MD033/no-inline-html - Inline HTML + "MD033": { "allowed_elements": ["a", "img", "br", "sup", "h1", "p"] }, + // MD034/no-bare-urls - Bare URL used + "MD034": false, + // MD035/hr-style - Horizontal rule style + "MD035": false, + // MD036/no-emphasis-as-heading/no-emphasis-as-header - Emphasis used instead of a heading + "MD036": true, + // MD037/no-space-in-emphasis - Spaces inside emphasis markers + "MD037": true, + // MD038/no-space-in-code - Spaces inside code span elements + "MD038": true, + // MD039/no-space-in-links - Spaces inside link text + "MD039": true, + // MD040/fenced-code-language - Fenced code blocks should have a language specified + "MD040": true, + // MD041/first-line-heading/first-line-h1 - First line in file should be a top level heading + "MD041": false, // would love to do this, but our README files use `

` as their heading + // MD042/no-empty-links - No empty links + "MD042": true, + // MD043/required-headings/required-headers - Required heading structure + "MD043": false, + // MD044/proper-names - Proper names should have the correct capitalization + "MD044": { + "names": ["JavaScript", "TypeScript", "TSLint", "ESLint"], + "code_blocks": false + }, + // MD045/no-alt-text - Images should have alternate text (alt text) + "MD045": true, + // MD046/code-block-style - Code block style + "MD046": { "style": "fenced" }, + // MD047/single-trailing-newline - Files should end with a single newline character + "MD047": false, + // MD048/code-fence-style - Code fence style + "MD048": { "style": "backtick" } +} diff --git a/.markdownlintignore b/.markdownlintignore new file mode 100644 index 00000000000..a5442e43554 --- /dev/null +++ b/.markdownlintignore @@ -0,0 +1,3 @@ +node_modules +CHANGELOG.md +tests/integration/fixtures/markdown diff --git a/.prettierrc.json b/.prettierrc.json index bf357fbbc08..a20502b7f06 100644 --- a/.prettierrc.json +++ b/.prettierrc.json @@ -1,3 +1,4 @@ { + "singleQuote": true, "trailingComma": "all" } diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 33430df8759..d91eb64d0d4 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -3,7 +3,8 @@ "esbenp.prettier-vscode", "dbaeumer.vscode-eslint", "editorconfig.editorconfig", - "streetsidesoftware.code-spell-checker" + "streetsidesoftware.code-spell-checker", + "davidanson.vscode-markdownlint" ], "unwantedRecommendations": ["hookyqr.beautify", "dbaeumer.jshint"] } diff --git a/.vscode/settings.json b/.vscode/settings.json index c03c3aac4dd..dc6744448f3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -17,5 +17,5 @@ "typescript.preferences.importModuleSpecifier": "auto", "javascript.preferences.quoteStyle": "single", "typescript.preferences.quoteStyle": "single", - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.defaultFormatter": "esbenp.prettier-vscode", } diff --git a/CHANGELOG.md b/CHANGELOG.md index df5d4854494..9d0a14f5669 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,78 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.28.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.27.0...v2.28.0) (2020-04-13) + + +### Bug Fixes + +* **eslint-plugin:** [method-signature-style] handle multiline params ([#1861](https://github.com/typescript-eslint/typescript-eslint/issues/1861)) ([5832a86](https://github.com/typescript-eslint/typescript-eslint/commit/5832a8643bbe174ec02df5966bb333e506e45f5d)) +* **eslint-plugin:** [no-empty-interface] use suggestion fixer for ambient contexts ([#1880](https://github.com/typescript-eslint/typescript-eslint/issues/1880)) ([62b2278](https://github.com/typescript-eslint/typescript-eslint/commit/62b2278aec0011c93eae17bed8b278114d3379a2)) +* **eslint-plugin:** [unbound-method] false positive on property function initializer ([#1890](https://github.com/typescript-eslint/typescript-eslint/issues/1890)) ([f1c3b18](https://github.com/typescript-eslint/typescript-eslint/commit/f1c3b18f7aadc81f7dca7aa32aa1a8fe424e04e7)) +* **eslint-plugin:** [unbound-method] ignore assignments _to_ methods ([#1736](https://github.com/typescript-eslint/typescript-eslint/issues/1736)) ([6b4680b](https://github.com/typescript-eslint/typescript-eslint/commit/6b4680b6e7343d9d98fa1de170f387a36d98b73e)) +* **eslint-plugin:** no-empty-interface autofix ([#1865](https://github.com/typescript-eslint/typescript-eslint/issues/1865)) ([829a2f7](https://github.com/typescript-eslint/typescript-eslint/commit/829a2f728f876d356908e2338c2d6620e58f9943)), closes [#1864](https://github.com/typescript-eslint/typescript-eslint/issues/1864) +* **eslint-plugin:** use `isTypeArrayTypeOrUnionOfArrayTypes` util for checking if type is array ([#1728](https://github.com/typescript-eslint/typescript-eslint/issues/1728)) ([05030f8](https://github.com/typescript-eslint/typescript-eslint/commit/05030f8d2bd5a50e95053bc61380891da71cc567)) + + +### Features + +* **eslint-plugin:** [ban-ts-comment] support `ts-expect-error` ([#1706](https://github.com/typescript-eslint/typescript-eslint/issues/1706)) ([469cff3](https://github.com/typescript-eslint/typescript-eslint/commit/469cff332c041f38f60de052769287342455cff1)) +* **eslint-plugin:** [consistent-type-assertions] always allow `const` assertions ([#1713](https://github.com/typescript-eslint/typescript-eslint/issues/1713)) ([af2c00d](https://github.com/typescript-eslint/typescript-eslint/commit/af2c00de62f7e31eaeb88996ebf3f330cc8473b9)) +* **eslint-plugin:** [explicit-function-return-type] add option to allow concise arrows that start with void ([#1732](https://github.com/typescript-eslint/typescript-eslint/issues/1732)) ([2e9c202](https://github.com/typescript-eslint/typescript-eslint/commit/2e9c2028a8a0b226e0f87d4bcc997fa259ca3ebd)) +* **eslint-plugin:** [explicit-module-boundary-types] add optio… ([#1778](https://github.com/typescript-eslint/typescript-eslint/issues/1778)) ([3eee804](https://github.com/typescript-eslint/typescript-eslint/commit/3eee804461d017ea6189cd7f64fcd473623684b4)) +* **eslint-plugin:** [no-base-to-string] add option to ignore tagged templates ([#1763](https://github.com/typescript-eslint/typescript-eslint/issues/1763)) ([f5edb99](https://github.com/typescript-eslint/typescript-eslint/commit/f5edb9938c33f8b68f026eba00db3abe9359ced3)) +* **eslint-plugin:** [restrict-template-expressions] add option `allowAny` ([#1762](https://github.com/typescript-eslint/typescript-eslint/issues/1762)) ([d44c0f9](https://github.com/typescript-eslint/typescript-eslint/commit/d44c0f9bed2404ca00b020b35fd825929e213398)) +* **eslint-plugin:** add rule `prefer-reduce-type-parameter` ([#1707](https://github.com/typescript-eslint/typescript-eslint/issues/1707)) ([c92d240](https://github.com/typescript-eslint/typescript-eslint/commit/c92d240e49113779053eac32038382b282812afc)) +* **eslint-plugin:** add rule `prefer-ts-expect-error` ([#1705](https://github.com/typescript-eslint/typescript-eslint/issues/1705)) ([7021f21](https://github.com/typescript-eslint/typescript-eslint/commit/7021f2151a25db2a8edf17e06cd6f21e90761ec8)) +* **eslint-plugin:** add rule no-unsafe-assignment ([#1694](https://github.com/typescript-eslint/typescript-eslint/issues/1694)) ([a49b860](https://github.com/typescript-eslint/typescript-eslint/commit/a49b860cbbb2c7d718b99f561e2fb6eaadf16f17)) + + + + + +# [2.27.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.26.0...v2.27.0) (2020-04-06) + + +### Bug Fixes + +* **eslint-plugin:** [no-throw-literal] fix crash caused by getBaseTypes ([#1830](https://github.com/typescript-eslint/typescript-eslint/issues/1830)) ([9d53c76](https://github.com/typescript-eslint/typescript-eslint/commit/9d53c761983dd964109b9f13eb9bfe20caf9defb)) +* **eslint-plugin:** [no-unsafe-call] fix incorrect selector ([#1826](https://github.com/typescript-eslint/typescript-eslint/issues/1826)) ([8ec53a3](https://github.com/typescript-eslint/typescript-eslint/commit/8ec53a3579fcb59cdffea0c60fbb755d056f4c8a)) +* **eslint-plugin:** [require-await] handle async generators ([#1782](https://github.com/typescript-eslint/typescript-eslint/issues/1782)) ([9642d9d](https://github.com/typescript-eslint/typescript-eslint/commit/9642d9dce693befac89a4e9d8bf8dd18f4361e2a)) +* **eslint-plugin:** no-explicit-any constructor functions (& mo… ([#1711](https://github.com/typescript-eslint/typescript-eslint/issues/1711)) ([ab8572e](https://github.com/typescript-eslint/typescript-eslint/commit/ab8572e30e14ebda91c8437be5ee35e7dc9add2e)) +* **typescript-estree:** add support for TS3.9 extra file extensions ([#1833](https://github.com/typescript-eslint/typescript-eslint/issues/1833)) ([1f0ff41](https://github.com/typescript-eslint/typescript-eslint/commit/1f0ff41aa1bc3b7c5330b2f5fe22e24bf578a6b2)) + + +### Features + +* **eslint-plugin:** new rule method-signature-style ([#1685](https://github.com/typescript-eslint/typescript-eslint/issues/1685)) ([c49d771](https://github.com/typescript-eslint/typescript-eslint/commit/c49d771ba62f1a21d3c1aec106341daddfcd3c9a)) +* **eslint-plugin:** sort members alphabetically ([#263](https://github.com/typescript-eslint/typescript-eslint/issues/263)) ([485e902](https://github.com/typescript-eslint/typescript-eslint/commit/485e90213a0f8baac0587f7d56925448883fc5bd)) +* **eslint-plugin-internal:** add plugin-test-formatting rule ([#1821](https://github.com/typescript-eslint/typescript-eslint/issues/1821)) ([9b0023a](https://github.com/typescript-eslint/typescript-eslint/commit/9b0023a4996ecdd7dfcb30abd1678091a78f3064)) +* **experimental-utils:** add types for suggestions from CLIEngine ([#1844](https://github.com/typescript-eslint/typescript-eslint/issues/1844)) ([7c11bd6](https://github.com/typescript-eslint/typescript-eslint/commit/7c11bd66f2d0e5ea9d3943e6b8c66e6ddff50862)) +* **experimental-utils:** update eslint types to match v6.8 ([#1846](https://github.com/typescript-eslint/typescript-eslint/issues/1846)) ([16ce74d](https://github.com/typescript-eslint/typescript-eslint/commit/16ce74d247781ac890dc0baa30c384f97e581b6b)) + + + + + +# [2.26.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.25.0...v2.26.0) (2020-03-30) + + +### Bug Fixes + +* **eslint-plugin:** [no-explicit-any] error with ignoreRestArgs ([#1796](https://github.com/typescript-eslint/typescript-eslint/issues/1796)) ([638d84d](https://github.com/typescript-eslint/typescript-eslint/commit/638d84ddd77d07117b3ec7c5431f3b0e44b1995d)) +* **eslint-plugin:** [no-unsafe-call] allow import expressions ([#1800](https://github.com/typescript-eslint/typescript-eslint/issues/1800)) ([4fa7107](https://github.com/typescript-eslint/typescript-eslint/commit/4fa710754ecc412b65ac3864fe0c7857c254ac1b)) +* **eslint-plugin:** [no-unsafe-return] error with (): ``` diff --git a/docs/getting-started/README.md b/docs/getting-started/README.md index f3abf26f1c4..27a99d4b9fa 100644 --- a/docs/getting-started/README.md +++ b/docs/getting-started/README.md @@ -6,6 +6,6 @@ The goal of these docs are to give you a quick overview of the project and all o The docs are broken down into the following categories: -### [I want to lint my TypeScript codebase.](./linting/README.md) +## [I want to lint my TypeScript codebase.](./linting/README.md) -### [(TODO) I want to write an ESLint plugin in TypeScript.](./plugin-development/README.md) +## [(TODO) I want to write an ESLint plugin in TypeScript.](./plugin-development/README.md) diff --git a/lerna.json b/lerna.json index f7a5aa4c731..dde84901cc1 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.25.0", + "version": "2.28.0", "npmClient": "yarn", "useWorkspaces": true, "stream": true diff --git a/package.json b/package.json index db9c7201528..562b5cd9370 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,8 @@ "kill-integration-test-containers": "docker-compose -f tests/integration/docker-compose.yml down -v --rmi local", "lint": "eslint . --ext .js,.ts", "lint-fix": "eslint . --ext .js,.ts --fix", + "lint:markdown": "markdownlint '**/*.md' --config=.markdownlint.json --ignore-path=.markdownlintignore", + "lint:markdown:fix": "lint:markdown --fix", "pre-commit": "yarn lint-staged", "pre-push": "yarn format-check", "postinstall": "lerna bootstrap && yarn build && lerna link", @@ -70,10 +72,11 @@ "jest": "^25.1.0", "lerna": "^3.20.2", "lint-staged": "^9.4.3", + "markdownlint-cli": "^0.22.0", "prettier": "^1.19.1", "ts-jest": "^25.0.0", "ts-node": "^8.5.0", - "tslint": "^5.20.1", + "tslint": "^6.1.0", "typescript": ">=3.2.1 <3.9.0" }, "resolutions": { diff --git a/packages/eslint-plugin-internal/CHANGELOG.md b/packages/eslint-plugin-internal/CHANGELOG.md index 42a8af8be87..c86faff06c0 100644 --- a/packages/eslint-plugin-internal/CHANGELOG.md +++ b/packages/eslint-plugin-internal/CHANGELOG.md @@ -3,6 +3,33 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.28.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.27.0...v2.28.0) (2020-04-13) + +**Note:** Version bump only for package @typescript-eslint/eslint-plugin-internal + + + + + +# [2.27.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.26.0...v2.27.0) (2020-04-06) + + +### Features + +* **eslint-plugin-internal:** add plugin-test-formatting rule ([#1821](https://github.com/typescript-eslint/typescript-eslint/issues/1821)) ([9b0023a](https://github.com/typescript-eslint/typescript-eslint/commit/9b0023a4996ecdd7dfcb30abd1678091a78f3064)) + + + + + +# [2.26.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.25.0...v2.26.0) (2020-03-30) + +**Note:** Version bump only for package @typescript-eslint/eslint-plugin-internal + + + + + # [2.25.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.24.0...v2.25.0) (2020-03-23) **Note:** Version bump only for package @typescript-eslint/eslint-plugin-internal diff --git a/packages/eslint-plugin-internal/package.json b/packages/eslint-plugin-internal/package.json index afbc489b9ba..40e465ce4eb 100644 --- a/packages/eslint-plugin-internal/package.json +++ b/packages/eslint-plugin-internal/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin-internal", - "version": "2.25.0", + "version": "2.28.0", "private": true, "main": "dist/index.js", "scripts": { @@ -12,6 +12,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/experimental-utils": "2.25.0" + "@typescript-eslint/experimental-utils": "2.28.0", + "prettier": "*" } } diff --git a/packages/eslint-plugin-internal/src/rules/index.ts b/packages/eslint-plugin-internal/src/rules/index.ts index f781e01e6df..eb8d8efe852 100644 --- a/packages/eslint-plugin-internal/src/rules/index.ts +++ b/packages/eslint-plugin-internal/src/rules/index.ts @@ -1,9 +1,11 @@ import noTypescriptDefaultImport from './no-typescript-default-import'; import noTypescriptEstreeImport from './no-typescript-estree-import'; +import pluginTestFormatting from './plugin-test-formatting'; import preferASTTypesEnum from './prefer-ast-types-enum'; export default { 'no-typescript-default-import': noTypescriptDefaultImport, 'no-typescript-estree-import': noTypescriptEstreeImport, + 'plugin-test-formatting': pluginTestFormatting, 'prefer-ast-types-enum': preferASTTypesEnum, }; diff --git a/packages/eslint-plugin-internal/src/rules/plugin-test-formatting.ts b/packages/eslint-plugin-internal/src/rules/plugin-test-formatting.ts new file mode 100644 index 00000000000..bc26d9d9d9a --- /dev/null +++ b/packages/eslint-plugin-internal/src/rules/plugin-test-formatting.ts @@ -0,0 +1,508 @@ +import { + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; +import { format, resolveConfig } from 'prettier'; +import { createRule } from '../util'; + +/* +The strings that are used for eslint plugins will not be checked for formatting. +This can lead to diff noise as one contributor adjusts formatting, uses different quotes, etc. + +This rule just enforces the following: +- all code samples are formatted with prettier +- all single line tests do not use backticks +- all multiline tests have: + - no code on the first line + - no code on the last line + - the closing backtick indentation === property indentation + - one of the following indentations: + - no indentation at all + - indentation of 1 + object indent + +eg: +[ + 'const a = 1;', + ` +const a = 1; + `, + ` + const a = 1; + `, + { + code: 'const a = 1', + } + { + code: ` +const a = 1; + `, + } + { + code: ` + const a = 1; + `, + } +] +*/ + +const prettierConfig = resolveConfig.sync(__dirname) ?? {}; +const START_OF_LINE_WHITESPACE_MATCHER = /^([ ]*)/; +const BACKTICK_REGEX = /`/g; +const TEMPLATE_EXPR_OPENER = /\$\{/g; + +function getExpectedIndentForNode( + node: TSESTree.Node, + sourceCodeLines: string[], +): number { + const lineIdx = node.loc.start.line - 1; + const indent = START_OF_LINE_WHITESPACE_MATCHER.exec( + sourceCodeLines[lineIdx], + )![1]; + return indent.length; +} +function doIndent(line: string, indent: number): string { + for (let i = 0; i < indent; i += 1) { + line = ' ' + line; + } + return line; +} + +function getQuote(code: string): "'" | '"' | null { + const hasSingleQuote = code.includes("'"); + const hasDoubleQuote = code.includes('"'); + if (hasSingleQuote && hasDoubleQuote) { + // be lazy and make them fix and escape the quotes manually + return null; + } + + return hasSingleQuote ? '"' : "'"; +} + +function escapeTemplateString(code: string): string { + let fixed = code; + fixed = fixed.replace(BACKTICK_REGEX, '\\`'); + fixed = fixed.replace(TEMPLATE_EXPR_OPENER, '\\${'); + return fixed; +} + +type Options = [ + { + // This option exists so that rules like type-annotation-spacing can exist without every test needing a prettier-ignore + formatWithPrettier?: boolean; + }, +]; + +type MessageIds = + | 'invalidFormatting' + | 'invalidFormattingErrorTest' + | 'singleLineQuotes' + | 'templateLiteralEmptyEnds' + | 'templateLiteralLastLineIndent' + | 'templateStringRequiresIndent' + | 'templateStringMinimumIndent' + | 'prettierException'; + +export default createRule({ + name: 'plugin-test-formatting', + meta: { + type: 'problem', + docs: { + description: `Enforces that eslint-plugin test snippets are correctly formatted`, + category: 'Stylistic Issues', + recommended: 'error', + }, + fixable: 'code', + schema: [ + { + type: 'object', + additionalProperties: false, + properties: { + formatWithPrettier: { + type: 'boolean', + }, + }, + }, + ], + messages: { + invalidFormatting: + 'This snippet should be formatted correctly. Use the fixer to format the code.', + invalidFormattingErrorTest: + 'This snippet should be formatted correctly. Use the fixer to format the code. Note that the automated fixer may break your test locations.', + singleLineQuotes: 'Use quotes (\' or ") for single line tests.', + templateLiteralEmptyEnds: + 'Template literals must start and end with an empty line.', + templateLiteralLastLineIndent: + 'The closing line of the template literal must be indented to align with its parent.', + templateStringRequiresIndent: + 'Test code should either have no indent, or be indented {{indent}} spaces.', + templateStringMinimumIndent: + 'Test code should be indented at least {{indent}} spaces.', + prettierException: + 'Prettier was unable to format this snippet: {{message}}', + }, + }, + defaultOptions: [ + { + formatWithPrettier: true, + }, + ], + create(context, [{ formatWithPrettier }]) { + const sourceCode = context.getSourceCode(); + + function prettierFormat( + code: string, + location: TSESTree.Node, + ): string | null { + if (formatWithPrettier === false) { + return null; + } + + try { + return format(code, { + ...prettierConfig, + parser: 'typescript', + }).trimRight(); // prettier will insert a new line at the end of the code + } catch (ex) { + // adapted from https://github.com/prettier/eslint-plugin-prettier/blob/185b1064d3dd674538456fb2fad97fbfcde49e0d/eslint-plugin-prettier.js#L242-L257 + if (!(ex instanceof SyntaxError)) { + throw ex; + } + const err = ex as Error & { + codeFrame: string; + loc?: unknown; + }; + + let message = err.message; + + if (err.codeFrame) { + message = message.replace(`\n${err.codeFrame}`, ''); + } + if (err.loc) { + message = message.replace(/ \(\d+:\d+\)$/, ''); + } + + context.report({ + node: location, + messageId: 'prettierException', + data: { + message, + }, + }); + return null; + } + } + + function checkExpression(node: TSESTree.Node, isErrorTest: boolean): void { + switch (node.type) { + case AST_NODE_TYPES.Literal: + checkLiteral(node, isErrorTest); + break; + + case AST_NODE_TYPES.TemplateLiteral: + checkTemplateLiteral(node, isErrorTest); + break; + + case AST_NODE_TYPES.TaggedTemplateExpression: + checkTaggedTemplateExpression(node, isErrorTest); + break; + + case AST_NODE_TYPES.CallExpression: + checkCallExpression(node, isErrorTest); + break; + } + } + + function checkLiteral( + literal: TSESTree.Literal, + isErrorTest: boolean, + quoteIn?: string, + ): void { + if (typeof literal.value === 'string') { + const output = prettierFormat(literal.value, literal); + if (output && output !== literal.value) { + context.report({ + node: literal, + messageId: isErrorTest + ? 'invalidFormattingErrorTest' + : 'invalidFormatting', + fix(fixer) { + if (output.includes('\n')) { + // formatted string is multiline, then have to use backticks + return fixer.replaceText( + literal, + `\`${escapeTemplateString(output)}\``, + ); + } + + const quote = quoteIn ?? getQuote(output); + if (quote == null) { + return null; + } + + return fixer.replaceText(literal, `${quote}${output}${quote}`); + }, + }); + } + } + } + + function checkTemplateLiteral( + literal: TSESTree.TemplateLiteral, + isErrorTest: boolean, + isNoFormatTagged = false, + ): void { + if (literal.quasis.length > 1) { + // ignore template literals with ${expressions} for simplicity + return; + } + + const text = literal.quasis[0].value.cooked; + + if (literal.loc.end.line === literal.loc.start.line) { + // don't use template strings for single line tests + return context.report({ + node: literal, + messageId: 'singleLineQuotes', + fix(fixer) { + const quote = getQuote(text); + if (quote == null) { + return null; + } + + return [ + fixer.replaceTextRange( + [literal.range[0], literal.range[0] + 1], + quote, + ), + fixer.replaceTextRange( + [literal.range[1] - 1, literal.range[1]], + quote, + ), + ]; + }, + }); + } + + const lines = text.split('\n'); + const lastLine = lines[lines.length - 1]; + // prettier will trim out the end of line on save, but eslint will check before then + const isStartEmpty = lines[0].trimRight() === ''; + // last line can be indented + const isEndEmpty = lastLine.trimLeft() === ''; + if (!isStartEmpty || !isEndEmpty) { + // multiline template strings must have an empty first/last line + return context.report({ + node: literal, + messageId: 'templateLiteralEmptyEnds', + *fix(fixer) { + if (!isStartEmpty) { + yield fixer.replaceTextRange( + [literal.range[0], literal.range[0] + 1], + '`\n', + ); + } + + if (!isEndEmpty) { + yield fixer.replaceTextRange( + [literal.range[1] - 1, literal.range[1]], + '\n`', + ); + } + }, + }); + } + + const parentIndent = getExpectedIndentForNode(literal, sourceCode.lines); + if (lastLine.length !== parentIndent) { + return context.report({ + node: literal, + messageId: 'templateLiteralLastLineIndent', + fix(fixer) { + return fixer.replaceTextRange( + [literal.range[1] - lastLine.length - 1, literal.range[1]], + doIndent('`', parentIndent), + ); + }, + }); + } + + // remove the empty lines + lines.pop(); + lines.shift(); + + // +2 because we expect the string contents are indented one level + const expectedIndent = parentIndent + 2; + + const firstLineIndent = START_OF_LINE_WHITESPACE_MATCHER.exec( + lines[0], + )![1]; + const requiresIndent = firstLineIndent.length > 0; + if (requiresIndent) { + if (firstLineIndent.length !== expectedIndent) { + return context.report({ + node: literal, + messageId: 'templateStringRequiresIndent', + data: { + indent: expectedIndent, + }, + }); + } + + // quick-and-dirty validation that lines are roughly indented correctly + for (const line of lines) { + if (line.length === 0) { + // empty lines are valid + continue; + } + + const matches = START_OF_LINE_WHITESPACE_MATCHER.exec(line)!; + + const indent = matches[1]; + if (indent.length < expectedIndent) { + return context.report({ + node: literal, + messageId: 'templateStringMinimumIndent', + data: { + indent: expectedIndent, + }, + }); + } + } + + // trim the lines to remove expectedIndent characters from the start + // this makes it easier to check formatting + for (let i = 0; i < lines.length; i += 1) { + lines[i] = lines[i].substring(expectedIndent); + } + } + + if (isNoFormatTagged) { + return; + } + + const code = lines.join('\n'); + const formatted = prettierFormat(code, literal); + if (formatted && formatted !== code) { + const formattedIndented = requiresIndent + ? formatted + .split('\n') + .map(l => doIndent(l, expectedIndent)) + .join('\n') + : formatted; + + return context.report({ + node: literal, + messageId: isErrorTest + ? 'invalidFormattingErrorTest' + : 'invalidFormatting', + fix(fixer) { + return fixer.replaceText( + literal, + `\`\n${escapeTemplateString(formattedIndented)}\n${doIndent( + '', + parentIndent, + )}\``, + ); + }, + }); + } + } + + function isNoFormatTemplateTag(tag: TSESTree.Expression): boolean { + return tag.type === AST_NODE_TYPES.Identifier && tag.name === 'noFormat'; + } + + function checkTaggedTemplateExpression( + expr: TSESTree.TaggedTemplateExpression, + isErrorTest: boolean, + ): void { + if (!isNoFormatTemplateTag(expr.tag)) { + return; + } + + if (expr.loc.start.line === expr.loc.end.line) { + // all we do on single line test cases is check format, but there's no formatting to do + return; + } + + checkTemplateLiteral( + expr.quasi, + isErrorTest, + isNoFormatTemplateTag(expr.tag), + ); + } + + function checkCallExpression( + callExpr: TSESTree.CallExpression, + isErrorTest: boolean, + ): void { + if (callExpr.callee.type !== AST_NODE_TYPES.MemberExpression) { + return; + } + const memberExpr = callExpr.callee; + // handle cases like 'aa'.trimRight and `aa`.trimRight() + checkExpression(memberExpr.object, isErrorTest); + } + + function checkInvalidTest( + test: TSESTree.ObjectExpression, + isErrorTest = true, + ): void { + for (const prop of test.properties) { + if ( + prop.type !== AST_NODE_TYPES.Property || + prop.computed || + prop.key.type !== AST_NODE_TYPES.Identifier + ) { + continue; + } + + if (prop.key.name === 'code' || prop.key.name === 'output') { + checkExpression(prop.value, isErrorTest); + } + } + } + + function checkValidTest(tests: TSESTree.ArrayExpression): void { + for (const test of tests.elements) { + switch (test.type) { + case AST_NODE_TYPES.ObjectExpression: + // delegate object-style tests to the invalid checker + checkInvalidTest(test, false); + break; + + default: + checkExpression(test, false); + break; + } + } + } + + const invalidTestsSelectorPath = [ + AST_NODE_TYPES.CallExpression, + AST_NODE_TYPES.ObjectExpression, + 'Property[key.name = "invalid"]', + AST_NODE_TYPES.ArrayExpression, + AST_NODE_TYPES.ObjectExpression, + ]; + + return { + // valid + 'CallExpression > ObjectExpression > Property[key.name = "valid"] > ArrayExpression': checkValidTest, + // invalid - errors + [invalidTestsSelectorPath.join(' > ')]: checkInvalidTest, + // invalid - suggestions + [[ + ...invalidTestsSelectorPath, + 'Property[key.name = "errors"]', + AST_NODE_TYPES.ArrayExpression, + AST_NODE_TYPES.ObjectExpression, + 'Property[key.name = "suggestions"]', + AST_NODE_TYPES.ArrayExpression, + AST_NODE_TYPES.ObjectExpression, + ].join(' > ')]: checkInvalidTest, + // special case for our batchedSingleLineTests utility + 'CallExpression[callee.name = "batchedSingleLineTests"] > ObjectExpression': checkInvalidTest, + }; + }, +}); diff --git a/packages/eslint-plugin-internal/tests/rules/no-typescript-estree.test.ts b/packages/eslint-plugin-internal/tests/rules/no-typescript-estree.test.ts index a76e03b7c1d..74b9478b58a 100644 --- a/packages/eslint-plugin-internal/tests/rules/no-typescript-estree.test.ts +++ b/packages/eslint-plugin-internal/tests/rules/no-typescript-estree.test.ts @@ -10,20 +10,20 @@ const ruleTester = new RuleTester({ ruleTester.run('no-typescript-estree-import', rule, { valid: [ - 'import { foo } from "@typescript-eslint/experimental-utils";', - 'import foo from "@typescript-eslint/experimental-utils";', - 'import * as foo from "@typescript-eslint/experimental-utils";', + "import { foo } from '@typescript-eslint/experimental-utils';", + "import foo from '@typescript-eslint/experimental-utils';", + "import * as foo from '@typescript-eslint/experimental-utils';", ], invalid: batchedSingleLineTests({ code: ` -import { foo } from "@typescript-eslint/typescript-estree"; -import foo from "@typescript-eslint/typescript-estree"; -import * as foo from "@typescript-eslint/typescript-estree"; +import { foo } from '@typescript-eslint/typescript-estree'; +import foo from '@typescript-eslint/typescript-estree'; +import * as foo from '@typescript-eslint/typescript-estree'; `, output: ` -import { foo } from "@typescript-eslint/experimental-utils"; -import foo from "@typescript-eslint/experimental-utils"; -import * as foo from "@typescript-eslint/experimental-utils"; +import { foo } from '@typescript-eslint/experimental-utils'; +import foo from '@typescript-eslint/experimental-utils'; +import * as foo from '@typescript-eslint/experimental-utils'; `, errors: [ { diff --git a/packages/eslint-plugin-internal/tests/rules/plugin-test-formatting.test.ts b/packages/eslint-plugin-internal/tests/rules/plugin-test-formatting.test.ts new file mode 100644 index 00000000000..c2af09bec90 --- /dev/null +++ b/packages/eslint-plugin-internal/tests/rules/plugin-test-formatting.test.ts @@ -0,0 +1,519 @@ +import rule from '../../src/rules/plugin-test-formatting'; +import { RuleTester } from '../RuleTester'; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', + parserOptions: { + sourceType: 'module', + }, +}); + +const CODE_INDENT = ' '; +const PARENT_INDENT = ' '; +function wrap(strings: TemplateStringsArray, ...keys: string[]): string { + const lastIndex = strings.length - 1; + const code = + strings.slice(0, lastIndex).reduce((p, s, i) => p + s + keys[i], '') + + strings[lastIndex]; + return ` +ruleTester.run({ + valid: [ + { + code: ${code}, + }, + ], +}); + `; +} +function wrapWithOutput( + strings: TemplateStringsArray, + ...keys: string[] +): string { + const lastIndex = strings.length - 1; + const code = + strings.slice(0, lastIndex).reduce((p, s, i) => p + s + keys[i], '') + + strings[lastIndex]; + return ` +ruleTester.run({ + invalid: [ + { + code: ${code}, + output: ${code}, + }, + ], +}); + `; +} + +ruleTester.run('plugin-test-formatting', rule, { + valid: [ + // sanity check for valid tests non-object style + ` +ruleTester.run({ + valid: [ + 'const a = 1;', + \` + const a = 1; + \`, + \` +const a = 1; + \`, + noFormat\`const x=1;\`, + ], +}); + `, + wrap`'const a = 1;'`, + wrap`\` +${CODE_INDENT}const a = 1; +${PARENT_INDENT}\``, + wrap`\` +const a = 1; +${PARENT_INDENT}\``, + wrap`noFormat\`const a = 1;\``, + // sanity check suggestion validation + // eslint-disable-next-line @typescript-eslint/internal/plugin-test-formatting + ` + ruleTester.run({ + invalid: [ + { + code: 'const a = 1;', + output: 'const a = 1;', + errors: [ + { + messageId: 'foo', + suggestions: [ + { + messageId: 'bar', + output: 'const a = 1;', + }, + ], + } + ] + }, + { + code: \` + const a = 1; + \`, + output: \` + const a = 1; + \`, + errors: [ + { + messageId: 'foo', + suggestions: [ + { + messageId: 'bar', + output: \` + const a = 1; + \`, + }, + ], + } + ] + }, + { + code: \` +const a = 1; + \`, + output: \` +const a = 1; + \`, + errors: [ + { + messageId: 'foo', + suggestions: [ + { + messageId: 'bar', + output: \` +const a = 1; + \`, + }, + ], + } + ] + }, + ], + }); + `, + + // test the only option + { + code: wrap`'const x=1;'`, + options: [ + { + formatWithPrettier: false, + }, + ], + }, + + // empty linems are valid when everything else is indented + wrap`\` +${CODE_INDENT}const a = 1; + +${CODE_INDENT}const b = 1; +${PARENT_INDENT}\``, + ], + invalid: [ + // Literal + { + code: wrap`'const a=1;'`, + output: wrap`'const a = 1;'`, + errors: [ + { + messageId: 'invalidFormatting', + }, + ], + }, + { + code: wrap`'const a="1";'`, + output: wrap`"const a = '1';"`, + errors: [ + { + messageId: 'invalidFormatting', + }, + ], + }, + { + code: wrap`"const a='1';"`, + output: wrap`"const a = '1';"`, + errors: [ + { + messageId: 'invalidFormatting', + }, + ], + }, + { + code: wrap`'for (const x of y) {}'`, + output: wrap`\`for (const x of y) { +}\``, + errors: [ + { + messageId: 'invalidFormatting', + }, + ], + }, + { + code: wrap`'for (const x of \`asdf\`) {}'`, + // make sure it escapes the backticks + output: wrap`\`for (const x of \\\`asdf\\\`) { +}\``, + errors: [ + { + messageId: 'invalidFormatting', + }, + ], + }, + // TemplateLiteral + // singleLineQuotes + { + code: wrap`\`const a = 1;\``, + output: wrap`'const a = 1;'`, + errors: [ + { + messageId: 'singleLineQuotes', + }, + ], + }, + { + code: wrap`\`const a = '1'\``, + output: wrap`"const a = '1'"`, + errors: [ + { + messageId: 'singleLineQuotes', + }, + ], + }, + { + code: wrap`\`const a = "1";\``, + output: wrap`'const a = "1";'`, + errors: [ + { + messageId: 'singleLineQuotes', + }, + ], + }, + // templateLiteralEmptyEnds + { + code: wrap`\`const a = "1"; +${PARENT_INDENT}\``, + output: wrap`\` +const a = "1"; +${PARENT_INDENT}\``, + errors: [ + { + messageId: 'templateLiteralEmptyEnds', + }, + ], + }, + { + code: wrap`\` +${CODE_INDENT}const a = "1";\``, + output: wrap`\` +${CODE_INDENT}const a = "1"; +\``, + errors: [ + { + messageId: 'templateLiteralEmptyEnds', + }, + ], + }, + { + code: wrap`\`const a = "1"; +${CODE_INDENT}const b = "2";\``, + output: wrap`\` +const a = "1"; +${CODE_INDENT}const b = "2"; +\``, + errors: [ + { + messageId: 'templateLiteralEmptyEnds', + }, + ], + }, + // templateLiteralLastLineIndent + { + code: wrap`\` +${CODE_INDENT}const a = "1"; +\``, + output: wrap`\` +${CODE_INDENT}const a = "1"; +${PARENT_INDENT}\``, + errors: [ + { + messageId: 'templateLiteralLastLineIndent', + }, + ], + }, + { + code: wrap`\` +${CODE_INDENT}const a = "1"; + \``, + output: wrap`\` +${CODE_INDENT}const a = "1"; +${PARENT_INDENT}\``, + errors: [ + { + messageId: 'templateLiteralLastLineIndent', + }, + ], + }, + // templateStringRequiresIndent + { + code: wrap`\` + const a = "1"; +${PARENT_INDENT}\``, + errors: [ + { + messageId: 'templateStringRequiresIndent', + data: { + indent: CODE_INDENT.length, + }, + }, + ], + }, + { + code: ` +ruleTester.run({ + valid: [ + \` + const a = "1"; + \`, + ], +}); + `, + errors: [ + { + messageId: 'templateStringRequiresIndent', + data: { + indent: 6, + }, + }, + ], + }, + // templateStringMinimumIndent + { + code: wrap`\` +${CODE_INDENT}const a = "1"; + const b = "2"; +${PARENT_INDENT}\``, + errors: [ + { + messageId: 'templateStringMinimumIndent', + data: { + indent: CODE_INDENT.length, + }, + }, + ], + }, + // invalidFormatting + { + code: wrap`\` +${CODE_INDENT}const a="1"; +${CODE_INDENT} const b = "2"; +${PARENT_INDENT}\``, + output: wrap`\` +${CODE_INDENT}const a = '1'; +${CODE_INDENT}const b = '2'; +${PARENT_INDENT}\``, + errors: [ + { + messageId: 'invalidFormatting', + }, + ], + }, + { + code: wrap`\` +${CODE_INDENT}const a=\\\`\\\${a}\\\`; +${PARENT_INDENT}\``, + // make sure it escapes backticks + output: wrap`\` +${CODE_INDENT}const a = \\\`\\\${a}\\\`; +${PARENT_INDENT}\``, + errors: [ + { + messageId: 'invalidFormatting', + }, + ], + }, + + // sanity check that it runs on both output and code properties + { + code: wrapWithOutput`\` +${CODE_INDENT}const a="1"; +${CODE_INDENT} const b = "2"; +${PARENT_INDENT}\``, + output: wrapWithOutput`\` +${CODE_INDENT}const a = '1'; +${CODE_INDENT}const b = '2'; +${PARENT_INDENT}\``, + errors: [ + { + messageId: 'invalidFormattingErrorTest', + }, + { + messageId: 'invalidFormattingErrorTest', + }, + ], + }, + + // sanity check that it handles suggestion output + { + code: ` +ruleTester.run({ + valid: [], + invalid: [ + { + code: 'const x=1;', + errors: [ + { + messageId: 'foo', + suggestions: [ + { + messageId: 'bar', + output: 'const x=1;', + }, + ], + }, + ], + }, + ], +}); + `, + errors: [ + { + messageId: 'invalidFormattingErrorTest', + }, + { + messageId: 'invalidFormattingErrorTest', + }, + ], + }, + + // sanity check that it runs on all tests + { + code: ` +ruleTester.run({ + valid: [ + { + code: \`foo\`, + }, + { + code: \`foo +\`, + }, + { + code: \` + foo\`, + }, + ], + invalid: [ + { + code: \`foo\`, + }, + { + code: \`foo +\`, + }, + { + code: \` + foo\`, + }, + ], +}); + `, + errors: [ + { + messageId: 'singleLineQuotes', + }, + { + messageId: 'templateLiteralEmptyEnds', + }, + { + messageId: 'templateLiteralEmptyEnds', + }, + { + messageId: 'singleLineQuotes', + }, + { + messageId: 'templateLiteralEmptyEnds', + }, + { + messageId: 'templateLiteralEmptyEnds', + }, + ], + }, + + // handles prettier errors + { + code: wrap`'const x = ";'`, + errors: [ + { + messageId: 'prettierException', + data: { + message: 'Unterminated string literal.', + }, + }, + ], + }, + + // checks tests with .trimRight calls + { + code: wrap`'const a=1;'.trimRight()`, + output: wrap`'const a = 1;'.trimRight()`, + errors: [ + { + messageId: 'invalidFormatting', + }, + ], + }, + { + code: wrap`\`const a = "1"; +${CODE_INDENT}\`.trimRight()`, + errors: [ + { + messageId: 'templateLiteralEmptyEnds', + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin-internal/tests/rules/prefer-ast-types-enum.test.ts b/packages/eslint-plugin-internal/tests/rules/prefer-ast-types-enum.test.ts index 2688e035b59..c2ffd8e489e 100644 --- a/packages/eslint-plugin-internal/tests/rules/prefer-ast-types-enum.test.ts +++ b/packages/eslint-plugin-internal/tests/rules/prefer-ast-types-enum.test.ts @@ -14,29 +14,29 @@ const ruleTester = new RuleTester({ ruleTester.run('prefer-ast-types-enum', rule, { valid: [ - 'node.type === "constructor"', - 'node.type === AST_NODE_TYPES.Literal', - 'node.type === AST_TOKEN_TYPES.Keyword', - 'node.type === 1', + "node.type === 'constructor';", + 'node.type === AST_NODE_TYPES.Literal;', + 'node.type === AST_TOKEN_TYPES.Keyword;', + 'node.type === 1;', ` - enum MY_ENUM { - Literal = 1 - } + enum MY_ENUM { + Literal = 1, + } `, ` - enum AST_NODE_TYPES { - Literal = 'Literal' - } + enum AST_NODE_TYPES { + Literal = 'Literal', + } `, ], invalid: batchedSingleLineTests({ code: ` -node.type === 'Literal' -node.type === 'Keyword' +node.type === 'Literal'; +node.type === 'Keyword'; `, output: ` -node.type === AST_NODE_TYPES.Literal -node.type === AST_TOKEN_TYPES.Keyword +node.type === AST_NODE_TYPES.Literal; +node.type === AST_TOKEN_TYPES.Keyword; `, errors: [ { diff --git a/packages/eslint-plugin-tslint/CHANGELOG.md b/packages/eslint-plugin-tslint/CHANGELOG.md index 38770408378..3c242a42c63 100644 --- a/packages/eslint-plugin-tslint/CHANGELOG.md +++ b/packages/eslint-plugin-tslint/CHANGELOG.md @@ -3,6 +3,33 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.28.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.27.0...v2.28.0) (2020-04-13) + +**Note:** Version bump only for package @typescript-eslint/eslint-plugin-tslint + + + + + +# [2.27.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.26.0...v2.27.0) (2020-04-06) + +**Note:** Version bump only for package @typescript-eslint/eslint-plugin-tslint + + + + + +# [2.26.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.25.0...v2.26.0) (2020-03-30) + + +### Features + +* **eslint-plugin-tslint:** support tslint 6 ([#1809](https://github.com/typescript-eslint/typescript-eslint/issues/1809)) ([7d963fd](https://github.com/typescript-eslint/typescript-eslint/commit/7d963fd846935acd91b7b0cd31c56a70a2b994d1)) + + + + + # [2.25.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.24.0...v2.25.0) (2020-03-23) diff --git a/packages/eslint-plugin-tslint/package.json b/packages/eslint-plugin-tslint/package.json index a10074080b2..158d9a84b15 100644 --- a/packages/eslint-plugin-tslint/package.json +++ b/packages/eslint-plugin-tslint/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin-tslint", - "version": "2.25.0", + "version": "2.28.0", "main": "dist/index.js", "typings": "src/index.ts", "description": "TSLint wrapper plugin for ESLint", @@ -31,16 +31,16 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/experimental-utils": "2.25.0", + "@typescript-eslint/experimental-utils": "2.28.0", "lodash": "^4.17.15" }, "peerDependencies": { "eslint": "^5.0.0 || ^6.0.0", - "tslint": "^5.0.0", + "tslint": "^5.0.0 || ^6.0.0", "typescript": "*" }, "devDependencies": { "@types/lodash": "^4.14.149", - "@typescript-eslint/parser": "2.25.0" + "@typescript-eslint/parser": "2.28.0" } } diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 074bd66c7ca..72d30f22b36 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -3,6 +3,69 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.28.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.27.0...v2.28.0) (2020-04-13) + + +### Bug Fixes + +* **eslint-plugin:** [method-signature-style] handle multiline params ([#1861](https://github.com/typescript-eslint/typescript-eslint/issues/1861)) ([5832a86](https://github.com/typescript-eslint/typescript-eslint/commit/5832a8643bbe174ec02df5966bb333e506e45f5d)) +* **eslint-plugin:** [no-empty-interface] use suggestion fixer for ambient contexts ([#1880](https://github.com/typescript-eslint/typescript-eslint/issues/1880)) ([62b2278](https://github.com/typescript-eslint/typescript-eslint/commit/62b2278aec0011c93eae17bed8b278114d3379a2)) +* **eslint-plugin:** [unbound-method] false positive on property function initializer ([#1890](https://github.com/typescript-eslint/typescript-eslint/issues/1890)) ([f1c3b18](https://github.com/typescript-eslint/typescript-eslint/commit/f1c3b18f7aadc81f7dca7aa32aa1a8fe424e04e7)) +* **eslint-plugin:** [unbound-method] ignore assignments _to_ methods ([#1736](https://github.com/typescript-eslint/typescript-eslint/issues/1736)) ([6b4680b](https://github.com/typescript-eslint/typescript-eslint/commit/6b4680b6e7343d9d98fa1de170f387a36d98b73e)) +* **eslint-plugin:** no-empty-interface autofix ([#1865](https://github.com/typescript-eslint/typescript-eslint/issues/1865)) ([829a2f7](https://github.com/typescript-eslint/typescript-eslint/commit/829a2f728f876d356908e2338c2d6620e58f9943)), closes [#1864](https://github.com/typescript-eslint/typescript-eslint/issues/1864) +* **eslint-plugin:** use `isTypeArrayTypeOrUnionOfArrayTypes` util for checking if type is array ([#1728](https://github.com/typescript-eslint/typescript-eslint/issues/1728)) ([05030f8](https://github.com/typescript-eslint/typescript-eslint/commit/05030f8d2bd5a50e95053bc61380891da71cc567)) + + +### Features + +* **eslint-plugin:** [ban-ts-comment] support `ts-expect-error` ([#1706](https://github.com/typescript-eslint/typescript-eslint/issues/1706)) ([469cff3](https://github.com/typescript-eslint/typescript-eslint/commit/469cff332c041f38f60de052769287342455cff1)) +* **eslint-plugin:** [consistent-type-assertions] always allow `const` assertions ([#1713](https://github.com/typescript-eslint/typescript-eslint/issues/1713)) ([af2c00d](https://github.com/typescript-eslint/typescript-eslint/commit/af2c00de62f7e31eaeb88996ebf3f330cc8473b9)) +* **eslint-plugin:** [explicit-function-return-type] add option to allow concise arrows that start with void ([#1732](https://github.com/typescript-eslint/typescript-eslint/issues/1732)) ([2e9c202](https://github.com/typescript-eslint/typescript-eslint/commit/2e9c2028a8a0b226e0f87d4bcc997fa259ca3ebd)) +* **eslint-plugin:** [explicit-module-boundary-types] add optio… ([#1778](https://github.com/typescript-eslint/typescript-eslint/issues/1778)) ([3eee804](https://github.com/typescript-eslint/typescript-eslint/commit/3eee804461d017ea6189cd7f64fcd473623684b4)) +* **eslint-plugin:** [no-base-to-string] add option to ignore tagged templates ([#1763](https://github.com/typescript-eslint/typescript-eslint/issues/1763)) ([f5edb99](https://github.com/typescript-eslint/typescript-eslint/commit/f5edb9938c33f8b68f026eba00db3abe9359ced3)) +* **eslint-plugin:** [restrict-template-expressions] add option `allowAny` ([#1762](https://github.com/typescript-eslint/typescript-eslint/issues/1762)) ([d44c0f9](https://github.com/typescript-eslint/typescript-eslint/commit/d44c0f9bed2404ca00b020b35fd825929e213398)) +* **eslint-plugin:** add rule `prefer-reduce-type-parameter` ([#1707](https://github.com/typescript-eslint/typescript-eslint/issues/1707)) ([c92d240](https://github.com/typescript-eslint/typescript-eslint/commit/c92d240e49113779053eac32038382b282812afc)) +* **eslint-plugin:** add rule `prefer-ts-expect-error` ([#1705](https://github.com/typescript-eslint/typescript-eslint/issues/1705)) ([7021f21](https://github.com/typescript-eslint/typescript-eslint/commit/7021f2151a25db2a8edf17e06cd6f21e90761ec8)) +* **eslint-plugin:** add rule no-unsafe-assignment ([#1694](https://github.com/typescript-eslint/typescript-eslint/issues/1694)) ([a49b860](https://github.com/typescript-eslint/typescript-eslint/commit/a49b860cbbb2c7d718b99f561e2fb6eaadf16f17)) + + + + + +# [2.27.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.26.0...v2.27.0) (2020-04-06) + + +### Bug Fixes + +* **eslint-plugin:** [no-throw-literal] fix crash caused by getBaseTypes ([#1830](https://github.com/typescript-eslint/typescript-eslint/issues/1830)) ([9d53c76](https://github.com/typescript-eslint/typescript-eslint/commit/9d53c761983dd964109b9f13eb9bfe20caf9defb)) +* **eslint-plugin:** [no-unsafe-call] fix incorrect selector ([#1826](https://github.com/typescript-eslint/typescript-eslint/issues/1826)) ([8ec53a3](https://github.com/typescript-eslint/typescript-eslint/commit/8ec53a3579fcb59cdffea0c60fbb755d056f4c8a)) +* **eslint-plugin:** [require-await] handle async generators ([#1782](https://github.com/typescript-eslint/typescript-eslint/issues/1782)) ([9642d9d](https://github.com/typescript-eslint/typescript-eslint/commit/9642d9dce693befac89a4e9d8bf8dd18f4361e2a)) +* **eslint-plugin:** no-explicit-any constructor functions (& mo… ([#1711](https://github.com/typescript-eslint/typescript-eslint/issues/1711)) ([ab8572e](https://github.com/typescript-eslint/typescript-eslint/commit/ab8572e30e14ebda91c8437be5ee35e7dc9add2e)) + + +### Features + +* **eslint-plugin:** new rule method-signature-style ([#1685](https://github.com/typescript-eslint/typescript-eslint/issues/1685)) ([c49d771](https://github.com/typescript-eslint/typescript-eslint/commit/c49d771ba62f1a21d3c1aec106341daddfcd3c9a)) +* **eslint-plugin:** sort members alphabetically ([#263](https://github.com/typescript-eslint/typescript-eslint/issues/263)) ([485e902](https://github.com/typescript-eslint/typescript-eslint/commit/485e90213a0f8baac0587f7d56925448883fc5bd)) +* **eslint-plugin-internal:** add plugin-test-formatting rule ([#1821](https://github.com/typescript-eslint/typescript-eslint/issues/1821)) ([9b0023a](https://github.com/typescript-eslint/typescript-eslint/commit/9b0023a4996ecdd7dfcb30abd1678091a78f3064)) + + + + + +# [2.26.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.25.0...v2.26.0) (2020-03-30) + + +### Bug Fixes + +* **eslint-plugin:** [no-explicit-any] error with ignoreRestArgs ([#1796](https://github.com/typescript-eslint/typescript-eslint/issues/1796)) ([638d84d](https://github.com/typescript-eslint/typescript-eslint/commit/638d84ddd77d07117b3ec7c5431f3b0e44b1995d)) +* **eslint-plugin:** [no-unsafe-call] allow import expressions ([#1800](https://github.com/typescript-eslint/typescript-eslint/issues/1800)) ([4fa7107](https://github.com/typescript-eslint/typescript-eslint/commit/4fa710754ecc412b65ac3864fe0c7857c254ac1b)) +* **eslint-plugin:** [no-unsafe-return] error with [1] Recommended config: `["error", { blankLine: "always", prev: "*", next: "return" }]`
[2] Doesn't check other control flow statements, such as `break` or `continue`. -## tslint-microsoft-contrib rules +## `tslint-microsoft-contrib` rules Rule listing is [here](https://github.com/Microsoft/tslint-microsoft-contrib#supported-rules). Deprecated rules are excluded (`missing-jsdoc`, `missing-optional-annotation`, `no-duplicate-case`, `no-duplicate-parameter-names`, `no-function-constructor-with-string-args`, `no-increment-decrement`, `no-empty-interfaces`, `no-missing-visibility-modifiers`, `no-multiple-var-decl`, `no-reserved-keywords`, `no-stateless-class`, `no-var-self`, `no-unnecessary-bind`, and `valid-typeof`). See the docs in the link above to find out what to use instead. @@ -211,7 +211,7 @@ Deprecated rules are excluded (`missing-jsdoc`, `missing-optional-annotation`, ` Relevant plugins: [`chai-expect-keywords`](https://github.com/gavinaiken/eslint-plugin-chai-expect-keywords), [`chai-expect`](https://github.com/Turbo87/eslint-plugin-chai-expect), [`chai-friendly`](https://github.com/ihordiachenko/eslint-plugin-chai-friendly), [`mocha`](https://github.com/lo1tuma/eslint-plugin-mocha), and [`jest`](https://github.com/jest-community/eslint-plugin-jest) -| tslint-microsoft-contrib rule | | ESLint rule | +| `tslint-microsoft-contrib` rule | | ESLint rule | | ---------------------------------- | :-: | ------------------------- | | `chai-prefer-contains-to-index-of` | 🛑 | N/A | | `chai-vague-errors` | 🛑 | N/A | @@ -220,16 +220,16 @@ Relevant plugins: [`chai-expect-keywords`](https://github.com/gavinaiken/eslint- ### TypeScript -| tslint-microsoft-contrib rule | | ESLint rule | -| ----------------------------- | :-: | ---------------------------------------------------------- | -| `prefer-array-literal` | 🌓 | [`@typescript-eslint/no-array-constructor`] [1] | -| `prefer-type-cast` | 🛑 | N/A | +| `tslint-microsoft-contrib` rule | | ESLint rule | +| ------------------------------- | :-: | ---------------------------------------------------------- | +| `prefer-array-literal` | 🌓 | [`@typescript-eslint/no-array-constructor`] [1] | +| `prefer-type-cast` | 🛑 | N/A | [1] ESLint rule is slightly less strict, allowing `new Array()` and `Array(2)`. ### Miscellaneous -| tslint-microsoft-contrib rule | | ESLint rule | +| `tslint-microsoft-contrib` rule | | ESLint rule | | ------------------------------------- | :-: | ---------------------------------------------------------------------- | | `export-name` | 🛑 | N/A ([relevant plugin][plugin:import]) | | `function-name` | 🛑 | N/A | @@ -274,7 +274,7 @@ Relevant plugins: [`chai-expect-keywords`](https://github.com/gavinaiken/eslint- ### Security -| tslint-microsoft-contrib rule | | ESLint rule | +| `tslint-microsoft-contrib` rule | | ESLint rule | | ------------------------------- | :-: | -------------------------------------------------- | | `no-disable-auto-sanitization` | 🛑 | N/A | | `no-document-domain` | 🌓 | Use [`no-restricted-syntax`][no-restricted-syntax] | @@ -291,7 +291,7 @@ Relevant plugins: [`chai-expect-keywords`](https://github.com/gavinaiken/eslint- ### Browser -| tslint-microsoft-contrib rule | | ESLint rule | +| `tslint-microsoft-contrib` rule | | ESLint rule | | ----------------------------------- | :-: | -------------------------------------------------- | | `jquery-deferred-must-complete` | 🛑 | N/A | | `no-backbone-get-set-outside-model` | 🛑 | N/A | @@ -306,7 +306,7 @@ Relevant plugins: [`chai-expect-keywords`](https://github.com/gavinaiken/eslint- ### React A11y -| tslint-microsoft-contrib rule | | ESLint rule | +| `tslint-microsoft-contrib` rule | | ESLint rule | | ----------------------------------------- | :-: | ---------------------------------------------------------- | | `react-a11y-accessible-headings` | 🌓 | [`jsx-a11y/heading-has-content`] [1] | | `react-a11y-anchors` | 🔌 | [`jsx-a11y/anchor-is-valid`] | @@ -338,6 +338,7 @@ Relevant plugins: [`chai-expect-keywords`](https://github.com/gavinaiken/eslint- [`adjacent-overload-signatures`]: https://palantir.github.io/tslint/rules/adjacent-overload-signatures [`ban-ts-ignore`]: https://palantir.github.io/tslint/rules/ban-ts-ignore/ [`ban-types`]: https://palantir.github.io/tslint/rules/ban-types +[`invalid-void`]: https://palantir.github.io/tslint/rules/invalid-void [`member-access`]: https://palantir.github.io/tslint/rules/member-access [`member-ordering`]: https://palantir.github.io/tslint/rules/member-ordering [`no-any`]: https://palantir.github.io/tslint/rules/no-any @@ -596,6 +597,7 @@ Relevant plugins: [`chai-expect-keywords`](https://github.com/gavinaiken/eslint- [`@typescript-eslint/consistent-type-definitions`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/consistent-type-definitions.md [`@typescript-eslint/explicit-member-accessibility`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/explicit-member-accessibility.md [`@typescript-eslint/member-ordering`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/member-ordering.md +[`@typescript-eslint/method-signature-style`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/method-signature-style.md [`@typescript-eslint/no-explicit-any`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-explicit-any.md [`@typescript-eslint/no-empty-interface`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-empty-interface.md [`@typescript-eslint/no-inferrable-types`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-inferrable-types.md diff --git a/packages/eslint-plugin/docs/rules/ban-ts-comment.md b/packages/eslint-plugin/docs/rules/ban-ts-comment.md index 58f99dc048a..cb540bc8d09 100644 --- a/packages/eslint-plugin/docs/rules/ban-ts-comment.md +++ b/packages/eslint-plugin/docs/rules/ban-ts-comment.md @@ -5,7 +5,8 @@ Using these to suppress TypeScript Compiler Errors reduces the effectiveness of The directive comments supported by TypeScript are: -``` +```ts +// @ts-expect-error // @ts-ignore // @ts-nocheck // @ts-check @@ -14,22 +15,24 @@ The directive comments supported by TypeScript are: ## Rule Details This rule lets you set which directive comments you want to allow in your codebase. -By default, only `@ts-check` is allowed, as it enables rather then suppresses errors. +By default, only `@ts-check` is allowed, as it enables rather than suppresses errors. The configuration looks like this: -``` +```ts interface Options { + 'ts-expect-error'?: boolean; 'ts-ignore'?: boolean; 'ts-nocheck'?: boolean; 'ts-check'?: boolean; } const defaultOptions: Options = { + 'ts-expect-error': true, 'ts-ignore': true, 'ts-nocheck': true, - 'ts-check': false -} + 'ts-check': false, +}; ``` A value of `true` for a particular directive means that this rule will report if it finds any usage of said directive. diff --git a/packages/eslint-plugin/docs/rules/ban-ts-ignore.md b/packages/eslint-plugin/docs/rules/ban-ts-ignore.md index 8f69c864669..936d7936afc 100644 --- a/packages/eslint-plugin/docs/rules/ban-ts-ignore.md +++ b/packages/eslint-plugin/docs/rules/ban-ts-ignore.md @@ -4,6 +4,11 @@ This rule has been deprecated in favor of [`ban-ts-comment`](./ban-ts-comment.md Suppressing TypeScript Compiler Errors can be hard to discover. +## DEPRECATED + +This rule has been deprecated in favour of the [`ban-ts-comment`](./ban-ts-comment.md) rule. +It will be removed in a future version of this plugin. + ## Rule Details Does not allow the use of `// @ts-ignore` comments. diff --git a/packages/eslint-plugin/docs/rules/brace-style.md b/packages/eslint-plugin/docs/rules/brace-style.md index c8268dea44e..06dfa1619fb 100644 --- a/packages/eslint-plugin/docs/rules/brace-style.md +++ b/packages/eslint-plugin/docs/rules/brace-style.md @@ -3,11 +3,11 @@ ## Rule Details This rule extends the base [`eslint/brace-style`](https://eslint.org/docs/rules/brace-style) rule. -It supports all options and features of the base rule. +It adds support for `enum`, `interface`, `namespace` and `module` declarations. ## How to use -```cjson +```jsonc { // note you must disable the base rule as it can report incorrect errors "brace-style": "off", diff --git a/packages/eslint-plugin/docs/rules/camelcase.md b/packages/eslint-plugin/docs/rules/camelcase.md index f0b49d0420f..89cd63aa929 100644 --- a/packages/eslint-plugin/docs/rules/camelcase.md +++ b/packages/eslint-plugin/docs/rules/camelcase.md @@ -1,135 +1,43 @@ # Enforce camelCase naming convention (`camelcase`) -When it comes to naming variables, style guides generally fall into one of two -camps: camelCase (`variableName`) and underscores (`variable_name`). This rule -focuses on using the camelCase approach. If your style guide calls for -camelCasing your variable names, then this rule is for you! +## DEPRECATED -## Rule Details +This rule has been deprecated in favour of the [`naming-convention`](./naming-convention.md) rule. +It will be removed in a future version of this plugin. -This rule looks for any underscores (`_`) located within the source code. -It ignores leading and trailing underscores and only checks those in the middle -of a variable name. If ESLint decides that the variable is a constant -(all uppercase), then no warning will be thrown. Otherwise, a warning will be -thrown. This rule only flags definitions and assignments but not function calls. -In case of ES6 `import` statements, this rule only targets the name of the -variable that will be imported into the local module scope. +## Rule Details -**_This rule was taken from the ESLint core rule `camelcase`._** -**_Available options and test cases may vary depending on the version of ESLint installed in the system._** +This rule extends the base [`eslint/camelcase`](https://eslint.org/docs/rules/camelcase) rule. +It adds support for numerous TypeScript features. -## Options +## How to use -```cjson +```jsonc { - // note you must disable the base rule as it can report incorrect errors - "camelcase": "off", - "@typescript-eslint/camelcase": ["error", { "properties": "always" }] + // note you must disable the base rule as it can report incorrect errors + "camelcase": "off", + "@typescript-eslint/camelcase": ["error"] } ``` -This rule has an object option: - -- `"properties": "never"` (default) does not check property names -- `"properties": "always"` enforces camelCase style for property names -- `"genericType": "never"` (default) does not check generic identifiers -- `"genericType": "always"` enforces camelCase style for generic identifiers -- `"ignoreDestructuring": false` (default) enforces camelCase style for destructured identifiers -- `"ignoreDestructuring": true` does not check destructured identifiers -- `allow` (`string[]`) list of properties to accept. Accept regex. - -### properties: "always" - -Examples of **incorrect** code for this rule with the default `{ "properties": "always" }` option: - -```js -/*eslint @typescript-eslint/camelcase: "error"*/ - -import { no_camelcased } from 'external-module'; - -var my_favorite_color = '#112C85'; - -function do_something() { - // ... -} - -obj.do_something = function() { - // ... -}; +## Options -function foo({ no_camelcased }) { - // ... -} +See [`eslint/camelcase` options](https://eslint.org/docs/rules/camelcase#options). +This rule adds the following options: -function foo({ isCamelcased: no_camelcased }) { - // ... +```ts +interface Options extends BaseCamelcaseOptions { + genericType?: 'always' | 'never'; } -function foo({ no_camelcased = 'default value' }) { - // ... -} - -var obj = { - my_pref: 1, +const defaultOptions: Options = { + ...baseCamelcaseDefaultOptions, + genericType: 'never', }; - -var { category_id = 1 } = query; - -var { foo: no_camelcased } = bar; - -var { foo: bar_baz = 1 } = quz; -``` - -Examples of **correct** code for this rule with the default `{ "properties": "always" }` option: - -```js -/*eslint @typescript-eslint/camelcase: "error"*/ - -import { no_camelcased as camelCased } from 'external-module'; - -var myFavoriteColor = '#112C85'; -var _myFavoriteColor = '#112C85'; -var myFavoriteColor_ = '#112C85'; -var MY_FAVORITE_COLOR = '#112C85'; -var foo = bar.baz_boom; -var foo = { qux: bar.baz_boom }; - -obj.do_something(); -do_something(); -new do_something(); - -var { category_id: category } = query; - -function foo({ isCamelCased }) { - // ... -} - -function foo({ isCamelCased: isAlsoCamelCased }) { - // ... -} - -function foo({ isCamelCased = 'default value' }) { - // ... -} - -var { categoryId = 1 } = query; - -var { foo: isCamelCased } = bar; - -var { foo: isCamelCased = 1 } = quz; ``` -### `properties: "never"` - -Examples of **correct** code for this rule with the `{ "properties": "never" }` option: - -```js -/*eslint @typescript-eslint/camelcase: ["error", {properties: "never"}]*/ - -var obj = { - my_pref: 1, -}; -``` +- `"genericType": "never"` (default) does not check generic identifiers +- `"genericType": "always"` enforces camelCase style for generic identifiers ### `genericType: "always"` @@ -225,74 +133,4 @@ class Foo { } ``` -### `ignoreDestructuring: false` - -Examples of **incorrect** code for this rule with the default `{ "ignoreDestructuring": false }` option: - -```js -/*eslint @typescript-eslint/camelcase: "error"*/ - -var { category_id } = query; - -var { category_id = 1 } = query; - -var { category_id: category_id } = query; - -var { category_id: category_alias } = query; - -var { category_id: categoryId, ...other_props } = query; -``` - -### `ignoreDestructuring: true` - -Examples of **incorrect** code for this rule with the `{ "ignoreDestructuring": true }` option: - -```js -/*eslint @typescript-eslint/camelcase: ["error", {ignoreDestructuring: true}]*/ - -var { category_id: category_alias } = query; - -var { category_id, ...other_props } = query; -``` - -Examples of **correct** code for this rule with the `{ "ignoreDestructuring": true }` option: - -```js -/*eslint @typescript-eslint/camelcase: ["error", {ignoreDestructuring: true}]*/ - -var { category_id } = query; - -var { category_id = 1 } = query; - -var { category_id: category_id } = query; -``` - -## allow - -Examples of **correct** code for this rule with the `allow` option: - -```js -/*eslint @typescript-eslint/camelcase: ["error", {allow: ["UNSAFE_componentWillMount"]}]*/ - -function UNSAFE_componentWillMount() { - // ... -} -``` - -```js -/*eslint @typescript-eslint/camelcase: ["error", {allow: ["^UNSAFE_"]}]*/ - -function UNSAFE_componentWillMount() { - // ... -} - -function UNSAFE_componentWillMount() { - // ... -} -``` - -## When Not To Use It - -If you have established coding standards using a different naming convention (separating words with underscores), turn this rule off. - Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/camelcase.md) diff --git a/packages/eslint-plugin/docs/rules/class-literal-property-style.md b/packages/eslint-plugin/docs/rules/class-literal-property-style.md index 903a695dd76..1bca8390fd3 100644 --- a/packages/eslint-plugin/docs/rules/class-literal-property-style.md +++ b/packages/eslint-plugin/docs/rules/class-literal-property-style.md @@ -1,7 +1,7 @@ # Ensures that literals on classes are exposed in a consistent style (`class-literal-property-style`) When writing TypeScript applications, it's typically safe to store literal values on classes using fields with the `readonly` modifier to prevent them from being reassigned. -When writing TypeScript libraries that could be used by Javascript users however, it's typically safer to expose these literals using `getter`s, since the `readonly` modifier is enforced at compile type. +When writing TypeScript libraries that could be used by JavaScript users however, it's typically safer to expose these literals using `getter`s, since the `readonly` modifier is enforced at compile type. ## Rule Details @@ -11,7 +11,7 @@ By default this rule prefers the `fields` style as it means JS doesn't have to s Note that this rule only checks for constant _literal_ values (string, template string, number, bigint, boolean, regexp, null). It does not check objects or arrays, because a readonly field behaves differently to a getter in those cases. It also does not check functions, as it is a common pattern to use readonly fields with arrow function values as auto-bound methods. This is because these types can be mutated and carry with them more complex implications about their usage. -#### The `fields` style +### The `fields` style This style checks for any getter methods that return literal values, and requires them to be defined using fields with the `readonly` modifier instead. @@ -50,7 +50,7 @@ class Mx { } ``` -#### The `getters` style +### The `getters` style This style checks for any `readonly` fields that are assigned literal values, and requires them to be defined as getters instead. This style pairs well with the [`@typescript-eslint/prefer-readonly`](prefer-readonly.md) rule, diff --git a/packages/eslint-plugin/docs/rules/class-name-casing.md b/packages/eslint-plugin/docs/rules/class-name-casing.md index 38c30609747..ae5d5da7a2e 100644 --- a/packages/eslint-plugin/docs/rules/class-name-casing.md +++ b/packages/eslint-plugin/docs/rules/class-name-casing.md @@ -2,6 +2,11 @@ This rule enforces PascalCase names for classes and interfaces. +## DEPRECATED + +This rule has been deprecated in favour of the [`naming-convention`](./naming-convention.md) rule. +It will be removed in a future version of this plugin. + ## Rule Details This rule aims to make it easy to differentiate classes from regular variables at a glance. diff --git a/packages/eslint-plugin/docs/rules/comma-spacing.md b/packages/eslint-plugin/docs/rules/comma-spacing.md index 145e3607726..0920180416a 100644 --- a/packages/eslint-plugin/docs/rules/comma-spacing.md +++ b/packages/eslint-plugin/docs/rules/comma-spacing.md @@ -7,7 +7,7 @@ It adds support for trailing comma in a types parameters list. ## How to use -```cjson +```jsonc { // note you must disable the base rule as it can report incorrect errors "comma-spacing": "off", diff --git a/packages/eslint-plugin/docs/rules/consistent-type-assertions.md b/packages/eslint-plugin/docs/rules/consistent-type-assertions.md index cdf0fb7656c..43e67b755b5 100644 --- a/packages/eslint-plugin/docs/rules/consistent-type-assertions.md +++ b/packages/eslint-plugin/docs/rules/consistent-type-assertions.md @@ -8,6 +8,8 @@ Type assertions are also commonly referred as "type casting" in TypeScript (even In addition to ensuring that type assertions are written in a consistent way, this rule also helps make your codebase more type-safe. +`const` assertions, [introduced in TypeScript 3.4](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-4.html#const-assertions), is always allowed by this rule. Examples of it include `let x = "hello" as const;` and `let x = "hello";`. + ## Options ```ts diff --git a/packages/eslint-plugin/docs/rules/default-param-last.md b/packages/eslint-plugin/docs/rules/default-param-last.md index f3eb9f27b9a..c9c51df40a3 100644 --- a/packages/eslint-plugin/docs/rules/default-param-last.md +++ b/packages/eslint-plugin/docs/rules/default-param-last.md @@ -2,7 +2,8 @@ ## Rule Details -This rule enforces default or optional parameters to be the last of parameters. +This rule extends the base [`eslint/default-param-last`](https://eslint.org/docs/rules/default-param-last) rule. +It adds support for optional parameters. Examples of **incorrect** code for this rule: @@ -38,4 +39,18 @@ class Foo { } ``` +## How to use + +```jsonc +{ + // note you must disable the base rule as it can report incorrect errors + "default-param-last": "off", + "@typescript-eslint/default-param-last": ["error"] +} +``` + +## Options + +See [`eslint/default-param-last` options](https://eslint.org/docs/rules/default-param-last#options). + Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/default-param-last.md) diff --git a/packages/eslint-plugin/docs/rules/explicit-function-return-type.md b/packages/eslint-plugin/docs/rules/explicit-function-return-type.md index 360fdce01e6..aa94a29a0ef 100644 --- a/packages/eslint-plugin/docs/rules/explicit-function-return-type.md +++ b/packages/eslint-plugin/docs/rules/explicit-function-return-type.md @@ -69,6 +69,8 @@ type Options = { allowTypedFunctionExpressions?: boolean; // if true, functions immediately returning another function expression will not be checked allowHigherOrderFunctions?: boolean; + // if true, concise arrow functions that start with the void keyword will not be checked + allowConciseArrowFunctionExpressionStartingWithVoid?: boolean; }; const defaults = { @@ -198,6 +200,24 @@ function fn() { } ``` +### `allowConciseArrowFunctionExpressionsStartingWithVoid` + +Examples of **incorrect** code for this rule with `{ allowConciseArrowFunctionExpressionsStartingWithVoid: true }`: + +```ts +var join = (a: string, b: string) => `${a}${b}`; + +const log = (message: string) => { + console.log(message); +}; +``` + +Examples of **correct** code for this rule with `{ allowConciseArrowFunctionExpressionsStartingWithVoid: true }`: + +```ts +var log = (message: string) => void console.log(message); +``` + ## When Not To Use It If you don't wish to prevent calling code from using function return values in unexpected ways, then diff --git a/packages/eslint-plugin/docs/rules/explicit-module-boundary-types.md b/packages/eslint-plugin/docs/rules/explicit-module-boundary-types.md index a5327a1e547..3c077a75c88 100644 --- a/packages/eslint-plugin/docs/rules/explicit-module-boundary-types.md +++ b/packages/eslint-plugin/docs/rules/explicit-module-boundary-types.md @@ -86,12 +86,17 @@ type Options = { * An array of function/method names that will not have their arguments or their return values checked. */ allowedNames?: string[]; + /** + * If true, track references to exported variables as well as direct exports. + */ + shouldTrackReferences?: boolean; }; const defaults = { allowTypedFunctionExpressions: true, allowHigherOrderFunctions: true, allowedNames: [], + shouldTrackReferences: true, }; ``` @@ -238,6 +243,28 @@ You may pass function/method names you would like this rule to ignore, like so: } ``` +### `shouldTrackReferences` + +Examples of **incorrect** code for this rule with `{ shouldTrackReferences: true }`: + +```ts +function foo(bar) { + return bar; +} + +export default foo; +``` + +Examples of **correct** code for this rule with `{ shouldTrackReferences: true }`: + +```ts +function foo(bar: string): string { + return bar; +} + +export default foo; +``` + ## When Not To Use It If you wish to make sure all functions have explicit return types, as opposed to only the module boundaries, you can use [explicit-function-return-type](https://github.com/eslint/eslint/blob/master/docs/rules/explicit-function-return-type.md) diff --git a/packages/eslint-plugin/docs/rules/func-call-spacing.md b/packages/eslint-plugin/docs/rules/func-call-spacing.md index aa1d3fde96a..8aa9e7e0cbf 100644 --- a/packages/eslint-plugin/docs/rules/func-call-spacing.md +++ b/packages/eslint-plugin/docs/rules/func-call-spacing.md @@ -1,17 +1,13 @@ # Require or disallow spacing between function identifiers and their invocations (`func-call-spacing`) -When calling a function, developers may insert optional whitespace between the function’s name and the parentheses that invoke it. -This rule requires or disallows spaces between the function name and the opening parenthesis that calls it. - ## Rule Details This rule extends the base [`eslint/func-call-spacing`](https://eslint.org/docs/rules/func-call-spacing) rule. -It supports all options and features of the base rule. -This version adds support for generic type parameters on function calls. +It adds support for generic type parameters on function calls. ## How to use -```cjson +```jsonc { // note you must disable the base rule as it can report incorrect errors "func-call-spacing": "off", diff --git a/packages/eslint-plugin/docs/rules/generic-type-naming.md b/packages/eslint-plugin/docs/rules/generic-type-naming.md index 185b3811d9a..dc964273535 100644 --- a/packages/eslint-plugin/docs/rules/generic-type-naming.md +++ b/packages/eslint-plugin/docs/rules/generic-type-naming.md @@ -3,6 +3,11 @@ It can be helpful to enforce a consistent naming style for generic type variables used within a type. For example, prefixing them with `T` and ensuring a somewhat descriptive name, or enforcing Hungarian notation. +## DEPRECATED + +This rule has been deprecated in favour of the [`naming-convention`](./naming-convention.md) rule. +It will be removed in a future version of this plugin. + ## Rule Details This rule allows you to enforce conventions over type variables. By default, it does nothing. @@ -13,7 +18,7 @@ The rule takes a single string option, which is a regular expression that type v Examples of **correct** code with a configuration of `'^T[A-Z][a-zA-Z]+$'`: -```typescript +```ts type ReadOnly = { readonly [TKey in keyof TType]: TType[TKey]; }; @@ -25,7 +30,7 @@ interface SimpleMap { Examples of **incorrect** code with a configuration of `'^T[A-Z][a-zA-Z]+$'`: -```typescript +```ts type ReadOnly = { readonly [Key in keyof T]: T[Key] }; interface SimpleMap { diff --git a/packages/eslint-plugin/docs/rules/indent.md b/packages/eslint-plugin/docs/rules/indent.md index 4636cfeaeb5..023c774657a 100644 --- a/packages/eslint-plugin/docs/rules/indent.md +++ b/packages/eslint-plugin/docs/rules/indent.md @@ -1,713 +1,24 @@ # Enforce consistent indentation (`indent`) -There are several common guidelines which require specific indentation of nested blocks and statements, like: - -```js -function hello(indentSize, type) { - if (indentSize === 4 && type !== 'tab') { - console.log('Each next indentation will increase on 4 spaces'); - } -} -``` - -These are the most common scenarios recommended in different style guides: - -- Two spaces, not longer and no tabs: Google, npm, NodeJS, Idiomatic, Felix -- Tabs: jQuery -- Four spaces: Crockford +## PLEASE READ THIS ISSUE BEFORE USING THIS RULE [#1824](https://github.com/typescript-eslint/typescript-eslint/issues/1824) ## Rule Details -This rule enforces a consistent indentation style. The default style is `4 spaces`. - -## Options - -This rule has a mixed option: +This rule extends the base [`eslint/indent`](https://eslint.org/docs/rules/indent) rule. +It adds support for TypeScript nodes. -For example, for 2-space indentation: +## How to use -```cjson +```jsonc { - // note you must disable the base rule as it can report incorrect errors - "indent": "off", - "@typescript-eslint/indent": ["error", 2] + // note you must disable the base rule as it can report incorrect errors + "indent": "off", + "@typescript-eslint/indent": ["error"] } ``` -Or for tabbed indentation: - -```cjson -{ - // note you must disable the base rule as it can report incorrect errors - "indent": "off", - "@typescript-eslint/indent": ["error", "tab"] -} -``` - -Examples of **incorrect** code for this rule with the default options: - - -```js -/*eslint @typescript-eslint/indent: "error"*/ - -if (a) { - b=c; - function foo(d) { - e=f; - } -} -``` - -Examples of **correct** code for this rule with the default options: - - -```js -/*eslint @typescript-eslint/indent: "error"*/ - -if (a) { - b=c; - function foo(d) { - e=f; - } -} -``` - -This rule has an object option: - -- `"SwitchCase"` (default: 0) enforces indentation level for `case` clauses in `switch` statements -- `"VariableDeclarator"` (default: 1) enforces indentation level for `var` declarators; can also take an object to define separate rules for `var`, `let` and `const` declarations. -- `"outerIIFEBody"` (default: 1) enforces indentation level for file-level IIFEs. -- `"MemberExpression"` (default: 1) enforces indentation level for multi-line property chains. This can also be set to `"off"` to disable checking for `MemberExpression` indentation. -- `"FunctionDeclaration"` takes an object to define rules for function declarations. - - `parameters` (default: 1) enforces indentation level for parameters in a function declaration. This can either be a number indicating indentation level, or the string `"first"` indicating that all parameters of the declaration must be aligned with the first parameter. This can also be set to `"off"` to disable checking for `FunctionDeclaration` parameters. - - `body` (default: 1) enforces indentation level for the body of a function declaration. -- `"FunctionExpression"` takes an object to define rules for function expressions. - - `parameters` (default: 1) enforces indentation level for parameters in a function expression. This can either be a number indicating indentation level, or the string `"first"` indicating that all parameters of the expression must be aligned with the first parameter. This can also be set to `"off"` to disable checking for `FunctionExpression` parameters. - - `body` (default: 1) enforces indentation level for the body of a function expression. -- `"CallExpression"` takes an object to define rules for function call expressions. - - `arguments` (default: 1) enforces indentation level for arguments in a call expression. This can either be a number indicating indentation level, or the string `"first"` indicating that all arguments of the expression must be aligned with the first argument. This can also be set to `"off"` to disable checking for `CallExpression` arguments. -- `"ArrayExpression"` (default: 1) enforces indentation level for elements in arrays. It can also be set to the string `"first"`, indicating that all the elements in the array should be aligned with the first element. This can also be set to `"off"` to disable checking for array elements. -- `"ObjectExpression"` (default: 1) enforces indentation level for properties in objects. It can be set to the string `"first"`, indicating that all properties in the object should be aligned with the first property. This can also be set to `"off"` to disable checking for object properties. -- `"ImportDeclaration"` (default: 1) enforces indentation level for import statements. It can be set to the string `"first"`, indicating that all imported members from a module should be aligned with the first member in the list. This can also be set to `"off"` to disable checking for imported module members. -- `"flatTernaryExpressions": true` (`false` by default) requires no indentation for ternary expressions which are nested in other ternary expressions. -- `"ignoredNodes"` accepts an array of [selectors](https://eslint.org/docs/developer-guide/selectors). If an AST node is matched by any of the selectors, the indentation of tokens which are direct children of that node will be ignored. This can be used as an escape hatch to relax the rule if you disagree with the indentation that it enforces for a particular syntactic pattern. -- `"ignoreComments"` (default: false) can be used when comments do not need to be aligned with nodes on the previous or next line. - -Level of indentation denotes the multiple of the indent specified. Example: - -- Indent of 4 spaces with `VariableDeclarator` set to `2` will indent the multi-line variable declarations with 8 spaces. -- Indent of 2 spaces with `VariableDeclarator` set to `2` will indent the multi-line variable declarations with 4 spaces. -- Indent of 2 spaces with `VariableDeclarator` set to `{"var": 2, "let": 2, "const": 3}` will indent the multi-line variable declarations with 4 spaces for `var` and `let`, 6 spaces for `const` statements. -- Indent of tab with `VariableDeclarator` set to `2` will indent the multi-line variable declarations with 2 tabs. -- Indent of 2 spaces with `SwitchCase` set to `0` will not indent `case` clauses with respect to `switch` statements. -- Indent of 2 spaces with `SwitchCase` set to `1` will indent `case` clauses with 2 spaces with respect to `switch` statements. -- Indent of 2 spaces with `SwitchCase` set to `2` will indent `case` clauses with 4 spaces with respect to `switch` statements. -- Indent of tab with `SwitchCase` set to `2` will indent `case` clauses with 2 tabs with respect to `switch` statements. -- Indent of 2 spaces with `MemberExpression` set to `0` will indent the multi-line property chains with 0 spaces. -- Indent of 2 spaces with `MemberExpression` set to `1` will indent the multi-line property chains with 2 spaces. -- Indent of 2 spaces with `MemberExpression` set to `2` will indent the multi-line property chains with 4 spaces. -- Indent of 4 spaces with `MemberExpression` set to `0` will indent the multi-line property chains with 0 spaces. -- Indent of 4 spaces with `MemberExpression` set to `1` will indent the multi-line property chains with 4 spaces. -- Indent of 4 spaces with `MemberExpression` set to `2` will indent the multi-line property chains with 8 spaces. - -### tab - -Examples of **incorrect** code for this rule with the `"tab"` option: - - -```js -/*eslint @typescript-eslint/indent: ["error", "tab"]*/ - -if (a) { - b=c; -function foo(d) { - e=f; - } -} -``` - -Examples of **correct** code for this rule with the `"tab"` option: - - -```js -/*eslint @typescript-eslint/indent: ["error", "tab"]*/ - -if (a) { -/*tab*/b=c; -/*tab*/function foo(d) { -/*tab*//*tab*/e=f; -/*tab*/} -} -``` - -### `SwitchCase` - -Examples of **incorrect** code for this rule with the `2, { "SwitchCase": 1 }` options: - - -```js -/*eslint @typescript-eslint/indent: ["error", 2, { "SwitchCase": 1 }]*/ - -switch(a){ -case "a": - break; -case "b": - break; -} -``` - -Examples of **correct** code for this rule with the `2, { "SwitchCase": 1 }` option: - - -```js -/*eslint @typescript-eslint/indent: ["error", 2, { "SwitchCase": 1 }]*/ - -switch(a){ - case "a": - break; - case "b": - break; -} -``` - -### `VariableDeclarator` - -Examples of **incorrect** code for this rule with the `2, { "VariableDeclarator": 1 }` options: - - -```js -/*eslint @typescript-eslint/indent: ["error", 2, { "VariableDeclarator": 1 }]*/ -/*eslint-env es6*/ - -var a, - b, - c; -let a, - b, - c; -const a = 1, - b = 2, - c = 3; -``` - -Examples of **correct** code for this rule with the `2, { "VariableDeclarator": 1 }` options: - - -```js -/*eslint @typescript-eslint/indent: ["error", 2, { "VariableDeclarator": 1 }]*/ -/*eslint-env es6*/ - -var a, - b, - c; -let a, - b, - c; -const a = 1, - b = 2, - c = 3; -``` - -Examples of **correct** code for this rule with the `2, { "VariableDeclarator": 2 }` options: - - -```js -/*eslint @typescript-eslint/indent: ["error", 2, { "VariableDeclarator": 2 }]*/ -/*eslint-env es6*/ - -var a, - b, - c; -let a, - b, - c; -const a = 1, - b = 2, - c = 3; -``` - -Examples of **correct** code for this rule with the `2, { "VariableDeclarator": { "var": 2, "let": 2, "const": 3 } }` options: - - -```js -/*eslint @typescript-eslint/indent: ["error", 2, { "VariableDeclarator": { "var": 2, "let": 2, "const": 3 } }]*/ -/*eslint-env es6*/ - -var a, - b, - c; -let a, - b, - c; -const a = 1, - b = 2, - c = 3; -``` - -### `outerIIFEBody` - -Examples of **incorrect** code for this rule with the options `2, { "outerIIFEBody": 0 }`: - - -```js -/*eslint @typescript-eslint/indent: ["error", 2, { "outerIIFEBody": 0 }]*/ - -(function() { - - function foo(x) { - return x + 1; - } - -})(); - - -if(y) { -console.log('foo'); -} -``` - -Examples of **correct** code for this rule with the options `2, {"outerIIFEBody": 0}`: - - -```js -/*eslint @typescript-eslint/indent: ["error", 2, { "outerIIFEBody": 0 }]*/ - -(function() { - -function foo(x) { - return x + 1; -} - -})(); - - -if(y) { - console.log('foo'); -} -``` - -### `MemberExpression` - -Examples of **incorrect** code for this rule with the `2, { "MemberExpression": 1 }` options: - - -```js -/*eslint @typescript-eslint/indent: ["error", 2, { "MemberExpression": 1 }]*/ - -foo -.bar -.baz() -``` - -Examples of **correct** code for this rule with the `2, { "MemberExpression": 1 }` option: - - -```js -/*eslint @typescript-eslint/indent: ["error", 2, { "MemberExpression": 1 }]*/ - -foo - .bar - .baz(); -``` - -### `FunctionDeclaration` - -Examples of **incorrect** code for this rule with the `2, { "FunctionDeclaration": {"body": 1, "parameters": 2} }` option: - - -```js -/*eslint @typescript-eslint/indent: ["error", 2, { "FunctionDeclaration": {"body": 1, "parameters": 2} }]*/ - -function foo(bar, - baz, - qux) { - qux(); -} -``` - -Examples of **correct** code for this rule with the `2, { "FunctionDeclaration": {"body": 1, "parameters": 2} }` option: - - -```js -/*eslint @typescript-eslint/indent: ["error", 2, { "FunctionDeclaration": {"body": 1, "parameters": 2} }]*/ - -function foo(bar, - baz, - qux) { - qux(); -} -``` - -Examples of **incorrect** code for this rule with the `2, { "FunctionDeclaration": {"parameters": "first"} }` option: - - -```js -/*eslint @typescript-eslint/indent: ["error", 2, {"FunctionDeclaration": {"parameters": "first"}}]*/ - -function foo(bar, baz, - qux, boop) { - qux(); -} -``` - -Examples of **correct** code for this rule with the `2, { "FunctionDeclaration": {"parameters": "first"} }` option: - - -```js -/*eslint @typescript-eslint/indent: ["error", 2, {"FunctionDeclaration": {"parameters": "first"}}]*/ - -function foo(bar, baz, - qux, boop) { - qux(); -} -``` - -### `FunctionExpression` - -Examples of **incorrect** code for this rule with the `2, { "FunctionExpression": {"body": 1, "parameters": 2} }` option: - - -```js -/*eslint @typescript-eslint/indent: ["error", 2, { "FunctionExpression": {"body": 1, "parameters": 2} }]*/ - -var foo = function(bar, - baz, - qux) { - qux(); -} -``` - -Examples of **correct** code for this rule with the `2, { "FunctionExpression": {"body": 1, "parameters": 2} }` option: - - -```js -/*eslint @typescript-eslint/indent: ["error", 2, { "FunctionExpression": {"body": 1, "parameters": 2} }]*/ - -var foo = function(bar, - baz, - qux) { - qux(); -} -``` - -Examples of **incorrect** code for this rule with the `2, { "FunctionExpression": {"parameters": "first"} }` option: - - -```js -/*eslint @typescript-eslint/indent: ["error", 2, {"FunctionExpression": {"parameters": "first"}}]*/ - -var foo = function(bar, baz, - qux, boop) { - qux(); -} -``` - -Examples of **correct** code for this rule with the `2, { "FunctionExpression": {"parameters": "first"} }` option: - - -```js -/*eslint @typescript-eslint/indent: ["error", 2, {"FunctionExpression": {"parameters": "first"}}]*/ - -var foo = function(bar, baz, - qux, boop) { - qux(); -} -``` - -### `CallExpression` - -Examples of **incorrect** code for this rule with the `2, { "CallExpression": {"arguments": 1} }` option: - - -```js -/*eslint @typescript-eslint/indent: ["error", 2, { "CallExpression": {"arguments": 1} }]*/ - -foo(bar, - baz, - qux -); -``` - -Examples of **correct** code for this rule with the `2, { "CallExpression": {"arguments": 1} }` option: - - -```js -/*eslint @typescript-eslint/indent: ["error", 2, { "CallExpression": {"arguments": 1} }]*/ - -foo(bar, - baz, - qux -); -``` - -Examples of **incorrect** code for this rule with the `2, { "CallExpression": {"arguments": "first"} }` option: - - -```js -/*eslint @typescript-eslint/indent: ["error", 2, {"CallExpression": {"arguments": "first"}}]*/ - -foo(bar, baz, - baz, boop, beep); -``` - -Examples of **correct** code for this rule with the `2, { "CallExpression": {"arguments": "first"} }` option: - - -```js -/*eslint @typescript-eslint/indent: ["error", 2, {"CallExpression": {"arguments": "first"}}]*/ - -foo(bar, baz, - baz, boop, beep); -``` - -### `ArrayExpression` - -Examples of **incorrect** code for this rule with the `2, { "ArrayExpression": 1 }` option: - - -```js -/*eslint @typescript-eslint/indent: ["error", 2, { "ArrayExpression": 1 }]*/ - -var foo = [ - bar, -baz, - qux -]; -``` - -Examples of **correct** code for this rule with the `2, { "ArrayExpression": 1 }` option: - - -```js -/*eslint @typescript-eslint/indent: ["error", 2, { "ArrayExpression": 1 }]*/ - -var foo = [ - bar, - baz, - qux -]; -``` - -Examples of **incorrect** code for this rule with the `2, { "ArrayExpression": "first" }` option: - - -```js -/*eslint @typescript-eslint/indent: ["error", 2, {"ArrayExpression": "first"}]*/ - -var foo = [bar, - baz, - qux -]; -``` - -Examples of **correct** code for this rule with the `2, { "ArrayExpression": "first" }` option: - - -```js -/*eslint @typescript-eslint/indent: ["error", 2, {"ArrayExpression": "first"}]*/ - -var foo = [bar, - baz, - qux -]; -``` - -### `ObjectExpression` - -Examples of **incorrect** code for this rule with the `2, { "ObjectExpression": 1 }` option: - - -```js -/*eslint @typescript-eslint/indent: ["error", 2, { "ObjectExpression": 1 }]*/ - -var foo = { - bar: 1, -baz: 2, - qux: 3 -}; -``` - -Examples of **correct** code for this rule with the `2, { "ObjectExpression": 1 }` option: - - -```js -/*eslint @typescript-eslint/indent: ["error", 2, { "ObjectExpression": 1 }]*/ - -var foo = { - bar: 1, - baz: 2, - qux: 3 -}; -``` - -Examples of **incorrect** code for this rule with the `2, { "ObjectExpression": "first" }` option: - - -```js -/*eslint @typescript-eslint/indent: ["error", 2, {"ObjectExpression": "first"}]*/ - -var foo = { bar: 1, - baz: 2 }; -``` - -Examples of **correct** code for this rule with the `2, { "ObjectExpression": "first" }` option: - - -```js -/*eslint @typescript-eslint/indent: ["error", 2, {"ObjectExpression": "first"}]*/ - -var foo = { bar: 1, - baz: 2 }; -``` - -### `ImportDeclaration` - -Examples of **correct** code for this rule with the `4, { "ImportDeclaration": 1 }` option (the default): - - -```js -/*eslint @typescript-eslint/indent: ["error", 4, { ImportDeclaration: 1 }]*/ - -import { foo, - bar, - baz, -} from 'qux'; - -import { - foo, - bar, - baz, -} from 'qux'; -``` - -Examples of **incorrect** code for this rule with the `4, { ImportDeclaration: "first" }` option: - - -```js -/*eslint @typescript-eslint/indent: ["error", 4, { ImportDeclaration: "first" }]*/ - -import { foo, - bar, - baz, -} from 'qux'; -``` - -Examples of **correct** code for this rule with the `4, { ImportDeclaration: "first" }` option: - - -```js -/*eslint @typescript-eslint/indent: ["error", 4, { ImportDeclaration: "first" }]*/ - -import { foo, - bar, - baz, -} from 'qux'; -``` - -### `flatTernaryExpressions` - -Examples of **incorrect** code for this rule with the default `4, { "flatTernaryExpressions": false }` option: - - -```js -/*eslint @typescript-eslint/indent: ["error", 4, { "flatTernaryExpressions": false }]*/ - -var a = - foo ? bar : - baz ? qux : - boop; -``` - -Examples of **correct** code for this rule with the default `4, { "flatTernaryExpressions": false }` option: - - -```js -/*eslint @typescript-eslint/indent: ["error", 4, { "flatTernaryExpressions": false }]*/ - -var a = - foo ? bar : - baz ? qux : - boop; -``` - -Examples of **incorrect** code for this rule with the `4, { "flatTernaryExpressions": true }` option: - - -```js -/*eslint @typescript-eslint/indent: ["error", 4, { "flatTernaryExpressions": true }]*/ - -var a = - foo ? bar : - baz ? qux : - boop; -``` - -Examples of **correct** code for this rule with the `4, { "flatTernaryExpressions": true }` option: - - -```js -/*eslint @typescript-eslint/indent: ["error", 4, { "flatTernaryExpressions": true }]*/ - -var a = - foo ? bar : - baz ? qux : - boop; -``` - -### `ignoredNodes` - -The following configuration ignores the indentation of `ConditionalExpression` ("ternary expression") nodes: - -Examples of **correct** code for this rule with the `4, { "ignoredNodes": ["ConditionalExpression"] }` option: - - -```js -/*eslint @typescript-eslint/indent: ["error", 4, { "ignoredNodes": ["ConditionalExpression"] }]*/ - -var a = foo - ? bar - : baz; - -var a = foo - ? bar -: baz; -``` - -The following configuration ignores indentation in the body of IIFEs. - -Examples of **correct** code for this rule with the `4, { "ignoredNodes": ["CallExpression > FunctionExpression.callee > BlockStatement.body"] }` option: - - -```js -/*eslint @typescript-eslint/indent: ["error", 4, { "ignoredNodes": ["CallExpression > FunctionExpression.callee > BlockStatement.body"] }]*/ - -(function() { - -foo(); -bar(); - -}) -``` - -### `ignoreComments` - -Examples of additional **correct** code for this rule with the `4, { "ignoreComments": true }` option: - - -```js -/*eslint @typescript-eslint/indent: ["error", 4, { "ignoreComments": true }] */ - -if (foo) { - doSomething(); - -// comment intentionally de-indented - doSomethingElse(); -} -``` - -## Compatibility +## Options -- **JSHint**: `indent` -- **JSCS**: [`validateIndentation`](https://jscs-dev.github.io/rule/validateIndentation) +See [`eslint/indent` options](https://eslint.org/docs/rules/indent#options). Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/indent.md) diff --git a/packages/eslint-plugin/docs/rules/interface-name-prefix.md b/packages/eslint-plugin/docs/rules/interface-name-prefix.md index f6439b490fd..6322c9c79d6 100644 --- a/packages/eslint-plugin/docs/rules/interface-name-prefix.md +++ b/packages/eslint-plugin/docs/rules/interface-name-prefix.md @@ -5,6 +5,11 @@ The unprefixed name is then available for a class that provides a standard imple Alternatively, the contributor guidelines for the TypeScript repo suggest [never prefixing](https://github.com/Microsoft/TypeScript/wiki/Coding-guidelines#names) interfaces with `I`. +## DEPRECATED + +This rule has been deprecated in favour of the [`naming-convention`](./naming-convention.md) rule. +It will be removed in a future version of this plugin. + ## Rule Details This rule enforces whether or not the `I` prefix is required for interface names. diff --git a/packages/eslint-plugin/docs/rules/member-naming.md b/packages/eslint-plugin/docs/rules/member-naming.md index e12f4f239b5..0dd8a5a2f29 100644 --- a/packages/eslint-plugin/docs/rules/member-naming.md +++ b/packages/eslint-plugin/docs/rules/member-naming.md @@ -2,6 +2,11 @@ It can be helpful to enforce naming conventions for `private` (and sometimes `protected`) members of an object. For example, prefixing private properties with a `_` allows them to be easily discerned when being inspected by tools that do not have knowledge of TypeScript (such as most debuggers). +## DEPRECATED + +This rule has been deprecated in favour of the [`naming-convention`](./naming-convention.md) rule. +It will be removed in a future version of this plugin. + ## Rule Details This rule allows you to enforce conventions for class property and method names by their visibility. By default, it enforces nothing. diff --git a/packages/eslint-plugin/docs/rules/member-ordering.md b/packages/eslint-plugin/docs/rules/member-ordering.md index d028d844368..9a2a4cdee8f 100644 --- a/packages/eslint-plugin/docs/rules/member-ordering.md +++ b/packages/eslint-plugin/docs/rules/member-ordering.md @@ -1,64 +1,89 @@ # Require a consistent member declaration order (`member-ordering`) -A consistent ordering of fields, methods and constructors can make interfaces, type literals, classes and class -expressions easier to read, navigate and edit. +A consistent ordering of fields, methods and constructors can make interfaces, type literals, classes and class expressions easier to read, navigate and edit. ## Rule Details -This rule aims to standardize the way class declarations, class expressions, interfaces and type literals are structured. +This rule aims to standardize the way class declarations, class expressions, interfaces and type literals are structured and ordered. -It allows to group members by their type (e.g. `public-static-field`, `protected-static-field`, `private-static-field`, `public-instance-field`, ...). By default, their order is the same inside `classes`, `classExpressions`, `interfaces` and `typeLiterals` (note: not all member types apply to `interfaces` and `typeLiterals`). It is possible to define the order for any of those individually or to change the default order for all of them by setting the `default` option. +### Grouping and sorting member groups + +It allows to group members by their type (e.g. `public-static-field`, `protected-static-field`, `private-static-field`, `public-instance-field`, ...) and enforce a certain order for these groups. By default, their order is the same inside `classes`, `classExpressions`, `interfaces` and `typeLiterals` (note: not all member types apply to `interfaces` and `typeLiterals`). It is possible to define the order for any of those individually or to change the default order for all of them by setting the `default` option. + +### Sorting members + +Besides grouping the members and sorting their groups, this rule also allows to sort the members themselves (e.g. `a`, `b`, `c`, ...). You have 2 options: Sort all of them while ignoring their type or sort them while respecting their types (e.g. sort all fields in an interface alphabetically). ## Options +These options allow to specify how to group the members and sort their groups. + +- Sort groups, don't enforce member order: Use `memberTypes` +- Sort members, don't enforce group order: Use `order` +- Sort members within groups: Use `memberTypes` and `order` + ```ts +type TypeOptions = + | { + memberTypes: Array | 'never', + order?: 'alphabetically' | 'as-written', + } + | { + order: 'alphabetically', + }; + { - default?: Array | never - classes?: Array | never - classExpressions?: Array | never + default?: TypeOptions, + + classes?: TypeOptions, + classExpressions?: TypeOptions, - interfaces?: ['signature' | 'field' | 'method' | 'constructor'] | never - typeLiterals?: ['signature' | 'field' | 'method' | 'constructor'] | never + interfaces?: TypeOptions<'signature' | 'field' | 'method' | 'constructor'>, + typeLiterals?: TypeOptions<'signature' | 'field' | 'method' | 'constructor'>, } ``` See below for the possible definitions of `MemberType`. +### Deprecated syntax + +Note: There is a deprecated syntax to specify the member types as an array. + ### Member types (granular form) There are multiple ways to specify the member types. The most explicit and granular form is the following: -```json5 +```jsonc [ // Index signature - 'signature', + "signature", // Fields - 'public-static-field', - 'protected-static-field', - 'private-static-field', - 'public-instance-field', - 'protected-instance-field', - 'private-instance-field', - 'public-abstract-field', - 'protected-abstract-field', - 'private-abstract-field', + "public-static-field", + "protected-static-field", + "private-static-field", + "public-instance-field", + "protected-instance-field", + "private-instance-field", + "public-abstract-field", + "protected-abstract-field", + "private-abstract-field", // Constructors - 'public-constructor', - 'protected-constructor', - 'private-constructor', + "public-constructor", + "protected-constructor", + "private-constructor", // Methods - 'public-static-method', - 'protected-static-method', - 'private-static-method', - 'public-instance-method', - 'protected-instance-method', - 'private-instance-method', - 'public-abstract-method', - 'protected-abstract-method', - 'private-abstract-method', + "public-static-method", + "protected-static-method", + "private-static-method", + "public-instance-method", + "protected-instance-method", + "private-instance-method", + "public-abstract-method", + "protected-abstract-method", + "private-abstract-method" ] ``` @@ -68,23 +93,23 @@ Note: If you only specify some of the possible types, the non-specified ones can It is also possible to group member types by their accessibility (`static`, `instance`, `abstract`), ignoring their scope. -```json5 +```jsonc [ // Index signature // No accessibility for index signature. See above. // Fields - 'public-field', // = ['public-static-field', 'public-instance-field']) - 'protected-field', // = ['protected-static-field', 'protected-instance-field']) - 'private-field', // = ['private-static-field', 'private-instance-field']) + "public-field", // = ["public-static-field", "public-instance-field"]) + "protected-field", // = ["protected-static-field", "protected-instance-field"]) + "private-field", // = ["private-static-field", "private-instance-field"]) // Constructors // Only the accessibility of constructors is configurable. See below. // Methods - 'public-method', // = ['public-static-method', 'public-instance-method']) - 'protected-method', // = ['protected-static-method', 'protected-instance-method']) - 'private-method', // = ['private-static-method', 'private-instance-method']) + "public-method", // = ["public-static-method", "public-instance-method"]) + "protected-method", // = ["protected-static-method", "protected-instance-method"]) + "private-method" // = ["private-static-method", "private-instance-method"]) ] ``` @@ -92,23 +117,23 @@ It is also possible to group member types by their accessibility (`static`, `ins Another option is to group the member types by their scope (`public`, `protected`, `private`), ignoring their accessibility. -```json5 +```jsonc [ // Index signature // No scope for index signature. See above. // Fields - 'static-field', // = ['public-static-field', 'protected-static-field', 'private-static-field']) - 'instance-field', // = ['public-instance-field', 'protected-instance-field', 'private-instance-field']) - 'abstract-field', // = ['public-abstract-field', 'protected-abstract-field', 'private-abstract-field']) + "static-field", // = ["public-static-field", "protected-static-field", "private-static-field"]) + "instance-field", // = ["public-instance-field", "protected-instance-field", "private-instance-field"]) + "abstract-field", // = ["public-abstract-field", "protected-abstract-field", "private-abstract-field"]) // Constructors - 'constructor', // = ['public-constructor', 'protected-constructor', 'private-constructor']) + "constructor", // = ["public-constructor", "protected-constructor", "private-constructor"]) // Methods - 'static-method', // = ['public-static-method', 'protected-static-method', 'private-static-method']) - 'instance-method', // = ['public-instance-method', 'protected-instance-method', 'private-instance-method']) - 'abstract-method', // = ['public-abstract-method', 'protected-abstract-method', 'private-abstract-method']) + "static-method", // = ["public-static-method", "protected-static-method", "private-static-method"]) + "instance-method", // = ["public-instance-method", "protected-instance-method", "private-instance-method"]) + "abstract-method" // = ["public-abstract-method", "protected-abstract-method", "private-abstract-method"]) ] ``` @@ -116,21 +141,21 @@ Another option is to group the member types by their scope (`public`, `protected The third grouping option is to ignore both scope and accessibility. -```json5 +```jsonc [ // Index signature // No grouping for index signature. See above. // Fields - 'field', // = ['public-static-field', 'protected-static-field', 'private-static-field', 'public-instance-field', 'protected-instance-field', 'private-instance-field', - // 'public-abstract-field', 'protected-abstract-field', private-abstract-field']) + "field", // = ["public-static-field", "protected-static-field", "private-static-field", "public-instance-field", "protected-instance-field", "private-instance-field", + // "public-abstract-field", "protected-abstract-field", private-abstract-field"]) // Constructors // Only the accessibility of constructors is configurable. See above. // Methods - 'method', // = ['public-static-method', 'protected-static-method', 'private-static-method', 'public-instance-method', 'protected-instance-method', 'private-instance-method', - // 'public-abstract-method', 'protected-abstract-method', 'private-abstract-method']) + "method" // = ["public-static-method", "protected-static-method", "private-static-method", "public-instance-method", "protected-instance-method", "private-instance-method", + // "public-abstract-method", "protected-abstract-method", "private-abstract-method"]) ] ``` @@ -138,11 +163,13 @@ The third grouping option is to ignore both scope and accessibility. The default configuration looks as follows: -```json +```jsonc { "default": [ + // Index signature "signature", + // Fields "public-static-field", "protected-static-field", "private-static-field", @@ -165,8 +192,14 @@ The default configuration looks as follows: "field", + // Constructors + "public-constructor", + "protected-constructor", + "private-constructor", + "constructor", + // Methods "public-static-method", "protected-static-method", "private-static-method", @@ -194,6 +227,8 @@ The default configuration looks as follows: Note: The default configuration contains member group types which contain other member types (see above). This is intentional to provide better error messages. +Note: By default, the members are not sorted. If you want to sort them alphabetically, you have to provide a custom configuration. + ## Examples ### Custom `default` configuration @@ -448,7 +483,7 @@ const foo = class { }; ``` -Issue: Public static fields should come first, followed by static fields and instance fields. +Note: Public static fields should come first, followed by static fields and instance fields. ##### Correct examples @@ -542,21 +577,19 @@ class Foo { ##### Correct example -Examples of **correct** code for `{ "classes": [...] }` option: - ```ts class Foo { private C: string; // (irrelevant) public D: string; // (irrelevant) - public static E: string; // -> public static field + public B(): void {} // -> public instance method constructor() {} // (irrelevant) public static A(): void {} // (irrelevant) - public B(): void {} // -> public instance method + public static E: string; // -> public static field } ``` @@ -712,6 +745,73 @@ type Foo = { }; ``` +### Sorting alphabetically within member groups + +It is possible to sort all members within a group alphabetically. + +#### Configuration: `{ default: { memberTypes: , order: "alphabetically" } }` + +This will apply the default order (see above) and enforce an alphabetic order within each group. + +##### Incorrect examples + +```ts +interface Foo { + a: x; + b: x; + c: x; + + new (): Bar; + (): Baz; + + a(): void; + b(): void; + c(): void; + + // Wrong group order, should be placed before all field definitions + [a: string]: number; +} +``` + +```ts +interface Foo { + [a: string]: number; + + a: x; + b: x; + c: x; + + new (): Bar; + (): Baz; + + // Wrong alphabetic order within group + c(): void; + b(): void; + a(): void; +} +``` + +### Sorting alphabetically while ignoring member groups + +It is also possible to sort all members and ignore the member groups completely. + +#### Configuration: `{ default: { memberTypes: "never", order: "alphabetically" } }` + +##### Incorrect example + +```ts +interface Foo { + b(): void; + a: b; + + [a: string]: number; // Order doesn't matter (no sortable identifier) + new (): Bar; // Order doesn't matter (no sortable identifier) + (): Baz; // Order doesn't matter (no sortable identifier) +} +``` + +Note: Wrong alphabetic order `b(): void` should come after `a: b`. + ## When Not To Use It If you don't care about the general structure of your classes and interfaces, then you will not need this rule. diff --git a/packages/eslint-plugin/docs/rules/method-signature-style.md b/packages/eslint-plugin/docs/rules/method-signature-style.md new file mode 100644 index 00000000000..d760c8d0193 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/method-signature-style.md @@ -0,0 +1,83 @@ +# Enforces using a particular method signature syntax. (`method-signature-style`) + +There are two ways to define an object/interface function property. + +```ts +// method shorthand syntax +interface T1 { + func(arg: string): number; +} + +// regular property with function type +interface T2 { + func: (arg: string) => number; +} +``` + +A good practice is to use the TypeScript's `strict` option (which implies `strictFunctionTypes`) which enables correct typechecking for function properties only (method signatures get old behavior). + +TypeScript FAQ: + +> A method and a function property of the same type behave differently. +> Methods are always bivariant in their argument, while function properties are contravariant in their argument under `strictFunctionTypes`. + +See the reasoning behind that in the [TypeScript PR for the compiler option](https://github.com/microsoft/TypeScript/pull/18654). + +## Options + +This rule accepts one string option: + +- `"property"`: Enforce using property signature for functions. Use this to enforce maximum correctness together with TypeScript's strict mode. +- `"method"`: Enforce using method signature for functions. Use this if you aren't using TypeScript's strict mode and prefer this style. + +The default is `"property"`. + +## Rule Details + +Examples of **incorrect** code with `property` option. + +```ts +interface T1 { + func(arg: string): number; +} +type T2 = { + func(arg: boolean): void; +}; +``` + +Examples of **correct** code with `property` option. + +```ts +interface T1 { + func: (arg: string) => number; +} +type T2 = { + func: (arg: boolean) => void; +}; +``` + +Examples of **incorrect** code with `method` option. + +```ts +interface T1 { + func: (arg: string) => number; +} +type T2 = { + func: (arg: boolean) => void; +}; +``` + +Examples of **correct** code with `method` option. + +```ts +interface T1 { + func(arg: string): number; +} +type T2 = { + func(arg: boolean): void; +}; +``` + +## When Not To Use It + +If you don't want to enforce a particular style for object/interface function types, and/or if you don't use `strictFunctionTypes`, then you don't need this rule. diff --git a/packages/eslint-plugin/docs/rules/no-array-constructor.md b/packages/eslint-plugin/docs/rules/no-array-constructor.md index 3ab59cbc6ff..fb9a68e7429 100644 --- a/packages/eslint-plugin/docs/rules/no-array-constructor.md +++ b/packages/eslint-plugin/docs/rules/no-array-constructor.md @@ -1,10 +1,9 @@ # Disallow generic `Array` constructors (`no-array-constructor`) -Use of the `Array` constructor to construct a new array is generally discouraged in favor of array literal notation because of the single-argument pitfall and because the `Array` global may be redefined. Two exceptions are when the Array constructor is used to intentionally create sparse arrays of a specified size by giving the constructor a single numeric argument, or when using TypeScript type parameters to specify the type of the items of the array (`new Array()`). - ## Rule Details -This rule disallows `Array` constructors. +This rule extends the base [`eslint/no-array-constructor`](https://eslint.org/docs/rules/no-array-constructor) rule. +It adds support for the generically typed `Array` constructor (`new Array()`). Examples of **incorrect** code for this rule: @@ -27,8 +26,18 @@ Array(500); new Array(someOtherArray.length); ``` -## When Not To Use It +## How to use + +```jsonc +{ + // note you must disable the base rule as it can report incorrect errors + "no-array-constructor": "off", + "@typescript-eslint/no-array-constructor": ["error"] +} +``` + +## Options -This rule enforces a nearly universal stylistic concern. That being said, this rule may be disabled if the constructor style is preferred. +See [`eslint/no-array-constructor` options](https://eslint.org/docs/rules/no-array-constructor#options). -Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/7685fed33b15763ee3cf7dbe1facfc5ba85173f3/docs/rules/no-array-constructor.md) +Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/no-array-constructor.md) diff --git a/packages/eslint-plugin/docs/rules/no-base-to-string.md b/packages/eslint-plugin/docs/rules/no-base-to-string.md index 4e73eb6339a..5c476b51303 100644 --- a/packages/eslint-plugin/docs/rules/no-base-to-string.md +++ b/packages/eslint-plugin/docs/rules/no-base-to-string.md @@ -1,6 +1,6 @@ # Requires that `.toString()` is only called on objects which provide useful information when stringified (`no-base-to-string`) -JavaScript will call `toString()` on an object when it is converted to a string, such as when `+` adding to a string or in `${}` template literals. +JavaScript will call `toString()` on an object when it is converted to a string, such as when `+` adding to a string or in `${}` template literals. The default Object `.toString()` returns `"[object Object]"`, so this rule requires stringified objects define a more useful `.toString()` method. @@ -52,6 +52,32 @@ const literalWithToString = { `Value: ${literalWithToString}`; ``` +## Options + +The rule accepts an options object with the following properties: + +```ts +type Options = { + // if true, interpolated expressions in tagged templates will not be checked + ignoreTaggedTemplateExpressions?: boolean; +}; + +const defaults = { + ignoreTaggedTemplateExpressions: false, +}; +``` + +### `ignoreTaggedTemplateExpressions` + +This allows to skip checking tagged templates, for cases where the tags do not necessarily stringify interpolated values. + +Examples of additional **correct** code for this rule with `{ ignoreTaggedTemplateExpressions: true }`: + +```ts +function tag() {} +tag`${{}}`; +``` + ## When Not To Use It If you don't mind `"[object Object]"` in your strings, then you will not need this rule. diff --git a/packages/eslint-plugin/docs/rules/no-dupe-class-members.md b/packages/eslint-plugin/docs/rules/no-dupe-class-members.md index d0b3b57d988..ddfc1b9f1bd 100644 --- a/packages/eslint-plugin/docs/rules/no-dupe-class-members.md +++ b/packages/eslint-plugin/docs/rules/no-dupe-class-members.md @@ -7,7 +7,7 @@ It adds support for TypeScript's method overload definitions. ## How to use -```cjson +```jsonc { // note you must disable the base rule as it can report incorrect errors "no-dupe-class-members": "off", @@ -15,4 +15,8 @@ It adds support for TypeScript's method overload definitions. } ``` +## Options + +See [`eslint/no-dupe-class-members` options](https://eslint.org/docs/rules/no-dupe-class-members#options). + Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/no-dupe-class-members.md) diff --git a/packages/eslint-plugin/docs/rules/no-empty-function.md b/packages/eslint-plugin/docs/rules/no-empty-function.md index 17628cb93c8..df6a3f82583 100644 --- a/packages/eslint-plugin/docs/rules/no-empty-function.md +++ b/packages/eslint-plugin/docs/rules/no-empty-function.md @@ -1,80 +1,24 @@ # Disallow empty functions (`no-empty-function`) -Empty functions can reduce readability because readers need to guess whether it’s intentional or not. So writing a clear comment for empty functions is a good practice. - ## Rule Details -The `@typescript-eslint/no-empty-function` rule extends the `no-empty-function` rule from ESLint core, and adds support for handling TypeScript specific code that would otherwise trigger the rule. +This rule extends the base [`eslint/no-empty-function`](https://eslint.org/docs/rules/no-empty-function) rule. +It adds support for handling TypeScript specific code that would otherwise trigger the rule. One example of valid TypeScript specific code that would otherwise trigger the `no-empty-function` rule is the use of [parameter properties](https://www.typescriptlang.org/docs/handbook/classes.html#parameter-properties) in constructor functions: -```typescript -class Person { - constructor(private firstName: string, private surname: string) {} -} -``` - -The above code is functionally equivalent to: +## How to use -```typescript -class Person { - private firstName: string; - private surname: string; - - constructor(firstName: string, surname: string) { - this.firstName = firstName; - this.surname = surname; - } -} -``` - -Parameter properties enable both the _declaration_ and _initialization_ of member properties in a single location, avoiding the boilerplate & duplication that is common when initializing member properties from parameter values in a constructor function. - -In these cases, although the constructor has an empty function body, it is technically valid and should not trigger an error. - -See the [ESLint documentation](https://eslint.org/docs/rules/no-empty-function) for more details on the `no-empty-function` rule. - -## Rule Changes - -```cjson +```jsonc { - // note you must disable the base rule as it can report incorrect errors - "no-empty-function": "off", - "@typescript-eslint/no-empty-function": "error" + // note you must disable the base rule as it can report incorrect errors + "no-empty-function": "off", + "@typescript-eslint/no-empty-function": ["error"] } ``` ## Options -This rule has an object option: - -- `allow` (`string[]`) - - `"protected-constructors"` - Protected class constructors. - - `"private-constructors"` - Private class constructors. - - [See the other options allowed](https://github.com/eslint/eslint/blob/master/docs/rules/no-empty-function.md#options) - -#### allow: protected-constructors - -Examples of **correct** code for the `{ "allow": ["protected-constructors"] }` option: - -```ts -/*eslint @typescript-eslint/no-empty-function: ["error", { "allow": ["protected-constructors"] }]*/ - -class Foo { - protected constructor() {} -} -``` - -#### allow: private-constructors - -Examples of **correct** code for the `{ "allow": ["private-constructors"] }` option: - -```ts -/*eslint @typescript-eslint/no-empty-function: ["error", { "allow": ["private-constructors"] }]*/ - -class Foo { - private constructor() {} -} -``` +See [`eslint/no-empty-function` options](https://eslint.org/docs/rules/no-empty-function#options). Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/no-empty-function.md) diff --git a/packages/eslint-plugin/docs/rules/no-explicit-any.md b/packages/eslint-plugin/docs/rules/no-explicit-any.md index 0c271392349..c440d3bdc60 100644 --- a/packages/eslint-plugin/docs/rules/no-explicit-any.md +++ b/packages/eslint-plugin/docs/rules/no-explicit-any.md @@ -119,30 +119,23 @@ function foo2(...args: readonly any[]): void {} function foo3(...args: Array): void {} function foo4(...args: ReadonlyArray): void {} -const bar1 = (...args: any[]): void {} -const bar2 = (...args: readonly any[]): void {} -const bar3 = (...args: Array): void {} -const bar4 = (...args: ReadonlyArray): void {} +declare function bar(...args: any[]): void; -const baz1 = function (...args: any[]) {} -const baz2 = function (...args: readonly any[]) {} -const baz3 = function (...args: Array) {} -const baz4 = function (...args: ReadonlyArray) {} +const baz = (...args: any[]) => {}; +const qux = function(...args: any[]) {}; -interface Qux1 { (...args: any[]): void; } -interface Qux2 { (...args: readonly any[]): void; } -interface Qux3 { (...args: Array): void; } -interface Qux4 { (...args: ReadonlyArray): void; } +type Quux = (...args: any[]) => void; +type Quuz = new (...args: any[]) => void; -function quux1(fn: (...args: any[]) => void): void {} -function quux2(fn: (...args: readonly any[]) => void): void {} -function quux3(fn: (...args: Array) => void): void {} -function quux4(fn: (...args: ReadonlyArray) => void): void {} - -function quuz1(): ((...args: any[]) => void) {} -function quuz2(): ((...args: readonly any[]) => void) {} -function quuz3(): ((...args: Array) => void) {} -function quuz4(): ((...args: ReadonlyArray) => void) {} +interface Grault { + (...args: any[]): void; +} +interface Corge { + new (...args: any[]): void; +} +interface Garply { + f(...args: any[]): void; +} ``` Examples of **correct** code for the `{ "ignoreRestArgs": true }` option: @@ -155,30 +148,23 @@ function foo2(...args: readonly any[]): void {} function foo3(...args: Array): void {} function foo4(...args: ReadonlyArray): void {} -const bar1 = (...args: any[]): void {} -const bar2 = (...args: readonly any[]): void {} -const bar3 = (...args: Array): void {} -const bar4 = (...args: ReadonlyArray): void {} - -const baz1 = function (...args: any[]) {} -const baz2 = function (...args: readonly any[]) {} -const baz3 = function (...args: Array) {} -const baz4 = function (...args: ReadonlyArray) {} - -interface Qux1 { (...args: any[]): void; } -interface Qux2 { (...args: readonly any[]): void; } -interface Qux3 { (...args: Array): void; } -interface Qux4 { (...args: ReadonlyArray): void; } - -function quux1(fn: (...args: any[]) => void): void {} -function quux2(fn: (...args: readonly any[]) => void): void {} -function quux3(fn: (...args: Array) => void): void {} -function quux4(fn: (...args: ReadonlyArray) => void): void {} - -function quuz1(): ((...args: any[]) => void) {} -function quuz2(): ((...args: readonly any[]) => void) {} -function quuz3(): ((...args: Array) => void) {} -function quuz4(): ((...args: ReadonlyArray) => void) {} +declare function bar(...args: any[]): void; + +const baz = (...args: any[]) => {}; +const qux = function(...args: any[]) {}; + +type Quux = (...args: any[]) => void; +type Quuz = new (...args: any[]) => void; + +interface Grault { + (...args: any[]): void; +} +interface Corge { + new (...args: any[]): void; +} +interface Garply { + f(...args: any[]): void; +} ``` ## When Not To Use It diff --git a/packages/eslint-plugin/docs/rules/no-extra-parens.md b/packages/eslint-plugin/docs/rules/no-extra-parens.md index bcb21dec28d..8173b2ef1ad 100644 --- a/packages/eslint-plugin/docs/rules/no-extra-parens.md +++ b/packages/eslint-plugin/docs/rules/no-extra-parens.md @@ -1,22 +1,22 @@ # Disallow unnecessary parentheses (`no-extra-parens`) -This rule restricts the use of parentheses to only where they are necessary. - ## Rule Details This rule extends the base [`eslint/no-extra-parens`](https://eslint.org/docs/rules/no-extra-parens) rule. -It supports all options and features of the base rule plus TS type assertions. +It adds support for TypeScript type assertions. ## How to use -```cjson +```jsonc { - // note you must disable the base rule as it can report incorrect errors - "no-extra-parens": "off", - "@typescript-eslint/no-extra-parens": ["error"] + // note you must disable the base rule as it can report incorrect errors + "no-extra-parens": "off", + "@typescript-eslint/no-extra-parens": ["error"] } ``` ## Options See [`eslint/no-extra-parens` options](https://eslint.org/docs/rules/no-extra-parens#options). + +Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/no-extra-parens.md) diff --git a/packages/eslint-plugin/docs/rules/no-extra-semi.md b/packages/eslint-plugin/docs/rules/no-extra-semi.md index bed5e29d657..f6865bd8b54 100644 --- a/packages/eslint-plugin/docs/rules/no-extra-semi.md +++ b/packages/eslint-plugin/docs/rules/no-extra-semi.md @@ -3,10 +3,11 @@ ## Rule Details This rule extends the base [`eslint/no-extra-semi`](https://eslint.org/docs/rules/no-extra-semi) rule. +It adds support for class properties. ## How to use -```cjson +```jsonc { // note you must disable the base rule as it can report incorrect errors "no-extra-semi": "off", @@ -14,4 +15,8 @@ This rule extends the base [`eslint/no-extra-semi`](https://eslint.org/docs/rule } ``` +## Options + +See [`eslint/no-extra-semi` options](https://eslint.org/docs/rules/no-extra-semi#options). + Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/no-extra-semi.md) diff --git a/packages/eslint-plugin/docs/rules/no-magic-numbers.md b/packages/eslint-plugin/docs/rules/no-magic-numbers.md index 377e9272360..f41204c132b 100644 --- a/packages/eslint-plugin/docs/rules/no-magic-numbers.md +++ b/packages/eslint-plugin/docs/rules/no-magic-numbers.md @@ -1,25 +1,72 @@ # Disallow magic numbers (`no-magic-numbers`) -'Magic numbers' are numbers that occur multiple times in code without an explicit meaning. -They should preferably be replaced by named constants. - ## Rule Details -The `@typescript-eslint/no-magic-numbers` rule extends the `no-magic-numbers` rule from ESLint core, and adds support for handling TypeScript specific code that would otherwise trigger the rule. +This rule extends the base [`eslint/no-magic-numbers`](https://eslint.org/docs/rules/no-magic-numbers) rule. +It adds support for: -See the [ESLint documentation](https://eslint.org/docs/rules/no-magic-numbers) for more details on the `no-magic-numbers` rule. +- numeric literal types (`type T = 1`), +- `enum` members (`enum Foo { bar = 1 }`), +- `readonly` class properties (`class Foo { readonly bar = 1 }`). -## Rule Changes +## How to use -```cjson +```jsonc { - // note you must disable the base rule as it can report incorrect errors - "no-magic-numbers": "off", - "@typescript-eslint/no-magic-numbers": ["error", { "ignoreNumericLiteralTypes": true }] + // note you must disable the base rule as it can report incorrect errors + "no-magic-numbers": "off", + "@typescript-eslint/no-magic-numbers": [ + "error", + { + /* options */ + } + ] +} +``` + +## Options + +See [`eslint/no-magic-numbers` options](https://eslint.org/docs/rules/no-magic-numbers#options). +This rule adds the following options: + +```ts +interface Options extends BaseNoMagicNumbersOptions { + ignoreEnums?: boolean; + ignoreNumericLiteralTypes?: boolean; + ignoreReadonlyClassProperties?: boolean; +} + +const defaultOptions: Options = { + ...baseNoMagicNumbersDefaultOptions, + ignoreEnums: false, + ignoreNumericLiteralTypes: false, + ignoreReadonlyClassProperties: false, +}; +``` + +### `ignoreEnums` + +A boolean to specify if enums used in TypeScript are considered okay. `false` by default. + +Examples of **incorrect** code for the `{ "ignoreEnums": false }` option: + +```ts +/*eslint @typescript-eslint/no-magic-numbers: ["error", { "ignoreEnums": false }]*/ + +enum foo = { + SECOND = 1000, } ``` -In addition to the options supported by the `no-magic-numbers` rule in ESLint core, the rule adds the following options: +Examples of **correct** code for the `{ "ignoreEnums": true }` option: + +```ts +/*eslint @typescript-eslint/no-magic-numbers: ["error", { "ignoreEnums": true }]*/ + +enum foo = { + SECOND = 1000, +} +``` ### `ignoreNumericLiteralTypes` @@ -69,28 +116,4 @@ class Foo { } ``` -### `ignoreEnums` - -A boolean to specify if enums used in TypeScript are considered okay. `false` by default. - -Examples of **incorrect** code for the `{ "ignoreEnums": false }` option: - -```ts -/*eslint @typescript-eslint/no-magic-numbers: ["error", { "ignoreEnums": false }]*/ - -enum foo = { - SECOND = 1000, -} -``` - -Examples of **correct** code for the `{ "ignoreEnums": true }` option: - -```ts -/*eslint @typescript-eslint/no-magic-numbers: ["error", { "ignoreEnums": true }]*/ - -enum foo = { - SECOND = 1000, -} -``` - Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/no-magic-numbers.md) diff --git a/packages/eslint-plugin/docs/rules/no-this-alias.md b/packages/eslint-plugin/docs/rules/no-this-alias.md index 7f5ee719fa0..b5d2afb542c 100644 --- a/packages/eslint-plugin/docs/rules/no-this-alias.md +++ b/packages/eslint-plugin/docs/rules/no-this-alias.md @@ -39,15 +39,15 @@ Examples of **correct** code for this rule: You can pass an object option: -```json5 +```jsonc { - '@typescript-eslint/no-this-alias': [ - 'error', + "@typescript-eslint/no-this-alias": [ + "error", { - allowDestructuring: true, // Allow `const { props, state } = this`; false by default - allowedNames: ['self'], // Allow `const self = this`; `[]` by default - }, - ], + "allowDestructuring": true, // Allow `const { props, state } = this`; false by default + "allowedNames": ["self"] // Allow `const self = this`; `[]` by default + } + ] } ``` diff --git a/packages/eslint-plugin/docs/rules/no-type-alias.md b/packages/eslint-plugin/docs/rules/no-type-alias.md index 9d7f1d47744..5b720b2f331 100644 --- a/packages/eslint-plugin/docs/rules/no-type-alias.md +++ b/packages/eslint-plugin/docs/rules/no-type-alias.md @@ -69,7 +69,7 @@ On the other hand, using a type alias as an interface can limit your ability to: - Debug your code: interfaces create a new name, so is easy to identify the base type of an object while debugging the application. -Finally, mapping types is an advance technique, leaving it open can quickly become a pain point +Finally, mapping types is an advanced technique and leaving it open can quickly become a pain point in your application. ## Rule Details diff --git a/packages/eslint-plugin/docs/rules/no-unsafe-assignment.md b/packages/eslint-plugin/docs/rules/no-unsafe-assignment.md new file mode 100644 index 00000000000..1a452e431eb --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-unsafe-assignment.md @@ -0,0 +1,62 @@ +# Disallows assigning any to variables and properties (`no-unsafe-assignment`) + +Despite your best intentions, the `any` type can sometimes leak into your codebase. +Assigning an `any` typed value to a variable can be hard to pick up on, particularly if it leaks in from an external library. Operations on the variable will not be checked at all by TypeScript, so it creates a potential safety hole, and source of bugs in your codebase. + +## Rule Details + +This rule disallows assigning `any` to a variable, and assigning `any[]` to an array destructuring. +This rule also compares the assigned type to the variable's type to ensure you don't assign an unsafe `any` in a generic position to a receiver that's expecting a specific type. For example, it will error if you assign `Set` to a variable declared as `Set`. + +Examples of **incorrect** code for this rule: + +```ts +const x = 1 as any, + y = 1 as any; +const [x] = 1 as any; +const [x] = [] as any[]; +const [x] = [1 as any]; +[x] = [1] as [any]; + +function foo(a = 1 as any) {} +class Foo { + constructor(private a = 1 as any) {} +} +class Foo { + private a = 1 as any; +} + +// generic position examples +const x: Set = new Set(); +const x: Map = new Map(); +const x: Set = new Set(); +const x: Set>> = new Set>>(); +``` + +Examples of **correct** code for this rule: + +```ts +const x = 1, + y = 1; +const [x] = [1]; +[x] = [1] as [number]; + +function foo(a = 1) {} +class Foo { + constructor(private a = 1) {} +} +class Foo { + private a = 1; +} + +// generic position examples +const x: Set = new Set(); +const x: Map = new Map(); +const x: Set = new Set(); +const x: Set>> = new Set>>(); +``` + +## Related to + +- [`no-explicit-any`](./no-explicit-any.md) +- TSLint: [`no-unsafe-any`](https://palantir.github.io/tslint/rules/no-unsafe-any/) diff --git a/packages/eslint-plugin/docs/rules/no-unsafe-call.md b/packages/eslint-plugin/docs/rules/no-unsafe-call.md index 3c18b7c1ee2..490ff78ced5 100644 --- a/packages/eslint-plugin/docs/rules/no-unsafe-call.md +++ b/packages/eslint-plugin/docs/rules/no-unsafe-call.md @@ -1,7 +1,7 @@ # Disallows calling an any type value (`no-unsafe-call`) Despite your best intentions, the `any` type can sometimes leak into your codebase. -Member access on `any` typed variables is not checked at all by TypeScript, so it creates a potential safety hole, and source of bugs in your codebase. +The arguments to, and return value of calling an `any` typed variable are not checked at all by TypeScript, so it creates a potential safety hole, and source of bugs in your codebase. ## Rule Details diff --git a/packages/eslint-plugin/docs/rules/no-untyped-public-signature.md b/packages/eslint-plugin/docs/rules/no-untyped-public-signature.md index 87a17895c38..ffcd083b57d 100644 --- a/packages/eslint-plugin/docs/rules/no-untyped-public-signature.md +++ b/packages/eslint-plugin/docs/rules/no-untyped-public-signature.md @@ -2,6 +2,11 @@ public methods are meant to be used by code outside of your class. By typing both the parameters and the return type of public methods they will be more readable and easy to use. +## DEPRECATED + +This rule has been deprecated in favour of the [`explicit-module-boundary-types`](./explicit-module-boundary-types.md) rule. +It will be removed in a future version of this plugin. + ## Rule Details This rule aims to ensure that only typed public methods are declared in the code. @@ -46,9 +51,12 @@ This rule, in its default state, does not require any argument. You may pass method names you would like this rule to ignore, like so: -```cjson +```jsonc { - "@typescript-eslint/no-untyped-public-signature": ["error", { "ignoredMethods": ["ignoredMethodName"] }] + "@typescript-eslint/no-untyped-public-signature": [ + "error", + { "ignoredMethods": ["ignoredMethodName"] } + ] } ``` diff --git a/packages/eslint-plugin/docs/rules/no-unused-expressions.md b/packages/eslint-plugin/docs/rules/no-unused-expressions.md index 63674120fbc..cb61c2ed040 100644 --- a/packages/eslint-plugin/docs/rules/no-unused-expressions.md +++ b/packages/eslint-plugin/docs/rules/no-unused-expressions.md @@ -1,16 +1,13 @@ # Disallow unused expressions (`no-unused-expressions`) -This rule aims to eliminate unused expressions which have no effect on the state of the program. - ## Rule Details This rule extends the base [`eslint/no-unused-expressions`](https://eslint.org/docs/rules/no-unused-expressions) rule. -It supports all options and features of the base rule. -This version adds support for numerous typescript features. +It adds support for optional call expressions `x?.()`, and directive in module declarations. ## How to use -```cjson +```jsonc { // note you must disable the base rule as it can report incorrect errors "no-unused-expressions": "off", diff --git a/packages/eslint-plugin/docs/rules/no-unused-vars.md b/packages/eslint-plugin/docs/rules/no-unused-vars.md index a84b4362d61..44cfae690f8 100644 --- a/packages/eslint-plugin/docs/rules/no-unused-vars.md +++ b/packages/eslint-plugin/docs/rules/no-unused-vars.md @@ -1,305 +1,24 @@ # Disallow unused variables (`no-unused-vars`) -Variables that are declared and not used anywhere in the code are most likely an error due to incomplete refactoring. Such variables take up space in the code and can lead to confusion by readers. +## PLEASE READ THIS ISSUE BEFORE USING THIS RULE [#1856](https://github.com/typescript-eslint/typescript-eslint/issues/1856) ## Rule Details -This rule is aimed at eliminating unused variables, functions, and parameters of functions. +This rule extends the base [`eslint/no-unused-vars`](https://eslint.org/docs/rules/no-unused-vars) rule. +It adds support for TypeScript features, such as types. -A variable is considered to be used if any of the following are true: +## How to use -- It represents a function that is called (`doSomething()`) -- It is read (`var y = x`) -- It is passed into a function as an argument (`doSomething(x)`) -- It is read inside of a function that is passed to another function (`doSomething(function() { foo(); })`) - -A variable is _not_ considered to be used if it is only ever assigned to (`var x = 5`) or declared. - -Examples of **incorrect** code for this rule: - -```js -/*eslint no-unused-vars: "error"*/ -/*global some_unused_var*/ - -// It checks variables you have defined as global -some_unused_var = 42; - -var x; - -// Write-only variables are not considered as used. -var y = 10; -y = 5; - -// A read for a modification of itself is not considered as used. -var z = 0; -z = z + 1; - -// By default, unused arguments cause warnings. -(function(foo) { - return 5; -})(); - -// Unused recursive functions also cause warnings. -function fact(n) { - if (n < 2) return 1; - return n * fact(n - 1); -} - -// When a function definition destructures an array, unused entries from the array also cause warnings. -function getY([x, y]) { - return y; -} -``` - -Examples of **correct** code for this rule: - -```js -/*eslint no-unused-vars: "error"*/ - -var x = 10; -alert(x); - -// foo is considered used here -myFunc( - function foo() { - // ... - }.bind(this), -); - -(function(foo) { - return foo; -})(); - -var myFunc; -myFunc = setTimeout(function() { - // myFunc is considered used - myFunc(); -}, 50); - -// Only the second argument from the destructured array is used. -function getY([, y]) { - return y; -} -``` - -### exported - -In environments outside of CommonJS or ECMAScript modules, you may use `var` to create a global variable that may be used by other scripts. You can use the `/* exported variableName */` comment block to indicate that this variable is being exported and therefore should not be considered unused. - -Note that `/* exported */` has no effect for any of the following: - -- when the environment is `node` or `commonjs` -- when `parserOptions.sourceType` is `module` -- when `ecmaFeatures.globalReturn` is `true` - -The line comment `// exported variableName` will not work as `exported` is not line-specific. - -Examples of **correct** code for `/* exported variableName */` operation: - -```js -/* exported global_var */ - -var global_var = 42; -``` - -## Options - -This rule takes one argument which can be a string or an object. The string settings are the same as those of the `vars` property (explained below). - -By default this rule is enabled with `all` option for variables and `after-used` for arguments. - -```CJSON +```jsonc { - "rules": { - // note you must disable the base rule as it can report incorrect errors - "no-unused-vars": "off", - "@typescript-eslint/no-unused-vars": ["error", { - "vars": "all", - "args": "after-used", - "ignoreRestSiblings": false - }] - } + // note you must disable the base rule as it can report incorrect errors + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": ["error"] } ``` -### `vars` - -The `vars` option has two settings: - -- `all` checks all variables for usage, including those in the global scope. This is the default setting. -- `local` checks only that locally-declared variables are used but will allow global variables to be unused. - -#### `vars: local` - -Examples of **correct** code for the `{ "vars": "local" }` option: - -```js -/*eslint no-unused-vars: ["error", { "vars": "local" }]*/ -/*global some_unused_var */ - -some_unused_var = 42; -``` - -### `varsIgnorePattern` - -The `varsIgnorePattern` option specifies exceptions not to check for usage: variables whose names match a regexp pattern. For example, variables whose names contain `ignored` or `Ignored`. - -Examples of **correct** code for the `{ "varsIgnorePattern": "[iI]gnored" }` option: - -```js -/*eslint no-unused-vars: ["error", { "varsIgnorePattern": "[iI]gnored" }]*/ - -var firstVarIgnored = 1; -var secondVar = 2; -console.log(secondVar); -``` - -### `args` - -The `args` option has three settings: - -- `after-used` - unused positional arguments that occur before the last used argument will not be checked, but all named arguments and all positional arguments after the last used argument will be checked. -- `all` - all named arguments must be used. -- `none` - do not check arguments. - -#### `args: after-used` - -Examples of **incorrect** code for the default `{ "args": "after-used" }` option: - -```js -/*eslint no-unused-vars: ["error", { "args": "after-used" }]*/ - -// 2 errors, for the parameters after the last used parameter (bar) -// "baz" is defined but never used -// "qux" is defined but never used -(function(foo, bar, baz, qux) { - return bar; -})(); -``` - -Examples of **correct** code for the default `{ "args": "after-used" }` option: - -```js -/*eslint no-unused-vars: ["error", {"args": "after-used"}]*/ - -(function(foo, bar, baz, qux) { - return qux; -})(); -``` - -#### `args: all` - -Examples of **incorrect** code for the `{ "args": "all" }` option: - -```js -/*eslint no-unused-vars: ["error", { "args": "all" }]*/ - -// 2 errors -// "foo" is defined but never used -// "baz" is defined but never used -(function(foo, bar, baz) { - return bar; -})(); -``` - -#### `args: none` - -Examples of **correct** code for the `{ "args": "none" }` option: - -```js -/*eslint no-unused-vars: ["error", { "args": "none" }]*/ - -(function(foo, bar, baz) { - return bar; -})(); -``` - -### `ignoreRestSiblings` - -The `ignoreRestSiblings` option is a boolean (default: `false`). Using a [Rest Property](https://github.com/tc39/proposal-object-rest-spread) it is possible to "omit" properties from an object, but by default the sibling properties are marked as "unused". With this option enabled the rest property's siblings are ignored. - -Examples of **correct** code for the `{ "ignoreRestSiblings": true }` option: - -```js -/*eslint no-unused-vars: ["error", { "ignoreRestSiblings": true }]*/ -// 'type' is ignored because it has a rest property sibling. -var { type, ...coords } = data; -``` - -### `argsIgnorePattern` - -The `argsIgnorePattern` option specifies exceptions not to check for usage: arguments whose names match a regexp pattern. For example, variables whose names begin with an underscore. - -Examples of **correct** code for the `{ "argsIgnorePattern": "^_" }` option: - -```js -/*eslint no-unused-vars: ["error", { "argsIgnorePattern": "^_" }]*/ - -function foo(x, _y) { - return x + 1; -} -foo(); -``` - -### `caughtErrors` - -The `caughtErrors` option is used for `catch` block arguments validation. - -It has two settings: - -- `none` - do not check error objects. This is the default setting. -- `all` - all named arguments must be used. - -#### `caughtErrors: none` - -Not specifying this rule is equivalent of assigning it to `none`. - -Examples of **correct** code for the `{ "caughtErrors": "none" }` option: - -```js -/*eslint no-unused-vars: ["error", { "caughtErrors": "none" }]*/ - -try { - //... -} catch (err) { - console.error('errors'); -} -``` - -#### `caughtErrors: all` - -Examples of **incorrect** code for the `{ "caughtErrors": "all" }` option: - -```js -/*eslint no-unused-vars: ["error", { "caughtErrors": "all" }]*/ - -// 1 error -// "err" is defined but never used -try { - //... -} catch (err) { - console.error('errors'); -} -``` - -### `caughtErrorsIgnorePattern` - -The `caughtErrorsIgnorePattern` option specifies exceptions not to check for usage: catch arguments whose names match a regexp pattern. For example, variables whose names begin with a string 'ignore'. - -Examples of **correct** code for the `{ "caughtErrorsIgnorePattern": "^ignore" }` option: - -```js -/*eslint no-unused-vars: ["error", { "caughtErrorsIgnorePattern": "^ignore" }]*/ - -try { - //... -} catch (ignoreErr) { - console.error('errors'); -} -``` - -## When Not To Use It +## Options -If you don't want to be notified about unused variables or function arguments, you can safely turn this rule off. +See [`eslint/no-unused-vars` options](https://eslint.org/docs/rules/no-unused-vars#options). Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/no-unused-vars.md) diff --git a/packages/eslint-plugin/docs/rules/no-use-before-define.md b/packages/eslint-plugin/docs/rules/no-use-before-define.md index e6acf604568..569b8130386 100644 --- a/packages/eslint-plugin/docs/rules/no-use-before-define.md +++ b/packages/eslint-plugin/docs/rules/no-use-before-define.md @@ -1,146 +1,46 @@ # Disallow the use of variables before they are defined (`no-use-before-define`) -In JavaScript, prior to ES6, variable and function declarations are hoisted to the top of a scope, so it's possible to use identifiers before their formal declarations in code. This can be confusing and some believe it is best to always declare variables and functions before using them. - -In ES6, block-level bindings (`let` and `const`) introduce a "temporal dead zone" where a `ReferenceError` will be thrown with any attempt to access the variable before its declaration. +## PLEASE READ THIS ISSUE BEFORE USING THIS RULE [#1856](https://github.com/typescript-eslint/typescript-eslint/issues/1856) ## Rule Details -This rule will warn when it encounters a reference to an identifier that has not yet been declared. - -Examples of **incorrect** code for this rule: - -```ts -/*eslint no-use-before-define: "error"*/ -/*eslint-env es6*/ - -alert(a); -var a = 10; - -f(); -function f() {} - -function g() { - return b; -} -var b = 1; - -// With blockBindings: true -{ - alert(c); - let c = 1; -} - -let myVar: StringOrNumber; -type StringOrNumber = string | number; -``` - -Examples of **correct** code for this rule: +This rule extends the base [`eslint/no-use-before-define`](https://eslint.org/docs/rules/no-use-before-define) rule. +It adds support for `type`, `interface` and `enum` declarations. -```ts -/*eslint no-use-before-define: "error"*/ -/*eslint-env es6*/ +## How to use -var a; -a = 10; -alert(a); - -function f() {} -f(1); - -var b = 1; -function g() { - return b; -} - -// With blockBindings: true +```jsonc { - let C; - c++; + // note you must disable the base rule as it can report incorrect errors + "no-use-before-define": "off", + "@typescript-eslint/no-use-before-define": ["error"] } - -type StringOrNumber = string | number; -let myVar: StringOrNumber; ``` ## Options -```json -{ - "no-use-before-define": ["error", { "functions": true, "classes": true }] -} -``` - -- `functions` (`boolean`) - - The flag which shows whether or not this rule checks function declarations. - If this is `true`, this rule warns every reference to a function before the function declaration. - Otherwise, ignores those references. - Function declarations are hoisted, so it's safe. - Default is `true`. -- `classes` (`boolean`) - - The flag which shows whether or not this rule checks class declarations of upper scopes. - If this is `true`, this rule warns every reference to a class before the class declaration. - Otherwise, ignores those references if the declaration is in upper function scopes. - Class declarations are not hoisted, so it might be danger. - Default is `true`. -- `enums` (`boolean`) - - The flag which shows whether or not this rule checks enum declarations of upper scopes. - If this is `true`, this rule warns every reference to a enum before the enum declaration. - Otherwise, ignores those references. - Default is `true`. -- `variables` (`boolean`) - - This flag determines whether or not the rule checks variable declarations in upper scopes. - If this is `true`, the rule warns every reference to a variable before the variable declaration. - Otherwise, the rule ignores a reference if the declaration is in an upper scope, while still reporting the reference if it's in the same scope as the declaration. - Default is `true`. -- `typedefs` (`boolean`, **added** in `@typescript-eslint/eslint-plugin`) - - The flag which shows whether or not this rule checks type declarations. - If this is `true`, this rule warns every reference to a type before the type declaration. - Otherwise, ignores those references. - Type declarations are hoisted, so it's safe. - Default is `true`. - -This rule accepts `"nofunc"` string as an option. -`"nofunc"` is the same as `{ "functions": false, "classes": true }`. - -### `functions` - -Examples of **correct** code for the `{ "functions": false }` option: - -```js -/*eslint no-use-before-define: ["error", { "functions": false }]*/ - -f(); -function f() {} -``` - -### `classes` +See [`eslint/no-use-before-define` options](https://eslint.org/docs/rules/no-use-before-define#options). +This rule adds the following options: -Examples of **incorrect** code for the `{ "classes": false }` option: - -```js -/*eslint no-use-before-define: ["error", { "classes": false }]*/ -/*eslint-env es6*/ - -new A(); -class A {} -``` - -Examples of **correct** code for the `{ "classes": false }` option: - -```js -/*eslint no-use-before-define: ["error", { "classes": false }]*/ -/*eslint-env es6*/ - -function foo() { - return new A(); +```ts +interface Options extends BaseNoMagicNumbersOptions { + enums?: boolean; + typedefs?: boolean; } -class A {} +const defaultOptions: Options = { + ...baseNoMagicNumbersDefaultOptions, + enums: true, + typedefs: true, +}; ``` ### `enums` +The flag which shows whether or not this rule checks enum declarations of upper scopes. +If this is `true`, this rule warns every reference to a enum before the enum declaration. +Otherwise, ignores those references. + Examples of **incorrect** code for the `{ "enums": true }` option: ```ts @@ -176,31 +76,13 @@ enum Foo { } ``` -### `variables` - -Examples of **incorrect** code for the `{ "variables": false }` option: - -```js -/*eslint no-use-before-define: ["error", { "variables": false }]*/ - -console.log(foo); -var foo = 1; -``` - -Examples of **correct** code for the `{ "variables": false }` option: - -```js -/*eslint no-use-before-define: ["error", { "variables": false }]*/ - -function baz() { - console.log(foo); -} - -var foo = 1; -``` - ### `typedefs` +The flag which shows whether or not this rule checks type declarations. +If this is `true`, this rule warns every reference to a type before the type declaration. +Otherwise, ignores those references. +Type declarations are hoisted, so it's safe. + Examples of **correct** code for the `{ "typedefs": false }` option: ```ts diff --git a/packages/eslint-plugin/docs/rules/no-useless-constructor.md b/packages/eslint-plugin/docs/rules/no-useless-constructor.md index 757ae5d4a12..524ad4b92d9 100644 --- a/packages/eslint-plugin/docs/rules/no-useless-constructor.md +++ b/packages/eslint-plugin/docs/rules/no-useless-constructor.md @@ -1,88 +1,26 @@ # Disallow unnecessary constructors (`no-useless-constructor`) -ES2015 provides a default class constructor if one is not specified. As such, it is unnecessary to provide an empty constructor or one that simply delegates into its parent class, as in the following examples: - -```js -class A { - constructor() {} -} - -class A extends B { - constructor(value) { - super(value); - } -} -``` - ## Rule Details -This rule flags class constructors that can be safely removed without changing how the class works. - -## Examples - -Examples of **incorrect** code for this rule: - -```js -/*eslint @typescript-eslint/no-useless-constructor: "error"*/ - -class A { - constructor() {} -} - -class A extends B { - constructor(...args) { - super(...args); - } -} -``` +This rule extends the base [`eslint/no-useless-constructor`](https://eslint.org/docs/rules/no-useless-constructor) rule. +It adds support for: -Examples of **correct** code for this rule: - -```js -/*eslint @typescript-eslint/no-useless-constructor: "error"*/ - -class A {} - -class A { - constructor() { - doSomething(); - } -} - -class A extends B { - constructor() { - super('foo'); - } -} - -class A extends B { - constructor() { - super(); - doSomething(); - } -} - -class A extends B { - constructor(protected name: string) {} -} - -class A extends B { - protected constructor() {} -} -``` +- constructors marked as `protected` / `private` (i.e. marking a constructor as non-public), +- `public` constructors when there is no superclass, +- constructors with only parameter properties. -## Rule Changes +## How to use -```cjson +```jsonc { - // note you must disable the base rule as it can report incorrect errors - "no-useless-constructor": "off", - "@typescript-eslint/no-useless-constructor": "error", + // note you must disable the base rule as it can report incorrect errors + "no-useless-constructor": "off", + "@typescript-eslint/no-useless-constructor": ["error"] } ``` -## When Not To Use It +## Options -If you don't want to be notified about unnecessary constructors, you can safely disable this rule. +See [`eslint/no-useless-constructor` options](https://eslint.org/docs/rules/no-useless-constructor#options). Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/no-useless-constructor.md) diff --git a/packages/eslint-plugin/docs/rules/prefer-as-const.md b/packages/eslint-plugin/docs/rules/prefer-as-const.md index bb288192d26..b710a8275e7 100644 --- a/packages/eslint-plugin/docs/rules/prefer-as-const.md +++ b/packages/eslint-plugin/docs/rules/prefer-as-const.md @@ -25,4 +25,4 @@ let foo = { bar: 'baz' }; ## When Not To Use It -If you are using typescript < 3.4 +If you are using TypeScript < 3.4 diff --git a/packages/eslint-plugin/docs/rules/prefer-readonly.md b/packages/eslint-plugin/docs/rules/prefer-readonly.md index 6731a758580..9ce16a8c35f 100644 --- a/packages/eslint-plugin/docs/rules/prefer-readonly.md +++ b/packages/eslint-plugin/docs/rules/prefer-readonly.md @@ -52,9 +52,9 @@ This rule, in its default state, does not require any argument. You may pass `"onlyInlineLambdas": true` as a rule option within an object to restrict checking only to members immediately assigned a lambda value. -```cjson +```jsonc { - "@typescript-eslint/prefer-readonly": ["error", { "onlyInlineLambdas": true }] + "@typescript-eslint/prefer-readonly": ["error", { "onlyInlineLambdas": true }] } ``` diff --git a/packages/eslint-plugin/docs/rules/prefer-reduce-type-parameter.md b/packages/eslint-plugin/docs/rules/prefer-reduce-type-parameter.md new file mode 100644 index 00000000000..d6f68021cc7 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/prefer-reduce-type-parameter.md @@ -0,0 +1,54 @@ +# Prefer using type parameter when calling `Array#reduce` instead of casting (`prefer-reduce-type-parameter`) + +It's common to call `Array#reduce` with a generic type, such as an array or object, as the initial value. +Since these values are empty, their types are not usable: + +- `[]` has type `never[]`, which can't have items pushed into it as nothing is type `never` +- `{}` has type `{}`, which doesn't have an index signature and so can't have properties added to it + +A common solution to this problem is to cast the initial value. While this will work, it's not the most optimal +solution as casting has subtle effects on the underlying types that can allow bugs to slip in. + +A better (and lesser known) solution is to pass the type in as a generic parameter to `Array#reduce` explicitly. +This means that TypeScript doesn't have to try to infer the type, and avoids the common pitfalls that come with casting. + +## Rule Details + +This rule looks for calls to `Array#reduce`, and warns if an initial value is being passed & casted, +suggesting instead to pass the cast type to `Array#reduce` as its generic parameter. + +Examples of **incorrect** code for this rule: + +```ts +[1, 2, 3].reduce((arr, num) => arr.concat(num * 2), [] as number[]); + +['a', 'b'].reduce( + (accum, name) => ({ + ...accum, + [name]: true, + }), + {} as Record, +); +``` + +Examples of **correct** code for this rule: + +```ts +[1, 2, 3].reduce((arr, num) => arr.concat(num * 2), []); + +['a', 'b'].reduce>( + (accum, name) => ({ + ...accum, + [name]: true, + }), + {}, +); +``` + +## Options + +There are no options. + +## When Not To Use It + +If you don't want to use typechecking in your linting, you can't use this rule. diff --git a/packages/eslint-plugin/docs/rules/prefer-ts-expect-error.md b/packages/eslint-plugin/docs/rules/prefer-ts-expect-error.md new file mode 100644 index 00000000000..7ec0cac771b --- /dev/null +++ b/packages/eslint-plugin/docs/rules/prefer-ts-expect-error.md @@ -0,0 +1,47 @@ +# Recommends using `// @ts-expect-error` over `// @ts-ignore` (`prefer-ts-expect-error`) + +TypeScript allows you to suppress all errors on a line by placing a single-line comment starting with `@ts-ignore` immediately before the erroring line. +While powerful, there is no way to know if a `@ts-ignore` is actually suppressing an error without manually investigating what happens when the `@ts-ignore` is removed. + +This means its easy for `@ts-ignore`s to be forgotten about, and remain in code even after the error they were suppressing is fixed. +This is dangerous, as if a new error arises on that line it'll be suppressed by the forgotten about `@ts-ignore`, and so be missed. + +To address this, TS3.9 ships with a new single-line comment directive: `// @ts-expect-error`. + +This directive operates in the same manner as `@ts-ignore`, but will error if the line it's meant to be suppressing doesn't actually contain an error, making it a lot safer. + +## Rule Details + +This rule looks for usages of `@ts-ignore`, and flags them to be replaced with `@ts-expect-error`. + +Examples of **incorrect** code for this rule: + +```ts +// @ts-ignore +const str: string = 1; + +const isOptionEnabled = (key: string): boolean => { + // @ts-ignore: if key isn't in globalOptions it'll be undefined which is false + return !!globalOptions[key]; +}; +``` + +Examples of **correct** code for this rule: + +```ts +// @ts-expect-error +const str: string = 1; + +const isOptionEnabled = (key: string): boolean => { + // @ts-expect-error: if key isn't in globalOptions it'll be undefined which is false + return !!globalOptions[key]; +}; +``` + +## When Not To Use It + +If you are not using TypeScript 3.9 (or greater), then you will not be able to use this rule, as the directive is not supported + +## Further Reading + +- [Original Implementing PR](https://github.com/microsoft/TypeScript/pull/36014) diff --git a/packages/eslint-plugin/docs/rules/quotes.md b/packages/eslint-plugin/docs/rules/quotes.md index e9c37df36f7..6b63587f6b4 100644 --- a/packages/eslint-plugin/docs/rules/quotes.md +++ b/packages/eslint-plugin/docs/rules/quotes.md @@ -3,11 +3,11 @@ ## Rule Details This rule extends the base [`eslint/quotes`](https://eslint.org/docs/rules/quotes) rule. -It supports all options and features of the base rule. +It adds support for TypeScript features which allow quoted names, but not backtick quoted names. ## How to use -```cjson +```jsonc { // note you must disable the base rule as it can report incorrect errors "quotes": "off", diff --git a/packages/eslint-plugin/docs/rules/require-await.md b/packages/eslint-plugin/docs/rules/require-await.md index 23160dc7db5..2d8ad41fda9 100644 --- a/packages/eslint-plugin/docs/rules/require-await.md +++ b/packages/eslint-plugin/docs/rules/require-await.md @@ -1,49 +1,32 @@ # Disallow async functions which have no `await` expression (`require-await`) -Asynchronous functions that don’t use `await` might not need to be asynchronous functions and could be the unintentional result of refactoring. - ## Rule Details -The `@typescript-eslint/require-await` rule extends the `require-await` rule from ESLint core, and allows for cases where the additional typing information can prevent false positives that would otherwise trigger the rule. - -One example is when a function marked as `async` returns a value that is: +This rule extends the base [`eslint/require-await`](https://eslint.org/docs/rules/require-await) rule. +It uses type information to add support for `async` functions that return a `Promise`. -1. already a promise; or -2. the result of calling another `async` function +Examples of **correct** code for this rule: -```typescript -async function numberOne(): Promise { +```ts +async function returnsPromise1() { return Promise.resolve(1); } -async function getDataFromApi(endpoint: string): Promise { - return fetch(endpoint); -} -``` - -In the above examples, the core `require-await` triggers the following warnings: - -``` -async function 'numberOne' has no 'await' expression -async function 'getDataFromApi' has no 'await' expression +const returnsPromise2 = () => returnsPromise1(); ``` -One way to resolve these errors is to remove the `async` keyword. However doing so can cause a conflict with the [`@typescript-eslint/promise-function-async`](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/promise-function-async.md) rule (if enabled), which requires any function returning a promise to be marked as `async`. +## How to use -Another way to resolve these errors is to add an `await` keyword to the return statements. However doing so can cause a conflict with the [`no-return-await`](https://eslint.org/docs/rules/no-return-await) rule (if enabled), which warns against using `return await` since the return value of an `async` function is always wrapped in `Promise.resolve` anyway. - -With the additional typing information available in TypeScript code, this extension to the `require-await` rule is able to look at the _actual_ return types of an `async` function (before being implicitly wrapped in `Promise.resolve`), and avoid the need for an `await` expression when the return value is already a promise. - -See the [ESLint documentation](https://eslint.org/docs/rules/require-await) for more details on the `require-await` rule. - -## Rule Changes - -```cjson +```jsonc { - // note you must disable the base rule as it can report incorrect errors - "require-await": "off", - "@typescript-eslint/require-await": "error" + // note you must disable the base rule as it can report incorrect errors + "require-await": "off", + "@typescript-eslint/require-await": "error" } ``` +## Options + +See [`eslint/require-await` options](https://eslint.org/docs/rules/require-await#options). + Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/require-await.md) diff --git a/packages/eslint-plugin/docs/rules/restrict-template-expressions.md b/packages/eslint-plugin/docs/rules/restrict-template-expressions.md index 549da98eebd..4c52e74445f 100644 --- a/packages/eslint-plugin/docs/rules/restrict-template-expressions.md +++ b/packages/eslint-plugin/docs/rules/restrict-template-expressions.md @@ -59,6 +59,16 @@ const msg1 = `arg = ${arg}`; const msg2 = `arg = ${arg || 'not truthy'}`; ``` +### `allowAny` + +Examples of additional **correct** code for this rule with `{ allowAny: true }`: + +```ts +const user = JSON.parse('{ "name": "foo" }'); +const msg1 = `arg = ${user.name}`; +const msg2 = `arg = ${user.name || 'the user with no name'}`; +``` + ### `allowNullable` Examples of additional **correct** code for this rule with `{ allowNullable: true }`: diff --git a/packages/eslint-plugin/docs/rules/return-await.md b/packages/eslint-plugin/docs/rules/return-await.md index c00c2be5d02..1f0adc71ec1 100644 --- a/packages/eslint-plugin/docs/rules/return-await.md +++ b/packages/eslint-plugin/docs/rules/return-await.md @@ -4,95 +4,114 @@ Returning an awaited promise can make sense for better stack trace information a ## Rule Details -The `@typescript-eslint/return-await` rule specifies that awaiting a returned non-promise is never allowed. By default, the rule requires awaiting a returned promise in a `try-catch-finally` block and disallows returning an awaited promise in any other context. Optionally, the rule can require awaiting returned promises in all contexts, or disallow them in all contexts. +This rule builds on top of the [`eslint/no-return-await`](https://eslint.org/docs/rules/no-return-await) rule. +It expands upon the base rule to add support for optionally requiring `return await` in certain cases. + +## How to use + +```jsonc +{ + // note you must disable the base rule as it can report incorrect errors + "no-return-await": "off", + "@typescript-eslint/return-await": "error" +} +``` ## Options -`in-try-catch` (default): `await`-ing a returned promise is required in `try-catch-finally` blocks and disallowed elsewhere. +```ts +type Options = 'in-try-catch' | 'always' | 'never'; + +const defaultOptions: Options = 'in-try-catch'; +``` -`always`: `await`-ing a returned promise is required everywhere. +### `in-try-catch` -`never`: `await`-ing a returned promise is disallowed everywhere. +Requires that a returned promise must be `await`ed in `try-catch-finally` blocks, and disallows it elsewhere. -```typescript -// valid in-try-catch -async function validInTryCatch1() { +Examples of **incorrect** code with `in-try-catch`: + +```ts +async function invalidInTryCatch1() { try { - return await Promise.resolve('try'); + return Promise.resolve('try'); } catch (e) {} } -async function validInTryCatch2() { - return Promise.resolve('try'); +async function invalidInTryCatch2() { + return await Promise.resolve('try'); } -async function validInTryCatch3() { - return 'value'; +async function invalidInTryCatch3() { + return await 'value'; } +``` -// valid always -async function validAlways1() { +Examples of **correct** code with `in-try-catch`: + +```ts +async function validInTryCatch1() { try { return await Promise.resolve('try'); } catch (e) {} } -async function validAlways2() { - return await Promise.resolve('try'); +async function validInTryCatch2() { + return Promise.resolve('try'); } -async function validAlways3() { +async function validInTryCatch3() { return 'value'; } +``` -// valid never -async function validNever1() { +### `always` + +Requires that all returned promises are `await`ed. + +Examples of **incorrect** code with `always`: + +```ts +async function invalidAlways1() { try { return Promise.resolve('try'); } catch (e) {} } -async function validNever2() { +async function invalidAlways2() { return Promise.resolve('try'); } -async function validNever3() { - return 'value'; +async function invalidAlways3() { + return await 'value'; } ``` -```typescript -// invalid in-try-catch -async function invalidInTryCatch1() { +Examples of **correct** code with `always`: + +```ts +async function validAlways1() { try { - return Promise.resolve('try'); + return await Promise.resolve('try'); } catch (e) {} } -async function invalidInTryCatch2() { +async function validAlways2() { return await Promise.resolve('try'); } -async function invalidInTryCatch3() { - return await 'value'; +async function validAlways3() { + return 'value'; } +``` -// invalid always -async function invalidAlways1() { - try { - return Promise.resolve('try'); - } catch (e) {} -} +### `never` -async function invalidAlways2() { - return Promise.resolve('try'); -} +Disallows all `await`ing any returned promises. -async function invalidAlways3() { - return await 'value'; -} +Examples of **incorrect** code with `never`: -// invalid never +```ts async function invalidNever1() { try { return await Promise.resolve('try'); @@ -108,16 +127,20 @@ async function invalidNever3() { } ``` -The rule also applies to `finally` blocks. So the following would be invalid with default options: +Examples of **correct** code with `never`: -```typescript -async function invalid() { +```ts +async function validNever1() { try { - return await Promise.resolve('try'); - } catch (e) { - return Promise.resolve('catch'); - } finally { - // cleanup - } + return Promise.resolve('try'); + } catch (e) {} +} + +async function validNever2() { + return Promise.resolve('try'); +} + +async function validNever3() { + return 'value'; } ``` diff --git a/packages/eslint-plugin/docs/rules/semi.md b/packages/eslint-plugin/docs/rules/semi.md index bbb3154fa1a..c233d354631 100644 --- a/packages/eslint-plugin/docs/rules/semi.md +++ b/packages/eslint-plugin/docs/rules/semi.md @@ -5,15 +5,13 @@ This rule enforces consistent use of semicolons after statements. ## Rule Details This rule extends the base [`eslint/semi`](https://eslint.org/docs/rules/semi) rule. -It supports all options and features of the base rule. -This version adds support for numerous typescript features. +It adds support for TypeScript features that require semicolons. -See also the [`@typescript-eslint/member-delimiter-style`](member-delimiter-style.md) rule, -which allows you to specify the delimiter for `type` and `interface` members. +See also the [`@typescript-eslint/member-delimiter-style`](member-delimiter-style.md) rule, which allows you to specify the delimiter for `type` and `interface` members. ## How to use -```cjson +```jsonc { // note you must disable the base rule as it can report incorrect errors "semi": "off", diff --git a/packages/eslint-plugin/docs/rules/space-before-function-paren.md b/packages/eslint-plugin/docs/rules/space-before-function-paren.md index bfbcb22d887..db2579e44d1 100644 --- a/packages/eslint-plugin/docs/rules/space-before-function-paren.md +++ b/packages/eslint-plugin/docs/rules/space-before-function-paren.md @@ -1,33 +1,13 @@ # Enforces consistent spacing before function parenthesis (`space-before-function-paren`) -When formatting a function, whitespace is allowed between the function name or `function` keyword and the opening parenthesis. Named functions also require a space between the `function` keyword and the function name, but anonymous functions require no whitespace. For example: - - -```ts -function withoutSpace(x) { - // ... -} - -function withSpace (x) { - // ... -} - -var anonymousWithoutSpace = function() {}; - -var anonymousWithSpace = function () {}; -``` - -Style guides may require a space after the `function` keyword for anonymous functions, while others specify no whitespace. Similarly, the space after a function name may or may not be required. - ## Rule Details This rule extends the base [`eslint/space-before-function-paren`](https://eslint.org/docs/rules/space-before-function-paren) rule. -It supports all options and features of the base rule. -This version adds support for generic type parameters on function calls. +It adds support for generic type parameters on function calls. ## How to use -```cjson +```jsonc { // note you must disable the base rule as it can report incorrect errors "space-before-function-paren": "off", diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index cb725f5be6d..d872079514a 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin", - "version": "2.25.0", + "version": "2.28.0", "description": "TypeScript plugin for ESLint", "keywords": [ "eslint", @@ -41,7 +41,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/experimental-utils": "2.25.0", + "@typescript-eslint/experimental-utils": "2.28.0", "functional-red-black-tree": "^1.0.1", "regexpp": "^3.0.0", "tsutils": "^3.17.1" diff --git a/packages/eslint-plugin/src/configs/all.json b/packages/eslint-plugin/src/configs/all.json index c5575d4970d..c736840a3ff 100644 --- a/packages/eslint-plugin/src/configs/all.json +++ b/packages/eslint-plugin/src/configs/all.json @@ -24,6 +24,7 @@ "@typescript-eslint/indent": "error", "@typescript-eslint/member-delimiter-style": "error", "@typescript-eslint/member-ordering": "error", + "@typescript-eslint/method-signature-style": "error", "@typescript-eslint/naming-convention": "error", "no-array-constructor": "off", "@typescript-eslint/no-array-constructor": "error", @@ -62,6 +63,7 @@ "@typescript-eslint/no-unnecessary-qualifier": "error", "@typescript-eslint/no-unnecessary-type-arguments": "error", "@typescript-eslint/no-unnecessary-type-assertion": "error", + "@typescript-eslint/no-unsafe-assignment": "error", "@typescript-eslint/no-unsafe-call": "error", "@typescript-eslint/no-unsafe-member-access": "error", "@typescript-eslint/no-unsafe-return": "error", @@ -84,8 +86,10 @@ "@typescript-eslint/prefer-optional-chain": "error", "@typescript-eslint/prefer-readonly": "error", "@typescript-eslint/prefer-readonly-parameter-types": "error", + "@typescript-eslint/prefer-reduce-type-parameter": "error", "@typescript-eslint/prefer-regexp-exec": "error", "@typescript-eslint/prefer-string-starts-ends-with": "error", + "@typescript-eslint/prefer-ts-expect-error": "error", "@typescript-eslint/promise-function-async": "error", "quotes": "off", "@typescript-eslint/quotes": "error", diff --git a/packages/eslint-plugin/src/rules/ban-ts-comment.ts b/packages/eslint-plugin/src/rules/ban-ts-comment.ts index 53505cfea22..a422349eefa 100644 --- a/packages/eslint-plugin/src/rules/ban-ts-comment.ts +++ b/packages/eslint-plugin/src/rules/ban-ts-comment.ts @@ -2,6 +2,7 @@ import { AST_TOKEN_TYPES } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; interface Options { + 'ts-expect-error'?: boolean; 'ts-ignore'?: boolean; 'ts-nocheck'?: boolean; 'ts-check'?: boolean; @@ -9,6 +10,7 @@ interface Options { const defaultOptions: [Options] = [ { + 'ts-expect-error': true, 'ts-ignore': true, 'ts-nocheck': true, 'ts-check': false, @@ -34,6 +36,10 @@ export default util.createRule<[Options], MessageIds>({ { type: 'object', properties: { + 'ts-expect-error': { + type: 'boolean', + default: true, + }, 'ts-ignore': { type: 'boolean', default: true, @@ -53,7 +59,7 @@ export default util.createRule<[Options], MessageIds>({ }, defaultOptions, create(context, [options]) { - const tsCommentRegExp = /^\/*\s*@ts-(ignore|check|nocheck)/; + const tsCommentRegExp = /^\/*\s*@ts-(expect-error|ignore|check|nocheck)/; const sourceCode = context.getSourceCode(); return { diff --git a/packages/eslint-plugin/src/rules/ban-ts-ignore.ts b/packages/eslint-plugin/src/rules/ban-ts-ignore.ts index 7551df8d022..551bb0401a9 100644 --- a/packages/eslint-plugin/src/rules/ban-ts-ignore.ts +++ b/packages/eslint-plugin/src/rules/ban-ts-ignore.ts @@ -16,7 +16,7 @@ export default util.createRule({ 'Do not use "// @ts-ignore" comments because they suppress compilation errors.', }, deprecated: true, - replacedBy: ['@typescript-eslint/ban-ts-comment'], + replacedBy: ['ban-ts-comment'], }, defaultOptions: [], create(context) { diff --git a/packages/eslint-plugin/src/rules/consistent-type-assertions.ts b/packages/eslint-plugin/src/rules/consistent-type-assertions.ts index ae54149d0a7..0620822f83c 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-assertions.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-assertions.ts @@ -75,10 +75,27 @@ export default util.createRule({ create(context, [options]) { const sourceCode = context.getSourceCode(); + function isConst(node: TSESTree.TypeNode): boolean { + if (node.type !== AST_NODE_TYPES.TSTypeReference) { + return false; + } + + return ( + node.typeName.type === AST_NODE_TYPES.Identifier && + node.typeName.name === 'const' + ); + } + function reportIncorrectAssertionType( node: TSESTree.TSTypeAssertion | TSESTree.TSAsExpression, ): void { + // If this node is `as const`, then don't report an error. + if (isConst(node.typeAnnotation)) { + return; + } + const messageId = options.assertionStyle; + context.report({ node, messageId, @@ -97,8 +114,7 @@ export default util.createRule({ case AST_NODE_TYPES.TSTypeReference: return ( // Ignore `as const` and `` - (node.typeName.type === AST_NODE_TYPES.Identifier && - node.typeName.name !== 'const') || + !isConst(node) || // Allow qualified names which have dots between identifiers, `Foo.Bar` node.typeName.type === AST_NODE_TYPES.TSQualifiedName ); diff --git a/packages/eslint-plugin/src/rules/explicit-function-return-type.ts b/packages/eslint-plugin/src/rules/explicit-function-return-type.ts index b8ee7ff584b..f0a5978d02f 100644 --- a/packages/eslint-plugin/src/rules/explicit-function-return-type.ts +++ b/packages/eslint-plugin/src/rules/explicit-function-return-type.ts @@ -1,4 +1,7 @@ -import { TSESTree } from '@typescript-eslint/experimental-utils'; +import { + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; import { checkFunctionReturnType, @@ -11,6 +14,7 @@ type Options = [ allowTypedFunctionExpressions?: boolean; allowHigherOrderFunctions?: boolean; allowDirectConstAssertionInArrowFunctions?: boolean; + allowConciseArrowFunctionExpressionsStartingWithVoid?: boolean; }, ]; type MessageIds = 'missingReturnType'; @@ -44,6 +48,9 @@ export default util.createRule({ allowDirectConstAssertionInArrowFunctions: { type: 'boolean', }, + allowConciseArrowFunctionExpressionsStartingWithVoid: { + type: 'boolean', + }, }, additionalProperties: false, }, @@ -55,6 +62,7 @@ export default util.createRule({ allowTypedFunctionExpressions: true, allowHigherOrderFunctions: true, allowDirectConstAssertionInArrowFunctions: true, + allowConciseArrowFunctionExpressionsStartingWithVoid: false, }, ], create(context, [options]) { @@ -64,6 +72,16 @@ export default util.createRule({ 'ArrowFunctionExpression, FunctionExpression'( node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression, ): void { + if ( + options.allowConciseArrowFunctionExpressionsStartingWithVoid && + node.type === AST_NODE_TYPES.ArrowFunctionExpression && + node.expression && + node.body.type === AST_NODE_TYPES.UnaryExpression && + node.body.operator === 'void' + ) { + return; + } + checkFunctionExpressionReturnType(node, options, sourceCode, loc => context.report({ node, diff --git a/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts b/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts index 55c6bd29f99..b00d8eb1087 100644 --- a/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts +++ b/packages/eslint-plugin/src/rules/explicit-module-boundary-types.ts @@ -1,6 +1,7 @@ import { TSESTree, AST_NODE_TYPES, + TSESLint, } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; import { @@ -15,6 +16,7 @@ type Options = [ allowHigherOrderFunctions?: boolean; allowDirectConstAssertionInArrowFunctions?: boolean; allowedNames?: string[]; + shouldTrackReferences?: boolean; }, ]; type MessageIds = 'missingReturnType' | 'missingArgType'; @@ -52,6 +54,9 @@ export default util.createRule({ type: 'string', }, }, + shouldTrackReferences: { + type: 'boolean', + }, }, additionalProperties: false, }, @@ -63,6 +68,7 @@ export default util.createRule({ allowHigherOrderFunctions: true, allowDirectConstAssertionInArrowFunctions: true, allowedNames: [], + shouldTrackReferences: true, }, ], create(context, [options]) { @@ -171,50 +177,201 @@ export default util.createRule({ return false; } + /** + * Finds an array of a function expression node referred by a variable passed from parameters + */ + function findFunctionExpressionsInScope( + variable: TSESLint.Scope.Variable, + ): + | (TSESTree.FunctionExpression | TSESTree.ArrowFunctionExpression)[] + | undefined { + const writeExprs = variable.references + .map(ref => ref.writeExpr) + .filter( + ( + expr, + ): expr is + | TSESTree.ArrowFunctionExpression + | TSESTree.FunctionExpression => + expr?.type === AST_NODE_TYPES.FunctionExpression || + expr?.type === AST_NODE_TYPES.ArrowFunctionExpression, + ); + + return writeExprs; + } + + /** + * Finds a function node referred by a variable passed from parameters + */ + function findFunctionInScope( + variable: TSESLint.Scope.Variable, + ): TSESTree.FunctionDeclaration | undefined { + if (variable.defs[0].type !== 'FunctionName') { + return; + } + + const functionNode = variable.defs[0].node; + + if (functionNode?.type !== AST_NODE_TYPES.FunctionDeclaration) { + return; + } + + return functionNode; + } + + /** + * Checks if a function referred by the identifier passed from parameters follow the rule + */ + function checkWithTrackingReferences(node: TSESTree.Identifier): void { + const scope = context.getScope(); + const variable = scope.set.get(node.name); + + if (!variable) { + return; + } + + if (variable.defs[0].type === 'ClassName') { + const classNode = variable.defs[0].node; + for (const classElement of classNode.body.body) { + if ( + classElement.type === AST_NODE_TYPES.MethodDefinition && + classElement.value.type === AST_NODE_TYPES.FunctionExpression + ) { + checkFunctionExpression(classElement.value); + } + + if ( + classElement.type === AST_NODE_TYPES.ClassProperty && + (classElement.value?.type === AST_NODE_TYPES.FunctionExpression || + classElement.value?.type === + AST_NODE_TYPES.ArrowFunctionExpression) + ) { + checkFunctionExpression(classElement.value); + } + } + } + + const functionNode = findFunctionInScope(variable); + if (functionNode) { + checkFunction(functionNode); + } + + const functionExpressions = findFunctionExpressionsInScope(variable); + if (functionExpressions && functionExpressions.length > 0) { + for (const functionExpression of functionExpressions) { + checkFunctionExpression(functionExpression); + } + } + } + + /** + * Checks if a function expression follow the rule + */ + function checkFunctionExpression( + node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression, + ): void { + if ( + node.parent?.type === AST_NODE_TYPES.MethodDefinition && + node.parent.accessibility === 'private' + ) { + // don't check private methods as they aren't part of the public signature + return; + } + + if ( + isAllowedName(node.parent) || + isTypedFunctionExpression(node, options) + ) { + return; + } + + checkFunctionExpressionReturnType(node, options, sourceCode, loc => + context.report({ + node, + loc, + messageId: 'missingReturnType', + }), + ); + + checkArguments(node); + } + + /** + * Checks if a function follow the rule + */ + function checkFunction(node: TSESTree.FunctionDeclaration): void { + if (isAllowedName(node.parent)) { + return; + } + + checkFunctionReturnType(node, options, sourceCode, loc => + context.report({ + node, + loc, + messageId: 'missingReturnType', + }), + ); + + checkArguments(node); + } + return { 'ArrowFunctionExpression, FunctionExpression'( node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression, ): void { - if ( - node.parent?.type === AST_NODE_TYPES.MethodDefinition && - node.parent.accessibility === 'private' - ) { - // don't check private methods as they aren't part of the public signature + if (isUnexported(node)) { return; } - if ( - isAllowedName(node.parent) || - isUnexported(node) || - isTypedFunctionExpression(node, options) - ) { + checkFunctionExpression(node); + }, + FunctionDeclaration(node): void { + if (isUnexported(node)) { return; } - checkFunctionExpressionReturnType(node, options, sourceCode, loc => - context.report({ - node, - loc, - messageId: 'missingReturnType', - }), - ); - - checkArguments(node); + checkFunction(node); }, - FunctionDeclaration(node): void { - if (isAllowedName(node.parent) || isUnexported(node)) { + 'ExportDefaultDeclaration, TSExportAssignment'( + node: TSESTree.ExportDefaultDeclaration | TSESTree.TSExportAssignment, + ): void { + if (!options.shouldTrackReferences) { return; } - checkFunctionReturnType(node, options, sourceCode, loc => - context.report({ - node, - loc, - messageId: 'missingReturnType', - }), - ); + let exported: TSESTree.Node; + + if (node.type === AST_NODE_TYPES.ExportDefaultDeclaration) { + exported = node.declaration; + } else { + exported = node.expression; + } - checkArguments(node); + switch (exported.type) { + case AST_NODE_TYPES.Identifier: { + checkWithTrackingReferences(exported); + break; + } + case AST_NODE_TYPES.ArrayExpression: { + for (const element of exported.elements) { + if (element.type === AST_NODE_TYPES.Identifier) { + checkWithTrackingReferences(element); + } + } + break; + } + case AST_NODE_TYPES.ObjectExpression: { + for (const property of exported.properties) { + if ( + property.type === AST_NODE_TYPES.Property && + property.value.type === AST_NODE_TYPES.Identifier + ) { + checkWithTrackingReferences(property.value); + } + } + break; + } + } }, }; }, diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 29db58b681a..901d31a8b9f 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -22,6 +22,7 @@ import interfaceNamePrefix from './interface-name-prefix'; import memberDelimiterStyle from './member-delimiter-style'; import memberNaming from './member-naming'; import memberOrdering from './member-ordering'; +import methodSignatureStyle from './method-signature-style'; import namingConvention from './naming-convention'; import noArrayConstructor from './no-array-constructor'; import noBaseToString from './no-base-to-string'; @@ -30,10 +31,10 @@ import noDynamicDelete from './no-dynamic-delete'; import noEmptyFunction from './no-empty-function'; import noEmptyInterface from './no-empty-interface'; import noExplicitAny from './no-explicit-any'; -import noExtraneousClass from './no-extraneous-class'; import noExtraNonNullAssertion from './no-extra-non-null-assertion'; import noExtraParens from './no-extra-parens'; import noExtraSemi from './no-extra-semi'; +import noExtraneousClass from './no-extraneous-class'; import noFloatingPromises from './no-floating-promises'; import noForInArray from './no-for-in-array'; import noImpliedEval from './no-implied-eval'; @@ -54,6 +55,7 @@ import noUnnecessaryCondition from './no-unnecessary-condition'; import noUnnecessaryQualifier from './no-unnecessary-qualifier'; import noUnnecessaryTypeArguments from './no-unnecessary-type-arguments'; import noUnnecessaryTypeAssertion from './no-unnecessary-type-assertion'; +import noUnsafeAssignment from './no-unsafe-assignment'; import noUnsafeCall from './no-unsafe-call'; import noUnsafeMemberAccess from './no-unsafe-member-access'; import noUnsafeReturn from './no-unsafe-return'; @@ -73,8 +75,10 @@ import preferNullishCoalescing from './prefer-nullish-coalescing'; import preferOptionalChain from './prefer-optional-chain'; import preferReadonly from './prefer-readonly'; import preferReadonlyParameterTypes from './prefer-readonly-parameter-types'; +import preferReduceTypeParameter from './prefer-reduce-type-parameter'; import preferRegexpExec from './prefer-regexp-exec'; import preferStringStartsEndsWith from './prefer-string-starts-ends-with'; +import preferTsExpectError from './prefer-ts-expect-error'; import promiseFunctionAsync from './promise-function-async'; import quotes from './quotes'; import requireArraySortCompare from './require-array-sort-compare'; @@ -99,7 +103,6 @@ export default { 'ban-ts-comment': banTsComment, 'ban-ts-ignore': banTsIgnore, 'ban-types': banTypes, - 'no-base-to-string': noBaseToString, 'brace-style': braceStyle, camelcase: camelcase, 'class-name-casing': classNameCasing, @@ -118,8 +121,10 @@ export default { 'member-delimiter-style': memberDelimiterStyle, 'member-naming': memberNaming, 'member-ordering': memberOrdering, + 'method-signature-style': methodSignatureStyle, 'naming-convention': namingConvention, 'no-array-constructor': noArrayConstructor, + 'no-base-to-string': noBaseToString, 'no-dupe-class-members': noDupeClassMembers, 'no-dynamic-delete': noDynamicDelete, 'no-empty-function': noEmptyFunction, @@ -149,6 +154,7 @@ export default { 'no-unnecessary-qualifier': noUnnecessaryQualifier, 'no-unnecessary-type-arguments': noUnnecessaryTypeArguments, 'no-unnecessary-type-assertion': noUnnecessaryTypeAssertion, + 'no-unsafe-assignment': noUnsafeAssignment, 'no-unsafe-call': noUnsafeCall, 'no-unsafe-member-access': noUnsafeMemberAccess, 'no-unsafe-return': noUnsafeReturn, @@ -168,8 +174,10 @@ export default { 'prefer-optional-chain': preferOptionalChain, 'prefer-readonly-parameter-types': preferReadonlyParameterTypes, 'prefer-readonly': preferReadonly, + 'prefer-reduce-type-parameter': preferReduceTypeParameter, 'prefer-regexp-exec': preferRegexpExec, 'prefer-string-starts-ends-with': preferStringStartsEndsWith, + 'prefer-ts-expect-error': preferTsExpectError, 'promise-function-async': promiseFunctionAsync, quotes: quotes, 'require-array-sort-compare': requireArraySortCompare, diff --git a/packages/eslint-plugin/src/rules/member-ordering.ts b/packages/eslint-plugin/src/rules/member-ordering.ts index b9fb0ee8ef0..14ddcc236a9 100644 --- a/packages/eslint-plugin/src/rules/member-ordering.ts +++ b/packages/eslint-plugin/src/rules/member-ordering.ts @@ -1,12 +1,21 @@ import { - TSESTree, AST_NODE_TYPES, + TSESLint, + TSESTree, } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; -type MessageIds = 'incorrectOrder'; -type OrderConfig = string[] | 'never'; -type Options = [ +export type MessageIds = 'incorrectGroupOrder' | 'incorrectOrder'; + +interface SortedOrderConfig { + memberTypes?: string[] | 'never'; + order: 'alphabetically' | 'as-written'; +} + +type OrderConfig = string[] | SortedOrderConfig | 'never'; +type Member = TSESTree.ClassElement | TSESTree.TypeElement; + +export type Options = [ { default?: OrderConfig; classes?: OrderConfig; @@ -16,30 +25,287 @@ type Options = [ }, ]; -const allMemberTypes = ['field', 'method', 'constructor'].reduce( - (all, type) => { - all.push(type); +const neverConfig = { + type: 'string', + enum: ['never'], +}; + +const arrayConfig = (memberTypes: string[]): object => ({ + type: 'array', + items: { + enum: memberTypes, + }, +}); + +const objectConfig = (memberTypes: string[]): object => ({ + type: 'object', + properties: { + memberTypes: { + oneOf: [arrayConfig(memberTypes), neverConfig], + }, + order: { + type: 'string', + enum: ['alphabetically', 'as-written'], + }, + }, + additionalProperties: false, +}); + +export const defaultOrder = [ + // Index signature + 'signature', - ['public', 'protected', 'private'].forEach(accessibility => { + // Fields + 'public-static-field', + 'protected-static-field', + 'private-static-field', + + 'public-instance-field', + 'protected-instance-field', + 'private-instance-field', + + 'public-abstract-field', + 'protected-abstract-field', + 'private-abstract-field', + + 'public-field', + 'protected-field', + 'private-field', + + 'static-field', + 'instance-field', + 'abstract-field', + + 'field', + + // Constructors + 'public-constructor', + 'protected-constructor', + 'private-constructor', + + 'constructor', + + // Methods + 'public-static-method', + 'protected-static-method', + 'private-static-method', + + 'public-instance-method', + 'protected-instance-method', + 'private-instance-method', + + 'public-abstract-method', + 'protected-abstract-method', + 'private-abstract-method', + + 'public-method', + 'protected-method', + 'private-method', + + 'static-method', + 'instance-method', + 'abstract-method', + + 'method', +]; + +const allMemberTypes = ['signature', 'field', 'method', 'constructor'].reduce< + string[] +>((all, type) => { + all.push(type); + + ['public', 'protected', 'private'].forEach(accessibility => { + if (type !== 'signature') { all.push(`${accessibility}-${type}`); // e.g. `public-field` + } - if (type !== 'constructor') { - // There is no `static-constructor` or `instance-constructor or `abstract-constructor` - ['static', 'instance', 'abstract'].forEach(scope => { - if (!all.includes(`${scope}-${type}`)) { - all.push(`${scope}-${type}`); - } + if (type !== 'constructor' && type !== 'signature') { + // There is no `static-constructor` or `instance-constructor` or `abstract-constructor` + ['static', 'instance', 'abstract'].forEach(scope => { + if (!all.includes(`${scope}-${type}`)) { + all.push(`${scope}-${type}`); + } - all.push(`${accessibility}-${scope}-${type}`); - }); - } - }); + all.push(`${accessibility}-${scope}-${type}`); + }); + } + }); - return all; - }, - [], -); -allMemberTypes.unshift('signature'); + return all; +}, []); + +const functionExpressions = [ + AST_NODE_TYPES.FunctionExpression, + AST_NODE_TYPES.ArrowFunctionExpression, +]; + +/** + * Gets the node type. + * + * @param node the node to be evaluated. + */ +function getNodeType(node: Member): string | null { + // TODO: add missing TSCallSignatureDeclaration + switch (node.type) { + case AST_NODE_TYPES.TSAbstractMethodDefinition: + case AST_NODE_TYPES.MethodDefinition: + return node.kind; + case AST_NODE_TYPES.TSMethodSignature: + return 'method'; + case AST_NODE_TYPES.TSConstructSignatureDeclaration: + return 'constructor'; + case AST_NODE_TYPES.TSAbstractClassProperty: + case AST_NODE_TYPES.ClassProperty: + return node.value && functionExpressions.includes(node.value.type) + ? 'method' + : 'field'; + case AST_NODE_TYPES.TSPropertySignature: + return 'field'; + case AST_NODE_TYPES.TSIndexSignature: + return 'signature'; + default: + return null; + } +} + +/** + * Gets the member name based on the member type. + * + * @param node the node to be evaluated. + * @param sourceCode + */ +function getMemberName( + node: Member, + sourceCode: TSESLint.SourceCode, +): string | null { + switch (node.type) { + case AST_NODE_TYPES.TSPropertySignature: + case AST_NODE_TYPES.TSMethodSignature: + case AST_NODE_TYPES.TSAbstractClassProperty: + case AST_NODE_TYPES.ClassProperty: + return util.getNameFromMember(node, sourceCode); + case AST_NODE_TYPES.TSAbstractMethodDefinition: + case AST_NODE_TYPES.MethodDefinition: + return node.kind === 'constructor' + ? 'constructor' + : util.getNameFromMember(node, sourceCode); + case AST_NODE_TYPES.TSConstructSignatureDeclaration: + return 'new'; + case AST_NODE_TYPES.TSIndexSignature: + return util.getNameFromIndexSignature(node); + default: + return null; + } +} + +/** + * Gets the calculated rank using the provided method definition. + * The algorithm is as follows: + * - Get the rank based on the accessibility-scope-type name, e.g. public-instance-field + * - If there is no order for accessibility-scope-type, then strip out the accessibility. + * - If there is no order for scope-type, then strip out the scope. + * - If there is no order for type, then return -1 + * @param memberGroups the valid names to be validated. + * @param orderConfig the current order to be validated. + * + * @return Index of the matching member type in the order configuration. + */ +function getRankOrder(memberGroups: string[], orderConfig: string[]): number { + let rank = -1; + const stack = memberGroups.slice(); // Get a copy of the member groups + + while (stack.length > 0 && rank === -1) { + rank = orderConfig.indexOf(stack.shift()!); + } + + return rank; +} + +/** + * Gets the rank of the node given the order. + * @param node the node to be evaluated. + * @param orderConfig the current order to be validated. + * @param supportsModifiers a flag indicating whether the type supports modifiers (scope or accessibility) or not. + */ +function getRank( + node: Member, + orderConfig: string[], + supportsModifiers: boolean, +): number { + const type = getNodeType(node); + + if (type === null) { + // shouldn't happen but just in case, put it on the end + return orderConfig.length - 1; + } + + const abstract = + node.type === AST_NODE_TYPES.TSAbstractClassProperty || + node.type === AST_NODE_TYPES.TSAbstractMethodDefinition; + + const scope = + 'static' in node && node.static + ? 'static' + : abstract + ? 'abstract' + : 'instance'; + const accessibility = + 'accessibility' in node && node.accessibility + ? node.accessibility + : 'public'; + + // Collect all existing member groups (e.g. 'public-instance-field', 'instance-field', 'public-field', 'constructor' etc.) + const memberGroups = []; + + if (supportsModifiers) { + if (type !== 'constructor') { + // Constructors have no scope + memberGroups.push(`${accessibility}-${scope}-${type}`); + memberGroups.push(`${scope}-${type}`); + } + + memberGroups.push(`${accessibility}-${type}`); + } + + memberGroups.push(type); + + return getRankOrder(memberGroups, orderConfig); +} + +/** + * Gets the lowest possible rank higher than target. + * e.g. given the following order: + * ... + * public-static-method + * protected-static-method + * private-static-method + * public-instance-method + * protected-instance-method + * private-instance-method + * ... + * and considering that a public-instance-method has already been declared, so ranks contains + * public-instance-method, then the lowest possible rank for public-static-method is + * public-instance-method. + * @param ranks the existing ranks in the object. + * @param target the target rank. + * @param order the current order to be validated. + * @returns the name of the lowest possible rank without dashes (-). + */ +function getLowestRank( + ranks: number[], + target: number, + order: string[], +): string { + let lowest = ranks[ranks.length - 1]; + + ranks.forEach(rank => { + if (rank > target) { + lowest = Math.min(lowest, rank); + } + }); + + return order[lowest].replace(/-/g, ' '); +} export default util.createRule({ name: 'member-ordering', @@ -52,6 +318,8 @@ export default util.createRule({ }, messages: { incorrectOrder: + 'Member "{{member}}" should be declared before member "{{beforeMember}}".', + incorrectGroupOrder: 'Member {{name}} should be declared before all {{rank}} definitions.', }, schema: [ @@ -60,67 +328,37 @@ export default util.createRule({ properties: { default: { oneOf: [ - { - enum: ['never'], - }, - { - type: 'array', - items: { - enum: allMemberTypes, - }, - }, + neverConfig, + arrayConfig(allMemberTypes), + objectConfig(allMemberTypes), ], }, classes: { oneOf: [ - { - enum: ['never'], - }, - { - type: 'array', - items: { - enum: allMemberTypes, - }, - }, + neverConfig, + arrayConfig(allMemberTypes), + objectConfig(allMemberTypes), ], }, classExpressions: { oneOf: [ - { - enum: ['never'], - }, - { - type: 'array', - items: { - enum: allMemberTypes, - }, - }, + neverConfig, + arrayConfig(allMemberTypes), + objectConfig(allMemberTypes), ], }, interfaces: { oneOf: [ - { - enum: ['never'], - }, - { - type: 'array', - items: { - enum: ['signature', 'field', 'method', 'constructor'], - }, - }, + neverConfig, + arrayConfig(['signature', 'field', 'method', 'constructor']), + objectConfig(['signature', 'field', 'method', 'constructor']), ], }, typeLiterals: { oneOf: [ - { - enum: ['never'], - }, - { - type: 'array', - items: { - enum: ['signature', 'field', 'method', 'constructor'], - }, - }, + neverConfig, + arrayConfig(['signature', 'field', 'method', 'constructor']), + objectConfig(['signature', 'field', 'method', 'constructor']), ], }, }, @@ -130,263 +368,140 @@ export default util.createRule({ }, defaultOptions: [ { - default: [ - 'signature', - - 'public-static-field', - 'protected-static-field', - 'private-static-field', - - 'public-instance-field', - 'protected-instance-field', - 'private-instance-field', - - 'public-abstract-field', - 'protected-abstract-field', - 'private-abstract-field', - - 'public-field', - 'protected-field', - 'private-field', - - 'static-field', - 'instance-field', - 'abstract-field', - - 'field', - - 'constructor', - - 'public-static-method', - 'protected-static-method', - 'private-static-method', - - 'public-instance-method', - 'protected-instance-method', - 'private-instance-method', - - 'public-abstract-method', - 'protected-abstract-method', - 'private-abstract-method', - - 'public-method', - 'protected-method', - 'private-method', - - 'static-method', - 'instance-method', - 'abstract-method', - - 'method', - ], + default: defaultOrder, }, ], create(context, [options]) { - const sourceCode = context.getSourceCode(); - - const functionExpressions = [ - AST_NODE_TYPES.FunctionExpression, - AST_NODE_TYPES.ArrowFunctionExpression, - ]; - /** - * Gets the node type. - * @param node the node to be evaluated. - */ - function getNodeType( - node: TSESTree.ClassElement | TSESTree.TypeElement, - ): string | null { - // TODO: add missing TSCallSignatureDeclaration - switch (node.type) { - case AST_NODE_TYPES.TSAbstractMethodDefinition: - case AST_NODE_TYPES.MethodDefinition: - return node.kind; - case AST_NODE_TYPES.TSMethodSignature: - return 'method'; - case AST_NODE_TYPES.TSConstructSignatureDeclaration: - return 'constructor'; - case AST_NODE_TYPES.TSAbstractClassProperty: - case AST_NODE_TYPES.ClassProperty: - return node.value && functionExpressions.includes(node.value.type) - ? 'method' - : 'field'; - case AST_NODE_TYPES.TSPropertySignature: - return 'field'; - case AST_NODE_TYPES.TSIndexSignature: - return 'signature'; - default: - return null; - } - } - - /** - * Gets the member name based on the member type. - * @param node the node to be evaluated. - */ - function getMemberName( - node: TSESTree.ClassElement | TSESTree.TypeElement, - ): string | null { - switch (node.type) { - case AST_NODE_TYPES.TSPropertySignature: - case AST_NODE_TYPES.TSMethodSignature: - case AST_NODE_TYPES.TSAbstractClassProperty: - case AST_NODE_TYPES.ClassProperty: - return util.getNameFromMember(node, sourceCode); - case AST_NODE_TYPES.TSAbstractMethodDefinition: - case AST_NODE_TYPES.MethodDefinition: - return node.kind === 'constructor' - ? 'constructor' - : util.getNameFromMember(node, sourceCode); - case AST_NODE_TYPES.TSConstructSignatureDeclaration: - return 'new'; - case AST_NODE_TYPES.TSIndexSignature: - return util.getNameFromIndexSignature(node); - default: - return null; - } - } - - /** - * Gets the calculated rank using the provided method definition. - * The algorithm is as follows: - * - Get the rank based on the accessibility-scope-type name, e.g. public-instance-field - * - If there is no order for accessibility-scope-type, then strip out the accessibility. - * - If there is no order for scope-type, then strip out the scope. - * - If there is no order for type, then return -1 - * @param memberTypes the valid names to be validated. - * @param order the current order to be validated. + * Checks if the member groups are correctly sorted. * - * @return Index of the matching member type in the order configuration. - */ - function getRankOrder(memberTypes: string[], order: string[]): number { - let rank = -1; - const stack = memberTypes.slice(); // Get a copy of the member types - - while (stack.length > 0 && rank === -1) { - rank = order.indexOf(stack.shift()!); - } - - return rank; - } - - /** - * Gets the rank of the node given the order. - * @param node the node to be evaluated. - * @param order the current order to be validated. - * @param supportsModifiers a flag indicating whether the type supports modifiers (scope or accessibility) or not. + * @param members Members to be validated. + * @param groupOrder Group order to be validated. + * @param supportsModifiers A flag indicating whether the type supports modifiers (scope or accessibility) or not. + * + * @return Array of member groups or null if one of the groups is not correctly sorted. */ - function getRank( - node: TSESTree.ClassElement | TSESTree.TypeElement, - order: string[], + function checkGroupSort( + members: Member[], + groupOrder: string[], supportsModifiers: boolean, - ): number { - const type = getNodeType(node); - if (type === null) { - // shouldn't happen but just in case, put it on the end - return order.length - 1; - } - - const abstract = - node.type === AST_NODE_TYPES.TSAbstractClassProperty || - node.type === AST_NODE_TYPES.TSAbstractMethodDefinition; - - const scope = - 'static' in node && node.static - ? 'static' - : abstract - ? 'abstract' - : 'instance'; - const accessibility = - 'accessibility' in node && node.accessibility - ? node.accessibility - : 'public'; - - const memberTypes = []; - - if (supportsModifiers) { - if (type !== 'constructor') { - // Constructors have no scope - memberTypes.push(`${accessibility}-${scope}-${type}`); - memberTypes.push(`${scope}-${type}`); + ): Array | null { + const previousRanks: number[] = []; + const memberGroups: Array = []; + let isCorrectlySorted = true; + + // Find first member which isn't correctly sorted + members.forEach(member => { + const rank = getRank(member, groupOrder, supportsModifiers); + const name = getMemberName(member, context.getSourceCode()); + const rankLastMember = previousRanks[previousRanks.length - 1]; + + if (rank !== -1) { + // Works for 1st item because x < undefined === false for any x (typeof string) + if (rank < rankLastMember) { + context.report({ + node: member, + messageId: 'incorrectGroupOrder', + data: { + name, + rank: getLowestRank(previousRanks, rank, groupOrder), + }, + }); + + isCorrectlySorted = false; + } else if (rank === rankLastMember) { + // Same member group --> Push to existing member group array + memberGroups[memberGroups.length - 1].push(member); + } else { + // New member group --> Create new member group array + previousRanks.push(rank); + memberGroups.push([member]); + } } + }); - memberTypes.push(`${accessibility}-${type}`); - } - - memberTypes.push(type); - - return getRankOrder(memberTypes, order); + return isCorrectlySorted ? memberGroups : null; } /** - * Gets the lowest possible rank higher than target. - * e.g. given the following order: - * ... - * public-static-method - * protected-static-method - * private-static-method - * public-instance-method - * protected-instance-method - * private-instance-method - * ... - * and considering that a public-instance-method has already been declared, so ranks contains - * public-instance-method, then the lowest possible rank for public-static-method is - * public-instance-method. - * @param ranks the existing ranks in the object. - * @param target the target rank. - * @param order the current order to be validated. - * @returns the name of the lowest possible rank without dashes (-). + * Checks if the members are alphabetically sorted. + * + * @param members Members to be validated. + * + * @return True if all members are correctly sorted. */ - function getLowestRank( - ranks: number[], - target: number, - order: string[], - ): string { - let lowest = ranks[ranks.length - 1]; - - ranks.forEach(rank => { - if (rank > target) { - lowest = Math.min(lowest, rank); + function checkAlphaSort(members: Member[]): boolean { + let previousName = ''; + let isCorrectlySorted = true; + + // Find first member which isn't correctly sorted + members.forEach(member => { + const name = getMemberName(member, context.getSourceCode()); + + // Note: Not all members have names + if (name) { + if (name < previousName) { + context.report({ + node: member, + messageId: 'incorrectOrder', + data: { + member: name, + beforeMember: previousName, + }, + }); + + isCorrectlySorted = false; + } + + previousName = name; } }); - return order[lowest].replace(/-/g, ' '); + return isCorrectlySorted; } /** * Validates if all members are correctly sorted. * * @param members Members to be validated. - * @param order Current order to be validated. + * @param orderConfig Order config to be validated. * @param supportsModifiers A flag indicating whether the type supports modifiers (scope or accessibility) or not. */ function validateMembersOrder( - members: (TSESTree.ClassElement | TSESTree.TypeElement)[], - order: OrderConfig, + members: Member[], + orderConfig: OrderConfig, supportsModifiers: boolean, ): void { - if (members && order !== 'never') { - const previousRanks: number[] = []; - - // Find first member which isn't correctly sorted - members.forEach(member => { - const rank = getRank(member, order, supportsModifiers); - - if (rank !== -1) { - if (rank < previousRanks[previousRanks.length - 1]) { - context.report({ - node: member, - messageId: 'incorrectOrder', - data: { - name: getMemberName(member), - rank: getLowestRank(previousRanks, rank, order), - }, - }); - } else { - previousRanks.push(rank); - } + if (orderConfig !== 'never') { + // Standardize config + let order = null; + let memberTypes; + + if (Array.isArray(orderConfig)) { + memberTypes = orderConfig; + } else { + order = orderConfig.order; + memberTypes = orderConfig.memberTypes; + } + + // Check order + if (Array.isArray(memberTypes)) { + const grouped = checkGroupSort( + members, + memberTypes, + supportsModifiers, + ); + + if (grouped === null) { + return; } - }); + + if (order === 'alphabetically') { + grouped.some(groupMember => !checkAlphaSort(groupMember)); + } + } else if (order === 'alphabetically') { + checkAlphaSort(members); + } } } diff --git a/packages/eslint-plugin/src/rules/method-signature-style.ts b/packages/eslint-plugin/src/rules/method-signature-style.ts new file mode 100644 index 00000000000..2d6366bbda1 --- /dev/null +++ b/packages/eslint-plugin/src/rules/method-signature-style.ts @@ -0,0 +1,148 @@ +import { + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; +import * as util from '../util'; + +export type Options = [('property' | 'method')?]; +export type MessageIds = 'errorMethod' | 'errorProperty'; + +export default util.createRule({ + name: 'method-signature-style', + meta: { + type: 'suggestion', + docs: { + description: 'Enforces using a particular method signature syntax.', + category: 'Best Practices', + recommended: false, + }, + fixable: 'code', + messages: { + errorMethod: + 'Shorthand method signature is forbidden. Use a function property instead.', + errorProperty: + 'Function property signature is forbidden. Use a method shorthand instead.', + }, + schema: [ + { + enum: ['property', 'method'], + }, + ], + }, + defaultOptions: ['property'], + + create(context, [mode]) { + const sourceCode = context.getSourceCode(); + + function getMethodKey( + node: TSESTree.TSMethodSignature | TSESTree.TSPropertySignature, + ): string { + let key = sourceCode.getText(node.key); + if (node.computed) { + key = `[${key}]`; + } + if (node.optional) { + key = `${key}?`; + } + if (node.readonly) { + key = `readonly ${key}`; + } + return key; + } + + function getMethodParams( + node: TSESTree.TSMethodSignature | TSESTree.TSFunctionType, + ): string { + let params = '()'; + if (node.params.length > 0) { + const openingParen = util.nullThrows( + sourceCode.getTokenBefore(node.params[0], util.isOpeningParenToken), + 'Missing opening paren before first parameter', + ); + const closingParen = util.nullThrows( + sourceCode.getTokenAfter( + node.params[node.params.length - 1], + util.isClosingParenToken, + ), + 'Missing closing paren after last parameter', + ); + + params = sourceCode.text.substring( + openingParen.range[0], + closingParen.range[1], + ); + } + if (node.typeParameters != null) { + const typeParams = sourceCode.getText(node.typeParameters); + params = `${typeParams}${params}`; + } + return params; + } + + function getMethodReturnType( + node: TSESTree.TSMethodSignature | TSESTree.TSFunctionType, + ): string { + return sourceCode.getText(node.returnType!.typeAnnotation); + } + + function getDelimiter(node: TSESTree.Node): string { + const lastToken = sourceCode.getLastToken(node); + if ( + lastToken && + (util.isSemicolonToken(lastToken) || util.isCommaToken(lastToken)) + ) { + return lastToken.value; + } + + return ''; + } + + return { + TSMethodSignature(methodNode): void { + if (mode === 'method') { + return; + } + + context.report({ + node: methodNode, + messageId: 'errorMethod', + fix: fixer => { + const key = getMethodKey(methodNode); + const params = getMethodParams(methodNode); + const returnType = getMethodReturnType(methodNode); + const delimiter = getDelimiter(methodNode); + return fixer.replaceText( + methodNode, + `${key}: ${params} => ${returnType}${delimiter}`, + ); + }, + }); + }, + TSPropertySignature(propertyNode): void { + const typeNode = propertyNode.typeAnnotation?.typeAnnotation; + if (typeNode?.type !== AST_NODE_TYPES.TSFunctionType) { + return; + } + + if (mode === 'property') { + return; + } + + context.report({ + node: propertyNode, + messageId: 'errorProperty', + fix: fixer => { + const key = getMethodKey(propertyNode); + const params = getMethodParams(typeNode); + const returnType = getMethodReturnType(typeNode); + const delimiter = getDelimiter(propertyNode); + return fixer.replaceText( + propertyNode, + `${key}${params}: ${returnType}${delimiter}`, + ); + }, + }); + }, + }; + }, +}); diff --git a/packages/eslint-plugin/src/rules/no-base-to-string.ts b/packages/eslint-plugin/src/rules/no-base-to-string.ts index 98a37c74084..47803f8e2aa 100644 --- a/packages/eslint-plugin/src/rules/no-base-to-string.ts +++ b/packages/eslint-plugin/src/rules/no-base-to-string.ts @@ -12,7 +12,14 @@ enum Usefulness { Sometimes = 'may', } -export default util.createRule({ +type Options = [ + { + ignoreTaggedTemplateExpressions?: boolean; + }, +]; +type MessageIds = 'baseToString'; + +export default util.createRule({ name: 'no-base-to-string', meta: { docs: { @@ -26,11 +33,22 @@ export default util.createRule({ baseToString: "'{{name}} {{certainty}} evaluate to '[object Object]' when stringified.", }, - schema: [], + schema: [ + { + type: 'object', + properties: { + ignoreTaggedTemplateExpressions: { + type: 'boolean', + default: false, + }, + }, + additionalProperties: false, + }, + ], type: 'suggestion', }, - defaultOptions: [], - create(context) { + defaultOptions: [{ ignoreTaggedTemplateExpressions: false }], + create(context, [options]) { const parserServices = util.getParserServices(context); const typeChecker = parserServices.program.getTypeChecker(); @@ -110,8 +128,14 @@ export default util.createRule({ const memberExpr = node.parent as TSESTree.MemberExpression; checkExpression(memberExpr.object); }, - TemplateLiteral(node: TSESTree.TemplateLiteral): void { + if ( + options.ignoreTaggedTemplateExpressions && + node.parent && + node.parent.type === AST_NODE_TYPES.TaggedTemplateExpression + ) { + return; + } for (const expression of node.expressions) { checkExpression(expression); } diff --git a/packages/eslint-plugin/src/rules/no-empty-interface.ts b/packages/eslint-plugin/src/rules/no-empty-interface.ts index 039415ad68f..d279e9bd4f6 100644 --- a/packages/eslint-plugin/src/rules/no-empty-interface.ts +++ b/packages/eslint-plugin/src/rules/no-empty-interface.ts @@ -1,4 +1,8 @@ import * as util from '../util'; +import { + AST_NODE_TYPES, + TSESLint, +} from '@typescript-eslint/experimental-utils'; type Options = [ { @@ -43,6 +47,7 @@ export default util.createRule({ return { TSInterfaceDeclaration(node): void { const sourceCode = context.getSourceCode(); + const filename = context.getFilename(); if (node.body.body.length !== 0) { // interface contains members --> Nothing to report @@ -58,16 +63,46 @@ export default util.createRule({ } else if (extend.length === 1) { // interface extends exactly 1 interface --> Report depending on rule setting if (!allowSingleExtends) { + const fix = (fixer: TSESLint.RuleFixer): TSESLint.RuleFix => { + let typeParam = ''; + if (node.typeParameters) { + typeParam = sourceCode.getText(node.typeParameters); + } + return fixer.replaceText( + node, + `type ${sourceCode.getText( + node.id, + )}${typeParam} = ${sourceCode.getText(extend[0])}`, + ); + }; + + // Check if interface is within ambient declaration + let useAutoFix = true; + if (util.isDefinitionFile(filename)) { + const scope = context.getScope(); + if ( + scope.block.parent && + scope.block.parent.type === + AST_NODE_TYPES.TSModuleDeclaration && + scope.block.parent.declare + ) { + useAutoFix = false; + } + } + context.report({ node: node.id, messageId: 'noEmptyWithSuper', - fix: fixer => - fixer.replaceText( - node, - `type ${sourceCode.getText(node.id)} = ${sourceCode.getText( - extend[0], - )}`, - ), + ...(useAutoFix + ? { fix } + : { + suggest: [ + { + messageId: 'noEmptyWithSuper', + fix, + }, + ], + }), }); } } diff --git a/packages/eslint-plugin/src/rules/no-explicit-any.ts b/packages/eslint-plugin/src/rules/no-explicit-any.ts index 29fb16651a2..795dae01725 100644 --- a/packages/eslint-plugin/src/rules/no-explicit-any.ts +++ b/packages/eslint-plugin/src/rules/no-explicit-any.ts @@ -53,18 +53,23 @@ export default util.createRule({ ], create(context, [{ ignoreRestArgs, fixToUnknown }]) { /** - * Checks if the node is an arrow function, function declaration or function expression + * Checks if the node is an arrow function, function/constructor declaration or function expression * @param node the node to be validated. - * @returns true if the node is an arrow function, function declaration, function expression, function type, or call signature + * @returns true if the node is any kind of function declaration or expression * @private */ function isNodeValidFunction(node: TSESTree.Node): boolean { return [ - AST_NODE_TYPES.ArrowFunctionExpression, - AST_NODE_TYPES.FunctionDeclaration, - AST_NODE_TYPES.FunctionExpression, - AST_NODE_TYPES.TSFunctionType, - AST_NODE_TYPES.TSCallSignatureDeclaration, + AST_NODE_TYPES.ArrowFunctionExpression, // const x = (...args: any[]) => {}; + AST_NODE_TYPES.FunctionDeclaration, // function f(...args: any[]) {} + AST_NODE_TYPES.FunctionExpression, // const x = function(...args: any[]) {}; + AST_NODE_TYPES.TSEmptyBodyFunctionExpression, // declare class A { f(...args: any[]): unknown; } + AST_NODE_TYPES.TSFunctionType, // type T = (...args: any[]) => unknown; + AST_NODE_TYPES.TSConstructorType, // type T = new (...args: any[]) => unknown + AST_NODE_TYPES.TSCallSignatureDeclaration, // type T = {(...args: any[]): unknown}; + AST_NODE_TYPES.TSConstructSignatureDeclaration, // type T = {new (...args: any[]): unknown}; + AST_NODE_TYPES.TSMethodSignature, // type T = {f(...args: any[]): unknown}; + AST_NODE_TYPES.TSDeclareFunction, // declare function _8(...args: any[]): unknown; ].includes(node.type); } @@ -131,9 +136,7 @@ export default util.createRule({ */ function isGreatGrandparentRestElement(node: TSESTree.Node): boolean { return ( - typeof node.parent !== 'undefined' && - typeof node.parent.parent !== 'undefined' && - typeof node.parent.parent.parent !== 'undefined' && + node?.parent?.parent?.parent != null && isNodeRestElementInFunction(node.parent.parent.parent) ); } @@ -146,11 +149,8 @@ export default util.createRule({ */ function isGreatGreatGrandparentRestElement(node: TSESTree.Node): boolean { return ( - typeof node.parent !== 'undefined' && - typeof node.parent.parent !== 'undefined' && + node.parent?.parent?.parent?.parent != null && isNodeValidTSType(node.parent.parent) && - typeof node.parent.parent.parent !== 'undefined' && - typeof node.parent.parent.parent.parent !== 'undefined' && isNodeRestElementInFunction(node.parent.parent.parent.parent) ); } diff --git a/packages/eslint-plugin/src/rules/no-for-in-array.ts b/packages/eslint-plugin/src/rules/no-for-in-array.ts index 511b744ee4c..f89d2ffd08d 100644 --- a/packages/eslint-plugin/src/rules/no-for-in-array.ts +++ b/packages/eslint-plugin/src/rules/no-for-in-array.ts @@ -25,11 +25,13 @@ export default util.createRule({ const checker = parserServices.program.getTypeChecker(); const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node); - const type = checker.getTypeAtLocation(originalNode.expression); + const type = util.getConstrainedTypeAtLocation( + checker, + originalNode.expression, + ); if ( - (typeof type.symbol !== 'undefined' && - type.symbol.name === 'Array') || + util.isTypeArrayTypeOrUnionOfArrayTypes(type, checker) || (type.flags & ts.TypeFlags.StringLike) !== 0 ) { context.report({ diff --git a/packages/eslint-plugin/src/rules/no-throw-literal.ts b/packages/eslint-plugin/src/rules/no-throw-literal.ts index c8c5bcc1837..ffa8a614e6b 100644 --- a/packages/eslint-plugin/src/rules/no-throw-literal.ts +++ b/packages/eslint-plugin/src/rules/no-throw-literal.ts @@ -43,10 +43,11 @@ export default util.createRule({ } } - const baseTypes = checker.getBaseTypes(type as ts.InterfaceType); - for (const baseType of baseTypes) { - if (isErrorLike(baseType)) { - return true; + if (symbol.flags & (ts.SymbolFlags.Class | ts.SymbolFlags.Interface)) { + for (const baseType of checker.getBaseTypes(type as ts.InterfaceType)) { + if (isErrorLike(baseType)) { + return true; + } } } diff --git a/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts b/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts new file mode 100644 index 00000000000..60f7b78acc0 --- /dev/null +++ b/packages/eslint-plugin/src/rules/no-unsafe-assignment.ts @@ -0,0 +1,370 @@ +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; +import * as ts from 'typescript'; +import * as util from '../util'; + +const enum ComparisonType { + /** Do no assignment comparison */ + None, + /** Use the receiver's type for comparison */ + Basic, + /** Use the sender's contextual type for comparison */ + Contextual, +} + +export default util.createRule({ + name: 'no-unsafe-assignment', + meta: { + type: 'problem', + docs: { + description: 'Disallows assigning any to variables and properties', + category: 'Possible Errors', + recommended: false, + requiresTypeChecking: true, + }, + messages: { + anyAssignment: 'Unsafe assignment of an any value', + unsafeArrayPattern: 'Unsafe array destructuring of an any array value', + unsafeArrayPatternFromTuple: + 'Unsafe array destructuring of a tuple element with an any value', + unsafeAssignment: + 'Unsafe asignment of type {{sender}} to a variable of type {{receiver}}', + unsafeArraySpread: 'Unsafe spread of an any value in an array', + }, + schema: [], + }, + defaultOptions: [], + create(context) { + const { program, esTreeNodeToTSNodeMap } = util.getParserServices(context); + const checker = program.getTypeChecker(); + + // returns true if the assignment reported + function checkArrayDestructureHelper( + receiverNode: TSESTree.Node, + senderNode: TSESTree.Node, + ): boolean { + if (receiverNode.type !== AST_NODE_TYPES.ArrayPattern) { + return false; + } + + const senderTsNode = esTreeNodeToTSNodeMap.get(senderNode); + const senderType = checker.getTypeAtLocation(senderTsNode); + + return checkArrayDestructure(receiverNode, senderType, senderTsNode); + } + + // returns true if the assignment reported + function checkArrayDestructure( + receiverNode: TSESTree.ArrayPattern, + senderType: ts.Type, + senderNode: ts.Node, + ): boolean { + // any array + // const [x] = ([] as any[]); + if (util.isTypeAnyArrayType(senderType, checker)) { + context.report({ + node: receiverNode, + messageId: 'unsafeArrayPattern', + }); + return false; + } + + if (!checker.isTupleType(senderType)) { + return true; + } + + const tupleElements = util.getTypeArguments(senderType, checker); + + // tuple with any + // const [x] = [1 as any]; + let didReport = false; + for ( + let receiverIndex = 0; + receiverIndex < receiverNode.elements.length; + receiverIndex += 1 + ) { + const receiverElement = receiverNode.elements[receiverIndex]; + if (!receiverElement) { + continue; + } + + if (receiverElement.type === AST_NODE_TYPES.RestElement) { + // don't handle rests as they're not a 1:1 assignment + continue; + } + + const senderType = tupleElements[receiverIndex] as ts.Type | undefined; + if (!senderType) { + continue; + } + + // check for the any type first so we can handle [[[x]]] = [any] + if (util.isTypeAnyType(senderType)) { + context.report({ + node: receiverElement, + messageId: 'unsafeArrayPatternFromTuple', + }); + // we want to report on every invalid element in the tuple + didReport = true; + } else if (receiverElement.type === AST_NODE_TYPES.ArrayPattern) { + didReport = checkArrayDestructure( + receiverElement, + senderType, + senderNode, + ); + } else if (receiverElement.type === AST_NODE_TYPES.ObjectPattern) { + didReport = checkObjectDestructure( + receiverElement, + senderType, + senderNode, + ); + } + } + + return didReport; + } + + // returns true if the assignment reported + function checkObjectDestructureHelper( + receiverNode: TSESTree.Node, + senderNode: TSESTree.Node, + ): boolean { + if (receiverNode.type !== AST_NODE_TYPES.ObjectPattern) { + return false; + } + + const senderTsNode = esTreeNodeToTSNodeMap.get(senderNode); + const senderType = checker.getTypeAtLocation(senderTsNode); + + return checkObjectDestructure(receiverNode, senderType, senderTsNode); + } + + // returns true if the assignment reported + function checkObjectDestructure( + receiverNode: TSESTree.ObjectPattern, + senderType: ts.Type, + senderNode: ts.Node, + ): boolean { + const properties = new Map( + senderType + .getProperties() + .map(property => [ + property.getName(), + checker.getTypeOfSymbolAtLocation(property, senderNode), + ]), + ); + + let didReport = false; + for ( + let receiverIndex = 0; + receiverIndex < receiverNode.properties.length; + receiverIndex += 1 + ) { + const receiverProperty = receiverNode.properties[receiverIndex]; + if (receiverProperty.type === AST_NODE_TYPES.RestElement) { + // don't bother checking rest + continue; + } + + let key: string; + if (receiverProperty.computed === false) { + key = + receiverProperty.key.type === AST_NODE_TYPES.Identifier + ? receiverProperty.key.name + : String(receiverProperty.key.value); + } else if (receiverProperty.key.type === AST_NODE_TYPES.Literal) { + key = String(receiverProperty.key.value); + } else if ( + receiverProperty.key.type === AST_NODE_TYPES.TemplateLiteral && + receiverProperty.key.quasis.length === 1 + ) { + key = String(receiverProperty.key.quasis[0].value.cooked); + } else { + // can't figure out the name, so skip it + continue; + } + + const senderType = properties.get(key); + if (!senderType) { + continue; + } + + // check for the any type first so we can handle {x: {y: z}} = {x: any} + if (util.isTypeAnyType(senderType)) { + context.report({ + node: receiverProperty.value, + messageId: 'unsafeArrayPatternFromTuple', + }); + didReport = true; + } else if ( + receiverProperty.value.type === AST_NODE_TYPES.ArrayPattern + ) { + didReport = checkArrayDestructure( + receiverProperty.value, + senderType, + senderNode, + ); + } else if ( + receiverProperty.value.type === AST_NODE_TYPES.ObjectPattern + ) { + didReport = checkObjectDestructure( + receiverProperty.value, + senderType, + senderNode, + ); + } + } + + return didReport; + } + + // returns true if the assignment reported + function checkAssignment( + receiverNode: TSESTree.Node, + senderNode: TSESTree.Expression, + reportingNode: TSESTree.Node, + comparisonType: ComparisonType, + ): boolean { + const receiverTsNode = esTreeNodeToTSNodeMap.get(receiverNode); + const receiverType = + comparisonType === ComparisonType.Contextual + ? util.getContextualType(checker, receiverTsNode as ts.Expression) ?? + checker.getTypeAtLocation(receiverTsNode) + : checker.getTypeAtLocation(receiverTsNode); + const senderType = checker.getTypeAtLocation( + esTreeNodeToTSNodeMap.get(senderNode), + ); + + if (util.isTypeAnyType(senderType)) { + context.report({ + node: reportingNode, + messageId: 'anyAssignment', + }); + return true; + } + + if (comparisonType === ComparisonType.None) { + return false; + } + + const result = util.isUnsafeAssignment(senderType, receiverType, checker); + if (!result) { + return false; + } + + const { sender, receiver } = result; + context.report({ + node: reportingNode, + messageId: 'unsafeAssignment', + data: { + sender: checker.typeToString(sender), + receiver: checker.typeToString(receiver), + }, + }); + return true; + } + + function getComparisonType( + typeAnnotation: TSESTree.TSTypeAnnotation | undefined, + ): ComparisonType { + return typeAnnotation + ? // if there's a type annotation, we can do a comparison + ComparisonType.Basic + : // no type annotation means the variable's type will just be inferred, thus equal + ComparisonType.None; + } + + return { + 'VariableDeclarator[init != null]'( + node: TSESTree.VariableDeclarator, + ): void { + const init = util.nullThrows( + node.init, + util.NullThrowsReasons.MissingToken(node.type, 'init'), + ); + let didReport = checkAssignment( + node.id, + init, + node, + getComparisonType(node.id.typeAnnotation), + ); + + if (!didReport) { + didReport = checkArrayDestructureHelper(node.id, init); + } + if (!didReport) { + checkObjectDestructureHelper(node.id, init); + } + }, + 'ClassProperty[value != null]'(node: TSESTree.ClassProperty): void { + checkAssignment( + node.key, + node.value!, + node, + getComparisonType(node.typeAnnotation), + ); + }, + 'AssignmentExpression[operator = "="], AssignmentPattern'( + node: TSESTree.AssignmentExpression | TSESTree.AssignmentPattern, + ): void { + let didReport = checkAssignment( + node.left, + node.right, + node, + // the variable already has some form of a type to compare against + ComparisonType.Basic, + ); + + if (!didReport) { + didReport = checkArrayDestructureHelper(node.left, node.right); + } + if (!didReport) { + checkObjectDestructureHelper(node.left, node.right); + } + }, + // object pattern props are checked via assignments + ':not(ObjectPattern) > Property'(node: TSESTree.Property): void { + if (node.value.type === AST_NODE_TYPES.AssignmentPattern) { + // handled by other selector + return; + } + + checkAssignment(node.key, node.value, node, ComparisonType.Contextual); + }, + 'ArrayExpression > SpreadElement'(node: TSESTree.SpreadElement): void { + const resetNode = esTreeNodeToTSNodeMap.get(node.argument); + const restType = checker.getTypeAtLocation(resetNode); + if ( + util.isTypeAnyType(restType) || + util.isTypeAnyArrayType(restType, checker) + ) { + context.report({ + node: node, + messageId: 'unsafeArraySpread', + }); + } + }, + 'JSXAttribute[value != null]'(node: TSESTree.JSXAttribute): void { + const value = util.nullThrows( + node.value, + util.NullThrowsReasons.MissingToken(node.type, 'value'), + ); + if ( + value.type !== AST_NODE_TYPES.JSXExpressionContainer || + value.expression.type === AST_NODE_TYPES.JSXEmptyExpression + ) { + return; + } + + checkAssignment( + node.name, + value.expression, + value.expression, + ComparisonType.Contextual, + ); + }, + }; + }, +}); diff --git a/packages/eslint-plugin/src/rules/no-unsafe-call.ts b/packages/eslint-plugin/src/rules/no-unsafe-call.ts index 42630181936..3661bd44f16 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-call.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-call.ts @@ -31,7 +31,8 @@ export default util.createRule<[], MessageIds>({ messageId: MessageIds, ): void { const tsNode = esTreeNodeToTSNodeMap.get(node); - const type = checker.getTypeAtLocation(tsNode); + const type = util.getConstrainedTypeAtLocation(checker, tsNode); + if (util.isTypeAnyType(type)) { context.report({ node: reportingNode, @@ -41,10 +42,10 @@ export default util.createRule<[], MessageIds>({ } return { - 'CallExpression, OptionalCallExpression'( - node: TSESTree.CallExpression | TSESTree.OptionalCallExpression, + ':matches(CallExpression, OptionalCallExpression) > :not(Import).callee'( + node: Exclude, ): void { - checkCall(node.callee, node.callee, 'unsafeCall'); + checkCall(node, node, 'unsafeCall'); }, NewExpression(node): void { checkCall(node.callee, node, 'unsafeNew'); diff --git a/packages/eslint-plugin/src/rules/no-unsafe-return.ts b/packages/eslint-plugin/src/rules/no-unsafe-return.ts index fef04cd6365..5fbbfac9b90 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-return.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-return.ts @@ -74,7 +74,8 @@ export default util.createRule({ } // function has an explicit return type, so ensure it's a safe return - const returnNodeType = checker.getTypeAtLocation( + const returnNodeType = util.getConstrainedTypeAtLocation( + checker, esTreeNodeToTSNodeMap.get(returnNode), ); const functionTSNode = esTreeNodeToTSNodeMap.get(functionNode); diff --git a/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts b/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts new file mode 100644 index 00000000000..7b63125e743 --- /dev/null +++ b/packages/eslint-plugin/src/rules/prefer-reduce-type-parameter.ts @@ -0,0 +1,104 @@ +import { + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; +import * as util from '../util'; + +type MemberExpressionWithCallExpressionParent = ( + | TSESTree.MemberExpression + | TSESTree.OptionalMemberExpression +) & { parent: TSESTree.CallExpression | TSESTree.OptionalCallExpression }; + +const getMemberExpressionName = ( + member: TSESTree.MemberExpression | TSESTree.OptionalMemberExpression, +): string | null => { + if (!member.computed) { + return member.property.name; + } + + if ( + member.property.type === AST_NODE_TYPES.Literal && + typeof member.property.value === 'string' + ) { + return member.property.value; + } + + return null; +}; + +export default util.createRule({ + name: 'prefer-reduce-type-parameter', + meta: { + type: 'problem', + docs: { + category: 'Best Practices', + recommended: false, + description: + 'Prefer using type parameter when calling `Array#reduce` instead of casting', + requiresTypeChecking: true, + }, + messages: { + preferTypeParameter: + 'Unnecessary cast: Array#reduce accepts a type parameter for the default value.', + }, + fixable: 'code', + schema: [], + }, + defaultOptions: [], + create(context) { + const service = util.getParserServices(context); + const checker = service.program.getTypeChecker(); + + return { + ':matches(CallExpression, OptionalCallExpression) > :matches(MemberExpression, OptionalMemberExpression).callee'( + callee: MemberExpressionWithCallExpressionParent, + ): void { + if (getMemberExpressionName(callee) !== 'reduce') { + return; + } + + const [, secondArg] = callee.parent.arguments; + + if ( + callee.parent.arguments.length < 2 || + !util.isTypeAssertion(secondArg) + ) { + return; + } + + // Get the symbol of the `reduce` method. + const tsNode = service.esTreeNodeToTSNodeMap.get(callee.object); + const calleeObjType = util.getConstrainedTypeAtLocation( + checker, + tsNode, + ); + + // Check the owner type of the `reduce` method. + if (checker.isArrayType(calleeObjType)) { + context.report({ + messageId: 'preferTypeParameter', + node: secondArg, + fix: fixer => [ + fixer.removeRange([ + secondArg.range[0], + secondArg.expression.range[0], + ]), + fixer.removeRange([ + secondArg.expression.range[1], + secondArg.range[1], + ]), + fixer.insertTextAfter( + callee, + `<${context + .getSourceCode() + .getText(secondArg.typeAnnotation)}>`, + ), + ], + }); + + return; + } + }, + }; + }, +}); diff --git a/packages/eslint-plugin/src/rules/prefer-ts-expect-error.ts b/packages/eslint-plugin/src/rules/prefer-ts-expect-error.ts new file mode 100644 index 00000000000..1b87b132786 --- /dev/null +++ b/packages/eslint-plugin/src/rules/prefer-ts-expect-error.ts @@ -0,0 +1,55 @@ +import { AST_TOKEN_TYPES } from '@typescript-eslint/experimental-utils'; +import * as util from '../util'; + +type MessageIds = 'preferExpectErrorComment'; + +export default util.createRule<[], MessageIds>({ + name: 'prefer-ts-expect-error', + meta: { + type: 'problem', + docs: { + description: + 'Recommends using `// @ts-expect-error` over `// @ts-ignore`', + category: 'Best Practices', + recommended: false, + }, + fixable: 'code', + messages: { + preferExpectErrorComment: + 'Use "// @ts-expect-error" to ensure an error is actually being suppressed.', + }, + schema: [], + }, + defaultOptions: [], + create(context) { + const tsIgnoreRegExp = /^\/*\s*@ts-ignore/; + const sourceCode = context.getSourceCode(); + + return { + Program(): void { + const comments = sourceCode.getAllComments(); + + comments.forEach(comment => { + if (comment.type !== AST_TOKEN_TYPES.Line) { + return; + } + + if (tsIgnoreRegExp.test(comment.value)) { + context.report({ + node: comment, + messageId: 'preferExpectErrorComment', + fix: fixer => + fixer.replaceText( + comment, + `//${comment.value.replace( + '@ts-ignore', + '@ts-expect-error', + )}`, + ), + }); + } + }); + }, + }; + }, +}); diff --git a/packages/eslint-plugin/src/rules/require-array-sort-compare.ts b/packages/eslint-plugin/src/rules/require-array-sort-compare.ts index 25c4c2e5747..2f388d364dd 100644 --- a/packages/eslint-plugin/src/rules/require-array-sort-compare.ts +++ b/packages/eslint-plugin/src/rules/require-array-sort-compare.ts @@ -1,5 +1,4 @@ import { TSESTree } from '@typescript-eslint/experimental-utils'; -import * as ts from 'typescript'; import * as util from '../util'; export default util.createRule({ @@ -27,26 +26,16 @@ export default util.createRule({ return { ":matches(CallExpression, OptionalCallExpression)[arguments.length=0] > :matches(MemberExpression, OptionalMemberExpression)[property.name='sort'][computed=false]"( - node: TSESTree.MemberExpression | TSESTree.OptionalMemberExpression, + callee: TSESTree.MemberExpression | TSESTree.OptionalMemberExpression, ): void { - // Get the symbol of the `sort` method. - const tsNode = service.esTreeNodeToTSNodeMap.get(node); - const sortSymbol = checker.getSymbolAtLocation(tsNode); - if (sortSymbol == null) { - return; - } + const tsNode = service.esTreeNodeToTSNodeMap.get(callee.object); + const calleeObjType = util.getConstrainedTypeAtLocation( + checker, + tsNode, + ); - // Check the owner type of the `sort` method. - for (const methodDecl of sortSymbol.declarations) { - const typeDecl = methodDecl.parent; - if ( - ts.isInterfaceDeclaration(typeDecl) && - ts.isSourceFile(typeDecl.parent) && - typeDecl.name.escapedText === 'Array' - ) { - context.report({ node: node.parent!, messageId: 'requireCompare' }); - return; - } + if (util.isTypeArrayTypeOrUnionOfArrayTypes(calleeObjType, checker)) { + context.report({ node: callee.parent!, messageId: 'requireCompare' }); } }, }; diff --git a/packages/eslint-plugin/src/rules/require-await.ts b/packages/eslint-plugin/src/rules/require-await.ts index e7b6a5f5959..99c455cd0f6 100644 --- a/packages/eslint-plugin/src/rules/require-await.ts +++ b/packages/eslint-plugin/src/rules/require-await.ts @@ -11,6 +11,8 @@ interface ScopeInfo { upper: ScopeInfo | null; hasAwait: boolean; hasAsync: boolean; + isGen: boolean; + isAsyncYield: boolean; } type FunctionNode = | TSESTree.FunctionDeclaration @@ -49,6 +51,8 @@ export default util.createRule({ upper: scopeInfo, hasAwait: false, hasAsync: node.async, + isGen: node.generator || false, + isAsyncYield: false, }; } @@ -62,7 +66,12 @@ export default util.createRule({ return; } - if (node.async && !scopeInfo.hasAwait && !isEmptyFunction(node)) { + if ( + node.async && + !scopeInfo.hasAwait && + !isEmptyFunction(node) && + !(scopeInfo.isGen && scopeInfo.isAsyncYield) + ) { context.report({ node, loc: getFunctionHeadLoc(node, sourceCode), @@ -92,10 +101,34 @@ export default util.createRule({ if (!scopeInfo) { return; } - scopeInfo.hasAwait = true; } + /** + * mark `scopeInfo.isAsyncYield` to `true` if its a generator + * function and the delegate is `true` + */ + function markAsHasDelegateGen(node: TSESTree.YieldExpression): void { + if (!scopeInfo || !scopeInfo.isGen || !node.argument) { + return; + } + + if (node?.argument?.type === AST_NODE_TYPES.Literal) { + // making this `false` as for literals we don't need to check the definition + // eg : async function* run() { yield* 1 } + scopeInfo.isAsyncYield = false; + } + + const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node?.argument); + const type = checker.getTypeAtLocation(tsNode); + const symbol = type.getSymbol(); + + // async function* test1() {yield* asyncGenerator() } + if (symbol?.getName() === 'AsyncGenerator') { + scopeInfo.isAsyncYield = true; + } + } + return { FunctionDeclaration: enterFunction, FunctionExpression: enterFunction, @@ -106,6 +139,7 @@ export default util.createRule({ AwaitExpression: markAsHasAwait, 'ForOfStatement[await = true]': markAsHasAwait, + 'YieldExpression[delegate = true]': markAsHasDelegateGen, // check body-less async arrow function. // ignore `async () => await foo` because it's obviously correct diff --git a/packages/eslint-plugin/src/rules/restrict-plus-operands.ts b/packages/eslint-plugin/src/rules/restrict-plus-operands.ts index a833825a207..2702009046f 100644 --- a/packages/eslint-plugin/src/rules/restrict-plus-operands.ts +++ b/packages/eslint-plugin/src/rules/restrict-plus-operands.ts @@ -54,16 +54,6 @@ export default util.createRule({ * Helper function to get base type of node */ function getBaseTypeOfLiteralType(type: ts.Type): BaseLiteral { - const constraint = type.getConstraint(); - if ( - constraint && - // for generic types with union constraints, it will return itself from getConstraint - // so we have to guard against infinite recursion... - constraint !== type - ) { - return getBaseTypeOfLiteralType(constraint); - } - if (type.isNumberLiteral()) { return 'number'; } @@ -98,7 +88,7 @@ export default util.createRule({ */ function getNodeType(node: TSESTree.Expression): BaseLiteral { const tsNode = service.esTreeNodeToTSNodeMap.get(node); - const type = typeChecker.getTypeAtLocation(tsNode); + const type = util.getConstrainedTypeAtLocation(typeChecker, tsNode); return getBaseTypeOfLiteralType(type); } diff --git a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts index 31d873d038b..0aabe299610 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -10,6 +10,7 @@ type Options = [ allowNullable?: boolean; allowNumber?: boolean; allowBoolean?: boolean; + allowAny?: boolean; }, ]; @@ -32,6 +33,7 @@ export default util.createRule({ { type: 'object', properties: { + allowAny: { type: 'boolean' }, allowBoolean: { type: 'boolean' }, allowNullable: { type: 'boolean' }, allowNumber: { type: 'boolean' }, diff --git a/packages/eslint-plugin/src/rules/unbound-method.ts b/packages/eslint-plugin/src/rules/unbound-method.ts index 1e9cb9131ad..fde58dc4e6f 100644 --- a/packages/eslint-plugin/src/rules/unbound-method.ts +++ b/packages/eslint-plugin/src/rules/unbound-method.ts @@ -198,6 +198,11 @@ function isDangerousMethod(symbol: ts.Symbol, ignoreStatic: boolean): boolean { } switch (valueDeclaration.kind) { + case ts.SyntaxKind.PropertyDeclaration: + return ( + (valueDeclaration as ts.PropertyDeclaration).initializer?.kind === + ts.SyntaxKind.FunctionExpression + ); case ts.SyntaxKind.MethodDeclaration: case ts.SyntaxKind.MethodSignature: return !( @@ -241,6 +246,9 @@ function isSafeUse(node: TSESTree.Node): boolean { case AST_NODE_TYPES.BinaryExpression: return ['instanceof', '==', '!=', '===', '!=='].includes(parent.operator); + case AST_NODE_TYPES.AssignmentExpression: + return parent.operator === '=' && node === parent.left; + case AST_NODE_TYPES.TSNonNullExpression: case AST_NODE_TYPES.TSAsExpression: case AST_NODE_TYPES.TSTypeAssertion: diff --git a/packages/eslint-plugin/src/util/types.ts b/packages/eslint-plugin/src/util/types.ts index 924c68e151d..261c154361b 100644 --- a/packages/eslint-plugin/src/util/types.ts +++ b/packages/eslint-plugin/src/util/types.ts @@ -1,6 +1,7 @@ import { isCallExpression, isJsxExpression, + isIdentifier, isNewExpression, isParameterDeclaration, isPropertyDeclaration, @@ -8,9 +9,27 @@ import { isUnionOrIntersectionType, isVariableDeclaration, unionTypeParts, + isPropertyAssignment, } from 'tsutils'; import * as ts from 'typescript'; +/** + * Checks if the given type is either an array type, + * or a union made up solely of array types. + */ +export function isTypeArrayTypeOrUnionOfArrayTypes( + type: ts.Type, + checker: ts.TypeChecker, +): boolean { + for (const t of unionTypeParts(type)) { + if (!checker.isArrayType(t)) { + return false; + } + } + + return true; +} + /** * @param type Type being checked by name. * @param allowedNames Symbol names checking on the type. @@ -297,6 +316,18 @@ export function getEqualsKind(operator: string): EqualsKind | undefined { } } +export function getTypeArguments( + type: ts.TypeReference, + checker: ts.TypeChecker, +): readonly ts.Type[] { + // getTypeArguments was only added in TS3.7 + if (checker.getTypeArguments) { + return checker.getTypeArguments(type); + } + + return type.typeArguments ?? []; +} + /** * @returns true if the type is `any` */ @@ -304,6 +335,22 @@ export function isTypeAnyType(type: ts.Type): boolean { return isTypeFlagSet(type, ts.TypeFlags.Any); } +/** + * @returns true if the type is `any[]` + */ +export function isTypeAnyArrayType( + type: ts.Type, + checker: ts.TypeChecker, +): boolean { + return ( + checker.isArrayType(type) && + isTypeAnyType( + // getTypeArguments was only added in TS3.7 + getTypeArguments(type, checker)[0], + ) + ); +} + export const enum AnyType { Any, AnyArray, @@ -321,10 +368,7 @@ export function isAnyOrAnyArrayTypeDiscriminated( if (isTypeAnyType(type)) { return AnyType.Any; } - if ( - checker.isArrayType(type) && - isTypeAnyType(checker.getTypeArguments(type)[0]) - ) { + if (isTypeAnyArrayType(type, checker)) { return AnyType.AnyArray; } return AnyType.Safe; @@ -345,6 +389,10 @@ export function isUnsafeAssignment( receiver: ts.Type, checker: ts.TypeChecker, ): false | { sender: ts.Type; receiver: ts.Type } { + if (isTypeAnyType(type) && !isTypeAnyType(receiver)) { + return { sender: type, receiver }; + } + if (isTypeReference(type) && isTypeReference(receiver)) { // TODO - figure out how to handle cases like this, // where the types are assignable, but not the same type @@ -381,9 +429,6 @@ export function isUnsafeAssignment( return false; } - if (isTypeAnyType(type) && !isTypeAnyType(receiver)) { - return { sender: type, receiver }; - } return false; } @@ -414,6 +459,8 @@ export function getContextualType( return parent.type ? checker.getTypeFromTypeNode(parent.type) : undefined; } else if (isJsxExpression(parent)) { return checker.getContextualType(parent); + } else if (isPropertyAssignment(parent) && isIdentifier(node)) { + return checker.getContextualType(node); } else if ( ![ts.SyntaxKind.TemplateSpan, ts.SyntaxKind.JsxExpression].includes( parent.kind, diff --git a/packages/eslint-plugin/tests/RuleTester.ts b/packages/eslint-plugin/tests/RuleTester.ts index c650c83449c..1774200db32 100644 --- a/packages/eslint-plugin/tests/RuleTester.ts +++ b/packages/eslint-plugin/tests/RuleTester.ts @@ -89,4 +89,16 @@ afterAll(() => { clearCaches(); }); -export { RuleTester, getFixturesRootDir, batchedSingleLineTests }; +/** + * Simple no-op tag to mark code samples as "should not format with prettier" + * for the internal/plugin-test-formatting lint rule + */ +function noFormat(strings: TemplateStringsArray, ...keys: string[]): string { + const lastIndex = strings.length - 1; + return ( + strings.slice(0, lastIndex).reduce((p, s, i) => p + s + keys[i], '') + + strings[lastIndex] + ); +} + +export { batchedSingleLineTests, getFixturesRootDir, noFormat, RuleTester }; diff --git a/packages/eslint-plugin/tests/fixtures/class.ts b/packages/eslint-plugin/tests/fixtures/class.ts index 74f222338ff..044e44e44ae 100644 --- a/packages/eslint-plugin/tests/fixtures/class.ts +++ b/packages/eslint-plugin/tests/fixtures/class.ts @@ -3,3 +3,8 @@ export class Error {} // used by unbound-method test case to test imports export const console = { log() {} }; + +// used by prefer-reduce-type-parameter to test native vs userland check +export class Reducable { + reduce() {} +} diff --git a/packages/eslint-plugin/tests/rules/adjacent-overload-signatures.test.ts b/packages/eslint-plugin/tests/rules/adjacent-overload-signatures.test.ts index c040362a128..8e844bb96be 100644 --- a/packages/eslint-plugin/tests/rules/adjacent-overload-signatures.test.ts +++ b/packages/eslint-plugin/tests/rules/adjacent-overload-signatures.test.ts @@ -11,217 +11,224 @@ ruleTester.run('adjacent-overload-signatures', rule, { code: ` function error(a: string); function error(b: number); -function error(ab: string|number){ } +function error(ab: string | number) {} export { error }; - `, + `, parserOptions: { sourceType: 'module' }, }, { code: ` import { connect } from 'react-redux'; -export interface ErrorMessageModel { message: string; } -function mapStateToProps() { } -function mapDispatchToProps() { } +export interface ErrorMessageModel { + message: string; +} +function mapStateToProps() {} +function mapDispatchToProps() {} export default connect(mapStateToProps, mapDispatchToProps)(ErrorMessage); - `, + `, parserOptions: { sourceType: 'module' }, }, ` -export const foo = "a", bar = "b"; +export const foo = 'a', + bar = 'b'; export interface Foo {} export class Foo {} - `, + `, ` export interface Foo {} -export const foo = "a", bar = "b"; +export const foo = 'a', + bar = 'b'; export class Foo {} - `, + `, ` -const foo = "a", bar = "b"; +const foo = 'a', + bar = 'b'; interface Foo {} class Foo {} - `, + `, ` interface Foo {} -const foo = "a", bar = "b"; +const foo = 'a', + bar = 'b'; class Foo {} - `, + `, ` export class Foo {} export class Bar {} export type FooBar = Foo | Bar; - `, + `, ` export interface Foo {} export class Foo {} export class Bar {} export type FooBar = Foo | Bar; - `, + `, ` export function foo(s: string); export function foo(n: number); export function foo(sn: string | number) {} export function bar(): void {} export function baz(): void {} - `, + `, ` function foo(s: string); function foo(n: number); function foo(sn: string | number) {} function bar(): void {} function baz(): void {} - `, + `, ` declare function foo(s: string); declare function foo(n: number); declare function foo(sn: string | number); declare function bar(): void; declare function baz(): void; - `, + `, ` -declare module "Foo" { - export function foo(s: string): void; - export function foo(n: number): void; - export function foo(sn: string | number): void; - export function bar(): void; - export function baz(): void; -} - `, +declare module 'Foo' { + export function foo(s: string): void; + export function foo(n: number): void; + export function foo(sn: string | number): void; + export function bar(): void; + export function baz(): void; +} + `, ` declare namespace Foo { - export function foo(s: string): void; - export function foo(n: number): void; - export function foo(sn: string | number): void; - export function bar(): void; - export function baz(): void; + export function foo(s: string): void; + export function foo(n: number): void; + export function foo(sn: string | number): void; + export function bar(): void; + export function baz(): void; } - `, + `, ` type Foo = { - foo(s: string): void; - foo(n: number): void; - foo(sn: string | number): void; - bar(): void; - baz(): void; -} - `, + foo(s: string): void; + foo(n: number): void; + foo(sn: string | number): void; + bar(): void; + baz(): void; +}; + `, ` type Foo = { - foo(s: string): void; - ["foo"](n: number): void; - foo(sn: string | number): void; - bar(): void; - baz(): void; -} - `, + foo(s: string): void; + ['foo'](n: number): void; + foo(sn: string | number): void; + bar(): void; + baz(): void; +}; + `, ` interface Foo { - (s: string): void; - (n: number): void; - (sn: string | number): void; - foo(n: number): void; - bar(): void; - baz(): void; -} - `, + (s: string): void; + (n: number): void; + (sn: string | number): void; + foo(n: number): void; + bar(): void; + baz(): void; +} + `, ` interface Foo { - foo(s: string): void; - foo(n: number): void; - foo(sn: string | number): void; - bar(): void; - baz(): void; + foo(s: string): void; + foo(n: number): void; + foo(sn: string | number): void; + bar(): void; + baz(): void; } - `, + `, ` interface Foo { - foo(s: string): void; - ["foo"](n: number): void; - foo(sn: string | number): void; - bar(): void; - baz(): void; + foo(s: string): void; + ['foo'](n: number): void; + foo(sn: string | number): void; + bar(): void; + baz(): void; } - `, + `, ` interface Foo { - foo(): void; - bar: { - baz(s: string): void; - baz(n: number): void; - baz(sn: string | number): void; - } -} - `, + foo(): void; + bar: { + baz(s: string): void; + baz(n: number): void; + baz(sn: string | number): void; + }; +} + `, ` interface Foo { - new(s: string); - new(n: number); - new(sn: string | number); - foo(): void; + new (s: string); + new (n: number); + new (sn: string | number); + foo(): void; } - `, + `, ` class Foo { - constructor(s: string); - constructor(n: number); - constructor(sn: string | number) {} - bar(): void {} - baz(): void {} + constructor(s: string); + constructor(n: number); + constructor(sn: string | number) {} + bar(): void {} + baz(): void {} } - `, + `, ` class Foo { - foo(s: string): void; - foo(n: number): void; - foo(sn: string | number): void {} - bar(): void {} - baz(): void {} + foo(s: string): void; + foo(n: number): void; + foo(sn: string | number): void {} + bar(): void {} + baz(): void {} } - `, + `, ` class Foo { - foo(s: string): void; - ["foo"](n: number): void; - foo(sn: string | number): void {} - bar(): void {} - baz(): void {} + foo(s: string): void; + ['foo'](n: number): void; + foo(sn: string | number): void {} + bar(): void {} + baz(): void {} } - `, + `, ` class Foo { - name: string; - foo(s: string): void; - foo(n: number): void; - foo(sn: string | number): void {} - bar(): void {} - baz(): void {} -} - `, + name: string; + foo(s: string): void; + foo(n: number): void; + foo(sn: string | number): void {} + bar(): void {} + baz(): void {} +} + `, ` class Foo { - name: string; - static foo(s: string): void; - static foo(n: number): void; - static foo(sn: string | number): void {} - bar(): void {} - baz(): void {} -} - `, + name: string; + static foo(s: string): void; + static foo(n: number): void; + static foo(sn: string | number): void {} + bar(): void {} + baz(): void {} +} + `, ` class Test { static test() {} untest() {} test() {} } - `, + `, // examples from https://github.com/nzakas/eslint-plugin-typescript/issues/138 - 'export default function(foo : T) {}', - 'export default function named(foo : T) {}', + 'export default function(foo: T) {}', + 'export default function named(foo: T) {}', ` interface Foo { [Symbol.toStringTag](): void; [Symbol.iterator](): void; -}`, +} + `, ], invalid: [ { @@ -231,7 +238,7 @@ export function foo(n: number); export function bar(): void {} export function baz(): void {} export function foo(sn: string | number) {} - `, + `, errors: [ { messageId: 'adjacentSignature', @@ -248,7 +255,7 @@ export function foo(n: number); export type bar = number; export type baz = number | string; export function foo(sn: string | number) {} - `, + `, errors: [ { messageId: 'adjacentSignature', @@ -265,7 +272,7 @@ function foo(n: number); function bar(): void {} function baz(): void {} function foo(sn: string | number) {} - `, + `, errors: [ { messageId: 'adjacentSignature', @@ -282,7 +289,7 @@ function foo(n: number); type bar = number; type baz = number | string; function foo(sn: string | number) {} - `, + `, errors: [ { messageId: 'adjacentSignature', @@ -296,10 +303,10 @@ function foo(sn: string | number) {} code: ` function foo(s: string) {} function foo(n: number) {} -const a = ""; -const b = ""; +const a = ''; +const b = ''; function foo(sn: string | number) {} - `, + `, errors: [ { messageId: 'adjacentSignature', @@ -315,7 +322,7 @@ function foo(s: string) {} function foo(n: number) {} class Bar {} function foo(sn: string | number) {} - `, + `, errors: [ { messageId: 'adjacentSignature', @@ -331,18 +338,18 @@ function foo(s: string) {} function foo(n: number) {} function foo(sn: string | number) {} class Bar { - foo(s: string); - foo(n: number); - name: string; - foo(sn: string | number) { } + foo(s: string); + foo(n: number); + name: string; + foo(sn: string | number) {} } - `, + `, errors: [ { messageId: 'adjacentSignature', data: { name: 'foo' }, line: 9, - column: 5, + column: 3, }, ], }, @@ -353,7 +360,7 @@ declare function foo(n: number); declare function bar(): void; declare function baz(): void; declare function foo(sn: string | number); - `, + `, errors: [ { messageId: 'adjacentSignature', @@ -367,10 +374,10 @@ declare function foo(sn: string | number); code: ` declare function foo(s: string); declare function foo(n: number); -const a = ""; -const b = ""; +const a = ''; +const b = ''; declare function foo(sn: string | number); - `, + `, errors: [ { messageId: 'adjacentSignature', @@ -382,437 +389,438 @@ declare function foo(sn: string | number); }, { code: ` -declare module "Foo" { - export function foo(s: string): void; - export function foo(n: number): void; - export function bar(): void; - export function baz(): void; - export function foo(sn: string | number): void; +declare module 'Foo' { + export function foo(s: string): void; + export function foo(n: number): void; + export function bar(): void; + export function baz(): void; + export function foo(sn: string | number): void; } - `, + `, errors: [ { messageId: 'adjacentSignature', data: { name: 'foo' }, line: 7, - column: 5, + column: 3, }, ], }, { code: ` -declare module "Foo" { - export function foo(s: string): void; - export function foo(n: number): void; - export function foo(sn: string | number): void; - function baz(s: string): void; - export function bar(): void; - function baz(n: number): void; - function baz(sn: string | number): void; +declare module 'Foo' { + export function foo(s: string): void; + export function foo(n: number): void; + export function foo(sn: string | number): void; + function baz(s: string): void; + export function bar(): void; + function baz(n: number): void; + function baz(sn: string | number): void; } - `, + `, errors: [ { messageId: 'adjacentSignature', data: { name: 'baz' }, line: 8, - column: 5, + column: 3, }, ], }, { code: ` declare namespace Foo { - export function foo(s: string): void; - export function foo(n: number): void; - export function bar(): void; - export function baz(): void; - export function foo(sn: string | number): void; + export function foo(s: string): void; + export function foo(n: number): void; + export function bar(): void; + export function baz(): void; + export function foo(sn: string | number): void; } - `, + `, errors: [ { messageId: 'adjacentSignature', data: { name: 'foo' }, line: 7, - column: 5, + column: 3, }, ], }, { code: ` declare namespace Foo { - export function foo(s: string): void; - export function foo(n: number): void; - export function foo(sn: string | number): void; - function baz(s: string): void; - export function bar(): void; - function baz(n: number): void; - function baz(sn: string | number): void; -} - `, + export function foo(s: string): void; + export function foo(n: number): void; + export function foo(sn: string | number): void; + function baz(s: string): void; + export function bar(): void; + function baz(n: number): void; + function baz(sn: string | number): void; +} + `, errors: [ { messageId: 'adjacentSignature', data: { name: 'baz' }, line: 8, - column: 5, + column: 3, }, ], }, { code: ` type Foo = { - foo(s: string): void; - foo(n: number): void; - bar(): void; - baz(): void; - foo(sn: string | number): void; -} - `, + foo(s: string): void; + foo(n: number): void; + bar(): void; + baz(): void; + foo(sn: string | number): void; +}; + `, errors: [ { messageId: 'adjacentSignature', data: { name: 'foo' }, line: 7, - column: 5, + column: 3, }, ], }, { code: ` type Foo = { - foo(s: string): void; - ["foo"](n: number): void; - bar(): void; - baz(): void; - foo(sn: string | number): void; -} - `, + foo(s: string): void; + ['foo'](n: number): void; + bar(): void; + baz(): void; + foo(sn: string | number): void; +}; + `, errors: [ { messageId: 'adjacentSignature', data: { name: 'foo' }, line: 7, - column: 5, + column: 3, }, ], }, { code: ` type Foo = { - foo(s: string): void; - name: string; - foo(n: number): void; - foo(sn: string | number): void; - bar(): void; - baz(): void; -} - `, + foo(s: string): void; + name: string; + foo(n: number): void; + foo(sn: string | number): void; + bar(): void; + baz(): void; +}; + `, errors: [ { messageId: 'adjacentSignature', data: { name: 'foo' }, line: 5, - column: 5, + column: 3, }, ], }, { code: ` interface Foo { - (s: string): void; - foo(n: number): void; - (n: number): void; - (sn: string | number): void; - bar(): void; - baz(): void; -} - `, + (s: string): void; + foo(n: number): void; + (n: number): void; + (sn: string | number): void; + bar(): void; + baz(): void; +} + `, errors: [ { messageId: 'adjacentSignature', data: { name: 'call' }, line: 5, - column: 5, + column: 3, }, ], }, { code: ` interface Foo { - foo(s: string): void; - foo(n: number): void; - bar(): void; - baz(): void; - foo(sn: string | number): void; + foo(s: string): void; + foo(n: number): void; + bar(): void; + baz(): void; + foo(sn: string | number): void; } - `, + `, errors: [ { messageId: 'adjacentSignature', data: { name: 'foo' }, line: 7, - column: 5, + column: 3, }, ], }, { code: ` interface Foo { - foo(s: string): void; - ["foo"](n: number): void; - bar(): void; - baz(): void; - foo(sn: string | number): void; + foo(s: string): void; + ['foo'](n: number): void; + bar(): void; + baz(): void; + foo(sn: string | number): void; } - `, + `, errors: [ { messageId: 'adjacentSignature', data: { name: 'foo' }, line: 7, - column: 5, + column: 3, }, ], }, { code: ` interface Foo { - foo(s: string): void; - "foo"(n: number): void; - bar(): void; - baz(): void; - foo(sn: string | number): void; + foo(s: string): void; + 'foo'(n: number): void; + bar(): void; + baz(): void; + foo(sn: string | number): void; } - `, + `, errors: [ { messageId: 'adjacentSignature', data: { name: 'foo' }, line: 7, - column: 5, + column: 3, }, ], }, { code: ` interface Foo { - foo(s: string): void; - name: string; - foo(n: number): void; - foo(sn: string | number): void; - bar(): void; - baz(): void; -} - `, + foo(s: string): void; + name: string; + foo(n: number): void; + foo(sn: string | number): void; + bar(): void; + baz(): void; +} + `, errors: [ { messageId: 'adjacentSignature', data: { name: 'foo' }, line: 5, - column: 5, + column: 3, }, ], }, { code: ` interface Foo { + foo(): void; + bar: { + baz(s: string): void; + baz(n: number): void; foo(): void; - bar: { - baz(s: string): void; - baz(n: number): void; - foo(): void; - baz(sn: string | number): void; - } -} - `, + baz(sn: string | number): void; + }; +} + `, errors: [ { messageId: 'adjacentSignature', data: { name: 'baz' }, line: 8, - column: 9, + column: 5, }, ], }, { code: ` interface Foo { - new(s: string); - new(n: number); - foo(): void; - bar(): void; - new(sn: string | number); + new (s: string); + new (n: number); + foo(): void; + bar(): void; + new (sn: string | number); } - `, + `, errors: [ { messageId: 'adjacentSignature', data: { name: 'new' }, line: 7, - column: 5, + column: 3, }, ], }, { code: ` interface Foo { - new(s: string); - foo(): void; - new(n: number); - bar(): void; - new(sn: string | number); + new (s: string); + foo(): void; + new (n: number); + bar(): void; + new (sn: string | number); } - `, + `, errors: [ { messageId: 'adjacentSignature', data: { name: 'new' }, line: 5, - column: 5, + column: 3, }, { messageId: 'adjacentSignature', data: { name: 'new' }, line: 7, - column: 5, + column: 3, }, ], }, { code: ` class Foo { - constructor(s: string); - constructor(n: number); - bar(): void {} - baz(): void {} - constructor(sn: string | number) {} + constructor(s: string); + constructor(n: number); + bar(): void {} + baz(): void {} + constructor(sn: string | number) {} } - `, + `, errors: [ { messageId: 'adjacentSignature', data: { name: 'constructor' }, line: 7, - column: 5, + column: 3, }, ], }, { code: ` class Foo { - foo(s: string): void; - foo(n: number): void; - bar(): void {} - baz(): void {} - foo(sn: string | number): void {} + foo(s: string): void; + foo(n: number): void; + bar(): void {} + baz(): void {} + foo(sn: string | number): void {} } - `, + `, errors: [ { messageId: 'adjacentSignature', data: { name: 'foo' }, line: 7, - column: 5, + column: 3, }, ], }, { code: ` class Foo { - foo(s: string): void; - ["foo"](n: number): void; - bar(): void {} - baz(): void {} - foo(sn: string | number): void {} + foo(s: string): void; + ['foo'](n: number): void; + bar(): void {} + baz(): void {} + foo(sn: string | number): void {} } - `, + `, errors: [ { messageId: 'adjacentSignature', data: { name: 'foo' }, line: 7, - column: 5, + column: 3, }, ], }, { code: ` class Foo { - foo(s: string): void; - "foo"(n: number): void; - bar(): void {} - baz(): void {} - foo(sn: string | number): void {} -} - `, + // prettier-ignore + "foo"(s: string): void; + foo(n: number): void; + bar(): void {} + baz(): void {} + foo(sn: string | number): void {} +} + `, errors: [ { messageId: 'adjacentSignature', data: { name: 'foo' }, - line: 7, - column: 5, + line: 8, + column: 3, }, ], }, { code: ` class Foo { - constructor(s: string); - name: string; - constructor(n: number); - constructor(sn: string | number) {} - bar(): void {} - baz(): void {} -} - `, + constructor(s: string); + name: string; + constructor(n: number); + constructor(sn: string | number) {} + bar(): void {} + baz(): void {} +} + `, errors: [ { messageId: 'adjacentSignature', data: { name: 'constructor' }, line: 5, - column: 5, + column: 3, }, ], }, { code: ` class Foo { - foo(s: string): void; - name: string; - foo(n: number): void; - foo(sn: string | number): void {} - bar(): void {} - baz(): void {} -} - `, + foo(s: string): void; + name: string; + foo(n: number): void; + foo(sn: string | number): void {} + bar(): void {} + baz(): void {} +} + `, errors: [ { messageId: 'adjacentSignature', data: { name: 'foo' }, line: 5, - column: 5, + column: 3, }, ], }, { code: ` class Foo { - static foo(s: string): void; - name: string; - static foo(n: number): void; - static foo(sn: string | number): void {} - bar(): void {} - baz(): void {} -} - `, + static foo(s: string): void; + name: string; + static foo(n: number): void; + static foo(sn: string | number): void {} + bar(): void {} + baz(): void {} +} + `, errors: [ { messageId: 'adjacentSignature', data: { name: 'static foo' }, line: 5, - column: 5, + column: 3, }, ], }, diff --git a/packages/eslint-plugin/tests/rules/array-type.test.ts b/packages/eslint-plugin/tests/rules/array-type.test.ts index b74c06c09fd..5f15ba5e822 100644 --- a/packages/eslint-plugin/tests/rules/array-type.test.ts +++ b/packages/eslint-plugin/tests/rules/array-type.test.ts @@ -10,35 +10,35 @@ const ruleTester = new RuleTester({ ruleTester.run('array-type', rule, { valid: [ { - code: 'let a: readonly any[] = []', + code: 'let a: readonly any[] = [];', options: [{ default: 'array' }], }, { - code: 'let a = new Array()', + code: 'let a = new Array();', options: [{ default: 'array' }], }, { - code: 'let a: string[] = []', + code: 'let a: string[] = [];', options: [{ default: 'array' }], }, { - code: 'let a: (string | number)[] = []', + code: 'let a: (string | number)[] = [];', options: [{ default: 'array' }], }, { - code: 'let a: ({ foo: Bar[] })[] = []', + code: 'let a: { foo: Bar[] }[] = [];', options: [{ default: 'array' }], }, { - code: 'let a: Array = []', + code: 'let a: Array = [];', options: [{ default: 'generic' }], }, { - code: 'let a: Array = []', + code: 'let a: Array = [];', options: [{ default: 'generic' }], }, { - code: 'let a: Array<{ foo: Array }> = []', + code: 'let a: Array<{ foo: Array }> = [];', options: [{ default: 'generic' }], }, { @@ -46,7 +46,7 @@ ruleTester.run('array-type', rule, { options: [{ default: 'generic' }], }, { - code: 'function foo (a: Array): Array {}', + code: 'function foo(a: Array): Array {}', options: [{ default: 'generic' }], }, { @@ -54,15 +54,19 @@ ruleTester.run('array-type', rule, { options: [{ default: 'array-simple' }], }, { - code: `function fooFunction(foo: Array>) { - return foo.map(e => e.foo); -}`, + code: ` +function fooFunction(foo: Array>) { + return foo.map(e => e.foo); +} + `, options: [{ default: 'array-simple' }], }, { - code: `function bazFunction(baz: Arr>) { - return baz.map(e => e.baz); -}`, + code: ` +function bazFunction(baz: Arr>) { + return baz.map(e => e.baz); +} + `, options: [{ default: 'array-simple' }], }, { @@ -78,16 +82,20 @@ ruleTester.run('array-type', rule, { options: [{ default: 'array-simple' }], }, { - code: `namespace fooName { - type BarType = { bar: string }; - type BazType = Arr; -}`, + code: ` +namespace fooName { + type BarType = { bar: string }; + type BazType = Arr; +} + `, options: [{ default: 'array-simple' }], }, { - code: `interface FooInterface { - '.bar': {baz: string[];}; -}`, + code: ` +interface FooInterface { + '.bar': { baz: string[] }; +} + `, options: [{ default: 'array-simple' }], }, { @@ -95,19 +103,23 @@ ruleTester.run('array-type', rule, { options: [{ default: 'array' }], }, { - code: 'let ya = [[1, "2"]] as[number, string][];', + code: "let ya = [[1, '2']] as [number, string][];", options: [{ default: 'array' }], }, { - code: `function barFunction(bar: ArrayClass[]) { - return bar.map(e => e.bar); -}`, + code: ` +function barFunction(bar: ArrayClass[]) { + return bar.map(e => e.bar); +} + `, options: [{ default: 'array' }], }, { - code: `function bazFunction(baz: Arr>) { - return baz.map(e => e.baz); -}`, + code: ` +function bazFunction(baz: Arr>) { + return baz.map(e => e.baz); +} + `, options: [{ default: 'array' }], }, { @@ -115,7 +127,7 @@ ruleTester.run('array-type', rule, { options: [{ default: 'array' }], }, { - code: 'type barUnion = (string|number|boolean)[];', + code: 'type barUnion = (string | number | boolean)[];', options: [{ default: 'array' }], }, { @@ -123,18 +135,20 @@ ruleTester.run('array-type', rule, { options: [{ default: 'array' }], }, { - code: `interface FooInterface { - '.bar': {baz: string[];}; -}`, + code: ` +interface FooInterface { + '.bar': { baz: string[] }; +} + `, options: [{ default: 'array' }], }, { // https://github.com/typescript-eslint/typescript-eslint/issues/172 - code: 'type Unwrap = T extends (infer E)[] ? E : T', + code: 'type Unwrap = T extends (infer E)[] ? E : T;', options: [{ default: 'array' }], }, { - code: 'let z: Array = [3, "4"];', + code: "let z: Array = [3, '4'];", options: [{ default: 'generic' }], }, { @@ -146,15 +160,19 @@ ruleTester.run('array-type', rule, { options: [{ default: 'generic' }], }, { - code: `function fooFunction(foo: Array>) { - return foo.map(e => e.foo); -}`, + code: ` +function fooFunction(foo: Array>) { + return foo.map(e => e.foo); +} + `, options: [{ default: 'generic' }], }, { - code: `function bazFunction(baz: Arr>) { - return baz.map(e => e.baz); -}`, + code: ` +function bazFunction(baz: Arr>) { + return baz.map(e => e.baz); +} + `, options: [{ default: 'generic' }], }, { @@ -162,7 +180,7 @@ ruleTester.run('array-type', rule, { options: [{ default: 'generic' }], }, { - code: 'type fooUnion = Array;', + code: 'type fooUnion = Array;', options: [{ default: 'generic' }], }, { @@ -171,40 +189,40 @@ ruleTester.run('array-type', rule, { }, { // https://github.com/typescript-eslint/typescript-eslint/issues/172 - code: 'type Unwrap = T extends Array ? E : T', + code: 'type Unwrap = T extends Array ? E : T;', options: [{ default: 'generic' }], }, // readonly { - code: 'let a: string[] = []', + code: 'let a: string[] = [];', options: [{ default: 'array', readonly: 'generic' }], }, { - code: 'let a: ReadonlyArray = []', + code: 'let a: ReadonlyArray = [];', options: [{ default: 'array', readonly: 'generic' }], }, { - code: 'let a: ReadonlyArray = [[]]', + code: 'let a: ReadonlyArray = [[]];', options: [{ default: 'array', readonly: 'generic' }], }, { - code: 'let a: Array = []', + code: 'let a: Array = [];', options: [{ default: 'generic', readonly: 'array' }], }, { - code: 'let a: readonly number[] = []', + code: 'let a: readonly number[] = [];', options: [{ default: 'generic', readonly: 'array' }], }, { - code: 'let a: readonly Array[] = [[]]', + code: 'let a: readonly Array[] = [[]];', options: [{ default: 'generic', readonly: 'array' }], }, ], invalid: [ { - code: 'let a: Array = []', - output: 'let a: string[] = []', + code: 'let a: Array = [];', + output: 'let a: string[] = [];', options: [{ default: 'array' }], errors: [ { @@ -216,8 +234,8 @@ ruleTester.run('array-type', rule, { ], }, { - code: 'let a: Array = []', - output: 'let a: (string | number)[] = []', + code: 'let a: Array = [];', + output: 'let a: (string | number)[] = [];', options: [{ default: 'array' }], errors: [ { @@ -229,21 +247,21 @@ ruleTester.run('array-type', rule, { ], }, { - code: 'let a: ({ foo: Array })[] = []', - output: 'let a: ({ foo: Bar[] })[] = []', + code: 'let a: { foo: Array }[] = [];', + output: 'let a: { foo: Bar[] }[] = [];', options: [{ default: 'array' }], errors: [ { messageId: 'errorStringArray', data: { type: 'Bar' }, line: 1, - column: 16, + column: 15, }, ], }, { - code: 'let a: string[] = []', - output: 'let a: Array = []', + code: 'let a: string[] = [];', + output: 'let a: Array = [];', options: [{ default: 'generic' }], errors: [ { @@ -255,8 +273,8 @@ ruleTester.run('array-type', rule, { ], }, { - code: 'let a: (string | number)[] = []', - output: 'let a: Array = []', + code: 'let a: (string | number)[] = [];', + output: 'let a: Array = [];', options: [{ default: 'generic' }], errors: [ { @@ -268,8 +286,8 @@ ruleTester.run('array-type', rule, { ], }, { - code: 'let a: Array<{ foo: Bar[] }> = []', - output: 'let a: Array<{ foo: Array }> = []', + code: 'let a: Array<{ foo: Bar[] }> = [];', + output: 'let a: Array<{ foo: Array }> = [];', options: [{ default: 'generic' }], errors: [ { @@ -281,8 +299,8 @@ ruleTester.run('array-type', rule, { ], }, { - code: 'let a: Array<{ foo: Foo | Bar[] }> = []', - output: 'let a: Array<{ foo: Foo | Array }> = []', + code: 'let a: Array<{ foo: Foo | Bar[] }> = [];', + output: 'let a: Array<{ foo: Foo | Array }> = [];', options: [{ default: 'generic' }], errors: [ { @@ -294,27 +312,27 @@ ruleTester.run('array-type', rule, { ], }, { - code: 'function foo (a: Array): Array {}', - output: 'function foo (a: Bar[]): Bar[] {}', + code: 'function foo(a: Array): Array {}', + output: 'function foo(a: Bar[]): Bar[] {}', options: [{ default: 'array' }], errors: [ { messageId: 'errorStringArray', data: { type: 'Bar' }, line: 1, - column: 18, + column: 17, }, { messageId: 'errorStringArray', data: { type: 'Bar' }, line: 1, - column: 31, + column: 30, }, ], }, { - code: 'let a: Array<>[] = []', - output: 'let a: any[][] = []', + code: 'let a: Array<>[] = [];', + output: 'let a: any[][] = [];', options: [{ default: 'array-simple' }], errors: [ { @@ -358,8 +376,8 @@ ruleTester.run('array-type', rule, { ], }, { - code: 'let y: string[] = >["2"];', - output: 'let y: string[] = ["2"];', + code: "let y: string[] = >['2'];", + output: "let y: string[] = ['2'];", options: [{ default: 'array-simple' }], errors: [ { @@ -371,8 +389,8 @@ ruleTester.run('array-type', rule, { ], }, { - code: 'let z: Array = [3, "4"];', - output: 'let z: any[] = [3, "4"];', + code: "let z: Array = [3, '4'];", + output: "let z: any[] = [3, '4'];", options: [{ default: 'array-simple' }], errors: [ { @@ -384,15 +402,15 @@ ruleTester.run('array-type', rule, { ], }, { - code: 'let ya = [[1, "2"]] as[number, string][];', - output: 'let ya = [[1, "2"]] as Array<[number, string]>;', + code: "let ya = [[1, '2']] as [number, string][];", + output: "let ya = [[1, '2']] as Array<[number, string]>;", options: [{ default: 'array-simple' }], errors: [ { messageId: 'errorStringGenericSimple', data: { type: 'T' }, line: 1, - column: 23, + column: 24, }, ], }, @@ -410,56 +428,68 @@ ruleTester.run('array-type', rule, { ], }, { - code: `// Ignore user defined aliases -let yyyy: Arr>[]> = [[[["2"]]]];`, - output: `// Ignore user defined aliases -let yyyy: Arr>>> = [[[["2"]]]];`, + code: ` +// Ignore user defined aliases +let yyyy: Arr>[]> = [[[['2']]]]; + `, + output: ` +// Ignore user defined aliases +let yyyy: Arr>>> = [[[['2']]]]; + `, options: [{ default: 'array-simple' }], errors: [ { messageId: 'errorStringGenericSimple', data: { type: 'T' }, - line: 2, + line: 3, column: 15, }, ], }, { - code: `interface ArrayClass { - foo: Array; - bar: T[]; - baz: Arr; - xyz: this[]; -}`, - output: `interface ArrayClass { - foo: T[]; - bar: T[]; - baz: Arr; - xyz: this[]; -}`, + code: ` +interface ArrayClass { + foo: Array; + bar: T[]; + baz: Arr; + xyz: this[]; +} + `, + output: ` +interface ArrayClass { + foo: T[]; + bar: T[]; + baz: Arr; + xyz: this[]; +} + `, options: [{ default: 'array-simple' }], errors: [ { messageId: 'errorStringArraySimple', data: { type: 'T' }, - line: 2, - column: 10, + line: 3, + column: 8, }, ], }, { - code: `function barFunction(bar: ArrayClass[]) { - return bar.map(e => e.bar); -}`, - output: `function barFunction(bar: Array>) { - return bar.map(e => e.bar); -}`, + code: ` +function barFunction(bar: ArrayClass[]) { + return bar.map(e => e.bar); +} + `, + output: ` +function barFunction(bar: Array>) { + return bar.map(e => e.bar); +} + `, options: [{ default: 'array-simple' }], errors: [ { messageId: 'errorStringGenericSimple', data: { type: 'T' }, - line: 1, + line: 2, column: 27, }, ], @@ -478,8 +508,8 @@ let yyyy: Arr>>> = [[[["2"]]]];`, ], }, { - code: 'type barUnion = (string|number|boolean)[];', - output: 'type barUnion = Array;', + code: 'type barUnion = (string | number | boolean)[];', + output: 'type barUnion = Array;', options: [{ default: 'array-simple' }], errors: [ { @@ -504,8 +534,8 @@ let yyyy: Arr>>> = [[[["2"]]]];`, ], }, { - code: 'let v: Array = [{ bar: "bar" }];', - output: 'let v: fooName.BarType[] = [{ bar: "bar" }];', + code: "let v: Array = [{ bar: 'bar' }];", + output: "let v: fooName.BarType[] = [{ bar: 'bar' }];", options: [{ default: 'array-simple' }], errors: [ { @@ -517,8 +547,8 @@ let yyyy: Arr>>> = [[[["2"]]]];`, ], }, { - code: 'let w: fooName.BazType[] = [["baz"]];', - output: 'let w: Array> = [["baz"]];', + code: "let w: fooName.BazType[] = [['baz']];", + output: "let w: Array> = [['baz']];", options: [{ default: 'array-simple' }], errors: [ { @@ -543,8 +573,8 @@ let yyyy: Arr>>> = [[[["2"]]]];`, ], }, { - code: 'let y: string[] = >["2"];', - output: 'let y: string[] = ["2"];', + code: "let y: string[] = >['2'];", + output: "let y: string[] = ['2'];", options: [{ default: 'array' }], errors: [ { @@ -556,8 +586,8 @@ let yyyy: Arr>>> = [[[["2"]]]];`, ], }, { - code: 'let z: Array = [3, "4"];', - output: 'let z: any[] = [3, "4"];', + code: "let z: Array = [3, '4'];", + output: "let z: any[] = [3, '4'];", options: [{ default: 'array' }], errors: [ { @@ -582,54 +612,66 @@ let yyyy: Arr>>> = [[[["2"]]]];`, ], }, { - code: `// Ignore user defined aliases -let yyyy: Arr>[]> = [[[["2"]]]];`, - output: `// Ignore user defined aliases -let yyyy: Arr[][]> = [[[["2"]]]];`, + code: ` +// Ignore user defined aliases +let yyyy: Arr>[]> = [[[['2']]]]; + `, + output: ` +// Ignore user defined aliases +let yyyy: Arr[][]> = [[[['2']]]]; + `, options: [{ default: 'array' }], errors: [ { messageId: 'errorStringArray', data: { type: 'T' }, - line: 2, + line: 3, column: 15, }, ], }, { - code: `interface ArrayClass { - foo: Array; - bar: T[]; - baz: Arr; -}`, - output: `interface ArrayClass { - foo: T[]; - bar: T[]; - baz: Arr; -}`, + code: ` +interface ArrayClass { + foo: Array; + bar: T[]; + baz: Arr; +} + `, + output: ` +interface ArrayClass { + foo: T[]; + bar: T[]; + baz: Arr; +} + `, options: [{ default: 'array' }], errors: [ { messageId: 'errorStringArray', data: { type: 'T' }, - line: 2, - column: 10, + line: 3, + column: 8, }, ], }, { - code: `function fooFunction(foo: Array>) { - return foo.map(e => e.foo); -}`, - output: `function fooFunction(foo: ArrayClass[]) { - return foo.map(e => e.foo); -}`, + code: ` +function fooFunction(foo: Array>) { + return foo.map(e => e.foo); +} + `, + output: ` +function fooFunction(foo: ArrayClass[]) { + return foo.map(e => e.foo); +} + `, options: [{ default: 'array' }], errors: [ { messageId: 'errorStringArray', data: { type: 'T' }, - line: 1, + line: 2, column: 27, }, ], @@ -648,8 +690,8 @@ let yyyy: Arr[][]> = [[[["2"]]]];`, ], }, { - code: 'type fooUnion = Array;', - output: 'type fooUnion = (string|number|boolean)[];', + code: 'type fooUnion = Array;', + output: 'type fooUnion = (string | number | boolean)[];', options: [{ default: 'array' }], errors: [ { @@ -711,8 +753,8 @@ let yyyy: Arr[][]> = [[[["2"]]]];`, ], }, { - code: 'let y: string[] = >["2"];', - output: 'let y: Array = >["2"];', + code: "let y: string[] = >['2'];", + output: "let y: Array = >['2'];", options: [{ default: 'generic' }], errors: [ { @@ -724,67 +766,79 @@ let yyyy: Arr[][]> = [[[["2"]]]];`, ], }, { - code: 'let ya = [[1, "2"]] as[number, string][];', - output: 'let ya = [[1, "2"]] as Array<[number, string]>;', + code: "let ya = [[1, '2']] as [number, string][];", + output: "let ya = [[1, '2']] as Array<[number, string]>;", options: [{ default: 'generic' }], errors: [ { messageId: 'errorStringGeneric', data: { type: 'T' }, line: 1, - column: 23, + column: 24, }, ], }, { - code: `// Ignore user defined aliases -let yyyy: Arr>[]> = [[[["2"]]]];`, - output: `// Ignore user defined aliases -let yyyy: Arr>>> = [[[["2"]]]];`, + code: ` +// Ignore user defined aliases +let yyyy: Arr>[]> = [[[['2']]]]; + `, + output: ` +// Ignore user defined aliases +let yyyy: Arr>>> = [[[['2']]]]; + `, options: [{ default: 'generic' }], errors: [ { messageId: 'errorStringGeneric', data: { type: 'T' }, - line: 2, + line: 3, column: 15, }, ], }, { - code: `interface ArrayClass { - foo: Array; - bar: T[]; - baz: Arr; -}`, - output: `interface ArrayClass { - foo: Array; - bar: Array; - baz: Arr; -}`, + code: ` +interface ArrayClass { + foo: Array; + bar: T[]; + baz: Arr; +} + `, + output: ` +interface ArrayClass { + foo: Array; + bar: Array; + baz: Arr; +} + `, options: [{ default: 'generic' }], errors: [ { messageId: 'errorStringGeneric', data: { type: 'T' }, - line: 3, - column: 10, + line: 4, + column: 8, }, ], }, { - code: `function barFunction(bar: ArrayClass[]) { - return bar.map(e => e.bar); -}`, - output: `function barFunction(bar: Array>) { - return bar.map(e => e.bar); -}`, + code: ` +function barFunction(bar: ArrayClass[]) { + return bar.map(e => e.bar); +} + `, + output: ` +function barFunction(bar: Array>) { + return bar.map(e => e.bar); +} + `, options: [{ default: 'generic' }], errors: [ { messageId: 'errorStringGeneric', data: { type: 'T' }, - line: 1, + line: 2, column: 27, }, ], @@ -803,8 +857,8 @@ let yyyy: Arr>>> = [[[["2"]]]];`, ], }, { - code: 'type barUnion = (string|number|boolean)[];', - output: 'type barUnion = Array;', + code: 'type barUnion = (string | number | boolean)[];', + output: 'type barUnion = Array;', options: [{ default: 'generic' }], errors: [ { @@ -829,26 +883,30 @@ let yyyy: Arr>>> = [[[["2"]]]];`, ], }, { - code: `interface FooInterface { - '.bar': {baz: string[];}; -}`, - output: `interface FooInterface { - '.bar': {baz: Array;}; -}`, + code: ` +interface FooInterface { + '.bar': { baz: string[] }; +} + `, + output: ` +interface FooInterface { + '.bar': { baz: Array }; +} + `, options: [{ default: 'generic' }], errors: [ { messageId: 'errorStringGeneric', data: { type: 'string' }, - line: 2, - column: 19, + line: 3, + column: 18, }, ], }, { // https://github.com/typescript-eslint/typescript-eslint/issues/172 - code: 'type Unwrap = T extends Array ? E : T', - output: 'type Unwrap = T extends (infer E)[] ? E : T', + code: 'type Unwrap = T extends Array ? E : T;', + output: 'type Unwrap = T extends (infer E)[] ? E : T;', options: [{ default: 'array' }], errors: [ { @@ -861,8 +919,8 @@ let yyyy: Arr>>> = [[[["2"]]]];`, }, { // https://github.com/typescript-eslint/typescript-eslint/issues/172 - code: 'type Unwrap = T extends (infer E)[] ? E : T', - output: 'type Unwrap = T extends Array ? E : T', + code: 'type Unwrap = T extends (infer E)[] ? E : T;', + output: 'type Unwrap = T extends Array ? E : T;', options: [{ default: 'generic' }], errors: [ { @@ -928,8 +986,8 @@ let yyyy: Arr>>> = [[[["2"]]]];`, ], }, { - code: 'const x: readonly number[] = []', - output: 'const x: ReadonlyArray = []', + code: 'const x: readonly number[] = [];', + output: 'const x: ReadonlyArray = [];', options: [{ default: 'array', readonly: 'generic' }], errors: [ { @@ -941,8 +999,8 @@ let yyyy: Arr>>> = [[[["2"]]]];`, ], }, { - code: 'const x: readonly number[][] = []', - output: 'const x: readonly Array[] = []', + code: 'const x: readonly number[][] = [];', + output: 'const x: readonly Array[] = [];', options: [{ default: 'generic', readonly: 'array' }], errors: [ { diff --git a/packages/eslint-plugin/tests/rules/await-thenable.test.ts b/packages/eslint-plugin/tests/rules/await-thenable.test.ts index 5ee0f7d0341..52fc1ca2306 100644 --- a/packages/eslint-plugin/tests/rules/await-thenable.test.ts +++ b/packages/eslint-plugin/tests/rules/await-thenable.test.ts @@ -13,51 +13,51 @@ const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', }); -ruleTester.run('await-promise', rule, { +ruleTester.run('await-thenable', rule, { valid: [ ` async function test() { - await Promise.resolve("value"); - await Promise.reject(new Error("message")); + await Promise.resolve('value'); + await Promise.reject(new Error('message')); } -`, + `, ` async function test() { await (async () => true)(); } -`, + `, ` async function test() { function returnsPromise() { - return Promise.resolve("value"); + return Promise.resolve('value'); } await returnsPromise(); } -`, + `, ` async function test() { async function returnsPromiseAsync() {} await returnsPromiseAsync(); } -`, + `, ` async function test() { let anyValue: any; await anyValue; } -`, + `, ` async function test() { let unknownValue: unknown; await unknownValue; } -`, + `, ` async function test() { const numberPromise: Promise; await numberPromise; } -`, + `, ` async function test() { class Foo extends Promise {} @@ -68,7 +68,7 @@ async function test() { const bar: Bar = Bar.resolve(2); await bar; } -`, + `, ` async function test() { await (Math.random() > 0.5 ? numberPromise : 0); @@ -78,15 +78,17 @@ async function test() { const intersectionPromise: Promise & number; await intersectionPromise; } -`, + `, ` async function test() { - class Thenable { then(callback: () => {}) { } }; + class Thenable { + then(callback: () => {}) {} + } const thenable = new Thenable(); await thenable; } -`, + `, ` // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/promise-polyfill/index.d.ts // Type definitions for promise-polyfill 6.0 @@ -96,7 +98,7 @@ async function test() { // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped interface PromisePolyfillConstructor extends PromiseConstructor { - _immediateFn?: (handler: (() => void) | string) => void; + _immediateFn?: (handler: (() => void) | string) => void; } declare const PromisePolyfill: PromisePolyfillConstructor; @@ -106,7 +108,7 @@ async function test() { await promise; } -`, + `, ` // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/bluebird/index.d.ts // Type definitions for bluebird 3.5 @@ -151,14 +153,21 @@ type CatchFilter = ((error: E) => boolean) | (object & E); type IterableItem = R extends Iterable ? U : never; type IterableOrNever = Extract>; type Resolvable = R | PromiseLike; -type IterateFunction = (item: T, index: number, arrayLength: number) => Resolvable; +type IterateFunction = ( + item: T, + index: number, + arrayLength: number, +) => Resolvable; declare class Bluebird implements PromiseLike { - then(onFulfill?: (value: R) => Resolvable, onReject?: (error: any) => Resolvable): Bluebird; // For simpler signature help. + then( + onFulfill?: (value: R) => Resolvable, + onReject?: (error: any) => Resolvable, + ): Bluebird; // For simpler signature help. then( - onfulfilled?: ((value: R) => Resolvable) | null, - onrejected?: ((reason: any) => Resolvable) | null - ): Bluebird; + onfulfilled?: ((value: R) => Resolvable) | null, + onrejected?: ((reason: any) => Resolvable) | null, + ): Bluebird; } declare const bluebird: Bluebird; @@ -166,7 +175,7 @@ declare const bluebird: Bluebird; async function test() { await bluebird; } -`, + `, ], invalid: [ @@ -174,14 +183,14 @@ async function test() { code: ` async function test() { await 0; - await "value"; + await 'value'; - await (Math.random() > 0.5 ? "" : 0); + await (Math.random() > 0.5 ? '' : 0); class NonPromise extends Array {} await new NonPromise(); } -`, + `, errors: [ { line: 3, @@ -204,7 +213,9 @@ async function test() { { code: ` async function test() { - class IncorrectThenable { then() { } }; + class IncorrectThenable { + then() {} + } const thenable = new IncorrectThenable(); await thenable; @@ -212,7 +223,7 @@ async function test() { `, errors: [ { - line: 6, + line: 8, messageId, }, ], diff --git a/packages/eslint-plugin/tests/rules/ban-ts-comment.test.ts b/packages/eslint-plugin/tests/rules/ban-ts-comment.test.ts index c2226f7041a..89ff1db4a61 100644 --- a/packages/eslint-plugin/tests/rules/ban-ts-comment.test.ts +++ b/packages/eslint-plugin/tests/rules/ban-ts-comment.test.ts @@ -5,14 +5,88 @@ const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', }); +ruleTester.run('ts-expect-error', rule, { + valid: [ + '// just a comment containing @ts-expect-error somewhere', + '/* @ts-expect-error */', + '/** @ts-expect-error */', + ` +/* +// @ts-expect-error in a block +*/ + `, + { + code: '// @ts-expect-error', + options: [{ 'ts-expect-error': false }], + }, + ], + invalid: [ + { + code: '// @ts-expect-error', + options: [{ 'ts-expect-error': true }], + errors: [ + { + data: { directive: 'expect-error' }, + messageId: 'tsDirectiveComment', + line: 1, + column: 1, + }, + ], + }, + { + code: '// @ts-expect-error: Suppress next line', + options: [{ 'ts-expect-error': true }], + errors: [ + { + data: { directive: 'expect-error' }, + messageId: 'tsDirectiveComment', + line: 1, + column: 1, + }, + ], + }, + { + code: '/////@ts-expect-error: Suppress next line', + options: [{ 'ts-expect-error': true }], + errors: [ + { + data: { directive: 'expect-error' }, + messageId: 'tsDirectiveComment', + line: 1, + column: 1, + }, + ], + }, + { + code: ` +if (false) { + // @ts-expect-error: Unreachable code error + console.log('hello'); +} + `, + options: [{ 'ts-expect-error': true }], + errors: [ + { + data: { directive: 'expect-error' }, + messageId: 'tsDirectiveComment', + line: 3, + column: 3, + }, + ], + }, + ], +}); + ruleTester.run('ts-ignore', rule, { valid: [ - `// just a comment containing @ts-ignore somewhere`, - `/* @ts-ignore */`, - `/** @ts-ignore */`, - `/* + '// just a comment containing @ts-ignore somewhere', + '/* @ts-ignore */', + '/** @ts-ignore */', + ` +/* // @ts-ignore in a block -*/`, +*/ + `, { code: '// @ts-ignore', options: [{ 'ts-ignore': false }], @@ -68,9 +142,9 @@ ruleTester.run('ts-ignore', rule, { code: ` if (false) { // @ts-ignore: Unreachable code error - console.log("hello"); + console.log('hello'); } - `, + `, errors: [ { data: { directive: 'ignore' }, @@ -85,12 +159,14 @@ if (false) { ruleTester.run('ts-nocheck', rule, { valid: [ - `// just a comment containing @ts-nocheck somewhere`, - `/* @ts-nocheck */`, - `/** @ts-nocheck */`, - `/* + '// just a comment containing @ts-nocheck somewhere', + '/* @ts-nocheck */', + '/** @ts-nocheck */', + ` +/* // @ts-nocheck in a block -*/`, +*/ + `, { code: '// @ts-nocheck', options: [{ 'ts-nocheck': false }], @@ -146,9 +222,9 @@ ruleTester.run('ts-nocheck', rule, { code: ` if (false) { // @ts-nocheck: Unreachable code error - console.log("hello"); + console.log('hello'); } - `, + `, errors: [ { data: { directive: 'nocheck' }, @@ -163,12 +239,14 @@ if (false) { ruleTester.run('ts-check', rule, { valid: [ - `// just a comment containing @ts-check somewhere`, - `/* @ts-check */`, - `/** @ts-check */`, - `/* + '// just a comment containing @ts-check somewhere', + '/* @ts-check */', + '/** @ts-check */', + ` +/* // @ts-check in a block -*/`, +*/ + `, { code: '// @ts-check', options: [{ 'ts-check': false }], @@ -216,9 +294,9 @@ ruleTester.run('ts-check', rule, { code: ` if (false) { // @ts-check: Unreachable code error - console.log("hello"); + console.log('hello'); } - `, + `, options: [{ 'ts-check': true }], errors: [ { diff --git a/packages/eslint-plugin/tests/rules/ban-ts-ignore.test.ts b/packages/eslint-plugin/tests/rules/ban-ts-ignore.test.ts index d9132df2b0d..e1471950f46 100644 --- a/packages/eslint-plugin/tests/rules/ban-ts-ignore.test.ts +++ b/packages/eslint-plugin/tests/rules/ban-ts-ignore.test.ts @@ -7,12 +7,14 @@ const ruleTester = new RuleTester({ ruleTester.run('ban-ts-ignore', rule, { valid: [ - `// just a comment containing @ts-ignore somewhere`, - `/* @ts-ignore */`, - `/** @ts-ignore */`, - `/* + '// just a comment containing @ts-ignore somewhere', + '/* @ts-ignore */', + '/** @ts-ignore */', + ` +/* // @ts-ignore in a block -*/`, +*/ + `, ], invalid: [ { @@ -49,9 +51,9 @@ ruleTester.run('ban-ts-ignore', rule, { code: ` if (false) { // @ts-ignore: Unreachable code error - console.log("hello"); + console.log('hello'); } - `, + `, errors: [ { messageId: 'tsIgnoreComment', diff --git a/packages/eslint-plugin/tests/rules/ban-types.test.ts b/packages/eslint-plugin/tests/rules/ban-types.test.ts index 0edeb74f8d1..772fa7716f8 100644 --- a/packages/eslint-plugin/tests/rules/ban-types.test.ts +++ b/packages/eslint-plugin/tests/rules/ban-types.test.ts @@ -1,5 +1,5 @@ import rule from '../../src/rules/ban-types'; -import { RuleTester } from '../RuleTester'; +import { RuleTester, noFormat } from '../RuleTester'; import { InferOptionsTypeFromRule } from '../../src/util'; const ruleTester = new RuleTester({ @@ -47,7 +47,7 @@ ruleTester.run('ban-types', rule, { valid: [ 'let f = Object();', // Should not fail if there is no options set 'let f: {} = {};', - 'let f: { x: number, y: number } = { x: 1, y: 1 };', + 'let f: { x: number; y: number } = { x: 1, y: 1 };', { code: 'let f = Object();', options, @@ -65,11 +65,11 @@ ruleTester.run('ban-types', rule, { options, }, { - code: 'let a: _.NS.Bad', + code: 'let a: _.NS.Bad;', options, }, { - code: 'let a: NS.Bad._', + code: 'let a: NS.Bad._;', options, }, // Replace default options instead of merging with extendDefaults: false @@ -88,11 +88,11 @@ ruleTester.run('ban-types', rule, { ], }, { - code: 'let a: undefined', + code: 'let a: undefined;', options: options2, }, { - code: 'let a: null', + code: 'let a: null;', options: options3, }, ], @@ -166,8 +166,8 @@ ruleTester.run('ban-types', rule, { ], }, { - code: 'let b: {c: String};', - output: 'let b: {c: string};', + code: 'let b: { c: String };', + output: 'let b: { c: string };', errors: [ { messageId: 'bannedTypeMessage', @@ -177,7 +177,7 @@ ruleTester.run('ban-types', rule, { customMessage: ' Use string instead.', }, line: 1, - column: 12, + column: 13, }, ], options, @@ -231,22 +231,22 @@ ruleTester.run('ban-types', rule, { { code: ` class Foo extends Bar implements Baz { - constructor (foo: String | Object) {} + constructor(foo: String | Object) {} - exit() : Array { - const foo: String = 1 as String + exit(): Array { + const foo: String = 1 as String; } } - `, + `, output: ` class Foo extends Bar implements Baz { - constructor (foo: string | Object) {} + constructor(foo: string | Object) {} - exit() : Array { - const foo: string = 1 as string + exit(): Array { + const foo: string = 1 as string; } } - `, + `, errors: [ { messageId: 'bannedTypeMessage', @@ -285,7 +285,7 @@ class Foo extends Bar implements Baz { customMessage: ' Use string instead.', }, line: 3, - column: 21, + column: 20, }, { messageId: 'bannedTypeMessage', @@ -294,13 +294,13 @@ class Foo extends Bar implements Baz { customMessage: " Use '{}' instead.", }, line: 3, - column: 30, + column: 29, }, { messageId: 'bannedTypeMessage', data: { name: 'Array', customMessage: '' }, line: 5, - column: 12, + column: 11, }, { messageId: 'bannedTypeMessage', @@ -310,7 +310,7 @@ class Foo extends Bar implements Baz { customMessage: ' Use string instead.', }, line: 5, - column: 18, + column: 17, }, { messageId: 'bannedTypeMessage', @@ -383,8 +383,8 @@ let b: Foo; options, }, { - code: `let foo: {} = {};`, - output: `let foo: object = {};`, + code: 'let foo: {} = {};', + output: 'let foo: object = {};', options: [ { types: { @@ -408,7 +408,7 @@ let b: Foo; ], }, { - code: ` + code: noFormat` let foo: {} = {}; let bar: { } = {}; `, @@ -473,8 +473,8 @@ let bar: object = {}; ], }, { - code: 'let a: Foo< F >;', - output: 'let a: Foo< T >;', + code: noFormat`let a: Foo< F >;`, + output: noFormat`let a: Foo< T >;`, errors: [ { messageId: 'bannedTypeMessage', diff --git a/packages/eslint-plugin/tests/rules/brace-style.test.ts b/packages/eslint-plugin/tests/rules/brace-style.test.ts index 21d10998eac..28b9413795d 100644 --- a/packages/eslint-plugin/tests/rules/brace-style.test.ts +++ b/packages/eslint-plugin/tests/rules/brace-style.test.ts @@ -1,3 +1,8 @@ +/* eslint-disable eslint-comments/no-use */ +// this rule tests the position of braces, which prettier will want to fix and break the tests +/* eslint "@typescript-eslint/internal/plugin-test-formatting": ["error", { formatWithPrettier: false }] */ +/* eslint-enable eslint-comments/no-use */ + import rule from '../../src/rules/brace-style'; import { RuleTester } from '../RuleTester'; @@ -211,67 +216,67 @@ catch (e) options: ['allman'], }, { - code: `function foo () { return; }`, + code: 'function foo () { return; }', options: ['1tbs', { allowSingleLine: true }], }, { - code: `function foo () { a(); b(); return; }`, + code: 'function foo () { a(); b(); return; }', options: ['1tbs', { allowSingleLine: true }], }, { - code: `function a(b,c,d) { }`, + code: 'function a(b,c,d) { }', options: ['1tbs', { allowSingleLine: true }], }, { - code: `!function foo () { return; }`, + code: '!function foo () { return; }', options: ['1tbs', { allowSingleLine: true }], }, { - code: `!function a(b,c,d) { }`, + code: '!function a(b,c,d) { }', options: ['1tbs', { allowSingleLine: true }], }, { - code: `if (foo) { bar(); }`, + code: 'if (foo) { bar(); }', options: ['1tbs', { allowSingleLine: true }], }, { - code: `if (a) { b(); } else { c(); }`, + code: 'if (a) { b(); } else { c(); }', options: ['1tbs', { allowSingleLine: true }], }, { - code: `while (foo) { bar(); }`, + code: 'while (foo) { bar(); }', options: ['1tbs', { allowSingleLine: true }], }, { - code: `for (;;) { bar(); }`, + code: 'for (;;) { bar(); }', options: ['1tbs', { allowSingleLine: true }], }, { - code: `with (foo) { bar(); }`, + code: 'with (foo) { bar(); }', options: ['1tbs', { allowSingleLine: true }], }, { - code: `switch (foo) { case 'bar': break; }`, + code: "switch (foo) { case 'bar': break; }", options: ['1tbs', { allowSingleLine: true }], }, { - code: `try { bar(); } catch (e) { baz(); }`, + code: 'try { bar(); } catch (e) { baz(); }', options: ['1tbs', { allowSingleLine: true }], }, { - code: `do { bar(); } while (true)`, + code: 'do { bar(); } while (true)', options: ['1tbs', { allowSingleLine: true }], }, { - code: `for (foo in bar) { baz(); }`, + code: 'for (foo in bar) { baz(); }', options: ['1tbs', { allowSingleLine: true }], }, { - code: `if (a && b && c) { }`, + code: 'if (a && b && c) { }', options: ['1tbs', { allowSingleLine: true }], }, { - code: `switch(0) {}`, + code: 'switch(0) {}', options: ['1tbs', { allowSingleLine: true }], }, { @@ -289,7 +294,7 @@ catch (e) { baz(); } options: ['stroustrup', { allowSingleLine: true }], }, { - code: `var foo = () => { return; }`, + code: 'var foo = () => { return; }', options: ['stroustrup', { allowSingleLine: true }], parserOptions: { ecmaVersion: 6 }, }, @@ -308,7 +313,7 @@ catch (e) { baz(); } options: ['allman', { allowSingleLine: true }], }, { - code: `var foo = () => { return; }`, + code: 'var foo = () => { return; }', options: ['allman', { allowSingleLine: true }], parserOptions: { ecmaVersion: 6 }, }, @@ -345,7 +350,7 @@ switch(x) options: ['allman'], }, { - code: `switch(x) {}`, + code: 'switch(x) {}', options: ['allman', { allowSingleLine: true }], }, { @@ -388,25 +393,25 @@ Foo options: ['allman'], }, { - code: `class Foo {}`, + code: 'class Foo {}', options: ['1tbs', { allowSingleLine: true }], }, { - code: `class Foo {}`, + code: 'class Foo {}', options: ['allman', { allowSingleLine: true }], }, { - code: `(class {})`, + code: '(class {})', options: ['1tbs', { allowSingleLine: true }], }, { - code: `(class {})`, + code: '(class {})', options: ['allman', { allowSingleLine: true }], }, // https://github.com/eslint/eslint/issues/7908 { - code: `{}`, + code: '{}', }, { code: ` @@ -568,7 +573,7 @@ enum Foo { options: ['stroustrup'], }, { - code: `enum Foo { A, B }`, + code: 'enum Foo { A, B }', options: ['1tbs', { allowSingleLine: true }], }, ], @@ -591,8 +596,8 @@ if (f) { errors: [{ messageId: 'nextLineClose' }], }, { - code: `var foo = () => { return; }`, - output: `var foo = () => {\n return; \n}`, + code: 'var foo = () => { return; }', + output: 'var foo = () => {\n return; \n}', parserOptions: { ecmaVersion: 6 }, errors: [ { messageId: 'blockSameLine' }, @@ -600,31 +605,31 @@ if (f) { ], }, { - code: `function foo() { return; }`, - output: `function foo() {\n return; \n}`, + code: 'function foo() { return; }', + output: 'function foo() {\n return; \n}', errors: [ { messageId: 'blockSameLine' }, { messageId: 'singleLineClose' }, ], }, { - code: `function foo() \n { \n return; }`, - output: `function foo() { \n return; \n}`, + code: 'function foo() \n { \n return; }', + output: 'function foo() { \n return; \n}', errors: [{ messageId: 'nextLineOpen' }, { messageId: 'singleLineClose' }], }, { - code: `!function foo() \n { \n return; }`, - output: `!function foo() { \n return; \n}`, + code: '!function foo() \n { \n return; }', + output: '!function foo() { \n return; \n}', errors: [{ messageId: 'nextLineOpen' }, { messageId: 'singleLineClose' }], }, { - code: `if (foo) \n { \n bar(); }`, - output: `if (foo) { \n bar(); \n}`, + code: 'if (foo) \n { \n bar(); }', + output: 'if (foo) { \n bar(); \n}', errors: [{ messageId: 'nextLineOpen' }, { messageId: 'singleLineClose' }], }, { - code: `if (a) { \nb();\n } else \n { c(); }`, - output: `if (a) { \nb();\n } else {\n c(); \n}`, + code: 'if (a) { \nb();\n } else \n { c(); }', + output: 'if (a) { \nb();\n } else {\n c(); \n}', errors: [ { messageId: 'nextLineOpen' }, { messageId: 'blockSameLine' }, @@ -632,104 +637,108 @@ if (f) { ], }, { - code: `while (foo) \n { \n bar(); }`, - output: `while (foo) { \n bar(); \n}`, + code: 'while (foo) \n { \n bar(); }', + output: 'while (foo) { \n bar(); \n}', errors: [{ messageId: 'nextLineOpen' }, { messageId: 'singleLineClose' }], }, { - code: `for (;;) \n { \n bar(); }`, - output: `for (;;) { \n bar(); \n}`, + code: 'for (;;) \n { \n bar(); }', + output: 'for (;;) { \n bar(); \n}', errors: [{ messageId: 'nextLineOpen' }, { messageId: 'singleLineClose' }], }, { - code: `with (foo) \n { \n bar(); }`, - output: `with (foo) { \n bar(); \n}`, + code: 'with (foo) \n { \n bar(); }', + output: 'with (foo) { \n bar(); \n}', errors: [{ messageId: 'nextLineOpen' }, { messageId: 'singleLineClose' }], }, { - code: `switch (foo) \n { \n case 'bar': break; }`, - output: `switch (foo) { \n case 'bar': break; \n}`, + code: "switch (foo) \n { \n case 'bar': break; }", + output: "switch (foo) { \n case 'bar': break; \n}", errors: [{ messageId: 'nextLineOpen' }, { messageId: 'singleLineClose' }], }, { - code: `switch (foo) \n { }`, - output: `switch (foo) { }`, + code: 'switch (foo) \n { }', + output: 'switch (foo) { }', errors: [{ messageId: 'nextLineOpen' }], }, { - code: `try \n { \n bar(); \n } catch (e) {}`, - output: `try { \n bar(); \n } catch (e) {}`, + code: 'try \n { \n bar(); \n } catch (e) {}', + output: 'try { \n bar(); \n } catch (e) {}', errors: [{ messageId: 'nextLineOpen' }], }, { - code: `try { \n bar(); \n } catch (e) \n {}`, - output: `try { \n bar(); \n } catch (e) {}`, + code: 'try { \n bar(); \n } catch (e) \n {}', + output: 'try { \n bar(); \n } catch (e) {}', errors: [{ messageId: 'nextLineOpen' }], }, { - code: `do \n { \n bar(); \n} while (true)`, - output: `do { \n bar(); \n} while (true)`, + code: 'do \n { \n bar(); \n} while (true)', + output: 'do { \n bar(); \n} while (true)', errors: [{ messageId: 'nextLineOpen' }], }, { - code: `for (foo in bar) \n { \n baz(); \n }`, - output: `for (foo in bar) { \n baz(); \n }`, + code: 'for (foo in bar) \n { \n baz(); \n }', + output: 'for (foo in bar) { \n baz(); \n }', errors: [{ messageId: 'nextLineOpen' }], }, { - code: `for (foo of bar) \n { \n baz(); \n }`, - output: `for (foo of bar) { \n baz(); \n }`, + code: 'for (foo of bar) \n { \n baz(); \n }', + output: 'for (foo of bar) { \n baz(); \n }', parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: 'nextLineOpen' }], }, { - code: `try { \n bar(); \n }\ncatch (e) {\n}`, - output: `try { \n bar(); \n } catch (e) {\n}`, + code: 'try { \n bar(); \n }\ncatch (e) {\n}', + output: 'try { \n bar(); \n } catch (e) {\n}', errors: [{ messageId: 'nextLineClose' }], }, { - code: `try { \n bar(); \n } catch (e) {\n}\n finally {\n}`, - output: `try { \n bar(); \n } catch (e) {\n} finally {\n}`, + code: 'try { \n bar(); \n } catch (e) {\n}\n finally {\n}', + output: 'try { \n bar(); \n } catch (e) {\n} finally {\n}', errors: [{ messageId: 'nextLineClose' }], }, { - code: `if (a) { \nb();\n } \n else { \nc();\n }`, - output: `if (a) { \nb();\n } else { \nc();\n }`, + code: 'if (a) { \nb();\n } \n else { \nc();\n }', + output: 'if (a) { \nb();\n } else { \nc();\n }', errors: [{ messageId: 'nextLineClose' }], }, { - code: `try { \n bar(); \n }\ncatch (e) {\n} finally {\n}`, - output: `try { \n bar(); \n }\ncatch (e) {\n}\n finally {\n}`, + code: 'try { \n bar(); \n }\ncatch (e) {\n} finally {\n}', + output: 'try { \n bar(); \n }\ncatch (e) {\n}\n finally {\n}', options: ['stroustrup'], errors: [{ messageId: 'sameLineClose' }], }, { - code: `try { \n bar(); \n } catch (e) {\n}\n finally {\n}`, - output: `try { \n bar(); \n }\n catch (e) {\n}\n finally {\n}`, + code: 'try { \n bar(); \n } catch (e) {\n}\n finally {\n}', + output: 'try { \n bar(); \n }\n catch (e) {\n}\n finally {\n}', options: ['stroustrup'], errors: [{ messageId: 'sameLineClose' }], }, { - code: `if (a) { \nb();\n } else { \nc();\n }`, - output: `if (a) { \nb();\n }\n else { \nc();\n }`, + code: 'if (a) { \nb();\n } else { \nc();\n }', + output: 'if (a) { \nb();\n }\n else { \nc();\n }', options: ['stroustrup'], errors: [{ messageId: 'sameLineClose' }], }, { - code: `if (foo) {\nbaz();\n} else if (bar) {\nbaz();\n}\nelse {\nqux();\n}`, - output: `if (foo) {\nbaz();\n}\n else if (bar) {\nbaz();\n}\nelse {\nqux();\n}`, + code: + 'if (foo) {\nbaz();\n} else if (bar) {\nbaz();\n}\nelse {\nqux();\n}', + output: + 'if (foo) {\nbaz();\n}\n else if (bar) {\nbaz();\n}\nelse {\nqux();\n}', options: ['stroustrup'], errors: [{ messageId: 'sameLineClose' }], }, { - code: `if (foo) {\npoop();\n} \nelse if (bar) {\nbaz();\n} else if (thing) {\nboom();\n}\nelse {\nqux();\n}`, - output: `if (foo) {\npoop();\n} \nelse if (bar) {\nbaz();\n}\n else if (thing) {\nboom();\n}\nelse {\nqux();\n}`, + code: + 'if (foo) {\npoop();\n} \nelse if (bar) {\nbaz();\n} else if (thing) {\nboom();\n}\nelse {\nqux();\n}', + output: + 'if (foo) {\npoop();\n} \nelse if (bar) {\nbaz();\n}\n else if (thing) {\nboom();\n}\nelse {\nqux();\n}', options: ['stroustrup'], errors: [{ messageId: 'sameLineClose' }], }, { - code: `try { \n bar(); \n }\n catch (e) {\n}\n finally {\n}`, - output: `try \n{ \n bar(); \n }\n catch (e) \n{\n}\n finally \n{\n}`, + code: 'try { \n bar(); \n }\n catch (e) {\n}\n finally {\n}', + output: 'try \n{ \n bar(); \n }\n catch (e) \n{\n}\n finally \n{\n}', options: ['allman'], errors: [ { messageId: 'sameLineOpen', line: 1 }, @@ -738,8 +747,8 @@ if (f) { ], }, { - code: `switch(x) { case 1: \nbar(); }\n `, - output: `switch(x) \n{\n case 1: \nbar(); \n}\n `, + code: 'switch(x) { case 1: \nbar(); }\n ', + output: 'switch(x) \n{\n case 1: \nbar(); \n}\n ', options: ['allman'], errors: [ { messageId: 'sameLineOpen', line: 1 }, @@ -748,8 +757,8 @@ if (f) { ], }, { - code: `if (a) { \nb();\n } else { \nc();\n }`, - output: `if (a) \n{ \nb();\n }\n else \n{ \nc();\n }`, + code: 'if (a) { \nb();\n } else { \nc();\n }', + output: 'if (a) \n{ \nb();\n }\n else \n{ \nc();\n }', options: ['allman'], errors: [ { messageId: 'sameLineOpen' }, @@ -758,8 +767,10 @@ if (f) { ], }, { - code: `if (foo) {\nbaz();\n} else if (bar) {\nbaz();\n}\nelse {\nqux();\n}`, - output: `if (foo) \n{\nbaz();\n}\n else if (bar) \n{\nbaz();\n}\nelse \n{\nqux();\n}`, + code: + 'if (foo) {\nbaz();\n} else if (bar) {\nbaz();\n}\nelse {\nqux();\n}', + output: + 'if (foo) \n{\nbaz();\n}\n else if (bar) \n{\nbaz();\n}\nelse \n{\nqux();\n}', options: ['allman'], errors: [ { messageId: 'sameLineOpen' }, @@ -769,8 +780,10 @@ if (f) { ], }, { - code: `if (foo)\n{ poop();\n} \nelse if (bar) {\nbaz();\n} else if (thing) {\nboom();\n}\nelse {\nqux();\n}`, - output: `if (foo)\n{\n poop();\n} \nelse if (bar) \n{\nbaz();\n}\n else if (thing) \n{\nboom();\n}\nelse \n{\nqux();\n}`, + code: + 'if (foo)\n{ poop();\n} \nelse if (bar) {\nbaz();\n} else if (thing) {\nboom();\n}\nelse {\nqux();\n}', + output: + 'if (foo)\n{\n poop();\n} \nelse if (bar) \n{\nbaz();\n}\n else if (thing) \n{\nboom();\n}\nelse \n{\nqux();\n}', options: ['allman'], errors: [ { messageId: 'blockSameLine' }, @@ -781,159 +794,162 @@ if (f) { ], }, { - code: `if (foo)\n{\n bar(); }`, - output: `if (foo)\n{\n bar(); \n}`, + code: 'if (foo)\n{\n bar(); }', + output: 'if (foo)\n{\n bar(); \n}', options: ['allman'], errors: [{ messageId: 'singleLineClose' }], }, { - code: `try\n{\n somethingRisky();\n} catch (e)\n{\n handleError()\n}`, - output: `try\n{\n somethingRisky();\n}\n catch (e)\n{\n handleError()\n}`, + code: 'try\n{\n somethingRisky();\n} catch (e)\n{\n handleError()\n}', + output: + 'try\n{\n somethingRisky();\n}\n catch (e)\n{\n handleError()\n}', options: ['allman'], errors: [{ messageId: 'sameLineClose' }], }, // allowSingleLine: true { - code: `function foo() { return; \n}`, - output: `function foo() {\n return; \n}`, + code: 'function foo() { return; \n}', + output: 'function foo() {\n return; \n}', options: ['1tbs', { allowSingleLine: true }], errors: [{ messageId: 'blockSameLine' }], }, { - code: `function foo() { a(); b(); return; \n}`, - output: `function foo() {\n a(); b(); return; \n}`, + code: 'function foo() { a(); b(); return; \n}', + output: 'function foo() {\n a(); b(); return; \n}', options: ['1tbs', { allowSingleLine: true }], errors: [{ messageId: 'blockSameLine' }], }, { - code: `function foo() { \n return; }`, - output: `function foo() { \n return; \n}`, + code: 'function foo() { \n return; }', + output: 'function foo() { \n return; \n}', options: ['1tbs', { allowSingleLine: true }], errors: [{ messageId: 'singleLineClose' }], }, { - code: `function foo() {\na();\nb();\nreturn; }`, - output: `function foo() {\na();\nb();\nreturn; \n}`, + code: 'function foo() {\na();\nb();\nreturn; }', + output: 'function foo() {\na();\nb();\nreturn; \n}', options: ['1tbs', { allowSingleLine: true }], errors: [{ messageId: 'singleLineClose' }], }, { - code: `!function foo() { \n return; }`, - output: `!function foo() { \n return; \n}`, + code: '!function foo() { \n return; }', + output: '!function foo() { \n return; \n}', options: ['1tbs', { allowSingleLine: true }], errors: [{ messageId: 'singleLineClose' }], }, { - code: `if (a) { b();\n } else { c(); }`, - output: `if (a) {\n b();\n } else { c(); }`, + code: 'if (a) { b();\n } else { c(); }', + output: 'if (a) {\n b();\n } else { c(); }', options: ['1tbs', { allowSingleLine: true }], errors: [{ messageId: 'blockSameLine' }], }, { - code: `if (a) { b(); }\nelse { c(); }`, - output: `if (a) { b(); } else { c(); }`, + code: 'if (a) { b(); }\nelse { c(); }', + output: 'if (a) { b(); } else { c(); }', options: ['1tbs', { allowSingleLine: true }], errors: [{ messageId: 'nextLineClose' }], }, { - code: `while (foo) { \n bar(); }`, - output: `while (foo) { \n bar(); \n}`, + code: 'while (foo) { \n bar(); }', + output: 'while (foo) { \n bar(); \n}', options: ['1tbs', { allowSingleLine: true }], errors: [{ messageId: 'singleLineClose' }], }, { - code: `for (;;) { bar(); \n }`, - output: `for (;;) {\n bar(); \n }`, + code: 'for (;;) { bar(); \n }', + output: 'for (;;) {\n bar(); \n }', options: ['1tbs', { allowSingleLine: true }], errors: [{ messageId: 'blockSameLine' }], }, { - code: `with (foo) { bar(); \n }`, - output: `with (foo) {\n bar(); \n }`, + code: 'with (foo) { bar(); \n }', + output: 'with (foo) {\n bar(); \n }', options: ['1tbs', { allowSingleLine: true }], errors: [{ messageId: 'blockSameLine' }], }, { - code: `switch (foo) \n { \n case \`bar\`: break; }`, - output: `switch (foo) { \n case \`bar\`: break; \n}`, + code: 'switch (foo) \n { \n case `bar`: break; }', + output: 'switch (foo) { \n case `bar`: break; \n}', options: ['1tbs', { allowSingleLine: true }], errors: [{ messageId: 'nextLineOpen' }, { messageId: 'singleLineClose' }], }, { - code: `switch (foo) \n { }`, - output: `switch (foo) { }`, + code: 'switch (foo) \n { }', + output: 'switch (foo) { }', options: ['1tbs', { allowSingleLine: true }], errors: [{ messageId: 'nextLineOpen' }], }, { - code: `try { bar(); }\ncatch (e) { baz(); }`, - output: `try { bar(); } catch (e) { baz(); }`, + code: 'try { bar(); }\ncatch (e) { baz(); }', + output: 'try { bar(); } catch (e) { baz(); }', options: ['1tbs', { allowSingleLine: true }], errors: [{ messageId: 'nextLineClose' }], }, { - code: `try \n { \n bar(); \n } catch (e) {}`, - output: `try { \n bar(); \n } catch (e) {}`, + code: 'try \n { \n bar(); \n } catch (e) {}', + output: 'try { \n bar(); \n } catch (e) {}', options: ['1tbs', { allowSingleLine: true }], errors: [{ messageId: 'nextLineOpen' }], }, { - code: `try { \n bar(); \n } catch (e) \n {}`, - output: `try { \n bar(); \n } catch (e) {}`, + code: 'try { \n bar(); \n } catch (e) \n {}', + output: 'try { \n bar(); \n } catch (e) {}', options: ['1tbs', { allowSingleLine: true }], errors: [{ messageId: 'nextLineOpen' }], }, { - code: `do \n { \n bar(); \n} while (true)`, - output: `do { \n bar(); \n} while (true)`, + code: 'do \n { \n bar(); \n} while (true)', + output: 'do { \n bar(); \n} while (true)', options: ['1tbs', { allowSingleLine: true }], errors: [{ messageId: 'nextLineOpen' }], }, { - code: `for (foo in bar) \n { \n baz(); \n }`, - output: `for (foo in bar) { \n baz(); \n }`, + code: 'for (foo in bar) \n { \n baz(); \n }', + output: 'for (foo in bar) { \n baz(); \n }', options: ['1tbs', { allowSingleLine: true }], errors: [{ messageId: 'nextLineOpen' }], }, { - code: `try { \n bar(); \n }\ncatch (e) {\n}`, - output: `try { \n bar(); \n } catch (e) {\n}`, + code: 'try { \n bar(); \n }\ncatch (e) {\n}', + output: 'try { \n bar(); \n } catch (e) {\n}', options: ['1tbs', { allowSingleLine: true }], errors: [{ messageId: 'nextLineClose' }], }, { - code: `try { \n bar(); \n } catch (e) {\n}\n finally {\n}`, - output: `try { \n bar(); \n } catch (e) {\n} finally {\n}`, + code: 'try { \n bar(); \n } catch (e) {\n}\n finally {\n}', + output: 'try { \n bar(); \n } catch (e) {\n} finally {\n}', options: ['1tbs', { allowSingleLine: true }], errors: [{ messageId: 'nextLineClose' }], }, { - code: `if (a) { \nb();\n } \n else { \nc();\n }`, - output: `if (a) { \nb();\n } else { \nc();\n }`, + code: 'if (a) { \nb();\n } \n else { \nc();\n }', + output: 'if (a) { \nb();\n } else { \nc();\n }', options: ['1tbs', { allowSingleLine: true }], errors: [{ messageId: 'nextLineClose' }], }, { - code: `try { \n bar(); \n }\ncatch (e) {\n} finally {\n}`, - output: `try { \n bar(); \n }\ncatch (e) {\n}\n finally {\n}`, + code: 'try { \n bar(); \n }\ncatch (e) {\n} finally {\n}', + output: 'try { \n bar(); \n }\ncatch (e) {\n}\n finally {\n}', options: ['stroustrup', { allowSingleLine: true }], errors: [{ messageId: 'sameLineClose' }], }, { - code: `try { \n bar(); \n } catch (e) {\n}\n finally {\n}`, - output: `try { \n bar(); \n }\n catch (e) {\n}\n finally {\n}`, + code: 'try { \n bar(); \n } catch (e) {\n}\n finally {\n}', + output: 'try { \n bar(); \n }\n catch (e) {\n}\n finally {\n}', options: ['stroustrup', { allowSingleLine: true }], errors: [{ messageId: 'sameLineClose' }], }, { - code: `if (a) { \nb();\n } else { \nc();\n }`, - output: `if (a) { \nb();\n }\n else { \nc();\n }`, + code: 'if (a) { \nb();\n } else { \nc();\n }', + output: 'if (a) { \nb();\n }\n else { \nc();\n }', options: ['stroustrup', { allowSingleLine: true }], errors: [{ messageId: 'sameLineClose' }], }, { - code: `if (foo)\n{ poop();\n} \nelse if (bar) {\nbaz();\n} else if (thing) {\nboom();\n}\nelse {\nqux();\n}`, - output: `if (foo)\n{\n poop();\n} \nelse if (bar) \n{\nbaz();\n}\n else if (thing) \n{\nboom();\n}\nelse \n{\nqux();\n}`, + code: + 'if (foo)\n{ poop();\n} \nelse if (bar) {\nbaz();\n} else if (thing) {\nboom();\n}\nelse {\nqux();\n}', + output: + 'if (foo)\n{\n poop();\n} \nelse if (bar) \n{\nbaz();\n}\n else if (thing) \n{\nboom();\n}\nelse \n{\nqux();\n}', options: ['allman', { allowSingleLine: true }], errors: [ { messageId: 'blockSameLine' }, @@ -945,25 +961,25 @@ if (f) { }, // Comment interferes with fix { - code: `if (foo) // comment \n{\nbar();\n}`, + code: 'if (foo) // comment \n{\nbar();\n}', output: null, errors: [{ messageId: 'nextLineOpen' }], }, // https://github.com/eslint/eslint/issues/7493 { - code: `if (foo) {\n bar\n.baz }`, - output: `if (foo) {\n bar\n.baz \n}`, + code: 'if (foo) {\n bar\n.baz }', + output: 'if (foo) {\n bar\n.baz \n}', errors: [{ messageId: 'singleLineClose' }], }, { - code: `if (foo)\n{\n bar\n.baz }`, - output: `if (foo)\n{\n bar\n.baz \n}`, + code: 'if (foo)\n{\n bar\n.baz }', + output: 'if (foo)\n{\n bar\n.baz \n}', options: ['allman'], errors: [{ messageId: 'singleLineClose' }], }, { - code: `if (foo) { bar\n.baz }`, - output: `if (foo) {\n bar\n.baz \n}`, + code: 'if (foo) { bar\n.baz }', + output: 'if (foo) {\n bar\n.baz \n}', options: ['1tbs', { allowSingleLine: true }], errors: [ { messageId: 'blockSameLine' }, @@ -971,8 +987,8 @@ if (f) { ], }, { - code: `if (foo) { bar\n.baz }`, - output: `if (foo) \n{\n bar\n.baz \n}`, + code: 'if (foo) { bar\n.baz }', + output: 'if (foo) \n{\n bar\n.baz \n}', options: ['allman', { allowSingleLine: true }], errors: [ { messageId: 'sameLineOpen' }, @@ -981,46 +997,46 @@ if (f) { ], }, { - code: `switch (x) {\n case 1: foo() }`, - output: `switch (x) {\n case 1: foo() \n}`, + code: 'switch (x) {\n case 1: foo() }', + output: 'switch (x) {\n case 1: foo() \n}', options: ['1tbs', { allowSingleLine: true }], errors: [{ messageId: 'singleLineClose' }], }, { - code: `class Foo\n{\n}`, - output: `class Foo {\n}`, + code: 'class Foo\n{\n}', + output: 'class Foo {\n}', errors: [{ messageId: 'nextLineOpen' }], }, { - code: `(class\n{\n})`, - output: `(class {\n})`, + code: '(class\n{\n})', + output: '(class {\n})', errors: [{ messageId: 'nextLineOpen' }], }, { - code: `class Foo{\n}`, - output: `class Foo\n{\n}`, + code: 'class Foo{\n}', + output: 'class Foo\n{\n}', options: ['allman'], errors: [{ messageId: 'sameLineOpen' }], }, { - code: `(class {\n})`, - output: `(class \n{\n})`, + code: '(class {\n})', + output: '(class \n{\n})', options: ['allman'], errors: [{ messageId: 'sameLineOpen' }], }, { - code: `class Foo {\nbar() {\n}}`, - output: `class Foo {\nbar() {\n}\n}`, + code: 'class Foo {\nbar() {\n}}', + output: 'class Foo {\nbar() {\n}\n}', errors: [{ messageId: 'singleLineClose' }], }, { - code: `(class Foo {\nbar() {\n}})`, - output: `(class Foo {\nbar() {\n}\n})`, + code: '(class Foo {\nbar() {\n}})', + output: '(class Foo {\nbar() {\n}\n})', errors: [{ messageId: 'singleLineClose' }], }, { - code: `class\nFoo{}`, - output: `class\nFoo\n{}`, + code: 'class\nFoo{}', + output: 'class\nFoo\n{}', options: ['allman'], errors: [{ messageId: 'sameLineOpen' }], }, @@ -1070,8 +1086,8 @@ interface Foo { errors: [{ messageId: 'nextLineOpen' }], }, { - code: `interface Foo { \n }`, - output: `interface Foo \n{ \n }`, + code: 'interface Foo { \n }', + output: 'interface Foo \n{ \n }', options: ['allman'], errors: [{ messageId: 'sameLineOpen' }], }, @@ -1101,8 +1117,8 @@ module "Foo" { errors: [{ messageId: 'nextLineOpen' }], }, { - code: `module "Foo" { \n }`, - output: `module "Foo" \n{ \n }`, + code: 'module "Foo" { \n }', + output: 'module "Foo" \n{ \n }', options: ['allman'], errors: [{ messageId: 'sameLineOpen' }], }, @@ -1132,8 +1148,8 @@ namespace Foo { errors: [{ messageId: 'nextLineOpen' }], }, { - code: `namespace Foo { \n }`, - output: `namespace Foo \n{ \n }`, + code: 'namespace Foo { \n }', + output: 'namespace Foo \n{ \n }', options: ['allman'], errors: [{ messageId: 'sameLineOpen' }], }, @@ -1163,8 +1179,8 @@ enum Foo { errors: [{ messageId: 'nextLineOpen' }], }, { - code: `enum Foo { A }`, - output: `enum Foo \n{\n A \n}`, + code: 'enum Foo { A }', + output: 'enum Foo \n{\n A \n}', options: ['allman'], errors: [ { messageId: 'sameLineOpen' }, diff --git a/packages/eslint-plugin/tests/rules/camelcase.test.ts b/packages/eslint-plugin/tests/rules/camelcase.test.ts index 617cdf3e40f..1a35ee87cc8 100644 --- a/packages/eslint-plugin/tests/rules/camelcase.test.ts +++ b/packages/eslint-plugin/tests/rules/camelcase.test.ts @@ -1,5 +1,5 @@ import rule from '../../src/rules/camelcase'; -import { RuleTester } from '../RuleTester'; +import { RuleTester, noFormat } from '../RuleTester'; const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', @@ -8,75 +8,147 @@ const ruleTester = new RuleTester({ ruleTester.run('camelcase', rule, { valid: [ { - code: 'interface Foo { b_ar: number }', + code: ` +interface Foo { + b_ar: number; +} + `, options: [{ properties: 'never' }], }, { - code: 'interface Foo { bar: number }', + code: ` +interface Foo { + bar: number; +} + `, options: [{ properties: 'always' }], }, { - code: 'class Foo { b_ar: number; }', + code: ` +class Foo { + b_ar: number; +} + `, options: [{ properties: 'never' }], }, { - code: 'class Foo { bar: number; }', + code: ` +class Foo { + bar: number; +} + `, options: [{ properties: 'always' }], }, { - code: 'class Foo { b_ar: number = 0; }', + code: ` +class Foo { + b_ar: number = 0; +} + `, options: [{ properties: 'never' }], }, { - code: 'class Foo { bar: number = 0; }', + code: ` +class Foo { + bar: number = 0; +} + `, options: [{ properties: 'always' }], }, { - code: 'class Foo { constructor(private b_ar: number) {} }', + code: ` +class Foo { + constructor(private b_ar: number) {} +} + `, options: [{ properties: 'never' }], }, { - code: 'class Foo { constructor(private bar: number) {} }', + code: ` +class Foo { + constructor(private bar: number) {} +} + `, options: [{ properties: 'always' }], }, { - code: 'class Foo { constructor(private b_ar: number = 0) {} }', + code: ` +class Foo { + constructor(private b_ar: number = 0) {} +} + `, options: [{ properties: 'never' }], }, { - code: 'class Foo { constructor(private bar: number = 0) {} }', + code: ` +class Foo { + constructor(private bar: number = 0) {} +} + `, options: [{ properties: 'always' }], }, { - code: 'abstract class Foo { b_ar: number; }', + code: ` +abstract class Foo { + b_ar: number; +} + `, options: [{ properties: 'never' }], }, { - code: 'abstract class Foo { bar: number; }', + code: ` +abstract class Foo { + bar: number; +} + `, options: [{ properties: 'always' }], }, { - code: 'abstract class Foo { b_ar: number = 0; }', + code: ` +abstract class Foo { + b_ar: number = 0; +} + `, options: [{ properties: 'never' }], }, { - code: 'abstract class Foo { bar: number = 0; }', + code: ` +abstract class Foo { + bar: number = 0; +} + `, options: [{ properties: 'always' }], }, { - code: 'abstract class Foo { abstract b_ar: number; }', + code: ` +abstract class Foo { + abstract b_ar: number; +} + `, options: [{ properties: 'never' }], }, { - code: 'abstract class Foo { abstract bar: number; }', + code: ` +abstract class Foo { + abstract bar: number; +} + `, options: [{ properties: 'always' }], }, { - code: 'abstract class Foo { abstract b_ar: number = 0; }', + code: ` +abstract class Foo { + abstract b_ar: number = 0; +} + `, options: [{ properties: 'never' }], }, { - code: 'abstract class Foo { abstract bar: number = 0; }', + code: ` +abstract class Foo { + abstract bar: number = 0; +} + `, options: [{ properties: 'always' }], }, { @@ -141,19 +213,19 @@ class Foo { }, { code: ` -type Foo = {} +type Foo = {}; `, options: [{ genericType: 'always' }], }, { code: ` -type Foo = {} +type Foo = {}; `, options: [{ genericType: 'always' }], }, { code: ` -type Foo = {} +type Foo = {}; `, options: [{ genericType: 'never' }], }, @@ -183,7 +255,7 @@ class Foo { code: 'const foo = foo.bar?.foo_bar_baz;', }, { - code: 'const foo = (foo?.bar?.baz)?.foo_bar_baz;', + code: noFormat`const foo = (foo?.bar?.baz)?.foo_bar_baz;`, }, { code: 'const foo = foo_bar?.foo;', @@ -193,7 +265,11 @@ class Foo { invalid: [ { - code: 'interface Foo { b_ar: number }', + code: ` +interface Foo { + b_ar: number; +} + `, options: [{ properties: 'always' }], errors: [ { @@ -201,13 +277,17 @@ class Foo { data: { name: 'b_ar', }, - line: 1, - column: 17, + line: 3, + column: 3, }, ], }, { - code: 'class Foo { b_ar: number; }', + code: ` +class Foo { + b_ar: number; +} + `, options: [{ properties: 'always' }], errors: [ { @@ -215,13 +295,17 @@ class Foo { data: { name: 'b_ar', }, - line: 1, - column: 13, + line: 3, + column: 3, }, ], }, { - code: 'class Foo { constructor(private b_ar: number) {} }', + code: ` +class Foo { + constructor(private b_ar: number) {} +} + `, options: [{ properties: 'always' }], errors: [ { @@ -229,13 +313,17 @@ class Foo { data: { name: 'b_ar', }, - line: 1, - column: 33, + line: 3, + column: 23, }, ], }, { - code: 'class Foo { constructor(private b_ar: number = 0) {} }', + code: ` +class Foo { + constructor(private b_ar: number = 0) {} +} + `, options: [{ properties: 'always' }], errors: [ { @@ -243,13 +331,17 @@ class Foo { data: { name: 'b_ar', }, - line: 1, - column: 33, + line: 3, + column: 23, }, ], }, { - code: 'abstract class Foo { b_ar: number; }', + code: ` +abstract class Foo { + b_ar: number; +} + `, options: [{ properties: 'always' }], errors: [ { @@ -257,13 +349,17 @@ class Foo { data: { name: 'b_ar', }, - line: 1, - column: 22, + line: 3, + column: 3, }, ], }, { - code: 'abstract class Foo { b_ar: number = 0; }', + code: ` +abstract class Foo { + b_ar: number = 0; +} + `, options: [{ properties: 'always' }], errors: [ { @@ -271,13 +367,17 @@ class Foo { data: { name: 'b_ar', }, - line: 1, - column: 22, + line: 3, + column: 3, }, ], }, { - code: 'abstract class Foo { abstract b_ar: number; }', + code: ` +abstract class Foo { + abstract b_ar: number; +} + `, options: [{ properties: 'always' }], errors: [ { @@ -285,13 +385,17 @@ class Foo { data: { name: 'b_ar', }, - line: 1, - column: 31, + line: 3, + column: 12, }, ], }, { - code: 'abstract class Foo { abstract b_ar: number = 0; }', + code: ` +abstract class Foo { + abstract b_ar: number = 0; +} + `, options: [{ properties: 'always' }], errors: [ { @@ -299,8 +403,8 @@ class Foo { data: { name: 'b_ar', }, - line: 1, - column: 31, + line: 3, + column: 12, }, ], }, @@ -319,7 +423,7 @@ class Foo { ], }, { - code: 'const foo = (foo_test?.bar)?.baz;', + code: noFormat`const foo = (foo_test?.bar)?.baz;`, options: [{ properties: 'always' }], errors: [ { diff --git a/packages/eslint-plugin/tests/rules/class-literal-property-style.test.ts b/packages/eslint-plugin/tests/rules/class-literal-property-style.test.ts index f0c5f2afc05..19d5fb183b5 100644 --- a/packages/eslint-plugin/tests/rules/class-literal-property-style.test.ts +++ b/packages/eslint-plugin/tests/rules/class-literal-property-style.test.ts @@ -1,5 +1,5 @@ import rule from '../../src/rules/class-literal-property-style'; -import { RuleTester } from '../RuleTester'; +import { RuleTester, noFormat } from '../RuleTester'; const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', @@ -7,18 +7,50 @@ const ruleTester = new RuleTester({ ruleTester.run('class-literal-property-style', rule, { valid: [ - 'class Mx { declare readonly p1 = 1; }', - 'class Mx { readonly p1 = "hello world"; }', - 'class Mx { p1 = "hello world"; }', - 'class Mx { static p1 = "hello world"; }', - 'class Mx { p1: string; }', - 'class Mx { get p1(); }', - 'class Mx { get p1() {} }', - 'abstract class Mx { abstract get p1(): string }', + ` +class Mx { + declare readonly p1 = 1; +} + `, + ` +class Mx { + readonly p1 = 'hello world'; +} + `, + ` +class Mx { + p1 = 'hello world'; +} + `, + ` +class Mx { + static p1 = 'hello world'; +} + `, + ` +class Mx { + p1: string; +} + `, + ` +class Mx { + get p1(); +} + `, + ` +class Mx { + get p1() {} +} + `, + ` +abstract class Mx { + abstract get p1(): string; +} + `, ` class Mx { get mySetting() { - if(this._aValue) { + if (this._aValue) { return 'on'; } @@ -29,14 +61,14 @@ ruleTester.run('class-literal-property-style', rule, { ` class Mx { get mySetting() { - return \`build-\${process.env.build}\` + return \`build-\${process.env.build}\`; } } `, ` class Mx { getMySetting() { - if(this._aValue) { + if (this._aValue) { return 'on'; } @@ -64,31 +96,63 @@ ruleTester.run('class-literal-property-style', rule, { options: ['fields'], }, { - code: 'class Mx { declare public readonly foo = 1; }', + code: ` +class Mx { + public declare readonly foo = 1; +} + `, options: ['getters'], }, { - code: 'class Mx { get p1() { return "hello world"; } }', + code: ` +class Mx { + get p1() { + return 'hello world'; + } +} + `, options: ['getters'], }, { - code: 'class Mx { p1 = "hello world"; }', + code: ` +class Mx { + p1 = 'hello world'; +} + `, options: ['getters'], }, { - code: 'class Mx { p1: string; }', + code: ` +class Mx { + p1: string; +} + `, options: ['getters'], }, { - code: 'class Mx { readonly p1 = [1, 2, 3]; }', + code: ` +class Mx { + readonly p1 = [1, 2, 3]; +} + `, options: ['getters'], }, { - code: 'class Mx { static p1: string; }', + code: ` +class Mx { + static p1: string; +} + `, options: ['getters'], }, { - code: 'class Mx { static get p1() { return "hello world"; } }', + code: ` +class Mx { + static get p1() { + return 'hello world'; + } +} + `, options: ['getters'], }, { @@ -116,259 +180,358 @@ ruleTester.run('class-literal-property-style', rule, { ], invalid: [ { - code: 'class Mx { get p1() { return "hello world"; } }', - output: 'class Mx { readonly p1 = "hello world"; }', + code: ` +class Mx { + get p1() { + return 'hello world'; + } +} + `, + output: ` +class Mx { + readonly p1 = 'hello world'; +} + `, errors: [ { messageId: 'preferFieldStyle', - column: 16, - line: 1, + column: 7, + line: 3, }, ], }, { - code: 'class Mx { get p1() { return `hello world`; } }', - output: 'class Mx { readonly p1 = `hello world`; }', + code: ` +class Mx { + get p1() { + return \`hello world\`; + } +} + `, + output: ` +class Mx { + readonly p1 = \`hello world\`; +} + `, errors: [ { messageId: 'preferFieldStyle', - column: 16, - line: 1, + column: 7, + line: 3, }, ], }, { - code: 'class Mx { static get p1() { return "hello world"; } }', - output: 'class Mx { static readonly p1 = "hello world"; }', + code: ` +class Mx { + static get p1() { + return 'hello world'; + } +} + `, + output: ` +class Mx { + static readonly p1 = 'hello world'; +} + `, errors: [ { messageId: 'preferFieldStyle', - column: 23, - line: 1, + column: 14, + line: 3, }, ], }, { - code: - 'class Mx { public static readonly static private public protected get foo() { return 1; } }', - output: 'class Mx { public static readonly foo = 1; }', + code: ` +class Mx { + public static get foo() { + return 1; + } +} + `, + output: ` +class Mx { + public static readonly foo = 1; +} + `, errors: [ { messageId: 'preferFieldStyle', - column: 71, - line: 1, + column: 21, + line: 3, }, ], }, { code: ` - class Mx { - public get [myValue]() { - return 'a literal value'; - } - } +class Mx { + public get [myValue]() { + return 'a literal value'; + } +} `, output: ` - class Mx { - public readonly [myValue] = 'a literal value'; - } +class Mx { + public readonly [myValue] = 'a literal value'; +} `, errors: [ { messageId: 'preferFieldStyle', - column: 23, + column: 15, line: 3, }, ], }, { code: ` - class Mx { - public get [myValue]() { - return 12345n; - } - } +class Mx { + public get [myValue]() { + return 12345n; + } +} `, output: ` - class Mx { - public readonly [myValue] = 12345n; - } +class Mx { + public readonly [myValue] = 12345n; +} `, errors: [ { messageId: 'preferFieldStyle', - column: 23, + column: 15, line: 3, }, ], }, { code: ` - class Mx { - public readonly [myValue] = 'a literal value'; - } +class Mx { + public readonly [myValue] = 'a literal value'; +} `, - output: ` - class Mx { - public get [myValue]() { return 'a literal value'; } - } + output: noFormat` +class Mx { + public get [myValue]() { return 'a literal value'; } +} `, errors: [ { messageId: 'preferGetterStyle', - column: 28, + column: 20, line: 3, }, ], options: ['getters'], }, { - code: 'class Mx { readonly p1 = "hello world"; }', - output: 'class Mx { get p1() { return "hello world"; } }', + code: ` +class Mx { + readonly p1 = 'hello world'; +} + `, + output: noFormat` +class Mx { + get p1() { return 'hello world'; } +} + `, errors: [ { messageId: 'preferGetterStyle', - column: 21, - line: 1, + column: 12, + line: 3, }, ], options: ['getters'], }, { - code: 'class Mx { readonly p1 = `hello world`; }', - output: 'class Mx { get p1() { return `hello world`; } }', + code: ` +class Mx { + readonly p1 = \`hello world\`; +} + `, + output: noFormat` +class Mx { + get p1() { return \`hello world\`; } +} + `, errors: [ { messageId: 'preferGetterStyle', - column: 21, - line: 1, + column: 12, + line: 3, }, ], options: ['getters'], }, { - code: 'class Mx { static readonly p1 = "hello world"; }', - output: 'class Mx { static get p1() { return "hello world"; } }', + code: ` +class Mx { + static readonly p1 = 'hello world'; +} + `, + output: noFormat` +class Mx { + static get p1() { return 'hello world'; } +} + `, errors: [ { messageId: 'preferGetterStyle', - column: 28, - line: 1, + column: 19, + line: 3, }, ], options: ['getters'], }, { - code: 'class Mx { protected get p1() { return "hello world"; } }', - output: 'class Mx { protected readonly p1 = "hello world"; }', + code: ` +class Mx { + protected get p1() { + return 'hello world'; + } +} + `, + output: ` +class Mx { + protected readonly p1 = 'hello world'; +} + `, errors: [ { messageId: 'preferFieldStyle', - column: 26, - line: 1, + column: 17, + line: 3, }, ], options: ['fields'], }, { - code: 'class Mx { protected readonly p1 = "hello world"; }', - output: 'class Mx { protected get p1() { return "hello world"; } }', + code: ` +class Mx { + protected readonly p1 = 'hello world'; +} + `, + output: noFormat` +class Mx { + protected get p1() { return 'hello world'; } +} + `, errors: [ { messageId: 'preferGetterStyle', - column: 31, - line: 1, + column: 22, + line: 3, }, ], options: ['getters'], }, { - code: 'class Mx { public static get p1() { return "hello world"; } }', - output: 'class Mx { public static readonly p1 = "hello world"; }', + code: ` +class Mx { + public static get p1() { + return 'hello world'; + } +} + `, + output: ` +class Mx { + public static readonly p1 = 'hello world'; +} + `, errors: [ { messageId: 'preferFieldStyle', - column: 30, - line: 1, + column: 21, + line: 3, }, ], }, { - code: 'class Mx { public static readonly p1 = "hello world"; }', - output: 'class Mx { public static get p1() { return "hello world"; } }', + code: ` +class Mx { + public static readonly p1 = 'hello world'; +} + `, + output: noFormat` +class Mx { + public static get p1() { return 'hello world'; } +} + `, errors: [ { messageId: 'preferGetterStyle', - column: 35, - line: 1, + column: 26, + line: 3, }, ], options: ['getters'], }, { code: ` - class Mx { - public get myValue() { - return gql\` - { - user(id: 5) { - firstName - lastName - } - } - \`; - } +class Mx { + public get myValue() { + return gql\` + { + user(id: 5) { + firstName + lastName } + } + \`; + } +} `, - output: ` - class Mx { - public readonly myValue = gql\` - { - user(id: 5) { - firstName - lastName - } - } - \`; + output: noFormat` +class Mx { + public readonly myValue = gql\` + { + user(id: 5) { + firstName + lastName } + } + \`; +} `, errors: [ { messageId: 'preferFieldStyle', - column: 22, + column: 14, line: 3, }, ], }, { code: ` - class Mx { - public readonly myValue = gql\` - { - user(id: 5) { - firstName - lastName - } - } - \`; - } +class Mx { + public readonly myValue = gql\` + { + user(id: 5) { + firstName + lastName + } + } + \`; +} `, - output: ` - class Mx { - public get myValue() { return gql\` - { - user(id: 5) { - firstName - lastName - } - } - \`; } - } + output: noFormat` +class Mx { + public get myValue() { return gql\` + { + user(id: 5) { + firstName + lastName + } + } + \`; } +} `, errors: [ { messageId: 'preferGetterStyle', - column: 27, + column: 19, line: 3, }, ], diff --git a/packages/eslint-plugin/tests/rules/class-name-casing.test.ts b/packages/eslint-plugin/tests/rules/class-name-casing.test.ts index 11f6dc8b888..1977a36415d 100644 --- a/packages/eslint-plugin/tests/rules/class-name-casing.test.ts +++ b/packages/eslint-plugin/tests/rules/class-name-casing.test.ts @@ -35,12 +35,19 @@ ruleTester.run('class-name-casing', rule, { 'class ÈClassNameWithUnicode {}', 'class ClassNameWithæUnicode {}', // Following test cases are valid, but no one is going to write code like this - 'var { bar } = class { static bar() { return 2 } }', - `var [ bar ] = class { - static [Symbol.iterator]() { - return { next: () => ({ value: 1, done: false}) } - } - } + ` +var { bar } = class { + static bar() { + return 2; + } +}; + `, + ` +var [bar] = class { + static [Symbol.iterator]() { + return { next: () => ({ value: 1, done: false }) }; + } +}; `, ], @@ -116,7 +123,7 @@ ruleTester.run('class-name-casing', rule, { ], }, { - code: 'var bar = class invalidName {}', + code: 'var bar = class invalidName {};', errors: [ { messageId: 'notPascalCased', diff --git a/packages/eslint-plugin/tests/rules/comma-spacing.test.ts b/packages/eslint-plugin/tests/rules/comma-spacing.test.ts index 12a6e0219f8..7147815b759 100644 --- a/packages/eslint-plugin/tests/rules/comma-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/comma-spacing.test.ts @@ -1,3 +1,8 @@ +/* eslint-disable eslint-comments/no-use */ +// this rule tests the spacing, which prettier will want to fix and break the tests +/* eslint "@typescript-eslint/internal/plugin-test-formatting": ["error", { formatWithPrettier: false }] */ +/* eslint-enable eslint-comments/no-use */ + import rule from '../../src/rules/comma-spacing'; import { RuleTester } from '../RuleTester'; @@ -7,280 +12,280 @@ const ruleTester = new RuleTester({ ruleTester.run('comma-spacing', rule, { valid: [ - `foo(1, true/* comment */, 'text');`, - `foo(1, true /* comment */, 'text');`, - `foo(1, true/* comment *//* comment */, 'text');`, - `foo(1, true/* comment */ /* comment */, 'text');`, - `foo(1, true, /* comment */ 'text');`, - `foo(1, // comment\n true, /* comment */ 'text');`, - { - code: `foo(1, // comment\n true,/* comment */ 'text');`, + "foo(1, true/* comment */, 'text');", + "foo(1, true /* comment */, 'text');", + "foo(1, true/* comment *//* comment */, 'text');", + "foo(1, true/* comment */ /* comment */, 'text');", + "foo(1, true, /* comment */ 'text');", + "foo(1, // comment\n true, /* comment */ 'text');", + { + code: "foo(1, // comment\n true,/* comment */ 'text');", options: [{ before: false, after: false }], }, - `const a = 1, b = 2;`, - `const foo = [, ];`, - `const foo = [1, ];`, - `const foo = [, 2];`, - `const foo = [1, 2];`, - `const foo = [, , ];`, - `const foo = [1, , ];`, - `const foo = [, 2, ];`, - `const foo = [, , 3];`, - `const foo = [1, 2, ];`, - `const foo = [, 2, 3];`, - `const foo = [1, , 3];`, - `const foo = [1, 2, 3];`, - `const foo = {'foo':'foo', 'baz':'baz'};`, - `const foo = {'foo':'foo', 'baz':\n'baz'};`, - `const foo = {'foo':\n'foo', 'baz':\n'baz'};`, - `function foo(a, b){}`, - `function foo(a, b = 1){}`, - `function foo(a = 1, b, c){}`, - `const foo = (a, b) => {}`, - `const foo = (a=1, b) => {}`, - `const foo = a => a + 2`, - `a, b`, - `const a = (1 + 2, 2)`, - `a(b, c)`, - `new A(b, c)`, - `foo((a), b)`, - `const b = ((1 + 2), 2)`, - `parseInt((a + b), 10)`, - `go.boom((a + b), 10)`, - `go.boom((a + b), 10, (4))`, - `const x = [ (a + c), (b + b) ]`, - `[' , ']`, + 'const a = 1, b = 2;', + 'const foo = [, ];', + 'const foo = [1, ];', + 'const foo = [, 2];', + 'const foo = [1, 2];', + 'const foo = [, , ];', + 'const foo = [1, , ];', + 'const foo = [, 2, ];', + 'const foo = [, , 3];', + 'const foo = [1, 2, ];', + 'const foo = [, 2, 3];', + 'const foo = [1, , 3];', + 'const foo = [1, 2, 3];', + "const foo = {'foo':'foo', 'baz':'baz'};", + "const foo = {'foo':'foo', 'baz':\n'baz'};", + "const foo = {'foo':\n'foo', 'baz':\n'baz'};", + 'function foo(a, b){}', + 'function foo(a, b = 1){}', + 'function foo(a = 1, b, c){}', + 'const foo = (a, b) => {}', + 'const foo = (a=1, b) => {}', + 'const foo = a => a + 2', + 'a, b', + 'const a = (1 + 2, 2)', + 'a(b, c)', + 'new A(b, c)', + 'foo((a), b)', + 'const b = ((1 + 2), 2)', + 'parseInt((a + b), 10)', + 'go.boom((a + b), 10)', + 'go.boom((a + b), 10, (4))', + 'const x = [ (a + c), (b + b) ]', + "[' , ']", '[` , `]', '`${[1, 2]}`', - `fn(a, b,)`, - `const fn = (a, b,) => {}`, - `const fn = function (a, b,) {}`, - `foo(/,/, 'a')`, - `const x = ',,,,,';`, - `const code = 'var foo = 1, bar = 3;'`, - `['apples', \n 'oranges'];`, - `{x: 'var x,y,z'}`, - { - code: `const foo = {'foo':\n'bar' ,'baz':\n'qur'};`, + 'fn(a, b,)', + 'const fn = (a, b,) => {}', + 'const fn = function (a, b,) {}', + "foo(/,/, 'a')", + "const x = ',,,,,';", + "const code = 'var foo = 1, bar = 3;'", + "['apples', \n 'oranges'];", + "{x: 'var x,y,z'}", + { + code: "const foo = {'foo':\n'bar' ,'baz':\n'qur'};", options: [{ before: true, after: false }], }, { - code: `const a = 1 ,b = 2;`, + code: 'const a = 1 ,b = 2;', options: [{ before: true, after: false }], }, { - code: `function foo(a ,b){}`, + code: 'function foo(a ,b){}', options: [{ before: true, after: false }], }, { - code: `const arr = [,];`, + code: 'const arr = [,];', options: [{ before: true, after: false }], }, { - code: `const arr = [1 ,];`, + code: 'const arr = [1 ,];', options: [{ before: true, after: false }], }, { - code: `const arr = [ ,2];`, + code: 'const arr = [ ,2];', options: [{ before: true, after: false }], }, { - code: `const arr = [1 ,2];`, + code: 'const arr = [1 ,2];', options: [{ before: true, after: false }], }, { - code: `const arr = [,,];`, + code: 'const arr = [,,];', options: [{ before: true, after: false }], }, { - code: `const arr = [1 , ,];`, + code: 'const arr = [1 , ,];', options: [{ before: true, after: false }], }, { - code: `const arr = [ ,2 ,];`, + code: 'const arr = [ ,2 ,];', options: [{ before: true, after: false }], }, { - code: `const arr = [ , ,3];`, + code: 'const arr = [ , ,3];', options: [{ before: true, after: false }], }, { - code: `const arr = [1 ,2 ,];`, + code: 'const arr = [1 ,2 ,];', options: [{ before: true, after: false }], }, { - code: `const arr = [ ,2 ,3];`, + code: 'const arr = [ ,2 ,3];', options: [{ before: true, after: false }], }, { - code: `const arr = [1 , ,3];`, + code: 'const arr = [1 , ,3];', options: [{ before: true, after: false }], }, { - code: `const arr = [1 ,2 ,3];`, + code: 'const arr = [1 ,2 ,3];', options: [{ before: true, after: false }], }, { - code: `const obj = {'foo':'bar' , 'baz':'qur'};`, + code: "const obj = {'foo':'bar' , 'baz':'qur'};", options: [{ before: true, after: true }], }, { - code: `const a = 1 , b = 2;`, + code: 'const a = 1 , b = 2;', options: [{ before: true, after: true }], }, { - code: `const arr = [, ];`, + code: 'const arr = [, ];', options: [{ before: true, after: true }], }, { - code: `const arr = [1 , ];`, + code: 'const arr = [1 , ];', options: [{ before: true, after: true }], }, { - code: `const arr = [ , 2];`, + code: 'const arr = [ , 2];', options: [{ before: true, after: true }], }, { - code: `const arr = [1 , 2];`, + code: 'const arr = [1 , 2];', options: [{ before: true, after: true }], }, { - code: `const arr = [, , ];`, + code: 'const arr = [, , ];', options: [{ before: true, after: true }], }, { - code: `const arr = [1 , , ];`, + code: 'const arr = [1 , , ];', options: [{ before: true, after: true }], }, { - code: `const arr = [ , 2 , ];`, + code: 'const arr = [ , 2 , ];', options: [{ before: true, after: true }], }, { - code: `const arr = [ , , 3];`, + code: 'const arr = [ , , 3];', options: [{ before: true, after: true }], }, { - code: `const arr = [1 , 2 , ];`, + code: 'const arr = [1 , 2 , ];', options: [{ before: true, after: true }], }, { - code: `const arr = [, 2 , 3];`, + code: 'const arr = [, 2 , 3];', options: [{ before: true, after: true }], }, { - code: `const arr = [1 , , 3];`, + code: 'const arr = [1 , , 3];', options: [{ before: true, after: true }], }, { - code: `const arr = [1 , 2 , 3];`, + code: 'const arr = [1 , 2 , 3];', options: [{ before: true, after: true }], }, { - code: `a , b`, + code: 'a , b', options: [{ before: true, after: true }], }, { - code: `const arr = [,];`, + code: 'const arr = [,];', options: [{ before: false, after: false }], }, { - code: `const arr = [ ,];`, + code: 'const arr = [ ,];', options: [{ before: false, after: false }], }, { - code: `const arr = [1,];`, + code: 'const arr = [1,];', options: [{ before: false, after: false }], }, { - code: `const arr = [,2];`, + code: 'const arr = [,2];', options: [{ before: false, after: false }], }, { - code: `const arr = [ ,2];`, + code: 'const arr = [ ,2];', options: [{ before: false, after: false }], }, { - code: `const arr = [1,2];`, + code: 'const arr = [1,2];', options: [{ before: false, after: false }], }, { - code: `const arr = [,,];`, + code: 'const arr = [,,];', options: [{ before: false, after: false }], }, { - code: `const arr = [ ,,];`, + code: 'const arr = [ ,,];', options: [{ before: false, after: false }], }, { - code: `const arr = [1,,];`, + code: 'const arr = [1,,];', options: [{ before: false, after: false }], }, { - code: `const arr = [,2,];`, + code: 'const arr = [,2,];', options: [{ before: false, after: false }], }, { - code: `const arr = [ ,2,];`, + code: 'const arr = [ ,2,];', options: [{ before: false, after: false }], }, { - code: `const arr = [,,3];`, + code: 'const arr = [,,3];', options: [{ before: false, after: false }], }, { - code: `const arr = [1,2,];`, + code: 'const arr = [1,2,];', options: [{ before: false, after: false }], }, { - code: `const arr = [,2,3];`, + code: 'const arr = [,2,3];', options: [{ before: false, after: false }], }, { - code: `const arr = [1,,3];`, + code: 'const arr = [1,,3];', options: [{ before: false, after: false }], }, { - code: `const arr = [1,2,3];`, + code: 'const arr = [1,2,3];', options: [{ before: false, after: false }], }, { - code: `const a = (1 + 2,2)`, + code: 'const a = (1 + 2,2)', options: [{ before: false, after: false }], }, 'const a; console.log(`${a}`, "a");', - `const [a, b] = [1, 2];`, - `const [a, b, ] = [1, 2];`, - `const [a, , b] = [1, 2, 3];`, - `const [ , b] = a;`, - `const [, b] = a;`, + 'const [a, b] = [1, 2];', + 'const [a, b, ] = [1, 2];', + 'const [a, , b] = [1, 2, 3];', + 'const [ , b] = a;', + 'const [, b] = a;', { - code: `,`, + code: ',', parserOptions: { ecmaFeatures: { jsx: true }, }, }, { - code: ` , `, + code: ' , ', parserOptions: { ecmaFeatures: { jsx: true }, }, }, { - code: `Hello, world`, + code: 'Hello, world', options: [{ before: true, after: false }], parserOptions: { ecmaFeatures: { jsx: true } }, }, - `const Foo = (foo: T) => {}`, - `function foo() {}`, - `class Foo {}`, - `interface Foo{}`, + 'const Foo = (foo: T) => {}', + 'function foo() {}', + 'class Foo {}', + 'interface Foo{}', ], invalid: [ { - code: `a(b,c)`, - output: `a(b , c)`, + code: 'a(b,c)', + output: 'a(b , c)', options: [{ before: true, after: true }], errors: [ { @@ -298,8 +303,8 @@ ruleTester.run('comma-spacing', rule, { ], }, { - code: `new A(b,c)`, - output: `new A(b , c)`, + code: 'new A(b,c)', + output: 'new A(b , c)', options: [{ before: true, after: true }], errors: [ { @@ -317,8 +322,8 @@ ruleTester.run('comma-spacing', rule, { ], }, { - code: `const a = 1 ,b = 2;`, - output: `const a = 1, b = 2;`, + code: 'const a = 1 ,b = 2;', + output: 'const a = 1, b = 2;', errors: [ { messageId: 'unexpected', @@ -335,8 +340,8 @@ ruleTester.run('comma-spacing', rule, { ], }, { - code: `const arr = [1 , 2];`, - output: `const arr = [1, 2];`, + code: 'const arr = [1 , 2];', + output: 'const arr = [1, 2];', errors: [ { messageId: 'unexpected', @@ -359,8 +364,8 @@ ruleTester.run('comma-spacing', rule, { ], }, { - code: `const arr = [1 , ];`, - output: `const arr = [1 ,];`, + code: 'const arr = [1 , ];', + output: 'const arr = [1 ,];', options: [{ before: true, after: false }], errors: [ { @@ -372,8 +377,8 @@ ruleTester.run('comma-spacing', rule, { ], }, { - code: `const arr = [1 ,2];`, - output: `const arr = [1, 2];`, + code: 'const arr = [1 ,2];', + output: 'const arr = [1, 2];', errors: [ { messageId: 'unexpected', @@ -390,8 +395,8 @@ ruleTester.run('comma-spacing', rule, { ], }, { - code: `const arr = [(1) , 2];`, - output: `const arr = [(1), 2];`, + code: 'const arr = [(1) , 2];', + output: 'const arr = [(1), 2];', errors: [ { messageId: 'unexpected', @@ -402,8 +407,8 @@ ruleTester.run('comma-spacing', rule, { ], }, { - code: `const arr = [1, 2];`, - output: `const arr = [1 ,2];`, + code: 'const arr = [1, 2];', + output: 'const arr = [1 ,2];', options: [{ before: true, after: false }], errors: [ { @@ -421,8 +426,8 @@ ruleTester.run('comma-spacing', rule, { ], }, { - code: `const arr = [1\n , 2];`, - output: `const arr = [1\n ,2];`, + code: 'const arr = [1\n , 2];', + output: 'const arr = [1\n ,2];', options: [{ before: false, after: false }], errors: [ { @@ -434,8 +439,8 @@ ruleTester.run('comma-spacing', rule, { ], }, { - code: `const arr = [1,\n 2];`, - output: `const arr = [1 ,\n 2];`, + code: 'const arr = [1,\n 2];', + output: 'const arr = [1 ,\n 2];', options: [{ before: true, after: false }], errors: [ { @@ -447,8 +452,8 @@ ruleTester.run('comma-spacing', rule, { ], }, { - code: `const obj = {'foo':\n'bar', 'baz':\n'qur'};`, - output: `const obj = {'foo':\n'bar' ,'baz':\n'qur'};`, + code: "const obj = {'foo':\n'bar', 'baz':\n'qur'};", + output: "const obj = {'foo':\n'bar' ,'baz':\n'qur'};", options: [{ before: true, after: false }], errors: [ { @@ -466,8 +471,8 @@ ruleTester.run('comma-spacing', rule, { ], }, { - code: `const obj = {a: 1\n ,b: 2};`, - output: `const obj = {a: 1\n , b: 2};`, + code: 'const obj = {a: 1\n ,b: 2};', + output: 'const obj = {a: 1\n , b: 2};', options: [{ before: false, after: true }], errors: [ { @@ -479,8 +484,8 @@ ruleTester.run('comma-spacing', rule, { ], }, { - code: `const obj = {a: 1 ,\n b: 2};`, - output: `const obj = {a: 1,\n b: 2};`, + code: 'const obj = {a: 1 ,\n b: 2};', + output: 'const obj = {a: 1,\n b: 2};', options: [{ before: false, after: false }], errors: [ { @@ -492,8 +497,8 @@ ruleTester.run('comma-spacing', rule, { ], }, { - code: `const arr = [1 ,2];`, - output: `const arr = [1 , 2];`, + code: 'const arr = [1 ,2];', + output: 'const arr = [1 , 2];', options: [{ before: true, after: true }], errors: [ { @@ -505,8 +510,8 @@ ruleTester.run('comma-spacing', rule, { ], }, { - code: `const arr = [1,2];`, - output: `const arr = [1 , 2];`, + code: 'const arr = [1,2];', + output: 'const arr = [1 , 2];', options: [{ before: true, after: true }], errors: [ { @@ -524,8 +529,8 @@ ruleTester.run('comma-spacing', rule, { ], }, { - code: `const obj = {'foo':\n'bar','baz':\n'qur'};`, - output: `const obj = {'foo':\n'bar' , 'baz':\n'qur'};`, + code: "const obj = {'foo':\n'bar','baz':\n'qur'};", + output: "const obj = {'foo':\n'bar' , 'baz':\n'qur'};", options: [{ before: true, after: true }], errors: [ { @@ -543,8 +548,8 @@ ruleTester.run('comma-spacing', rule, { ], }, { - code: `const arr = [1 , 2];`, - output: `const arr = [1,2];`, + code: 'const arr = [1 , 2];', + output: 'const arr = [1,2];', options: [{ before: false, after: false }], errors: [ { @@ -562,8 +567,8 @@ ruleTester.run('comma-spacing', rule, { ], }, { - code: `a ,b`, - output: `a, b`, + code: 'a ,b', + output: 'a, b', options: [{ before: false, after: true }], errors: [ { @@ -581,8 +586,8 @@ ruleTester.run('comma-spacing', rule, { ], }, { - code: `function foo(a,b){}`, - output: `function foo(a , b){}`, + code: 'function foo(a,b){}', + output: 'function foo(a , b){}', options: [{ before: true, after: true }], errors: [ { @@ -600,8 +605,8 @@ ruleTester.run('comma-spacing', rule, { ], }, { - code: `const foo = (a,b) => {}`, - output: `const foo = (a , b) => {}`, + code: 'const foo = (a,b) => {}', + output: 'const foo = (a , b) => {}', options: [{ before: true, after: true }], errors: [ { @@ -619,8 +624,8 @@ ruleTester.run('comma-spacing', rule, { ], }, { - code: `const foo = (a = 1,b) => {}`, - output: `const foo = (a = 1 , b) => {}`, + code: 'const foo = (a = 1,b) => {}', + output: 'const foo = (a = 1 , b) => {}', options: [{ before: true, after: true }], errors: [ { @@ -638,8 +643,8 @@ ruleTester.run('comma-spacing', rule, { ], }, { - code: `function foo(a = 1 ,b = 2) {}`, - output: `function foo(a = 1, b = 2) {}`, + code: 'function foo(a = 1 ,b = 2) {}', + output: 'function foo(a = 1, b = 2) {}', options: [{ before: false, after: true }], errors: [ { @@ -657,8 +662,8 @@ ruleTester.run('comma-spacing', rule, { ], }, { - code: `{foo(1 ,2)}`, - output: `{foo(1, 2)}`, + code: '{foo(1 ,2)}', + output: '{foo(1, 2)}', parserOptions: { ecmaFeatures: { jsx: true }, }, @@ -678,8 +683,8 @@ ruleTester.run('comma-spacing', rule, { ], }, { - code: `foo(1, true/* comment */ , 'foo');`, - output: `foo(1, true/* comment */, 'foo');`, + code: "foo(1, true/* comment */ , 'foo');", + output: "foo(1, true/* comment */, 'foo');", errors: [ { messageId: 'unexpected', @@ -690,8 +695,8 @@ ruleTester.run('comma-spacing', rule, { ], }, { - code: `foo(1, true,/* comment */ 'foo');`, - output: `foo(1, true, /* comment */ 'foo');`, + code: "foo(1, true,/* comment */ 'foo');", + output: "foo(1, true, /* comment */ 'foo');", errors: [ { messageId: 'missing', @@ -702,8 +707,8 @@ ruleTester.run('comma-spacing', rule, { ], }, { - code: `foo(404,// comment\n true, 'hello');`, - output: `foo(404, // comment\n true, 'hello');`, + code: "foo(404,// comment\n true, 'hello');", + output: "foo(404, // comment\n true, 'hello');", errors: [ { messageId: 'missing', @@ -714,8 +719,8 @@ ruleTester.run('comma-spacing', rule, { ], }, { - code: `function Foo() {}`, - output: `function Foo() {}`, + code: 'function Foo() {}', + output: 'function Foo() {}', errors: [ { messageId: 'missing', @@ -726,8 +731,8 @@ ruleTester.run('comma-spacing', rule, { ], }, { - code: `function Foo() {}`, - output: `function Foo() {}`, + code: 'function Foo() {}', + output: 'function Foo() {}', errors: [ { messageId: 'unexpected', @@ -738,8 +743,8 @@ ruleTester.run('comma-spacing', rule, { ], }, { - code: `function Foo() {}`, - output: `function Foo() {}`, + code: 'function Foo() {}', + output: 'function Foo() {}', errors: [ { messageId: 'unexpected', @@ -756,8 +761,8 @@ ruleTester.run('comma-spacing', rule, { ], }, { - code: `function Foo() {}`, - output: `function Foo() {}`, + code: 'function Foo() {}', + output: 'function Foo() {}', options: [{ before: false, after: false }], errors: [ { @@ -769,8 +774,8 @@ ruleTester.run('comma-spacing', rule, { ], }, { - code: `function Foo() {}`, - output: `function Foo() {}`, + code: 'function Foo() {}', + output: 'function Foo() {}', options: [{ before: true, after: false }], errors: [ { diff --git a/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts b/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts index f885d503091..010bb894ad7 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-assertions.test.ts @@ -9,14 +9,12 @@ const ANGLE_BRACKET_TESTS = ` const x = new Generic(); const x = b; const x = [1]; -const x = [1]; const x = ('string'); `; const AS_TESTS = ` const x = new Generic() as Foo; const x = b as A; const x = [1] as readonly number[]; -const x = [1] as const; const x = ('string') as a | b; `; const OBJECT_LITERAL_AS_CASTS = ` @@ -98,6 +96,22 @@ ruleTester.run('consistent-type-assertions', rule, { }, ], }), + { + code: 'const x = [1];', + options: [ + { + assertionStyle: 'never', + }, + ], + }, + { + code: 'const x = [1] as const;', + options: [ + { + assertionStyle: 'never', + }, + ], + }, ], invalid: [ ...batchedSingleLineTests({ diff --git a/packages/eslint-plugin/tests/rules/consistent-type-definitions.test.ts b/packages/eslint-plugin/tests/rules/consistent-type-definitions.test.ts index b777a1fc68f..78e5d3ceb01 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-definitions.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-definitions.test.ts @@ -1,5 +1,5 @@ import rule from '../../src/rules/consistent-type-definitions'; -import { RuleTester } from '../RuleTester'; +import { RuleTester, noFormat } from '../RuleTester'; const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', @@ -8,58 +8,62 @@ const ruleTester = new RuleTester({ ruleTester.run('consistent-type-definitions', rule, { valid: [ { - code: `var foo = { };`, + code: 'var foo = {};', options: ['interface'], }, { - code: `interface A {}`, + code: 'interface A {}', options: ['interface'], }, { - code: `interface A extends B { x: number; }`, + code: ` +interface A extends B { + x: number; +} + `, options: ['interface'], }, { - code: `type U = string;`, + code: 'type U = string;', options: ['interface'], }, { - code: `type V = { x: number; } | { y: string; };`, + code: 'type V = { x: number } | { y: string };', options: ['interface'], }, { code: ` type Record = { - [K in T]: U; -} -`, + [K in T]: U; +}; + `, options: ['interface'], }, { - code: `type T = { x: number; }`, + code: 'type T = { x: number };', options: ['type'], }, { - code: `type A = { x: number; } & B & C;`, + code: 'type A = { x: number } & B & C;', options: ['type'], }, { - code: `type A = { x: number; } & B & C;`, + code: 'type A = { x: number } & B & C;', options: ['type'], }, { code: ` export type W = { - x: T, + x: T; }; -`, + `, options: ['type'], }, ], invalid: [ { - code: `type T = { x: number; };`, - output: `interface T { x: number; }`, + code: noFormat`type T = { x: number; };`, + output: noFormat`interface T { x: number; }`, options: ['interface'], errors: [ { @@ -70,8 +74,8 @@ export type W = { ], }, { - code: `type T={ x: number; };`, - output: `interface T { x: number; }`, + code: noFormat`type T={ x: number; };`, + output: noFormat`interface T { x: number; }`, options: ['interface'], errors: [ { @@ -82,8 +86,8 @@ export type W = { ], }, { - code: `type T= { x: number; };`, - output: `interface T { x: number; }`, + code: noFormat`type T= { x: number; };`, + output: noFormat`interface T { x: number; }`, options: ['interface'], errors: [ { @@ -96,14 +100,14 @@ export type W = { { code: ` export type W = { - x: T, + x: T; }; -`, + `, output: ` export interface W { - x: T, + x: T; } -`, + `, options: ['interface'], errors: [ { @@ -114,8 +118,8 @@ export interface W { ], }, { - code: `interface T { x: number; }`, - output: `type T = { x: number; }`, + code: noFormat`interface T { x: number; }`, + output: noFormat`type T = { x: number; }`, options: ['type'], errors: [ { @@ -126,8 +130,8 @@ export interface W { ], }, { - code: `interface T{ x: number; }`, - output: `type T = { x: number; }`, + code: noFormat`interface T{ x: number; }`, + output: noFormat`type T = { x: number; }`, options: ['type'], errors: [ { @@ -138,8 +142,8 @@ export interface W { ], }, { - code: `interface T { x: number; }`, - output: `type T = { x: number; }`, + code: noFormat`interface T { x: number; }`, + output: noFormat`type T = { x: number; }`, options: ['type'], errors: [ { @@ -150,8 +154,8 @@ export interface W { ], }, { - code: `interface A extends B, C { x: number; };`, - output: `type A = { x: number; } & B & C;`, + code: noFormat`interface A extends B, C { x: number; };`, + output: noFormat`type A = { x: number; } & B & C;`, options: ['type'], errors: [ { @@ -162,8 +166,8 @@ export interface W { ], }, { - code: `interface A extends B, C { x: number; };`, - output: `type A = { x: number; } & B & C;`, + code: noFormat`interface A extends B, C { x: number; };`, + output: noFormat`type A = { x: number; } & B & C;`, options: ['type'], errors: [ { @@ -176,14 +180,14 @@ export interface W { { code: ` export interface W { - x: T, -}; -`, - output: ` + x: T; +} + `, + output: noFormat` export type W = { - x: T, -}; -`, + x: T; +} + `, options: ['type'], errors: [ { diff --git a/packages/eslint-plugin/tests/rules/default-param-last.test.ts b/packages/eslint-plugin/tests/rules/default-param-last.test.ts index d965df3c77f..76e23bbe8a7 100644 --- a/packages/eslint-plugin/tests/rules/default-param-last.test.ts +++ b/packages/eslint-plugin/tests/rules/default-param-last.test.ts @@ -19,29 +19,29 @@ ruleTester.run('default-param-last', rule, { 'function foo(a: number, b?: number, c = 1) {}', 'function foo(a: number, b = 1, ...c) {}', - 'const foo = function () {}', - 'const foo = function (a: number) {}', - 'const foo = function (a = 1) {}', - 'const foo = function (a?: number) {}', - 'const foo = function (a: number, b: number) {}', - 'const foo = function (a: number, b: number, c?: number) {}', - 'const foo = function (a: number, b = 1) {}', - 'const foo = function (a: number, b = 1, c = 1) {}', - 'const foo = function (a: number, b = 1, c?: number) {}', - 'const foo = function (a: number, b?: number, c = 1) {}', - 'const foo = function (a: number, b = 1, ...c) {}', + 'const foo = function() {};', + 'const foo = function(a: number) {};', + 'const foo = function(a = 1) {};', + 'const foo = function(a?: number) {};', + 'const foo = function(a: number, b: number) {};', + 'const foo = function(a: number, b: number, c?: number) {};', + 'const foo = function(a: number, b = 1) {};', + 'const foo = function(a: number, b = 1, c = 1) {};', + 'const foo = function(a: number, b = 1, c?: number) {};', + 'const foo = function(a: number, b?: number, c = 1) {};', + 'const foo = function(a: number, b = 1, ...c) {};', - 'const foo = () => {}', - 'const foo = (a: number) => {}', - 'const foo = (a = 1) => {}', - 'const foo = (a?: number) => {}', - 'const foo = (a: number, b: number) => {}', - 'const foo = (a: number, b: number, c?: number) => {}', - 'const foo = (a: number, b = 1) => {}', - 'const foo = (a: number, b = 1, c = 1) => {}', - 'const foo = (a: number, b = 1, c?: number) => {}', - 'const foo = (a: number, b?: number, c = 1) => {}', - 'const foo = (a: number, b = 1, ...c) => {}', + 'const foo = () => {};', + 'const foo = (a: number) => {};', + 'const foo = (a = 1) => {};', + 'const foo = (a?: number) => {};', + 'const foo = (a: number, b: number) => {};', + 'const foo = (a: number, b: number, c?: number) => {};', + 'const foo = (a: number, b = 1) => {};', + 'const foo = (a: number, b = 1, c = 1) => {};', + 'const foo = (a: number, b = 1, c?: number) => {};', + 'const foo = (a: number, b?: number, c = 1) => {};', + 'const foo = (a: number, b = 1, ...c) => {};', ` class Foo { constructor(a: number, b: number, c: number) {} @@ -251,7 +251,7 @@ class Foo { ], }, { - code: 'const foo = function(a = 1, b: number) {}', + code: 'const foo = function(a = 1, b: number) {};', errors: [ { messageId: 'shouldBeLast', @@ -262,7 +262,7 @@ class Foo { ], }, { - code: 'const foo = function(a = 1, b = 2, c: number) {}', + code: 'const foo = function(a = 1, b = 2, c: number) {};', errors: [ { messageId: 'shouldBeLast', @@ -279,7 +279,7 @@ class Foo { ], }, { - code: 'const foo = function(a = 1, b: number, c = 2, d: number) {}', + code: 'const foo = function(a = 1, b: number, c = 2, d: number) {};', errors: [ { messageId: 'shouldBeLast', @@ -296,7 +296,7 @@ class Foo { ], }, { - code: 'const foo = function(a = 1, b: number, c = 2) {}', + code: 'const foo = function(a = 1, b: number, c = 2) {};', errors: [ { messageId: 'shouldBeLast', @@ -307,7 +307,7 @@ class Foo { ], }, { - code: 'const foo = function(a = 1, b: number, ...c) {}', + code: 'const foo = function(a = 1, b: number, ...c) {};', errors: [ { messageId: 'shouldBeLast', @@ -318,7 +318,7 @@ class Foo { ], }, { - code: 'const foo = function(a?: number, b: number) {}', + code: 'const foo = function(a?: number, b: number) {};', errors: [ { messageId: 'shouldBeLast', @@ -329,7 +329,7 @@ class Foo { ], }, { - code: 'const foo = function(a: number, b?: number, c: number) {}', + code: 'const foo = function(a: number, b?: number, c: number) {};', errors: [ { messageId: 'shouldBeLast', @@ -340,7 +340,7 @@ class Foo { ], }, { - code: 'const foo = function(a = 1, b?: number, c: number) {}', + code: 'const foo = function(a = 1, b?: number, c: number) {};', errors: [ { messageId: 'shouldBeLast', @@ -357,7 +357,7 @@ class Foo { ], }, { - code: 'const foo = function(a = 1, { b }) {}', + code: 'const foo = function(a = 1, { b }) {};', errors: [ { messageId: 'shouldBeLast', @@ -368,7 +368,7 @@ class Foo { ], }, { - code: 'const foo = function({ a } = {}, b) {}', + code: 'const foo = function({ a } = {}, b) {};', errors: [ { messageId: 'shouldBeLast', @@ -379,7 +379,7 @@ class Foo { ], }, { - code: 'const foo = function({ a, b } = { a: 1, b: 2 }, c) {}', + code: 'const foo = function({ a, b } = { a: 1, b: 2 }, c) {};', errors: [ { messageId: 'shouldBeLast', @@ -390,7 +390,7 @@ class Foo { ], }, { - code: 'const foo = function([a] = [], b) {}', + code: 'const foo = function([a] = [], b) {};', errors: [ { messageId: 'shouldBeLast', @@ -401,7 +401,7 @@ class Foo { ], }, { - code: 'const foo = function([a, b] = [1, 2], c) {}', + code: 'const foo = function([a, b] = [1, 2], c) {};', errors: [ { messageId: 'shouldBeLast', @@ -412,7 +412,7 @@ class Foo { ], }, { - code: 'const foo = (a = 1, b: number) => {}', + code: 'const foo = (a = 1, b: number) => {};', errors: [ { messageId: 'shouldBeLast', @@ -423,7 +423,7 @@ class Foo { ], }, { - code: 'const foo = (a = 1, b = 2, c: number) => {}', + code: 'const foo = (a = 1, b = 2, c: number) => {};', errors: [ { messageId: 'shouldBeLast', @@ -440,7 +440,7 @@ class Foo { ], }, { - code: 'const foo = (a = 1, b: number, c = 2, d: number) => {}', + code: 'const foo = (a = 1, b: number, c = 2, d: number) => {};', errors: [ { messageId: 'shouldBeLast', @@ -457,7 +457,7 @@ class Foo { ], }, { - code: 'const foo = (a = 1, b: number, c = 2) => {}', + code: 'const foo = (a = 1, b: number, c = 2) => {};', errors: [ { messageId: 'shouldBeLast', @@ -468,7 +468,7 @@ class Foo { ], }, { - code: 'const foo = (a = 1, b: number, ...c) => {}', + code: 'const foo = (a = 1, b: number, ...c) => {};', errors: [ { messageId: 'shouldBeLast', @@ -479,7 +479,7 @@ class Foo { ], }, { - code: 'const foo = (a?: number, b: number) => {}', + code: 'const foo = (a?: number, b: number) => {};', errors: [ { messageId: 'shouldBeLast', @@ -490,7 +490,7 @@ class Foo { ], }, { - code: 'const foo = (a: number, b?: number, c: number) => {}', + code: 'const foo = (a: number, b?: number, c: number) => {};', errors: [ { messageId: 'shouldBeLast', @@ -501,7 +501,7 @@ class Foo { ], }, { - code: 'const foo = (a = 1, b?: number, c: number) => {}', + code: 'const foo = (a = 1, b?: number, c: number) => {};', errors: [ { messageId: 'shouldBeLast', @@ -518,7 +518,7 @@ class Foo { ], }, { - code: 'const foo = (a = 1, { b }) => {}', + code: 'const foo = (a = 1, { b }) => {};', errors: [ { messageId: 'shouldBeLast', @@ -529,7 +529,7 @@ class Foo { ], }, { - code: 'const foo = ({ a } = {}, b) => {}', + code: 'const foo = ({ a } = {}, b) => {};', errors: [ { messageId: 'shouldBeLast', @@ -540,7 +540,7 @@ class Foo { ], }, { - code: 'const foo = ({ a, b } = { a: 1, b: 2 }, c) => {}', + code: 'const foo = ({ a, b } = { a: 1, b: 2 }, c) => {};', errors: [ { messageId: 'shouldBeLast', @@ -551,7 +551,7 @@ class Foo { ], }, { - code: 'const foo = ([a] = [], b) => {}', + code: 'const foo = ([a] = [], b) => {};', errors: [ { messageId: 'shouldBeLast', @@ -562,7 +562,7 @@ class Foo { ], }, { - code: 'const foo = ([a, b] = [1, 2], c) => {}', + code: 'const foo = ([a, b] = [1, 2], c) => {};', errors: [ { messageId: 'shouldBeLast', diff --git a/packages/eslint-plugin/tests/rules/explicit-function-return-type.test.ts b/packages/eslint-plugin/tests/rules/explicit-function-return-type.test.ts index fb353eec1a3..dd3e3815676 100644 --- a/packages/eslint-plugin/tests/rules/explicit-function-return-type.test.ts +++ b/packages/eslint-plugin/tests/rules/explicit-function-return-type.test.ts @@ -11,23 +11,23 @@ ruleTester.run('explicit-function-return-type', rule, { filename: 'test.ts', code: ` function test(): void { - return; + return; } - `, + `, }, { filename: 'test.ts', code: ` var fn = function(): number { - return 1; + return 1; }; - `, + `, }, { filename: 'test.ts', code: ` var arrowFn = (): string => 'test'; - `, + `, }, { filename: 'test.ts', @@ -43,11 +43,11 @@ class Test { } arrow = (): string => 'arrow'; } - `, + `, }, { filename: 'test.ts', - code: `fn(() => {});`, + code: 'fn(() => {});', options: [ { allowExpressions: true, @@ -56,7 +56,7 @@ class Test { }, { filename: 'test.ts', - code: `fn(function() {});`, + code: 'fn(function() {});', options: [ { allowExpressions: true, @@ -65,7 +65,7 @@ class Test { }, { filename: 'test.ts', - code: `[function() {}, () => {}]`, + code: '[function() {}, () => {}];', options: [ { allowExpressions: true, @@ -74,7 +74,7 @@ class Test { }, { filename: 'test.ts', - code: `(function() {});`, + code: '(function() {});', options: [ { allowExpressions: true, @@ -83,7 +83,7 @@ class Test { }, { filename: 'test.ts', - code: `(() => {})();`, + code: '(() => {})();', options: [ { allowExpressions: true, @@ -92,7 +92,7 @@ class Test { }, { filename: 'test.ts', - code: `export default (): void => {}`, + code: 'export default (): void => {};', options: [ { allowExpressions: true, @@ -103,7 +103,7 @@ class Test { filename: 'test.ts', code: ` var arrowFn: Foo = () => 'test'; - `, + `, options: [ { allowTypedFunctionExpressions: true, @@ -113,8 +113,10 @@ var arrowFn: Foo = () => 'test'; { filename: 'test.ts', code: ` -var funcExpr: Foo = function() { return 'test'; }; - `, +var funcExpr: Foo = function() { + return 'test'; +}; + `, options: [ { allowTypedFunctionExpressions: true, @@ -123,12 +125,12 @@ var funcExpr: Foo = function() { return 'test'; }; }, { filename: 'test.ts', - code: `const x = (() => {}) as Foo`, + code: 'const x = (() => {}) as Foo;', options: [{ allowTypedFunctionExpressions: true }], }, { filename: 'test.ts', - code: `const x = (() => {})`, + code: 'const x = (() => {});', options: [{ allowTypedFunctionExpressions: true }], }, { @@ -136,7 +138,7 @@ var funcExpr: Foo = function() { return 'test'; }; code: ` const x = { foo: () => {}, -} as Foo +} as Foo; `, options: [{ allowTypedFunctionExpressions: true }], }, @@ -145,7 +147,7 @@ const x = { code: ` const x = { foo: () => {}, -} +}; `, options: [{ allowTypedFunctionExpressions: true }], }, @@ -154,7 +156,7 @@ const x = { code: ` const x: Foo = { foo: () => {}, -} +}; `, options: [{ allowTypedFunctionExpressions: true }], }, @@ -165,7 +167,7 @@ const x: Foo = { type MethodType = () => void; class App { - private method: MethodType = () => {} + private method: MethodType = () => {}; } `, options: [{ allowTypedFunctionExpressions: true }], @@ -185,42 +187,50 @@ const myObj = { filename: 'test.ts', code: ` () => (): void => {}; - `, + `, options: [{ allowHigherOrderFunctions: true }], }, { filename: 'test.ts', code: ` -() => function (): void {}; - `, +() => function(): void {}; + `, options: [{ allowHigherOrderFunctions: true }], }, { filename: 'test.ts', code: ` -() => { return (): void => {} }; - `, +() => { + return (): void => {}; +}; + `, options: [{ allowHigherOrderFunctions: true }], }, { filename: 'test.ts', code: ` -() => { return function (): void {} }; - `, +() => { + return function(): void {}; +}; + `, options: [{ allowHigherOrderFunctions: true }], }, { filename: 'test.ts', code: ` -function fn() { return (): void => {} }; - `, +function fn() { + return (): void => {}; +} + `, options: [{ allowHigherOrderFunctions: true }], }, { filename: 'test.ts', code: ` -function fn() { return function (): void {} }; - `, +function fn() { + return function(): void {}; +} + `, options: [{ allowHigherOrderFunctions: true }], }, { @@ -229,33 +239,39 @@ function fn() { return function (): void {} }; function FunctionDeclaration() { return function FunctionExpression_Within_FunctionDeclaration() { return function FunctionExpression_Within_FunctionExpression() { - return () => { // ArrowFunctionExpression_Within_FunctionExpression - return () => // ArrowFunctionExpression_Within_ArrowFunctionExpression - (): number => 1 // ArrowFunctionExpression_Within_ArrowFunctionExpression_WithNoBody - } - } - } + return () => { + // ArrowFunctionExpression_Within_FunctionExpression + return () => + // ArrowFunctionExpression_Within_ArrowFunctionExpression + (): number => 1; // ArrowFunctionExpression_Within_ArrowFunctionExpression_WithNoBody + }; + }; + }; } - `, + `, options: [{ allowHigherOrderFunctions: true }], }, { filename: 'test.ts', code: ` -() => () => { return (): void => { return; } }; - `, +() => () => { + return (): void => { + return; + }; +}; + `, options: [{ allowHigherOrderFunctions: true }], }, // https://github.com/typescript-eslint/typescript-eslint/issues/679 { filename: 'test.ts', code: ` -declare function foo(arg: () => void): void -foo(() => 1) -foo(() => {}) -foo(() => null) -foo(() => true) -foo(() => '') +declare function foo(arg: () => void): void; +foo(() => 1); +foo(() => {}); +foo(() => null); +foo(() => true); +foo(() => ''); `, options: [ { @@ -266,12 +282,12 @@ foo(() => '') { filename: 'test.ts', code: ` -declare function foo(arg: () => void): void -foo?.(() => 1) -foo?.bar(() => {}) -foo?.bar?.(() => null) -foo.bar?.(() => true) -foo?.(() => '') +declare function foo(arg: () => void): void; +foo?.(() => 1); +foo?.bar(() => {}); +foo?.bar?.(() => null); +foo.bar?.(() => true); +foo?.(() => ''); `, options: [ { @@ -301,22 +317,22 @@ new Accumulator().accumulate(() => 1); { filename: 'test.ts', code: ` -declare function foo(arg: { meth: () => number }): void +declare function foo(arg: { meth: () => number }): void; foo({ meth() { return 1; }, -}) +}); foo({ - meth: function () { + meth: function() { return 1; }, -}) +}); foo({ meth: () => { return 1; }, -}) +}); `, options: [ { @@ -327,9 +343,9 @@ foo({ { filename: 'test.ts', code: ` -const func = (value: number) => (({ type: "X", value }) as const); -const func = (value: number) => ({ type: "X", value } as const); -const func = (value: number) => (x as const); +const func = (value: number) => ({ type: 'X', value } as const); +const func = (value: number) => ({ type: 'X', value } as const); +const func = (value: number) => x as const; const func = (value: number) => x as const; `, options: [ @@ -350,15 +366,17 @@ new Foo(1, () => {}); }, ], }, + { + filename: 'test.ts', + code: 'const log = (message: string) => void console.log(message);', + options: [{ allowConciseArrowFunctionExpressionsStartingWithVoid: true }], + }, ], invalid: [ { filename: 'test.ts', code: ` -function test( - a: number, - b: number, -) { +function test(a: number, b: number) { return; } `, @@ -366,9 +384,9 @@ function test( { messageId: 'missingReturnType', line: 2, - endLine: 5, + endLine: 2, column: 1, - endColumn: 2, + endColumn: 36, }, ], }, @@ -427,7 +445,7 @@ var arrowFn = () => 'test'; class Test { constructor() {} get prop() { - return 1; + return 1; } set prop() {} method() { @@ -532,7 +550,7 @@ function test() { }, { filename: 'test.ts', - code: 'export default function() {};', + code: 'export default function() {}', options: [{ allowExpressions: true }], errors: [ { @@ -549,11 +567,11 @@ function test() { code: ` class Foo { public a = () => {}; - public b = function () {}; + public b = function() {}; public c = function test() {}; static d = () => {}; - static e = function () {}; + static e = function() {}; } `, options: [{ allowExpressions: true }], @@ -570,7 +588,7 @@ class Foo { line: 4, endLine: 4, column: 14, - endColumn: 25, + endColumn: 24, }, { messageId: 'missingReturnType', @@ -591,7 +609,7 @@ class Foo { line: 8, endLine: 8, column: 14, - endColumn: 25, + endColumn: 24, }, ], }, @@ -611,13 +629,17 @@ class Foo { }, { filename: 'test.ts', - code: "var funcExpr = function() { return 'test'; };", + code: ` +var funcExpr = function() { + return 'test'; +}; + `, options: [{ allowTypedFunctionExpressions: true }], errors: [ { messageId: 'missingReturnType', - line: 1, - endLine: 1, + line: 2, + endLine: 2, column: 16, endColumn: 26, }, @@ -626,7 +648,7 @@ class Foo { { filename: 'test.ts', - code: 'const x = (() => {}) as Foo', + code: 'const x = (() => {}) as Foo;', options: [{ allowTypedFunctionExpressions: false }], errors: [ { @@ -644,7 +666,7 @@ class Foo { interface Foo {} const x = { foo: () => {}, -} as Foo +} as Foo; `, options: [{ allowTypedFunctionExpressions: false }], errors: [ @@ -663,7 +685,7 @@ const x = { interface Foo {} const x: Foo = { foo: () => {}, -} +}; `, options: [{ allowTypedFunctionExpressions: false }], errors: [ @@ -692,7 +714,7 @@ const x: Foo = { }, { filename: 'test.ts', - code: '() => function () {};', + code: '() => function() {};', options: [{ allowHigherOrderFunctions: true }], errors: [ { @@ -700,63 +722,79 @@ const x: Foo = { line: 1, endLine: 1, column: 7, - endColumn: 18, + endColumn: 17, }, ], }, { filename: 'test.ts', - code: '() => { return () => {} };', + code: ` +() => { + return () => {}; +}; + `, options: [{ allowHigherOrderFunctions: true }], errors: [ { messageId: 'missingReturnType', - line: 1, - endLine: 1, - column: 16, - endColumn: 21, + line: 3, + endLine: 3, + column: 10, + endColumn: 15, }, ], }, { filename: 'test.ts', - code: '() => { return function () {} };', + code: ` +() => { + return function() {}; +}; + `, options: [{ allowHigherOrderFunctions: true }], errors: [ { messageId: 'missingReturnType', - line: 1, - endLine: 1, - column: 16, - endColumn: 27, + line: 3, + endLine: 3, + column: 10, + endColumn: 20, }, ], }, { filename: 'test.ts', - code: 'function fn() { return () => {} };', + code: ` +function fn() { + return () => {}; +} + `, options: [{ allowHigherOrderFunctions: true }], errors: [ { messageId: 'missingReturnType', - line: 1, - endLine: 1, - column: 24, - endColumn: 29, + line: 3, + endLine: 3, + column: 10, + endColumn: 15, }, ], }, { filename: 'test.ts', - code: 'function fn() { return function () {} };', + code: ` +function fn() { + return function() {}; +} + `, options: [{ allowHigherOrderFunctions: true }], errors: [ { messageId: 'missingReturnType', - line: 1, - endLine: 1, - column: 24, - endColumn: 35, + line: 3, + endLine: 3, + column: 10, + endColumn: 20, }, ], }, @@ -766,20 +804,22 @@ const x: Foo = { function FunctionDeclaration() { return function FunctionExpression_Within_FunctionDeclaration() { return function FunctionExpression_Within_FunctionExpression() { - return () => { // ArrowFunctionExpression_Within_FunctionExpression - return () => // ArrowFunctionExpression_Within_ArrowFunctionExpression - () => 1 // ArrowFunctionExpression_Within_ArrowFunctionExpression_WithNoBody - } - } - } + return () => { + // ArrowFunctionExpression_Within_FunctionExpression + return () => + // ArrowFunctionExpression_Within_ArrowFunctionExpression + () => 1; // ArrowFunctionExpression_Within_ArrowFunctionExpression_WithNoBody + }; + }; + }; } - `, + `, options: [{ allowHigherOrderFunctions: true }], errors: [ { messageId: 'missingReturnType', - line: 7, - endLine: 7, + line: 9, + endLine: 9, column: 11, endColumn: 16, }, @@ -787,15 +827,21 @@ function FunctionDeclaration() { }, { filename: 'test.ts', - code: '() => () => { return () => { return; } };', + code: ` +() => () => { + return () => { + return; + }; +}; + `, options: [{ allowHigherOrderFunctions: true }], errors: [ { messageId: 'missingReturnType', - line: 1, - endLine: 1, - column: 22, - endColumn: 27, + line: 3, + endLine: 3, + column: 10, + endColumn: 15, }, ], }, @@ -803,12 +849,12 @@ function FunctionDeclaration() { { filename: 'test.ts', code: ` -declare function foo(arg: () => void): void -foo(() => 1) -foo(() => {}) -foo(() => null) -foo(() => true) -foo(() => '') +declare function foo(arg: () => void): void; +foo(() => 1); +foo(() => {}); +foo(() => null); +foo(() => true); +foo(() => ''); `, options: [ { @@ -883,7 +929,7 @@ new Accumulator().accumulate(() => 1); }, { filename: 'test.ts', - code: '(() => true)()', + code: '(() => true)();', options: [ { allowTypedFunctionExpressions: false, @@ -902,22 +948,22 @@ new Accumulator().accumulate(() => 1); { filename: 'test.ts', code: ` -declare function foo(arg: { meth: () => number }): void +declare function foo(arg: { meth: () => number }): void; foo({ meth() { return 1; }, -}) +}); foo({ - meth: function () { + meth: function() { return 1; }, -}) +}); foo({ meth: () => { return 1; }, -}) +}); `, options: [ { @@ -937,7 +983,7 @@ foo({ line: 9, endLine: 9, column: 9, - endColumn: 20, + endColumn: 19, }, { messageId: 'missingReturnType', @@ -951,8 +997,8 @@ foo({ { filename: 'test.ts', code: ` -const func = (value: number) => ({ type: "X", value } as any); -const func = (value: number) => ({ type: "X", value } as Action); +const func = (value: number) => ({ type: 'X', value } as any); +const func = (value: number) => ({ type: 'X', value } as Action); `, options: [ { @@ -979,7 +1025,7 @@ const func = (value: number) => ({ type: "X", value } as Action); { filename: 'test.ts', code: ` -const func = (value: number) => ({ type: "X", value } as const); +const func = (value: number) => ({ type: 'X', value } as const); `, options: [ { @@ -996,5 +1042,39 @@ const func = (value: number) => ({ type: "X", value } as const); }, ], }, + { + filename: 'test.ts', + code: 'const log = (message: string) => void console.log(message);', + options: [ + { allowConciseArrowFunctionExpressionsStartingWithVoid: false }, + ], + errors: [ + { + messageId: 'missingReturnType', + line: 1, + endLine: 1, + column: 13, + endColumn: 33, + }, + ], + }, + { + filename: 'test.ts', + code: ` + const log = (message: string) => { + void console.log(message); + }; + `, + options: [{ allowConciseArrowFunctionExpressionsStartingWithVoid: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 2, + endLine: 2, + column: 21, + endColumn: 41, + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/explicit-member-accessibility.test.ts b/packages/eslint-plugin/tests/rules/explicit-member-accessibility.test.ts index fe0a09dd4c1..906f1b6cc60 100644 --- a/packages/eslint-plugin/tests/rules/explicit-member-accessibility.test.ts +++ b/packages/eslint-plugin/tests/rules/explicit-member-accessibility.test.ts @@ -1,5 +1,5 @@ import rule from '../../src/rules/explicit-member-accessibility'; -import { RuleTester } from '../RuleTester'; +import { RuleTester, noFormat } from '../RuleTester'; const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', @@ -109,72 +109,72 @@ class Test { filename: 'test.ts', code: ` class Test { - protected name: string - private x: number - public getX () { - return this.x + protected name: string; + private x: number; + public getX() { + return this.x; } } - `, + `, }, { filename: 'test.ts', code: ` class Test { - protected name: string - protected foo?: string - public "foo-bar"?: string + protected name: string; + protected foo?: string; + public 'foo-bar'?: string; } - `, + `, }, { filename: 'test.ts', code: ` class Test { - public constructor({x, y}: {x: number; y: number;}) {} + public constructor({ x, y }: { x: number; y: number }) {} } - `, + `, }, { filename: 'test.ts', code: ` class Test { - protected name: string - protected foo?: string - public getX () { - return this.x + protected name: string; + protected foo?: string; + public getX() { + return this.x; } } - `, + `, options: [{ accessibility: 'explicit' }], }, { filename: 'test.ts', code: ` class Test { - protected name: string - protected foo?: string - getX () { - return this.x + protected name: string; + protected foo?: string; + getX() { + return this.x; } } - `, + `, options: [{ accessibility: 'no-public' }], }, { filename: 'test.ts', code: ` class Test { - name: string - foo?: string - getX () { - return this.x + name: string; + foo?: string; + getX() { + return this.x; } get fooName(): string { - return this.foo + ' ' + this.name + return this.foo + ' ' + this.name; } } - `, + `, options: [{ accessibility: 'no-public' }], }, { @@ -182,7 +182,7 @@ class Test { code: ` class Test { private x: number; - constructor (x: number) { + constructor(x: number) { this.x = x; } get internalValue() { @@ -191,7 +191,7 @@ class Test { private set internalValue(value: number) { this.x = value; } - public square (): number { + public square(): number { return this.x * this.x; } } @@ -203,7 +203,7 @@ class Test { code: ` class Test { private x: number; - public constructor (x: number) { + public constructor(x: number) { this.x = x; } public get internalValue() { @@ -212,10 +212,10 @@ class Test { public set internalValue(value: number) { this.x = value; } - public square (): number { + public square(): number { return this.x * this.x; } - half (): number { + half(): number { return this.x / 2; } } @@ -226,7 +226,7 @@ class Test { filename: 'test.ts', code: ` class Test { - constructor(private x: number){} + constructor(private x: number) {} } `, options: [{ accessibility: 'no-public' }], @@ -235,7 +235,7 @@ class Test { filename: 'test.ts', code: ` class Test { - constructor(public x: number){} + constructor(public x: number) {} } `, options: [ @@ -249,7 +249,7 @@ class Test { filename: 'test.ts', code: ` class Test { - constructor(public foo: number){} + constructor(public foo: number) {} } `, options: [{ accessibility: 'no-public' }], @@ -258,8 +258,8 @@ class Test { filename: 'test.ts', code: ` class Test { - public getX () { - return this.x + public getX() { + return this.x; } } `, @@ -269,8 +269,8 @@ class Test { filename: 'test.ts', code: ` class Test { - public static getX () { - return this.x + public static getX() { + return this.x; } } `, @@ -280,8 +280,8 @@ class Test { filename: 'test.ts', code: ` class Test { - get getX () { - return this.x + get getX() { + return this.x; } } `, @@ -291,8 +291,8 @@ class Test { filename: 'test.ts', code: ` class Test { - getX () { - return this.x + getX() { + return this.x; } } `, @@ -300,24 +300,38 @@ class Test { }, { filename: 'test.ts', - code: 'class Test { x = 2 }', + code: ` +class Test { + x = 2; +} + `, options: [{ overrides: { properties: 'off' } }], }, { filename: 'test.ts', - code: 'class Test { private x = 2 }', + code: ` +class Test { + private x = 2; +} + `, options: [{ overrides: { properties: 'explicit' } }], }, { filename: 'test.ts', - code: `class Test { - x = 2 - private x = 2 - }`, + code: ` +class Test { + x = 2; + private x = 2; +} + `, options: [{ overrides: { properties: 'no-public' } }], }, { - code: 'class Test { constructor(private { x }: any[]) { }}', + code: ` +class Test { + constructor(private { x }: any[]) {} +} + `, options: [{ accessibility: 'no-public' }], }, ], @@ -412,12 +426,12 @@ class Test { filename: 'test.ts', code: ` class Test { - x: number - public getX () { - return this.x + x: number; + public getX() { + return this.x; } } - `, + `, errors: [ { messageId: 'missingAccessibility', @@ -431,23 +445,23 @@ class Test { ], output: ` class Test { - x: number - public getX () { - return this.x + x: number; + public getX() { + return this.x; } } - `, + `, }, { filename: 'test.ts', code: ` class Test { - private x: number - getX () { - return this.x + private x: number; + getX() { + return this.x; } } - `, + `, errors: [ { messageId: 'missingAccessibility', @@ -461,23 +475,23 @@ class Test { ], output: ` class Test { - private x: number - getX () { - return this.x + private x: number; + getX() { + return this.x; } } - `, + `, }, { filename: 'test.ts', code: ` class Test { - x?: number - getX? () { - return this.x + x?: number; + getX?() { + return this.x; } } - `, + `, errors: [ { messageId: 'missingAccessibility', @@ -500,24 +514,24 @@ class Test { ], output: ` class Test { - x?: number - getX? () { - return this.x + x?: number; + getX?() { + return this.x; } } - `, + `, }, { filename: 'test.ts', code: ` class Test { - protected name: string - protected foo?: string - public getX () { - return this.x + protected name: string; + protected foo?: string; + public getX() { + return this.x; } } - `, + `, options: [{ accessibility: 'no-public' }], errors: [ { @@ -532,25 +546,25 @@ class Test { ], output: ` class Test { - protected name: string - protected foo?: string - getX () { - return this.x + protected name: string; + protected foo?: string; + getX() { + return this.x; } } - `, + `, }, { filename: 'test.ts', code: ` class Test { - protected name: string - public foo?: string - getX () { - return this.x + protected name: string; + public foo?: string; + getX() { + return this.x; } } - `, + `, options: [{ accessibility: 'no-public' }], errors: [ { @@ -565,24 +579,24 @@ class Test { ], output: ` class Test { - protected name: string - foo?: string - getX () { - return this.x + protected name: string; + foo?: string; + getX() { + return this.x; } } - `, + `, }, { filename: 'test.ts', code: ` class Test { - public x: number - public getX () { - return this.x + public x: number; + public getX() { + return this.x; } } - `, + `, errors: [ { messageId: 'unwantedPublicAccessibility', @@ -598,19 +612,19 @@ class Test { options: [{ accessibility: 'no-public' }], output: ` class Test { - x: number - getX () { - return this.x + x: number; + getX() { + return this.x; } } - `, + `, }, { filename: 'test.ts', code: ` class Test { private x: number; - constructor (x: number) { + constructor(x: number) { this.x = x; } get internalValue() { @@ -637,7 +651,7 @@ class Test { output: ` class Test { private x: number; - constructor (x: number) { + constructor(x: number) { this.x = x; } get internalValue() { @@ -654,7 +668,7 @@ class Test { code: ` class Test { private x: number; - constructor (x: number) { + constructor(x: number) { this.x = x; } get internalValue() { @@ -685,7 +699,7 @@ class Test { output: ` class Test { private x: number; - constructor (x: number) { + constructor(x: number) { this.x = x; } get internalValue() { @@ -701,7 +715,7 @@ class Test { filename: 'test.ts', code: ` class Test { - constructor(public x: number){} + constructor(public x: number) {} public foo(): string { return 'foo'; } @@ -721,7 +735,7 @@ class Test { ], output: ` class Test { - constructor(public x: number){} + constructor(public x: number) {} public foo(): string { return 'foo'; } @@ -732,7 +746,7 @@ class Test { filename: 'test.ts', code: ` class Test { - constructor(public x: number){} + constructor(public x: number) {} } `, errors: [ @@ -744,7 +758,7 @@ class Test { ], output: ` class Test { - constructor(public x: number){} + constructor(public x: number) {} } `, }, @@ -752,7 +766,7 @@ class Test { filename: 'test.ts', code: ` class Test { - constructor(public readonly x: number){} + constructor(public readonly x: number) {} } `, options: [ @@ -770,13 +784,17 @@ class Test { ], output: ` class Test { - constructor(readonly x: number){} + constructor(readonly x: number) {} } `, }, { filename: 'test.ts', - code: 'class Test { x = 2 }', + code: ` +class Test { + x = 2; +} + `, options: [ { accessibility: 'off', @@ -786,18 +804,24 @@ class Test { errors: [ { messageId: 'missingAccessibility', - line: 1, - column: 14, + line: 3, + column: 3, }, ], - output: 'class Test { x = 2 }', + output: ` +class Test { + x = 2; +} + `, }, { filename: 'test.ts', - code: `class Test { - public x = 2 - private x = 2 - }`, + code: ` +class Test { + public x = 2; + private x = 2; +} + `, options: [ { accessibility: 'off', @@ -807,30 +831,40 @@ class Test { errors: [ { messageId: 'unwantedPublicAccessibility', - line: 2, - column: 9, + line: 3, + column: 3, }, ], - output: `class Test { - x = 2 - private x = 2 - }`, + output: ` +class Test { + x = 2; + private x = 2; +} + `, }, { - code: 'class Test { constructor(public ...x: any[]) { }}', + code: ` +class Test { + constructor(public ...x: any[]) {} +} + `, options: [{ accessibility: 'explicit' }], errors: [ { messageId: 'missingAccessibility', - line: 1, - column: 14, + line: 3, + column: 3, }, ], - output: 'class Test { constructor(public ...x: any[]) { }}', + output: ` +class Test { + constructor(public ...x: any[]) {} +} + `, }, { filename: 'test.ts', - code: ` + code: noFormat` class Test { @public public /*public*/constructor(private foo: string) {} @@ -848,7 +882,7 @@ class Test { column: 3, }, ], - output: ` + output: noFormat` class Test { @public /*public*/constructor(private foo: string) {} @@ -914,7 +948,7 @@ class Test { filename: 'test.ts', code: ` class Test { - public foo = ""; + public foo = ''; } `, options: [ @@ -931,16 +965,16 @@ class Test { ], output: ` class Test { - foo = ""; + foo = ''; } `, }, { filename: 'test.ts', - code: ` + code: noFormat` class Test { - contructor(public/* Hi there */ readonly foo) + contructor(public/* Hi there */ readonly foo); } `, options: [ @@ -958,7 +992,7 @@ class Test { ], output: ` class Test { - contructor(/* Hi there */ readonly foo) + contructor(/* Hi there */ readonly foo); } `, }, @@ -966,7 +1000,7 @@ class Test { filename: 'test.ts', code: ` class Test { - contructor(public readonly foo: string) + contructor(public readonly foo: string); } `, options: [ @@ -983,33 +1017,17 @@ class Test { ], output: ` class Test { - contructor(readonly foo: string) + contructor(readonly foo: string); } `, }, { filename: 'test.ts', - code: - 'class EnsureWhiteSPaceSpan { public constructor() {}}', - options: [ - { - accessibility: 'no-public', - overrides: { parameterProperties: 'no-public' }, - }, - ], - errors: [ - { - messageId: 'unwantedPublicAccessibility', - line: 1, - column: 30, - }, - ], - output: 'class EnsureWhiteSPaceSpan { constructor() {}}', - }, - { - filename: 'test.ts', - code: - 'class EnsureWhiteSPaceSpan { public /* */ constructor() {}}', + code: ` +class EnsureWhiteSPaceSpan { + public constructor() {} +} + `, options: [ { accessibility: 'no-public', @@ -1019,17 +1037,23 @@ class Test { errors: [ { messageId: 'unwantedPublicAccessibility', - line: 1, - column: 30, + line: 3, + column: 3, }, ], - output: - 'class EnsureWhiteSPaceSpan { /* */ constructor() {}}', + output: ` +class EnsureWhiteSPaceSpan { + constructor() {} +} + `, }, { filename: 'test.ts', - code: - 'class EnsureWhiteSPaceSpan { public /* */ constructor() {}}', + code: ` +class EnsureWhiteSPaceSpan { + public /* */ constructor() {} +} + `, options: [ { accessibility: 'no-public', @@ -1039,11 +1063,15 @@ class Test { errors: [ { messageId: 'unwantedPublicAccessibility', - line: 1, - column: 30, + line: 3, + column: 3, }, ], - output: 'class EnsureWhiteSPaceSpan { /* */ constructor() {}}', + output: ` +class EnsureWhiteSPaceSpan { + /* */ constructor() {} +} + `, }, ], }); diff --git a/packages/eslint-plugin/tests/rules/explicit-module-boundary-types.test.ts b/packages/eslint-plugin/tests/rules/explicit-module-boundary-types.test.ts index 388adb99431..5d63e8e1036 100644 --- a/packages/eslint-plugin/tests/rules/explicit-module-boundary-types.test.ts +++ b/packages/eslint-plugin/tests/rules/explicit-module-boundary-types.test.ts @@ -10,28 +10,28 @@ ruleTester.run('explicit-module-boundary-types', rule, { { code: ` function test(): void { - return; + return; } - `, + `, }, { code: ` export function test(): void { - return; + return; } - `, + `, }, { code: ` export var fn = function(): number { - return 1; + return 1; }; - `, + `, }, { code: ` export var arrowFn = (): string => 'test'; - `, + `, }, { code: ` @@ -46,7 +46,7 @@ class Test { } arrow = (): string => 'arrow'; } - `, + `, }, { code: ` @@ -61,42 +61,42 @@ export class Test { } arrow = (): string => 'arrow'; } - `, + `, }, { code: ` export function test(): void { - nested(); - return; + nested(); + return; - function nested() {} + function nested() {} } - `, + `, }, { code: ` export function test(): string { - const nested = () => 'value'; - return nested(); + const nested = () => 'value'; + return nested(); } - `, + `, }, { code: ` export function test(): string { - class Nested { - public method() { - return 'value'; - } + class Nested { + public method() { + return 'value'; } - return new Nested().method(); + } + return new Nested().method(); } - `, + `, }, { code: ` export var arrowFn: Foo = () => 'test'; - `, + `, options: [ { allowTypedFunctionExpressions: true, @@ -105,8 +105,10 @@ export var arrowFn: Foo = () => 'test'; }, { code: ` -export var funcExpr: Foo = function() { return 'test'; }; - `, +export var funcExpr: Foo = function() { + return 'test'; +}; + `, options: [ { allowTypedFunctionExpressions: true, @@ -114,18 +116,18 @@ export var funcExpr: Foo = function() { return 'test'; }; ], }, { - code: `const x = (() => {}) as Foo`, + code: 'const x = (() => {}) as Foo;', options: [{ allowTypedFunctionExpressions: true }], }, { - code: `const x = (() => {})`, + code: 'const x = (() => {});', options: [{ allowTypedFunctionExpressions: true }], }, { code: ` export const x = { foo: () => {}, -} as Foo +} as Foo; `, options: [{ allowTypedFunctionExpressions: true }], }, @@ -133,7 +135,7 @@ export const x = { code: ` export const x = { foo: () => {}, -} +}; `, options: [{ allowTypedFunctionExpressions: true }], }, @@ -141,7 +143,7 @@ export const x = { code: ` export const x: Foo = { foo: () => {}, -} +}; `, options: [{ allowTypedFunctionExpressions: true }], }, @@ -151,7 +153,7 @@ export const x: Foo = { type MethodType = () => void; export class App { - public method: MethodType = () => {} + public method: MethodType = () => {}; } `, options: [{ allowTypedFunctionExpressions: true }], @@ -169,37 +171,45 @@ export const myObj = { { code: ` export default () => (): void => {}; - `, + `, options: [{ allowHigherOrderFunctions: true }], }, { code: ` -export default () => function (): void {}; - `, +export default () => function(): void {}; + `, options: [{ allowHigherOrderFunctions: true }], }, { code: ` -export default () => { return (): void => {} }; - `, +export default () => { + return (): void => {}; +}; + `, options: [{ allowHigherOrderFunctions: true }], }, { code: ` -export default () => { return function (): void {} }; - `, +export default () => { + return function(): void {}; +}; + `, options: [{ allowHigherOrderFunctions: true }], }, { code: ` -export function fn() { return (): void => {} }; - `, +export function fn() { + return (): void => {}; +} + `, options: [{ allowHigherOrderFunctions: true }], }, { code: ` -export function fn() { return function (): void {} }; - `, +export function fn() { + return function(): void {}; +} + `, options: [{ allowHigherOrderFunctions: true }], }, { @@ -207,20 +217,26 @@ export function fn() { return function (): void {} }; export function FunctionDeclaration() { return function FunctionExpression_Within_FunctionDeclaration() { return function FunctionExpression_Within_FunctionExpression() { - return () => { // ArrowFunctionExpression_Within_FunctionExpression - return () => // ArrowFunctionExpression_Within_ArrowFunctionExpression - (): number => 1 // ArrowFunctionExpression_Within_ArrowFunctionExpression_WithNoBody - } - } - } + return () => { + // ArrowFunctionExpression_Within_FunctionExpression + return () => + // ArrowFunctionExpression_Within_ArrowFunctionExpression + (): number => 1; // ArrowFunctionExpression_Within_ArrowFunctionExpression_WithNoBody + }; + }; + }; } - `, + `, options: [{ allowHigherOrderFunctions: true }], }, { code: ` -export default () => () => { return (): void => { return; } }; - `, +export default () => () => { + return (): void => { + return; + }; +}; + `, options: [{ allowHigherOrderFunctions: true }], }, { @@ -243,9 +259,9 @@ new Accumulator().accumulate(() => 1); }, { code: ` -export const func1 = (value: number) => (({ type: "X", value }) as const); -export const func2 = (value: number) => ({ type: "X", value } as const); -export const func3 = (value: number) => (x as const); +export const func1 = (value: number) => ({ type: 'X', value } as const); +export const func2 = (value: number) => ({ type: 'X', value } as const); +export const func3 = (value: number) => x as const; export const func4 = (value: number) => x as const; `, options: [ @@ -257,7 +273,7 @@ export const func4 = (value: number) => x as const; { code: ` export const func1 = (value: string) => value; -export const func2 = (value: number) => ({ type: "X", value }); +export const func2 = (value: number) => ({ type: 'X', value }); `, options: [ { @@ -315,13 +331,8 @@ export class Test { { code: ` export const Foo: FC = () => ( -
{}} - b={function (e) {}} - c={function foo(e) {}} - > -
-) +
{}} b={function(e) {}} c={function foo(e) {}}>
+); `, parserOptions: { ecmaFeatures: { jsx: true }, @@ -329,26 +340,108 @@ export const Foo: FC = () => ( }, { code: ` -export const Foo: JSX.Element = -
{}} - b={function (e) {}} - c={function foo(e) {}} - > -
+export const Foo: JSX.Element = ( +
{}} b={function(e) {}} c={function foo(e) {}}>
+); `, parserOptions: { ecmaFeatures: { jsx: true }, }, }, + { + code: ` +const test = (): void => { + return; +}; +export default test; + `, + options: [{ shouldTrackReferences: true }], + }, + { + code: ` +function test(): void { + return; +} +export default test; + `, + options: [{ shouldTrackReferences: true }], + }, + { + code: ` +const test = (): void => { + return; +}; +export default [test]; + `, + options: [{ shouldTrackReferences: true }], + }, + { + code: ` +function test(): void { + return; +} +export default [test]; + `, + options: [{ shouldTrackReferences: true }], + }, + { + code: ` +const test = (): void => { + return; +}; +export default { test }; + `, + options: [{ shouldTrackReferences: true }], + }, + { + code: ` +function test(): void { + return; +} +export default { test }; + `, + options: [{ shouldTrackReferences: true }], + }, + { + code: ` +const foo = (arg => arg) as Foo; +export default foo; + `, + options: [{ shouldTrackReferences: true }], + }, + { + code: ` +let foo = (arg => arg) as Foo; +foo = 3; +export default foo; + `, + options: [{ shouldTrackReferences: true }], + }, + { + code: ` +class Foo { + bar = (arg: string): string => arg; +} +export default { Foo }; + `, + options: [{ shouldTrackReferences: true }], + }, + { + code: ` +class Foo { + bar(): void { + return; + } +} +export default { Foo }; + `, + options: [{ shouldTrackReferences: true }], + }, ], invalid: [ { code: ` -export function test( - a: number, - b: number, -) { +export function test(a: number, b: number) { return; } `, @@ -356,9 +449,9 @@ export function test( { messageId: 'missingReturnType', line: 2, - endLine: 5, + endLine: 2, column: 8, - endColumn: 2, + endColumn: 43, }, ], }, @@ -413,13 +506,13 @@ export var arrowFn = () => 'test'; export class Test { constructor() {} get prop() { - return 1; + return 1; } set prop(value) {} method() { return; } - arrow = (arg) => 'arrow'; + arrow = arg => 'arrow'; private method() { return; } @@ -452,14 +545,14 @@ export class Test { line: 11, endLine: 11, column: 11, - endColumn: 19, + endColumn: 17, }, { messageId: 'missingArgType', line: 11, endLine: 11, column: 11, - endColumn: 27, + endColumn: 25, }, ], }, @@ -467,11 +560,11 @@ export class Test { code: ` export class Foo { public a = () => {}; - public b = function () {}; + public b = function() {}; public c = function test() {}; static d = () => {}; - static e = function () {}; + static e = function() {}; } `, errors: [ @@ -487,7 +580,7 @@ export class Foo { line: 4, endLine: 4, column: 14, - endColumn: 25, + endColumn: 24, }, { messageId: 'missingReturnType', @@ -508,12 +601,12 @@ export class Foo { line: 8, endLine: 8, column: 14, - endColumn: 25, + endColumn: 24, }, ], }, { - code: 'export default () => true ? (() => {}) : ((): void => {});', + code: 'export default () => (true ? () => {} : (): void => {});', errors: [ { messageId: 'missingReturnType', @@ -545,20 +638,24 @@ export class Foo { ], }, { - code: "export var funcExpr = function() { return 'test'; };", + code: ` +export var funcExpr = function() { + return 'test'; +}; + `, options: [{ allowTypedFunctionExpressions: true }], errors: [ { messageId: 'missingReturnType', - line: 1, - endLine: 1, + line: 2, + endLine: 2, column: 23, endColumn: 33, }, ], }, { - code: 'export const x = (() => {}) as Foo', + code: 'export const x = (() => {}) as Foo;', options: [{ allowTypedFunctionExpressions: false }], errors: [ { @@ -575,7 +672,7 @@ export class Foo { interface Foo {} export const x = { foo: () => {}, -} as Foo +} as Foo; `, options: [{ allowTypedFunctionExpressions: false }], errors: [ @@ -593,7 +690,7 @@ export const x = { interface Foo {} export const x: Foo = { foo: () => {}, -} +}; `, options: [{ allowTypedFunctionExpressions: false }], errors: [ @@ -620,7 +717,7 @@ export const x: Foo = { ], }, { - code: 'export default () => function () {};', + code: 'export default () => function() {};', options: [{ allowHigherOrderFunctions: true }], errors: [ { @@ -628,59 +725,75 @@ export const x: Foo = { line: 1, endLine: 1, column: 22, - endColumn: 33, + endColumn: 32, }, ], }, { - code: 'export default () => { return () => {} };', + code: ` +export default () => { + return () => {}; +}; + `, options: [{ allowHigherOrderFunctions: true }], errors: [ { messageId: 'missingReturnType', - line: 1, - endLine: 1, - column: 31, - endColumn: 36, + line: 3, + endLine: 3, + column: 10, + endColumn: 15, }, ], }, { - code: 'export default () => { return function () {} };', + code: ` +export default () => { + return function() {}; +}; + `, options: [{ allowHigherOrderFunctions: true }], errors: [ { messageId: 'missingReturnType', - line: 1, - endLine: 1, - column: 31, - endColumn: 42, + line: 3, + endLine: 3, + column: 10, + endColumn: 20, }, ], }, { - code: 'export function fn() { return () => {} };', + code: ` +export function fn() { + return () => {}; +} + `, options: [{ allowHigherOrderFunctions: true }], errors: [ { messageId: 'missingReturnType', - line: 1, - endLine: 1, - column: 31, - endColumn: 36, + line: 3, + endLine: 3, + column: 10, + endColumn: 15, }, ], }, { - code: 'export function fn() { return function () {} };', + code: ` +export function fn() { + return function() {}; +} + `, options: [{ allowHigherOrderFunctions: true }], errors: [ { messageId: 'missingReturnType', - line: 1, - endLine: 1, - column: 31, - endColumn: 42, + line: 3, + endLine: 3, + column: 10, + endColumn: 20, }, ], }, @@ -689,40 +802,48 @@ export const x: Foo = { export function FunctionDeclaration() { return function FunctionExpression_Within_FunctionDeclaration() { return function FunctionExpression_Within_FunctionExpression() { - return () => { // ArrowFunctionExpression_Within_FunctionExpression - return () => // ArrowFunctionExpression_Within_ArrowFunctionExpression - () => 1 // ArrowFunctionExpression_Within_ArrowFunctionExpression_WithNoBody - } - } - } + return () => { + // ArrowFunctionExpression_Within_FunctionExpression + return () => + // ArrowFunctionExpression_Within_ArrowFunctionExpression + () => 1; // ArrowFunctionExpression_Within_ArrowFunctionExpression_WithNoBody + }; + }; + }; } - `, + `, options: [{ allowHigherOrderFunctions: true }], errors: [ { messageId: 'missingReturnType', - line: 7, - endLine: 7, + line: 9, + endLine: 9, column: 11, endColumn: 16, }, ], }, { - code: 'export default () => () => { return () => { return; } };', + code: ` +export default () => () => { + return () => { + return; + }; +}; + `, options: [{ allowHigherOrderFunctions: true }], errors: [ { messageId: 'missingReturnType', - line: 1, - endLine: 1, - column: 37, - endColumn: 42, + line: 3, + endLine: 3, + column: 10, + endColumn: 15, }, ], }, { - code: 'export default (() => true)()', + code: 'export default (() => true)();', options: [ { allowTypedFunctionExpressions: false, @@ -740,8 +861,8 @@ export function FunctionDeclaration() { }, { code: ` -export const func1 = (value: number) => ({ type: "X", value } as any); -export const func2 = (value: number) => ({ type: "X", value } as Action); +export const func1 = (value: number) => ({ type: 'X', value } as any); +export const func2 = (value: number) => ({ type: 'X', value } as Action); `, options: [ { @@ -767,7 +888,7 @@ export const func2 = (value: number) => ({ type: "X", value } as Action); }, { code: ` -export const func = (value: number) => ({ type: "X", value } as const); +export const func = (value: number) => ({ type: 'X', value } as const); `, options: [ { @@ -834,19 +955,23 @@ export const func2 = (value: number) => value; ], }, { - code: 'export function fn(test): string { return "123" };', + code: ` +export function fn(test): string { + return '123'; +} + `, errors: [ { messageId: 'missingArgType', - line: 1, - endLine: 1, + line: 2, + endLine: 4, column: 8, - endColumn: 50, + endColumn: 2, }, ], }, { - code: 'export const fn = (one: number, two): string => "123";', + code: "export const fn = (one: number, two): string => '123';", errors: [ { messageId: 'missingArgType', @@ -889,5 +1014,271 @@ export const func2 = (value: number) => value; }, ], }, + { + code: ` +const foo = arg => arg; +export default foo; + `, + options: [{ shouldTrackReferences: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 2, + }, + { + messageId: 'missingArgType', + line: 2, + }, + ], + }, + { + code: ` +const foo = arg => arg; +export = foo; + `, + options: [{ shouldTrackReferences: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 2, + }, + { + messageId: 'missingArgType', + line: 2, + }, + ], + }, + { + code: ` +let foo = (arg: number): number => arg; +foo = arg => arg; +export default foo; + `, + options: [{ shouldTrackReferences: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 3, + }, + { + messageId: 'missingArgType', + line: 3, + }, + ], + }, + { + code: ` +const foo = arg => arg; +export default [foo]; + `, + options: [{ shouldTrackReferences: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 2, + }, + { + messageId: 'missingArgType', + line: 2, + }, + ], + }, + { + code: ` +const foo = arg => arg; +export default { foo }; + `, + options: [{ shouldTrackReferences: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 2, + }, + { + messageId: 'missingArgType', + line: 2, + }, + ], + }, + { + code: ` +function foo(arg) { + return arg; +} +export default foo; + `, + options: [{ shouldTrackReferences: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 2, + }, + { + messageId: 'missingArgType', + line: 2, + }, + ], + }, + { + code: ` +function foo(arg) { + return arg; +} +export default [foo]; + `, + options: [{ shouldTrackReferences: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 2, + }, + { + messageId: 'missingArgType', + line: 2, + }, + ], + }, + { + code: ` +function foo(arg) { + return arg; +} +export default { foo }; + `, + options: [{ shouldTrackReferences: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 2, + }, + { + messageId: 'missingArgType', + line: 2, + }, + ], + }, + { + code: ` +const bar = function foo(arg) { + return arg; +}; +export default { bar }; + `, + options: [{ shouldTrackReferences: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 2, + }, + { + messageId: 'missingArgType', + line: 2, + }, + ], + }, + { + code: ` +class Foo { + bool(arg) { + return arg; + } +} +export default Foo; + `, + options: [{ shouldTrackReferences: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 3, + }, + { + messageId: 'missingArgType', + line: 3, + }, + ], + }, + { + code: ` +class Foo { + bool = arg => { + return arg; + }; +} +export default Foo; + `, + options: [{ shouldTrackReferences: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 3, + }, + { + messageId: 'missingArgType', + line: 3, + }, + ], + }, + { + code: ` +class Foo { + bool = function(arg) { + return arg; + }; +} +export default Foo; + `, + options: [{ shouldTrackReferences: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 3, + }, + { + messageId: 'missingArgType', + line: 3, + }, + ], + }, + { + code: ` +class Foo { + bool = function(arg) { + return arg; + }; +} +export default [Foo]; + `, + options: [{ shouldTrackReferences: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 3, + }, + { + messageId: 'missingArgType', + line: 3, + }, + ], + }, + { + code: ` +let test = arg => argl; +test = (): void => { + return; +}; +export default test; + `, + options: [{ shouldTrackReferences: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 2, + }, + { + messageId: 'missingArgType', + line: 2, + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/func-call-spacing.test.ts b/packages/eslint-plugin/tests/rules/func-call-spacing.test.ts index e2250716a07..db884fd3cf1 100644 --- a/packages/eslint-plugin/tests/rules/func-call-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/func-call-spacing.test.ts @@ -1,3 +1,8 @@ +/* eslint-disable eslint-comments/no-use */ +// this rule tests the spacing, which prettier will want to fix and break the tests +/* eslint "@typescript-eslint/internal/plugin-test-formatting": ["error", { formatWithPrettier: false }] */ +/* eslint-enable eslint-comments/no-use */ + import { TSESLint } from '@typescript-eslint/experimental-utils'; import rule, { MessageIds, Options } from '../../src/rules/func-call-spacing'; import { RuleTester } from '../RuleTester'; @@ -180,7 +185,7 @@ ruleTester.run('func-call-spacing', rule, { this.cancelled.add(request) this.decrement(request) (request.reject(new api.Cancel())) - `, + `, output: null, // no change errors: [ { @@ -208,7 +213,7 @@ var a = foo code: ` var a = foo (baz()) - `, + `, output: null, // no change errors: [ { diff --git a/packages/eslint-plugin/tests/rules/generic-type-naming.test.ts b/packages/eslint-plugin/tests/rules/generic-type-naming.test.ts index 11f7d1618bb..da26e9675b6 100644 --- a/packages/eslint-plugin/tests/rules/generic-type-naming.test.ts +++ b/packages/eslint-plugin/tests/rules/generic-type-naming.test.ts @@ -7,35 +7,60 @@ const ruleTester = new RuleTester({ ruleTester.run('generic-type-naming', rule, { valid: [ - { code: 'class { }', options: [] }, - { code: 'type ReadOnly = {}', options: [] }, - { code: 'interface SimpleMap { }', options: [] }, - { code: 'function get() {}', options: [] }, - { code: 'interface GenericIdentityFn { (arg: T): T }', options: [] }, - { code: 'class { }', options: ['^x+$'] }, - { code: 'class
{ }', options: ['^[A-Z]$'] }, - { - code: 'class extends B implements Foo { }', + { + code: 'class {}', + options: [], + }, + { + code: 'type ReadOnly = {};', + options: [], + }, + { + code: 'interface SimpleMap {}', + options: [], + }, + { + code: 'function get() {}', + options: [], + }, + { + code: ` +interface GenericIdentityFn { + (arg: T): T; +} + `, + options: [], + }, + { + code: 'class {}', + options: ['^x+$'], + }, + { + code: 'class {}', + options: ['^[A-Z]$'], + }, + { + code: 'class extends B implements Foo {}', options: ['^[A-Z]$'], }, { code: ` class extends B implements Foo { - test () { - type Foo = Bar - } + test() { + type Foo = Bar; + } } - `, + `, options: ['^[A-Z]$'], }, { - code: 'class CounterContainer extends Container { }', + code: 'class CounterContainer extends Container {}', options: ['^T$'], }, ], invalid: [ { - code: 'class { }', + code: 'class {}', options: [], errors: [ { @@ -49,7 +74,7 @@ class extends B implements Foo { ], }, { - code: 'class { }', + code: 'class {}', options: ['^[A-Z]+$'], errors: [ { @@ -61,7 +86,7 @@ class extends B implements Foo { ], }, { - code: 'interface SimpleMap { }', + code: 'interface SimpleMap {}', options: ['^[A-Z]+$'], errors: [ { @@ -73,7 +98,7 @@ class extends B implements Foo { ], }, { - code: 'type R = {}', + code: 'type R = {};', options: ['^[A-Z]+$'], errors: [ { @@ -97,25 +122,29 @@ class extends B implements Foo { ], }, { - code: 'interface GenericIdentityFn { (arg: x): x }', + code: ` +interface GenericIdentityFn { + (arg: x): x; +} + `, options: ['^[A-Z]+$'], errors: [ { messageId: 'paramNotMatchRule', data: { name: 'x', rule: '^[A-Z]+$' }, - line: 1, - column: 32, + line: 3, + column: 4, }, ], }, { code: ` class extends B implements Foo { - test () { - type Foo = Bar - } + test() { + type Foo = Bar; + } } - `, + `, options: ['^[A-Z][0-9]$'], errors: [ { @@ -128,24 +157,24 @@ class extends B implements Foo { messageId: 'paramNotMatchRule', data: { name: 'Z', rule: '^[A-Z][0-9]$' }, line: 3, - column: 10, + column: 8, }, { messageId: 'paramNotMatchRule', data: { name: 'T', rule: '^[A-Z][0-9]$' }, line: 4, - column: 18, + column: 14, }, ], }, { code: ` abstract class extends B implements Foo { - test () { - type Foo = Bar - } + test() { + type Foo = Bar; + } } - `, + `, options: ['^[A-Z][0-9]$'], errors: [ { @@ -164,13 +193,13 @@ abstract class extends B implements Foo { messageId: 'paramNotMatchRule', data: { name: 'Z', rule: '^[A-Z][0-9]$' }, line: 3, - column: 10, + column: 8, }, { messageId: 'paramNotMatchRule', data: { name: 'T', rule: '^[A-Z][0-9]$' }, line: 4, - column: 18, + column: 14, }, ], }, diff --git a/packages/eslint-plugin/tests/rules/indent/indent-eslint.test.ts b/packages/eslint-plugin/tests/rules/indent/indent-eslint.test.ts index d726a5e2c95..a9dfda6b127 100644 --- a/packages/eslint-plugin/tests/rules/indent/indent-eslint.test.ts +++ b/packages/eslint-plugin/tests/rules/indent/indent-eslint.test.ts @@ -4,6 +4,11 @@ // NOTE - this test suite is intentionally kept in a separate file to our // custom tests. This is to keep a clear boundary between the two. +/* eslint-disable eslint-comments/no-use */ +// this rule tests the spacing, which prettier will want to fix and break the tests +/* eslint "@typescript-eslint/internal/plugin-test-formatting": ["error", { formatWithPrettier: false }] */ +/* eslint-enable eslint-comments/no-use */ + import { AST_TOKEN_TYPES, AST_NODE_TYPES, diff --git a/packages/eslint-plugin/tests/rules/indent/indent.test.ts b/packages/eslint-plugin/tests/rules/indent/indent.test.ts index fe88d3de17a..6cec699c053 100644 --- a/packages/eslint-plugin/tests/rules/indent/indent.test.ts +++ b/packages/eslint-plugin/tests/rules/indent/indent.test.ts @@ -1,3 +1,8 @@ +/* eslint-disable eslint-comments/no-use */ +// this rule tests the spacing, which prettier will want to fix and break the tests +/* eslint "@typescript-eslint/internal/plugin-test-formatting": ["error", { formatWithPrettier: false }] */ +/* eslint-enable eslint-comments/no-use */ + import { AST_NODE_TYPES, TSESLint, @@ -704,7 +709,7 @@ export default class App extends Vue return this.$store.state.errorHandler.error } } - `, + `, // https://github.com/eslint/typescript-eslint-parser/issues/474 ` /** @@ -713,7 +718,7 @@ export default class App extends Vue * @returns {string} */ function foo(name: string, age: number): string {} - `, + `, ` const firebaseApp = firebase.apps.length ? firebase.app() @@ -725,7 +730,7 @@ const firebaseApp = firebase.apps.length storageBucket: __FIREBASE_STORAGE_BUCKET__, messagingSenderId: __FIREBASE_MESSAGING_SENDER_ID__, }) - `, + `, // https://github.com/bradzacher/eslint-plugin-typescript/issues/271 { code: ` @@ -734,7 +739,7 @@ const foo = { b: 2 }, bar = 1; - `, + `, options: [4, { VariableDeclarator: { const: 3 } }], }, { @@ -744,7 +749,7 @@ const foo : Foo = { b: 2 }, bar = 1; - `, + `, options: [4, { VariableDeclarator: { const: 3 } }], }, { @@ -756,7 +761,7 @@ const name: string = ' Typescript ' greeting: string = (" Hello " + name) .toUpperCase() .trim(); - `, + `, options: [2, { VariableDeclarator: { const: 3 } }], }, { @@ -768,15 +773,15 @@ const div: JQuery = $('
') button: JQuery = $('