Repository navigation
False error TS4094: ... exported class expression may not be private or protected. #30355
Description
Activity
- addedWorking as IntendedThe behavior described is the intended behavior; this is not a bugThe behavior described is the intended behavior; this is not a bug
on Mar 14, 2019 RyanCavanaugh commented
on Mar 14, 2019 MemberMore actionsThe error message didn't accidently write itself... exported anonymous classes can't have private or protected members if declaration emit is enabled, because there's no way to represent that in a .d.ts file.
Reacted by zbranevichfinstek, letmein and Daniele OrlandoReacted by Sahin Yort, Joe Pea, Narayan Sainaney, 陈虎, hezhuojie, Roman, Shukant Pal, Hugh Collins, Kaylee Sachs, Henrique Cesar and 107 moreReacted by Rico Hermans, Tar and Jamie HaywoodReacted by Alex Leung, Fernando Chen, Leo Doldan, Zero Liu, Michał Miszczyszyn, Joe Pea, Anton Korniychuk, Henrique Cesar, Ihor Sakailiuk, Haytham Halimeh and 13 moreReacted by Tommaso Ricci, Zim, Paul Coch, joooKiwi and Ryan Goreetypescript-bot commented
on Mar 17, 2019 ContributorMore actionsThis issue has been marked 'Working as Intended' and has seen no recent activity. It has been automatically closed for house-keeping purposes.
Reacted by solanamonkIt is possible to use private in declaration files. In my project, for example, I found a lot of them in node_modules folder:
tslint/lib/formatters/checkstyleFormatter.d.ts:export declare class Formatter extends AbstractFormatter { static metadata: IFormatterMetadata; format(failures: RuleFailure[]): string; private escapeXml; <- HERE }
Reacted by Alex Leung, Aki Rodić, Caleb Williams, Eugene Staten II, Yaojian, Liu CZ, Csaba Benkő, Konstantin Shutkin, Jayatubi and Frog ChenReacted by Rick van MookI think this is an issue, too.
Reacted by Aki Rodić, Caleb Williams, Csaba Benkő, solanamonk, Vladimir Govorov, seth and Mohammad Jawad (Kasir) BaratiPerhaps we should extend d.ts to handle this case then. Ideally the declarations file and the ts source file would be 100% compatible.
Reacted by Rob Moran, Nicolas Jakob, Alexander Bird, Silas Rioux, Marcin Baszczewski, Seph Reed, chocolateboy, Elvynia, Fernando Chen, Leo Doldan and 33 moreReacted by Joe Pea, Christopher Joel, Nikita Domin, Marcelo Martínez, cad and Frog ChenIt is currently not possible to generate declaration files when using a class mixin pattern, even if that pattern would otherwise compile and type-check just fine. It would be nice to have support for this 🙏
Reacted by Andrew Smiley, Nikita Domin, Hugh Collins, Steve Hansen, a11delavar, Roxane Létourneau, Aki Rodić, Haytham Halimeh, Leonardo Pliskieviski, Daniel Betts and 19 moreReacted by Aki RodićReacted by Aki RodićShouldn't this issue be open?
Reacted by Chase Moskal, Henrique Cesar, Alice T'Poteat, Klemen Oslaj, a11delavar, Caleb Williams, Haytham Halimeh, Oliver Anteros, Daniel Betts, Forrest and 24 moreIn particular, it'd be nice to be able to do something like
function foo<T>(arg: T): SomeType<T> { ... } type Bar = {num: number} type FooBar = ReturnType<typeof foo<Bar>>
I get what you're saying, that
foo<Bar>is already a type, so typeof doesn't make sense. But I think you get what's missing.This is valid:
type FooBar = ReturnType<typeof foo>
But in that example, TypeScript sets the type of
Ttounknown, so the type of FooBar isSomeType<T>.Basically, it just intuitively seems like there should be some way to pass a generic arg there, but we can't, and TypeScript automatically sticks
unknowninto it. Example on playground shows automatic unknown type forT.Reacted by Patrick Geyer, Erik Eckhardt, Forrest, Huan Li and ruskevychIf a function
foohas a generic param, then thetypeofoperator could perhaps return a generic type, and we could write:type FooBar = ReturnType<(typeof foo)<Bar>>
That would be a breaking change to TypeScript though.
Please see #35822
I re-opened this at #36060
sebastiandittrich commented
on Aug 9, 2020 More actionsInteresting fix:
Moving the mixin to its own file and doing a
defaultexport works:type Constructor<T = {}> = new (...args: any[]) => T export default <T extends Constructor>(base: T) => class SomeClass extends base { protected something: boolean = false }
While a named export throws the error:
type Constructor<T = {}> = new (...args: any[]) => T export const mixin = <T extends Constructor>(base: T) => class SomeClass extends base { protected something: boolean = false }
Reacted by Harsh K, Declan Vong, a11delavar, Sergio Alcantara, Safwan Shaheer, rogerkjoy, Forrest, David H, Saša Đurić, thilllon and 5 moreReacted by patatouilleReacted by a11delavar, Eric Simonton, Chase Moskal and Ethan ResnickReacted by a11delavarReacted by Huan Li and WangJiehere is how you can fix it, instead of
function makeClass() { return class { private x = 0; public y = 1; } }
do
type PublicConstructor<T> = new () => T; interface MyPublicInterface { y: number; } function makeClass(): PublicConstructor<MyPublicInterface> { return class { private x = 0; public y = 1; } }
Reacted by zhao-huo-long, TGuoW, Husam Elbashir, Long Nguyen, Jeremy Miller, MaestroDD0S, Patrick Meppe, Baoshan Sheng, sevenwithawp and HubbleReacted by syl-avena, Forrest, Zhaopeng XUAN, Davi Aquino, Stephan Hesse, yingziwu, electrovir, Husam Elbashir, Long Nguyen, Jeremy Miller and 5 more3 remaining items
Just play with Omit type 😎. Just a example with vitest :
import { vi as _vi } from 'vitest'; type VitestUtils = Omit< typeof _vi, '_timers' | '_mockedDate' | '_mocker' | 'getImporter' | '_config' // never also works fine >; type Primitive = number | boolean | string; type Props = { [key in string]?: | Primitive | Primitive[] // eslint-disable-next-line @typescript-eslint/no-explicit-any | ((this: VitestUtils, ...args: any) => any); }; export default function extendVitest<T extends Props>(options: T) { return Object.assign(_vi as VitestUtils, options); }
NB: If you don't know the private members of the class, just put never as second parameter of Omit.
Reacted by Alexandre Moreau-Lemay, Leaftail and SunlhoSame thing with "constant class".
export const testClass = class { private field = {} } Property 'field' of exported class expression may not be private or protected.ts(4094)Interestingly
#private class fields indeed work fine.Using workaround:
class TestClass {} export const testClass = new TestClass();Although use
#to declare private member can avoid this declaration error, it will make the property not readable, plus it can not replaceprotected. I only want some method or property not overridable in some cases.Reacted by Petr Plenkov and lenyou can write like this
class C { private privateMember () { } } export function test () { return C; } export function test2 (): typeof C { return class extends C {}; }Reacted by GenluoAlthough use
#to declare private member can avoid this declaration error, it will make the property not readable, plus it can not replaceprotected. I only want some method or property not overridable in some cases.it would be great if ESlint rule will be already aware. I had some error and quick fix was generation of private _data + get/set data methods. And it was throwing errors. Replacing with # helped to solve it.
Interesting fix:
Moving the mixin to its own file and doing a
defaultexport works:type Constructor<T = {}> = new (...args: any[]) => T export default <T extends Constructor>(base: T) => class SomeClass extends base { protected something: boolean = false }
While a named export throws the error:
type Constructor<T = {}> = new (...args: any[]) => T export const mixin = <T extends Constructor>(base: T) => class SomeClass extends base { protected something: boolean = false }
isn't it a bug then? So it's doing exactly same, but with two files instead of one. I'm quite surprised to see that this issue is Closed while indeed it must not and should be fixed
Reacted by CHENG KUN, Valerii Smirnov, Michael Urban, len, Stiliyan Kushev, Bojan Vazmer and Madeorsk- added a commit that references this issue
on Oct 5, 2023 - added a commit that references this issue
on Nov 18, 2023 StiliyanKushev commented
on Jan 29, 2025 More actionsHow that's still closed is beyond me. Please revise.
Reacted by Saikat Dey, Jonas Lüchau, Nedo, Felix Gertz, Andrew Jefferson, Neil de Carteret and Manuel Mauky- added a commit that references this issue
on Apr 25, 2025 - added a commit that references this issue
on Apr 30, 2025 - added a commit that references this issue
on Apr 30, 2025 - added a commit that references this issue
on Apr 30, 2025 - added a commit that references this issue
on Apr 30, 2025 - locked as resolved and limited conversation to collaborators
on Oct 21, 2025
TypeScript Version: 3.3.3333
Code
src/test.ts:
tsconfig.json:
{ "compilerOptions": { "target": "es5", "module": "commonjs", "declaration": true }, "include": [ "src/**/*" ], "exclude": [ "node_modules" ] }Expected behavior:
No compilation error.
Actual behavior:
Compiler prints error:
$ tsc
src/test.ts(1,17): error TS4094: Property 'privateMember' of exported class expression may not be private or protected.
Playground Link: not possible to provide.
Workaround
Declare the return type explicitly: