From 4dcf1ba1a0e9952c82cb366000e1104a406633b1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Feb 2022 18:40:51 -0800 Subject: [PATCH 01/12] chore: bump rollup from 2.67.2 to 2.67.3 (#4578) Bumps [rollup](https://github.com/rollup/rollup) from 2.67.2 to 2.67.3. - [Release notes](https://github.com/rollup/rollup/releases) - [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md) - [Commits](https://github.com/rollup/rollup/compare/v2.67.2...v2.67.3) --- updated-dependencies: - dependency-name: rollup dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index e86a9f956597..418345426037 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13468,9 +13468,9 @@ rimraf@^2.6.3: glob "^7.1.3" rollup@^2.59.0: - version "2.67.2" - resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.67.2.tgz#d95e15f60932ad21e05a870bd0aa0b235d056f04" - integrity sha512-hoEiBWwZtf1QdK3jZIq59L0FJj4Fiv4RplCO4pvCRC86qsoFurWB4hKQIjoRf3WvJmk5UZ9b0y5ton+62fC7Tw== + version "2.67.3" + resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.67.3.tgz#3f04391fc296f807d067c9081d173e0a33dbd37e" + integrity sha512-G/x1vUwbGtP6O5ZM8/sWr8+p7YfZhI18pPqMRtMYMWSbHjKZ/ajHGiM+GWNTlWyOR0EHIdT8LHU+Z4ciIZ1oBw== optionalDependencies: fsevents "~2.3.2" From 0c63f26ad0b72517a59ad8608ccdaa4dbcde7659 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Feb 2022 18:41:05 -0800 Subject: [PATCH 02/12] chore: bump webpack from 5.69.0 to 5.69.1 (#4579) Bumps [webpack](https://github.com/webpack/webpack) from 5.69.0 to 5.69.1. - [Release notes](https://github.com/webpack/webpack/releases) - [Commits](https://github.com/webpack/webpack/compare/v5.69.0...v5.69.1) --- updated-dependencies: - dependency-name: webpack dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/yarn.lock b/yarn.lock index 418345426037..ccca33603c21 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6887,7 +6887,7 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" -enhanced-resolve@^5.8.3, enhanced-resolve@^5.9.0: +enhanced-resolve@^5.8.3: version "5.9.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.9.0.tgz#49ac24953ac8452ed8fed2ef1340fc8e043667ee" integrity sha512-weDYmzbBygL7HzGGS26M3hGQx68vehdEg6VUmqSOaFzXExFqlnKuSvsEJCVGQHScS8CQMbrAqftT+AzzHNt/YA== @@ -15373,9 +15373,9 @@ webpack-sources@^3.2.3: integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== webpack@^5.61.0, webpack@^5.64.0: - version "5.69.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.69.0.tgz#c9eb607d4f6c49f1e5755492323a7b055c3450e3" - integrity sha512-E5Fqu89Gu8fR6vejRqu26h8ld/k6/dCVbeGUcuZjc+goQHDfCPU9rER71JmdtBYGmci7Ec2aFEATQ2IVXKy2wg== + version "5.69.1" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.69.1.tgz#8cfd92c192c6a52c99ab00529b5a0d33aa848dc5" + integrity sha512-+VyvOSJXZMT2V5vLzOnDuMz5GxEqLk7hKWQ56YxPW/PQRUuKimPqmEIJOx8jHYeyo65pKbapbW464mvsKbaj4A== dependencies: "@types/eslint-scope" "^3.7.3" "@types/estree" "^0.0.51" @@ -15386,7 +15386,7 @@ webpack@^5.61.0, webpack@^5.64.0: acorn-import-assertions "^1.7.6" browserslist "^4.14.5" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.9.0" + enhanced-resolve "^5.8.3" es-module-lexer "^0.9.0" eslint-scope "5.1.1" events "^3.2.0" From f106e4b95e824ebb68141bce3d3207448d50c860 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20De=20Boey?= Date: Thu, 24 Feb 2022 00:37:08 +0100 Subject: [PATCH 03/12] feat(utils): expose `ast-utils`' helpers (#4503) --- packages/utils/src/ast-utils/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/utils/src/ast-utils/index.ts b/packages/utils/src/ast-utils/index.ts index c0ab325299ad..66e528aa3e34 100644 --- a/packages/utils/src/ast-utils/index.ts +++ b/packages/utils/src/ast-utils/index.ts @@ -1,3 +1,4 @@ +export * from './eslint-utils'; +export * from './helpers'; export * from './misc'; export * from './predicates'; -export * from './eslint-utils'; From 63d051eed29dcf71015a23992feac0a8f92717a0 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 23 Feb 2022 18:39:15 -0500 Subject: [PATCH 04/12] feat(eslint-plugin): add `no-redundant-type-constituents` rule (#4378) --- .cspell.json | 5 +- packages/eslint-plugin/README.md | 1 + .../rules/no-redundant-type-constituents.md | 85 ++ packages/eslint-plugin/src/configs/all.ts | 1 + packages/eslint-plugin/src/rules/index.ts | 2 + .../rules/no-redundant-type-constituents.ts | 457 ++++++++++ packages/eslint-plugin/src/util/misc.ts | 21 + .../no-redundant-type-constituents.test.ts | 787 ++++++++++++++++++ packages/type-utils/src/predicates.ts | 19 + 9 files changed, 1376 insertions(+), 2 deletions(-) create mode 100644 packages/eslint-plugin/docs/rules/no-redundant-type-constituents.md create mode 100644 packages/eslint-plugin/src/rules/no-redundant-type-constituents.ts create mode 100644 packages/eslint-plugin/tests/rules/no-redundant-type-constituents.test.ts diff --git a/.cspell.json b/.cspell.json index 898e10932518..4a8c0ad226be 100644 --- a/.cspell.json +++ b/.cspell.json @@ -71,8 +71,8 @@ "IIFE", "IIFEs", "linebreaks", - "markdownlint", "lzstring", + "markdownlint", "necroing", "nocheck", "nullish", @@ -101,14 +101,15 @@ "transpiled", "transpiles", "transpiling", - "tsvfs", "tsconfigs", "tsutils", + "tsvfs", "typedef", "typedefs", "unfixable", "unoptimized", "unprefixed", + "upsert", "Zacher" ], "overrides": [ diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 47012b81b8fc..d10d9f079907 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -136,6 +136,7 @@ Pro Tip: For larger codebases you may want to consider splitting our linting int | [`@typescript-eslint/no-non-null-asserted-optional-chain`](./docs/rules/no-non-null-asserted-optional-chain.md) | Disallows using a non-null assertion after an optional chain expression | :white_check_mark: | | | | [`@typescript-eslint/no-non-null-assertion`](./docs/rules/no-non-null-assertion.md) | Disallows non-null assertions using the `!` postfix operator | :white_check_mark: | | | | [`@typescript-eslint/no-parameter-properties`](./docs/rules/no-parameter-properties.md) | Disallow the use of parameter properties in class constructors | | | | +| [`@typescript-eslint/no-redundant-type-constituents`](./docs/rules/no-redundant-type-constituents.md) | Disallow members of unions and intersections that do nothing or override type information | | | :thought_balloon: | | [`@typescript-eslint/no-require-imports`](./docs/rules/no-require-imports.md) | Disallows invocation of `require()` | | | | | [`@typescript-eslint/no-this-alias`](./docs/rules/no-this-alias.md) | Disallow aliasing `this` | :white_check_mark: | | | | [`@typescript-eslint/no-type-alias`](./docs/rules/no-type-alias.md) | Disallow the use of type aliases | | | | diff --git a/packages/eslint-plugin/docs/rules/no-redundant-type-constituents.md b/packages/eslint-plugin/docs/rules/no-redundant-type-constituents.md new file mode 100644 index 000000000000..e427c671d3fc --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-redundant-type-constituents.md @@ -0,0 +1,85 @@ +# `no-redundant-type-constituents` + +Disallow members of unions and intersections that do nothing or override type information. + +## Rule Details + +Some types can override some other types ("constituents") in a union or intersection and/or be overridden by some other types. +TypeScript's set theory of types includes cases where a constituent type might be useless in the parent union or intersection. + +Within `|` unions: + +- `any` and `unknown` "override" all other union members +- `never` is dropped from unions in any position except when in a return type position +- primitive types such as `string` "override" any of their literal types such as `""` + +Within `&` intersections: + +- `any` and `never` "override" all other intersection members +- `unknown` is dropped from intersections +- literal types "override" any primitive types in an intersection +- literal types such as `""` "override" any of their primitive types such as `string` + +Examples of code for this rule: + + + +### ❌ Incorrect + +```ts +type UnionAny = any | 'foo'; +type UnionUnknown = unknown | 'foo'; +type UnionNever = never | 'foo'; + +type UnionBooleanLiteral = boolean | false; +type UnionNumberLiteral = number | 1; +type UnionStringLiteral = string | 'foo'; + +type IntersectionAny = any & 'foo'; +type IntersectionUnknown = string & unknown; +type IntersectionNever = string | never; + +type IntersectionBooleanLiteral = boolean & false; +type IntersectionNumberLiteral = number & 1; +type IntersectionStringLiteral = string & 'foo'; +``` + +### ✅ Correct + +```ts +type UnionAny = any; +type UnionUnknown = unknown; +type UnionNever = never; + +type UnionBooleanLiteral = boolean; +type UnionNumberLiteral = number; +type UnionStringLiteral = string; + +type IntersectionAny = any; +type IntersectionUnknown = string; +type IntersectionNever = string; + +type IntersectionBooleanLiteral = false; +type IntersectionNumberLiteral = 1; +type IntersectionStringLiteral = 'foo'; + +type ReturnUnionNever = () => string | never; +``` + +## Limitations + +This rule plays it safe and only works with bottom types, top types, and comparing literal types to primitive types. +It also does not provide an auto-fixer just yet. + +## Further Reading + +- [Union Types](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types) +- [Intersection Types](https://www.typescriptlang.org/docs/handbook/2/objects.html#intersection-types) +- [Bottom Types](https://en.wikipedia.org/wiki/Bottom_type) +- [Top Types](https://en.wikipedia.org/wiki/Top_type) + +## Attributes + +- [ ] ✅ Recommended +- [ ] 🔧 Fixable +- [x] 💭 Requires type information diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index 538be53ce583..c052ce4eb37d 100644 --- a/packages/eslint-plugin/src/configs/all.ts +++ b/packages/eslint-plugin/src/configs/all.ts @@ -88,6 +88,7 @@ export = { '@typescript-eslint/no-parameter-properties': 'error', 'no-redeclare': 'off', '@typescript-eslint/no-redeclare': 'error', + '@typescript-eslint/no-redundant-type-constituents': 'error', '@typescript-eslint/no-require-imports': 'error', 'no-restricted-imports': 'off', '@typescript-eslint/no-restricted-imports': 'error', diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 83e76f0a23d0..96251c3c22f0 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -60,6 +60,7 @@ import noNonNullAssertedOptionalChain from './no-non-null-asserted-optional-chai import noNonNullAssertion from './no-non-null-assertion'; import noParameterProperties from './no-parameter-properties'; import noRedeclare from './no-redeclare'; +import noRedundantTypeConstituents from './no-redundant-type-constituents'; import noRequireImports from './no-require-imports'; import noRestrictedImports from './no-restricted-imports'; import noShadow from './no-shadow'; @@ -183,6 +184,7 @@ export default { 'no-non-null-assertion': noNonNullAssertion, 'no-parameter-properties': noParameterProperties, 'no-redeclare': noRedeclare, + 'no-redundant-type-constituents': noRedundantTypeConstituents, 'no-require-imports': noRequireImports, 'no-restricted-imports': noRestrictedImports, 'no-shadow': noShadow, diff --git a/packages/eslint-plugin/src/rules/no-redundant-type-constituents.ts b/packages/eslint-plugin/src/rules/no-redundant-type-constituents.ts new file mode 100644 index 000000000000..1ee8c3c49594 --- /dev/null +++ b/packages/eslint-plugin/src/rules/no-redundant-type-constituents.ts @@ -0,0 +1,457 @@ +import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/utils'; +import * as tsutils from 'tsutils'; +import * as ts from 'typescript'; +import * as util from '../util'; + +const literalToPrimitiveTypeFlags = { + [ts.TypeFlags.BigIntLiteral]: ts.TypeFlags.BigInt, + [ts.TypeFlags.BooleanLiteral]: ts.TypeFlags.Boolean, + [ts.TypeFlags.NumberLiteral]: ts.TypeFlags.Number, + [ts.TypeFlags.StringLiteral]: ts.TypeFlags.String, + [ts.TypeFlags.TemplateLiteral]: ts.TypeFlags.String, +} as const; + +const literalTypeFlags = [ + ts.TypeFlags.BigIntLiteral, + ts.TypeFlags.BooleanLiteral, + ts.TypeFlags.NumberLiteral, + ts.TypeFlags.StringLiteral, + ts.TypeFlags.TemplateLiteral, +] as const; + +const primitiveTypeFlags = [ + ts.TypeFlags.BigInt, + ts.TypeFlags.Boolean, + ts.TypeFlags.Number, + ts.TypeFlags.String, +] as const; + +const primitiveTypeFlagNames = { + [ts.TypeFlags.BigInt]: 'bigint', + [ts.TypeFlags.Boolean]: 'boolean', + [ts.TypeFlags.Number]: 'number', + [ts.TypeFlags.String]: 'string', +} as const; + +const primitiveTypeFlagTypes = { + bigint: ts.TypeFlags.BigIntLiteral, + boolean: ts.TypeFlags.BooleanLiteral, + number: ts.TypeFlags.NumberLiteral, + string: ts.TypeFlags.StringLiteral, +} as const; + +const keywordNodeTypesToTsTypes = new Map([ + [TSESTree.AST_NODE_TYPES.TSAnyKeyword, ts.TypeFlags.Any], + [TSESTree.AST_NODE_TYPES.TSBigIntKeyword, ts.TypeFlags.BigInt], + [TSESTree.AST_NODE_TYPES.TSBooleanKeyword, ts.TypeFlags.Boolean], + [TSESTree.AST_NODE_TYPES.TSNeverKeyword, ts.TypeFlags.Never], + [TSESTree.AST_NODE_TYPES.TSUnknownKeyword, ts.TypeFlags.Unknown], + [TSESTree.AST_NODE_TYPES.TSNumberKeyword, ts.TypeFlags.Number], + [TSESTree.AST_NODE_TYPES.TSStringKeyword, ts.TypeFlags.String], +]); + +type PrimitiveTypeFlag = typeof primitiveTypeFlags[number]; + +interface TypeFlagsWithName { + typeFlags: ts.TypeFlags; + typeName: string; +} + +interface TypeNodeWithValue { + literalValue: unknown; + typeNode: TSESTree.TypeNode; +} + +function addToMapGroup( + map: Map, + key: Key, + value: Value, +): void { + const existing = map.get(key); + + if (existing) { + existing.push(value); + } else { + map.set(key, [value]); + } +} + +function describeLiteralType(type: ts.Type): string { + if (type.isStringLiteral()) { + return JSON.stringify(type.value); + } + + if (type.isLiteral()) { + return type.value.toString(); + } + + if (util.isTypeAnyType(type)) { + return 'any'; + } + + if (util.isTypeNeverType(type)) { + return 'never'; + } + + if (util.isTypeUnknownType(type)) { + return 'unknown'; + } + + if (util.isTypeTemplateLiteralType(type)) { + return 'template literal type'; + } + + if (util.isTypeBigIntLiteralType(type)) { + return `${type.value.negative ? '-' : ''}${type.value.base10Value}n`; + } + + if (tsutils.isBooleanLiteralType(type, true)) { + return 'true'; + } + + if (tsutils.isBooleanLiteralType(type, false)) { + return 'false'; + } + + return 'literal type'; +} + +function describeLiteralTypeNode(typeNode: TSESTree.TypeNode): string { + switch (typeNode.type) { + case AST_NODE_TYPES.TSAnyKeyword: + return 'any'; + case AST_NODE_TYPES.TSBooleanKeyword: + return 'boolean'; + case AST_NODE_TYPES.TSNeverKeyword: + return 'never'; + case AST_NODE_TYPES.TSNumberKeyword: + return 'number'; + case AST_NODE_TYPES.TSStringKeyword: + return 'string'; + case AST_NODE_TYPES.TSUnknownKeyword: + return 'unknown'; + case AST_NODE_TYPES.TSLiteralType: + switch (typeNode.literal.type) { + case TSESTree.AST_NODE_TYPES.Literal: + switch (typeof typeNode.literal.value) { + case 'bigint': + return `${typeNode.literal.value < 0 ? '-' : ''}${ + typeNode.literal.value + }n`; + case 'string': + return JSON.stringify(typeNode.literal.value); + default: + return `${typeNode.literal.value}`; + } + case TSESTree.AST_NODE_TYPES.TemplateLiteral: + return 'template literal type'; + } + } + + return 'literal type'; +} + +function isNodeInsideReturnType(node: TSESTree.TSUnionType): boolean { + return !!( + node.parent?.type === AST_NODE_TYPES.TSTypeAnnotation && + node.parent.parent && + (util.isFunctionType(node.parent.parent) || + util.isFunction(node.parent.parent)) + ); +} + +/** + * @remarks TypeScript stores boolean types as the union false | true, always. + */ +function unionTypePartsUnlessBoolean(type: ts.Type): ts.Type[] { + return type.isUnion() && + type.types.length === 2 && + tsutils.isBooleanLiteralType(type.types[0], false) && + tsutils.isBooleanLiteralType(type.types[1], true) + ? [type] + : tsutils.unionTypeParts(type); +} + +export default util.createRule({ + name: 'no-redundant-type-constituents', + meta: { + docs: { + description: + 'Disallow members of unions and intersections that do nothing or override type information', + recommended: false, + requiresTypeChecking: true, + }, + messages: { + literalOverridden: `{{literal}} is overridden by {{primitive}} in this union type.`, + primitiveOverridden: `{{primitive}} is overridden by the {{literal}} in this intersection type.`, + overridden: `'{{typeName}}' is overridden by other types in this {{container}} type.`, + overrides: `'{{typeName}}' overrides all other types in this {{container}} type.`, + }, + schema: [], + type: 'suggestion', + }, + defaultOptions: [], + create(context) { + const parserServices = util.getParserServices(context); + const typesCache = new Map(); + + function getTypeNodeTypePartFlags( + typeNode: TSESTree.TypeNode, + ): TypeFlagsWithName[] { + const keywordTypeFlags = keywordNodeTypesToTsTypes.get(typeNode.type); + if (keywordTypeFlags) { + return [ + { + typeFlags: keywordTypeFlags, + typeName: describeLiteralTypeNode(typeNode), + }, + ]; + } + + if ( + typeNode.type === AST_NODE_TYPES.TSLiteralType && + typeNode.literal.type === AST_NODE_TYPES.Literal + ) { + return [ + { + typeFlags: + primitiveTypeFlagTypes[ + typeof typeNode.literal + .value as keyof typeof primitiveTypeFlagTypes + ], + typeName: describeLiteralTypeNode(typeNode), + }, + ]; + } + + if (typeNode.type === AST_NODE_TYPES.TSUnionType) { + return typeNode.types.flatMap(getTypeNodeTypePartFlags); + } + + const tsNode = parserServices.esTreeNodeToTSNodeMap.get(typeNode); + const checker = parserServices.program.getTypeChecker(); + const nodeType = checker.getTypeAtLocation(tsNode); + const typeParts = unionTypePartsUnlessBoolean(nodeType); + + return typeParts.map(typePart => ({ + typeFlags: typePart.flags, + typeName: describeLiteralType(typePart), + })); + } + + function getTypeNodeTypePartFlagsCached( + typeNode: TSESTree.TypeNode, + ): TypeFlagsWithName[] { + const existing = typesCache.get(typeNode); + if (existing) { + return existing; + } + + const created = getTypeNodeTypePartFlags(typeNode); + typesCache.set(typeNode, created); + return created; + } + + return { + 'TSIntersectionType:exit'(node: TSESTree.TSIntersectionType): void { + const seenLiteralTypes = new Map(); + const seenPrimitiveTypes = new Map< + PrimitiveTypeFlag, + TSESTree.TypeNode[] + >(); + + function checkIntersectionBottomAndTopTypes( + { typeFlags, typeName }: TypeFlagsWithName, + typeNode: TSESTree.TypeNode, + ): boolean { + for (const [messageId, checkFlag] of [ + ['overrides', ts.TypeFlags.Any], + ['overrides', ts.TypeFlags.Never], + ['overridden', ts.TypeFlags.Unknown], + ] as const) { + if (typeFlags === checkFlag) { + context.report({ + data: { + container: 'intersection', + typeName, + }, + messageId, + node: typeNode, + }); + return true; + } + } + + return false; + } + + for (const typeNode of node.types) { + const typePartFlags = getTypeNodeTypePartFlagsCached(typeNode); + + for (const typePart of typePartFlags) { + if (checkIntersectionBottomAndTopTypes(typePart, typeNode)) { + continue; + } + + for (const literalTypeFlag of literalTypeFlags) { + if (typePart.typeFlags === literalTypeFlag) { + addToMapGroup( + seenLiteralTypes, + literalToPrimitiveTypeFlags[literalTypeFlag], + typePart.typeName, + ); + break; + } + } + + for (const primitiveTypeFlag of primitiveTypeFlags) { + if (typePart.typeFlags === primitiveTypeFlag) { + addToMapGroup(seenPrimitiveTypes, primitiveTypeFlag, typeNode); + } + } + } + } + + // For each primitive type of all the seen primitive types, + // if there was a literal type seen that overrides it, + // report each of the primitive type's type nodes + for (const [primitiveTypeFlag, typeNodes] of seenPrimitiveTypes) { + const matchedLiteralTypes = seenLiteralTypes.get(primitiveTypeFlag); + if (matchedLiteralTypes) { + for (const typeNode of typeNodes) { + context.report({ + data: { + literal: matchedLiteralTypes.join(' | '), + primitive: primitiveTypeFlagNames[primitiveTypeFlag], + }, + messageId: 'primitiveOverridden', + node: typeNode, + }); + } + } + } + }, + 'TSUnionType:exit'(node: TSESTree.TSUnionType): void { + const seenLiteralTypes = new Map< + PrimitiveTypeFlag, + TypeNodeWithValue[] + >(); + const seenPrimitiveTypes = new Set(); + + function checkUnionBottomAndTopTypes( + { typeFlags, typeName }: TypeFlagsWithName, + typeNode: TSESTree.TypeNode, + ): boolean { + for (const checkFlag of [ + ts.TypeFlags.Any, + ts.TypeFlags.Unknown, + ] as const) { + if (typeFlags === checkFlag) { + context.report({ + data: { + container: 'union', + typeName, + }, + messageId: 'overrides', + node: typeNode, + }); + return true; + } + } + + if ( + typeFlags === ts.TypeFlags.Never && + !isNodeInsideReturnType(node) + ) { + context.report({ + data: { + container: 'union', + typeName: 'never', + }, + messageId: 'overridden', + node: typeNode, + }); + return true; + } + + return false; + } + + for (const typeNode of node.types) { + const typePartFlags = getTypeNodeTypePartFlagsCached(typeNode); + + for (const typePart of typePartFlags) { + if (checkUnionBottomAndTopTypes(typePart, typeNode)) { + continue; + } + + for (const literalTypeFlag of literalTypeFlags) { + if (typePart.typeFlags === literalTypeFlag) { + addToMapGroup( + seenLiteralTypes, + literalToPrimitiveTypeFlags[literalTypeFlag], + { + literalValue: typePart.typeName, + typeNode, + }, + ); + break; + } + } + + for (const primitiveTypeFlag of primitiveTypeFlags) { + if ((typePart.typeFlags & primitiveTypeFlag) !== 0) { + seenPrimitiveTypes.add(primitiveTypeFlag); + } + } + } + } + + interface TypeFlagWithText { + literalValue: unknown; + primitiveTypeFlag: PrimitiveTypeFlag; + } + + const overriddenTypeNodes = new Map< + TSESTree.TypeNode, + TypeFlagWithText[] + >(); + + // For each primitive type of all the seen literal types, + // if there was a primitive type seen that overrides it, + // upsert the literal text and primitive type under the backing type node + for (const [primitiveTypeFlag, typeNodesWithText] of seenLiteralTypes) { + if (seenPrimitiveTypes.has(primitiveTypeFlag)) { + for (const { literalValue, typeNode } of typeNodesWithText) { + addToMapGroup(overriddenTypeNodes, typeNode, { + literalValue, + primitiveTypeFlag, + }); + } + } + } + + // For each type node that had at least one overridden literal, + // group those literals by their primitive type, + // then report each primitive type with all its literals + for (const [typeNode, typeFlagsWithText] of overriddenTypeNodes) { + const grouped = util.arrayGroupByToMap( + typeFlagsWithText, + pair => pair.primitiveTypeFlag, + ); + + for (const [primitiveTypeFlag, pairs] of grouped) { + context.report({ + data: { + literal: pairs.map(pair => pair.literalValue).join(' | '), + primitive: primitiveTypeFlagNames[primitiveTypeFlag], + }, + messageId: 'literalOverridden', + node: typeNode, + }); + } + } + }, + }; + }, +}); diff --git a/packages/eslint-plugin/src/util/misc.ts b/packages/eslint-plugin/src/util/misc.ts index eb62a029b154..8479feb728dd 100644 --- a/packages/eslint-plugin/src/util/misc.ts +++ b/packages/eslint-plugin/src/util/misc.ts @@ -19,6 +19,26 @@ function upperCaseFirst(str: string): string { return str[0].toUpperCase() + str.slice(1); } +function arrayGroupByToMap( + array: T[], + getKey: (item: T) => Key, +): Map { + const groups = new Map(); + + for (const item of array) { + const key = getKey(item); + const existing = groups.get(key); + + if (existing) { + existing.push(item); + } else { + groups.set(key, [item]); + } + } + + return groups; +} + /** Return true if both parameters are equal. */ type Equal = (a: T, b: T) => boolean; @@ -148,6 +168,7 @@ function formatWordList(words: string[]): string { } export { + arrayGroupByToMap, arraysAreEqual, Equal, ExcludeKeys, diff --git a/packages/eslint-plugin/tests/rules/no-redundant-type-constituents.test.ts b/packages/eslint-plugin/tests/rules/no-redundant-type-constituents.test.ts new file mode 100644 index 000000000000..4994459278f7 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/no-redundant-type-constituents.test.ts @@ -0,0 +1,787 @@ +import rule from '../../src/rules/no-redundant-type-constituents'; +import { RuleTester, getFixturesRootDir } from '../RuleTester'; + +const rootDir = getFixturesRootDir(); +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 2021, + tsconfigRootDir: rootDir, + project: './tsconfig.json', + }, + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('no-redundant-type-constituents', rule, { + valid: [ + ` + type T = any; + type U = T; + `, + ` + type T = never; + type U = T; + `, + ` + type T = 1 | 2; + type U = T | 3; + type V = U; + `, + 'type T = () => never;', + 'type T = () => never | string;', + ` + type B = never; + type T = () => B | string; + `, + ` + type B = string; + type T = () => B | never; + `, + 'type T = () => string | never;', + 'type T = { (): string | never };', + ` + function _(): string | never { + return ''; + } + `, + ` + const _ = (): string | never => { + return ''; + }; + `, + ` + type B = string; + type T = { (): B | never }; + `, + 'type T = { new (): string | never };', + ` + type B = never; + type T = { new (): string | B }; + `, + ` + type B = unknown; + type T = B; + `, + 'type T = bigint;', + ` + type B = bigint; + type T = B; + `, + 'type T = 1n | 2n;', + ` + type B = 1n; + type T = B | 2n; + `, + 'type T = boolean;', + ` + type B = boolean; + type T = B; + `, + 'type T = false | true;', + ` + type B = false; + type T = B | true; + `, + ` + type B = true; + type T = B | false; + `, + 'type T = number;', + ` + type B = number; + type T = B; + `, + 'type T = 1 | 2;', + ` + type B = 1; + type T = B | 2; + `, + 'type T = 1 | false;', + ` + type B = 1; + type T = B | false; + `, + 'type T = string;', + ` + type B = string; + type T = B; + `, + "type T = 'a' | 'b';", + ` + type B = 'b'; + type T = 'a' | B; + `, + ` + type B = 'a'; + type T = B | 'b'; + `, + 'type T = bigint | null;', + ` + type B = bigint; + type T = B | null; + `, + 'type T = boolean | null;', + ` + type B = boolean; + type T = B | null; + `, + 'type T = number | null;', + ` + type B = number; + type T = B | null; + `, + 'type T = string | null;', + ` + type B = string; + type T = B | null; + `, + 'type T = bigint & null;', + ` + type B = bigint; + type T = B & null; + `, + 'type T = boolean & null;', + ` + type B = boolean; + type T = B & null; + `, + 'type T = number & null;', + ` + type B = number; + type T = B & null; + `, + 'type T = string & null;', + ` + type B = string; + type T = B & null; + `, + 'type T = `${string}` & null;', + ` + type B = \`\${string}\`; + type T = B & null; + `, + ], + + invalid: [ + { + code: 'type T = number | any;', + errors: [ + { + column: 19, + data: { + container: 'union', + typeName: 'any', + }, + messageId: 'overrides', + }, + ], + }, + { + code: ` + type B = number; + type T = B | any; + `, + errors: [ + { + column: 22, + data: { + container: 'union', + typeName: 'any', + }, + messageId: 'overrides', + }, + ], + }, + { + code: 'type T = any | number;', + errors: [ + { + column: 10, + data: { + container: 'union', + typeName: 'any', + }, + messageId: 'overrides', + }, + ], + }, + { + code: ` + type B = any; + type T = B | number; + `, + errors: [ + { + column: 18, + data: { + container: 'union', + typeName: 'any', + }, + messageId: 'overrides', + }, + ], + }, + { + code: 'type T = number | never;', + errors: [ + { + column: 19, + data: { + container: 'union', + typeName: 'never', + }, + messageId: 'overridden', + }, + ], + }, + { + code: ` + type B = number; + type T = B | never; + `, + errors: [ + { + column: 22, + data: { + container: 'union', + typeName: 'never', + }, + messageId: 'overridden', + }, + ], + }, + { + code: ` + type B = never; + type T = B | number; + `, + errors: [ + { + column: 18, + data: { + container: 'union', + typeName: 'never', + }, + messageId: 'overridden', + }, + ], + }, + { + code: 'type T = never | number;', + errors: [ + { + column: 10, + data: { + container: 'union', + typeName: 'never', + }, + messageId: 'overridden', + }, + ], + }, + { + code: 'type T = number | unknown;', + errors: [ + { + column: 19, + data: { + container: 'union', + typeName: 'unknown', + }, + messageId: 'overrides', + }, + ], + }, + { + code: 'type T = unknown | number;', + errors: [ + { + column: 10, + data: { + container: 'union', + typeName: 'unknown', + }, + messageId: 'overrides', + }, + ], + }, + { + code: 'type T = number | 0;', + errors: [ + { + column: 19, + data: { + literal: '0', + primitive: 'number', + }, + messageId: 'literalOverridden', + }, + ], + }, + { + code: 'type T = number | (0 | 1);', + errors: [ + { + column: 20, + data: { + literal: '0 | 1', + primitive: 'number', + }, + messageId: 'literalOverridden', + }, + ], + }, + { + code: 'type T = (0 | 0) | number;', + errors: [ + { + column: 11, + data: { + literal: '0 | 0', + primitive: 'number', + }, + messageId: 'literalOverridden', + }, + ], + }, + { + code: ` + type B = 0 | 1; + type T = (2 | B) | number; + `, + errors: [ + { + column: 19, + data: { + literal: '2 | 0 | 1', + primitive: 'number', + }, + messageId: 'literalOverridden', + }, + ], + }, + { + code: 'type T = (0 | (1 | 2)) | number;', + errors: [ + { + column: 11, + data: { + literal: '0 | 1 | 2', + primitive: 'number', + }, + messageId: 'literalOverridden', + }, + ], + }, + { + code: 'type T = (0 | 1) | number;', + errors: [ + { + column: 11, + data: { + literal: '0 | 1', + primitive: 'number', + }, + messageId: 'literalOverridden', + }, + ], + }, + { + code: 'type T = (0 | (0 | 1)) | number;', + errors: [ + { + column: 11, + data: { + literal: '0 | 0 | 1', + primitive: 'number', + }, + messageId: 'literalOverridden', + }, + ], + }, + { + code: "type T = (2 | 'other' | 3) | number;", + errors: [ + { + column: 11, + data: { + literal: '2 | 3', + primitive: 'number', + }, + messageId: 'literalOverridden', + }, + ], + }, + { + code: "type T = '' | string;", + errors: [ + { + column: 10, + data: { + literal: '""', + primitive: 'string', + }, + messageId: 'literalOverridden', + }, + ], + }, + { + code: ` + type B = 'b'; + type T = B | string; + `, + errors: [ + { + column: 18, + data: { + literal: '"b"', + primitive: 'string', + }, + messageId: 'literalOverridden', + }, + ], + }, + { + code: 'type T = `a${number}c` | string;', + errors: [ + { + column: 10, + data: { + literal: 'template literal type', + primitive: 'string', + }, + messageId: 'literalOverridden', + }, + ], + }, + { + code: ` + type B = \`a\${number}c\`; + type T = B | string; + `, + errors: [ + { + column: 18, + data: { + literal: 'template literal type', + primitive: 'string', + }, + messageId: 'literalOverridden', + }, + ], + }, + { + code: 'type T = `${number}` | string;', + errors: [ + { + column: 10, + data: { + literal: 'template literal type', + primitive: 'string', + }, + messageId: 'literalOverridden', + }, + ], + }, + { + code: 'type T = 0n | bigint;', + errors: [ + { + column: 10, + data: { + literal: '0n', + primitive: 'bigint', + }, + messageId: 'literalOverridden', + }, + ], + }, + { + code: 'type T = -1n | bigint;', + errors: [ + { + column: 10, + data: { + literal: '-1n', + primitive: 'bigint', + }, + messageId: 'literalOverridden', + }, + ], + }, + { + code: 'type T = (-1n | 1n) | bigint;', + errors: [ + { + column: 11, + data: { + literal: '-1n | 1n', + primitive: 'bigint', + }, + messageId: 'literalOverridden', + }, + ], + }, + { + code: ` + type B = boolean; + type T = B | false; + `, + errors: [ + { + column: 22, + data: { + literal: 'false', + primitive: 'boolean', + }, + messageId: 'literalOverridden', + }, + ], + }, + { + code: 'type T = false | boolean;', + errors: [ + { + column: 10, + data: { + literal: 'false', + primitive: 'boolean', + }, + messageId: 'literalOverridden', + }, + ], + }, + { + code: 'type T = true | boolean;', + errors: [ + { + column: 10, + data: { + literal: 'true', + primitive: 'boolean', + }, + messageId: 'literalOverridden', + }, + ], + }, + { + code: 'type T = false & boolean;', + errors: [ + { + column: 18, + data: { + literal: 'false', + primitive: 'boolean', + }, + messageId: 'primitiveOverridden', + }, + ], + }, + { + code: ` + type B = false; + type T = B & boolean; + `, + errors: [ + { + column: 22, + data: { + literal: 'false', + primitive: 'boolean', + }, + messageId: 'primitiveOverridden', + }, + ], + }, + { + code: ` + type B = true; + type T = B & boolean; + `, + errors: [ + { + column: 22, + data: { + literal: 'true', + primitive: 'boolean', + }, + messageId: 'primitiveOverridden', + }, + ], + }, + { + code: 'type T = true & boolean;', + errors: [ + { + column: 17, + data: { + literal: 'true', + primitive: 'boolean', + }, + messageId: 'primitiveOverridden', + }, + ], + }, + { + code: 'type T = number & any;', + errors: [ + { + column: 19, + data: { + container: 'intersection', + typeName: 'any', + }, + messageId: 'overrides', + }, + ], + }, + { + code: 'type T = any & number;', + errors: [ + { + column: 10, + data: { + container: 'intersection', + typeName: 'any', + }, + messageId: 'overrides', + }, + ], + }, + { + code: 'type T = number & never;', + errors: [ + { + column: 19, + data: { + container: 'intersection', + typeName: 'never', + }, + messageId: 'overrides', + }, + ], + }, + { + code: ` + type B = never; + type T = B & number; + `, + errors: [ + { + column: 18, + data: { + container: 'intersection', + typeName: 'never', + }, + messageId: 'overrides', + }, + ], + }, + { + code: 'type T = never & number;', + errors: [ + { + column: 10, + data: { + container: 'intersection', + typeName: 'never', + }, + messageId: 'overrides', + }, + ], + }, + { + code: 'type T = number & unknown;', + errors: [ + { + column: 19, + data: { + container: 'intersection', + typeName: 'unknown', + }, + messageId: 'overridden', + }, + ], + }, + { + code: 'type T = unknown & number;', + errors: [ + { + column: 10, + data: { + container: 'intersection', + typeName: 'unknown', + }, + messageId: 'overridden', + }, + ], + }, + { + code: 'type T = number & 0;', + errors: [ + { + column: 10, + data: { + literal: '0', + primitive: 'number', + }, + messageId: 'primitiveOverridden', + }, + ], + }, + { + code: "type T = '' & string;", + errors: [ + { + column: 15, + data: { + literal: '""', + primitive: 'string', + }, + messageId: 'primitiveOverridden', + }, + ], + }, + { + code: ` + type B = 0n; + type T = B & bigint; + `, + errors: [ + { + column: 22, + data: { + literal: '0n', + primitive: 'bigint', + }, + messageId: 'primitiveOverridden', + }, + ], + }, + { + code: 'type T = 0n & bigint;', + errors: [ + { + column: 15, + data: { + literal: '0n', + primitive: 'bigint', + }, + messageId: 'primitiveOverridden', + }, + ], + }, + { + code: 'type T = -1n & bigint;', + errors: [ + { + column: 16, + data: { + literal: '-1n', + primitive: 'bigint', + }, + messageId: 'primitiveOverridden', + }, + ], + }, + ], +}); diff --git a/packages/type-utils/src/predicates.ts b/packages/type-utils/src/predicates.ts index c3ece8aa3c7a..541d236bd7c9 100644 --- a/packages/type-utils/src/predicates.ts +++ b/packages/type-utils/src/predicates.ts @@ -47,6 +47,13 @@ export function isTypeArrayTypeOrUnionOfArrayTypes( return true; } +/** + * @returns true if the type is `never` + */ +export function isTypeNeverType(type: ts.Type): boolean { + return isTypeFlagSet(type, ts.TypeFlags.Never); +} + /** * @returns true if the type is `unknown` */ @@ -168,3 +175,15 @@ export function typeIsOrHasBaseType( return false; } + +export function isTypeBigIntLiteralType( + type: ts.Type, +): type is ts.BigIntLiteralType { + return isTypeFlagSet(type, ts.TypeFlags.BigIntLiteral); +} + +export function isTypeTemplateLiteralType( + type: ts.Type, +): type is ts.TemplateLiteralType { + return isTypeFlagSet(type, ts.TypeFlags.TemplateLiteral); +} From 823b945c8f9e83d0246a2a5d07519f01e1a64518 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 23 Feb 2022 18:40:45 -0500 Subject: [PATCH 05/12] feat(eslint-plugin): add `no-useless-empty-export` rule (#4380) --- packages/eslint-plugin/README.md | 1 + .../docs/rules/no-useless-empty-export.md | 45 +++++++ packages/eslint-plugin/src/configs/all.ts | 1 + packages/eslint-plugin/src/rules/index.ts | 2 + .../src/rules/no-useless-empty-export.ts | 79 +++++++++++ .../rules/no-useless-empty-export.test.ts | 125 ++++++++++++++++++ 6 files changed, 253 insertions(+) create mode 100644 packages/eslint-plugin/docs/rules/no-useless-empty-export.md create mode 100644 packages/eslint-plugin/src/rules/no-useless-empty-export.ts create mode 100644 packages/eslint-plugin/tests/rules/no-useless-empty-export.test.ts diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index d10d9f079907..ec14afa8c908 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -151,6 +151,7 @@ Pro Tip: For larger codebases you may want to consider splitting our linting int | [`@typescript-eslint/no-unsafe-call`](./docs/rules/no-unsafe-call.md) | Disallows calling an any type value | :white_check_mark: | | :thought_balloon: | | [`@typescript-eslint/no-unsafe-member-access`](./docs/rules/no-unsafe-member-access.md) | Disallows member access on any typed variables | :white_check_mark: | | :thought_balloon: | | [`@typescript-eslint/no-unsafe-return`](./docs/rules/no-unsafe-return.md) | Disallows returning any from a function | :white_check_mark: | | :thought_balloon: | +| [`@typescript-eslint/no-useless-empty-export`](./docs/rules/no-useless-empty-export.md) | Disallow empty exports that don't change anything in a module file | | :wrench: | | | [`@typescript-eslint/no-var-requires`](./docs/rules/no-var-requires.md) | Disallows the use of require statements except in import statements | :white_check_mark: | | | | [`@typescript-eslint/non-nullable-type-assertion-style`](./docs/rules/non-nullable-type-assertion-style.md) | Prefers a non-null assertion over explicit type cast when possible | | :wrench: | :thought_balloon: | | [`@typescript-eslint/prefer-as-const`](./docs/rules/prefer-as-const.md) | Prefer usage of `as const` over literal type | :white_check_mark: | :wrench: | | diff --git a/packages/eslint-plugin/docs/rules/no-useless-empty-export.md b/packages/eslint-plugin/docs/rules/no-useless-empty-export.md new file mode 100644 index 000000000000..0cb24763f125 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-useless-empty-export.md @@ -0,0 +1,45 @@ +# `no-useless-empty-export` + +Disallow empty exports that don't change anything in a module file. + +## Rule Details + +An empty `export {}` statement is sometimes useful in TypeScript code to turn a file that would otherwise be a script file into a module file. +Per the TypeScript Handbook [Modules](https://www.typescriptlang.org/docs/handbook/modules.html) page: + +> In TypeScript, just as in ECMAScript 2015, any file containing a top-level import or export is considered a module. +> Conversely, a file without any top-level import or export declarations is treated as a script whose contents are available in the global scope (and therefore to modules as well). + +However, an `export {}` statement does nothing if there are any other top-level import or export statements in a file. + +Examples of code for this rule: + + + +### ❌ Incorrect + +```ts +export const value = 'Hello, world!'; +export {}; +``` + +```ts +import 'some-other-module'; +export {}; +``` + +### ✅ Correct + +```ts +export const value = 'Hello, world!'; +``` + +```ts +import 'some-other-module'; +``` + +## Attributes + +- [ ] ✅ Recommended +- [x] 🔧 Fixable +- [ ] 💭 Requires type information diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index c052ce4eb37d..8b63b936b4a8 100644 --- a/packages/eslint-plugin/src/configs/all.ts +++ b/packages/eslint-plugin/src/configs/all.ts @@ -115,6 +115,7 @@ export = { '@typescript-eslint/no-unused-vars': 'error', 'no-use-before-define': 'off', '@typescript-eslint/no-use-before-define': 'error', + '@typescript-eslint/no-useless-empty-export': 'error', 'no-useless-constructor': 'off', '@typescript-eslint/no-useless-constructor': 'error', '@typescript-eslint/no-var-requires': 'error', diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 96251c3c22f0..3249559746ae 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -82,6 +82,7 @@ import noUnusedExpressions from './no-unused-expressions'; import noUnusedVars from './no-unused-vars'; import noUseBeforeDefine from './no-use-before-define'; import noUselessConstructor from './no-useless-constructor'; +import noUselessEmptyExport from './no-useless-empty-export'; import noVarRequires from './no-var-requires'; import nonNullableTypeAssertionStyle from './non-nullable-type-assertion-style'; import objectCurlySpacing from './object-curly-spacing'; @@ -206,6 +207,7 @@ export default { 'no-unused-vars': noUnusedVars, 'no-use-before-define': noUseBeforeDefine, 'no-useless-constructor': noUselessConstructor, + 'no-useless-empty-export': noUselessEmptyExport, 'no-var-requires': noVarRequires, 'non-nullable-type-assertion-style': nonNullableTypeAssertionStyle, 'object-curly-spacing': objectCurlySpacing, diff --git a/packages/eslint-plugin/src/rules/no-useless-empty-export.ts b/packages/eslint-plugin/src/rules/no-useless-empty-export.ts new file mode 100644 index 000000000000..c06c47b8f8e3 --- /dev/null +++ b/packages/eslint-plugin/src/rules/no-useless-empty-export.ts @@ -0,0 +1,79 @@ +import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/utils'; +import * as util from '../util'; + +function isEmptyExport( + node: TSESTree.Node, +): node is TSESTree.ExportNamedDeclaration { + return ( + node.type === AST_NODE_TYPES.ExportNamedDeclaration && + node.specifiers.length === 0 && + !node.declaration + ); +} + +const exportOrImportNodeTypes = new Set([ + AST_NODE_TYPES.ExportAllDeclaration, + AST_NODE_TYPES.ExportDefaultDeclaration, + AST_NODE_TYPES.ExportNamedDeclaration, + AST_NODE_TYPES.ExportSpecifier, + AST_NODE_TYPES.ImportDeclaration, + AST_NODE_TYPES.TSExportAssignment, + AST_NODE_TYPES.TSImportEqualsDeclaration, +]); + +export default util.createRule({ + name: 'no-useless-empty-export', + meta: { + docs: { + description: + "Disallow empty exports that don't change anything in a module file", + recommended: false, + suggestion: true, + }, + fixable: 'code', + hasSuggestions: true, + messages: { + uselessExport: 'Empty export does nothing and can be removed.', + }, + schema: [], + type: 'suggestion', + }, + defaultOptions: [], + create(context) { + function checkNode( + node: TSESTree.Program | TSESTree.TSModuleDeclaration, + ): void { + if (!Array.isArray(node.body)) { + return; + } + + let emptyExport: TSESTree.ExportNamedDeclaration | undefined; + let foundOtherExport = false; + + for (const statement of node.body) { + if (isEmptyExport(statement)) { + emptyExport = statement; + + if (foundOtherExport) { + break; + } + } else if (exportOrImportNodeTypes.has(statement.type)) { + foundOtherExport = true; + } + } + + if (emptyExport && foundOtherExport) { + context.report({ + fix: fixer => fixer.remove(emptyExport!), + messageId: 'uselessExport', + node: emptyExport, + }); + } + } + + return { + Program: checkNode, + TSModuleDeclaration: checkNode, + }; + }, +}); diff --git a/packages/eslint-plugin/tests/rules/no-useless-empty-export.test.ts b/packages/eslint-plugin/tests/rules/no-useless-empty-export.test.ts new file mode 100644 index 000000000000..ea13395ec9ed --- /dev/null +++ b/packages/eslint-plugin/tests/rules/no-useless-empty-export.test.ts @@ -0,0 +1,125 @@ +/* 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 rule from '../../src/rules/no-useless-empty-export'; +import { RuleTester } from '../RuleTester'; + +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 2020, + sourceType: 'module', + }, + parser: '@typescript-eslint/parser', +}); + +const error = { + messageId: 'uselessExport', +} as const; + +ruleTester.run('no-useless-empty-export', rule, { + valid: [ + "declare module '_'", + "import {} from '_';", + "import * as _ from '_';", + 'export = {};', + 'export = 3;', + 'export const _ = {};', + ` + const _ = {}; + export default _; + `, + ` + export * from '_'; + export = {}; + `, + ` + export {}; + `, + ], + invalid: [ + { + code: ` +export const _ = {}; +export {}; + `, + errors: [error], + output: ` +export const _ = {}; + + `, + }, + { + code: ` +export * from '_'; +export {}; + `, + errors: [error], + output: ` +export * from '_'; + + `, + }, + { + code: ` +export {}; +export * from '_'; + `, + errors: [error], + output: ` + +export * from '_'; + `, + }, + { + code: ` +const _ = {}; +export default _; +export {}; + `, + errors: [error], + output: ` +const _ = {}; +export default _; + + `, + }, + { + code: ` +export {}; +const _ = {}; +export default _; + `, + errors: [error], + output: ` + +const _ = {}; +export default _; + `, + }, + { + code: ` +const _ = {}; +export { _ }; +export {}; + `, + errors: [error], + output: ` +const _ = {}; +export { _ }; + + `, + }, + { + code: ` +import _ = require('_'); +export {}; + `, + errors: [error], + output: ` +import _ = require('_'); + + `, + }, + ], +}); From 66501d6dd7e97c22c671efaa6d1ba8237907e417 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20De=20Boey?= Date: Thu, 24 Feb 2022 06:35:18 +0100 Subject: [PATCH 06/12] feat(utils): extract `isNotTokenOfTypeWithConditions` out of `ast-utils`' `predicates` (#4502) --- packages/utils/src/ast-utils/helpers.ts | 21 +++++++++++++++++++++ packages/utils/src/ast-utils/predicates.ts | 22 +++++++++------------- 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/packages/utils/src/ast-utils/helpers.ts b/packages/utils/src/ast-utils/helpers.ts index f2a4ac2e2442..baf4daf68101 100644 --- a/packages/utils/src/ast-utils/helpers.ts +++ b/packages/utils/src/ast-utils/helpers.ts @@ -56,3 +56,24 @@ export const isTokenOfTypeWithConditions = < token?.type === tokenType && entries.every(([key, value]) => token[key] === value); }; + +export const isNotTokenOfTypeWithConditions = + < + TokenType extends AST_TOKEN_TYPES, + Conditions extends Partial, + >( + tokenType: TokenType, + conditions: Conditions, + ): (( + token: TSESTree.Token | null | undefined, + ) => token is Exclude< + TSESTree.Token, + TSESTree.Token & { type: TokenType } & Conditions + >) => + ( + token, + ): token is Exclude< + TSESTree.Token, + TSESTree.Token & { type: TokenType } & Conditions + > => + !isTokenOfTypeWithConditions(tokenType, conditions)(token); diff --git a/packages/utils/src/ast-utils/predicates.ts b/packages/utils/src/ast-utils/predicates.ts index 0038f092bbe0..10e50522d672 100644 --- a/packages/utils/src/ast-utils/predicates.ts +++ b/packages/utils/src/ast-utils/predicates.ts @@ -4,6 +4,7 @@ import { isNodeOfType, isNodeOfTypes, isNodeOfTypeWithConditions, + isNotTokenOfTypeWithConditions, isTokenOfTypeWithConditions, } from './helpers'; @@ -12,25 +13,20 @@ const isOptionalChainPunctuator = isTokenOfTypeWithConditions( { value: '?.' }, ); -function isNotOptionalChainPunctuator( - token: TSESTree.Token, -): token is Exclude< - TSESTree.Token, - TSESTree.PunctuatorToken & { value: '?.' } -> { - return !isOptionalChainPunctuator(token); -} +const isNotOptionalChainPunctuator = isNotTokenOfTypeWithConditions( + AST_TOKEN_TYPES.Punctuator, + { value: '?.' }, +); const isNonNullAssertionPunctuator = isTokenOfTypeWithConditions( AST_TOKEN_TYPES.Punctuator, { value: '!' }, ); -function isNotNonNullAssertionPunctuator( - token: TSESTree.Token, -): token is Exclude { - return !isNonNullAssertionPunctuator(token); -} +const isNotNonNullAssertionPunctuator = isNotTokenOfTypeWithConditions( + AST_TOKEN_TYPES.Punctuator, + { value: '!' }, +); /** * Returns true if and only if the node represents: foo?.() or foo.bar?.() From 208b6d02252dff2bf272329d3e4a4a82e56c52c0 Mon Sep 17 00:00:00 2001 From: Domas Trijonis Date: Thu, 24 Feb 2022 16:41:53 +0100 Subject: [PATCH 07/12] feat(eslint-plugin): add extension rule `space-before-blocks` (#1606) (#4184) Co-authored-by: Josh Goldberg --- packages/eslint-plugin/README.md | 1 + .../docs/rules/space-before-blocks.md | 60 ++++ packages/eslint-plugin/src/configs/all.ts | 2 + packages/eslint-plugin/src/rules/index.ts | 2 + .../src/rules/space-before-blocks.ts | 90 ++++++ .../src/util/getESLintCoreRule.ts | 1 + .../tests/rules/space-before-blocks.test.ts | 258 ++++++++++++++++++ .../eslint-plugin/typings/eslint-rules.d.ts | 23 ++ packages/utils/src/ast-utils/misc.ts | 4 +- 9 files changed, 439 insertions(+), 2 deletions(-) create mode 100644 packages/eslint-plugin/docs/rules/space-before-blocks.md create mode 100644 packages/eslint-plugin/src/rules/space-before-blocks.ts create mode 100644 packages/eslint-plugin/tests/rules/space-before-blocks.test.ts diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index ec14afa8c908..34209e621cbd 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -231,6 +231,7 @@ In these cases, we create what we call an extension rule; a rule within our plug | [`@typescript-eslint/require-await`](./docs/rules/require-await.md) | Disallow async functions which have no `await` expression | :white_check_mark: | | :thought_balloon: | | [`@typescript-eslint/return-await`](./docs/rules/return-await.md) | Enforces consistent returning of awaited values | | :wrench: | :thought_balloon: | | [`@typescript-eslint/semi`](./docs/rules/semi.md) | Require or disallow semicolons instead of ASI | | :wrench: | | +| [`@typescript-eslint/space-before-blocks`](./docs/rules/space-before-blocks.md) | Enforces consistent spacing before blocks | | :wrench: | | | [`@typescript-eslint/space-before-function-paren`](./docs/rules/space-before-function-paren.md) | Enforces consistent spacing before function parenthesis | | :wrench: | | | [`@typescript-eslint/space-infix-ops`](./docs/rules/space-infix-ops.md) | This rule is aimed at ensuring there are spaces around infix operators. | | :wrench: | | diff --git a/packages/eslint-plugin/docs/rules/space-before-blocks.md b/packages/eslint-plugin/docs/rules/space-before-blocks.md new file mode 100644 index 000000000000..52f8a121df69 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/space-before-blocks.md @@ -0,0 +1,60 @@ +# `space-before-blocks` + +Enforces consistent spacing before blocks. + +## Rule Details + +This rule extends the base [`eslint/space-before-blocks`](https://eslint.org/docs/rules/space-before-blocks) rule. +It adds support for interfaces and enums: + +### ❌ Incorrect + +```ts +enum Breakpoint{ + Large, Medium; +} + +interface State{ + currentBreakpoint: Breakpoint; +} +``` + +### ✅ Correct + +```ts +enum Breakpoint { + Large, Medium; +} + +interface State { + currentBreakpoint: Breakpoint; +} +``` + +In case a more specific options object is passed these blocks will follow `classes` configuration option. + +## How to Use + +```jsonc +{ + // note you must disable the base rule as it can report incorrect errors + "space-before-blocks": "off", + "@typescript-eslint/space-before-blocks": ["error"] +} +``` + +## Options + +See [`eslint/space-before-blocks` options](https://eslint.org/docs/rules/space-before-blocks#options). + + + +Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/space-before-blocks.md) + + + +## Attributes + +- [ ] ✅ Recommended +- [x] 🔧 Fixable +- [ ] 💭 Requires type information diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index 8b63b936b4a8..03188b724648 100644 --- a/packages/eslint-plugin/src/configs/all.ts +++ b/packages/eslint-plugin/src/configs/all.ts @@ -157,6 +157,8 @@ export = { '@typescript-eslint/space-before-function-paren': 'error', 'space-infix-ops': 'off', '@typescript-eslint/space-infix-ops': 'error', + 'space-before-blocks': 'off', + '@typescript-eslint/space-before-blocks': 'error', '@typescript-eslint/strict-boolean-expressions': 'error', '@typescript-eslint/switch-exhaustiveness-check': 'error', '@typescript-eslint/triple-slash-reference': 'error', diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 3249559746ae..c2f90319e386 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -112,6 +112,7 @@ import restrictTemplateExpressions from './restrict-template-expressions'; import returnAwait from './return-await'; import semi from './semi'; import sortTypeUnionIntersectionMembers from './sort-type-union-intersection-members'; +import spaceBeforeBlocks from './space-before-blocks'; import spaceBeforeFunctionParen from './space-before-function-paren'; import spaceInfixOps from './space-infix-ops'; import strictBooleanExpressions from './strict-boolean-expressions'; @@ -237,6 +238,7 @@ export default { 'return-await': returnAwait, semi: semi, 'sort-type-union-intersection-members': sortTypeUnionIntersectionMembers, + 'space-before-blocks': spaceBeforeBlocks, 'space-before-function-paren': spaceBeforeFunctionParen, 'space-infix-ops': spaceInfixOps, 'strict-boolean-expressions': strictBooleanExpressions, diff --git a/packages/eslint-plugin/src/rules/space-before-blocks.ts b/packages/eslint-plugin/src/rules/space-before-blocks.ts new file mode 100644 index 000000000000..5bf827df96c3 --- /dev/null +++ b/packages/eslint-plugin/src/rules/space-before-blocks.ts @@ -0,0 +1,90 @@ +import { TSESTree } from '@typescript-eslint/utils'; +import { getESLintCoreRule } from '../util/getESLintCoreRule'; +import * as util from '../util'; + +const baseRule = getESLintCoreRule('space-before-blocks'); + +export type Options = util.InferOptionsTypeFromRule; +export type MessageIds = util.InferMessageIdsTypeFromRule; + +export default util.createRule({ + name: 'space-before-blocks', + meta: { + type: 'layout', + docs: { + description: 'Enforces consistent spacing before blocks', + recommended: false, + extendsBaseRule: true, + }, + fixable: baseRule.meta.fixable, + hasSuggestions: baseRule.meta.hasSuggestions, + schema: baseRule.meta.schema, + messages: { + // @ts-expect-error -- we report on this messageId so we need to ensure it's there in case ESLint changes in future + unexpectedSpace: 'Unexpected space before opening brace.', + // @ts-expect-error -- we report on this messageId so we need to ensure it's there in case ESLint changes in future + missingSpace: 'Missing space before opening brace.', + ...baseRule.meta.messages, + }, + }, + defaultOptions: ['always'], + create(context) { + const rules = baseRule.create(context); + const config = context.options[0]; + const sourceCode = context.getSourceCode(); + + let requireSpace = true; + + if (typeof config === 'object') { + requireSpace = config.classes === 'always'; + } else if (config === 'never') { + requireSpace = false; + } + + function checkPrecedingSpace( + node: TSESTree.Token | TSESTree.TSInterfaceBody, + ): void { + const precedingToken = sourceCode.getTokenBefore(node); + if (precedingToken && util.isTokenOnSameLine(precedingToken, node)) { + const hasSpace = sourceCode.isSpaceBetweenTokens( + precedingToken, + node as TSESTree.Token, + ); + + if (requireSpace && !hasSpace) { + context.report({ + node, + messageId: 'missingSpace', + fix(fixer) { + return fixer.insertTextBefore(node, ' '); + }, + }); + } else if (!requireSpace && hasSpace) { + context.report({ + node, + messageId: 'unexpectedSpace', + fix(fixer) { + return fixer.removeRange([ + precedingToken.range[1], + node.range[0], + ]); + }, + }); + } + } + } + + function checkSpaceAfterEnum(node: TSESTree.TSEnumDeclaration): void { + const punctuator = sourceCode.getTokenAfter(node.id); + if (punctuator) { + checkPrecedingSpace(punctuator); + } + } + + return { + ...rules, + TSEnumDeclaration: checkSpaceAfterEnum, + TSInterfaceBody: checkPrecedingSpace, + }; + }, +}); diff --git a/packages/eslint-plugin/src/util/getESLintCoreRule.ts b/packages/eslint-plugin/src/util/getESLintCoreRule.ts index f61bdb0c3094..5ba9ae369659 100644 --- a/packages/eslint-plugin/src/util/getESLintCoreRule.ts +++ b/packages/eslint-plugin/src/util/getESLintCoreRule.ts @@ -33,6 +33,7 @@ interface RuleMap { 'prefer-const': typeof import('eslint/lib/rules/prefer-const'); quotes: typeof import('eslint/lib/rules/quotes'); semi: typeof import('eslint/lib/rules/semi'); + 'space-before-blocks': typeof import('eslint/lib/rules/space-before-blocks'); 'space-infix-ops': typeof import('eslint/lib/rules/space-infix-ops'); strict: typeof import('eslint/lib/rules/strict'); } diff --git a/packages/eslint-plugin/tests/rules/space-before-blocks.test.ts b/packages/eslint-plugin/tests/rules/space-before-blocks.test.ts new file mode 100644 index 000000000000..7e1338c6fc5d --- /dev/null +++ b/packages/eslint-plugin/tests/rules/space-before-blocks.test.ts @@ -0,0 +1,258 @@ +/* eslint-disable eslint-comments/no-use */ +// this rule tests 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 rule from '../../src/rules/space-before-blocks'; +import { RuleTester } from '../RuleTester'; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('space-before-blocks', rule, { + valid: [ + { + code: ` + enum Test{ + KEY1 = 2, + } + `, + options: ['never'], + }, + { + code: ` + interface Test{ + prop1: number; + } + `, + options: ['never'], + }, + { + code: ` + enum Test { + KEY1 = 2, + } + `, + options: ['always'], + }, + { + code: ` + interface Test { + prop1: number; + } + `, + options: ['always'], + }, + { + code: ` + enum Test{ + KEY1 = 2, + } + `, + options: [{ classes: 'never' }], + }, + { + code: ` + interface Test{ + prop1: number; + } + `, + options: [{ classes: 'never' }], + }, + { + code: ` + enum Test { + KEY1 = 2, + } + `, + options: [{ classes: 'always' }], + }, + { + code: ` + interface Test { + prop1: number; + } + `, + options: [{ classes: 'always' }], + }, + { + code: ` + interface Test{ + prop1: number; + } + `, + options: [{ classes: 'off' }], + }, + ], + invalid: [ + { + code: ` + enum Test{ + A = 2, + B = 1, + } + `, + output: ` + enum Test { + A = 2, + B = 1, + } + `, + errors: [ + { + messageId: 'missingSpace', + column: 18, + line: 2, + }, + ], + options: ['always'], + }, + { + code: ` + interface Test{ + prop1: number; + } + `, + output: ` + interface Test { + prop1: number; + } + `, + errors: [ + { + messageId: 'missingSpace', + column: 23, + line: 2, + }, + ], + options: ['always'], + }, + { + code: ` + enum Test{ + A = 2, + B = 1, + } + `, + output: ` + enum Test { + A = 2, + B = 1, + } + `, + errors: [ + { + messageId: 'missingSpace', + column: 18, + line: 2, + }, + ], + options: [{ classes: 'always' }], + }, + { + code: ` + interface Test{ + prop1: number; + } + `, + output: ` + interface Test { + prop1: number; + } + `, + errors: [ + { + messageId: 'missingSpace', + column: 23, + line: 2, + }, + ], + options: [{ classes: 'always' }], + }, + { + code: ` + enum Test { + A = 2, + B = 1, + } + `, + output: ` + enum Test{ + A = 2, + B = 1, + } + `, + errors: [ + { + messageId: 'unexpectedSpace', + column: 19, + line: 2, + }, + ], + options: ['never'], + }, + { + code: ` + interface Test { + prop1: number; + } + `, + output: ` + interface Test{ + prop1: number; + } + `, + errors: [ + { + messageId: 'unexpectedSpace', + column: 24, + line: 2, + }, + ], + options: ['never'], + }, + { + code: ` + enum Test { + A = 2, + B = 1, + } + `, + output: ` + enum Test{ + A = 2, + B = 1, + } + `, + errors: [ + { + messageId: 'unexpectedSpace', + column: 19, + line: 2, + }, + ], + options: [{ classes: 'never' }], + }, + { + code: ` + interface Test { + prop1: number; + } + `, + output: ` + interface Test{ + prop1: number; + } + `, + errors: [ + { + messageId: 'unexpectedSpace', + column: 24, + line: 2, + }, + ], + options: [{ classes: 'never' }], + }, + ], +}); diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index d53ef43a747f..fa7d5934b263 100644 --- a/packages/eslint-plugin/typings/eslint-rules.d.ts +++ b/packages/eslint-plugin/typings/eslint-rules.d.ts @@ -842,6 +842,29 @@ declare module 'eslint/lib/rules/space-infix-ops' { export = rule; } +declare module 'eslint/lib/rules/space-before-blocks' { + import { TSESLint, TSESTree } from '@typescript-eslint/utils'; + + const rule: TSESLint.RuleModule< + 'missingSpace' | 'unexpectedSpace', + [ + | 'always' + | 'never' + | { + classes?: 'always' | 'never' | 'off'; + functions?: 'always' | 'never' | 'off'; + keywords?: 'always' | 'never' | 'off'; + }, + ], + { + BlockStatement(node: TSESTree.BlockStatement): void; + ClassBody(node: TSESTree.ClassBody): void; + SwitchStatement(node: TSESTree.SwitchStatement): void; + } + >; + export = rule; +} + declare module 'eslint/lib/rules/prefer-const' { import { TSESLint, TSESTree } from '@typescript-eslint/utils'; diff --git a/packages/utils/src/ast-utils/misc.ts b/packages/utils/src/ast-utils/misc.ts index 923424d9f928..72a7898c31c0 100644 --- a/packages/utils/src/ast-utils/misc.ts +++ b/packages/utils/src/ast-utils/misc.ts @@ -6,8 +6,8 @@ const LINEBREAK_MATCHER = /\r\n|[\r\n\u2028\u2029]/; * Determines whether two adjacent tokens are on the same line */ function isTokenOnSameLine( - left: TSESTree.Token, - right: TSESTree.Token, + left: TSESTree.Node | TSESTree.Token, + right: TSESTree.Node | TSESTree.Token, ): boolean { return left.loc.end.line === right.loc.start.line; } From 6afcaea0160a1ccd1c6483ca677c544ca1b8cb4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Balogh?= <34451173+grabofus@users.noreply.github.com> Date: Thu, 24 Feb 2022 16:15:04 +0000 Subject: [PATCH 08/12] feat(eslint-plugin): added member group support to member-ordering rule (#4538) * feat(eslint-plugin): added member group support to member-ordering rule * test(eslint-plugin): added more test cases for member-ordering * test(eslint-plugin): added more test cases for member-ordering Co-authored-by: Josh Goldberg --- .../docs/rules/member-ordering.md | 23 +++ .../src/rules/member-ordering.ts | 49 ++++-- .../tests/rules/member-ordering.test.ts | 156 ++++++++++++++++++ 3 files changed, 215 insertions(+), 13 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/member-ordering.md b/packages/eslint-plugin/docs/rules/member-ordering.md index b8e1e9f711fe..ab54967aa3ab 100644 --- a/packages/eslint-plugin/docs/rules/member-ordering.md +++ b/packages/eslint-plugin/docs/rules/member-ordering.md @@ -292,6 +292,29 @@ The third grouping option is to ignore both scope and accessibility. ] ``` +### Grouping different member types at the same rank + +It is also possible to group different member types at the same rank. + +```jsonc +[ + // Index signature + "signature", + + // Fields + "field", + + // Constructors + "constructor", + + // Getters and Setters at the same rank + ["get", "set"], + + // Methods + "method" +] +``` + ### Default configuration The default configuration looks as follows: diff --git a/packages/eslint-plugin/src/rules/member-ordering.ts b/packages/eslint-plugin/src/rules/member-ordering.ts index ce2edabe058f..92c794caf07f 100644 --- a/packages/eslint-plugin/src/rules/member-ordering.ts +++ b/packages/eslint-plugin/src/rules/member-ordering.ts @@ -13,12 +13,14 @@ type Order = | 'alphabetically-case-insensitive' | 'as-written'; +type MemberType = string | string[]; + interface SortedOrderConfig { - memberTypes?: string[] | 'never'; + memberTypes?: MemberType[] | 'never'; order: Order; } -type OrderConfig = string[] | SortedOrderConfig | 'never'; +type OrderConfig = MemberType[] | SortedOrderConfig | 'never'; type Member = TSESTree.ClassElement | TSESTree.TypeElement; export type Options = [ @@ -36,14 +38,24 @@ const neverConfig: JSONSchema.JSONSchema4 = { enum: ['never'], }; -const arrayConfig = (memberTypes: string[]): JSONSchema.JSONSchema4 => ({ +const arrayConfig = (memberTypes: MemberType[]): JSONSchema.JSONSchema4 => ({ type: 'array', items: { - enum: memberTypes, + oneOf: [ + { + enum: memberTypes, + }, + { + type: 'array', + items: { + enum: memberTypes, + }, + }, + ], }, }); -const objectConfig = (memberTypes: string[]): JSONSchema.JSONSchema4 => ({ +const objectConfig = (memberTypes: MemberType[]): JSONSchema.JSONSchema4 => ({ type: 'object', properties: { memberTypes: { @@ -339,12 +351,20 @@ function getMemberName( * * @return Index of the matching member type in the order configuration. */ -function getRankOrder(memberGroups: string[], orderConfig: string[]): number { +function getRankOrder( + memberGroups: string[], + orderConfig: MemberType[], +): number { let rank = -1; const stack = memberGroups.slice(); // Get a copy of the member groups while (stack.length > 0 && rank === -1) { - rank = orderConfig.indexOf(stack.shift()!); + const memberGroup = stack.shift()!; + rank = orderConfig.findIndex(memberType => + Array.isArray(memberType) + ? memberType.includes(memberGroup) + : memberType === memberGroup, + ); } return rank; @@ -358,7 +378,7 @@ function getRankOrder(memberGroups: string[], orderConfig: string[]): number { */ function getRank( node: Member, - orderConfig: string[], + orderConfig: MemberType[], supportsModifiers: boolean, ): number { const type = getNodeType(node); @@ -414,7 +434,7 @@ function getRank( } /** - * Gets the lowest possible rank higher than target. + * Gets the lowest possible rank(s) higher than target. * e.g. given the following order: * ... * public-static-method @@ -427,15 +447,16 @@ function getRank( * and considering that a public-instance-method has already been declared, so ranks contains * public-instance-method, then the lowest possible rank for public-static-method is * public-instance-method. + * If a lowest possible rank is a member group, a comma separated list of ranks is returned. * @param ranks the existing ranks in the object. * @param target the target rank. * @param order the current order to be validated. - * @returns the name of the lowest possible rank without dashes (-). + * @returns the name(s) of the lowest possible rank without dashes (-). */ function getLowestRank( ranks: number[], target: number, - order: string[], + order: MemberType[], ): string { let lowest = ranks[ranks.length - 1]; @@ -445,7 +466,9 @@ function getLowestRank( } }); - return order[lowest].replace(/-/g, ' '); + const lowestRank = order[lowest]; + const lowestRanks = Array.isArray(lowestRank) ? lowestRank : [lowestRank]; + return lowestRanks.map(rank => rank.replace(/-/g, ' ')).join(', '); } export default util.createRule({ @@ -523,7 +546,7 @@ export default util.createRule({ */ function checkGroupSort( members: Member[], - groupOrder: string[], + groupOrder: MemberType[], supportsModifiers: boolean, ): Array | null { const previousRanks: number[] = []; diff --git a/packages/eslint-plugin/tests/rules/member-ordering.test.ts b/packages/eslint-plugin/tests/rules/member-ordering.test.ts index c9c1090c7348..c21272ca510a 100644 --- a/packages/eslint-plugin/tests/rules/member-ordering.test.ts +++ b/packages/eslint-plugin/tests/rules/member-ordering.test.ts @@ -1417,6 +1417,74 @@ class Foo { }, ], }, + { + code: ` +class Foo { + A: string; + constructor() {} + get B() {} + set B() {} + get C() {} + set C() {} + D(): void; +} `, + options: [ + { + default: ['field', 'constructor', ['get', 'set'], 'method'], + }, + ], + }, + { + code: ` +class Foo { + A: string; + constructor() {} + B(): void; +} `, + options: [ + { + default: ['field', 'constructor', [], 'method'], + }, + ], + }, + { + code: ` +class Foo { + A: string; + constructor() {} + @Dec() private B: string; + private C(): void; + set D() {} + E(): void; +} `, + options: [ + { + default: [ + 'public-field', + 'constructor', + ['private-decorated-field', 'public-set', 'private-method'], + 'public-method', + ], + }, + ], + }, + { + code: ` +class Foo { + A: string; + constructor() {} + get B() {} + get C() {} + set B() {} + set C() {} + D(): void; +} `, + options: [ + { + default: ['field', 'constructor', ['get'], ['set'], 'method'], + }, + ], + }, ], invalid: [ { @@ -3823,6 +3891,94 @@ class Foo { }, ], }, + { + code: ` +class Foo { + A: string; + get B() {} + constructor() {} + set B() {} + get C() {} + set C() {} + D(): void; +} `, + options: [ + { + default: ['field', 'constructor', ['get', 'set'], 'method'], + }, + ], + errors: [ + { + messageId: 'incorrectGroupOrder', + data: { + name: 'constructor', + rank: 'get, set', + }, + line: 5, + column: 5, + }, + ], + }, + { + code: ` +class Foo { + A: string; + private C(): void; + constructor() {} + @Dec() private B: string; + set D() {} + E(): void; +} `, + options: [ + { + default: [ + 'public-field', + 'constructor', + ['private-decorated-field', 'public-set', 'private-method'], + 'public-method', + ], + }, + ], + errors: [ + { + messageId: 'incorrectGroupOrder', + data: { + name: 'constructor', + rank: 'private decorated field, public set, private method', + }, + line: 5, + column: 5, + }, + ], + }, + { + code: ` +class Foo { + A: string; + constructor() {} + get B() {} + set B() {} + get C() {} + set C() {} + D(): void; +} `, + options: [ + { + default: ['field', 'constructor', 'get', ['set'], 'method'], + }, + ], + errors: [ + { + messageId: 'incorrectGroupOrder', + data: { + name: 'C', + rank: 'set', + }, + line: 7, + column: 5, + }, + ], + }, ], }; From 851bb90216e20b7679efc891dc445e6152d4837f Mon Sep 17 00:00:00 2001 From: islandryu <65934663+islandryu@users.noreply.github.com> Date: Fri, 25 Feb 2022 01:50:41 +0900 Subject: [PATCH 09/12] fix(eslint-plugin): [sort-type-union-intersection-members] Wrap the constructorType in parentheses (#4590) Co-authored-by: Josh Goldberg --- .../src/rules/sort-type-union-intersection-members.ts | 5 ++++- .../rules/sort-type-union-intersection-members.test.ts | 9 +++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/sort-type-union-intersection-members.ts b/packages/eslint-plugin/src/rules/sort-type-union-intersection-members.ts index fcdaa00152de..fe7f698949c7 100644 --- a/packages/eslint-plugin/src/rules/sort-type-union-intersection-members.ts +++ b/packages/eslint-plugin/src/rules/sort-type-union-intersection-members.ts @@ -85,7 +85,10 @@ function getGroup(node: TSESTree.TypeNode): Group { } function requiresParentheses(node: TSESTree.TypeNode): boolean { - return node.type === AST_NODE_TYPES.TSFunctionType; + return ( + node.type === AST_NODE_TYPES.TSFunctionType || + node.type === AST_NODE_TYPES.TSConstructorType + ); } export type Options = [ diff --git a/packages/eslint-plugin/tests/rules/sort-type-union-intersection-members.test.ts b/packages/eslint-plugin/tests/rules/sort-type-union-intersection-members.test.ts index a5eeb2f2fa48..1d29a45ce610 100644 --- a/packages/eslint-plugin/tests/rules/sort-type-union-intersection-members.test.ts +++ b/packages/eslint-plugin/tests/rules/sort-type-union-intersection-members.test.ts @@ -272,6 +272,15 @@ type T = }, ], }, + { + code: `type Expected = (new (x: number) => boolean) ${operator} string;`, + output: `type Expected = string ${operator} (new (x: number) => boolean);`, + errors: [ + { + messageId: 'notSortedNamed', + }, + ], + }, ]; }; From 052cf51fe663283afe89dc7bf97c947e750df095 Mon Sep 17 00:00:00 2001 From: uhyo Date: Fri, 25 Feb 2022 03:08:53 +0900 Subject: [PATCH 10/12] feat(eslint-plugin): [no-misused-promises] check more places for checksVoidReturn (#4541) * refactor(eslint-plugin): create isVoidReturningFunctionType * feat(eslint-plugin): add checkAssignments and checkVariableDeclaration * test(eslint-plugin): add valid test cases * feat(eslint-plugin): add object property checks * feat(eslint-plugin): add checkReturnStatements * feat(eslint-plugin): add checkJSXAttributes * fix(website): resolve newly introduced lint errors * test(eslint-plugin): worship code coverage * Apply suggestions from code review Co-authored-by: Josh Goldberg * fix(eslint-plugin): rename checker functions to singular * fix(eslint-plugin): align error messages * refactor(eslint-plugin): change MessageId * refactor(eslint-plugin): fix if statements style * refactor(eslint-plugin): no need of getTypeAtLocation * refactor(eslint-plugin): make coverage 100% * refactor(eslint-plugin): update comment Co-authored-by: Josh Goldberg --- .../src/rules/no-misused-promises.ts | 228 +++++++++++++-- .../tests/rules/no-misused-promises.test.ts | 269 +++++++++++++++++- .../src/components/OptionsSelector.tsx | 16 +- 3 files changed, 476 insertions(+), 37 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts index 6e42e5474361..7f08402334cc 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -11,7 +11,15 @@ type Options = [ }, ]; -export default util.createRule({ +type MessageId = + | 'conditional' + | 'voidReturnArgument' + | 'voidReturnVariable' + | 'voidReturnProperty' + | 'voidReturnReturnValue' + | 'voidReturnAttribute'; + +export default util.createRule({ name: 'no-misused-promises', meta: { docs: { @@ -20,8 +28,16 @@ export default util.createRule({ requiresTypeChecking: true, }, messages: { - voidReturn: + voidReturnArgument: 'Promise returned in function argument where a void return was expected.', + voidReturnVariable: + 'Promise-returning function provided to variable where a void return was expected.', + voidReturnProperty: + 'Promise-returning function provided to property where a void return was expected.', + voidReturnReturnValue: + 'Promise-returning function provided to return value where a void return was expected.', + voidReturnAttribute: + 'Promise-returning function provided to attribute where a void return was expected.', conditional: 'Expected non-Promise value in a boolean conditional.', }, schema: [ @@ -67,6 +83,11 @@ export default util.createRule({ const voidReturnChecks: TSESLint.RuleListener = { CallExpression: checkArguments, NewExpression: checkArguments, + AssignmentExpression: checkAssignment, + VariableDeclarator: checkVariableDeclaration, + Property: checkProperty, + ReturnStatement: checkReturnStatement, + JSXAttribute: checkJSXAttribute, }; function checkTestConditional(node: { @@ -130,13 +151,168 @@ export default util.createRule({ const tsNode = parserServices.esTreeNodeToTSNodeMap.get(argument); if (returnsThenable(checker, tsNode as ts.Expression)) { context.report({ - messageId: 'voidReturn', + messageId: 'voidReturnArgument', node: argument, }); } } } + function checkAssignment(node: TSESTree.AssignmentExpression): void { + const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); + const varType = checker.getTypeAtLocation(tsNode.left); + if (!isVoidReturningFunctionType(checker, tsNode.left, varType)) { + return; + } + + if (returnsThenable(checker, tsNode.right)) { + context.report({ + messageId: 'voidReturnVariable', + node: node.right, + }); + } + } + + function checkVariableDeclaration(node: TSESTree.VariableDeclarator): void { + const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); + if (tsNode.initializer === undefined || node.init === null) { + return; + } + const varType = checker.getTypeAtLocation(tsNode.name); + if (!isVoidReturningFunctionType(checker, tsNode.initializer, varType)) { + return; + } + + if (returnsThenable(checker, tsNode.initializer)) { + context.report({ + messageId: 'voidReturnVariable', + node: node.init, + }); + } + } + + function checkProperty(node: TSESTree.Property): void { + const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); + if (ts.isPropertyAssignment(tsNode)) { + const contextualType = checker.getContextualType(tsNode.initializer); + if ( + contextualType !== undefined && + isVoidReturningFunctionType( + checker, + tsNode.initializer, + contextualType, + ) && + returnsThenable(checker, tsNode.initializer) + ) { + context.report({ + messageId: 'voidReturnProperty', + node: node.value, + }); + } + } else if (ts.isShorthandPropertyAssignment(tsNode)) { + const contextualType = checker.getContextualType(tsNode.name); + if ( + contextualType !== undefined && + isVoidReturningFunctionType(checker, tsNode.name, contextualType) && + returnsThenable(checker, tsNode.name) + ) { + context.report({ + messageId: 'voidReturnProperty', + node: node.value, + }); + } + } else if (ts.isMethodDeclaration(tsNode)) { + if (ts.isComputedPropertyName(tsNode.name)) { + return; + } + const obj = tsNode.parent; + + // Below condition isn't satisfied unless something goes wrong, + // but is needed for type checking. + // 'node' does not include class method declaration so 'obj' is + // always an object literal expression, but after converting 'node' + // to TypeScript AST, its type includes MethodDeclaration which + // does include the case of class method declaration. + if (!ts.isObjectLiteralExpression(obj)) { + return; + } + + const objType = checker.getContextualType(obj); + if (objType === undefined) { + return; + } + const propertySymbol = checker.getPropertyOfType( + objType, + tsNode.name.text, + ); + if (propertySymbol === undefined) { + return; + } + + const contextualType = checker.getTypeOfSymbolAtLocation( + propertySymbol, + tsNode.name, + ); + + if ( + isVoidReturningFunctionType(checker, tsNode.name, contextualType) && + returnsThenable(checker, tsNode) + ) { + context.report({ + messageId: 'voidReturnProperty', + node: node.value, + }); + } + return; + } + } + + function checkReturnStatement(node: TSESTree.ReturnStatement): void { + const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); + if (tsNode.expression === undefined || node.argument === null) { + return; + } + const contextualType = checker.getContextualType(tsNode.expression); + if ( + contextualType !== undefined && + isVoidReturningFunctionType( + checker, + tsNode.expression, + contextualType, + ) && + returnsThenable(checker, tsNode.expression) + ) { + context.report({ + messageId: 'voidReturnReturnValue', + node: node.argument, + }); + } + } + + function checkJSXAttribute(node: TSESTree.JSXAttribute): void { + const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); + const value = tsNode.initializer; + if ( + node.value === null || + value === undefined || + !ts.isJsxExpression(value) || + value.expression === undefined + ) { + return; + } + const contextualType = checker.getContextualType(value); + if ( + contextualType !== undefined && + isVoidReturningFunctionType(checker, value, contextualType) && + returnsThenable(checker, value.expression) + ) { + context.report({ + messageId: 'voidReturnAttribute', + node: node.value, + }); + } + } + return { ...(checksConditionals ? conditionalChecks : {}), ...(checksVoidReturn ? voidReturnChecks : {}), @@ -219,7 +395,6 @@ function voidFunctionParams( node: ts.CallExpression | ts.NewExpression, ): Set { const voidReturnIndices = new Set(); - const thenableReturnIndices = new Set(); const type = checker.getTypeAtLocation(node.expression); for (const subType of tsutils.unionTypeParts(type)) { @@ -233,36 +408,41 @@ function voidFunctionParams( parameter, node.expression, ); - for (const subType of tsutils.unionTypeParts(type)) { - for (const signature of subType.getCallSignatures()) { - const returnType = signature.getReturnType(); - if (tsutils.isTypeFlagSet(returnType, ts.TypeFlags.Void)) { - voidReturnIndices.add(index); - } else if ( - tsutils.isThenableType(checker, node.expression, returnType) - ) { - thenableReturnIndices.add(index); - } - } + if (isVoidReturningFunctionType(checker, node.expression, type)) { + voidReturnIndices.add(index); } } } } - // If a certain positional argument accepts both thenable and void returns, - // a promise-returning function is valid - for (const thenable of thenableReturnIndices) { - voidReturnIndices.delete(thenable); - } - return voidReturnIndices; } -// Returns true if the expression is a function that returns a thenable -function returnsThenable( +// Returns true if given type is a void-returning function. +function isVoidReturningFunctionType( checker: ts.TypeChecker, - node: ts.Expression, + node: ts.Node, + type: ts.Type, ): boolean { + let hasVoidReturningFunction = false; + let hasThenableReturningFunction = false; + for (const subType of tsutils.unionTypeParts(type)) { + for (const signature of subType.getCallSignatures()) { + const returnType = signature.getReturnType(); + if (tsutils.isTypeFlagSet(returnType, ts.TypeFlags.Void)) { + hasVoidReturningFunction = true; + } else if (tsutils.isThenableType(checker, node, returnType)) { + hasThenableReturningFunction = true; + } + } + } + // If a certain positional argument accepts both thenable and void returns, + // a promise-returning function is valid + return hasVoidReturningFunction && !hasThenableReturningFunction; +} + +// Returns true if the expression is a function that returns a thenable +function returnsThenable(checker: ts.TypeChecker, node: ts.Node): boolean { const type = checker.getApparentType(checker.getTypeAtLocation(node)); for (const subType of tsutils.unionTypeParts(type)) { diff --git a/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts b/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts index 63e429000c03..961a21d4c892 100644 --- a/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts +++ b/packages/eslint-plugin/tests/rules/no-misused-promises.test.ts @@ -166,6 +166,92 @@ async function test(p: Promise | undefined) { } } `, + ` +let f; +f = async () => 10; + `, + ` +let f: () => Promise; +f = async () => 10; +const g = async () => 0; +const h: () => Promise = async () => 10; + `, + ` +const obj = { + f: async () => 10, +}; + `, + ` +const f = async () => 123; +const obj = { + f, +}; + `, + ` +const obj = { + async f() { + return 0; + }, +}; + `, + ` +type O = { f: () => Promise; g: () => Promise }; +const g = async () => 0; +const obj: O = { + f: async () => 10, + g, +}; + `, + ` +type O = { f: () => Promise }; +const name = 'f'; +const obj: O = { + async [name]() { + return 10; + }, +}; + `, + ` +const obj: number = { + g() { + return 10; + }, +}; + `, + ` +const obj = { + f: async () => 'foo', + async g() { + return 0; + }, +}; + `, + ` +function f() { + return async () => 0; +} +function g() { + return; +} + `, + { + code: ` +type O = { + bool: boolean; + func: () => Promise; +}; +const Component = (obj: O) => null; + 10} />; + `, + filename: 'react.tsx', + }, + { + code: ` +const Component: any = () => null; + 10} />; + `, + filename: 'react.tsx', + }, ], invalid: [ @@ -265,7 +351,7 @@ if (!Promise.resolve()) { errors: [ { line: 2, - messageId: 'voidReturn', + messageId: 'voidReturnArgument', }, ], }, @@ -279,7 +365,7 @@ new Promise(async (resolve, reject) => { errors: [ { line: 2, - messageId: 'voidReturn', + messageId: 'voidReturnArgument', }, ], }, @@ -296,7 +382,7 @@ fnWithCallback('val', async (err, res) => { errors: [ { line: 6, - messageId: 'voidReturn', + messageId: 'voidReturnArgument', }, ], }, @@ -311,7 +397,7 @@ fnWithCallback('val', (err, res) => Promise.resolve(res)); errors: [ { line: 6, - messageId: 'voidReturn', + messageId: 'voidReturnArgument', }, ], }, @@ -332,7 +418,7 @@ fnWithCallback('val', (err, res) => { errors: [ { line: 6, - messageId: 'voidReturn', + messageId: 'voidReturnArgument', }, ], }, @@ -349,7 +435,7 @@ fnWithCallback?.('val', (err, res) => Promise.resolve(res)); errors: [ { line: 8, - messageId: 'voidReturn', + messageId: 'voidReturnArgument', }, ], }, @@ -372,7 +458,7 @@ fnWithCallback('val', (err, res) => { errors: [ { line: 8, - messageId: 'voidReturn', + messageId: 'voidReturnArgument', }, ], }, @@ -432,5 +518,174 @@ function test(p: Promise | undefined) { }, ], }, + { + code: ` +let f: () => void; +f = async () => { + return 3; +}; + `, + errors: [ + { + line: 3, + messageId: 'voidReturnVariable', + }, + ], + }, + { + code: ` +const f: () => void = async () => { + return 0; +}; +const g = async () => 1, + h: () => void = async () => {}; + `, + errors: [ + { + line: 2, + messageId: 'voidReturnVariable', + }, + { + line: 6, + messageId: 'voidReturnVariable', + }, + ], + }, + { + code: ` +const obj: { + f?: () => void; +} = {}; +obj.f = async () => { + return 0; +}; + `, + errors: [ + { + line: 5, + messageId: 'voidReturnVariable', + }, + ], + }, + { + code: ` +type O = { f: () => void }; +const obj: O = { + f: async () => 'foo', +}; + `, + errors: [ + { + line: 4, + messageId: 'voidReturnProperty', + }, + ], + }, + { + code: ` +type O = { f: () => void }; +const f = async () => 0; +const obj: O = { + f, +}; + `, + errors: [ + { + line: 5, + messageId: 'voidReturnProperty', + }, + ], + }, + { + code: ` +type O = { f: () => void }; +const obj: O = { + async f() { + return 0; + }, +}; + `, + errors: [ + { + line: 4, + messageId: 'voidReturnProperty', + }, + ], + }, + { + code: ` +type O = { f: () => void; g: () => void; h: () => void }; +function f(): O { + const h = async () => 0; + return { + async f() { + return 123; + }, + g: async () => 0, + h, + }; +} + `, + errors: [ + { + line: 6, + messageId: 'voidReturnProperty', + }, + { + line: 9, + messageId: 'voidReturnProperty', + }, + { + line: 10, + messageId: 'voidReturnProperty', + }, + ], + }, + { + code: ` +function f(): () => void { + return async () => 0; +} + `, + errors: [ + { + line: 3, + messageId: 'voidReturnReturnValue', + }, + ], + }, + { + code: ` +type O = { + func: () => void; +}; +const Component = (obj: O) => null; + 0} />; + `, + filename: 'react.tsx', + errors: [ + { + line: 6, + messageId: 'voidReturnAttribute', + }, + ], + }, + { + code: ` +type O = { + func: () => void; +}; +const g = async () => 'foo'; +const Component = (obj: O) => null; +; + `, + filename: 'react.tsx', + errors: [ + { + line: 7, + messageId: 'voidReturnAttribute', + }, + ], + }, ], }); diff --git a/packages/website/src/components/OptionsSelector.tsx b/packages/website/src/components/OptionsSelector.tsx index 83715fc3ae5c..86c842965073 100644 --- a/packages/website/src/components/OptionsSelector.tsx +++ b/packages/website/src/components/OptionsSelector.tsx @@ -74,17 +74,21 @@ function OptionsSelector({ [setState], ); - const copyLinkToClipboard = useCallback(async () => { - await navigator.clipboard.writeText(document.location.toString()); - setCopyLink(true); + const copyLinkToClipboard = useCallback(() => { + void navigator.clipboard + .writeText(document.location.toString()) + .then(() => { + setCopyLink(true); + }); }, []); - const copyMarkdownToClipboard = useCallback(async () => { + const copyMarkdownToClipboard = useCallback(() => { if (isLoading) { return; } - await navigator.clipboard.writeText(createMarkdown(state)); - setCopyMarkdown(true); + void navigator.clipboard.writeText(createMarkdown(state)).then(() => { + setCopyMarkdown(true); + }); }, [state, isLoading]); const openIssue = useCallback(() => { From 4aefecc063d83b12ecac4fc3485ba3b6b6ca8e89 Mon Sep 17 00:00:00 2001 From: xuchaobei Date: Sat, 26 Feb 2022 01:09:45 +0800 Subject: [PATCH 11/12] Update README.md (#4592) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a3535cd83c38..64c2d50e9aeb 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ All of the packages are published with the same version number to make it easier We publish a canary release on every successful merge to `main`, so **you never need to wait for a new stable version to make use of any updates**. -Additionally, we promote the to the `latest` tag on NPM once per week, **on Mondays at 1 pm Eastern**. +Additionally, we promote to the `latest` tag on NPM once per week, **on Mondays at 1 pm Eastern**. The latest version under the `latest` tag is: From 9d47a8b94577540ae445e5ec17373da25423eeb8 Mon Sep 17 00:00:00 2001 From: James Henry Date: Mon, 28 Feb 2022 18:02:54 +0000 Subject: [PATCH 12/12] chore: publish v5.13.0 --- CHANGELOG.md | 22 ++++++++++++++++++++ lerna.json | 2 +- packages/ast-spec/CHANGELOG.md | 8 +++++++ packages/ast-spec/package.json | 2 +- packages/eslint-plugin-internal/CHANGELOG.md | 8 +++++++ packages/eslint-plugin-internal/package.json | 6 +++--- packages/eslint-plugin-tslint/CHANGELOG.md | 8 +++++++ packages/eslint-plugin-tslint/package.json | 6 +++--- packages/eslint-plugin/CHANGELOG.md | 20 ++++++++++++++++++ packages/eslint-plugin/package.json | 8 +++---- packages/experimental-utils/CHANGELOG.md | 8 +++++++ packages/experimental-utils/package.json | 4 ++-- packages/parser/CHANGELOG.md | 8 +++++++ packages/parser/package.json | 8 +++---- packages/scope-manager/CHANGELOG.md | 8 +++++++ packages/scope-manager/package.json | 8 +++---- packages/shared-fixtures/CHANGELOG.md | 8 +++++++ packages/shared-fixtures/package.json | 2 +- packages/type-utils/CHANGELOG.md | 11 ++++++++++ packages/type-utils/package.json | 6 +++--- packages/types/CHANGELOG.md | 8 +++++++ packages/types/package.json | 2 +- packages/typescript-estree/CHANGELOG.md | 8 +++++++ packages/typescript-estree/package.json | 8 +++---- packages/utils/CHANGELOG.md | 13 ++++++++++++ packages/utils/package.json | 8 +++---- packages/visitor-keys/CHANGELOG.md | 8 +++++++ packages/visitor-keys/package.json | 4 ++-- packages/website-eslint/CHANGELOG.md | 8 +++++++ packages/website-eslint/package.json | 16 +++++++------- packages/website/CHANGELOG.md | 11 ++++++++++ packages/website/package.json | 4 ++-- 32 files changed, 212 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c2a68154c59..227285069704 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,28 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [5.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.12.1...v5.13.0) (2022-02-28) + + +### Bug Fixes + +* **eslint-plugin:** [sort-type-union-intersection-members] Wrap the constructorType in parentheses ([#4590](https://github.com/typescript-eslint/typescript-eslint/issues/4590)) ([851bb90](https://github.com/typescript-eslint/typescript-eslint/commit/851bb90216e20b7679efc891dc445e6152d4837f)) + + +### Features + +* **eslint-plugin:** [no-misused-promises] check more places for checksVoidReturn ([#4541](https://github.com/typescript-eslint/typescript-eslint/issues/4541)) ([052cf51](https://github.com/typescript-eslint/typescript-eslint/commit/052cf51fe663283afe89dc7bf97c947e750df095)) +* **eslint-plugin:** add `no-redundant-type-constituents` rule ([#4378](https://github.com/typescript-eslint/typescript-eslint/issues/4378)) ([63d051e](https://github.com/typescript-eslint/typescript-eslint/commit/63d051eed29dcf71015a23992feac0a8f92717a0)) +* **eslint-plugin:** add `no-useless-empty-export` rule ([#4380](https://github.com/typescript-eslint/typescript-eslint/issues/4380)) ([823b945](https://github.com/typescript-eslint/typescript-eslint/commit/823b945c8f9e83d0246a2a5d07519f01e1a64518)) +* **eslint-plugin:** add extension rule `space-before-blocks` ([#1606](https://github.com/typescript-eslint/typescript-eslint/issues/1606)) ([#4184](https://github.com/typescript-eslint/typescript-eslint/issues/4184)) ([208b6d0](https://github.com/typescript-eslint/typescript-eslint/commit/208b6d02252dff2bf272329d3e4a4a82e56c52c0)) +* **eslint-plugin:** added member group support to member-ordering rule ([#4538](https://github.com/typescript-eslint/typescript-eslint/issues/4538)) ([6afcaea](https://github.com/typescript-eslint/typescript-eslint/commit/6afcaea0160a1ccd1c6483ca677c544ca1b8cb4f)) +* **utils:** expose `ast-utils`' helpers ([#4503](https://github.com/typescript-eslint/typescript-eslint/issues/4503)) ([f106e4b](https://github.com/typescript-eslint/typescript-eslint/commit/f106e4b95e824ebb68141bce3d3207448d50c860)) +* **utils:** extract `isNotTokenOfTypeWithConditions` out of `ast-utils`' `predicates` ([#4502](https://github.com/typescript-eslint/typescript-eslint/issues/4502)) ([66501d6](https://github.com/typescript-eslint/typescript-eslint/commit/66501d6dd7e97c22c671efaa6d1ba8237907e417)) + + + + + ## [5.12.1](https://github.com/typescript-eslint/typescript-eslint/compare/v5.12.0...v5.12.1) (2022-02-21) diff --git a/lerna.json b/lerna.json index 7adf338d75cc..64055f7146da 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "5.12.1", + "version": "5.13.0", "npmClient": "yarn", "useWorkspaces": true, "stream": true diff --git a/packages/ast-spec/CHANGELOG.md b/packages/ast-spec/CHANGELOG.md index a34608c74db3..6c09876810e8 100644 --- a/packages/ast-spec/CHANGELOG.md +++ b/packages/ast-spec/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. +# [5.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.12.1...v5.13.0) (2022-02-28) + +**Note:** Version bump only for package @typescript-eslint/ast-spec + + + + + ## [5.12.1](https://github.com/typescript-eslint/typescript-eslint/compare/v5.12.0...v5.12.1) (2022-02-21) **Note:** Version bump only for package @typescript-eslint/ast-spec diff --git a/packages/ast-spec/package.json b/packages/ast-spec/package.json index 230d6657ee1c..6e5f0c3db6b4 100644 --- a/packages/ast-spec/package.json +++ b/packages/ast-spec/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/ast-spec", - "version": "5.12.1", + "version": "5.13.0", "description": "TypeScript-ESTree AST spec", "private": true, "keywords": [ diff --git a/packages/eslint-plugin-internal/CHANGELOG.md b/packages/eslint-plugin-internal/CHANGELOG.md index 39e4b11f2610..4cb6c2804ff5 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. +# [5.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.12.1...v5.13.0) (2022-02-28) + +**Note:** Version bump only for package @typescript-eslint/eslint-plugin-internal + + + + + ## [5.12.1](https://github.com/typescript-eslint/typescript-eslint/compare/v5.12.0...v5.12.1) (2022-02-21) **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 b27e1afce240..353fae18f2ef 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": "5.12.1", + "version": "5.13.0", "private": true, "main": "dist/index.js", "scripts": { @@ -14,8 +14,8 @@ }, "dependencies": { "@types/prettier": "*", - "@typescript-eslint/scope-manager": "5.12.1", - "@typescript-eslint/utils": "5.12.1", + "@typescript-eslint/scope-manager": "5.13.0", + "@typescript-eslint/utils": "5.13.0", "prettier": "*" } } diff --git a/packages/eslint-plugin-tslint/CHANGELOG.md b/packages/eslint-plugin-tslint/CHANGELOG.md index d1b858a2d95c..768a35dc57c6 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. +# [5.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.12.1...v5.13.0) (2022-02-28) + +**Note:** Version bump only for package @typescript-eslint/eslint-plugin-tslint + + + + + ## [5.12.1](https://github.com/typescript-eslint/typescript-eslint/compare/v5.12.0...v5.12.1) (2022-02-21) **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 4cd996d123b7..c98cbbbc654d 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": "5.12.1", + "version": "5.13.0", "main": "dist/index.js", "typings": "src/index.ts", "description": "TSLint wrapper plugin for ESLint", @@ -38,7 +38,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/utils": "5.12.1", + "@typescript-eslint/utils": "5.13.0", "lodash": "^4.17.21" }, "peerDependencies": { @@ -48,6 +48,6 @@ }, "devDependencies": { "@types/lodash": "*", - "@typescript-eslint/parser": "5.12.1" + "@typescript-eslint/parser": "5.13.0" } } diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 7bba47fd7bce..947c63d50256 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -3,6 +3,26 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [5.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.12.1...v5.13.0) (2022-02-28) + + +### Bug Fixes + +* **eslint-plugin:** [sort-type-union-intersection-members] Wrap the constructorType in parentheses ([#4590](https://github.com/typescript-eslint/typescript-eslint/issues/4590)) ([851bb90](https://github.com/typescript-eslint/typescript-eslint/commit/851bb90216e20b7679efc891dc445e6152d4837f)) + + +### Features + +* **eslint-plugin:** [no-misused-promises] check more places for checksVoidReturn ([#4541](https://github.com/typescript-eslint/typescript-eslint/issues/4541)) ([052cf51](https://github.com/typescript-eslint/typescript-eslint/commit/052cf51fe663283afe89dc7bf97c947e750df095)) +* **eslint-plugin:** add `no-redundant-type-constituents` rule ([#4378](https://github.com/typescript-eslint/typescript-eslint/issues/4378)) ([63d051e](https://github.com/typescript-eslint/typescript-eslint/commit/63d051eed29dcf71015a23992feac0a8f92717a0)) +* **eslint-plugin:** add `no-useless-empty-export` rule ([#4380](https://github.com/typescript-eslint/typescript-eslint/issues/4380)) ([823b945](https://github.com/typescript-eslint/typescript-eslint/commit/823b945c8f9e83d0246a2a5d07519f01e1a64518)) +* **eslint-plugin:** add extension rule `space-before-blocks` ([#1606](https://github.com/typescript-eslint/typescript-eslint/issues/1606)) ([#4184](https://github.com/typescript-eslint/typescript-eslint/issues/4184)) ([208b6d0](https://github.com/typescript-eslint/typescript-eslint/commit/208b6d02252dff2bf272329d3e4a4a82e56c52c0)) +* **eslint-plugin:** added member group support to member-ordering rule ([#4538](https://github.com/typescript-eslint/typescript-eslint/issues/4538)) ([6afcaea](https://github.com/typescript-eslint/typescript-eslint/commit/6afcaea0160a1ccd1c6483ca677c544ca1b8cb4f)) + + + + + ## [5.12.1](https://github.com/typescript-eslint/typescript-eslint/compare/v5.12.0...v5.12.1) (2022-02-21) diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 69d7a5529ea7..2ed93a352529 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin", - "version": "5.12.1", + "version": "5.13.0", "description": "TypeScript plugin for ESLint", "keywords": [ "eslint", @@ -44,9 +44,9 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/scope-manager": "5.12.1", - "@typescript-eslint/type-utils": "5.12.1", - "@typescript-eslint/utils": "5.12.1", + "@typescript-eslint/scope-manager": "5.13.0", + "@typescript-eslint/type-utils": "5.13.0", + "@typescript-eslint/utils": "5.13.0", "debug": "^4.3.2", "functional-red-black-tree": "^1.0.1", "ignore": "^5.1.8", diff --git a/packages/experimental-utils/CHANGELOG.md b/packages/experimental-utils/CHANGELOG.md index 6a2cbe78f1ac..a2431353a456 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. +# [5.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.12.1...v5.13.0) (2022-02-28) + +**Note:** Version bump only for package @typescript-eslint/experimental-utils + + + + + ## [5.12.1](https://github.com/typescript-eslint/typescript-eslint/compare/v5.12.0...v5.12.1) (2022-02-21) **Note:** Version bump only for package @typescript-eslint/experimental-utils diff --git a/packages/experimental-utils/package.json b/packages/experimental-utils/package.json index 1bc0685e05a8..2bf8d3f2cfbc 100644 --- a/packages/experimental-utils/package.json +++ b/packages/experimental-utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/experimental-utils", - "version": "5.12.1", + "version": "5.13.0", "description": "(Experimental) Utilities for working with TypeScript + ESLint together", "keywords": [ "eslint", @@ -38,7 +38,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/utils": "5.12.1" + "@typescript-eslint/utils": "5.13.0" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" diff --git a/packages/parser/CHANGELOG.md b/packages/parser/CHANGELOG.md index 1f0b280b4506..eb904406d62b 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. +# [5.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.12.1...v5.13.0) (2022-02-28) + +**Note:** Version bump only for package @typescript-eslint/parser + + + + + ## [5.12.1](https://github.com/typescript-eslint/typescript-eslint/compare/v5.12.0...v5.12.1) (2022-02-21) **Note:** Version bump only for package @typescript-eslint/parser diff --git a/packages/parser/package.json b/packages/parser/package.json index c9f6f962a510..bbe3659fc921 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/parser", - "version": "5.12.1", + "version": "5.13.0", "description": "An ESLint custom parser which leverages TypeScript ESTree", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -44,9 +44,9 @@ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "dependencies": { - "@typescript-eslint/scope-manager": "5.12.1", - "@typescript-eslint/types": "5.12.1", - "@typescript-eslint/typescript-estree": "5.12.1", + "@typescript-eslint/scope-manager": "5.13.0", + "@typescript-eslint/types": "5.13.0", + "@typescript-eslint/typescript-estree": "5.13.0", "debug": "^4.3.2" }, "devDependencies": { diff --git a/packages/scope-manager/CHANGELOG.md b/packages/scope-manager/CHANGELOG.md index e0dd15ec5814..faa953a077a6 100644 --- a/packages/scope-manager/CHANGELOG.md +++ b/packages/scope-manager/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. +# [5.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.12.1...v5.13.0) (2022-02-28) + +**Note:** Version bump only for package @typescript-eslint/scope-manager + + + + + ## [5.12.1](https://github.com/typescript-eslint/typescript-eslint/compare/v5.12.0...v5.12.1) (2022-02-21) **Note:** Version bump only for package @typescript-eslint/scope-manager diff --git a/packages/scope-manager/package.json b/packages/scope-manager/package.json index b4ddedb62332..c010b60160d7 100644 --- a/packages/scope-manager/package.json +++ b/packages/scope-manager/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/scope-manager", - "version": "5.12.1", + "version": "5.13.0", "description": "TypeScript scope analyser for ESLint", "keywords": [ "eslint", @@ -39,12 +39,12 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/types": "5.12.1", - "@typescript-eslint/visitor-keys": "5.12.1" + "@typescript-eslint/types": "5.13.0", + "@typescript-eslint/visitor-keys": "5.13.0" }, "devDependencies": { "@types/glob": "*", - "@typescript-eslint/typescript-estree": "5.12.1", + "@typescript-eslint/typescript-estree": "5.13.0", "glob": "*", "jest-specific-snapshot": "*", "make-dir": "*", diff --git a/packages/shared-fixtures/CHANGELOG.md b/packages/shared-fixtures/CHANGELOG.md index f935e72e5ba3..66d60cd5e848 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. +# [5.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.12.1...v5.13.0) (2022-02-28) + +**Note:** Version bump only for package @typescript-eslint/shared-fixtures + + + + + ## [5.12.1](https://github.com/typescript-eslint/typescript-eslint/compare/v5.12.0...v5.12.1) (2022-02-21) **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 762ad6131868..b350512e48bb 100644 --- a/packages/shared-fixtures/package.json +++ b/packages/shared-fixtures/package.json @@ -1,5 +1,5 @@ { "name": "@typescript-eslint/shared-fixtures", - "version": "5.12.1", + "version": "5.13.0", "private": true } diff --git a/packages/type-utils/CHANGELOG.md b/packages/type-utils/CHANGELOG.md index 56e63599e4cd..7fee288a635f 100644 --- a/packages/type-utils/CHANGELOG.md +++ b/packages/type-utils/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. +# [5.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.12.1...v5.13.0) (2022-02-28) + + +### Features + +* **eslint-plugin:** add `no-redundant-type-constituents` rule ([#4378](https://github.com/typescript-eslint/typescript-eslint/issues/4378)) ([63d051e](https://github.com/typescript-eslint/typescript-eslint/commit/63d051eed29dcf71015a23992feac0a8f92717a0)) + + + + + ## [5.12.1](https://github.com/typescript-eslint/typescript-eslint/compare/v5.12.0...v5.12.1) (2022-02-21) diff --git a/packages/type-utils/package.json b/packages/type-utils/package.json index 1f5685e67955..16a23faf7702 100644 --- a/packages/type-utils/package.json +++ b/packages/type-utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/type-utils", - "version": "5.12.1", + "version": "5.13.0", "description": "Type utilities for working with TypeScript + ESLint together", "keywords": [ "eslint", @@ -39,12 +39,12 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/utils": "5.12.1", + "@typescript-eslint/utils": "5.13.0", "debug": "^4.3.2", "tsutils": "^3.21.0" }, "devDependencies": { - "@typescript-eslint/parser": "5.12.1", + "@typescript-eslint/parser": "5.13.0", "typescript": "*" }, "peerDependencies": { diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md index 90dfc70926e4..26062862c481 100644 --- a/packages/types/CHANGELOG.md +++ b/packages/types/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. +# [5.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.12.1...v5.13.0) (2022-02-28) + +**Note:** Version bump only for package @typescript-eslint/types + + + + + ## [5.12.1](https://github.com/typescript-eslint/typescript-eslint/compare/v5.12.0...v5.12.1) (2022-02-21) **Note:** Version bump only for package @typescript-eslint/types diff --git a/packages/types/package.json b/packages/types/package.json index 206ebf9545ed..a0193e6067aa 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/types", - "version": "5.12.1", + "version": "5.13.0", "description": "Types for the TypeScript-ESTree AST spec", "keywords": [ "eslint", diff --git a/packages/typescript-estree/CHANGELOG.md b/packages/typescript-estree/CHANGELOG.md index 4d7ad425b84b..6919033b0411 100644 --- a/packages/typescript-estree/CHANGELOG.md +++ b/packages/typescript-estree/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [5.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.12.1...v5.13.0) (2022-02-28) + +**Note:** Version bump only for package @typescript-eslint/typescript-estree + + + + + ## [5.12.1](https://github.com/typescript-eslint/typescript-eslint/compare/v5.12.0...v5.12.1) (2022-02-21) **Note:** Version bump only for package @typescript-eslint/typescript-estree diff --git a/packages/typescript-estree/package.json b/packages/typescript-estree/package.json index fa26a4ef6892..d4ec037e5df9 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/typescript-estree", - "version": "5.12.1", + "version": "5.13.0", "description": "A parser that converts TypeScript source code into an ESTree compatible form", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -41,8 +41,8 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/types": "5.12.1", - "@typescript-eslint/visitor-keys": "5.12.1", + "@typescript-eslint/types": "5.13.0", + "@typescript-eslint/visitor-keys": "5.13.0", "debug": "^4.3.2", "globby": "^11.0.4", "is-glob": "^4.0.3", @@ -59,7 +59,7 @@ "@types/is-glob": "*", "@types/semver": "*", "@types/tmp": "*", - "@typescript-eslint/shared-fixtures": "5.12.1", + "@typescript-eslint/shared-fixtures": "5.13.0", "glob": "*", "jest-specific-snapshot": "*", "make-dir": "*", diff --git a/packages/utils/CHANGELOG.md b/packages/utils/CHANGELOG.md index f1c92f415df4..a699c9f1aad6 100644 --- a/packages/utils/CHANGELOG.md +++ b/packages/utils/CHANGELOG.md @@ -3,6 +3,19 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [5.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.12.1...v5.13.0) (2022-02-28) + + +### Features + +* **eslint-plugin:** add extension rule `space-before-blocks` ([#1606](https://github.com/typescript-eslint/typescript-eslint/issues/1606)) ([#4184](https://github.com/typescript-eslint/typescript-eslint/issues/4184)) ([208b6d0](https://github.com/typescript-eslint/typescript-eslint/commit/208b6d02252dff2bf272329d3e4a4a82e56c52c0)) +* **utils:** expose `ast-utils`' helpers ([#4503](https://github.com/typescript-eslint/typescript-eslint/issues/4503)) ([f106e4b](https://github.com/typescript-eslint/typescript-eslint/commit/f106e4b95e824ebb68141bce3d3207448d50c860)) +* **utils:** extract `isNotTokenOfTypeWithConditions` out of `ast-utils`' `predicates` ([#4502](https://github.com/typescript-eslint/typescript-eslint/issues/4502)) ([66501d6](https://github.com/typescript-eslint/typescript-eslint/commit/66501d6dd7e97c22c671efaa6d1ba8237907e417)) + + + + + ## [5.12.1](https://github.com/typescript-eslint/typescript-eslint/compare/v5.12.0...v5.12.1) (2022-02-21) **Note:** Version bump only for package @typescript-eslint/utils diff --git a/packages/utils/package.json b/packages/utils/package.json index d3fafa8278fd..03d6e47493e1 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/utils", - "version": "5.12.1", + "version": "5.13.0", "description": "Utilities for working with TypeScript + ESLint together", "keywords": [ "eslint", @@ -40,9 +40,9 @@ }, "dependencies": { "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.12.1", - "@typescript-eslint/types": "5.12.1", - "@typescript-eslint/typescript-estree": "5.12.1", + "@typescript-eslint/scope-manager": "5.13.0", + "@typescript-eslint/types": "5.13.0", + "@typescript-eslint/typescript-estree": "5.13.0", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0" }, diff --git a/packages/visitor-keys/CHANGELOG.md b/packages/visitor-keys/CHANGELOG.md index 88312dcfe057..5e0ff65320fc 100644 --- a/packages/visitor-keys/CHANGELOG.md +++ b/packages/visitor-keys/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. +# [5.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.12.1...v5.13.0) (2022-02-28) + +**Note:** Version bump only for package @typescript-eslint/visitor-keys + + + + + ## [5.12.1](https://github.com/typescript-eslint/typescript-eslint/compare/v5.12.0...v5.12.1) (2022-02-21) **Note:** Version bump only for package @typescript-eslint/visitor-keys diff --git a/packages/visitor-keys/package.json b/packages/visitor-keys/package.json index 0fc64f259de9..798598914661 100644 --- a/packages/visitor-keys/package.json +++ b/packages/visitor-keys/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/visitor-keys", - "version": "5.12.1", + "version": "5.13.0", "description": "Visitor keys used to help traverse the TypeScript-ESTree AST", "keywords": [ "eslint", @@ -38,7 +38,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/types": "5.12.1", + "@typescript-eslint/types": "5.13.0", "eslint-visitor-keys": "^3.0.0" }, "devDependencies": { diff --git a/packages/website-eslint/CHANGELOG.md b/packages/website-eslint/CHANGELOG.md index 6268851171ce..928b86d944d3 100644 --- a/packages/website-eslint/CHANGELOG.md +++ b/packages/website-eslint/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. +# [5.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.12.1...v5.13.0) (2022-02-28) + +**Note:** Version bump only for package @typescript-eslint/website-eslint + + + + + ## [5.12.1](https://github.com/typescript-eslint/typescript-eslint/compare/v5.12.0...v5.12.1) (2022-02-21) **Note:** Version bump only for package @typescript-eslint/website-eslint diff --git a/packages/website-eslint/package.json b/packages/website-eslint/package.json index 45ec14a00ffe..5d1eb3dfa376 100644 --- a/packages/website-eslint/package.json +++ b/packages/website-eslint/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/website-eslint", - "version": "5.12.1", + "version": "5.13.0", "private": true, "description": "ESLint which works in browsers.", "engines": { @@ -16,19 +16,19 @@ "format": "prettier --write \"./**/*.{ts,js,json,md}\" --ignore-path ../../.prettierignore" }, "dependencies": { - "@typescript-eslint/types": "5.12.1", - "@typescript-eslint/utils": "5.12.1" + "@typescript-eslint/types": "5.13.0", + "@typescript-eslint/utils": "5.13.0" }, "devDependencies": { "@rollup/plugin-commonjs": "^21.0.1", "@rollup/plugin-json": "^4.1.0", "@rollup/plugin-node-resolve": "^13.0.6", "@rollup/pluginutils": "^4.1.1", - "@typescript-eslint/eslint-plugin": "5.12.1", - "@typescript-eslint/parser": "5.12.1", - "@typescript-eslint/scope-manager": "5.12.1", - "@typescript-eslint/typescript-estree": "5.12.1", - "@typescript-eslint/visitor-keys": "5.12.1", + "@typescript-eslint/eslint-plugin": "5.13.0", + "@typescript-eslint/parser": "5.13.0", + "@typescript-eslint/scope-manager": "5.13.0", + "@typescript-eslint/typescript-estree": "5.13.0", + "@typescript-eslint/visitor-keys": "5.13.0", "eslint": "*", "rollup": "^2.59.0", "semver": "^7.3.5" diff --git a/packages/website/CHANGELOG.md b/packages/website/CHANGELOG.md index d29faabc6c4d..85539eb69c32 100644 --- a/packages/website/CHANGELOG.md +++ b/packages/website/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. +# [5.13.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.12.1...v5.13.0) (2022-02-28) + + +### Features + +* **eslint-plugin:** [no-misused-promises] check more places for checksVoidReturn ([#4541](https://github.com/typescript-eslint/typescript-eslint/issues/4541)) ([052cf51](https://github.com/typescript-eslint/typescript-eslint/commit/052cf51fe663283afe89dc7bf97c947e750df095)) + + + + + ## [5.12.1](https://github.com/typescript-eslint/typescript-eslint/compare/v5.12.0...v5.12.1) (2022-02-21) **Note:** Version bump only for package website diff --git a/packages/website/package.json b/packages/website/package.json index 50f187c41011..36f082444338 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -1,6 +1,6 @@ { "name": "website", - "version": "5.12.1", + "version": "5.13.0", "private": true, "scripts": { "build": "docusaurus build", @@ -22,7 +22,7 @@ "@docusaurus/theme-common": "^2.0.0-beta.15", "@docusaurus/theme-search-algolia": "^2.0.0-beta.15", "@mdx-js/react": "1.6.22", - "@typescript-eslint/website-eslint": "5.12.1", + "@typescript-eslint/website-eslint": "5.13.0", "clsx": "^1.1.1", "eslint": "*", "json5": "^2.2.0",