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
JSDoc @private / @protected are dropped in declaration emit for properties declared by constructor assignment #64628
When a .js file declares a class property the classical JSDoc way — a JSDoc-annotated assignment inside
the constructor — Corsa emits the member without its visibility modifier, and for @private also
without its type:
The member therefore becomes public in the published types, and a @private one additionally becomes
untyped, which turns into error TS7008: Member 'p1' implicitly has an 'any' type for every consumer that
does not set skipLibCheck: true.
Note the sibling form is emitted correctly — a property declared in the class body keeps the modifier:
Minimum repro is the A class above. Declaring it in the class body (B) is unaffected.
Real-world hit: @lion/ui (ING's design system)
@lion/ui is a ~900-file JSDoc-typed .js library that ships dist-types/*.d.ts. Everything is authored in
this style, so the regression affects 64 members in 21 files (measured by building the same commit with typescript@4.9.5 and with 7.0.2 and comparing the emitted members); 58 of them can be worked around in
source, the other six cannot (see Notes).
Concrete reference on the current default branch (commit 949d32402 of https://github.com/ing-bank/lion — file packages/ui/components/calendar/src/LionCalendar.js, around lines
190-191):
Strada (the same commit built with typescript@4.9.5, i.e. what the package published until the
TypeScript 7 move) emits, in dist-types/components/calendar/src/LionCalendar.d.ts:
/** @private */
private __today;
Corsa 7.0.2 (that commit's actual output) emits:
/** @private */
__today;
Measured over the whole package (same sources, same consumer):
emit of @lion/ui
consumer errors at skipLibCheck: false
typescript@4.9.5 output
25, of which 0 × TS7008
7.0.2 output
100, of which 74 × TS7008
The 74 break down as 42 unique members across 16 files, e.g. LionCalendar (__today, __connectedCallbackDone, __eventsAdded, __bound*Delegation …), OverlayController (7 members), LionCombobox (5), OverlaysManager (4). The @protected half is worse than noise: protected _ariaVersion
becomes public _ariaVersion, and several downstream TS2611/TS2416/TS2417 errors follow from
protected members turning public.
Expected behaviour
For the constructor-assignment form, Corsa should emit what Strada emits: the visibility modifier
(private / protected, and presumably @readonly/@public likewise) and, for @private, keep dropping the
type exactly as Strada does — i.e. private p1;.
Notes
declare p1; is not an option for us in .js (TS8009: The 'declare' modifier can only be used in TypeScript files), so there is no source-level way to ask for the Strada output. An interface or @typedef that redeclares the member is inert too - an interface cannot narrow a class member's
visibility and it does not influence what is emitted for it. The only workaround is to add a
class-body field declaration carrying the JSDoc, which is not semantics-preserving (it changes
initialization order / property-existence, and for Lit reactive properties it shadows the accessor Lit
installs on the prototype), so we cannot take it for all members. We took it for 58 of the 64 members;
the remaining six are Lit reactive properties.
The native-port staging repo (microsoft/typescript-go, now closed and pointing here) documented that declaration emit for .js input "has substantially changed behavior" and asked for issues where the .d.ts output from a .js file is incorrect - this looks like one.mdsays declaration emit for.jsinput "has substantially changed behavior" and asks for issues where the.d.tsoutput from a.js` file is incorrect — this looks like one.
This is a declaration emit bug that affects JSDoc visibility annotations on constructor-assigned properties.
Impact:
When you declare a property via constructor parameter assignment (TypeScript's parameter properties), the Kristoffer Langeland Knudsen (@Private) and Protected JSDoc tags are not preserved in the generated .d.ts files. This breaks encapsulation documentation for consumers.
Fix location: Likely in the declaration emit transformer where parameter properties are converted to property declarations. The JSDoc extraction needs to run before the parameter property transformation.
Workaround: Declare properties explicitly in the class body instead of using parameter properties.
Want me to investigate the exact code path in the compiler?
Version:
typescript@7.0.2(tsgo) · Area: Declaration Emit / JavaScript + JSDocSummary
When a
.jsfile declares a class property the classical JSDoc way — a JSDoc-annotated assignment insidethe constructor — Corsa emits the member without its visibility modifier, and for
@privatealsowithout its type:
a.d.ts/** @private @type {boolean} */private p1;/** @protected @type {string} */protected p3: string;/** @private @type {boolean} */p1;/** @protected @type {string} */p3: string;The member therefore becomes public in the published types, and a
@privateone additionally becomesuntyped, which turns into
error TS7008: Member 'p1' implicitly has an 'any' typefor every consumer thatdoes not set
skipLibCheck: true.Note the sibling form is emitted correctly — a property declared in the class body keeps the modifier:
→
private f1;(both Strada and Corsa). Only the constructor-assignment form regresses.Reproduction
tsconfig.json{ "compilerOptions": { "target": "ESNext", "module": "NodeNext", "moduleResolution": "NodeNext", "allowJs": true, "checkJs": true, "strict": true, "declaration": true, "emitDeclarationOnly": true, "outDir": "out", "skipLibCheck": true, "lib": ["es2017", "dom"] }, "include": ["a.js"] }npx tsgo -p tsconfig.json && cat out/a.d.tsMinimum repro is the
Aclass above. Declaring it in the class body (B) is unaffected.Real-world hit: @lion/ui (ING's design system)
@lion/uiis a ~900-file JSDoc-typed.jslibrary that shipsdist-types/*.d.ts. Everything is authored inthis style, so the regression affects 64 members in 21 files (measured by building the same commit with
typescript@4.9.5and with7.0.2and comparing the emitted members); 58 of them can be worked around insource, the other six cannot (see Notes).
Concrete reference on the current default branch (commit
949d32402ofhttps://github.com/ing-bank/lion — file
packages/ui/components/calendar/src/LionCalendar.js, around lines190-191):
typescript@4.9.5, i.e. what the package published until theTypeScript 7 move) emits, in
dist-types/components/calendar/src/LionCalendar.d.ts:Measured over the whole package (same sources, same consumer):
@lion/uiskipLibCheck: falsetypescript@4.9.5output7.0.2outputThe 74 break down as 42 unique members across 16 files, e.g.
LionCalendar(__today,__connectedCallbackDone,__eventsAdded,__bound*Delegation…),OverlayController(7 members),LionCombobox(5),OverlaysManager(4). The@protectedhalf is worse than noise:protected _ariaVersionbecomes public
_ariaVersion, and several downstreamTS2611/TS2416/TS2417errors follow fromprotected members turning public.
Expected behaviour
For the constructor-assignment form, Corsa should emit what Strada emits: the visibility modifier
(
private/protected, and presumably@readonly/@publiclikewise) and, for@private, keep dropping thetype exactly as Strada does — i.e.
private p1;.Notes
declare p1;is not an option for us in.js(TS8009: The 'declare' modifier can only be used in TypeScript files), so there is no source-level way to ask for the Strada output. An interface or@typedefthat redeclares the member is inert too - an interface cannot narrow a class member'svisibility and it does not influence what is emitted for it. The only workaround is to add a
class-body field declaration carrying the JSDoc, which is not semantics-preserving (it changes
initialization order / property-existence, and for Lit reactive properties it shadows the accessor Lit
installs on the prototype), so we cannot take it for all members. We took it for 58 of the 64 members;
the remaining six are Lit reactive properties.
microsoft/typescript-go, now closed and pointing here) documented that declaration emit for.jsinput "has substantially changed behavior" and asked for issues where the.d.tsoutput from a.jsfile is incorrect - this looks like one.mdsays declaration emit for.jsinput "has substantially changed behavior" and asks for issues where the.d.tsoutput from a.js` file is incorrect — this looks like one.@privatestatic methods), JSDoc@privateon constructor without parameters is omitted in declaration files #58653 (@privateon aparameterless constructor), and Fix line endings in program.ts #3541, which targets the same
this.xcollection in JS declaration emit - the code path this report exercises.