Skip to content

Instantly share code, notes, and snippets.

@tusharxoxoxo
Last active February 20, 2025 07:44
Show Gist options
  • Save tusharxoxoxo/ca8812602ac91596523b8d70485f336b to your computer and use it in GitHub Desktop.
Save tusharxoxoxo/ca8812602ac91596523b8d70485f336b to your computer and use it in GitHub Desktop.
//1st implementation, without using treesitter
import * as fs from 'fs';
import * as path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
function isJSDocComment(comment: string): boolean {
return comment.startsWith('/**') && comment.endsWith('*/');
}
function extractJSDocAndFunctions(code: string): { functions: Array<{ name: string; hasJSDoc: boolean }> } {
const lines = code.split('\n');
const functions: Array<{ name: string; hasJSDoc: boolean }> = [];
let currentJSDoc = '';
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
if (line.startsWith('/**')) {
currentJSDoc = line;
while (i + 1 < lines.length && !lines[i + 1].includes('*/')) {
i++;
currentJSDoc += '\n' + lines[i];
}
if (i + 1 < lines.length && lines[i + 1].includes('*/')) {
i++;
currentJSDoc += '\n' + lines[i].trim();
}
}
else if (line.startsWith('function')) {
const functionMatch = line.match(/function\s+(\w+)/);
if (functionMatch) {
functions.push({
name: functionMatch[1],
hasJSDoc: isJSDocComment(currentJSDoc)
});
}
currentJSDoc = '';
}
}
return { functions };
}
function analyzeJSDocCoverage(filePath: string): void {
const code = fs.readFileSync(filePath, 'utf8');
const { functions } = extractJSDocAndFunctions(code);
const totalFunctions = functions.length;
const functionsWithJSDoc = functions.filter(f => f.hasJSDoc).length;
const coverage = (functionsWithJSDoc / totalFunctions) * 100;
console.log('JSDoc Coverage Analysis:');
console.log('------------------------');
console.log(`Total functions: ${totalFunctions}`);
console.log(`Functions with JSDoc: ${functionsWithJSDoc}`);
console.log(`Coverage: ${coverage.toFixed(2)}%`);
console.log('\nDetailed Analysis:');
console.log('------------------------');
functions.forEach(func => {
console.log(`${func.name}: ${func.hasJSDoc ? '✓ Has JSDoc' : '✗ No JSDoc'}`);
});
}
const filePath = path.join(__dirname, 'jj.js');
analyzeJSDocCoverage(filePath);
//another implementation using treesitter
import * as fs from 'fs';
import * as path from 'path';
import { fileURLToPath } from 'url';
import Parser from 'tree-sitter';
import JavaScript from 'tree-sitter-javascript';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
interface FunctionInfo {
name: string;
hasJSDoc: boolean;
startLine: number;
}
function isJSDocComment(comment: string): boolean {
return comment.trim().startsWith('/**') && comment.trim().endsWith('*/');
}
function findPrecedingComment(code: string, startLine: number): string {
const lines = code.split('\n');
let comment = '';
let currentLine = startLine - 1;
while (currentLine >= 0) {
const line = lines[currentLine].trim();
if (line === '') {
currentLine--;
continue;
}
if (line.startsWith('/*')) {
comment = line;
while (currentLine + 1 < lines.length && !lines[currentLine].includes('*/')) {
currentLine++;
comment += '\n' + lines[currentLine];
}
break;
}
break;
}
return comment;
}
function extractJSDocAndFunctions(code: string): { functions: FunctionInfo[] } {
const parser = new Parser();
parser.setLanguage(JavaScript);
const tree = parser.parse(code);
const functions: FunctionInfo[] = [];
// Query to find all function declarations and function expressions
const query = JavaScript.query(`
(function_declaration name: (identifier) @func_name) @function
(function name: (identifier) @func_name) @function
(method_definition name: (property_identifier) @func_name) @function
(arrow_function) @function
`);
const matches = query.matches(tree.rootNode);
for (const match of matches) {
const funcNode = match.captures.find(capture => capture.name === 'function')?.node;
const nameNode = match.captures.find(capture => capture.name === 'func_name')?.node;
if (funcNode) {
const startLine = funcNode.startPosition.row;
const comment = findPrecedingComment(code, startLine);
functions.push({
name: nameNode ? nameNode.text : '(anonymous)',
hasJSDoc: isJSDocComment(comment),
startLine
});
}
}
return { functions };
}
function analyzeJSDocCoverage(filePath: string): void {
const code = fs.readFileSync(filePath, 'utf8');
const { functions } = extractJSDocAndFunctions(code);
// Sort functions by line number for clearer output
functions.sort((a, b) => a.startLine - b.startLine);
const totalFunctions = functions.length;
const functionsWithJSDoc = functions.filter(f => f.hasJSDoc).length;
const coverage = totalFunctions > 0 ? (functionsWithJSDoc / totalFunctions) * 100 : 0;
console.log('JSDoc Coverage Analysis:');
console.log('------------------------');
console.log(`Total functions: ${totalFunctions}`);
console.log(`Functions with JSDoc: ${functionsWithJSDoc}`);
console.log(`Coverage: ${coverage.toFixed(2)}%`);
console.log('\nDetailed Analysis:');
console.log('------------------------');
functions.forEach(func => {
console.log(`${func.name} (line ${func.startLine + 1}): ${func.hasJSDoc ? '✓ Has JSDoc' : '✗ No JSDoc'}`);
});
}
const filePath = path.join(__dirname, 'test.js');
analyzeJSDocCoverage(filePath);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment