Skip to content

Commit 2e5c650

Browse files
shawninderGerrit0
authored andcommitted
Add language-dependant regions and line number syntax + docs & logs
1 parent fba0b91 commit 2e5c650

File tree

8 files changed

+388
-9
lines changed

8 files changed

+388
-9
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ title: Changelog
44

55
## Unreleased
66

7+
### Features
8+
9+
- `@includeCode` can now inject parts of files using region names or line
10+
numbers, #2816.
11+
712
## v0.27.6 (2024-12-26)
813

914
### Features

example/src/documents/include-code.md

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
---
2+
title: Include Code
3+
category: Documents
4+
---
5+
6+
# Including Code
7+
It can be convenient to write long-form guides/tutorials outside of doc comments.
8+
To support this, TypeDoc supports including documents (like this page!) which exist
9+
as standalone `.md` files in your repository.
10+
These files can then import code from other files using the `@includeCode` tag.
11+
12+
## The `@includeCode` Tag
13+
The `@includeCode` tag can be placed in an md file to insert a code snippet at that location. As an example, this file is inserting the code block below using:
14+
15+
```md
16+
{@includeCode ../reexports.ts}
17+
```
18+
19+
**Result:**
20+
{@includeCode ../reexports.ts}
21+
22+
### Include parts of files
23+
24+
#### Using regions
25+
The `@includeCode` tag can also include only parts of a file using language-dependent region syntax as defined in the VS Code documentation for [Folding](https://code.visualstudio.com/docs/editor/codebasics#_folding), reproduced here for convenience:
26+
27+
Language | Start region | End region
28+
---------|--------------|------------
29+
Bat | `::#region regionName` or `REM #region regionName` | `::#endregion regionName` or `REM #endregion regionName`
30+
C# | `#region regionName` | `#endregion regionName`
31+
C/C++ | `#pragma region regionName` | `#pragma endregion regionName`
32+
CSS/Less/SCSS | `/*#region regionName*/` | `/*#endregion regionName*/`
33+
Coffeescript | `#region regionName` | `#endregion regionName`
34+
F# | `//#region regionName` or `(#_region) regionName` | `//#endregion regionName` or `(#_endregion) regionName`
35+
Java | `//#region regionName` or `//<editor-fold> regionName` | `//#endregion regionName` or `//</editor-fold> regionName`
36+
Markdown | `<!-- #region regionName -->` | `<!-- #endregion regionName -->`
37+
Perl5 | `#region regionName` or `=pod regionName` | `#endregion regionName` or `=cut regionName`
38+
PHP | `#region regionName` | `#endregion regionName`
39+
PowerShell | `#region regionName` | `#endregion regionName`
40+
Python | `#region regionName` or `# region regionName` | `#endregion regionName` or `# endregion regionName`
41+
TypeScript/JavaScript | `//#region regionName` | `//#endregion regionName`
42+
Visual Basic | `#Region regionName` | `#End Region regionName`
43+
44+
For example:
45+
46+
```md
47+
{@includeCode ../enums.ts#simpleEnum}
48+
```
49+
50+
**Result:**
51+
52+
{@includeCode ../enums.ts#simpleEnum}
53+
54+
#### Using line numbers
55+
For cases where you can't modify the source file or where comments are not allowed (in JSON files, for example), you can use line numbers to include a specific region of a file.
56+
57+
For example:
58+
59+
```md
60+
{@includeCode ../../package.json:2,6-7}
61+
```
62+
63+
**Result:**
64+
65+
{@includeCode ../../package.json:2,6-7}
66+
67+
> **Warning:** This makes it difficult to maintain the file, as you may need to update the line numbers if you change the code.

example/src/enums.ts

+2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/** Describes the status of a delivery order. */
2+
// #region simpleEnum
23
export enum SimpleEnum {
34
/** This order has just been placed and is yet to be processed. */
45
Pending,
@@ -9,6 +10,7 @@ export enum SimpleEnum {
910
/** The order has been delivered. */
1011
Complete = "COMPLETE",
1112
}
13+
// #endregion simpleEnum
1214

1315
/**
1416
* [A crazy enum from the TypeScript

example/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
* @document documents/external-markdown.md
88
* @document documents/markdown.md
99
* @document documents/syntax-highlighting.md
10+
* @document documents/include-code.md
1011
*/
1112
export * from "./functions";
1213
export * from "./variables";

site/tags/include.md

+40
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,46 @@ selecting the syntax highlighting language.
3030
function doSomething() {}
3131
```
3232

33+
### Include parts of files
34+
35+
#### Using regions
36+
The `@include` and `@includeCode` tags can also include only parts of a file using language-dependent region syntax as defined in the VS Code documentation for [Folding](https://code.visualstudio.com/docs/editor/codebasics#_folding), reproduced here for convenience:
37+
38+
Language | Start region | End region
39+
---------|--------------|------------
40+
Bat | `::#region regionName` or `REM #region regionName` | `::#endregion regionName` or `REM #endregion regionName`
41+
C# | `#region regionName` | `#endregion regionName`
42+
C/C++ | `#pragma region regionName` | `#pragma endregion regionName`
43+
CSS/Less/SCSS | `/*#region regionName*/` | `/*#endregion regionName*/`
44+
Coffeescript | `#region regionName` | `#endregion regionName`
45+
F# | `//#region regionName` or `(#_region) regionName` | `//#endregion regionName` or `(#_endregion) regionName`
46+
Java | `//#region regionName` or `//<editor-fold> regionName` | `//#endregion regionName` or `//</editor-fold> regionName`
47+
Markdown | `<!-- #region regionName -->` | `<!-- #endregion regionName -->`
48+
Perl5 | `#region regionName` or `=pod regionName` | `#endregion regionName` or `=cut regionName`
49+
PHP | `#region regionName` | `#endregion regionName`
50+
PowerShell | `#region regionName` | `#endregion regionName`
51+
Python | `#region regionName` or `# region regionName` | `#endregion regionName` or `# endregion regionName`
52+
TypeScript/JavaScript | `//#region regionName` | `//#endregion regionName`
53+
Visual Basic | `#Region regionName` | `#End Region regionName`
54+
55+
##### Example
56+
57+
```md
58+
Here is a simple enum:
59+
{@includeCode ../enums.js#simpleEnum}
60+
```
61+
62+
#### Using line numbers
63+
For cases where you can't modify the source file or where comments are not allowed (in JSON files, for example), you can use line numbers to include a specific region of a file.
64+
65+
##### Example
66+
67+
```md
68+
In package.json, notice the following information:
69+
{@includeCode ../../package.json:2,6-7}
70+
```
71+
> **Warning:** This makes it difficult to maintain the file, as you may need to update the line numbers if you change the code.
72+
3373
## See Also
3474

3575
- The [jsdocCompatibility](../options/comments.md#jsdoccompatibility) option.

src/lib/converter/plugins/IncludePlugin.ts

+190-9
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { CommentDisplayPart, Reflection } from "../../models/index.js";
77
import { MinimalSourceFile } from "../../utils/minimalSourceFile.js";
88
import type { Converter } from "../converter.js";
99
import { isFile } from "../../utils/fs.js";
10+
import regionTagREsByExt from "../utils/regionTagREsByExt.js";
1011

1112
/**
1213
* Handles `@include` and `@includeCode` within comments/documents.
@@ -62,7 +63,10 @@ export class IncludePlugin extends ConverterComponent {
6263
continue;
6364
}
6465

65-
const [filename, target] = part.text.trim().split("#");
66+
const [filename, target, requestedLines] = parseIncludeCodeTextPart(
67+
part.text,
68+
);
69+
6670
const file = path.resolve(relative, filename);
6771
if (included.includes(file) && part.tag === "@include") {
6872
this.logger.error(
@@ -89,19 +93,29 @@ export class IncludePlugin extends ConverterComponent {
8993
);
9094
parts.splice(i, 1, ...content);
9195
} else {
92-
const regionStart = `// #region ${target}`;
93-
const regionEnd = `// #endregion ${target}`;
96+
const ext = path.extname(file).substring(1);
9497
parts[i] = {
9598
kind: "code",
9699
text: makeCodeBlock(
97-
path.extname(file).substring(1),
100+
ext,
98101
target
99-
? text.substring(
100-
text.indexOf(regionStart) +
101-
regionStart.length,
102-
text.indexOf(regionEnd),
102+
? this.getRegion(
103+
refl,
104+
file,
105+
ext,
106+
part.text,
107+
text,
108+
target,
103109
)
104-
: text,
110+
: requestedLines
111+
? this.getLines(
112+
refl,
113+
file,
114+
part.text,
115+
text,
116+
requestedLines,
117+
)
118+
: text,
105119
),
106120
};
107121
}
@@ -117,8 +131,175 @@ export class IncludePlugin extends ConverterComponent {
117131
}
118132
}
119133
}
134+
135+
getRegion(
136+
refl: Reflection,
137+
file: string,
138+
ext: string,
139+
textPart: string,
140+
text: string,
141+
target: string,
142+
) {
143+
const regionTagsList = regionTagREsByExt[ext];
144+
let found: string | false = false;
145+
for (const [startTag, endTag] of regionTagsList) {
146+
const start = text.match(startTag(target));
147+
const end = text.match(endTag(target));
148+
149+
const foundStart = start && start.length > 0;
150+
const foundEnd = end && end.length > 0;
151+
if (foundStart && !foundEnd) {
152+
this.logger.error(
153+
this.logger.i18n.includeCode_tag_in_0_specified_1_file_2_region_3_region_close_not_found(
154+
refl.getFriendlyFullName(),
155+
textPart,
156+
file,
157+
target,
158+
),
159+
);
160+
return "";
161+
}
162+
if (!foundStart && foundEnd) {
163+
this.logger.error(
164+
this.logger.i18n.includeCode_tag_in_0_specified_1_file_2_region_3_region_open_not_found(
165+
refl.getFriendlyFullName(),
166+
textPart,
167+
file,
168+
target,
169+
),
170+
);
171+
return "";
172+
}
173+
if (foundStart && foundEnd) {
174+
if (start.length > 1) {
175+
this.logger.error(
176+
this.logger.i18n.includeCode_tag_in_0_specified_1_file_2_region_3_region_open_found_multiple_times(
177+
refl.getFriendlyFullName(),
178+
textPart,
179+
file,
180+
target,
181+
),
182+
);
183+
return "";
184+
}
185+
if (end.length > 1) {
186+
this.logger.error(
187+
this.logger.i18n.includeCode_tag_in_0_specified_1_file_2_region_3_region_close_found_multiple_times(
188+
refl.getFriendlyFullName(),
189+
textPart,
190+
file,
191+
target,
192+
),
193+
);
194+
return "";
195+
}
196+
if (found) {
197+
this.logger.error(
198+
this.logger.i18n.includeCode_tag_in_0_specified_1_file_2_region_3_region_found_multiple_times(
199+
refl.getFriendlyFullName(),
200+
textPart,
201+
file,
202+
target,
203+
),
204+
);
205+
return "";
206+
}
207+
found = text.substring(
208+
text.indexOf(start[0]) + start[0].length,
209+
text.indexOf(end[0]),
210+
);
211+
}
212+
}
213+
if (!found) {
214+
this.logger.error(
215+
this.logger.i18n.includeCode_tag_in_0_specified_1_file_2_region_3_region_not_found(
216+
refl.getFriendlyFullName(),
217+
textPart,
218+
file,
219+
target,
220+
),
221+
);
222+
return "";
223+
}
224+
return found;
225+
}
226+
227+
getLines(
228+
refl: Reflection,
229+
file: string,
230+
textPart: string,
231+
text: string,
232+
requestedLines: string,
233+
) {
234+
let output = "";
235+
const lines = text.split(/\r\n|\r|\n/);
236+
requestedLines.split(",").forEach((requestedLineString) => {
237+
if (requestedLineString.includes("-")) {
238+
const [start, end] = requestedLineString.split("-").map(Number);
239+
if (start > end) {
240+
this.logger.error(
241+
this.logger.i18n.includeCode_tag_in_0_specified_1_file_2_lines_3_invalid_range(
242+
refl.getFriendlyFullName(),
243+
textPart,
244+
file,
245+
requestedLines,
246+
),
247+
);
248+
return "";
249+
}
250+
if (start > lines.length || end > lines.length) {
251+
this.logger.error(
252+
this.logger.i18n.includeCode_tag_in_0_specified_1_file_2_lines_3_but_only_4_lines(
253+
refl.getFriendlyFullName(),
254+
textPart,
255+
file,
256+
requestedLines,
257+
lines.length.toString(),
258+
),
259+
);
260+
return "";
261+
}
262+
output += lines.slice(start - 1, end).join("\n") + "\n";
263+
} else {
264+
const requestedLine = Number(requestedLineString);
265+
if (requestedLine > lines.length) {
266+
this.logger.error(
267+
this.logger.i18n.includeCode_tag_in_0_specified_1_file_2_lines_3_but_only_4_lines(
268+
refl.getFriendlyFullName(),
269+
textPart,
270+
file,
271+
requestedLines,
272+
lines.length.toString(),
273+
),
274+
);
275+
return "";
276+
}
277+
output += lines[requestedLine - 1] + "\n";
278+
}
279+
});
280+
281+
return output;
282+
}
120283
}
121284
function makeCodeBlock(lang: string, code: string) {
122285
const escaped = code.replace(/`(?=`)/g, "`\u200B");
123286
return "\n\n```" + lang + "\n" + escaped.trimEnd() + "\n```";
124287
}
288+
289+
function parseIncludeCodeTextPart(
290+
text: string,
291+
): [string, string | undefined, string | undefined] {
292+
let filename = text.trim();
293+
let target;
294+
let requestedLines;
295+
if (filename.includes("#")) {
296+
const parsed = filename.split("#");
297+
filename = parsed[0];
298+
target = parsed[1];
299+
} else if (filename.includes(":")) {
300+
const parsed = filename.split(":");
301+
filename = parsed[0];
302+
requestedLines = parsed[1];
303+
}
304+
return [filename, target, requestedLines];
305+
}

0 commit comments

Comments
 (0)