@aiready/doc-drift 0.14.15 → 0.14.17
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/.turbo/turbo-build.log +10 -10
- package/.turbo/turbo-format-check.log +2 -2
- package/.turbo/turbo-lint$colon$fix.log +2 -0
- package/.turbo/turbo-lint.log +1 -1
- package/.turbo/turbo-test.log +36 -9
- package/.turbo/turbo-type-check.log +1 -1
- package/dist/chunk-35XBIDVM.mjs +347 -0
- package/dist/chunk-57NUPDIO.mjs +357 -0
- package/dist/chunk-IQBAXGOK.mjs +373 -0
- package/dist/cli.js +266 -35
- package/dist/cli.mjs +1 -1
- package/dist/index.d.mts +0 -7
- package/dist/index.d.ts +0 -7
- package/dist/index.js +266 -35
- package/dist/index.mjs +1 -1
- package/package.json +15 -12
- package/src/__tests__/contract.test.ts +48 -0
- package/src/analyzer.ts +28 -56
- package/src/constants.ts +214 -0
- package/src/heuristics.ts +53 -0
- package/tsconfig.json +2 -1
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// src/analyzer.ts
|
|
9
|
+
import {
|
|
10
|
+
scanFiles,
|
|
11
|
+
calculateDocDrift,
|
|
12
|
+
getFileCommitTimestamps,
|
|
13
|
+
getLineRangeLastModifiedCached,
|
|
14
|
+
Severity,
|
|
15
|
+
IssueType,
|
|
16
|
+
emitProgress,
|
|
17
|
+
getParser
|
|
18
|
+
} from "@aiready/core";
|
|
19
|
+
import { readFileSync } from "fs";
|
|
20
|
+
|
|
21
|
+
// src/constants.ts
|
|
22
|
+
var DESCRIPTIVE_PARAMS = /* @__PURE__ */ new Set([
|
|
23
|
+
"data",
|
|
24
|
+
"item",
|
|
25
|
+
"value",
|
|
26
|
+
"key",
|
|
27
|
+
"index",
|
|
28
|
+
"id",
|
|
29
|
+
"name",
|
|
30
|
+
"type",
|
|
31
|
+
"config",
|
|
32
|
+
"options",
|
|
33
|
+
"params",
|
|
34
|
+
"args",
|
|
35
|
+
"input",
|
|
36
|
+
"output",
|
|
37
|
+
"result",
|
|
38
|
+
"response",
|
|
39
|
+
"request",
|
|
40
|
+
"callback",
|
|
41
|
+
"handler",
|
|
42
|
+
"event",
|
|
43
|
+
"error",
|
|
44
|
+
"message",
|
|
45
|
+
"context",
|
|
46
|
+
"file",
|
|
47
|
+
"path",
|
|
48
|
+
"url",
|
|
49
|
+
"source",
|
|
50
|
+
"target",
|
|
51
|
+
"content",
|
|
52
|
+
"body",
|
|
53
|
+
"payload",
|
|
54
|
+
"user",
|
|
55
|
+
"userid",
|
|
56
|
+
"user_id",
|
|
57
|
+
"starttime",
|
|
58
|
+
"endtime",
|
|
59
|
+
"duration",
|
|
60
|
+
"timeout",
|
|
61
|
+
"retry",
|
|
62
|
+
"count",
|
|
63
|
+
"limit",
|
|
64
|
+
"offset",
|
|
65
|
+
"page",
|
|
66
|
+
"size",
|
|
67
|
+
"length",
|
|
68
|
+
"width",
|
|
69
|
+
"height",
|
|
70
|
+
"depth",
|
|
71
|
+
"color",
|
|
72
|
+
"background",
|
|
73
|
+
"foreground",
|
|
74
|
+
"border",
|
|
75
|
+
"margin",
|
|
76
|
+
"padding",
|
|
77
|
+
"position",
|
|
78
|
+
"top",
|
|
79
|
+
"bottom",
|
|
80
|
+
"left",
|
|
81
|
+
"right",
|
|
82
|
+
"center",
|
|
83
|
+
"start",
|
|
84
|
+
"end",
|
|
85
|
+
"begin",
|
|
86
|
+
"finish",
|
|
87
|
+
"complete",
|
|
88
|
+
"pending",
|
|
89
|
+
"active",
|
|
90
|
+
"inactive",
|
|
91
|
+
"enabled",
|
|
92
|
+
"disabled",
|
|
93
|
+
"visible",
|
|
94
|
+
"hidden",
|
|
95
|
+
"open",
|
|
96
|
+
"closed",
|
|
97
|
+
"locked",
|
|
98
|
+
"unlocked",
|
|
99
|
+
"read",
|
|
100
|
+
"write",
|
|
101
|
+
"append",
|
|
102
|
+
"prepend",
|
|
103
|
+
"insert",
|
|
104
|
+
"replace",
|
|
105
|
+
"merge",
|
|
106
|
+
"split",
|
|
107
|
+
"join",
|
|
108
|
+
"filter",
|
|
109
|
+
"map",
|
|
110
|
+
"reduce",
|
|
111
|
+
"find",
|
|
112
|
+
"sort",
|
|
113
|
+
"reverse",
|
|
114
|
+
"unique",
|
|
115
|
+
"flatten",
|
|
116
|
+
"compact",
|
|
117
|
+
"chunk",
|
|
118
|
+
"zip",
|
|
119
|
+
"unzip",
|
|
120
|
+
"completed",
|
|
121
|
+
"failed",
|
|
122
|
+
"healthy",
|
|
123
|
+
"high",
|
|
124
|
+
"medium",
|
|
125
|
+
"low",
|
|
126
|
+
"running",
|
|
127
|
+
"stopped",
|
|
128
|
+
"title",
|
|
129
|
+
"subtitle",
|
|
130
|
+
"label",
|
|
131
|
+
"description",
|
|
132
|
+
"version",
|
|
133
|
+
"timestamp",
|
|
134
|
+
"date",
|
|
135
|
+
"time",
|
|
136
|
+
"status",
|
|
137
|
+
"mode",
|
|
138
|
+
"action",
|
|
139
|
+
"effect",
|
|
140
|
+
"resource",
|
|
141
|
+
"principal",
|
|
142
|
+
"statement",
|
|
143
|
+
"sid",
|
|
144
|
+
"allow",
|
|
145
|
+
"deny",
|
|
146
|
+
"roi",
|
|
147
|
+
"unifiedbudget",
|
|
148
|
+
"filepath",
|
|
149
|
+
"rootdir",
|
|
150
|
+
"fp",
|
|
151
|
+
"email",
|
|
152
|
+
"username",
|
|
153
|
+
"password",
|
|
154
|
+
"token",
|
|
155
|
+
"secret",
|
|
156
|
+
"apikey",
|
|
157
|
+
"api_key",
|
|
158
|
+
"baseurl",
|
|
159
|
+
"base_url",
|
|
160
|
+
"endpoint",
|
|
161
|
+
"method",
|
|
162
|
+
"headers",
|
|
163
|
+
"queryparams",
|
|
164
|
+
"query_params",
|
|
165
|
+
"bodyparams",
|
|
166
|
+
"body_params",
|
|
167
|
+
"formdata",
|
|
168
|
+
"form_data",
|
|
169
|
+
"filename",
|
|
170
|
+
"file_name",
|
|
171
|
+
"filetype",
|
|
172
|
+
"file_type",
|
|
173
|
+
"filesize",
|
|
174
|
+
"file_size",
|
|
175
|
+
"filecontent",
|
|
176
|
+
"file_content",
|
|
177
|
+
"fileurl",
|
|
178
|
+
"file_url",
|
|
179
|
+
"fileid",
|
|
180
|
+
"file_id",
|
|
181
|
+
"filekey",
|
|
182
|
+
"file_key",
|
|
183
|
+
"filepath",
|
|
184
|
+
"file_path",
|
|
185
|
+
"filedir",
|
|
186
|
+
"file_dir",
|
|
187
|
+
"fileext",
|
|
188
|
+
"file_ext",
|
|
189
|
+
"filebase",
|
|
190
|
+
"file_base",
|
|
191
|
+
"filenamewithoutext",
|
|
192
|
+
"file_name_without_ext",
|
|
193
|
+
"filedirname",
|
|
194
|
+
"file_dirname",
|
|
195
|
+
"filebasename",
|
|
196
|
+
"file_basename",
|
|
197
|
+
"fileextname",
|
|
198
|
+
"file_extname",
|
|
199
|
+
"fileroot",
|
|
200
|
+
"file_root",
|
|
201
|
+
"filesep",
|
|
202
|
+
"file_sep",
|
|
203
|
+
"filejoin",
|
|
204
|
+
"file_join",
|
|
205
|
+
"fileresolve",
|
|
206
|
+
"file_resolve",
|
|
207
|
+
"filenormalize",
|
|
208
|
+
"file_normalize",
|
|
209
|
+
"exp",
|
|
210
|
+
"ctx",
|
|
211
|
+
"options",
|
|
212
|
+
"config",
|
|
213
|
+
"directory",
|
|
214
|
+
"useroptions",
|
|
215
|
+
"resultslength",
|
|
216
|
+
"totalissues",
|
|
217
|
+
"totaltokencost",
|
|
218
|
+
"elapsedtime"
|
|
219
|
+
]);
|
|
220
|
+
|
|
221
|
+
// src/heuristics.ts
|
|
222
|
+
function getMissingParams(exp) {
|
|
223
|
+
if (exp.type !== "function" || !exp.parameters || !exp.documentation) {
|
|
224
|
+
return [];
|
|
225
|
+
}
|
|
226
|
+
const docContent = exp.documentation.content;
|
|
227
|
+
const params = exp.parameters;
|
|
228
|
+
return params.filter((p) => {
|
|
229
|
+
if (DESCRIPTIVE_PARAMS.has(p.toLowerCase())) {
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
const regex = new RegExp(`\\b${p}\\b`);
|
|
233
|
+
return !regex.test(docContent);
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
function isUndocumentedComplexity(exp) {
|
|
237
|
+
if (exp.documentation) return false;
|
|
238
|
+
if (!exp.loc) return false;
|
|
239
|
+
const lines = exp.loc.end.line - exp.loc.start.line;
|
|
240
|
+
return lines > 20;
|
|
241
|
+
}
|
|
242
|
+
function hasTemporalDrift(bodyModified, docModified) {
|
|
243
|
+
if (bodyModified <= 0 || docModified <= 0) return false;
|
|
244
|
+
const DRIFT_THRESHOLD_SECONDS = 24 * 60 * 60;
|
|
245
|
+
return bodyModified - docModified > DRIFT_THRESHOLD_SECONDS;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// src/analyzer.ts
|
|
249
|
+
async function analyzeDocDrift(options) {
|
|
250
|
+
const files = await scanFiles(options);
|
|
251
|
+
const issues = [];
|
|
252
|
+
let uncommentedExports = 0;
|
|
253
|
+
let totalExports = 0;
|
|
254
|
+
let outdatedComments = 0;
|
|
255
|
+
let undocumentedComplexityCount = 0;
|
|
256
|
+
let actualDrift = 0;
|
|
257
|
+
let processed = 0;
|
|
258
|
+
for (const file of files) {
|
|
259
|
+
processed++;
|
|
260
|
+
emitProgress(
|
|
261
|
+
processed,
|
|
262
|
+
files.length,
|
|
263
|
+
"doc-drift",
|
|
264
|
+
"analyzing files",
|
|
265
|
+
options.onProgress
|
|
266
|
+
);
|
|
267
|
+
const parser = await getParser(file);
|
|
268
|
+
if (!parser) continue;
|
|
269
|
+
let code;
|
|
270
|
+
try {
|
|
271
|
+
code = readFileSync(file, "utf-8");
|
|
272
|
+
} catch {
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
try {
|
|
276
|
+
await parser.initialize();
|
|
277
|
+
const parseResult = parser.parse(code, file);
|
|
278
|
+
let fileLineStamps;
|
|
279
|
+
for (const exp of parseResult.exports) {
|
|
280
|
+
if (exp.type === "function" || exp.type === "class") {
|
|
281
|
+
totalExports++;
|
|
282
|
+
if (!exp.documentation) {
|
|
283
|
+
uncommentedExports++;
|
|
284
|
+
if (isUndocumentedComplexity(exp)) undocumentedComplexityCount++;
|
|
285
|
+
} else {
|
|
286
|
+
const doc = exp.documentation;
|
|
287
|
+
const missingParams = getMissingParams(exp);
|
|
288
|
+
if (missingParams.length > 0) {
|
|
289
|
+
outdatedComments++;
|
|
290
|
+
issues.push({
|
|
291
|
+
type: IssueType.DocDrift,
|
|
292
|
+
severity: Severity.Major,
|
|
293
|
+
message: `Documentation mismatch: function parameters (${missingParams.join(", ")}) are not mentioned in the docs.`,
|
|
294
|
+
location: { file, line: exp.loc?.start.line || 1 }
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
if (exp.loc && doc.loc) {
|
|
298
|
+
if (!fileLineStamps)
|
|
299
|
+
fileLineStamps = getFileCommitTimestamps(file);
|
|
300
|
+
const bodyModified = getLineRangeLastModifiedCached(
|
|
301
|
+
fileLineStamps,
|
|
302
|
+
exp.loc.start.line,
|
|
303
|
+
exp.loc.end.line
|
|
304
|
+
);
|
|
305
|
+
const docModified = getLineRangeLastModifiedCached(
|
|
306
|
+
fileLineStamps,
|
|
307
|
+
doc.loc.start.line,
|
|
308
|
+
doc.loc.end.line
|
|
309
|
+
);
|
|
310
|
+
if (hasTemporalDrift(bodyModified, docModified)) {
|
|
311
|
+
actualDrift++;
|
|
312
|
+
issues.push({
|
|
313
|
+
type: IssueType.DocDrift,
|
|
314
|
+
severity: Severity.Major,
|
|
315
|
+
message: `Documentation drift: logic was modified on ${new Date(bodyModified * 1e3).toLocaleDateString()} but documentation was last updated on ${new Date(docModified * 1e3).toLocaleDateString()}.`,
|
|
316
|
+
location: { file, line: doc.loc.start.line }
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
} catch (error) {
|
|
324
|
+
console.warn(`Doc-drift: Failed to parse ${file}: ${error}`);
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
const riskResult = calculateDocDrift({
|
|
329
|
+
uncommentedExports,
|
|
330
|
+
totalExports,
|
|
331
|
+
outdatedComments,
|
|
332
|
+
undocumentedComplexity: undocumentedComplexityCount,
|
|
333
|
+
actualDrift
|
|
334
|
+
});
|
|
335
|
+
return {
|
|
336
|
+
summary: {
|
|
337
|
+
filesAnalyzed: files.length,
|
|
338
|
+
functionsAnalyzed: totalExports,
|
|
339
|
+
score: riskResult.score,
|
|
340
|
+
rating: riskResult.rating
|
|
341
|
+
},
|
|
342
|
+
issues,
|
|
343
|
+
rawData: {
|
|
344
|
+
uncommentedExports,
|
|
345
|
+
totalExports,
|
|
346
|
+
outdatedComments,
|
|
347
|
+
undocumentedComplexity: undocumentedComplexityCount,
|
|
348
|
+
actualDrift
|
|
349
|
+
},
|
|
350
|
+
recommendations: riskResult.recommendations
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
export {
|
|
355
|
+
__require,
|
|
356
|
+
analyzeDocDrift
|
|
357
|
+
};
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
+
}) : x)(function(x) {
|
|
4
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
// src/analyzer.ts
|
|
9
|
+
import {
|
|
10
|
+
scanFiles,
|
|
11
|
+
calculateDocDrift,
|
|
12
|
+
getFileCommitTimestamps,
|
|
13
|
+
getLineRangeLastModifiedCached,
|
|
14
|
+
Severity,
|
|
15
|
+
IssueType,
|
|
16
|
+
emitProgress,
|
|
17
|
+
getParser
|
|
18
|
+
} from "@aiready/core";
|
|
19
|
+
import { readFileSync } from "fs";
|
|
20
|
+
|
|
21
|
+
// src/constants.ts
|
|
22
|
+
var DESCRIPTIVE_PARAMS = /* @__PURE__ */ new Set([
|
|
23
|
+
"data",
|
|
24
|
+
"item",
|
|
25
|
+
"value",
|
|
26
|
+
"key",
|
|
27
|
+
"index",
|
|
28
|
+
"id",
|
|
29
|
+
"name",
|
|
30
|
+
"type",
|
|
31
|
+
"config",
|
|
32
|
+
"options",
|
|
33
|
+
"params",
|
|
34
|
+
"args",
|
|
35
|
+
"input",
|
|
36
|
+
"output",
|
|
37
|
+
"result",
|
|
38
|
+
"response",
|
|
39
|
+
"request",
|
|
40
|
+
"callback",
|
|
41
|
+
"handler",
|
|
42
|
+
"event",
|
|
43
|
+
"error",
|
|
44
|
+
"message",
|
|
45
|
+
"context",
|
|
46
|
+
"file",
|
|
47
|
+
"path",
|
|
48
|
+
"url",
|
|
49
|
+
"source",
|
|
50
|
+
"target",
|
|
51
|
+
"content",
|
|
52
|
+
"body",
|
|
53
|
+
"payload",
|
|
54
|
+
"user",
|
|
55
|
+
"userid",
|
|
56
|
+
"user_id",
|
|
57
|
+
"starttime",
|
|
58
|
+
"endtime",
|
|
59
|
+
"duration",
|
|
60
|
+
"timeout",
|
|
61
|
+
"retry",
|
|
62
|
+
"count",
|
|
63
|
+
"limit",
|
|
64
|
+
"offset",
|
|
65
|
+
"page",
|
|
66
|
+
"size",
|
|
67
|
+
"length",
|
|
68
|
+
"width",
|
|
69
|
+
"height",
|
|
70
|
+
"depth",
|
|
71
|
+
"color",
|
|
72
|
+
"background",
|
|
73
|
+
"foreground",
|
|
74
|
+
"border",
|
|
75
|
+
"margin",
|
|
76
|
+
"padding",
|
|
77
|
+
"position",
|
|
78
|
+
"top",
|
|
79
|
+
"bottom",
|
|
80
|
+
"left",
|
|
81
|
+
"right",
|
|
82
|
+
"center",
|
|
83
|
+
"start",
|
|
84
|
+
"end",
|
|
85
|
+
"begin",
|
|
86
|
+
"finish",
|
|
87
|
+
"complete",
|
|
88
|
+
"pending",
|
|
89
|
+
"active",
|
|
90
|
+
"inactive",
|
|
91
|
+
"enabled",
|
|
92
|
+
"disabled",
|
|
93
|
+
"visible",
|
|
94
|
+
"hidden",
|
|
95
|
+
"open",
|
|
96
|
+
"closed",
|
|
97
|
+
"locked",
|
|
98
|
+
"unlocked",
|
|
99
|
+
"read",
|
|
100
|
+
"write",
|
|
101
|
+
"append",
|
|
102
|
+
"prepend",
|
|
103
|
+
"insert",
|
|
104
|
+
"replace",
|
|
105
|
+
"merge",
|
|
106
|
+
"split",
|
|
107
|
+
"join",
|
|
108
|
+
"filter",
|
|
109
|
+
"map",
|
|
110
|
+
"reduce",
|
|
111
|
+
"find",
|
|
112
|
+
"sort",
|
|
113
|
+
"reverse",
|
|
114
|
+
"unique",
|
|
115
|
+
"flatten",
|
|
116
|
+
"compact",
|
|
117
|
+
"chunk",
|
|
118
|
+
"zip",
|
|
119
|
+
"unzip",
|
|
120
|
+
"completed",
|
|
121
|
+
"failed",
|
|
122
|
+
"healthy",
|
|
123
|
+
"high",
|
|
124
|
+
"medium",
|
|
125
|
+
"low",
|
|
126
|
+
"running",
|
|
127
|
+
"stopped",
|
|
128
|
+
"title",
|
|
129
|
+
"subtitle",
|
|
130
|
+
"label",
|
|
131
|
+
"description",
|
|
132
|
+
"version",
|
|
133
|
+
"timestamp",
|
|
134
|
+
"date",
|
|
135
|
+
"time",
|
|
136
|
+
"status",
|
|
137
|
+
"mode",
|
|
138
|
+
"action",
|
|
139
|
+
"effect",
|
|
140
|
+
"resource",
|
|
141
|
+
"principal",
|
|
142
|
+
"statement",
|
|
143
|
+
"sid",
|
|
144
|
+
"allow",
|
|
145
|
+
"deny",
|
|
146
|
+
"roi",
|
|
147
|
+
"unifiedbudget",
|
|
148
|
+
"filepath",
|
|
149
|
+
"rootdir",
|
|
150
|
+
"fp",
|
|
151
|
+
"email",
|
|
152
|
+
"username",
|
|
153
|
+
"password",
|
|
154
|
+
"token",
|
|
155
|
+
"secret",
|
|
156
|
+
"apikey",
|
|
157
|
+
"api_key",
|
|
158
|
+
"baseurl",
|
|
159
|
+
"base_url",
|
|
160
|
+
"endpoint",
|
|
161
|
+
"method",
|
|
162
|
+
"headers",
|
|
163
|
+
"queryparams",
|
|
164
|
+
"query_params",
|
|
165
|
+
"bodyparams",
|
|
166
|
+
"body_params",
|
|
167
|
+
"formdata",
|
|
168
|
+
"form_data",
|
|
169
|
+
"filename",
|
|
170
|
+
"file_name",
|
|
171
|
+
"filetype",
|
|
172
|
+
"file_type",
|
|
173
|
+
"filesize",
|
|
174
|
+
"file_size",
|
|
175
|
+
"filecontent",
|
|
176
|
+
"file_content",
|
|
177
|
+
"fileurl",
|
|
178
|
+
"file_url",
|
|
179
|
+
"fileid",
|
|
180
|
+
"file_id",
|
|
181
|
+
"filekey",
|
|
182
|
+
"file_key",
|
|
183
|
+
"filepath",
|
|
184
|
+
"file_path",
|
|
185
|
+
"filedir",
|
|
186
|
+
"file_dir",
|
|
187
|
+
"fileext",
|
|
188
|
+
"file_ext",
|
|
189
|
+
"filebase",
|
|
190
|
+
"file_base",
|
|
191
|
+
"filenamewithoutext",
|
|
192
|
+
"file_name_without_ext",
|
|
193
|
+
"filedirname",
|
|
194
|
+
"file_dirname",
|
|
195
|
+
"filebasename",
|
|
196
|
+
"file_basename",
|
|
197
|
+
"fileextname",
|
|
198
|
+
"file_extname",
|
|
199
|
+
"fileroot",
|
|
200
|
+
"file_root",
|
|
201
|
+
"filesep",
|
|
202
|
+
"file_sep",
|
|
203
|
+
"filejoin",
|
|
204
|
+
"file_join",
|
|
205
|
+
"fileresolve",
|
|
206
|
+
"file_resolve",
|
|
207
|
+
"filenormalize",
|
|
208
|
+
"file_normalize",
|
|
209
|
+
"exp",
|
|
210
|
+
"ctx",
|
|
211
|
+
"options",
|
|
212
|
+
"done",
|
|
213
|
+
"next",
|
|
214
|
+
"reject",
|
|
215
|
+
"resolve",
|
|
216
|
+
"error",
|
|
217
|
+
"err",
|
|
218
|
+
"req",
|
|
219
|
+
"res",
|
|
220
|
+
"event",
|
|
221
|
+
"payload",
|
|
222
|
+
"metadata",
|
|
223
|
+
"params",
|
|
224
|
+
"props",
|
|
225
|
+
"state",
|
|
226
|
+
"dispatch",
|
|
227
|
+
"action",
|
|
228
|
+
"config",
|
|
229
|
+
"directory",
|
|
230
|
+
"useroptions",
|
|
231
|
+
"resultslength",
|
|
232
|
+
"totalissues",
|
|
233
|
+
"totaltokencost",
|
|
234
|
+
"elapsedtime"
|
|
235
|
+
]);
|
|
236
|
+
|
|
237
|
+
// src/heuristics.ts
|
|
238
|
+
function getMissingParams(exp) {
|
|
239
|
+
if (exp.type !== "function" || !exp.parameters || !exp.documentation) {
|
|
240
|
+
return [];
|
|
241
|
+
}
|
|
242
|
+
const docContent = exp.documentation.content;
|
|
243
|
+
const params = exp.parameters;
|
|
244
|
+
return params.filter((p) => {
|
|
245
|
+
if (DESCRIPTIVE_PARAMS.has(p.toLowerCase())) {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
const regex = new RegExp(`\\b${p}\\b`);
|
|
249
|
+
return !regex.test(docContent);
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
function isUndocumentedComplexity(exp) {
|
|
253
|
+
if (exp.documentation) return false;
|
|
254
|
+
if (!exp.loc) return false;
|
|
255
|
+
const lines = exp.loc.end.line - exp.loc.start.line;
|
|
256
|
+
return lines > 20;
|
|
257
|
+
}
|
|
258
|
+
function hasTemporalDrift(bodyModified, docModified) {
|
|
259
|
+
if (bodyModified <= 0 || docModified <= 0) return false;
|
|
260
|
+
const DRIFT_THRESHOLD_SECONDS = 24 * 60 * 60;
|
|
261
|
+
return bodyModified - docModified > DRIFT_THRESHOLD_SECONDS;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// src/analyzer.ts
|
|
265
|
+
async function analyzeDocDrift(options) {
|
|
266
|
+
const files = await scanFiles(options);
|
|
267
|
+
const issues = [];
|
|
268
|
+
let uncommentedExports = 0;
|
|
269
|
+
let totalExports = 0;
|
|
270
|
+
let outdatedComments = 0;
|
|
271
|
+
let undocumentedComplexityCount = 0;
|
|
272
|
+
let actualDrift = 0;
|
|
273
|
+
let processed = 0;
|
|
274
|
+
for (const file of files) {
|
|
275
|
+
processed++;
|
|
276
|
+
emitProgress(
|
|
277
|
+
processed,
|
|
278
|
+
files.length,
|
|
279
|
+
"doc-drift",
|
|
280
|
+
"analyzing files",
|
|
281
|
+
options.onProgress
|
|
282
|
+
);
|
|
283
|
+
const parser = await getParser(file);
|
|
284
|
+
if (!parser) continue;
|
|
285
|
+
let code;
|
|
286
|
+
try {
|
|
287
|
+
code = readFileSync(file, "utf-8");
|
|
288
|
+
} catch {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
try {
|
|
292
|
+
await parser.initialize();
|
|
293
|
+
const parseResult = parser.parse(code, file);
|
|
294
|
+
let fileLineStamps;
|
|
295
|
+
for (const exp of parseResult.exports) {
|
|
296
|
+
if (exp.type === "function" || exp.type === "class") {
|
|
297
|
+
totalExports++;
|
|
298
|
+
if (!exp.documentation) {
|
|
299
|
+
uncommentedExports++;
|
|
300
|
+
if (isUndocumentedComplexity(exp)) undocumentedComplexityCount++;
|
|
301
|
+
} else {
|
|
302
|
+
const doc = exp.documentation;
|
|
303
|
+
const missingParams = getMissingParams(exp);
|
|
304
|
+
if (missingParams.length > 0) {
|
|
305
|
+
outdatedComments++;
|
|
306
|
+
issues.push({
|
|
307
|
+
type: IssueType.DocDrift,
|
|
308
|
+
severity: Severity.Major,
|
|
309
|
+
message: `Documentation mismatch: function parameters (${missingParams.join(", ")}) are not mentioned in the docs.`,
|
|
310
|
+
location: { file, line: exp.loc?.start.line || 1 }
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
if (exp.loc && doc.loc) {
|
|
314
|
+
if (!fileLineStamps)
|
|
315
|
+
fileLineStamps = getFileCommitTimestamps(file);
|
|
316
|
+
const bodyModified = getLineRangeLastModifiedCached(
|
|
317
|
+
fileLineStamps,
|
|
318
|
+
exp.loc.start.line,
|
|
319
|
+
exp.loc.end.line
|
|
320
|
+
);
|
|
321
|
+
const docModified = getLineRangeLastModifiedCached(
|
|
322
|
+
fileLineStamps,
|
|
323
|
+
doc.loc.start.line,
|
|
324
|
+
doc.loc.end.line
|
|
325
|
+
);
|
|
326
|
+
if (hasTemporalDrift(bodyModified, docModified)) {
|
|
327
|
+
actualDrift++;
|
|
328
|
+
issues.push({
|
|
329
|
+
type: IssueType.DocDrift,
|
|
330
|
+
severity: Severity.Major,
|
|
331
|
+
message: `Documentation drift: logic was modified on ${new Date(bodyModified * 1e3).toLocaleDateString()} but documentation was last updated on ${new Date(docModified * 1e3).toLocaleDateString()}.`,
|
|
332
|
+
location: { file, line: doc.loc.start.line }
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
} catch (error) {
|
|
340
|
+
console.warn(`Doc-drift: Failed to parse ${file}: ${error}`);
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
const riskResult = calculateDocDrift({
|
|
345
|
+
uncommentedExports,
|
|
346
|
+
totalExports,
|
|
347
|
+
outdatedComments,
|
|
348
|
+
undocumentedComplexity: undocumentedComplexityCount,
|
|
349
|
+
actualDrift
|
|
350
|
+
});
|
|
351
|
+
return {
|
|
352
|
+
summary: {
|
|
353
|
+
filesAnalyzed: files.length,
|
|
354
|
+
functionsAnalyzed: totalExports,
|
|
355
|
+
score: riskResult.score,
|
|
356
|
+
rating: riskResult.rating
|
|
357
|
+
},
|
|
358
|
+
issues,
|
|
359
|
+
rawData: {
|
|
360
|
+
uncommentedExports,
|
|
361
|
+
totalExports,
|
|
362
|
+
outdatedComments,
|
|
363
|
+
undocumentedComplexity: undocumentedComplexityCount,
|
|
364
|
+
actualDrift
|
|
365
|
+
},
|
|
366
|
+
recommendations: riskResult.recommendations
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export {
|
|
371
|
+
__require,
|
|
372
|
+
analyzeDocDrift
|
|
373
|
+
};
|