@aiready/doc-drift 0.13.4 → 0.13.6

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.
@@ -1,209 +0,0 @@
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 { calculateDocDrift } from "@aiready/core";
10
- import { readdirSync, statSync, readFileSync } from "fs";
11
- import { join, extname } from "path";
12
- import { parse } from "@typescript-eslint/typescript-estree";
13
- import { execSync } from "child_process";
14
- var SRC_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx"]);
15
- var DEFAULT_EXCLUDES = [
16
- "node_modules",
17
- "dist",
18
- ".git",
19
- "coverage",
20
- ".turbo",
21
- "build"
22
- ];
23
- function collectFiles(dir, options, depth = 0) {
24
- if (depth > 20) return [];
25
- const excludes = [...DEFAULT_EXCLUDES, ...options.exclude ?? []];
26
- let entries;
27
- try {
28
- entries = readdirSync(dir);
29
- } catch {
30
- return [];
31
- }
32
- const files = [];
33
- for (const entry of entries) {
34
- if (excludes.some((ex) => entry === ex || entry.includes(ex))) continue;
35
- const full = join(dir, entry);
36
- let stat;
37
- try {
38
- stat = statSync(full);
39
- } catch {
40
- continue;
41
- }
42
- if (stat.isDirectory()) {
43
- files.push(...collectFiles(full, options, depth + 1));
44
- } else if (stat.isFile() && SRC_EXTENSIONS.has(extname(full))) {
45
- if (!options.include || options.include.some((p) => full.includes(p))) {
46
- files.push(full);
47
- }
48
- }
49
- }
50
- return files;
51
- }
52
- function getFileCommitTimestamps(file) {
53
- const lineStamps = {};
54
- try {
55
- const output = execSync(`git blame -t "${file}"`, {
56
- encoding: "utf-8",
57
- stdio: ["ignore", "pipe", "ignore"]
58
- });
59
- const lines = output.split("\n");
60
- for (const line of lines) {
61
- if (!line) continue;
62
- const match = line.match(/^\S+\s+\(.*?(\d{10,})\s+[-+]\d+\s+(\d+)\)/);
63
- if (match) {
64
- const ts = parseInt(match[1], 10);
65
- const ln = parseInt(match[2], 10);
66
- lineStamps[ln] = ts;
67
- }
68
- }
69
- } catch {
70
- }
71
- return lineStamps;
72
- }
73
- function getLineRangeLastModifiedCached(lineStamps, startLine, endLine) {
74
- let latest = 0;
75
- for (let i = startLine; i <= endLine; i++) {
76
- if (lineStamps[i] && lineStamps[i] > latest) {
77
- latest = lineStamps[i];
78
- }
79
- }
80
- return latest;
81
- }
82
- async function analyzeDocDrift(options) {
83
- const rootDir = options.rootDir;
84
- const files = collectFiles(rootDir, options);
85
- const staleMonths = options.staleMonths ?? 6;
86
- const staleSeconds = staleMonths * 30 * 24 * 60 * 60;
87
- let uncommentedExports = 0;
88
- let totalExports = 0;
89
- let outdatedComments = 0;
90
- let undocumentedComplexity = 0;
91
- const issues = [];
92
- const now = Math.floor(Date.now() / 1e3);
93
- let processed = 0;
94
- for (const file of files) {
95
- processed++;
96
- options.onProgress?.(processed, files.length, `doc-drift: analyzing ${file.substring(rootDir.length + 1)}`);
97
- let code;
98
- try {
99
- code = readFileSync(file, "utf-8");
100
- } catch {
101
- continue;
102
- }
103
- let ast;
104
- try {
105
- ast = parse(code, {
106
- jsx: file.endsWith(".tsx") || file.endsWith(".jsx"),
107
- loc: true,
108
- comment: true
109
- });
110
- } catch {
111
- continue;
112
- }
113
- const comments = ast.comments || [];
114
- let fileLineStamps;
115
- for (const node of ast.body) {
116
- if (node.type === "ExportNamedDeclaration" || node.type === "ExportDefaultDeclaration") {
117
- const decl = node.declaration;
118
- if (!decl) continue;
119
- if (decl.type === "FunctionDeclaration" || decl.type === "ClassDeclaration" || decl.type === "VariableDeclaration") {
120
- totalExports++;
121
- const nodeLine = node.loc.start.line;
122
- const jsdocs = comments.filter(
123
- (c) => c.type === "Block" && c.value.startsWith("*") && c.loc.end.line === nodeLine - 1
124
- );
125
- if (jsdocs.length === 0) {
126
- uncommentedExports++;
127
- if (decl.type === "FunctionDeclaration" && decl.body?.loc) {
128
- const lines = decl.body.loc.end.line - decl.body.loc.start.line;
129
- if (lines > 20) undocumentedComplexity++;
130
- }
131
- } else {
132
- const jsdoc = jsdocs[0];
133
- const jsdocText = jsdoc.value;
134
- if (decl.type === "FunctionDeclaration") {
135
- const params = decl.params.map((p) => p.name || p.left && p.left.name).filter(Boolean);
136
- const paramTags = Array.from(
137
- jsdocText.matchAll(/@param\s+(?:\{[^}]+\}\s+)?([a-zA-Z0-9_]+)/g)
138
- ).map((m) => m[1]);
139
- const missingParams = params.filter(
140
- (p) => !paramTags.includes(p)
141
- );
142
- if (missingParams.length > 0) {
143
- outdatedComments++;
144
- issues.push({
145
- type: "doc-drift",
146
- severity: "major",
147
- message: `JSDoc @param mismatch: function has parameters (${missingParams.join(", ")}) not documented in JSDoc.`,
148
- location: { file, line: nodeLine }
149
- });
150
- continue;
151
- }
152
- }
153
- if (!fileLineStamps) {
154
- fileLineStamps = getFileCommitTimestamps(file);
155
- }
156
- const commentModified = getLineRangeLastModifiedCached(
157
- fileLineStamps,
158
- jsdoc.loc.start.line,
159
- jsdoc.loc.end.line
160
- );
161
- const bodyModified = getLineRangeLastModifiedCached(
162
- fileLineStamps,
163
- decl.loc.start.line,
164
- decl.loc.end.line
165
- );
166
- if (commentModified > 0 && bodyModified > 0) {
167
- if (now - commentModified > staleSeconds && bodyModified - commentModified > staleSeconds / 2) {
168
- outdatedComments++;
169
- issues.push({
170
- type: "doc-drift",
171
- severity: "minor",
172
- message: `JSDoc is significantly older than the function body implementation. Code may have drifted.`,
173
- location: { file, line: jsdoc.loc.start.line }
174
- });
175
- }
176
- }
177
- }
178
- }
179
- }
180
- }
181
- }
182
- const riskResult = calculateDocDrift({
183
- uncommentedExports,
184
- totalExports,
185
- outdatedComments,
186
- undocumentedComplexity
187
- });
188
- return {
189
- summary: {
190
- filesAnalyzed: files.length,
191
- functionsAnalyzed: totalExports,
192
- score: riskResult.score,
193
- rating: riskResult.rating
194
- },
195
- issues,
196
- rawData: {
197
- uncommentedExports,
198
- totalExports,
199
- outdatedComments,
200
- undocumentedComplexity
201
- },
202
- recommendations: riskResult.recommendations
203
- };
204
- }
205
-
206
- export {
207
- __require,
208
- analyzeDocDrift
209
- };