import os import re import pathspec def scan_for_reusability(start_path='.', output_file='docs/05_Reusabilidade_Ativos.md', gitignore_file='.gitignore'): """ Varre o projeto em busca de Classes, Funções Exportadas e Web Components. Gera um relatório Markdown com JSDocs extraídos. """ # Configurações de Filtro ignore_patterns = ['.git', '.agent', 'node_modules', 'venv', 'dist', 'build', '*.min.js'] if os.path.exists(gitignore_file): with open(gitignore_file, 'r', encoding='utf-8') as f: ignore_patterns.extend(f.read().splitlines()) spec = pathspec.PathSpec.from_lines('gitwildmatch', ignore_patterns) # Regex patterns para identificar componentes reutilizáveis (JS/TS) patterns = { 'Class': r'export\s+class\s+(\w+)', 'Function': r'export\s+function\s+(\w+)', 'WebComponent': r'customElements\.define\([\'"](.*?)[\'"]', 'Vue/React': r'export\s+default\s+\{\s*name:\s*[\'"](\w+)[\'"]' } # Regex para capturar comentários JSDoc imediatamente antes doc_pattern = r'/\*\*(.*?)\*/\s*' inventory = [] print("🕵️ Iniciando varredura de reusabilidade...") for root, dirs, files in os.walk(start_path): # Filtra pastas ignoradas dirs[:] = [d for d in dirs if not spec.match_file(os.path.join(root, d))] for file in files: file_path = os.path.join(root, file) # Analisa apenas arquivos .js, .ts, .jsx, .vue if not file.endswith(('.js', '.ts', '.jsx', '.vue')) or spec.match_file(file_path): continue try: with open(file_path, 'r', encoding='utf-8', errors='ignore') as f: content = f.read() # Procura por definições for tipo, regex in patterns.items(): matches = re.finditer(regex, content) for match in matches: name = match.group(1) # Tenta pegar o JSDoc antes da definição start_index = match.start() preceding_text = content[:start_index].strip() description = "Sem documentação." # Verifica se termina com um bloco de comentário # Usamos rfind para garantir que pegamos apenas o ÚLTIMO bloco de comentário antes da definição last_doc_start = preceding_text.rfind('/**') if last_doc_start != -1: potential_doc = preceding_text[last_doc_start:] doc_match = re.search(r'/\*\*(.*?)\*/$', potential_doc, re.DOTALL) if doc_match: # Limpa os asteriscos do JSDoc raw_doc = doc_match.group(1) clean_doc = '\n'.join([line.strip().lstrip('*').strip() for line in raw_doc.splitlines() if line.strip()]) description = clean_doc if clean_doc else "Documentação vazia." inventory.append({ 'file': file_path, 'type': tipo, 'name': name, 'desc': description, 'size': os.path.getsize(file_path) }) except Exception as e: print(f"⚠️ Erro ao ler {file_path}: {e}") # Gera o Relatório Markdown return generate_markdown_report(inventory, output_file) def generate_markdown_report(inventory, output_file): os.makedirs(os.path.dirname(output_file), exist_ok=True) md = [ "# ♻️ Catálogo de Reusabilidade", f"> Gerado automaticamente. Total de ativos encontrados: **{len(inventory)}**", "", "## 🧩 Componentes e Módulos", "" ] # Agrupa por arquivo para ficar organizado files_map = {} for item in inventory: if item['file'] not in files_map: files_map[item['file']] = [] files_map[item['file']].append(item) for file_path, items in files_map.items(): file_size_kb = os.path.getsize(file_path) / 1024 md.append(f"### 📄 `{file_path}` ({file_size_kb:.2f} KB)") md.append("| Tipo | Nome | Descrição (JSDoc) |") md.append("| :--- | :--- | :--- |") for item in items: desc_short = item['desc'].replace('\n', '
') md.append(f"| `{item['type']}` | **{item['name']}** | {desc_short} |") md.append("") with open(output_file, 'w', encoding='utf-8') as f: f.write('\n'.join(md)) return f"✅ Relatório salvo em: {output_file}" if __name__ == "__main__": import sys # Se passar argumento, muda o arquivo de saída out = sys.argv[1] if len(sys.argv) > 1 else 'docs/05_Reusabilidade_Ativos.md' print(scan_for_reusability(output_file=out))