You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Unexpected TS4094 with the build parameter declaration: true #17293
Expected behavior:-d should not cause this message: test.ts(1,5): error TS4094: Property '_message' of exported class expression may not be private or protected.
Actual behavior: It does.
There should at least be a suppression parameter to ignore this error.
Like many of our still-open declaration emit issues, fixing this would require significant changes to our declaration emit. Today, we'll try to emit the example in the OP as:
declarevarFoo: {new(): {// Try to write type for `_message` herehi(): string;}}
except since we're in an anonymous object literal type, there is no way to write a private or protected member, so instead we write a visibility error and refuse to generate declarations. The only "correct" thing to do to fix this is to generate an interface for Foo's instance type, that way member modifiers can be preserved. The issue with that is that generating an interface would change the publicly visible declarations for your file (namely, it would introduce one you never actually wrote, but was implicit in the usage of a class expression).
For now, the workaround is to explicitly give a type to your var Foo = /* ... */, ie var Foo: {new(): IFoo} = /* ... */, where you've defined IFoo.
The reason this issue and others like it (issues assigned to me in the category of declaration emit which I have not already created fixes for) are sitting for so long is because we're in the process of overhauling our declaration emitter to make it easier to generate things like these implicit interfaces (or imports, dependent variables, etc) without changing the public shape of your declaration file, and it's a long process to do the refactoring (generally speaking, big changes are difficult to review, and this is a series of big changes). We are replacing a 3-yr-old subsystem that contains >2000 LOC, countless edge-cases and deep connections to the type checker and emitter, after all. ;)
We are working on it, though. While I can't make any promises for this specific issue, we're hoping to have the prerequisite changes we want to make to make this possible done in the 2.7 timeframe.
Mohamed Hegazy (@mhegazy) The fix for this issue (and #17744) seem predicated on that idea that we could hoist these class expressions to a private name within a module and the reference them in the type definitions elsewhere. While this can work well inside modules or namespaces, in the global scope (like the example in the OP here), there's no way to do that hoisting without introducing a name that doesn't actually exist into the global scope (which is dangerous from an API perspective). Maybe we should consider a different approach? (anonymous class type nodes?)
I just discovered this today, 13 Aug 2019. Reproable code is at matthewadams/typetrait@9b19118. Just clone & git checkout 9b19118aa33525b9d701fbee4f6ff8f223c1b6e8 && npm run build. Wesley Wigham (@weswigham), can I get an update on when this will be fixed, please? OP was over two years ago. tsc --version yields Version 3.5.3.
Hmm, honestly we need a proposal for class expression type nodes. You could also argue that inline type aliases with expanded modifiers in anonymous types would also do the trick. We've talked about the first before, informally, but not actually tossed together a proposal yet, since the only two issues that seem to require that level of expressivity and precision are, well, patterns like this and other similar mixin-type patterns. It's in the back of my mind, at least, if not a direct priority.
Reacted by Titian Cernicova-Dragomir and Matthew Adams
Wesley Wigham (@weswigham) Well, until it's addressed, my workaround is to devolve back to using _ (underscore) prefixes to indicate that a method is not public, which seems just dumb given TypeScript's access modifiers. Further, traits/mixins are, IMHO, the best (read: most practical) way to achieve composability and avoid brittle inheritance hierarchies, which is why I feel that it would do all of us good to have this fixed sooner rather than later.
Reacted by Marcin Baszczewski, Darrin Massena, Rudolf Meijering, Serhii Kulykov and Will Stott
This issue is important for us to write mixins that return classes with protected members.
So I summarised the workarounds we have to choose from at #17744 (comment)
While I like using Symbol, I would highly appreciate the fix for this issue (and #17744).
Can someone explain in simple terms what is wrong with a class extending another class that has private properties/methods? This seems like very reasonable thing to do in OO design.
Can someone explain in simple terms what is wrong with a class extending another class that has private properties/methods? This seems like very reasonable thing to do in OO design.
Can someone explain in simple terms what is wrong with a class extending another class that has private properties/methods? This seems like very reasonable thing to do in OO design.
Depends on what you understand to be "wrong". Your example creates a new class for every call of bar and returns an instance of that new class (what's that called? A singleton factory, maybe?). Which means that for your example the instanceof operator would "break" (unless one really intends to produce a whole bunch of singletons for whatever reason); assuming you could somehow get the class/constructor into scope to use the instanceof operator on Bar in the first place, which in your example is also not possible outside of bar. Some might argue there is very little "correct" JS code to begin with, but it might of course be the case that the example is just stripped down to reproduce the error you're seeing, leaving out features that could explain why code like this exists.
is not really the reason there's something "wrong", nor is it helpful. As explained, that code should not have been written to challenge declaration file capabilities in the first place, because it has all sorts of unwanted consequences at runtime. There's a couple of things one should write differently and so would not run into this edge case at all.
E.g., assuming bar exists as a wrapper to make Bar "newless", not questioning why classes have been chosen over factories and interfaces, one could write:
classFoo{privatefoo='FOO';}classBarextendsFoo{constructor(x: unknown){super();this.value=x;}value: unknown;}constbar=(value: unknown)=>newBar(value);// Might export `Bar` too for `instanceof` and `extends`, or // introduce wrappers for some of those operators too.export{bar}
This rewritten example shows you:
can keep your classes,
don't create throw away classes (by accident?),
don't hit compiler limitations and therefore actually can solve your problem without having to wait some years for a fix,
TS4094 is expected for this declaration shape. --declaration must write the inferred constructor and instance type of Foo; its anonymous instance type cannot express _message as a private member while preserving the declaration file's public shape. What does the error "Exported variable [name] has or is using private name [name]" mean? describes this limitation.
With tsc test.ts -t es2017 -d, TypeScript 2.4.1 reports TS4094 on Foo. TypeScript 7.1.0-dev.20260831.1 also reports TS4094, now referring to an exported anonymous class type.
Give Foo an explicit declaration-facing type, for example var Foo: { new (): IFoo } = class Foo { ... }, where IFoo describes the public instance members. This gives declaration emit a type it can write without exposing the private member.
Activity
tusharmath commentedon Sep 6, 2017
I am facing the same issue.
yeliex commentedon Dec 6, 2017
any progress? Wesley Wigham (@weswigham)
weswigham commentedon Dec 6, 2017
Like many of our still-open declaration emit issues, fixing this would require significant changes to our declaration emit. Today, we'll try to emit the example in the OP as:
except since we're in an anonymous object literal type, there is no way to write a private or protected member, so instead we write a visibility error and refuse to generate declarations. The only "correct" thing to do to fix this is to generate an interface for
Foo's instance type, that way member modifiers can be preserved. The issue with that is that generating an interface would change the publicly visible declarations for your file (namely, it would introduce one you never actually wrote, but was implicit in the usage of a class expression).For now, the workaround is to explicitly give a type to your
var Foo = /* ... */, ievar Foo: {new(): IFoo} = /* ... */, where you've definedIFoo.The reason this issue and others like it (issues assigned to me in the category of declaration emit which I have not already created fixes for) are sitting for so long is because we're in the process of overhauling our declaration emitter to make it easier to generate things like these implicit interfaces (or imports, dependent variables, etc) without changing the public shape of your declaration file, and it's a long process to do the refactoring (generally speaking, big changes are difficult to review, and this is a series of big changes). We are replacing a 3-yr-old subsystem that contains >2000 LOC, countless edge-cases and deep connections to the type checker and emitter, after all. ;)
We are working on it, though. While I can't make any promises for this specific issue, we're hoping to have the prerequisite changes we want to make to make this possible done in the 2.7 timeframe.
weswigham commentedon Mar 22, 2018
Mohamed Hegazy (@mhegazy) The fix for this issue (and #17744) seem predicated on that idea that we could hoist these class expressions to a private name within a module and the reference them in the type definitions elsewhere. While this can work well inside modules or namespaces, in the global scope (like the example in the OP here), there's no way to do that hoisting without introducing a name that doesn't actually exist into the global scope (which is dangerous from an API perspective). Maybe we should consider a different approach? (anonymous class type nodes?)
11 remaining items
fix(tsc): add a temporary hack to ensure `tsc -d` works
matthewadams commentedon Aug 13, 2019
I just discovered this today, 13 Aug 2019. Reproable code is at matthewadams/typetrait@9b19118. Just clone &
git checkout 9b19118aa33525b9d701fbee4f6ff8f223c1b6e8 && npm run build. Wesley Wigham (@weswigham), can I get an update on when this will be fixed, please? OP was over two years ago.tsc --versionyieldsVersion 3.5.3.workaround microsoft/TypeScript#17293
weswigham commentedon Aug 19, 2019
Hmm, honestly we need a proposal for class expression type nodes. You could also argue that inline type aliases with expanded modifiers in anonymous types would also do the trick. We've talked about the first before, informally, but not actually tossed together a proposal yet, since the only two issues that seem to require that level of expressivity and precision are, well, patterns like this and other similar mixin-type patterns. It's in the back of my mind, at least, if not a direct priority.
matthewadams commentedon Aug 19, 2019
Wesley Wigham (@weswigham) Well, until it's addressed, my workaround is to devolve back to using
_(underscore) prefixes to indicate that a method is not public, which seems just dumb given TypeScript's access modifiers. Further, traits/mixins are, IMHO, the best (read: most practical) way to achieve composability and avoid brittle inheritance hierarchies, which is why I feel that it would do all of us good to have this fixed sooner rather than later.web-padawan commentedon Nov 27, 2019
This issue is important for us to write mixins that return classes with protected members.
So I summarised the workarounds we have to choose from at #17744 (comment)
While I like using
Symbol, I would highly appreciate the fix for this issue (and #17744).trusktr commentedon Dec 21, 2019
Please see #35822
sukima commentedon Feb 17, 2023
Can someone explain in simple terms what is wrong with a class extending another class that has private properties/methods? This seems like very reasonable thing to do in OO design.
Specifically what is wrong with this:
Marckon commentedon Feb 22, 2023
I don't know why but export Foo will eliminate this error
rjamesnw commentedon Feb 22, 2023
Devin Weaver (@sukima), see here: #30355 (comment)
Removes all protected due to bug in TS, fixes eslint issue
valler commentedon Jan 11, 2026
Devin Weaver (@sukima)
Nothing. See:
Depends on what you understand to be "wrong". Your example creates a new class for every call of
barand returns an instance of that new class (what's that called? A singleton factory, maybe?). Which means that for your example theinstanceofoperator would "break" (unless one really intends to produce a whole bunch of singletons for whatever reason); assuming you could somehow get the class/constructor into scope to use theinstanceofoperator onBarin the first place, which in your example is also not possible outside ofbar. Some might argue there is very little "correct" JS code to begin with, but it might of course be the case that the example is just stripped down to reproduce the error you're seeing, leaving out features that could explain why code like this exists.Therefore (and also)
is not really the reason there's something "wrong", nor is it helpful. As explained, that code should not have been written to challenge declaration file capabilities in the first place, because it has all sorts of unwanted consequences at runtime. There's a couple of things one should write differently and so would not run into this edge case at all.
E.g., assuming
barexists as a wrapper to makeBar"newless", not questioning why classes have been chosen over factories and interfaces, one could write:This rewritten example shows you:
declaration: true#17293 (comment))As rules of thumb (or linting rules):
RyanCavanaugh commentedon Aug 31, 2026
TS4094 is expected for this declaration shape.
--declarationmust write the inferred constructor and instance type ofFoo; its anonymous instance type cannot express_messageas a private member while preserving the declaration file's public shape. What does the error "Exported variable [name] has or is using private name [name]" mean? describes this limitation.With
tsc test.ts -t es2017 -d, TypeScript 2.4.1 reports TS4094 onFoo. TypeScript 7.1.0-dev.20260831.1 also reports TS4094, now referring to an exported anonymous class type.Give
Fooan explicit declaration-facing type, for examplevar Foo: { new (): IFoo } = class Foo { ... }, whereIFoodescribes the public instance members. This gives declaration emit a type it can write without exposing the private member.