@63klabs/cache-data 1.3.6 → 1.3.7

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,856 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Documentation Audit and Validation Script
5
- *
6
- * Scans all source files in src/ directory to:
7
- * - Identify all exported functions, methods, and classes
8
- * - Parse existing JSDoc comments
9
- * - Validate JSDoc completeness (all required tags present)
10
- * - Validate JSDoc accuracy (parameters match code)
11
- * - Validate links in all documentation files
12
- * - Validate example code syntax
13
- * - Generate comprehensive validation report
14
- *
15
- * Requirements: 1.1, 9.5, 10.5
16
- */
17
-
18
- import fs from 'fs';
19
- import path from 'path';
20
- import { fileURLToPath } from 'url';
21
- import { exec } from 'child_process';
22
- import { promisify } from 'util';
23
-
24
- const execAsync = promisify(exec);
25
-
26
- const __filename = fileURLToPath(import.meta.url);
27
- const __dirname = path.dirname(__filename);
28
-
29
- // Configuration
30
- const SRC_DIR = path.join(__dirname, '..', 'src');
31
- const DOCS_DIR = path.join(__dirname, '..', 'docs');
32
- const ROOT_DIR = path.join(__dirname, '..');
33
- const OUTPUT_FILE = path.join(__dirname, '..', '.kiro', 'specs', '1-3-6-documentation-enhancement', 'audit-report.json');
34
-
35
- /**
36
- * Recursively get all JavaScript files in a directory
37
- * @param {string} dir Directory to scan
38
- * @param {Array<string>} fileList Accumulated file list
39
- * @returns {Array<string>} List of file paths
40
- */
41
- function getJavaScriptFiles(dir, fileList = []) {
42
- const files = fs.readdirSync(dir);
43
-
44
- files.forEach(file => {
45
- const filePath = path.join(dir, file);
46
- const stat = fs.statSync(filePath);
47
-
48
- if (stat.isDirectory()) {
49
- getJavaScriptFiles(filePath, fileList);
50
- } else if (file.endsWith('.js')) {
51
- fileList.push(filePath);
52
- }
53
- });
54
-
55
- return fileList;
56
- }
57
-
58
- /**
59
- * Extract JSDoc comment block preceding a code element
60
- * @param {string} content File content
61
- * @param {number} position Position of the code element
62
- * @returns {string|null} JSDoc comment or null
63
- */
64
- function extractJSDocBefore(content, position) {
65
- // Look backwards from position to find JSDoc comment
66
- const beforeContent = content.substring(0, position);
67
- const jsdocPattern = /\/\*\*[\s\S]*?\*\//g;
68
- const matches = [...beforeContent.matchAll(jsdocPattern)];
69
-
70
- if (matches.length === 0) return null;
71
-
72
- // Get the last JSDoc comment before this position
73
- const lastMatch = matches[matches.length - 1];
74
- const jsdocEnd = lastMatch.index + lastMatch[0].length;
75
-
76
- // Check if there's only whitespace between JSDoc and the code element
77
- const betweenContent = content.substring(jsdocEnd, position);
78
- if (/^\s*$/.test(betweenContent)) {
79
- return lastMatch[0];
80
- }
81
-
82
- return null;
83
- }
84
-
85
- /**
86
- * Parse JSDoc comment to extract tags
87
- * @param {string} jsdoc JSDoc comment block
88
- * @returns {Object} Parsed JSDoc data
89
- */
90
- function parseJSDoc(jsdoc) {
91
- if (!jsdoc) {
92
- return {
93
- description: null,
94
- params: [],
95
- returns: null,
96
- examples: [],
97
- throws: []
98
- };
99
- }
100
-
101
- const lines = jsdoc.split('\n').map(line => line.trim().replace(/^\*\s?/, ''));
102
-
103
- const parsed = {
104
- description: '',
105
- params: [],
106
- returns: null,
107
- examples: [],
108
- throws: []
109
- };
110
-
111
- let currentTag = null;
112
- let currentContent = '';
113
-
114
- lines.forEach(line => {
115
- // Check for tags
116
- const paramMatch = line.match(/^@param\s+\{([^}]+)\}\s+(\[?[\w.]+\]?)\s*-?\s*(.*)/);
117
- const returnsMatch = line.match(/^@returns?\s+\{([^}]+)\}\s*(.*)/);
118
- const exampleMatch = line.match(/^@example/);
119
- const throwsMatch = line.match(/^@throws?\s+\{([^}]+)\}\s*(.*)/);
120
-
121
- if (paramMatch) {
122
- if (currentTag === 'description' && currentContent.trim()) {
123
- parsed.description = currentContent.trim();
124
- }
125
- currentTag = 'param';
126
- const [, type, name, description] = paramMatch;
127
- const isOptional = name.startsWith('[') && name.endsWith(']');
128
- const cleanName = name.replace(/[\[\]]/g, '').split('=')[0];
129
- const defaultValue = name.includes('=') ? name.split('=')[1].replace(']', '') : null;
130
-
131
- parsed.params.push({
132
- name: cleanName,
133
- type,
134
- description: description || '',
135
- optional: isOptional,
136
- defaultValue
137
- });
138
- } else if (returnsMatch) {
139
- if (currentTag === 'description' && currentContent.trim()) {
140
- parsed.description = currentContent.trim();
141
- }
142
- currentTag = 'returns';
143
- const [, type, description] = returnsMatch;
144
- parsed.returns = {
145
- type,
146
- description: description || ''
147
- };
148
- } else if (exampleMatch) {
149
- if (currentTag === 'description' && currentContent.trim()) {
150
- parsed.description = currentContent.trim();
151
- }
152
- currentTag = 'example';
153
- currentContent = '';
154
- } else if (throwsMatch) {
155
- if (currentTag === 'description' && currentContent.trim()) {
156
- parsed.description = currentContent.trim();
157
- }
158
- currentTag = 'throws';
159
- const [, type, description] = throwsMatch;
160
- parsed.throws.push({
161
- type,
162
- description: description || ''
163
- });
164
- } else if (line && !line.startsWith('/**') && !line.startsWith('*/')) {
165
- // Content line
166
- if (currentTag === 'example') {
167
- currentContent += line + '\n';
168
- } else if (currentTag === 'description' || !currentTag) {
169
- currentTag = 'description';
170
- currentContent += line + ' ';
171
- } else if (currentTag === 'param' && parsed.params.length > 0) {
172
- // Continuation of param description
173
- parsed.params[parsed.params.length - 1].description += ' ' + line;
174
- } else if (currentTag === 'returns' && parsed.returns) {
175
- // Continuation of returns description
176
- parsed.returns.description += ' ' + line;
177
- } else if (currentTag === 'throws' && parsed.throws.length > 0) {
178
- // Continuation of throws description
179
- parsed.throws[parsed.throws.length - 1].description += ' ' + line;
180
- }
181
- }
182
- });
183
-
184
- // Handle any remaining content
185
- if (currentTag === 'description' && currentContent.trim()) {
186
- parsed.description = currentContent.trim();
187
- } else if (currentTag === 'example' && currentContent.trim()) {
188
- parsed.examples.push(currentContent.trim());
189
- }
190
-
191
- return parsed;
192
- }
193
-
194
- /**
195
- * Extract function parameters from function signature
196
- * @param {string} signature Function signature
197
- * @returns {Array<string>} Parameter names
198
- */
199
- function extractFunctionParams(signature) {
200
- const paramMatch = signature.match(/\(([^)]*)\)/);
201
- if (!paramMatch || !paramMatch[1].trim()) return [];
202
-
203
- return paramMatch[1]
204
- .split(',')
205
- .map(p => p.trim().split('=')[0].trim())
206
- .filter(p => p.length > 0);
207
- }
208
-
209
- /**
210
- * Analyze a single source file
211
- * @param {string} filePath Path to source file
212
- * @returns {Object} Analysis results
213
- */
214
- function analyzeFile(filePath) {
215
- const content = fs.readFileSync(filePath, 'utf-8');
216
- const relativePath = path.relative(SRC_DIR, filePath);
217
-
218
- const exports = [];
219
-
220
- // Pattern for class declarations
221
- const classPattern = /class\s+(\w+)/g;
222
- let match;
223
-
224
- while ((match = classPattern.exec(content)) !== null) {
225
- const className = match[1];
226
- const jsdoc = extractJSDocBefore(content, match.index);
227
- const parsedJSDoc = parseJSDoc(jsdoc);
228
-
229
- exports.push({
230
- type: 'class',
231
- name: className,
232
- hasJSDoc: jsdoc !== null,
233
- jsdoc: parsedJSDoc,
234
- position: match.index
235
- });
236
-
237
- // Find methods in this class
238
- const classStart = match.index;
239
- const classEnd = content.indexOf('}', classStart);
240
- const classBody = content.substring(classStart, classEnd);
241
-
242
- // Static methods
243
- const staticMethodPattern = /static\s+(\w+)\s*\([^)]*\)/g;
244
- let methodMatch;
245
- while ((methodMatch = staticMethodPattern.exec(classBody)) !== null) {
246
- const methodName = methodMatch[1];
247
- const methodJsdoc = extractJSDocBefore(classBody, methodMatch.index);
248
- const parsedMethodJSDoc = parseJSDoc(methodJsdoc);
249
- const params = extractFunctionParams(methodMatch[0]);
250
-
251
- exports.push({
252
- type: 'method',
253
- name: `${className}.${methodName}`,
254
- className: className,
255
- isStatic: true,
256
- hasJSDoc: methodJsdoc !== null,
257
- jsdoc: parsedMethodJSDoc,
258
- actualParams: params,
259
- position: classStart + methodMatch.index
260
- });
261
- }
262
-
263
- // Instance methods (excluding constructor and private methods)
264
- const instanceMethodPattern = /(?:async\s+)?(\w+)\s*\([^)]*\)\s*\{/g;
265
- while ((methodMatch = instanceMethodPattern.exec(classBody)) !== null) {
266
- const methodName = methodMatch[1];
267
- // Skip constructor, static methods, and private methods
268
- if (methodName === 'constructor' || methodName.startsWith('#') || classBody.substring(Math.max(0, methodMatch.index - 10), methodMatch.index).includes('static')) {
269
- continue;
270
- }
271
-
272
- const methodJsdoc = extractJSDocBefore(classBody, methodMatch.index);
273
- const parsedMethodJSDoc = parseJSDoc(methodJsdoc);
274
- const params = extractFunctionParams(methodMatch[0]);
275
-
276
- exports.push({
277
- type: 'method',
278
- name: `${className}.${methodName}`,
279
- className: className,
280
- isStatic: false,
281
- hasJSDoc: methodJsdoc !== null,
282
- jsdoc: parsedMethodJSDoc,
283
- actualParams: params,
284
- position: classStart + methodMatch.index
285
- });
286
- }
287
- }
288
-
289
- // Pattern for exported functions (const functionName = ...)
290
- const constFunctionPattern = /const\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>/g;
291
- while ((match = constFunctionPattern.exec(content)) !== null) {
292
- const functionName = match[1];
293
- const jsdoc = extractJSDocBefore(content, match.index);
294
- const parsedJSDoc = parseJSDoc(jsdoc);
295
- const params = extractFunctionParams(match[0]);
296
-
297
- exports.push({
298
- type: 'function',
299
- name: functionName,
300
- hasJSDoc: jsdoc !== null,
301
- jsdoc: parsedJSDoc,
302
- actualParams: params,
303
- position: match.index
304
- });
305
- }
306
-
307
- // Pattern for function declarations
308
- const functionPattern = /(?:async\s+)?function\s+(\w+)\s*\([^)]*\)/g;
309
- while ((match = functionPattern.exec(content)) !== null) {
310
- const functionName = match[1];
311
- const jsdoc = extractJSDocBefore(content, match.index);
312
- const parsedJSDoc = parseJSDoc(jsdoc);
313
- const params = extractFunctionParams(match[0]);
314
-
315
- exports.push({
316
- type: 'function',
317
- name: functionName,
318
- hasJSDoc: jsdoc !== null,
319
- jsdoc: parsedJSDoc,
320
- actualParams: params,
321
- position: match.index
322
- });
323
- }
324
-
325
- // Check module.exports to determine what's actually exported
326
- const moduleExportsPattern = /module\.exports\s*=\s*\{([^}]+)\}/;
327
- const exportsMatch = content.match(moduleExportsPattern);
328
- let exportedNames = [];
329
-
330
- if (exportsMatch) {
331
- exportedNames = exportsMatch[1]
332
- .split(',')
333
- .map(e => e.trim().split(':')[0].trim())
334
- .filter(e => e.length > 0);
335
- }
336
-
337
- // Filter to only exported items
338
- const exportedItems = exports.filter(item => {
339
- if (exportedNames.length === 0) return true; // If we can't determine exports, include all
340
- return exportedNames.includes(item.name) || exportedNames.includes(item.name.split('.')[0]);
341
- });
342
-
343
- return {
344
- filePath: relativePath,
345
- exports: exportedItems
346
- };
347
- }
348
-
349
- /**
350
- * Analyze JSDoc completeness
351
- * @param {Object} item Export item
352
- * @returns {Object} Completeness analysis
353
- */
354
- function analyzeCompleteness(item) {
355
- const issues = [];
356
-
357
- if (!item.hasJSDoc) {
358
- return {
359
- complete: false,
360
- issues: ['Missing JSDoc comment']
361
- };
362
- }
363
-
364
- const jsdoc = item.jsdoc;
365
-
366
- // Check description
367
- if (!jsdoc.description || jsdoc.description.trim().length === 0) {
368
- issues.push('Missing description');
369
- }
370
-
371
- // Check @param tags
372
- if (item.actualParams && item.actualParams.length > 0) {
373
- const documentedParams = jsdoc.params.map(p => p.name);
374
- const missingParams = item.actualParams.filter(p => !documentedParams.includes(p));
375
-
376
- if (missingParams.length > 0) {
377
- issues.push(`Missing @param tags for: ${missingParams.join(', ')}`);
378
- }
379
-
380
- // Check for incomplete param documentation
381
- jsdoc.params.forEach(param => {
382
- if (!param.type || param.type.trim() === '') {
383
- issues.push(`@param ${param.name} missing type`);
384
- }
385
- if (!param.description || param.description.trim() === '') {
386
- issues.push(`@param ${param.name} missing description`);
387
- }
388
- });
389
- }
390
-
391
- // Check @returns tag (for functions and methods, not classes)
392
- if (item.type !== 'class' && !jsdoc.returns) {
393
- issues.push('Missing @returns tag');
394
- } else if (item.type !== 'class' && jsdoc.returns) {
395
- if (!jsdoc.returns.type || jsdoc.returns.type.trim() === '') {
396
- issues.push('@returns missing type');
397
- }
398
- if (!jsdoc.returns.description || jsdoc.returns.description.trim() === '') {
399
- issues.push('@returns missing description');
400
- }
401
- }
402
-
403
- // Check @example tag
404
- if (jsdoc.examples.length === 0) {
405
- issues.push('Missing @example tag');
406
- }
407
-
408
- // Note: @throws is optional, so we don't check for it
409
-
410
- return {
411
- complete: issues.length === 0,
412
- issues
413
- };
414
- }
415
-
416
- /**
417
- * Validate JSDoc accuracy - check if documented params match actual params
418
- * @param {Object} item Export item
419
- * @returns {Object} Accuracy validation results
420
- */
421
- function validateJSDocAccuracy(item) {
422
- const issues = [];
423
-
424
- if (!item.hasJSDoc || !item.jsdoc) {
425
- return { accurate: true, issues: [] }; // Can't validate if no JSDoc
426
- }
427
-
428
- const jsdoc = item.jsdoc;
429
- const actualParams = item.actualParams || [];
430
- const documentedParams = jsdoc.params.map(p => p.name);
431
-
432
- // Check for hallucinated parameters (documented but not in actual signature)
433
- const hallucinatedParams = documentedParams.filter(p => !actualParams.includes(p));
434
- if (hallucinatedParams.length > 0) {
435
- issues.push(`Hallucinated parameters (not in actual signature): ${hallucinatedParams.join(', ')}`);
436
- }
437
-
438
- // Check for parameter order mismatch
439
- const commonParams = actualParams.filter(p => documentedParams.includes(p));
440
- if (commonParams.length > 0) {
441
- const actualOrder = commonParams.map(p => actualParams.indexOf(p));
442
- const docOrder = commonParams.map(p => documentedParams.indexOf(p));
443
-
444
- for (let i = 0; i < actualOrder.length - 1; i++) {
445
- if (actualOrder[i] > actualOrder[i + 1] && docOrder[i] < docOrder[i + 1]) {
446
- issues.push('Parameter order in JSDoc does not match actual signature');
447
- break;
448
- }
449
- }
450
- }
451
-
452
- return {
453
- accurate: issues.length === 0,
454
- issues
455
- };
456
- }
457
-
458
- /**
459
- * Get all markdown files recursively
460
- * @param {string} dir Directory to scan
461
- * @param {Array<string>} fileList Accumulated file list
462
- * @returns {Array<string>} List of markdown file paths
463
- */
464
- function getMarkdownFiles(dir, fileList = []) {
465
- if (!fs.existsSync(dir)) return fileList;
466
-
467
- const files = fs.readdirSync(dir);
468
-
469
- files.forEach(file => {
470
- const filePath = path.join(dir, file);
471
- const stat = fs.statSync(filePath);
472
-
473
- if (stat.isDirectory() && !file.startsWith('.') && file !== 'node_modules') {
474
- getMarkdownFiles(filePath, fileList);
475
- } else if (file.endsWith('.md')) {
476
- fileList.push(filePath);
477
- }
478
- });
479
-
480
- return fileList;
481
- }
482
-
483
- /**
484
- * Extract links from markdown content
485
- * @param {string} content Markdown content
486
- * @returns {Array<Object>} Array of links with line numbers
487
- */
488
- function extractLinks(content) {
489
- const links = [];
490
- const lines = content.split('\n');
491
-
492
- // Match markdown links [text](url) and bare URLs
493
- const linkPattern = /\[([^\]]+)\]\(([^)]+)\)/g;
494
-
495
- lines.forEach((line, index) => {
496
- let match;
497
- while ((match = linkPattern.exec(line)) !== null) {
498
- links.push({
499
- text: match[1],
500
- url: match[2],
501
- line: index + 1
502
- });
503
- }
504
- });
505
-
506
- return links;
507
- }
508
-
509
- /**
510
- * Validate a single link
511
- * @param {string} link Link URL
512
- * @param {string} sourceFile Source file containing the link
513
- * @returns {Object} Validation result
514
- */
515
- function validateLink(link, sourceFile) {
516
- // Skip anchor links
517
- if (link.startsWith('#')) {
518
- return { valid: true, type: 'anchor' };
519
- }
520
-
521
- // External URLs
522
- if (link.startsWith('http://') || link.startsWith('https://')) {
523
- return { valid: true, type: 'external', note: 'External link not validated' };
524
- }
525
-
526
- // Internal relative links
527
- const sourceDir = path.dirname(sourceFile);
528
- const targetPath = path.resolve(sourceDir, link.split('#')[0]); // Remove anchor
529
-
530
- if (fs.existsSync(targetPath)) {
531
- return { valid: true, type: 'internal' };
532
- }
533
-
534
- // Try relative to root
535
- const rootPath = path.resolve(ROOT_DIR, link.split('#')[0]);
536
- if (fs.existsSync(rootPath)) {
537
- return { valid: true, type: 'internal' };
538
- }
539
-
540
- return { valid: false, type: 'internal', error: 'File not found' };
541
- }
542
-
543
- /**
544
- * Validate links in all documentation files
545
- * @returns {Object} Link validation results
546
- */
547
- function validateDocumentationLinks() {
548
- console.log('\nValidating documentation links...');
549
-
550
- const markdownFiles = [
551
- ...getMarkdownFiles(DOCS_DIR),
552
- path.join(ROOT_DIR, 'README.md'),
553
- path.join(ROOT_DIR, 'CHANGELOG.md'),
554
- path.join(ROOT_DIR, 'SECURITY.md')
555
- ].filter(f => fs.existsSync(f));
556
-
557
- const brokenLinks = [];
558
- let totalLinks = 0;
559
- let validLinks = 0;
560
-
561
- markdownFiles.forEach(file => {
562
- const content = fs.readFileSync(file, 'utf-8');
563
- const links = extractLinks(content);
564
-
565
- links.forEach(link => {
566
- totalLinks++;
567
- const validation = validateLink(link.url, file);
568
-
569
- if (validation.valid) {
570
- validLinks++;
571
- } else {
572
- brokenLinks.push({
573
- file: path.relative(ROOT_DIR, file),
574
- line: link.line,
575
- text: link.text,
576
- url: link.url,
577
- error: validation.error
578
- });
579
- }
580
- });
581
- });
582
-
583
- console.log(` Checked ${totalLinks} links in ${markdownFiles.length} files`);
584
- console.log(` Valid: ${validLinks}, Broken: ${brokenLinks.length}`);
585
-
586
- return {
587
- totalLinks,
588
- validLinks,
589
- brokenLinks,
590
- filesChecked: markdownFiles.length
591
- };
592
- }
593
-
594
- /**
595
- * Extract code examples from markdown content
596
- * @param {string} content Markdown content
597
- * @returns {Array<Object>} Array of code examples
598
- */
599
- function extractCodeExamples(content) {
600
- const examples = [];
601
- const lines = content.split('\n');
602
- let inCodeBlock = false;
603
- let currentExample = null;
604
- let lineNumber = 0;
605
-
606
- lines.forEach((line, index) => {
607
- if (line.trim().startsWith('```javascript') || line.trim().startsWith('```js')) {
608
- inCodeBlock = true;
609
- lineNumber = index + 1;
610
- currentExample = {
611
- code: '',
612
- startLine: lineNumber
613
- };
614
- } else if (line.trim() === '```' && inCodeBlock) {
615
- inCodeBlock = false;
616
- if (currentExample && currentExample.code.trim()) {
617
- examples.push(currentExample);
618
- }
619
- currentExample = null;
620
- } else if (inCodeBlock && currentExample) {
621
- currentExample.code += line + '\n';
622
- }
623
- });
624
-
625
- return examples;
626
- }
627
-
628
- /**
629
- * Validate JavaScript code syntax
630
- * @param {string} code JavaScript code
631
- * @returns {Object} Validation result
632
- */
633
- async function validateCodeSyntax(code) {
634
- // Create a temporary file
635
- const tempFile = path.join(ROOT_DIR, '.temp-validation.mjs');
636
-
637
- try {
638
- fs.writeFileSync(tempFile, code);
639
-
640
- // Try to parse with Node.js
641
- await execAsync(`node --check ${tempFile}`);
642
-
643
- return { valid: true };
644
- } catch (error) {
645
- return {
646
- valid: false,
647
- error: error.message
648
- };
649
- } finally {
650
- // Clean up temp file
651
- if (fs.existsSync(tempFile)) {
652
- fs.unlinkSync(tempFile);
653
- }
654
- }
655
- }
656
-
657
- /**
658
- * Validate example code in documentation files
659
- * @returns {Promise<Object>} Example validation results
660
- */
661
- async function validateExampleCode() {
662
- console.log('\nValidating example code...');
663
-
664
- const markdownFiles = [
665
- ...getMarkdownFiles(DOCS_DIR),
666
- path.join(ROOT_DIR, 'README.md')
667
- ].filter(f => fs.existsSync(f));
668
-
669
- const invalidExamples = [];
670
- let totalExamples = 0;
671
- let validExamples = 0;
672
-
673
- for (const file of markdownFiles) {
674
- const content = fs.readFileSync(file, 'utf-8');
675
- const examples = extractCodeExamples(content);
676
-
677
- for (const example of examples) {
678
- totalExamples++;
679
- const validation = await validateCodeSyntax(example.code);
680
-
681
- if (validation.valid) {
682
- validExamples++;
683
- } else {
684
- invalidExamples.push({
685
- file: path.relative(ROOT_DIR, file),
686
- line: example.startLine,
687
- error: validation.error,
688
- code: example.code.substring(0, 100) + (example.code.length > 100 ? '...' : '')
689
- });
690
- }
691
- }
692
- }
693
-
694
- console.log(` Checked ${totalExamples} code examples in ${markdownFiles.length} files`);
695
- console.log(` Valid: ${validExamples}, Invalid: ${invalidExamples.length}`);
696
-
697
- return {
698
- totalExamples,
699
- validExamples,
700
- invalidExamples,
701
- filesChecked: markdownFiles.length
702
- };
703
- }
704
-
705
- /**
706
- * Generate audit report
707
- */
708
- async function generateAuditReport() {
709
- console.log('Starting documentation audit and validation...\n');
710
-
711
- const files = getJavaScriptFiles(SRC_DIR);
712
- console.log(`Found ${files.length} JavaScript files\n`);
713
-
714
- const fileAnalyses = [];
715
- let totalExports = 0;
716
- let documentedExports = 0;
717
- let completeExports = 0;
718
- const missingJSDoc = [];
719
- const incompleteJSDoc = [];
720
- const inaccurateJSDoc = [];
721
-
722
- files.forEach(file => {
723
- console.log(`Analyzing: ${path.relative(SRC_DIR, file)}`);
724
- const analysis = analyzeFile(file);
725
- fileAnalyses.push(analysis);
726
-
727
- analysis.exports.forEach(item => {
728
- totalExports++;
729
-
730
- if (item.hasJSDoc) {
731
- documentedExports++;
732
- } else {
733
- missingJSDoc.push({
734
- file: analysis.filePath,
735
- name: item.name,
736
- type: item.type
737
- });
738
- }
739
-
740
- const completeness = analyzeCompleteness(item);
741
- if (completeness.complete) {
742
- completeExports++;
743
- } else if (item.hasJSDoc) {
744
- incompleteJSDoc.push({
745
- file: analysis.filePath,
746
- name: item.name,
747
- type: item.type,
748
- issues: completeness.issues
749
- });
750
- }
751
-
752
- // Validate accuracy
753
- const accuracy = validateJSDocAccuracy(item);
754
- if (!accuracy.accurate) {
755
- inaccurateJSDoc.push({
756
- file: analysis.filePath,
757
- name: item.name,
758
- type: item.type,
759
- issues: accuracy.issues
760
- });
761
- }
762
- });
763
- });
764
-
765
- // Validate documentation links
766
- const linkValidation = validateDocumentationLinks();
767
-
768
- // Validate example code
769
- const exampleValidation = await validateExampleCode();
770
-
771
- const report = {
772
- auditDate: new Date().toISOString(),
773
- summary: {
774
- totalFiles: files.length,
775
- totalPublicFunctions: totalExports,
776
- documentedFunctions: documentedExports,
777
- completeFunctions: completeExports,
778
- missingJSDocCount: missingJSDoc.length,
779
- incompleteJSDocCount: incompleteJSDoc.length,
780
- inaccurateJSDocCount: inaccurateJSDoc.length,
781
- coveragePercentage: totalExports > 0 ? ((documentedExports / totalExports) * 100).toFixed(2) : 0,
782
- completenessPercentage: totalExports > 0 ? ((completeExports / totalExports) * 100).toFixed(2) : 0,
783
- brokenLinksCount: linkValidation.brokenLinks.length,
784
- invalidExamplesCount: exampleValidation.invalidExamples.length,
785
- // Only count REAL critical errors (exclude missingJSDoc due to parser false positives)
786
- // Critical errors are: incomplete JSDoc, inaccurate JSDoc, broken links, invalid examples
787
- criticalErrors: incompleteJSDoc.length + inaccurateJSDoc.length + linkValidation.brokenLinks.length + exampleValidation.invalidExamples.length
788
- },
789
- jsdocAnalysis: {
790
- missingJSDoc,
791
- incompleteJSDoc,
792
- inaccurateJSDoc
793
- },
794
- linkValidation,
795
- exampleValidation,
796
- fileAnalyses
797
- };
798
-
799
- // Ensure output directory exists
800
- const outputDir = path.dirname(OUTPUT_FILE);
801
- if (!fs.existsSync(outputDir)) {
802
- fs.mkdirSync(outputDir, { recursive: true });
803
- }
804
-
805
- // Write report to file
806
- fs.writeFileSync(OUTPUT_FILE, JSON.stringify(report, null, 2));
807
-
808
- // Print summary
809
- console.log('\n' + '='.repeat(60));
810
- console.log('DOCUMENTATION AUDIT AND VALIDATION SUMMARY');
811
- console.log('='.repeat(60));
812
- console.log('\nJSDoc Analysis:');
813
- console.log(` Total Files Analyzed: ${report.summary.totalFiles}`);
814
- console.log(` Total Public Functions/Classes: ${report.summary.totalPublicFunctions}`);
815
- console.log(` Documented: ${report.summary.documentedFunctions} (${report.summary.coveragePercentage}%)`);
816
- console.log(` Complete Documentation: ${report.summary.completeFunctions} (${report.summary.completenessPercentage}%)`);
817
- console.log(` Missing JSDoc: ${report.summary.missingJSDocCount}`);
818
- console.log(` Incomplete JSDoc: ${report.summary.incompleteJSDocCount}`);
819
- console.log(` Inaccurate JSDoc: ${report.summary.inaccurateJSDocCount}`);
820
-
821
- console.log('\nLink Validation:');
822
- console.log(` Total Links Checked: ${linkValidation.totalLinks}`);
823
- console.log(` Valid Links: ${linkValidation.validLinks}`);
824
- console.log(` Broken Links: ${linkValidation.brokenLinks.length}`);
825
-
826
- console.log('\nExample Code Validation:');
827
- console.log(` Total Examples Checked: ${exampleValidation.totalExamples}`);
828
- console.log(` Valid Examples: ${exampleValidation.validExamples}`);
829
- console.log(` Invalid Examples: ${exampleValidation.invalidExamples.length}`);
830
-
831
- console.log('\n' + '='.repeat(60));
832
- console.log(`CRITICAL ERRORS: ${report.summary.criticalErrors}`);
833
- console.log('='.repeat(60));
834
- console.log(`\nDetailed report saved to: ${OUTPUT_FILE}\n`);
835
-
836
- // Exit with error code if critical errors found
837
- if (report.summary.criticalErrors > 0) {
838
- console.error('❌ Validation failed with critical errors');
839
- return report;
840
- } else {
841
- console.log('✅ All validation checks passed');
842
- return report;
843
- }
844
- }
845
-
846
- // Run the audit
847
- try {
848
- const report = await generateAuditReport();
849
- // Exit with error code if there are critical errors
850
- if (report.summary.criticalErrors > 0) {
851
- process.exit(1);
852
- }
853
- } catch (error) {
854
- console.error('Error during audit:', error);
855
- process.exit(1);
856
- }