Summary
A protected constructor is emitted as a public one:
export class Singleton {
protected constructor(name: string) {}
static create(): Singleton { return new Singleton('x') }
}emits constructor(name: string);, so new Singleton('x') type-checks against the declarations although only the class and its subclasses may construct it. tsc emits protected constructor(name: string);. Home's bundled bun-debug-adapter-protocol (protected constructor(inspector: T, ...)) comes out this way.
Changes
buildClassBodyDts reads the protected modifier for every member but dropped it when it emitted the constructor. It now keeps it, as it does for methods, when protected stands on the same line as constructor. After a line break, protected is a field of that name and the constructor stays public, as TypeScript reads it.
Testing
New test/protected-constructor.test.ts, each case type-checked through a consumer with the existing runTsc helper. A plain protected constructor, one with a parameter property, and overloads must reject new from outside (@ts-expect-error) while a subclass constructs normally; on main these 3 fail. A public constructor, a field named protected on the line before constructor, and a line break inside a comment after protected must stay public.
bun test (Zig library built with the pinned Zig, so its tests run too), typecheck, lint and zig build test pass. Over 4,192 files from vue-core, vue-router, pinia and knip (LF and CRLF) nothing changes. Over 11,774 more from tailwindcss, stacks, eslint-plugin-vue and home, the only change is the home file above, whose constructor is now protected.