diff --git a/.all-contributorsrc b/.all-contributorsrc index b6b3e43aa31c..f44ccc64c550 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -23,13 +23,6 @@ "profile": "https://github.com/armano2", "contributions": [] }, - { - "login": "soda0289", - "name": "Reyad Attiyat", - "avatar_url": "https://avatars1.githubusercontent.com/u/2373964?v=4", - "profile": "https://github.com/soda0289", - "contributions": [] - }, { "login": "bradzacher", "name": "Brad Zacher", @@ -37,6 +30,13 @@ "profile": "https://github.com/bradzacher", "contributions": [] }, + { + "login": "soda0289", + "name": "Reyad Attiyat", + "avatar_url": "https://avatars1.githubusercontent.com/u/2373964?v=4", + "profile": "https://github.com/soda0289", + "contributions": [] + }, { "login": "weirdpattern", "name": "Patricio Trevino", @@ -60,7 +60,7 @@ }, { "login": "uniqueiniquity", - "name": "Benjamin Lichtman", + "name": "Ben Lichtman", "avatar_url": "https://avatars1.githubusercontent.com/u/9092011?v=4", "profile": "https://github.com/uniqueiniquity", "contributions": [] @@ -79,6 +79,20 @@ "profile": "https://github.com/Pajn", "contributions": [] }, + { + "login": "mysticatea", + "name": "Toru Nagashima", + "avatar_url": "https://avatars2.githubusercontent.com/u/1937871?v=4", + "profile": "https://github.com/mysticatea", + "contributions": [] + }, + { + "login": "JoshuaKGoldberg", + "name": "Josh Goldberg", + "avatar_url": "https://avatars1.githubusercontent.com/u/3335181?v=4", + "profile": "https://github.com/JoshuaKGoldberg", + "contributions": [] + }, { "login": "azz", "name": "Lucas Azzola", @@ -101,10 +115,10 @@ "contributions": [] }, { - "login": "mysticatea", - "name": "Toru Nagashima", - "avatar_url": "https://avatars2.githubusercontent.com/u/1937871?v=4", - "profile": "https://github.com/mysticatea", + "login": "scottohara", + "name": "Scott O'Hara", + "avatar_url": "https://avatars3.githubusercontent.com/u/289327?v=4", + "profile": "https://github.com/scottohara", "contributions": [] }, { @@ -121,6 +135,27 @@ "profile": "https://github.com/lukyth", "contributions": [] }, + { + "login": "ldrick", + "name": "Ricky Lippmann", + "avatar_url": "https://avatars3.githubusercontent.com/u/3674067?v=4", + "profile": "https://github.com/ldrick", + "contributions": [] + }, + { + "login": "SimenB", + "name": "Simen Bekkhus", + "avatar_url": "https://avatars1.githubusercontent.com/u/1404810?v=4", + "profile": "https://github.com/SimenB", + "contributions": [] + }, + { + "login": "gavinbarron", + "name": "Gavin Barron", + "avatar_url": "https://avatars2.githubusercontent.com/u/7122716?v=4", + "profile": "https://github.com/gavinbarron", + "contributions": [] + }, { "login": "platinumazure", "name": "Kevin Partington", @@ -128,18 +163,39 @@ "profile": "https://github.com/platinumazure", "contributions": [] }, + { + "login": "duailibe", + "name": "Lucas Duailibe", + "avatar_url": "https://avatars3.githubusercontent.com/u/1574588?v=4", + "profile": "https://github.com/duailibe", + "contributions": [] + }, + { + "login": "octogonz", + "name": "Pete Gonzalez", + "avatar_url": "https://avatars0.githubusercontent.com/u/4673363?v=4", + "profile": "https://github.com/octogonz", + "contributions": [] + }, { "login": "mightyiam", - "name": "Shahar Or", + "name": "Shahar Dawn Or", "avatar_url": "https://avatars2.githubusercontent.com/u/635591?v=4", "profile": "https://github.com/mightyiam", "contributions": [] }, { - "login": "invalid-email-address", - "name": "Check your git settings!", - "avatar_url": "https://avatars0.githubusercontent.com/u/148100?v=4", - "profile": "https://github.com/invalid-email-address", + "login": "a-tarasyuk", + "name": "Alexander T.", + "avatar_url": "https://avatars0.githubusercontent.com/u/509265?v=4", + "profile": "https://github.com/a-tarasyuk", + "contributions": [] + }, + { + "login": "webschik", + "name": "Denys Kniazevych", + "avatar_url": "https://avatars2.githubusercontent.com/u/1665314?v=4", + "profile": "https://github.com/webschik", "contributions": [] }, { @@ -155,7 +211,21 @@ "avatar_url": "https://avatars1.githubusercontent.com/u/17216317?v=4", "profile": "https://github.com/g-plane", "contributions": [] + }, + { + "login": "ThomasdenH", + "name": "Thomas den Hollander", + "avatar_url": "https://avatars0.githubusercontent.com/u/3889750?v=4", + "profile": "https://github.com/ThomasdenH", + "contributions": [] + }, + { + "login": "madbence", + "name": "Bence Dányi", + "avatar_url": "https://avatars2.githubusercontent.com/u/296735?v=4", + "profile": "https://github.com/madbence", + "contributions": [] } ], - "contributorsPerLine": 7 + "contributorsPerLine": 5 } \ No newline at end of file diff --git a/.gitattributes b/.gitattributes index 8db1f2632df7..73968f2a6144 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,7 @@ * text=auto *.js eol=lf +*.json eol=lf +*.md eol=lf *.ts eol=lf *.tsx eol=lf *.yml eol=lf diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 000000000000..e219ef75b4fd --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,37 @@ +--- +name: catch-all template +about: Provides some general structure to issues that people choose to log outside the normal flow +title: '' +labels: triage +assignees: '' +--- + +**What were you trying to do?** + +```json +{ + "rules": { + "@typescript-eslint/": [""] + } +} +``` + +```ts +// Put your code here +``` + +**What did you expect to happen?** + +**What actually happened?** + +**Versions** + +| package | version | +| --------------------------------------- | ------- | +| `@typescript-eslint/eslint-plugin` | `X.Y.Z` | +| `@typescript-eslint/parser` | `X.Y.Z` | +| `@typescript-eslint/typescript-estree` | `X.Y.Z` | +| `@typescript-eslint/experimental-utils` | `X.Y.Z` | +| `TypeScript` | `X.Y.Z` | +| `node` | `X.Y.Z` | +| `npm` | `X.Y.Z` | diff --git a/.github/ISSUE_TEMPLATE/eslint-plugin-typescript.md b/.github/ISSUE_TEMPLATE/eslint-plugin-typescript.md index 010b5c01cb3c..ad7d8fb4944f 100644 --- a/.github/ISSUE_TEMPLATE/eslint-plugin-typescript.md +++ b/.github/ISSUE_TEMPLATE/eslint-plugin-typescript.md @@ -22,7 +22,7 @@ Are you opening an issue because the rule you're trying to use is not found? 1) Check the releases log: https://github.com/typescript-eslint/typescript-eslint/releases - If the rule isn't listed there, then chances are it hasn't been released to the main npm tag yet. 2) Try installing the `canary` tag: `npm i @typescript-eslint/eslint-plugin@canary`. - - The canary tag is built for every commit to master, so it contains the bleeding edge build. + - The canary tag is built for every commit to master, so it contains the bleeding edge build. 3) If ESLint still can't find the rule, then consider reporting an issue. --> diff --git a/.github/ISSUE_TEMPLATE/typescript-eslint-utils.md b/.github/ISSUE_TEMPLATE/typescript-eslint-utils.md new file mode 100644 index 000000000000..8403568e4ac2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/typescript-eslint-utils.md @@ -0,0 +1,20 @@ +--- +name: '@typescript-eslint/experimental-utils' +about: Report an issue with the `@typescript-eslint/experimental-utils` package +title: '' +labels: 'package: utils, triage' +assignees: '' +--- + +**What did you expect to happen?** + +**What actually happened?** + +**Versions** + +| package | version | +| --------------------------------------- | ------- | +| `@typescript-eslint/experimental-utils` | `X.Y.Z` | +| `TypeScript` | `X.Y.Z` | +| `node` | `X.Y.Z` | +| `npm` | `X.Y.Z` | diff --git a/.gitignore b/.gitignore index 55599f0c1844..3a06baccf6f5 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,6 @@ jspm_packages/ .DS_Store .idea dist + +# Editor-specific metadata folders +.vs diff --git a/.prettierignore b/.prettierignore index a86a2f04fc90..aed86e816d43 100644 --- a/.prettierignore +++ b/.prettierignore @@ -9,6 +9,8 @@ packages/eslint-plugin-tslint/tests/test-tslint-rules-directory/alwaysFailRule.js .github packages/eslint-plugin/src/configs/*.json +.all-contributorsrc +CONTRIBUTORS.md # Ignore CHANGELOG.md files to avoid issues with automated release job CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 306e15820217..547ce16d62b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,36 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.1.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.0.0...v2.1.0) (2019-09-02) + + +### Bug Fixes + +* **eslint-plugin:** [member-naming] should match constructor args ([#771](https://github.com/typescript-eslint/typescript-eslint/issues/771)) ([b006667](https://github.com/typescript-eslint/typescript-eslint/commit/b006667)) +* **eslint-plugin:** [no-inferrable-types] ignore optional props ([#918](https://github.com/typescript-eslint/typescript-eslint/issues/918)) ([a4e625f](https://github.com/typescript-eslint/typescript-eslint/commit/a4e625f)) +* **eslint-plugin:** [promise-function-async] Allow async get/set ([#820](https://github.com/typescript-eslint/typescript-eslint/issues/820)) ([cddfdca](https://github.com/typescript-eslint/typescript-eslint/commit/cddfdca)) +* **eslint-plugin:** [require-await] Allow concise arrow function bodies ([#826](https://github.com/typescript-eslint/typescript-eslint/issues/826)) ([29fddfd](https://github.com/typescript-eslint/typescript-eslint/commit/29fddfd)) +* **eslint-plugin:** [typedef] don't flag destructuring when variables is disabled ([#819](https://github.com/typescript-eslint/typescript-eslint/issues/819)) ([5603473](https://github.com/typescript-eslint/typescript-eslint/commit/5603473)) +* **eslint-plugin:** [typedef] handle AssignmentPattern inside TSParameterProperty ([#923](https://github.com/typescript-eslint/typescript-eslint/issues/923)) ([6bd7f2d](https://github.com/typescript-eslint/typescript-eslint/commit/6bd7f2d)) +* **eslint-plugin:** [unbound-method] Allow typeof expressions (Fixes [#692](https://github.com/typescript-eslint/typescript-eslint/issues/692)) ([#904](https://github.com/typescript-eslint/typescript-eslint/issues/904)) ([344bafe](https://github.com/typescript-eslint/typescript-eslint/commit/344bafe)) +* **eslint-plugin:** [unbound-method] false positive in equality comparisons ([#914](https://github.com/typescript-eslint/typescript-eslint/issues/914)) ([29a01b8](https://github.com/typescript-eslint/typescript-eslint/commit/29a01b8)) +* **eslint-plugin:** [unified-signatures] type comparison and exported nodes ([#839](https://github.com/typescript-eslint/typescript-eslint/issues/839)) ([580eceb](https://github.com/typescript-eslint/typescript-eslint/commit/580eceb)) +* **eslint-plugin:** readme typo ([#867](https://github.com/typescript-eslint/typescript-eslint/issues/867)) ([5eb40dc](https://github.com/typescript-eslint/typescript-eslint/commit/5eb40dc)) +* **typescript-estree:** improve missing project file error msg ([#866](https://github.com/typescript-eslint/typescript-eslint/issues/866)) ([8f3b0a8](https://github.com/typescript-eslint/typescript-eslint/commit/8f3b0a8)), closes [#853](https://github.com/typescript-eslint/typescript-eslint/issues/853) + + +### Features + +* [no-unnecessary-type-assertion] allow `as const` arrow functions ([#876](https://github.com/typescript-eslint/typescript-eslint/issues/876)) ([14c6f80](https://github.com/typescript-eslint/typescript-eslint/commit/14c6f80)) +* **eslint-plugin:** [expl-func-ret-type] make error loc smaller ([#919](https://github.com/typescript-eslint/typescript-eslint/issues/919)) ([65eb993](https://github.com/typescript-eslint/typescript-eslint/commit/65eb993)) +* **eslint-plugin:** [no-type-alias] support tuples ([#775](https://github.com/typescript-eslint/typescript-eslint/issues/775)) ([c68e033](https://github.com/typescript-eslint/typescript-eslint/commit/c68e033)) +* **eslint-plugin:** add quotes [extension] ([#762](https://github.com/typescript-eslint/typescript-eslint/issues/762)) ([9f82099](https://github.com/typescript-eslint/typescript-eslint/commit/9f82099)) +* **typescript-estree:** Accept a glob pattern for `options.project` ([#806](https://github.com/typescript-eslint/typescript-eslint/issues/806)) ([9e5f21e](https://github.com/typescript-eslint/typescript-eslint/commit/9e5f21e)) + + + + + # [2.0.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.13.0...v2.0.0) (2019-08-13) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index d0f6e0b84d64..d458acc3c542 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -3,8 +3,58 @@ Thanks goes to these wonderful people ([emoji key](https://github.com/all-contributors/all-contributors#emoji-key)): - -
James Henry
James Henry

Armano
Armano

Reyad Attiyat
Reyad Attiyat

Brad Zacher
Brad Zacher

Patricio Trevino
Patricio Trevino

Nicholas C. Zakas
Nicholas C. Zakas

Jed Fox
Jed Fox

Benjamin Lichtman
Benjamin Lichtman

Kai Cataldo
Kai Cataldo

Rasmus Eneman
Rasmus Eneman

Lucas Azzola
Lucas Azzola

Danny Fritz
Danny Fritz

Ika
Ika

Toru Nagashima
Toru Nagashima

mackie
mackie

Kanitkorn Sujautra
Kanitkorn Sujautra

Kevin Partington
Kevin Partington

Shahar Or
Shahar Or

Check your git settings!
Check your git settings!

Philipp A.
Philipp A.

Pig Fang
Pig Fang

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
James Henry
James Henry

Armano
Armano

Brad Zacher
Brad Zacher

Reyad Attiyat
Reyad Attiyat

Patricio Trevino
Patricio Trevino

Nicholas C. Zakas
Nicholas C. Zakas

Jed Fox
Jed Fox

Ben Lichtman
Ben Lichtman

Kai Cataldo
Kai Cataldo

Rasmus Eneman
Rasmus Eneman

Toru Nagashima
Toru Nagashima

Josh Goldberg
Josh Goldberg

Lucas Azzola
Lucas Azzola

Danny Fritz
Danny Fritz

Ika
Ika

Scott O'Hara
Scott O'Hara

mackie
mackie

Kanitkorn Sujautra
Kanitkorn Sujautra

Ricky Lippmann
Ricky Lippmann

Simen Bekkhus
Simen Bekkhus

Gavin Barron
Gavin Barron

Kevin Partington
Kevin Partington

Lucas Duailibe
Lucas Duailibe

Pete Gonzalez
Pete Gonzalez

Shahar Dawn Or
Shahar Dawn Or

Alexander T.
Alexander T.

Denys Kniazevych
Denys Kniazevych

Philipp A.
Philipp A.

Pig Fang
Pig Fang

Thomas den Hollander
Thomas den Hollander

Bence Dányi
Bence Dányi

+ + + -This list is auto-generated using `yarn generate-contributors`. +This list is auto-generated using `yarn generate-contributors`. It shows the top 100 contributors with > 3 contributions. diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e3bdabad3471..960eb86b11d5 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -77,28 +77,28 @@ jobs: yarn test displayName: 'Run unit tests' - # - job: publish_canary_version - # displayName: Publish the latest code as a canary version - # dependsOn: - # - primary_code_validation_and_tests - # - unit_tests_on_other_node_versions - # condition: and(succeeded(), eq(variables['Build.SourceBranchName'], 'master'), ne(variables['Build.Reason'], 'PullRequest')) - # pool: - # vmImage: 'Ubuntu-16.04' - # steps: - # - task: NodeTool@0 - # inputs: - # versionSpec: 11 - # displayName: 'Install Node.js 11' - - # - script: | - # # This also runs a build as part of the postinstall - # # bootstrap - # yarn --ignore-engines --frozen-lockfile - - # - script: | - # npm config set //registry.npmjs.org/:_authToken=$(NPM_TOKEN) - - # - script: | - # npx lerna publish --canary --exact --force-publish --yes - # displayName: 'Publish all packages to npm' + - job: publish_canary_version + displayName: Publish the latest code as a canary version + dependsOn: + - primary_code_validation_and_tests + - unit_tests_on_other_node_versions + condition: and(succeeded(), eq(variables['Build.SourceBranchName'], 'master'), ne(variables['Build.Reason'], 'PullRequest')) + pool: + vmImage: 'Ubuntu-16.04' + steps: + - task: NodeTool@0 + inputs: + versionSpec: 11 + displayName: 'Install Node.js 11' + + - script: | + # This also runs a build as part of the postinstall + # bootstrap + yarn --ignore-engines --frozen-lockfile + + - script: | + npm config set //registry.npmjs.org/:_authToken=$(NPM_TOKEN) + + - script: | + npx lerna publish --canary --exact --force-publish --yes + displayName: 'Publish all packages to npm' diff --git a/lerna.json b/lerna.json index e4b8a9078807..0380f9287e83 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.0.0", + "version": "2.1.0", "npmClient": "yarn", "useWorkspaces": true, "stream": true diff --git a/package.json b/package.json index d0c730ff1b4f..da363951d700 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "@commitlint/travis-cli": "^8.1.0", "@types/jest": "^24.0.15", "@types/node": "^12.6.8", - "all-contributors-cli": "^6.8.0", + "all-contributors-cli": "^6.8.1", "cz-conventional-changelog": "2.1.0", "eslint": "^6.0.0", "eslint-plugin-eslint-comments": "^3.1.2", diff --git a/packages/eslint-plugin-tslint/CHANGELOG.md b/packages/eslint-plugin-tslint/CHANGELOG.md index 1339f31787c9..ff9b5e6cecef 100644 --- a/packages/eslint-plugin-tslint/CHANGELOG.md +++ b/packages/eslint-plugin-tslint/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.1.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.0.0...v2.1.0) (2019-09-02) + +**Note:** Version bump only for package @typescript-eslint/eslint-plugin-tslint + + + + + # [2.0.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.13.0...v2.0.0) (2019-08-13) diff --git a/packages/eslint-plugin-tslint/package.json b/packages/eslint-plugin-tslint/package.json index a35949dc244b..7250c2f6cb70 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.0.0", + "version": "2.1.0", "main": "dist/index.js", "typings": "src/index.ts", "description": "TSLint wrapper plugin for ESLint", @@ -31,7 +31,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/experimental-utils": "2.0.0", + "@typescript-eslint/experimental-utils": "2.1.0", "lodash.memoize": "^4.1.2" }, "peerDependencies": { @@ -41,6 +41,6 @@ }, "devDependencies": { "@types/lodash.memoize": "^4.1.4", - "@typescript-eslint/parser": "2.0.0" + "@typescript-eslint/parser": "2.1.0" } } diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index ac4b1f894c4d..699d0e12c9bc 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -3,6 +3,35 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.1.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.0.0...v2.1.0) (2019-09-02) + + +### Bug Fixes + +* **eslint-plugin:** [member-naming] should match constructor args ([#771](https://github.com/typescript-eslint/typescript-eslint/issues/771)) ([b006667](https://github.com/typescript-eslint/typescript-eslint/commit/b006667)) +* **eslint-plugin:** [no-inferrable-types] ignore optional props ([#918](https://github.com/typescript-eslint/typescript-eslint/issues/918)) ([a4e625f](https://github.com/typescript-eslint/typescript-eslint/commit/a4e625f)) +* **eslint-plugin:** [promise-function-async] Allow async get/set ([#820](https://github.com/typescript-eslint/typescript-eslint/issues/820)) ([cddfdca](https://github.com/typescript-eslint/typescript-eslint/commit/cddfdca)) +* **eslint-plugin:** [require-await] Allow concise arrow function bodies ([#826](https://github.com/typescript-eslint/typescript-eslint/issues/826)) ([29fddfd](https://github.com/typescript-eslint/typescript-eslint/commit/29fddfd)) +* **eslint-plugin:** [typedef] don't flag destructuring when variables is disabled ([#819](https://github.com/typescript-eslint/typescript-eslint/issues/819)) ([5603473](https://github.com/typescript-eslint/typescript-eslint/commit/5603473)) +* **eslint-plugin:** [typedef] handle AssignmentPattern inside TSParameterProperty ([#923](https://github.com/typescript-eslint/typescript-eslint/issues/923)) ([6bd7f2d](https://github.com/typescript-eslint/typescript-eslint/commit/6bd7f2d)) +* **eslint-plugin:** [unbound-method] Allow typeof expressions (Fixes [#692](https://github.com/typescript-eslint/typescript-eslint/issues/692)) ([#904](https://github.com/typescript-eslint/typescript-eslint/issues/904)) ([344bafe](https://github.com/typescript-eslint/typescript-eslint/commit/344bafe)) +* **eslint-plugin:** [unbound-method] false positive in equality comparisons ([#914](https://github.com/typescript-eslint/typescript-eslint/issues/914)) ([29a01b8](https://github.com/typescript-eslint/typescript-eslint/commit/29a01b8)) +* **eslint-plugin:** [unified-signatures] type comparison and exported nodes ([#839](https://github.com/typescript-eslint/typescript-eslint/issues/839)) ([580eceb](https://github.com/typescript-eslint/typescript-eslint/commit/580eceb)) +* **eslint-plugin:** readme typo ([#867](https://github.com/typescript-eslint/typescript-eslint/issues/867)) ([5eb40dc](https://github.com/typescript-eslint/typescript-eslint/commit/5eb40dc)) +* **typescript-estree:** improve missing project file error msg ([#866](https://github.com/typescript-eslint/typescript-eslint/issues/866)) ([8f3b0a8](https://github.com/typescript-eslint/typescript-eslint/commit/8f3b0a8)), closes [#853](https://github.com/typescript-eslint/typescript-eslint/issues/853) + + +### Features + +* [no-unnecessary-type-assertion] allow `as const` arrow functions ([#876](https://github.com/typescript-eslint/typescript-eslint/issues/876)) ([14c6f80](https://github.com/typescript-eslint/typescript-eslint/commit/14c6f80)) +* **eslint-plugin:** [expl-func-ret-type] make error loc smaller ([#919](https://github.com/typescript-eslint/typescript-eslint/issues/919)) ([65eb993](https://github.com/typescript-eslint/typescript-eslint/commit/65eb993)) +* **eslint-plugin:** [no-type-alias] support tuples ([#775](https://github.com/typescript-eslint/typescript-eslint/issues/775)) ([c68e033](https://github.com/typescript-eslint/typescript-eslint/commit/c68e033)) +* **eslint-plugin:** add quotes [extension] ([#762](https://github.com/typescript-eslint/typescript-eslint/issues/762)) ([9f82099](https://github.com/typescript-eslint/typescript-eslint/commit/9f82099)) + + + + + # [2.0.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.13.0...v2.0.0) (2019-08-13) diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 1ecc3e86cf97..a3ce816616ba 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -65,7 +65,7 @@ You can also use [eslint:recommended](https://eslint.org/docs/rules/) (the set o As of version 2 of this plugin, _by design_, none of the rules in the main `recommended` config require type-checking in order to run. This means that they are more lightweight and faster to run. -Some highly valuable rules simply require type-checking in order to be implemented correctly, however, so we provide an additional config you can extend from called `recommended-requiring-type-checking`. You wou apply this _in addition_ to the recommended configs previously mentioned, e.g.: +Some highly valuable rules simply require type-checking in order to be implemented correctly, however, so we provide an additional config you can extend from called `recommended-requiring-type-checking`. You would apply this _in addition_ to the recommended configs previously mentioned, e.g.: ```json { @@ -191,6 +191,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e | [`@typescript-eslint/prefer-regexp-exec`](./docs/rules/prefer-regexp-exec.md) | Prefer RegExp#exec() over String#match() if no global flag is provided | :heavy_check_mark: | | :thought_balloon: | | [`@typescript-eslint/prefer-string-starts-ends-with`](./docs/rules/prefer-string-starts-ends-with.md) | Enforce the use of `String#startsWith` and `String#endsWith` instead of other equivalent methods of checking substrings | :heavy_check_mark: | :wrench: | :thought_balloon: | | [`@typescript-eslint/promise-function-async`](./docs/rules/promise-function-async.md) | Requires any function or method that returns a Promise to be marked async | | | :thought_balloon: | +| [`@typescript-eslint/quotes`](./docs/rules/quotes.md) | Enforce the consistent use of either backticks, double, or single quotes | | :wrench: | | | [`@typescript-eslint/require-array-sort-compare`](./docs/rules/require-array-sort-compare.md) | Enforce giving `compare` argument to `Array#sort` | | | :thought_balloon: | | [`@typescript-eslint/require-await`](./docs/rules/require-await.md) | Disallow async functions which have no `await` expression | :heavy_check_mark: | | :thought_balloon: | | [`@typescript-eslint/restrict-plus-operands`](./docs/rules/restrict-plus-operands.md) | When adding two variables, operands must both be of type number or of type string | | | :thought_balloon: | 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 f31d6f42b8ba..5f729129b7a3 100644 --- a/packages/eslint-plugin/docs/rules/explicit-function-return-type.md +++ b/packages/eslint-plugin/docs/rules/explicit-function-return-type.md @@ -73,8 +73,8 @@ type Options = { const defaults = { allowExpressions: false, - allowTypedFunctionExpressions: false, - allowHigherOrderFunctions: false, + allowTypedFunctionExpressions: true, + allowHigherOrderFunctions: true, }; ``` diff --git a/packages/eslint-plugin/docs/rules/explicit-member-accessibility.md b/packages/eslint-plugin/docs/rules/explicit-member-accessibility.md index 3ed9bb7e5d07..9f8c313d3df6 100644 --- a/packages/eslint-plugin/docs/rules/explicit-member-accessibility.md +++ b/packages/eslint-plugin/docs/rules/explicit-member-accessibility.md @@ -79,7 +79,7 @@ The following patterns are considered correct with the default options `{ access ```ts class Animal { - public constructor(public breed, animalName) { + public constructor(public breed, name) { // Parameter property and constructor this.animalName = name; } @@ -102,7 +102,7 @@ The following patterns are considered incorrect with the accessibility set to ** ```ts class Animal { - public constructor(public breed, animalName) { + public constructor(public breed, name) { // Parameter property and constructor this.animalName = name; } @@ -125,7 +125,7 @@ The following patterns are considered correct with the accessibility set to **no ```ts class Animal { - constructor(protected breed, animalName) { + constructor(protected breed, name) { // Parameter property and constructor this.name = name; } diff --git a/packages/eslint-plugin/docs/rules/indent.md b/packages/eslint-plugin/docs/rules/indent.md index 5678ba291da2..4ffd6f894c70 100644 --- a/packages/eslint-plugin/docs/rules/indent.md +++ b/packages/eslint-plugin/docs/rules/indent.md @@ -90,7 +90,7 @@ This rule has an object option: - `"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](/docs/developer-guide/selectors.md). 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. +- `"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: diff --git a/packages/eslint-plugin/docs/rules/no-type-alias.md b/packages/eslint-plugin/docs/rules/no-type-alias.md index 63656dfada80..46230f3d3293 100644 --- a/packages/eslint-plugin/docs/rules/no-type-alias.md +++ b/packages/eslint-plugin/docs/rules/no-type-alias.md @@ -86,6 +86,7 @@ or more of the following you may pass an object with the options set as follows: - `allowCallbacks` set to `"always"` will allow you to use type aliases with callbacks (Defaults to `"never"`) - `allowLiterals` set to `"always"` will allow you to use type aliases with literal objects (Defaults to `"never"`) - `allowMappedTypes` set to `"always"` will allow you to use type aliases as mapping tools (Defaults to `"never"`) +- `allowTupleTypes` set to `"always"` will allow you to use type aliases with tuples (Defaults to `"never"`) ### allowAliases @@ -453,6 +454,81 @@ type Foo = { readonly [P in keyof T]: T[P] } & type Foo = { [P in keyof T]?: T[P] } & { [P in keyof U]?: U[P] }; ``` +### allowTupleTypes + +This applies to tuple types (`type Foo = [number]`). + +The setting accepts the following options: + +- `"always"` or `"never"` to active or deactivate the feature. +- `"in-unions"`, allows tuples in union statements, e.g. `type Foo = [string] | [string, string];` +- `"in-intersections"`, allows tuples in intersection statements, e.g. `type Foo = [string] & [string, string];` +- `"in-unions-and-intersections"`, allows tuples in union and/or intersection statements. + +Examples of **correct** code for the `{ "allowTupleTypes": "always" }` options: + +```ts +type Foo = [number]; + +type Foo = [number] | [number, number]; + +type Foo = [number] & [number, number]; + +type Foo = [number] | [number, number] & [string, string]; +``` + +Examples of **incorrect** code for the `{ "allowTupleTypes": "in-unions" }` option: + +```ts +type Foo = [number]; + +type Foo = [number] & [number, number]; + +type Foo = [string] & [number]; +``` + +Examples of **correct** code for the `{ "allowTupleTypes": "in-unions" }` option: + +```ts +type Foo = [number] | [number, number]; + +type Foo = [string] | [number]; +``` + +Examples of **incorrect** code for the `{ "allowTupleTypes": "in-intersections" }` option: + +```ts +type Foo = [number]; + +type Foo = [number] | [number, number]; + +type Foo = [string] | [number]; +``` + +Examples of **correct** code for the `{ "allowTupleTypes": "in-intersections" }` option: + +```ts +type Foo = [number] & [number, number]; + +type Foo = [string] & [number]; +``` + +Examples of **incorrect** code for the `{ "allowTupleTypes": "in-unions-and-intersections" }` option: + +```ts +type Foo = [number]; + +type Foo = [string]; +``` + +Examples of **correct** code for the `{ "allowLiterals": "in-unions-and-intersections" }` option: + +```ts +type Foo = [number] & [number, number]; + +type Foo = [string] | [number]; +``` + ## When Not To Use It When you can't express some shape with an interface or you need to use a union, tuple type, diff --git a/packages/eslint-plugin/docs/rules/no-unused-vars.md b/packages/eslint-plugin/docs/rules/no-unused-vars.md index 9cb7a52fe651..f6091165b7ce 100644 --- a/packages/eslint-plugin/docs/rules/no-unused-vars.md +++ b/packages/eslint-plugin/docs/rules/no-unused-vars.md @@ -76,7 +76,7 @@ myFunc = setTimeout(function() { myFunc(); }, 50); -// Only the second argument from the descructured array is used. +// Only the second argument from the destructured array is used. function getY([, y]) { return y; } diff --git a/packages/eslint-plugin/docs/rules/no-var-requires.md b/packages/eslint-plugin/docs/rules/no-var-requires.md index b5e57bdcbfe2..fb8e7a547216 100644 --- a/packages/eslint-plugin/docs/rules/no-var-requires.md +++ b/packages/eslint-plugin/docs/rules/no-var-requires.md @@ -17,6 +17,7 @@ Examples of **correct** code for this rule: ```ts import foo = require('foo'); require('foo'); +import foo from 'foo'; ``` ## When Not To Use It diff --git a/packages/eslint-plugin/docs/rules/quotes.md b/packages/eslint-plugin/docs/rules/quotes.md new file mode 100644 index 000000000000..e707a94b3d36 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/quotes.md @@ -0,0 +1,22 @@ +# Enforce the consistent use of either backticks, double, or single quotes + +## 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. + +## How to use + +```cjson +{ + // note you must disable the base rule as it can report incorrect errors + "quotes": "off", + "@typescript-eslint/quotes": ["error"] +} +``` + +## Options + +See [eslint/quotes options](https://eslint.org/docs/rules/quotes#options). + +Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/quotes.md) diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index b3d3734d15e2..9eb195cabe59 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin", - "version": "2.0.0", + "version": "2.1.0", "description": "TypeScript plugin for ESLint", "keywords": [ "eslint", @@ -40,7 +40,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/experimental-utils": "2.0.0", + "@typescript-eslint/experimental-utils": "2.1.0", "eslint-utils": "^1.4.0", "functional-red-black-tree": "^1.0.1", "regexpp": "^2.0.1", @@ -56,7 +56,7 @@ "typescript": "*" }, "peerDependencies": { - "@typescript-eslint/parser": "^2.0.0-alpha.0", + "@typescript-eslint/parser": "^2.0.0", "eslint": "^5.0.0 || ^6.0.0" } } diff --git a/packages/eslint-plugin/src/configs/README.md b/packages/eslint-plugin/src/configs/README.md index 3ba76f661e66..95accb694903 100644 --- a/packages/eslint-plugin/src/configs/README.md +++ b/packages/eslint-plugin/src/configs/README.md @@ -52,4 +52,4 @@ If you disagree with a rule (or it disagrees with your codebase), consider using ### Suggesting changes to the recommended set -If you feel _very_, **very**, **_very_** strongly that a specific rule should (or should not) be in the recommended ruleset, please feel free to file an issue along with a **detailed** argument explaning your reasoning. We expect to see you citing concrete evidence supporting why (or why not) a rule is considered best practice. **Please note that if your reasoning is along the lines of "it's what my project/company does", or "I don't like the rule", then we will likely close the request without discussion.** +If you feel _very_, **very**, **_very_** strongly that a specific rule should (or should not) be in the recommended ruleset, please feel free to file an issue along with a **detailed** argument explaining your reasoning. We expect to see you citing concrete evidence supporting why (or why not) a rule is considered best practice. **Please note that if your reasoning is along the lines of "it's what my project/company does", or "I don't like the rule", then we will likely close the request without discussion.** diff --git a/packages/eslint-plugin/src/configs/all.json b/packages/eslint-plugin/src/configs/all.json index 30a7cafafb56..cce896718db1 100644 --- a/packages/eslint-plugin/src/configs/all.json +++ b/packages/eslint-plugin/src/configs/all.json @@ -62,6 +62,8 @@ "@typescript-eslint/prefer-regexp-exec": "error", "@typescript-eslint/prefer-string-starts-ends-with": "error", "@typescript-eslint/promise-function-async": "error", + "quotes": "off", + "@typescript-eslint/quotes": "error", "@typescript-eslint/require-array-sort-compare": "error", "require-await": "off", "@typescript-eslint/require-await": "error", 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 67692c20fc03..b050578c189a 100644 --- a/packages/eslint-plugin/src/rules/explicit-function-return-type.ts +++ b/packages/eslint-plugin/src/rules/explicit-function-return-type.ts @@ -1,6 +1,7 @@ import { TSESTree, AST_NODE_TYPES, + AST_TOKEN_TYPES, } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; @@ -9,6 +10,7 @@ type Options = [ allowExpressions?: boolean; allowTypedFunctionExpressions?: boolean; allowHigherOrderFunctions?: boolean; + allowDirectConstAssertionInArrowFunctions?: boolean; }, ]; type MessageIds = 'missingReturnType'; @@ -39,6 +41,9 @@ export default util.createRule({ allowHigherOrderFunctions: { type: 'boolean', }, + allowDirectConstAssertionInArrowFunctions: { + type: 'boolean', + }, }, additionalProperties: false, }, @@ -49,9 +54,57 @@ export default util.createRule({ allowExpressions: false, allowTypedFunctionExpressions: true, allowHigherOrderFunctions: true, + allowDirectConstAssertionInArrowFunctions: true, }, ], create(context, [options]) { + const sourceCode = context.getSourceCode(); + + /** + * Returns start column position + * @param node + */ + function getLocStart( + node: + | TSESTree.ArrowFunctionExpression + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression, + ): TSESTree.LineAndColumnData { + /* highlight method name */ + const parent = node.parent; + if ( + parent && + (parent.type === AST_NODE_TYPES.MethodDefinition || + (parent.type === AST_NODE_TYPES.Property && parent.method)) + ) { + return parent.loc.start; + } + + return node.loc.start; + } + + /** + * Returns end column position + * @param node + */ + function getLocEnd( + node: + | TSESTree.ArrowFunctionExpression + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression, + ): TSESTree.LineAndColumnData { + /* highlight `=>` */ + if (node.type === AST_NODE_TYPES.ArrowFunctionExpression) { + return sourceCode.getTokenBefore( + node.body, + token => + token.type === AST_TOKEN_TYPES.Punctuator && token.value === '=>', + )!.loc.end; + } + + return sourceCode.getTokenBefore(node.body!)!.loc.end; + } + /** * Checks if a node is a constructor. * @param node The node to check @@ -203,6 +256,30 @@ export default util.createRule({ ); } + /** + * Checks if a function belongs to: + * `() => ({ action: 'xxx' }) as const` + */ + function returnsConstAssertionDirectly( + node: TSESTree.ArrowFunctionExpression, + ): boolean { + const { body } = node; + if (body.type === AST_NODE_TYPES.TSAsExpression) { + const { typeAnnotation } = body; + if (typeAnnotation.type === AST_NODE_TYPES.TSTypeReference) { + const { typeName } = typeAnnotation; + if ( + typeName.type === AST_NODE_TYPES.Identifier && + typeName.name === 'const' + ) { + return true; + } + } + } + + return false; + } + /** * Checks if a function declaration/expression has a return type. */ @@ -229,6 +306,7 @@ export default util.createRule({ context.report({ node, + loc: { start: getLocStart(node), end: getLocEnd(node) }, messageId: 'missingReturnType', }); } @@ -263,6 +341,15 @@ export default util.createRule({ } } + // https://github.com/typescript-eslint/typescript-eslint/issues/653 + if ( + node.type === AST_NODE_TYPES.ArrowFunctionExpression && + options.allowDirectConstAssertionInArrowFunctions && + returnsConstAssertionDirectly(node) + ) { + return; + } + checkFunctionReturnType(node); } diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index db155e691bfa..bd39c7867e33 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -48,6 +48,7 @@ import preferReadonly from './prefer-readonly'; import preferRegexpExec from './prefer-regexp-exec'; import preferStringStartsEndsWith from './prefer-string-starts-ends-with'; import promiseFunctionAsync from './promise-function-async'; +import quotes from './quotes'; import requireArraySortCompare from './require-array-sort-compare'; import requireAwait from './require-await'; import restrictPlusOperands from './restrict-plus-operands'; @@ -112,6 +113,7 @@ export default { 'prefer-regexp-exec': preferRegexpExec, 'prefer-string-starts-ends-with': preferStringStartsEndsWith, 'promise-function-async': promiseFunctionAsync, + quotes: quotes, 'require-array-sort-compare': requireArraySortCompare, 'require-await': requireAwait, 'restrict-plus-operands': restrictPlusOperands, diff --git a/packages/eslint-plugin/src/rules/member-naming.ts b/packages/eslint-plugin/src/rules/member-naming.ts index 9995b6cf1e35..d850ef69060e 100644 --- a/packages/eslint-plugin/src/rules/member-naming.ts +++ b/packages/eslint-plugin/src/rules/member-naming.ts @@ -1,4 +1,7 @@ -import { TSESTree } from '@typescript-eslint/experimental-utils'; +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; interface Config { @@ -61,37 +64,73 @@ export default util.createRule({ return acc; }, {}); - /** - * Check that the property name matches the convention for its - * accessibility. - * @param {ASTNode} node the named node to evaluate. - * @returns {void} - * @private - */ + function getParameterNode( + node: TSESTree.TSParameterProperty, + ): TSESTree.Identifier | null { + if (node.parameter.type === AST_NODE_TYPES.AssignmentPattern) { + return node.parameter.left as TSESTree.Identifier; + } + + if (node.parameter.type === AST_NODE_TYPES.Identifier) { + return node.parameter; + } + + return null; + } + + function validateParameterName(node: TSESTree.TSParameterProperty): void { + const parameterNode = getParameterNode(node); + if (!parameterNode) { + return; + } + + validate(parameterNode, parameterNode.name, node.accessibility); + } + function validateName( node: TSESTree.MethodDefinition | TSESTree.ClassProperty, ): void { - const name = util.getNameFromClassMember(node, sourceCode); - const accessibility: Modifiers = node.accessibility || 'public'; - const convention = conventions[accessibility]; - - const method = node as TSESTree.MethodDefinition; - if (method.kind === 'constructor') { + if ( + node.type === AST_NODE_TYPES.MethodDefinition && + node.kind === 'constructor' + ) { return; } + validate( + node.key, + util.getNameFromClassMember(node, sourceCode), + node.accessibility, + ); + } + + /** + * Check that the name matches the convention for its accessibility. + * @param {ASTNode} node the named node to evaluate. + * @param {string} name + * @param {Modifiers} accessibility + * @returns {void} + * @private + */ + function validate( + node: TSESTree.Identifier | TSESTree.Expression, + name: string, + accessibility: Modifiers = 'public', + ): void { + const convention = conventions[accessibility]; if (!convention || convention.test(name)) { return; } context.report({ - node: node.key, + node, messageId: 'incorrectName', data: { accessibility, name, convention }, }); } return { + TSParameterProperty: validateParameterName, MethodDefinition: validateName, ClassProperty: validateName, }; diff --git a/packages/eslint-plugin/src/rules/no-inferrable-types.ts b/packages/eslint-plugin/src/rules/no-inferrable-types.ts index 40fc6a60c7eb..92a582526795 100644 --- a/packages/eslint-plugin/src/rules/no-inferrable-types.ts +++ b/packages/eslint-plugin/src/rules/no-inferrable-types.ts @@ -250,7 +250,7 @@ export default util.createRule({ // Essentially a readonly property without a type // will result in its value being the type, leading to // compile errors if the type is stripped. - if (ignoreProperties || node.readonly) { + if (ignoreProperties || node.readonly || node.optional) { return; } reportInferrableType(node, node.typeAnnotation, node.value); diff --git a/packages/eslint-plugin/src/rules/no-type-alias.ts b/packages/eslint-plugin/src/rules/no-type-alias.ts index 1648b89f0dfa..68d537cc09b8 100644 --- a/packages/eslint-plugin/src/rules/no-type-alias.ts +++ b/packages/eslint-plugin/src/rules/no-type-alias.ts @@ -4,27 +4,27 @@ import { } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; +type Values = + | 'always' + | 'never' + | 'in-unions' + | 'in-intersections' + | 'in-unions-and-intersections'; +const enumValues: Values[] = [ + 'always', + 'never', + 'in-unions', + 'in-intersections', + 'in-unions-and-intersections', +]; + type Options = [ { - allowAliases?: - | 'always' - | 'never' - | 'in-unions' - | 'in-intersections' - | 'in-unions-and-intersections'; + allowAliases?: Values; allowCallbacks?: 'always' | 'never'; - allowLiterals?: - | 'always' - | 'never' - | 'in-unions' - | 'in-intersections' - | 'in-unions-and-intersections'; - allowMappedTypes?: - | 'always' - | 'never' - | 'in-unions' - | 'in-intersections' - | 'in-unions-and-intersections'; + allowLiterals?: Values; + allowMappedTypes?: Values; + allowTupleTypes?: Values; }, ]; type MessageIds = 'noTypeAlias' | 'noCompositionAlias'; @@ -57,34 +57,19 @@ export default util.createRule({ type: 'object', properties: { allowAliases: { - enum: [ - 'always', - 'never', - 'in-unions', - 'in-intersections', - 'in-unions-and-intersections', - ], + enum: enumValues, }, allowCallbacks: { enum: ['always', 'never'], }, allowLiterals: { - enum: [ - 'always', - 'never', - 'in-unions', - 'in-intersections', - 'in-unions-and-intersections', - ], + enum: enumValues, }, allowMappedTypes: { - enum: [ - 'always', - 'never', - 'in-unions', - 'in-intersections', - 'in-unions-and-intersections', - ], + enum: enumValues, + }, + allowTupleTypes: { + enum: enumValues, }, }, additionalProperties: false, @@ -97,11 +82,20 @@ export default util.createRule({ allowCallbacks: 'never', allowLiterals: 'never', allowMappedTypes: 'never', + allowTupleTypes: 'never', }, ], create( context, - [{ allowAliases, allowCallbacks, allowLiterals, allowMappedTypes }], + [ + { + allowAliases, + allowCallbacks, + allowLiterals, + allowMappedTypes, + allowTupleTypes, + }, + ], ) { const unions = ['always', 'in-unions', 'in-unions-and-intersections']; const intersections = [ @@ -180,6 +174,36 @@ export default util.createRule({ }); } + const isValidTupleType = (type: TypeWithLabel) => { + if (type.node.type === AST_NODE_TYPES.TSTupleType) { + return true; + } + if (type.node.type === AST_NODE_TYPES.TSTypeOperator) { + if ( + ['keyof', 'readonly'].includes(type.node.operator) && + type.node.typeAnnotation && + type.node.typeAnnotation.type === AST_NODE_TYPES.TSTupleType + ) { + return true; + } + } + return false; + }; + + const checkAndReport = ( + optionValue: Values, + isTopLevel: boolean, + type: TypeWithLabel, + label: string, + ) => { + if ( + optionValue === 'never' || + !isSupportedComposition(isTopLevel, type.compositionType, optionValue) + ) { + reportError(type.node, type.compositionType, isTopLevel, label); + } + }; + /** * Validates the node looking for aliases, callbacks and literals. * @param node the node to be validated. @@ -198,48 +222,19 @@ export default util.createRule({ } } else if (type.node.type === AST_NODE_TYPES.TSTypeLiteral) { // literal object type - if ( - allowLiterals === 'never' || - !isSupportedComposition( - isTopLevel, - type.compositionType, - allowLiterals!, - ) - ) { - reportError(type.node, type.compositionType, isTopLevel, 'Literals'); - } + checkAndReport(allowLiterals!, isTopLevel, type, 'Literals'); } else if (type.node.type === AST_NODE_TYPES.TSMappedType) { // mapped type - if ( - allowMappedTypes === 'never' || - !isSupportedComposition( - isTopLevel, - type.compositionType, - allowMappedTypes!, - ) - ) { - reportError( - type.node, - type.compositionType, - isTopLevel, - 'Mapped types', - ); - } + checkAndReport(allowMappedTypes!, isTopLevel, type, 'Mapped types'); + } else if (isValidTupleType(type)) { + // tuple types + checkAndReport(allowTupleTypes!, isTopLevel, type, 'Tuple Types'); } else if ( type.node.type.endsWith('Keyword') || aliasTypes.has(type.node.type) ) { // alias / keyword - if ( - allowAliases === 'never' || - !isSupportedComposition( - isTopLevel, - type.compositionType, - allowAliases!, - ) - ) { - reportError(type.node, type.compositionType, isTopLevel, 'Aliases'); - } + checkAndReport(allowAliases!, isTopLevel, type, 'Aliases'); } else { // unhandled type - shouldn't happen reportError(type.node, type.compositionType, isTopLevel, 'Unhandled'); diff --git a/packages/eslint-plugin/src/rules/promise-function-async.ts b/packages/eslint-plugin/src/rules/promise-function-async.ts index 225b2bd83675..8ec816b456b1 100644 --- a/packages/eslint-plugin/src/rules/promise-function-async.ts +++ b/packages/eslint-plugin/src/rules/promise-function-async.ts @@ -103,6 +103,14 @@ export default util.createRule({ return; } + if ( + node.parent && + node.parent.type === 'Property' && + (node.parent.kind === 'get' || node.parent.kind === 'set') + ) { + return; + } + context.report({ messageId: 'missingAsync', node, diff --git a/packages/eslint-plugin/src/rules/quotes.ts b/packages/eslint-plugin/src/rules/quotes.ts new file mode 100644 index 000000000000..97efc04c821d --- /dev/null +++ b/packages/eslint-plugin/src/rules/quotes.ts @@ -0,0 +1,62 @@ +import { + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; +import baseRule from 'eslint/lib/rules/quotes'; +import * as util from '../util'; + +export type Options = util.InferOptionsTypeFromRule; +export type MessageIds = util.InferMessageIdsTypeFromRule; + +export default util.createRule({ + name: 'quotes', + meta: { + type: 'layout', + docs: { + description: + 'Enforce the consistent use of either backticks, double, or single quotes', + category: 'Stylistic Issues', + recommended: false, + }, + fixable: 'code', + messages: baseRule.meta.messages, + schema: baseRule.meta.schema, + }, + defaultOptions: [ + 'double', + { + allowTemplateLiterals: false, + avoidEscape: false, + }, + ], + create(context, [option]) { + const rules = baseRule.create(context); + + const isModuleDeclaration = (node: TSESTree.Literal): boolean => { + return ( + !!node.parent && node.parent.type === AST_NODE_TYPES.TSModuleDeclaration + ); + }; + + const isTypeLiteral = (node: TSESTree.Literal): boolean => { + return !!node.parent && node.parent.type === AST_NODE_TYPES.TSLiteralType; + }; + + return { + Literal(node) { + if ( + option === 'backtick' && + (isModuleDeclaration(node) || isTypeLiteral(node)) + ) { + return; + } + + rules.Literal(node); + }, + + TemplateLiteral(node) { + rules.TemplateLiteral(node); + }, + }; + }, +}); diff --git a/packages/eslint-plugin/src/rules/require-await.ts b/packages/eslint-plugin/src/rules/require-await.ts index 7441b4648966..065066dece2c 100644 --- a/packages/eslint-plugin/src/rules/require-await.ts +++ b/packages/eslint-plugin/src/rules/require-await.ts @@ -64,6 +64,15 @@ export default util.createRule({ case AST_NODE_TYPES.ArrowFunctionExpression: rules.ArrowFunctionExpression(node); + + // If body type is not BlockStatment, we need to check the return type here + if (node.body.type !== AST_NODE_TYPES.BlockStatement) { + const expression = parserServices.esTreeNodeToTSNodeMap.get( + node.body, + ); + scopeInfo.returnsPromise = isThenableType(expression); + } + break; } } @@ -102,6 +111,18 @@ export default util.createRule({ } } + /** + * Checks if the node returns a thenable type + * + * @param {ASTNode} node - The node to check + * @returns {boolean} + */ + function isThenableType(node: ts.Node) { + const type = checker.getTypeAtLocation(node); + + return tsutils.isThenableType(checker, node, type); + } + return { 'FunctionDeclaration[async = true]': enterFunction, 'FunctionExpression[async = true]': enterFunction, @@ -122,10 +143,7 @@ export default util.createRule({ return; } - const type = checker.getTypeAtLocation(expression); - if (tsutils.isThenableType(checker, expression, type)) { - scopeInfo.returnsPromise = true; - } + scopeInfo.returnsPromise = isThenableType(expression); }, AwaitExpression: rules.AwaitExpression as TSESLint.RuleFunction< diff --git a/packages/eslint-plugin/src/rules/typedef.ts b/packages/eslint-plugin/src/rules/typedef.ts index 182568e57488..19c4244d9f99 100644 --- a/packages/eslint-plugin/src/rules/typedef.ts +++ b/packages/eslint-plugin/src/rules/typedef.ts @@ -79,6 +79,15 @@ export default util.createRule<[Options], MessageIds>({ break; case AST_NODE_TYPES.TSParameterProperty: annotationNode = param.parameter; + + // Check TS parameter property with default value like `constructor(private param: string = 'something') {}` + if ( + annotationNode && + annotationNode.type === AST_NODE_TYPES.AssignmentPattern + ) { + annotationNode = annotationNode.left; + } + break; default: annotationNode = param; @@ -141,36 +150,35 @@ export default util.createRule<[Options], MessageIds>({ }, VariableDeclarator(node): void { if ( - options[OptionKeys.VariableDeclaration] && - !node.id.typeAnnotation + !options[OptionKeys.VariableDeclaration] || + node.id.typeAnnotation || + (node.id.type === AST_NODE_TYPES.ArrayPattern && + !options[OptionKeys.ArrayDestructuring]) || + (node.id.type === AST_NODE_TYPES.ObjectPattern && + !options[OptionKeys.ObjectDestructuring]) ) { - // Are we inside a context that does not allow type annotations? - let typeAnnotationRequired = true; - - let current: TSESTree.Node | undefined = node.parent; - while (current) { - switch (current.type) { - case AST_NODE_TYPES.VariableDeclaration: - // Keep looking upwards - current = current.parent; - break; - case AST_NODE_TYPES.ForOfStatement: - case AST_NODE_TYPES.ForInStatement: - // Stop traversing and don't report an error - typeAnnotationRequired = false; - current = undefined; - break; - default: - // Stop traversing - current = undefined; - break; - } - } + return; + } - if (typeAnnotationRequired) { - report(node, getNodeName(node.id)); + let current: TSESTree.Node | undefined = node.parent; + while (current) { + switch (current.type) { + case AST_NODE_TYPES.VariableDeclaration: + // Keep looking upwards + current = current.parent; + break; + case AST_NODE_TYPES.ForOfStatement: + case AST_NODE_TYPES.ForInStatement: + // Stop traversing and don't report an error + return; + default: + // Stop traversing + current = undefined; + break; } } + + report(node, getNodeName(node.id)); }, }; }, diff --git a/packages/eslint-plugin/src/rules/unbound-method.ts b/packages/eslint-plugin/src/rules/unbound-method.ts index c76ac76f9d03..46bbe779c66c 100644 --- a/packages/eslint-plugin/src/rules/unbound-method.ts +++ b/packages/eslint-plugin/src/rules/unbound-method.ts @@ -120,6 +120,12 @@ function isSafeUse(node: TSESTree.Node): boolean { case AST_NODE_TYPES.TaggedTemplateExpression: return parent.tag === node; + case AST_NODE_TYPES.UnaryExpression: + return parent.operator === 'typeof'; + + case AST_NODE_TYPES.BinaryExpression: + return ['instanceof', '==', '!=', '===', '!=='].includes(parent.operator); + case AST_NODE_TYPES.TSNonNullExpression: case AST_NODE_TYPES.TSAsExpression: case AST_NODE_TYPES.TSTypeAssertion: diff --git a/packages/eslint-plugin/src/rules/unified-signatures.ts b/packages/eslint-plugin/src/rules/unified-signatures.ts index 1c3f747bc51b..c4c7f9bf4275 100644 --- a/packages/eslint-plugin/src/rules/unified-signatures.ts +++ b/packages/eslint-plugin/src/rules/unified-signatures.ts @@ -35,6 +35,9 @@ type ScopeNode = | TSESTree.TSTypeLiteral; type OverloadNode = MethodDefinition | SignatureDefinition; +type ContainingNode = + | TSESTree.ExportNamedDeclaration + | TSESTree.ExportDefaultDeclaration; type SignatureDefinition = | TSESTree.FunctionExpression @@ -424,7 +427,8 @@ export default util.createRule({ a === b || (a !== undefined && b !== undefined && - a.typeAnnotation.type === b.typeAnnotation.type) + sourceCode.getText(a.typeAnnotation) === + sourceCode.getText(b.typeAnnotation)) ); } @@ -495,9 +499,16 @@ export default util.createRule({ currentScope = scopes.pop()!; } - function addOverload(signature: OverloadNode, key?: string): void { + function addOverload( + signature: OverloadNode, + key?: string, + containingNode?: ContainingNode, + ): void { key = key || getOverloadKey(signature); - if (currentScope && signature.parent === currentScope.parent && key) { + if ( + currentScope && + (containingNode || signature).parent === currentScope.parent + ) { const overloads = currentScope.overloads.get(key); if (overloads !== undefined) { overloads.push(signature); @@ -521,11 +532,10 @@ export default util.createRule({ createScope(node.body, node.typeParameters); }, TSTypeLiteral: createScope, + // collect overloads TSDeclareFunction(node): void { - if (node.id && !node.body) { - addOverload(node, node.id.name); - } + addOverload(node, node.id.name, getExportingNode(node)); }, TSCallSignatureDeclaration: addOverload, TSConstructSignatureDeclaration: addOverload, @@ -540,6 +550,7 @@ export default util.createRule({ addOverload(node); } }, + // validate scopes 'Program:exit': checkScope, 'TSModuleBlock:exit': checkScope, @@ -550,7 +561,20 @@ export default util.createRule({ }, }); -function getOverloadKey(node: OverloadNode): string | undefined { +function getExportingNode( + node: TSESTree.TSDeclareFunction, +): + | TSESTree.ExportNamedDeclaration + | TSESTree.ExportDefaultDeclaration + | undefined { + return node.parent && + (node.parent.type === AST_NODE_TYPES.ExportNamedDeclaration || + node.parent.type === AST_NODE_TYPES.ExportDefaultDeclaration) + ? node.parent + : undefined; +} + +function getOverloadKey(node: OverloadNode): string { const info = getOverloadInfo(node); return ( 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 32f23792591e..0c67b903ceeb 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 @@ -308,20 +308,56 @@ 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) => x as const; + `, + options: [ + { + allowDirectConstAssertionInArrowFunctions: true, + }, + ], + }, ], invalid: [ { filename: 'test.ts', code: ` +function test( + a: number, + b: number, +) { + return; +} + `, + errors: [ + { + messageId: 'missingReturnType', + line: 2, + endLine: 5, + column: 1, + endColumn: 2, + }, + ], + }, + { + filename: 'test.ts', + code: ` function test() { - return; + return; } `, errors: [ { messageId: 'missingReturnType', line: 2, + endLine: 2, column: 1, + endColumn: 16, }, ], }, @@ -329,14 +365,16 @@ function test() { filename: 'test.ts', code: ` var fn = function() { - return 1; + return 1; }; `, errors: [ { messageId: 'missingReturnType', line: 2, + endLine: 2, column: 10, + endColumn: 20, }, ], }, @@ -349,7 +387,9 @@ var arrowFn = () => 'test'; { messageId: 'missingReturnType', line: 2, + endLine: 2, column: 15, + endColumn: 20, }, ], }, @@ -366,23 +406,39 @@ class Test { return; } arrow = () => 'arrow'; + private method() { + return; + } } `, errors: [ { messageId: 'missingReturnType', line: 4, - column: 11, + endLine: 4, + column: 3, + endColumn: 13, }, { messageId: 'missingReturnType', line: 8, - column: 9, + endLine: 8, + column: 3, + endColumn: 11, }, { messageId: 'missingReturnType', line: 11, + endLine: 11, column: 11, + endColumn: 16, + }, + { + messageId: 'missingReturnType', + line: 12, + endLine: 12, + column: 3, + endColumn: 19, }, ], }, @@ -398,7 +454,9 @@ function test() { { messageId: 'missingReturnType', line: 2, + endLine: 2, column: 1, + endColumn: 16, }, ], }, @@ -410,7 +468,9 @@ function test() { { messageId: 'missingReturnType', line: 1, + endLine: 1, column: 13, + endColumn: 18, }, ], }, @@ -422,7 +482,9 @@ function test() { { messageId: 'missingReturnType', line: 1, + endLine: 1, column: 13, + endColumn: 23, }, ], }, @@ -434,7 +496,9 @@ function test() { { messageId: 'missingReturnType', line: 1, + endLine: 1, column: 16, + endColumn: 21, }, ], }, @@ -446,7 +510,9 @@ function test() { { messageId: 'missingReturnType', line: 1, + endLine: 1, column: 16, + endColumn: 26, }, ], }, @@ -458,7 +524,9 @@ function test() { { messageId: 'missingReturnType', line: 1, + endLine: 1, column: 15, + endColumn: 20, }, ], }, @@ -470,7 +538,9 @@ function test() { { messageId: 'missingReturnType', line: 1, + endLine: 1, column: 16, + endColumn: 26, }, ], }, @@ -483,6 +553,9 @@ function test() { { messageId: 'missingReturnType', line: 1, + endLine: 1, + column: 12, + endColumn: 17, }, ], }, @@ -499,6 +572,9 @@ const x = { { messageId: 'missingReturnType', line: 4, + endLine: 4, + column: 8, + endColumn: 13, }, ], }, @@ -515,6 +591,9 @@ const x: Foo = { { messageId: 'missingReturnType', line: 4, + endLine: 4, + column: 8, + endColumn: 13, }, ], }, @@ -526,7 +605,9 @@ const x: Foo = { { messageId: 'missingReturnType', line: 1, + endLine: 1, column: 7, + endColumn: 12, }, ], }, @@ -538,7 +619,9 @@ const x: Foo = { { messageId: 'missingReturnType', line: 1, + endLine: 1, column: 7, + endColumn: 18, }, ], }, @@ -550,7 +633,9 @@ const x: Foo = { { messageId: 'missingReturnType', line: 1, + endLine: 1, column: 16, + endColumn: 21, }, ], }, @@ -562,7 +647,9 @@ const x: Foo = { { messageId: 'missingReturnType', line: 1, + endLine: 1, column: 16, + endColumn: 27, }, ], }, @@ -574,7 +661,9 @@ const x: Foo = { { messageId: 'missingReturnType', line: 1, + endLine: 1, column: 24, + endColumn: 29, }, ], }, @@ -586,7 +675,9 @@ const x: Foo = { { messageId: 'missingReturnType', line: 1, + endLine: 1, column: 24, + endColumn: 35, }, ], }, @@ -609,7 +700,9 @@ function FunctionDeclaration() { { messageId: 'missingReturnType', line: 7, + endLine: 7, column: 11, + endColumn: 16, }, ], }, @@ -621,7 +714,9 @@ function FunctionDeclaration() { { messageId: 'missingReturnType', line: 1, + endLine: 1, column: 22, + endColumn: 27, }, ], }, @@ -645,22 +740,37 @@ foo(() => '') { messageId: 'missingReturnType', line: 3, + endLine: 3, + column: 5, + endColumn: 10, }, { messageId: 'missingReturnType', line: 4, + endLine: 4, + column: 5, + endColumn: 10, }, { messageId: 'missingReturnType', line: 5, + endLine: 5, + column: 5, + endColumn: 10, }, { messageId: 'missingReturnType', line: 6, + endLine: 6, + column: 5, + endColumn: 10, }, { messageId: 'missingReturnType', line: 7, + endLine: 7, + column: 5, + endColumn: 10, }, ], }, @@ -686,7 +796,9 @@ new Accumulator().accumulate(() => 1); { messageId: 'missingReturnType', line: 10, + endLine: 10, column: 30, + endColumn: 35, }, ], }, @@ -702,7 +814,9 @@ new Accumulator().accumulate(() => 1); { messageId: 'missingReturnType', line: 1, + endLine: 1, column: 2, + endColumn: 7, }, ], }, @@ -735,17 +849,71 @@ foo({ { messageId: 'missingReturnType', line: 4, - column: 7, + endLine: 4, + column: 3, + endColumn: 9, }, { messageId: 'missingReturnType', line: 9, + endLine: 9, column: 9, + endColumn: 20, }, { messageId: 'missingReturnType', line: 14, + endLine: 14, column: 9, + endColumn: 14, + }, + ], + }, + { + filename: 'test.ts', + code: ` +const func = (value: number) => ({ type: "X", value } as any); +const func = (value: number) => ({ type: "X", value } as Action); + `, + options: [ + { + allowDirectConstAssertionInArrowFunctions: true, + }, + ], + errors: [ + { + messageId: 'missingReturnType', + line: 2, + endLine: 2, + column: 14, + endColumn: 32, + }, + { + messageId: 'missingReturnType', + line: 3, + endLine: 3, + column: 14, + endColumn: 32, + }, + ], + }, + { + filename: 'test.ts', + code: ` +const func = (value: number) => ({ type: "X", value } as const); + `, + options: [ + { + allowDirectConstAssertionInArrowFunctions: false, + }, + ], + errors: [ + { + messageId: 'missingReturnType', + line: 2, + endLine: 2, + column: 14, + endColumn: 32, }, ], }, diff --git a/packages/eslint-plugin/tests/rules/member-naming.test.ts b/packages/eslint-plugin/tests/rules/member-naming.test.ts index 96ec4b104f3c..851d70ea706c 100644 --- a/packages/eslint-plugin/tests/rules/member-naming.test.ts +++ b/packages/eslint-plugin/tests/rules/member-naming.test.ts @@ -86,6 +86,30 @@ class Class { }, ], }, + + { + code: ` +class Test { + constructor(public __a: string, protected __b: string, private __c: string = 100) {} +} + `, + options: [ + { + protected: '^__', + private: '^__', + public: '^__', + }, + ], + }, + { + code: + // Semantically invalid test case, TS has to throw an error. + ` +class Foo { + constructor(private ...name: string[], private [test]: [string]) {} +} + `, + }, ], invalid: [ { @@ -329,5 +353,51 @@ class Class { }, ], }, + { + code: ` +class Test { + constructor(public a: string, protected b: string, private c: string = 100) {} +} + `, + options: [ + { + public: '^__', + protected: '^__', + private: '^__', + }, + ], + errors: [ + { + messageId: 'incorrectName', + data: { + accessibility: 'public', + convention: '/^__/', + name: 'a', + }, + line: 3, + column: 24, + }, + { + messageId: 'incorrectName', + data: { + accessibility: 'protected', + convention: '/^__/', + name: 'b', + }, + line: 3, + column: 45, + }, + { + messageId: 'incorrectName', + data: { + accessibility: 'private', + convention: '/^__/', + name: 'c', + }, + line: 3, + column: 64, + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/no-inferrable-types.test.ts b/packages/eslint-plugin/tests/rules/no-inferrable-types.test.ts index fb2563ed4eeb..2301d6eda5c2 100644 --- a/packages/eslint-plugin/tests/rules/no-inferrable-types.test.ts +++ b/packages/eslint-plugin/tests/rules/no-inferrable-types.test.ts @@ -123,6 +123,15 @@ ruleTester.run('no-inferrable-types', rule, { "class Foo { a: number = 5; b: boolean = true; c: string = 'foo'; }", options: [{ ignoreProperties: true }], }, + { + code: ` +class Foo { + a?: number = 5; + b?: boolean = true; + c?: string = 'foo'; +} + `, + }, ], invalid: [ diff --git a/packages/eslint-plugin/tests/rules/no-type-alias.test.ts b/packages/eslint-plugin/tests/rules/no-type-alias.test.ts index 3c3d83520e8f..d17348af7460 100644 --- a/packages/eslint-plugin/tests/rules/no-type-alias.test.ts +++ b/packages/eslint-plugin/tests/rules/no-type-alias.test.ts @@ -376,6 +376,69 @@ type Foo = { code: 'type Foo = typeof bar | typeof baz;', options: [{ allowAliases: 'in-unions' }], }, + { + code: 'type Foo = keyof [string]', + options: [{ allowTupleTypes: 'always' }], + }, + { + code: 'type Foo = [string] | [number, number];', + options: [{ allowTupleTypes: 'always' }], + }, + { + code: 'type Foo = [string] | [number, number];', + options: [{ allowTupleTypes: 'in-unions' }], + }, + { + code: 'type Foo = [string] & [number, number];', + options: [{ allowTupleTypes: 'in-intersections' }], + }, + { + code: + 'type Foo = [string] & [number, number] | [number, number, number];', + options: [{ allowTupleTypes: 'in-unions-and-intersections' }], + }, + { + code: 'type Foo = readonly [string] | [number, number];', + options: [{ allowTupleTypes: 'always' }], + }, + { + code: 'type Foo = readonly [string] | readonly [number, number];', + options: [{ allowTupleTypes: 'always' }], + }, + { + code: 'type Foo = readonly [string] | [number, number];', + options: [{ allowTupleTypes: 'in-unions' }], + }, + { + code: 'type Foo = [string] & readonly [number, number];', + options: [{ allowTupleTypes: 'in-intersections' }], + }, + { + code: + 'type Foo = [string] & [number, number] | readonly [number, number, number];', + options: [{ allowTupleTypes: 'in-unions-and-intersections' }], + }, + { + code: 'type Foo = keyof [string] | [number, number];', + options: [{ allowTupleTypes: 'always' }], + }, + { + code: 'type Foo = keyof [string] | keyof [number, number];', + options: [{ allowTupleTypes: 'always' }], + }, + { + code: 'type Foo = keyof [string] | [number, number];', + options: [{ allowTupleTypes: 'in-unions' }], + }, + { + code: 'type Foo = [string] & keyof [number, number];', + options: [{ allowTupleTypes: 'in-intersections' }], + }, + { + code: + 'type Foo = [string] & [number, number] | keyof [number, number, number];', + options: [{ allowTupleTypes: 'in-unions-and-intersections' }], + }, ], invalid: [ { @@ -2915,5 +2978,203 @@ type Foo = { }, ], }, + { + code: 'type Foo = [number] | [number, number]', + options: [{ allowTupleTypes: 'never' }], + errors: [ + { + messageId: 'noCompositionAlias', + data: { + compositionType: 'union', + typeName: 'Tuple Types', + }, + line: 1, + column: 12, + }, + { + messageId: 'noCompositionAlias', + data: { + compositionType: 'union', + typeName: 'Tuple Types', + }, + line: 1, + column: 23, + }, + ], + }, + { + code: 'type Foo = [number] & [number, number]', + options: [{ allowTupleTypes: 'in-unions' }], + errors: [ + { + messageId: 'noCompositionAlias', + data: { + compositionType: 'intersection', + typeName: 'Tuple Types', + }, + line: 1, + column: 12, + }, + { + messageId: 'noCompositionAlias', + data: { + compositionType: 'intersection', + typeName: 'Tuple Types', + }, + line: 1, + column: 23, + }, + ], + }, + { + code: 'type Foo = [number] | [number, number]', + options: [{ allowTupleTypes: 'in-intersections' }], + errors: [ + { + messageId: 'noCompositionAlias', + data: { + compositionType: 'union', + typeName: 'Tuple Types', + }, + line: 1, + column: 12, + }, + { + messageId: 'noCompositionAlias', + data: { + compositionType: 'union', + typeName: 'Tuple Types', + }, + line: 1, + column: 23, + }, + ], + }, + { + code: 'type Foo = [number];', + options: [{ allowTupleTypes: 'in-intersections' }], + errors: [ + { + messageId: 'noTypeAlias', + }, + ], + }, + { + code: 'type Foo = [number];', + options: [{ allowTupleTypes: 'in-unions' }], + errors: [ + { + messageId: 'noTypeAlias', + }, + ], + }, + { + code: 'type Foo = [number];', + options: [{ allowTupleTypes: 'in-unions-and-intersections' }], + errors: [ + { + messageId: 'noTypeAlias', + }, + ], + }, + { + code: 'type Foo = readonly [number] | keyof [number, number]', + options: [{ allowTupleTypes: 'never' }], + errors: [ + { + messageId: 'noCompositionAlias', + data: { + compositionType: 'union', + typeName: 'Tuple Types', + }, + line: 1, + column: 12, + }, + { + messageId: 'noCompositionAlias', + data: { + compositionType: 'union', + typeName: 'Tuple Types', + }, + line: 1, + column: 32, + }, + ], + }, + { + code: 'type Foo = keyof [number] & [number, number]', + options: [{ allowTupleTypes: 'in-unions' }], + errors: [ + { + messageId: 'noCompositionAlias', + data: { + compositionType: 'intersection', + typeName: 'Tuple Types', + }, + line: 1, + column: 12, + }, + { + messageId: 'noCompositionAlias', + data: { + compositionType: 'intersection', + typeName: 'Tuple Types', + }, + line: 1, + column: 29, + }, + ], + }, + { + code: 'type Foo = [number] | readonly [number, number]', + options: [{ allowTupleTypes: 'in-intersections' }], + errors: [ + { + messageId: 'noCompositionAlias', + data: { + compositionType: 'union', + typeName: 'Tuple Types', + }, + line: 1, + column: 12, + }, + { + messageId: 'noCompositionAlias', + data: { + compositionType: 'union', + typeName: 'Tuple Types', + }, + line: 1, + column: 23, + }, + ], + }, + { + code: 'type Foo = readonly [number];', + options: [{ allowTupleTypes: 'in-intersections' }], + errors: [ + { + messageId: 'noTypeAlias', + }, + ], + }, + { + code: 'type Foo = keyof [number];', + options: [{ allowTupleTypes: 'in-unions' }], + errors: [ + { + messageId: 'noTypeAlias', + }, + ], + }, + { + code: 'type Foo = readonly [number];', + options: [{ allowTupleTypes: 'in-unions-and-intersections' }], + errors: [ + { + messageId: 'noTypeAlias', + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts index 4c06e95a042e..4b4e496253f5 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts @@ -99,12 +99,6 @@ foo(str!); ` declare function a(a: string): any; declare const b: string | null; -class Mx { - @a(b!) - private prop = 1; -} - `, - ` class Mx { @a(b!) private prop = 1; diff --git a/packages/eslint-plugin/tests/rules/promise-function-async.test.ts b/packages/eslint-plugin/tests/rules/promise-function-async.test.ts index 829bb9a091c6..ed798ca18e27 100644 --- a/packages/eslint-plugin/tests/rules/promise-function-async.test.ts +++ b/packages/eslint-plugin/tests/rules/promise-function-async.test.ts @@ -63,6 +63,12 @@ const invalidAsyncModifiers = { }, set asyncGetter(p: Promise) { return p; + }, + get asyncGetterFunc() { + return async () => new Promise(); + }, + set asyncGetterFunc(p: () => Promise) { + return p; } } `, diff --git a/packages/eslint-plugin/tests/rules/quotes.test.ts b/packages/eslint-plugin/tests/rules/quotes.test.ts new file mode 100644 index 000000000000..bec1f4b7e98c --- /dev/null +++ b/packages/eslint-plugin/tests/rules/quotes.test.ts @@ -0,0 +1,662 @@ +import rule from '../../src/rules/quotes'; +import { RuleTester } from '../RuleTester'; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaVersion: 6, + sourceType: 'module', + ecmaFeatures: {}, + }, +}); + +/** + * the base rule `quotes` doesn't use a message id + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const useDoubleQuote: any = { + message: 'Strings must use doublequote.', +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const useSingleQuote: any = { + message: 'Strings must use singlequote.', +}; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const useBacktick: any = { + message: 'Strings must use backtick.', +}; + +ruleTester.run('quotes', rule, { + valid: [ + { + code: `declare module '*.html' {}`, + options: ['backtick'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: ` + class A { + public prop: IProps['prop']; + } + `, + options: ['backtick'], + parserOptions: { ecmaVersion: 6 }, + }, + + /** ESLint */ + `var foo = "bar";`, + { + code: `var foo = 'bar';`, + options: ['single'], + }, + { + code: `var foo = "bar";`, + options: ['double'], + }, + { + code: `var foo = 1;`, + options: ['single'], + }, + { + code: `var foo = 1;`, + options: ['double'], + }, + { + code: `var foo = "'";`, + options: [ + 'single', + { + avoidEscape: true, + }, + ], + }, + { + code: `var foo = '"';`, + options: [ + 'double', + { + avoidEscape: true, + }, + ], + }, + { + code: `var foo = <>Hello world;`, + options: ['single'], + parserOptions: { + ecmaVersion: 6, + ecmaFeatures: { + jsx: true, + }, + }, + }, + { + code: `var foo = <>Hello world;`, + options: ['double'], + parserOptions: { + ecmaVersion: 6, + ecmaFeatures: { + jsx: true, + }, + }, + }, + { + code: `var foo = <>Hello world;`, + options: [ + 'double', + { + avoidEscape: true, + }, + ], + parserOptions: { + ecmaVersion: 6, + ecmaFeatures: { + jsx: true, + }, + }, + }, + { + code: `var foo = <>Hello world;`, + options: ['backtick'], + parserOptions: { + ecmaVersion: 6, + ecmaFeatures: { + jsx: true, + }, + }, + }, + { + code: `var foo =
Hello world
;`, + options: ['single'], + parserOptions: { + ecmaVersion: 6, + ecmaFeatures: { + jsx: true, + }, + }, + }, + { + code: `var foo =
;`, + options: ['single'], + parserOptions: { + ecmaVersion: 6, + ecmaFeatures: { + jsx: true, + }, + }, + }, + { + code: `var foo =
Hello world
;`, + options: ['double'], + parserOptions: { + ecmaVersion: 6, + ecmaFeatures: { + jsx: true, + }, + }, + }, + { + code: `var foo =
Hello world
;`, + options: [ + 'double', + { + avoidEscape: true, + }, + ], + parserOptions: { + ecmaVersion: 6, + ecmaFeatures: { + jsx: true, + }, + }, + }, + { + code: 'var foo = `bar`;', + options: ['backtick'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: "var foo = `bar 'baz'`;", + options: ['backtick'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var foo = `bar "baz"`;', + options: ['backtick'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: `var foo = 1;`, + options: ['backtick'], + }, + { + code: 'var foo = "a string containing `backtick` quotes";', + options: [ + 'backtick', + { + avoidEscape: true, + }, + ], + }, + { + code: `var foo =
;`, + options: ['backtick'], + parserOptions: { + ecmaVersion: 6, + ecmaFeatures: { + jsx: true, + }, + }, + }, + { + code: `var foo =
Hello world
;`, + options: ['backtick'], + parserOptions: { + ecmaVersion: 6, + ecmaFeatures: { + jsx: true, + }, + }, + }, + + // Backticks are only okay if they have substitutions, contain a line break, or are tagged + { + code: 'var foo = `back\ntick`;', + options: ['single'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var foo = `back\rtick`;', + options: ['single'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var foo = `back\u2028tick`;', + options: ['single'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var foo = `back\u2029tick`;', + options: ['single'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var foo = `back\\\\\ntick`;', // 2 backslashes followed by a newline + options: ['single'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var foo = `back\\\\\\\\\ntick`;', + options: ['single'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var foo = `\n`;', + options: ['single'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var foo = `back${x}tick`;', + options: ['double'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var foo = tag`backtick`;', + options: ['double'], + parserOptions: { ecmaVersion: 6 }, + }, + + // Backticks are also okay if allowTemplateLiterals + { + code: "var foo = `bar 'foo' baz` + 'bar';", + options: [ + 'single', + { + allowTemplateLiterals: true, + }, + ], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var foo = `bar \'foo\' baz` + "bar";', + options: [ + 'double', + { + allowTemplateLiterals: true, + }, + ], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: "var foo = `bar 'foo' baz` + `bar`;", + options: [ + 'backtick', + { + allowTemplateLiterals: true, + }, + ], + parserOptions: { ecmaVersion: 6 }, + }, + + // `backtick` should not warn the directive prologues. + { + code: '"use strict"; var foo = `backtick`;', + options: ['backtick'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: '"use strict"; \'use strong\'; "use asm"; var foo = `backtick`;', + options: ['backtick'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: + 'function foo() { "use strict"; "use strong"; "use asm"; var foo = `backtick`; }', + options: ['backtick'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: + "(function() { 'use strict'; 'use strong'; 'use asm'; var foo = `backtick`; })();", + options: ['backtick'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: + '(() => { "use strict"; "use strong"; "use asm"; var foo = `backtick`; })();', + options: ['backtick'], + parserOptions: { ecmaVersion: 6 }, + }, + + // `backtick` should not warn import/export sources. + { + code: `import "a"; import 'b';`, + options: ['backtick'], + parserOptions: { + ecmaVersion: 6, + sourceType: 'module', + }, + }, + { + code: `import a from "a"; import b from 'b';`, + options: ['backtick'], + parserOptions: { + ecmaVersion: 6, + sourceType: 'module', + }, + }, + { + code: `export * from "a"; export * from 'b';`, + options: ['backtick'], + parserOptions: { + ecmaVersion: 6, + sourceType: 'module', + }, + }, + + // `backtick` should not warn property/method names (not computed). + { + code: `var obj = {"key0": 0, 'key1': 1};`, + options: ['backtick'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: `class Foo { 'bar'(){} }`, + options: ['backtick'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: `class Foo { static ''(){} }`, + options: ['backtick'], + parserOptions: { ecmaVersion: 6 }, + }, + ], + + invalid: [ + { + code: `var foo = 'bar';`, + output: `var foo = "bar";`, + errors: [useDoubleQuote], + }, + { + code: `var foo = "bar";`, + output: `var foo = 'bar';`, + options: ['single'], + errors: [useSingleQuote], + }, + { + code: 'var foo = `bar`;', + output: `var foo = 'bar';`, + options: ['single'], + parserOptions: { ecmaVersion: 6 }, + errors: [useSingleQuote], + }, + { + code: `var foo = 'don\\'t';`, + output: `var foo = "don't";`, + errors: [useDoubleQuote], + }, + { + code: `var msg = "Plugin '" + name + "' not found"`, + output: `var msg = 'Plugin \\'' + name + '\\' not found'`, + options: ['single'], + errors: [ + { ...useSingleQuote, column: 11 }, + { ...useSingleQuote, column: 31 }, + ], + }, + { + code: `var foo = 'bar';`, + output: `var foo = "bar";`, + options: ['double'], + errors: [useDoubleQuote], + }, + { + code: 'var foo = `bar`;', + output: `var foo = "bar";`, + options: ['double'], + parserOptions: { ecmaVersion: 6 }, + errors: [useDoubleQuote], + }, + { + code: `var foo = "bar";`, + output: `var foo = 'bar';`, + options: [ + 'single', + { + avoidEscape: true, + }, + ], + errors: [useSingleQuote], + }, + { + code: `var foo = 'bar';`, + output: `var foo = "bar";`, + options: [ + 'double', + { + avoidEscape: true, + }, + ], + errors: [useDoubleQuote], + }, + { + code: `var foo = '\\\\';`, + output: `var foo = "\\\\\";`, // eslint-disable-line no-useless-escape + options: [ + 'double', + { + avoidEscape: true, + }, + ], + errors: [useDoubleQuote], + }, + { + code: `var foo = "bar";`, + output: `var foo = 'bar';`, + options: [ + 'single', + { + allowTemplateLiterals: true, + }, + ], + errors: [useSingleQuote], + }, + { + code: `var foo = 'bar';`, + output: `var foo = "bar";`, + options: [ + 'double', + { + allowTemplateLiterals: true, + }, + ], + errors: [useDoubleQuote], + }, + { + code: `var foo = 'bar';`, + output: 'var foo = `bar`;', + options: ['backtick'], + parserOptions: { ecmaVersion: 2015 }, + errors: [useBacktick], + }, + { + code: "var foo = 'b${x}a$r';", + output: 'var foo = `b\\${x}a$r`;', + options: ['backtick'], + parserOptions: { ecmaVersion: 2015 }, + errors: [useBacktick], + }, + { + code: 'var foo = "bar";', + output: 'var foo = `bar`;', + options: ['backtick'], + parserOptions: { ecmaVersion: 2015 }, + errors: [useBacktick], + }, + { + code: `var foo = "bar";`, + output: 'var foo = `bar`;', + options: [ + 'backtick', + { + avoidEscape: true, + }, + ], + parserOptions: { ecmaVersion: 2015 }, + errors: [useBacktick], + }, + { + code: `var foo = 'bar';`, + output: 'var foo = `bar`;', + options: [ + 'backtick', + { + avoidEscape: true, + }, + ], + parserOptions: { ecmaVersion: 2015 }, + errors: [useBacktick], + }, + + // "use strict" is *not* a directive prologue in these statements so is subject to the rule + { + code: 'var foo = `backtick`; "use strict";', + output: 'var foo = `backtick`; `use strict`;', + options: ['backtick'], + parserOptions: { ecmaVersion: 6 }, + errors: [useBacktick], + }, + { + code: '{ "use strict"; var foo = `backtick`; }', + output: '{ `use strict`; var foo = `backtick`; }', + options: ['backtick'], + parserOptions: { ecmaVersion: 6 }, + errors: [useBacktick], + }, + { + code: 'if (1) { "use strict"; var foo = `backtick`; }', + output: 'if (1) { `use strict`; var foo = `backtick`; }', + options: ['backtick'], + parserOptions: { ecmaVersion: 6 }, + errors: [useBacktick], + }, + + // `backtick` should warn computed property names. + { + code: `var obj = {["key0"]: 0, ['key1']: 1};`, + output: 'var obj = {[`key0`]: 0, [`key1`]: 1};', + options: ['backtick'], + parserOptions: { ecmaVersion: 6 }, + errors: [useBacktick, useBacktick], + }, + { + code: `class Foo { ['a'](){} static ['b'](){} }`, + output: 'class Foo { [`a`](){} static [`b`](){} }', + options: ['backtick'], + parserOptions: { ecmaVersion: 6 }, + errors: [useBacktick, useBacktick], + }, + + // https://github.com/eslint/eslint/issues/7084 + { + code: `
`, + output: `
`, + options: [`single`], + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + errors: [useSingleQuote], + }, + { + code: `
`, + output: `
`, + options: ['double'], + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + errors: [useDoubleQuote], + }, + { + code: `
`, + output: '
', + options: ['backtick'], + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + ecmaVersion: 2015, + }, + errors: [useBacktick], + }, + + // https://github.com/eslint/eslint/issues/7610 + { + code: '`use strict`;', + output: null, + parserOptions: { ecmaVersion: 6 }, + errors: [useDoubleQuote], + }, + { + code: 'function foo() { `use strict`; foo(); }', + output: null, + parserOptions: { ecmaVersion: 6 }, + errors: [useDoubleQuote], + }, + { + code: 'foo = function() { `use strict`; foo(); }', + output: null, + parserOptions: { ecmaVersion: 6 }, + errors: [useDoubleQuote], + }, + { + code: '() => { `use strict`; foo(); }', + output: null, + parserOptions: { ecmaVersion: 6 }, + errors: [useDoubleQuote], + }, + { + code: '() => { foo(); `use strict`; }', + output: `() => { foo(); "use strict"; }`, + parserOptions: { ecmaVersion: 6 }, + errors: [useDoubleQuote], + }, + { + code: 'foo(); `use strict`;', + output: 'foo(); "use strict";', + parserOptions: { ecmaVersion: 6 }, + errors: [useDoubleQuote], + }, + + // https://github.com/eslint/eslint/issues/7646 + { + code: 'var foo = `foo\\nbar`;', + output: 'var foo = "foo\\nbar";', + parserOptions: { ecmaVersion: 6 }, + errors: [useDoubleQuote], + }, + { + code: 'var foo = `foo\\\nbar`;', // 1 backslash followed by a newline + output: 'var foo = "foo\\\nbar";', + parserOptions: { ecmaVersion: 6 }, + errors: [useDoubleQuote], + }, + { + code: 'var foo = `foo\\\\\\\nbar`;', // 3 backslashes followed by a newline + output: 'var foo = "foo\\\\\\\nbar";', + parserOptions: { ecmaVersion: 6 }, + errors: [useDoubleQuote], + }, + { + code: '````', + output: '""``', + parserOptions: { ecmaVersion: 6 }, + errors: [{ ...useDoubleQuote, line: 1, column: 1 }], + }, + ], +}); diff --git a/packages/eslint-plugin/tests/rules/require-await.test.ts b/packages/eslint-plugin/tests/rules/require-await.test.ts index 3ea207713b2d..38a925de1d2c 100644 --- a/packages/eslint-plugin/tests/rules/require-await.test.ts +++ b/packages/eslint-plugin/tests/rules/require-await.test.ts @@ -41,9 +41,15 @@ ruleTester.run('require-await', rule, { }`, }, { - // Non-async arrow function expression + // Non-async arrow function expression (concise-body) code: `const numberOne = (): number => 1;`, }, + { + // Non-async arrow function expression (block-body) + code: `const numberOne = (): number => { + return 1; + };`, + }, { // Async function declaration with await code: `async function numberOne(): Promise { @@ -57,9 +63,15 @@ ruleTester.run('require-await', rule, { }`, }, { - // Async arrow function expression with await + // Async arrow function expression with await (concise-body) code: `const numberOne = async (): Promise => await 1;`, }, + { + // Async arrow function expression with await (block-body) + code: `const numberOne = async (): Promise => { + return await 1; + };`, + }, { // Async function declaration with promise return code: `async function numberOne(): Promise { @@ -72,6 +84,16 @@ ruleTester.run('require-await', rule, { return Promise.resolve(1); }`, }, + { + // Async arrow function with promise return (concise-body) + code: `const numberOne = async (): Promise => Promise.resolve(1);`, + }, + { + // Async arrow function with promise return (block-body) + code: `const numberOne = async (): Promise => { + return Promise.resolve(1); + };`, + }, { // Async function declaration with async function return code: `async function numberOne(): Promise { @@ -90,6 +112,22 @@ ruleTester.run('require-await', rule, { return Promise.resolve(x); }`, }, + { + // Async arrow function with async function return (concise-body) + code: `const numberOne = async (): Promise => getAsyncNumber(1); + const getAsyncNumber = async function(x: number): Promise { + return Promise.resolve(x); + }`, + }, + { + // Async arrow function with async function return (block-body) + code: `const numberOne = async (): Promise => { + return getAsyncNumber(1); + }; + const getAsyncNumber = async function(x: number): Promise { + return Promise.resolve(x); + }`, + }, ], invalid: [ diff --git a/packages/eslint-plugin/tests/rules/restrict-plus-operands.test.ts b/packages/eslint-plugin/tests/rules/restrict-plus-operands.test.ts index fdda8fe0ef4b..44583a202a1d 100644 --- a/packages/eslint-plugin/tests/rules/restrict-plus-operands.test.ts +++ b/packages/eslint-plugin/tests/rules/restrict-plus-operands.test.ts @@ -26,37 +26,37 @@ ruleTester.run('restrict-plus-operands', rule, { `var foo = BigInt(1) + 1n`, `var foo = 1n; foo + 2n`, ` -function test () : number { return 2; } +function test(s: string, n: number) : number { return 2; } var foo = test("5.5", 10) + 10; - `, + `, ` var x = 5; var z = 8.2; var foo = x + z; - `, + `, ` var w = "6.5"; var y = "10"; var foo = y + w; - `, + `, 'var foo = 1 + 1;', "var foo = '1' + '1';", ` var pair: { first: number, second: string } = { first: 5, second: "10" }; var foo = pair.first + 10; - `, + `, ` var pair: { first: number, second: string } = { first: 5, second: "10" }; var foo = pair.first + (10 as number); - `, + `, ` var pair: { first: number, second: string } = { first: 5, second: "10" }; var foo = "5.5" + pair.second; - `, + `, ` var pair: { first: number, second: string } = { first: 5, second: "10" }; var foo = ("5.5" as string) + pair.second; - `, + `, `const foo = 'hello' + (someBoolean ? 'a' : 'b') + (() => someBoolean ? 'c' : 'd')() + 'e';`, `const balls = true;`, `balls === true;`, diff --git a/packages/eslint-plugin/tests/rules/typedef.test.ts b/packages/eslint-plugin/tests/rules/typedef.test.ts index b1a7b08bcf09..c9e35c356eb2 100644 --- a/packages/eslint-plugin/tests/rules/typedef.test.ts +++ b/packages/eslint-plugin/tests/rules/typedef.test.ts @@ -109,6 +109,19 @@ ruleTester.run('typedef', rule, { `function receivesString({ a }: { a: string }): void { }`, `function receivesStrings({ a, b }: { [i: string ]: string }): void { }`, `function receivesNumber(a: number = 123): void { }`, + // Constructor parameters + `class Test { + constructor() {} + }`, + `class Test { + constructor(param: string) {} + }`, + `class Test { + constructor(param: string = 'something') {} + }`, + `class Test { + constructor(private param: string = 'something') {} + }`, // Method parameters `class Test { public method(x: number): number { return x; } @@ -198,6 +211,24 @@ ruleTester.run('typedef', rule, { }, ], }, + { + code: `const [a, b] = [1, 2];`, + options: [ + { + objectDestructuring: false, + variableDeclaration: true, + }, + ], + }, + { + code: `const { a, b } = { a: '', b: '' };`, + options: [ + { + objectDestructuring: false, + variableDeclaration: true, + }, + ], + }, // Contexts where TypeScript doesn't allow annotations { code: `for (x of [1, 2, 3]) { }`, @@ -383,6 +414,40 @@ ruleTester.run('typedef', rule, { }, ], }, + // Constructor parameters + { + code: `class Test { + constructor(param) {} + }`, + errors: [ + { + column: 21, + messageId: 'expectedTypedefNamed', + }, + ], + }, + { + code: `class Test { + constructor(param = 'something') {} + }`, + errors: [ + { + column: 21, + messageId: 'expectedTypedef', + }, + ], + }, + { + code: `class Test { + constructor(private param = 'something') {} + }`, + errors: [ + { + column: 21, + messageId: 'expectedTypedef', + }, + ], + }, // Method parameters { code: `class Test { diff --git a/packages/eslint-plugin/tests/rules/unbound-method.test.ts b/packages/eslint-plugin/tests/rules/unbound-method.test.ts index 061b4edf524b..520b28fc1d1d 100644 --- a/packages/eslint-plugin/tests/rules/unbound-method.test.ts +++ b/packages/eslint-plugin/tests/rules/unbound-method.test.ts @@ -52,15 +52,27 @@ instance.unbound\`\`; if (instance.bound) { } if (instance.unbound) { } +if (instance.bound !== undefined) { } +if (instance.unbound !== undefined) { } + if (ContainsMethods.boundStatic) { } if (ContainsMethods.unboundStatic) { } +if (ContainsMethods.boundStatic !== undefined) { } +if (ContainsMethods.unboundStatic !== undefined) { } + while (instance.bound) { } while (instance.unbound) { } +while (instance.bound !== undefined) { } +while (instance.unbound !== undefined) { } + while (ContainsMethods.boundStatic) { } while (ContainsMethods.unboundStatic) { } +while (ContainsMethods.boundStatic !== undefined) { } +while (ContainsMethods.unboundStatic !== undefined) { } + instance.bound as any; ContainsMethods.boundStatic as any; @@ -97,6 +109,12 @@ instane.boundStatic && 0; ContainsMethods.boundStatic ? 1 : 0; ContainsMethods.unboundStatic ? 1 : 0; + +typeof instance.bound === 'function'; +typeof instance.unbound === 'function'; + +typeof ContainsMethods.boundStatic === 'function'; +typeof ContainsMethods.unboundStatic === 'function'; `, `interface RecordA { readonly type: "A" diff --git a/packages/eslint-plugin/tests/rules/unified-signatures.test.ts b/packages/eslint-plugin/tests/rules/unified-signatures.test.ts index 5d65e2f18013..c8f9740662dd 100644 --- a/packages/eslint-plugin/tests/rules/unified-signatures.test.ts +++ b/packages/eslint-plugin/tests/rules/unified-signatures.test.ts @@ -105,6 +105,30 @@ interface I { function f(x: T[]): void; function f(x: T): void; `, + // Same name, different scopes + ` +declare function foo(n: number): number; + +declare module "hello" { + function foo(n: number, s: string): number; +} +`, + // children of block not checked to match TSLint + ` +{ + function block(): number; + function block(n: number): number; + function block(n?: number): number { + return 3; + } +} +`, + ` +export interface Foo { + bar(baz: string): number[]; + bar(): string[]; +} +`, ], invalid: [ { @@ -591,5 +615,39 @@ class Foo { }, ], }, + { + code: ` +export function foo(line: number): number; +export function foo(line: number, character?: number): number; +`, + errors: [ + { + messageId: 'omittingSingleParameter', + data: { + failureStringStart: + 'These overloads can be combined into one signature', + }, + line: 3, + column: 35, + }, + ], + }, + { + code: ` +declare function foo(line: number): number; +export function foo(line: number, character?: number): number; +`, + errors: [ + { + messageId: 'omittingSingleParameter', + data: { + failureStringStart: + 'These overloads can be combined into one signature', + }, + line: 3, + column: 35, + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tools/generate-configs.ts b/packages/eslint-plugin/tools/generate-configs.ts index cda1b0771ead..b400ca057224 100644 --- a/packages/eslint-plugin/tools/generate-configs.ts +++ b/packages/eslint-plugin/tools/generate-configs.ts @@ -29,6 +29,7 @@ const BASE_RULES_TO_BE_OVERRIDDEN = new Set([ 'no-empty-function', 'no-extra-parens', 'no-magic-numbers', + 'quotes', 'no-unused-vars', 'no-use-before-define', 'no-useless-constructor', diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index 4e5fe5b64495..dcc37035c9de 100644 --- a/packages/eslint-plugin/typings/eslint-rules.d.ts +++ b/packages/eslint-plugin/typings/eslint-rules.d.ts @@ -461,3 +461,23 @@ declare module 'eslint/lib/rules/semi' { >; export = rule; } + +declare module 'eslint/lib/rules/quotes' { + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; + + const rule: TSESLint.RuleModule< + never, + [ + 'single' | 'double' | 'backtick', + { + allowTemplateLiterals?: boolean; + avoidEscape?: boolean; + }?, + ], + { + Literal(node: TSESTree.Literal): void; + TemplateLiteral(node: TSESTree.TemplateLiteral): void; + } + >; + export = rule; +} diff --git a/packages/experimental-utils/CHANGELOG.md b/packages/experimental-utils/CHANGELOG.md index 8ea9fd4a22c6..4ed8006f4c7e 100644 --- a/packages/experimental-utils/CHANGELOG.md +++ b/packages/experimental-utils/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.1.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.0.0...v2.1.0) (2019-09-02) + +**Note:** Version bump only for package @typescript-eslint/experimental-utils + + + + + # [2.0.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.13.0...v2.0.0) (2019-08-13) diff --git a/packages/experimental-utils/package.json b/packages/experimental-utils/package.json index c4fde675f184..2f58babdc68d 100644 --- a/packages/experimental-utils/package.json +++ b/packages/experimental-utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/experimental-utils", - "version": "2.0.0", + "version": "2.1.0", "description": "(Experimental) Utilities for working with TypeScript + ESLint together", "keywords": [ "eslint", @@ -37,7 +37,7 @@ }, "dependencies": { "@types/json-schema": "^7.0.3", - "@typescript-eslint/typescript-estree": "2.0.0", + "@typescript-eslint/typescript-estree": "2.1.0", "eslint-scope": "^4.0.0" }, "peerDependencies": { diff --git a/packages/parser/CHANGELOG.md b/packages/parser/CHANGELOG.md index 23eb1182e2a2..0a9561dc15f9 100644 --- a/packages/parser/CHANGELOG.md +++ b/packages/parser/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.1.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.0.0...v2.1.0) (2019-09-02) + + +### Bug Fixes + +* **typescript-estree:** improve missing project file error msg ([#866](https://github.com/typescript-eslint/typescript-eslint/issues/866)) ([8f3b0a8](https://github.com/typescript-eslint/typescript-eslint/commit/8f3b0a8)), closes [#853](https://github.com/typescript-eslint/typescript-eslint/issues/853) + + +### Features + +* **typescript-estree:** Accept a glob pattern for `options.project` ([#806](https://github.com/typescript-eslint/typescript-eslint/issues/806)) ([9e5f21e](https://github.com/typescript-eslint/typescript-eslint/commit/9e5f21e)) + + + + + # [2.0.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.13.0...v2.0.0) (2019-08-13) diff --git a/packages/parser/README.md b/packages/parser/README.md index 45c620280f15..042fb4f5f540 100644 --- a/packages/parser/README.md +++ b/packages/parser/README.md @@ -50,27 +50,50 @@ The following additional configuration options are available by specifying them - **`project`** - default `undefined`. This option allows you to provide a path to your project's `tsconfig.json`. **This setting is required if you want to use rules which require type information**. You may want to use this setting in tandem with the `tsconfigRootDir` option below. + - Accepted values: + + ```js + // path + project: './tsconfig.json'; + + // glob pattern + project: './packages/**/tsconfig.json'; + + // array of paths and/or glob patterns + project: [ + './packages/**/tsconfig.json', + './separate-package/tsconfig.json', + ]; + ``` + + - Note that if you use project references, TypeScript will not automatically use project references to resolve files. This means that you will have to add each referenced tsconfig to the `project` field either separately, or via a glob. + - Note that if this setting is specified and `createDefaultProgram` is not, you must only lint files that are included in the projects as defined by the provided `tsconfig.json` files. If your existing configuration does not include all of the files you would like to lint, you can create a separate `tsconfig.eslint.json` as follows: - ```ts - { - "extends": "./tsconfig.json", // path to existing tsconfig - "includes": [ - "src/**/*.ts", - "test/**/*.ts", - // etc - ] - } - ``` + ```jsonc + { + // extend your base config so you don't have to redefine your compilerOptions + "extends": "./tsconfig.json", + "include": [ + "src/**/*.ts", + "test/**/*.ts", + "typings/**/*.ts", + // etc + + // if you have a mixed JS/TS codebase, don't forget to include your JS files + "src/**/*.js" + ] + } + ``` - **`tsconfigRootDir`** - default `undefined`. This option allows you to provide the root directory for relative tsconfig paths specified in the `project` option above. -- **`createDefaultProgram`** - default `false`. This option allows you to request that when the `project` setting is specified, files will be allowed when not included in the projects defined by the provided `tsconfig.json` files. However, this may incur significant performance costs, so this option is primarily included for backwards-compatibility. See the **`project`** section for more information. - - **`extraFileExtensions`** - default `undefined`. This option allows you to provide one or more additional file extensions which should be considered in the TypeScript Program compilation. E.g. a `.vue` file - **`warnOnUnsupportedTypeScriptVersion`** - default `true`. This option allows you to toggle the warning that the parser will give you if you use a version of TypeScript which is not explicitly supported +- **`createDefaultProgram`** - default `false`. This option allows you to request that when the `project` setting is specified, files will be allowed when not included in the projects defined by the provided `tsconfig.json` files. **Using this option will incur significant performance costs. This option is primarily included for backwards-compatibility.** See the **`project`** section above for more information. + ### .eslintrc.json ```json diff --git a/packages/parser/package.json b/packages/parser/package.json index 4d67b9a555e5..34e5fb24f220 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -1,8 +1,9 @@ { "name": "@typescript-eslint/parser", - "version": "2.0.0", + "version": "2.1.0", "description": "An ESLint custom parser which leverages TypeScript ESTree", "main": "dist/parser.js", + "types": "dist/parser.d.ts", "files": [ "dist", "README.md", @@ -42,13 +43,13 @@ }, "dependencies": { "@types/eslint-visitor-keys": "^1.0.0", - "@typescript-eslint/experimental-utils": "2.0.0", - "@typescript-eslint/typescript-estree": "2.0.0", + "@typescript-eslint/experimental-utils": "2.1.0", + "@typescript-eslint/typescript-estree": "2.1.0", "eslint-visitor-keys": "^1.0.0" }, "devDependencies": { "@types/glob": "^7.1.1", - "@typescript-eslint/shared-fixtures": "2.0.0", + "@typescript-eslint/shared-fixtures": "2.1.0", "glob": "^7.1.4" } } diff --git a/packages/shared-fixtures/CHANGELOG.md b/packages/shared-fixtures/CHANGELOG.md index 3aec5aabbcd5..4f65be77af09 100644 --- a/packages/shared-fixtures/CHANGELOG.md +++ b/packages/shared-fixtures/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.1.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.0.0...v2.1.0) (2019-09-02) + +**Note:** Version bump only for package @typescript-eslint/shared-fixtures + + + + + # [2.0.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.13.0...v2.0.0) (2019-08-13) diff --git a/packages/shared-fixtures/package.json b/packages/shared-fixtures/package.json index 0fedd2edbe95..86b93d8157cc 100644 --- a/packages/shared-fixtures/package.json +++ b/packages/shared-fixtures/package.json @@ -1,5 +1,5 @@ { "name": "@typescript-eslint/shared-fixtures", - "version": "2.0.0", + "version": "2.1.0", "private": true } diff --git a/packages/typescript-estree/CHANGELOG.md b/packages/typescript-estree/CHANGELOG.md index 27a2f9ea6a3c..0fea11fa0f9c 100644 --- a/packages/typescript-estree/CHANGELOG.md +++ b/packages/typescript-estree/CHANGELOG.md @@ -3,6 +3,24 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [2.1.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.0.0...v2.1.0) (2019-09-02) + + +### Bug Fixes + +* **eslint-plugin:** [unified-signatures] type comparison and exported nodes ([#839](https://github.com/typescript-eslint/typescript-eslint/issues/839)) ([580eceb](https://github.com/typescript-eslint/typescript-eslint/commit/580eceb)) +* **typescript-estree:** improve missing project file error msg ([#866](https://github.com/typescript-eslint/typescript-eslint/issues/866)) ([8f3b0a8](https://github.com/typescript-eslint/typescript-eslint/commit/8f3b0a8)), closes [#853](https://github.com/typescript-eslint/typescript-eslint/issues/853) + + +### Features + +* **eslint-plugin:** [no-type-alias] support tuples ([#775](https://github.com/typescript-eslint/typescript-eslint/issues/775)) ([c68e033](https://github.com/typescript-eslint/typescript-eslint/commit/c68e033)) +* **typescript-estree:** Accept a glob pattern for `options.project` ([#806](https://github.com/typescript-eslint/typescript-eslint/issues/806)) ([9e5f21e](https://github.com/typescript-eslint/typescript-eslint/commit/9e5f21e)) + + + + + # [2.0.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.13.0...v2.0.0) (2019-08-13) diff --git a/packages/typescript-estree/jest.config.js b/packages/typescript-estree/jest.config.js index 4005947d2777..e01f6ed07751 100644 --- a/packages/typescript-estree/jest.config.js +++ b/packages/typescript-estree/jest.config.js @@ -10,4 +10,12 @@ module.exports = { collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}'], moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], coverageReporters: ['text-summary', 'lcov'], + globals: { + 'ts-jest': { + diagnostics: { + // ignore the diagnostic error for the invalidFileErrors fixtures + ignoreCodes: [5056], + }, + }, + }, }; diff --git a/packages/typescript-estree/package.json b/packages/typescript-estree/package.json index d7c8bae66ef9..4abba4d4ca0c 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/typescript-estree", - "version": "2.0.0", + "version": "2.1.0", "description": "A parser that converts TypeScript source code into an ESTree compatible form", "main": "dist/parser.js", "types": "dist/parser.d.ts", @@ -41,6 +41,8 @@ "unit-tests": "jest \"./tests/lib/.*\"" }, "dependencies": { + "glob": "^7.1.4", + "is-glob": "^4.0.1", "lodash.unescape": "4.0.1", "semver": "^6.2.0" }, @@ -50,10 +52,11 @@ "@babel/types": "^7.3.2", "@types/babel-code-frame": "^6.20.1", "@types/glob": "^7.1.1", + "@types/is-glob": "^4.0.1", "@types/lodash.isplainobject": "^4.0.4", "@types/lodash.unescape": "^4.0.4", "@types/semver": "^6.0.1", - "@typescript-eslint/shared-fixtures": "2.0.0", + "@typescript-eslint/shared-fixtures": "2.1.0", "babel-code-frame": "^6.26.0", "glob": "^7.1.4", "lodash.isplainobject": "4.0.6", diff --git a/packages/typescript-estree/src/convert.ts b/packages/typescript-estree/src/convert.ts index 3a7d2de99dc1..43ab7889c6bb 100644 --- a/packages/typescript-estree/src/convert.ts +++ b/packages/typescript-estree/src/convert.ts @@ -67,7 +67,7 @@ export class Converter { */ constructor(ast: ts.SourceFile, options: ConverterOptions) { this.ast = ast; - this.options = options; + this.options = { ...options }; } getASTMaps(): ASTMaps { diff --git a/packages/typescript-estree/src/parser.ts b/packages/typescript-estree/src/parser.ts index f215fa44492c..df8df03f0c29 100644 --- a/packages/typescript-estree/src/parser.ts +++ b/packages/typescript-estree/src/parser.ts @@ -1,5 +1,8 @@ +import path from 'path'; import semver from 'semver'; import * as ts from 'typescript'; // leave this as * as ts so people using util package don't need syntheticDefaultImports +import { sync as globSync } from 'glob'; +import isGlob from 'is-glob'; import { astConverter } from './ast-converter'; import { convertError } from './convert'; import { firstDefined } from './node-utils'; @@ -9,6 +12,7 @@ import { TSESTree } from './ts-estree'; import { calculateProjectParserOptions, createProgram, + defaultCompilerOptions, } from './tsconfig-parser'; /** @@ -87,9 +91,39 @@ function getASTFromProject( ); if (!astAndProgram && !createDefaultProgram) { - throw new Error( - `If "parserOptions.project" has been set for @typescript-eslint/parser, ${filePath} must be included in at least one of the projects provided.`, - ); + // the file was either not matched within the tsconfig, or the extension wasn't expected + const errorLines = [ + '"parserOptions.project" has been set for @typescript-eslint/parser.', + `The file does not match your project config: ${filePath}.`, + ]; + let hasMatchedAnError = false; + + const fileExtension = path.extname(filePath); + if (!['.ts', '.tsx', '.js', '.jsx'].includes(fileExtension)) { + const nonStandardExt = `The extension for the file (${fileExtension}) is non-standard`; + if (extra.extraFileExtensions && extra.extraFileExtensions.length > 0) { + if (!extra.extraFileExtensions.includes(fileExtension)) { + errorLines.push( + `${nonStandardExt}. It should be added to your existing "parserOptions.extraFileExtensions".`, + ); + hasMatchedAnError = true; + } + } else { + errorLines.push( + `${nonStandardExt}. You should add "parserOptions.extraFileExtensions" to your config.`, + ); + hasMatchedAnError = true; + } + } + + if (!hasMatchedAnError) { + errorLines.push( + 'The file must be included in at least one of the projects provided.', + ); + hasMatchedAnError = true; + } + + throw new Error(errorLines.join('\n')); } return astAndProgram; @@ -158,6 +192,7 @@ function createNewProgram(code: string): ASTAndProgram { noResolve: true, target: ts.ScriptTarget.Latest, jsx: extra.jsx ? ts.JsxEmit.Preserve : undefined, + ...defaultCompilerOptions, }, compilerHost, ); @@ -252,6 +287,15 @@ function applyParserOptionsToExtra(options: TSESTreeOptions): void { extra.projects = options.project; } + // Transform glob patterns into paths + if (extra.projects) { + extra.projects = extra.projects.reduce( + (projects, project) => + projects.concat(isGlob(project) ? globSync(project) : project), + [], + ); + } + if (typeof options.tsconfigRootDir === 'string') { extra.tsconfigRootDir = options.tsconfigRootDir; } diff --git a/packages/typescript-estree/src/ts-estree/ts-estree.ts b/packages/typescript-estree/src/ts-estree/ts-estree.ts index f079fd68d471..51529a470cba 100644 --- a/packages/typescript-estree/src/ts-estree/ts-estree.ts +++ b/packages/typescript-estree/src/ts-estree/ts-estree.ts @@ -1040,6 +1040,7 @@ export interface TSConstructSignatureDeclaration extends FunctionSignatureBase { } export interface TSDeclareFunction extends FunctionDeclarationBase { + id: Identifier; type: AST_NODE_TYPES.TSDeclareFunction; } @@ -1323,7 +1324,7 @@ export interface TSTypeLiteral extends BaseNode { export interface TSTypeOperator extends BaseNode { type: AST_NODE_TYPES.TSTypeOperator; operator: 'keyof' | 'unique' | 'readonly'; - typeAnnotation?: TSTypeAnnotation; + typeAnnotation?: TypeNode; } export interface TSTypeParameter extends BaseNode { diff --git a/packages/typescript-estree/src/tsconfig-parser.ts b/packages/typescript-estree/src/tsconfig-parser.ts index 88c631715450..364dd0543c8e 100644 --- a/packages/typescript-estree/src/tsconfig-parser.ts +++ b/packages/typescript-estree/src/tsconfig-parser.ts @@ -9,9 +9,10 @@ import { Extra } from './parser-options'; /** * Default compiler options for program generation from single root file */ -const defaultCompilerOptions: ts.CompilerOptions = { +export const defaultCompilerOptions: ts.CompilerOptions = { allowNonTsExtensions: true, allowJs: true, + checkJs: true, }; /** @@ -71,7 +72,7 @@ function getTsconfigPath(tsconfigPath: string, extra: Extra): string { * @param code The code being linted * @param filePath The path of the file being parsed * @param extra.tsconfigRootDir The root directory for relative tsconfig paths - * @param extra.project Provided tsconfig paths + * @param extra.projects Provided tsconfig paths * @returns The programs corresponding to the supplied tsconfig paths */ export function calculateProjectParserOptions( @@ -109,7 +110,7 @@ export function calculateProjectParserOptions( // create compiler host const watchCompilerHost = ts.createWatchCompilerHost( tsconfigPath, - /*optionsToExtend*/ { allowNonTsExtensions: true } as ts.CompilerOptions, + defaultCompilerOptions, ts.sys, ts.createSemanticDiagnosticsBuilderProgram, diagnosticReporter, @@ -206,7 +207,7 @@ export function calculateProjectParserOptions( * @param code The code being linted * @param filePath The file being linted * @param extra.tsconfigRootDir The root directory for relative tsconfig paths - * @param extra.project Provided tsconfig paths + * @param extra.projects Provided tsconfig paths * @returns The program containing just the file being linted and associated library files */ export function createProgram( diff --git a/packages/typescript-estree/tests/fixtures/invalidFileErrors/js/included.js b/packages/typescript-estree/tests/fixtures/invalidFileErrors/js/included.js new file mode 100644 index 000000000000..25005f98f8fd --- /dev/null +++ b/packages/typescript-estree/tests/fixtures/invalidFileErrors/js/included.js @@ -0,0 +1 @@ +export var a = true; diff --git a/packages/typescript-estree/tests/fixtures/invalidFileErrors/js/included.jsx b/packages/typescript-estree/tests/fixtures/invalidFileErrors/js/included.jsx new file mode 100644 index 000000000000..25005f98f8fd --- /dev/null +++ b/packages/typescript-estree/tests/fixtures/invalidFileErrors/js/included.jsx @@ -0,0 +1 @@ +export var a = true; diff --git a/packages/typescript-estree/tests/fixtures/invalidFileErrors/js/notIncluded.js b/packages/typescript-estree/tests/fixtures/invalidFileErrors/js/notIncluded.js new file mode 100644 index 000000000000..25005f98f8fd --- /dev/null +++ b/packages/typescript-estree/tests/fixtures/invalidFileErrors/js/notIncluded.js @@ -0,0 +1 @@ +export var a = true; diff --git a/packages/typescript-estree/tests/fixtures/invalidFileErrors/js/notIncluded.jsx b/packages/typescript-estree/tests/fixtures/invalidFileErrors/js/notIncluded.jsx new file mode 100644 index 000000000000..25005f98f8fd --- /dev/null +++ b/packages/typescript-estree/tests/fixtures/invalidFileErrors/js/notIncluded.jsx @@ -0,0 +1 @@ +export var a = true; diff --git a/packages/typescript-estree/tests/fixtures/invalidFileErrors/other/included.vue b/packages/typescript-estree/tests/fixtures/invalidFileErrors/other/included.vue new file mode 100644 index 000000000000..25005f98f8fd --- /dev/null +++ b/packages/typescript-estree/tests/fixtures/invalidFileErrors/other/included.vue @@ -0,0 +1 @@ +export var a = true; diff --git a/packages/typescript-estree/tests/fixtures/invalidFileErrors/other/notIncluded.vue b/packages/typescript-estree/tests/fixtures/invalidFileErrors/other/notIncluded.vue new file mode 100644 index 000000000000..25005f98f8fd --- /dev/null +++ b/packages/typescript-estree/tests/fixtures/invalidFileErrors/other/notIncluded.vue @@ -0,0 +1 @@ +export var a = true; diff --git a/packages/typescript-estree/tests/fixtures/invalidFileErrors/other/unknownFileType.unknown b/packages/typescript-estree/tests/fixtures/invalidFileErrors/other/unknownFileType.unknown new file mode 100644 index 000000000000..25005f98f8fd --- /dev/null +++ b/packages/typescript-estree/tests/fixtures/invalidFileErrors/other/unknownFileType.unknown @@ -0,0 +1 @@ +export var a = true; diff --git a/packages/typescript-estree/tests/fixtures/invalidFileErrors/ts/included.ts b/packages/typescript-estree/tests/fixtures/invalidFileErrors/ts/included.ts new file mode 100644 index 000000000000..25005f98f8fd --- /dev/null +++ b/packages/typescript-estree/tests/fixtures/invalidFileErrors/ts/included.ts @@ -0,0 +1 @@ +export var a = true; diff --git a/packages/typescript-estree/tests/fixtures/invalidFileErrors/ts/included.tsx b/packages/typescript-estree/tests/fixtures/invalidFileErrors/ts/included.tsx new file mode 100644 index 000000000000..25005f98f8fd --- /dev/null +++ b/packages/typescript-estree/tests/fixtures/invalidFileErrors/ts/included.tsx @@ -0,0 +1 @@ +export var a = true; diff --git a/packages/typescript-estree/tests/fixtures/invalidFileErrors/ts/notIncluded.ts b/packages/typescript-estree/tests/fixtures/invalidFileErrors/ts/notIncluded.ts new file mode 100644 index 000000000000..25005f98f8fd --- /dev/null +++ b/packages/typescript-estree/tests/fixtures/invalidFileErrors/ts/notIncluded.ts @@ -0,0 +1 @@ +export var a = true; diff --git a/packages/typescript-estree/tests/fixtures/invalidFileErrors/ts/notIncluded.tsx b/packages/typescript-estree/tests/fixtures/invalidFileErrors/ts/notIncluded.tsx new file mode 100644 index 000000000000..25005f98f8fd --- /dev/null +++ b/packages/typescript-estree/tests/fixtures/invalidFileErrors/ts/notIncluded.tsx @@ -0,0 +1 @@ +export var a = true; diff --git a/packages/typescript-estree/tests/fixtures/invalidFileErrors/tsconfig.json b/packages/typescript-estree/tests/fixtures/invalidFileErrors/tsconfig.json new file mode 100644 index 000000000000..de5d69d736c8 --- /dev/null +++ b/packages/typescript-estree/tests/fixtures/invalidFileErrors/tsconfig.json @@ -0,0 +1,9 @@ +{ + "include": [ + "ts/included.ts", + "ts/included.tsx", + "js/included.js", + "js/included.jsx", + "other/included.vue" + ] +} diff --git a/packages/typescript-estree/tests/lib/__snapshots__/parse.ts.snap b/packages/typescript-estree/tests/lib/__snapshots__/parse.ts.snap index 38e378d9f7e0..bca89ec8d8c6 100644 --- a/packages/typescript-estree/tests/lib/__snapshots__/parse.ts.snap +++ b/packages/typescript-estree/tests/lib/__snapshots__/parse.ts.snap @@ -189,6 +189,42 @@ Object { } `; +exports[`parse() invalid file error messages "parserOptions.extraFileExtensions" is non-empty the extension does not match 1`] = ` +"\\"parserOptions.project\\" has been set for @typescript-eslint/parser. +The file does not match your project config: tests/fixtures/invalidFileErrors/other/unknownFileType.unknown. +The extension for the file (.unknown) is non-standard. It should be added to your existing \\"parserOptions.extraFileExtensions\\"." +`; + +exports[`parse() invalid file error messages "parserOptions.extraFileExtensions" is non-empty the extension matches the file isn't included 1`] = ` +"\\"parserOptions.project\\" has been set for @typescript-eslint/parser. +The file does not match your project config: tests/fixtures/invalidFileErrors/other/notIncluded.vue. +The file must be included in at least one of the projects provided." +`; + +exports[`parse() invalid file error messages project includes errors for not included files 1`] = ` +"\\"parserOptions.project\\" has been set for @typescript-eslint/parser. +The file does not match your project config: tests/fixtures/invalidFileErrors/ts/notIncluded.ts. +The file must be included in at least one of the projects provided." +`; + +exports[`parse() invalid file error messages project includes errors for not included files 2`] = ` +"\\"parserOptions.project\\" has been set for @typescript-eslint/parser. +The file does not match your project config: tests/fixtures/invalidFileErrors/ts/notIncluded.tsx. +The file must be included in at least one of the projects provided." +`; + +exports[`parse() invalid file error messages project includes errors for not included files 3`] = ` +"\\"parserOptions.project\\" has been set for @typescript-eslint/parser. +The file does not match your project config: tests/fixtures/invalidFileErrors/js/notIncluded.js. +The file must be included in at least one of the projects provided." +`; + +exports[`parse() invalid file error messages project includes errors for not included files 4`] = ` +"\\"parserOptions.project\\" has been set for @typescript-eslint/parser. +The file does not match your project config: tests/fixtures/invalidFileErrors/js/notIncluded.jsx. +The file must be included in at least one of the projects provided." +`; + exports[`parse() non string code should correctly convert code to a string for parse() 1`] = ` Object { "body": Array [ diff --git a/packages/typescript-estree/tests/lib/parse.ts b/packages/typescript-estree/tests/lib/parse.ts index 57a4bc057424..6ed8db76cbc9 100644 --- a/packages/typescript-estree/tests/lib/parse.ts +++ b/packages/typescript-estree/tests/lib/parse.ts @@ -1,8 +1,8 @@ +import { join, resolve, relative } from 'path'; import * as parser from '../../src/parser'; import * as astConverter from '../../src/ast-converter'; import { TSESTreeOptions } from '../../src/parser-options'; import { createSnapshotTestBlock } from '../../tools/test-utils'; -import { join } from 'path'; const FIXTURES_DIR = './tests/fixtures/simpleProject'; @@ -145,7 +145,7 @@ describe('parse()', () => { }; const projectConfig: TSESTreeOptions = { ...baseConfig, - tsconfigRootDir: join(process.cwd(), FIXTURES_DIR), + tsconfigRootDir: FIXTURES_DIR, project: './tsconfig.json', }; @@ -241,4 +241,60 @@ describe('parse()', () => { ).toBeUndefined(); }); }); + + describe('invalid file error messages', () => { + const PROJECT_DIR = resolve(FIXTURES_DIR, '../invalidFileErrors'); + const code = 'var a = true'; + const config: TSESTreeOptions = { + comment: true, + tokens: true, + range: true, + loc: true, + tsconfigRootDir: PROJECT_DIR, + project: './tsconfig.json', + extraFileExtensions: ['.vue'], + }; + const testParse = (filePath: string) => (): void => { + parser.parseAndGenerateServices(code, { + ...config, + filePath: relative(process.cwd(), join(PROJECT_DIR, filePath)), + }); + }; + + describe('project includes', () => { + it("doesn't error for matched files", () => { + expect(testParse('ts/included.ts')).not.toThrow(); + expect(testParse('ts/included.tsx')).not.toThrow(); + expect(testParse('js/included.js')).not.toThrow(); + expect(testParse('js/included.jsx')).not.toThrow(); + }); + + it('errors for not included files', () => { + expect(testParse('ts/notIncluded.ts')).toThrowErrorMatchingSnapshot(); + expect(testParse('ts/notIncluded.tsx')).toThrowErrorMatchingSnapshot(); + expect(testParse('js/notIncluded.js')).toThrowErrorMatchingSnapshot(); + expect(testParse('js/notIncluded.jsx')).toThrowErrorMatchingSnapshot(); + }); + }); + + describe('"parserOptions.extraFileExtensions" is non-empty', () => { + describe('the extension matches', () => { + it('the file is included', () => { + expect(testParse('other/included.vue')).not.toThrow(); + }); + + it("the file isn't included", () => { + expect( + testParse('other/notIncluded.vue'), + ).toThrowErrorMatchingSnapshot(); + }); + }); + + it('the extension does not match', () => { + expect( + testParse('other/unknownFileType.unknown'), + ).toThrowErrorMatchingSnapshot(); + }); + }); + }); }); diff --git a/packages/typescript-estree/tests/lib/semanticInfo.ts b/packages/typescript-estree/tests/lib/semanticInfo.ts index 1afab0c5e444..157edec74126 100644 --- a/packages/typescript-estree/tests/lib/semanticInfo.ts +++ b/packages/typescript-estree/tests/lib/semanticInfo.ts @@ -236,9 +236,7 @@ describe('semanticInfo', () => { `function M() { return Base }`, createOptions(''), ), - ).toThrow( - `If "parserOptions.project" has been set for @typescript-eslint/parser, must be included in at least one of the projects provided.`, - ); + ).toThrow(/The file does not match your project config: /); }); it('non-existent project file', () => { diff --git a/packages/typescript-estree/tsconfig.json b/packages/typescript-estree/tsconfig.json index e389d7edef33..2ea9199d2638 100644 --- a/packages/typescript-estree/tsconfig.json +++ b/packages/typescript-estree/tsconfig.json @@ -3,5 +3,6 @@ "compilerOptions": { "outDir": "./dist" }, - "include": ["src", "tests", "tools"] + "include": ["src", "tests", "tools"], + "exclude": ["tests/fixtures/**/*"] } diff --git a/tools/generate-contributors.ts b/tools/generate-contributors.ts index 11b237656153..86de07723951 100644 --- a/tools/generate-contributors.ts +++ b/tools/generate-contributors.ts @@ -99,7 +99,7 @@ async function main(): Promise { imageSize: 100, commit: false, contributors, - contributorsPerLine: 7, + contributorsPerLine: 5, }; const rcPath = path.resolve(__dirname, '../.all-contributorsrc'); fs.writeFileSync(rcPath, JSON.stringify(allContributorsConfig, null, 2)); diff --git a/yarn.lock b/yarn.lock index c86527946ff0..7fef7edc509e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1345,6 +1345,11 @@ "@types/minimatch" "*" "@types/node" "*" +"@types/is-glob@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@types/is-glob/-/is-glob-4.0.1.tgz#a93eec1714172c8eb3225a1cc5eb88c2477b7d00" + integrity sha512-k3RS5HyBPu4h+5hTmIEfPB2rl5P3LnGdQEZrV2b9OWTJVtsUQ2VBcedqYKGqxvZqle5UALUXdSfVA8nf3HfyWQ== + "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff" @@ -1701,10 +1706,10 @@ ajv@^6.10.0, ajv@^6.5.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -all-contributors-cli@^6.8.0: - version "6.8.0" - resolved "https://registry.yarnpkg.com/all-contributors-cli/-/all-contributors-cli-6.8.0.tgz#1b98e9ee60ca3724ef50fb7469b8e85de1ebdec9" - integrity sha512-7xYAmljxgGL4w0XTRBuGTJMqf/xTGhvPyRbIp2InKfn0INo08faCT6gP18iyYMpVPotgAUcaGTeLrewh2IP54Q== +all-contributors-cli@^6.8.1: + version "6.8.1" + resolved "https://registry.yarnpkg.com/all-contributors-cli/-/all-contributors-cli-6.8.1.tgz#70c8c560ad05d054c09798d4a155887c82c5d553" + integrity sha512-06nnLE9Gl0gGqUIzmELNT/k8IWF31Xgq97GkuMJjEOS+3DFXuJ/0U+AJwa9UxP3Ivlqn484xXx4o3XDqPhytjA== dependencies: "@babel/runtime" "^7.2.0" async "^3.0.1" @@ -4706,7 +4711,7 @@ is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" -is-glob@^4.0.0: +is-glob@^4.0.0, is-glob@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==