typedoc 0.28.7 → 0.28.8

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 (31) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/lib/converter/comments/blockLexer.d.ts +2 -1
  3. package/dist/lib/converter/comments/blockLexer.js +6 -5
  4. package/dist/lib/converter/comments/index.d.ts +20 -5
  5. package/dist/lib/converter/comments/index.js +26 -23
  6. package/dist/lib/converter/comments/lineLexer.js +1 -1
  7. package/dist/lib/converter/comments/parser.d.ts +2 -2
  8. package/dist/lib/converter/comments/parser.js +6 -6
  9. package/dist/lib/converter/comments/rawLexer.js +1 -1
  10. package/dist/lib/converter/comments/textParser.js +93 -19
  11. package/dist/lib/converter/context.d.ts +10 -0
  12. package/dist/lib/converter/context.js +30 -10
  13. package/dist/lib/converter/factories/signature.js +1 -2
  14. package/dist/lib/converter/factories/symbol-id.d.ts +1 -1
  15. package/dist/lib/converter/factories/symbol-id.js +2 -1
  16. package/dist/lib/converter/jsdoc.js +1 -2
  17. package/dist/lib/converter/plugins/GroupPlugin.d.ts +1 -1
  18. package/dist/lib/converter/plugins/ImplementsPlugin.js +49 -11
  19. package/dist/lib/converter/types.js +2 -3
  20. package/dist/lib/internationalization/locales/en.cjs +1 -0
  21. package/dist/lib/internationalization/locales/en.d.cts +1 -0
  22. package/dist/lib/serialization/schema.d.ts +1 -1
  23. package/dist/lib/utils/options/declaration.d.ts +17 -9
  24. package/dist/lib/utils/options/declaration.js +38 -9
  25. package/dist/lib/utils/options/readers/arguments.js +2 -0
  26. package/dist/lib/utils/options/sources/typedoc.js +1 -1
  27. package/dist/lib/utils/plugins.d.ts +2 -1
  28. package/dist/lib/utils/plugins.js +26 -17
  29. package/dist/lib/utils-common/path.d.ts +6 -0
  30. package/dist/lib/utils-common/validation.js +1 -1
  31. package/package.json +1 -1
package/dist/index.d.ts CHANGED
@@ -41,7 +41,7 @@ export type { Icons, NavigationElement, PageDefinition, PageHeading, RendererEve
41
41
  export { Outputs } from "./lib/output/output.js";
42
42
  export { ArgumentsReader, CommentStyle, EntryPointStrategy, normalizePath, Option, OptionDefaults, Options, PackageJsonReader, ParameterHint, ParameterType, TSConfigReader, TypeDocReader, ValidatingFileRegistry, } from "./lib/utils/index.js";
43
43
  export type { ArrayDeclarationOption, BooleanDeclarationOption, DeclarationOption, DeclarationOptionBase, DeclarationOptionToOptionType, DocumentationEntryPoint, FancyConsoleLogger, FlagsDeclarationOption, JsDocCompatibility, KeyToDeclaration, ManuallyValidatedOption, MapDeclarationOption, MixedDeclarationOption, NumberDeclarationOption, ObjectDeclarationOption, OptionsReader, OutputSpecification, ParameterTypeToOptionTypeMap, SortStrategy, StringDeclarationOption, TypeDocOptionMap, TypeDocOptions, TypeDocOptionValues, ValidationOptions, } from "./lib/utils/index.js";
44
- export { type ComponentPath, ConsoleLogger, type DeclarationReference, type EnumKeys, EventDispatcher, EventHooks, type GlobString, i18n, JSX, Logger, LogLevel, type Meaning, type MeaningKeyword, type MinimalNode, MinimalSourceFile, type NormalizedPath, type NormalizedPathOrModule, type SymbolReference, type TranslatedString, translateTagName, } from "#utils";
44
+ export { type ComponentPath, ConsoleLogger, type DeclarationReference, type EnumKeys, EventDispatcher, EventHooks, type GlobString, i18n, JSX, Logger, LogLevel, type Meaning, type MeaningKeyword, type MinimalNode, MinimalSourceFile, type NormalizedPath, type NormalizedPathOrModule, type NormalizedPathOrModuleOrFunction, type SymbolReference, type TranslatedString, translateTagName, } from "#utils";
45
45
  export { type Deserializable, Deserializer, type DeserializerComponent, JSONOutput, SerializeEvent, Serializer, type SerializerComponent, type SerializerEvents, } from "./lib/serialization/index.js";
46
46
  export * as Internationalization from "./lib/internationalization/index.js";
47
47
  export type { TranslatableStrings } from "./lib/internationalization/internationalization.js";
@@ -1,3 +1,4 @@
1
1
  import ts from "typescript";
2
2
  import { type Token } from "./lexer.js";
3
- export declare function lexBlockComment(file: string, pos?: number, end?: number, jsDoc?: ts.JSDoc | undefined, checker?: ts.TypeChecker | undefined): Generator<Token, undefined, undefined>;
3
+ import type { Context } from "../context.js";
4
+ export declare function lexBlockComment(file: string, pos?: number, end?: number, createSymbolId?: Context["createSymbolId"], jsDoc?: ts.JSDoc | undefined, checker?: ts.TypeChecker | undefined): Generator<Token, undefined, undefined>;
@@ -1,11 +1,12 @@
1
1
  import ts from "typescript";
2
2
  import { TokenSyntaxKind } from "./lexer.js";
3
3
  import { resolveAliasedSymbol } from "../utils/symbols.js";
4
- import { createSymbolId } from "../factories/symbol-id.js";
5
- export function* lexBlockComment(file, pos = 0, end = file.length, jsDoc = undefined, checker = undefined) {
4
+ export function* lexBlockComment(file, pos = 0, end = file.length, createSymbolId = () => {
5
+ throw new Error("unreachable");
6
+ }, jsDoc = undefined, checker = undefined) {
6
7
  // Wrapper around our real lex function to collapse adjacent text tokens.
7
8
  let textToken;
8
- for (const token of lexBlockComment2(file, pos, end, getLinkTags(jsDoc), checker)) {
9
+ for (const token of lexBlockComment2(file, pos, end, getLinkTags(jsDoc), checker, createSymbolId)) {
9
10
  if (token.kind === TokenSyntaxKind.Text) {
10
11
  if (textToken) {
11
12
  textToken.text += token.text;
@@ -53,7 +54,7 @@ function getLinkTags(jsDoc) {
53
54
  }
54
55
  return result;
55
56
  }
56
- function* lexBlockComment2(file, pos, end, linkTags, checker) {
57
+ function* lexBlockComment2(file, pos, end, linkTags, checker, createSymbolId) {
57
58
  pos += 2; // Leading '/*'
58
59
  end -= 2; // Trailing '*/'
59
60
  if (pos < end && file[pos] === "*") {
@@ -208,7 +209,7 @@ function* lexBlockComment2(file, pos, end, linkTags, checker) {
208
209
  }
209
210
  if (lookahead !== pos + 1) {
210
211
  while (lookahead < end &&
211
- /[a-z0-9]/i.test(file[lookahead])) {
212
+ /[a-z0-9-]/i.test(file[lookahead])) {
212
213
  lookahead++;
213
214
  }
214
215
  }
@@ -3,6 +3,7 @@ import { Comment, ReflectionKind } from "../../models/index.js";
3
3
  import type { CommentStyle, JsDocCompatibility } from "../../utils/options/declaration.js";
4
4
  import type { FileRegistry } from "../../models/FileRegistry.js";
5
5
  import { type Logger } from "#utils";
6
+ import type { Context } from "../context.js";
6
7
  export interface CommentParserConfig {
7
8
  blockTags: Set<string>;
8
9
  inlineTags: Set<string>;
@@ -12,9 +13,23 @@ export interface CommentParserConfig {
12
13
  useTsLinkResolution: boolean;
13
14
  commentStyle: CommentStyle;
14
15
  }
16
+ export interface CommentContext {
17
+ config: CommentParserConfig;
18
+ logger: Logger;
19
+ checker: ts.TypeChecker;
20
+ files: FileRegistry;
21
+ createSymbolId: Context["createSymbolId"];
22
+ }
23
+ export interface CommentContextOptionalChecker {
24
+ config: CommentParserConfig;
25
+ logger: Logger;
26
+ checker?: ts.TypeChecker | undefined;
27
+ files: FileRegistry;
28
+ createSymbolId: Context["createSymbolId"];
29
+ }
15
30
  export declare function clearCommentCache(): void;
16
- export declare function getComment(symbol: ts.Symbol, kind: ReflectionKind, config: CommentParserConfig, logger: Logger, checker: ts.TypeChecker, files: FileRegistry): Comment | undefined;
17
- export declare function getNodeComment(node: ts.Node, moduleComment: boolean, config: CommentParserConfig, logger: Logger, checker: ts.TypeChecker | undefined, files: FileRegistry): Comment | undefined;
18
- export declare function getFileComment(file: ts.SourceFile, config: CommentParserConfig, logger: Logger, checker: ts.TypeChecker | undefined, files: FileRegistry): Comment | undefined;
19
- export declare function getSignatureComment(declaration: ts.SignatureDeclaration | ts.JSDocSignature, config: CommentParserConfig, logger: Logger, checker: ts.TypeChecker, files: FileRegistry): Comment | undefined;
20
- export declare function getJsDocComment(declaration: ts.JSDocPropertyLikeTag | ts.JSDocCallbackTag | ts.JSDocTypedefTag | ts.JSDocTemplateTag | ts.JSDocEnumTag, config: CommentParserConfig, logger: Logger, checker: ts.TypeChecker | undefined, files: FileRegistry): Comment | undefined;
31
+ export declare function getComment(symbol: ts.Symbol, kind: ReflectionKind, context: CommentContext): Comment | undefined;
32
+ export declare function getNodeComment(node: ts.Node, moduleComment: boolean, context: CommentContext): Comment | undefined;
33
+ export declare function getFileComment(file: ts.SourceFile, context: CommentContext): Comment | undefined;
34
+ export declare function getSignatureComment(declaration: ts.SignatureDeclaration | ts.JSDocSignature, context: CommentContext): Comment | undefined;
35
+ export declare function getJsDocComment(declaration: ts.JSDocPropertyLikeTag | ts.JSDocCallbackTag | ts.JSDocTypedefTag | ts.JSDocTemplateTag | ts.JSDocEnumTag, context: CommentContext): Comment | undefined;
@@ -21,7 +21,7 @@ export function clearCommentCache() {
21
21
  commentCache = new WeakMap();
22
22
  commentDiscoveryId = 0;
23
23
  }
24
- function getCommentWithCache(discovered, config, logger, checker, files) {
24
+ function getCommentWithCache(discovered, context) {
25
25
  if (!discovered)
26
26
  return;
27
27
  const { file, ranges, jsDoc } = discovered;
@@ -34,10 +34,10 @@ function getCommentWithCache(discovered, config, logger, checker, files) {
34
34
  let comment;
35
35
  switch (ranges[0].kind) {
36
36
  case ts.SyntaxKind.MultiLineCommentTrivia:
37
- comment = parseComment(lexBlockComment(file.text, ranges[0].pos, ranges[0].end, jsDoc, checker), config, file, logger, files);
37
+ comment = parseComment(lexBlockComment(file.text, ranges[0].pos, ranges[0].end, context.createSymbolId, jsDoc, context.checker), file, context);
38
38
  break;
39
39
  case ts.SyntaxKind.SingleLineCommentTrivia:
40
- comment = parseComment(lexLineComments(file.text, ranges), config, file, logger, files);
40
+ comment = parseComment(lexLineComments(file.text, ranges), file, context);
41
41
  break;
42
42
  default:
43
43
  assertNever(ranges[0].kind);
@@ -48,8 +48,11 @@ function getCommentWithCache(discovered, config, logger, checker, files) {
48
48
  commentCache.set(file, cache);
49
49
  return comment.clone();
50
50
  }
51
- function getCommentImpl(commentSource, config, logger, moduleComment, checker, files) {
52
- const comment = getCommentWithCache(commentSource, config, logger, config.useTsLinkResolution ? checker : undefined, files);
51
+ function getCommentImpl(commentSource, moduleComment, context) {
52
+ const comment = getCommentWithCache(commentSource, {
53
+ ...context,
54
+ checker: context.config.useTsLinkResolution ? context.checker : undefined,
55
+ });
53
56
  if (comment?.getTag("@import") || comment?.getTag("@license")) {
54
57
  return;
55
58
  }
@@ -70,15 +73,15 @@ function getCommentImpl(commentSource, config, logger, moduleComment, checker, f
70
73
  }
71
74
  return comment;
72
75
  }
73
- export function getComment(symbol, kind, config, logger, checker, files) {
76
+ export function getComment(symbol, kind, context) {
74
77
  const declarations = symbol.declarations || [];
75
78
  if (declarations.length &&
76
79
  declarations.every((d) => jsDocCommentKinds.includes(d.kind))) {
77
- return getJsDocComment(declarations[0], config, logger, checker, files);
80
+ return getJsDocComment(declarations[0], context);
78
81
  }
79
82
  const sf = declarations.find(ts.isSourceFile);
80
83
  if (sf) {
81
- return getFileComment(sf, config, logger, checker, files);
84
+ return getFileComment(sf, context);
82
85
  }
83
86
  const isModule = declarations.some((decl) => {
84
87
  if (ts.isModuleDeclaration(decl) && ts.isStringLiteral(decl.name)) {
@@ -86,18 +89,18 @@ export function getComment(symbol, kind, config, logger, checker, files) {
86
89
  }
87
90
  return false;
88
91
  });
89
- const comment = getCommentImpl(discoverComment(symbol, kind, logger, config.commentStyle, checker, !config.suppressCommentWarningsInDeclarationFiles), config, logger, isModule, checker, files);
92
+ const comment = getCommentImpl(discoverComment(symbol, kind, context.logger, context.config.commentStyle, context.checker, !context.config.suppressCommentWarningsInDeclarationFiles), isModule, context);
90
93
  if (!comment && kind === ReflectionKind.Property) {
91
- return getConstructorParamPropertyComment(symbol, config, logger, checker, files);
94
+ return getConstructorParamPropertyComment(symbol, context);
92
95
  }
93
96
  return comment;
94
97
  }
95
- export function getNodeComment(node, moduleComment, config, logger, checker, files) {
96
- return getCommentImpl(discoverNodeComment(node, config.commentStyle), config, logger, moduleComment, checker, files);
98
+ export function getNodeComment(node, moduleComment, context) {
99
+ return getCommentImpl(discoverNodeComment(node, context.config.commentStyle), moduleComment, context);
97
100
  }
98
- export function getFileComment(file, config, logger, checker, files) {
99
- for (const commentSource of discoverFileComments(file, config.commentStyle)) {
100
- const comment = getCommentWithCache(commentSource, config, logger, config.useTsLinkResolution ? checker : undefined, files);
101
+ export function getFileComment(file, context) {
102
+ for (const commentSource of discoverFileComments(file, context.config.commentStyle)) {
103
+ const comment = getCommentWithCache(commentSource, context);
101
104
  if (comment?.getTag("@license") || comment?.getTag("@import")) {
102
105
  continue;
103
106
  }
@@ -108,12 +111,12 @@ export function getFileComment(file, config, logger, checker, files) {
108
111
  return;
109
112
  }
110
113
  }
111
- function getConstructorParamPropertyComment(symbol, config, logger, checker, files) {
114
+ function getConstructorParamPropertyComment(symbol, context) {
112
115
  const decl = symbol.declarations?.find(ts.isParameter);
113
116
  if (!decl)
114
117
  return;
115
118
  const ctor = decl.parent;
116
- const comment = getSignatureComment(ctor, config, logger, checker, files);
119
+ const comment = getSignatureComment(ctor, context);
117
120
  const paramTag = comment?.getIdentifiedTag(symbol.name, "@param");
118
121
  if (paramTag) {
119
122
  const result = new Comment(paramTag.content);
@@ -121,10 +124,10 @@ function getConstructorParamPropertyComment(symbol, config, logger, checker, fil
121
124
  return result;
122
125
  }
123
126
  }
124
- export function getSignatureComment(declaration, config, logger, checker, files) {
125
- return getCommentImpl(discoverSignatureComment(declaration, checker, config.commentStyle), config, logger, false, checker, files);
127
+ export function getSignatureComment(declaration, context) {
128
+ return getCommentImpl(discoverSignatureComment(declaration, context.checker, context.config.commentStyle), false, context);
126
129
  }
127
- export function getJsDocComment(declaration, config, logger, checker, files) {
130
+ export function getJsDocComment(declaration, context) {
128
131
  const file = declaration.getSourceFile();
129
132
  // First, get the whole comment. We know we'll need all of it.
130
133
  let parent = declaration.parent;
@@ -143,7 +146,7 @@ export function getJsDocComment(declaration, config, logger, checker, files) {
143
146
  ],
144
147
  jsDoc: parent,
145
148
  inheritedFromParentDeclaration: false,
146
- }, config, logger, config.useTsLinkResolution ? checker : undefined, files);
149
+ }, context);
147
150
  // And pull out the tag we actually care about.
148
151
  if (ts.isJSDocEnumTag(declaration)) {
149
152
  const result = new Comment(comment.getTag("@enum")?.content);
@@ -156,7 +159,7 @@ export function getJsDocComment(declaration, config, logger, checker, files) {
156
159
  // We could just put the same comment on everything, but due to how comment parsing works,
157
160
  // we'd have to search for any @template with a name starting with the first type parameter's name
158
161
  // which feels horribly hacky.
159
- logger.warn(i18n.multiple_type_parameters_on_template_tag_unsupported(), declaration);
162
+ context.logger.warn(i18n.multiple_type_parameters_on_template_tag_unsupported(), declaration);
160
163
  return;
161
164
  }
162
165
  let name;
@@ -176,7 +179,7 @@ export function getJsDocComment(declaration, config, logger, checker, files) {
176
179
  // was a comment attached. If there wasn't, then don't error about failing to find
177
180
  // a tag because this is unsupported.
178
181
  if (!ts.isJSDocTemplateTag(declaration)) {
179
- logger.error(i18n.failed_to_find_jsdoc_tag_for_name_0(name), declaration);
182
+ context.logger.error(i18n.failed_to_find_jsdoc_tag_for_name_0(name), declaration);
180
183
  }
181
184
  }
182
185
  else {
@@ -145,7 +145,7 @@ function* lexLineComments2(file, pos, end) {
145
145
  }
146
146
  if (lookahead !== pos + 1) {
147
147
  while (lookahead < end &&
148
- /[a-z0-9]/i.test(file[lookahead])) {
148
+ /[a-z0-9-]/i.test(file[lookahead])) {
149
149
  lookahead++;
150
150
  }
151
151
  }
@@ -1,10 +1,10 @@
1
- import type { CommentParserConfig } from "./index.js";
1
+ import type { CommentContextOptionalChecker, CommentParserConfig } from "./index.js";
2
2
  import { Comment, type CommentDisplayPart } from "../../models/index.js";
3
3
  import type { MinimalSourceFile } from "#utils";
4
4
  import { type Token } from "./lexer.js";
5
5
  import { FileRegistry } from "../../models/FileRegistry.js";
6
6
  import { type Logger } from "#utils";
7
- export declare function parseComment(tokens: Generator<Token, undefined, undefined>, config: CommentParserConfig, file: MinimalSourceFile, logger: Logger, files: FileRegistry): Comment;
7
+ export declare function parseComment(tokens: Generator<Token, undefined, undefined>, file: MinimalSourceFile, context: CommentContextOptionalChecker): Comment;
8
8
  /**
9
9
  * Intended for parsing markdown documents. This only parses code blocks and
10
10
  * inline tags outside of code blocks, everything else is text.
@@ -40,24 +40,24 @@ function makeLookaheadGenerator(gen) {
40
40
  },
41
41
  };
42
42
  }
43
- export function parseComment(tokens, config, file, logger, files) {
43
+ export function parseComment(tokens, file, context) {
44
44
  const lexer = makeLookaheadGenerator(tokens);
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, config, i18n, warningImpl, files);
48
+ comment.summary = blockContent(comment, lexer, context.config, i18n, warningImpl, context.files);
49
49
  while (!lexer.done()) {
50
- comment.blockTags.push(blockTag(comment, lexer, config, i18n, warningImpl, files));
50
+ comment.blockTags.push(blockTag(comment, lexer, context.config, i18n, warningImpl, context.files));
51
51
  }
52
52
  const tok2 = tok;
53
- postProcessComment(comment, i18n, () => `${nicePath(file.fileName)}:${file.getLineAndCharacterOfPosition(tok2.pos).line + 1}`, (message) => logger.warn(message));
53
+ postProcessComment(comment, i18n, () => `${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
- if (config.suppressCommentWarningsInDeclarationFiles &&
56
+ if (context.config.suppressCommentWarningsInDeclarationFiles &&
57
57
  hasDeclarationFileExtension(file.fileName)) {
58
58
  return;
59
59
  }
60
- logger.warn(message, token.pos, file);
60
+ context.logger.warn(message, token.pos, file);
61
61
  }
62
62
  }
63
63
  /**
@@ -151,7 +151,7 @@ function* lexCommentString2(file) {
151
151
  }
152
152
  if (lookahead !== pos + 1) {
153
153
  while (lookahead < end &&
154
- /[a-z0-9]/i.test(file[lookahead])) {
154
+ /[a-z0-9-]/i.test(file[lookahead])) {
155
155
  lookahead++;
156
156
  }
157
157
  }
@@ -85,9 +85,11 @@ export function textContent(sourcePath, token, i18n, warning, outContent, files,
85
85
  addRef(reference);
86
86
  continue;
87
87
  }
88
- const tagLink = checkTagLink(data);
89
- if (tagLink) {
90
- addRef(tagLink);
88
+ const tagLinks = checkTagLink(data);
89
+ if (tagLinks.length) {
90
+ for (const tagLink of tagLinks) {
91
+ addRef(tagLink);
92
+ }
91
93
  continue;
92
94
  }
93
95
  const atNewLine = token.text[data.pos] === "\n";
@@ -221,39 +223,111 @@ function checkReference(data) {
221
223
  }
222
224
  }
223
225
  /**
224
- * Looks for `<a href="./relative">` and `<img src="./relative">`
226
+ * Looks for `<a href="./relative">`, `<img src="./relative">`, and `<source srcset="./relative">`
225
227
  */
226
228
  function checkTagLink(data) {
227
229
  const { pos, token } = data;
228
230
  if (token.text.startsWith("<img ", pos)) {
229
231
  data.pos += 4;
230
- return checkAttribute(data, "src");
232
+ return checkAttributes(data, {
233
+ src: checkAttributeDirectPath,
234
+ srcset: checkAttributeSrcSet,
235
+ });
236
+ }
237
+ if (token.text.startsWith("<link ", pos)) {
238
+ data.pos += 4;
239
+ return checkAttributes(data, {
240
+ imagesrcset: checkAttributeSrcSet,
241
+ });
231
242
  }
232
243
  if (token.text.startsWith("<a ", pos)) {
233
244
  data.pos += 3;
234
- return checkAttribute(data, "href");
245
+ return checkAttributes(data, { href: checkAttributeDirectPath });
235
246
  }
247
+ if (token.text.startsWith("<source ", pos)) {
248
+ data.pos += 8;
249
+ return checkAttributes(data, {
250
+ src: checkAttributeDirectPath,
251
+ srcset: checkAttributeSrcSet,
252
+ });
253
+ }
254
+ return [];
236
255
  }
237
- function checkAttribute(data, attr) {
256
+ function checkAttributes(data, attributes) {
257
+ const links = [];
238
258
  const parser = new HtmlAttributeParser(data.token.text, data.pos);
239
259
  while (parser.state !== ParserState.END) {
240
260
  if (parser.state === ParserState.BeforeAttributeValue &&
241
- parser.currentAttributeName === attr) {
261
+ Object.prototype.hasOwnProperty.call(attributes, parser.currentAttributeName)) {
242
262
  parser.step();
243
- if (isRelativePath(parser.currentAttributeValue)) {
244
- data.pos = parser.pos;
245
- const { target, anchor } = data.files.register(data.sourcePath, parser.currentAttributeValue) || { target: undefined, anchor: undefined };
246
- return {
247
- pos: parser.currentAttributeValueStart,
248
- end: parser.currentAttributeValueEnd,
249
- target,
250
- targetAnchor: anchor,
251
- };
252
- }
253
- return;
263
+ links.push(...attributes[parser.currentAttributeName](data, parser.currentAttributeValue, parser.currentAttributeValueStart, parser.currentAttributeValueEnd));
254
264
  }
255
265
  parser.step();
256
266
  }
267
+ return links;
268
+ }
269
+ function checkAttributeDirectPath(data, text, pos, end) {
270
+ if (isRelativePath(text.trim())) {
271
+ const { target, anchor } = data.files.register(data.sourcePath, text.trim()) || { target: undefined, anchor: undefined };
272
+ return [{
273
+ pos,
274
+ end,
275
+ target,
276
+ targetAnchor: anchor,
277
+ }];
278
+ }
279
+ return [];
280
+ }
281
+ // See https://html.spec.whatwg.org/multipage/images.html#srcset-attribute
282
+ function checkAttributeSrcSet(data, text, pos, _end) {
283
+ const result = [];
284
+ let textPos = 0;
285
+ parseImageCandidate();
286
+ while (textPos < text.length && text[textPos] == ",") {
287
+ ++textPos;
288
+ parseImageCandidate();
289
+ }
290
+ return result;
291
+ function parseImageCandidate() {
292
+ // 1. Zero or more ASCII whitespace
293
+ while (textPos < text.length && /[\t\r\f\n ]/.test(text[textPos]))
294
+ ++textPos;
295
+ // 2. A valid non-empty URL that does not start or end with a comma
296
+ // TypeDoc: We don't exactly match this, PR welcome! For now, just permit anything
297
+ // that's not whitespace or a comma
298
+ const url = text.slice(textPos).match(/^[^\t\r\f\n ,]+/);
299
+ if (url && isRelativePath(url[0])) {
300
+ const { target, anchor } = data.files.register(data.sourcePath, url[0]) || { target: undefined, anchor: undefined };
301
+ result.push({
302
+ pos: pos + textPos,
303
+ end: pos + textPos + url[0].length,
304
+ target,
305
+ targetAnchor: anchor,
306
+ });
307
+ }
308
+ textPos += url ? url[0].length : 0;
309
+ // 3. Zero or more ASCII whitespace
310
+ while (textPos < text.length && /[\t\r\f\n ]/.test(text[textPos]))
311
+ ++textPos;
312
+ // 4. Zero or one of the following:
313
+ {
314
+ // A width descriptor, consisting of: ASCII whitespace, a valid non-negative integer giving
315
+ // a number greater than zero representing the width descriptor value, and a U+0077 LATIN
316
+ // SMALL LETTER W character.
317
+ const w = text.slice(textPos).match(/^\+?\d+\s*w/);
318
+ textPos += w ? w[0].length : 0;
319
+ // A pixel density descriptor, consisting of: ASCII whitespace, a valid floating-point number
320
+ // giving a number greater than zero representing the pixel density descriptor value, and a
321
+ // U+0078 LATIN SMALL LETTER X character.
322
+ if (!w) {
323
+ const x = text.slice(textPos).match(/^\+?\d+(\.\d+)?([eE][+-]\d+)?\s*x/);
324
+ textPos += x ? x[0].length : 0;
325
+ }
326
+ }
327
+ // 5. Zero or more ASCII whitespace
328
+ while (textPos < text.length && /[\t\r\f\n ]/.test(text[textPos]))
329
+ ++textPos;
330
+ }
257
331
  }
258
332
  function isRelativePath(link) {
259
333
  // Lots of edge cases encoded right here!
@@ -67,6 +67,15 @@ export declare class Context {
67
67
  * This is available on Context so that it can be monkey-patched by typedoc-plugin-missing-exports
68
68
  */
69
69
  createSymbolReference(symbol: ts.Symbol, context: Context, name?: string): ReferenceType;
70
+ /**
71
+ * Create a stable {@link ReflectionSymbolId} for the provided symbol,
72
+ * optionally targeting a specific declaration.
73
+ *
74
+ * @privateRemarks
75
+ * This is available on Context so that it can be monkey-patched by typedoc-plugin-missing-exports
76
+ * It might also turn out to be generally useful for other plugin users.
77
+ */
78
+ createSymbolId(symbol: ts.Symbol, declaration?: ts.Declaration): import("../models/ReflectionSymbolId.js").ReflectionSymbolId;
70
79
  addChild(reflection: DeclarationReflection | DocumentReflection): void;
71
80
  shouldIgnore(symbol: ts.Symbol): boolean;
72
81
  /**
@@ -81,6 +90,7 @@ export declare class Context {
81
90
  getSymbolFromReflection(reflection: Reflection): ts.Symbol | undefined;
82
91
  /** @internal */
83
92
  setActiveProgram(program: ts.Program | undefined): void;
93
+ private createCommentContext;
84
94
  getComment(symbol: ts.Symbol, kind: ReflectionKind): Comment | undefined;
85
95
  getNodeComment(node: ts.Node, moduleComment: boolean): Comment | undefined;
86
96
  getFileComment(node: ts.SourceFile): Comment | undefined;
@@ -4,10 +4,10 @@ import { Comment, ContainerReflection, DeclarationReflection, ReferenceType, Ref
4
4
  import { isNamedNode } from "./utils/nodes.js";
5
5
  import { ConverterEvents } from "./converter-events.js";
6
6
  import { resolveAliasedSymbol } from "./utils/symbols.js";
7
- import { getComment, getFileComment, getJsDocComment, getNodeComment, getSignatureComment } from "./comments/index.js";
7
+ import { getComment, getFileComment, getJsDocComment, getNodeComment, getSignatureComment, } from "./comments/index.js";
8
8
  import { getHumanName, getQualifiedName } from "../utils/tsutils.js";
9
9
  import { findPackageForPath, normalizePath } from "#node-utils";
10
- import { createSymbolId } from "./factories/symbol-id.js";
10
+ import { createSymbolIdImpl } from "./factories/symbol-id.js";
11
11
  import { removeIf } from "#utils";
12
12
  /**
13
13
  * The context describes the current state the converter is in.
@@ -185,7 +185,7 @@ export class Context {
185
185
  * This is available on Context so that it can be monkey-patched by typedoc-plugin-missing-exports
186
186
  */
187
187
  createSymbolReference(symbol, context, name) {
188
- const ref = ReferenceType.createUnresolvedReference(name ?? symbol.name, createSymbolId(symbol), context.project, getQualifiedName(symbol, name ?? symbol.name));
188
+ const ref = ReferenceType.createUnresolvedReference(name ?? symbol.name, this.createSymbolId(symbol), context.project, getQualifiedName(symbol, name ?? symbol.name));
189
189
  ref.refersToTypeParameter = !!(symbol.flags & ts.SymbolFlags.TypeParameter);
190
190
  const symbolPath = symbol.declarations?.[0]?.getSourceFile().fileName;
191
191
  if (!symbolPath)
@@ -193,6 +193,17 @@ export class Context {
193
193
  ref.package = findPackageForPath(symbolPath)?.[0];
194
194
  return ref;
195
195
  }
196
+ /**
197
+ * Create a stable {@link ReflectionSymbolId} for the provided symbol,
198
+ * optionally targeting a specific declaration.
199
+ *
200
+ * @privateRemarks
201
+ * This is available on Context so that it can be monkey-patched by typedoc-plugin-missing-exports
202
+ * It might also turn out to be generally useful for other plugin users.
203
+ */
204
+ createSymbolId(symbol, declaration) {
205
+ return createSymbolIdImpl(symbol, declaration);
206
+ }
196
207
  addChild(reflection) {
197
208
  if (this.scope instanceof ContainerReflection) {
198
209
  this.scope.addChild(reflection);
@@ -211,7 +222,7 @@ export class Context {
211
222
  registerReflection(reflection, symbol, filePath) {
212
223
  if (symbol) {
213
224
  this.reflectionIdToSymbolMap.set(reflection.id, symbol);
214
- const id = createSymbolId(symbol);
225
+ const id = this.createSymbolId(symbol);
215
226
  // #2466
216
227
  // If we just registered a member of a class or interface, then we need to check if
217
228
  // we've registered this symbol before under the wrong parent reflection.
@@ -237,7 +248,7 @@ export class Context {
237
248
  }
238
249
  }
239
250
  getReflectionFromSymbol(symbol) {
240
- return this.project.getReflectionFromSymbolId(createSymbolId(symbol));
251
+ return this.project.getReflectionFromSymbolId(this.createSymbolId(symbol));
241
252
  }
242
253
  getSymbolFromReflection(reflection) {
243
254
  return this.reflectionIdToSymbolMap.get(reflection.id);
@@ -246,20 +257,29 @@ export class Context {
246
257
  setActiveProgram(program) {
247
258
  this._program = program;
248
259
  }
260
+ createCommentContext() {
261
+ return {
262
+ config: this.converter.config,
263
+ logger: this.logger,
264
+ checker: this.checker,
265
+ files: this.project.files,
266
+ createSymbolId: (s, d) => this.createSymbolId(s, d),
267
+ };
268
+ }
249
269
  getComment(symbol, kind) {
250
- return getComment(symbol, kind, this.converter.config, this.logger, this.checker, this.project.files);
270
+ return getComment(symbol, kind, this.createCommentContext());
251
271
  }
252
272
  getNodeComment(node, moduleComment) {
253
- return getNodeComment(node, moduleComment, this.converter.config, this.logger, this.checker, this.project.files);
273
+ return getNodeComment(node, moduleComment, this.createCommentContext());
254
274
  }
255
275
  getFileComment(node) {
256
- return getFileComment(node, this.converter.config, this.logger, this.checker, this.project.files);
276
+ return getFileComment(node, this.createCommentContext());
257
277
  }
258
278
  getJsDocComment(declaration) {
259
- return getJsDocComment(declaration, this.converter.config, this.logger, this.checker, this.project.files);
279
+ return getJsDocComment(declaration, this.createCommentContext());
260
280
  }
261
281
  getSignatureComment(declaration) {
262
- return getSignatureComment(declaration, this.converter.config, this.logger, this.checker, this.project.files);
282
+ return getSignatureComment(declaration, this.createCommentContext());
263
283
  }
264
284
  shouldInline(symbol, name) {
265
285
  if (this.preventInline.has(name))
@@ -4,7 +4,6 @@ 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
- import { createSymbolId } from "./symbol-id.js";
8
7
  export function createSignature(context, kind, signature, symbol, declaration) {
9
8
  assert(context.scope instanceof DeclarationReflection);
10
9
  declaration ||= signature.getDeclaration();
@@ -17,7 +16,7 @@ export function createSignature(context, kind, signature, symbol, declaration) {
17
16
  sigRef.setFlag(ReflectionFlag.Static);
18
17
  }
19
18
  if (symbol && declaration) {
20
- context.project.registerSymbolId(sigRef, createSymbolId(symbol, declaration));
19
+ context.project.registerSymbolId(sigRef, context.createSymbolId(symbol, declaration));
21
20
  }
22
21
  let parentReflection = context.scope;
23
22
  if (parentReflection.kindOf(ReflectionKind.TypeLiteral) &&
@@ -1,4 +1,4 @@
1
1
  import { ReflectionSymbolId } from "#models";
2
2
  import ts from "typescript";
3
- export declare function createSymbolId(symbol: ts.Symbol, declaration?: ts.Declaration): ReflectionSymbolId;
3
+ export declare function createSymbolIdImpl(symbol: ts.Symbol, declaration?: ts.Declaration): ReflectionSymbolId;
4
4
  export declare function addInferredDeclarationMapPaths(opts: ts.CompilerOptions, files: readonly string[]): void;
@@ -7,7 +7,8 @@ import ts from "typescript";
7
7
  const declarationMapCache = new Map();
8
8
  let transientCount = 0;
9
9
  const transientIds = new WeakMap();
10
- export function createSymbolId(symbol, declaration) {
10
+ // Don't use this directly, use Context.createSymbolId instead.
11
+ export function createSymbolIdImpl(symbol, declaration) {
11
12
  declaration ??= symbol.declarations?.[0];
12
13
  const tsSource = declaration?.getSourceFile().fileName ?? "";
13
14
  const sourceFileName = resolveDeclarationMaps(tsSource);
@@ -6,7 +6,6 @@ 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
8
  import { convertParameterNodes, convertTemplateParameterNodes } from "./factories/signature.js";
9
- import { createSymbolId } from "./factories/symbol-id.js";
10
9
  export function convertJsDocAlias(context, symbol, declaration, exportSymbol) {
11
10
  if (declaration.typeExpression &&
12
11
  ts.isJSDocTypeLiteral(declaration.typeExpression)) {
@@ -57,7 +56,7 @@ function convertJsDocSignature(context, node) {
57
56
  context.registerReflection(reflection, symbol);
58
57
  context.converter.trigger(ConverterEvents.CREATE_DECLARATION, context, reflection);
59
58
  const signature = new SignatureReflection("__type", ReflectionKind.CallSignature, reflection);
60
- context.project.registerSymbolId(signature, createSymbolId(symbol, node));
59
+ context.project.registerSymbolId(signature, context.createSymbolId(symbol, node));
61
60
  context.registerReflection(signature, void 0);
62
61
  const signatureCtx = rc.withScope(signature);
63
62
  reflection.signatures = [signature];
@@ -40,7 +40,7 @@ export declare class GroupPlugin extends ConverterComponent {
40
40
  * @returns An array containing all children of the given reflection grouped by their kind.
41
41
  */
42
42
  getReflectionGroups(parent: ContainerReflection, reflections: Array<DeclarationReflection | DocumentReflection>): ReflectionGroup[];
43
- getSortFunction(reflection: ContainerReflection): (reflections: (DeclarationReflection | DocumentReflection)[]) => void;
43
+ getSortFunction(reflection: ContainerReflection): (reflections: Array<DeclarationReflection | DocumentReflection>) => void;
44
44
  /**
45
45
  * Callback used to sort groups by name.
46
46
  */
@@ -58,7 +58,9 @@ export class ImplementsPlugin extends ConverterComponent {
58
58
  });
59
59
  }
60
60
  analyzeInheritance(project, reflection) {
61
- const extendedTypes = filterMap(reflection.extendedTypes ?? [], (type) => {
61
+ if (!reflection.extendedTypes)
62
+ return;
63
+ const extendedTypes = filterMap(reflection.extendedTypes, (type) => {
62
64
  return type instanceof ReferenceType &&
63
65
  type.reflection instanceof DeclarationReflection
64
66
  ? type
@@ -73,13 +75,42 @@ export class ImplementsPlugin extends ConverterComponent {
73
75
  ? "overwrites"
74
76
  : "inheritedFrom";
75
77
  for (const [childSig, parentSig] of zip(child.signatures ?? [], parentMember.signatures ?? [])) {
76
- childSig[key] = ReferenceType.createResolvedReference(`${parent.name}.${parentMember.name}`, parentSig, project);
78
+ // If we're already pointing at something because TS said we should reference
79
+ // it, then don't overwrite the reference.
80
+ if (!childSig[key]?.reflection) {
81
+ childSig[key] = ReferenceType.createResolvedReference(`${parent.name}.${parentMember.name}`, parentSig, project);
82
+ }
83
+ }
84
+ if (!child[key]?.reflection) {
85
+ child[key] = ReferenceType.createResolvedReference(`${parent.name}.${parentMember.name}`, parentMember, project);
77
86
  }
78
- child[key] = ReferenceType.createResolvedReference(`${parent.name}.${parentMember.name}`, parentMember, project);
79
87
  this.handleInheritedComments(child, parentMember);
80
88
  }
81
89
  }
82
90
  }
91
+ // #2978, this is very unfortunate. If a child's parent links are broken at this point,
92
+ // we replace them with an intentionally broken link so that they won't ever be resolved.
93
+ // This is done because if we don't do it then we run into issues where we have a link which
94
+ // points to some ReflectionSymbolId which might not exist now, but once we've gone through
95
+ // serialization/deserialization, might point to an unexpected location. (See the mixin
96
+ // converter tests, I suspect this might actually be an indication of something else slightly
97
+ // broken there, but don't want to spend more time with this right now.)
98
+ for (const child of reflection.children || []) {
99
+ if (child.inheritedFrom && !child.inheritedFrom.reflection) {
100
+ child.inheritedFrom = ReferenceType.createBrokenReference(child.inheritedFrom.name, project);
101
+ }
102
+ if (child.overwrites && !child.overwrites.reflection) {
103
+ child.overwrites = ReferenceType.createBrokenReference(child.overwrites.name, project);
104
+ }
105
+ for (const childSig of child.getAllSignatures()) {
106
+ if (childSig.inheritedFrom && !childSig.inheritedFrom.reflection) {
107
+ childSig.inheritedFrom = ReferenceType.createBrokenReference(childSig.inheritedFrom.name, project);
108
+ }
109
+ if (childSig.overwrites && !childSig.overwrites.reflection) {
110
+ childSig.overwrites = ReferenceType.createBrokenReference(childSig.overwrites.name, project);
111
+ }
112
+ }
113
+ }
83
114
  }
84
115
  onResolveEnd(context) {
85
116
  this.resolve(context.project);
@@ -310,8 +341,19 @@ function findProperty(reflection, parent) {
310
341
  });
311
342
  }
312
343
  function createLink(context, reflection, clause, expr, symbol, isInherit) {
313
- const project = context.project;
314
344
  const name = `${expr.expression.getText()}.${getHumanName(symbol.name)}`;
345
+ // We should always have rootSymbols, but check just in case. We use the first
346
+ // symbol here as TypeDoc's models don't have multiple symbols for the parent
347
+ // reference. This is technically wrong because symbols might be declared in
348
+ // multiple locations (interface declaration merging), but that's an uncommon
349
+ // enough use case that it doesn't seem worthwhile to complicate the rest of the
350
+ // world to deal with it.
351
+ // Note that we also need to check that the root symbol isn't this symbol.
352
+ // This seems to happen sometimes when dealing with interface inheritance.
353
+ const rootSymbols = context.checker.getRootSymbols(symbol);
354
+ const ref = rootSymbols.length && rootSymbols[0] != symbol
355
+ ? context.createSymbolReference(rootSymbols[0], context, name)
356
+ : ReferenceType.createBrokenReference(name, context.project);
315
357
  link(reflection);
316
358
  link(reflection.getSignature);
317
359
  link(reflection.setSignature);
@@ -321,23 +363,19 @@ function createLink(context, reflection, clause, expr, symbol, isInherit) {
321
363
  for (const sig of reflection.signatures ?? []) {
322
364
  link(sig);
323
365
  }
324
- // Intentionally create broken links here. These will be replaced with real links during
325
- // resolution if we can do so. We create broken links rather than real links because in the
326
- // case of an inherited symbol, we'll end up referencing a single symbol ID rather than one
327
- // for each class.
328
366
  function link(target) {
329
367
  if (!target)
330
368
  return;
331
369
  if (clause.token === ts.SyntaxKind.ImplementsKeyword) {
332
- target.implementationOf ??= ReferenceType.createBrokenReference(name, project);
370
+ target.implementationOf ??= ref;
333
371
  return;
334
372
  }
335
373
  if (isInherit) {
336
374
  target.setFlag(ReflectionFlag.Inherited);
337
- target.inheritedFrom ??= ReferenceType.createBrokenReference(name, project);
375
+ target.inheritedFrom ??= ref;
338
376
  }
339
377
  else {
340
- target.overwrites ??= ReferenceType.createBrokenReference(name, project);
378
+ target.overwrites ??= ref;
341
379
  }
342
380
  }
343
381
  }
@@ -8,7 +8,6 @@ import { convertParameterNodes, convertTypeParameterNodes, createSignature } fro
8
8
  import { convertSymbol } from "./symbols.js";
9
9
  import { isObjectType, isTypeReference } from "./utils/nodes.js";
10
10
  import { removeUndefined } from "./utils/reflections.js";
11
- import { createSymbolId } from "./factories/symbol-id.js";
12
11
  const converters = new Map();
13
12
  export function loadConverters() {
14
13
  if (converters.size)
@@ -159,7 +158,7 @@ const constructorConverter = {
159
158
  if (node.modifiers?.some((m) => m.kind === ts.SyntaxKind.AbstractKeyword)) {
160
159
  signature.setFlag(ReflectionFlag.Abstract);
161
160
  }
162
- context.project.registerSymbolId(signature, createSymbolId(symbol, node));
161
+ context.project.registerSymbolId(signature, context.createSymbolId(symbol, node));
163
162
  context.registerReflection(signature, void 0);
164
163
  const signatureCtx = rc.withScope(signature);
165
164
  reflection.signatures = [signature];
@@ -208,7 +207,7 @@ const functionTypeConverter = {
208
207
  context.registerReflection(reflection, symbol);
209
208
  context.converter.trigger(ConverterEvents.CREATE_DECLARATION, context, reflection);
210
209
  const signature = new SignatureReflection("__type", ReflectionKind.CallSignature, reflection);
211
- context.project.registerSymbolId(signature, createSymbolId(symbol, node));
210
+ context.project.registerSymbolId(signature, context.createSymbolId(symbol, node));
212
211
  context.registerReflection(signature, undefined);
213
212
  const signatureCtx = rc.withScope(signature);
214
213
  reflection.signatures = [signature];
@@ -276,6 +276,7 @@ module.exports = {
276
276
  favicon_must_have_one_of_the_following_extensions_0: "Favicon must have one of the following extensions: {0}",
277
277
  option_0_must_be_an_object: "The '{0}' option must be a non-array object",
278
278
  option_0_must_be_an_array_of_string: "The '{0}' option must be set to an array of strings",
279
+ option_0_must_be_an_array_of_string_or_functions: "The '{0}' option must be set to an array of strings/functions",
279
280
  option_0_must_be_a_function: "The '{0}' option must be a function",
280
281
  option_0_must_be_object_with_urls: `{0} must be an object with string labels as keys and URL values`,
281
282
  visibility_filters_only_include_0: `visibilityFilters can only include the following non-@ keys: {0}`,
@@ -260,6 +260,7 @@ declare const _default: {
260
260
  readonly favicon_must_have_one_of_the_following_extensions_0: "Favicon must have one of the following extensions: {0}";
261
261
  readonly option_0_must_be_an_object: "The '{0}' option must be a non-array object";
262
262
  readonly option_0_must_be_an_array_of_string: "The '{0}' option must be set to an array of strings";
263
+ readonly option_0_must_be_an_array_of_string_or_functions: "The '{0}' option must be set to an array of strings/functions";
263
264
  readonly option_0_must_be_a_function: "The '{0}' option must be a function";
264
265
  readonly option_0_must_be_object_with_urls: "{0} must be an object with string labels as keys and URL values";
265
266
  readonly visibility_filters_only_include_0: "visibilityFilters can only include the following non-@ keys: {0}";
@@ -37,7 +37,7 @@ export declare const SCHEMA_VERSION = "2.0";
37
37
  export type ModelToObject<T> = [T] extends [Array<infer U>] ? ModelToObject<U>[] : [
38
38
  M.SomeType
39
39
  ] extends [T] ? SomeType : _ModelToObject<T>;
40
- type _ModelToObject<T> = T extends Primitive ? T : Required<T> extends Required<M.ReflectionGroup> ? ReflectionGroup : Required<T> extends Required<M.ReflectionCategory> ? ReflectionCategory : T extends M.ReflectionVariant[keyof M.ReflectionVariant] ? ReflectionVariantMap[T["variant"]] : T extends M.SomeType ? TypeKindMap[T["type"]] : T extends M.Type ? SomeType : T extends M.Comment ? Comment : T extends M.CommentTag ? CommentTag : T extends M.CommentDisplayPart ? CommentDisplayPart : T extends M.SourceReference ? SourceReference : T extends M.FileRegistry ? FileRegistry : never;
40
+ type _ModelToObject<T> = T extends Primitive ? T : Required<T> extends Required<M.ReflectionGroup> ? ReflectionGroup : Required<T> extends Required<M.ReflectionCategory> ? ReflectionCategory : T extends M.ReflectionVariant[keyof M.ReflectionVariant] ? ReflectionVariantMap[T["variant"]] : T extends M.SomeType ? TypeKindMap[T["type"]] : T extends M.Type ? SomeType : T extends M.Comment ? Comment : T extends M.CommentTag ? CommentTag : T extends M.CommentDisplayPart ? CommentDisplayPart : T extends M.SourceReference ? SourceReference : T extends M.FileRegistry ? FileRegistry : T extends M.ReflectionSymbolId ? ReflectionSymbolId : never;
41
41
  type Primitive = string | number | undefined | null | boolean;
42
42
  type ToSerialized<T> = T extends Primitive ? T : T extends bigint ? {
43
43
  value: string;
@@ -2,8 +2,9 @@ import type { BundledTheme as ShikiTheme } from "@gerrit0/mini-shiki";
2
2
  import type { SortStrategy } from "../sort.js";
3
3
  import type { EntryPointStrategy } from "../entry-point.js";
4
4
  import type { ReflectionKind } from "../../models/kind.js";
5
- import { type GlobString, type LogLevel, type NeverIfInternal, type NormalizedPath, type NormalizedPathOrModule } from "#utils";
5
+ import { type GlobString, type LogLevel, type NeverIfInternal, type NormalizedPath, type NormalizedPathOrModule, type NormalizedPathOrModuleOrFunction } from "#utils";
6
6
  import type { TranslationProxy } from "../../internationalization/internationalization.js";
7
+ import type { Application } from "../../application.js";
7
8
  /** @enum */
8
9
  export declare const EmitStrategy: {
9
10
  readonly both: "both";
@@ -40,7 +41,7 @@ export declare const rootPackageOptions: readonly ["plugin", "packageOptions", "
40
41
  * @interface
41
42
  */
42
43
  export type TypeDocOptions = {
43
- [K in keyof TypeDocOptionMap]?: unknown extends TypeDocOptionMap[K] ? unknown : TypeDocOptionMap[K] extends ManuallyValidatedOption<infer ManuallyValidated> ? ManuallyValidated : TypeDocOptionMap[K] extends NormalizedPath[] | NormalizedPathOrModule[] | GlobString[] ? string[] : TypeDocOptionMap[K] extends NormalizedPath ? string : TypeDocOptionMap[K] extends string | string[] | number | boolean ? TypeDocOptionMap[K] : TypeDocOptionMap[K] extends Record<string, boolean> ? Partial<TypeDocOptionMap[K]> | boolean : keyof TypeDocOptionMap[K] | TypeDocOptionMap[K][keyof TypeDocOptionMap[K]];
44
+ [K in keyof TypeDocOptionMap]?: unknown extends TypeDocOptionMap[K] ? unknown : TypeDocOptionMap[K] extends ManuallyValidatedOption<infer ManuallyValidated> ? ManuallyValidated : TypeDocOptionMap[K] extends NormalizedPath[] | NormalizedPathOrModule[] | NormalizedPathOrModuleOrFunction[] | GlobString[] ? string[] : TypeDocOptionMap[K] extends NormalizedPath ? string : TypeDocOptionMap[K] extends string | string[] | number | boolean ? TypeDocOptionMap[K] : TypeDocOptionMap[K] extends Record<string, boolean> ? Partial<TypeDocOptionMap[K]> | boolean : keyof TypeDocOptionMap[K] | TypeDocOptionMap[K][keyof TypeDocOptionMap[K]];
44
45
  };
45
46
  /**
46
47
  * Describes all TypeDoc specific options as returned by {@link Options.getValue}, this is
@@ -50,7 +51,7 @@ export type TypeDocOptions = {
50
51
  * @interface
51
52
  */
52
53
  export type TypeDocOptionValues = {
53
- [K in keyof TypeDocOptionMap]: unknown extends TypeDocOptionMap[K] ? unknown : TypeDocOptionMap[K] extends ManuallyValidatedOption<infer ManuallyValidated> ? ManuallyValidated : TypeDocOptionMap[K] extends string | string[] | GlobString[] | number | boolean | Record<string, boolean> ? TypeDocOptionMap[K] : TypeDocOptionMap[K][keyof TypeDocOptionMap[K]];
54
+ [K in keyof TypeDocOptionMap]: unknown extends TypeDocOptionMap[K] ? unknown : TypeDocOptionMap[K] extends ManuallyValidatedOption<infer ManuallyValidated> ? ManuallyValidated : TypeDocOptionMap[K] extends string | string[] | GlobString[] | NormalizedPathOrModule[] | NormalizedPathOrModuleOrFunction[] | number | boolean | Record<string, boolean> ? TypeDocOptionMap[K] : TypeDocOptionMap[K][keyof TypeDocOptionMap[K]];
54
55
  };
55
56
  /**
56
57
  * Describes TypeDoc options suitable for setting within the `packageOptions` setting.
@@ -78,7 +79,7 @@ export interface TypeDocOptionMap {
78
79
  options: NormalizedPath;
79
80
  tsconfig: NormalizedPath;
80
81
  compilerOptions: unknown;
81
- plugin: NormalizedPathOrModule[];
82
+ plugin: NormalizedPathOrModuleOrFunction[];
82
83
  lang: string;
83
84
  locales: ManuallyValidatedOption<Record<string, Record<string, string>>>;
84
85
  packageOptions: ManuallyValidatedOption<TypeDocPackageOptions>;
@@ -276,7 +277,7 @@ export type JsDocCompatibility = {
276
277
  /**
277
278
  * Converts a given TypeDoc option key to the type of the declaration expected.
278
279
  */
279
- export type KeyToDeclaration<K extends keyof TypeDocOptionMap> = TypeDocOptionMap[K] extends boolean ? BooleanDeclarationOption : TypeDocOptionMap[K] extends string | NormalizedPath ? StringDeclarationOption : TypeDocOptionMap[K] extends number ? NumberDeclarationOption : TypeDocOptionMap[K] extends GlobString[] ? GlobArrayDeclarationOption : TypeDocOptionMap[K] extends string[] | NormalizedPath[] | NormalizedPathOrModule[] ? ArrayDeclarationOption : unknown extends TypeDocOptionMap[K] ? MixedDeclarationOption | ObjectDeclarationOption : TypeDocOptionMap[K] extends ManuallyValidatedOption<unknown> ? (MixedDeclarationOption & {
280
+ export type KeyToDeclaration<K extends keyof TypeDocOptionMap> = TypeDocOptionMap[K] extends boolean ? BooleanDeclarationOption : TypeDocOptionMap[K] extends string | NormalizedPath ? StringDeclarationOption : TypeDocOptionMap[K] extends number ? NumberDeclarationOption : TypeDocOptionMap[K] extends GlobString[] ? GlobArrayDeclarationOption : TypeDocOptionMap[K] extends string[] | NormalizedPath[] | NormalizedPathOrModule[] | NormalizedPathOrModuleOrFunction[] ? ArrayDeclarationOption : unknown extends TypeDocOptionMap[K] ? MixedDeclarationOption | ObjectDeclarationOption : TypeDocOptionMap[K] extends ManuallyValidatedOption<unknown> ? (MixedDeclarationOption & {
280
281
  validate(value: unknown, i18n: TranslationProxy): void;
281
282
  }) | (ObjectDeclarationOption & {
282
283
  validate(value: unknown, i18n: TranslationProxy): void;
@@ -306,20 +307,26 @@ export declare enum ParameterType {
306
307
  PathArray = 8,
307
308
  /**
308
309
  * Resolved according to the config directory if it starts with `.`
310
+ * @deprecated since 0.28.8, will be removed in 0.29
309
311
  */
310
312
  ModuleArray = 9,
313
+ /**
314
+ * Resolved according to the config directory if it starts with `.`
315
+ * @internal - only intended for use with the plugin option
316
+ */
317
+ PluginArray = 10,
311
318
  /**
312
319
  * Relative to the config directory.
313
320
  */
314
- GlobArray = 10,
321
+ GlobArray = 11,
315
322
  /**
316
323
  * An object which partially merges user-set values into the defaults.
317
324
  */
318
- Object = 11,
325
+ Object = 12,
319
326
  /**
320
327
  * An object with true/false flags
321
328
  */
322
- Flags = 12
329
+ Flags = 13
323
330
  }
324
331
  export interface DeclarationOptionBase {
325
332
  /**
@@ -401,7 +408,7 @@ export interface BooleanDeclarationOption extends DeclarationOptionBase {
401
408
  defaultValue?: boolean;
402
409
  }
403
410
  export interface ArrayDeclarationOption extends DeclarationOptionBase {
404
- type: ParameterType.Array | ParameterType.PathArray | ParameterType.ModuleArray;
411
+ type: ParameterType.Array | ParameterType.PathArray | ParameterType.ModuleArray | ParameterType.PluginArray;
405
412
  /**
406
413
  * If not specified defaults to an empty array.
407
414
  */
@@ -482,6 +489,7 @@ export interface ParameterTypeToOptionTypeMap {
482
489
  [ParameterType.Array]: string[];
483
490
  [ParameterType.PathArray]: NormalizedPath[];
484
491
  [ParameterType.ModuleArray]: NormalizedPathOrModule[];
492
+ [ParameterType.PluginArray]: Array<NormalizedPathOrModule | ((app: Application) => void | Promise<void>)>;
485
493
  [ParameterType.GlobArray]: GlobString[];
486
494
  [ParameterType.Flags]: Record<string, boolean>;
487
495
  [ParameterType.Map]: unknown;
@@ -112,20 +112,26 @@ export var ParameterType;
112
112
  ParameterType[ParameterType["PathArray"] = 8] = "PathArray";
113
113
  /**
114
114
  * Resolved according to the config directory if it starts with `.`
115
+ * @deprecated since 0.28.8, will be removed in 0.29
115
116
  */
116
117
  ParameterType[ParameterType["ModuleArray"] = 9] = "ModuleArray";
118
+ /**
119
+ * Resolved according to the config directory if it starts with `.`
120
+ * @internal - only intended for use with the plugin option
121
+ */
122
+ ParameterType[ParameterType["PluginArray"] = 10] = "PluginArray";
117
123
  /**
118
124
  * Relative to the config directory.
119
125
  */
120
- ParameterType[ParameterType["GlobArray"] = 10] = "GlobArray";
126
+ ParameterType[ParameterType["GlobArray"] = 11] = "GlobArray";
121
127
  /**
122
128
  * An object which partially merges user-set values into the defaults.
123
129
  */
124
- ParameterType[ParameterType["Object"] = 11] = "Object";
130
+ ParameterType[ParameterType["Object"] = 12] = "Object";
125
131
  /**
126
132
  * An object with true/false flags
127
133
  */
128
- ParameterType[ParameterType["Flags"] = 12] = "Flags";
134
+ ParameterType[ParameterType["Flags"] = 13] = "Flags";
129
135
  })(ParameterType || (ParameterType = {}));
130
136
  function toStringArray(value, option) {
131
137
  if (Array.isArray(value) && value.every(v => typeof v === "string")) {
@@ -136,6 +142,15 @@ function toStringArray(value, option) {
136
142
  }
137
143
  throw new Error(i18n.option_0_must_be_an_array_of_string(option.name));
138
144
  }
145
+ function toStringOrFunctionArray(value, option) {
146
+ if (Array.isArray(value) && value.every(v => typeof v === "string" || typeof v === "function")) {
147
+ return value;
148
+ }
149
+ else if (typeof value === "string") {
150
+ return [value];
151
+ }
152
+ throw new Error(i18n.option_0_must_be_an_array_of_string_or_functions(option.name));
153
+ }
139
154
  const converters = {
140
155
  [ParameterType.String](value, option) {
141
156
  // eslint-disable-next-line @typescript-eslint/no-base-to-string
@@ -183,12 +198,18 @@ const converters = {
183
198
  option.validate?.(normalized);
184
199
  return normalized;
185
200
  },
201
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
186
202
  [ParameterType.ModuleArray](value, option, configPath) {
187
203
  const strArrValue = toStringArray(value, option);
188
204
  const resolved = resolveModulePaths(strArrValue, configPath);
189
205
  option.validate?.(resolved);
190
206
  return resolved;
191
207
  },
208
+ [ParameterType.PluginArray](value, option, configPath) {
209
+ const arrayValue = toStringOrFunctionArray(value, option);
210
+ const resolved = arrayValue.map(plugin => typeof plugin === "function" ? plugin : resolveModulePath(plugin, configPath));
211
+ return resolved;
212
+ },
192
213
  [ParameterType.GlobArray](value, option, configPath) {
193
214
  const toGlobString = (v) => {
194
215
  const s = String(v);
@@ -321,12 +342,19 @@ const defaultGetters = {
321
342
  [ParameterType.PathArray](option) {
322
343
  return (option.defaultValue?.map((value) => normalizePath(resolve(process.cwd(), value))) ?? []);
323
344
  },
345
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
324
346
  [ParameterType.ModuleArray](option) {
325
347
  if (option.defaultValue) {
326
348
  return resolveModulePaths(option.defaultValue, process.cwd());
327
349
  }
328
350
  return [];
329
351
  },
352
+ [ParameterType.PluginArray](option) {
353
+ if (option.defaultValue) {
354
+ return resolveModulePaths(option.defaultValue, process.cwd());
355
+ }
356
+ return [];
357
+ },
330
358
  [ParameterType.GlobArray](option) {
331
359
  return (option.defaultValue ?? []).map(g => createGlobString(normalizePath(process.cwd()), g));
332
360
  },
@@ -339,12 +367,13 @@ export function getDefaultValue(option) {
339
367
  return getters[option.type ?? ParameterType.String](option);
340
368
  }
341
369
  function resolveModulePaths(modules, configPath) {
342
- return modules.map((path) => {
343
- if (path.startsWith(".")) {
344
- return normalizePath(resolve(configPath, path));
345
- }
346
- return normalizePath(path);
347
- });
370
+ return modules.map(path => resolveModulePath(path, configPath));
371
+ }
372
+ function resolveModulePath(path, configPath) {
373
+ if (path.startsWith(".")) {
374
+ return normalizePath(resolve(configPath, path));
375
+ }
376
+ return normalizePath(path);
348
377
  }
349
378
  function isTsNumericEnum(map) {
350
379
  return Object.values(map).every((key) => map[map[key]] === key);
@@ -4,7 +4,9 @@ import { i18n } from "#utils";
4
4
  const ARRAY_OPTION_TYPES = new Set([
5
5
  ParameterType.Array,
6
6
  ParameterType.PathArray,
7
+ // eslint-disable-next-line @typescript-eslint/no-deprecated
7
8
  ParameterType.ModuleArray,
9
+ ParameterType.PluginArray,
8
10
  ParameterType.GlobArray,
9
11
  ]);
10
12
  /**
@@ -801,7 +801,7 @@ export function addTypeDocOptions(options) {
801
801
  options.addDeclaration({
802
802
  name: "plugin",
803
803
  help: () => i18n.help_plugin(),
804
- type: ParameterType.ModuleArray,
804
+ type: ParameterType.PluginArray,
805
805
  });
806
806
  options.addDeclaration({
807
807
  name: "logLevel",
@@ -1,2 +1,3 @@
1
1
  import type { Application } from "../application.js";
2
- export declare function loadPlugins(app: Application, plugins: readonly string[]): Promise<void>;
2
+ import { type NormalizedPathOrModuleOrFunction } from "#utils";
3
+ export declare function loadPlugins(app: Application, plugins: readonly NormalizedPathOrModuleOrFunction[]): Promise<void>;
@@ -6,27 +6,33 @@ export async function loadPlugins(app, plugins) {
6
6
  for (const plugin of plugins) {
7
7
  const pluginDisplay = getPluginDisplayName(plugin);
8
8
  try {
9
- let instance;
10
- // Try importing first to avoid warnings about requiring ESM being experimental.
11
- // If that fails due to importing a directory, fall back to require.
12
- try {
13
- // On Windows, we need to ensure this path is a file path.
14
- // Or we'll get ERR_UNSUPPORTED_ESM_URL_SCHEME
15
- const esmPath = isAbsolute(plugin)
16
- ? pathToFileURL(plugin).toString()
17
- : plugin;
18
- instance = await import(esmPath);
9
+ let initFunction;
10
+ if (typeof plugin === "function") {
11
+ initFunction = plugin;
19
12
  }
20
- catch (error) {
21
- if (error.code === "ERR_UNSUPPORTED_DIR_IMPORT") {
22
- // eslint-disable-next-line @typescript-eslint/no-require-imports
23
- instance = require(plugin);
13
+ else {
14
+ let instance;
15
+ // Try importing first to avoid warnings about requiring ESM being experimental.
16
+ // If that fails due to importing a directory, fall back to require.
17
+ try {
18
+ // On Windows, we need to ensure this path is a file path.
19
+ // Or we'll get ERR_UNSUPPORTED_ESM_URL_SCHEME
20
+ const esmPath = isAbsolute(plugin)
21
+ ? pathToFileURL(plugin).toString()
22
+ : plugin;
23
+ instance = await import(esmPath);
24
24
  }
25
- else {
26
- throw error;
25
+ catch (error) {
26
+ if (error.code === "ERR_UNSUPPORTED_DIR_IMPORT") {
27
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
28
+ instance = require(plugin);
29
+ }
30
+ else {
31
+ throw error;
32
+ }
27
33
  }
34
+ initFunction = instance.load;
28
35
  }
29
- const initFunction = instance.load;
30
36
  if (typeof initFunction === "function") {
31
37
  await initFunction(app);
32
38
  app.logger.info(i18n.loaded_plugin_0(pluginDisplay));
@@ -44,6 +50,9 @@ export async function loadPlugins(app, plugins) {
44
50
  }
45
51
  }
46
52
  function getPluginDisplayName(plugin) {
53
+ if (typeof plugin === "function") {
54
+ return plugin.name || "function";
55
+ }
47
56
  const path = nicePath(plugin);
48
57
  if (path.startsWith("./node_modules/")) {
49
58
  return path.substring("./node_modules/".length);
@@ -1,3 +1,4 @@
1
+ import type { Application } from "../application.js";
1
2
  /**
2
3
  * Represents a normalized path with path separators being `/`
3
4
  * On Windows, drives are represented like `C:/Users` for consistency
@@ -15,6 +16,11 @@ export type NormalizedPath = "" | "/" | string & {
15
16
  export type NormalizedPathOrModule = NormalizedPath | string & {
16
17
  readonly __normPathOrModule: unique symbol;
17
18
  };
19
+ /**
20
+ * Represents either a {@link NormalizedPath} or a Node module name
21
+ * (e.g. `typedoc-plugin-mdn-links` or `@gerrit0/typedoc-plugin`)
22
+ */
23
+ export type NormalizedPathOrModuleOrFunction = NormalizedPathOrModule | ((app: Application) => Promise<void> | void);
18
24
  /**
19
25
  * Represents a glob path configured by a user.
20
26
  */
@@ -46,5 +46,5 @@ export function optional(x) {
46
46
  return { [opt]: x };
47
47
  }
48
48
  export function isTagString(x) {
49
- return typeof x === "string" && /^@[a-zA-Z][a-zA-Z0-9]*$/.test(x);
49
+ return typeof x === "string" && /^@[a-z][a-z0-9-]*$/i.test(x);
50
50
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "typedoc",
3
3
  "description": "Create api documentation for TypeScript projects.",
4
- "version": "0.28.7",
4
+ "version": "0.28.8",
5
5
  "homepage": "https://typedoc.org",
6
6
  "type": "module",
7
7
  "exports": {