@aiready/doc-drift 0.13.4 → 0.13.5

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,143 +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 {
10
- scanFiles,
11
- calculateDocDrift,
12
- getFileCommitTimestamps,
13
- getLineRangeLastModifiedCached
14
- } from "@aiready/core";
15
- import { readFileSync } from "fs";
16
- import { parse } from "@typescript-eslint/typescript-estree";
17
- async function analyzeDocDrift(options) {
18
- const files = await scanFiles(options);
19
- const issues = [];
20
- const staleMonths = options.staleMonths ?? 6;
21
- const staleSeconds = staleMonths * 30 * 24 * 60 * 60;
22
- let uncommentedExports = 0;
23
- let totalExports = 0;
24
- let outdatedComments = 0;
25
- let undocumentedComplexity = 0;
26
- const now = Math.floor(Date.now() / 1e3);
27
- let processed = 0;
28
- for (const file of files) {
29
- processed++;
30
- options.onProgress?.(processed, files.length, `doc-drift: analyzing files`);
31
- let code;
32
- try {
33
- code = readFileSync(file, "utf-8");
34
- } catch {
35
- continue;
36
- }
37
- let ast;
38
- try {
39
- ast = parse(code, {
40
- jsx: file.endsWith(".tsx") || file.endsWith(".jsx"),
41
- loc: true,
42
- comment: true
43
- });
44
- } catch {
45
- continue;
46
- }
47
- const comments = ast.comments || [];
48
- let fileLineStamps;
49
- for (const node of ast.body) {
50
- if (node.type === "ExportNamedDeclaration" || node.type === "ExportDefaultDeclaration") {
51
- const decl = node.declaration;
52
- if (!decl) continue;
53
- if (decl.type === "FunctionDeclaration" || decl.type === "ClassDeclaration" || decl.type === "VariableDeclaration") {
54
- totalExports++;
55
- const nodeLine = node.loc.start.line;
56
- const jsdocs = comments.filter(
57
- (c) => c.type === "Block" && c.value.startsWith("*") && c.loc.end.line === nodeLine - 1
58
- );
59
- if (jsdocs.length === 0) {
60
- uncommentedExports++;
61
- if (decl.type === "FunctionDeclaration" && decl.body?.loc) {
62
- const lines = decl.body.loc.end.line - decl.body.loc.start.line;
63
- if (lines > 20) undocumentedComplexity++;
64
- }
65
- } else {
66
- const jsdoc = jsdocs[0];
67
- const jsdocText = jsdoc.value;
68
- if (decl.type === "FunctionDeclaration") {
69
- const params = decl.params.map((p) => p.name || p.left && p.left.name).filter(Boolean);
70
- const paramTags = Array.from(
71
- jsdocText.matchAll(/@param\s+(?:\{[^}]+\}\s+)?([a-zA-Z0-9_]+)/g)
72
- ).map((m) => m[1]);
73
- const missingParams = params.filter(
74
- (p) => !paramTags.includes(p)
75
- );
76
- if (missingParams.length > 0) {
77
- outdatedComments++;
78
- issues.push({
79
- type: "doc-drift",
80
- severity: "major",
81
- message: `JSDoc @param mismatch: function has parameters (${missingParams.join(", ")}) not documented in JSDoc.`,
82
- location: { file, line: nodeLine }
83
- });
84
- continue;
85
- }
86
- }
87
- if (!fileLineStamps) {
88
- fileLineStamps = getFileCommitTimestamps(file);
89
- }
90
- const commentModified = getLineRangeLastModifiedCached(
91
- fileLineStamps,
92
- jsdoc.loc.start.line,
93
- jsdoc.loc.end.line
94
- );
95
- const bodyModified = getLineRangeLastModifiedCached(
96
- fileLineStamps,
97
- decl.loc.start.line,
98
- decl.loc.end.line
99
- );
100
- if (commentModified > 0 && bodyModified > 0) {
101
- if (now - commentModified > staleSeconds && bodyModified - commentModified > staleSeconds / 2) {
102
- outdatedComments++;
103
- issues.push({
104
- type: "doc-drift",
105
- severity: "minor",
106
- message: `JSDoc is significantly older than the function body implementation. Code may have drifted.`,
107
- location: { file, line: jsdoc.loc.start.line }
108
- });
109
- }
110
- }
111
- }
112
- }
113
- }
114
- }
115
- }
116
- const riskResult = calculateDocDrift({
117
- uncommentedExports,
118
- totalExports,
119
- outdatedComments,
120
- undocumentedComplexity
121
- });
122
- return {
123
- summary: {
124
- filesAnalyzed: files.length,
125
- functionsAnalyzed: totalExports,
126
- score: riskResult.score,
127
- rating: riskResult.rating
128
- },
129
- issues,
130
- rawData: {
131
- uncommentedExports,
132
- totalExports,
133
- outdatedComments,
134
- undocumentedComplexity
135
- },
136
- recommendations: riskResult.recommendations
137
- };
138
- }
139
-
140
- export {
141
- __require,
142
- analyzeDocDrift
143
- };
@@ -1,129 +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 {
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
- async function analyzeDocDrift(options) {
21
- const files = await scanFiles(options);
22
- const issues = [];
23
- const staleMonths = options.staleMonths ?? 6;
24
- const staleSeconds = staleMonths * 30 * 24 * 60 * 60;
25
- let uncommentedExports = 0;
26
- let totalExports = 0;
27
- let outdatedComments = 0;
28
- let undocumentedComplexity = 0;
29
- const now = Math.floor(Date.now() / 1e3);
30
- let processed = 0;
31
- for (const file of files) {
32
- processed++;
33
- emitProgress(
34
- processed,
35
- files.length,
36
- "doc-drift",
37
- "analyzing files",
38
- options.onProgress
39
- );
40
- const parser = getParser(file);
41
- if (!parser) continue;
42
- let code;
43
- try {
44
- code = readFileSync(file, "utf-8");
45
- } catch {
46
- continue;
47
- }
48
- try {
49
- await parser.initialize();
50
- const parseResult = parser.parse(code, file);
51
- let fileLineStamps;
52
- for (const exp of parseResult.exports) {
53
- if (exp.type === "function" || exp.type === "class") {
54
- totalExports++;
55
- if (!exp.documentation) {
56
- uncommentedExports++;
57
- if (exp.loc) {
58
- const lines = exp.loc.end.line - exp.loc.start.line;
59
- if (lines > 20) undocumentedComplexity++;
60
- }
61
- } else {
62
- const doc = exp.documentation;
63
- const docContent = doc.content;
64
- if (exp.type === "function" && exp.parameters) {
65
- const params = exp.parameters;
66
- const missingParams = params.filter(
67
- (p) => !docContent.includes(p) && !docContent.toLowerCase().includes(p.toLowerCase())
68
- );
69
- if (missingParams.length > 0 && params.length > 2) {
70
- outdatedComments++;
71
- issues.push({
72
- type: IssueType.DocDrift,
73
- severity: Severity.Major,
74
- message: `Documentation mismatch: function parameters (${missingParams.join(", ")}) are not mentioned in the docs.`,
75
- location: { file, line: exp.loc?.start.line || 1 }
76
- });
77
- continue;
78
- }
79
- }
80
- if (exp.loc) {
81
- if (!fileLineStamps) {
82
- fileLineStamps = getFileCommitTimestamps(file);
83
- }
84
- const bodyModified = getLineRangeLastModifiedCached(
85
- fileLineStamps,
86
- exp.loc.start.line,
87
- exp.loc.end.line
88
- );
89
- if (bodyModified > 0) {
90
- if (now - bodyModified < staleSeconds / 4 && exp.documentation.isStale === true) {
91
- }
92
- }
93
- }
94
- }
95
- }
96
- }
97
- } catch (error) {
98
- console.warn(`Doc-drift: Failed to parse ${file}: ${error}`);
99
- continue;
100
- }
101
- }
102
- const riskResult = calculateDocDrift({
103
- uncommentedExports,
104
- totalExports,
105
- outdatedComments,
106
- undocumentedComplexity
107
- });
108
- return {
109
- summary: {
110
- filesAnalyzed: files.length,
111
- functionsAnalyzed: totalExports,
112
- score: riskResult.score,
113
- rating: riskResult.rating
114
- },
115
- issues,
116
- rawData: {
117
- uncommentedExports,
118
- totalExports,
119
- outdatedComments,
120
- undocumentedComplexity
121
- },
122
- recommendations: riskResult.recommendations
123
- };
124
- }
125
-
126
- export {
127
- __require,
128
- analyzeDocDrift
129
- };
@@ -1,165 +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 = ["node_modules", "dist", ".git", "coverage", ".turbo", "build"];
16
- function collectFiles(dir, options, depth = 0) {
17
- if (depth > 20) return [];
18
- const excludes = [...DEFAULT_EXCLUDES, ...options.exclude ?? []];
19
- let entries;
20
- try {
21
- entries = readdirSync(dir);
22
- } catch {
23
- return [];
24
- }
25
- const files = [];
26
- for (const entry of entries) {
27
- if (excludes.some((ex) => entry === ex || entry.includes(ex))) continue;
28
- const full = join(dir, entry);
29
- let stat;
30
- try {
31
- stat = statSync(full);
32
- } catch {
33
- continue;
34
- }
35
- if (stat.isDirectory()) {
36
- files.push(...collectFiles(full, options, depth + 1));
37
- } else if (stat.isFile() && SRC_EXTENSIONS.has(extname(full))) {
38
- if (!options.include || options.include.some((p) => full.includes(p))) {
39
- files.push(full);
40
- }
41
- }
42
- }
43
- return files;
44
- }
45
- function getLineRangeLastModified(file, startLine, endLine) {
46
- try {
47
- const output = execSync(`git log -1 --format=%ct -L ${startLine},${endLine}:"${file}"`, {
48
- encoding: "utf-8",
49
- stdio: ["ignore", "pipe", "ignore"]
50
- });
51
- const match = output.trim().split("\n")[0];
52
- if (match && !isNaN(parseInt(match, 10))) {
53
- return parseInt(match, 10);
54
- }
55
- } catch {
56
- }
57
- return 0;
58
- }
59
- async function analyzeDocDrift(options) {
60
- const rootDir = options.rootDir;
61
- const files = collectFiles(rootDir, options);
62
- const staleMonths = options.staleMonths ?? 6;
63
- const staleSeconds = staleMonths * 30 * 24 * 60 * 60;
64
- let uncommentedExports = 0;
65
- let totalExports = 0;
66
- let outdatedComments = 0;
67
- let undocumentedComplexity = 0;
68
- const issues = [];
69
- const now = Math.floor(Date.now() / 1e3);
70
- for (const file of files) {
71
- let code;
72
- try {
73
- code = readFileSync(file, "utf-8");
74
- } catch {
75
- continue;
76
- }
77
- let ast;
78
- try {
79
- ast = parse(code, {
80
- jsx: file.endsWith(".tsx") || file.endsWith(".jsx"),
81
- loc: true,
82
- comment: true
83
- });
84
- } catch {
85
- continue;
86
- }
87
- const comments = ast.comments || [];
88
- for (const node of ast.body) {
89
- if (node.type === "ExportNamedDeclaration" || node.type === "ExportDefaultDeclaration") {
90
- const decl = node.declaration;
91
- if (!decl) continue;
92
- if (decl.type === "FunctionDeclaration" || decl.type === "ClassDeclaration" || decl.type === "VariableDeclaration") {
93
- totalExports++;
94
- const nodeLine = node.loc.start.line;
95
- const jsdocs = comments.filter((c) => c.type === "Block" && c.value.startsWith("*") && c.loc.end.line === nodeLine - 1);
96
- if (jsdocs.length === 0) {
97
- uncommentedExports++;
98
- if (decl.type === "FunctionDeclaration" && decl.body?.loc) {
99
- const lines = decl.body.loc.end.line - decl.body.loc.start.line;
100
- if (lines > 20) undocumentedComplexity++;
101
- }
102
- } else {
103
- const jsdoc = jsdocs[0];
104
- const jsdocText = jsdoc.value;
105
- if (decl.type === "FunctionDeclaration") {
106
- const params = decl.params.map((p) => p.name || p.left && p.left.name).filter(Boolean);
107
- const paramTags = Array.from(jsdocText.matchAll(/@param\s+(?:\{[^}]+\}\s+)?([a-zA-Z0-9_]+)/g)).map((m) => m[1]);
108
- const missingParams = params.filter((p) => !paramTags.includes(p));
109
- if (missingParams.length > 0) {
110
- outdatedComments++;
111
- issues.push({
112
- type: "doc-drift",
113
- severity: "major",
114
- message: `JSDoc @param mismatch: function has parameters (${missingParams.join(", ")}) not documented in JSDoc.`,
115
- location: { file, line: nodeLine }
116
- });
117
- continue;
118
- }
119
- }
120
- const commentModified = getLineRangeLastModified(file, jsdoc.loc.start.line, jsdoc.loc.end.line);
121
- const bodyModified = getLineRangeLastModified(file, decl.loc.start.line, decl.loc.end.line);
122
- if (commentModified > 0 && bodyModified > 0) {
123
- if (now - commentModified > staleSeconds && bodyModified - commentModified > staleSeconds / 2) {
124
- outdatedComments++;
125
- issues.push({
126
- type: "doc-drift",
127
- severity: "minor",
128
- message: `JSDoc is significantly older than the function body implementation. Code may have drifted.`,
129
- location: { file, line: jsdoc.loc.start.line }
130
- });
131
- }
132
- }
133
- }
134
- }
135
- }
136
- }
137
- }
138
- const riskResult = calculateDocDrift({
139
- uncommentedExports,
140
- totalExports,
141
- outdatedComments,
142
- undocumentedComplexity
143
- });
144
- return {
145
- summary: {
146
- filesAnalyzed: files.length,
147
- functionsAnalyzed: totalExports,
148
- score: riskResult.score,
149
- rating: riskResult.rating
150
- },
151
- issues,
152
- rawData: {
153
- uncommentedExports,
154
- totalExports,
155
- outdatedComments,
156
- undocumentedComplexity
157
- },
158
- recommendations: riskResult.recommendations
159
- };
160
- }
161
-
162
- export {
163
- __require,
164
- analyzeDocDrift
165
- };
@@ -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
- };