@netoalmanca/advpl-sensei 1.1.6 → 1.2.0

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.
@@ -2,8 +2,11 @@
2
2
  * Registry Quality Check - Ferranha para verificar qualidade do Function Registry
3
3
  */
4
4
  import { RegistryAnalyzer } from "../registry-analyzer.js";
5
+ import { loadFunctionRegistry } from "../function-registry.js";
5
6
  export async function checkRegistryQuality() {
6
7
  console.log("\n🔍 Analisando Function Registry...\n");
8
+ // Garante que o registry está carregado do JSON
9
+ await loadFunctionRegistry();
7
10
  const analysis = RegistryAnalyzer.analyze();
8
11
  const report = RegistryAnalyzer.generateReport(analysis);
9
12
  console.log(report);
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Command: advpl_migrate
3
+ * Converte código ADVPL procedural (.prw) para TLPP OO (.tlpp)
4
+ */
5
+ import { promises as fs } from "node:fs";
6
+ import path from "node:path";
7
+ export class AdvplMigrator {
8
+ /**
9
+ * Executa a migração de um arquivo
10
+ */
11
+ static async migrate(options) {
12
+ const { target, output, namespace = "advpl.sensei.migrated", className: optClassName, wrapper = true } = options;
13
+ try {
14
+ const content = await fs.readFile(target, "utf-8");
15
+ const filename = path.basename(target);
16
+ const className = optClassName || filename.replace(".prw", "").replace(".tlpp", "");
17
+ const lines = content.split(/\r?\n/);
18
+ const migratedLines = [];
19
+ const functionsFound = [];
20
+ // 1. Headers e Namespace
21
+ migratedLines.push("#include 'tlpp-core.th'");
22
+ migratedLines.push("#include 'tlpp-rest.th'");
23
+ migratedLines.push("");
24
+ migratedLines.push(`namespace ${namespace}`);
25
+ migratedLines.push("");
26
+ migratedLines.push(`/**`);
27
+ migratedLines.push(` * Classe ${className} - Migrada automaticamente pelo Advpl Sensei`);
28
+ migratedLines.push(` */`);
29
+ migratedLines.push(`class ${className}`);
30
+ const methodDeclarations = [];
31
+ const methodImplementations = [];
32
+ let currentMethod = null;
33
+ let isStatic = false;
34
+ for (let i = 0; i < lines.length; i++) {
35
+ const line = lines[i];
36
+ const trimmed = line.trim();
37
+ const upper = trimmed.toUpperCase();
38
+ // Ignora includes antigos
39
+ if (upper.startsWith("#INCLUDE")) {
40
+ continue;
41
+ }
42
+ // Detecta funções
43
+ const funcMatch = trimmed.match(/^(USER\s+FUNCTION|STATIC\s+FUNCTION|FUNCTION)\s+([A-Za-z0-9_]+)\s*\((.*)\)/i);
44
+ if (funcMatch) {
45
+ const type = funcMatch[1].toUpperCase();
46
+ const name = funcMatch[2];
47
+ const params = funcMatch[3];
48
+ isStatic = type.includes("STATIC");
49
+ const access = isStatic ? "private" : "public";
50
+ functionsFound.push(name);
51
+ currentMethod = name;
52
+ // Adiciona declaração na classe
53
+ methodDeclarations.push(` ${access} method ${name}(${this.typeParams(params)})`);
54
+ // Inicia implementação do método
55
+ methodImplementations.push("");
56
+ methodImplementations.push(`/**`);
57
+ methodImplementations.push(` * Método ${name}`);
58
+ methodImplementations.push(` */`);
59
+ methodImplementations.push(`method ${name}(${this.typeParams(params)}) class ${className}`);
60
+ }
61
+ else if (currentMethod) {
62
+ // Conteúdo do método
63
+ if (upper.startsWith("RETURN")) {
64
+ methodImplementations.push(this.transformLine(line));
65
+ currentMethod = null;
66
+ }
67
+ else {
68
+ methodImplementations.push(this.transformLine(line));
69
+ }
70
+ }
71
+ }
72
+ // Monta a classe final
73
+ migratedLines.push(...methodDeclarations);
74
+ migratedLines.push("endclass");
75
+ migratedLines.push("");
76
+ migratedLines.push(...methodImplementations);
77
+ const migratedContent = migratedLines.join("\n");
78
+ const outFilename = output || target.replace(".prw", ".tlpp");
79
+ let report = `🚀 Migração concluída com sucesso!\n\n`;
80
+ report += `📦 **Classe:** \`${className}\`\n`;
81
+ report += `🌐 **Namespace:** \`${namespace}\`\n`;
82
+ report += `🛠️ **Funções Migradas:** ${functionsFound.length}\n`;
83
+ functionsFound.forEach(f => report += ` - \`${f}\` -> \`oObj:${f}()\`\n`);
84
+ return {
85
+ success: true,
86
+ content: migratedContent,
87
+ filename: outFilename,
88
+ originalFunctions: functionsFound,
89
+ report
90
+ };
91
+ }
92
+ catch (e) {
93
+ return {
94
+ success: false,
95
+ content: "",
96
+ filename: "",
97
+ originalFunctions: [],
98
+ report: `Erro na migração: ${e.message}`
99
+ };
100
+ }
101
+ }
102
+ /**
103
+ * Tenta tipar parâmetros baseados em notação húngara
104
+ */
105
+ static typeParams(params) {
106
+ if (!params.trim())
107
+ return "";
108
+ return params.split(",").map(p => {
109
+ const param = p.trim();
110
+ if (param.startsWith("c"))
111
+ return `${param} as string`;
112
+ if (param.startsWith("n"))
113
+ return `${param} as numeric`;
114
+ if (param.startsWith("l"))
115
+ return `${param} as logical`;
116
+ if (param.startsWith("d"))
117
+ return `${param} as date`;
118
+ if (param.startsWith("a"))
119
+ return `${param} as array`;
120
+ if (param.startsWith("o"))
121
+ return `${param} as object`;
122
+ return param;
123
+ }).join(", ");
124
+ }
125
+ /**
126
+ * Transforma linhas de código (ex: Local -> tipado)
127
+ */
128
+ static transformLine(line) {
129
+ const trimmed = line.trim();
130
+ const upper = trimmed.toUpperCase();
131
+ // Transforma Local cVar := "" para Local cVar as string := ""
132
+ if (upper.startsWith("LOCAL ")) {
133
+ const rest = line.substring(line.toUpperCase().indexOf("LOCAL ") + 6);
134
+ const parts = rest.split(",");
135
+ const typedParts = parts.map(p => {
136
+ const part = p.trim();
137
+ const varNameMatch = part.match(/^([a-z][A-Za-z0-9_]*)/);
138
+ if (varNameMatch) {
139
+ const varName = varNameMatch[1];
140
+ const type = this.getTlppType(varName);
141
+ if (type && !part.includes(" as ")) {
142
+ return part.replace(varName, `${varName} as ${type}`);
143
+ }
144
+ }
145
+ return p;
146
+ });
147
+ return line.substring(0, line.toUpperCase().indexOf("LOCAL ") + 6) + typedParts.join(", ");
148
+ }
149
+ return line;
150
+ }
151
+ static getTlppType(varName) {
152
+ const prefix = varName[0];
153
+ switch (prefix) {
154
+ case 'c': return 'string';
155
+ case 'n': return 'numeric';
156
+ case 'l': return 'logical';
157
+ case 'd': return 'date';
158
+ case 'a': return 'array';
159
+ case 'o': return 'object';
160
+ case 'b': return 'codeblock';
161
+ default: return null;
162
+ }
163
+ }
164
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Command: advpl_review
3
+ * Executa revisão completa de código (Lint + Arquitetura)
4
+ */
5
+ import { SenseiLinter } from "../linter.js";
6
+ export class AdvplReviewer {
7
+ /**
8
+ * Realiza o review de um arquivo
9
+ */
10
+ static review(content, filename) {
11
+ // 1. Lint Básico
12
+ const lintResult = SenseiLinter.lint(content, filename);
13
+ // 2. Análise de Arquitetura
14
+ const archIssues = [];
15
+ const recommendations = [];
16
+ const upperContent = content.toUpperCase();
17
+ // Check para Area Handling
18
+ if (upperContent.includes("DBSELECTAREA") && !upperContent.includes("GETAREA")) {
19
+ archIssues.push("Uso de DbSelectArea sem salvamento de área (GetArea/RestArea).");
20
+ recommendations.push("Sempre utilize GetArea() e RestArea() ao manipular tabelas para evitar efeitos colaterais em outras rotinas.");
21
+ }
22
+ // Check para Error Handling
23
+ if (!upperContent.includes("BEGIN SEQUENCE") && content.length > 500) {
24
+ archIssues.push("Código extenso sem tratamento de erros (Begin Sequence).");
25
+ recommendations.push("Implemente blocos Begin Sequence / Recover para tornar o código mais resiliente a falhas de banco ou runtime.");
26
+ }
27
+ // Check para MVC (heurística simples)
28
+ if (upperContent.includes("FWMBROWSE") && !upperContent.includes("MODELDEF")) {
29
+ archIssues.push("Uso de FWMBrowse sem estrutura MVC clara.");
30
+ recommendations.push("Considere migrar para o padrão MVC completo (ModelDef, ViewDef, MenuDef) para melhor manutenibilidade.");
31
+ }
32
+ // Cálculo de Score
33
+ const totalIssues = lintResult.issues.length + archIssues.length;
34
+ let score = 100 - (totalIssues * 5);
35
+ if (score < 0)
36
+ score = 0;
37
+ // Sumário
38
+ let summary = `📊 Review de \`${filename}\` concluído.\n`;
39
+ summary += `🎯 **Score:** ${score}/100\n`;
40
+ summary += `⚠️ **Problemas de Lint:** ${lintResult.issues.length}\n`;
41
+ summary += `🏛️ **Problemas de Arquitetura:** ${archIssues.length}\n`;
42
+ return {
43
+ filename,
44
+ score,
45
+ lint: lintResult,
46
+ architectureIssues: archIssues,
47
+ recommendations,
48
+ summary
49
+ };
50
+ }
51
+ /**
52
+ * Formata o resultado para exibição no MCP
53
+ */
54
+ static formatResult(result) {
55
+ let output = result.summary + "\n";
56
+ if (result.lint.issues.length > 0) {
57
+ output += `### 🔍 Problemas de Estilo/Lint\n`;
58
+ result.lint.issues.forEach(issue => {
59
+ const icon = issue.severity === "error" ? "🔴" : "🟡";
60
+ output += `- ${icon} [${issue.code}] Linha ${issue.line}: ${issue.message}\n`;
61
+ });
62
+ output += "\n";
63
+ }
64
+ if (result.architectureIssues.length > 0) {
65
+ output += `### 🏛️ Problemas de Arquitetura\n`;
66
+ result.architectureIssues.forEach(issue => {
67
+ output += `- ❌ ${issue}\n`;
68
+ });
69
+ output += "\n";
70
+ }
71
+ if (result.recommendations.length > 0) {
72
+ output += `### 💡 Recomendações do Sensei\n`;
73
+ result.recommendations.forEach(rec => {
74
+ output += `- ${rec}\n`;
75
+ });
76
+ }
77
+ if (result.score >= 90) {
78
+ output += `\n✅ **Parabéns!** O código segue as melhores práticas do ecossistema Protheus.`;
79
+ }
80
+ return output;
81
+ }
82
+ }