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": "*" }, 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