Skip to content

Reusing classes with private fields across entrypoints/submodules causes not working type definitions to be generated #90

@AKuederle

Description

@AKuederle

I ran into an issue with classes shared across entry-points with private fields. I feel like I am missing the proper terminology here, so I hope I am able to explain this properly.

The issue

Classes with private attributes are considered different types when exported from multiple different entrypoints.

Setup and Reproduction

First of all, a reproduction can be found in this repo: https://github.com/AKuederle/dts-buddy-repro
This should explain the setup required better than words can.

But here the a short explanation:

I have a package with to entrypoints:

    ".": {
      "types": "./types/index.d.ts",
      "import": "./src/index.js"
    },
    "./format": {
      "types": "./types/index.d.ts",
      "import": "./src/format.js"
    }

Both entrypoints contain functions/methods that make use of the same class. In the reproduction this is the Person class from ./src/person.js. This class is directly exported via the main entrypoint in ./src/index.js and used as import type in a function exported from ./src/format.js

In the type file created by dts-buddy, this shared class is repeated in the type defintion of both entrypoints as follows:

	class Person {
		/**
		 * Create a person object.
		 * @param name - The name of the person.
		 * @param age - The age of the person.
		 * @param email - The email address of the person.
		 */
		constructor(name: string, age: number, email: string);
		name: string;
		age: number;
		email: string;
		
		get id(): string;
		#private;
	}

Importantly, because the class has a private field #id, dts-buddy includes a #private; at the end. Typescript considers this field to have the type any.
However, because the definition is repeated in the types file and one of the fields are any, Typescript considers these two exported classes to be different.
When attempting to use functions from both entrypoints together a type error is raised:

import { Person } from 'exampleLib';
import { formatPerson } from 'exampleLib/format';

const person = new Person('John Doe', 34, "2@be.de");
// This does not work, because the Person class has a private field.
// This triggers dts-buddy to include a `#private` field in the generated types.
console.log(formatPerson(person));
                         ^^^^^^^ 
Argument of type 'import("exampleLib").Person' is not assignable to parameter of type 'Person'.
  Property '#private' in type 'Person' refers to a different member that cannot be accessed from within type 'Person'.ts(2345)

If the Person class has no private field (tested with the PersonNoPrivate class in the reproduction), Typescript considers the two type exports the same and no error is raised.

Expectation

As I am using the same class, I would assume that Typescript considers them the same independent on how they are imported.

Actually, I was surprised, that dts-buddy repeated the type definition across both modules. My naive assumption was, that the exported type module for the format entrypoint would be this:

declare module 'exampleLib/format' {
	import { Person } from 'exampleLib';

	export function formatPerson(person: Person): string;

	export {};
}

There is a lot here, I don't understand and in particular don't understand the impact of exporting types one way or the other. So, I hope you can shed some light on what is going on here and if this is actually a bug or just me misunderstanding, how types should be re-exported.

Thanks in advance! And thanks for making jsdoc types a viable alternative to writing TS

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions