feat(linter): add typescript/no-generated-empty-object-type (#26958)

Adds `typescript/no-generated-empty-object-type` as a suspicious type-aware rule, reporting type operations such as `Pick<Data, never>` that resolve to `{}`. Type analysis stays in TSGolint; the rule has no options, fixes, or suggestions.

Requires a backend containing https://github.com/oxc-project/tsgolint/pull/1229. The shared type-aware snapshots were validated against a local build of that implementation. This PR leaves the pinned backend package unchanged.
This commit is contained in:
camc314 committed 2026-09-24 16:02:32 +00:00
1 parent 88a0096214
commit e05b155a6d
13 files changed
+788 -624

No files matched your search

@@ -14,6 +14,7 @@
"typescript/no-duplicate-type-constituents": "error",
"typescript/no-floating-promises": "error",
"typescript/no-for-in-array": "error",
"typescript/no-generated-empty-object-type": "error",
"typescript/no-implied-eval": "error",
"typescript/no-meaningless-void-operator": "error",
"typescript/no-misused-spread": "error",
@@ -0,0 +1,15 @@
type Data = { name: string; value: number };
// These operations resolve to the empty object type.
type EmptyOmit = Omit<Data, "name" | "value">;
type EmptyPick = Pick<Data, never>;
type EmptyNonNullable = NonNullable<unknown>;
type EmptyIntersection = Omit<Data, "name" | "value"> & Omit<Data, "name" | "value">;
// These operations retain properties.
type WithValue = Omit<Data, "name">;
type WithOther = Omit<Data, "name" | "value"> & { other: string };
type Keys<T> = T extends infer U ? keyof U : never;
type Mapped<T extends object> = { [Key in Keys<T>]: Key };
type Referenced<T extends object> = Mapped<T>;
export {};
@@ -1457,6 +1457,7 @@ export interface DummyRuleMap {
"typescript/no-extraneous-class"?: RuleNoConfig | [AllowWarnDeny, NoExtraneousClass];
"typescript/no-floating-promises"?: RuleNoConfig | [AllowWarnDeny, NoFloatingPromisesConfig];
"typescript/no-for-in-array"?: RuleNoConfig;
"typescript/no-generated-empty-object-type"?: RuleNoConfig;
"typescript/no-implied-eval"?: RuleNoConfig;
"typescript/no-import-type-side-effects"?: RuleNoConfig;
"typescript/no-inferrable-types"?: RuleNoConfig | [AllowWarnDeny, NoInferrableTypes];
@@ -6,8 +6,8 @@ arguments: --type-aware --silent
working directory: fixtures/cli/tsgolint
----------
Found 0 warnings and 81 errors.
Finished in <variable>ms on 60 files with 59 rules using 1 threads.
Found 0 warnings and 86 errors.
Finished in <variable>ms on 61 files with 60 rules using 1 threads.
----------
CLI result: LintFoundErrors
----------
@@ -42,7 +42,7 @@ working directory: fixtures/cli/tsgolint
help: Remove the debugger statement
Found 2 warnings and 2 errors.
Finished in <variable>ms on 60 files with 2 rules using 1 threads.
Finished in <variable>ms on 61 files with 2 rules using 1 threads.
----------
CLI result: LintFoundErrors
----------
@@ -16,7 +16,7 @@ working directory: fixtures/cli/tsgolint
help: Remove the debugger statement
Found 0 warnings and 1 error.
Finished in <variable>ms on 1 file with 59 rules using 1 threads.
Finished in <variable>ms on 1 file with 60 rules using 1 threads.
----------
CLI result: LintFoundErrors
----------
@@ -113,6 +113,47 @@ working directory: fixtures/cli/tsgolint
`----
help: Use a more robust iteration method such as for-of or array.forEach instead.
x typescript(no-generated-empty-object-type): This type resolves to `{}`, the empty object type. This was likely not intentional.
,-[no-generated-empty-object-type.ts:3:18]
2 | // These operations resolve to the empty object type.
3 | type EmptyOmit = Omit<Data, "name" | "value">;
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4 | type EmptyPick = Pick<Data, never>;
`----
x typescript(no-generated-empty-object-type): This type resolves to `{}`, the empty object type. This was likely not intentional.
,-[no-generated-empty-object-type.ts:4:18]
3 | type EmptyOmit = Omit<Data, "name" | "value">;
4 | type EmptyPick = Pick<Data, never>;
: ^^^^^^^^^^^^^^^^^
5 | type EmptyNonNullable = NonNullable<unknown>;
`----
x typescript(no-generated-empty-object-type): This type resolves to `{}`, the empty object type. This was likely not intentional.
,-[no-generated-empty-object-type.ts:5:25]
4 | type EmptyPick = Pick<Data, never>;
5 | type EmptyNonNullable = NonNullable<unknown>;
: ^^^^^^^^^^^^^^^^^^^^
6 | type EmptyIntersection = Omit<Data, "name" | "value"> & Omit<Data, "name" | "value">;
`----
x typescript(no-duplicate-type-constituents): Intersection type constituent is duplicated with Omit<Data, "name" | "value">.
,-[no-generated-empty-object-type.ts:6:57]
5 | type EmptyNonNullable = NonNullable<unknown>;
6 | type EmptyIntersection = Omit<Data, "name" | "value"> & Omit<Data, "name" | "value">;
: ^^^^^^^^^^^^^^|^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
: `-- Type 'Omit<Data, "name" | "value">' is first declared here.
7 | // These operations retain properties.
`----
x typescript(no-generated-empty-object-type): This type resolves to `{}`, the empty object type. This was likely not intentional.
,-[no-generated-empty-object-type.ts:6:26]
5 | type EmptyNonNullable = NonNullable<unknown>;
6 | type EmptyIntersection = Omit<Data, "name" | "value"> & Omit<Data, "name" | "value">;
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
7 | // These operations retain properties.
`----
x typescript(no-implied-eval): Implied eval.
,-[no-implied-eval.ts:1:12]
1 | setTimeout('alert("Hi!");', 100);
@@ -694,8 +735,8 @@ working directory: fixtures/cli/tsgolint
`----
help: If your function does not access `this`, you can annotate it with `this: void`, or consider using an arrow function instead.
Found 0 warnings and 81 errors.
Finished in <variable>ms on 60 files with 59 rules using 1 threads.
Found 0 warnings and 86 errors.
Finished in <variable>ms on 61 files with 60 rules using 1 threads.
----------
CLI result: LintFoundErrors
----------
@@ -42,7 +42,7 @@ working directory: fixtures/cli/tsgolint
help: Remove the debugger statement
Found 2 warnings and 2 errors.
Finished in <variable>ms on 60 files with 2 rules using 1 threads.
Finished in <variable>ms on 61 files with 2 rules using 1 threads.
----------
CLI result: LintFoundErrors
----------
+7
View File
@@ -1748,6 +1748,13 @@ impl RuleRunner for crate::rules::typescript::no_for_in_array::NoForInArray {
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Unknown;
}
impl RuleRunner
for crate::rules::typescript::no_generated_empty_object_type::NoGeneratedEmptyObjectType
{
const NODE_TYPES: Option<&AstTypesBitset> = None;
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Unknown;
}
impl RuleRunner for crate::rules::typescript::no_implied_eval::NoImpliedEval {
const NODE_TYPES: Option<&AstTypesBitset> = None;
const RUN_FUNCTIONS: RuleRunFunctionsImplemented = RuleRunFunctionsImplemented::Unknown;
File diff suppressed because it is too large. Load diff
+1
View File
@@ -267,6 +267,7 @@ pub(crate) mod typescript {
pub mod no_extraneous_class;
pub mod no_floating_promises;
pub mod no_for_in_array;
pub mod no_generated_empty_object_type;
pub mod no_implied_eval;
pub mod no_import_type_side_effects;
pub mod no_inferrable_types;
@@ -0,0 +1,51 @@
use oxc_macros::declare_oxc_lint;
use crate::rule::Rule;
#[derive(Debug, Default, Clone)]
pub struct NoGeneratedEmptyObjectType;
declare_oxc_lint!(
/// ### What it does
///
/// Disallows type operations that resolve to the empty object type `{}`.
/// This includes generic type references and intersections, but excludes
/// mapped types whose keys have not yet been resolved.
///
/// ### Why is this bad?
///
/// The empty object type `{}` accepts any non-nullish value, including
/// primitives such as strings and numbers. Accidentally producing this type
/// with a utility type can therefore allow values that were not intended.
///
/// This rule checks generated types. Use `typescript/no-empty-object-type`
/// to disallow explicitly written empty object types.
///
/// ### Examples
///
/// Examples of **incorrect** code for this rule:
/// ```ts
/// type Data = { name: string; value: number };
/// type Empty = Omit<Data, 'name' | 'value'>;
/// type NoProperties = Pick<Data, never>;
/// type NonNullish = NonNullable<unknown>;
/// ```
///
/// Examples of **correct** code for this rule:
/// ```ts
/// type Data = { name: string; value: number };
/// type WithValue = Omit<Data, 'name'>;
/// type WithOther = Omit<Data, 'name' | 'value'> & { other: string };
///
/// type Keys<T> = T extends infer U ? keyof U : never;
/// type Mapped<T extends object> = { [Key in Keys<T>]: Key };
/// type Referenced<T extends object> = Mapped<T>;
/// ```
NoGeneratedEmptyObjectType(tsgolint),
typescript,
suspicious,
version = "next",
short_description = "Disallow type operations that resolve to the empty object type.",
);
impl Rule for NoGeneratedEmptyObjectType {}
+3
View File
@@ -8042,6 +8042,9 @@
"typescript/no-for-in-array": {
"$ref": "#/definitions/RuleNoConfig"
},
"typescript/no-generated-empty-object-type": {
"$ref": "#/definitions/RuleNoConfig"
},
"typescript/no-implied-eval": {
"$ref": "#/definitions/RuleNoConfig"
},