shelving 1.235.0 → 1.236.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,6 +5,7 @@ import { FileExtractor } from "./FileExtractor.js";
5
5
  * - Uses the TypeScript compiler API to parse the AST.
6
6
  * - Extracts exported, public, non-`_`-prefixed declarations as `tree-documentation` children.
7
7
  * - Overloaded declarations sharing a name are merged into a single `tree-documentation` with multiple `signatures`.
8
+ * - Class declarations synthesise their `signatures`, `params`, and `returns` from the constructor — `new ClassName<…>(…)` including generics, one signature per constructor overload, with `returns` set to the class type. Param descriptions come from the constructor's `@param` first, then the class's `@param`.
8
9
  * - Top-of-file JSDoc comment becomes the file's `content`.
9
10
  * - Sets `description` (a plain-text summary from the first JSDoc paragraph) on the file and every `tree-documentation` child.
10
11
  * - Sets `title` on every `tree-documentation` child — `name()` for functions and methods, bare `name` for other kinds. Parent class context comes from the `class` prop ("member of …" affordance), never the title.
@@ -6,6 +6,7 @@ import { extractMarkdownProps } from "./MarkupExtractor.js";
6
6
  * - Uses the TypeScript compiler API to parse the AST.
7
7
  * - Extracts exported, public, non-`_`-prefixed declarations as `tree-documentation` children.
8
8
  * - Overloaded declarations sharing a name are merged into a single `tree-documentation` with multiple `signatures`.
9
+ * - Class declarations synthesise their `signatures`, `params`, and `returns` from the constructor — `new ClassName<…>(…)` including generics, one signature per constructor overload, with `returns` set to the class type. Param descriptions come from the constructor's `@param` first, then the class's `@param`.
9
10
  * - Top-of-file JSDoc comment becomes the file's `content`.
10
11
  * - Sets `description` (a plain-text summary from the first JSDoc paragraph) on the file and every `tree-documentation` child.
11
12
  * - Sets `title` on every `tree-documentation` child — `name()` for functions and methods, bare `name` for other kinds. Parent class context comes from the `class` prop ("member of …" affordance), never the title.
@@ -108,9 +109,9 @@ function _extractStatement(statement, source) {
108
109
  const kind = _getKind(statement);
109
110
  if (!kind)
110
111
  return;
111
- const signature = _getSignature(statement, source, name);
112
+ const signatures = _getSignatures(statement, source, name);
112
113
  const params = _getParams(statement, source, jsDoc?.params);
113
- const returns = _getReturns(statement, source, jsDoc?.returns);
114
+ const returns = _getReturns(statement, source, jsDoc?.returns, name);
114
115
  const throws = jsDoc?.throws;
115
116
  const examples = jsDoc?.examples;
116
117
  // Heritage (`extends` / `implements`) is only meaningful for classes and interfaces.
@@ -126,7 +127,7 @@ function _extractStatement(statement, source) {
126
127
  kind,
127
128
  description: extractMarkdownProps(jsDoc?.description ?? "").description,
128
129
  content: _buildJSDocContent(jsDoc?.description, jsDoc?.unhandled),
129
- signatures: signature ? [signature] : undefined,
130
+ signatures,
130
131
  params,
131
132
  returns,
132
133
  throws,
@@ -200,30 +201,60 @@ function _getKind(statement) {
200
201
  if (ts.isVariableStatement(statement))
201
202
  return "constant";
202
203
  }
203
- /** Get the text signature of a statement — a complete, name-prefixed declaration usable as a heading. */
204
- function _getSignature(statement, source, name) {
204
+ /** Get the text signature(s) of a statement — complete, name-prefixed declarations usable as headings. */
205
+ function _getSignatures(statement, source, name) {
205
206
  if (ts.isFunctionDeclaration(statement)) {
206
207
  const params = statement.parameters.map(p => p.getText(source)).join(", ");
207
208
  const ret = statement.type ? statement.type.getText(source) : "void";
208
- return `${name}(${params}): ${ret}`;
209
+ return [`${name}(${params}): ${ret}`];
210
+ }
211
+ if (ts.isClassDeclaration(statement)) {
212
+ // Synthesise `new ClassName<…>(…)` constructor signatures so a class page reads like a function's.
213
+ return _getConstructorSignatures(statement, source, name);
209
214
  }
210
215
  if (ts.isInterfaceDeclaration(statement)) {
211
216
  // Emit `{ member; member }` — the same shape a `type` object body produces, distinguished only by the `kind` badge.
212
217
  const members = statement.members.map(m => m.getText(source).replace(/;\s*$/, "").trim()).join("; ");
213
- return members ? `{ ${members} }` : "{}";
218
+ return [members ? `{ ${members} }` : "{}"];
214
219
  }
215
220
  if (ts.isTypeAliasDeclaration(statement)) {
216
221
  // Emit only the type body (e.g. `{ a: string }` or `string | null`) — the alias name is already the page title.
217
- return statement.type.getText(source);
222
+ return [statement.type.getText(source)];
218
223
  }
219
224
  if (ts.isVariableStatement(statement)) {
220
225
  const declaration = statement.declarationList.declarations[0];
221
226
  if (declaration?.type)
222
- return `${name}: ${declaration.type.getText(source)}`;
227
+ return [`${name}: ${declaration.type.getText(source)}`];
223
228
  }
224
229
  }
225
- /** Extract parameters from a function or method declaration, enriched with JSDoc `@param` descriptions. */
230
+ /** Render a class's generic parameter names as `<P, R>` (names only, no constraints), or `""` when non-generic. */
231
+ function _getTypeParamNames(statement, source) {
232
+ const { typeParameters } = statement;
233
+ if (!typeParameters?.length)
234
+ return "";
235
+ return `<${typeParameters.map(t => t.name.getText(source)).join(", ")}>`;
236
+ }
237
+ /** Get a class's constructor declarations (overload signatures + implementation), in source order. */
238
+ function _getConstructors(statement) {
239
+ return statement.members.filter(ts.isConstructorDeclaration);
240
+ }
241
+ /**
242
+ * Synthesise `new ClassName<…>(…)` signatures from a class's constructor declaration(s).
243
+ * - Generics are included so the reader sees how they're inferred from the arguments (e.g. `new MockAPIProvider<P, R>(…)`).
244
+ * - Multiple constructor overloads each become a signature, same as function overloads.
245
+ * - A class with no explicit constructor yields a single degenerate `new ClassName()` signature.
246
+ */
247
+ function _getConstructorSignatures(statement, source, name) {
248
+ const generics = _getTypeParamNames(statement, source);
249
+ const constructors = _getConstructors(statement);
250
+ if (!constructors.length)
251
+ return [`new ${name}${generics}()`];
252
+ return constructors.map(c => `new ${name}${generics}(${c.parameters.map(p => p.getText(source)).join(", ")})`);
253
+ }
254
+ /** Extract parameters from a function or class declaration, enriched with JSDoc `@param` descriptions. */
226
255
  function _getParams(statement, source, jsDocParams) {
256
+ if (ts.isClassDeclaration(statement))
257
+ return _getConstructorParams(statement, source, jsDocParams);
227
258
  if (!ts.isFunctionDeclaration(statement))
228
259
  return;
229
260
  const params = statement.parameters.map(p => {
@@ -235,8 +266,34 @@ function _getParams(statement, source, jsDocParams) {
235
266
  });
236
267
  return params.length ? params : undefined;
237
268
  }
269
+ /**
270
+ * Extract a class's constructor parameters as `params`, mirroring the function shape.
271
+ * - Descriptions are sourced from the constructor's own `@param` first, falling back to the class-level `@param`.
272
+ * - Overloaded constructors contribute their parameters in source order, de-duplicated by identity (same as function overloads).
273
+ */
274
+ function _getConstructorParams(statement, source, classJsDocParams) {
275
+ let params;
276
+ for (const ctor of _getConstructors(statement)) {
277
+ const ctorJsDocParams = _getJSDoc(ctor, source)?.params;
278
+ const next = ctor.parameters.map(p => {
279
+ const name = p.name.getText(source);
280
+ const type = p.type?.getText(source);
281
+ const optional = !!p.questionToken || !!p.initializer;
282
+ // Constructor-level `@param` wins over the class-level `@param` on collision.
283
+ const description = ctorJsDocParams?.find(d => d.name === name)?.description ?? classJsDocParams?.find(d => d.name === name)?.description;
284
+ return { name, type, description, optional };
285
+ });
286
+ params = _concatUnique(params, next, p => `${p.name}\0${p.type}\0${p.description}\0${p.optional}`);
287
+ }
288
+ return params?.length ? params : undefined;
289
+ }
238
290
  /** Extract return entries — combines the signature return type with any `@returns` descriptions. */
239
- function _getReturns(statement, source, jsDocReturns) {
291
+ function _getReturns(statement, source, jsDocReturns, name) {
292
+ if (ts.isClassDeclaration(statement)) {
293
+ // A constructor returns an instance of the class, including its generics (e.g. `ChoiceSchema<T>`).
294
+ const type = `${name}${_getTypeParamNames(statement, source)}`;
295
+ return [{ type, description: jsDocReturns?.[0]?.description }];
296
+ }
240
297
  if (!ts.isFunctionDeclaration(statement))
241
298
  return jsDocReturns;
242
299
  const type = statement.type?.getText(source);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shelving",
3
- "version": "1.235.0",
3
+ "version": "1.236.0",
4
4
  "author": "Dave Houlbrooke <dave@shax.com>",
5
5
  "repository": {
6
6
  "type": "git",