Skip to content

Commit 8d9239e

Browse files
committed
Support relative paths, mostly?
1 parent 8c1acb2 commit 8d9239e

40 files changed

+724
-166
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
Documentation generator for TypeScript projects.
44

5+
Plugins: [plugins](./internal-docs/plugins.md)
6+
57
[![CI](https://github.com/TypeStrong/typedoc/workflows/CI/badge.svg)](https://github.com/TypeStrong/typedoc/actions)
68
[![NPM Version](https://img.shields.io/npm/v/typedoc?color=33cd56&logo=npm)](https://www.npmjs.com/package/typedoc)
79

scripts/rebuild_specs.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ function rebuildConverterTests(app, dirs) {
106106
for (const [file, before, after] of conversions) {
107107
const out = path.join(fullPath, `${file}.json`);
108108
if (fs.existsSync(out)) {
109+
app.media = new td.ValidatingMediaRegistry();
109110
td.resetReflectionID();
110111
before(app);
111112
const entry = getExpandedEntryPointsForPaths(

src/lib/application.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import {
4747
type TranslatedString,
4848
} from "./internationalization/internationalization";
4949
import { loadShikiMetadata } from "./utils/highlighter";
50+
import { ValidatingMediaRegistry, MediaRegistry } from "./models/MediaRegistry";
5051

5152
// eslint-disable-next-line @typescript-eslint/no-var-requires
5253
const packageInfo = require("../../package.json") as {
@@ -62,7 +63,9 @@ const DETECTOR = Symbol();
6263

6364
export function createAppForTesting(): Application {
6465
// @ts-expect-error private constructor
65-
return new Application(DETECTOR);
66+
const app: Application = new Application(DETECTOR);
67+
app.media = new MediaRegistry();
68+
return app;
6669
}
6770

6871
const DEFAULT_READERS = [
@@ -127,6 +130,8 @@ export class Application extends ChildableComponent<
127130

128131
options = new Options(this.i18n);
129132

133+
media: MediaRegistry = new ValidatingMediaRegistry();
134+
130135
/** @internal */
131136
@Option("lang")
132137
accessor lang!: string;
@@ -687,6 +692,8 @@ export class Application extends ChildableComponent<
687692
const result = this.deserializer.reviveProjects(
688693
this.options.getValue("name") || "Documentation",
689694
projects,
695+
process.cwd(),
696+
this.media,
690697
);
691698
this.trigger(ApplicationEvents.REVIVE, result);
692699
return result;
@@ -734,6 +741,8 @@ export class Application extends ChildableComponent<
734741
const result = this.deserializer.reviveProjects(
735742
this.options.getValue("name"),
736743
jsonProjects,
744+
process.cwd(),
745+
this.media,
737746
);
738747
this.logger.verbose(`Reviving projects took ${Date.now() - start}ms`);
739748

src/lib/converter/comments/parser.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ export function parseCommentString(
125125
config: CommentParserConfig,
126126
file: MinimalSourceFile,
127127
logger: Logger,
128+
media: MediaRegistry,
128129
) {
129130
const suppressWarningsConfig: CommentParserConfig = {
130131
...config,
@@ -139,6 +140,7 @@ export function parseCommentString(
139140
const content: CommentDisplayPart[] = [];
140141
const lexer = makeLookaheadGenerator(tokens);
141142

143+
let atNewLine = false;
142144
while (!lexer.done()) {
143145
let consume = true;
144146
const next = lexer.peek();
@@ -152,7 +154,15 @@ export function parseCommentString(
152154
case TokenSyntaxKind.Text:
153155
case TokenSyntaxKind.Tag:
154156
case TokenSyntaxKind.CloseBrace:
155-
content.push({ kind: "text", text: next.text });
157+
textContent(
158+
file.fileName,
159+
next,
160+
logger.i18n,
161+
(msg, token) => logger.warn(msg, token.pos, file),
162+
content,
163+
media,
164+
atNewLine,
165+
);
156166
break;
157167

158168
case TokenSyntaxKind.Code:
@@ -174,6 +184,7 @@ export function parseCommentString(
174184
assertNever(next.kind);
175185
}
176186

187+
atNewLine = next.kind === TokenSyntaxKind.NewLine;
177188
if (consume) {
178189
lexer.take();
179190
}
@@ -573,7 +584,7 @@ function blockContent(
573584

574585
case TokenSyntaxKind.Text:
575586
textContent(
576-
comment,
587+
comment.sourcePath!,
577588
next,
578589
i18n,
579590
warning,

src/lib/converter/comments/textParser.ts

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@
66
* @module
77
*/
88
import { TranslationProxy, TranslatedString } from "../../internationalization";
9-
import { Comment, CommentDisplayPart } from "../../models";
9+
import type { CommentDisplayPart } from "../../models";
1010
import { MediaRegistry } from "../../models/MediaRegistry";
11-
import { Token } from "./lexer";
11+
import { Token, TokenSyntaxKind } from "./lexer";
1212

1313
import MarkdownIt from "markdown-it";
1414
const MdHelpers = new MarkdownIt().helpers;
1515

1616
interface TextParserData {
17-
comment: Comment;
17+
sourcePath: string;
1818
token: Token;
1919
pos: number;
2020
i18n: TranslationProxy;
@@ -26,7 +26,8 @@ interface TextParserData {
2626
interface RelativeLink {
2727
pos: number;
2828
end: number;
29-
target: number;
29+
/** May be undefined if the registry can't find this file */
30+
target: number | undefined;
3031
}
3132

3233
/**
@@ -37,7 +38,7 @@ interface RelativeLink {
3738
*
3839
*/
3940
export function textContent(
40-
comment: Comment,
41+
sourcePath: string,
4142
token: Token,
4243
i18n: TranslationProxy,
4344
warning: (msg: TranslatedString, token: Token) => void,
@@ -47,7 +48,7 @@ export function textContent(
4748
) {
4849
let lastPartEnd = 0;
4950
const data: TextParserData = {
50-
comment,
51+
sourcePath,
5152
token,
5253
pos: 0,
5354
i18n,
@@ -67,7 +68,19 @@ export function textContent(
6768
target: ref.target,
6869
});
6970
lastPartEnd = ref.end;
70-
data.pos = lastPartEnd;
71+
data.pos = ref.end;
72+
if (!ref.target) {
73+
warning(
74+
i18n.relative_path_0_does_not_exist(
75+
token.text.slice(ref.pos, ref.end),
76+
),
77+
{
78+
kind: TokenSyntaxKind.Text,
79+
pos: ref.pos,
80+
text: token.text.slice(ref.pos, ref.end),
81+
},
82+
);
83+
}
7184
}
7285

7386
while (data.pos < token.text.length) {
@@ -103,7 +116,7 @@ export function textContent(
103116
*
104117
*/
105118
function checkMarkdownLink(data: TextParserData): RelativeLink | undefined {
106-
const { token, comment, media } = data;
119+
const { token, sourcePath, media } = data;
107120

108121
if (token.text[data.pos] === "[") {
109122
const labelEnd = findLabelEnd(token.text, data.pos + 1);
@@ -125,7 +138,7 @@ function checkMarkdownLink(data: TextParserData): RelativeLink | undefined {
125138
return {
126139
pos: labelEnd + 2,
127140
end: link.pos,
128-
target: media.register(comment.sourcePath!, link.str),
141+
target: media.register(sourcePath, link.str),
129142
};
130143
}
131144

@@ -146,7 +159,7 @@ function checkMarkdownLink(data: TextParserData): RelativeLink | undefined {
146159
* separating it from an above paragraph. For a first cut, this is good enough.
147160
*/
148161
function checkReference(data: TextParserData): RelativeLink | undefined {
149-
const { atNewLine, pos, token, media, comment } = data;
162+
const { atNewLine, pos, token, media, sourcePath } = data;
150163

151164
if (atNewLine) {
152165
let lookahead = pos;
@@ -177,10 +190,7 @@ function checkReference(data: TextParserData): RelativeLink | undefined {
177190
return {
178191
pos: lookahead,
179192
end: link.pos,
180-
target: media.register(
181-
comment.sourcePath!,
182-
link.str,
183-
),
193+
target: media.register(sourcePath, link.str),
184194
};
185195
}
186196

src/lib/converter/context.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,13 @@ export class Context {
222222
if (exportSymbol) {
223223
this.registerReflection(reflection, exportSymbol);
224224
}
225-
this.registerReflection(reflection, symbol);
225+
226+
const path = reflection.kindOf(
227+
ReflectionKind.Namespace | ReflectionKind.Module,
228+
)
229+
? symbol?.declarations?.find(ts.isSourceFile)?.fileName
230+
: undefined;
231+
this.project.registerReflection(reflection, symbol, path);
226232
}
227233

228234
finalizeDeclarationReflection(reflection: DeclarationReflection) {
@@ -255,7 +261,7 @@ export class Context {
255261
* @param symbol The symbol the given reflection was resolved from.
256262
*/
257263
registerReflection(reflection: Reflection, symbol: ts.Symbol | undefined) {
258-
this.project.registerReflection(reflection, symbol);
264+
this.project.registerReflection(reflection, symbol, void 0);
259265
}
260266

261267
/**

src/lib/converter/converter.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import {
4747
type DeclarationReference,
4848
} from "./comments/declarationReference";
4949
import { basename, dirname, resolve } from "path";
50+
import { MediaRegistry } from "../models/MediaRegistry";
5051

5152
/**
5253
* Compiles source files using TypeScript and converts compiler symbols to reflections.
@@ -243,6 +244,7 @@ export class Converter extends ChildableComponent<
243244

244245
const project = new ProjectReflection(
245246
this.application.options.getValue("name"),
247+
this.application.media,
246248
);
247249
const context = new Context(this, programs, project);
248250

@@ -266,15 +268,18 @@ export class Converter extends ChildableComponent<
266268
);
267269
for (const { displayName, path } of projectDocuments) {
268270
const file = new MinimalSourceFile(readFile(path), path);
269-
const { content, frontmatter } = this.parseRawComment(file);
271+
const { content, frontmatter } = this.parseRawComment(
272+
file,
273+
project.media,
274+
);
270275
const docRefl = new DocumentReflection(
271276
displayName,
272277
project,
273278
content,
274279
frontmatter,
275280
);
276281
project.addChild(docRefl);
277-
project.registerReflection(docRefl);
282+
project.registerReflection(docRefl, undefined, path);
278283
}
279284
}
280285

@@ -304,12 +309,13 @@ export class Converter extends ChildableComponent<
304309
/**
305310
* Parse the given file into a comment. Intended to be used with markdown files.
306311
*/
307-
parseRawComment(file: MinimalSourceFile) {
312+
parseRawComment(file: MinimalSourceFile, media: MediaRegistry) {
308313
return parseCommentString(
309314
lexCommentString(file.text),
310315
this.config,
311316
file,
312317
this.application.logger,
318+
media,
313319
);
314320
}
315321

@@ -426,7 +432,11 @@ export class Converter extends ChildableComponent<
426432
if (createModuleReflections === false) {
427433
// Special case for when we're giving a single entry point, we don't need to
428434
// create modules for each entry. Register the project as this module.
429-
context.project.registerReflection(context.project, symbol);
435+
context.project.registerReflection(
436+
context.project,
437+
symbol,
438+
entryPoint.sourceFile.fileName,
439+
);
430440
context.project.comment = symbol
431441
? context.getComment(symbol, context.project.kind)
432442
: context.getFileComment(node);
@@ -452,6 +462,7 @@ export class Converter extends ChildableComponent<
452462
const readme = readFile(entryPoint.readmeFile);
453463
const { content } = this.parseRawComment(
454464
new MinimalSourceFile(readme, entryPoint.readmeFile),
465+
context.project.media,
455466
);
456467
reflection.readme = content;
457468
}
@@ -564,15 +575,22 @@ export class Converter extends ChildableComponent<
564575
continue;
565576
}
566577

567-
const { content, frontmatter } = this.parseRawComment(file);
578+
const { content, frontmatter } = this.parseRawComment(
579+
file,
580+
reflection.project.media,
581+
);
568582
const docRefl = new DocumentReflection(
569583
basename(file.fileName).replace(/\.[^.]+$/, ""),
570584
parent,
571585
content,
572586
frontmatter,
573587
);
574588
parent.addChild(docRefl);
575-
parent.project.registerReflection(docRefl);
589+
parent.project.registerReflection(
590+
docRefl,
591+
undefined,
592+
file.fileName,
593+
);
576594
}
577595
}
578596
}

src/lib/converter/plugins/PackagePlugin.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ export class PackagePlugin extends ConverterComponent {
131131
if (this.readmeFile && this.readmeContents) {
132132
const { content } = this.application.converter.parseRawComment(
133133
new MinimalSourceFile(this.readmeContents, this.readmeFile),
134+
project.media,
134135
);
135136

136137
project.readme = content;

src/lib/converter/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ const functionTypeConverter: TypeConverter<ts.FunctionTypeNode, ts.Type> = {
360360
signature,
361361
new ReflectionSymbolId(symbol, node),
362362
);
363-
context.registerReflection(signature, void 0);
363+
context.registerReflection(signature, undefined);
364364
const signatureCtx = rc.withScope(signature);
365365

366366
reflection.signatures = [signature];

src/lib/internationalization/translatable.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export const translatable = {
5959
// comments/parser.ts
6060
multiple_type_parameters_on_template_tag_unsupported: `TypeDoc does not support multiple type parameters defined in a single @template tag with a comment.`,
6161
failed_to_find_jsdoc_tag_for_name_0: `Failed to find JSDoc tag for {0} after parsing comment, please file a bug report.`,
62+
relative_path_0_does_not_exist: `The relative path {0} does not exist.`,
6263

6364
inline_inheritdoc_should_not_appear_in_block_tag_in_comment_at_0:
6465
"An inline @inheritDoc tag should not appear within a block tag as it will not be processed in comment at {0}",
@@ -142,6 +143,7 @@ export const translatable = {
142143

143144
// deserialization
144145
serialized_project_referenced_0_not_part_of_project: `Serialized project referenced reflection {0}, which was not a part of the project.`,
146+
saved_relative_path_0_resolved_from_1_does_not_exist: `Serialized project referenced {0}, which does not exist relative to {1}.`,
145147

146148
// options
147149
circular_reference_extends_0: `Circular reference encountered for "extends" field of {0}`,

0 commit comments

Comments
 (0)