Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Enhancement: no-unused-vars option for allowing type-onlydeclare class expressions #10918

Open
Labels
enhancement: plugin rule optionNew rule option for an existing eslint-plugin ruleevaluating community engagementwe're looking for community engagement on this issue to show that this problem is widely importantpackage: eslint-pluginIssues related to @typescript-eslint/eslint-plugin
@LukeAbby

Description

@LukeAbby

Before You File a Proposal Please Confirm You Have Done The Following...

My proposal is suitable for this project

  • I believe my proposal would be useful to the broader TypeScript community (meaning it is not a niche proposal).

Link to the rule's documentation

https://eslint.org/docs/latest/rules/no-unused-vars

Description

Whileno-unused-var triggering on most values that are only used in types makes sense, this breaks down when doing advanced things with classes. I propose an option to allowdeclare class expressions to be counted as used by types.

I run into this frequently when writing declaration files, specifically because I write type-only subclasses to widen the class (the most frequent example is to create a bound that allows abstract classes and changed constructors). I recognize this is niche, a more universal example would be typing JS mixins or in general functions that return internally-scoped classes.

For example if you encounter a JS function like:

functionMixin(BaseClass){returnclassMixedextendsBaseClass{ ...}}

It is most nicely typed as:

// Not really defined at the top level of this file but hoisted because there's no way to inline and it'd be really hideous if it did work. Module visibility means that this fake value existing doesn't matter in practice.declareclassMixed{ ...}typeAnyClass=new(arg0:never, ...args:never[])=>object;functionMixin<BaseClassextendsAnyClass>(BaseClass:BaseClass):BaseClass&Mixed;

For those who want to comply with this rule, some fake-value classescan be successfully turned into types, though it is more verbose:

declareclassSomeClass{staticstaticProp:number;instanceProp:number;}

Can be converted to:

interfaceSomeClassConstructor{staticProp:number;new():SomeClass;}interfaceSomeClass{instanceProp:number;}

This already is a mild sacrifice because it's more verbose, forces you to separate instance and static props (even if they make more intuitive sense to order them interspersed), and you lose the ability to writetypeof SomeClass to mean the class, which to me feels more intuitive if you're used to regular classes.

However there's no valid transformations when:

  • The class has an accessibility modifier on a property, i.e.private,protected,readonly, andinternal
  • The class has a truly private property. This may seem unimportant but they have implications towards the variance of the class so stripping them isnot a true 1:1 mapping.
  • The class has a getter or setter. While in common cases you could emulate them by creating a property this breaks down when the getter/setter can't be unified (e.g. setnumber, getstring) and it also breaks subclassing as you can't substitute a getter/setter pair for a property.
  • The class is abstract.
  • The class has aninternal,private, orprotected constructor. You could simply leave out the constructor inSomeClassConstructor but this gives worse diagnostics.

While I'm quite comfortable with classes and translating them to regular interfaces, I also imagine most people will find the syntax for generic classes annoying. It also becomes moreawkward when the base class expression is complex like mixins, especially when generic passthrough is involved:

typeAnyClass=new(...args:any[])=>object;functionMixin<BaseClassextendsAnyClass>(BaseClass:BaseClass){returnclassextendsBaseClass{ ...}}declareclassGeneric<T>{prop:T;}classMixed<T>extendsMixin(Generic)<T>{ ...}

Youcan transformMixed to:

interfaceMixedConstructor{new<T>():Mixed<T>;}typeMixed<T>=typeofMixin<typeofGeneric<T>>&{ ...};

This code is obviously ugly and it doesn't generalize well to more complex mixins, especially where the generic parameters and parameters differ significantly.

Fail

declareabstractclassAnyErrorextendsError{constructor(arg0:never, ...args:never[]);}

Pass

declareabstractclassAnyErrorextendsError{constructor(arg0:never, ...args:never[]);}typeErrorClasses=Array<typeofAnyError>;

Additional Info

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancement: plugin rule optionNew rule option for an existing eslint-plugin ruleevaluating community engagementwe're looking for community engagement on this issue to show that this problem is widely importantpackage: eslint-pluginIssues related to @typescript-eslint/eslint-plugin

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions


      [8]ページ先頭

      ©2009-2025 Movatter.jp