diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml new file mode 100644 index 000000000000..bde4f5f8bd53 --- /dev/null +++ b/.github/workflows/lock.yml @@ -0,0 +1,20 @@ +name: "Lock threads" + +on: + schedule: + # TODO - change to "0 0 * * *" once the backlog is processed + - cron: "0 * * * *" + +jobs: + lock: + runs-on: ubuntu-latest + steps: + - uses: dessant/lock-threads@v2 + with: + github-token: ${{ github.token }} + issue-lock-inactive-days: "30" + issue-lock-reason: "resolved" + issue-lock-comment: "" + pr-lock-inactive-days: "30" + pr-lock-reason: "resolved" + pr-lock-comment: "" diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d0a14f5669d..0921927763f4 100644 --- a/CHANGELOG.md +++ b/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. +# [2.29.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.28.0...v2.29.0) (2020-04-20) + + +### Bug Fixes + +* **eslint-plugin:** [no-base-to-string] soft remove `ignoreTaggedTemplateExpressions` option ([#1916](https://github.com/typescript-eslint/typescript-eslint/issues/1916)) ([369978e](https://github.com/typescript-eslint/typescript-eslint/commit/369978e9685bacb3e3882b0510ff06eaf8df4ca1)) + + +### Features + +* **eslint-plugin:** [no-floating-promise] add option to ignore IIFEs ([#1799](https://github.com/typescript-eslint/typescript-eslint/issues/1799)) ([cea51bf](https://github.com/typescript-eslint/typescript-eslint/commit/cea51bf130d6d3c2935f5e2dcc468196f2ad9d00)) +* **eslint-plugin:** [restrict-template-expressions] add support for intersection types ([#1803](https://github.com/typescript-eslint/typescript-eslint/issues/1803)) ([cc70e4f](https://github.com/typescript-eslint/typescript-eslint/commit/cc70e4fbadd0b15fd6af913a2e1e2ddd346fa558)) +* **eslint-plugin:** add extension rule `init-declarations` ([#1814](https://github.com/typescript-eslint/typescript-eslint/issues/1814)) ([b01f5e7](https://github.com/typescript-eslint/typescript-eslint/commit/b01f5e778ac28e0797a3734fc58d025bb224f418)) +* **eslint-plugin:** add extension rule `keyword-spacing` ([#1739](https://github.com/typescript-eslint/typescript-eslint/issues/1739)) ([c5106dd](https://github.com/typescript-eslint/typescript-eslint/commit/c5106dd4bf2bc8846cc39aa8bb50c33bec026d4d)) + + + + + # [2.28.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.27.0...v2.28.0) (2020-04-13) diff --git a/lerna.json b/lerna.json index dde84901cc10..3c1dacefef4d 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.28.0", + "version": "2.29.0", "npmClient": "yarn", "useWorkspaces": true, "stream": true diff --git a/packages/eslint-plugin-internal/CHANGELOG.md b/packages/eslint-plugin-internal/CHANGELOG.md index c86faff06c0c..1634ffba2fa8 100644 --- a/packages/eslint-plugin-internal/CHANGELOG.md +++ b/packages/eslint-plugin-internal/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.29.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.28.0...v2.29.0) (2020-04-20) + +**Note:** Version bump only for package @typescript-eslint/eslint-plugin-internal + + + + + # [2.28.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.27.0...v2.28.0) (2020-04-13) **Note:** Version bump only for package @typescript-eslint/eslint-plugin-internal diff --git a/packages/eslint-plugin-internal/package.json b/packages/eslint-plugin-internal/package.json index 40e465ce4eb4..6348c281ab4e 100644 --- a/packages/eslint-plugin-internal/package.json +++ b/packages/eslint-plugin-internal/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin-internal", - "version": "2.28.0", + "version": "2.29.0", "private": true, "main": "dist/index.js", "scripts": { @@ -12,7 +12,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/experimental-utils": "2.28.0", + "@typescript-eslint/experimental-utils": "2.29.0", "prettier": "*" } } diff --git a/packages/eslint-plugin-tslint/CHANGELOG.md b/packages/eslint-plugin-tslint/CHANGELOG.md index 3c242a42c633..d43881609278 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.29.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.28.0...v2.29.0) (2020-04-20) + +**Note:** Version bump only for package @typescript-eslint/eslint-plugin-tslint + + + + + # [2.28.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.27.0...v2.28.0) (2020-04-13) **Note:** Version bump only for package @typescript-eslint/eslint-plugin-tslint diff --git a/packages/eslint-plugin-tslint/package.json b/packages/eslint-plugin-tslint/package.json index 158d9a84b159..fa6905b5a4f0 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.28.0", + "version": "2.29.0", "main": "dist/index.js", "typings": "src/index.ts", "description": "TSLint wrapper plugin for ESLint", @@ -31,7 +31,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/experimental-utils": "2.28.0", + "@typescript-eslint/experimental-utils": "2.29.0", "lodash": "^4.17.15" }, "peerDependencies": { @@ -41,6 +41,6 @@ }, "devDependencies": { "@types/lodash": "^4.14.149", - "@typescript-eslint/parser": "2.28.0" + "@typescript-eslint/parser": "2.29.0" } } diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 72d30f22b36c..37883be34309 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. +# [2.29.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.28.0...v2.29.0) (2020-04-20) + + +### Bug Fixes + +* **eslint-plugin:** [no-base-to-string] soft remove `ignoreTaggedTemplateExpressions` option ([#1916](https://github.com/typescript-eslint/typescript-eslint/issues/1916)) ([369978e](https://github.com/typescript-eslint/typescript-eslint/commit/369978e9685bacb3e3882b0510ff06eaf8df4ca1)) + + +### Features + +* **eslint-plugin:** [no-floating-promise] add option to ignore IIFEs ([#1799](https://github.com/typescript-eslint/typescript-eslint/issues/1799)) ([cea51bf](https://github.com/typescript-eslint/typescript-eslint/commit/cea51bf130d6d3c2935f5e2dcc468196f2ad9d00)) +* **eslint-plugin:** [restrict-template-expressions] add support for intersection types ([#1803](https://github.com/typescript-eslint/typescript-eslint/issues/1803)) ([cc70e4f](https://github.com/typescript-eslint/typescript-eslint/commit/cc70e4fbadd0b15fd6af913a2e1e2ddd346fa558)) +* **eslint-plugin:** add extension rule `init-declarations` ([#1814](https://github.com/typescript-eslint/typescript-eslint/issues/1814)) ([b01f5e7](https://github.com/typescript-eslint/typescript-eslint/commit/b01f5e778ac28e0797a3734fc58d025bb224f418)) +* **eslint-plugin:** add extension rule `keyword-spacing` ([#1739](https://github.com/typescript-eslint/typescript-eslint/issues/1739)) ([c5106dd](https://github.com/typescript-eslint/typescript-eslint/commit/c5106dd4bf2bc8846cc39aa8bb50c33bec026d4d)) + + + + + # [2.28.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.27.0...v2.28.0) (2020-04-13) diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 7ed22981926c..c4507267c6f0 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -183,6 +183,8 @@ In these cases, we create what we call an extension rule; a rule within our plug | [`@typescript-eslint/default-param-last`](./docs/rules/default-param-last.md) | Enforce default parameters to be last | | | | | [`@typescript-eslint/func-call-spacing`](./docs/rules/func-call-spacing.md) | Require or disallow spacing between function identifiers and their invocations | | :wrench: | | | [`@typescript-eslint/indent`](./docs/rules/indent.md) | Enforce consistent indentation | | :wrench: | | +| [`@typescript-eslint/init-declarations`](./docs/rules/init-declarations.md) | require or disallow initialization in variable declarations | | | | +| [`@typescript-eslint/keyword-spacing`](./docs/rules/keyword-spacing.md) | Enforce consistent spacing before and after keywords | | :wrench: | | | [`@typescript-eslint/no-array-constructor`](./docs/rules/no-array-constructor.md) | Disallow generic `Array` constructors | :heavy_check_mark: | :wrench: | | | [`@typescript-eslint/no-dupe-class-members`](./docs/rules/no-dupe-class-members.md) | Disallow duplicate class members | | | | | [`@typescript-eslint/no-empty-function`](./docs/rules/no-empty-function.md) | Disallow empty functions | :heavy_check_mark: | | | diff --git a/packages/eslint-plugin/docs/rules/init-declarations.md b/packages/eslint-plugin/docs/rules/init-declarations.md new file mode 100644 index 000000000000..8888e2efef27 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/init-declarations.md @@ -0,0 +1,22 @@ +# require or disallow initialization in variable declarations (`init-declarations`) + +## Rule Details + +This rule extends the base [`eslint/init-declarations`](https://eslint.org/docs/rules/init-declarations) rule. +It adds support for TypeScript's `declare` variables. + +## How to use + +```cjson +{ + // note you must disable the base rule as it can report incorrect errors + "init-declarations": "off", + "@typescript-eslint/init-declarations": ["error"] +} +``` + +## Options + +See [`eslint/init-declarations` options](https://eslint.org/docs/rules/init-declarations#options). + +Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/init-declarations.md) diff --git a/packages/eslint-plugin/docs/rules/keyword-spacing.md b/packages/eslint-plugin/docs/rules/keyword-spacing.md new file mode 100644 index 000000000000..ca2926d6c825 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/keyword-spacing.md @@ -0,0 +1,22 @@ +# Enforce consistent spacing before and after keywords (`keyword-spacing`) + +## Rule Details + +This rule extends the base [`eslint/keyword-spacing`](https://eslint.org/docs/rules/keyword-spacing) rule. +This version adds support for generic type parameters on function calls. + +## How to use + +```cjson +{ + // note you must disable the base rule as it can report incorrect errors + "keyword-spacing": "off", + "@typescript-eslint/keyword-spacing": ["error"] +} +``` + +## Options + +See [`eslint/keyword-spacing` options](https://eslint.org/docs/rules/keyword-spacing#options). + +Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/keyword-spacing.md) diff --git a/packages/eslint-plugin/docs/rules/no-base-to-string.md b/packages/eslint-plugin/docs/rules/no-base-to-string.md index 5c476b51303d..569ffe0e6d97 100644 --- a/packages/eslint-plugin/docs/rules/no-base-to-string.md +++ b/packages/eslint-plugin/docs/rules/no-base-to-string.md @@ -52,32 +52,6 @@ const literalWithToString = { `Value: ${literalWithToString}`; ``` -## Options - -The rule accepts an options object with the following properties: - -```ts -type Options = { - // if true, interpolated expressions in tagged templates will not be checked - ignoreTaggedTemplateExpressions?: boolean; -}; - -const defaults = { - ignoreTaggedTemplateExpressions: false, -}; -``` - -### `ignoreTaggedTemplateExpressions` - -This allows to skip checking tagged templates, for cases where the tags do not necessarily stringify interpolated values. - -Examples of additional **correct** code for this rule with `{ ignoreTaggedTemplateExpressions: true }`: - -```ts -function tag() {} -tag`${{}}`; -``` - ## When Not To Use It If you don't mind `"[object Object]"` in your strings, then you will not need this rule. diff --git a/packages/eslint-plugin/docs/rules/no-floating-promises.md b/packages/eslint-plugin/docs/rules/no-floating-promises.md index ffaa542f3f11..4ea2e6addc99 100644 --- a/packages/eslint-plugin/docs/rules/no-floating-promises.md +++ b/packages/eslint-plugin/docs/rules/no-floating-promises.md @@ -51,6 +51,8 @@ The rule accepts an options object with the following properties: type Options = { // if true, checking void expressions will be skipped ignoreVoid?: boolean; + // if true, checking for async iife will be skipped + ignoreIIFE?: boolean; }; const defaults = { @@ -60,7 +62,8 @@ const defaults = { ### `ignoreVoid` -This allows to easily suppress false-positives with void operator. +This allows you to stop the rule reporting promises consumed with void operator. +This can be a good way to explicitly mark a promise as intentionally not awaited. Examples of **correct** code for this rule with `{ ignoreVoid: true }`: @@ -73,10 +76,25 @@ void returnsPromise(); void Promise.reject('value'); ``` +### `ignoreIIFE` + +This allows you to skip checking of async iife + +Examples of **correct** code for this rule with `{ ignoreIIFE: true }`: + +```ts +await(async function() { + await res(1); +})(); + +(async function() { + await res(1); +})(); +``` + ## When Not To Use It -If you do not use Promise-like values in your codebase or want to allow them to -remain unhandled. +If you do not use Promise-like values in your codebase, or want to allow them to remain unhandled. ## Related to diff --git a/packages/eslint-plugin/docs/rules/restrict-template-expressions.md b/packages/eslint-plugin/docs/rules/restrict-template-expressions.md index 4c52e74445f5..816a0b0f9d65 100644 --- a/packages/eslint-plugin/docs/rules/restrict-template-expressions.md +++ b/packages/eslint-plugin/docs/rules/restrict-template-expressions.md @@ -6,6 +6,9 @@ Examples of **correct** code: const arg = 'foo'; const msg1 = `arg = ${arg}`; const msg2 = `arg = ${arg || 'default'}`; + +const stringWithKindProp: string & { _kind?: 'MyString' } = 'foo'; +const msg3 = `stringWithKindProp = ${stringWithKindProp}`; ``` Examples of **incorrect** code: @@ -28,6 +31,8 @@ type Options = { allowNumber?: boolean; // if true, also allow boolean type in template expressions allowBoolean?: boolean; + // if true, also allow any in template expressions + allowAny?: boolean; // if true, also allow null and undefined in template expressions allowNullable?: boolean; }; diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index d872079514a8..29b9e0d308cd 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin", - "version": "2.28.0", + "version": "2.29.0", "description": "TypeScript plugin for ESLint", "keywords": [ "eslint", @@ -41,7 +41,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/experimental-utils": "2.28.0", + "@typescript-eslint/experimental-utils": "2.29.0", "functional-red-black-tree": "^1.0.1", "regexpp": "^3.0.0", "tsutils": "^3.17.1" diff --git a/packages/eslint-plugin/src/configs/all.json b/packages/eslint-plugin/src/configs/all.json index c736840a3fff..99e6060d6709 100644 --- a/packages/eslint-plugin/src/configs/all.json +++ b/packages/eslint-plugin/src/configs/all.json @@ -22,6 +22,10 @@ "@typescript-eslint/func-call-spacing": "error", "indent": "off", "@typescript-eslint/indent": "error", + "init-declarations": "off", + "@typescript-eslint/init-declarations": "error", + "keyword-spacing": "off", + "@typescript-eslint/keyword-spacing": "error", "@typescript-eslint/member-delimiter-style": "error", "@typescript-eslint/member-ordering": "error", "@typescript-eslint/method-signature-style": "error", diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 901d31a8b9fb..cbc3b8c68f9d 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -19,6 +19,7 @@ import funcCallSpacing from './func-call-spacing'; import genericTypeNaming from './generic-type-naming'; import indent from './indent'; import interfaceNamePrefix from './interface-name-prefix'; +import keywordSpacing from './keyword-spacing'; import memberDelimiterStyle from './member-delimiter-style'; import memberNaming from './member-naming'; import memberOrdering from './member-ordering'; @@ -31,10 +32,10 @@ import noDynamicDelete from './no-dynamic-delete'; import noEmptyFunction from './no-empty-function'; import noEmptyInterface from './no-empty-interface'; import noExplicitAny from './no-explicit-any'; +import noExtraneousClass from './no-extraneous-class'; import noExtraNonNullAssertion from './no-extra-non-null-assertion'; import noExtraParens from './no-extra-parens'; import noExtraSemi from './no-extra-semi'; -import noExtraneousClass from './no-extraneous-class'; import noFloatingPromises from './no-floating-promises'; import noForInArray from './no-for-in-array'; import noImpliedEval from './no-implied-eval'; @@ -86,6 +87,7 @@ import requireAwait from './require-await'; import restrictPlusOperands from './restrict-plus-operands'; import restrictTemplateExpressions from './restrict-template-expressions'; import returnAwait from './return-await'; +import initDeclarations from './init-declarations'; import semi from './semi'; import spaceBeforeFunctionParen from './space-before-function-paren'; import strictBooleanExpressions from './strict-boolean-expressions'; @@ -117,7 +119,9 @@ export default { 'func-call-spacing': funcCallSpacing, 'generic-type-naming': genericTypeNaming, indent: indent, + 'init-declarations': initDeclarations, 'interface-name-prefix': interfaceNamePrefix, + 'keyword-spacing': keywordSpacing, 'member-delimiter-style': memberDelimiterStyle, 'member-naming': memberNaming, 'member-ordering': memberOrdering, diff --git a/packages/eslint-plugin/src/rules/init-declarations.ts b/packages/eslint-plugin/src/rules/init-declarations.ts new file mode 100644 index 000000000000..b4368527e0cd --- /dev/null +++ b/packages/eslint-plugin/src/rules/init-declarations.ts @@ -0,0 +1,53 @@ +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; +import baseRule from 'eslint/lib/rules/init-declarations'; +import { + InferOptionsTypeFromRule, + InferMessageIdsTypeFromRule, + createRule, +} from '../util'; + +export type Options = InferOptionsTypeFromRule; +export type MessageIds = InferMessageIdsTypeFromRule; + +export default createRule({ + name: 'init-declarations', + meta: { + type: 'suggestion', + docs: { + description: + 'require or disallow initialization in variable declarations', + category: 'Variables', + recommended: false, + extendsBaseRule: true, + }, + schema: baseRule.meta.schema, + messages: baseRule.meta.messages, + }, + defaultOptions: ['always'], + create(context) { + const rules = baseRule.create(context); + const mode = context.options[0] || 'always'; + + return { + 'VariableDeclaration:exit'(node: TSESTree.VariableDeclaration): void { + if (mode === 'always') { + if (node.declare) { + return; + } + if ( + node.parent?.type === AST_NODE_TYPES.TSModuleBlock && + node.parent.parent?.type === AST_NODE_TYPES.TSModuleDeclaration && + node.parent.parent?.declare + ) { + return; + } + } + + rules['VariableDeclaration:exit'](node); + }, + }; + }, +}); diff --git a/packages/eslint-plugin/src/rules/keyword-spacing.ts b/packages/eslint-plugin/src/rules/keyword-spacing.ts new file mode 100644 index 000000000000..8c4ff1d38ff6 --- /dev/null +++ b/packages/eslint-plugin/src/rules/keyword-spacing.ts @@ -0,0 +1,52 @@ +import { AST_TOKEN_TYPES } from '@typescript-eslint/experimental-utils'; +import baseRule from 'eslint/lib/rules/keyword-spacing'; +import * as util from '../util'; + +export type Options = util.InferOptionsTypeFromRule; +export type MessageIds = util.InferMessageIdsTypeFromRule; + +export default util.createRule({ + name: 'keyword-spacing', + meta: { + type: 'layout', + docs: { + description: 'Enforce consistent spacing before and after keywords', + category: 'Stylistic Issues', + recommended: false, + extendsBaseRule: true, + }, + fixable: 'whitespace', + schema: baseRule.meta.schema, + messages: baseRule.meta.messages, + }, + defaultOptions: [{}], + + create(context) { + const sourceCode = context.getSourceCode(); + const baseRules = baseRule.create(context); + return { + ...baseRules, + TSAsExpression(node): void { + const asToken = util.nullThrows( + sourceCode.getTokenAfter( + node.expression, + token => token.value === 'as', + ), + util.NullThrowsReasons.MissingToken('as', node.type), + ); + const oldTokenType = asToken.type; + // as is a contextual keyword, so it's always reported as an Identifier + // the rule looks for keyword tokens, so we temporarily override it + // we mutate it at the token level because the rule calls sourceCode.getFirstToken, + // so mutating a copy would not change the underlying copy returned by that method + asToken.type = AST_TOKEN_TYPES.Keyword; + + // use this selector just because it is just a call to `checkSpacingAroundFirstToken` + baseRules.DebuggerStatement(asToken as never); + + // make sure to reset the type afterward so we don't permanently mutate the AST + asToken.type = oldTokenType; + }, + }; + }, +}); diff --git a/packages/eslint-plugin/src/rules/no-base-to-string.ts b/packages/eslint-plugin/src/rules/no-base-to-string.ts index 47803f8e2aa4..a1121f37abd7 100644 --- a/packages/eslint-plugin/src/rules/no-base-to-string.ts +++ b/packages/eslint-plugin/src/rules/no-base-to-string.ts @@ -14,6 +14,7 @@ enum Usefulness { type Options = [ { + /** @deprecated This option is now ignored and treated as always true, it will be removed in 3.0 */ ignoreTaggedTemplateExpressions?: boolean; }, ]; @@ -39,7 +40,7 @@ export default util.createRule({ properties: { ignoreTaggedTemplateExpressions: { type: 'boolean', - default: false, + default: true, }, }, additionalProperties: false, @@ -47,8 +48,8 @@ export default util.createRule({ ], type: 'suggestion', }, - defaultOptions: [{ ignoreTaggedTemplateExpressions: false }], - create(context, [options]) { + defaultOptions: [{ ignoreTaggedTemplateExpressions: true }], + create(context) { const parserServices = util.getParserServices(context); const typeChecker = parserServices.program.getTypeChecker(); @@ -130,7 +131,6 @@ export default util.createRule({ }, TemplateLiteral(node: TSESTree.TemplateLiteral): void { if ( - options.ignoreTaggedTemplateExpressions && node.parent && node.parent.type === AST_NODE_TYPES.TaggedTemplateExpression ) { diff --git a/packages/eslint-plugin/src/rules/no-floating-promises.ts b/packages/eslint-plugin/src/rules/no-floating-promises.ts index c3ee7ffccf3c..09d70bac42c0 100644 --- a/packages/eslint-plugin/src/rules/no-floating-promises.ts +++ b/packages/eslint-plugin/src/rules/no-floating-promises.ts @@ -1,12 +1,17 @@ import * as tsutils from 'tsutils'; import * as ts from 'typescript'; -import { TSESLint } from '@typescript-eslint/experimental-utils'; +import { + TSESLint, + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; type Options = [ { ignoreVoid?: boolean; + ignoreIIFE?: boolean; }, ]; @@ -33,6 +38,7 @@ export default util.createRule({ type: 'object', properties: { ignoreVoid: { type: 'boolean' }, + ignoreIIFE: { type: 'boolean' }, }, additionalProperties: false, }, @@ -42,6 +48,7 @@ export default util.createRule({ defaultOptions: [ { ignoreVoid: false, + ignoreIIFE: false, }, ], @@ -54,6 +61,10 @@ export default util.createRule({ ExpressionStatement(node): void { const { expression } = parserServices.esTreeNodeToTSNodeMap.get(node); + if (options.ignoreIIFE && isAsyncIife(node)) { + return; + } + if (isUnhandledPromise(checker, expression)) { if (options.ignoreVoid) { context.report({ @@ -80,6 +91,19 @@ export default util.createRule({ }, }; + function isAsyncIife(node: TSESTree.ExpressionStatement): boolean { + if (node.expression.type !== AST_NODE_TYPES.CallExpression) { + return false; + } + + return ( + node.expression.type === AST_NODE_TYPES.CallExpression && + (node.expression.callee.type === + AST_NODE_TYPES.ArrowFunctionExpression || + node.expression.callee.type === AST_NODE_TYPES.FunctionExpression) + ); + } + function isUnhandledPromise( checker: ts.TypeChecker, node: ts.Node, diff --git a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts index d73d8d98a254..90dd363f8204 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -7,10 +7,10 @@ import * as util from '../util'; type Options = [ { - allowNullable?: boolean; allowNumber?: boolean; allowBoolean?: boolean; allowAny?: boolean; + allowNullable?: boolean; }, ]; @@ -33,10 +33,10 @@ export default util.createRule({ { type: 'object', properties: { - allowAny: { type: 'boolean' }, + allowNumber: { type: 'boolean' }, allowBoolean: { type: 'boolean' }, + allowAny: { type: 'boolean' }, allowNullable: { type: 'boolean' }, - allowNumber: { type: 'boolean' }, }, }, ], @@ -46,31 +46,40 @@ export default util.createRule({ const service = util.getParserServices(context); const typeChecker = service.program.getTypeChecker(); - type BaseType = - | 'string' - | 'number' - | 'bigint' - | 'boolean' - | 'null' - | 'undefined' - | 'any' - | 'other'; - - const allowedTypes: BaseType[] = [ - 'string', - ...(options.allowNumber ? (['number', 'bigint'] as const) : []), - ...(options.allowBoolean ? (['boolean'] as const) : []), - ...(options.allowNullable ? (['null', 'undefined'] as const) : []), - ...(options.allowAny ? (['any'] as const) : []), - ]; - - function isAllowedType(types: BaseType[]): boolean { - for (const type of types) { - if (!allowedTypes.includes(type)) { - return false; - } + function isUnderlyingTypePrimitive(type: ts.Type): boolean { + if (util.isTypeFlagSet(type, ts.TypeFlags.StringLike)) { + return true; + } + + if ( + options.allowNumber && + util.isTypeFlagSet( + type, + ts.TypeFlags.NumberLike | ts.TypeFlags.BigIntLike, + ) + ) { + return true; } - return true; + + if ( + options.allowBoolean && + util.isTypeFlagSet(type, ts.TypeFlags.BooleanLike) + ) { + return true; + } + + if (options.allowAny && util.isTypeFlagSet(type, ts.TypeFlags.Any)) { + return true; + } + + if ( + options.allowNullable && + util.isTypeFlagSet(type, ts.TypeFlags.Null | ts.TypeFlags.Undefined) + ) { + return true; + } + + return false; } return { @@ -80,11 +89,15 @@ export default util.createRule({ return; } - for (const expr of node.expressions) { - const type = getNodeType(expr); - if (!isAllowedType(type)) { + for (const expression of node.expressions) { + if ( + !isUnderlyingExpressionTypeConfirmingTo( + expression, + isUnderlyingTypePrimitive, + ) + ) { context.report({ - node: expr, + node: expression, messageId: 'invalidType', }); } @@ -92,58 +105,31 @@ export default util.createRule({ }, }; - /** - * Helper function to get base type of node - * @param node the node to be evaluated. - */ - function getNodeType(node: TSESTree.Expression): BaseType[] { - const tsNode = service.esTreeNodeToTSNodeMap.get(node); - const type = util.getConstrainedTypeAtLocation(typeChecker, tsNode); + function isUnderlyingExpressionTypeConfirmingTo( + expression: TSESTree.Expression, + predicate: (underlyingType: ts.Type) => boolean, + ): boolean { + return rec(getExpressionNodeType(expression)); - return getBaseType(type); - } - - function getBaseType(type: ts.Type): BaseType[] { - if (type.isStringLiteral()) { - return ['string']; - } - if (type.isNumberLiteral()) { - return ['number']; - } - if (type.flags & ts.TypeFlags.BigIntLiteral) { - return ['bigint']; - } - if (type.flags & ts.TypeFlags.BooleanLiteral) { - return ['boolean']; - } - if (type.flags & ts.TypeFlags.Null) { - return ['null']; - } - if (type.flags & ts.TypeFlags.Undefined) { - return ['undefined']; - } - if (type.flags & ts.TypeFlags.Any) { - return ['any']; - } + function rec(type: ts.Type): boolean { + if (type.isUnion()) { + return type.types.every(rec); + } - if (type.isUnion()) { - return type.types - .map(getBaseType) - .reduce((all, array) => [...all, ...array], []); - } + if (type.isIntersection()) { + return type.types.some(rec); + } - const stringType = typeChecker.typeToString(type); - if ( - stringType === 'string' || - stringType === 'number' || - stringType === 'bigint' || - stringType === 'boolean' || - stringType === 'any' - ) { - return [stringType]; + return predicate(type); } + } - return ['other']; + /** + * Helper function to extract the TS type of an TSESTree expression. + */ + function getExpressionNodeType(node: TSESTree.Expression): ts.Type { + const tsNode = service.esTreeNodeToTSNodeMap.get(node); + return util.getConstrainedTypeAtLocation(typeChecker, tsNode); } }, }); diff --git a/packages/eslint-plugin/tests/rules/init-declarations.test.ts b/packages/eslint-plugin/tests/rules/init-declarations.test.ts new file mode 100644 index 000000000000..6f4c8f75ebbc --- /dev/null +++ b/packages/eslint-plugin/tests/rules/init-declarations.test.ts @@ -0,0 +1,728 @@ +import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; +import rule from '../../src/rules/init-declarations'; +import { RuleTester } from '../RuleTester'; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('init-declarations', rule, { + valid: [ + // checking compatibility with base rule + 'var foo = null;', + 'foo = true;', + ` +var foo = 1, + bar = false, + baz = {}; + `, + ` +function foo() { + var foo = 0; + var bar = []; +} + `, + 'var fn = function() {};', + 'var foo = (bar = 2);', + 'for (var i = 0; i < 1; i++) {}', + ` +for (var foo in []) { +} + `, + { + code: ` +for (var foo of []) { +} + `, + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'let a = true;', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'const a = {};', + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: ` +function foo() { + let a = 1, + b = false; + if (a) { + let c = 3, + d = null; + } +} + `, + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: ` +function foo() { + const a = 1, + b = true; + if (a) { + const c = 3, + d = null; + } +} + `, + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: ` +function foo() { + let a = 1; + const b = false; + var c = true; +} + `, + options: ['always'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var foo;', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'var foo, bar, baz;', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: ` +function foo() { + var foo; + var bar; +} + `, + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'let a;', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'const a = 1;', + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: ` +function foo() { + let a, b; + if (a) { + let c, d; + } +} + `, + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: ` +function foo() { + const a = 1, + b = true; + if (a) { + const c = 3, + d = null; + } +} + `, + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: ` +function foo() { + let a; + const b = false; + var c; +} + `, + options: ['never'], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: 'for (var i = 0; i < 1; i++) {}', + options: ['never', { ignoreForLoopInit: true }], + }, + { + code: ` +for (var foo in []) { +} + `, + options: ['never', { ignoreForLoopInit: true }], + }, + { + code: ` +for (var foo of []) { +} + `, + options: ['never', { ignoreForLoopInit: true }], + parserOptions: { ecmaVersion: 6 }, + }, + { + code: ` +function foo() { + var bar = 1; + let baz = 2; + const qux = 3; +} + `, + options: ['always'], + }, + + // typescript-eslint + { + code: 'declare const foo: number;', + options: ['always'], + }, + { + code: 'declare const foo: number;', + options: ['never'], + }, + { + code: ` +declare namespace myLib { + let numberOfGreetings: number; +} + `, + options: ['always'], + }, + { + code: ` +declare namespace myLib { + let numberOfGreetings: number; +} + `, + options: ['never'], + }, + { + code: ` +interface GreetingSettings { + greeting: string; + duration?: number; + color?: string; +} + `, + }, + { + code: ` +interface GreetingSettings { + greeting: string; + duration?: number; + color?: string; +} + `, + options: ['never'], + }, + 'type GreetingLike = string | (() => string) | Greeter;', + { + code: 'type GreetingLike = string | (() => string) | Greeter;', + options: ['never'], + }, + { + code: ` +function foo() { + var bar: string; +} + `, + options: ['never'], + }, + { + code: 'var bar: string;', + options: ['never'], + }, + { + code: ` +var bar: string = function(): string { + return 'string'; +}; + `, + options: ['always'], + }, + { + code: ` +var bar: string = function(arg1: stirng): string { + return 'string'; +}; + `, + options: ['always'], + }, + { + code: "function foo(arg1: string = 'string'): void {}", + options: ['never'], + }, + { + code: "const foo: string = 'hello';", + options: ['never'], + }, + { + code: ` +const class1 = class NAME { + constructor() { + var name1: string = 'hello'; + } +}; + `, + }, + { + code: ` +const class1 = class NAME { + static pi: number = 3.14; +}; + `, + }, + { + code: ` +const class1 = class NAME { + static pi: number = 3.14; +}; + `, + options: ['never'], + }, + { + code: ` +interface IEmployee { + empCode: number; + empName: string; + getSalary: (number) => number; // arrow function + getManagerName(number): string; +} + `, + }, + { + code: ` +interface IEmployee { + empCode: number; + empName: string; + getSalary: (number) => number; // arrow function + getManagerName(number): string; +} + `, + options: ['never'], + }, + { + code: "declare const foo: number = 'asd';", + options: ['always'], + }, + + { + code: "const foo: number = 'asd';", + options: ['always'], + }, + { + code: 'const foo: number;', + options: ['never'], + }, + { + code: ` +namespace myLib { + let numberOfGreetings: number; +} + `, + options: ['never'], + }, + { + code: ` +namespace myLib { + let numberOfGreetings: number = 2; +} + `, + options: ['always'], + }, + ], + invalid: [ + // checking compatibility with base rule + { + code: 'var foo;', + options: ['always'], + errors: [ + { + messageId: 'initialized', + data: { idName: 'foo' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'for (var a in []) var foo;', + options: ['always'], + errors: [ + { + messageId: 'initialized', + data: { idName: 'foo' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: ` +var foo, + bar = false, + baz; + `, + options: ['always'], + errors: [ + { + messageId: 'initialized', + data: { idName: 'foo' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + { + messageId: 'initialized', + data: { idName: 'baz' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: ` +function foo() { + var foo = 0; + var bar; +} + `, + options: ['always'], + errors: [ + { + messageId: 'initialized', + data: { idName: 'bar' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: ` +function foo() { + var foo; + var bar = foo; +} + `, + options: ['always'], + errors: [ + { + messageId: 'initialized', + data: { idName: 'foo' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'let a;', + options: ['always'], + errors: [ + { + messageId: 'initialized', + data: { idName: 'a' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: ` +function foo() { + let a = 1, + b; + if (a) { + let c = 3, + d = null; + } +} + `, + options: ['always'], + errors: [ + { + messageId: 'initialized', + data: { idName: 'b' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: ` +function foo() { + let a; + const b = false; + var c; +} + `, + options: ['always'], + errors: [ + { + messageId: 'initialized', + data: { idName: 'a' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + { + messageId: 'initialized', + data: { idName: 'c' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = (bar = 2);', + options: ['never'], + errors: [ + { + messageId: 'notInitialized', + data: { idName: 'foo' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'var foo = true;', + options: ['never'], + errors: [ + { + messageId: 'notInitialized', + data: { idName: 'foo' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: ` +var foo, + bar = 5, + baz = 3; + `, + options: ['never'], + errors: [ + { + messageId: 'notInitialized', + data: { idName: 'bar' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + { + messageId: 'notInitialized', + data: { idName: 'baz' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: ` +function foo() { + var foo; + var bar = foo; +} + `, + options: ['never'], + errors: [ + { + messageId: 'notInitialized', + data: { idName: 'bar' }, + + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'let a = 1;', + options: ['never'], + errors: [ + { + messageId: 'notInitialized', + data: { idName: 'a' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: ` +function foo() { + let a = 'foo', + b; + if (a) { + let c, d; + } +} + `, + options: ['never'], + errors: [ + { + messageId: 'notInitialized', + data: { idName: 'a' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: ` +function foo() { + let a; + const b = false; + var c = 1; +} + `, + options: ['never'], + errors: [ + { + messageId: 'notInitialized', + data: { idName: 'c' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'for (var i = 0; i < 1; i++) {}', + options: ['never'], + errors: [ + { + messageId: 'notInitialized', + data: { idName: 'i' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: ` +for (var foo in []) { +} + `, + options: ['never'], + errors: [ + { + messageId: 'notInitialized', + data: { idName: 'foo' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: ` +for (var foo of []) { +} + `, + options: ['never'], + errors: [ + { + messageId: 'notInitialized', + data: { idName: 'foo' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: ` +function foo() { + var bar; +} + `, + options: ['always'], + errors: [ + { + messageId: 'initialized', + data: { idName: 'bar' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + + // typescript-eslint + { + code: "let arr: string[] = ['arr', 'ar'];", + options: ['never'], + errors: [ + { + messageId: 'notInitialized', + data: { idName: 'arr' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'let arr: string = function() {};', + options: ['never'], + errors: [ + { + messageId: 'notInitialized', + data: { idName: 'arr' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: ` +const class1 = class NAME { + constructor() { + var name1: string = 'hello'; + } +}; + `, + options: ['never'], + errors: [ + { + messageId: 'notInitialized', + data: { idName: 'name1' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: 'let arr: string;', + options: ['always'], + errors: [ + { + messageId: 'initialized', + data: { idName: 'arr' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: "declare var foo: number = 'asd';", + options: ['never'], + errors: [ + { + messageId: 'notInitialized', + data: { idName: 'foo' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: ` +namespace myLib { + let numberOfGreetings: number; +} + `, + options: ['always'], + errors: [ + { + messageId: 'initialized', + data: { idName: 'numberOfGreetings' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + { + code: ` +namespace myLib { + let numberOfGreetings: number = 2; +} + `, + options: ['never'], + errors: [ + { + messageId: 'notInitialized', + data: { idName: 'numberOfGreetings' }, + type: AST_NODE_TYPES.VariableDeclarator, + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/tests/rules/keyword-spacing.test.ts b/packages/eslint-plugin/tests/rules/keyword-spacing.test.ts new file mode 100644 index 000000000000..987caebb1e43 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/keyword-spacing.test.ts @@ -0,0 +1,153 @@ +/* eslint-disable eslint-comments/no-use */ +// this rule tests the spacing, which prettier will want to fix and break the tests +/* eslint "@typescript-eslint/internal/plugin-test-formatting": ["error", { formatWithPrettier: false }] */ +/* eslint-enable eslint-comments/no-use */ +import { TSESLint } from '@typescript-eslint/experimental-utils'; +import rule, { MessageIds, Options } from '../../src/rules/keyword-spacing'; +import { RuleTester } from '../RuleTester'; + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const BOTH = { before: true, after: true }; +const NEITHER = { before: false, after: false }; + +/** + * Creates an option object to test an 'overrides' option. + * + * e.g. + * + * override('as', BOTH) + * + * returns + * + * { + * before: false, + * after: false, + * overrides: {as: {before: true, after: true}} + * } + * @param keyword A keyword to be overridden. + * @param value A value to override. + * @returns An option object to test an 'overrides' option. + */ +function overrides(keyword: string, value: Options[0]): Options[0] { + return { + before: value.before === false, + after: value.after === false, + overrides: { [keyword]: value }, + }; +} + +/** + * Gets an error message that expected space(s) before a specified keyword. + * @param keyword A keyword. + * @returns An error message. + */ +function expectedBefore(keyword: string): TSESLint.TestCaseError[] { + return [{ messageId: 'expectedBefore', data: { value: keyword } }]; +} + +/** + * Gets an error message that expected space(s) after a specified keyword. + * @param keyword A keyword. + * @returns An error message. + */ +function expectedAfter(keyword: string): TSESLint.TestCaseError[] { + return [{ messageId: 'expectedAfter', data: { value: keyword } }]; +} + +/** + * Gets an error message that unexpected space(s) before a specified keyword. + * @param keyword A keyword. + * @returns An error message. + */ +function unexpectedBefore( + keyword: string, +): TSESLint.TestCaseError[] { + return [{ messageId: 'unexpectedBefore', data: { value: keyword } }]; +} + +/** + * Gets an error message that unexpected space(s) after a specified keyword. + * @param keyword A keyword. + * @returns An error message. + */ +function unexpectedAfter( + keyword: string, +): TSESLint.TestCaseError[] { + return [{ messageId: 'unexpectedAfter', data: { value: keyword } }]; +} + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('keyword-spacing', rule, { + valid: [ + //---------------------------------------------------------------------- + // as (typing) + //---------------------------------------------------------------------- + { + code: 'const foo = {} as {};', + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'const foo = {}as{};', + options: [NEITHER], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'const foo = {} as {};', + options: [overrides('as', BOTH)], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'const foo = {}as{};', + options: [overrides('as', NEITHER)], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + { + code: 'const foo = {} as {};', + options: [{ overrides: { as: {} } }], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + }, + ], + invalid: [ + //---------------------------------------------------------------------- + // as (typing) + //---------------------------------------------------------------------- + { + code: 'const foo = {}as {};', + output: 'const foo = {} as {};', + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: expectedBefore('as'), + }, + { + code: 'const foo = {} as{};', + output: 'const foo = {}as{};', + options: [NEITHER], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: unexpectedBefore('as'), + }, + { + code: 'const foo = {} as{};', + output: 'const foo = {} as {};', + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: expectedAfter('as'), + }, + { + code: 'const foo = {}as {};', + output: 'const foo = {}as{};', + options: [NEITHER], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: unexpectedAfter('as'), + }, + { + code: 'const foo = {} as{};', + options: [{ overrides: { as: {} } }], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: expectedAfter('as'), + }, + ], +}); diff --git a/packages/eslint-plugin/tests/rules/no-base-to-string.test.ts b/packages/eslint-plugin/tests/rules/no-base-to-string.test.ts index 59abfbb1f602..82bd4e0302e1 100644 --- a/packages/eslint-plugin/tests/rules/no-base-to-string.test.ts +++ b/packages/eslint-plugin/tests/rules/no-base-to-string.test.ts @@ -70,6 +70,10 @@ const literalWithToString = { 'let _ = {} ^ {};', 'let _ = {} << {};', 'let _ = {} >> {};', + ` +function tag() {} +tag\`\${{}}\`; + `, { code: ` function tag() {} @@ -95,21 +99,6 @@ const literalWithToString = { }, ], }, - { - code: ` - function tag() {} - tag\`\${{}}\`; - `, - errors: [ - { - data: { - certainty: 'will', - name: '{}', - }, - messageId: 'baseToString', - }, - ], - }, { code: '({}.toString());', errors: [ diff --git a/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts b/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts index c7e688c9852e..f6601a0613a1 100644 --- a/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts +++ b/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts @@ -335,6 +335,54 @@ async function test() { return returnsPromise(); } `, + // ignoreIIFE + { + code: ` + (async () => { + await something(); + })(); + `, + options: [{ ignoreIIFE: true }], + }, + { + code: ` + (async () => { + something(); + })(); + `, + options: [{ ignoreIIFE: true }], + }, + { + code: '(async function foo() {})();', + options: [{ ignoreIIFE: true }], + }, + { + code: ` + function foo() { + (async function bar() {})(); + } + `, + options: [{ ignoreIIFE: true }], + }, + { + code: ` + const foo = () => + new Promise(res => { + (async function() { + await res(1); + })(); + }); + `, + options: [{ ignoreIIFE: true }], + }, + { + code: ` + (async function() { + await res(1); + })(); + `, + options: [{ ignoreIIFE: true }], + }, ], invalid: [ @@ -726,5 +774,126 @@ async function test() { }, ], }, + { + code: ` + (async () => { + await something(); + })(); + `, + errors: [ + { + line: 2, + messageId: 'floating', + }, + ], + }, + { + code: ` + (async () => { + something(); + })(); + `, + errors: [ + { + line: 2, + messageId: 'floating', + }, + ], + }, + { + code: '(async function foo() {})();', + errors: [ + { + line: 1, + messageId: 'floating', + }, + ], + }, + { + code: ` + function foo() { + (async function bar() {})(); + } + `, + errors: [ + { + line: 3, + messageId: 'floating', + }, + ], + }, + { + code: ` + const foo = () => + new Promise(res => { + (async function() { + await res(1); + })(); + }); + `, + errors: [ + { + line: 4, + messageId: 'floating', + }, + ], + }, + { + code: ` + (async function() { + await res(1); + })(); + `, + errors: [ + { + line: 2, + messageId: 'floating', + }, + ], + }, + { + code: ` + (async function() { + Promise.resolve(); + })(); + `, + options: [{ ignoreIIFE: true }], + errors: [ + { + line: 3, + messageId: 'floating', + }, + ], + }, + { + code: ` + (async function() { + const promiseIntersection: Promise & number; + promiseIntersection; + promiseIntersection.then(() => {}); + promiseIntersection.catch(); + promiseIntersection.finally(); + })(); + `, + options: [{ ignoreIIFE: true }], + errors: [ + { + line: 4, + messageId: 'floating', + }, + { + line: 5, + messageId: 'floating', + }, + { + line: 6, + messageId: 'floating', + }, + { + line: 7, + messageId: 'floating', + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts b/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts index 6a2861efa323..2f5900f3d2ac 100644 --- a/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts +++ b/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts @@ -30,6 +30,12 @@ ruleTester.run('restrict-template-expressions', rule, { return \`arg = \${arg}\`; } `, + // Base case - intersection type + ` + function test(arg: T) { + return \`arg = \${arg}\`; + } + `, // Base case - don't check tagged templates ` tag\`arg = \${null}\`; @@ -68,6 +74,14 @@ ruleTester.run('restrict-template-expressions', rule, { } `, }, + { + options: [{ allowNumber: true }], + code: ` + function test(arg: T) { + return \`arg = \${arg}\`; + } + `, + }, { options: [{ allowNumber: true }], code: ` @@ -236,6 +250,13 @@ ruleTester.run('restrict-template-expressions', rule, { `, errors: [{ messageId: 'invalidType', line: 3, column: 30 }], }, + { + code: ` + declare const arg: { a: string } & { b: string }; + const msg = \`arg = \${arg}\`; + `, + errors: [{ messageId: 'invalidType', line: 3, column: 30 }], + }, { options: [{ allowNumber: true, allowBoolean: true, allowNullable: true }], code: ` diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index ea60d9b31697..eb2d1cd9c60e 100644 --- a/packages/eslint-plugin/typings/eslint-rules.d.ts +++ b/packages/eslint-plugin/typings/eslint-rules.d.ts @@ -142,6 +142,85 @@ declare module 'eslint/lib/rules/indent' { export = rule; } +declare module 'eslint/lib/rules/keyword-spacing' { + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; + import { RuleFunction } from '@typescript-eslint/experimental-utils/dist/ts-eslint'; + + type Options = [ + { + before?: boolean; + after?: boolean; + overrides?: Record< + string, + { + before?: boolean; + after?: boolean; + } + >; + }, + ]; + type MessageIds = + | 'expectedBefore' + | 'expectedAfter' + | 'unexpectedBefore' + | 'unexpectedAfter'; + + const rule: TSESLint.RuleModule< + MessageIds, + Options, + { + // Statements + DebuggerStatement: RuleFunction; + WithStatement: RuleFunction; + + // Statements - Control flow + BreakStatement: RuleFunction; + ContinueStatement: RuleFunction; + ReturnStatement: RuleFunction; + ThrowStatement: RuleFunction; + TryStatement: RuleFunction; + + // Statements - Choice + IfStatement: RuleFunction; + SwitchStatement: RuleFunction; + SwitchCase: RuleFunction; + + // Statements - Loops + DoWhileStatement: RuleFunction; + ForInStatement: RuleFunction; + ForOfStatement: RuleFunction; + ForStatement: RuleFunction; + WhileStatement: RuleFunction; + + // Statements - Declarations + ClassDeclaration: RuleFunction; + ExportNamedDeclaration: RuleFunction; + ExportDefaultDeclaration: RuleFunction; + ExportAllDeclaration: RuleFunction; + FunctionDeclaration: RuleFunction; + ImportDeclaration: RuleFunction; + VariableDeclaration: RuleFunction; + + // Expressions + ArrowFunctionExpression: RuleFunction; + AwaitExpression: RuleFunction; + ClassExpression: RuleFunction; + FunctionExpression: RuleFunction; + NewExpression: RuleFunction; + Super: RuleFunction; + ThisExpression: RuleFunction; + UnaryExpression: RuleFunction; + YieldExpression: RuleFunction; + + // Others + ImportNamespaceSpecifier: RuleFunction; + MethodDefinition: RuleFunction; + Property: RuleFunction; + } + >; + export = rule; +} + declare module 'eslint/lib/rules/no-dupe-class-members' { import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; @@ -543,3 +622,21 @@ declare module 'eslint/lib/rules/no-extra-semi' { >; export = rule; } + +declare module 'eslint/lib/rules/init-declarations' { + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; + + const rule: TSESLint.RuleModule< + 'initialized' | 'notInitialized', + [ + 'always' | 'never', + { + ignoreForLoopInit?: boolean; + }?, + ], + { + 'VariableDeclaration:exit'(node: TSESTree.VariableDeclaration): void; + } + >; + export = rule; +} diff --git a/packages/experimental-utils/CHANGELOG.md b/packages/experimental-utils/CHANGELOG.md index ece5fcbb544c..b1f38a523313 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.29.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.28.0...v2.29.0) (2020-04-20) + +**Note:** Version bump only for package @typescript-eslint/experimental-utils + + + + + # [2.28.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.27.0...v2.28.0) (2020-04-13) diff --git a/packages/experimental-utils/package.json b/packages/experimental-utils/package.json index b623c12ed314..ac9a07b11c45 100644 --- a/packages/experimental-utils/package.json +++ b/packages/experimental-utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/experimental-utils", - "version": "2.28.0", + "version": "2.29.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.28.0", + "@typescript-eslint/typescript-estree": "2.29.0", "eslint-scope": "^5.0.0", "eslint-utils": "^2.0.0" }, diff --git a/packages/parser/CHANGELOG.md b/packages/parser/CHANGELOG.md index c99a618a07ef..74cb4ee49709 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. +# [2.29.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.28.0...v2.29.0) (2020-04-20) + +**Note:** Version bump only for package @typescript-eslint/parser + + + + + # [2.28.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.27.0...v2.28.0) (2020-04-13) **Note:** Version bump only for package @typescript-eslint/parser diff --git a/packages/parser/package.json b/packages/parser/package.json index a36625794ad1..8aa5d8930483 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/parser", - "version": "2.28.0", + "version": "2.29.0", "description": "An ESLint custom parser which leverages TypeScript ESTree", "main": "dist/parser.js", "types": "dist/parser.d.ts", @@ -43,13 +43,13 @@ }, "dependencies": { "@types/eslint-visitor-keys": "^1.0.0", - "@typescript-eslint/experimental-utils": "2.28.0", - "@typescript-eslint/typescript-estree": "2.28.0", + "@typescript-eslint/experimental-utils": "2.29.0", + "@typescript-eslint/typescript-estree": "2.29.0", "eslint-visitor-keys": "^1.1.0" }, "devDependencies": { "@types/glob": "^7.1.1", - "@typescript-eslint/shared-fixtures": "2.28.0", + "@typescript-eslint/shared-fixtures": "2.29.0", "glob": "*" }, "peerDependenciesMeta": { diff --git a/packages/shared-fixtures/CHANGELOG.md b/packages/shared-fixtures/CHANGELOG.md index f460b702eb95..181fc307a111 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.29.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.28.0...v2.29.0) (2020-04-20) + +**Note:** Version bump only for package @typescript-eslint/shared-fixtures + + + + + # [2.28.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.27.0...v2.28.0) (2020-04-13) **Note:** Version bump only for package @typescript-eslint/shared-fixtures diff --git a/packages/shared-fixtures/package.json b/packages/shared-fixtures/package.json index e843de63a56b..610efbaabd73 100644 --- a/packages/shared-fixtures/package.json +++ b/packages/shared-fixtures/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/shared-fixtures", - "version": "2.28.0", + "version": "2.29.0", "private": true, "scripts": { "build": "tsc -b tsconfig.build.json", diff --git a/packages/typescript-estree/CHANGELOG.md b/packages/typescript-estree/CHANGELOG.md index 8750d8a65ed5..9ea9a9c6ec7a 100644 --- a/packages/typescript-estree/CHANGELOG.md +++ b/packages/typescript-estree/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. +# [2.29.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.28.0...v2.29.0) (2020-04-20) + + +### Features + +* **eslint-plugin:** [no-floating-promise] add option to ignore IIFEs ([#1799](https://github.com/typescript-eslint/typescript-eslint/issues/1799)) ([cea51bf](https://github.com/typescript-eslint/typescript-eslint/commit/cea51bf130d6d3c2935f5e2dcc468196f2ad9d00)) + + + + + # [2.28.0](https://github.com/typescript-eslint/typescript-eslint/compare/v2.27.0...v2.28.0) (2020-04-13) diff --git a/packages/typescript-estree/package.json b/packages/typescript-estree/package.json index bc75ebdf786c..8f69a2a71a0b 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/typescript-estree", - "version": "2.28.0", + "version": "2.29.0", "description": "A parser that converts TypeScript source code into an ESTree compatible form", "main": "dist/parser.js", "types": "dist/parser.d.ts", @@ -58,7 +58,7 @@ "@types/lodash": "^4.14.149", "@types/semver": "^6.2.0", "@types/tmp": "^0.1.0", - "@typescript-eslint/shared-fixtures": "2.28.0", + "@typescript-eslint/shared-fixtures": "2.29.0", "tmp": "^0.1.0", "typescript": "*" }, diff --git a/packages/typescript-estree/src/ts-estree/ts-estree.ts b/packages/typescript-estree/src/ts-estree/ts-estree.ts index 5792ebc02058..1781ffd5fff1 100644 --- a/packages/typescript-estree/src/ts-estree/ts-estree.ts +++ b/packages/typescript-estree/src/ts-estree/ts-estree.ts @@ -392,7 +392,8 @@ export type LeftHandSideExpression = | PrimaryExpression | TaggedTemplateExpression | TSNonNullExpression - | TSAsExpression; + | TSAsExpression + | ArrowFunctionExpression; export type Literal = | BooleanLiteral | NumberLiteral pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy