typedoc-plugin-dt-links 1.0.0
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.
- package/LICENSE +19 -0
- package/README.md +53 -0
- package/data/dt_history.txt +87798 -0
- package/dist/plugin.js +189 -0
- package/package.json +39 -0
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { ParameterType, splitUnquotedString, TypeScript as ts, } from "typedoc";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { dirname } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
const PLUGIN_PREFIX = "[typedoc-plugin-dt-links]";
|
|
6
|
+
const DT_DEFAULT_BRANCH = "master";
|
|
7
|
+
export const DT_COMMITS = readFileSync(dirname(fileURLToPath(import.meta.url)) + "/../data/dt_history.txt", "utf-8")
|
|
8
|
+
.split("\n")
|
|
9
|
+
.map((line) => {
|
|
10
|
+
const split = line.split(" ");
|
|
11
|
+
return [split[0], +split[1]];
|
|
12
|
+
});
|
|
13
|
+
// Binary search DT_COMMITS for the commit just before date
|
|
14
|
+
export function findDtCommitHash(date) {
|
|
15
|
+
// If the date is after the most recent commit, we don't have it in history
|
|
16
|
+
// and should use the default git branch.
|
|
17
|
+
if (date > DT_COMMITS[0][1]) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
let low = 0;
|
|
21
|
+
let high = DT_COMMITS.length;
|
|
22
|
+
// Bias the search towards the package having been published fairly recently.
|
|
23
|
+
let guess = 4096;
|
|
24
|
+
while (high > low) {
|
|
25
|
+
if (DT_COMMITS[guess][1] < date) {
|
|
26
|
+
high = guess;
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
low = guess + 1;
|
|
30
|
+
}
|
|
31
|
+
guess = low + Math.floor((high - low) / 2);
|
|
32
|
+
}
|
|
33
|
+
if (guess >= DT_COMMITS.length) {
|
|
34
|
+
return DT_COMMITS[DT_COMMITS.length - 1][0];
|
|
35
|
+
}
|
|
36
|
+
if (guess > 0 && DT_COMMITS[guess - 1][1] === date) {
|
|
37
|
+
return DT_COMMITS[guess - 1][0];
|
|
38
|
+
}
|
|
39
|
+
return DT_COMMITS[guess][0];
|
|
40
|
+
}
|
|
41
|
+
function discoverSourceFilePosition(sf, qualifiedName) {
|
|
42
|
+
const path = splitUnquotedString(qualifiedName, ".");
|
|
43
|
+
return walkPath(0, sf);
|
|
44
|
+
function walkPath(index, node) {
|
|
45
|
+
if (index === path.length) {
|
|
46
|
+
const name = node.name;
|
|
47
|
+
if (name &&
|
|
48
|
+
(ts.isMemberName(name) || ts.isComputedPropertyName(name))) {
|
|
49
|
+
return name.getStart(sf, false);
|
|
50
|
+
}
|
|
51
|
+
return node.getStart(sf, false);
|
|
52
|
+
}
|
|
53
|
+
return ts.forEachChild(node, (child) => {
|
|
54
|
+
// declare module "events" {
|
|
55
|
+
// namespace NodeJS {
|
|
56
|
+
// global {
|
|
57
|
+
if (ts.isModuleDeclaration(child)) {
|
|
58
|
+
if (ts.isStringLiteral(child.name)) {
|
|
59
|
+
// Not included in the qualified name from TypeDoc
|
|
60
|
+
return walkPath(index, child);
|
|
61
|
+
}
|
|
62
|
+
else if (child.name.text === path[index]) {
|
|
63
|
+
return walkPath(index + 1, child);
|
|
64
|
+
}
|
|
65
|
+
else if (child.name.text === "global" &&
|
|
66
|
+
// Not quite sure why TypeDoc gives this name...
|
|
67
|
+
path[index] === "__global") {
|
|
68
|
+
return walkPath(index + 1, child);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (ts.isModuleBlock(child) ||
|
|
72
|
+
ts.isVariableDeclaration(child) ||
|
|
73
|
+
ts.isVariableDeclarationList(child)) {
|
|
74
|
+
return walkPath(index, child);
|
|
75
|
+
}
|
|
76
|
+
if (ts.isClassDeclaration(child) ||
|
|
77
|
+
ts.isInterfaceDeclaration(child)) {
|
|
78
|
+
if ((child.name?.text ?? "default") === path[index]) {
|
|
79
|
+
return walkPath(index + 1, child);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (ts.isFunctionDeclaration(child) ||
|
|
83
|
+
ts.isPropertyDeclaration(child) ||
|
|
84
|
+
ts.isVariableDeclaration(child) ||
|
|
85
|
+
ts.isMethodDeclaration(child) ||
|
|
86
|
+
ts.isMethodSignature(child) ||
|
|
87
|
+
ts.isPropertySignature(child) ||
|
|
88
|
+
ts.isPropertyAssignment(child)) {
|
|
89
|
+
if (child.name?.getText() === path[index]) {
|
|
90
|
+
return walkPath(index + 1, child);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const sourceFileCache = new Map();
|
|
97
|
+
export function getLineNumber(symbolId) {
|
|
98
|
+
let sf = sourceFileCache.get(symbolId.fileName);
|
|
99
|
+
if (!sf) {
|
|
100
|
+
try {
|
|
101
|
+
sf = ts.createSourceFile(symbolId.fileName, readFileSync(symbolId.fileName, "utf-8"), {
|
|
102
|
+
languageVersion: ts.ScriptTarget.ESNext,
|
|
103
|
+
jsDocParsingMode: ts.JSDocParsingMode.ParseNone,
|
|
104
|
+
impliedNodeFormat: ts.ModuleKind.ESNext,
|
|
105
|
+
}, true, ts.ScriptKind.TS);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
sf = ts.createSourceFile(symbolId.fileName, "", ts.ScriptTarget.ESNext, true, ts.ScriptKind.TS);
|
|
109
|
+
}
|
|
110
|
+
sourceFileCache.set(symbolId.fileName, sf);
|
|
111
|
+
}
|
|
112
|
+
if (Number.isFinite(symbolId.pos)) {
|
|
113
|
+
// Add 1 as the pos we get from TypeDoc will reference the start of
|
|
114
|
+
// the node, not the actual line on which the declaration lives.
|
|
115
|
+
return sf.getLineAndCharacterOfPosition(symbolId.pos).line + 1;
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
// Ick! pos isn't serialized, so if we're dealing with JSON we have to
|
|
119
|
+
// make an educated guess about what line we ought to link to. If we can't
|
|
120
|
+
// find it, just link to the top of the file.
|
|
121
|
+
const pos = discoverSourceFilePosition(sf, symbolId.qualifiedName) ?? 0;
|
|
122
|
+
return sf.getLineAndCharacterOfPosition(pos).line;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
export function load(app) {
|
|
126
|
+
app.options.addDeclaration({
|
|
127
|
+
name: "warnOnUnstableDtLink",
|
|
128
|
+
defaultValue: true,
|
|
129
|
+
help: `${PLUGIN_PREFIX} Generate a warning if unable to link to a specific commit for a DT package.`,
|
|
130
|
+
type: ParameterType.Boolean,
|
|
131
|
+
});
|
|
132
|
+
app.converter.addUnknownSymbolResolver(resolveSymbol);
|
|
133
|
+
const publishHashCache = new Map();
|
|
134
|
+
function getPublishHash(packagePath, packageName) {
|
|
135
|
+
let hash = publishHashCache.get(packagePath);
|
|
136
|
+
if (!hash) {
|
|
137
|
+
try {
|
|
138
|
+
const readme = readFileSync(packagePath + "/README.md", "utf-8");
|
|
139
|
+
const update = readme.match(/Last updated:(.*)$/im);
|
|
140
|
+
if (!update) {
|
|
141
|
+
hash = DT_DEFAULT_BRANCH;
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
const publishDate = Date.parse(update[1]);
|
|
145
|
+
app.logger.verbose(`${PLUGIN_PREFIX} @types/${packageName} was updated at ${new Date(publishDate).toISOString()}`);
|
|
146
|
+
hash =
|
|
147
|
+
findDtCommitHash(publishDate / 1000) ??
|
|
148
|
+
DT_DEFAULT_BRANCH;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
hash = DT_DEFAULT_BRANCH;
|
|
153
|
+
}
|
|
154
|
+
if (hash === DT_DEFAULT_BRANCH &&
|
|
155
|
+
app.options.getValue("warnOnUnstableDtLink")) {
|
|
156
|
+
const version = JSON.parse(readFileSync(packagePath + "/package.json", "utf-8")).version;
|
|
157
|
+
app.logger.warn(`${PLUGIN_PREFIX} Failed to discover git hash for @types/${packageName} v${version}, linking to ${DT_DEFAULT_BRANCH} branch. This will eventually cause broken links.`);
|
|
158
|
+
}
|
|
159
|
+
app.logger.verbose(`${PLUGIN_PREFIX} mapping @types/${packageName} to ${hash}`);
|
|
160
|
+
publishHashCache.set(packagePath, hash);
|
|
161
|
+
}
|
|
162
|
+
return publishHashCache.get(packagePath);
|
|
163
|
+
}
|
|
164
|
+
function resolveSymbol(_declaration, _refl, _part, symbolId) {
|
|
165
|
+
if (!symbolId) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
// Attempt to decide package name from path if it contains "node_modules"
|
|
169
|
+
let startIndex = symbolId.fileName.lastIndexOf("node_modules/@types/");
|
|
170
|
+
if (startIndex === -1)
|
|
171
|
+
return;
|
|
172
|
+
startIndex += "node_modules/@types/".length;
|
|
173
|
+
let stopIndex = symbolId.fileName.indexOf("/", startIndex);
|
|
174
|
+
const packageName = symbolId.fileName.substring(startIndex, stopIndex);
|
|
175
|
+
const innerPath = symbolId.fileName
|
|
176
|
+
.substring(stopIndex)
|
|
177
|
+
.replaceAll("\\", "/");
|
|
178
|
+
const hash = getPublishHash(symbolId.fileName.substring(0, stopIndex), packageName);
|
|
179
|
+
return [
|
|
180
|
+
"https://github.com/DefinitelyTyped/DefinitelyTyped/blob/",
|
|
181
|
+
hash,
|
|
182
|
+
"/types/",
|
|
183
|
+
packageName,
|
|
184
|
+
innerPath,
|
|
185
|
+
"#L",
|
|
186
|
+
getLineNumber(symbolId) + 1,
|
|
187
|
+
].join("");
|
|
188
|
+
}
|
|
189
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "typedoc-plugin-dt-links",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "dist/plugin.js",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"devDependencies": {
|
|
8
|
+
"@types/node": "22.7.4",
|
|
9
|
+
"@voxpelli/node-test-pretty-reporter": "^1.1.2",
|
|
10
|
+
"faucet": "^0.0.4",
|
|
11
|
+
"prettier": "^3.3.3",
|
|
12
|
+
"tap-prettify": "^0.0.2",
|
|
13
|
+
"tsx": "^4.19.1",
|
|
14
|
+
"typedoc": "^0.26.7",
|
|
15
|
+
"typescript": "^5.6.2"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"typedoc-plugin"
|
|
19
|
+
],
|
|
20
|
+
"files": [
|
|
21
|
+
"dist/plugin.js",
|
|
22
|
+
"data/dt_history.txt"
|
|
23
|
+
],
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"typedoc": ">= 0.23.14 || 0.24.x || 0.25.x || 0.26.x"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"lint": "prettier --check .",
|
|
29
|
+
"build": "tsc",
|
|
30
|
+
"test": "tsx --test --test-reporter=@voxpelli/node-test-pretty-reporter src/test/plugin.test.ts"
|
|
31
|
+
},
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "https://github.com/Gerrit0/typedoc-plugin-dt-links.git"
|
|
35
|
+
},
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/Gerrit0/typedoc-plugin-dt-links/issues"
|
|
38
|
+
}
|
|
39
|
+
}
|