diff --git a/CHANGELOG.md b/CHANGELOG.md index f2df49f1cc62..ff90c01198b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,27 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.12.0...v1.13.0) (2019-07-21) + + +### Bug Fixes + +* Correct `@types/json-schema` dependency ([#675](https://github.com/typescript-eslint/typescript-eslint/issues/675)) ([a5398ce](https://github.com/typescript-eslint/typescript-eslint/commit/a5398ce)) +* **eslint-plugin:** remove imports from typescript-estree ([#706](https://github.com/typescript-eslint/typescript-eslint/issues/706)) ([ceb2d32](https://github.com/typescript-eslint/typescript-eslint/commit/ceb2d32)), closes [#705](https://github.com/typescript-eslint/typescript-eslint/issues/705) +* **eslint-plugin:** undo breaking changes to recommended config ([93f72e3](https://github.com/typescript-eslint/typescript-eslint/commit/93f72e3)) +* **utils:** move `typescript` from peer dep to dev dep ([#712](https://github.com/typescript-eslint/typescript-eslint/issues/712)) ([f949355](https://github.com/typescript-eslint/typescript-eslint/commit/f949355)) +* **utils:** RuleTester should not require a parser ([#713](https://github.com/typescript-eslint/typescript-eslint/issues/713)) ([158a417](https://github.com/typescript-eslint/typescript-eslint/commit/158a417)) + + +### Features + +* **eslint-plugin:** add new rule no-misused-promises ([#612](https://github.com/typescript-eslint/typescript-eslint/issues/612)) ([28a131d](https://github.com/typescript-eslint/typescript-eslint/commit/28a131d)) +* **eslint-plugin:** add new rule require-await ([#674](https://github.com/typescript-eslint/typescript-eslint/issues/674)) ([807bc2d](https://github.com/typescript-eslint/typescript-eslint/commit/807bc2d)) + + + + + # [1.12.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.11.0...v1.12.0) (2019-07-12) diff --git a/README.md b/README.md index 6900259d2784..eabb3ee2eac6 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,7 @@ Babel does now support parsing (but not type-checking) TypeScript source code. T The key trade-off can be summarized as: `babel-eslint` supports additional syntax which TypeScript itself does not, but `typescript-eslint` supports creating rules based on type information, which is not available to babel because there is no type-checker. -Because they are therefore separate projects powered by different underlying tooling, they are currently not intended to be used together. +Because they are separate projects powered by different underlying tooling, they are currently not intended to be used together. Some of the people involved in `typescript-eslint` are also involved in Babel and `babel-eslint`, and in this project we are working hard to align on the AST format for non-standard JavaScript syntax. This is an ongoing effort. diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 8dcbd9ea422b..68b6badba15a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -32,9 +32,13 @@ jobs: displayName: 'Run linting' - script: | - yarn docs:check + yarn check:docs displayName: 'Validate documentation' + - script: | + yarn check:configs + displayName: 'Validate plugin configs' + - script: | yarn test displayName: 'Run unit tests' diff --git a/lerna.json b/lerna.json index d494e08b3c92..b700c7342eb2 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "1.12.0", + "version": "1.13.0", "npmClient": "yarn", "useWorkspaces": true, "stream": true diff --git a/package.json b/package.json index 2f0d94d38bd8..1ec463befed2 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "build": "lerna run build", "clean": "lerna clean && lerna run clean", "cz": "git-cz", - "docs:check": "lerna run docs:check", + "check:docs": "lerna run check:docs", + "check:configs": "lerna run check:configs", "generate-contributors": "yarn ts-node ./tools/generate-contributors.ts && yarn all-contributors generate", "format": "prettier --write \"./**/*.{ts,js,json,md}\"", "format-check": "prettier --list-different \"./**/*.{ts,js,json,md}\"", @@ -30,7 +31,8 @@ "lint-fix": "eslint . --ext .js,.ts --fix", "pre-commit": "yarn lint-staged", "pre-push": "yarn format-check", - "postinstall": "lerna bootstrap && yarn build && lerna link", + "postinstall": "lerna bootstrap && yarn build && lerna link && npm run check-clean-workspace-after-install", + "check-clean-workspace-after-install": "git diff --quiet --exit-code", "test": "lerna run test --parallel", "typecheck": "lerna run typecheck" }, diff --git a/packages/eslint-plugin-tslint/CHANGELOG.md b/packages/eslint-plugin-tslint/CHANGELOG.md index c0e725bdfcbc..fc36ec031752 100644 --- a/packages/eslint-plugin-tslint/CHANGELOG.md +++ b/packages/eslint-plugin-tslint/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.12.0...v1.13.0) (2019-07-21) + + +### Bug Fixes + +* Correct `@types/json-schema` dependency ([#675](https://github.com/typescript-eslint/typescript-eslint/issues/675)) ([a5398ce](https://github.com/typescript-eslint/typescript-eslint/commit/a5398ce)) + + + + + # [1.12.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.11.0...v1.12.0) (2019-07-12) diff --git a/packages/eslint-plugin-tslint/package.json b/packages/eslint-plugin-tslint/package.json index f370f0c58470..788f41c147e6 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": "1.12.0", + "version": "1.13.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": "1.12.0", + "@typescript-eslint/experimental-utils": "1.13.0", "lodash.memoize": "^4.1.2" }, "peerDependencies": { @@ -39,8 +39,7 @@ "tslint": "^5.0.0" }, "devDependencies": { - "@types/json-schema": "^7.0.3", "@types/lodash.memoize": "^4.1.4", - "@typescript-eslint/parser": "1.12.0" + "@typescript-eslint/parser": "1.13.0" } } diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index e5a04d77763b..b7ff8b2d6ac6 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -3,6 +3,25 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.12.0...v1.13.0) (2019-07-21) + + +### Bug Fixes + +* Correct `@types/json-schema` dependency ([#675](https://github.com/typescript-eslint/typescript-eslint/issues/675)) ([a5398ce](https://github.com/typescript-eslint/typescript-eslint/commit/a5398ce)) +* **eslint-plugin:** remove imports from typescript-estree ([#706](https://github.com/typescript-eslint/typescript-eslint/issues/706)) ([ceb2d32](https://github.com/typescript-eslint/typescript-eslint/commit/ceb2d32)), closes [#705](https://github.com/typescript-eslint/typescript-eslint/issues/705) +* **eslint-plugin:** undo breaking changes to recommended config ([93f72e3](https://github.com/typescript-eslint/typescript-eslint/commit/93f72e3)) + + +### Features + +* **eslint-plugin:** add new rule no-misused-promises ([#612](https://github.com/typescript-eslint/typescript-eslint/issues/612)) ([28a131d](https://github.com/typescript-eslint/typescript-eslint/commit/28a131d)) +* **eslint-plugin:** add new rule require-await ([#674](https://github.com/typescript-eslint/typescript-eslint/issues/674)) ([807bc2d](https://github.com/typescript-eslint/typescript-eslint/commit/807bc2d)) + + + + + # [1.12.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.11.0...v1.12.0) (2019-07-12) diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index c436e32be6c1..ce069a2aff6d 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -116,7 +116,6 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e ## Supported Rules - **Key**: :heavy_check_mark: = recommended, :wrench: = fixable, :thought_balloon: = requires type information @@ -131,7 +130,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e | [`@typescript-eslint/ban-types`](./docs/rules/ban-types.md) | Enforces that types will not to be used | :heavy_check_mark: | :wrench: | | | [`@typescript-eslint/camelcase`](./docs/rules/camelcase.md) | Enforce camelCase naming convention | :heavy_check_mark: | | | | [`@typescript-eslint/class-name-casing`](./docs/rules/class-name-casing.md) | Require PascalCased class and interface names | :heavy_check_mark: | | | -| [`@typescript-eslint/consistent-type-definitions`](./docs/rules/consistent-type-definitions.md) | Consistent with type definition either `interface` or `type` | :heavy_check_mark: | :wrench: | | +| [`@typescript-eslint/consistent-type-definitions`](./docs/rules/consistent-type-definitions.md) | Consistent with type definition either `interface` or `type` | | :wrench: | | | [`@typescript-eslint/explicit-function-return-type`](./docs/rules/explicit-function-return-type.md) | Require explicit return types on functions and class methods | :heavy_check_mark: | | | | [`@typescript-eslint/explicit-member-accessibility`](./docs/rules/explicit-member-accessibility.md) | Require explicit accessibility modifiers on class properties and methods | :heavy_check_mark: | | | | [`@typescript-eslint/func-call-spacing`](./docs/rules/func-call-spacing.md) | Require or disallow spacing between function identifiers and their invocations | | :wrench: | | @@ -153,6 +152,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e | [`@typescript-eslint/no-inferrable-types`](./docs/rules/no-inferrable-types.md) | Disallows explicit type declarations for variables or parameters initialized to a number, string, or boolean | :heavy_check_mark: | :wrench: | | | [`@typescript-eslint/no-magic-numbers`](./docs/rules/no-magic-numbers.md) | Disallows magic numbers | | | | | [`@typescript-eslint/no-misused-new`](./docs/rules/no-misused-new.md) | Enforce valid definition of `new` and `constructor` | :heavy_check_mark: | | | +| [`@typescript-eslint/no-misused-promises`](./docs/rules/no-misused-promises.md) | Avoid using promises in places not designed to handle them | | | :thought_balloon: | | [`@typescript-eslint/no-namespace`](./docs/rules/no-namespace.md) | Disallow the use of custom TypeScript modules and namespaces | :heavy_check_mark: | | | | [`@typescript-eslint/no-non-null-assertion`](./docs/rules/no-non-null-assertion.md) | Disallows non-null assertions using the `!` postfix operator | :heavy_check_mark: | | | | [`@typescript-eslint/no-object-literal-type-assertion`](./docs/rules/no-object-literal-type-assertion.md) | Forbids an object literal to appear in a type assertion expression | :heavy_check_mark: | | | @@ -175,6 +175,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e | [`@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 | | :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/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 | | | :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: | | [`@typescript-eslint/semi`](./docs/rules/semi.md) | Require or disallow semicolons instead of ASI | | :wrench: | | | [`@typescript-eslint/strict-boolean-expressions`](./docs/rules/strict-boolean-expressions.md) | Restricts the types allowed in boolean expressions | | | :thought_balloon: | diff --git a/packages/eslint-plugin/docs/rules/no-magic-numbers.md b/packages/eslint-plugin/docs/rules/no-magic-numbers.md index b5a1e92fd092..cf647833887d 100644 --- a/packages/eslint-plugin/docs/rules/no-magic-numbers.md +++ b/packages/eslint-plugin/docs/rules/no-magic-numbers.md @@ -45,20 +45,20 @@ type SmallPrimes = 2 | 3 | 5 | 7 | 11; A boolean to specify if enums used in Typescript are considered okay. `false` by default. -Examples of **incorrect** code for the `{ "ignoreEnum": false }` option: +Examples of **incorrect** code for the `{ "ignoreEnums": false }` option: ```ts -/*eslint @typescript-eslint/no-magic-numbers: ["error", { "ignoreEnum": false }]*/ +/*eslint @typescript-eslint/no-magic-numbers: ["error", { "ignoreEnums": false }]*/ enum foo = { SECOND = 1000, } ``` -Examples of **correct** code for the `{ "ignoreEnum": true }` option: +Examples of **correct** code for the `{ "ignoreEnums": true }` option: ```ts -/*eslint @typescript-eslint/no-magic-numbers: ["error", { "ignoreEnum": true }]*/ +/*eslint @typescript-eslint/no-magic-numbers: ["error", { "ignoreEnums": true }]*/ enum foo = { SECOND = 1000, diff --git a/packages/eslint-plugin/docs/rules/no-misused-promises.md b/packages/eslint-plugin/docs/rules/no-misused-promises.md new file mode 100644 index 000000000000..cd8a2f8b9570 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-misused-promises.md @@ -0,0 +1,119 @@ +# Avoid using promises in places not designed to handle them (no-misused-promises) + +This rule forbids using promises in places where the Typescript compiler +allows them but they are not handled properly. These situations can often arise +due to a missing `await` keyword or just a misunderstanding of the way async +functions are handled/awaited. + +## Rule Details + +Examples of **incorrect** code for this rule with `checksConditionals: true`: + +```ts +const promise = Promise.resolve('value'); + +if (promise) { + // Do something +} + +const val = promise ? 123 : 456; + +while (promise) { + // Do something +} +``` + +Examples of **incorrect** code for this rule with `checksVoidReturn: true`: + +```ts +[1, 2, 3].forEach(async value => { + await doSomething(value); +}); + +new Promise(async (resolve, reject) => { + await doSomething(); + resolve(); +}); + +const eventEmitter = new EventEmitter(); +eventEmitter.on('some-event', async () => { + await doSomething(); +}); +``` + +Examples of **correct** code for this rule: + +```ts +const promise = Promise.resolve('value'); + +if (await promise) { + // Do something +} + +const val = (await promise) ? 123 : 456; + +while (await promise) { + // Do something +} + +for (const value of [1, 2, 3]) { + await doSomething(value); +} + +new Promise((resolve, reject) => { + // Do something + resolve(); +}); + +const eventEmitter = new EventEmitter(); +eventEmitter.on('some-event', () => { + doSomething(); +}); +``` + +## Options + +This rule accepts a single option which is an object with `checksConditionals` +and `checksVoidReturn` properties indicating which types of misuse to flag. +Both are enabled by default + +If you don't want functions that return promises where a void return is +expected to be checked, your configuration will look like this: + +```json +{ + "@typescript-eslint/no-misused-promises": [ + "error", + { + "checksVoidReturn": false + } + ] +} +``` + +Likewise, if you don't want to check conditionals, you can configure the rule +like this: + +```json +{ + "@typescript-eslint/no-misused-promises": [ + "error", + { + "checksConditionals": false + } + ] +} +``` + +## When Not To Use It + +If you do not use Promises in your codebase or are not concerned with possible +misuses of them outside of what the Typescript compiler will check. + +## Related to + +- [`no-floating-promises`](./no-floating-promises.md) + +## Further Reading + +- [Typescript void function assignability](https://github.com/Microsoft/TypeScript/wiki/FAQ#why-are-functions-returning-non-void-assignable-to-function-returning-void) diff --git a/packages/eslint-plugin/docs/rules/require-await.md b/packages/eslint-plugin/docs/rules/require-await.md new file mode 100644 index 000000000000..cbc86681727c --- /dev/null +++ b/packages/eslint-plugin/docs/rules/require-await.md @@ -0,0 +1,49 @@ +# Disallow async functions which have no await expression (@typescript-eslint/require-await) + +Asynchronous functions that don’t use `await` might not need to be asynchronous functions and could be the unintentional result of refactoring. + +## Rule Details + +The `@typescript-eslint/require-await` rule extends the `require-await` rule from ESLint core, and allows for cases where the additional typing information can prevent false positives that would otherwise trigger the rule. + +One example is when a function marked as `async` returns a value that is: + +1. already a promise; or +2. the result of calling another `async` function + +```typescript +async function numberOne(): Promise { + return Promise.resolve(1); +} + +async function getDataFromApi(endpoint: string): Promise { + return fetch(endpoint); +} +``` + +In the above examples, the core `require-await` triggers the following warnings: + +``` +async function 'numberOne' has no 'await' expression +async function 'getDataFromApi' has no 'await' expression +``` + +One way to resolve these errors is to remove the `async` keyword. However doing so can cause a conflict with the [`@typescript-eslint/promise-function-async`](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/promise-function-async.md) rule (if enabled), which requires any function returning a promise to be marked as `async`. + +Another way to resolve these errors is to add an `await` keyword to the return statements. However doing so can cause a conflict with the [`no-return-await`](https://eslint.org/docs/rules/no-return-await) rule (if enabled), which warns against using `return await` since the return value of an `async` function is always wrapped in `Promise.resolve` anyway. + +With the additional typing information available in Typescript code, this extension to the `require-await` rule is able to look at the _actual_ return types of an `async` function (before being implicitly wrapped in `Promise.resolve`), and avoid the need for an `await` expression when the return value is already a promise. + +See the [ESLint documentation](https://eslint.org/docs/rules/require-await) for more details on the `require-await` rule. + +## Rule Changes + +```cjson +{ + // note you must disable the base rule as it can report incorrect errors + "require-await": "off", + "@typescript-eslint/require-await": "error" +} +``` + +Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/require-await.md) diff --git a/packages/eslint-plugin/docs/rules/semi.md b/packages/eslint-plugin/docs/rules/semi.md index 69fd5c57be84..77f8a7134617 100644 --- a/packages/eslint-plugin/docs/rules/semi.md +++ b/packages/eslint-plugin/docs/rules/semi.md @@ -1,6 +1,6 @@ # require or disallow semicolons instead of ASI (semi) -This rule enforces consistent use of semicolons. +This rule enforces consistent use of semicolons after statements. ## Rule Details @@ -8,6 +8,9 @@ This rule extends the base [eslint/semi](https://eslint.org/docs/rules/semi) rul It supports all options and features of the base rule. This version adds support for numerous typescript features. +See also the [@typescript-eslint/member-delimiter-style](member-delimiter-style.md) rule, +which allows you to specify the delimiter for `type` and `interface` members. + ## How to use ```cjson diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 28dcc098a7fd..19b61dc0d5f8 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin", - "version": "1.12.0", + "version": "1.13.0", "description": "TypeScript plugin for ESLint", "keywords": [ "eslint", @@ -30,24 +30,24 @@ "main": "dist/index.js", "scripts": { "build": "tsc -p tsconfig.build.json", + "check:docs": "ts-node --files ./tools/validate-docs/index.ts", + "check:configs": "ts-node --files ./tools/validate-configs/index.ts", "clean": "rimraf dist/", - "docs": "eslint-docs", - "docs:check": "ts-node --files ./tools/validate-docs/index.ts", "format": "prettier --write \"./**/*.{ts,js,json,md}\" --ignore-path ../../.prettierignore", "generate:configs": "ts-node --files tools/generate-configs.ts", "prebuild": "npm run clean", - "recommended:update": "ts-node tools/update-recommended.ts", "test": "jest --coverage", "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/experimental-utils": "1.12.0", + "@typescript-eslint/experimental-utils": "1.13.0", "eslint-utils": "^1.3.1", "functional-red-black-tree": "^1.0.1", "regexpp": "^2.0.1", "tsutils": "^3.7.0" }, "devDependencies": { + "@types/json-schema": "^7.0.3", "@types/marked": "^0.6.5", "chalk": "^2.4.2", "marked": "^0.6.2" diff --git a/packages/eslint-plugin/src/configs/all.json b/packages/eslint-plugin/src/configs/all.json index cd5b2bb9cbe9..cf3f61c69cd6 100644 --- a/packages/eslint-plugin/src/configs/all.json +++ b/packages/eslint-plugin/src/configs/all.json @@ -36,6 +36,7 @@ "no-magic-numbers": "off", "@typescript-eslint/no-magic-numbers": "error", "@typescript-eslint/no-misused-new": "error", + "@typescript-eslint/no-misused-promises": "error", "@typescript-eslint/no-namespace": "error", "@typescript-eslint/no-non-null-assertion": "error", "@typescript-eslint/no-object-literal-type-assertion": "error", @@ -61,9 +62,11 @@ "@typescript-eslint/prefer-string-starts-ends-with": "error", "@typescript-eslint/promise-function-async": "error", "@typescript-eslint/require-array-sort-compare": "error", + "@typescript-eslint/require-await": "error", "@typescript-eslint/restrict-plus-operands": "error", "semi": "off", "@typescript-eslint/semi": "error", + "@typescript-eslint/strict-boolean-expressions": "error", "@typescript-eslint/triple-slash-reference": "error", "@typescript-eslint/type-annotation-spacing": "error", "@typescript-eslint/unbound-method": "error", diff --git a/packages/eslint-plugin/src/rules/consistent-type-definitions.ts b/packages/eslint-plugin/src/rules/consistent-type-definitions.ts index eea12b9cf845..01fa92dd3d07 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-definitions.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-definitions.ts @@ -9,7 +9,7 @@ export default util.createRule({ description: 'Consistent with type definition either `interface` or `type`', category: 'Stylistic Issues', - recommended: 'error', + recommended: false, }, messages: { interfaceOverType: 'Use an `interface` instead of a `type`', diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 00107aa57a8d..482806640756 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -27,6 +27,7 @@ import noForInArray from './no-for-in-array'; import noInferrableTypes from './no-inferrable-types'; import noMagicNumbers from './no-magic-numbers'; import noMisusedNew from './no-misused-new'; +import noMisusedPromises from './no-misused-promises'; import noNamespace from './no-namespace'; import noNonNullAssertion from './no-non-null-assertion'; import noObjectLiteralTypeAssertion from './no-object-literal-type-assertion'; @@ -51,6 +52,7 @@ import preferRegexpExec from './prefer-regexp-exec'; import preferStringStartsEndsWith from './prefer-string-starts-ends-with'; import promiseFunctionAsync from './promise-function-async'; import requireArraySortCompare from './require-array-sort-compare'; +import requireAwait from './require-await'; import restrictPlusOperands from './restrict-plus-operands'; import semi from './semi'; import strictBooleanExpressions from './strict-boolean-expressions'; @@ -89,6 +91,7 @@ export default { 'no-inferrable-types': noInferrableTypes, 'no-magic-numbers': noMagicNumbers, 'no-misused-new': noMisusedNew, + 'no-misused-promises': noMisusedPromises, 'no-namespace': noNamespace, 'no-non-null-assertion': noNonNullAssertion, 'no-object-literal-type-assertion': noObjectLiteralTypeAssertion, @@ -113,6 +116,7 @@ export default { 'prefer-string-starts-ends-with': preferStringStartsEndsWith, 'promise-function-async': promiseFunctionAsync, 'require-array-sort-compare': requireArraySortCompare, + 'require-await': requireAwait, 'restrict-plus-operands': restrictPlusOperands, semi: semi, 'strict-boolean-expressions': strictBooleanExpressions, diff --git a/packages/eslint-plugin/src/rules/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts new file mode 100644 index 000000000000..7b1181ff179d --- /dev/null +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -0,0 +1,253 @@ +import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; +import * as tsutils from 'tsutils'; +import ts from 'typescript'; + +import * as util from '../util'; + +type Options = [ + { + checksConditionals?: boolean; + checksVoidReturn?: boolean; + } +]; + +export default util.createRule({ + name: 'no-misused-promises', + meta: { + docs: { + description: 'Avoid using promises in places not designed to handle them', + category: 'Best Practices', + recommended: false, + }, + messages: { + voidReturn: + 'Promise returned in function argument where a void return was expected.', + conditional: 'Expected non-Promise value in a boolean conditional.', + }, + schema: [ + { + type: 'object', + properties: { + checksConditionals: { + type: 'boolean', + }, + checksVoidReturn: { + type: 'boolean', + }, + }, + }, + ], + type: 'problem', + }, + defaultOptions: [ + { + checksConditionals: true, + checksVoidReturn: true, + }, + ], + + create(context, [{ checksConditionals, checksVoidReturn }]) { + const parserServices = util.getParserServices(context); + const checker = parserServices.program.getTypeChecker(); + + const conditionalChecks: TSESLint.RuleListener = { + ConditionalExpression: checkTestConditional, + DoWhileStatement: checkTestConditional, + ForStatement: checkTestConditional, + IfStatement: checkTestConditional, + LogicalExpression(node) { + // We only check the lhs of a logical expression because the rhs might + // be the return value of a short circuit expression. + checkConditional(node.left); + }, + UnaryExpression(node) { + if (node.operator === '!') { + checkConditional(node.argument); + } + }, + WhileStatement: checkTestConditional, + }; + + const voidReturnChecks: TSESLint.RuleListener = { + CallExpression: checkArguments, + NewExpression: checkArguments, + }; + + function checkTestConditional(node: { test: TSESTree.Expression | null }) { + if (node.test) { + checkConditional(node.test); + } + } + + function checkConditional(node: TSESTree.Expression) { + const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); + if (isAlwaysThenable(checker, tsNode)) { + context.report({ + messageId: 'conditional', + node, + }); + } + } + + function checkArguments( + node: TSESTree.CallExpression | TSESTree.NewExpression, + ) { + const tsNode = parserServices.esTreeNodeToTSNodeMap.get< + ts.CallExpression | ts.NewExpression + >(node); + const voidParams = voidFunctionParams(checker, tsNode); + if (voidParams.size === 0) { + return; + } + + for (const [index, argument] of node.arguments.entries()) { + if (!voidParams.has(index)) { + continue; + } + + const tsNode = parserServices.esTreeNodeToTSNodeMap.get(argument); + if (returnsThenable(checker, tsNode as ts.Expression)) { + context.report({ + messageId: 'voidReturn', + node: argument, + }); + } + } + } + + return { + ...(checksConditionals ? conditionalChecks : {}), + ...(checksVoidReturn ? voidReturnChecks : {}), + }; + }, +}); + +// Variation on the thenable check which requires all forms of the type (read: +// alternates in a union) to be thenable. Otherwise, you might be trying to +// check if something is defined or undefined and get caught because one of the +// branches is thenable. +function isAlwaysThenable(checker: ts.TypeChecker, node: ts.Node) { + const type = checker.getTypeAtLocation(node); + + for (const subType of tsutils.unionTypeParts(checker.getApparentType(type))) { + const thenProp = subType.getProperty('then'); + + // If one of the alternates has no then property, it is not thenable in all + // cases. + if (thenProp === undefined) { + return false; + } + + // We walk through each variation of the then property. Since we know it + // exists at this point, we just need at least one of the alternates to + // be of the right form to consider it thenable. + const thenType = checker.getTypeOfSymbolAtLocation(thenProp, node); + let hasThenableSignature = false; + for (const subType of tsutils.unionTypeParts(thenType)) { + for (const signature of subType.getCallSignatures()) { + if ( + signature.parameters.length !== 0 && + isFunctionParam(checker, signature.parameters[0], node) + ) { + hasThenableSignature = true; + break; + } + } + + // We only need to find one variant of the then property that has a + // function signature for it to be thenable. + if (hasThenableSignature) { + break; + } + } + + // If no flavors of the then property are thenable, we don't consider the + // overall type to be thenable + if (!hasThenableSignature) { + return false; + } + } + + // If all variants are considered thenable (i.e. haven't returned false), we + // consider the overall type thenable + return true; +} + +function isFunctionParam( + checker: ts.TypeChecker, + param: ts.Symbol, + node: ts.Node, +): boolean { + const type: ts.Type | undefined = checker.getApparentType( + checker.getTypeOfSymbolAtLocation(param, node), + ); + for (const subType of tsutils.unionTypeParts(type)) { + if (subType.getCallSignatures().length !== 0) { + return true; + } + } + return false; +} + +// Get the positions of parameters which are void functions (and not also +// thenable functions). These are the candidates for the void-return check at +// the current call site. +function voidFunctionParams( + checker: ts.TypeChecker, + node: ts.CallExpression | ts.NewExpression, +) { + const voidReturnIndices = new Set(); + const thenableReturnIndices = new Set(); + const type = checker.getTypeAtLocation(node.expression); + + for (const subType of tsutils.unionTypeParts(type)) { + // Standard function calls and `new` have two different types of signatures + const signatures = ts.isCallExpression(node) + ? subType.getCallSignatures() + : subType.getConstructSignatures(); + for (const signature of signatures) { + for (const [index, parameter] of signature.parameters.entries()) { + const type = checker.getTypeOfSymbolAtLocation( + parameter, + node.expression, + ); + for (const subType of tsutils.unionTypeParts(type)) { + for (const signature of subType.getCallSignatures()) { + const returnType = signature.getReturnType(); + if (tsutils.isTypeFlagSet(returnType, ts.TypeFlags.Void)) { + voidReturnIndices.add(index); + } else if ( + tsutils.isThenableType(checker, node.expression, returnType) + ) { + thenableReturnIndices.add(index); + } + } + } + } + } + } + + // If a certain positional argument accepts both thenable and void returns, + // a promise-returning function is valid + for (const thenable of thenableReturnIndices) { + voidReturnIndices.delete(thenable); + } + + return voidReturnIndices; +} + +// Returns true if the expression is a function that returns a thenable +function returnsThenable(checker: ts.TypeChecker, node: ts.Expression) { + const type = checker.getApparentType(checker.getTypeAtLocation(node)); + + for (const subType of tsutils.unionTypeParts(type)) { + for (const signature of subType.getCallSignatures()) { + const returnType = signature.getReturnType(); + if (tsutils.isThenableType(checker, node, returnType)) { + return true; + } + } + } + + return false; +} diff --git a/packages/eslint-plugin/src/rules/prefer-readonly.ts b/packages/eslint-plugin/src/rules/prefer-readonly.ts index 24685d306762..c82aaf45ab34 100644 --- a/packages/eslint-plugin/src/rules/prefer-readonly.ts +++ b/packages/eslint-plugin/src/rules/prefer-readonly.ts @@ -2,7 +2,10 @@ import * as tsutils from 'tsutils'; import ts from 'typescript'; import * as util from '../util'; import { typeIsOrHasBaseType } from '../util'; -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; type MessageIds = 'preferReadonly'; diff --git a/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts b/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts index ffcf5aef9755..dae7b8f13c1f 100644 --- a/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts +++ b/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts @@ -1,4 +1,4 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { TSESTree } from '@typescript-eslint/experimental-utils'; import { createRule, getParserServices, getTypeName } from '../util'; import { getStaticValue } from 'eslint-utils'; diff --git a/packages/eslint-plugin/src/rules/require-await.ts b/packages/eslint-plugin/src/rules/require-await.ts new file mode 100644 index 000000000000..cbdd08791d9f --- /dev/null +++ b/packages/eslint-plugin/src/rules/require-await.ts @@ -0,0 +1,138 @@ +import { + TSESTree, + TSESLint, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; +import baseRule from 'eslint/lib/rules/require-await'; +import * as tsutils from 'tsutils'; +import ts from 'typescript'; +import * as util from '../util'; + +type Options = util.InferOptionsTypeFromRule; +type MessageIds = util.InferMessageIdsTypeFromRule; + +interface ScopeInfo { + upper: ScopeInfo | null; + returnsPromise: boolean; +} + +export default util.createRule({ + name: 'require-await', + meta: { + type: 'suggestion', + docs: { + description: 'Disallow async functions which have no `await` expression', + category: 'Best Practices', + recommended: false, + }, + schema: baseRule.meta.schema, + messages: baseRule.meta.messages, + }, + defaultOptions: [], + create(context) { + const rules = baseRule.create(context); + const parserServices = util.getParserServices(context); + const checker = parserServices.program.getTypeChecker(); + + let scopeInfo: ScopeInfo | null = null; + + /** + * Push the scope info object to the stack. + * + * @returns {void} + */ + function enterFunction( + node: + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression + | TSESTree.ArrowFunctionExpression, + ) { + scopeInfo = { + upper: scopeInfo, + returnsPromise: false, + }; + + switch (node.type) { + case AST_NODE_TYPES.FunctionDeclaration: + rules.FunctionDeclaration(node); + break; + + case AST_NODE_TYPES.FunctionExpression: + rules.FunctionExpression(node); + break; + + case AST_NODE_TYPES.ArrowFunctionExpression: + rules.ArrowFunctionExpression(node); + break; + } + } + + /** + * Pop the top scope info object from the stack. + * Passes through to the base rule if the function doesn't return a promise + * + * @param {ASTNode} node - The node exiting + * @returns {void} + */ + function exitFunction( + node: + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression + | TSESTree.ArrowFunctionExpression, + ) { + if (scopeInfo) { + if (!scopeInfo.returnsPromise) { + switch (node.type) { + case AST_NODE_TYPES.FunctionDeclaration: + rules['FunctionDeclaration:exit'](node); + break; + + case AST_NODE_TYPES.FunctionExpression: + rules['FunctionExpression:exit'](node); + break; + + case AST_NODE_TYPES.ArrowFunctionExpression: + rules['ArrowFunctionExpression:exit'](node); + break; + } + } + + scopeInfo = scopeInfo.upper; + } + } + + return { + 'FunctionDeclaration[async = true]': enterFunction, + 'FunctionExpression[async = true]': enterFunction, + 'ArrowFunctionExpression[async = true]': enterFunction, + 'FunctionDeclaration[async = true]:exit': exitFunction, + 'FunctionExpression[async = true]:exit': exitFunction, + 'ArrowFunctionExpression[async = true]:exit': exitFunction, + + ReturnStatement(node: TSESTree.ReturnStatement) { + if (!scopeInfo) { + return; + } + + const { expression } = parserServices.esTreeNodeToTSNodeMap.get< + ts.ReturnStatement + >(node); + if (!expression) { + return; + } + + const type = checker.getTypeAtLocation(expression); + if (tsutils.isThenableType(checker, expression, type)) { + scopeInfo.returnsPromise = true; + } + }, + + AwaitExpression: rules.AwaitExpression as TSESLint.RuleFunction< + TSESTree.Node + >, + ForOfStatement: rules.ForOfStatement as TSESLint.RuleFunction< + TSESTree.Node + >, + }; + }, +}); diff --git a/packages/eslint-plugin/src/rules/triple-slash-reference.ts b/packages/eslint-plugin/src/rules/triple-slash-reference.ts index 286f445c9661..906efaa7db81 100644 --- a/packages/eslint-plugin/src/rules/triple-slash-reference.ts +++ b/packages/eslint-plugin/src/rules/triple-slash-reference.ts @@ -1,10 +1,5 @@ import * as util from '../util'; -import { - Literal, - Node, - TSExternalModuleReference, -} from '@typescript-eslint/typescript-estree/dist/ts-estree/ts-estree'; -import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { TSESTree } from '@typescript-eslint/experimental-utils'; type Options = [ { @@ -55,14 +50,14 @@ export default util.createRule({ }, ], create(context, [{ lib, path, types }]) { - let programNode: Node; + let programNode: TSESTree.Node; const sourceCode = context.getSourceCode(); const references: ({ comment: TSESTree.Comment; importName: string; })[] = []; - function hasMatchingReference(source: Literal) { + function hasMatchingReference(source: TSESTree.Literal) { references.forEach(reference => { if (reference.importName === source.value) { context.report({ @@ -78,14 +73,14 @@ export default util.createRule({ return { ImportDeclaration(node) { if (programNode) { - const source = node.source as Literal; + const source = node.source as TSESTree.Literal; hasMatchingReference(source); } }, TSImportEqualsDeclaration(node) { if (programNode) { - const source = (node.moduleReference as TSExternalModuleReference) - .expression as Literal; + const source = (node.moduleReference as TSESTree.TSExternalModuleReference) + .expression as TSESTree.Literal; hasMatchingReference(source); } }, diff --git a/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts b/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts new file mode 100644 index 000000000000..5d4c7fcf4b85 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts @@ -0,0 +1,282 @@ +import rule from '../../src/rules/no-misused-promises'; +import { RuleTester, getFixturesRootDir } from '../RuleTester'; + +const rootDir = getFixturesRootDir(); + +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 2018, + tsconfigRootDir: rootDir, + project: './tsconfig.json', + }, + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('no-misused-promises', rule, { + valid: [ + `if (true) {}`, + { + code: `if (Promise.resolve()) {}`, + options: [{ checksConditionals: false }], + }, + ` +if (true) {} +else if (false) {} +else {} +`, + { + code: ` +if (Promise.resolve()) {} +else if (Promise.resolve()) {} +else {} +`, + options: [{ checksConditionals: false }], + }, + `for (;;) {}`, + `for (let i; i < 10; i++) {}`, + { + code: `for (let i; Promise.resolve(); i++) {}`, + options: [{ checksConditionals: false }], + }, + `do {} while (true);`, + { + code: `do {} while (Promise.resolve())`, + options: [{ checksConditionals: false }], + }, + `while (true) {}`, + { + code: `while (Promise.resolve()) {}`, + options: [{ checksConditionals: false }], + }, + `true ? 123 : 456`, + { + code: `Promise.resolve() ? 123 : 456`, + options: [{ checksConditionals: false }], + }, + `if (!true) {}`, + { + code: `if (!Promise.resolve()) {}`, + options: [{ checksConditionals: false }], + }, + `(await Promise.resolve()) || false`, + { + code: `Promise.resolve() || false`, + options: [{ checksConditionals: false }], + }, + `(true && await Promise.resolve()) || false`, + { + code: `(true && Promise.resolve()) || false`, + options: [{ checksConditionals: false }], + }, + `false || (true && Promise.resolve())`, + ` +async function test() { + if (await Promise.resolve()) {} +} +`, + ` +async function test() { + const mixed: Promise | undefined = Promise.resolve(); + if (mixed) { + await mixed; + } +} +`, + `if (~Promise.resolve()) {}`, + ` +interface NotQuiteThenable { + then(param: string): void; + then(): void; +} +const value: NotQuiteThenable = { then() {} }; +if (value) {} +`, + `[1, 2, 3].forEach(val => {});`, + { + code: `[1, 2, 3].forEach(async val => {});`, + options: [{ checksVoidReturn: false }], + }, + `new Promise((resolve, reject) => resolve());`, + { + code: `new Promise(async (resolve, reject) => resolve());`, + options: [{ checksVoidReturn: false }], + }, + `Promise.all(['abc', 'def'].map(async val => { await val; }))`, + ` +const fn: (arg: () => Promise | void) => void = () => {}; +fn(() => Promise.resolve()); +`, + ], + + invalid: [ + { + code: `if (Promise.resolve()) {}`, + errors: [ + { + line: 1, + messageId: 'conditional', + }, + ], + }, + { + code: ` +if (Promise.resolve()) {} +else if (Promise.resolve()) {} +else {} +`, + errors: [ + { + line: 2, + messageId: 'conditional', + }, + { + line: 3, + messageId: 'conditional', + }, + ], + }, + { + code: `for (let i; Promise.resolve(); i++) {}`, + errors: [ + { + line: 1, + messageId: 'conditional', + }, + ], + }, + { + code: `do {} while (Promise.resolve())`, + errors: [ + { + line: 1, + messageId: 'conditional', + }, + ], + }, + { + code: `while (Promise.resolve()) {}`, + errors: [ + { + line: 1, + messageId: 'conditional', + }, + ], + }, + { + code: `Promise.resolve() ? 123 : 456`, + errors: [ + { + line: 1, + messageId: 'conditional', + }, + ], + }, + { + code: `if (!Promise.resolve()) {}`, + errors: [ + { + line: 1, + messageId: 'conditional', + }, + ], + }, + { + code: `Promise.resolve() || false`, + errors: [ + { + line: 1, + messageId: 'conditional', + }, + ], + }, + { + code: `(true && Promise.resolve()) || false`, + errors: [ + { + line: 1, + messageId: 'conditional', + }, + ], + }, + { + code: ` +[Promise.resolve(), Promise.reject()].forEach( + async val => { await val; } +); +`, + errors: [ + { + line: 3, + messageId: 'voidReturn', + }, + ], + }, + { + code: ` +new Promise(async (resolve, reject) => { + await Promise.resolve(); + resolve(); +}); +`, + errors: [ + { + line: 2, + messageId: 'voidReturn', + }, + ], + }, + { + code: ` +const fnWithCallback = (arg: string, cb: (err: any, res: string) => void) => { + cb(null, arg); +}; + +fnWithCallback('val', async (err, res) => { + await res; +}); +`, + errors: [ + { + line: 6, + messageId: 'voidReturn', + }, + ], + }, + { + code: ` +const fnWithCallback = (arg: string, cb: (err: any, res: string) => void) => { + cb(null, arg); +}; + +fnWithCallback('val', (err, res) => Promise.resolve(res)); +`, + errors: [ + { + line: 6, + messageId: 'voidReturn', + }, + ], + }, + { + code: ` +const fnWithCallback = (arg: string, cb: (err: any, res: string) => void) => { + cb(null, arg); +}; + +fnWithCallback('val', (err, res) => { + if (err) { + return 'abc'; + } else { + return Promise.resolve(res); + } +}); +`, + errors: [ + { + line: 6, + messageId: 'voidReturn', + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/tests/rules/require-await.test.ts b/packages/eslint-plugin/tests/rules/require-await.test.ts new file mode 100644 index 000000000000..0b5f299e579d --- /dev/null +++ b/packages/eslint-plugin/tests/rules/require-await.test.ts @@ -0,0 +1,114 @@ +import rule from '../../src/rules/require-await'; +import { RuleTester, getFixturesRootDir } from '../RuleTester'; + +const rootDir = getFixturesRootDir(); + +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 2018, + tsconfigRootDir: rootDir, + project: './tsconfig.json', + }, + parser: '@typescript-eslint/parser', +}); + +const noAwaitFunctionDeclaration: any = { + message: "Async function 'numberOne' has no 'await' expression.", +}; + +const noAwaitFunctionExpression: any = { + message: "Async function has no 'await' expression.", +}; + +const noAwaitAsyncFunctionExpression: any = { + message: "Async arrow function has no 'await' expression.", +}; + +ruleTester.run('require-await', rule, { + valid: [ + { + // Non-async function declaration + code: `function numberOne(): number { + return 1; + }`, + }, + { + // Non-async function expression + code: `const numberOne = function(): number { + return 1; + }`, + }, + { + // Non-async arrow function expression + code: `const numberOne = (): number => 1;`, + }, + { + // Async function declaration with await + code: `async function numberOne(): Promise { + return await 1; + }`, + }, + { + // Async function expression with await + code: `const numberOne = async function(): Promise { + return await 1; + }`, + }, + { + // Async arrow function expression with await + code: `const numberOne = async (): Promise => await 1;`, + }, + { + // Async function declaration with promise return + code: `async function numberOne(): Promise { + return Promise.resolve(1); + }`, + }, + { + // Async function expression with promise return + code: `const numberOne = async function(): Promise { + return Promise.resolve(1); + }`, + }, + { + // Async function declaration with async function return + code: `async function numberOne(): Promise { + return getAsyncNumber(1); + } + async function getAsyncNumber(x: number): Promise { + return Promise.resolve(x); + }`, + }, + { + // Async function expression with async function return + code: `const numberOne = async function(): Promise { + return getAsyncNumber(1); + } + const getAsyncNumber = async function(x: number): Promise { + return Promise.resolve(x); + }`, + }, + ], + + invalid: [ + { + // Async function declaration with no await + code: `async function numberOne(): Promise { + return 1; + }`, + errors: [noAwaitFunctionDeclaration], + }, + { + // Async function expression with no await + code: `const numberOne = async function(): Promise { + return 1; + }`, + errors: [noAwaitFunctionExpression], + }, + { + // Async arrow function expression with no await + code: `const numberOne = async (): Promise => 1;`, + errors: [noAwaitAsyncFunctionExpression], + }, + ], +}); diff --git a/packages/eslint-plugin/tools/generate-configs.ts b/packages/eslint-plugin/tools/generate-configs.ts index 322c4aedae1f..d268ccd5d2ea 100644 --- a/packages/eslint-plugin/tools/generate-configs.ts +++ b/packages/eslint-plugin/tools/generate-configs.ts @@ -124,7 +124,7 @@ const recommendedConfig: LinterConfig = { rules: ruleEntries .filter(entry => !!entry[1].meta.docs.recommended) .reduce( - (config, entry) => reducer(config, entry, { filterDeprecated: true }), + (config, entry) => reducer(config, entry, { filterDeprecated: false }), {}, ), }; diff --git a/packages/eslint-plugin/tools/validate-docs/log.ts b/packages/eslint-plugin/tools/log.ts similarity index 100% rename from packages/eslint-plugin/tools/validate-docs/log.ts rename to packages/eslint-plugin/tools/log.ts diff --git a/packages/eslint-plugin/tools/validate-configs/checkConfigAll.ts b/packages/eslint-plugin/tools/validate-configs/checkConfigAll.ts new file mode 100644 index 000000000000..84981b8816e7 --- /dev/null +++ b/packages/eslint-plugin/tools/validate-configs/checkConfigAll.ts @@ -0,0 +1,35 @@ +import plugin from '../../src/index'; +import { logRule } from '../log'; + +const prefix = '@typescript-eslint/'; + +function checkConfigAll() { + const { rules } = plugin; + + const all = plugin.configs.all.rules; + const allNames = new Set(Object.keys(all)); + + return Object.entries(rules).reduce((acc, [ruleName, rule]) => { + if (!rule.meta.deprecated) { + const prefixed = `${prefix}${ruleName}` as keyof typeof all; + if (allNames.has(prefixed)) { + if (all[prefixed] !== 'error') { + logRule( + false, + ruleName, + 'incorrect setting compared to the rule meta.', + ); + return true; + } + } else { + logRule(false, ruleName, 'missing in the config.'); + return true; + } + } + + logRule(true, ruleName); + return acc; + }, false); +} + +export { checkConfigAll }; diff --git a/packages/eslint-plugin/tools/validate-configs/checkConfigRecommended.ts b/packages/eslint-plugin/tools/validate-configs/checkConfigRecommended.ts new file mode 100644 index 000000000000..21f2faa876dc --- /dev/null +++ b/packages/eslint-plugin/tools/validate-configs/checkConfigRecommended.ts @@ -0,0 +1,35 @@ +import plugin from '../../src/index'; +import { logRule } from '../log'; + +const prefix = '@typescript-eslint/'; + +function checkConfigRecommended() { + const { rules } = plugin; + + const recommended = plugin.configs.recommended.rules; + const recommendedNames = new Set(Object.keys(recommended)); + + return Object.entries(rules).reduce((acc, [ruleName, rule]) => { + if (!rule.meta.deprecated && rule.meta.docs.recommended !== false) { + const prefixed = `${prefix}${ruleName}` as keyof typeof recommended; + if (recommendedNames.has(prefixed)) { + if (recommended[prefixed] !== rule.meta.docs.recommended) { + logRule( + false, + ruleName, + 'incorrect setting compared to the rule meta.', + ); + return true; + } + } else { + logRule(false, ruleName, 'missing in the config.'); + return true; + } + } + + logRule(true, ruleName); + return acc; + }, false); +} + +export { checkConfigRecommended }; diff --git a/packages/eslint-plugin/tools/validate-configs/index.ts b/packages/eslint-plugin/tools/validate-configs/index.ts new file mode 100644 index 000000000000..cb64df536481 --- /dev/null +++ b/packages/eslint-plugin/tools/validate-configs/index.ts @@ -0,0 +1,22 @@ +import chalk from 'chalk'; +import { checkConfigRecommended } from './checkConfigRecommended'; +import { checkConfigAll } from './checkConfigAll'; + +let hasErrors = false; +console.log(chalk.underline('Checking config "recommended"')); +hasErrors = checkConfigRecommended() || hasErrors; + +console.log(); +console.log(chalk.underline('Checking config "all"')); +hasErrors = checkConfigAll() || hasErrors; + +if (hasErrors) { + console.log('\n\n'); + console.error( + chalk.bold.bgRed.white('There were errors found in the configs'), + ); + console.error(`Please run ${chalk.inverse('yarn generate:configs')}`); + console.log('\n\n'); + // eslint-disable-next-line no-process-exit + process.exit(1); +} diff --git a/packages/eslint-plugin/tools/validate-docs/check-for-rule-docs.ts b/packages/eslint-plugin/tools/validate-docs/check-for-rule-docs.ts index eeb67c9d5280..158af4513f3e 100644 --- a/packages/eslint-plugin/tools/validate-docs/check-for-rule-docs.ts +++ b/packages/eslint-plugin/tools/validate-docs/check-for-rule-docs.ts @@ -1,7 +1,7 @@ import { TSESLint } from '@typescript-eslint/experimental-utils'; import fs from 'fs'; import path from 'path'; -import { logRule } from './log'; +import { logRule } from '../log'; function checkForRuleDocs( rules: Record>>, diff --git a/packages/eslint-plugin/tools/validate-docs/validate-table-rules.ts b/packages/eslint-plugin/tools/validate-docs/validate-table-rules.ts index 1ab532e31606..4e9405a653da 100644 --- a/packages/eslint-plugin/tools/validate-docs/validate-table-rules.ts +++ b/packages/eslint-plugin/tools/validate-docs/validate-table-rules.ts @@ -3,7 +3,7 @@ import chalk from 'chalk'; import fs from 'fs'; import marked from 'marked'; import path from 'path'; -import { logRule } from './log'; +import { logRule } from '../log'; function validateTableRules( rules: Record>>, diff --git a/packages/eslint-plugin/tools/validate-docs/validate-table-structure.ts b/packages/eslint-plugin/tools/validate-docs/validate-table-structure.ts index 02da5a661518..377769741c35 100644 --- a/packages/eslint-plugin/tools/validate-docs/validate-table-structure.ts +++ b/packages/eslint-plugin/tools/validate-docs/validate-table-structure.ts @@ -1,7 +1,7 @@ import { TSESLint } from '@typescript-eslint/experimental-utils'; import chalk from 'chalk'; import marked from 'marked'; -import { logError } from './log'; +import { logError } from '../log'; function validateTableStructure( rules: Record>>, diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index df9131dae4d0..85c05b0dfe24 100644 --- a/packages/eslint-plugin/typings/eslint-rules.d.ts +++ b/packages/eslint-plugin/typings/eslint-rules.d.ts @@ -409,6 +409,29 @@ declare module 'eslint/lib/rules/no-extra-parens' { export = rule; } +declare module 'eslint/lib/rules/require-await' { + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; + + const rule: TSESLint.RuleModule< + never, + [], + { + FunctionDeclaration(node: TSESTree.FunctionDeclaration): void; + FunctionExpression(node: TSESTree.FunctionExpression): void; + ArrowFunctionExpression(node: TSESTree.ArrowFunctionExpression): void; + 'FunctionDeclaration:exit'(node: TSESTree.FunctionDeclaration): void; + 'FunctionExpression:exit'(node: TSESTree.FunctionExpression): void; + 'ArrowFunctionExpression:exit'( + node: TSESTree.ArrowFunctionExpression, + ): void; + ReturnStatement(node: TSESTree.ReturnStatement): void; + AwaitExpression(node: TSESTree.AwaitExpression): void; + ForOfStatement(node: TSESTree.ForOfStatement): void; + } + >; + export = rule; +} + declare module 'eslint/lib/rules/semi' { import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; diff --git a/packages/experimental-utils/CHANGELOG.md b/packages/experimental-utils/CHANGELOG.md index 40646cc4204d..243445d33cda 100644 --- a/packages/experimental-utils/CHANGELOG.md +++ b/packages/experimental-utils/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. +# [1.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.12.0...v1.13.0) (2019-07-21) + + +### Bug Fixes + +* Correct `@types/json-schema` dependency ([#675](https://github.com/typescript-eslint/typescript-eslint/issues/675)) ([a5398ce](https://github.com/typescript-eslint/typescript-eslint/commit/a5398ce)) +* **utils:** move `typescript` from peer dep to dev dep ([#712](https://github.com/typescript-eslint/typescript-eslint/issues/712)) ([f949355](https://github.com/typescript-eslint/typescript-eslint/commit/f949355)) +* **utils:** RuleTester should not require a parser ([#713](https://github.com/typescript-eslint/typescript-eslint/issues/713)) ([158a417](https://github.com/typescript-eslint/typescript-eslint/commit/158a417)) + + +### Features + +* **eslint-plugin:** add new rule no-misused-promises ([#612](https://github.com/typescript-eslint/typescript-eslint/issues/612)) ([28a131d](https://github.com/typescript-eslint/typescript-eslint/commit/28a131d)) + + + + + # [1.12.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.11.0...v1.12.0) (2019-07-12) **Note:** Version bump only for package @typescript-eslint/experimental-utils diff --git a/packages/experimental-utils/package.json b/packages/experimental-utils/package.json index d0a85ed8a905..5cbedf3e6cb3 100644 --- a/packages/experimental-utils/package.json +++ b/packages/experimental-utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/experimental-utils", - "version": "1.12.0", + "version": "1.13.0", "description": "(Experimental) Utilities for working with TypeScript + ESLint together", "keywords": [ "eslint", @@ -36,11 +36,14 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/typescript-estree": "1.12.0", + "@types/json-schema": "^7.0.3", + "@typescript-eslint/typescript-estree": "1.13.0", "eslint-scope": "^4.0.0" }, "peerDependencies": { - "eslint": "*", + "eslint": "*" + }, + "devDependencies": { "typescript": "*" } } diff --git a/packages/experimental-utils/src/ts-eslint/Rule.ts b/packages/experimental-utils/src/ts-eslint/Rule.ts index 2759a3d33d24..bc546e25caee 100644 --- a/packages/experimental-utils/src/ts-eslint/Rule.ts +++ b/packages/experimental-utils/src/ts-eslint/Rule.ts @@ -270,6 +270,7 @@ interface RuleListener { JSXSpreadChild?: RuleFunction; JSXText?: RuleFunction; LabeledStatement?: RuleFunction; + LogicalExpression?: RuleFunction; MemberExpression?: RuleFunction; MetaProperty?: RuleFunction; MethodDefinition?: RuleFunction; diff --git a/packages/experimental-utils/src/ts-eslint/RuleTester.ts b/packages/experimental-utils/src/ts-eslint/RuleTester.ts index bfaa9a453345..ed67ce505000 100644 --- a/packages/experimental-utils/src/ts-eslint/RuleTester.ts +++ b/packages/experimental-utils/src/ts-eslint/RuleTester.ts @@ -46,7 +46,12 @@ interface RunTests< invalid: InvalidTestCase[]; } interface RuleTesterConfig { - parser: '@typescript-eslint/parser'; + parser?: + | '@typescript-eslint/parser' + | 'espree' + | 'babel-eslint' + | 'esprima' + | string; parserOptions?: ParserOptions; } declare interface RuleTester { diff --git a/packages/parser/CHANGELOG.md b/packages/parser/CHANGELOG.md index 78926d846a92..0b158483fcf8 100644 --- a/packages/parser/CHANGELOG.md +++ b/packages/parser/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. +# [1.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.12.0...v1.13.0) (2019-07-21) + +**Note:** Version bump only for package @typescript-eslint/parser + + + + + # [1.12.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.11.0...v1.12.0) (2019-07-12) diff --git a/packages/parser/package.json b/packages/parser/package.json index cc6b85720b00..e0bab93702cb 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/parser", - "version": "1.12.0", + "version": "1.13.0", "description": "An ESLint custom parser which leverages TypeScript ESTree", "main": "dist/parser.js", "files": [ @@ -42,11 +42,11 @@ }, "dependencies": { "@types/eslint-visitor-keys": "^1.0.0", - "@typescript-eslint/experimental-utils": "1.12.0", - "@typescript-eslint/typescript-estree": "1.12.0", + "@typescript-eslint/experimental-utils": "1.13.0", + "@typescript-eslint/typescript-estree": "1.13.0", "eslint-visitor-keys": "^1.0.0" }, "devDependencies": { - "@typescript-eslint/shared-fixtures": "1.12.0" + "@typescript-eslint/shared-fixtures": "1.13.0" } } diff --git a/packages/shared-fixtures/CHANGELOG.md b/packages/shared-fixtures/CHANGELOG.md index 06c3ba92d001..2c796c965bbf 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. +# [1.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.12.0...v1.13.0) (2019-07-21) + +**Note:** Version bump only for package @typescript-eslint/shared-fixtures + + + + + # [1.12.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.11.0...v1.12.0) (2019-07-12) diff --git a/packages/shared-fixtures/package.json b/packages/shared-fixtures/package.json index 080975e7bb28..d211eed6e38a 100644 --- a/packages/shared-fixtures/package.json +++ b/packages/shared-fixtures/package.json @@ -1,5 +1,5 @@ { "name": "@typescript-eslint/shared-fixtures", - "version": "1.12.0", + "version": "1.13.0", "private": true } diff --git a/packages/typescript-estree/CHANGELOG.md b/packages/typescript-estree/CHANGELOG.md index 183ff3c672d4..2267143f5c7a 100644 --- a/packages/typescript-estree/CHANGELOG.md +++ b/packages/typescript-estree/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. +# [1.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.12.0...v1.13.0) (2019-07-21) + +**Note:** Version bump only for package @typescript-eslint/typescript-estree + + + + + # [1.12.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.11.0...v1.12.0) (2019-07-12) diff --git a/packages/typescript-estree/package.json b/packages/typescript-estree/package.json index 33079d27f036..9e39fd352cd2 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/typescript-estree", - "version": "1.12.0", + "version": "1.13.0", "description": "A parser that converts TypeScript source code into an ESTree compatible form", "main": "dist/parser.js", "types": "dist/parser.d.ts", @@ -46,6 +46,6 @@ }, "devDependencies": { "@babel/types": "^7.3.2", - "@typescript-eslint/shared-fixtures": "1.12.0" + "@typescript-eslint/shared-fixtures": "1.13.0" } } diff --git a/yarn.lock b/yarn.lock index c6bebb98da3a..445e9a7955df 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7155,6 +7155,11 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +typescript@*: + version "3.5.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.3.tgz#c830f657f93f1ea846819e929092f5fe5983e977" + integrity sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g== + "typescript@>=3.2.1 <3.6.0": version "3.5.1" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.1.tgz#ba72a6a600b2158139c5dd8850f700e231464202"