Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ e este projeto adere ao [Semantic Versioning](https://semver.org/lang/pt-BR/spec

## [Unreleased]

### Added
- **Suporte a Go e Java**: Adicionados parsers universais (`goUniversalParser`, `javaUniversalParser`) para análise heurística de complexidade em arquivos `.go` e `.java`, incluindo detecção de laços lineares/logarítmicos e recursão. Resolve [vncsmnl/BigON#1](https://github.com/vncsmnl/BigON/issues/1).

## [1.0.4] - 2026-08-23

### Fixed
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Diferente de profilings de execução, o BigON realiza **análise estática** at
- **Análise Instantânea**: Feedback visual de complexidade enquanto você digita.
- **Não Executa Código**: Análise 100% estática e segura via AST.
- **Foco Educacional**: Explication detalhada embasada nos princípios de algoritmos (CLRS / Cormen et al.) com gráficos interativos de curvas Big-O.
- **Suporte Multi-Linguagem**: JavaScript, TypeScript, React (JSX/TSX), Python, Ruby, C++ e C.
- **Suporte Multi-Linguagem**: JavaScript, TypeScript, React (JSX/TSX), Python, Ruby, C++, C, Go e Java.

<div align="center">
<p><b>CodeLens e Anotações In-line no Editor</b></p>
Expand Down Expand Up @@ -95,7 +95,7 @@ cursor --install-extension BigON-X.X.X.vsix

## Como Usar

Assim que instalada, a extensão é ativada automaticamente ao abrir qualquer arquivo em uma das linguagens suportadas (`.js`, `.ts`, `.jsx`, `.tsx`, `.py`, `.rb`, `.cpp`, `.c`).
Assim que instalada, a extensão é ativada automaticamente ao abrir qualquer arquivo em uma das linguagens suportadas (`.js`, `.ts`, `.jsx`, `.tsx`, `.py`, `.rb`, `.cpp`, `.c`, `.go`, `.java`).

### Recursos no Editor

Expand Down Expand Up @@ -164,7 +164,7 @@ Se você é desenvolvedor e deseja compilar o projeto localmente, debugar, adici

## Limitações da Análise Estática

A extensão **BigON** utiliza análise estática via **AST** (para JavaScript/TypeScript) e identificadores heurísticos sintáticos para Python, Ruby, C++ e C.
A extensão **BigON** utiliza análise estática via **AST** (para JavaScript/TypeScript) e identificadores heurísticos sintáticos para Python, Ruby, C++, C, Go e Java.

As estimativas representam diagnósticos estáticos baseados em padrões estruturais típicos de código e **não constituem provas matemáticas formais em runtime**. Códigos com compilação dinâmica, metaprogramação, dependência exclusiva de dados recebidos em tempo de execução ou recursões indiretas complexas podem apresentar estimativas aproximadas.

Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
"onLanguage:ruby",
"onLanguage:cpp",
"onLanguage:c",
"onLanguage:go",
"onLanguage:java",
"onCommand:BigON.analyzeFile",
"onCommand:BigON.toggleDecorations",
"onCommand:BigON.openExplanation"
Expand Down
9 changes: 8 additions & 1 deletion src/analyzer/complexityEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,14 @@ export class ComplexityEngine {
public analyzeCode(code: string, filePath: string = 'file.ts', languageId: string = 'typescript'): FileAnalysisResult {
const normLang = normalizeLanguageId(languageId, filePath);

if (normLang === 'python' || normLang === 'ruby' || normLang === 'cpp' || normLang === 'c') {
if (
normLang === 'python' ||
normLang === 'ruby' ||
normLang === 'cpp' ||
normLang === 'c' ||
normLang === 'go' ||
normLang === 'java'
) {
return this.analyzeUniversalCode(code, filePath, normLang);
}

Expand Down
197 changes: 197 additions & 0 deletions src/analyzer/universal/parsers/goUniversalParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import { UniversalFunctionNode, UniversalLoopNode, UniversalCallNode } from '../types';

export class GoUniversalParser {
public parse(code: string): UniversalFunctionNode[] {
const lines = code.split(/\r?\n/);
const functions: UniversalFunctionNode[] = [];

let currentFn: {
name: string;
startLine: number;
parameters: string[];
bodyLines: { line: number; text: string }[];
} | null = null;

const topLevelLines: { line: number; text: string }[] = [];
let fnBraceDepth = 0;

for (let i = 0; i < lines.length; i++) {
const lineText = lines[i];
const trimmed = lineText.trim();
const lineNum = i + 1;

if (!trimmed || trimmed.startsWith('//') || trimmed.startsWith('/*') || trimmed.startsWith('*')) {
continue;
}

if (!currentFn) {
const funcMatch = trimmed.match(/^func\s+(?:\([^)]*\)\s*)?([a-zA-Z0-9_]+)\s*\(([^)]*)\)/);
if (funcMatch) {
currentFn = {
name: funcMatch[1],
startLine: lineNum,
parameters: funcMatch[2] ? funcMatch[2].split(',').map((p) => p.trim()).filter(Boolean) : [],
bodyLines: [],
};
fnBraceDepth = (trimmed.match(/\{/g) || []).length - (trimmed.match(/\}/g) || []).length;
if (fnBraceDepth <= 0) {
functions.push(this.buildFunctionNode(currentFn, lineNum));
currentFn = null;
fnBraceDepth = 0;
}
continue;
}
topLevelLines.push({ line: lineNum, text: trimmed });
} else {
currentFn.bodyLines.push({ line: lineNum, text: trimmed });
const openB = (trimmed.match(/\{/g) || []).length;
const closeB = (trimmed.match(/\}/g) || []).length;
fnBraceDepth += openB - closeB;

if (fnBraceDepth <= 0) {
functions.push(this.buildFunctionNode(currentFn, lineNum));
currentFn = null;
fnBraceDepth = 0;
}
}
}

if (currentFn) {
functions.push(this.buildFunctionNode(currentFn, lines.length));
}

const mainLoops = this.extractLoops(topLevelLines);
if (mainLoops.length > 0 || functions.length === 0) {
const scriptText = topLevelLines.map((l) => l.text).join('\n');
const hasDivisionInBody =
scriptText.includes('/ 2') ||
scriptText.includes('/= 2') ||
scriptText.includes('>> 1') ||
scriptText.includes('>>= 1');

functions.unshift({
type: 'function',
name: '<script principal>',
startLine: 1,
endLine: lines.length,
bodyText: scriptText,
loops: mainLoops,
recursiveCalls: [],
hasDivisionInBody,
});
}

return functions;
}

private buildFunctionNode(
rawFn: {
name: string;
startLine: number;
parameters: string[];
bodyLines: { line: number; text: string }[];
},
endLine: number
): UniversalFunctionNode {
const bodyText = rawFn.bodyLines.map((l) => l.text).join('\n');
const hasDivisionInBody =
bodyText.includes('/ 2') ||
bodyText.includes('/= 2') ||
bodyText.includes('>> 1') ||
bodyText.includes('>>= 1');

const recursiveCalls: UniversalCallNode[] = [];
const callRegex = new RegExp(`\\b${rawFn.name}\\s*\\(([^)]*)\\)`, 'g');
for (const bLine of rawFn.bodyLines) {
let match;
while ((match = callRegex.exec(bLine.text)) !== null) {
recursiveCalls.push({
type: 'call',
name: rawFn.name,
line: bLine.line,
argsText: match[1],
});
}
}

const loops = this.extractLoops(rawFn.bodyLines);

return {
type: 'function',
name: rawFn.name,
startLine: rawFn.startLine,
endLine,
bodyText,
loops,
recursiveCalls,
hasDivisionInBody,
};
}

private extractLoops(lines: { line: number; text: string }[]): UniversalLoopNode[] {
const topLoops: UniversalLoopNode[] = [];
const stack: UniversalLoopNode[] = [];

for (const l of lines) {
const isFor = l.text.startsWith('for ') || l.text.startsWith('for{') || l.text === 'for {';

if (isFor) {
let stepType: 'linear' | 'logarithmic' | 'sqrt' = 'linear';
let explanation = 'Laço Go com passo linear O(n)';

if (
/\/=\s*\d+/.test(l.text) ||
/\*=\s*\d+/.test(l.text) ||
/\/\s*\d+/.test(l.text) ||
/\*\s*\d+/.test(l.text) ||
/>>=\s*\d+/.test(l.text) ||
/<<=\s*\d+/.test(l.text) ||
/>>\s*\d+/.test(l.text)
) {
stepType = 'logarithmic';
explanation = 'Laço Go com passo multiplicativo/divisivo -> O(log n)';
}

const loopNode: UniversalLoopNode = {
type: 'loop',
loopKind: 'for',
line: l.line,
stepType,
explanation,
subLoops: [],
};

if (stack.length === 0) {
topLoops.push(loopNode);
} else {
stack[stack.length - 1].subLoops.push(loopNode);
}

stack.push(loopNode);
} else {
if (stack.length > 0) {
if (
/\/=\s*\d+/.test(l.text) ||
/\*=\s*\d+/.test(l.text) ||
/\/\s*\d+/.test(l.text) ||
/\*\s*\d+/.test(l.text) ||
/>>=\s*\d+/.test(l.text) ||
/<<=\s*\d+/.test(l.text) ||
/>>\s*\d+/.test(l.text)
) {
stack[stack.length - 1].stepType = 'logarithmic';
stack[stack.length - 1].explanation =
'Laço Go com alteração multiplicativa/divisiva -> O(log n)';
}
}
if (l.text.includes('}')) {
if (stack.length > 0) {
stack.pop();
}
}
}
}

return topLoops;
}
}
Loading