readme-wiz
Version:
A CLI tool to auto-generate professional README.md files using GitHub markdown syntax
393 lines (359 loc) • 11.1 kB
JavaScript
const fs = require("fs");
const path = require("path");
const globby = require("globby");
const Parser = require("tree-sitter");
const JavaScript = require("tree-sitter-javascript");
const Java = require("tree-sitter-java");
const Python = require("tree-sitter-python");
const HTML = require("tree-sitter-html");
const CSS = require("tree-sitter-css");
const Ruby = require("tree-sitter-ruby");
const PHPModule = require("tree-sitter-php");
const Go = require("tree-sitter-go");
const CSharp = require("tree-sitter-c-sharp");
const Rust = require("tree-sitter-rust");
const TypeScript = require("tree-sitter-typescript").typescript;
const TSX = require("tree-sitter-typescript").tsx;
const CPP = require("tree-sitter-cpp");
// Enhanced node types with more granular parsing
const nodeTypes = {
".js": [
"function_declaration",
"class_declaration",
"method_definition",
"export_statement",
"import_statement",
"variable_declarator",
"call_expression",
],
".ts": [
"function_declaration",
"class_declaration",
"interface_declaration",
"type_alias_declaration",
"enum_declaration",
"decorator",
"import_statement",
],
".jsx": [
"function_declaration",
"class_declaration",
"jsx_element",
"jsx_opening_element",
],
".tsx": [
"function_declaration",
"class_declaration",
"interface_declaration",
"jsx_element",
"jsx_opening_element",
],
".java": [
"method_declaration",
"class_declaration",
"interface_declaration",
"enum_declaration",
"annotation_declaration",
"import_declaration",
],
".py": [
"function_definition",
"class_definition",
"decorated_definition",
"import_statement",
"call",
],
".html": ["element", "start_tag", "script_element", "style_element"],
".css": ["rule_set", "qualified_rule", "at_rule", "keyframes"],
".rb": ["method", "class", "module", "require"],
".php": [
"function_definition",
"class_declaration",
"namespace_use_declaration",
],
".go": ["function_declaration", "type_declaration", "import_spec"],
".cs": [
"method_declaration",
"class_declaration",
"namespace_declaration",
"using_directive",
],
".rs": ["function_item", "struct_item", "mod_item", "use_declaration"],
".cpp": [
"function_definition",
"class_specifier",
"namespace_definition",
"using_directive",
],
};
// PHP language object handling
let PHP;
try {
PHP = PHPModule?.default || PHPModule?.language || PHPModule;
if (!PHP) {
throw new Error("PHP parser not available");
}
} catch (err) {
console.log("ℹ️ PHP parsing support is coming soon!");
PHP = null;
}
// Enhanced CSS parsing with more detailed information
function parseCSSFallback(code) {
const rules = [];
const ruleMatches = code.matchAll(/([^{]+)\s*{([^}]+)}/g);
for (const match of ruleMatches) {
const selector = match[1].trim();
const properties = match[2].trim();
if (selector) {
rules.push(
`CSS Rule: ${selector} with ${
properties.split(";").filter((p) => p.trim()).length
} properties`
);
}
}
return rules;
}
// Enhanced Vue parsing with component analysis
function parseVueComponent(code) {
const results = [];
// Template analysis
const templateMatch = code.match(/<template>([\s\S]*?)<\/template>/i);
if (templateMatch) {
const templateContent = templateMatch[1];
const elementCount = (templateContent.match(/<\w+/g) || []).length;
results.push(`Vue Template: ${elementCount} elements`);
}
// Script analysis
const scriptMatch = code.match(/<script\b[^>]*>([\s\S]*?)<\/script>/i);
if (scriptMatch) {
const scriptContent = scriptMatch[1];
const componentNameMatch = scriptContent.match(
/name\s*:\s*["']([^"']+)["']/
);
const componentName = componentNameMatch
? componentNameMatch[1]
: "Anonymous";
const methodMatches = scriptContent.matchAll(/(\w+)\s*\([^)]*\)\s*{/g);
const methods = [];
for (const match of methodMatches) {
if (!["if", "for", "while", "catch"].includes(match[1])) {
methods.push(match[1]);
}
}
results.push(
`Vue Component: ${componentName} with ${methods.length} methods`
);
if (methods.length > 0) {
results.push(
`Methods: ${methods.slice(0, 5).join(", ")}${
methods.length > 5 ? "..." : ""
}`
);
}
}
// Style analysis
const styleMatch = code.match(/<style\b[^>]*>([\s\S]*?)<\/style>/i);
if (styleMatch) {
const styleContent = styleMatch[1];
const ruleCount = (styleContent.match(/[^{]+\s*{/g) || []).length;
results.push(`Vue Styles: ${ruleCount} CSS rules`);
}
return results;
}
async function getProjectFiles() {
const entries = await globby([
"**/*.{js,ts,jsx,tsx,py,java,html,css,rb,php,go,cs,rs,cpp,vue}",
"!**/node_modules/**",
"!**/dist/**",
"!**/build/**",
"!**/test/**",
"!**/tests/**",
"!**/__tests__/**",
]);
return entries;
}
function parseCode(filePath) {
try {
const code = fs.readFileSync(filePath, "utf8");
const parser = new Parser();
const ext = path.extname(filePath).toLowerCase();
// Special handling for PHP files
if (ext === ".php") {
return [
"ℹ️ PHP support is coming soon! This file will be properly parsed in a future update.",
];
}
const languageMap = {
".js": JavaScript,
".ts": TypeScript,
".jsx": JavaScript,
".tsx": TSX,
".java": Java,
".py": Python,
".html": HTML,
".css": CSS,
".rb": Ruby,
".php": PHP,
".go": Go,
".cs": CSharp,
".rs": Rust,
".cpp": CPP,
};
// Special handling for CSS files
if (ext === ".css") {
const fallbackResults = parseCSSFallback(code);
if (fallbackResults.length > 0) {
return fallbackResults;
}
}
// Special handling for Vue files
if (ext === ".vue") {
return parseVueComponent(code);
}
const language = languageMap[ext];
if (!language) {
console.warn(`No parser available for ${ext} files`);
return null;
}
try {
parser.setLanguage(language);
} catch (err) {
console.error(`❌ Invalid language object for ${ext}: ${err.message}`);
return null;
}
const tree = parser.parse(code);
const summary = summarizeTree(tree.rootNode, code, ext);
return summary;
} catch (err) {
console.error(`Error parsing ${filePath}: ${err.message}`);
return null;
}
}
function summarizeTree(root, code, ext) {
const summaries = [];
const MAX_SUMMARIES_PER_FILE = 25;
const typeLabels = {
function_declaration: "Function",
class_declaration: "Class",
method_definition: "Method",
export_statement: "Export",
import_statement: "Import",
variable_declarator: "Variable",
call_expression: "Function Call",
interface_declaration: "Interface",
type_alias_declaration: "Type Alias",
enum_declaration: "Enum",
decorated_definition: "Decorated",
method_declaration: "Method",
class_definition: "Class",
element: "HTML Element",
start_tag: "HTML Tag",
script_element: "Script Tag",
style_element: "Style Tag",
rule_set: "CSS Rule",
qualified_rule: "CSS Rule",
at_rule: "@ Rule",
keyframes: "@keyframes",
method: "Method",
class: "Class",
module: "Module",
require: "Require",
type_declaration: "Type",
function_item: "Function",
struct_item: "Struct",
mod_item: "Module",
use_declaration: "Use",
function_definition: "Function",
class_specifier: "Class",
namespace_definition: "Namespace",
using_directive: "Using",
jsx_element: "JSX Element",
jsx_opening_element: "JSX Opening Element",
};
const getFunctionDetails = (node) => {
const params = node
.descendantsOfType(["formal_parameters", "parameters"])
.map((p) => code.slice(p.startIndex, p.endIndex))
.join(", ");
const returns = node
.descendantsOfType(["type_annotation", "return_type"])
.map((t) => code.slice(t.startIndex, t.endIndex))
.join(", ");
return {
params: params ? `(${params})` : "()",
returns: returns ? ` → ${returns}` : "",
};
};
const getClassDetails = (node) => {
const modifiers = node
.descendantsOfType("modifier")
.map((m) => code.slice(m.startIndex, m.endIndex))
.join(" ");
return modifiers ? ` ${modifiers}` : "";
};
const nodes = root.descendantsOfType(nodeTypes[ext] || []);
for (let i = 0; i < Math.min(nodes.length, MAX_SUMMARIES_PER_FILE); i++) {
const node = nodes[i];
const nodeType = getNodeType(node, ext);
const label = typeLabels[nodeType] || nodeType;
// Function/Method handling
if (nodeType.includes("function") || nodeType.includes("method")) {
const details = getFunctionDetails(node);
const nameNode = node.childForFieldName("name");
if (nameNode) {
const name = code.slice(nameNode.startIndex, nameNode.endIndex);
summaries.push(`${label}: ${name}${details.params}${details.returns}`);
continue;
}
}
// Class/Interface handling
if (nodeType.includes("class") || nodeType.includes("interface")) {
const nameNode = node.childForFieldName("name");
if (nameNode) {
const name = code.slice(nameNode.startIndex, nameNode.endIndex);
const modifiers = getClassDetails(node);
summaries.push(`${label}:${modifiers} ${name}`);
continue;
}
}
// JSX/HTML Elements
if (nodeType.includes("jsx") || nodeType.includes("element")) {
const nameNode =
node.childForFieldName("name") || node.childForFieldName("tag_name");
if (nameNode) {
const name = code.slice(nameNode.startIndex, nameNode.endIndex);
const attrs = node.descendantsOfType(["attribute", "jsx_attribute"]);
summaries.push(
`${label}: <${name}${
attrs.length ? ` with ${attrs.length} attrs` : ""
}>`
);
continue;
}
}
// Default handling
const nameNode =
node.childForFieldName("name") ||
node.childForFieldName("declarator")?.childForFieldName("name");
if (nameNode) {
const name = code.slice(nameNode.startIndex, nameNode.endIndex);
summaries.push(`${label}: ${name}`);
} else if (nodeType === "call_expression") {
const funcNode = node.childForFieldName("function");
if (funcNode) {
const funcName = code.slice(funcNode.startIndex, funcNode.endIndex);
summaries.push(`${label}: ${funcName}()`);
}
}
}
return summaries;
}
function getNodeType(node, ext) {
// Special handling for C++ function definitions
if (ext === ".cpp" && node.type === "function_definition") {
return "function_definition";
}
return node.type;
}
module.exports = { getProjectFiles, parseCode, summarizeTree };