From cc70e4fbadd0b15fd6af913a2e1e2ddd346fa558 Mon Sep 17 00:00:00 2001 From: ulrichb Date: Tue, 14 Apr 2020 18:15:08 +0200 Subject: [PATCH 1/7] feat(eslint-plugin): [restrict-template-expressions] add support for intersection types (#1803) --- .../rules/restrict-template-expressions.md | 5 + .../rules/restrict-template-expressions.ts | 142 ++++++++---------- .../restrict-template-expressions.test.ts | 21 +++ 3 files changed, 90 insertions(+), 78 deletions(-) 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/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/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: ` From 369978e9685bacb3e3882b0510ff06eaf8df4ca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20Unneb=C3=A4ck?= Date: Mon, 20 Apr 2020 02:42:03 +0100 Subject: [PATCH 2/7] fix(eslint-plugin): [no-base-to-string] soft remove `ignoreTaggedTemplateExpressions` option (#1916) --- .../docs/rules/no-base-to-string.md | 26 ------------------- .../src/rules/no-base-to-string.ts | 8 +++--- .../tests/rules/no-base-to-string.test.ts | 19 +++----------- 3 files changed, 8 insertions(+), 45 deletions(-) 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/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/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: [ From c5106dd4bf2bc8846cc39aa8bb50c33bec026d4d Mon Sep 17 00:00:00 2001 From: Pascal Heitz Date: Mon, 20 Apr 2020 07:42:48 +0200 Subject: [PATCH 3/7] feat(eslint-plugin): add extension rule `keyword-spacing` (#1739) --- packages/eslint-plugin/README.md | 1 + .../docs/rules/keyword-spacing.md | 22 +++ packages/eslint-plugin/src/configs/all.json | 2 + packages/eslint-plugin/src/rules/index.ts | 4 +- .../src/rules/keyword-spacing.ts | 52 ++++++ .../tests/rules/keyword-spacing.test.ts | 153 ++++++++++++++++++ .../eslint-plugin/typings/eslint-rules.d.ts | 79 +++++++++ 7 files changed, 312 insertions(+), 1 deletion(-) create mode 100644 packages/eslint-plugin/docs/rules/keyword-spacing.md create mode 100644 packages/eslint-plugin/src/rules/keyword-spacing.ts create mode 100644 packages/eslint-plugin/tests/rules/keyword-spacing.test.ts diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 7ed22981926c..0428cff43fc5 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -183,6 +183,7 @@ 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/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/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/src/configs/all.json b/packages/eslint-plugin/src/configs/all.json index c736840a3fff..ec65f143c590 100644 --- a/packages/eslint-plugin/src/configs/all.json +++ b/packages/eslint-plugin/src/configs/all.json @@ -22,6 +22,8 @@ "@typescript-eslint/func-call-spacing": "error", "indent": "off", "@typescript-eslint/indent": "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..63c402297e39 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'; @@ -118,6 +119,7 @@ export default { 'generic-type-naming': genericTypeNaming, indent: indent, '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/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/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/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index ea60d9b31697..ac8700d42e56 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'; From cea51bf130d6d3c2935f5e2dcc468196f2ad9d00 Mon Sep 17 00:00:00 2001 From: Anix Date: Mon, 20 Apr 2020 11:13:18 +0530 Subject: [PATCH 4/7] feat(eslint-plugin): [no-floating-promise] add option to ignore IIFEs (#1799) --- .../docs/rules/no-floating-promises.md | 24 ++- .../src/rules/no-floating-promises.ts | 26 ++- .../tests/rules/no-floating-promises.test.ts | 169 ++++++++++++++++++ .../src/ts-estree/ts-estree.ts | 3 +- 4 files changed, 217 insertions(+), 5 deletions(-) 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/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/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/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 From b01f5e778ac28e0797a3734fc58d025bb224f418 Mon Sep 17 00:00:00 2001 From: Anix Date: Mon, 20 Apr 2020 11:32:22 +0530 Subject: [PATCH 5/7] feat(eslint-plugin): add extension rule `init-declarations` (#1814) Co-authored-by: Brad Zacher --- packages/eslint-plugin/README.md | 1 + .../docs/rules/init-declarations.md | 22 + packages/eslint-plugin/src/configs/all.json | 2 + packages/eslint-plugin/src/rules/index.ts | 2 + .../src/rules/init-declarations.ts | 53 ++ .../tests/rules/init-declarations.test.ts | 728 ++++++++++++++++++ .../eslint-plugin/typings/eslint-rules.d.ts | 18 + 7 files changed, 826 insertions(+) create mode 100644 packages/eslint-plugin/docs/rules/init-declarations.md create mode 100644 packages/eslint-plugin/src/rules/init-declarations.ts create mode 100644 packages/eslint-plugin/tests/rules/init-declarations.test.ts diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 0428cff43fc5..c4507267c6f0 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -183,6 +183,7 @@ 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 | | | | 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/src/configs/all.json b/packages/eslint-plugin/src/configs/all.json index ec65f143c590..99e6060d6709 100644 --- a/packages/eslint-plugin/src/configs/all.json +++ b/packages/eslint-plugin/src/configs/all.json @@ -22,6 +22,8 @@ "@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", diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 63c402297e39..cbc3b8c68f9d 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -87,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'; @@ -118,6 +119,7 @@ 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, 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/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/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index ac8700d42e56..eb2d1cd9c60e 100644 --- a/packages/eslint-plugin/typings/eslint-rules.d.ts +++ b/packages/eslint-plugin/typings/eslint-rules.d.ts @@ -622,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; +} From 81f51603820eb7478ecccf653e5bd2bbb2c9c698 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Mon, 20 Apr 2020 09:08:13 -0700 Subject: [PATCH 6/7] chore: add gh action to lock old issues and PRs (#1888) As per our [contributing guidelines](https://github.com/typescript-eslint/typescript-eslint/blob/master/CONTRIBUTING.md#commenting), we prefer if users raise new issues instead of commenting on old, closed ones. This adds a github action to automatically lock issues and PRs after they've been closed for 30 days. --- .github/workflows/lock.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/lock.yml 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: "" From f3bef4c086bd241d7d663b79551a9a96064a6334 Mon Sep 17 00:00:00 2001 From: James Henry Date: Mon, 20 Apr 2020 17:01:51 +0000 Subject: [PATCH 7/7] chore: publish v2.29.0 --- CHANGELOG.md | 19 +++++++++++++++++++ lerna.json | 2 +- packages/eslint-plugin-internal/CHANGELOG.md | 8 ++++++++ packages/eslint-plugin-internal/package.json | 4 ++-- packages/eslint-plugin-tslint/CHANGELOG.md | 8 ++++++++ packages/eslint-plugin-tslint/package.json | 6 +++--- packages/eslint-plugin/CHANGELOG.md | 19 +++++++++++++++++++ packages/eslint-plugin/package.json | 4 ++-- packages/experimental-utils/CHANGELOG.md | 8 ++++++++ packages/experimental-utils/package.json | 4 ++-- packages/parser/CHANGELOG.md | 8 ++++++++ packages/parser/package.json | 8 ++++---- packages/shared-fixtures/CHANGELOG.md | 8 ++++++++ packages/shared-fixtures/package.json | 2 +- packages/typescript-estree/CHANGELOG.md | 11 +++++++++++ packages/typescript-estree/package.json | 4 ++-- 16 files changed, 106 insertions(+), 17 deletions(-) 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/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/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": "*" },