I'm trying to use JSDoc annotations for a base class where a constructor param (and some getter/method return vals) will be the classes which extend the base class.
Is there a way to specify that @template
must be derived from base class?
For context I'm writing an abstraction for file system (files + folders) where their name on disk may contain #tags
- the bulk of code is the same, hence desire for base class, but there are some subtle differences depending on whether it's file or folder (eg. for files the .ext
is after the #tags
thus a file name needs slightly different parsing to a folder name). Additionally, there will be classes derived from the file/folder classes, eg. certain parts of the folder tree merit contextual helper methods.
/**
* @template TParent the parent item, or `null` if root item
* @type Base<TParent?, string, boolean>
*/
class Base {
/** @type {TParent?} parent item or `null` if root
#parent
/** @type {Set<string>} list of tags from parsed item name
#tags
/** @type {boolean} `true` if this item represents a file
#isFile
/**
* @constructor
* @param {TParent?} parent - the parent item, or `null` if root
* etc...
*/
constructor(parent, name, isFile = false) {
this.#parent = parent
this.#isFile = isFile
this.#tags = this.#parse(name)
}
/** @returns {TParent} parent item, or `null` if root
get parent() { return this.#parent }
get tags() { return this.#tags }
#parse(name) { return new Set() }
methodActsOnParent() {
const goo = this.parent?.tags.has("#Goo") ?? false
// ... ^ TParent might not have .tags
}
}
class Folder extends Base {
// folder specific stuff
constructor(...) { ... }
}
class File extends Base {
// file specific stuff
constructor(...) { ... }
}
// in some external file:
class Foo extends Folder {
// foo specific stuff
constructor(...) { ... }
/** @returns {TParent} */
get parent() { return this.parent }
}
Is there some way to say " TParent
must be derived from Base
" so the code hints, etc., know that the base properties/methods/etc will be available?
And, if I know class Foo
will always have a parent (it's never root), is there some way to specify that, given that Base
class allows null
for parent
? That way I could reduce null checking without resorting to // @ts-ignore
cheats.
You can set a constraint on generic type by @template {boolean} T
. This is the equivalent of TypeScript Foo<T extends boolean>
/**
* @template {Base<any> | null} T
*/
class Base {
/**@type {T}*/
#parent;
constructor(/**@type {T}*/parent) {
this.#parent = parent;
}
get parent() { return this.#parent; }
methodActsOnParent() {
this.parent.methodActsOnParent();
}
}
/**
* @template {Base<any> | null} T
* @extends {Base<T>}
*/
class Foo extends Base {
}
const b = new Base(new Base(null));
b.parent.parent;
b.parent.parent.methodActsOnParent();
const f = new Foo(new Foo(null));
f.parent.parent;
f.parent.parent.methodActsOnParent();