Skip to content

Commit

Permalink
Support multiple regions in @include and @includeCode
Browse files Browse the repository at this point in the history
  • Loading branch information
Gerrit0 committed Feb 2, 2025
1 parent c372df3 commit aff795d
Show file tree
Hide file tree
Showing 10 changed files with 225 additions and 178 deletions.
7 changes: 7 additions & 0 deletions site/tags/include.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ For example:
{@includeCode ../../example/src/enums.ts#simpleEnum}
```

Multiple regions may be specified, separated by commas. If multiple regions are
specified, TypeDoc will combine them into a single code block.

```md
{@includeCode file.ts#region1,region2}
```

Regions are specified in the files themselves via comments.

In TypeScript for example, the following would be a valid region:
Expand Down
253 changes: 121 additions & 132 deletions src/lib/converter/plugins/IncludePlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { CommentDisplayPart, Reflection } from "../../models/index.js";
import { MinimalSourceFile } from "../../utils/minimalSourceFile.js";
import type { Converter } from "../converter.js";
import { isFile } from "../../utils/fs.js";
import { escapeRegExp } from "../../utils/general.js";
import { dedent, escapeRegExp } from "../../utils/general.js";

/**
* Handles `@include` and `@includeCode` within comments/documents.
Expand Down Expand Up @@ -63,9 +63,8 @@ export class IncludePlugin extends ConverterComponent {
continue;
}

const [filename, target, requestedLines] = parseIncludeCodeTextPart(
part.text,
);
const { filename, regionTarget, requestedLines } =
parseIncludeCodeTextPart(part.text);

const file = path.resolve(relative, filename);
if (included.includes(file) && part.tag === "@include") {
Expand All @@ -80,30 +79,31 @@ export class IncludePlugin extends ConverterComponent {
} else if (isFile(file)) {
const text = fs.readFileSync(file, "utf-8");
const ext = path.extname(file).substring(1);

const includedText = regionTarget
? this.getRegions(
refl,
file,
ext,
part.text,
text,
regionTarget,
part.tag,
part.tag === "@includeCode",
)
: requestedLines
? this.getLines(
refl,
file,
part.text,
text,
requestedLines,
part.tag,
)
: text;

if (part.tag === "@include") {
const sf = new MinimalSourceFile(
target
? this.getRegion(
refl,
file,
ext,
part.text,
text,
target,
part.tag,
)
: requestedLines
? this.getLines(
refl,
file,
part.text,
text,
requestedLines,
part.tag,
)
: text,
file,
);
const sf = new MinimalSourceFile(includedText, file);
const { content } = this.owner.parseRawComment(
sf,
refl.project.files,
Expand All @@ -118,29 +118,7 @@ export class IncludePlugin extends ConverterComponent {
} else {
parts[i] = {
kind: "code",
text: makeCodeBlock(
ext,
target
? this.getRegion(
refl,
file,
ext,
part.text,
text,
target,
part.tag,
)
: requestedLines
? this.getLines(
refl,
file,
part.text,
text,
requestedLines,
part.tag,
)
: text,
),
text: makeCodeBlock(ext, includedText),
};
}
} else {
Expand All @@ -156,14 +134,15 @@ export class IncludePlugin extends ConverterComponent {
}
}

getRegion(
getRegions(
refl: Reflection,
file: string,
ext: string,
textPart: string,
text: string,
target: string,
regionTargets: string,
tag: string,
ignoreIndent: boolean,
) {
const regionTagsList = regionTagREsByExt[ext];
if (!regionTagsList) {
Expand All @@ -177,42 +156,21 @@ export class IncludePlugin extends ConverterComponent {
return "";
}

let found: string | false = false;
for (const [startTag, endTag] of regionTagsList) {
const safeTarget = escapeRegExp(target);
const start = text.match(startTag(safeTarget));
const end = text.match(endTag(safeTarget));
const targets = regionTargets.split(",").map((s) => s.trim());
let content = "";

const foundStart = start && start.length > 0;
const foundEnd = end && end.length > 0;
if (foundStart && !foundEnd) {
this.logger.error(
this.logger.i18n.include_0_tag_in_1_specified_2_file_3_region_4_region_close_not_found(
tag,
refl.getFriendlyFullName(),
textPart,
file,
target,
),
);
return "";
}
if (!foundStart && foundEnd) {
this.logger.error(
this.logger.i18n.include_0_tag_in_1_specified_2_file_3_region_4_region_open_not_found(
tag,
refl.getFriendlyFullName(),
textPart,
file,
target,
),
);
return "";
}
if (foundStart && foundEnd) {
if (start.length > 1) {
for (const target of targets) {
let found: string | false = false;
for (const [startTag, endTag] of regionTagsList) {
const safeTarget = escapeRegExp(target);
const start = text.match(startTag(safeTarget));
const end = text.match(endTag(safeTarget));

const foundStart = start && start.length > 0;
const foundEnd = end && end.length > 0;
if (foundStart && !foundEnd) {
this.logger.error(
this.logger.i18n.include_0_tag_in_1_specified_2_file_3_region_4_region_open_found_multiple_times(
this.logger.i18n.include_0_tag_in_1_specified_2_file_3_region_4_region_close_not_found(
tag,
refl.getFriendlyFullName(),
textPart,
Expand All @@ -222,9 +180,9 @@ export class IncludePlugin extends ConverterComponent {
);
return "";
}
if (end.length > 1) {
if (!foundStart && foundEnd) {
this.logger.error(
this.logger.i18n.include_0_tag_in_1_specified_2_file_3_region_4_region_close_found_multiple_times(
this.logger.i18n.include_0_tag_in_1_specified_2_file_3_region_4_region_open_not_found(
tag,
refl.getFriendlyFullName(),
textPart,
Expand All @@ -234,48 +192,77 @@ export class IncludePlugin extends ConverterComponent {
);
return "";
}
if (found) {
this.logger.error(
this.logger.i18n.include_0_tag_in_1_specified_2_file_3_region_4_region_found_multiple_times(
tag,
refl.getFriendlyFullName(),
textPart,
file,
target,
),
if (foundStart && foundEnd) {
if (start.length > 1) {
this.logger.error(
this.logger.i18n.include_0_tag_in_1_specified_2_file_3_region_4_region_open_found_multiple_times(
tag,
refl.getFriendlyFullName(),
textPart,
file,
target,
),
);
return "";
}
if (end.length > 1) {
this.logger.error(
this.logger.i18n.include_0_tag_in_1_specified_2_file_3_region_4_region_close_found_multiple_times(
tag,
refl.getFriendlyFullName(),
textPart,
file,
target,
),
);
return "";
}
if (found) {
this.logger.error(
this.logger.i18n.include_0_tag_in_1_specified_2_file_3_region_4_region_found_multiple_times(
tag,
refl.getFriendlyFullName(),
textPart,
file,
target,
),
);
return "";
}
found = text.substring(
text.indexOf(start[0]) + start[0].length,
text.indexOf(end[0]),
);
return "";
}
found = text.substring(
text.indexOf(start[0]) + start[0].length,
text.indexOf(end[0]),
}
if (found === false) {
this.logger.error(
this.logger.i18n.include_0_tag_in_1_specified_2_file_3_region_4_region_not_found(
tag,
refl.getFriendlyFullName(),
textPart,
file,
target,
),
);
return "";
}
if (found.trim() === "") {
this.logger.warn(
this.logger.i18n.include_0_tag_in_1_specified_2_file_3_region_4_region_empty(
tag,
refl.getFriendlyFullName(),
textPart,
file,
target,
),
);
}

content += ignoreIndent ? dedent(found) + "\n" : found;
}
if (found === false) {
this.logger.error(
this.logger.i18n.include_0_tag_in_1_specified_2_file_3_region_4_region_not_found(
tag,
refl.getFriendlyFullName(),
textPart,
file,
target,
),
);
return "";
}
if (found.trim() === "") {
this.logger.warn(
this.logger.i18n.include_0_tag_in_1_specified_2_file_3_region_4_region_empty(
tag,
refl.getFriendlyFullName(),
textPart,
file,
target,
),
);
}
return found;

return content;
}

getLines(
Expand Down Expand Up @@ -345,22 +332,24 @@ function makeCodeBlock(lang: string, code: string) {
return "\n\n```" + lang + "\n" + escaped.trimEnd() + "\n```";
}

function parseIncludeCodeTextPart(
text: string,
): [string, string | undefined, string | undefined] {
function parseIncludeCodeTextPart(text: string): {
filename: string;
regionTarget: string | undefined;
requestedLines: string | undefined;
} {
let filename = text.trim();
let target;
let requestedLines;
let regionTarget: string | undefined;
let requestedLines: string | undefined;
if (filename.includes("#")) {
const parsed = filename.split("#");
filename = parsed[0];
target = parsed[1];
regionTarget = parsed[1];
} else if (filename.includes(":")) {
const parsed = filename.split(":");
filename = parsed[0];
requestedLines = parsed[1];
}
return [filename, target, requestedLines];
return { filename, regionTarget, requestedLines };
}

type RegionTagRETuple = [
Expand Down
18 changes: 18 additions & 0 deletions src/lib/utils/general.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,24 @@ export function editDistance(s: string, t: string): number {
return v0[t.length];
}

export function dedent(text: string) {
const lines = text.split(/\r?\n/);
while (lines.length && lines[0].search(/\S/) === -1) {
lines.shift();
}
while (lines.length && lines[lines.length - 1].search(/\S/) === -1) {
lines.pop();
}

const minIndent = lines.reduce(
(indent, line) =>
line.length ? Math.min(indent, line.search(/\S/)) : indent,
Infinity,
);

return lines.map((line) => line.substring(minIndent)).join("\n");
}

export function getSimilarValues(values: Iterable<string>, compareTo: string) {
const results = new DefaultMap<number, string[]>(() => []);
let lowest = Infinity;
Expand Down
Loading

0 comments on commit aff795d

Please sign in to comment.