@doccov/sdk 0.15.1 → 0.18.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 +1 -1
- package/dist/index.d.ts +754 -96
- package/dist/index.js +1958 -646
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -4,19 +4,169 @@ var __getProtoOf = Object.getPrototypeOf;
|
|
|
4
4
|
var __defProp = Object.defineProperty;
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
var __toESM = (
|
|
8
|
-
target =
|
|
9
|
-
const to = isNodeMode || !
|
|
10
|
-
for (let key of __getOwnPropNames(
|
|
7
|
+
var __toESM = (mod2, isNodeMode, target) => {
|
|
8
|
+
target = mod2 != null ? __create(__getProtoOf(mod2)) : {};
|
|
9
|
+
const to = isNodeMode || !mod2 || !mod2.__esModule ? __defProp(target, "default", { value: mod2, enumerable: true }) : target;
|
|
10
|
+
for (let key of __getOwnPropNames(mod2))
|
|
11
11
|
if (!__hasOwnProp.call(to, key))
|
|
12
12
|
__defProp(to, key, {
|
|
13
|
-
get: () =>
|
|
13
|
+
get: () => mod2[key],
|
|
14
14
|
enumerable: true
|
|
15
15
|
});
|
|
16
16
|
return to;
|
|
17
17
|
};
|
|
18
|
-
var __require = createRequire(import.meta.url);
|
|
18
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
19
|
|
|
20
|
+
// src/analysis/schema-detection.ts
|
|
21
|
+
import * as fs from "node:fs";
|
|
22
|
+
|
|
23
|
+
// src/extraction/standard-schema.ts
|
|
24
|
+
function isStandardJSONSchema(value) {
|
|
25
|
+
if (typeof value !== "object" || value === null) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
const obj = value;
|
|
29
|
+
const standard = obj["~standard"];
|
|
30
|
+
if (typeof standard !== "object" || standard === null) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
const stdObj = standard;
|
|
34
|
+
if (typeof stdObj.version !== "number") {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
const jsonSchema = stdObj.jsonSchema;
|
|
38
|
+
if (typeof jsonSchema !== "object" || jsonSchema === null) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
const jsonSchemaObj = jsonSchema;
|
|
42
|
+
return typeof jsonSchemaObj.output === "function";
|
|
43
|
+
}
|
|
44
|
+
function extractViaStandardSchema(schema, options = {}) {
|
|
45
|
+
const standard = schema["~standard"];
|
|
46
|
+
const target = options.target ?? "draft-2020-12";
|
|
47
|
+
const extractedSchema = standard.jsonSchema.output({ target });
|
|
48
|
+
return {
|
|
49
|
+
schema: extractedSchema,
|
|
50
|
+
vendor: standard.vendor,
|
|
51
|
+
version: standard.version
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function tryExtractStandardSchema(value, options = {}) {
|
|
55
|
+
if (!isStandardJSONSchema(value)) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
return extractViaStandardSchema(value, options);
|
|
60
|
+
} catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
var KNOWN_VENDORS = {
|
|
65
|
+
zod: { minVersion: "4.2.0", homepage: "https://zod.dev" },
|
|
66
|
+
arktype: { minVersion: "2.0.0", homepage: "https://arktype.io" },
|
|
67
|
+
valibot: { minVersion: "1.0.0", homepage: "https://valibot.dev" }
|
|
68
|
+
};
|
|
69
|
+
// src/analysis/schema-detection.ts
|
|
70
|
+
var moduleCache = new Map;
|
|
71
|
+
function getFileMtime(filePath) {
|
|
72
|
+
try {
|
|
73
|
+
return fs.statSync(filePath).mtimeMs;
|
|
74
|
+
} catch {
|
|
75
|
+
return 0;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async function loadModule(modulePath) {
|
|
79
|
+
const currentMtime = getFileMtime(modulePath);
|
|
80
|
+
const cached = moduleCache.get(modulePath);
|
|
81
|
+
if (cached && cached.mtime === currentMtime && currentMtime > 0) {
|
|
82
|
+
return cached.module;
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const requireCache = eval("require").cache;
|
|
86
|
+
if (requireCache && requireCache[modulePath]) {
|
|
87
|
+
delete requireCache[modulePath];
|
|
88
|
+
}
|
|
89
|
+
} catch {}
|
|
90
|
+
let mod;
|
|
91
|
+
try {
|
|
92
|
+
mod = eval("require")(modulePath);
|
|
93
|
+
} catch (err) {
|
|
94
|
+
const nodeErr = err;
|
|
95
|
+
if (nodeErr.code === "ERR_REQUIRE_ESM") {
|
|
96
|
+
const cacheBuster = currentMtime > 0 ? `?t=${currentMtime}` : "";
|
|
97
|
+
mod = await import(`${modulePath}${cacheBuster}`);
|
|
98
|
+
} else {
|
|
99
|
+
throw err;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
moduleCache.set(modulePath, { module: mod, mtime: currentMtime });
|
|
103
|
+
return mod;
|
|
104
|
+
}
|
|
105
|
+
async function detectRuntimeSchemas(context) {
|
|
106
|
+
const result = {
|
|
107
|
+
schemas: new Map,
|
|
108
|
+
errors: []
|
|
109
|
+
};
|
|
110
|
+
try {
|
|
111
|
+
const modulePath2 = resolveModulePath(context.entryFile, context.baseDir);
|
|
112
|
+
if (!modulePath2) {
|
|
113
|
+
result.errors.push("Could not resolve compiled module path");
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
const mod2 = await loadModule(modulePath2);
|
|
117
|
+
if (!mod2 || typeof mod2 !== "object") {
|
|
118
|
+
result.errors.push("Module did not export an object");
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
const exports = mod2;
|
|
122
|
+
for (const [name, value] of Object.entries(exports)) {
|
|
123
|
+
if (name.startsWith("_") || name === "default") {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const schemaResult = tryExtractStandardSchema(value);
|
|
127
|
+
if (schemaResult) {
|
|
128
|
+
result.schemas.set(name, schemaResult);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if ("default" in exports && exports.default && typeof exports.default === "object") {
|
|
132
|
+
const defaultExports = exports.default;
|
|
133
|
+
for (const [name, value] of Object.entries(defaultExports)) {
|
|
134
|
+
if (name.startsWith("_"))
|
|
135
|
+
continue;
|
|
136
|
+
const schemaResult = tryExtractStandardSchema(value);
|
|
137
|
+
if (schemaResult && !result.schemas.has(name)) {
|
|
138
|
+
result.schemas.set(name, schemaResult);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
} catch (err) {
|
|
143
|
+
result.errors.push(`Runtime detection failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
144
|
+
}
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
function resolveModulePath(entryFile, baseDir) {
|
|
148
|
+
if (entryFile.endsWith(".js") || entryFile.endsWith(".mjs") || entryFile.endsWith(".cjs")) {
|
|
149
|
+
return entryFile;
|
|
150
|
+
}
|
|
151
|
+
const tsFile = entryFile;
|
|
152
|
+
const possiblePaths = [
|
|
153
|
+
tsFile.replace(/\.tsx?$/, ".js"),
|
|
154
|
+
tsFile.replace(/\.tsx?$/, ".mjs"),
|
|
155
|
+
tsFile.replace(/\/src\//, "/dist/").replace(/\.tsx?$/, ".js"),
|
|
156
|
+
tsFile.replace(/\/src\//, "/build/").replace(/\.tsx?$/, ".js"),
|
|
157
|
+
tsFile.replace(/\/src\//, "/lib/").replace(/\.tsx?$/, ".js")
|
|
158
|
+
];
|
|
159
|
+
for (const testPath of possiblePaths) {
|
|
160
|
+
try {
|
|
161
|
+
__require.resolve(testPath, { paths: [baseDir] });
|
|
162
|
+
return testPath;
|
|
163
|
+
} catch {}
|
|
164
|
+
}
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
function clearSchemaCache() {
|
|
168
|
+
moduleCache.clear();
|
|
169
|
+
}
|
|
20
170
|
// src/analysis/docs-coverage.ts
|
|
21
171
|
import {
|
|
22
172
|
DRIFT_CATEGORIES
|
|
@@ -446,7 +596,7 @@ function categorizeDrifts(drifts) {
|
|
|
446
596
|
return { fixable, nonFixable };
|
|
447
597
|
}
|
|
448
598
|
// src/fix/jsdoc-writer.ts
|
|
449
|
-
import * as
|
|
599
|
+
import * as fs2 from "node:fs";
|
|
450
600
|
import * as path from "node:path";
|
|
451
601
|
|
|
452
602
|
// src/ts-module.ts
|
|
@@ -786,7 +936,7 @@ async function applyEdits(edits) {
|
|
|
786
936
|
}
|
|
787
937
|
for (const [filePath, fileEdits] of editsByFile) {
|
|
788
938
|
try {
|
|
789
|
-
const content =
|
|
939
|
+
const content = fs2.readFileSync(filePath, "utf-8");
|
|
790
940
|
const lines = content.split(`
|
|
791
941
|
`);
|
|
792
942
|
const sortedEdits = [...fileEdits].sort((a, b) => b.startLine - a.startLine);
|
|
@@ -800,7 +950,7 @@ async function applyEdits(edits) {
|
|
|
800
950
|
}
|
|
801
951
|
result.editsApplied++;
|
|
802
952
|
}
|
|
803
|
-
|
|
953
|
+
fs2.writeFileSync(filePath, lines.join(`
|
|
804
954
|
`));
|
|
805
955
|
result.filesModified++;
|
|
806
956
|
} catch (error) {
|
|
@@ -813,7 +963,7 @@ async function applyEdits(edits) {
|
|
|
813
963
|
return result;
|
|
814
964
|
}
|
|
815
965
|
function createSourceFile(filePath) {
|
|
816
|
-
const content =
|
|
966
|
+
const content = fs2.readFileSync(filePath, "utf-8");
|
|
817
967
|
return ts.createSourceFile(path.basename(filePath), content, ts.ScriptTarget.Latest, true, filePath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
|
|
818
968
|
}
|
|
819
969
|
// src/utils/builtin-detection.ts
|
|
@@ -2013,6 +2163,122 @@ function ensureSpecCoverage(spec) {
|
|
|
2013
2163
|
};
|
|
2014
2164
|
}
|
|
2015
2165
|
// src/quality/rules.ts
|
|
2166
|
+
var BUILTIN_TYPES = new Set([
|
|
2167
|
+
"string",
|
|
2168
|
+
"number",
|
|
2169
|
+
"boolean",
|
|
2170
|
+
"object",
|
|
2171
|
+
"any",
|
|
2172
|
+
"unknown",
|
|
2173
|
+
"void",
|
|
2174
|
+
"never",
|
|
2175
|
+
"null",
|
|
2176
|
+
"undefined",
|
|
2177
|
+
"symbol",
|
|
2178
|
+
"bigint",
|
|
2179
|
+
"Array",
|
|
2180
|
+
"Promise",
|
|
2181
|
+
"Map",
|
|
2182
|
+
"Set",
|
|
2183
|
+
"Record",
|
|
2184
|
+
"Partial",
|
|
2185
|
+
"Required",
|
|
2186
|
+
"Readonly",
|
|
2187
|
+
"Pick",
|
|
2188
|
+
"Omit",
|
|
2189
|
+
"Exclude",
|
|
2190
|
+
"Extract",
|
|
2191
|
+
"NonNullable",
|
|
2192
|
+
"ReturnType",
|
|
2193
|
+
"Parameters",
|
|
2194
|
+
"InstanceType",
|
|
2195
|
+
"ConstructorParameters",
|
|
2196
|
+
"Awaited"
|
|
2197
|
+
]);
|
|
2198
|
+
function extractTypeReferences(exp) {
|
|
2199
|
+
const refs = new Set;
|
|
2200
|
+
function collectFromSchema(schema) {
|
|
2201
|
+
if (!schema)
|
|
2202
|
+
return;
|
|
2203
|
+
if (typeof schema === "string") {
|
|
2204
|
+
if (!BUILTIN_TYPES.has(schema)) {
|
|
2205
|
+
refs.add(schema);
|
|
2206
|
+
}
|
|
2207
|
+
return;
|
|
2208
|
+
}
|
|
2209
|
+
if (typeof schema === "object") {
|
|
2210
|
+
const obj = schema;
|
|
2211
|
+
if (typeof obj.$ref === "string") {
|
|
2212
|
+
const ref = obj.$ref;
|
|
2213
|
+
const name = ref.startsWith("#/types/") ? ref.slice("#/types/".length) : ref;
|
|
2214
|
+
if (!BUILTIN_TYPES.has(name)) {
|
|
2215
|
+
refs.add(name);
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
if (typeof obj.type === "string" && !BUILTIN_TYPES.has(obj.type)) {
|
|
2219
|
+
refs.add(obj.type);
|
|
2220
|
+
}
|
|
2221
|
+
if (obj.items) {
|
|
2222
|
+
collectFromSchema(obj.items);
|
|
2223
|
+
}
|
|
2224
|
+
if (obj.properties && typeof obj.properties === "object") {
|
|
2225
|
+
for (const prop of Object.values(obj.properties)) {
|
|
2226
|
+
collectFromSchema(prop);
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
if (Array.isArray(obj.anyOf)) {
|
|
2230
|
+
for (const item of obj.anyOf) {
|
|
2231
|
+
collectFromSchema(item);
|
|
2232
|
+
}
|
|
2233
|
+
}
|
|
2234
|
+
if (Array.isArray(obj.oneOf)) {
|
|
2235
|
+
for (const item of obj.oneOf) {
|
|
2236
|
+
collectFromSchema(item);
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
if (Array.isArray(obj.allOf)) {
|
|
2240
|
+
for (const item of obj.allOf) {
|
|
2241
|
+
collectFromSchema(item);
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
if (obj.additionalProperties && typeof obj.additionalProperties === "object") {
|
|
2245
|
+
collectFromSchema(obj.additionalProperties);
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
for (const sig of exp.signatures ?? []) {
|
|
2250
|
+
for (const param of sig.parameters ?? []) {
|
|
2251
|
+
collectFromSchema(param.schema);
|
|
2252
|
+
}
|
|
2253
|
+
if (sig.returns?.schema) {
|
|
2254
|
+
collectFromSchema(sig.returns.schema);
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
for (const member of exp.members ?? []) {
|
|
2258
|
+
collectFromSchema(member.schema);
|
|
2259
|
+
for (const sig of member.signatures ?? []) {
|
|
2260
|
+
for (const param of sig.parameters ?? []) {
|
|
2261
|
+
collectFromSchema(param.schema);
|
|
2262
|
+
}
|
|
2263
|
+
if (sig.returns?.schema) {
|
|
2264
|
+
collectFromSchema(sig.returns.schema);
|
|
2265
|
+
}
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
collectFromSchema(exp.schema);
|
|
2269
|
+
if (typeof exp.type === "string" && !BUILTIN_TYPES.has(exp.type)) {
|
|
2270
|
+
refs.add(exp.type);
|
|
2271
|
+
}
|
|
2272
|
+
if (exp.extends && !BUILTIN_TYPES.has(exp.extends)) {
|
|
2273
|
+
refs.add(exp.extends);
|
|
2274
|
+
}
|
|
2275
|
+
for (const impl of exp.implements ?? []) {
|
|
2276
|
+
if (!BUILTIN_TYPES.has(impl)) {
|
|
2277
|
+
refs.add(impl);
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
return refs;
|
|
2281
|
+
}
|
|
2016
2282
|
var CORE_RULES = [
|
|
2017
2283
|
{
|
|
2018
2284
|
id: "has-description",
|
|
@@ -2099,6 +2365,99 @@ var CORE_RULES = [
|
|
|
2099
2365
|
}
|
|
2100
2366
|
}
|
|
2101
2367
|
];
|
|
2368
|
+
var TSDOC_RULES = [
|
|
2369
|
+
{
|
|
2370
|
+
id: "require-release-tag",
|
|
2371
|
+
name: "Require Release Tag",
|
|
2372
|
+
description: "All exports must have @public, @beta, @alpha, or @internal",
|
|
2373
|
+
affectsCoverage: false,
|
|
2374
|
+
defaultSeverity: "off",
|
|
2375
|
+
check(ctx) {
|
|
2376
|
+
const tags = ctx.export.tags ?? [];
|
|
2377
|
+
return tags.some((t) => ["public", "beta", "alpha", "internal"].includes(t.name.toLowerCase()));
|
|
2378
|
+
},
|
|
2379
|
+
getViolation(ctx) {
|
|
2380
|
+
return {
|
|
2381
|
+
ruleId: "require-release-tag",
|
|
2382
|
+
severity: "warn",
|
|
2383
|
+
message: `Export '${ctx.export.name}' is missing a release tag (@public, @beta, @alpha, or @internal)`,
|
|
2384
|
+
fixable: true
|
|
2385
|
+
};
|
|
2386
|
+
}
|
|
2387
|
+
},
|
|
2388
|
+
{
|
|
2389
|
+
id: "internal-underscore",
|
|
2390
|
+
name: "Internal Underscore Prefix",
|
|
2391
|
+
description: "@internal exports should have underscore prefix",
|
|
2392
|
+
affectsCoverage: false,
|
|
2393
|
+
defaultSeverity: "off",
|
|
2394
|
+
check(ctx) {
|
|
2395
|
+
const tags = ctx.export.tags ?? [];
|
|
2396
|
+
const isInternal = tags.some((t) => t.name.toLowerCase() === "internal");
|
|
2397
|
+
if (!isInternal)
|
|
2398
|
+
return true;
|
|
2399
|
+
return ctx.export.name.startsWith("_");
|
|
2400
|
+
},
|
|
2401
|
+
getViolation(ctx) {
|
|
2402
|
+
return {
|
|
2403
|
+
ruleId: "internal-underscore",
|
|
2404
|
+
severity: "warn",
|
|
2405
|
+
message: `Internal export '${ctx.export.name}' should have underscore prefix (_${ctx.export.name})`,
|
|
2406
|
+
fixable: false
|
|
2407
|
+
};
|
|
2408
|
+
}
|
|
2409
|
+
},
|
|
2410
|
+
{
|
|
2411
|
+
id: "no-conflicting-tags",
|
|
2412
|
+
name: "No Conflicting Tags",
|
|
2413
|
+
description: "Cannot have both @internal and @public/@beta/@alpha",
|
|
2414
|
+
affectsCoverage: false,
|
|
2415
|
+
defaultSeverity: "warn",
|
|
2416
|
+
check(ctx) {
|
|
2417
|
+
const tags = ctx.export.tags ?? [];
|
|
2418
|
+
const tagNames = tags.map((t) => t.name.toLowerCase());
|
|
2419
|
+
const hasInternal = tagNames.includes("internal");
|
|
2420
|
+
const hasPublicish = tagNames.some((n) => ["public", "beta", "alpha"].includes(n));
|
|
2421
|
+
return !(hasInternal && hasPublicish);
|
|
2422
|
+
},
|
|
2423
|
+
getViolation(ctx) {
|
|
2424
|
+
return {
|
|
2425
|
+
ruleId: "no-conflicting-tags",
|
|
2426
|
+
severity: "error",
|
|
2427
|
+
message: `Export '${ctx.export.name}' has conflicting release tags (@internal with @public/@beta/@alpha)`,
|
|
2428
|
+
fixable: false
|
|
2429
|
+
};
|
|
2430
|
+
}
|
|
2431
|
+
},
|
|
2432
|
+
{
|
|
2433
|
+
id: "no-forgotten-export",
|
|
2434
|
+
name: "No Forgotten Export",
|
|
2435
|
+
description: "All referenced types must be exported",
|
|
2436
|
+
affectsCoverage: false,
|
|
2437
|
+
defaultSeverity: "off",
|
|
2438
|
+
check(ctx) {
|
|
2439
|
+
if (!ctx.exportRegistry)
|
|
2440
|
+
return true;
|
|
2441
|
+
const refs = extractTypeReferences(ctx.export);
|
|
2442
|
+
for (const ref of refs) {
|
|
2443
|
+
if (!ctx.exportRegistry.has(ref)) {
|
|
2444
|
+
return false;
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
return true;
|
|
2448
|
+
},
|
|
2449
|
+
getViolation(ctx) {
|
|
2450
|
+
const refs = extractTypeReferences(ctx.export);
|
|
2451
|
+
const missing = ctx.exportRegistry ? [...refs].filter((r) => !ctx.exportRegistry.has(r)) : [];
|
|
2452
|
+
return {
|
|
2453
|
+
ruleId: "no-forgotten-export",
|
|
2454
|
+
severity: "warn",
|
|
2455
|
+
message: missing.length > 0 ? `Export '${ctx.export.name}' references unexported types: ${missing.join(", ")}` : `Export '${ctx.export.name}' references types that are not exported`,
|
|
2456
|
+
fixable: false
|
|
2457
|
+
};
|
|
2458
|
+
}
|
|
2459
|
+
}
|
|
2460
|
+
];
|
|
2102
2461
|
var STYLE_RULES = [
|
|
2103
2462
|
{
|
|
2104
2463
|
id: "no-empty-returns",
|
|
@@ -2165,7 +2524,7 @@ var STYLE_RULES = [
|
|
|
2165
2524
|
}
|
|
2166
2525
|
}
|
|
2167
2526
|
];
|
|
2168
|
-
var BUILTIN_RULES = [...CORE_RULES, ...STYLE_RULES];
|
|
2527
|
+
var BUILTIN_RULES = [...CORE_RULES, ...TSDOC_RULES, ...STYLE_RULES];
|
|
2169
2528
|
function getCoverageRules() {
|
|
2170
2529
|
return BUILTIN_RULES.filter((r) => r.affectsCoverage);
|
|
2171
2530
|
}
|
|
@@ -2188,7 +2547,7 @@ function getDefaultConfig() {
|
|
|
2188
2547
|
}
|
|
2189
2548
|
|
|
2190
2549
|
// src/quality/engine.ts
|
|
2191
|
-
function evaluateExportQuality(exp, rawJSDoc, config = { rules: {} }) {
|
|
2550
|
+
function evaluateExportQuality(exp, rawJSDoc, config = { rules: {} }, exportRegistry) {
|
|
2192
2551
|
const kind = exp.kind ?? "variable";
|
|
2193
2552
|
const applicableRules = getRulesForKind(kind);
|
|
2194
2553
|
const defaults = getDefaultConfig();
|
|
@@ -2213,7 +2572,7 @@ function evaluateExportQuality(exp, rawJSDoc, config = { rules: {} }) {
|
|
|
2213
2572
|
fixableCount: 0
|
|
2214
2573
|
}
|
|
2215
2574
|
};
|
|
2216
|
-
const context = { export: exp, rawJSDoc };
|
|
2575
|
+
const context = { export: exp, rawJSDoc, exportRegistry };
|
|
2217
2576
|
for (const rule of applicableRules) {
|
|
2218
2577
|
const passed = rule.check(context);
|
|
2219
2578
|
const severity = getSeverity(rule.id, rule.defaultSeverity);
|
|
@@ -2313,7 +2672,7 @@ function enrichSpec(spec, options = {}) {
|
|
|
2313
2672
|
let totalCoverage = 0;
|
|
2314
2673
|
const enrichedExports = spec.exports.map((exp) => {
|
|
2315
2674
|
const rawJSDoc = rawJSDocByExport?.get(exp.id);
|
|
2316
|
-
const quality = evaluateExportQuality(exp, rawJSDoc, qualityConfig);
|
|
2675
|
+
const quality = evaluateExportQuality(exp, rawJSDoc, qualityConfig, exportRegistry);
|
|
2317
2676
|
const drift = computeExportDrift(exp, exportRegistry);
|
|
2318
2677
|
const additionalDrift = driftByExport?.get(exp.id);
|
|
2319
2678
|
const allDrift2 = additionalDrift ? [...drift, ...additionalDrift] : drift;
|
|
@@ -2360,7 +2719,7 @@ function enrichSpec(spec, options = {}) {
|
|
|
2360
2719
|
};
|
|
2361
2720
|
}
|
|
2362
2721
|
// src/analysis/report.ts
|
|
2363
|
-
import * as
|
|
2722
|
+
import * as fs3 from "node:fs";
|
|
2364
2723
|
import * as path2 from "node:path";
|
|
2365
2724
|
|
|
2366
2725
|
// src/types/report.ts
|
|
@@ -2424,7 +2783,7 @@ function generateReportFromEnriched(enriched) {
|
|
|
2424
2783
|
driftSummary
|
|
2425
2784
|
};
|
|
2426
2785
|
return {
|
|
2427
|
-
$schema: "https://doccov.
|
|
2786
|
+
$schema: "https://doccov.com/schemas/v1.0.0/report.schema.json",
|
|
2428
2787
|
version: REPORT_VERSION,
|
|
2429
2788
|
generatedAt: new Date().toISOString(),
|
|
2430
2789
|
spec: {
|
|
@@ -2438,10 +2797,10 @@ function generateReportFromEnriched(enriched) {
|
|
|
2438
2797
|
function loadCachedReport(reportPath = DEFAULT_REPORT_PATH) {
|
|
2439
2798
|
try {
|
|
2440
2799
|
const fullPath = path2.resolve(reportPath);
|
|
2441
|
-
if (!
|
|
2800
|
+
if (!fs3.existsSync(fullPath)) {
|
|
2442
2801
|
return null;
|
|
2443
2802
|
}
|
|
2444
|
-
const content =
|
|
2803
|
+
const content = fs3.readFileSync(fullPath, "utf-8");
|
|
2445
2804
|
return JSON.parse(content);
|
|
2446
2805
|
} catch {
|
|
2447
2806
|
return null;
|
|
@@ -2450,10 +2809,10 @@ function loadCachedReport(reportPath = DEFAULT_REPORT_PATH) {
|
|
|
2450
2809
|
function saveReport(report, reportPath = DEFAULT_REPORT_PATH) {
|
|
2451
2810
|
const fullPath = path2.resolve(reportPath);
|
|
2452
2811
|
const dir = path2.dirname(fullPath);
|
|
2453
|
-
if (!
|
|
2454
|
-
|
|
2812
|
+
if (!fs3.existsSync(dir)) {
|
|
2813
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
2455
2814
|
}
|
|
2456
|
-
|
|
2815
|
+
fs3.writeFileSync(fullPath, JSON.stringify(report, null, 2));
|
|
2457
2816
|
}
|
|
2458
2817
|
function isCachedReportValid(reportPath = DEFAULT_REPORT_PATH, sourceFiles = []) {
|
|
2459
2818
|
const report = loadCachedReport(reportPath);
|
|
@@ -2463,7 +2822,7 @@ function isCachedReportValid(reportPath = DEFAULT_REPORT_PATH, sourceFiles = [])
|
|
|
2463
2822
|
const reportTime = new Date(report.generatedAt).getTime();
|
|
2464
2823
|
for (const file of sourceFiles) {
|
|
2465
2824
|
try {
|
|
2466
|
-
const stat =
|
|
2825
|
+
const stat = fs3.statSync(file);
|
|
2467
2826
|
if (stat.mtimeMs > reportTime) {
|
|
2468
2827
|
return false;
|
|
2469
2828
|
}
|
|
@@ -2473,58 +2832,460 @@ function isCachedReportValid(reportPath = DEFAULT_REPORT_PATH, sourceFiles = [])
|
|
|
2473
2832
|
}
|
|
2474
2833
|
return true;
|
|
2475
2834
|
}
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
}
|
|
2485
|
-
|
|
2835
|
+
function formatSignature(name, signature) {
|
|
2836
|
+
const params = (signature.parameters ?? []).map((p) => {
|
|
2837
|
+
const optional = p.required === false ? "?" : "";
|
|
2838
|
+
const rest = p.rest ? "..." : "";
|
|
2839
|
+
const typeStr = typeof p.schema === "string" ? p.schema : p.schema?.type ?? "unknown";
|
|
2840
|
+
return `${rest}${p.name}${optional}: ${typeStr}`;
|
|
2841
|
+
}).join(", ");
|
|
2842
|
+
const returnType = signature.returns ? typeof signature.returns.schema === "string" ? signature.returns.schema : signature.returns.schema?.type ?? "unknown" : "void";
|
|
2843
|
+
const typeParams = signature.typeParameters?.length ? `<${signature.typeParameters.map((tp) => tp.name).join(", ")}>` : "";
|
|
2844
|
+
return `${name}${typeParams}(${params}): ${returnType}`;
|
|
2845
|
+
}
|
|
2846
|
+
function formatExportToApiSurface(exp) {
|
|
2847
|
+
const lines = [];
|
|
2848
|
+
lines.push(`### ${exp.name}`);
|
|
2849
|
+
switch (exp.kind) {
|
|
2850
|
+
case "function": {
|
|
2851
|
+
const signatures = exp.signatures ?? [];
|
|
2852
|
+
if (signatures.length === 0) {
|
|
2853
|
+
lines.push(`\`\`\`typescript
|
|
2854
|
+
function ${exp.name}(): unknown
|
|
2855
|
+
\`\`\``);
|
|
2856
|
+
} else {
|
|
2857
|
+
for (const sig of signatures) {
|
|
2858
|
+
lines.push(`\`\`\`typescript
|
|
2859
|
+
function ${formatSignature(exp.name, sig)}
|
|
2860
|
+
\`\`\``);
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
break;
|
|
2864
|
+
}
|
|
2865
|
+
case "class": {
|
|
2866
|
+
const extendsClause = exp.extends ? ` extends ${exp.extends}` : "";
|
|
2867
|
+
const implementsClause = exp.implements?.length ? ` implements ${exp.implements.join(", ")}` : "";
|
|
2868
|
+
lines.push(`\`\`\`typescript
|
|
2869
|
+
class ${exp.name}${extendsClause}${implementsClause}
|
|
2870
|
+
\`\`\``);
|
|
2871
|
+
break;
|
|
2872
|
+
}
|
|
2873
|
+
case "interface":
|
|
2874
|
+
case "type": {
|
|
2875
|
+
const typeStr = typeof exp.type === "string" ? exp.type : exp.type?.type ?? "{ ... }";
|
|
2876
|
+
lines.push(`\`\`\`typescript
|
|
2877
|
+
type ${exp.name} = ${typeStr}
|
|
2878
|
+
\`\`\``);
|
|
2879
|
+
break;
|
|
2880
|
+
}
|
|
2881
|
+
case "variable": {
|
|
2882
|
+
const typeStr = typeof exp.type === "string" ? exp.type : exp.type?.type ?? "unknown";
|
|
2883
|
+
lines.push(`\`\`\`typescript
|
|
2884
|
+
const ${exp.name}: ${typeStr}
|
|
2885
|
+
\`\`\``);
|
|
2886
|
+
break;
|
|
2887
|
+
}
|
|
2888
|
+
case "enum": {
|
|
2889
|
+
lines.push(`\`\`\`typescript
|
|
2890
|
+
enum ${exp.name} { ... }
|
|
2891
|
+
\`\`\``);
|
|
2892
|
+
break;
|
|
2893
|
+
}
|
|
2894
|
+
default: {
|
|
2895
|
+
lines.push(`\`\`\`typescript
|
|
2896
|
+
${exp.kind} ${exp.name}
|
|
2897
|
+
\`\`\``);
|
|
2898
|
+
}
|
|
2486
2899
|
}
|
|
2900
|
+
return lines.join(`
|
|
2901
|
+
`);
|
|
2487
2902
|
}
|
|
2488
|
-
function
|
|
2489
|
-
|
|
2490
|
-
}
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2903
|
+
function formatTypeToApiSurface(type) {
|
|
2904
|
+
const lines = [];
|
|
2905
|
+
lines.push(`### ${type.name}`);
|
|
2906
|
+
switch (type.kind) {
|
|
2907
|
+
case "interface": {
|
|
2908
|
+
const extendsClause = type.extends ? ` extends ${type.extends}` : "";
|
|
2909
|
+
lines.push(`\`\`\`typescript
|
|
2910
|
+
interface ${type.name}${extendsClause} { ... }
|
|
2911
|
+
\`\`\``);
|
|
2912
|
+
break;
|
|
2913
|
+
}
|
|
2914
|
+
case "type": {
|
|
2915
|
+
const typeStr = typeof type.type === "string" ? type.type : type.type?.type ?? "{ ... }";
|
|
2916
|
+
lines.push(`\`\`\`typescript
|
|
2917
|
+
type ${type.name} = ${typeStr}
|
|
2918
|
+
\`\`\``);
|
|
2919
|
+
break;
|
|
2920
|
+
}
|
|
2921
|
+
case "class": {
|
|
2922
|
+
const extendsClause = type.extends ? ` extends ${type.extends}` : "";
|
|
2923
|
+
lines.push(`\`\`\`typescript
|
|
2924
|
+
class ${type.name}${extendsClause}
|
|
2925
|
+
\`\`\``);
|
|
2926
|
+
break;
|
|
2927
|
+
}
|
|
2928
|
+
case "enum": {
|
|
2929
|
+
lines.push(`\`\`\`typescript
|
|
2930
|
+
enum ${type.name} { ... }
|
|
2931
|
+
\`\`\``);
|
|
2932
|
+
break;
|
|
2933
|
+
}
|
|
2934
|
+
default: {
|
|
2935
|
+
lines.push(`\`\`\`typescript
|
|
2936
|
+
${type.kind} ${type.name}
|
|
2937
|
+
\`\`\``);
|
|
2498
2938
|
}
|
|
2499
2939
|
}
|
|
2500
|
-
return
|
|
2940
|
+
return lines.join(`
|
|
2941
|
+
`);
|
|
2501
2942
|
}
|
|
2502
|
-
function
|
|
2503
|
-
const
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2943
|
+
function renderApiSurface(spec) {
|
|
2944
|
+
const lines = [];
|
|
2945
|
+
const version = spec.meta.version ? ` v${spec.meta.version}` : "";
|
|
2946
|
+
lines.push(`# API Surface: ${spec.meta.name}${version}`);
|
|
2947
|
+
lines.push("");
|
|
2948
|
+
lines.push("> This file is auto-generated. Do not edit manually.");
|
|
2949
|
+
lines.push("> Run `doccov spec --format api-surface` to regenerate.");
|
|
2950
|
+
lines.push("");
|
|
2951
|
+
const exportsByKind = {};
|
|
2952
|
+
for (const exp of spec.exports) {
|
|
2953
|
+
const kind = exp.kind;
|
|
2954
|
+
if (!exportsByKind[kind]) {
|
|
2955
|
+
exportsByKind[kind] = [];
|
|
2956
|
+
}
|
|
2957
|
+
exportsByKind[kind].push(exp);
|
|
2958
|
+
}
|
|
2959
|
+
for (const kind of Object.keys(exportsByKind)) {
|
|
2960
|
+
exportsByKind[kind].sort((a, b) => a.name.localeCompare(b.name));
|
|
2961
|
+
}
|
|
2962
|
+
const kindOrder = [
|
|
2963
|
+
"function",
|
|
2964
|
+
"class",
|
|
2965
|
+
"interface",
|
|
2966
|
+
"type",
|
|
2967
|
+
"variable",
|
|
2968
|
+
"enum",
|
|
2969
|
+
"namespace",
|
|
2970
|
+
"module"
|
|
2971
|
+
];
|
|
2972
|
+
for (const kind of kindOrder) {
|
|
2973
|
+
const exports = exportsByKind[kind];
|
|
2974
|
+
if (!exports || exports.length === 0)
|
|
2975
|
+
continue;
|
|
2976
|
+
const kindTitle = kind.charAt(0).toUpperCase() + kind.slice(1) + "s";
|
|
2977
|
+
lines.push(`## ${kindTitle}`);
|
|
2978
|
+
lines.push("");
|
|
2979
|
+
for (const exp of exports) {
|
|
2980
|
+
lines.push(formatExportToApiSurface(exp));
|
|
2981
|
+
lines.push("");
|
|
2507
2982
|
}
|
|
2508
2983
|
}
|
|
2509
|
-
for (const
|
|
2510
|
-
if (
|
|
2511
|
-
|
|
2984
|
+
for (const kind of Object.keys(exportsByKind).sort()) {
|
|
2985
|
+
if (kindOrder.includes(kind))
|
|
2986
|
+
continue;
|
|
2987
|
+
const exports = exportsByKind[kind];
|
|
2988
|
+
if (!exports || exports.length === 0)
|
|
2989
|
+
continue;
|
|
2990
|
+
const kindTitle = kind.charAt(0).toUpperCase() + kind.slice(1) + "s";
|
|
2991
|
+
lines.push(`## ${kindTitle}`);
|
|
2992
|
+
lines.push("");
|
|
2993
|
+
for (const exp of exports) {
|
|
2994
|
+
lines.push(formatExportToApiSurface(exp));
|
|
2995
|
+
lines.push("");
|
|
2512
2996
|
}
|
|
2513
2997
|
}
|
|
2514
|
-
|
|
2998
|
+
const types = spec.types ?? [];
|
|
2999
|
+
if (types.length > 0) {
|
|
3000
|
+
const sortedTypes = [...types].sort((a, b) => a.name.localeCompare(b.name));
|
|
3001
|
+
lines.push("## Internal Types");
|
|
3002
|
+
lines.push("");
|
|
3003
|
+
for (const type of sortedTypes) {
|
|
3004
|
+
lines.push(formatTypeToApiSurface(type));
|
|
3005
|
+
lines.push("");
|
|
3006
|
+
}
|
|
3007
|
+
}
|
|
3008
|
+
return lines.join(`
|
|
3009
|
+
`);
|
|
2515
3010
|
}
|
|
2516
|
-
// src/
|
|
3011
|
+
// src/analysis/history.ts
|
|
2517
3012
|
import * as fs4 from "node:fs";
|
|
2518
|
-
import * as
|
|
2519
|
-
var
|
|
3013
|
+
import * as path3 from "node:path";
|
|
3014
|
+
var HISTORY_DIR = ".doccov/history";
|
|
3015
|
+
var RETENTION_DAYS = {
|
|
3016
|
+
free: 7,
|
|
3017
|
+
team: 30,
|
|
3018
|
+
pro: 90
|
|
3019
|
+
};
|
|
3020
|
+
function getSnapshotFilename(timestamp) {
|
|
3021
|
+
const pad = (n) => n.toString().padStart(2, "0");
|
|
3022
|
+
const year = timestamp.getFullYear();
|
|
3023
|
+
const month = pad(timestamp.getMonth() + 1);
|
|
3024
|
+
const day = pad(timestamp.getDate());
|
|
3025
|
+
const hours = pad(timestamp.getHours());
|
|
3026
|
+
const minutes = pad(timestamp.getMinutes());
|
|
3027
|
+
const seconds = pad(timestamp.getSeconds());
|
|
3028
|
+
return `${year}-${month}-${day}-${hours}${minutes}${seconds}.json`;
|
|
3029
|
+
}
|
|
3030
|
+
function computeSnapshot(spec, options) {
|
|
3031
|
+
const exports = spec.exports ?? [];
|
|
3032
|
+
const documented = exports.filter((e) => e.description && e.description.trim().length > 0);
|
|
3033
|
+
const driftCount = exports.reduce((sum, e) => {
|
|
3034
|
+
const docs = e.docs;
|
|
3035
|
+
return sum + (docs?.drift?.length ?? 0);
|
|
3036
|
+
}, 0);
|
|
3037
|
+
const coverageScore = exports.length > 0 ? Math.round(documented.length / exports.length * 100) : 100;
|
|
3038
|
+
return {
|
|
3039
|
+
timestamp: new Date().toISOString(),
|
|
3040
|
+
package: spec.meta.name,
|
|
3041
|
+
version: spec.meta.version,
|
|
3042
|
+
coverageScore,
|
|
3043
|
+
totalExports: exports.length,
|
|
3044
|
+
documentedExports: documented.length,
|
|
3045
|
+
driftCount,
|
|
3046
|
+
commit: options?.commit,
|
|
3047
|
+
branch: options?.branch
|
|
3048
|
+
};
|
|
3049
|
+
}
|
|
3050
|
+
function saveSnapshot(snapshot, cwd) {
|
|
3051
|
+
const historyDir = path3.resolve(cwd, HISTORY_DIR);
|
|
3052
|
+
if (!fs4.existsSync(historyDir)) {
|
|
3053
|
+
fs4.mkdirSync(historyDir, { recursive: true });
|
|
3054
|
+
}
|
|
3055
|
+
const filename = getSnapshotFilename(new Date(snapshot.timestamp));
|
|
3056
|
+
const filepath = path3.join(historyDir, filename);
|
|
3057
|
+
fs4.writeFileSync(filepath, JSON.stringify(snapshot, null, 2));
|
|
3058
|
+
}
|
|
3059
|
+
function loadSnapshots(cwd) {
|
|
3060
|
+
const historyDir = path3.resolve(cwd, HISTORY_DIR);
|
|
3061
|
+
if (!fs4.existsSync(historyDir)) {
|
|
3062
|
+
return [];
|
|
3063
|
+
}
|
|
3064
|
+
const files = fs4.readdirSync(historyDir).filter((f) => f.endsWith(".json")).sort().reverse();
|
|
3065
|
+
const snapshots = [];
|
|
3066
|
+
for (const file of files) {
|
|
3067
|
+
try {
|
|
3068
|
+
const content = fs4.readFileSync(path3.join(historyDir, file), "utf-8");
|
|
3069
|
+
snapshots.push(JSON.parse(content));
|
|
3070
|
+
} catch {}
|
|
3071
|
+
}
|
|
3072
|
+
return snapshots;
|
|
3073
|
+
}
|
|
3074
|
+
function getTrend(spec, cwd, options) {
|
|
3075
|
+
const current = computeSnapshot(spec, options);
|
|
3076
|
+
const history = loadSnapshots(cwd);
|
|
3077
|
+
const delta = history.length > 0 ? current.coverageScore - history[0].coverageScore : undefined;
|
|
3078
|
+
const sparklineHistory = history.slice(0, 9).map((s) => s.coverageScore);
|
|
3079
|
+
const sparkline = [current.coverageScore, ...sparklineHistory].reverse();
|
|
3080
|
+
return {
|
|
3081
|
+
current,
|
|
3082
|
+
history,
|
|
3083
|
+
delta,
|
|
3084
|
+
sparkline
|
|
3085
|
+
};
|
|
3086
|
+
}
|
|
3087
|
+
function renderSparkline(values) {
|
|
3088
|
+
if (values.length === 0)
|
|
3089
|
+
return "";
|
|
3090
|
+
const chars = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
|
|
3091
|
+
const min = Math.min(...values);
|
|
3092
|
+
const max = Math.max(...values);
|
|
3093
|
+
const range = max - min || 1;
|
|
3094
|
+
return values.map((v) => {
|
|
3095
|
+
const normalized = (v - min) / range;
|
|
3096
|
+
const index = Math.min(Math.floor(normalized * chars.length), chars.length - 1);
|
|
3097
|
+
return chars[index];
|
|
3098
|
+
}).join("");
|
|
3099
|
+
}
|
|
3100
|
+
function formatDelta(delta) {
|
|
3101
|
+
if (delta > 0)
|
|
3102
|
+
return `↑${delta}%`;
|
|
3103
|
+
if (delta < 0)
|
|
3104
|
+
return `↓${Math.abs(delta)}%`;
|
|
3105
|
+
return "→0%";
|
|
3106
|
+
}
|
|
3107
|
+
function pruneHistory(cwd, keepCount = 100) {
|
|
3108
|
+
const historyDir = path3.resolve(cwd, HISTORY_DIR);
|
|
3109
|
+
if (!fs4.existsSync(historyDir)) {
|
|
3110
|
+
return 0;
|
|
3111
|
+
}
|
|
3112
|
+
const files = fs4.readdirSync(historyDir).filter((f) => f.endsWith(".json")).sort().reverse();
|
|
3113
|
+
const toDelete = files.slice(keepCount);
|
|
3114
|
+
for (const file of toDelete) {
|
|
3115
|
+
try {
|
|
3116
|
+
fs4.unlinkSync(path3.join(historyDir, file));
|
|
3117
|
+
} catch {}
|
|
3118
|
+
}
|
|
3119
|
+
return toDelete.length;
|
|
3120
|
+
}
|
|
3121
|
+
function pruneByTier(cwd, tier) {
|
|
3122
|
+
const retentionDays = RETENTION_DAYS[tier];
|
|
3123
|
+
const cutoffDate = new Date;
|
|
3124
|
+
cutoffDate.setDate(cutoffDate.getDate() - retentionDays);
|
|
3125
|
+
const historyDir = path3.resolve(cwd, HISTORY_DIR);
|
|
3126
|
+
if (!fs4.existsSync(historyDir)) {
|
|
3127
|
+
return 0;
|
|
3128
|
+
}
|
|
3129
|
+
const files = fs4.readdirSync(historyDir).filter((f) => f.endsWith(".json"));
|
|
3130
|
+
let deleted = 0;
|
|
3131
|
+
for (const file of files) {
|
|
3132
|
+
try {
|
|
3133
|
+
const filepath = path3.join(historyDir, file);
|
|
3134
|
+
const content = fs4.readFileSync(filepath, "utf-8");
|
|
3135
|
+
const snapshot = JSON.parse(content);
|
|
3136
|
+
const snapshotDate = new Date(snapshot.timestamp);
|
|
3137
|
+
if (snapshotDate < cutoffDate) {
|
|
3138
|
+
fs4.unlinkSync(filepath);
|
|
3139
|
+
deleted++;
|
|
3140
|
+
}
|
|
3141
|
+
} catch {}
|
|
3142
|
+
}
|
|
3143
|
+
return deleted;
|
|
3144
|
+
}
|
|
3145
|
+
function loadSnapshotsForDays(cwd, days) {
|
|
3146
|
+
const cutoffDate = new Date;
|
|
3147
|
+
cutoffDate.setDate(cutoffDate.getDate() - days);
|
|
3148
|
+
const allSnapshots = loadSnapshots(cwd);
|
|
3149
|
+
return allSnapshots.filter((s) => new Date(s.timestamp) >= cutoffDate);
|
|
3150
|
+
}
|
|
3151
|
+
function calculateVelocity(snapshots) {
|
|
3152
|
+
if (snapshots.length < 2)
|
|
3153
|
+
return 0;
|
|
3154
|
+
const newest = snapshots[0];
|
|
3155
|
+
const oldest = snapshots[snapshots.length - 1];
|
|
3156
|
+
const newestDate = new Date(newest.timestamp);
|
|
3157
|
+
const oldestDate = new Date(oldest.timestamp);
|
|
3158
|
+
const daysDiff = (newestDate.getTime() - oldestDate.getTime()) / (1000 * 60 * 60 * 24);
|
|
3159
|
+
if (daysDiff < 1)
|
|
3160
|
+
return 0;
|
|
3161
|
+
const coverageDiff = newest.coverageScore - oldest.coverageScore;
|
|
3162
|
+
return Math.round(coverageDiff / daysDiff * 100) / 100;
|
|
3163
|
+
}
|
|
3164
|
+
function getWeekStart(date) {
|
|
3165
|
+
const result = new Date(date);
|
|
3166
|
+
result.setDate(result.getDate() - result.getDay());
|
|
3167
|
+
result.setHours(0, 0, 0, 0);
|
|
3168
|
+
return result;
|
|
3169
|
+
}
|
|
3170
|
+
function generateWeeklySummaries(snapshots) {
|
|
3171
|
+
if (snapshots.length === 0)
|
|
3172
|
+
return [];
|
|
3173
|
+
const weeklyGroups = new Map;
|
|
3174
|
+
for (const snapshot of snapshots) {
|
|
3175
|
+
const weekStart = getWeekStart(new Date(snapshot.timestamp));
|
|
3176
|
+
const weekKey = weekStart.toISOString().split("T")[0];
|
|
3177
|
+
const group = weeklyGroups.get(weekKey) ?? [];
|
|
3178
|
+
group.push(snapshot);
|
|
3179
|
+
weeklyGroups.set(weekKey, group);
|
|
3180
|
+
}
|
|
3181
|
+
const summaries = [];
|
|
3182
|
+
for (const [weekKey, weekSnapshots] of weeklyGroups) {
|
|
3183
|
+
const sorted = [...weekSnapshots].sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
3184
|
+
const weekStart = new Date(weekKey);
|
|
3185
|
+
const weekEnd = new Date(weekStart);
|
|
3186
|
+
weekEnd.setDate(weekEnd.getDate() + 6);
|
|
3187
|
+
const scores = sorted.map((s) => s.coverageScore);
|
|
3188
|
+
const avgCoverage = Math.round(scores.reduce((a, b) => a + b, 0) / scores.length);
|
|
3189
|
+
const startCoverage = sorted[0].coverageScore;
|
|
3190
|
+
const endCoverage = sorted[sorted.length - 1].coverageScore;
|
|
3191
|
+
summaries.push({
|
|
3192
|
+
weekStart: weekStart.toISOString(),
|
|
3193
|
+
weekEnd: weekEnd.toISOString(),
|
|
3194
|
+
avgCoverage,
|
|
3195
|
+
startCoverage,
|
|
3196
|
+
endCoverage,
|
|
3197
|
+
delta: endCoverage - startCoverage,
|
|
3198
|
+
snapshotCount: sorted.length
|
|
3199
|
+
});
|
|
3200
|
+
}
|
|
3201
|
+
return summaries.sort((a, b) => new Date(b.weekStart).getTime() - new Date(a.weekStart).getTime());
|
|
3202
|
+
}
|
|
3203
|
+
function getExtendedTrend(spec, cwd, options) {
|
|
3204
|
+
const tier = options?.tier ?? "pro";
|
|
3205
|
+
const retentionDays = RETENTION_DAYS[tier];
|
|
3206
|
+
const trend = getTrend(spec, cwd, options);
|
|
3207
|
+
const periodSnapshots = loadSnapshotsForDays(cwd, retentionDays);
|
|
3208
|
+
const snapshots7d = loadSnapshotsForDays(cwd, 7);
|
|
3209
|
+
const snapshots30d = loadSnapshotsForDays(cwd, 30);
|
|
3210
|
+
const snapshots90d = tier === "pro" ? loadSnapshotsForDays(cwd, 90) : [];
|
|
3211
|
+
const velocity7d = calculateVelocity(snapshots7d);
|
|
3212
|
+
const velocity30d = calculateVelocity(snapshots30d);
|
|
3213
|
+
const velocity90d = tier === "pro" ? calculateVelocity(snapshots90d) : undefined;
|
|
3214
|
+
const currentScore = trend.current.coverageScore;
|
|
3215
|
+
const projected30d = Math.min(100, Math.max(0, Math.round(currentScore + velocity30d * 30)));
|
|
3216
|
+
const allScores = [trend.current.coverageScore, ...trend.history.map((s) => s.coverageScore)];
|
|
3217
|
+
const allTimeHigh = Math.max(...allScores);
|
|
3218
|
+
const allTimeLow = Math.min(...allScores);
|
|
3219
|
+
const dataRange = trend.history.length > 0 ? {
|
|
3220
|
+
start: trend.history[trend.history.length - 1].timestamp,
|
|
3221
|
+
end: trend.current.timestamp
|
|
3222
|
+
} : null;
|
|
3223
|
+
const allSnapshots = [trend.current, ...periodSnapshots];
|
|
3224
|
+
const weeklySummaries = generateWeeklySummaries(allSnapshots);
|
|
3225
|
+
return {
|
|
3226
|
+
trend,
|
|
3227
|
+
weeklySummaries,
|
|
3228
|
+
velocity7d,
|
|
3229
|
+
velocity30d,
|
|
3230
|
+
velocity90d,
|
|
3231
|
+
projected30d,
|
|
3232
|
+
allTimeHigh,
|
|
3233
|
+
allTimeLow,
|
|
3234
|
+
dataRange
|
|
3235
|
+
};
|
|
3236
|
+
}
|
|
3237
|
+
// src/cache/hash.ts
|
|
3238
|
+
import * as crypto from "node:crypto";
|
|
3239
|
+
import * as fs5 from "node:fs";
|
|
3240
|
+
import * as path4 from "node:path";
|
|
3241
|
+
function hashFile(filePath) {
|
|
3242
|
+
try {
|
|
3243
|
+
const content = fs5.readFileSync(filePath);
|
|
3244
|
+
return crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
3245
|
+
} catch {
|
|
3246
|
+
return null;
|
|
3247
|
+
}
|
|
3248
|
+
}
|
|
3249
|
+
function hashString(content) {
|
|
3250
|
+
return crypto.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
3251
|
+
}
|
|
3252
|
+
function hashFiles(filePaths, cwd) {
|
|
3253
|
+
const hashes = {};
|
|
3254
|
+
for (const filePath of filePaths) {
|
|
3255
|
+
const hash = hashFile(filePath);
|
|
3256
|
+
if (hash) {
|
|
3257
|
+
const relativePath = path4.relative(cwd, filePath);
|
|
3258
|
+
hashes[relativePath] = hash;
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
return hashes;
|
|
3262
|
+
}
|
|
3263
|
+
function diffHashes(cached2, current) {
|
|
3264
|
+
const changed = [];
|
|
3265
|
+
for (const [file, hash] of Object.entries(cached2)) {
|
|
3266
|
+
if (current[file] !== hash) {
|
|
3267
|
+
changed.push(file);
|
|
3268
|
+
}
|
|
3269
|
+
}
|
|
3270
|
+
for (const file of Object.keys(current)) {
|
|
3271
|
+
if (!(file in cached2)) {
|
|
3272
|
+
changed.push(file);
|
|
3273
|
+
}
|
|
3274
|
+
}
|
|
3275
|
+
return changed;
|
|
3276
|
+
}
|
|
3277
|
+
// src/cache/spec-cache.ts
|
|
3278
|
+
import * as fs6 from "node:fs";
|
|
3279
|
+
import * as path5 from "node:path";
|
|
3280
|
+
var CACHE_VERSION = "1.0.0";
|
|
2520
3281
|
var SPEC_CACHE_FILE = ".doccov/spec.cache.json";
|
|
2521
3282
|
function loadSpecCache(cwd) {
|
|
2522
3283
|
try {
|
|
2523
|
-
const cachePath =
|
|
2524
|
-
if (!
|
|
3284
|
+
const cachePath = path5.resolve(cwd, SPEC_CACHE_FILE);
|
|
3285
|
+
if (!fs6.existsSync(cachePath)) {
|
|
2525
3286
|
return null;
|
|
2526
3287
|
}
|
|
2527
|
-
const content =
|
|
3288
|
+
const content = fs6.readFileSync(cachePath, "utf-8");
|
|
2528
3289
|
return JSON.parse(content);
|
|
2529
3290
|
} catch {
|
|
2530
3291
|
return null;
|
|
@@ -2536,7 +3297,7 @@ function saveSpecCache(spec, context) {
|
|
|
2536
3297
|
cacheVersion: CACHE_VERSION,
|
|
2537
3298
|
generatedAt: new Date().toISOString(),
|
|
2538
3299
|
specVersion: spec.openpkg,
|
|
2539
|
-
entryFile:
|
|
3300
|
+
entryFile: path5.relative(cwd, entryFile),
|
|
2540
3301
|
hashes: {
|
|
2541
3302
|
tsconfig: tsconfigPath ? hashFile(tsconfigPath) : null,
|
|
2542
3303
|
packageJson: hashFile(packageJsonPath) ?? "",
|
|
@@ -2545,19 +3306,19 @@ function saveSpecCache(spec, context) {
|
|
|
2545
3306
|
config,
|
|
2546
3307
|
spec
|
|
2547
3308
|
};
|
|
2548
|
-
const cachePath =
|
|
2549
|
-
const dir =
|
|
2550
|
-
if (!
|
|
2551
|
-
|
|
3309
|
+
const cachePath = path5.resolve(cwd, SPEC_CACHE_FILE);
|
|
3310
|
+
const dir = path5.dirname(cachePath);
|
|
3311
|
+
if (!fs6.existsSync(dir)) {
|
|
3312
|
+
fs6.mkdirSync(dir, { recursive: true });
|
|
2552
3313
|
}
|
|
2553
|
-
|
|
3314
|
+
fs6.writeFileSync(cachePath, JSON.stringify(cache, null, 2));
|
|
2554
3315
|
}
|
|
2555
3316
|
function validateSpecCache(cache, context) {
|
|
2556
3317
|
const { entryFile, sourceFiles, tsconfigPath, packageJsonPath, config, cwd } = context;
|
|
2557
3318
|
if (cache.cacheVersion !== CACHE_VERSION) {
|
|
2558
3319
|
return { valid: false, reason: "cache-version-mismatch" };
|
|
2559
3320
|
}
|
|
2560
|
-
const relativeEntry =
|
|
3321
|
+
const relativeEntry = path5.relative(cwd, entryFile);
|
|
2561
3322
|
if (cache.entryFile !== relativeEntry) {
|
|
2562
3323
|
return { valid: false, reason: "entry-file-changed" };
|
|
2563
3324
|
}
|
|
@@ -2580,34 +3341,333 @@ function validateSpecCache(cache, context) {
|
|
|
2580
3341
|
return { valid: true };
|
|
2581
3342
|
}
|
|
2582
3343
|
function clearSpecCache(cwd) {
|
|
2583
|
-
const cachePath =
|
|
2584
|
-
if (
|
|
2585
|
-
|
|
3344
|
+
const cachePath = path5.resolve(cwd, SPEC_CACHE_FILE);
|
|
3345
|
+
if (fs6.existsSync(cachePath)) {
|
|
3346
|
+
fs6.unlinkSync(cachePath);
|
|
2586
3347
|
return true;
|
|
2587
3348
|
}
|
|
2588
3349
|
return false;
|
|
2589
3350
|
}
|
|
2590
3351
|
function getSpecCachePath(cwd) {
|
|
2591
|
-
return
|
|
3352
|
+
return path5.resolve(cwd, SPEC_CACHE_FILE);
|
|
3353
|
+
}
|
|
3354
|
+
// src/codeowners/index.ts
|
|
3355
|
+
import * as fs7 from "node:fs";
|
|
3356
|
+
import * as path6 from "node:path";
|
|
3357
|
+
import { minimatch } from "minimatch";
|
|
3358
|
+
var CODEOWNERS_LOCATIONS = [
|
|
3359
|
+
"CODEOWNERS",
|
|
3360
|
+
".github/CODEOWNERS",
|
|
3361
|
+
"docs/CODEOWNERS"
|
|
3362
|
+
];
|
|
3363
|
+
function parseCodeOwners(content) {
|
|
3364
|
+
const rules = [];
|
|
3365
|
+
const lines = content.split(`
|
|
3366
|
+
`);
|
|
3367
|
+
for (let i = 0;i < lines.length; i++) {
|
|
3368
|
+
const line = lines[i].trim();
|
|
3369
|
+
if (!line || line.startsWith("#"))
|
|
3370
|
+
continue;
|
|
3371
|
+
const parts = line.split(/\s+/);
|
|
3372
|
+
if (parts.length < 2)
|
|
3373
|
+
continue;
|
|
3374
|
+
const pattern = parts[0];
|
|
3375
|
+
const owners = parts.slice(1).filter((o) => o.startsWith("@") || o.includes("@"));
|
|
3376
|
+
if (owners.length > 0) {
|
|
3377
|
+
rules.push({
|
|
3378
|
+
line: i + 1,
|
|
3379
|
+
pattern,
|
|
3380
|
+
owners
|
|
3381
|
+
});
|
|
3382
|
+
}
|
|
3383
|
+
}
|
|
3384
|
+
return rules;
|
|
3385
|
+
}
|
|
3386
|
+
function loadCodeOwners(baseDir) {
|
|
3387
|
+
for (const location of CODEOWNERS_LOCATIONS) {
|
|
3388
|
+
const filePath = path6.join(baseDir, location);
|
|
3389
|
+
if (fs7.existsSync(filePath)) {
|
|
3390
|
+
const content = fs7.readFileSync(filePath, "utf-8");
|
|
3391
|
+
return {
|
|
3392
|
+
filePath,
|
|
3393
|
+
rules: parseCodeOwners(content)
|
|
3394
|
+
};
|
|
3395
|
+
}
|
|
3396
|
+
}
|
|
3397
|
+
return null;
|
|
3398
|
+
}
|
|
3399
|
+
function findOwners(filePath, rules) {
|
|
3400
|
+
const normalizedPath = filePath.replace(/^\.\//, "");
|
|
3401
|
+
let matchingOwners = [];
|
|
3402
|
+
for (const rule of rules) {
|
|
3403
|
+
let pattern = rule.pattern;
|
|
3404
|
+
if (pattern.endsWith("/")) {
|
|
3405
|
+
pattern = pattern + "**";
|
|
3406
|
+
}
|
|
3407
|
+
if (pattern.startsWith("/")) {
|
|
3408
|
+
pattern = pattern.slice(1);
|
|
3409
|
+
} else if (!pattern.startsWith("*")) {
|
|
3410
|
+
pattern = "**/" + pattern;
|
|
3411
|
+
}
|
|
3412
|
+
if (minimatch(normalizedPath, pattern, { matchBase: true, dot: true })) {
|
|
3413
|
+
matchingOwners = rule.owners;
|
|
3414
|
+
}
|
|
3415
|
+
}
|
|
3416
|
+
return matchingOwners;
|
|
3417
|
+
}
|
|
3418
|
+
function attributeOwners(exports, rules, baseDir) {
|
|
3419
|
+
const ownershipMap = new Map;
|
|
3420
|
+
for (const exp of exports) {
|
|
3421
|
+
const filePath = exp.source?.file;
|
|
3422
|
+
if (!filePath) {
|
|
3423
|
+
ownershipMap.set(exp, []);
|
|
3424
|
+
continue;
|
|
3425
|
+
}
|
|
3426
|
+
let relativePath = filePath;
|
|
3427
|
+
if (path6.isAbsolute(filePath)) {
|
|
3428
|
+
relativePath = path6.relative(baseDir, filePath);
|
|
3429
|
+
}
|
|
3430
|
+
const owners = findOwners(relativePath, rules);
|
|
3431
|
+
ownershipMap.set(exp, owners);
|
|
3432
|
+
}
|
|
3433
|
+
return ownershipMap;
|
|
3434
|
+
}
|
|
3435
|
+
function analyzeOwnership(spec, codeowners, baseDir) {
|
|
3436
|
+
const exports = spec.exports ?? [];
|
|
3437
|
+
const ownershipMap = attributeOwners(exports, codeowners.rules, baseDir);
|
|
3438
|
+
const byOwnerExports = new Map;
|
|
3439
|
+
const unowned = [];
|
|
3440
|
+
for (const [exp, owners] of ownershipMap) {
|
|
3441
|
+
if (owners.length === 0) {
|
|
3442
|
+
unowned.push(exp);
|
|
3443
|
+
} else {
|
|
3444
|
+
for (const owner of owners) {
|
|
3445
|
+
const existing = byOwnerExports.get(owner) ?? [];
|
|
3446
|
+
existing.push(exp);
|
|
3447
|
+
byOwnerExports.set(owner, existing);
|
|
3448
|
+
}
|
|
3449
|
+
}
|
|
3450
|
+
}
|
|
3451
|
+
const byOwner = new Map;
|
|
3452
|
+
for (const [owner, ownerExports] of byOwnerExports) {
|
|
3453
|
+
const documented = ownerExports.filter((e) => e.description);
|
|
3454
|
+
const withDrift = ownerExports.filter((e) => e.docs?.drift && e.docs.drift.length > 0);
|
|
3455
|
+
const withExamples = ownerExports.filter((e) => e.examples && e.examples.length > 0);
|
|
3456
|
+
const undocumented = ownerExports.filter((e) => !e.description);
|
|
3457
|
+
const coverageScore = ownerExports.length === 0 ? 100 : Math.round(documented.length / ownerExports.length * 100);
|
|
3458
|
+
const driftScore = ownerExports.length === 0 ? 0 : Math.round(withDrift.length / ownerExports.length * 100);
|
|
3459
|
+
byOwner.set(owner, {
|
|
3460
|
+
owner,
|
|
3461
|
+
totalExports: ownerExports.length,
|
|
3462
|
+
documentedExports: documented.length,
|
|
3463
|
+
coverageScore,
|
|
3464
|
+
exportsWithDrift: withDrift.length,
|
|
3465
|
+
driftScore,
|
|
3466
|
+
missingExamples: ownerExports.length - withExamples.length,
|
|
3467
|
+
undocumentedExports: undocumented.map((e) => e.name)
|
|
3468
|
+
});
|
|
3469
|
+
}
|
|
3470
|
+
return {
|
|
3471
|
+
codeownersPath: codeowners.filePath,
|
|
3472
|
+
byOwner,
|
|
3473
|
+
unowned,
|
|
3474
|
+
totalExports: exports.length
|
|
3475
|
+
};
|
|
3476
|
+
}
|
|
3477
|
+
function analyzeSpecOwnership(spec, options) {
|
|
3478
|
+
const codeowners = loadCodeOwners(options.baseDir);
|
|
3479
|
+
if (!codeowners)
|
|
3480
|
+
return null;
|
|
3481
|
+
return analyzeOwnership(spec, codeowners, options.baseDir);
|
|
3482
|
+
}
|
|
3483
|
+
// src/contributors/index.ts
|
|
3484
|
+
import { execSync } from "node:child_process";
|
|
3485
|
+
import * as path7 from "node:path";
|
|
3486
|
+
function parseBlameOutput(output) {
|
|
3487
|
+
const results = [];
|
|
3488
|
+
const lines = output.split(`
|
|
3489
|
+
`);
|
|
3490
|
+
let currentCommit = "";
|
|
3491
|
+
let currentAuthor = "";
|
|
3492
|
+
let currentEmail = "";
|
|
3493
|
+
let currentTime = 0;
|
|
3494
|
+
let lineNumber = 0;
|
|
3495
|
+
for (const line of lines) {
|
|
3496
|
+
if (line.match(/^[0-9a-f]{40}/)) {
|
|
3497
|
+
const parts = line.split(" ");
|
|
3498
|
+
currentCommit = parts[0];
|
|
3499
|
+
lineNumber = parseInt(parts[2], 10);
|
|
3500
|
+
} else if (line.startsWith("author ")) {
|
|
3501
|
+
currentAuthor = line.slice(7);
|
|
3502
|
+
} else if (line.startsWith("author-mail ")) {
|
|
3503
|
+
currentEmail = line.slice(12).replace(/[<>]/g, "");
|
|
3504
|
+
} else if (line.startsWith("author-time ")) {
|
|
3505
|
+
currentTime = parseInt(line.slice(12), 10);
|
|
3506
|
+
} else if (line.startsWith("\t")) {
|
|
3507
|
+
results.push({
|
|
3508
|
+
commit: currentCommit,
|
|
3509
|
+
author: currentAuthor,
|
|
3510
|
+
email: currentEmail,
|
|
3511
|
+
line: lineNumber,
|
|
3512
|
+
timestamp: currentTime
|
|
3513
|
+
});
|
|
3514
|
+
}
|
|
3515
|
+
}
|
|
3516
|
+
return results;
|
|
3517
|
+
}
|
|
3518
|
+
function getFileBlame(filePath, cwd) {
|
|
3519
|
+
try {
|
|
3520
|
+
const relativePath = path7.isAbsolute(filePath) ? path7.relative(cwd, filePath) : filePath;
|
|
3521
|
+
const output = execSync(`git blame --porcelain "${relativePath}"`, {
|
|
3522
|
+
cwd,
|
|
3523
|
+
encoding: "utf-8",
|
|
3524
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
3525
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3526
|
+
});
|
|
3527
|
+
return parseBlameOutput(output);
|
|
3528
|
+
} catch {
|
|
3529
|
+
return null;
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
3532
|
+
function getBlameForLines(filePath, startLine, endLine, cwd) {
|
|
3533
|
+
try {
|
|
3534
|
+
const relativePath = path7.isAbsolute(filePath) ? path7.relative(cwd, filePath) : filePath;
|
|
3535
|
+
const output = execSync(`git blame --porcelain -L ${startLine},${endLine} "${relativePath}"`, {
|
|
3536
|
+
cwd,
|
|
3537
|
+
encoding: "utf-8",
|
|
3538
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
3539
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3540
|
+
});
|
|
3541
|
+
return parseBlameOutput(output);
|
|
3542
|
+
} catch {
|
|
3543
|
+
return null;
|
|
3544
|
+
}
|
|
3545
|
+
}
|
|
3546
|
+
function findPrimaryAuthor(blameLines) {
|
|
3547
|
+
if (blameLines.length === 0)
|
|
3548
|
+
return null;
|
|
3549
|
+
const authorCounts = new Map;
|
|
3550
|
+
for (const line of blameLines) {
|
|
3551
|
+
const key = line.email;
|
|
3552
|
+
const existing = authorCounts.get(key);
|
|
3553
|
+
if (existing) {
|
|
3554
|
+
existing.count++;
|
|
3555
|
+
if (line.timestamp > existing.info.timestamp) {
|
|
3556
|
+
existing.info = line;
|
|
3557
|
+
}
|
|
3558
|
+
} else {
|
|
3559
|
+
authorCounts.set(key, { count: 1, info: line });
|
|
3560
|
+
}
|
|
3561
|
+
}
|
|
3562
|
+
let maxCount = 0;
|
|
3563
|
+
let primaryAuthor = null;
|
|
3564
|
+
for (const { count, info } of authorCounts.values()) {
|
|
3565
|
+
if (count > maxCount) {
|
|
3566
|
+
maxCount = count;
|
|
3567
|
+
primaryAuthor = info;
|
|
3568
|
+
}
|
|
3569
|
+
}
|
|
3570
|
+
return primaryAuthor;
|
|
3571
|
+
}
|
|
3572
|
+
function analyzeContributors(spec, baseDir) {
|
|
3573
|
+
const exports = spec.exports ?? [];
|
|
3574
|
+
const byContributor = new Map;
|
|
3575
|
+
const unattributed = [];
|
|
3576
|
+
let totalDocumented = 0;
|
|
3577
|
+
const fileBlameCache = new Map;
|
|
3578
|
+
for (const exp of exports) {
|
|
3579
|
+
if (!exp.description)
|
|
3580
|
+
continue;
|
|
3581
|
+
const filePath = exp.source?.file;
|
|
3582
|
+
const line = exp.source?.line;
|
|
3583
|
+
if (!filePath || !line) {
|
|
3584
|
+
unattributed.push(exp.name);
|
|
3585
|
+
continue;
|
|
3586
|
+
}
|
|
3587
|
+
let fileBlame = fileBlameCache.get(filePath);
|
|
3588
|
+
if (fileBlame === undefined) {
|
|
3589
|
+
fileBlame = getFileBlame(filePath, baseDir);
|
|
3590
|
+
fileBlameCache.set(filePath, fileBlame);
|
|
3591
|
+
}
|
|
3592
|
+
if (!fileBlame) {
|
|
3593
|
+
unattributed.push(exp.name);
|
|
3594
|
+
continue;
|
|
3595
|
+
}
|
|
3596
|
+
const jsDocStartLine = Math.max(1, line - 20);
|
|
3597
|
+
const declarationLine = line;
|
|
3598
|
+
const jsDocBlame = fileBlame.filter((b) => b.line >= jsDocStartLine && b.line < declarationLine);
|
|
3599
|
+
const primaryAuthor = findPrimaryAuthor(jsDocBlame);
|
|
3600
|
+
if (!primaryAuthor) {
|
|
3601
|
+
const declBlame = fileBlame.find((b) => b.line === declarationLine);
|
|
3602
|
+
if (declBlame) {
|
|
3603
|
+
attributeToContributor(byContributor, declBlame, exp.name, 1);
|
|
3604
|
+
totalDocumented++;
|
|
3605
|
+
} else {
|
|
3606
|
+
unattributed.push(exp.name);
|
|
3607
|
+
}
|
|
3608
|
+
continue;
|
|
3609
|
+
}
|
|
3610
|
+
const linesAuthored = jsDocBlame.filter((b) => b.email === primaryAuthor.email).length;
|
|
3611
|
+
attributeToContributor(byContributor, primaryAuthor, exp.name, linesAuthored);
|
|
3612
|
+
totalDocumented++;
|
|
3613
|
+
}
|
|
3614
|
+
return {
|
|
3615
|
+
byContributor,
|
|
3616
|
+
totalDocumented,
|
|
3617
|
+
unattributed
|
|
3618
|
+
};
|
|
3619
|
+
}
|
|
3620
|
+
function attributeToContributor(byContributor, blame, exportName, lines) {
|
|
3621
|
+
const key = blame.email;
|
|
3622
|
+
const existing = byContributor.get(key);
|
|
3623
|
+
if (existing) {
|
|
3624
|
+
existing.documentedExports++;
|
|
3625
|
+
existing.exports.push(exportName);
|
|
3626
|
+
existing.linesAuthored += lines;
|
|
3627
|
+
const blameDate = new Date(blame.timestamp * 1000);
|
|
3628
|
+
if (!existing.lastContribution || blameDate > existing.lastContribution) {
|
|
3629
|
+
existing.lastContribution = blameDate;
|
|
3630
|
+
}
|
|
3631
|
+
} else {
|
|
3632
|
+
byContributor.set(key, {
|
|
3633
|
+
name: blame.author,
|
|
3634
|
+
email: blame.email,
|
|
3635
|
+
documentedExports: 1,
|
|
3636
|
+
exports: [exportName],
|
|
3637
|
+
linesAuthored: lines,
|
|
3638
|
+
lastContribution: new Date(blame.timestamp * 1000)
|
|
3639
|
+
});
|
|
3640
|
+
}
|
|
3641
|
+
}
|
|
3642
|
+
function analyzeSpecContributors(spec, options) {
|
|
3643
|
+
try {
|
|
3644
|
+
execSync("git rev-parse --git-dir", {
|
|
3645
|
+
cwd: options.baseDir,
|
|
3646
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
3647
|
+
});
|
|
3648
|
+
} catch {
|
|
3649
|
+
return null;
|
|
3650
|
+
}
|
|
3651
|
+
return analyzeContributors(spec, options.baseDir);
|
|
2592
3652
|
}
|
|
2593
3653
|
// src/config/types.ts
|
|
2594
3654
|
function defineConfig(config) {
|
|
2595
3655
|
return config;
|
|
2596
3656
|
}
|
|
2597
3657
|
// src/detect/utils.ts
|
|
2598
|
-
async function safeParseJson(
|
|
3658
|
+
async function safeParseJson(fs8, path8) {
|
|
2599
3659
|
try {
|
|
2600
|
-
if (!await
|
|
3660
|
+
if (!await fs8.exists(path8))
|
|
2601
3661
|
return null;
|
|
2602
|
-
const content = await
|
|
3662
|
+
const content = await fs8.readFile(path8);
|
|
2603
3663
|
return JSON.parse(content);
|
|
2604
3664
|
} catch {
|
|
2605
3665
|
return null;
|
|
2606
3666
|
}
|
|
2607
3667
|
}
|
|
2608
|
-
async function readPackageJson(
|
|
2609
|
-
const
|
|
2610
|
-
return safeParseJson(
|
|
3668
|
+
async function readPackageJson(fs8, dir) {
|
|
3669
|
+
const path8 = dir === "." ? "package.json" : `${dir}/package.json`;
|
|
3670
|
+
return safeParseJson(fs8, path8);
|
|
2611
3671
|
}
|
|
2612
3672
|
|
|
2613
3673
|
// src/detect/build.ts
|
|
@@ -2637,8 +3697,8 @@ var BUILD_TOOL_PATTERNS = [
|
|
|
2637
3697
|
"babel",
|
|
2638
3698
|
"ncc build"
|
|
2639
3699
|
];
|
|
2640
|
-
async function detectBuildInfo(
|
|
2641
|
-
const pkgJson = await readPackageJson(
|
|
3700
|
+
async function detectBuildInfo(fs8, packagePath = ".") {
|
|
3701
|
+
const pkgJson = await readPackageJson(fs8, packagePath);
|
|
2642
3702
|
const scripts = pkgJson?.scripts ?? {};
|
|
2643
3703
|
const scriptNames = Object.keys(scripts);
|
|
2644
3704
|
const buildScriptsByName = scriptNames.filter((name) => BUILD_SCRIPT_NAMES.has(name) || BUILD_SCRIPT_PREFIXES.some((prefix) => name.startsWith(prefix)));
|
|
@@ -2650,10 +3710,10 @@ async function detectBuildInfo(fs5, packagePath = ".") {
|
|
|
2650
3710
|
});
|
|
2651
3711
|
const buildScripts = [...new Set([...buildScriptsByName, ...buildScriptsByContent])];
|
|
2652
3712
|
const tsconfigPath = packagePath === "." ? "tsconfig.json" : `${packagePath}/tsconfig.json`;
|
|
2653
|
-
const hasTsConfig = await
|
|
3713
|
+
const hasTsConfig = await fs8.exists(tsconfigPath);
|
|
2654
3714
|
const hasTsDep = pkgJson?.devDependencies?.typescript !== undefined || pkgJson?.dependencies?.typescript !== undefined;
|
|
2655
3715
|
const hasTypeScript = hasTsConfig || hasTsDep;
|
|
2656
|
-
const wasm = await detectWasmProject(
|
|
3716
|
+
const wasm = await detectWasmProject(fs8, packagePath, pkgJson);
|
|
2657
3717
|
const napi = detectNapiProject(pkgJson);
|
|
2658
3718
|
return {
|
|
2659
3719
|
scripts: buildScripts,
|
|
@@ -2670,11 +3730,11 @@ var WASM_PACKAGES = new Set([
|
|
|
2670
3730
|
"wasm-bindgen",
|
|
2671
3731
|
"@aspect/aspect-cli"
|
|
2672
3732
|
]);
|
|
2673
|
-
async function detectWasmProject(
|
|
3733
|
+
async function detectWasmProject(fs8, packagePath, pkgJson) {
|
|
2674
3734
|
const pkgCargoPath = packagePath === "." ? "Cargo.toml" : `${packagePath}/Cargo.toml`;
|
|
2675
|
-
if (await
|
|
3735
|
+
if (await fs8.exists(pkgCargoPath))
|
|
2676
3736
|
return true;
|
|
2677
|
-
if (packagePath !== "." && await
|
|
3737
|
+
if (packagePath !== "." && await fs8.exists("Cargo.toml"))
|
|
2678
3738
|
return true;
|
|
2679
3739
|
if (pkgJson) {
|
|
2680
3740
|
const deps = Object.keys({
|
|
@@ -2715,15 +3775,15 @@ function getPrimaryBuildScript(buildInfo) {
|
|
|
2715
3775
|
return buildInfo.scripts[0] ?? null;
|
|
2716
3776
|
}
|
|
2717
3777
|
// src/detect/entry-point.ts
|
|
2718
|
-
async function detectEntryPoint(
|
|
2719
|
-
const pkgJson = await readPackageJson(
|
|
3778
|
+
async function detectEntryPoint(fs8, packagePath = ".") {
|
|
3779
|
+
const pkgJson = await readPackageJson(fs8, packagePath);
|
|
2720
3780
|
if (!pkgJson) {
|
|
2721
3781
|
throw new Error("No package.json found - not a valid npm package");
|
|
2722
3782
|
}
|
|
2723
|
-
const tsConfig = await parseTsConfig(
|
|
3783
|
+
const tsConfig = await parseTsConfig(fs8, packagePath);
|
|
2724
3784
|
const typesField = pkgJson.types || pkgJson.typings;
|
|
2725
3785
|
if (typesField && typeof typesField === "string") {
|
|
2726
|
-
const resolved = await resolveToSource(
|
|
3786
|
+
const resolved = await resolveToSource(fs8, packagePath, typesField, tsConfig);
|
|
2727
3787
|
if (resolved) {
|
|
2728
3788
|
return { ...resolved, source: "types" };
|
|
2729
3789
|
}
|
|
@@ -2733,7 +3793,7 @@ async function detectEntryPoint(fs5, packagePath = ".") {
|
|
|
2733
3793
|
if (dotExport && typeof dotExport === "object" && "types" in dotExport) {
|
|
2734
3794
|
const typesPath = dotExport.types;
|
|
2735
3795
|
if (typesPath && typeof typesPath === "string") {
|
|
2736
|
-
const resolved = await resolveToSource(
|
|
3796
|
+
const resolved = await resolveToSource(fs8, packagePath, typesPath, tsConfig);
|
|
2737
3797
|
if (resolved) {
|
|
2738
3798
|
return { ...resolved, source: "exports" };
|
|
2739
3799
|
}
|
|
@@ -2741,13 +3801,13 @@ async function detectEntryPoint(fs5, packagePath = ".") {
|
|
|
2741
3801
|
}
|
|
2742
3802
|
}
|
|
2743
3803
|
if (pkgJson.main && typeof pkgJson.main === "string") {
|
|
2744
|
-
const resolved = await resolveToSource(
|
|
3804
|
+
const resolved = await resolveToSource(fs8, packagePath, pkgJson.main, tsConfig);
|
|
2745
3805
|
if (resolved) {
|
|
2746
3806
|
return { ...resolved, source: "main" };
|
|
2747
3807
|
}
|
|
2748
3808
|
}
|
|
2749
3809
|
if (pkgJson.module && typeof pkgJson.module === "string") {
|
|
2750
|
-
const resolved = await resolveToSource(
|
|
3810
|
+
const resolved = await resolveToSource(fs8, packagePath, pkgJson.module, tsConfig);
|
|
2751
3811
|
if (resolved) {
|
|
2752
3812
|
return { ...resolved, source: "module" };
|
|
2753
3813
|
}
|
|
@@ -2765,27 +3825,27 @@ async function detectEntryPoint(fs5, packagePath = ".") {
|
|
|
2765
3825
|
];
|
|
2766
3826
|
for (const fallback of fallbacks) {
|
|
2767
3827
|
const checkPath = packagePath === "." ? fallback : `${packagePath}/${fallback}`;
|
|
2768
|
-
if (await
|
|
3828
|
+
if (await fs8.exists(checkPath)) {
|
|
2769
3829
|
return { path: fallback, source: "fallback", isDeclarationOnly: false };
|
|
2770
3830
|
}
|
|
2771
3831
|
}
|
|
2772
3832
|
throw new Error("Could not detect TypeScript entry point. No types field in package.json and no common entry paths found.");
|
|
2773
3833
|
}
|
|
2774
|
-
async function parseTsConfig(
|
|
3834
|
+
async function parseTsConfig(fs8, packagePath) {
|
|
2775
3835
|
const tsconfigPath = packagePath === "." ? "tsconfig.json" : `${packagePath}/tsconfig.json`;
|
|
2776
|
-
const tsconfig = await safeParseJson(
|
|
3836
|
+
const tsconfig = await safeParseJson(fs8, tsconfigPath);
|
|
2777
3837
|
if (!tsconfig?.compilerOptions) {
|
|
2778
3838
|
return null;
|
|
2779
3839
|
}
|
|
2780
3840
|
const { outDir, rootDir, baseUrl, paths } = tsconfig.compilerOptions;
|
|
2781
3841
|
return { outDir, rootDir, baseUrl, paths };
|
|
2782
3842
|
}
|
|
2783
|
-
async function resolveToSource(
|
|
3843
|
+
async function resolveToSource(fs8, basePath, filePath, tsConfig) {
|
|
2784
3844
|
const normalized = filePath.replace(/^\.\//, "");
|
|
2785
3845
|
const checkPath = (p) => basePath === "." ? p : `${basePath}/${p}`;
|
|
2786
3846
|
const isSourceTs = normalized.endsWith(".ts") && !normalized.endsWith(".d.ts") || normalized.endsWith(".tsx");
|
|
2787
3847
|
if (isSourceTs) {
|
|
2788
|
-
if (await
|
|
3848
|
+
if (await fs8.exists(checkPath(normalized))) {
|
|
2789
3849
|
return { path: normalized, isDeclarationOnly: false };
|
|
2790
3850
|
}
|
|
2791
3851
|
}
|
|
@@ -2827,19 +3887,19 @@ async function resolveToSource(fs5, basePath, filePath, tsConfig) {
|
|
|
2827
3887
|
for (const candidate of candidates) {
|
|
2828
3888
|
if (candidate.endsWith(".d.ts"))
|
|
2829
3889
|
continue;
|
|
2830
|
-
if (await
|
|
3890
|
+
if (await fs8.exists(checkPath(candidate))) {
|
|
2831
3891
|
return { path: candidate, isDeclarationOnly: false };
|
|
2832
3892
|
}
|
|
2833
3893
|
}
|
|
2834
3894
|
if (normalized.endsWith(".d.ts")) {
|
|
2835
|
-
if (await
|
|
3895
|
+
if (await fs8.exists(checkPath(normalized))) {
|
|
2836
3896
|
return { path: normalized, isDeclarationOnly: true };
|
|
2837
3897
|
}
|
|
2838
3898
|
}
|
|
2839
3899
|
return null;
|
|
2840
3900
|
}
|
|
2841
3901
|
// src/detect/filesystem.ts
|
|
2842
|
-
import * as
|
|
3902
|
+
import * as fs8 from "node:fs";
|
|
2843
3903
|
import * as nodePath from "node:path";
|
|
2844
3904
|
import { Writable } from "node:stream";
|
|
2845
3905
|
|
|
@@ -2852,19 +3912,19 @@ class NodeFileSystem {
|
|
|
2852
3912
|
return nodePath.join(this.basePath, relativePath);
|
|
2853
3913
|
}
|
|
2854
3914
|
async exists(relativePath) {
|
|
2855
|
-
return
|
|
3915
|
+
return fs8.existsSync(this.resolve(relativePath));
|
|
2856
3916
|
}
|
|
2857
3917
|
async readFile(relativePath) {
|
|
2858
|
-
return
|
|
3918
|
+
return fs8.readFileSync(this.resolve(relativePath), "utf-8");
|
|
2859
3919
|
}
|
|
2860
3920
|
async readDir(relativePath) {
|
|
2861
|
-
return
|
|
3921
|
+
return fs8.readdirSync(this.resolve(relativePath));
|
|
2862
3922
|
}
|
|
2863
3923
|
async isDirectory(relativePath) {
|
|
2864
3924
|
const fullPath = this.resolve(relativePath);
|
|
2865
|
-
if (!
|
|
3925
|
+
if (!fs8.existsSync(fullPath))
|
|
2866
3926
|
return false;
|
|
2867
|
-
return
|
|
3927
|
+
return fs8.statSync(fullPath).isDirectory();
|
|
2868
3928
|
}
|
|
2869
3929
|
}
|
|
2870
3930
|
function createCaptureStream() {
|
|
@@ -2880,9 +3940,9 @@ function createCaptureStream() {
|
|
|
2880
3940
|
|
|
2881
3941
|
class FileNotFoundError extends Error {
|
|
2882
3942
|
path;
|
|
2883
|
-
constructor(
|
|
2884
|
-
super(message ?? `File not found: ${
|
|
2885
|
-
this.path =
|
|
3943
|
+
constructor(path8, message) {
|
|
3944
|
+
super(message ?? `File not found: ${path8}`);
|
|
3945
|
+
this.path = path8;
|
|
2886
3946
|
this.name = "FileNotFoundError";
|
|
2887
3947
|
}
|
|
2888
3948
|
}
|
|
@@ -2892,34 +3952,34 @@ class SandboxFileSystem {
|
|
|
2892
3952
|
constructor(sandbox) {
|
|
2893
3953
|
this.sandbox = sandbox;
|
|
2894
3954
|
}
|
|
2895
|
-
async exists(
|
|
3955
|
+
async exists(path8) {
|
|
2896
3956
|
const result = await this.sandbox.runCommand({
|
|
2897
3957
|
cmd: "test",
|
|
2898
|
-
args: ["-e",
|
|
3958
|
+
args: ["-e", path8]
|
|
2899
3959
|
});
|
|
2900
3960
|
return result.exitCode === 0;
|
|
2901
3961
|
}
|
|
2902
|
-
async readFile(
|
|
2903
|
-
const exists = await this.exists(
|
|
3962
|
+
async readFile(path8) {
|
|
3963
|
+
const exists = await this.exists(path8);
|
|
2904
3964
|
if (!exists) {
|
|
2905
|
-
throw new FileNotFoundError(
|
|
3965
|
+
throw new FileNotFoundError(path8);
|
|
2906
3966
|
}
|
|
2907
3967
|
const capture = createCaptureStream();
|
|
2908
3968
|
const result = await this.sandbox.runCommand({
|
|
2909
3969
|
cmd: "cat",
|
|
2910
|
-
args: [
|
|
3970
|
+
args: [path8],
|
|
2911
3971
|
stdout: capture.stream
|
|
2912
3972
|
});
|
|
2913
3973
|
if (result.exitCode !== 0) {
|
|
2914
|
-
throw new FileNotFoundError(
|
|
3974
|
+
throw new FileNotFoundError(path8, `Failed to read file: ${path8}`);
|
|
2915
3975
|
}
|
|
2916
3976
|
return capture.getOutput();
|
|
2917
3977
|
}
|
|
2918
|
-
async readDir(
|
|
3978
|
+
async readDir(path8) {
|
|
2919
3979
|
const capture = createCaptureStream();
|
|
2920
3980
|
const result = await this.sandbox.runCommand({
|
|
2921
3981
|
cmd: "ls",
|
|
2922
|
-
args: ["-1",
|
|
3982
|
+
args: ["-1", path8],
|
|
2923
3983
|
stdout: capture.stream
|
|
2924
3984
|
});
|
|
2925
3985
|
if (result.exitCode !== 0) {
|
|
@@ -2928,20 +3988,20 @@ class SandboxFileSystem {
|
|
|
2928
3988
|
return capture.getOutput().split(`
|
|
2929
3989
|
`).filter(Boolean);
|
|
2930
3990
|
}
|
|
2931
|
-
async isDirectory(
|
|
3991
|
+
async isDirectory(path8) {
|
|
2932
3992
|
const result = await this.sandbox.runCommand({
|
|
2933
3993
|
cmd: "test",
|
|
2934
|
-
args: ["-d",
|
|
3994
|
+
args: ["-d", path8]
|
|
2935
3995
|
});
|
|
2936
3996
|
return result.exitCode === 0;
|
|
2937
3997
|
}
|
|
2938
3998
|
}
|
|
2939
3999
|
// src/detect/monorepo.ts
|
|
2940
|
-
async function detectMonorepo(
|
|
2941
|
-
const pkgJson = await readPackageJson(
|
|
4000
|
+
async function detectMonorepo(fs9) {
|
|
4001
|
+
const pkgJson = await readPackageJson(fs9, ".");
|
|
2942
4002
|
if (pkgJson?.workspaces) {
|
|
2943
4003
|
const patterns = extractWorkspacePatterns(pkgJson.workspaces);
|
|
2944
|
-
const packages = await resolveWorkspacePackages(
|
|
4004
|
+
const packages = await resolveWorkspacePackages(fs9, patterns, pkgJson.name, pkgJson.private);
|
|
2945
4005
|
return {
|
|
2946
4006
|
isMonorepo: packages.length > 0,
|
|
2947
4007
|
type: "npm-workspaces",
|
|
@@ -2949,10 +4009,10 @@ async function detectMonorepo(fs6) {
|
|
|
2949
4009
|
packages
|
|
2950
4010
|
};
|
|
2951
4011
|
}
|
|
2952
|
-
if (await
|
|
2953
|
-
const content = await
|
|
4012
|
+
if (await fs9.exists("pnpm-workspace.yaml")) {
|
|
4013
|
+
const content = await fs9.readFile("pnpm-workspace.yaml");
|
|
2954
4014
|
const patterns = parsePnpmWorkspace(content);
|
|
2955
|
-
const packages = await resolveWorkspacePackages(
|
|
4015
|
+
const packages = await resolveWorkspacePackages(fs9, patterns, pkgJson?.name, pkgJson?.private);
|
|
2956
4016
|
return {
|
|
2957
4017
|
isMonorepo: packages.length > 0,
|
|
2958
4018
|
type: "pnpm-workspaces",
|
|
@@ -2960,10 +4020,10 @@ async function detectMonorepo(fs6) {
|
|
|
2960
4020
|
packages
|
|
2961
4021
|
};
|
|
2962
4022
|
}
|
|
2963
|
-
if (await
|
|
2964
|
-
const lerna = await safeParseJson(
|
|
4023
|
+
if (await fs9.exists("lerna.json")) {
|
|
4024
|
+
const lerna = await safeParseJson(fs9, "lerna.json");
|
|
2965
4025
|
const patterns = lerna?.packages ?? ["packages/*"];
|
|
2966
|
-
const packages = await resolveWorkspacePackages(
|
|
4026
|
+
const packages = await resolveWorkspacePackages(fs9, patterns, pkgJson?.name, pkgJson?.private);
|
|
2967
4027
|
return {
|
|
2968
4028
|
isMonorepo: packages.length > 0,
|
|
2969
4029
|
type: "lerna",
|
|
@@ -3037,7 +4097,7 @@ function parsePnpmWorkspace(content) {
|
|
|
3037
4097
|
}
|
|
3038
4098
|
return patterns.length > 0 ? patterns : ["packages/*"];
|
|
3039
4099
|
}
|
|
3040
|
-
async function resolveWorkspacePackages(
|
|
4100
|
+
async function resolveWorkspacePackages(fs9, patterns, rootPackageName, rootIsPrivate) {
|
|
3041
4101
|
const packages = [];
|
|
3042
4102
|
const seen = new Set;
|
|
3043
4103
|
if (rootPackageName && !rootIsPrivate && rootPackageName !== "root") {
|
|
@@ -3059,18 +4119,18 @@ async function resolveWorkspacePackages(fs6, patterns, rootPackageName, rootIsPr
|
|
|
3059
4119
|
}
|
|
3060
4120
|
dirsToScan.add("packages");
|
|
3061
4121
|
for (const dir of dirsToScan) {
|
|
3062
|
-
if (!await
|
|
4122
|
+
if (!await fs9.exists(dir))
|
|
3063
4123
|
continue;
|
|
3064
|
-
if (!await
|
|
4124
|
+
if (!await fs9.isDirectory(dir))
|
|
3065
4125
|
continue;
|
|
3066
|
-
const subdirs = await
|
|
4126
|
+
const subdirs = await fs9.readDir(dir);
|
|
3067
4127
|
for (const subdir of subdirs) {
|
|
3068
4128
|
const pkgPath = `${dir}/${subdir}`;
|
|
3069
4129
|
const pkgJsonPath = `${pkgPath}/package.json`;
|
|
3070
|
-
if (!await
|
|
4130
|
+
if (!await fs9.exists(pkgJsonPath))
|
|
3071
4131
|
continue;
|
|
3072
4132
|
try {
|
|
3073
|
-
const content = await
|
|
4133
|
+
const content = await fs9.readFile(pkgJsonPath);
|
|
3074
4134
|
const pkg = JSON.parse(content);
|
|
3075
4135
|
if (pkg.name && !seen.has(pkg.name)) {
|
|
3076
4136
|
seen.add(pkg.name);
|
|
@@ -3142,14 +4202,14 @@ var DEFAULT_PM = {
|
|
|
3142
4202
|
installArgs: ["install", "--legacy-peer-deps"],
|
|
3143
4203
|
runPrefix: ["npm", "run"]
|
|
3144
4204
|
};
|
|
3145
|
-
async function detectPackageManager(
|
|
3146
|
-
const pkgJson = await safeParseJson(
|
|
4205
|
+
async function detectPackageManager(fs9) {
|
|
4206
|
+
const pkgJson = await safeParseJson(fs9, "package.json");
|
|
3147
4207
|
if (pkgJson?.packageManager) {
|
|
3148
4208
|
const pmName = parsePackageManagerField(pkgJson.packageManager);
|
|
3149
4209
|
if (pmName && PM_CONFIGS[pmName]) {
|
|
3150
4210
|
const config = PM_CONFIGS[pmName];
|
|
3151
4211
|
for (const lockfile of config.lockfiles) {
|
|
3152
|
-
if (await
|
|
4212
|
+
if (await fs9.exists(lockfile)) {
|
|
3153
4213
|
return { ...config.info, lockfile };
|
|
3154
4214
|
}
|
|
3155
4215
|
}
|
|
@@ -3159,7 +4219,7 @@ async function detectPackageManager(fs6) {
|
|
|
3159
4219
|
const foundLockfiles = [];
|
|
3160
4220
|
for (const [pmName, config] of Object.entries(PM_CONFIGS)) {
|
|
3161
4221
|
for (const lockfile of config.lockfiles) {
|
|
3162
|
-
if (await
|
|
4222
|
+
if (await fs9.exists(lockfile)) {
|
|
3163
4223
|
foundLockfiles.push({ lockfile, pm: pmName });
|
|
3164
4224
|
}
|
|
3165
4225
|
}
|
|
@@ -3191,10 +4251,10 @@ function getRunCommand(pm, script) {
|
|
|
3191
4251
|
return [...pm.runPrefix, script];
|
|
3192
4252
|
}
|
|
3193
4253
|
// src/detect/index.ts
|
|
3194
|
-
async function analyzeProject(
|
|
4254
|
+
async function analyzeProject(fs9, options = {}) {
|
|
3195
4255
|
const [packageManager, monorepo] = await Promise.all([
|
|
3196
|
-
detectPackageManager(
|
|
3197
|
-
detectMonorepo(
|
|
4256
|
+
detectPackageManager(fs9),
|
|
4257
|
+
detectMonorepo(fs9)
|
|
3198
4258
|
]);
|
|
3199
4259
|
let targetPath = ".";
|
|
3200
4260
|
if (monorepo.isMonorepo) {
|
|
@@ -3211,8 +4271,8 @@ async function analyzeProject(fs6, options = {}) {
|
|
|
3211
4271
|
targetPath = pkg.path;
|
|
3212
4272
|
}
|
|
3213
4273
|
const [entryPoint, build] = await Promise.all([
|
|
3214
|
-
detectEntryPoint(
|
|
3215
|
-
detectBuildInfo(
|
|
4274
|
+
detectEntryPoint(fs9, targetPath),
|
|
4275
|
+
detectBuildInfo(fs9, targetPath)
|
|
3216
4276
|
]);
|
|
3217
4277
|
return { packageManager, monorepo, entryPoint, build };
|
|
3218
4278
|
}
|
|
@@ -3256,17 +4316,17 @@ function shouldValidate(validations, check) {
|
|
|
3256
4316
|
return validations.includes(check);
|
|
3257
4317
|
}
|
|
3258
4318
|
// src/typecheck/example-typechecker.ts
|
|
3259
|
-
import * as
|
|
3260
|
-
import * as
|
|
4319
|
+
import * as fs9 from "node:fs";
|
|
4320
|
+
import * as path8 from "node:path";
|
|
3261
4321
|
import ts3 from "typescript";
|
|
3262
4322
|
function stripCodeBlockMarkers(code) {
|
|
3263
4323
|
return code.replace(/^```(?:ts|typescript|js|javascript)?\n?/i, "").replace(/\n?```$/i, "").trim();
|
|
3264
4324
|
}
|
|
3265
4325
|
function getPackageName(packagePath) {
|
|
3266
|
-
const pkgJsonPath =
|
|
3267
|
-
if (
|
|
4326
|
+
const pkgJsonPath = path8.join(packagePath, "package.json");
|
|
4327
|
+
if (fs9.existsSync(pkgJsonPath)) {
|
|
3268
4328
|
try {
|
|
3269
|
-
const pkgJson = JSON.parse(
|
|
4329
|
+
const pkgJson = JSON.parse(fs9.readFileSync(pkgJsonPath, "utf-8"));
|
|
3270
4330
|
return pkgJson.name;
|
|
3271
4331
|
} catch {
|
|
3272
4332
|
return;
|
|
@@ -3276,12 +4336,12 @@ function getPackageName(packagePath) {
|
|
|
3276
4336
|
}
|
|
3277
4337
|
function findTsConfig(packagePath) {
|
|
3278
4338
|
let dir = packagePath;
|
|
3279
|
-
while (dir !==
|
|
3280
|
-
const tsConfigPath =
|
|
3281
|
-
if (
|
|
4339
|
+
while (dir !== path8.dirname(dir)) {
|
|
4340
|
+
const tsConfigPath = path8.join(dir, "tsconfig.json");
|
|
4341
|
+
if (fs9.existsSync(tsConfigPath)) {
|
|
3282
4342
|
return tsConfigPath;
|
|
3283
4343
|
}
|
|
3284
|
-
dir =
|
|
4344
|
+
dir = path8.dirname(dir);
|
|
3285
4345
|
}
|
|
3286
4346
|
return;
|
|
3287
4347
|
}
|
|
@@ -3297,10 +4357,10 @@ function createVirtualSource(example, packageName, exportNames) {
|
|
|
3297
4357
|
`);
|
|
3298
4358
|
}
|
|
3299
4359
|
function getCompilerOptions(tsconfigPath) {
|
|
3300
|
-
if (tsconfigPath &&
|
|
4360
|
+
if (tsconfigPath && fs9.existsSync(tsconfigPath)) {
|
|
3301
4361
|
const configFile = ts3.readConfigFile(tsconfigPath, ts3.sys.readFile);
|
|
3302
4362
|
if (!configFile.error) {
|
|
3303
|
-
const parsed = ts3.parseJsonConfigFileContent(configFile.config, ts3.sys,
|
|
4363
|
+
const parsed = ts3.parseJsonConfigFileContent(configFile.config, ts3.sys, path8.dirname(tsconfigPath));
|
|
3304
4364
|
return {
|
|
3305
4365
|
...parsed.options,
|
|
3306
4366
|
noEmit: true,
|
|
@@ -3326,7 +4386,7 @@ function typecheckExample(example, packagePath, options = {}) {
|
|
|
3326
4386
|
const tsconfigPath = options.tsconfig ?? findTsConfig(packagePath);
|
|
3327
4387
|
const compilerOptions = getCompilerOptions(tsconfigPath);
|
|
3328
4388
|
const virtualSource = createVirtualSource(cleanCode, packageName, exportNames);
|
|
3329
|
-
const virtualFileName =
|
|
4389
|
+
const virtualFileName = path8.join(packagePath, "__doccov_example__.ts");
|
|
3330
4390
|
const hasImport = packageName !== undefined && exportNames && exportNames.length > 0;
|
|
3331
4391
|
const lineOffset = hasImport ? 2 : 0;
|
|
3332
4392
|
const sourceFile = ts3.createSourceFile(virtualFileName, virtualSource, ts3.ScriptTarget.ES2022, true);
|
|
@@ -3398,20 +4458,20 @@ function typecheckExamples(examples, packagePath, options = {}) {
|
|
|
3398
4458
|
|
|
3399
4459
|
// src/utils/example-runner.ts
|
|
3400
4460
|
import { spawn } from "node:child_process";
|
|
3401
|
-
import * as
|
|
4461
|
+
import * as fs10 from "node:fs";
|
|
3402
4462
|
import * as os from "node:os";
|
|
3403
|
-
import * as
|
|
4463
|
+
import * as path9 from "node:path";
|
|
3404
4464
|
function stripCodeBlockMarkers2(code) {
|
|
3405
4465
|
return code.replace(/^```(?:ts|typescript|js|javascript)?\n?/i, "").replace(/\n?```$/i, "").trim();
|
|
3406
4466
|
}
|
|
3407
4467
|
async function runExample(code, options = {}) {
|
|
3408
4468
|
const { timeout = 5000, cwd = process.cwd() } = options;
|
|
3409
4469
|
const cleanCode = stripCodeBlockMarkers2(code);
|
|
3410
|
-
const tmpFile =
|
|
4470
|
+
const tmpFile = path9.join(cwd, `doccov-example-${Date.now()}-${Math.random().toString(36).slice(2)}.ts`);
|
|
3411
4471
|
try {
|
|
3412
|
-
|
|
4472
|
+
fs10.writeFileSync(tmpFile, cleanCode, "utf-8");
|
|
3413
4473
|
const startTime = Date.now();
|
|
3414
|
-
return await new Promise((
|
|
4474
|
+
return await new Promise((resolve5) => {
|
|
3415
4475
|
let stdout = "";
|
|
3416
4476
|
let stderr = "";
|
|
3417
4477
|
let killed = false;
|
|
@@ -3434,7 +4494,7 @@ async function runExample(code, options = {}) {
|
|
|
3434
4494
|
clearTimeout(timeoutId);
|
|
3435
4495
|
const duration = Date.now() - startTime;
|
|
3436
4496
|
if (killed) {
|
|
3437
|
-
|
|
4497
|
+
resolve5({
|
|
3438
4498
|
success: false,
|
|
3439
4499
|
stdout,
|
|
3440
4500
|
stderr: stderr || `Example timed out after ${timeout}ms`,
|
|
@@ -3442,7 +4502,7 @@ async function runExample(code, options = {}) {
|
|
|
3442
4502
|
duration
|
|
3443
4503
|
});
|
|
3444
4504
|
} else {
|
|
3445
|
-
|
|
4505
|
+
resolve5({
|
|
3446
4506
|
success: exitCode === 0,
|
|
3447
4507
|
stdout,
|
|
3448
4508
|
stderr,
|
|
@@ -3453,7 +4513,7 @@ async function runExample(code, options = {}) {
|
|
|
3453
4513
|
});
|
|
3454
4514
|
proc.on("error", (error) => {
|
|
3455
4515
|
clearTimeout(timeoutId);
|
|
3456
|
-
|
|
4516
|
+
resolve5({
|
|
3457
4517
|
success: false,
|
|
3458
4518
|
stdout,
|
|
3459
4519
|
stderr: error.message,
|
|
@@ -3464,7 +4524,7 @@ async function runExample(code, options = {}) {
|
|
|
3464
4524
|
});
|
|
3465
4525
|
} finally {
|
|
3466
4526
|
try {
|
|
3467
|
-
|
|
4527
|
+
fs10.unlinkSync(tmpFile);
|
|
3468
4528
|
} catch {}
|
|
3469
4529
|
}
|
|
3470
4530
|
}
|
|
@@ -3479,15 +4539,15 @@ async function runExamples(examples, options = {}) {
|
|
|
3479
4539
|
return results;
|
|
3480
4540
|
}
|
|
3481
4541
|
function detectPackageManager2(cwd) {
|
|
3482
|
-
if (
|
|
4542
|
+
if (fs10.existsSync(path9.join(cwd, "bun.lockb")))
|
|
3483
4543
|
return "bun";
|
|
3484
|
-
if (
|
|
4544
|
+
if (fs10.existsSync(path9.join(cwd, "bun.lock")))
|
|
3485
4545
|
return "bun";
|
|
3486
|
-
if (
|
|
4546
|
+
if (fs10.existsSync(path9.join(cwd, "pnpm-lock.yaml")))
|
|
3487
4547
|
return "pnpm";
|
|
3488
|
-
if (
|
|
4548
|
+
if (fs10.existsSync(path9.join(cwd, "yarn.lock")))
|
|
3489
4549
|
return "yarn";
|
|
3490
|
-
if (
|
|
4550
|
+
if (fs10.existsSync(path9.join(cwd, "package-lock.json")))
|
|
3491
4551
|
return "npm";
|
|
3492
4552
|
return "npm";
|
|
3493
4553
|
}
|
|
@@ -3504,7 +4564,7 @@ function getInstallCommand2(pm, packagePath) {
|
|
|
3504
4564
|
}
|
|
3505
4565
|
}
|
|
3506
4566
|
async function runCommand(cmd, args, options) {
|
|
3507
|
-
return new Promise((
|
|
4567
|
+
return new Promise((resolve5) => {
|
|
3508
4568
|
let stdout = "";
|
|
3509
4569
|
let stderr = "";
|
|
3510
4570
|
let killed = false;
|
|
@@ -3525,14 +4585,14 @@ async function runCommand(cmd, args, options) {
|
|
|
3525
4585
|
proc.on("close", (exitCode) => {
|
|
3526
4586
|
clearTimeout(timeoutId);
|
|
3527
4587
|
if (killed) {
|
|
3528
|
-
|
|
4588
|
+
resolve5({
|
|
3529
4589
|
success: false,
|
|
3530
4590
|
stdout,
|
|
3531
4591
|
stderr: stderr || `Command timed out after ${options.timeout}ms`,
|
|
3532
4592
|
exitCode: exitCode ?? 1
|
|
3533
4593
|
});
|
|
3534
4594
|
} else {
|
|
3535
|
-
|
|
4595
|
+
resolve5({
|
|
3536
4596
|
success: exitCode === 0,
|
|
3537
4597
|
stdout,
|
|
3538
4598
|
stderr,
|
|
@@ -3542,7 +4602,7 @@ async function runCommand(cmd, args, options) {
|
|
|
3542
4602
|
});
|
|
3543
4603
|
proc.on("error", (error) => {
|
|
3544
4604
|
clearTimeout(timeoutId);
|
|
3545
|
-
|
|
4605
|
+
resolve5({
|
|
3546
4606
|
success: false,
|
|
3547
4607
|
stdout,
|
|
3548
4608
|
stderr: error.message,
|
|
@@ -3555,12 +4615,12 @@ async function runExamplesWithPackage(examples, options) {
|
|
|
3555
4615
|
const { packagePath, packageManager, installTimeout = 60000, timeout = 5000 } = options;
|
|
3556
4616
|
const startTime = Date.now();
|
|
3557
4617
|
const results = new Map;
|
|
3558
|
-
const absolutePackagePath =
|
|
3559
|
-
const workDir =
|
|
4618
|
+
const absolutePackagePath = path9.resolve(packagePath);
|
|
4619
|
+
const workDir = path9.join(os.tmpdir(), `doccov-examples-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
3560
4620
|
try {
|
|
3561
|
-
|
|
4621
|
+
fs10.mkdirSync(workDir, { recursive: true });
|
|
3562
4622
|
const pkgJson = { name: "doccov-example-runner", type: "module" };
|
|
3563
|
-
|
|
4623
|
+
fs10.writeFileSync(path9.join(workDir, "package.json"), JSON.stringify(pkgJson, null, 2));
|
|
3564
4624
|
const pm = packageManager ?? detectPackageManager2(options.cwd ?? process.cwd());
|
|
3565
4625
|
const { cmd, args } = getInstallCommand2(pm, absolutePackagePath);
|
|
3566
4626
|
const installResult = await runCommand(cmd, args, {
|
|
@@ -3588,7 +4648,7 @@ async function runExamplesWithPackage(examples, options) {
|
|
|
3588
4648
|
};
|
|
3589
4649
|
} finally {
|
|
3590
4650
|
try {
|
|
3591
|
-
|
|
4651
|
+
fs10.rmSync(workDir, { recursive: true, force: true });
|
|
3592
4652
|
} catch {}
|
|
3593
4653
|
}
|
|
3594
4654
|
}
|
|
@@ -3768,8 +4828,8 @@ async function validateExamples(exports, options) {
|
|
|
3768
4828
|
return result;
|
|
3769
4829
|
}
|
|
3770
4830
|
// src/analysis/run-analysis.ts
|
|
3771
|
-
import * as
|
|
3772
|
-
import * as
|
|
4831
|
+
import * as fs12 from "node:fs";
|
|
4832
|
+
import * as path13 from "node:path";
|
|
3773
4833
|
// src/utils/type-utils.ts
|
|
3774
4834
|
function getTypeId(type, typeChecker) {
|
|
3775
4835
|
const internalId = type.id;
|
|
@@ -3892,7 +4952,7 @@ function collectReferencedTypesFromNode(node, typeChecker, referencedTypes) {
|
|
|
3892
4952
|
}
|
|
3893
4953
|
|
|
3894
4954
|
// src/analysis/context.ts
|
|
3895
|
-
import * as
|
|
4955
|
+
import * as path11 from "node:path";
|
|
3896
4956
|
|
|
3897
4957
|
// src/options.ts
|
|
3898
4958
|
var DEFAULT_MAX_TYPE_DEPTH = 20;
|
|
@@ -3913,7 +4973,7 @@ function normalizeDocCovOptions(options = {}) {
|
|
|
3913
4973
|
}
|
|
3914
4974
|
|
|
3915
4975
|
// src/analysis/program.ts
|
|
3916
|
-
import * as
|
|
4976
|
+
import * as path10 from "node:path";
|
|
3917
4977
|
var DEFAULT_COMPILER_OPTIONS = {
|
|
3918
4978
|
target: ts.ScriptTarget.Latest,
|
|
3919
4979
|
module: ts.ModuleKind.CommonJS,
|
|
@@ -3923,14 +4983,14 @@ var DEFAULT_COMPILER_OPTIONS = {
|
|
|
3923
4983
|
};
|
|
3924
4984
|
function createProgram({
|
|
3925
4985
|
entryFile,
|
|
3926
|
-
baseDir =
|
|
4986
|
+
baseDir = path10.dirname(entryFile),
|
|
3927
4987
|
content
|
|
3928
4988
|
}) {
|
|
3929
4989
|
const configPath = ts.findConfigFile(baseDir, ts.sys.fileExists, "tsconfig.json");
|
|
3930
4990
|
let compilerOptions = { ...DEFAULT_COMPILER_OPTIONS };
|
|
3931
4991
|
if (configPath) {
|
|
3932
4992
|
const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
|
|
3933
|
-
const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys,
|
|
4993
|
+
const parsedConfig = ts.parseJsonConfigFileContent(configFile.config, ts.sys, path10.dirname(configPath));
|
|
3934
4994
|
compilerOptions = { ...compilerOptions, ...parsedConfig.options };
|
|
3935
4995
|
}
|
|
3936
4996
|
const allowJsVal = compilerOptions.allowJs;
|
|
@@ -3965,9 +5025,10 @@ function createAnalysisContext({
|
|
|
3965
5025
|
entryFile,
|
|
3966
5026
|
packageDir,
|
|
3967
5027
|
content,
|
|
3968
|
-
options
|
|
5028
|
+
options,
|
|
5029
|
+
detectedSchemas
|
|
3969
5030
|
}) {
|
|
3970
|
-
const baseDir = packageDir ??
|
|
5031
|
+
const baseDir = packageDir ?? path11.dirname(entryFile);
|
|
3971
5032
|
const normalizedOptions = normalizeDocCovOptions(options);
|
|
3972
5033
|
const programResult = createProgram({ entryFile, baseDir, content });
|
|
3973
5034
|
if (!programResult.sourceFile) {
|
|
@@ -3982,13 +5043,14 @@ function createAnalysisContext({
|
|
|
3982
5043
|
compilerOptions: programResult.compilerOptions,
|
|
3983
5044
|
compilerHost: programResult.compilerHost,
|
|
3984
5045
|
options: normalizedOptions,
|
|
3985
|
-
configPath: programResult.configPath
|
|
5046
|
+
configPath: programResult.configPath,
|
|
5047
|
+
detectedSchemas
|
|
3986
5048
|
};
|
|
3987
5049
|
}
|
|
3988
5050
|
|
|
3989
5051
|
// src/analysis/spec-builder.ts
|
|
3990
|
-
import * as
|
|
3991
|
-
import * as
|
|
5052
|
+
import * as fs11 from "node:fs";
|
|
5053
|
+
import * as path12 from "node:path";
|
|
3992
5054
|
import { SCHEMA_URL, SCHEMA_VERSION } from "@openpkg-ts/spec";
|
|
3993
5055
|
|
|
3994
5056
|
// src/analysis/decorator-utils.ts
|
|
@@ -4020,291 +5082,37 @@ function extractParameterDecorators(param) {
|
|
|
4020
5082
|
return extractDecorators(param);
|
|
4021
5083
|
}
|
|
4022
5084
|
|
|
4023
|
-
// src/utils/
|
|
4024
|
-
var
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
5085
|
+
// src/utils/schema-builder.ts
|
|
5086
|
+
var BUILTIN_TYPE_SCHEMAS = {
|
|
5087
|
+
Date: { type: "string", format: "date-time" },
|
|
5088
|
+
RegExp: { type: "object", description: "RegExp" },
|
|
5089
|
+
Error: { type: "object" },
|
|
5090
|
+
Promise: { type: "object" },
|
|
5091
|
+
Map: { type: "object" },
|
|
5092
|
+
Set: { type: "object" },
|
|
5093
|
+
WeakMap: { type: "object" },
|
|
5094
|
+
WeakSet: { type: "object" },
|
|
5095
|
+
Function: { type: "object" },
|
|
5096
|
+
ArrayBuffer: { type: "string", format: "binary" },
|
|
5097
|
+
ArrayBufferLike: { type: "string", format: "binary" },
|
|
5098
|
+
DataView: { type: "string", format: "binary" },
|
|
5099
|
+
Uint8Array: { type: "string", format: "byte" },
|
|
5100
|
+
Uint16Array: { type: "string", format: "byte" },
|
|
5101
|
+
Uint32Array: { type: "string", format: "byte" },
|
|
5102
|
+
Int8Array: { type: "string", format: "byte" },
|
|
5103
|
+
Int16Array: { type: "string", format: "byte" },
|
|
5104
|
+
Int32Array: { type: "string", format: "byte" },
|
|
5105
|
+
Float32Array: { type: "string", format: "byte" },
|
|
5106
|
+
Float64Array: { type: "string", format: "byte" },
|
|
5107
|
+
BigInt64Array: { type: "string", format: "byte" },
|
|
5108
|
+
BigUint64Array: { type: "string", format: "byte" }
|
|
4035
5109
|
};
|
|
4036
|
-
function
|
|
4037
|
-
|
|
4038
|
-
}
|
|
4039
|
-
function isInternalProperty(name) {
|
|
4040
|
-
return name.startsWith("__@");
|
|
4041
|
-
}
|
|
4042
|
-
function isTypeBoxOptionalMarker(type) {
|
|
4043
|
-
const props = type.getProperties();
|
|
4044
|
-
if (props.length !== 1)
|
|
5110
|
+
function isObjectLiteralType(type) {
|
|
5111
|
+
if (!(type.getFlags() & ts.TypeFlags.Object)) {
|
|
4045
5112
|
return false;
|
|
4046
|
-
return isInternalProperty(props[0].getName());
|
|
4047
|
-
}
|
|
4048
|
-
function unwrapTypeBoxOptional(type) {
|
|
4049
|
-
if (!type.isIntersection()) {
|
|
4050
|
-
return { innerTypes: [type], isOptional: false };
|
|
4051
5113
|
}
|
|
4052
|
-
const
|
|
4053
|
-
|
|
4054
|
-
const hadMarker = filtered.length < intersectionType.types.length;
|
|
4055
|
-
return { innerTypes: filtered, isOptional: hadMarker };
|
|
4056
|
-
}
|
|
4057
|
-
function getPropertyType(prop, parentType, typeChecker) {
|
|
4058
|
-
if (prop.valueDeclaration) {
|
|
4059
|
-
return typeChecker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration);
|
|
4060
|
-
}
|
|
4061
|
-
const propType = typeChecker.getTypeOfPropertyOfType(parentType, prop.getName());
|
|
4062
|
-
if (propType) {
|
|
4063
|
-
return propType;
|
|
4064
|
-
}
|
|
4065
|
-
const decl = prop.declarations?.[0];
|
|
4066
|
-
if (decl) {
|
|
4067
|
-
return typeChecker.getTypeOfSymbolAtLocation(prop, decl);
|
|
4068
|
-
}
|
|
4069
|
-
return typeChecker.getAnyType();
|
|
4070
|
-
}
|
|
4071
|
-
var _formatTypeReference = null;
|
|
4072
|
-
function setFormatTypeReference(fn) {
|
|
4073
|
-
_formatTypeReference = fn;
|
|
4074
|
-
}
|
|
4075
|
-
function formatTypeReferenceInternal(type, typeChecker, typeRefs, referencedTypes, visited, depth, maxDepth, typeIds) {
|
|
4076
|
-
if (_formatTypeReference) {
|
|
4077
|
-
return _formatTypeReference(type, typeChecker, typeRefs, referencedTypes, visited, depth, maxDepth, typeIds);
|
|
4078
|
-
}
|
|
4079
|
-
return { type: "object" };
|
|
4080
|
-
}
|
|
4081
|
-
function formatTypeBoxSchema(type, typeChecker, typeRefs, referencedTypes, visited, depth = 0, maxDepth = DEFAULT_MAX_TYPE_DEPTH, typeIds) {
|
|
4082
|
-
if (depth > maxDepth) {
|
|
4083
|
-
return { type: "unknown" };
|
|
4084
|
-
}
|
|
4085
|
-
const symbol = type.getSymbol();
|
|
4086
|
-
if (!symbol)
|
|
4087
|
-
return null;
|
|
4088
|
-
const symbolName = symbol.getName();
|
|
4089
|
-
if (TYPEBOX_PRIMITIVE_MAP[symbolName]) {
|
|
4090
|
-
return { ...TYPEBOX_PRIMITIVE_MAP[symbolName] };
|
|
4091
|
-
}
|
|
4092
|
-
const objectType = type;
|
|
4093
|
-
if (!(objectType.objectFlags & ts.ObjectFlags.Reference)) {
|
|
4094
|
-
return null;
|
|
4095
|
-
}
|
|
4096
|
-
const typeRef = type;
|
|
4097
|
-
const typeArgs = typeRef.typeArguments;
|
|
4098
|
-
switch (symbolName) {
|
|
4099
|
-
case "TObject": {
|
|
4100
|
-
if (!typeArgs || typeArgs.length === 0) {
|
|
4101
|
-
return { type: "object" };
|
|
4102
|
-
}
|
|
4103
|
-
const propsType = typeArgs[0];
|
|
4104
|
-
const properties = {};
|
|
4105
|
-
const required = [];
|
|
4106
|
-
for (const prop of propsType.getProperties()) {
|
|
4107
|
-
const propName = prop.getName();
|
|
4108
|
-
if (isInternalProperty(propName)) {
|
|
4109
|
-
continue;
|
|
4110
|
-
}
|
|
4111
|
-
const propType = getPropertyType(prop, propsType, typeChecker);
|
|
4112
|
-
const propSymbol = propType.getSymbol();
|
|
4113
|
-
const propSymbolName = propSymbol?.getName();
|
|
4114
|
-
if (propSymbolName && typeRefs.has(propSymbolName)) {
|
|
4115
|
-
properties[propName] = { $ref: `#/types/${propSymbolName}` };
|
|
4116
|
-
} else if (propSymbolName && isTypeBoxSchemaType(propSymbolName)) {
|
|
4117
|
-
const nested = formatTypeBoxSchema(propType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
|
|
4118
|
-
properties[propName] = nested ?? { type: "object" };
|
|
4119
|
-
} else {
|
|
4120
|
-
properties[propName] = formatTypeReferenceInternal(propType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
|
|
4121
|
-
}
|
|
4122
|
-
const { isOptional } = unwrapTypeBoxOptional(propType);
|
|
4123
|
-
if (propSymbolName !== "TOptional" && !isOptional) {
|
|
4124
|
-
required.push(propName);
|
|
4125
|
-
}
|
|
4126
|
-
}
|
|
4127
|
-
const schema = { type: "object", properties };
|
|
4128
|
-
if (required.length > 0) {
|
|
4129
|
-
schema.required = required;
|
|
4130
|
-
}
|
|
4131
|
-
return schema;
|
|
4132
|
-
}
|
|
4133
|
-
case "TArray": {
|
|
4134
|
-
if (!typeArgs || typeArgs.length === 0) {
|
|
4135
|
-
return { type: "array" };
|
|
4136
|
-
}
|
|
4137
|
-
const itemType = typeArgs[0];
|
|
4138
|
-
const itemSymbol = itemType.getSymbol();
|
|
4139
|
-
const itemSymbolName = itemSymbol?.getName();
|
|
4140
|
-
let items;
|
|
4141
|
-
if (itemSymbolName && typeRefs.has(itemSymbolName)) {
|
|
4142
|
-
items = { $ref: `#/types/${itemSymbolName}` };
|
|
4143
|
-
} else if (itemSymbolName && isTypeBoxSchemaType(itemSymbolName)) {
|
|
4144
|
-
items = formatTypeBoxSchema(itemType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
|
|
4145
|
-
type: "object"
|
|
4146
|
-
};
|
|
4147
|
-
} else {
|
|
4148
|
-
items = formatTypeReferenceInternal(itemType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
|
|
4149
|
-
}
|
|
4150
|
-
return { type: "array", items };
|
|
4151
|
-
}
|
|
4152
|
-
case "TUnion": {
|
|
4153
|
-
if (!typeArgs || typeArgs.length === 0) {
|
|
4154
|
-
return { anyOf: [] };
|
|
4155
|
-
}
|
|
4156
|
-
const tupleType = typeArgs[0];
|
|
4157
|
-
const members = [];
|
|
4158
|
-
if (tupleType.isUnion()) {
|
|
4159
|
-
for (const memberType of tupleType.types) {
|
|
4160
|
-
const memberSymbol = memberType.getSymbol();
|
|
4161
|
-
const memberSymbolName = memberSymbol?.getName();
|
|
4162
|
-
if (memberSymbolName && typeRefs.has(memberSymbolName)) {
|
|
4163
|
-
members.push({ $ref: `#/types/${memberSymbolName}` });
|
|
4164
|
-
} else if (memberSymbolName && isTypeBoxSchemaType(memberSymbolName)) {
|
|
4165
|
-
members.push(formatTypeBoxSchema(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
|
|
4166
|
-
type: "object"
|
|
4167
|
-
});
|
|
4168
|
-
} else {
|
|
4169
|
-
members.push(formatTypeReferenceInternal(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds));
|
|
4170
|
-
}
|
|
4171
|
-
}
|
|
4172
|
-
} else if (tupleType.typeArguments) {
|
|
4173
|
-
for (const memberType of tupleType.typeArguments) {
|
|
4174
|
-
const memberSymbol = memberType.getSymbol();
|
|
4175
|
-
const memberSymbolName = memberSymbol?.getName();
|
|
4176
|
-
if (memberSymbolName && typeRefs.has(memberSymbolName)) {
|
|
4177
|
-
members.push({ $ref: `#/types/${memberSymbolName}` });
|
|
4178
|
-
} else if (memberSymbolName && isTypeBoxSchemaType(memberSymbolName)) {
|
|
4179
|
-
members.push(formatTypeBoxSchema(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
|
|
4180
|
-
type: "object"
|
|
4181
|
-
});
|
|
4182
|
-
} else {
|
|
4183
|
-
members.push(formatTypeReferenceInternal(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds));
|
|
4184
|
-
}
|
|
4185
|
-
}
|
|
4186
|
-
}
|
|
4187
|
-
return { anyOf: members };
|
|
4188
|
-
}
|
|
4189
|
-
case "TIntersect": {
|
|
4190
|
-
if (!typeArgs || typeArgs.length === 0) {
|
|
4191
|
-
return { allOf: [] };
|
|
4192
|
-
}
|
|
4193
|
-
const tupleType = typeArgs[0];
|
|
4194
|
-
const members = [];
|
|
4195
|
-
if (tupleType.typeArguments) {
|
|
4196
|
-
for (const memberType of tupleType.typeArguments) {
|
|
4197
|
-
const memberSymbol = memberType.getSymbol();
|
|
4198
|
-
const memberSymbolName = memberSymbol?.getName();
|
|
4199
|
-
if (memberSymbolName && typeRefs.has(memberSymbolName)) {
|
|
4200
|
-
members.push({ $ref: `#/types/${memberSymbolName}` });
|
|
4201
|
-
} else if (memberSymbolName && isTypeBoxSchemaType(memberSymbolName)) {
|
|
4202
|
-
members.push(formatTypeBoxSchema(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
|
|
4203
|
-
type: "object"
|
|
4204
|
-
});
|
|
4205
|
-
} else {
|
|
4206
|
-
members.push(formatTypeReferenceInternal(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds));
|
|
4207
|
-
}
|
|
4208
|
-
}
|
|
4209
|
-
}
|
|
4210
|
-
return { allOf: members };
|
|
4211
|
-
}
|
|
4212
|
-
case "TOptional": {
|
|
4213
|
-
if (!typeArgs || typeArgs.length === 0) {
|
|
4214
|
-
return {};
|
|
4215
|
-
}
|
|
4216
|
-
const innerType = typeArgs[0];
|
|
4217
|
-
const innerSymbol = innerType.getSymbol();
|
|
4218
|
-
const innerSymbolName = innerSymbol?.getName();
|
|
4219
|
-
if (innerSymbolName && typeRefs.has(innerSymbolName)) {
|
|
4220
|
-
return { $ref: `#/types/${innerSymbolName}` };
|
|
4221
|
-
} else if (innerSymbolName && isTypeBoxSchemaType(innerSymbolName)) {
|
|
4222
|
-
return formatTypeBoxSchema(innerType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
|
|
4223
|
-
type: "object"
|
|
4224
|
-
};
|
|
4225
|
-
}
|
|
4226
|
-
return formatTypeReferenceInternal(innerType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
|
|
4227
|
-
}
|
|
4228
|
-
case "TLiteral": {
|
|
4229
|
-
if (!typeArgs || typeArgs.length === 0) {
|
|
4230
|
-
return { enum: [] };
|
|
4231
|
-
}
|
|
4232
|
-
const literalType = typeArgs[0];
|
|
4233
|
-
if (literalType.isLiteral()) {
|
|
4234
|
-
const value = literalType.value;
|
|
4235
|
-
return { enum: [value] };
|
|
4236
|
-
}
|
|
4237
|
-
const literalStr = typeChecker.typeToString(literalType);
|
|
4238
|
-
if (literalStr.startsWith('"') && literalStr.endsWith('"')) {
|
|
4239
|
-
return { enum: [literalStr.slice(1, -1)] };
|
|
4240
|
-
}
|
|
4241
|
-
return { enum: [literalStr] };
|
|
4242
|
-
}
|
|
4243
|
-
case "TRecord": {
|
|
4244
|
-
if (!typeArgs || typeArgs.length < 2) {
|
|
4245
|
-
return { type: "object", additionalProperties: true };
|
|
4246
|
-
}
|
|
4247
|
-
const valueType = typeArgs[1];
|
|
4248
|
-
const valueSymbol = valueType.getSymbol();
|
|
4249
|
-
const valueSymbolName = valueSymbol?.getName();
|
|
4250
|
-
let additionalProperties;
|
|
4251
|
-
if (valueSymbolName && typeRefs.has(valueSymbolName)) {
|
|
4252
|
-
additionalProperties = { $ref: `#/types/${valueSymbolName}` };
|
|
4253
|
-
} else if (valueSymbolName && isTypeBoxSchemaType(valueSymbolName)) {
|
|
4254
|
-
additionalProperties = formatTypeBoxSchema(valueType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? true;
|
|
4255
|
-
} else {
|
|
4256
|
-
additionalProperties = formatTypeReferenceInternal(valueType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
|
|
4257
|
-
}
|
|
4258
|
-
return { type: "object", additionalProperties };
|
|
4259
|
-
}
|
|
4260
|
-
case "TRef": {
|
|
4261
|
-
if (!typeArgs || typeArgs.length === 0) {
|
|
4262
|
-
return { $ref: "#/types/unknown" };
|
|
4263
|
-
}
|
|
4264
|
-
const refType = typeArgs[0];
|
|
4265
|
-
const refSymbol = refType.getSymbol();
|
|
4266
|
-
const refSymbolName = refSymbol?.getName();
|
|
4267
|
-
if (refSymbolName) {
|
|
4268
|
-
return { $ref: `#/types/${refSymbolName}` };
|
|
4269
|
-
}
|
|
4270
|
-
return { type: "object" };
|
|
4271
|
-
}
|
|
4272
|
-
default:
|
|
4273
|
-
return null;
|
|
4274
|
-
}
|
|
4275
|
-
}
|
|
4276
|
-
|
|
4277
|
-
// src/utils/schema-builder.ts
|
|
4278
|
-
var BUILTIN_TYPE_SCHEMAS = {
|
|
4279
|
-
Date: { type: "string", format: "date-time" },
|
|
4280
|
-
RegExp: { type: "object", description: "RegExp" },
|
|
4281
|
-
Error: { type: "object" },
|
|
4282
|
-
Promise: { type: "object" },
|
|
4283
|
-
Map: { type: "object" },
|
|
4284
|
-
Set: { type: "object" },
|
|
4285
|
-
WeakMap: { type: "object" },
|
|
4286
|
-
WeakSet: { type: "object" },
|
|
4287
|
-
Function: { type: "object" },
|
|
4288
|
-
ArrayBuffer: { type: "string", format: "binary" },
|
|
4289
|
-
ArrayBufferLike: { type: "string", format: "binary" },
|
|
4290
|
-
DataView: { type: "string", format: "binary" },
|
|
4291
|
-
Uint8Array: { type: "string", format: "byte" },
|
|
4292
|
-
Uint16Array: { type: "string", format: "byte" },
|
|
4293
|
-
Uint32Array: { type: "string", format: "byte" },
|
|
4294
|
-
Int8Array: { type: "string", format: "byte" },
|
|
4295
|
-
Int16Array: { type: "string", format: "byte" },
|
|
4296
|
-
Int32Array: { type: "string", format: "byte" },
|
|
4297
|
-
Float32Array: { type: "string", format: "byte" },
|
|
4298
|
-
Float64Array: { type: "string", format: "byte" },
|
|
4299
|
-
BigInt64Array: { type: "string", format: "byte" },
|
|
4300
|
-
BigUint64Array: { type: "string", format: "byte" }
|
|
4301
|
-
};
|
|
4302
|
-
function isObjectLiteralType(type) {
|
|
4303
|
-
if (!(type.getFlags() & ts.TypeFlags.Object)) {
|
|
4304
|
-
return false;
|
|
4305
|
-
}
|
|
4306
|
-
const objectFlags = type.objectFlags;
|
|
4307
|
-
return (objectFlags & ts.ObjectFlags.ObjectLiteral) !== 0;
|
|
5114
|
+
const objectFlags = type.objectFlags;
|
|
5115
|
+
return (objectFlags & ts.ObjectFlags.ObjectLiteral) !== 0;
|
|
4308
5116
|
}
|
|
4309
5117
|
function isPureRefSchema(value) {
|
|
4310
5118
|
return Object.keys(value).length === 1 && "$ref" in value;
|
|
@@ -4357,9 +5165,9 @@ function propertiesToSchema(properties, description) {
|
|
|
4357
5165
|
}
|
|
4358
5166
|
return schema;
|
|
4359
5167
|
}
|
|
4360
|
-
var
|
|
5168
|
+
var _formatTypeReference = null;
|
|
4361
5169
|
function setSchemaBuilderFormatTypeReference(fn) {
|
|
4362
|
-
|
|
5170
|
+
_formatTypeReference = fn;
|
|
4363
5171
|
}
|
|
4364
5172
|
function buildSchemaFromTypeNode(node, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName) {
|
|
4365
5173
|
if (ts.isParenthesizedTypeNode(node)) {
|
|
@@ -4390,7 +5198,7 @@ function buildSchemaFromTypeNode(node, typeChecker, typeRefs, referencedTypes, f
|
|
|
4390
5198
|
let schema2 = "any";
|
|
4391
5199
|
if (member.type) {
|
|
4392
5200
|
const memberType = typeChecker.getTypeFromTypeNode(member.type);
|
|
4393
|
-
const formatted =
|
|
5201
|
+
const formatted = _formatTypeReference ? _formatTypeReference(memberType, typeChecker, typeRefs, referencedTypes) : { type: "any" };
|
|
4394
5202
|
if (typeof formatted === "string") {
|
|
4395
5203
|
if (formatted === "any") {
|
|
4396
5204
|
schema2 = buildSchemaFromTypeNode(member.type, typeChecker, typeRefs, referencedTypes, functionDoc, parentParamName);
|
|
@@ -4522,54 +5330,307 @@ function findDiscriminatorProperty(unionTypes, typeChecker) {
|
|
|
4522
5330
|
isDiscriminator = false;
|
|
4523
5331
|
break;
|
|
4524
5332
|
}
|
|
4525
|
-
values.add(value);
|
|
5333
|
+
values.add(value);
|
|
5334
|
+
}
|
|
5335
|
+
if (isDiscriminator) {
|
|
5336
|
+
return propName;
|
|
5337
|
+
}
|
|
5338
|
+
}
|
|
5339
|
+
return;
|
|
5340
|
+
}
|
|
5341
|
+
function schemaIsAny(schema) {
|
|
5342
|
+
if (typeof schema === "string") {
|
|
5343
|
+
return schema === "any";
|
|
5344
|
+
}
|
|
5345
|
+
if ("type" in schema && schema.type === "any" && Object.keys(schema).length === 1) {
|
|
5346
|
+
return true;
|
|
5347
|
+
}
|
|
5348
|
+
return false;
|
|
5349
|
+
}
|
|
5350
|
+
function schemasAreEqual(left, right) {
|
|
5351
|
+
if (typeof left !== typeof right) {
|
|
5352
|
+
return false;
|
|
5353
|
+
}
|
|
5354
|
+
if (typeof left === "string" && typeof right === "string") {
|
|
5355
|
+
return left === right;
|
|
5356
|
+
}
|
|
5357
|
+
if (left == null || right == null) {
|
|
5358
|
+
return left === right;
|
|
5359
|
+
}
|
|
5360
|
+
const normalize = (value) => {
|
|
5361
|
+
if (Array.isArray(value)) {
|
|
5362
|
+
return value.map((item) => normalize(item));
|
|
5363
|
+
}
|
|
5364
|
+
if (value && typeof value === "object") {
|
|
5365
|
+
const sortedEntries = Object.entries(value).map(([key, val]) => [key, normalize(val)]).sort(([keyA], [keyB]) => keyA.localeCompare(keyB));
|
|
5366
|
+
return Object.fromEntries(sortedEntries);
|
|
5367
|
+
}
|
|
5368
|
+
return value;
|
|
5369
|
+
};
|
|
5370
|
+
return JSON.stringify(normalize(left)) === JSON.stringify(normalize(right));
|
|
5371
|
+
}
|
|
5372
|
+
function deduplicateSchemas(schemas) {
|
|
5373
|
+
const result = [];
|
|
5374
|
+
for (const schema of schemas) {
|
|
5375
|
+
const isDuplicate = result.some((existing) => schemasAreEqual(existing, schema));
|
|
5376
|
+
if (!isDuplicate) {
|
|
5377
|
+
result.push(schema);
|
|
5378
|
+
}
|
|
5379
|
+
}
|
|
5380
|
+
return result;
|
|
5381
|
+
}
|
|
5382
|
+
// src/utils/typebox-handler.ts
|
|
5383
|
+
var TYPEBOX_PRIMITIVE_MAP = {
|
|
5384
|
+
TString: { type: "string" },
|
|
5385
|
+
TNumber: { type: "number" },
|
|
5386
|
+
TBoolean: { type: "boolean" },
|
|
5387
|
+
TInteger: { type: "integer" },
|
|
5388
|
+
TNull: { type: "null" },
|
|
5389
|
+
TAny: {},
|
|
5390
|
+
TUnknown: {},
|
|
5391
|
+
TNever: { not: {} },
|
|
5392
|
+
TVoid: { type: "null" },
|
|
5393
|
+
TUndefined: { type: "null" }
|
|
5394
|
+
};
|
|
5395
|
+
function isTypeBoxSchemaType(symbolName) {
|
|
5396
|
+
return /^T[A-Z][a-zA-Z]*$/.test(symbolName);
|
|
5397
|
+
}
|
|
5398
|
+
function isInternalProperty(name) {
|
|
5399
|
+
return name.startsWith("__@");
|
|
5400
|
+
}
|
|
5401
|
+
function isTypeBoxOptionalMarker(type) {
|
|
5402
|
+
const props = type.getProperties();
|
|
5403
|
+
if (props.length !== 1)
|
|
5404
|
+
return false;
|
|
5405
|
+
return isInternalProperty(props[0].getName());
|
|
5406
|
+
}
|
|
5407
|
+
function unwrapTypeBoxOptional(type) {
|
|
5408
|
+
if (!type.isIntersection()) {
|
|
5409
|
+
return { innerTypes: [type], isOptional: false };
|
|
5410
|
+
}
|
|
5411
|
+
const intersectionType = type;
|
|
5412
|
+
const filtered = intersectionType.types.filter((t) => !isTypeBoxOptionalMarker(t));
|
|
5413
|
+
const hadMarker = filtered.length < intersectionType.types.length;
|
|
5414
|
+
return { innerTypes: filtered, isOptional: hadMarker };
|
|
5415
|
+
}
|
|
5416
|
+
function getPropertyType(prop, parentType, typeChecker) {
|
|
5417
|
+
if (prop.valueDeclaration) {
|
|
5418
|
+
return typeChecker.getTypeOfSymbolAtLocation(prop, prop.valueDeclaration);
|
|
5419
|
+
}
|
|
5420
|
+
const propType = typeChecker.getTypeOfPropertyOfType(parentType, prop.getName());
|
|
5421
|
+
if (propType) {
|
|
5422
|
+
return propType;
|
|
5423
|
+
}
|
|
5424
|
+
const decl = prop.declarations?.[0];
|
|
5425
|
+
if (decl) {
|
|
5426
|
+
return typeChecker.getTypeOfSymbolAtLocation(prop, decl);
|
|
5427
|
+
}
|
|
5428
|
+
return typeChecker.getAnyType();
|
|
5429
|
+
}
|
|
5430
|
+
var _formatTypeReference2 = null;
|
|
5431
|
+
function setFormatTypeReference(fn) {
|
|
5432
|
+
_formatTypeReference2 = fn;
|
|
5433
|
+
}
|
|
5434
|
+
function formatTypeReferenceInternal(type, typeChecker, typeRefs, referencedTypes, visited, depth, maxDepth, typeIds) {
|
|
5435
|
+
if (_formatTypeReference2) {
|
|
5436
|
+
return _formatTypeReference2(type, typeChecker, typeRefs, referencedTypes, visited, depth, maxDepth, typeIds);
|
|
5437
|
+
}
|
|
5438
|
+
return { type: "object" };
|
|
5439
|
+
}
|
|
5440
|
+
function formatTypeBoxSchema(type, typeChecker, typeRefs, referencedTypes, visited, depth = 0, maxDepth = DEFAULT_MAX_TYPE_DEPTH, typeIds) {
|
|
5441
|
+
if (depth > maxDepth) {
|
|
5442
|
+
return { type: "unknown" };
|
|
5443
|
+
}
|
|
5444
|
+
const symbol = type.getSymbol();
|
|
5445
|
+
if (!symbol)
|
|
5446
|
+
return null;
|
|
5447
|
+
const symbolName = symbol.getName();
|
|
5448
|
+
if (TYPEBOX_PRIMITIVE_MAP[symbolName]) {
|
|
5449
|
+
return { ...TYPEBOX_PRIMITIVE_MAP[symbolName] };
|
|
5450
|
+
}
|
|
5451
|
+
const objectType = type;
|
|
5452
|
+
if (!(objectType.objectFlags & ts.ObjectFlags.Reference)) {
|
|
5453
|
+
return null;
|
|
5454
|
+
}
|
|
5455
|
+
const typeRef = type;
|
|
5456
|
+
const typeArgs = typeRef.typeArguments;
|
|
5457
|
+
switch (symbolName) {
|
|
5458
|
+
case "TObject": {
|
|
5459
|
+
if (!typeArgs || typeArgs.length === 0) {
|
|
5460
|
+
return { type: "object" };
|
|
5461
|
+
}
|
|
5462
|
+
const propsType = typeArgs[0];
|
|
5463
|
+
const properties = {};
|
|
5464
|
+
const required = [];
|
|
5465
|
+
for (const prop of propsType.getProperties()) {
|
|
5466
|
+
const propName = prop.getName();
|
|
5467
|
+
if (isInternalProperty(propName)) {
|
|
5468
|
+
continue;
|
|
5469
|
+
}
|
|
5470
|
+
const propType = getPropertyType(prop, propsType, typeChecker);
|
|
5471
|
+
const propSymbol = propType.getSymbol();
|
|
5472
|
+
const propSymbolName = propSymbol?.getName();
|
|
5473
|
+
if (propSymbolName && typeRefs.has(propSymbolName)) {
|
|
5474
|
+
properties[propName] = { $ref: `#/types/${propSymbolName}` };
|
|
5475
|
+
} else if (propSymbolName && isTypeBoxSchemaType(propSymbolName)) {
|
|
5476
|
+
const nested = formatTypeBoxSchema(propType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
|
|
5477
|
+
properties[propName] = nested ?? { type: "object" };
|
|
5478
|
+
} else {
|
|
5479
|
+
properties[propName] = formatTypeReferenceInternal(propType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
|
|
5480
|
+
}
|
|
5481
|
+
const { isOptional } = unwrapTypeBoxOptional(propType);
|
|
5482
|
+
if (propSymbolName !== "TOptional" && !isOptional) {
|
|
5483
|
+
required.push(propName);
|
|
5484
|
+
}
|
|
5485
|
+
}
|
|
5486
|
+
const schema = { type: "object", properties };
|
|
5487
|
+
if (required.length > 0) {
|
|
5488
|
+
schema.required = required;
|
|
5489
|
+
}
|
|
5490
|
+
return schema;
|
|
5491
|
+
}
|
|
5492
|
+
case "TArray": {
|
|
5493
|
+
if (!typeArgs || typeArgs.length === 0) {
|
|
5494
|
+
return { type: "array" };
|
|
5495
|
+
}
|
|
5496
|
+
const itemType = typeArgs[0];
|
|
5497
|
+
const itemSymbol = itemType.getSymbol();
|
|
5498
|
+
const itemSymbolName = itemSymbol?.getName();
|
|
5499
|
+
let items;
|
|
5500
|
+
if (itemSymbolName && typeRefs.has(itemSymbolName)) {
|
|
5501
|
+
items = { $ref: `#/types/${itemSymbolName}` };
|
|
5502
|
+
} else if (itemSymbolName && isTypeBoxSchemaType(itemSymbolName)) {
|
|
5503
|
+
items = formatTypeBoxSchema(itemType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
|
|
5504
|
+
type: "object"
|
|
5505
|
+
};
|
|
5506
|
+
} else {
|
|
5507
|
+
items = formatTypeReferenceInternal(itemType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
|
|
5508
|
+
}
|
|
5509
|
+
return { type: "array", items };
|
|
5510
|
+
}
|
|
5511
|
+
case "TUnion": {
|
|
5512
|
+
if (!typeArgs || typeArgs.length === 0) {
|
|
5513
|
+
return { anyOf: [] };
|
|
5514
|
+
}
|
|
5515
|
+
const tupleType = typeArgs[0];
|
|
5516
|
+
const members = [];
|
|
5517
|
+
if (tupleType.isUnion()) {
|
|
5518
|
+
for (const memberType of tupleType.types) {
|
|
5519
|
+
const memberSymbol = memberType.getSymbol();
|
|
5520
|
+
const memberSymbolName = memberSymbol?.getName();
|
|
5521
|
+
if (memberSymbolName && typeRefs.has(memberSymbolName)) {
|
|
5522
|
+
members.push({ $ref: `#/types/${memberSymbolName}` });
|
|
5523
|
+
} else if (memberSymbolName && isTypeBoxSchemaType(memberSymbolName)) {
|
|
5524
|
+
members.push(formatTypeBoxSchema(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
|
|
5525
|
+
type: "object"
|
|
5526
|
+
});
|
|
5527
|
+
} else {
|
|
5528
|
+
members.push(formatTypeReferenceInternal(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds));
|
|
5529
|
+
}
|
|
5530
|
+
}
|
|
5531
|
+
} else if (tupleType.typeArguments) {
|
|
5532
|
+
for (const memberType of tupleType.typeArguments) {
|
|
5533
|
+
const memberSymbol = memberType.getSymbol();
|
|
5534
|
+
const memberSymbolName = memberSymbol?.getName();
|
|
5535
|
+
if (memberSymbolName && typeRefs.has(memberSymbolName)) {
|
|
5536
|
+
members.push({ $ref: `#/types/${memberSymbolName}` });
|
|
5537
|
+
} else if (memberSymbolName && isTypeBoxSchemaType(memberSymbolName)) {
|
|
5538
|
+
members.push(formatTypeBoxSchema(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
|
|
5539
|
+
type: "object"
|
|
5540
|
+
});
|
|
5541
|
+
} else {
|
|
5542
|
+
members.push(formatTypeReferenceInternal(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds));
|
|
5543
|
+
}
|
|
5544
|
+
}
|
|
5545
|
+
}
|
|
5546
|
+
return { anyOf: members };
|
|
5547
|
+
}
|
|
5548
|
+
case "TIntersect": {
|
|
5549
|
+
if (!typeArgs || typeArgs.length === 0) {
|
|
5550
|
+
return { allOf: [] };
|
|
5551
|
+
}
|
|
5552
|
+
const tupleType = typeArgs[0];
|
|
5553
|
+
const members = [];
|
|
5554
|
+
if (tupleType.typeArguments) {
|
|
5555
|
+
for (const memberType of tupleType.typeArguments) {
|
|
5556
|
+
const memberSymbol = memberType.getSymbol();
|
|
5557
|
+
const memberSymbolName = memberSymbol?.getName();
|
|
5558
|
+
if (memberSymbolName && typeRefs.has(memberSymbolName)) {
|
|
5559
|
+
members.push({ $ref: `#/types/${memberSymbolName}` });
|
|
5560
|
+
} else if (memberSymbolName && isTypeBoxSchemaType(memberSymbolName)) {
|
|
5561
|
+
members.push(formatTypeBoxSchema(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
|
|
5562
|
+
type: "object"
|
|
5563
|
+
});
|
|
5564
|
+
} else {
|
|
5565
|
+
members.push(formatTypeReferenceInternal(memberType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds));
|
|
5566
|
+
}
|
|
5567
|
+
}
|
|
5568
|
+
}
|
|
5569
|
+
return { allOf: members };
|
|
4526
5570
|
}
|
|
4527
|
-
|
|
4528
|
-
|
|
5571
|
+
case "TOptional": {
|
|
5572
|
+
if (!typeArgs || typeArgs.length === 0) {
|
|
5573
|
+
return {};
|
|
5574
|
+
}
|
|
5575
|
+
const innerType = typeArgs[0];
|
|
5576
|
+
const innerSymbol = innerType.getSymbol();
|
|
5577
|
+
const innerSymbolName = innerSymbol?.getName();
|
|
5578
|
+
if (innerSymbolName && typeRefs.has(innerSymbolName)) {
|
|
5579
|
+
return { $ref: `#/types/${innerSymbolName}` };
|
|
5580
|
+
} else if (innerSymbolName && isTypeBoxSchemaType(innerSymbolName)) {
|
|
5581
|
+
return formatTypeBoxSchema(innerType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? {
|
|
5582
|
+
type: "object"
|
|
5583
|
+
};
|
|
5584
|
+
}
|
|
5585
|
+
return formatTypeReferenceInternal(innerType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
|
|
4529
5586
|
}
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
}
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
}
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
return false;
|
|
4545
|
-
}
|
|
4546
|
-
if (typeof left === "string" && typeof right === "string") {
|
|
4547
|
-
return left === right;
|
|
4548
|
-
}
|
|
4549
|
-
if (left == null || right == null) {
|
|
4550
|
-
return left === right;
|
|
4551
|
-
}
|
|
4552
|
-
const normalize = (value) => {
|
|
4553
|
-
if (Array.isArray(value)) {
|
|
4554
|
-
return value.map((item) => normalize(item));
|
|
5587
|
+
case "TLiteral": {
|
|
5588
|
+
if (!typeArgs || typeArgs.length === 0) {
|
|
5589
|
+
return { enum: [] };
|
|
5590
|
+
}
|
|
5591
|
+
const literalType = typeArgs[0];
|
|
5592
|
+
if (literalType.isLiteral()) {
|
|
5593
|
+
const value = literalType.value;
|
|
5594
|
+
return { enum: [value] };
|
|
5595
|
+
}
|
|
5596
|
+
const literalStr = typeChecker.typeToString(literalType);
|
|
5597
|
+
if (literalStr.startsWith('"') && literalStr.endsWith('"')) {
|
|
5598
|
+
return { enum: [literalStr.slice(1, -1)] };
|
|
5599
|
+
}
|
|
5600
|
+
return { enum: [literalStr] };
|
|
4555
5601
|
}
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
5602
|
+
case "TRecord": {
|
|
5603
|
+
if (!typeArgs || typeArgs.length < 2) {
|
|
5604
|
+
return { type: "object", additionalProperties: true };
|
|
5605
|
+
}
|
|
5606
|
+
const valueType = typeArgs[1];
|
|
5607
|
+
const valueSymbol = valueType.getSymbol();
|
|
5608
|
+
const valueSymbolName = valueSymbol?.getName();
|
|
5609
|
+
let additionalProperties;
|
|
5610
|
+
if (valueSymbolName && typeRefs.has(valueSymbolName)) {
|
|
5611
|
+
additionalProperties = { $ref: `#/types/${valueSymbolName}` };
|
|
5612
|
+
} else if (valueSymbolName && isTypeBoxSchemaType(valueSymbolName)) {
|
|
5613
|
+
additionalProperties = formatTypeBoxSchema(valueType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds) ?? true;
|
|
5614
|
+
} else {
|
|
5615
|
+
additionalProperties = formatTypeReferenceInternal(valueType, typeChecker, typeRefs, referencedTypes, visited, depth + 1, maxDepth, typeIds);
|
|
5616
|
+
}
|
|
5617
|
+
return { type: "object", additionalProperties };
|
|
4559
5618
|
}
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
}
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
5619
|
+
case "TRef": {
|
|
5620
|
+
if (!typeArgs || typeArgs.length === 0) {
|
|
5621
|
+
return { $ref: "#/types/unknown" };
|
|
5622
|
+
}
|
|
5623
|
+
const refType = typeArgs[0];
|
|
5624
|
+
const refSymbol = refType.getSymbol();
|
|
5625
|
+
const refSymbolName = refSymbol?.getName();
|
|
5626
|
+
if (refSymbolName) {
|
|
5627
|
+
return { $ref: `#/types/${refSymbolName}` };
|
|
5628
|
+
}
|
|
5629
|
+
return { type: "object" };
|
|
4570
5630
|
}
|
|
5631
|
+
default:
|
|
5632
|
+
return null;
|
|
4571
5633
|
}
|
|
4572
|
-
return result;
|
|
4573
5634
|
}
|
|
4574
5635
|
|
|
4575
5636
|
// src/utils/type-formatter.ts
|
|
@@ -5679,10 +6740,10 @@ function serializeClassMembers(declaration, checker, typeRefs, referencedTypes)
|
|
|
5679
6740
|
if (member.questionToken || isOptionalSymbol) {
|
|
5680
6741
|
flags.optional = true;
|
|
5681
6742
|
}
|
|
5682
|
-
if (member.modifiers?.some((
|
|
6743
|
+
if (member.modifiers?.some((mod2) => mod2.kind === ts.SyntaxKind.ReadonlyKeyword)) {
|
|
5683
6744
|
flags.readonly = true;
|
|
5684
6745
|
}
|
|
5685
|
-
if (member.modifiers?.some((
|
|
6746
|
+
if (member.modifiers?.some((mod2) => mod2.kind === ts.SyntaxKind.StaticKeyword)) {
|
|
5686
6747
|
flags.static = true;
|
|
5687
6748
|
}
|
|
5688
6749
|
members.push({
|
|
@@ -5804,20 +6865,20 @@ function serializeSignature(signature, checker, typeRefs, referencedTypes, doc,
|
|
|
5804
6865
|
function getMemberVisibility(modifiers) {
|
|
5805
6866
|
if (!modifiers)
|
|
5806
6867
|
return;
|
|
5807
|
-
if (modifiers.some((
|
|
6868
|
+
if (modifiers.some((mod2) => mod2.kind === ts.SyntaxKind.PrivateKeyword)) {
|
|
5808
6869
|
return "private";
|
|
5809
6870
|
}
|
|
5810
|
-
if (modifiers.some((
|
|
6871
|
+
if (modifiers.some((mod2) => mod2.kind === ts.SyntaxKind.ProtectedKeyword)) {
|
|
5811
6872
|
return "protected";
|
|
5812
6873
|
}
|
|
5813
|
-
if (modifiers.some((
|
|
6874
|
+
if (modifiers.some((mod2) => mod2.kind === ts.SyntaxKind.PublicKeyword)) {
|
|
5814
6875
|
return "public";
|
|
5815
6876
|
}
|
|
5816
6877
|
return;
|
|
5817
6878
|
}
|
|
5818
6879
|
function getMethodFlags(member) {
|
|
5819
6880
|
const flags = {};
|
|
5820
|
-
if (member.modifiers?.some((
|
|
6881
|
+
if (member.modifiers?.some((mod2) => mod2.kind === ts.SyntaxKind.StaticKeyword)) {
|
|
5821
6882
|
flags.static = true;
|
|
5822
6883
|
}
|
|
5823
6884
|
if (member.asteriskToken) {
|
|
@@ -6002,7 +7063,7 @@ function serializeInterfaceMembers(declaration, checker, typeRefs, referencedTyp
|
|
|
6002
7063
|
if (member.questionToken) {
|
|
6003
7064
|
flags.optional = true;
|
|
6004
7065
|
}
|
|
6005
|
-
if (member.modifiers?.some((
|
|
7066
|
+
if (member.modifiers?.some((mod2) => mod2.kind === ts.SyntaxKind.ReadonlyKeyword)) {
|
|
6006
7067
|
flags.readonly = true;
|
|
6007
7068
|
}
|
|
6008
7069
|
members.push({
|
|
@@ -6300,7 +7361,7 @@ function extractNamespaceMembers(declaration, checker) {
|
|
|
6300
7361
|
return members;
|
|
6301
7362
|
}
|
|
6302
7363
|
for (const statement of body.statements) {
|
|
6303
|
-
const hasExportModifier = ts.canHaveModifiers(statement) && ts.getModifiers(statement)?.some((
|
|
7364
|
+
const hasExportModifier = ts.canHaveModifiers(statement) && ts.getModifiers(statement)?.some((mod2) => mod2.kind === ts.SyntaxKind.ExportKeyword);
|
|
6304
7365
|
if (!hasExportModifier) {
|
|
6305
7366
|
continue;
|
|
6306
7367
|
}
|
|
@@ -6509,6 +7570,25 @@ function serializeVariable(declaration, symbol, context) {
|
|
|
6509
7570
|
}
|
|
6510
7571
|
const typeRefs = typeRegistry.getTypeRefs();
|
|
6511
7572
|
const referencedTypes = typeRegistry.getReferencedTypes();
|
|
7573
|
+
const symbolName = symbol.getName();
|
|
7574
|
+
const detectedSchema = context.detectedSchemas?.get(symbolName);
|
|
7575
|
+
if (detectedSchema) {
|
|
7576
|
+
return {
|
|
7577
|
+
id: symbolName,
|
|
7578
|
+
name: symbolName,
|
|
7579
|
+
...metadata,
|
|
7580
|
+
kind: "variable",
|
|
7581
|
+
deprecated: isSymbolDeprecated(symbol),
|
|
7582
|
+
schema: detectedSchema.schema,
|
|
7583
|
+
description,
|
|
7584
|
+
source: getSourceLocation(declaration),
|
|
7585
|
+
tags: [
|
|
7586
|
+
...parsedDoc?.tags ?? [],
|
|
7587
|
+
{ name: "standardSchema", text: detectedSchema.vendor }
|
|
7588
|
+
],
|
|
7589
|
+
examples: parsedDoc?.examples
|
|
7590
|
+
};
|
|
7591
|
+
}
|
|
6512
7592
|
return {
|
|
6513
7593
|
id: symbol.getName(),
|
|
6514
7594
|
name: symbol.getName(),
|
|
@@ -6597,9 +7677,9 @@ function createDefaultGenerationInfo(entryFile) {
|
|
|
6597
7677
|
}
|
|
6598
7678
|
function buildOpenPkgSpec(context, resolveExternalTypes, generation) {
|
|
6599
7679
|
const { baseDir, checker: typeChecker, sourceFile, program, entryFile } = context;
|
|
6600
|
-
const packageJsonPath =
|
|
6601
|
-
const packageJson =
|
|
6602
|
-
const generationInfo = generation ?? createDefaultGenerationInfo(
|
|
7680
|
+
const packageJsonPath = path12.join(baseDir, "package.json");
|
|
7681
|
+
const packageJson = fs11.existsSync(packageJsonPath) ? JSON.parse(fs11.readFileSync(packageJsonPath, "utf-8")) : {};
|
|
7682
|
+
const generationInfo = generation ?? createDefaultGenerationInfo(path12.relative(baseDir, entryFile));
|
|
6603
7683
|
const spec = {
|
|
6604
7684
|
$schema: SCHEMA_URL,
|
|
6605
7685
|
openpkg: SCHEMA_VERSION,
|
|
@@ -6619,7 +7699,8 @@ function buildOpenPkgSpec(context, resolveExternalTypes, generation) {
|
|
|
6619
7699
|
const serializerContext = {
|
|
6620
7700
|
checker: typeChecker,
|
|
6621
7701
|
typeRegistry,
|
|
6622
|
-
maxTypeDepth: context.options.maxDepth
|
|
7702
|
+
maxTypeDepth: context.options.maxDepth,
|
|
7703
|
+
detectedSchemas: context.detectedSchemas
|
|
6623
7704
|
};
|
|
6624
7705
|
const moduleSymbol = typeChecker.getSymbolAtLocation(sourceFile);
|
|
6625
7706
|
if (!moduleSymbol) {
|
|
@@ -6797,11 +7878,11 @@ function deriveImportPath(sourceFile, baseDir) {
|
|
|
6797
7878
|
if (!sourceFile) {
|
|
6798
7879
|
return;
|
|
6799
7880
|
}
|
|
6800
|
-
const
|
|
6801
|
-
if (!
|
|
7881
|
+
const relative6 = path12.relative(baseDir, sourceFile);
|
|
7882
|
+
if (!relative6 || relative6.startsWith("..")) {
|
|
6802
7883
|
return;
|
|
6803
7884
|
}
|
|
6804
|
-
const normalized =
|
|
7885
|
+
const normalized = relative6.replace(/\\/g, "/");
|
|
6805
7886
|
const withoutExt = stripExtensions(normalized);
|
|
6806
7887
|
if (!withoutExt) {
|
|
6807
7888
|
return;
|
|
@@ -6840,11 +7921,11 @@ function withExportName(entry, exportName) {
|
|
|
6840
7921
|
function findNearestPackageJson(startDir) {
|
|
6841
7922
|
let current = startDir;
|
|
6842
7923
|
while (true) {
|
|
6843
|
-
const candidate =
|
|
6844
|
-
if (
|
|
7924
|
+
const candidate = path13.join(current, "package.json");
|
|
7925
|
+
if (fs12.existsSync(candidate)) {
|
|
6845
7926
|
return candidate;
|
|
6846
7927
|
}
|
|
6847
|
-
const parent =
|
|
7928
|
+
const parent = path13.dirname(current);
|
|
6848
7929
|
if (parent === current) {
|
|
6849
7930
|
return;
|
|
6850
7931
|
}
|
|
@@ -6872,11 +7953,11 @@ function canResolveExternalModules(program, baseDir) {
|
|
|
6872
7953
|
function hasNodeModulesDirectoryFallback(startDir) {
|
|
6873
7954
|
let current = startDir;
|
|
6874
7955
|
while (true) {
|
|
6875
|
-
const candidate =
|
|
6876
|
-
if (
|
|
7956
|
+
const candidate = path13.join(current, "node_modules");
|
|
7957
|
+
if (fs12.existsSync(candidate)) {
|
|
6877
7958
|
return true;
|
|
6878
7959
|
}
|
|
6879
|
-
const parent =
|
|
7960
|
+
const parent = path13.dirname(current);
|
|
6880
7961
|
if (parent === current) {
|
|
6881
7962
|
break;
|
|
6882
7963
|
}
|
|
@@ -6921,8 +8002,8 @@ function hasExternalImports(sourceFile) {
|
|
|
6921
8002
|
if (ts.isImportDeclaration(node) && node.moduleSpecifier) {
|
|
6922
8003
|
const specifier = node.moduleSpecifier;
|
|
6923
8004
|
if (ts.isStringLiteral(specifier)) {
|
|
6924
|
-
const
|
|
6925
|
-
if (!
|
|
8005
|
+
const modulePath2 = specifier.text;
|
|
8006
|
+
if (!modulePath2.startsWith(".") && !modulePath2.startsWith("/")) {
|
|
6926
8007
|
found = true;
|
|
6927
8008
|
}
|
|
6928
8009
|
}
|
|
@@ -6977,6 +8058,17 @@ function runAnalysis(input, generationInput) {
|
|
|
6977
8058
|
issues: generationIssues
|
|
6978
8059
|
} : undefined;
|
|
6979
8060
|
const spec = buildOpenPkgSpec(context, resolveExternalTypes, generation);
|
|
8061
|
+
if (input.detectedSchemas && input.detectedSchemas.size > 0 && generation) {
|
|
8062
|
+
const vendors = new Set;
|
|
8063
|
+
for (const entry of input.detectedSchemas.values()) {
|
|
8064
|
+
vendors.add(entry.vendor);
|
|
8065
|
+
}
|
|
8066
|
+
generation.analysis.schemaExtraction = {
|
|
8067
|
+
method: "hybrid",
|
|
8068
|
+
runtimeCount: input.detectedSchemas.size,
|
|
8069
|
+
vendors: Array.from(vendors)
|
|
8070
|
+
};
|
|
8071
|
+
}
|
|
6980
8072
|
const danglingRefs = collectDanglingRefs(spec);
|
|
6981
8073
|
for (const ref of danglingRefs) {
|
|
6982
8074
|
const issue = {
|
|
@@ -7118,16 +8210,33 @@ async function fetchSpecFromGitHub(parsed) {
|
|
|
7118
8210
|
}
|
|
7119
8211
|
return null;
|
|
7120
8212
|
}
|
|
7121
|
-
async function fetchSpec(owner, repo,
|
|
7122
|
-
|
|
8213
|
+
async function fetchSpec(owner, repo, branchOrOptions = "main") {
|
|
8214
|
+
const options = typeof branchOrOptions === "string" ? { ref: branchOrOptions, path: "openpkg.json" } : { ref: branchOrOptions.ref ?? "main", path: branchOrOptions.path ?? "openpkg.json" };
|
|
8215
|
+
const parsed = { owner, repo, ref: options.ref };
|
|
8216
|
+
return fetchSpecFromGitHubWithPath(parsed, options.path);
|
|
8217
|
+
}
|
|
8218
|
+
async function fetchSpecFromGitHubWithPath(parsed, specPath = "openpkg.json") {
|
|
8219
|
+
const urls = [
|
|
8220
|
+
buildRawUrl(parsed, specPath),
|
|
8221
|
+
`https://raw.githubusercontent.com/${parsed.owner}/${parsed.repo}/master/${specPath}`
|
|
8222
|
+
];
|
|
8223
|
+
for (const url of urls) {
|
|
8224
|
+
try {
|
|
8225
|
+
const response = await fetch(url);
|
|
8226
|
+
if (response.ok) {
|
|
8227
|
+
return await response.json();
|
|
8228
|
+
}
|
|
8229
|
+
} catch {}
|
|
8230
|
+
}
|
|
8231
|
+
return null;
|
|
7123
8232
|
}
|
|
7124
8233
|
// src/install/index.ts
|
|
7125
8234
|
var DEFAULT_FALLBACK_ORDER = ["bun", "npm"];
|
|
7126
|
-
async function installDependencies(
|
|
8235
|
+
async function installDependencies(fs13, cwd, runCommand2, options = {}) {
|
|
7127
8236
|
const { timeout = 180000, fallbackOrder = DEFAULT_FALLBACK_ORDER, onProgress } = options;
|
|
7128
8237
|
const errors = [];
|
|
7129
8238
|
onProgress?.({ stage: "installing", message: "Detecting package manager..." });
|
|
7130
|
-
const pmInfo = await detectPackageManager(
|
|
8239
|
+
const pmInfo = await detectPackageManager(fs13);
|
|
7131
8240
|
if (pmInfo.lockfile) {
|
|
7132
8241
|
onProgress?.({
|
|
7133
8242
|
stage: "installing",
|
|
@@ -7202,10 +8311,10 @@ function getFallbackInstallCommand(pm) {
|
|
|
7202
8311
|
}
|
|
7203
8312
|
function createNodeCommandRunner() {
|
|
7204
8313
|
return async (cmd, args, options) => {
|
|
7205
|
-
const { execSync } = await import("node:child_process");
|
|
8314
|
+
const { execSync: execSync2 } = await import("node:child_process");
|
|
7206
8315
|
const fullCmd = [cmd, ...args].join(" ");
|
|
7207
8316
|
try {
|
|
7208
|
-
const stdout =
|
|
8317
|
+
const stdout = execSync2(fullCmd, {
|
|
7209
8318
|
cwd: options.cwd,
|
|
7210
8319
|
stdio: "pipe",
|
|
7211
8320
|
timeout: options.timeout ?? 180000
|
|
@@ -7782,7 +8891,7 @@ function diffMemberChanges(oldSpec, newSpec, changedClassNames) {
|
|
|
7782
8891
|
memberName,
|
|
7783
8892
|
memberKind: getMemberKind(newMember),
|
|
7784
8893
|
changeType: "added",
|
|
7785
|
-
newSignature:
|
|
8894
|
+
newSignature: formatSignature2(newMember)
|
|
7786
8895
|
});
|
|
7787
8896
|
}
|
|
7788
8897
|
}
|
|
@@ -7794,7 +8903,7 @@ function diffMemberChanges(oldSpec, newSpec, changedClassNames) {
|
|
|
7794
8903
|
memberName,
|
|
7795
8904
|
memberKind: getMemberKind(oldMember),
|
|
7796
8905
|
changeType: "removed",
|
|
7797
|
-
oldSignature:
|
|
8906
|
+
oldSignature: formatSignature2(oldMember),
|
|
7798
8907
|
suggestion
|
|
7799
8908
|
});
|
|
7800
8909
|
}
|
|
@@ -7807,8 +8916,8 @@ function diffMemberChanges(oldSpec, newSpec, changedClassNames) {
|
|
|
7807
8916
|
memberName,
|
|
7808
8917
|
memberKind: getMemberKind(newMember),
|
|
7809
8918
|
changeType: "signature-changed",
|
|
7810
|
-
oldSignature:
|
|
7811
|
-
newSignature:
|
|
8919
|
+
oldSignature: formatSignature2(oldMember),
|
|
8920
|
+
newSignature: formatSignature2(newMember)
|
|
7812
8921
|
});
|
|
7813
8922
|
}
|
|
7814
8923
|
}
|
|
@@ -7857,7 +8966,7 @@ function getMemberKind(member) {
|
|
|
7857
8966
|
return "method";
|
|
7858
8967
|
}
|
|
7859
8968
|
}
|
|
7860
|
-
function
|
|
8969
|
+
function formatSignature2(member) {
|
|
7861
8970
|
if (!member.signatures?.length) {
|
|
7862
8971
|
return member.name ?? "";
|
|
7863
8972
|
}
|
|
@@ -8032,11 +9141,34 @@ function getDocsImpactSummary(diff) {
|
|
|
8032
9141
|
}
|
|
8033
9142
|
// src/openpkg.ts
|
|
8034
9143
|
import * as fsSync from "node:fs";
|
|
8035
|
-
import * as
|
|
8036
|
-
import * as
|
|
9144
|
+
import * as fs13 from "node:fs/promises";
|
|
9145
|
+
import * as path14 from "node:path";
|
|
8037
9146
|
|
|
8038
9147
|
// src/filtering/apply-filters.ts
|
|
8039
9148
|
var TYPE_REF_PREFIX = "#/types/";
|
|
9149
|
+
var getExportReleaseTag = (exp) => {
|
|
9150
|
+
const tags = exp.tags ?? [];
|
|
9151
|
+
for (const tag of tags) {
|
|
9152
|
+
const tagName = tag.name?.toLowerCase();
|
|
9153
|
+
if (tagName === "public")
|
|
9154
|
+
return "public";
|
|
9155
|
+
if (tagName === "beta")
|
|
9156
|
+
return "beta";
|
|
9157
|
+
if (tagName === "alpha")
|
|
9158
|
+
return "alpha";
|
|
9159
|
+
if (tagName === "internal")
|
|
9160
|
+
return "internal";
|
|
9161
|
+
}
|
|
9162
|
+
return;
|
|
9163
|
+
};
|
|
9164
|
+
var matchesVisibility = (exp, visibility) => {
|
|
9165
|
+
if (!visibility || visibility.length === 0) {
|
|
9166
|
+
return true;
|
|
9167
|
+
}
|
|
9168
|
+
const tag = getExportReleaseTag(exp);
|
|
9169
|
+
const effectiveTag = tag ?? "public";
|
|
9170
|
+
return visibility.includes(effectiveTag);
|
|
9171
|
+
};
|
|
8040
9172
|
var toLowerKey = (value) => value.trim().toLowerCase();
|
|
8041
9173
|
var buildLookupMap = (values) => {
|
|
8042
9174
|
const map = new Map;
|
|
@@ -8098,7 +9230,8 @@ var collectTypeRefs = (value, refs, seen = new Set) => {
|
|
|
8098
9230
|
var applyFilters = (spec, options) => {
|
|
8099
9231
|
const includeLookup = buildLookupMap(options.include);
|
|
8100
9232
|
const excludeLookup = buildLookupMap(options.exclude);
|
|
8101
|
-
|
|
9233
|
+
const visibility = options.visibility;
|
|
9234
|
+
if (includeLookup.size === 0 && excludeLookup.size === 0 && (!visibility || visibility.length === 0)) {
|
|
8102
9235
|
return { spec, diagnostics: [], changed: false };
|
|
8103
9236
|
}
|
|
8104
9237
|
const includeMatches = new Set;
|
|
@@ -8111,10 +9244,11 @@ var applyFilters = (spec, options) => {
|
|
|
8111
9244
|
const excludeMatch = matches(entry, excludeLookup);
|
|
8112
9245
|
const allowedByInclude = includeLookup.size === 0 || Boolean(includeMatch);
|
|
8113
9246
|
const allowedByExclude = !excludeMatch;
|
|
9247
|
+
const allowedByVisibility = matchesVisibility(entry, visibility);
|
|
8114
9248
|
if (includeMatch) {
|
|
8115
9249
|
includeMatches.add(includeMatch);
|
|
8116
9250
|
}
|
|
8117
|
-
if (allowedByInclude && allowedByExclude) {
|
|
9251
|
+
if (allowedByInclude && allowedByExclude && allowedByVisibility) {
|
|
8118
9252
|
keptExports.push(entry);
|
|
8119
9253
|
}
|
|
8120
9254
|
}
|
|
@@ -8216,14 +9350,14 @@ class DocCov {
|
|
|
8216
9350
|
this.options = normalizeDocCovOptions(options);
|
|
8217
9351
|
}
|
|
8218
9352
|
async analyze(code, fileName = "temp.ts", analyzeOptions = {}) {
|
|
8219
|
-
const resolvedFileName =
|
|
8220
|
-
const tempDir =
|
|
9353
|
+
const resolvedFileName = path14.resolve(fileName);
|
|
9354
|
+
const tempDir = path14.dirname(resolvedFileName);
|
|
8221
9355
|
const spec = await extractPackageSpec(resolvedFileName, tempDir, code, this.options);
|
|
8222
9356
|
return this.applySpecFilters(spec, analyzeOptions.filters).spec;
|
|
8223
9357
|
}
|
|
8224
9358
|
async analyzeFile(filePath, analyzeOptions = {}) {
|
|
8225
|
-
const resolvedPath =
|
|
8226
|
-
const content = await
|
|
9359
|
+
const resolvedPath = path14.resolve(filePath);
|
|
9360
|
+
const content = await fs13.readFile(resolvedPath, "utf-8");
|
|
8227
9361
|
const packageDir = resolvePackageDir(resolvedPath);
|
|
8228
9362
|
const spec = await extractPackageSpec(resolvedPath, packageDir, content, this.options);
|
|
8229
9363
|
return this.applySpecFilters(spec, analyzeOptions.filters).spec;
|
|
@@ -8232,13 +9366,16 @@ class DocCov {
|
|
|
8232
9366
|
return this.analyzeFile(entryPath, analyzeOptions);
|
|
8233
9367
|
}
|
|
8234
9368
|
async analyzeWithDiagnostics(code, fileName, analyzeOptions = {}) {
|
|
8235
|
-
const resolvedFileName =
|
|
9369
|
+
const resolvedFileName = path14.resolve(fileName ?? "temp.ts");
|
|
8236
9370
|
const packageDir = resolvePackageDir(resolvedFileName);
|
|
9371
|
+
const isRealFile = fileName && !fileName.includes("temp.ts");
|
|
9372
|
+
const detectedSchemas = isRealFile ? await this.detectSchemas(resolvedFileName, packageDir) : undefined;
|
|
8237
9373
|
const analysis = runAnalysis({
|
|
8238
9374
|
entryFile: resolvedFileName,
|
|
8239
9375
|
packageDir,
|
|
8240
9376
|
content: code,
|
|
8241
|
-
options: this.options
|
|
9377
|
+
options: this.options,
|
|
9378
|
+
detectedSchemas
|
|
8242
9379
|
}, analyzeOptions.generationInput);
|
|
8243
9380
|
const filterOutcome = this.applySpecFilters(analysis.spec, analyzeOptions.filters);
|
|
8244
9381
|
return {
|
|
@@ -8252,7 +9389,7 @@ class DocCov {
|
|
|
8252
9389
|
};
|
|
8253
9390
|
}
|
|
8254
9391
|
async analyzeFileWithDiagnostics(filePath, analyzeOptions = {}) {
|
|
8255
|
-
const resolvedPath =
|
|
9392
|
+
const resolvedPath = path14.resolve(filePath);
|
|
8256
9393
|
const packageDir = resolvePackageDir(resolvedPath);
|
|
8257
9394
|
const { useCache, resolveExternalTypes } = this.options;
|
|
8258
9395
|
if (useCache) {
|
|
@@ -8268,12 +9405,14 @@ class DocCov {
|
|
|
8268
9405
|
};
|
|
8269
9406
|
}
|
|
8270
9407
|
}
|
|
8271
|
-
const content = await
|
|
9408
|
+
const content = await fs13.readFile(resolvedPath, "utf-8");
|
|
9409
|
+
const detectedSchemas = await this.detectSchemas(resolvedPath, packageDir);
|
|
8272
9410
|
const analysis = runAnalysis({
|
|
8273
9411
|
entryFile: resolvedPath,
|
|
8274
9412
|
packageDir,
|
|
8275
9413
|
content,
|
|
8276
|
-
options: this.options
|
|
9414
|
+
options: this.options,
|
|
9415
|
+
detectedSchemas
|
|
8277
9416
|
}, analyzeOptions.generationInput);
|
|
8278
9417
|
const filterOutcome = this.applySpecFilters(analysis.spec, analyzeOptions.filters);
|
|
8279
9418
|
const metadata = this.normalizeMetadata(analysis.metadata);
|
|
@@ -8303,10 +9442,10 @@ class DocCov {
|
|
|
8303
9442
|
if (!packageJsonPath) {
|
|
8304
9443
|
return null;
|
|
8305
9444
|
}
|
|
8306
|
-
const
|
|
9445
|
+
const currentSourceFiles = this.getCurrentSourceFiles(entryFile, packageDir);
|
|
8307
9446
|
const cacheContext = {
|
|
8308
9447
|
entryFile,
|
|
8309
|
-
sourceFiles,
|
|
9448
|
+
sourceFiles: currentSourceFiles,
|
|
8310
9449
|
tsconfigPath,
|
|
8311
9450
|
packageJsonPath,
|
|
8312
9451
|
config: {
|
|
@@ -8318,6 +9457,7 @@ class DocCov {
|
|
|
8318
9457
|
if (!validation.valid) {
|
|
8319
9458
|
return null;
|
|
8320
9459
|
}
|
|
9460
|
+
const cachedSourceFiles = Object.keys(cache.hashes.sourceFiles).map((relativePath) => path14.resolve(cwd, relativePath));
|
|
8321
9461
|
return {
|
|
8322
9462
|
spec: cache.spec,
|
|
8323
9463
|
metadata: {
|
|
@@ -8326,10 +9466,18 @@ class DocCov {
|
|
|
8326
9466
|
packageJsonPath,
|
|
8327
9467
|
hasNodeModules: true,
|
|
8328
9468
|
resolveExternalTypes: cache.config.resolveExternalTypes,
|
|
8329
|
-
sourceFiles
|
|
9469
|
+
sourceFiles: cachedSourceFiles
|
|
8330
9470
|
}
|
|
8331
9471
|
};
|
|
8332
9472
|
}
|
|
9473
|
+
getCurrentSourceFiles(entryFile, baseDir) {
|
|
9474
|
+
try {
|
|
9475
|
+
const { program } = createProgram({ entryFile, baseDir });
|
|
9476
|
+
return program.getSourceFiles().filter((sf) => !sf.isDeclarationFile && sf.fileName.startsWith(baseDir)).map((sf) => sf.fileName);
|
|
9477
|
+
} catch {
|
|
9478
|
+
return [];
|
|
9479
|
+
}
|
|
9480
|
+
}
|
|
8333
9481
|
saveToCache(result, entryFile, metadata) {
|
|
8334
9482
|
const { cwd } = this.options;
|
|
8335
9483
|
if (!metadata.packageJsonPath) {
|
|
@@ -8352,11 +9500,11 @@ class DocCov {
|
|
|
8352
9500
|
findTsConfig(startDir) {
|
|
8353
9501
|
let current = startDir;
|
|
8354
9502
|
while (true) {
|
|
8355
|
-
const candidate =
|
|
9503
|
+
const candidate = path14.join(current, "tsconfig.json");
|
|
8356
9504
|
if (fsSync.existsSync(candidate)) {
|
|
8357
9505
|
return candidate;
|
|
8358
9506
|
}
|
|
8359
|
-
const parent =
|
|
9507
|
+
const parent = path14.dirname(current);
|
|
8360
9508
|
if (parent === current) {
|
|
8361
9509
|
return null;
|
|
8362
9510
|
}
|
|
@@ -8366,17 +9514,38 @@ class DocCov {
|
|
|
8366
9514
|
findPackageJson(startDir) {
|
|
8367
9515
|
let current = startDir;
|
|
8368
9516
|
while (true) {
|
|
8369
|
-
const candidate =
|
|
9517
|
+
const candidate = path14.join(current, "package.json");
|
|
8370
9518
|
if (fsSync.existsSync(candidate)) {
|
|
8371
9519
|
return candidate;
|
|
8372
9520
|
}
|
|
8373
|
-
const parent =
|
|
9521
|
+
const parent = path14.dirname(current);
|
|
8374
9522
|
if (parent === current) {
|
|
8375
9523
|
return null;
|
|
8376
9524
|
}
|
|
8377
9525
|
current = parent;
|
|
8378
9526
|
}
|
|
8379
9527
|
}
|
|
9528
|
+
async detectSchemas(entryFile, packageDir) {
|
|
9529
|
+
try {
|
|
9530
|
+
const result = await detectRuntimeSchemas({
|
|
9531
|
+
baseDir: packageDir,
|
|
9532
|
+
entryFile
|
|
9533
|
+
});
|
|
9534
|
+
if (result.schemas.size === 0) {
|
|
9535
|
+
return;
|
|
9536
|
+
}
|
|
9537
|
+
const detected = new Map;
|
|
9538
|
+
for (const [name, schema] of result.schemas) {
|
|
9539
|
+
detected.set(name, {
|
|
9540
|
+
schema: schema.schema,
|
|
9541
|
+
vendor: schema.vendor
|
|
9542
|
+
});
|
|
9543
|
+
}
|
|
9544
|
+
return detected;
|
|
9545
|
+
} catch {
|
|
9546
|
+
return;
|
|
9547
|
+
}
|
|
9548
|
+
}
|
|
8380
9549
|
normalizeDiagnostic(tsDiagnostic) {
|
|
8381
9550
|
const message = ts.flattenDiagnosticMessageText(tsDiagnostic.messageText, `
|
|
8382
9551
|
`);
|
|
@@ -8438,27 +9607,127 @@ async function analyzeFile(filePath, options = {}) {
|
|
|
8438
9607
|
return new DocCov().analyzeFile(filePath, options);
|
|
8439
9608
|
}
|
|
8440
9609
|
function resolvePackageDir(entryFile) {
|
|
8441
|
-
const fallbackDir =
|
|
9610
|
+
const fallbackDir = path14.dirname(entryFile);
|
|
8442
9611
|
let currentDir = fallbackDir;
|
|
8443
9612
|
while (true) {
|
|
8444
|
-
const candidate =
|
|
9613
|
+
const candidate = path14.join(currentDir, "package.json");
|
|
8445
9614
|
if (fsSync.existsSync(candidate)) {
|
|
8446
9615
|
return currentDir;
|
|
8447
9616
|
}
|
|
8448
|
-
const parentDir =
|
|
9617
|
+
const parentDir = path14.dirname(currentDir);
|
|
8449
9618
|
if (parentDir === currentDir) {
|
|
8450
9619
|
return fallbackDir;
|
|
8451
9620
|
}
|
|
8452
9621
|
currentDir = parentDir;
|
|
8453
9622
|
}
|
|
8454
9623
|
}
|
|
9624
|
+
// src/policies/index.ts
|
|
9625
|
+
import * as path15 from "node:path";
|
|
9626
|
+
import { minimatch as minimatch2 } from "minimatch";
|
|
9627
|
+
function matchesPolicy(exp, policy, baseDir) {
|
|
9628
|
+
const filePath = exp.source?.file;
|
|
9629
|
+
if (!filePath)
|
|
9630
|
+
return false;
|
|
9631
|
+
let normalizedPath = filePath;
|
|
9632
|
+
if (baseDir && path15.isAbsolute(filePath)) {
|
|
9633
|
+
normalizedPath = path15.relative(baseDir, filePath);
|
|
9634
|
+
}
|
|
9635
|
+
normalizedPath = normalizedPath.replace(/^\.\//, "");
|
|
9636
|
+
return minimatch2(normalizedPath, policy.path, { matchBase: true });
|
|
9637
|
+
}
|
|
9638
|
+
function calculateCoverage(exports) {
|
|
9639
|
+
if (exports.length === 0)
|
|
9640
|
+
return 100;
|
|
9641
|
+
let documented = 0;
|
|
9642
|
+
for (const exp of exports) {
|
|
9643
|
+
if (exp.description) {
|
|
9644
|
+
documented++;
|
|
9645
|
+
}
|
|
9646
|
+
}
|
|
9647
|
+
return Math.round(documented / exports.length * 100);
|
|
9648
|
+
}
|
|
9649
|
+
function calculateDrift(exports) {
|
|
9650
|
+
if (exports.length === 0)
|
|
9651
|
+
return 0;
|
|
9652
|
+
let withDrift = 0;
|
|
9653
|
+
for (const exp of exports) {
|
|
9654
|
+
if (exp.docs?.drift && exp.docs.drift.length > 0) {
|
|
9655
|
+
withDrift++;
|
|
9656
|
+
}
|
|
9657
|
+
}
|
|
9658
|
+
return Math.round(withDrift / exports.length * 100);
|
|
9659
|
+
}
|
|
9660
|
+
function countMissingExamples(exports) {
|
|
9661
|
+
let missing = 0;
|
|
9662
|
+
for (const exp of exports) {
|
|
9663
|
+
const hasExample = exp.examples && exp.examples.length > 0;
|
|
9664
|
+
if (!hasExample) {
|
|
9665
|
+
missing++;
|
|
9666
|
+
}
|
|
9667
|
+
}
|
|
9668
|
+
return missing;
|
|
9669
|
+
}
|
|
9670
|
+
function evaluatePolicy(policy, allExports, baseDir) {
|
|
9671
|
+
const matchedExports = allExports.filter((exp) => matchesPolicy(exp, policy, baseDir));
|
|
9672
|
+
const coverageScore = calculateCoverage(matchedExports);
|
|
9673
|
+
const driftScore = calculateDrift(matchedExports);
|
|
9674
|
+
const missingExamples = countMissingExamples(matchedExports);
|
|
9675
|
+
const failures = [];
|
|
9676
|
+
if (policy.minCoverage !== undefined && coverageScore < policy.minCoverage) {
|
|
9677
|
+
failures.push({
|
|
9678
|
+
type: "coverage",
|
|
9679
|
+
message: `Coverage ${coverageScore}% below minimum ${policy.minCoverage}%`,
|
|
9680
|
+
actual: coverageScore,
|
|
9681
|
+
threshold: policy.minCoverage
|
|
9682
|
+
});
|
|
9683
|
+
}
|
|
9684
|
+
if (policy.maxDrift !== undefined && driftScore > policy.maxDrift) {
|
|
9685
|
+
failures.push({
|
|
9686
|
+
type: "drift",
|
|
9687
|
+
message: `Drift ${driftScore}% exceeds maximum ${policy.maxDrift}%`,
|
|
9688
|
+
actual: driftScore,
|
|
9689
|
+
threshold: policy.maxDrift
|
|
9690
|
+
});
|
|
9691
|
+
}
|
|
9692
|
+
if (policy.requireExamples && missingExamples > 0) {
|
|
9693
|
+
failures.push({
|
|
9694
|
+
type: "examples",
|
|
9695
|
+
message: `${missingExamples} exports missing @example`,
|
|
9696
|
+
actual: missingExamples,
|
|
9697
|
+
threshold: 0
|
|
9698
|
+
});
|
|
9699
|
+
}
|
|
9700
|
+
return {
|
|
9701
|
+
policy,
|
|
9702
|
+
matchedExports,
|
|
9703
|
+
coverageScore,
|
|
9704
|
+
driftScore,
|
|
9705
|
+
missingExamples,
|
|
9706
|
+
passed: failures.length === 0,
|
|
9707
|
+
failures
|
|
9708
|
+
};
|
|
9709
|
+
}
|
|
9710
|
+
function evaluatePolicies(policies, spec, options = {}) {
|
|
9711
|
+
const { baseDir } = options;
|
|
9712
|
+
const exports = spec.exports ?? [];
|
|
9713
|
+
const results = policies.map((policy) => evaluatePolicy(policy, exports, baseDir));
|
|
9714
|
+
const passedCount = results.filter((r) => r.passed).length;
|
|
9715
|
+
const failedCount = results.length - passedCount;
|
|
9716
|
+
return {
|
|
9717
|
+
results,
|
|
9718
|
+
allPassed: failedCount === 0,
|
|
9719
|
+
totalPolicies: policies.length,
|
|
9720
|
+
passedCount,
|
|
9721
|
+
failedCount
|
|
9722
|
+
};
|
|
9723
|
+
}
|
|
8455
9724
|
// src/resolve/index.ts
|
|
8456
|
-
import * as
|
|
8457
|
-
async function resolveTarget(
|
|
9725
|
+
import * as path16 from "node:path";
|
|
9726
|
+
async function resolveTarget(fs14, options) {
|
|
8458
9727
|
let targetDir = options.cwd;
|
|
8459
9728
|
let packageInfo;
|
|
8460
9729
|
if (options.package) {
|
|
8461
|
-
const mono = await detectMonorepo(
|
|
9730
|
+
const mono = await detectMonorepo(fs14);
|
|
8462
9731
|
if (!mono.isMonorepo) {
|
|
8463
9732
|
throw new Error("Not a monorepo. Remove --package flag for single-package repos.");
|
|
8464
9733
|
}
|
|
@@ -8467,21 +9736,21 @@ async function resolveTarget(fs11, options) {
|
|
|
8467
9736
|
const available = mono.packages.map((p) => p.name).join(", ");
|
|
8468
9737
|
throw new Error(`Package "${options.package}" not found. Available: ${available}`);
|
|
8469
9738
|
}
|
|
8470
|
-
targetDir =
|
|
9739
|
+
targetDir = path16.join(options.cwd, pkg.path);
|
|
8471
9740
|
packageInfo = pkg;
|
|
8472
9741
|
}
|
|
8473
9742
|
let entryFile;
|
|
8474
9743
|
let entryPointInfo;
|
|
8475
9744
|
if (!options.entry) {
|
|
8476
|
-
entryPointInfo = await detectEntryPoint(
|
|
8477
|
-
entryFile =
|
|
9745
|
+
entryPointInfo = await detectEntryPoint(fs14, getRelativePath(options.cwd, targetDir));
|
|
9746
|
+
entryFile = path16.join(targetDir, entryPointInfo.path);
|
|
8478
9747
|
} else {
|
|
8479
|
-
const explicitPath =
|
|
8480
|
-
const isDirectory = await isDir(
|
|
9748
|
+
const explicitPath = path16.resolve(targetDir, options.entry);
|
|
9749
|
+
const isDirectory = await isDir(fs14, getRelativePath(options.cwd, explicitPath));
|
|
8481
9750
|
if (isDirectory) {
|
|
8482
9751
|
targetDir = explicitPath;
|
|
8483
|
-
entryPointInfo = await detectEntryPoint(
|
|
8484
|
-
entryFile =
|
|
9752
|
+
entryPointInfo = await detectEntryPoint(fs14, getRelativePath(options.cwd, explicitPath));
|
|
9753
|
+
entryFile = path16.join(explicitPath, entryPointInfo.path);
|
|
8485
9754
|
} else {
|
|
8486
9755
|
entryFile = explicitPath;
|
|
8487
9756
|
entryPointInfo = {
|
|
@@ -8501,52 +9770,20 @@ async function resolveTarget(fs11, options) {
|
|
|
8501
9770
|
function getRelativePath(base, target) {
|
|
8502
9771
|
if (base === target)
|
|
8503
9772
|
return ".";
|
|
8504
|
-
const rel =
|
|
9773
|
+
const rel = path16.relative(base, target);
|
|
8505
9774
|
return rel || ".";
|
|
8506
9775
|
}
|
|
8507
|
-
async function isDir(
|
|
8508
|
-
const hasPackageJson = await
|
|
9776
|
+
async function isDir(fs14, relativePath) {
|
|
9777
|
+
const hasPackageJson = await fs14.exists(path16.join(relativePath, "package.json"));
|
|
8509
9778
|
if (hasPackageJson)
|
|
8510
9779
|
return true;
|
|
8511
9780
|
const commonEntryFiles = ["index.ts", "index.tsx", "src/index.ts", "main.ts"];
|
|
8512
9781
|
for (const entry of commonEntryFiles) {
|
|
8513
|
-
if (await
|
|
9782
|
+
if (await fs14.exists(path16.join(relativePath, entry))) {
|
|
8514
9783
|
return true;
|
|
8515
9784
|
}
|
|
8516
9785
|
}
|
|
8517
|
-
return !
|
|
8518
|
-
}
|
|
8519
|
-
// src/scan/summary.ts
|
|
8520
|
-
function extractSpecSummary(spec) {
|
|
8521
|
-
const exports = spec.exports ?? [];
|
|
8522
|
-
const undocumented = [];
|
|
8523
|
-
const drift = [];
|
|
8524
|
-
for (const exp of exports) {
|
|
8525
|
-
const docs = exp.docs;
|
|
8526
|
-
if (!docs)
|
|
8527
|
-
continue;
|
|
8528
|
-
const hasMissing = (docs.missing?.length ?? 0) > 0;
|
|
8529
|
-
const isPartial = (docs.coverageScore ?? 0) < 100;
|
|
8530
|
-
if (hasMissing || isPartial) {
|
|
8531
|
-
undocumented.push(exp.name);
|
|
8532
|
-
}
|
|
8533
|
-
for (const d of docs.drift ?? []) {
|
|
8534
|
-
drift.push({
|
|
8535
|
-
export: exp.name,
|
|
8536
|
-
type: d.type,
|
|
8537
|
-
issue: d.issue,
|
|
8538
|
-
suggestion: d.suggestion
|
|
8539
|
-
});
|
|
8540
|
-
}
|
|
8541
|
-
}
|
|
8542
|
-
return {
|
|
8543
|
-
coverage: spec.docs?.coverageScore ?? 0,
|
|
8544
|
-
exportCount: exports.length,
|
|
8545
|
-
typeCount: spec.types?.length ?? 0,
|
|
8546
|
-
driftCount: drift.length,
|
|
8547
|
-
undocumented,
|
|
8548
|
-
drift
|
|
8549
|
-
};
|
|
9786
|
+
return !path16.extname(relativePath);
|
|
8550
9787
|
}
|
|
8551
9788
|
// src/scan/github-context.ts
|
|
8552
9789
|
function parseGitHubUrl2(url) {
|
|
@@ -8557,10 +9794,14 @@ function parseGitHubUrl2(url) {
|
|
|
8557
9794
|
return null;
|
|
8558
9795
|
}
|
|
8559
9796
|
}
|
|
8560
|
-
async function fetchRawFile(owner, repo, ref,
|
|
8561
|
-
const url = `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${
|
|
9797
|
+
async function fetchRawFile(owner, repo, ref, path17, authToken) {
|
|
9798
|
+
const url = `https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${path17}`;
|
|
8562
9799
|
try {
|
|
8563
|
-
const
|
|
9800
|
+
const headers = {};
|
|
9801
|
+
if (authToken) {
|
|
9802
|
+
headers.Authorization = `Bearer ${authToken}`;
|
|
9803
|
+
}
|
|
9804
|
+
const response = await fetch(url, { headers });
|
|
8564
9805
|
if (response.ok) {
|
|
8565
9806
|
return await response.text();
|
|
8566
9807
|
}
|
|
@@ -8569,14 +9810,16 @@ async function fetchRawFile(owner, repo, ref, path13) {
|
|
|
8569
9810
|
return null;
|
|
8570
9811
|
}
|
|
8571
9812
|
}
|
|
8572
|
-
async function fetchRepoMetadata(owner, repo) {
|
|
9813
|
+
async function fetchRepoMetadata(owner, repo, authToken) {
|
|
8573
9814
|
const url = `https://api.github.com/repos/${owner}/${repo}`;
|
|
8574
|
-
const
|
|
8575
|
-
|
|
8576
|
-
|
|
8577
|
-
|
|
8578
|
-
|
|
8579
|
-
|
|
9815
|
+
const headers = {
|
|
9816
|
+
Accept: "application/vnd.github.v3+json",
|
|
9817
|
+
"User-Agent": "DocCov-Scanner"
|
|
9818
|
+
};
|
|
9819
|
+
if (authToken) {
|
|
9820
|
+
headers.Authorization = `Bearer ${authToken}`;
|
|
9821
|
+
}
|
|
9822
|
+
const response = await fetch(url, { headers });
|
|
8580
9823
|
if (!response.ok) {
|
|
8581
9824
|
throw new Error(`Failed to fetch repository: ${response.status} ${response.statusText}`);
|
|
8582
9825
|
}
|
|
@@ -8591,7 +9834,7 @@ async function fetchRepoMetadata(owner, repo) {
|
|
|
8591
9834
|
isPrivate: data.private
|
|
8592
9835
|
};
|
|
8593
9836
|
}
|
|
8594
|
-
async function detectPackageManager3(owner, repo, ref) {
|
|
9837
|
+
async function detectPackageManager3(owner, repo, ref, authToken) {
|
|
8595
9838
|
const lockfiles = [
|
|
8596
9839
|
{ name: "bun.lockb", manager: "bun" },
|
|
8597
9840
|
{ name: "pnpm-lock.yaml", manager: "pnpm" },
|
|
@@ -8599,15 +9842,15 @@ async function detectPackageManager3(owner, repo, ref) {
|
|
|
8599
9842
|
{ name: "package-lock.json", manager: "npm" }
|
|
8600
9843
|
];
|
|
8601
9844
|
for (const { name, manager } of lockfiles) {
|
|
8602
|
-
const content = await fetchRawFile(owner, repo, ref, name);
|
|
9845
|
+
const content = await fetchRawFile(owner, repo, ref, name, authToken);
|
|
8603
9846
|
if (content !== null) {
|
|
8604
9847
|
return { manager, lockfile: { name, content: content.slice(0, 1e4) } };
|
|
8605
9848
|
}
|
|
8606
9849
|
}
|
|
8607
9850
|
return { manager: "unknown" };
|
|
8608
9851
|
}
|
|
8609
|
-
async function detectWorkspace(packageJson, owner, repo, ref) {
|
|
8610
|
-
const pnpmWorkspace = await fetchRawFile(owner, repo, ref, "pnpm-workspace.yaml");
|
|
9852
|
+
async function detectWorkspace(packageJson, owner, repo, ref, authToken) {
|
|
9853
|
+
const pnpmWorkspace = await fetchRawFile(owner, repo, ref, "pnpm-workspace.yaml", authToken);
|
|
8611
9854
|
if (pnpmWorkspace) {
|
|
8612
9855
|
const packagesMatch = pnpmWorkspace.match(/packages:\s*\n((?:\s+-\s+['"]?[^\n]+['"]?\n?)+)/);
|
|
8613
9856
|
const packages = packagesMatch ? packagesMatch[1].split(`
|
|
@@ -8618,7 +9861,7 @@ async function detectWorkspace(packageJson, owner, repo, ref) {
|
|
|
8618
9861
|
packages
|
|
8619
9862
|
};
|
|
8620
9863
|
}
|
|
8621
|
-
const lernaJson = await fetchRawFile(owner, repo, ref, "lerna.json");
|
|
9864
|
+
const lernaJson = await fetchRawFile(owner, repo, ref, "lerna.json", authToken);
|
|
8622
9865
|
if (lernaJson) {
|
|
8623
9866
|
return { isMonorepo: true, tool: "lerna" };
|
|
8624
9867
|
}
|
|
@@ -8678,18 +9921,20 @@ function detectBuildHints(packageJson, tsconfigJson) {
|
|
|
8678
9921
|
}
|
|
8679
9922
|
return hints;
|
|
8680
9923
|
}
|
|
8681
|
-
async function fetchGitHubContext(repoUrl,
|
|
9924
|
+
async function fetchGitHubContext(repoUrl, refOrOptions) {
|
|
8682
9925
|
const parsed = parseGitHubUrl2(repoUrl);
|
|
8683
9926
|
if (!parsed) {
|
|
8684
9927
|
throw new Error(`Invalid GitHub URL: ${repoUrl}`);
|
|
8685
9928
|
}
|
|
9929
|
+
const options = typeof refOrOptions === "string" ? { ref: refOrOptions } : refOrOptions ?? {};
|
|
9930
|
+
const { authToken } = options;
|
|
8686
9931
|
const { owner, repo } = parsed;
|
|
8687
|
-
const metadata = await fetchRepoMetadata(owner, repo);
|
|
8688
|
-
const targetRef = ref ?? metadata.defaultBranch;
|
|
9932
|
+
const metadata = await fetchRepoMetadata(owner, repo, authToken);
|
|
9933
|
+
const targetRef = options.ref ?? metadata.defaultBranch;
|
|
8689
9934
|
const [packageJsonRaw, tsconfigJsonRaw, pmResult] = await Promise.all([
|
|
8690
|
-
fetchRawFile(owner, repo, targetRef, "package.json"),
|
|
8691
|
-
fetchRawFile(owner, repo, targetRef, "tsconfig.json"),
|
|
8692
|
-
detectPackageManager3(owner, repo, targetRef)
|
|
9935
|
+
fetchRawFile(owner, repo, targetRef, "package.json", authToken),
|
|
9936
|
+
fetchRawFile(owner, repo, targetRef, "tsconfig.json", authToken),
|
|
9937
|
+
detectPackageManager3(owner, repo, targetRef, authToken)
|
|
8693
9938
|
]);
|
|
8694
9939
|
let packageJson = null;
|
|
8695
9940
|
let tsconfigJson = null;
|
|
@@ -8703,7 +9948,7 @@ async function fetchGitHubContext(repoUrl, ref) {
|
|
|
8703
9948
|
tsconfigJson = JSON.parse(tsconfigJsonRaw);
|
|
8704
9949
|
} catch {}
|
|
8705
9950
|
}
|
|
8706
|
-
const workspace = await detectWorkspace(packageJson, owner, repo, targetRef);
|
|
9951
|
+
const workspace = await detectWorkspace(packageJson, owner, repo, targetRef, authToken);
|
|
8707
9952
|
const buildHints = detectBuildHints(packageJson, tsconfigJson);
|
|
8708
9953
|
return {
|
|
8709
9954
|
metadata,
|
|
@@ -8718,18 +9963,20 @@ async function fetchGitHubContext(repoUrl, ref) {
|
|
|
8718
9963
|
}
|
|
8719
9964
|
};
|
|
8720
9965
|
}
|
|
8721
|
-
async function listWorkspacePackages(owner, repo, ref, patterns) {
|
|
9966
|
+
async function listWorkspacePackages(owner, repo, ref, patterns, authToken) {
|
|
8722
9967
|
const packages = [];
|
|
8723
9968
|
for (const pattern of patterns) {
|
|
8724
9969
|
const baseDir = pattern.replace(/\/\*.*$/, "");
|
|
8725
9970
|
try {
|
|
8726
9971
|
const url = `https://api.github.com/repos/${owner}/${repo}/contents/${baseDir}?ref=${ref}`;
|
|
8727
|
-
const
|
|
8728
|
-
|
|
8729
|
-
|
|
8730
|
-
|
|
8731
|
-
|
|
8732
|
-
|
|
9972
|
+
const headers = {
|
|
9973
|
+
Accept: "application/vnd.github.v3+json",
|
|
9974
|
+
"User-Agent": "DocCov-Scanner"
|
|
9975
|
+
};
|
|
9976
|
+
if (authToken) {
|
|
9977
|
+
headers.Authorization = `Bearer ${authToken}`;
|
|
9978
|
+
}
|
|
9979
|
+
const response = await fetch(url, { headers });
|
|
8733
9980
|
if (response.ok) {
|
|
8734
9981
|
const contents = await response.json();
|
|
8735
9982
|
for (const item of contents) {
|
|
@@ -8742,21 +9989,59 @@ async function listWorkspacePackages(owner, repo, ref, patterns) {
|
|
|
8742
9989
|
}
|
|
8743
9990
|
return packages;
|
|
8744
9991
|
}
|
|
9992
|
+
// src/scan/summary.ts
|
|
9993
|
+
function extractSpecSummary(spec) {
|
|
9994
|
+
const exports = spec.exports ?? [];
|
|
9995
|
+
const undocumented = [];
|
|
9996
|
+
const drift = [];
|
|
9997
|
+
for (const exp of exports) {
|
|
9998
|
+
const docs = exp.docs;
|
|
9999
|
+
if (!docs)
|
|
10000
|
+
continue;
|
|
10001
|
+
const hasMissing = (docs.missing?.length ?? 0) > 0;
|
|
10002
|
+
const isPartial = (docs.coverageScore ?? 0) < 100;
|
|
10003
|
+
if (hasMissing || isPartial) {
|
|
10004
|
+
undocumented.push(exp.name);
|
|
10005
|
+
}
|
|
10006
|
+
for (const d of docs.drift ?? []) {
|
|
10007
|
+
drift.push({
|
|
10008
|
+
export: exp.name,
|
|
10009
|
+
type: d.type,
|
|
10010
|
+
issue: d.issue,
|
|
10011
|
+
suggestion: d.suggestion
|
|
10012
|
+
});
|
|
10013
|
+
}
|
|
10014
|
+
}
|
|
10015
|
+
return {
|
|
10016
|
+
coverage: spec.docs?.coverageScore ?? 0,
|
|
10017
|
+
exportCount: exports.length,
|
|
10018
|
+
typeCount: spec.types?.length ?? 0,
|
|
10019
|
+
driftCount: drift.length,
|
|
10020
|
+
undocumented,
|
|
10021
|
+
drift
|
|
10022
|
+
};
|
|
10023
|
+
}
|
|
8745
10024
|
export {
|
|
8746
10025
|
validateSpecCache,
|
|
8747
10026
|
validateExamples,
|
|
8748
10027
|
typecheckExamples,
|
|
8749
10028
|
typecheckExample,
|
|
10029
|
+
tryExtractStandardSchema,
|
|
8750
10030
|
shouldValidate,
|
|
8751
10031
|
serializeJSDoc,
|
|
8752
10032
|
saveSpecCache,
|
|
10033
|
+
saveSnapshot,
|
|
8753
10034
|
saveReport,
|
|
8754
10035
|
safeParseJson,
|
|
8755
10036
|
runExamplesWithPackage,
|
|
8756
10037
|
runExamples,
|
|
8757
10038
|
runExample,
|
|
8758
10039
|
resolveTarget,
|
|
10040
|
+
renderSparkline,
|
|
10041
|
+
renderApiSurface,
|
|
8759
10042
|
readPackageJson,
|
|
10043
|
+
pruneHistory,
|
|
10044
|
+
pruneByTier,
|
|
8760
10045
|
parseGitHubUrl2 as parseScanGitHubUrl,
|
|
8761
10046
|
parseMarkdownFiles,
|
|
8762
10047
|
parseMarkdownFile,
|
|
@@ -8764,13 +10049,18 @@ export {
|
|
|
8764
10049
|
parseJSDocToPatch,
|
|
8765
10050
|
parseGitHubUrl,
|
|
8766
10051
|
parseExamplesFlag,
|
|
10052
|
+
parseCodeOwners,
|
|
8767
10053
|
parseAssertions,
|
|
8768
10054
|
mergeFixes,
|
|
8769
10055
|
mergeFilters,
|
|
8770
10056
|
mergeConfig,
|
|
8771
10057
|
loadSpecCache,
|
|
10058
|
+
loadSnapshotsForDays,
|
|
10059
|
+
loadSnapshots,
|
|
10060
|
+
loadCodeOwners,
|
|
8772
10061
|
loadCachedReport,
|
|
8773
10062
|
listWorkspacePackages,
|
|
10063
|
+
isStandardJSONSchema,
|
|
8774
10064
|
isFixableDrift,
|
|
8775
10065
|
isExecutableLang,
|
|
8776
10066
|
isCachedReportValid,
|
|
@@ -8783,6 +10073,7 @@ export {
|
|
|
8783
10073
|
hasDocsForExport,
|
|
8784
10074
|
groupDriftsByCategory,
|
|
8785
10075
|
getUndocumentedExports,
|
|
10076
|
+
getTrend,
|
|
8786
10077
|
getSpecCachePath,
|
|
8787
10078
|
getRunCommand,
|
|
8788
10079
|
getRulesForKind,
|
|
@@ -8790,36 +10081,46 @@ export {
|
|
|
8790
10081
|
getReportPath,
|
|
8791
10082
|
getPrimaryBuildScript,
|
|
8792
10083
|
getInstallCommand,
|
|
10084
|
+
getFileBlame,
|
|
10085
|
+
getExtendedTrend,
|
|
8793
10086
|
getDriftSummary,
|
|
8794
10087
|
getDocumentedExports,
|
|
8795
10088
|
getDocsImpactSummary,
|
|
8796
10089
|
getDiffReportPath,
|
|
8797
10090
|
getDefaultConfig,
|
|
8798
10091
|
getCoverageRules,
|
|
10092
|
+
getBlameForLines,
|
|
10093
|
+
generateWeeklySummaries,
|
|
8799
10094
|
generateReportFromEnriched,
|
|
8800
10095
|
generateReport,
|
|
8801
10096
|
generateFixesForExport,
|
|
8802
10097
|
generateFix,
|
|
8803
10098
|
formatPackageList,
|
|
8804
10099
|
formatDriftSummaryLine,
|
|
10100
|
+
formatDelta,
|
|
8805
10101
|
findRemovedReferences,
|
|
8806
10102
|
findPackageByName,
|
|
10103
|
+
findOwners,
|
|
8807
10104
|
findJSDocLocation,
|
|
8808
10105
|
findExportReferences,
|
|
8809
10106
|
findDeprecatedReferences,
|
|
8810
10107
|
fetchSpecFromGitHub,
|
|
8811
10108
|
fetchSpec,
|
|
8812
10109
|
fetchGitHubContext,
|
|
10110
|
+
extractViaStandardSchema,
|
|
8813
10111
|
extractSpecSummary,
|
|
8814
10112
|
extractPackageSpec,
|
|
8815
10113
|
extractImports,
|
|
8816
10114
|
extractFunctionCalls,
|
|
8817
10115
|
evaluateQuality,
|
|
10116
|
+
evaluatePolicy,
|
|
10117
|
+
evaluatePolicies,
|
|
8818
10118
|
evaluateExportQuality,
|
|
8819
10119
|
ensureSpecCoverage,
|
|
8820
10120
|
enrichSpec,
|
|
8821
10121
|
diffSpecWithDocs,
|
|
8822
10122
|
diffHashes,
|
|
10123
|
+
detectRuntimeSchemas,
|
|
8823
10124
|
detectPackageManager,
|
|
8824
10125
|
detectMonorepo,
|
|
8825
10126
|
detectExampleRuntimeErrors,
|
|
@@ -8829,9 +10130,11 @@ export {
|
|
|
8829
10130
|
defineConfig,
|
|
8830
10131
|
createSourceFile,
|
|
8831
10132
|
createNodeCommandRunner,
|
|
10133
|
+
computeSnapshot,
|
|
8832
10134
|
computeExportDrift,
|
|
8833
10135
|
computeDrift,
|
|
8834
10136
|
clearSpecCache,
|
|
10137
|
+
clearSchemaCache,
|
|
8835
10138
|
categorizeDrifts,
|
|
8836
10139
|
categorizeDrift,
|
|
8837
10140
|
calculateAggregateCoverage,
|
|
@@ -8840,19 +10143,28 @@ export {
|
|
|
8840
10143
|
buildDisplayUrl,
|
|
8841
10144
|
buildCloneUrl,
|
|
8842
10145
|
blockReferencesExport,
|
|
10146
|
+
attributeOwners,
|
|
8843
10147
|
applyPatchToJSDoc,
|
|
8844
10148
|
applyEdits,
|
|
10149
|
+
analyzeSpecOwnership,
|
|
10150
|
+
analyzeSpecContributors,
|
|
8845
10151
|
analyzeProject,
|
|
10152
|
+
analyzeOwnership,
|
|
8846
10153
|
analyzeFile,
|
|
8847
10154
|
analyzeDocsImpact,
|
|
10155
|
+
analyzeContributors,
|
|
8848
10156
|
analyze,
|
|
8849
10157
|
VALIDATION_INFO,
|
|
10158
|
+
TSDOC_RULES,
|
|
8850
10159
|
SandboxFileSystem,
|
|
8851
10160
|
STYLE_RULES,
|
|
8852
10161
|
SPEC_CACHE_FILE,
|
|
10162
|
+
RETENTION_DAYS,
|
|
8853
10163
|
REPORT_VERSION,
|
|
8854
10164
|
REPORT_EXTENSIONS,
|
|
8855
10165
|
NodeFileSystem,
|
|
10166
|
+
KNOWN_VENDORS,
|
|
10167
|
+
HISTORY_DIR,
|
|
8856
10168
|
DocCov,
|
|
8857
10169
|
DEFAULT_REPORT_PATH,
|
|
8858
10170
|
DEFAULT_REPORT_DIR,
|