Skip to content

Commit 437a441

Browse files
committed
☕ Replace JSDoc for manually defined functions
1 parent fb160c7 commit 437a441

File tree

7 files changed

+179
-36
lines changed

7 files changed

+179
-36
lines changed

scripts/gen-function/format.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ const translate: Record<string, string> = {
1818
"lnum-end": "lnum_end",
1919
};
2020

21-
function formatDocs(docs: string): string[] {
21+
export function formatDocs(docs: string): string[] {
2222
const lines = docs.replaceAll(/\*\//g, "* /").split("\n");
2323
const normalizedLines = lines.map((v) => ` * ${v}`.trimEnd());
2424
return ["/**", ...normalizedLines, " */"];

scripts/gen-function/gen-function.ts

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,34 @@ import {
33
intersection,
44
} from "https://deno.land/x/set_operations@v1.1.0/mod.ts";
55
import * as path from "https://deno.land/std@0.183.0/path/mod.ts";
6-
import * as commonManual from "../../denops_std/function/_manual.ts";
7-
import * as vimManual from "../../denops_std/function/vim/_manual.ts";
8-
import * as nvimManual from "../../denops_std/function/nvim/_manual.ts";
96
import { parse } from "./parse.ts";
107
import { format } from "./format.ts";
8+
import { transform } from "./transform.ts";
119
import { downloadString } from "../utils.ts";
1210

1311
const VIM_VERSION = "9.0.0472";
1412
const NVIM_VERSION = "0.8.0";
1513

16-
const manualFnSet = new Set([
17-
...Object.keys(commonManual),
18-
...Object.keys(vimManual),
19-
...Object.keys(nvimManual),
20-
]);
14+
const commonGenerateModule = "../../denops_std/function/_generated.ts";
15+
const vimGenerateModule = "../../denops_std/function/vim/_generated.ts";
16+
const nvimGenerateModule = "../../denops_std/function/nvim/_generated.ts";
17+
18+
const commonManualModule = "../../denops_std/function/_manual.ts";
19+
const vimManualModule = "../../denops_std/function/vim/_manual.ts";
20+
const nvimManualModule = "../../denops_std/function/nvim/_manual.ts";
21+
22+
const manualModules = [
23+
commonManualModule,
24+
vimManualModule,
25+
nvimManualModule,
26+
];
27+
const manualFnSet = new Set(
28+
(await Promise.all(
29+
manualModules.map(async (moduleName) =>
30+
Object.keys(await import(moduleName))
31+
),
32+
)).flat(),
33+
);
2134

2235
const vimHelpDownloadUrls = [
2336
`https://raw.githubusercontent.com/vim/vim/v${VIM_VERSION}/runtime/doc/builtin.txt`,
@@ -64,20 +77,22 @@ const nvimOnlyCode = format(
6477
);
6578

6679
await Deno.writeTextFile(
67-
path.fromFileUrl(
68-
new URL("../../denops_std/function/_generated.ts", import.meta.url),
69-
),
80+
resolvePath(commonGenerateModule),
7081
commonCode.join("\n"),
7182
);
7283
await Deno.writeTextFile(
73-
path.fromFileUrl(
74-
new URL("../../denops_std/function/vim/_generated.ts", import.meta.url),
75-
),
84+
resolvePath(vimGenerateModule),
7685
vimOnlyCode.join("\n"),
7786
);
7887
await Deno.writeTextFile(
79-
path.fromFileUrl(
80-
new URL("../../denops_std/function/nvim/_generated.ts", import.meta.url),
81-
),
88+
resolvePath(nvimGenerateModule),
8289
nvimOnlyCode.join("\n"),
8390
);
91+
92+
await transform(resolvePath(commonManualModule), vimDefs);
93+
await transform(resolvePath(vimManualModule), vimDefs);
94+
await transform(resolvePath(nvimManualModule), nvimDefs);
95+
96+
function resolvePath(p: string): string {
97+
return path.fromFileUrl(new URL(p, import.meta.url));
98+
}

scripts/gen-function/transform.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { transform as transformSource } from "../transform.ts";
2+
import { formatDocs } from "./format.ts";
3+
import type { Definition } from "./types.ts";
4+
5+
export function transform(
6+
rootSourcePath: string,
7+
definitions: Definition[],
8+
): Promise<void> {
9+
const fnJSDocs = new Map(
10+
definitions.map(({ fn, docs }) => [fn, formatDocs(docs).join("\n")]),
11+
);
12+
return transformSource(rootSourcePath, fnJSDocs);
13+
}

scripts/gen-option/format.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ function defaultValue(type: OptionType): string {
2424
}
2525
}
2626

27-
function formatDocs(docs: string): string[] {
27+
export function formatDocs(docs: string): string[] {
2828
const lines = docs.replaceAll(/\*\//g, "* /").split("\n");
2929
const normalizedLines = lines.map((v) => ` * ${v}`.trimEnd());
3030
return ["/**", ...normalizedLines, " */"];

scripts/gen-option/gen-option.ts

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,34 @@ import {
33
intersection,
44
} from "https://deno.land/x/set_operations@v1.1.0/mod.ts";
55
import * as path from "https://deno.land/std@0.183.0/path/mod.ts";
6-
import * as commonManual from "../../denops_std/option/_manual.ts";
7-
import * as vimManual from "../../denops_std/option/vim/_manual.ts";
8-
import * as nvimManual from "../../denops_std/option/nvim/_manual.ts";
96
import { parse } from "./parse.ts";
107
import { format } from "./format.ts";
8+
import { transform } from "./transform.ts";
119
import { downloadString } from "../utils.ts";
1210

1311
const VIM_VERSION = "9.0.0472";
1412
const NVIM_VERSION = "0.8.0";
1513

16-
const manualOptionSet = new Set([
17-
...Object.keys(commonManual),
18-
...Object.keys(vimManual),
19-
...Object.keys(nvimManual),
20-
]);
14+
const commonGenerateModule = "../../denops_std/option/_generated.ts";
15+
const vimGenerateModule = "../../denops_std/option/vim/_generated.ts";
16+
const nvimGenerateModule = "../../denops_std/option/nvim/_generated.ts";
17+
18+
const commonManualModule = "../../denops_std/option/_manual.ts";
19+
const vimManualModule = "../../denops_std/option/vim/_manual.ts";
20+
const nvimManualModule = "../../denops_std/option/nvim/_manual.ts";
21+
22+
const manualModules = [
23+
commonManualModule,
24+
vimManualModule,
25+
nvimManualModule,
26+
];
27+
const manualOptionSet = new Set(
28+
(await Promise.all(
29+
manualModules.map(async (moduleName) =>
30+
Object.keys(await import(moduleName))
31+
),
32+
)).flat(),
33+
);
2134

2235
const vimHelpDownloadUrls = [
2336
`https://raw.githubusercontent.com/vim/vim/v${VIM_VERSION}/runtime/doc/options.txt`,
@@ -63,20 +76,22 @@ const nvimOnlyCode = format(
6376
);
6477

6578
await Deno.writeTextFile(
66-
path.fromFileUrl(
67-
new URL("../../denops_std/option/_generated.ts", import.meta.url),
68-
),
79+
resolvePath(commonGenerateModule),
6980
commonCode.join("\n"),
7081
);
7182
await Deno.writeTextFile(
72-
path.fromFileUrl(
73-
new URL("../../denops_std/option/vim/_generated.ts", import.meta.url),
74-
),
83+
resolvePath(vimGenerateModule),
7584
vimOnlyCode.join("\n"),
7685
);
7786
await Deno.writeTextFile(
78-
path.fromFileUrl(
79-
new URL("../../denops_std/option/nvim/_generated.ts", import.meta.url),
80-
),
87+
resolvePath(nvimGenerateModule),
8188
nvimOnlyCode.join("\n"),
8289
);
90+
91+
await transform(resolvePath(commonManualModule), vimDefs);
92+
await transform(resolvePath(vimManualModule), vimDefs);
93+
await transform(resolvePath(nvimManualModule), nvimDefs);
94+
95+
function resolvePath(p: string): string {
96+
return path.fromFileUrl(new URL(p, import.meta.url));
97+
}

scripts/gen-option/transform.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { transform as transformSource } from "../transform.ts";
2+
import { formatDocs } from "./format.ts";
3+
import type { Option } from "./types.ts";
4+
5+
export function transform(
6+
rootSourcePath: string,
7+
options: Option[],
8+
): Promise<void> {
9+
const fnJSDocs = new Map(
10+
options.map(({ name, docs }) => [name, formatDocs(docs).join("\n")]),
11+
);
12+
return transformSource(rootSourcePath, fnJSDocs);
13+
}

scripts/transform.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import {
2+
fromFileUrl,
3+
toFileUrl,
4+
} from "https://deno.land/std@0.183.0/path/mod.ts";
5+
import { intersection } from "https://deno.land/x/set_operations@v1.1.0/mod.ts";
6+
7+
interface ModuleInformation {
8+
sourcePath: string;
9+
sourceText: string;
10+
functions: Set<string>;
11+
}
12+
13+
function extractExportModulePaths(
14+
sourcePath: string,
15+
sourceText: string,
16+
): string[] {
17+
const baseUrl = new URL("./", toFileUrl(sourcePath));
18+
return [...sourceText.matchAll(/^export \* from "(?<url>.*)"/gm)].map((
19+
[, url]: string[],
20+
) => fromFileUrl(new URL(url, baseUrl)));
21+
}
22+
23+
function extractExportFunctions(sourceText: string): Set<string> {
24+
return new Set(
25+
[...sourceText.matchAll(
26+
/^export\s+(?:async\s+)?function\s+(?:\*\s*)?(?<name>\w+)/gms,
27+
)].map(([, name]: string[]) => name),
28+
);
29+
}
30+
31+
async function findExportModules(
32+
sourcePath: string,
33+
): Promise<ModuleInformation[]> {
34+
const sourceText = await Deno.readTextFile(sourcePath);
35+
const modules = extractExportModulePaths(sourcePath, sourceText);
36+
const descendants = await Promise.all(modules.map(findExportModules));
37+
const functions = extractExportFunctions(sourceText);
38+
return [
39+
{ sourcePath, sourceText, functions },
40+
...descendants.flat(),
41+
].filter(({ functions }) => functions.size > 0);
42+
}
43+
44+
function replaceFunctionDocs(
45+
sourceText: string,
46+
fnJSDocs: Map<string, string>,
47+
): string {
48+
const reLeading = /(?<leading>\s*)/.source;
49+
const reJSDoc = /(?:\/\*\*[^/](?:[^*]|\*[^/])*\*\/)?/.source;
50+
const reLineComments = /(?:\s|\/\/[^\n]*\n)*/.source;
51+
const reFunction =
52+
/^export\s+(?:async\s+)?function\s+(?:\*\s*)?(?<name>\w+)/.source;
53+
const reDef = `(?<def>${reLineComments}${reFunction})`;
54+
const reFunctions = new RegExp(`${reLeading}${reJSDoc}${reDef}`, "gms");
55+
const fnReplaced = new Set<string>();
56+
return sourceText.replaceAll(reFunctions, (prev, ...args) => {
57+
const { leading, def, name } = args.at(-1) as Record<string, string>;
58+
const jsdoc = fnJSDocs.get(name);
59+
if (jsdoc && !fnReplaced.has(name)) {
60+
fnReplaced.add(name);
61+
return `${leading}${jsdoc}${def}`;
62+
}
63+
return prev;
64+
});
65+
}
66+
67+
/**
68+
* Overwrite the source file by replacing the JSDoc in the export functions.
69+
* Everything except JSDoc is kept.
70+
* Files referenced by `export * from "..."` are replaced recursively.
71+
*
72+
* @param rootSourcePath - origin source file path
73+
* @param fnJSDocs - Map of function names to JSDoc
74+
*/
75+
export async function transform(
76+
rootSourcePath: string,
77+
fnJSDocs: Map<string, string>,
78+
): Promise<void> {
79+
const fnNames = new Set(fnJSDocs.keys());
80+
const informations = await findExportModules(rootSourcePath);
81+
for (const { sourcePath, sourceText, functions } of informations) {
82+
if (intersection(functions, fnNames).size > 0) {
83+
const transformed = replaceFunctionDocs(sourceText, fnJSDocs);
84+
await Deno.writeTextFile(sourcePath, transformed);
85+
}
86+
}
87+
}

0 commit comments

Comments
 (0)