typedoc 0.28.14 → 0.28.16

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.
Files changed (50) hide show
  1. package/dist/lib/cli.js +2 -1
  2. package/dist/lib/converter/comments/index.d.ts +3 -2
  3. package/dist/lib/converter/comments/index.js +69 -18
  4. package/dist/lib/converter/comments/parser.js +45 -21
  5. package/dist/lib/converter/comments/textParser.d.ts +13 -9
  6. package/dist/lib/converter/comments/textParser.js +21 -16
  7. package/dist/lib/converter/converter.js +1 -0
  8. package/dist/lib/converter/factories/signature.d.ts +1 -0
  9. package/dist/lib/converter/factories/signature.js +13 -0
  10. package/dist/lib/converter/jsdoc.d.ts +1 -1
  11. package/dist/lib/converter/jsdoc.js +33 -2
  12. package/dist/lib/converter/plugins/CommentPlugin.js +13 -0
  13. package/dist/lib/converter/plugins/ImplementsPlugin.d.ts +1 -0
  14. package/dist/lib/converter/plugins/ImplementsPlugin.js +23 -4
  15. package/dist/lib/converter/plugins/IncludePlugin.js +4 -0
  16. package/dist/lib/converter/plugins/SourcePlugin.js +3 -3
  17. package/dist/lib/converter/symbols.js +9 -22
  18. package/dist/lib/converter/utils/repository.js +3 -0
  19. package/dist/lib/internationalization/locales/en.cjs +1 -0
  20. package/dist/lib/internationalization/locales/en.d.cts +1 -0
  21. package/dist/lib/models/Comment.d.ts +1 -1
  22. package/dist/lib/models/Comment.js +1 -1
  23. package/dist/lib/models/DeclarationReflection.d.ts +7 -0
  24. package/dist/lib/models/DeclarationReflection.js +9 -0
  25. package/dist/lib/output/events.d.ts +2 -0
  26. package/dist/lib/output/events.js +1 -0
  27. package/dist/lib/output/plugins/JavascriptIndexPlugin.js +1 -1
  28. package/dist/lib/output/themes/MarkedPlugin.js +15 -8
  29. package/dist/lib/output/themes/default/partials/icon.js +1 -1
  30. package/dist/lib/output/themes/default/partials/member.js +1 -0
  31. package/dist/lib/output/themes/default/partials/moduleReflection.js +1 -0
  32. package/dist/lib/output/themes/default/partials/navigation.js +1 -1
  33. package/dist/lib/serialization/schema.d.ts +1 -1
  34. package/dist/lib/utils/entry-point.js +1 -1
  35. package/dist/lib/utils/options/declaration.d.ts +4 -0
  36. package/dist/lib/utils/options/sources/typedoc.js +1 -0
  37. package/dist/lib/utils/options/tsdoc-defaults.d.ts +2 -2
  38. package/dist/lib/utils/options/tsdoc-defaults.js +1 -1
  39. package/dist/lib/utils-common/jsx.elements.d.ts +1 -0
  40. package/dist/lib/utils-common/logger.d.ts +12 -1
  41. package/dist/lib/utils-common/logger.js +9 -0
  42. package/dist/lib/utils-common/set.d.ts +1 -0
  43. package/dist/lib/utils-common/set.js +7 -0
  44. package/dist/lib/validation/documentation.js +2 -2
  45. package/dist/lib/validation/exports.js +2 -2
  46. package/dist/lib/validation/links.js +7 -7
  47. package/package.json +16 -14
  48. package/static/style.css +3 -3
  49. package/tsdoc.json +0 -4
  50. package/typedoc-config.schema.json +941 -0
package/dist/lib/cli.js CHANGED
@@ -75,7 +75,8 @@ async function run(app) {
75
75
  }
76
76
  const preValidationWarnCount = app.logger.warningCount;
77
77
  app.validate(project);
78
- const hadValidationWarnings = app.logger.warningCount !== preValidationWarnCount;
78
+ const hadValidationWarnings = app.logger.warningCount !== preValidationWarnCount ||
79
+ app.logger.validationWarningCount != 0;
79
80
  if (app.logger.hasErrors()) {
80
81
  return ExitCodes.ValidationError;
81
82
  }
@@ -1,8 +1,8 @@
1
1
  import ts from "typescript";
2
2
  import { Comment, ReflectionKind } from "../../models/index.js";
3
- import type { CommentStyle, JsDocCompatibility } from "../../utils/options/declaration.js";
3
+ import type { CommentStyle, JsDocCompatibility, ValidationOptions } from "../../utils/options/declaration.js";
4
4
  import type { FileRegistry } from "../../models/FileRegistry.js";
5
- import { type Logger } from "#utils";
5
+ import { Logger } from "#utils";
6
6
  import type { Context } from "../context.js";
7
7
  export interface CommentParserConfig {
8
8
  blockTags: Set<string>;
@@ -13,6 +13,7 @@ export interface CommentParserConfig {
13
13
  suppressCommentWarningsInDeclarationFiles: boolean;
14
14
  useTsLinkResolution: boolean;
15
15
  commentStyle: CommentStyle;
16
+ validationOptions: ValidationOptions;
16
17
  }
17
18
  export interface CommentContext {
18
19
  config: CommentParserConfig;
@@ -4,7 +4,7 @@ import { lexBlockComment } from "./blockLexer.js";
4
4
  import { discoverComment, discoverFileComments, discoverNodeComment, discoverSignatureComment, } from "./discovery.js";
5
5
  import { lexLineComments } from "./lineLexer.js";
6
6
  import { parseComment } from "./parser.js";
7
- import { assertNever, i18n } from "#utils";
7
+ import { assertNever, i18n, Logger, setUnion } from "#utils";
8
8
  const jsDocCommentKinds = [
9
9
  ts.SyntaxKind.JSDocPropertyTag,
10
10
  ts.SyntaxKind.JSDocCallbackTag,
@@ -21,16 +21,10 @@ export function clearCommentCache() {
21
21
  commentCache = new WeakMap();
22
22
  commentDiscoveryId = 0;
23
23
  }
24
- function getCommentWithCache(discovered, context) {
24
+ function getCommentIgnoringCacheNoDiscoveryId(discovered, context) {
25
25
  if (!discovered)
26
26
  return;
27
27
  const { file, ranges, jsDoc } = discovered;
28
- const cache = commentCache.get(file) || new Map();
29
- if (cache.has(ranges[0].pos)) {
30
- const clone = cache.get(ranges[0].pos).clone();
31
- clone.inheritedFromParentDeclaration = discovered.inheritedFromParentDeclaration;
32
- return clone;
33
- }
34
28
  let comment;
35
29
  switch (ranges[0].kind) {
36
30
  case ts.SyntaxKind.MultiLineCommentTrivia:
@@ -42,8 +36,23 @@ function getCommentWithCache(discovered, context) {
42
36
  default:
43
37
  assertNever(ranges[0].kind);
44
38
  }
45
- comment.discoveryId = ++commentDiscoveryId;
46
39
  comment.inheritedFromParentDeclaration = discovered.inheritedFromParentDeclaration;
40
+ return comment;
41
+ }
42
+ function getCommentWithCache(discovered, context) {
43
+ if (!discovered)
44
+ return;
45
+ const { file, ranges } = discovered;
46
+ const cache = commentCache.get(file) || new Map();
47
+ if (cache.has(ranges[0].pos)) {
48
+ const clone = cache.get(ranges[0].pos).clone();
49
+ clone.inheritedFromParentDeclaration = discovered.inheritedFromParentDeclaration;
50
+ return clone;
51
+ }
52
+ const comment = getCommentIgnoringCacheNoDiscoveryId(discovered, context);
53
+ if (!comment)
54
+ return;
55
+ comment.discoveryId = ++commentDiscoveryId;
47
56
  cache.set(ranges[0].pos, comment);
48
57
  commentCache.set(file, cache);
49
58
  return comment.clone();
@@ -99,14 +108,24 @@ export function getNodeComment(node, moduleComment, context) {
99
108
  return getCommentImpl(discoverNodeComment(node, context.config.commentStyle), moduleComment, context);
100
109
  }
101
110
  export function getFileComment(file, context) {
111
+ const quietContext = {
112
+ ...context,
113
+ logger: new Logger(),
114
+ };
102
115
  for (const commentSource of discoverFileComments(file, context.config.commentStyle)) {
103
- const comment = getCommentWithCache(commentSource, context);
116
+ // First parse the comment without adding the parse to the cache
117
+ // and without logging any messages. If we end up not using this as
118
+ // a file comment we want to avoid parsing it with warnings here as
119
+ // it might be associated with a JSDoc parse which has special
120
+ // handling to allow modifier tags to be specified as {@mod}
121
+ // and if it gets added to the cache here we'll get unwanted warnings
122
+ const comment = getCommentIgnoringCacheNoDiscoveryId(commentSource, quietContext);
104
123
  if (comment?.getTag("@license") || comment?.getTag("@import")) {
105
124
  continue;
106
125
  }
107
126
  if (comment?.getTag("@module") ||
108
127
  comment?.hasModifier("@packageDocumentation")) {
109
- return comment;
128
+ return getCommentWithCache(commentSource, context);
110
129
  }
111
130
  return;
112
131
  }
@@ -127,6 +146,33 @@ function getConstructorParamPropertyComment(symbol, context) {
127
146
  export function getSignatureComment(declaration, context) {
128
147
  return getCommentImpl(discoverSignatureComment(declaration, context.checker, context.config.commentStyle), false, context);
129
148
  }
149
+ function buildJsDocCommentFromParts(declaration, parts, sourceComment, context) {
150
+ if (!parts) {
151
+ return undefined;
152
+ }
153
+ const comment = new Comment(Comment.cloneDisplayParts(parts));
154
+ comment.sourcePath = sourceComment.sourcePath;
155
+ for (let i = 0; i < comment.summary.length;) {
156
+ const part = comment.summary[i];
157
+ if (part.kind === "inline-tag" &&
158
+ !part.text.trim() &&
159
+ context.config.modifierTags.has(part.tag)) {
160
+ comment.modifierTags.add(part.tag);
161
+ comment.summary.splice(i, 1);
162
+ }
163
+ else if (part.kind === "inline-tag" &&
164
+ part.text.trim() &&
165
+ context.config.modifierTags.has(part.tag) &&
166
+ !context.config.inlineTags.has(part.tag)) {
167
+ context.logger.warn(i18n.inline_tag_0_not_parsed_as_modifier_tag_1(part.tag, part.text.trim()), declaration);
168
+ ++i;
169
+ }
170
+ else {
171
+ ++i;
172
+ }
173
+ }
174
+ return comment;
175
+ }
130
176
  export function getJsDocComment(declaration, context) {
131
177
  const file = declaration.getSourceFile();
132
178
  // First, get the whole comment. We know we'll need all of it.
@@ -134,6 +180,15 @@ export function getJsDocComment(declaration, context) {
134
180
  while (!ts.isJSDoc(parent)) {
135
181
  parent = parent.parent;
136
182
  }
183
+ // Build a custom context to allow modifier tags to be written as inline
184
+ // tags as otherwise there's no way to specify them here #2916 #3050
185
+ const contextWithInline = {
186
+ ...context,
187
+ config: {
188
+ ...context.config,
189
+ inlineTags: setUnion(context.config.inlineTags, context.config.modifierTags),
190
+ },
191
+ };
137
192
  // Then parse it.
138
193
  const comment = getCommentWithCache({
139
194
  file,
@@ -146,12 +201,10 @@ export function getJsDocComment(declaration, context) {
146
201
  ],
147
202
  jsDoc: parent,
148
203
  inheritedFromParentDeclaration: false,
149
- }, context);
204
+ }, contextWithInline);
150
205
  // And pull out the tag we actually care about.
151
206
  if (ts.isJSDocEnumTag(declaration)) {
152
- const result = new Comment(comment.getTag("@enum")?.content);
153
- result.sourcePath = comment.sourcePath;
154
- return result;
207
+ return buildJsDocCommentFromParts(declaration, comment.getTag("@enum")?.content, comment, context);
155
208
  }
156
209
  if (ts.isJSDocTemplateTag(declaration) &&
157
210
  declaration.comment &&
@@ -183,8 +236,6 @@ export function getJsDocComment(declaration, context) {
183
236
  }
184
237
  }
185
238
  else {
186
- const result = new Comment(Comment.cloneDisplayParts(tag.content));
187
- result.sourcePath = comment.sourcePath;
188
- return result;
239
+ return buildJsDocCommentFromParts(declaration, tag.content, comment, context);
189
240
  }
190
241
  }
@@ -45,12 +45,12 @@ export function parseComment(tokens, file, context) {
45
45
  const tok = lexer.done() || lexer.peek();
46
46
  const comment = new Comment();
47
47
  comment.sourcePath = file.fileName;
48
- comment.summary = blockContent(comment, lexer, context.config, i18n, warningImpl, context.files);
48
+ comment.summary = blockContent(comment, lexer, context.config, warningImpl, validationWarningImpl, context.files);
49
49
  while (!lexer.done()) {
50
- comment.blockTags.push(blockTag(comment, lexer, context.config, i18n, warningImpl, context.files));
50
+ comment.blockTags.push(blockTag(comment, lexer, context.config, warningImpl, validationWarningImpl, context.files));
51
51
  }
52
52
  const tok2 = tok;
53
- postProcessComment(comment, i18n, () => `${nicePath(file.fileName)}:${file.getLineAndCharacterOfPosition(tok2.pos).line + 1}`, (message) => context.logger.warn(message));
53
+ postProcessComment(comment, () => `${nicePath(file.fileName)}:${file.getLineAndCharacterOfPosition(tok2.pos).line + 1}`, (message) => context.logger.warn(message));
54
54
  return comment;
55
55
  function warningImpl(message, token) {
56
56
  if (context.config.suppressCommentWarningsInDeclarationFiles &&
@@ -59,6 +59,13 @@ export function parseComment(tokens, file, context) {
59
59
  }
60
60
  context.logger.warn(message, token.pos, file);
61
61
  }
62
+ function validationWarningImpl(message, token) {
63
+ if (context.config.suppressCommentWarningsInDeclarationFiles &&
64
+ hasDeclarationFileExtension(file.fileName)) {
65
+ return;
66
+ }
67
+ context.logger.validationWarning(message, token.pos, file);
68
+ }
62
69
  }
63
70
  /**
64
71
  * Intended for parsing markdown documents. This only parses code blocks and
@@ -95,13 +102,22 @@ export function parseCommentString(tokens, config, file, logger, files) {
95
102
  case TokenSyntaxKind.Text:
96
103
  case TokenSyntaxKind.Tag:
97
104
  case TokenSyntaxKind.CloseBrace:
98
- textContent(file.fileName, next, i18n, (msg, token) => logger.warn(msg, token.pos, file), content, files, atNewLine, reentry);
105
+ textContent({
106
+ sourcePath: file.fileName,
107
+ token: next,
108
+ warning: (msg, token) => logger.warn(msg, token.pos, file),
109
+ validationWarning: (msg, token) => logger.validationWarning(msg, token.pos, file),
110
+ files,
111
+ atNewLine,
112
+ validationOptions: config.validationOptions,
113
+ },
114
+ /* out */ content, reentry);
99
115
  break;
100
116
  case TokenSyntaxKind.Code:
101
117
  content.push({ kind: "code", text: next.text });
102
118
  break;
103
119
  case TokenSyntaxKind.OpenBrace:
104
- inlineTag(lexer, content, suppressWarningsConfig, i18n, (message, token) => logger.warn(message, token.pos, file));
120
+ inlineTag(lexer, content, suppressWarningsConfig, (message, token) => logger.warn(message, token.pos, file));
105
121
  consume = false;
106
122
  break;
107
123
  default:
@@ -161,7 +177,7 @@ function makeCodeBlock(text) {
161
177
  * Loop over comment, produce lint warnings, and set tag names for tags
162
178
  * which have them.
163
179
  */
164
- function postProcessComment(comment, i18n, getPosition, warning) {
180
+ function postProcessComment(comment, getPosition, warning) {
165
181
  for (const tag of comment.blockTags) {
166
182
  if (HAS_USER_IDENTIFIER.includes(tag.tag) && tag.content.length) {
167
183
  const first = tag.content[0];
@@ -211,7 +227,7 @@ function postProcessComment(comment, i18n, getPosition, warning) {
211
227
  }
212
228
  }
213
229
  const aliasedTags = new Map([["@return", "@returns"]]);
214
- function blockTag(comment, lexer, config, i18n, warning, files) {
230
+ function blockTag(comment, lexer, config, warning, validationWarning, files) {
215
231
  const blockTag = lexer.take();
216
232
  ok(blockTag.kind === TokenSyntaxKind.Tag, "blockTag called not at the start of a block tag."); // blockContent is broken if this fails.
217
233
  if (!config.blockTags.has(blockTag.text)) {
@@ -220,7 +236,7 @@ function blockTag(comment, lexer, config, i18n, warning, files) {
220
236
  const tagName = aliasedTags.get(blockTag.text) || blockTag.text;
221
237
  let content;
222
238
  if (tagName === "@example") {
223
- return exampleBlock(comment, lexer, config, i18n, warning, files);
239
+ return exampleBlock(comment, lexer, config, warning, validationWarning, files);
224
240
  }
225
241
  let typeAnnotation;
226
242
  if (!lexer.done() &&
@@ -234,10 +250,10 @@ function blockTag(comment, lexer, config, i18n, warning, files) {
234
250
  }
235
251
  if (["@default", "@defaultValue"].includes(tagName) &&
236
252
  config.jsDocCompatibility.defaultTag) {
237
- content = defaultBlockContent(comment, lexer, config, i18n, warning, files);
253
+ content = defaultBlockContent(comment, lexer, config, warning, validationWarning, files);
238
254
  }
239
255
  else {
240
- content = blockContent(comment, lexer, config, i18n, warning, files);
256
+ content = blockContent(comment, lexer, config, warning, validationWarning, files);
241
257
  }
242
258
  const tag = new CommentTag(tagName, content);
243
259
  if (typeAnnotation) {
@@ -249,14 +265,14 @@ function blockTag(comment, lexer, config, i18n, warning, files) {
249
265
  * The `@default` tag gets a special case because otherwise we will produce many warnings
250
266
  * about unescaped/mismatched/missing braces in legacy JSDoc comments
251
267
  */
252
- function defaultBlockContent(comment, lexer, config, i18n, warning, files) {
268
+ function defaultBlockContent(comment, lexer, config, warning, validationWarning, files) {
253
269
  lexer.mark();
254
270
  const tempRegistry = new FileRegistry();
255
- const content = blockContent(comment, lexer, config, i18n, () => { }, tempRegistry);
271
+ const content = blockContent(comment, lexer, config, () => { }, () => { }, tempRegistry);
256
272
  const end = lexer.done() || lexer.peek();
257
273
  lexer.release();
258
274
  if (content.some((part) => part.kind === "code" || part.kind === "inline-tag")) {
259
- return blockContent(comment, lexer, config, i18n, warning, files);
275
+ return blockContent(comment, lexer, config, warning, validationWarning, files);
260
276
  }
261
277
  const tokens = [];
262
278
  while ((lexer.done() || lexer.peek()) !== end) {
@@ -279,10 +295,10 @@ function defaultBlockContent(comment, lexer, config, i18n, warning, files) {
279
295
  *
280
296
  * In TSDoc, we also want to treat the first line of the block as the example name.
281
297
  */
282
- function exampleBlock(comment, lexer, config, i18n, warning, files) {
298
+ function exampleBlock(comment, lexer, config, warning, validationWarning, files) {
283
299
  lexer.mark();
284
300
  const tempRegistry = new FileRegistry();
285
- const content = blockContent(comment, lexer, config, i18n, () => { }, tempRegistry);
301
+ const content = blockContent(comment, lexer, config, () => { }, () => { }, tempRegistry);
286
302
  const end = lexer.done() || lexer.peek();
287
303
  lexer.release();
288
304
  if (!config.jsDocCompatibility.exampleTag ||
@@ -323,7 +339,7 @@ function exampleBlock(comment, lexer, config, i18n, warning, files) {
323
339
  assertNever(next.kind);
324
340
  }
325
341
  }
326
- const content = blockContent(comment, lexer, config, i18n, warning, files);
342
+ const content = blockContent(comment, lexer, config, warning, validationWarning, files);
327
343
  const tag = new CommentTag("@example", content);
328
344
  if (exampleName.trim()) {
329
345
  tag.name = exampleName.trim();
@@ -362,7 +378,7 @@ function exampleBlock(comment, lexer, config, i18n, warning, files) {
362
378
  * If you change this, also look at parseCommentString as it
363
379
  * likely needs similar modifications to ensure parsing is consistent.
364
380
  */
365
- function blockContent(comment, lexer, config, i18n, warning, files) {
381
+ function blockContent(comment, lexer, config, warning, validationWarning, files) {
366
382
  const content = [];
367
383
  let atNewLine = true;
368
384
  const reentry = new TextParserReentryState();
@@ -375,8 +391,16 @@ function blockContent(comment, lexer, config, i18n, warning, files) {
375
391
  content.push({ kind: "text", text: next.text });
376
392
  break;
377
393
  case TokenSyntaxKind.Text:
378
- textContent(comment.sourcePath, next, i18n, warning,
379
- /*out*/ content, files, atNewLine, reentry);
394
+ textContent({
395
+ sourcePath: comment.sourcePath,
396
+ token: next,
397
+ files,
398
+ atNewLine,
399
+ warning,
400
+ validationWarning,
401
+ validationOptions: config.validationOptions,
402
+ },
403
+ /*out*/ content, reentry);
380
404
  break;
381
405
  case TokenSyntaxKind.Code:
382
406
  content.push({ kind: "code", text: next.text });
@@ -414,7 +438,7 @@ function blockContent(comment, lexer, config, i18n, warning, files) {
414
438
  content.push({ kind: "text", text: next.text });
415
439
  break;
416
440
  case TokenSyntaxKind.OpenBrace:
417
- inlineTag(lexer, content, config, i18n, warning);
441
+ inlineTag(lexer, content, config, warning);
418
442
  consume = false;
419
443
  break;
420
444
  default:
@@ -451,7 +475,7 @@ function blockContent(comment, lexer, config, i18n, warning, files) {
451
475
  }
452
476
  return content;
453
477
  }
454
- function inlineTag(lexer, block, config, i18n, warning) {
478
+ function inlineTag(lexer, block, config, warning) {
455
479
  const openBrace = lexer.take();
456
480
  // Now skip whitespace to grab the tag name.
457
481
  // If the first non-whitespace text after the brace isn't a tag,
@@ -1,15 +1,18 @@
1
- /**
2
- * Parser to handle plain text markdown.
3
- *
4
- * Responsible for recognizing relative paths within the text and turning
5
- * them into references.
6
- * @module
7
- */
8
- import type { TranslationProxy } from "../../internationalization/index.js";
9
1
  import type { CommentDisplayPart } from "../../models/index.js";
10
2
  import type { FileRegistry } from "../../models/FileRegistry.js";
3
+ import { type ValidationOptions } from "#node-utils";
11
4
  import { type Token } from "./lexer.js";
12
5
  import type { NormalizedPath, TranslatedString } from "#utils";
6
+ interface TextParserData {
7
+ sourcePath: NormalizedPath;
8
+ token: Token;
9
+ pos: number;
10
+ warning: (msg: TranslatedString, token: Token) => void;
11
+ validationWarning: (msg: TranslatedString, token: Token) => void;
12
+ files: FileRegistry;
13
+ atNewLine: boolean;
14
+ validationOptions: ValidationOptions;
15
+ }
13
16
  /**
14
17
  * This is incredibly unfortunate. The comment lexer owns the responsibility
15
18
  * for splitting up text into text/code, this is totally fine for HTML links
@@ -26,4 +29,5 @@ export declare class TextParserReentryState {
26
29
  * Look for relative links within a piece of text and add them to the {@link FileRegistry}
27
30
  * so that they can be correctly resolved during rendering.
28
31
  */
29
- export declare function textContent(sourcePath: NormalizedPath, token: Token, i18n: TranslationProxy, warning: (msg: TranslatedString, token: Token) => void, outContent: CommentDisplayPart[], files: FileRegistry, atNewLine: boolean, reentry: TextParserReentryState): void;
32
+ export declare function textContent(parserData: Omit<TextParserData, "pos">, outContent: CommentDisplayPart[], reentry: TextParserReentryState): void;
33
+ export {};
@@ -1,3 +1,11 @@
1
+ /**
2
+ * Parser to handle plain text markdown.
3
+ *
4
+ * Responsible for recognizing relative paths within the text and turning
5
+ * them into references.
6
+ * @module
7
+ */
8
+ import { i18n } from "#utils";
1
9
  import { HtmlAttributeParser, ParserState } from "#node-utils";
2
10
  import { TokenSyntaxKind } from "./lexer.js";
3
11
  import MarkdownIt from "markdown-it";
@@ -34,42 +42,38 @@ export class TextParserReentryState {
34
42
  * Look for relative links within a piece of text and add them to the {@link FileRegistry}
35
43
  * so that they can be correctly resolved during rendering.
36
44
  */
37
- export function textContent(sourcePath, token, i18n, warning, outContent, files, atNewLine, reentry) {
45
+ export function textContent(parserData, outContent, reentry) {
38
46
  let lastPartEnd = 0;
39
47
  let canEndMarkdownLink = true;
40
48
  const data = {
41
- sourcePath,
42
- token,
49
+ ...parserData,
43
50
  pos: 0, // relative to the token
44
- warning,
45
- files: files,
46
- atNewLine,
47
51
  };
48
52
  function addRef(ref) {
49
53
  canEndMarkdownLink = true;
50
54
  outContent.push({
51
55
  kind: "text",
52
- text: token.text.slice(lastPartEnd, ref.pos),
56
+ text: data.token.text.slice(lastPartEnd, ref.pos),
53
57
  });
54
58
  const link = {
55
59
  kind: "relative-link",
56
- text: token.text.slice(ref.pos, ref.end),
60
+ text: data.token.text.slice(ref.pos, ref.end),
57
61
  target: ref.target,
58
62
  targetAnchor: ref.targetAnchor,
59
63
  };
60
64
  outContent.push(link);
61
65
  lastPartEnd = ref.end;
62
66
  data.pos = ref.end;
63
- if (!ref.target) {
64
- warning(i18n.relative_path_0_is_not_a_file_and_will_not_be_copied_to_output(token.text.slice(ref.pos, ref.end)), {
67
+ if (!ref.target && data.validationOptions.invalidPath) {
68
+ data.validationWarning(i18n.relative_path_0_is_not_a_file_and_will_not_be_copied_to_output(data.token.text.slice(ref.pos, ref.end)), {
65
69
  kind: TokenSyntaxKind.Text,
66
70
  // ref.pos is relative to the token, but this pos is relative to the file.
67
- pos: token.pos + ref.pos,
68
- text: token.text.slice(ref.pos, ref.end),
71
+ pos: data.token.pos + ref.pos,
72
+ text: data.token.text.slice(ref.pos, ref.end),
69
73
  });
70
74
  }
71
75
  }
72
- while (data.pos < token.text.length) {
76
+ while (data.pos < data.token.text.length) {
73
77
  if (canEndMarkdownLink) {
74
78
  const link = checkMarkdownLink(data, reentry);
75
79
  if (link) {
@@ -92,14 +96,14 @@ export function textContent(sourcePath, token, i18n, warning, outContent, files,
92
96
  }
93
97
  continue;
94
98
  }
95
- const atNewLine = token.text[data.pos] === "\n";
99
+ const atNewLine = data.token.text[data.pos] === "\n";
96
100
  data.atNewLine = atNewLine;
97
101
  if (atNewLine && !reentry.withinLinkDest)
98
102
  canEndMarkdownLink = true;
99
103
  ++data.pos;
100
104
  }
101
- if (lastPartEnd !== token.text.length) {
102
- outContent.push({ kind: "text", text: token.text.slice(lastPartEnd) });
105
+ if (lastPartEnd !== data.token.text.length) {
106
+ outContent.push({ kind: "text", text: data.token.text.slice(lastPartEnd) });
103
107
  }
104
108
  }
105
109
  /**
@@ -245,6 +249,7 @@ function checkTagLink(data) {
245
249
  if (token.text.startsWith("<link ", pos)) {
246
250
  data.pos += 4;
247
251
  return checkAttributes(data, {
252
+ // cspell:words imagesrcset
248
253
  imagesrcset: checkAttributeSrcSet,
249
254
  });
250
255
  }
@@ -614,6 +614,7 @@ let Converter = (() => {
614
614
  suppressCommentWarningsInDeclarationFiles: this.application.options.getValue("suppressCommentWarningsInDeclarationFiles"),
615
615
  useTsLinkResolution: this.application.options.getValue("useTsLinkResolution"),
616
616
  commentStyle: this.application.options.getValue("commentStyle"),
617
+ validationOptions: this.application.options.getValue("validation"),
617
618
  };
618
619
  // Can't be included in options because the TSDoc parser blows up if we do.
619
620
  // TypeDoc supports it as one, so it should always be included here.
@@ -1,6 +1,7 @@
1
1
  import ts from "typescript";
2
2
  import { ParameterReflection, type Reflection, ReflectionKind, SignatureReflection, TypeParameterReflection } from "../../models/index.js";
3
3
  import type { Context } from "../context.js";
4
+ export declare function convertConstructSignatures(context: Context, symbol: ts.Symbol): void;
4
5
  export declare function createSignature(context: Context, kind: ReflectionKind.CallSignature | ReflectionKind.ConstructorSignature | ReflectionKind.GetSignature | ReflectionKind.SetSignature, signature: ts.Signature, symbol: ts.Symbol | undefined, declaration?: ts.SignatureDeclaration | ts.JSDocSignature): void;
5
6
  /**
6
7
  * Special cased constructor factory for functions tagged with `@class`
@@ -4,6 +4,19 @@ import { DeclarationReflection, IntrinsicType, ParameterReflection, PredicateTyp
4
4
  import { ConverterEvents } from "../converter-events.js";
5
5
  import { convertDefaultValue } from "../convert-expression.js";
6
6
  import { removeUndefined } from "../utils/reflections.js";
7
+ export function convertConstructSignatures(context, symbol) {
8
+ const type = context.checker.getDeclaredTypeOfSymbol(symbol);
9
+ // These get added as a "constructor" member of this interface. This is a problem... but nobody
10
+ // has complained yet. We really ought to have a constructSignatures property on the reflection instead.
11
+ const constructSignatures = context.checker.getSignaturesOfType(type, ts.SignatureKind.Construct);
12
+ if (constructSignatures.length) {
13
+ const constructMember = new DeclarationReflection("constructor", ReflectionKind.Constructor, context.scope);
14
+ context.postReflectionCreation(constructMember, symbol, void 0);
15
+ context.finalizeDeclarationReflection(constructMember);
16
+ const constructContext = context.withScope(constructMember);
17
+ constructSignatures.forEach((sig) => createSignature(constructContext, ReflectionKind.ConstructorSignature, sig, symbol));
18
+ }
19
+ }
7
20
  export function createSignature(context, kind, signature, symbol, declaration) {
8
21
  assert(context.scope instanceof DeclarationReflection);
9
22
  declaration ||= signature.getDeclaration();
@@ -1,4 +1,4 @@
1
1
  import ts from "typescript";
2
2
  import type { Context } from "./context.js";
3
- export declare function convertJsDocAlias(context: Context, symbol: ts.Symbol, declaration: ts.JSDocTypedefTag | ts.JSDocEnumTag, exportSymbol?: ts.Symbol): void;
3
+ export declare function convertJsDocAlias(context: Context, symbol: ts.Symbol, declaration: ts.JSDocTypedefTag | ts.JSDocEnumTag, exportSymbol?: ts.Symbol): undefined;
4
4
  export declare function convertJsDocCallback(context: Context, symbol: ts.Symbol, declaration: ts.JSDocCallbackTag, exportSymbol?: ts.Symbol): void;
@@ -5,13 +5,44 @@ import { ok } from "assert";
5
5
  import ts from "typescript";
6
6
  import { DeclarationReflection, IntrinsicType, ReflectionKind, ReflectionType, SignatureReflection, } from "../models/index.js";
7
7
  import { ConverterEvents } from "./converter-events.js";
8
- import { convertParameterNodes, convertTemplateParameterNodes } from "./factories/signature.js";
8
+ import { convertConstructSignatures, convertParameterNodes, convertTemplateParameterNodes, createSignature, } from "./factories/signature.js";
9
+ import { i18n } from "#utils";
10
+ import { convertIndexSignatures } from "./factories/index-signature.js";
11
+ // This is almost convertTypeAliasAsInterface, but unfortunately needs to be separate
12
+ // due to type parameters being different in JSDoc comments
13
+ function convertJsDocAliasAsInterface(context, symbol, exportSymbol, declaration) {
14
+ const reflection = context.createDeclarationReflection(ReflectionKind.Interface, symbol, exportSymbol);
15
+ context.finalizeDeclarationReflection(reflection);
16
+ const rc = context.withScope(reflection);
17
+ const type = context.checker.getTypeAtLocation(declaration);
18
+ if (type.getFlags() & ts.TypeFlags.Union) {
19
+ context.logger.warn(i18n.converting_union_as_interface(), declaration);
20
+ }
21
+ // Interfaces have properties
22
+ for (const prop of type.getProperties()) {
23
+ context.converter.convertSymbol(rc, prop);
24
+ }
25
+ // And type parameters
26
+ convertTemplateParameters(rc, declaration.parent);
27
+ // And maybe call signatures
28
+ context.checker
29
+ .getSignaturesOfType(type, ts.SignatureKind.Call)
30
+ .forEach((sig) => createSignature(rc, ReflectionKind.CallSignature, sig, symbol));
31
+ // And maybe constructor signatures
32
+ convertConstructSignatures(rc, symbol);
33
+ // And finally, index signatures
34
+ convertIndexSignatures(rc, type);
35
+ }
9
36
  export function convertJsDocAlias(context, symbol, declaration, exportSymbol) {
10
37
  if (declaration.typeExpression &&
11
38
  ts.isJSDocTypeLiteral(declaration.typeExpression)) {
12
39
  convertJsDocInterface(context, declaration, symbol, exportSymbol);
13
40
  return;
14
41
  }
42
+ const comment = context.getJsDocComment(declaration);
43
+ if (comment?.hasModifier("@interface")) {
44
+ return convertJsDocAliasAsInterface(context, symbol, exportSymbol, declaration);
45
+ }
15
46
  // If the typedef tag is just referring to another type-space symbol, with no type parameters
16
47
  // or appropriate forwarding type parameters, then we treat it as a re-export instead of creating
17
48
  // a type alias with an import type.
@@ -21,7 +52,7 @@ export function convertJsDocAlias(context, symbol, declaration, exportSymbol) {
21
52
  return;
22
53
  }
23
54
  const reflection = context.createDeclarationReflection(ReflectionKind.TypeAlias, symbol, exportSymbol);
24
- reflection.comment = context.getJsDocComment(declaration);
55
+ reflection.comment = comment;
25
56
  reflection.type = context.converter.convertType(context.withScope(reflection), declaration.typeExpression?.type);
26
57
  convertTemplateParameters(context.withScope(reflection), declaration.parent);
27
58
  context.finalizeDeclarationReflection(reflection);
@@ -290,6 +290,8 @@ let CommentPlugin = (() => {
290
290
  * @param reflection The reflection that is currently processed.
291
291
  */
292
292
  onCreateTypeParameter(_context, reflection) {
293
+ if (reflection.comment)
294
+ return;
293
295
  const comment = reflection.parent?.comment;
294
296
  if (comment) {
295
297
  let tag = comment.getIdentifiedTag(reflection.name, "@typeParam");
@@ -306,6 +308,17 @@ let CommentPlugin = (() => {
306
308
  reflection.comment = new Comment(tag.content);
307
309
  reflection.comment.sourcePath = comment.sourcePath;
308
310
  removeIfPresent(comment.blockTags, tag);
311
+ return;
312
+ }
313
+ }
314
+ // #3031 if this is a class constructor, also check for type parameters
315
+ // that live on the class itself and potentially copy their comment.
316
+ if (reflection.parent?.kindOf(ReflectionKind.ConstructorSignature) &&
317
+ reflection.parent.parent?.kindOf(ReflectionKind.Constructor)) {
318
+ const cls = reflection.parent.parent.parent;
319
+ const typeParam = cls.typeParameters?.find(param => param.name === reflection.name);
320
+ if (typeParam?.comment) {
321
+ reflection.comment = typeParam.comment.clone();
309
322
  }
310
323
  }
311
324
  }
@@ -14,6 +14,7 @@ export declare class ImplementsPlugin extends ConverterComponent {
14
14
  */
15
15
  private analyzeImplements;
16
16
  private analyzeInheritance;
17
+ private cleanUpImplements;
17
18
  private onResolveEnd;
18
19
  private onRevive;
19
20
  private resolve;