@semacode/cli 0.8.2
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.
- package/LICENSE +22 -0
- package/README.md +52 -0
- package/dist/cpp-symbols.d.ts +10 -0
- package/dist/cpp-symbols.js +71 -0
- package/dist/cpp-symbols.js.map +1 -0
- package/dist/dotnet-http.d.ts +23 -0
- package/dist/dotnet-http.js +301 -0
- package/dist/dotnet-http.js.map +1 -0
- package/dist/drift.d.ts +74 -0
- package/dist/drift.js +878 -0
- package/dist/drift.js.map +1 -0
- package/dist/go-http.d.ts +23 -0
- package/dist/go-http.js +90 -0
- package/dist/go-http.js.map +1 -0
- package/dist/importador.d.ts +29 -0
- package/dist/importador.js +2094 -0
- package/dist/importador.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2150 -0
- package/dist/index.js.map +1 -0
- package/dist/java-http.d.ts +23 -0
- package/dist/java-http.js +204 -0
- package/dist/java-http.js.map +1 -0
- package/dist/projeto.d.ts +48 -0
- package/dist/projeto.js +560 -0
- package/dist/projeto.js.map +1 -0
- package/dist/python-http.d.ts +23 -0
- package/dist/python-http.js +200 -0
- package/dist/python-http.js.map +1 -0
- package/dist/rust-http.d.ts +23 -0
- package/dist/rust-http.js +95 -0
- package/dist/rust-http.js.map +1 -0
- package/dist/tipos.d.ts +3 -0
- package/dist/tipos.js +2 -0
- package/dist/tipos.js.map +1 -0
- package/dist/typescript-http.d.ts +35 -0
- package/dist/typescript-http.js +854 -0
- package/dist/typescript-http.js.map +1 -0
- package/logo.png +0 -0
- package/node_modules/@sema/gerador-dart/dist/index.d.ts +3 -0
- package/node_modules/@sema/gerador-dart/dist/index.js +44 -0
- package/node_modules/@sema/gerador-dart/dist/index.js.map +1 -0
- package/node_modules/@sema/gerador-dart/package.json +7 -0
- package/node_modules/@sema/gerador-python/dist/index.d.ts +6 -0
- package/node_modules/@sema/gerador-python/dist/index.js +510 -0
- package/node_modules/@sema/gerador-python/dist/index.js.map +1 -0
- package/node_modules/@sema/gerador-python/package.json +7 -0
- package/node_modules/@sema/gerador-typescript/dist/index.d.ts +6 -0
- package/node_modules/@sema/gerador-typescript/dist/index.js +646 -0
- package/node_modules/@sema/gerador-typescript/dist/index.js.map +1 -0
- package/node_modules/@sema/gerador-typescript/package.json +7 -0
- package/node_modules/@sema/nucleo/dist/ast/tipos.d.ts +103 -0
- package/node_modules/@sema/nucleo/dist/ast/tipos.js +2 -0
- package/node_modules/@sema/nucleo/dist/ast/tipos.js.map +1 -0
- package/node_modules/@sema/nucleo/dist/diagnosticos/index.d.ts +21 -0
- package/node_modules/@sema/nucleo/dist/diagnosticos/index.js +12 -0
- package/node_modules/@sema/nucleo/dist/diagnosticos/index.js.map +1 -0
- package/node_modules/@sema/nucleo/dist/formatador/index.d.ts +9 -0
- package/node_modules/@sema/nucleo/dist/formatador/index.js +289 -0
- package/node_modules/@sema/nucleo/dist/formatador/index.js.map +1 -0
- package/node_modules/@sema/nucleo/dist/index.d.ts +34 -0
- package/node_modules/@sema/nucleo/dist/index.js +95 -0
- package/node_modules/@sema/nucleo/dist/index.js.map +1 -0
- package/node_modules/@sema/nucleo/dist/ir/conversor.d.ts +5 -0
- package/node_modules/@sema/nucleo/dist/ir/conversor.js +241 -0
- package/node_modules/@sema/nucleo/dist/ir/conversor.js.map +1 -0
- package/node_modules/@sema/nucleo/dist/ir/modelos.d.ts +131 -0
- package/node_modules/@sema/nucleo/dist/ir/modelos.js +2 -0
- package/node_modules/@sema/nucleo/dist/ir/modelos.js.map +1 -0
- package/node_modules/@sema/nucleo/dist/lexer/lexer.d.ts +7 -0
- package/node_modules/@sema/nucleo/dist/lexer/lexer.js +122 -0
- package/node_modules/@sema/nucleo/dist/lexer/lexer.js.map +1 -0
- package/node_modules/@sema/nucleo/dist/lexer/tokens.d.ts +8 -0
- package/node_modules/@sema/nucleo/dist/lexer/tokens.js +30 -0
- package/node_modules/@sema/nucleo/dist/lexer/tokens.js.map +1 -0
- package/node_modules/@sema/nucleo/dist/parser/parser.d.ts +9 -0
- package/node_modules/@sema/nucleo/dist/parser/parser.js +423 -0
- package/node_modules/@sema/nucleo/dist/parser/parser.js.map +1 -0
- package/node_modules/@sema/nucleo/dist/semantico/analisador.d.ts +52 -0
- package/node_modules/@sema/nucleo/dist/semantico/analisador.js +837 -0
- package/node_modules/@sema/nucleo/dist/semantico/analisador.js.map +1 -0
- package/node_modules/@sema/nucleo/dist/semantico/estruturas.d.ts +99 -0
- package/node_modules/@sema/nucleo/dist/semantico/estruturas.js +395 -0
- package/node_modules/@sema/nucleo/dist/semantico/estruturas.js.map +1 -0
- package/node_modules/@sema/nucleo/dist/util/arquivos.d.ts +2 -0
- package/node_modules/@sema/nucleo/dist/util/arquivos.js +25 -0
- package/node_modules/@sema/nucleo/dist/util/arquivos.js.map +1 -0
- package/node_modules/@sema/nucleo/package.json +7 -0
- package/node_modules/@sema/padroes/dist/index.d.ts +20 -0
- package/node_modules/@sema/padroes/dist/index.js +79 -0
- package/node_modules/@sema/padroes/dist/index.js.map +1 -0
- package/node_modules/@sema/padroes/package.json +7 -0
- package/package.json +57 -0
|
@@ -0,0 +1,2094 @@
|
|
|
1
|
+
import { access, readdir, readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import ts from "typescript";
|
|
4
|
+
import { compilarProjeto, formatarCodigo, temErros } from "@sema/nucleo";
|
|
5
|
+
import { normalizarSegmentoModulo } from "@sema/padroes";
|
|
6
|
+
import { extrairSimbolosCpp } from "./cpp-symbols.js";
|
|
7
|
+
import { extrairRotasDotnet, extrairSimbolosDotnet } from "./dotnet-http.js";
|
|
8
|
+
import { extrairRotasGo, extrairSimbolosGo } from "./go-http.js";
|
|
9
|
+
import { extrairRotasJava, extrairSimbolosJava } from "./java-http.js";
|
|
10
|
+
import { extrairParametrosCaminhoFlask, extrairRotasFlaskDecoradas } from "./python-http.js";
|
|
11
|
+
import { extrairRotasRust, extrairSimbolosRust } from "./rust-http.js";
|
|
12
|
+
import { extrairRotasTypeScriptHttp, inferirSemanticaHandlerTypeScriptHttp, localizarExportacaoTypeScriptHttp, } from "./typescript-http.js";
|
|
13
|
+
const DIRETORIOS_IGNORADOS = new Set([
|
|
14
|
+
".git",
|
|
15
|
+
".hg",
|
|
16
|
+
".svn",
|
|
17
|
+
".gradle",
|
|
18
|
+
".cargo",
|
|
19
|
+
"node_modules",
|
|
20
|
+
"dist",
|
|
21
|
+
"build",
|
|
22
|
+
"bin",
|
|
23
|
+
"obj",
|
|
24
|
+
".next",
|
|
25
|
+
".nuxt",
|
|
26
|
+
".dart_tool",
|
|
27
|
+
"__pycache__",
|
|
28
|
+
".venv",
|
|
29
|
+
"venv",
|
|
30
|
+
"coverage",
|
|
31
|
+
".tmp",
|
|
32
|
+
"generated",
|
|
33
|
+
"vendor",
|
|
34
|
+
"ephemeral",
|
|
35
|
+
]);
|
|
36
|
+
const SUFIXOS_WRAPPER = ["Entrada", "Saida", "Dto", "Request", "Response", "Payload", "Body", "Input", "Output"];
|
|
37
|
+
const NOMES_RESERVADOS_CAMPO = new Set([
|
|
38
|
+
"module",
|
|
39
|
+
"use",
|
|
40
|
+
"type",
|
|
41
|
+
"entity",
|
|
42
|
+
"enum",
|
|
43
|
+
"task",
|
|
44
|
+
"input",
|
|
45
|
+
"output",
|
|
46
|
+
"rules",
|
|
47
|
+
"effects",
|
|
48
|
+
"impl",
|
|
49
|
+
"guarantees",
|
|
50
|
+
"state",
|
|
51
|
+
"flow",
|
|
52
|
+
"route",
|
|
53
|
+
"tests",
|
|
54
|
+
"error",
|
|
55
|
+
"docs",
|
|
56
|
+
"comments",
|
|
57
|
+
"fields",
|
|
58
|
+
"invariants",
|
|
59
|
+
"transitions",
|
|
60
|
+
"given",
|
|
61
|
+
"when",
|
|
62
|
+
"expect",
|
|
63
|
+
"caso",
|
|
64
|
+
"required",
|
|
65
|
+
]);
|
|
66
|
+
function escaparTexto(texto) {
|
|
67
|
+
return texto.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
68
|
+
}
|
|
69
|
+
function paraSnakeCase(valor) {
|
|
70
|
+
return valor
|
|
71
|
+
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
72
|
+
.replace(/[^A-Za-z0-9_]+/g, "_")
|
|
73
|
+
.replace(/_{2,}/g, "_")
|
|
74
|
+
.replace(/^_+|_+$/g, "")
|
|
75
|
+
.toLowerCase();
|
|
76
|
+
}
|
|
77
|
+
function paraIdentificadorModulo(valor) {
|
|
78
|
+
return normalizarSegmentoModulo(valor).replace(/_+/g, "_").replace(/^_+|_+$/g, "").toLowerCase();
|
|
79
|
+
}
|
|
80
|
+
function normalizarNomeCampoImportado(valor) {
|
|
81
|
+
const normalizado = paraSnakeCase(valor);
|
|
82
|
+
return NOMES_RESERVADOS_CAMPO.has(normalizado)
|
|
83
|
+
? `${normalizado}_campo`
|
|
84
|
+
: normalizado;
|
|
85
|
+
}
|
|
86
|
+
function nomeProjetoPadrao(diretorio) {
|
|
87
|
+
const base = path.basename(diretorio);
|
|
88
|
+
if (["src", "app", "api", "backend", "server"].includes(base.toLowerCase())) {
|
|
89
|
+
return `${path.basename(path.dirname(diretorio))}.${base}`;
|
|
90
|
+
}
|
|
91
|
+
return base;
|
|
92
|
+
}
|
|
93
|
+
export function inferirNamespaceBase(diretorio, namespaceExplicito) {
|
|
94
|
+
if (namespaceExplicito) {
|
|
95
|
+
return namespaceExplicito
|
|
96
|
+
.split(".")
|
|
97
|
+
.map((segmento) => paraIdentificadorModulo(segmento))
|
|
98
|
+
.filter(Boolean)
|
|
99
|
+
.join(".");
|
|
100
|
+
}
|
|
101
|
+
return ["legado", ...nomeProjetoPadrao(diretorio).split(/[\\/._-]+/g)]
|
|
102
|
+
.map((segmento) => paraIdentificadorModulo(segmento))
|
|
103
|
+
.filter(Boolean)
|
|
104
|
+
.join(".");
|
|
105
|
+
}
|
|
106
|
+
async function listarArquivosRecursivos(diretorio, extensoes) {
|
|
107
|
+
const entradas = await readdir(diretorio, { withFileTypes: true });
|
|
108
|
+
const encontrados = [];
|
|
109
|
+
for (const entrada of entradas) {
|
|
110
|
+
if (DIRETORIOS_IGNORADOS.has(entrada.name)) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const caminhoAtual = path.join(diretorio, entrada.name);
|
|
114
|
+
if (entrada.isDirectory()) {
|
|
115
|
+
encontrados.push(...await listarArquivosRecursivos(caminhoAtual, extensoes));
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (extensoes.some((extensao) => entrada.name.endsWith(extensao))) {
|
|
119
|
+
encontrados.push(caminhoAtual);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return encontrados.sort((a, b) => a.localeCompare(b, "pt-BR"));
|
|
123
|
+
}
|
|
124
|
+
function inferirContextoPorArquivo(relacao, opcoes) {
|
|
125
|
+
const semExtensao = relacao.replace(/\.[^.]+$/, "");
|
|
126
|
+
const segmentosOriginais = semExtensao.split(path.sep).filter(Boolean);
|
|
127
|
+
const segmentos = segmentosOriginais.map((segmento) => paraIdentificadorModulo(segmento)).filter(Boolean);
|
|
128
|
+
if (segmentos[0] === "src" || segmentos[0] === "app") {
|
|
129
|
+
segmentos.shift();
|
|
130
|
+
segmentosOriginais.shift();
|
|
131
|
+
}
|
|
132
|
+
const ultimo = segmentos.at(-1) ?? "";
|
|
133
|
+
const semSufixo = ultimo
|
|
134
|
+
.replace(/(\.controller|\.service|\.module|_router|_service|_schemas|_contract)$/g, "")
|
|
135
|
+
.replace(/(controller|service|module|router|schemas|contract)$/g, "")
|
|
136
|
+
.replace(/_+$/g, "");
|
|
137
|
+
if (segmentos.length === 0) {
|
|
138
|
+
return ["importado"];
|
|
139
|
+
}
|
|
140
|
+
if (!opcoes?.preservarUltimo && semSufixo && semSufixo !== ultimo) {
|
|
141
|
+
segmentos[segmentos.length - 1] = semSufixo;
|
|
142
|
+
}
|
|
143
|
+
if (opcoes?.snakeCaseUltimo && segmentos.length > 0) {
|
|
144
|
+
segmentos[segmentos.length - 1] = paraSnakeCase(segmentosOriginais[segmentosOriginais.length - 1] ?? ultimo);
|
|
145
|
+
}
|
|
146
|
+
if (segmentos.length > 1 && segmentos[segmentos.length - 1] === segmentos[segmentos.length - 2]) {
|
|
147
|
+
segmentos.pop();
|
|
148
|
+
}
|
|
149
|
+
return segmentos;
|
|
150
|
+
}
|
|
151
|
+
function juntarCaminhoHttp(base, sufixo) {
|
|
152
|
+
const partes = [base ?? "", sufixo ?? ""]
|
|
153
|
+
.map((item) => item.trim())
|
|
154
|
+
.filter(Boolean)
|
|
155
|
+
.map((item) => item.replace(/^\/+|\/+$/g, ""));
|
|
156
|
+
const caminho = `/${partes.join("/")}`.replace(/\/+/g, "/");
|
|
157
|
+
return caminho === "//" ? "/" : caminho;
|
|
158
|
+
}
|
|
159
|
+
function extrairTextoLiteral(expr) {
|
|
160
|
+
if (!expr) {
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
if (ts.isStringLiteralLike(expr) || ts.isNoSubstitutionTemplateLiteral(expr)) {
|
|
164
|
+
return expr.text;
|
|
165
|
+
}
|
|
166
|
+
if (ts.isNumericLiteral(expr)) {
|
|
167
|
+
return expr.text;
|
|
168
|
+
}
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
function listarDecoradores(node) {
|
|
172
|
+
return ts.canHaveDecorators(node) ? ts.getDecorators(node) ?? [] : [];
|
|
173
|
+
}
|
|
174
|
+
function lerDecorator(node, nomes) {
|
|
175
|
+
for (const decorator of listarDecoradores(node)) {
|
|
176
|
+
const expressao = decorator.expression;
|
|
177
|
+
if (ts.isCallExpression(expressao)) {
|
|
178
|
+
const alvo = expressao.expression;
|
|
179
|
+
if (ts.isIdentifier(alvo) && nomes.includes(alvo.text)) {
|
|
180
|
+
return { nome: alvo.text, argumentos: expressao.arguments };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
else if (ts.isIdentifier(expressao) && nomes.includes(expressao.text)) {
|
|
184
|
+
return { nome: expressao.text, argumentos: ts.factory.createNodeArray() };
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
function mapearTipoPrimitivo(tipo) {
|
|
190
|
+
const limpo = tipo.trim().replace(/\s+/g, "");
|
|
191
|
+
const base = limpo
|
|
192
|
+
.replace(/^Promise<(.*)>$/, "$1")
|
|
193
|
+
.replace(/\|undefined/g, "")
|
|
194
|
+
.replace(/\|null/g, "")
|
|
195
|
+
.replace(/\bundefined\|/g, "")
|
|
196
|
+
.replace(/\bnull\|/g, "")
|
|
197
|
+
.trim();
|
|
198
|
+
const minusculo = base.toLowerCase();
|
|
199
|
+
if (minusculo === "string" || minusculo === "texto") {
|
|
200
|
+
return "Texto";
|
|
201
|
+
}
|
|
202
|
+
if (minusculo === "number" || minusculo === "float" || minusculo === "double") {
|
|
203
|
+
return "Decimal";
|
|
204
|
+
}
|
|
205
|
+
if (minusculo === "int" || minusculo === "integer" || minusculo === "inteiro") {
|
|
206
|
+
return "Inteiro";
|
|
207
|
+
}
|
|
208
|
+
if (minusculo === "boolean" || minusculo === "bool") {
|
|
209
|
+
return "Booleano";
|
|
210
|
+
}
|
|
211
|
+
if (minusculo === "date") {
|
|
212
|
+
return "Data";
|
|
213
|
+
}
|
|
214
|
+
if (minusculo === "datetime" || minusculo === "timestamp") {
|
|
215
|
+
return "DataHora";
|
|
216
|
+
}
|
|
217
|
+
if (minusculo === "id" || minusculo.endsWith("id")) {
|
|
218
|
+
return "Id";
|
|
219
|
+
}
|
|
220
|
+
if (minusculo.includes("[]") || minusculo.startsWith("array<") || minusculo.startsWith("record<") || minusculo.startsWith("list[") || minusculo.startsWith("dict[")) {
|
|
221
|
+
return "Json";
|
|
222
|
+
}
|
|
223
|
+
if (minusculo === "json" || minusculo === "object" || minusculo === "unknown" || minusculo === "any" || minusculo === "void" || minusculo === "none") {
|
|
224
|
+
return minusculo === "void" || minusculo === "none" ? "Vazio" : "Json";
|
|
225
|
+
}
|
|
226
|
+
return tipo.trim();
|
|
227
|
+
}
|
|
228
|
+
function limparTipoBackend(tipo) {
|
|
229
|
+
if (!tipo) {
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
return tipo
|
|
233
|
+
.trim()
|
|
234
|
+
.replace(/^Task<(.+)>$/i, "$1")
|
|
235
|
+
.replace(/^ActionResult<(.+)>$/i, "$1")
|
|
236
|
+
.replace(/^IActionResult$/i, "Json")
|
|
237
|
+
.replace(/^Results<(.+)>$/i, "$1")
|
|
238
|
+
.replace(/^ResponseEntity<(.+)>$/i, "$1")
|
|
239
|
+
.replace(/^Optional<(.+)>$/i, "$1")
|
|
240
|
+
.replace(/^Result<(.+)>$/i, "$1")
|
|
241
|
+
.replace(/^impl\s+IntoResponse$/i, "Json")
|
|
242
|
+
.replace(/^Json<(.+)>$/i, "$1")
|
|
243
|
+
.replace(/^Option<(.+)>$/i, "$1")
|
|
244
|
+
.replace(/^Vec<(.+)>$/i, "Json")
|
|
245
|
+
.replace(/^List<(.+)>$/i, "Json")
|
|
246
|
+
.replace(/^Map<(.+)>$/i, "Json")
|
|
247
|
+
.replace(/^Dictionary<(.+)>$/i, "Json");
|
|
248
|
+
}
|
|
249
|
+
function mapearTipoBackendParaSema(tipo) {
|
|
250
|
+
const limpo = limparTipoBackend(tipo);
|
|
251
|
+
if (!limpo) {
|
|
252
|
+
return "Json";
|
|
253
|
+
}
|
|
254
|
+
const basico = mapearTipoPrimitivo(limpo);
|
|
255
|
+
if (basico !== limpo) {
|
|
256
|
+
return basico;
|
|
257
|
+
}
|
|
258
|
+
if (/\[\]$/.test(limpo) || /^(IEnumerable|IReadOnlyList|List|Vec|HashMap|Map|Dictionary)</.test(limpo)) {
|
|
259
|
+
return "Json";
|
|
260
|
+
}
|
|
261
|
+
if (/^(void|unit|\(\)|nil)$/i.test(limpo)) {
|
|
262
|
+
return "Vazio";
|
|
263
|
+
}
|
|
264
|
+
if (/\b(uuid|guid)\b/i.test(limpo)) {
|
|
265
|
+
return "Id";
|
|
266
|
+
}
|
|
267
|
+
return "Json";
|
|
268
|
+
}
|
|
269
|
+
function criarCampoResultadoBackend(tipo) {
|
|
270
|
+
const tipoSema = mapearTipoBackendParaSema(tipo);
|
|
271
|
+
return tipoSema === "Vazio"
|
|
272
|
+
? []
|
|
273
|
+
: [{ nome: "resultado", tipo: tipoSema, obrigatorio: false }];
|
|
274
|
+
}
|
|
275
|
+
function camposDeParametrosRotaBackend(parametros) {
|
|
276
|
+
return parametros.map((parametro) => ({
|
|
277
|
+
nome: normalizarNomeCampoImportado(parametro.nome),
|
|
278
|
+
tipo: parametro.tipoSema,
|
|
279
|
+
obrigatorio: true,
|
|
280
|
+
}));
|
|
281
|
+
}
|
|
282
|
+
function pareceWrapperTipo(nome) {
|
|
283
|
+
return SUFIXOS_WRAPPER.some((sufixo) => nome.endsWith(sufixo)) || /(entrada|saida|dto|request|response|payload|body|input|output)/i.test(nome);
|
|
284
|
+
}
|
|
285
|
+
function descreverEfeitosPorHeuristica(codigo) {
|
|
286
|
+
const texto = codigo.toLowerCase();
|
|
287
|
+
const efeitos = [];
|
|
288
|
+
const adicionar = (categoria, alvo, criticidade) => {
|
|
289
|
+
if (!efeitos.some((efeito) => efeito.categoria === categoria && efeito.alvo === alvo)) {
|
|
290
|
+
efeitos.push({ categoria, alvo, criticidade });
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
if (/(prisma\.|repository\.|\.create\(|\.update\(|\.delete\(|\.save\()/i.test(codigo)) {
|
|
294
|
+
adicionar("persistencia", "banco", "alta");
|
|
295
|
+
}
|
|
296
|
+
if (/(findmany|findunique|findfirst|\.find\(|\.select\(|\.query\(|\.get\()/i.test(texto)) {
|
|
297
|
+
adicionar("consulta", "dados", "media");
|
|
298
|
+
}
|
|
299
|
+
if (/(emit\(|publish\(|dispatch\(|eventbus|event_emitter)/i.test(texto)) {
|
|
300
|
+
adicionar("evento", "dominio", "media");
|
|
301
|
+
}
|
|
302
|
+
if (/(notify|notification|sendmessage|send_email|telegram|smtp|mail)/i.test(texto)) {
|
|
303
|
+
adicionar("notificacao", "usuarios", "media");
|
|
304
|
+
}
|
|
305
|
+
if (/(audit|logger|logging|log\.|trace|observability)/i.test(texto)) {
|
|
306
|
+
adicionar("auditoria", "operacao", "baixa");
|
|
307
|
+
}
|
|
308
|
+
return efeitos;
|
|
309
|
+
}
|
|
310
|
+
function normalizarNomeErroBruto(nome) {
|
|
311
|
+
return paraSnakeCase(nome.replace(/(Error|Erro|Exception)$/i, "")) || "erro_importado";
|
|
312
|
+
}
|
|
313
|
+
function extrairErrosTs(node, sourceFile) {
|
|
314
|
+
const encontrados = new Map();
|
|
315
|
+
const visitar = (atual) => {
|
|
316
|
+
if (ts.isThrowStatement(atual) && atual.expression) {
|
|
317
|
+
const expressao = atual.expression;
|
|
318
|
+
if (ts.isNewExpression(expressao)) {
|
|
319
|
+
const nomeErro = expressao.expression.getText(sourceFile);
|
|
320
|
+
const mensagem = extrairTextoLiteral(expressao.arguments?.[0]) ?? `Erro importado automaticamente de ${nomeErro}.`;
|
|
321
|
+
encontrados.set(normalizarNomeErroBruto(nomeErro), mensagem);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
atual.forEachChild(visitar);
|
|
325
|
+
};
|
|
326
|
+
node.forEachChild(visitar);
|
|
327
|
+
return [...encontrados.entries()].map(([nome, mensagem]) => ({ nome, mensagem }));
|
|
328
|
+
}
|
|
329
|
+
function extrairTiposTs(sourceFile) {
|
|
330
|
+
const tipos = new Map();
|
|
331
|
+
const adicionarObjeto = (nome, campos) => {
|
|
332
|
+
if (!campos.length || tipos.has(nome)) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
tipos.set(nome, { tipo: "objeto", nome, campos });
|
|
336
|
+
};
|
|
337
|
+
const adicionarEnum = (nome, valores) => {
|
|
338
|
+
if (!valores.length || tipos.has(nome)) {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
tipos.set(nome, { tipo: "enum", nome, valores });
|
|
342
|
+
};
|
|
343
|
+
sourceFile.forEachChild((node) => {
|
|
344
|
+
if (ts.isInterfaceDeclaration(node) && node.name) {
|
|
345
|
+
const campos = node.members
|
|
346
|
+
.filter(ts.isPropertySignature)
|
|
347
|
+
.map((member) => ({
|
|
348
|
+
nome: member.name.getText(sourceFile),
|
|
349
|
+
tipoTexto: member.type?.getText(sourceFile),
|
|
350
|
+
obrigatorio: !member.questionToken,
|
|
351
|
+
}));
|
|
352
|
+
adicionarObjeto(node.name.text, campos);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
if (ts.isClassDeclaration(node) && node.name) {
|
|
356
|
+
const campos = node.members
|
|
357
|
+
.filter((member) => ts.isPropertyDeclaration(member) && Boolean(member.name))
|
|
358
|
+
.map((member) => ({
|
|
359
|
+
nome: member.name.getText(sourceFile),
|
|
360
|
+
tipoTexto: member.type?.getText(sourceFile),
|
|
361
|
+
obrigatorio: !member.questionToken,
|
|
362
|
+
}));
|
|
363
|
+
adicionarObjeto(node.name.text, campos);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
if (ts.isTypeAliasDeclaration(node) && node.name) {
|
|
367
|
+
if (ts.isUnionTypeNode(node.type) && node.type.types.every((tipo) => ts.isLiteralTypeNode(tipo) && ts.isStringLiteralLike(tipo.literal))) {
|
|
368
|
+
adicionarEnum(node.name.text, node.type.types
|
|
369
|
+
.map((tipo) => ts.isLiteralTypeNode(tipo) && ts.isStringLiteralLike(tipo.literal) ? tipo.literal.text : undefined)
|
|
370
|
+
.filter((valor) => Boolean(valor)));
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
if (ts.isTypeLiteralNode(node.type)) {
|
|
374
|
+
const campos = node.type.members
|
|
375
|
+
.filter(ts.isPropertySignature)
|
|
376
|
+
.map((member) => ({
|
|
377
|
+
nome: member.name.getText(sourceFile),
|
|
378
|
+
tipoTexto: member.type?.getText(sourceFile),
|
|
379
|
+
obrigatorio: !member.questionToken,
|
|
380
|
+
}));
|
|
381
|
+
adicionarObjeto(node.name.text, campos);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
return tipos;
|
|
386
|
+
}
|
|
387
|
+
function mapearTipoTsParaSema(tipoTexto, tipos, entidadesReferenciadas, enumsReferenciados) {
|
|
388
|
+
if (!tipoTexto) {
|
|
389
|
+
return "Json";
|
|
390
|
+
}
|
|
391
|
+
const basico = mapearTipoPrimitivo(tipoTexto);
|
|
392
|
+
if (basico !== tipoTexto.trim()) {
|
|
393
|
+
return basico;
|
|
394
|
+
}
|
|
395
|
+
const limpo = tipoTexto
|
|
396
|
+
.trim()
|
|
397
|
+
.replace(/^Promise<(.*)>$/, "$1")
|
|
398
|
+
.replace(/\| undefined/g, "")
|
|
399
|
+
.replace(/\| null/g, "")
|
|
400
|
+
.replace(/Readonly<(.+)>/, "$1")
|
|
401
|
+
.replace(/Partial<(.+)>/, "$1")
|
|
402
|
+
.trim();
|
|
403
|
+
if (tipos.has(limpo)) {
|
|
404
|
+
const encontrado = tipos.get(limpo);
|
|
405
|
+
if (encontrado.tipo === "enum") {
|
|
406
|
+
enumsReferenciados.add(encontrado.nome);
|
|
407
|
+
return encontrado.nome;
|
|
408
|
+
}
|
|
409
|
+
if (!pareceWrapperTipo(encontrado.nome)) {
|
|
410
|
+
entidadesReferenciadas.add(encontrado.nome);
|
|
411
|
+
return encontrado.nome;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return "Json";
|
|
415
|
+
}
|
|
416
|
+
function expandirCamposTs(nomeParametro, tipoTexto, tipos, entidadesReferenciadas, enumsReferenciados, obrigatorio) {
|
|
417
|
+
if (!tipoTexto) {
|
|
418
|
+
return [{ nome: normalizarNomeCampoImportado(nomeParametro), tipo: "Json", obrigatorio }];
|
|
419
|
+
}
|
|
420
|
+
const limpo = tipoTexto
|
|
421
|
+
.trim()
|
|
422
|
+
.replace(/^Promise<(.*)>$/, "$1")
|
|
423
|
+
.replace(/\| undefined/g, "")
|
|
424
|
+
.replace(/\| null/g, "")
|
|
425
|
+
.replace(/Readonly<(.+)>/, "$1")
|
|
426
|
+
.replace(/Partial<(.+)>/, "$1")
|
|
427
|
+
.trim();
|
|
428
|
+
const descoberto = tipos.get(limpo);
|
|
429
|
+
if (descoberto?.tipo === "objeto" && pareceWrapperTipo(descoberto.nome)) {
|
|
430
|
+
return descoberto.campos.map((campo) => ({
|
|
431
|
+
nome: normalizarNomeCampoImportado(campo.nome),
|
|
432
|
+
tipo: mapearTipoTsParaSema(campo.tipoTexto, tipos, entidadesReferenciadas, enumsReferenciados),
|
|
433
|
+
obrigatorio: campo.obrigatorio,
|
|
434
|
+
}));
|
|
435
|
+
}
|
|
436
|
+
return [{
|
|
437
|
+
nome: normalizarNomeCampoImportado(nomeParametro),
|
|
438
|
+
tipo: mapearTipoTsParaSema(tipoTexto, tipos, entidadesReferenciadas, enumsReferenciados),
|
|
439
|
+
obrigatorio,
|
|
440
|
+
}];
|
|
441
|
+
}
|
|
442
|
+
function criarEntidadesReferenciadas(tipos, entidadesReferenciadas, enumsReferenciados) {
|
|
443
|
+
const fila = [...entidadesReferenciadas];
|
|
444
|
+
const processadas = new Set();
|
|
445
|
+
const entities = [];
|
|
446
|
+
while (fila.length > 0) {
|
|
447
|
+
const nomeAtual = fila.shift();
|
|
448
|
+
if (processadas.has(nomeAtual)) {
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
processadas.add(nomeAtual);
|
|
452
|
+
const tipo = tipos.get(nomeAtual);
|
|
453
|
+
if (!tipo || tipo.tipo !== "objeto") {
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
const entidade = {
|
|
457
|
+
nome: tipo.nome,
|
|
458
|
+
campos: tipo.campos.map((campo) => ({
|
|
459
|
+
nome: normalizarNomeCampoImportado(campo.nome),
|
|
460
|
+
tipo: mapearTipoTsParaSema(campo.tipoTexto, tipos, entidadesReferenciadas, enumsReferenciados),
|
|
461
|
+
obrigatorio: campo.obrigatorio,
|
|
462
|
+
})),
|
|
463
|
+
};
|
|
464
|
+
entities.push(entidade);
|
|
465
|
+
for (const referenciado of entidadesReferenciadas) {
|
|
466
|
+
if (!processadas.has(referenciado) && !fila.includes(referenciado)) {
|
|
467
|
+
fila.push(referenciado);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
const enums = [...enumsReferenciados]
|
|
472
|
+
.map((nome) => tipos.get(nome))
|
|
473
|
+
.filter((item) => Boolean(item && item.tipo === "enum"))
|
|
474
|
+
.map((tipo) => ({
|
|
475
|
+
nome: tipo.nome,
|
|
476
|
+
valores: tipo.valores,
|
|
477
|
+
}))
|
|
478
|
+
.filter((enumItem, indice, lista) => lista.findIndex((item) => item.nome === enumItem.nome) === indice);
|
|
479
|
+
return { entities: deduplicarEntidades(entities), enums };
|
|
480
|
+
}
|
|
481
|
+
function caminhoImplTs(diretorioBase, arquivo, simbolo) {
|
|
482
|
+
const relativo = path.relative(diretorioBase, arquivo).replace(/\.[^.]+$/, "");
|
|
483
|
+
const segmentos = relativo.split(path.sep).map((segmento) => paraIdentificadorModulo(segmento)).filter(Boolean);
|
|
484
|
+
return [...segmentos, simbolo].join(".");
|
|
485
|
+
}
|
|
486
|
+
function mapearCampoInferidoTypeScriptHttp(campo, tipos, entidadesReferenciadas, enumsReferenciados) {
|
|
487
|
+
const tipoBasico = campo.tipoTexto ? mapearTipoPrimitivo(campo.tipoTexto) : "Json";
|
|
488
|
+
return {
|
|
489
|
+
nome: paraSnakeCase(campo.nome),
|
|
490
|
+
tipo: ["Texto", "Decimal", "Inteiro", "Booleano", "Data", "DataHora", "Id", "Json", "Vazio"].includes(tipoBasico)
|
|
491
|
+
? tipoBasico
|
|
492
|
+
: campo.tipoTexto
|
|
493
|
+
? mapearTipoTsParaSema(campo.tipoTexto, tipos, entidadesReferenciadas, enumsReferenciados)
|
|
494
|
+
: "Json",
|
|
495
|
+
obrigatorio: campo.obrigatorio,
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
function camposDeSemanticaTypeScriptHttp(campos, tipos, entidadesReferenciadas, enumsReferenciados) {
|
|
499
|
+
return campos.map((campo) => mapearCampoInferidoTypeScriptHttp(campo, tipos, entidadesReferenciadas, enumsReferenciados));
|
|
500
|
+
}
|
|
501
|
+
function camposEstruturadosTypeScriptHttp(nomeParametro, tipoTexto, tipos, entidadesReferenciadas, enumsReferenciados) {
|
|
502
|
+
const campos = expandirCamposTs(nomeParametro, tipoTexto, tipos, entidadesReferenciadas, enumsReferenciados, false);
|
|
503
|
+
const nomeWrapper = normalizarNomeCampoImportado(nomeParametro);
|
|
504
|
+
if (campos.length === 1 && campos[0]?.nome === nomeWrapper && campos[0]?.tipo === "Json") {
|
|
505
|
+
return [];
|
|
506
|
+
}
|
|
507
|
+
return campos;
|
|
508
|
+
}
|
|
509
|
+
function errosPorStatusHttp(statuses) {
|
|
510
|
+
return [...new Set(statuses)].map((status) => {
|
|
511
|
+
switch (status) {
|
|
512
|
+
case 401:
|
|
513
|
+
return { nome: "nao_autorizado", mensagem: "Falha HTTP importada automaticamente com status 401." };
|
|
514
|
+
case 403:
|
|
515
|
+
return { nome: "acesso_negado", mensagem: "Falha HTTP importada automaticamente com status 403." };
|
|
516
|
+
case 404:
|
|
517
|
+
return { nome: "nao_encontrado", mensagem: "Falha HTTP importada automaticamente com status 404." };
|
|
518
|
+
case 409:
|
|
519
|
+
return { nome: "conflito", mensagem: "Falha HTTP importada automaticamente com status 409." };
|
|
520
|
+
case 422:
|
|
521
|
+
return { nome: "entrada_invalida", mensagem: "Falha HTTP importada automaticamente com status 422." };
|
|
522
|
+
case 500:
|
|
523
|
+
return { nome: "erro_interno", mensagem: "Falha HTTP importada automaticamente com status 500." };
|
|
524
|
+
default:
|
|
525
|
+
return { nome: `erro_http_${status}`, mensagem: `Falha HTTP importada automaticamente com status ${status}.` };
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
function resolverEscopoImportacaoNextJs(diretorioEntrada) {
|
|
530
|
+
const resolvido = path.resolve(diretorioEntrada);
|
|
531
|
+
const partes = path.parse(resolvido);
|
|
532
|
+
const relativoSemRaiz = resolvido.slice(partes.root.length);
|
|
533
|
+
const segmentos = relativoSemRaiz.split(path.sep).filter(Boolean);
|
|
534
|
+
const procurarSequencia = (sequencia) => segmentos.findIndex((segmento, indice) => sequencia.every((item, deslocamento) => segmentos[indice + deslocamento]?.toLowerCase() === item));
|
|
535
|
+
const montarBase = (indice) => indice <= 0
|
|
536
|
+
? partes.root
|
|
537
|
+
: path.join(partes.root, ...segmentos.slice(0, indice));
|
|
538
|
+
const indiceSrcAppApi = procurarSequencia(["src", "app", "api"]);
|
|
539
|
+
if (indiceSrcAppApi >= 0) {
|
|
540
|
+
return {
|
|
541
|
+
baseProjeto: montarBase(indiceSrcAppApi),
|
|
542
|
+
diretorioEscopo: resolvido,
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
const indiceAppApi = procurarSequencia(["app", "api"]);
|
|
546
|
+
if (indiceAppApi >= 0) {
|
|
547
|
+
return {
|
|
548
|
+
baseProjeto: montarBase(indiceAppApi),
|
|
549
|
+
diretorioEscopo: resolvido,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
const indiceSrcApp = procurarSequencia(["src", "app"]);
|
|
553
|
+
if (indiceSrcApp >= 0) {
|
|
554
|
+
return {
|
|
555
|
+
baseProjeto: montarBase(indiceSrcApp),
|
|
556
|
+
diretorioEscopo: resolvido,
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
const indiceApp = procurarSequencia(["app"]);
|
|
560
|
+
if (indiceApp >= 0) {
|
|
561
|
+
return {
|
|
562
|
+
baseProjeto: montarBase(indiceApp),
|
|
563
|
+
diretorioEscopo: resolvido,
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
return {
|
|
567
|
+
baseProjeto: resolvido,
|
|
568
|
+
diretorioEscopo: resolvido,
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
function extrairChamadaServiceTs(node) {
|
|
572
|
+
let encontrado;
|
|
573
|
+
const visitar = (atual) => {
|
|
574
|
+
if (encontrado) {
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
if (ts.isCallExpression(atual) && ts.isPropertyAccessExpression(atual.expression)) {
|
|
578
|
+
const alvo = atual.expression.expression;
|
|
579
|
+
if (ts.isPropertyAccessExpression(alvo) && alvo.expression.kind === ts.SyntaxKind.ThisKeyword && alvo.name.text.endsWith("Service")) {
|
|
580
|
+
encontrado = atual.expression.name.text;
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
atual.forEachChild(visitar);
|
|
585
|
+
};
|
|
586
|
+
node.forEachChild(visitar);
|
|
587
|
+
return encontrado;
|
|
588
|
+
}
|
|
589
|
+
function deduplicarCampos(campos) {
|
|
590
|
+
const mapa = new Map();
|
|
591
|
+
for (const campo of campos) {
|
|
592
|
+
if (!mapa.has(campo.nome)) {
|
|
593
|
+
mapa.set(campo.nome, campo);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
return [...mapa.values()];
|
|
597
|
+
}
|
|
598
|
+
function deduplicarErros(errors) {
|
|
599
|
+
const mapa = new Map();
|
|
600
|
+
for (const erro of errors) {
|
|
601
|
+
if (!mapa.has(erro.nome)) {
|
|
602
|
+
mapa.set(erro.nome, erro);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
return [...mapa.values()];
|
|
606
|
+
}
|
|
607
|
+
function deduplicarEfeitos(effects) {
|
|
608
|
+
const mapa = new Map();
|
|
609
|
+
for (const effect of effects) {
|
|
610
|
+
const chave = `${effect.categoria}:${effect.alvo}`;
|
|
611
|
+
if (!mapa.has(chave)) {
|
|
612
|
+
mapa.set(chave, effect);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return [...mapa.values()];
|
|
616
|
+
}
|
|
617
|
+
function deduplicarEntidades(entities) {
|
|
618
|
+
const mapa = new Map();
|
|
619
|
+
for (const entity of entities) {
|
|
620
|
+
if (!mapa.has(entity.nome)) {
|
|
621
|
+
mapa.set(entity.nome, entity);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
return [...mapa.values()];
|
|
625
|
+
}
|
|
626
|
+
function deduplicarEnums(enums) {
|
|
627
|
+
const mapa = new Map();
|
|
628
|
+
for (const enumItem of enums) {
|
|
629
|
+
if (!mapa.has(enumItem.nome)) {
|
|
630
|
+
mapa.set(enumItem.nome, enumItem);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
return [...mapa.values()];
|
|
634
|
+
}
|
|
635
|
+
function deduplicarTarefas(tasks) {
|
|
636
|
+
const mapa = new Map();
|
|
637
|
+
for (const task of tasks) {
|
|
638
|
+
if (!mapa.has(task.nome)) {
|
|
639
|
+
mapa.set(task.nome, task);
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
const existente = mapa.get(task.nome);
|
|
643
|
+
existente.input = deduplicarCampos([...existente.input, ...task.input]);
|
|
644
|
+
existente.output = deduplicarCampos([...existente.output, ...task.output]);
|
|
645
|
+
existente.errors = deduplicarErros([...existente.errors, ...task.errors]);
|
|
646
|
+
existente.effects = deduplicarEfeitos([...existente.effects, ...task.effects]);
|
|
647
|
+
}
|
|
648
|
+
return [...mapa.values()];
|
|
649
|
+
}
|
|
650
|
+
function deduplicarRotas(routes) {
|
|
651
|
+
const mapa = new Map();
|
|
652
|
+
for (const route of routes) {
|
|
653
|
+
const chave = `${route.metodo}:${route.caminho}`;
|
|
654
|
+
if (!mapa.has(chave)) {
|
|
655
|
+
mapa.set(chave, route);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
return [...mapa.values()];
|
|
659
|
+
}
|
|
660
|
+
function sincronizarRotasComTasks(routes, tasks) {
|
|
661
|
+
const mapaTasks = new Map(tasks.map((task) => [task.nome, task]));
|
|
662
|
+
for (const route of routes) {
|
|
663
|
+
const task = mapaTasks.get(route.task);
|
|
664
|
+
if (!task) {
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
if (route.output.length === 0) {
|
|
668
|
+
route.output = task.output.map((campo) => ({ ...campo, obrigatorio: true }));
|
|
669
|
+
}
|
|
670
|
+
if (route.errors.length === 0) {
|
|
671
|
+
route.errors = task.errors;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
function renderizarCampos(bloco, campos, indentacao = " ", sempre = false) {
|
|
676
|
+
if (campos.length === 0 && !sempre) {
|
|
677
|
+
return [];
|
|
678
|
+
}
|
|
679
|
+
return [
|
|
680
|
+
`${indentacao}${bloco} {`,
|
|
681
|
+
...campos.map((campo) => `${indentacao} ${normalizarNomeCampoImportado(campo.nome)}: ${campo.tipo}${campo.obrigatorio ? " required" : ""}`),
|
|
682
|
+
`${indentacao}}`,
|
|
683
|
+
"",
|
|
684
|
+
];
|
|
685
|
+
}
|
|
686
|
+
function renderizarErrors(erros, indentacao = " ") {
|
|
687
|
+
if (erros.length === 0) {
|
|
688
|
+
return [];
|
|
689
|
+
}
|
|
690
|
+
return [
|
|
691
|
+
`${indentacao}error {`,
|
|
692
|
+
...erros.map((erro) => `${indentacao} ${erro.nome}: "${escaparTexto(erro.mensagem)}"`),
|
|
693
|
+
`${indentacao}}`,
|
|
694
|
+
"",
|
|
695
|
+
];
|
|
696
|
+
}
|
|
697
|
+
function renderizarEffects(effects, indentacao = " ") {
|
|
698
|
+
if (effects.length === 0) {
|
|
699
|
+
return [];
|
|
700
|
+
}
|
|
701
|
+
return [
|
|
702
|
+
`${indentacao}effects {`,
|
|
703
|
+
...effects.map((effect) => `${indentacao} ${effect.categoria} ${effect.alvo}${effect.criticidade ? ` criticidade = ${effect.criticidade}` : ""}`),
|
|
704
|
+
`${indentacao}}`,
|
|
705
|
+
"",
|
|
706
|
+
];
|
|
707
|
+
}
|
|
708
|
+
function renderizarImpl(impl, indentacao = " ") {
|
|
709
|
+
if (!impl || Object.keys(impl).length === 0) {
|
|
710
|
+
return [];
|
|
711
|
+
}
|
|
712
|
+
return [
|
|
713
|
+
`${indentacao}impl {`,
|
|
714
|
+
...(impl.ts ? [`${indentacao} ts: ${impl.ts}`] : []),
|
|
715
|
+
...(impl.py ? [`${indentacao} py: ${impl.py}`] : []),
|
|
716
|
+
...(impl.dart ? [`${indentacao} dart: ${impl.dart}`] : []),
|
|
717
|
+
...(impl.cs ? [`${indentacao} cs: ${impl.cs}`] : []),
|
|
718
|
+
...(impl.java ? [`${indentacao} java: ${impl.java}`] : []),
|
|
719
|
+
...(impl.go ? [`${indentacao} go: ${impl.go}`] : []),
|
|
720
|
+
...(impl.rust ? [`${indentacao} rust: ${impl.rust}`] : []),
|
|
721
|
+
...(impl.cpp ? [`${indentacao} cpp: ${impl.cpp}`] : []),
|
|
722
|
+
`${indentacao}}`,
|
|
723
|
+
"",
|
|
724
|
+
];
|
|
725
|
+
}
|
|
726
|
+
function renderizarTask(task) {
|
|
727
|
+
const linhas = [
|
|
728
|
+
` task ${task.nome} {`,
|
|
729
|
+
" docs {",
|
|
730
|
+
` resumo: "${escaparTexto(task.resumo)}"`,
|
|
731
|
+
" }",
|
|
732
|
+
"",
|
|
733
|
+
...renderizarCampos("input", task.input, " ", true),
|
|
734
|
+
...renderizarCampos("output", task.output, " ", true),
|
|
735
|
+
...renderizarEffects(task.effects, " "),
|
|
736
|
+
...renderizarImpl(task.impl, " "),
|
|
737
|
+
...renderizarErrors(task.errors, " "),
|
|
738
|
+
];
|
|
739
|
+
linhas.push(" guarantees {");
|
|
740
|
+
for (const campo of task.output) {
|
|
741
|
+
linhas.push(` ${normalizarNomeCampoImportado(campo.nome)} existe`);
|
|
742
|
+
}
|
|
743
|
+
linhas.push(" }");
|
|
744
|
+
linhas.push("");
|
|
745
|
+
linhas.push(" }");
|
|
746
|
+
linhas.push("");
|
|
747
|
+
return linhas;
|
|
748
|
+
}
|
|
749
|
+
function renderizarRoute(route) {
|
|
750
|
+
const caminhoRenderizado = /[{}]/.test(route.caminho)
|
|
751
|
+
? `"${escaparTexto(route.caminho)}"`
|
|
752
|
+
: route.caminho;
|
|
753
|
+
return [
|
|
754
|
+
` route ${route.nome} {`,
|
|
755
|
+
" docs {",
|
|
756
|
+
` resumo: "${escaparTexto(route.resumo)}"`,
|
|
757
|
+
" }",
|
|
758
|
+
"",
|
|
759
|
+
` metodo: ${route.metodo}`,
|
|
760
|
+
` caminho: ${caminhoRenderizado}`,
|
|
761
|
+
` task: ${route.task}`,
|
|
762
|
+
...renderizarCampos("input", route.input, " "),
|
|
763
|
+
...renderizarCampos("output", route.output, " "),
|
|
764
|
+
...renderizarErrors(route.errors, " "),
|
|
765
|
+
" }",
|
|
766
|
+
"",
|
|
767
|
+
];
|
|
768
|
+
}
|
|
769
|
+
function renderizarEnum(enumItem) {
|
|
770
|
+
return [
|
|
771
|
+
` enum ${enumItem.nome} {`,
|
|
772
|
+
` ${enumItem.valores.join(",\n ")}`,
|
|
773
|
+
" }",
|
|
774
|
+
"",
|
|
775
|
+
];
|
|
776
|
+
}
|
|
777
|
+
function renderizarEntidade(entity) {
|
|
778
|
+
return [
|
|
779
|
+
` entity ${entity.nome} {`,
|
|
780
|
+
" fields {",
|
|
781
|
+
...entity.campos.map((campo) => ` ${normalizarNomeCampoImportado(campo.nome)}: ${campo.tipo}`),
|
|
782
|
+
" }",
|
|
783
|
+
" }",
|
|
784
|
+
"",
|
|
785
|
+
];
|
|
786
|
+
}
|
|
787
|
+
function moduloParaCodigo(modulo) {
|
|
788
|
+
const linhas = [
|
|
789
|
+
`module ${modulo.nome} {`,
|
|
790
|
+
" docs {",
|
|
791
|
+
` resumo: "${escaparTexto(modulo.resumo)}"`,
|
|
792
|
+
" }",
|
|
793
|
+
"",
|
|
794
|
+
...modulo.enums.flatMap(renderizarEnum),
|
|
795
|
+
...modulo.entities.flatMap(renderizarEntidade),
|
|
796
|
+
...modulo.tasks.flatMap(renderizarTask),
|
|
797
|
+
...modulo.routes.flatMap(renderizarRoute),
|
|
798
|
+
"}",
|
|
799
|
+
"",
|
|
800
|
+
];
|
|
801
|
+
return linhas.join("\n");
|
|
802
|
+
}
|
|
803
|
+
async function formatarModuloImportado(codigo, caminhoVirtual) {
|
|
804
|
+
const formatado = formatarCodigo(codigo, caminhoVirtual);
|
|
805
|
+
return formatado.codigoFormatado ?? codigo;
|
|
806
|
+
}
|
|
807
|
+
function nomeArquivoModulo(modulo) {
|
|
808
|
+
const segmentos = modulo.split(".");
|
|
809
|
+
return `${segmentos.at(-1) ?? "modulo"}.sema`;
|
|
810
|
+
}
|
|
811
|
+
function contextoArquivoModulo(modulo, namespaceBase) {
|
|
812
|
+
const prefixo = namespaceBase.split(".");
|
|
813
|
+
const segmentos = modulo.split(".");
|
|
814
|
+
const relativos = segmentos.slice(prefixo.length, -1);
|
|
815
|
+
return relativos.length ? path.join(...relativos) : "";
|
|
816
|
+
}
|
|
817
|
+
function montarArquivoImportado(modulo, namespaceBase, conteudo) {
|
|
818
|
+
const pasta = contextoArquivoModulo(modulo.nome, namespaceBase);
|
|
819
|
+
const caminhoRelativo = pasta ? path.join(pasta, nomeArquivoModulo(modulo.nome)) : nomeArquivoModulo(modulo.nome);
|
|
820
|
+
return {
|
|
821
|
+
caminhoRelativo,
|
|
822
|
+
conteudo,
|
|
823
|
+
modulo: modulo.nome,
|
|
824
|
+
tarefas: modulo.tasks.length,
|
|
825
|
+
rotas: modulo.routes.length,
|
|
826
|
+
entidades: modulo.entities.length,
|
|
827
|
+
enums: modulo.enums.length,
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
function consolidarTiposTs(contextos) {
|
|
831
|
+
const tipos = new Map();
|
|
832
|
+
for (const contexto of contextos) {
|
|
833
|
+
for (const [nome, tipo] of extrairTiposTs(contexto.sourceFile)) {
|
|
834
|
+
if (!tipos.has(nome)) {
|
|
835
|
+
tipos.set(nome, tipo);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
return tipos;
|
|
840
|
+
}
|
|
841
|
+
function importarNestJsDeArquivo(diretorioBase, arquivo, namespaceBase, tiposGlobais) {
|
|
842
|
+
const relacao = path.relative(diretorioBase, arquivo);
|
|
843
|
+
const codigo = ts.sys.readFile(arquivo, "utf8") ?? "";
|
|
844
|
+
const sourceFile = ts.createSourceFile(arquivo, codigo, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
845
|
+
const contextoSegmentos = inferirContextoPorArquivo(relacao);
|
|
846
|
+
const nomeModulo = [namespaceBase, ...contextoSegmentos].join(".");
|
|
847
|
+
const entitiesRef = new Set();
|
|
848
|
+
const enumsRef = new Set();
|
|
849
|
+
const tasks = [];
|
|
850
|
+
const routes = [];
|
|
851
|
+
for (const node of sourceFile.statements) {
|
|
852
|
+
if (!ts.isClassDeclaration(node)) {
|
|
853
|
+
continue;
|
|
854
|
+
}
|
|
855
|
+
const controllerDecorator = lerDecorator(node, ["Controller"]);
|
|
856
|
+
if (controllerDecorator) {
|
|
857
|
+
const basePath = extrairTextoLiteral(controllerDecorator.argumentos[0]);
|
|
858
|
+
for (const member of node.members) {
|
|
859
|
+
if (!ts.isMethodDeclaration(member) || !member.body) {
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
862
|
+
const httpDecorator = lerDecorator(member, ["Get", "Post", "Put", "Patch", "Delete"]);
|
|
863
|
+
if (!httpDecorator) {
|
|
864
|
+
continue;
|
|
865
|
+
}
|
|
866
|
+
const taskOriginal = extrairChamadaServiceTs(member.body) ?? member.name.getText(sourceFile);
|
|
867
|
+
const taskNome = paraSnakeCase(taskOriginal);
|
|
868
|
+
const routeInput = member.parameters.flatMap((parametro) => expandirCamposTs(parametro.name.getText(sourceFile), parametro.type?.getText(sourceFile), tiposGlobais, entitiesRef, enumsRef, !parametro.questionToken));
|
|
869
|
+
const routeOutputTipo = member.type?.getText(sourceFile);
|
|
870
|
+
const routeOutput = !routeOutputTipo || mapearTipoPrimitivo(routeOutputTipo) === "Vazio"
|
|
871
|
+
? []
|
|
872
|
+
: deduplicarCampos(expandirCamposTs("resultado", routeOutputTipo, tiposGlobais, entitiesRef, enumsRef, false));
|
|
873
|
+
routes.push({
|
|
874
|
+
nome: `${taskNome}_publico`,
|
|
875
|
+
resumo: `Rota importada automaticamente de ${relacao}#${member.name.getText(sourceFile)}.`,
|
|
876
|
+
metodo: httpDecorator.nome.toUpperCase(),
|
|
877
|
+
caminho: juntarCaminhoHttp(basePath, extrairTextoLiteral(httpDecorator.argumentos[0])),
|
|
878
|
+
task: taskNome,
|
|
879
|
+
input: deduplicarCampos(routeInput),
|
|
880
|
+
output: routeOutput,
|
|
881
|
+
errors: [],
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
if (!node.name?.text.endsWith("Service")) {
|
|
886
|
+
continue;
|
|
887
|
+
}
|
|
888
|
+
for (const member of node.members) {
|
|
889
|
+
if (!ts.isMethodDeclaration(member) || !member.body || !member.name) {
|
|
890
|
+
continue;
|
|
891
|
+
}
|
|
892
|
+
if (member.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.PrivateKeyword || modifier.kind === ts.SyntaxKind.ProtectedKeyword)) {
|
|
893
|
+
continue;
|
|
894
|
+
}
|
|
895
|
+
const nomeMetodo = member.name.getText(sourceFile);
|
|
896
|
+
if (nomeMetodo === "constructor") {
|
|
897
|
+
continue;
|
|
898
|
+
}
|
|
899
|
+
const input = member.parameters.flatMap((parametro) => expandirCamposTs(parametro.name.getText(sourceFile), parametro.type?.getText(sourceFile), tiposGlobais, entitiesRef, enumsRef, !parametro.questionToken));
|
|
900
|
+
const output = member.type?.getText(sourceFile) && mapearTipoPrimitivo(member.type.getText(sourceFile)) === "Vazio"
|
|
901
|
+
? []
|
|
902
|
+
: deduplicarCampos(expandirCamposTs("resultado", member.type?.getText(sourceFile), tiposGlobais, entitiesRef, enumsRef, false));
|
|
903
|
+
tasks.push({
|
|
904
|
+
nome: paraSnakeCase(nomeMetodo),
|
|
905
|
+
resumo: `Task importada automaticamente de ${relacao}#${nomeMetodo}.`,
|
|
906
|
+
input: deduplicarCampos(input),
|
|
907
|
+
output,
|
|
908
|
+
errors: extrairErrosTs(member.body, sourceFile),
|
|
909
|
+
effects: descreverEfeitosPorHeuristica(member.body.getText(sourceFile)),
|
|
910
|
+
impl: { ts: caminhoImplTs(diretorioBase, arquivo, nomeMetodo) },
|
|
911
|
+
origemArquivo: relacao,
|
|
912
|
+
origemSimbolo: nomeMetodo,
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
if (!tasks.length && !routes.length) {
|
|
917
|
+
return [];
|
|
918
|
+
}
|
|
919
|
+
const { entities, enums } = criarEntidadesReferenciadas(tiposGlobais, entitiesRef, enumsRef);
|
|
920
|
+
sincronizarRotasComTasks(routes, tasks);
|
|
921
|
+
return [{
|
|
922
|
+
nome: nomeModulo,
|
|
923
|
+
resumo: `Rascunho Sema importado de um contexto NestJS legado em ${contextoSegmentos.join("/")}.`,
|
|
924
|
+
entities,
|
|
925
|
+
enums,
|
|
926
|
+
tasks: deduplicarTarefas(tasks),
|
|
927
|
+
routes: deduplicarRotas(routes),
|
|
928
|
+
}];
|
|
929
|
+
}
|
|
930
|
+
async function importarTypeScriptBase(diretorio, namespaceBase, modoNestjs = false) {
|
|
931
|
+
const arquivos = await listarArquivosRecursivos(diretorio, [".ts"]);
|
|
932
|
+
const uteis = arquivos.filter((arquivo) => !arquivo.endsWith(".spec.ts")
|
|
933
|
+
&& !arquivo.endsWith(".test.ts")
|
|
934
|
+
&& !arquivo.endsWith(".d.ts")
|
|
935
|
+
&& !(modoNestjs && !arquivo.endsWith(".controller.ts") && !arquivo.endsWith(".service.ts")));
|
|
936
|
+
const contextosTodos = await Promise.all(arquivos
|
|
937
|
+
.filter((arquivo) => !arquivo.endsWith(".spec.ts") && !arquivo.endsWith(".test.ts") && !arquivo.endsWith(".d.ts"))
|
|
938
|
+
.map(async (arquivo) => {
|
|
939
|
+
const texto = await readFile(arquivo, "utf8");
|
|
940
|
+
return {
|
|
941
|
+
sourceFile: ts.createSourceFile(arquivo, texto, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS),
|
|
942
|
+
texto,
|
|
943
|
+
relacao: path.relative(diretorio, arquivo),
|
|
944
|
+
};
|
|
945
|
+
}));
|
|
946
|
+
const contextos = await Promise.all(uteis.map(async (arquivo) => {
|
|
947
|
+
const texto = await readFile(arquivo, "utf8");
|
|
948
|
+
return {
|
|
949
|
+
sourceFile: ts.createSourceFile(arquivo, texto, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS),
|
|
950
|
+
texto,
|
|
951
|
+
relacao: path.relative(diretorio, arquivo),
|
|
952
|
+
};
|
|
953
|
+
}));
|
|
954
|
+
const tiposGlobais = consolidarTiposTs(contextosTodos);
|
|
955
|
+
const modulos = new Map();
|
|
956
|
+
if (modoNestjs) {
|
|
957
|
+
for (const contexto of contextos) {
|
|
958
|
+
for (const modulo of importarNestJsDeArquivo(diretorio, path.join(diretorio, contexto.relacao), namespaceBase, tiposGlobais)) {
|
|
959
|
+
const existente = modulos.get(modulo.nome);
|
|
960
|
+
if (!existente) {
|
|
961
|
+
modulos.set(modulo.nome, modulo);
|
|
962
|
+
continue;
|
|
963
|
+
}
|
|
964
|
+
existente.tasks = deduplicarTarefas([...existente.tasks, ...modulo.tasks]);
|
|
965
|
+
existente.routes = deduplicarRotas([...existente.routes, ...modulo.routes]);
|
|
966
|
+
existente.entities = deduplicarEntidades([...existente.entities, ...modulo.entities]);
|
|
967
|
+
existente.enums = deduplicarEnums([...existente.enums, ...modulo.enums]);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
return [...modulos.values()];
|
|
971
|
+
}
|
|
972
|
+
for (const contexto of contextos) {
|
|
973
|
+
const entitiesRef = new Set();
|
|
974
|
+
const enumsRef = new Set();
|
|
975
|
+
const contextoSegmentos = inferirContextoPorArquivo(contexto.relacao);
|
|
976
|
+
const nomeModulo = [namespaceBase, ...contextoSegmentos].join(".");
|
|
977
|
+
const tasks = [];
|
|
978
|
+
contexto.sourceFile.forEachChild((node) => {
|
|
979
|
+
if (ts.isFunctionDeclaration(node) && node.name && node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
|
|
980
|
+
const nome = node.name.text;
|
|
981
|
+
const input = node.parameters.flatMap((parametro) => expandirCamposTs(parametro.name.getText(contexto.sourceFile), parametro.type?.getText(contexto.sourceFile), tiposGlobais, entitiesRef, enumsRef, !parametro.questionToken));
|
|
982
|
+
const output = node.type?.getText(contexto.sourceFile) && mapearTipoPrimitivo(node.type.getText(contexto.sourceFile)) === "Vazio"
|
|
983
|
+
? []
|
|
984
|
+
: deduplicarCampos(expandirCamposTs("resultado", node.type?.getText(contexto.sourceFile), tiposGlobais, entitiesRef, enumsRef, false));
|
|
985
|
+
const errors = node.body ? extrairErrosTs(node.body, contexto.sourceFile) : [];
|
|
986
|
+
const effects = node.body ? descreverEfeitosPorHeuristica(node.body.getText(contexto.sourceFile)) : [];
|
|
987
|
+
tasks.push({
|
|
988
|
+
nome: paraSnakeCase(nome),
|
|
989
|
+
resumo: `Task importada automaticamente de ${contexto.relacao}#${nome}.`,
|
|
990
|
+
input: deduplicarCampos(input),
|
|
991
|
+
output,
|
|
992
|
+
errors,
|
|
993
|
+
effects,
|
|
994
|
+
impl: { ts: caminhoImplTs(diretorio, path.join(diretorio, contexto.relacao), nome) },
|
|
995
|
+
origemArquivo: contexto.relacao,
|
|
996
|
+
origemSimbolo: nome,
|
|
997
|
+
});
|
|
998
|
+
}
|
|
999
|
+
if (ts.isClassDeclaration(node) && node.name && node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
|
|
1000
|
+
const nomeClasse = node.name.text;
|
|
1001
|
+
for (const member of node.members) {
|
|
1002
|
+
if (!ts.isMethodDeclaration(member) || !member.name || !member.body) {
|
|
1003
|
+
continue;
|
|
1004
|
+
}
|
|
1005
|
+
if (member.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.PrivateKeyword || modifier.kind === ts.SyntaxKind.ProtectedKeyword)) {
|
|
1006
|
+
continue;
|
|
1007
|
+
}
|
|
1008
|
+
const nomeMetodo = member.name.getText(contexto.sourceFile);
|
|
1009
|
+
if (nomeMetodo === "constructor") {
|
|
1010
|
+
continue;
|
|
1011
|
+
}
|
|
1012
|
+
const input = member.parameters.flatMap((parametro) => expandirCamposTs(parametro.name.getText(contexto.sourceFile), parametro.type?.getText(contexto.sourceFile), tiposGlobais, entitiesRef, enumsRef, !parametro.questionToken));
|
|
1013
|
+
const output = member.type?.getText(contexto.sourceFile) && mapearTipoPrimitivo(member.type.getText(contexto.sourceFile)) === "Vazio"
|
|
1014
|
+
? []
|
|
1015
|
+
: deduplicarCampos(expandirCamposTs("resultado", member.type?.getText(contexto.sourceFile), tiposGlobais, entitiesRef, enumsRef, false));
|
|
1016
|
+
tasks.push({
|
|
1017
|
+
nome: paraSnakeCase(nomeMetodo),
|
|
1018
|
+
resumo: `Task importada automaticamente de ${contexto.relacao}#${nomeClasse}.${nomeMetodo}.`,
|
|
1019
|
+
input: deduplicarCampos(input),
|
|
1020
|
+
output,
|
|
1021
|
+
errors: extrairErrosTs(member.body, contexto.sourceFile),
|
|
1022
|
+
effects: descreverEfeitosPorHeuristica(member.body.getText(contexto.sourceFile)),
|
|
1023
|
+
impl: { ts: caminhoImplTs(diretorio, path.join(diretorio, contexto.relacao), `${nomeClasse}.${nomeMetodo}`) },
|
|
1024
|
+
origemArquivo: contexto.relacao,
|
|
1025
|
+
origemSimbolo: `${nomeClasse}.${nomeMetodo}`,
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
});
|
|
1030
|
+
if (tasks.length === 0) {
|
|
1031
|
+
continue;
|
|
1032
|
+
}
|
|
1033
|
+
const { entities, enums } = criarEntidadesReferenciadas(tiposGlobais, entitiesRef, enumsRef);
|
|
1034
|
+
modulos.set(nomeModulo, {
|
|
1035
|
+
nome: nomeModulo,
|
|
1036
|
+
resumo: `Rascunho Sema importado automaticamente de ${contexto.relacao}.`,
|
|
1037
|
+
tasks: deduplicarTarefas(tasks),
|
|
1038
|
+
routes: [],
|
|
1039
|
+
entities,
|
|
1040
|
+
enums,
|
|
1041
|
+
});
|
|
1042
|
+
}
|
|
1043
|
+
return [...modulos.values()];
|
|
1044
|
+
}
|
|
1045
|
+
function nomeTaskParaRotaTypeScript(caminho, metodo) {
|
|
1046
|
+
const segmentos = caminho
|
|
1047
|
+
.replace(/^\/+|\/+$/g, "")
|
|
1048
|
+
.split("/")
|
|
1049
|
+
.filter(Boolean)
|
|
1050
|
+
.map((segmento) => segmento.replace(/[{}]/g, ""));
|
|
1051
|
+
return paraSnakeCase([...segmentos, metodo.toLowerCase()].join("_")) || `rota_${metodo.toLowerCase()}`;
|
|
1052
|
+
}
|
|
1053
|
+
function camposDeParametrosRotaTypeScript(parametros) {
|
|
1054
|
+
return parametros.map((parametro) => ({
|
|
1055
|
+
nome: paraSnakeCase(parametro.nome),
|
|
1056
|
+
tipo: parametro.tipoSema,
|
|
1057
|
+
obrigatorio: true,
|
|
1058
|
+
}));
|
|
1059
|
+
}
|
|
1060
|
+
function extrairColecoesFirebaseImportacao(texto) {
|
|
1061
|
+
const encontrados = new Set();
|
|
1062
|
+
for (const match of texto.matchAll(/\b(?:export\s+)?const\s+\w*COLLECTIONS?\w*\s*=\s*\{([\s\S]*?)\n\}/g)) {
|
|
1063
|
+
const corpo = match[1] ?? "";
|
|
1064
|
+
for (const valor of corpo.matchAll(/:\s*["'`]([^"'`]+)["'`]/g)) {
|
|
1065
|
+
encontrados.add(valor[1]);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
return [...encontrados];
|
|
1069
|
+
}
|
|
1070
|
+
async function importarNextJsBase(diretorio, namespaceBase) {
|
|
1071
|
+
const escopo = resolverEscopoImportacaoNextJs(diretorio);
|
|
1072
|
+
const arquivos = await listarArquivosRecursivos(escopo.diretorioEscopo, [".ts", ".tsx", ".js", ".jsx"]);
|
|
1073
|
+
const uteis = arquivos.filter((arquivo) => !arquivo.endsWith(".spec.ts")
|
|
1074
|
+
&& !arquivo.endsWith(".test.ts")
|
|
1075
|
+
&& !arquivo.endsWith(".d.ts")
|
|
1076
|
+
&& /(\\|\/)(?:src\\|src\/)?app(\\|\/)api(\\|\/).+(\\|\/)route\.(ts|tsx|js|jsx)$/i.test(arquivo));
|
|
1077
|
+
const contextos = await Promise.all(uteis.map(async (arquivo) => {
|
|
1078
|
+
const texto = await readFile(arquivo, "utf8");
|
|
1079
|
+
const scriptKind = arquivo.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS;
|
|
1080
|
+
return {
|
|
1081
|
+
sourceFile: ts.createSourceFile(arquivo, texto, ts.ScriptTarget.Latest, true, scriptKind),
|
|
1082
|
+
texto,
|
|
1083
|
+
relacao: path.relative(escopo.baseProjeto, arquivo),
|
|
1084
|
+
};
|
|
1085
|
+
}));
|
|
1086
|
+
const tiposGlobais = consolidarTiposTs(contextos);
|
|
1087
|
+
const modulos = new Map();
|
|
1088
|
+
for (const contexto of contextos) {
|
|
1089
|
+
const entitiesRef = new Set();
|
|
1090
|
+
const enumsRef = new Set();
|
|
1091
|
+
const contextoSegmentos = inferirContextoPorArquivo(contexto.relacao.replace(/[\\/]route\.(?:ts|tsx|js|jsx)$/i, "")).filter((segmento, indice) => !(indice === 0 && segmento === "app"));
|
|
1092
|
+
const nomeModulo = [namespaceBase, ...contextoSegmentos].join(".");
|
|
1093
|
+
const tasks = [];
|
|
1094
|
+
const routes = [];
|
|
1095
|
+
for (const rota of extrairRotasTypeScriptHttp(contexto.sourceFile, contexto.relacao).filter((item) => item.origem === "nextjs")) {
|
|
1096
|
+
const taskNome = nomeTaskParaRotaTypeScript(rota.caminho, rota.metodo);
|
|
1097
|
+
const exportacao = localizarExportacaoTypeScriptHttp(contexto.sourceFile, rota.simbolo);
|
|
1098
|
+
const semantica = inferirSemanticaHandlerTypeScriptHttp(contexto.sourceFile, rota.simbolo);
|
|
1099
|
+
const input = deduplicarCampos([
|
|
1100
|
+
...camposDeParametrosRotaTypeScript(rota.parametros),
|
|
1101
|
+
...camposDeSemanticaTypeScriptHttp(semantica?.query ?? [], tiposGlobais, entitiesRef, enumsRef),
|
|
1102
|
+
...camposEstruturadosTypeScriptHttp("body", semantica?.bodyTipoTexto, tiposGlobais, entitiesRef, enumsRef),
|
|
1103
|
+
...camposDeSemanticaTypeScriptHttp(semantica?.body ?? [], tiposGlobais, entitiesRef, enumsRef),
|
|
1104
|
+
]);
|
|
1105
|
+
const output = semantica && semantica.response.length > 0
|
|
1106
|
+
? deduplicarCampos(camposDeSemanticaTypeScriptHttp(semantica.response, tiposGlobais, entitiesRef, enumsRef))
|
|
1107
|
+
: semantica?.responseTipoTexto
|
|
1108
|
+
? deduplicarCampos(expandirCamposTs("resultado", semantica.responseTipoTexto, tiposGlobais, entitiesRef, enumsRef, false))
|
|
1109
|
+
: exportacao?.retorno && mapearTipoPrimitivo(exportacao.retorno) === "Vazio"
|
|
1110
|
+
? []
|
|
1111
|
+
: deduplicarCampos(expandirCamposTs("resultado", exportacao?.retorno, tiposGlobais, entitiesRef, enumsRef, false));
|
|
1112
|
+
const taskOutput = output.length > 0 ? output : [{ nome: "resultado", tipo: "Json", obrigatorio: false }];
|
|
1113
|
+
const resumoBase = `Rota Next.js App Router importada automaticamente de ${contexto.relacao}#${rota.simbolo}.`;
|
|
1114
|
+
const errors = deduplicarErros([
|
|
1115
|
+
...(exportacao?.corpo ? extrairErrosTs(exportacao.corpo, contexto.sourceFile) : []),
|
|
1116
|
+
...errosPorStatusHttp(semantica?.errorStatuses ?? []),
|
|
1117
|
+
]);
|
|
1118
|
+
tasks.push({
|
|
1119
|
+
nome: taskNome,
|
|
1120
|
+
resumo: `Task derivada automaticamente de ${contexto.relacao}#${rota.simbolo}.`,
|
|
1121
|
+
input,
|
|
1122
|
+
output: taskOutput,
|
|
1123
|
+
errors,
|
|
1124
|
+
effects: exportacao?.corpo ? descreverEfeitosPorHeuristica(exportacao.corpo.getText(contexto.sourceFile)) : descreverEfeitosPorHeuristica(contexto.texto),
|
|
1125
|
+
impl: { ts: caminhoImplTs(escopo.baseProjeto, path.join(escopo.baseProjeto, contexto.relacao), rota.simbolo) },
|
|
1126
|
+
origemArquivo: contexto.relacao,
|
|
1127
|
+
origemSimbolo: rota.simbolo,
|
|
1128
|
+
});
|
|
1129
|
+
routes.push({
|
|
1130
|
+
nome: `${taskNome}_publico`,
|
|
1131
|
+
resumo: resumoBase,
|
|
1132
|
+
metodo: rota.metodo,
|
|
1133
|
+
caminho: rota.caminho,
|
|
1134
|
+
task: taskNome,
|
|
1135
|
+
input,
|
|
1136
|
+
output: taskOutput,
|
|
1137
|
+
errors,
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
if (!tasks.length && !routes.length) {
|
|
1141
|
+
continue;
|
|
1142
|
+
}
|
|
1143
|
+
sincronizarRotasComTasks(routes, tasks);
|
|
1144
|
+
const { entities, enums } = criarEntidadesReferenciadas(tiposGlobais, entitiesRef, enumsRef);
|
|
1145
|
+
modulos.set(nomeModulo, {
|
|
1146
|
+
nome: nomeModulo,
|
|
1147
|
+
resumo: `Rascunho Sema importado automaticamente de ${contexto.relacao}.`,
|
|
1148
|
+
tasks: deduplicarTarefas(tasks),
|
|
1149
|
+
routes: deduplicarRotas(routes),
|
|
1150
|
+
entities,
|
|
1151
|
+
enums,
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
return [...modulos.values()];
|
|
1155
|
+
}
|
|
1156
|
+
async function importarFirebaseBase(diretorio, namespaceBase) {
|
|
1157
|
+
const arquivos = await listarArquivosRecursivos(diretorio, [".ts", ".tsx", ".js", ".jsx"]);
|
|
1158
|
+
const uteis = arquivos.filter((arquivo) => !arquivo.endsWith(".spec.ts")
|
|
1159
|
+
&& !arquivo.endsWith(".test.ts")
|
|
1160
|
+
&& !arquivo.endsWith(".d.ts")
|
|
1161
|
+
&& /(sema_contract_bridge|health-check|collections?|firestore)/i.test(arquivo));
|
|
1162
|
+
const contextos = await Promise.all(uteis.map(async (arquivo) => {
|
|
1163
|
+
const texto = await readFile(arquivo, "utf8");
|
|
1164
|
+
const scriptKind = arquivo.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS;
|
|
1165
|
+
return {
|
|
1166
|
+
sourceFile: ts.createSourceFile(arquivo, texto, ts.ScriptTarget.Latest, true, scriptKind),
|
|
1167
|
+
texto,
|
|
1168
|
+
relacao: path.relative(diretorio, arquivo),
|
|
1169
|
+
};
|
|
1170
|
+
}));
|
|
1171
|
+
const tiposGlobais = consolidarTiposTs(contextos);
|
|
1172
|
+
const modulos = new Map();
|
|
1173
|
+
for (const contexto of contextos) {
|
|
1174
|
+
const entitiesRef = new Set();
|
|
1175
|
+
const enumsRef = new Set();
|
|
1176
|
+
const contextoSegmentos = inferirContextoPorArquivo(contexto.relacao);
|
|
1177
|
+
const nomeModulo = [namespaceBase, ...contextoSegmentos].join(".");
|
|
1178
|
+
const tasks = [];
|
|
1179
|
+
const routes = [];
|
|
1180
|
+
for (const node of contexto.sourceFile.statements) {
|
|
1181
|
+
if (ts.isFunctionDeclaration(node) && node.name && node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
|
|
1182
|
+
const nome = node.name.text;
|
|
1183
|
+
const input = node.parameters.flatMap((parametro) => expandirCamposTs(parametro.name.getText(contexto.sourceFile), parametro.type?.getText(contexto.sourceFile), tiposGlobais, entitiesRef, enumsRef, !parametro.questionToken));
|
|
1184
|
+
const output = node.type?.getText(contexto.sourceFile) && mapearTipoPrimitivo(node.type.getText(contexto.sourceFile)) === "Vazio"
|
|
1185
|
+
? []
|
|
1186
|
+
: deduplicarCampos(expandirCamposTs("resultado", node.type?.getText(contexto.sourceFile), tiposGlobais, entitiesRef, enumsRef, false));
|
|
1187
|
+
tasks.push({
|
|
1188
|
+
nome: paraSnakeCase(nome.replace(/^sema/, "")) || paraSnakeCase(nome),
|
|
1189
|
+
resumo: `Task Firebase/worker importada automaticamente de ${contexto.relacao}#${nome}.`,
|
|
1190
|
+
input: deduplicarCampos(input),
|
|
1191
|
+
output: output.length > 0 ? output : [{ nome: "resultado", tipo: "Json", obrigatorio: false }],
|
|
1192
|
+
errors: node.body ? extrairErrosTs(node.body, contexto.sourceFile) : [],
|
|
1193
|
+
effects: node.body ? descreverEfeitosPorHeuristica(node.body.getText(contexto.sourceFile)) : descreverEfeitosPorHeuristica(contexto.texto),
|
|
1194
|
+
impl: { ts: caminhoImplTs(diretorio, path.join(diretorio, contexto.relacao), nome) },
|
|
1195
|
+
origemArquivo: contexto.relacao,
|
|
1196
|
+
origemSimbolo: nome,
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
for (const rota of extrairRotasTypeScriptHttp(contexto.sourceFile, contexto.relacao).filter((item) => item.origem === "firebase")) {
|
|
1201
|
+
const taskNome = nomeTaskParaRotaTypeScript(rota.caminho, rota.metodo);
|
|
1202
|
+
const exportacao = localizarExportacaoTypeScriptHttp(contexto.sourceFile, rota.simbolo);
|
|
1203
|
+
const input = deduplicarCampos(camposDeParametrosRotaTypeScript(rota.parametros));
|
|
1204
|
+
const output = exportacao?.retorno && mapearTipoPrimitivo(exportacao.retorno) === "Vazio"
|
|
1205
|
+
? []
|
|
1206
|
+
: deduplicarCampos(expandirCamposTs("resultado", exportacao?.retorno, tiposGlobais, entitiesRef, enumsRef, false));
|
|
1207
|
+
const taskOutput = output.length > 0 ? output : [{ nome: "resultado", tipo: "Json", obrigatorio: false }];
|
|
1208
|
+
tasks.push({
|
|
1209
|
+
nome: taskNome,
|
|
1210
|
+
resumo: `Task HTTP do worker importada automaticamente de ${contexto.relacao}#${rota.simbolo}.`,
|
|
1211
|
+
input,
|
|
1212
|
+
output: taskOutput,
|
|
1213
|
+
errors: exportacao?.corpo ? extrairErrosTs(exportacao.corpo, contexto.sourceFile) : [],
|
|
1214
|
+
effects: exportacao?.corpo ? descreverEfeitosPorHeuristica(exportacao.corpo.getText(contexto.sourceFile)) : descreverEfeitosPorHeuristica(contexto.texto),
|
|
1215
|
+
impl: { ts: caminhoImplTs(diretorio, path.join(diretorio, contexto.relacao), rota.simbolo) },
|
|
1216
|
+
origemArquivo: contexto.relacao,
|
|
1217
|
+
origemSimbolo: rota.simbolo,
|
|
1218
|
+
});
|
|
1219
|
+
routes.push({
|
|
1220
|
+
nome: `${taskNome}_publico`,
|
|
1221
|
+
resumo: `Rota do worker importada automaticamente de ${contexto.relacao}#${rota.simbolo}.`,
|
|
1222
|
+
metodo: rota.metodo,
|
|
1223
|
+
caminho: rota.caminho,
|
|
1224
|
+
task: taskNome,
|
|
1225
|
+
input,
|
|
1226
|
+
output: taskOutput,
|
|
1227
|
+
errors: exportacao?.corpo ? extrairErrosTs(exportacao.corpo, contexto.sourceFile) : [],
|
|
1228
|
+
});
|
|
1229
|
+
}
|
|
1230
|
+
for (const colecao of extrairColecoesFirebaseImportacao(contexto.texto)) {
|
|
1231
|
+
tasks.push({
|
|
1232
|
+
nome: paraSnakeCase(`inventariar_${colecao}`),
|
|
1233
|
+
resumo: `Task sintetica para registrar o recurso persistido ${colecao} descoberto em ${contexto.relacao}.`,
|
|
1234
|
+
input: [],
|
|
1235
|
+
output: [{ nome: "colecao", tipo: "Texto", obrigatorio: false }],
|
|
1236
|
+
errors: [],
|
|
1237
|
+
effects: [{ categoria: "persistencia", alvo: colecao, criticidade: "media" }],
|
|
1238
|
+
origemArquivo: contexto.relacao,
|
|
1239
|
+
origemSimbolo: colecao,
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
if (!tasks.length && !routes.length) {
|
|
1243
|
+
continue;
|
|
1244
|
+
}
|
|
1245
|
+
sincronizarRotasComTasks(routes, tasks);
|
|
1246
|
+
const { entities, enums } = criarEntidadesReferenciadas(tiposGlobais, entitiesRef, enumsRef);
|
|
1247
|
+
modulos.set(nomeModulo, {
|
|
1248
|
+
nome: nomeModulo,
|
|
1249
|
+
resumo: `Rascunho Sema importado automaticamente de ${contexto.relacao}.`,
|
|
1250
|
+
tasks: deduplicarTarefas(tasks),
|
|
1251
|
+
routes: deduplicarRotas(routes),
|
|
1252
|
+
entities,
|
|
1253
|
+
enums,
|
|
1254
|
+
});
|
|
1255
|
+
}
|
|
1256
|
+
return [...modulos.values()];
|
|
1257
|
+
}
|
|
1258
|
+
function extrairTiposPython(texto) {
|
|
1259
|
+
const encontrados = new Map();
|
|
1260
|
+
const regexBaseModel = /^class\s+(\w+)(?:\(([^)]*)\))?:\n((?:^[ \t].*(?:\n|$))*)/gm;
|
|
1261
|
+
for (const match of texto.matchAll(regexBaseModel)) {
|
|
1262
|
+
const [, nome, bases = "", corpo] = match;
|
|
1263
|
+
if (!bases.includes("BaseModel")) {
|
|
1264
|
+
continue;
|
|
1265
|
+
}
|
|
1266
|
+
const campos = [...corpo.matchAll(/^\s{4}(\w+)\s*:\s*([^\n=]+)(?:\s*=.+)?$/gm)].map((campo) => ({
|
|
1267
|
+
nome: campo[1],
|
|
1268
|
+
tipoTexto: campo[2].trim(),
|
|
1269
|
+
obrigatorio: !/=/.test(campo[0]),
|
|
1270
|
+
}));
|
|
1271
|
+
encontrados.set(nome, { tipo: "objeto", nome: nome, campos });
|
|
1272
|
+
}
|
|
1273
|
+
const regexEnum = /^class\s+(\w+)(?:\(([^)]*)\))?:\n((?:^[ \t].*(?:\n|$))*)/gm;
|
|
1274
|
+
for (const match of texto.matchAll(regexEnum)) {
|
|
1275
|
+
const [, nome, bases = "", corpo] = match;
|
|
1276
|
+
if (!/(Enum|StrEnum)/.test(bases)) {
|
|
1277
|
+
continue;
|
|
1278
|
+
}
|
|
1279
|
+
const valores = [...corpo.matchAll(/^\s{4}(\w+)\s*=\s*["']([^"']+)["']$/gm)].map((valor) => valor[1]).filter(Boolean);
|
|
1280
|
+
if (valores.length) {
|
|
1281
|
+
encontrados.set(nome, { tipo: "enum", nome: nome, valores });
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
return encontrados;
|
|
1285
|
+
}
|
|
1286
|
+
function mapearTipoPythonParaSema(tipoTexto, tipos, entidadesReferenciadas, enumsReferenciados) {
|
|
1287
|
+
if (!tipoTexto) {
|
|
1288
|
+
return "Json";
|
|
1289
|
+
}
|
|
1290
|
+
const limpo = tipoTexto.replace(/\s+/g, "");
|
|
1291
|
+
const basico = mapearTipoPrimitivo(limpo);
|
|
1292
|
+
if (basico !== limpo) {
|
|
1293
|
+
return basico;
|
|
1294
|
+
}
|
|
1295
|
+
const simples = limpo.replace(/Optional\[(.+)\]/, "$1").replace(/list\[(.+)\]/i, "Json").replace(/dict\[(.+)\]/i, "Json");
|
|
1296
|
+
if (tipos.has(simples)) {
|
|
1297
|
+
const encontrado = tipos.get(simples);
|
|
1298
|
+
if (encontrado.tipo === "enum") {
|
|
1299
|
+
enumsReferenciados.add(encontrado.nome);
|
|
1300
|
+
return encontrado.nome;
|
|
1301
|
+
}
|
|
1302
|
+
if (!pareceWrapperTipo(encontrado.nome)) {
|
|
1303
|
+
entidadesReferenciadas.add(encontrado.nome);
|
|
1304
|
+
return encontrado.nome;
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
return "Json";
|
|
1308
|
+
}
|
|
1309
|
+
function expandirCamposPython(nomeParametro, tipoTexto, tipos, entidadesReferenciadas, enumsReferenciados, obrigatorio) {
|
|
1310
|
+
if (!tipoTexto) {
|
|
1311
|
+
return [{ nome: paraSnakeCase(nomeParametro), tipo: "Json", obrigatorio }];
|
|
1312
|
+
}
|
|
1313
|
+
const limpo = tipoTexto.replace(/\s+/g, "").replace(/Optional\[(.+)\]/, "$1");
|
|
1314
|
+
const descoberto = tipos.get(limpo);
|
|
1315
|
+
if (descoberto?.tipo === "objeto" && pareceWrapperTipo(descoberto.nome) && descoberto.campos) {
|
|
1316
|
+
return descoberto.campos.map((campo) => ({
|
|
1317
|
+
nome: paraSnakeCase(campo.nome),
|
|
1318
|
+
tipo: mapearTipoPythonParaSema(campo.tipoTexto, tipos, entidadesReferenciadas, enumsReferenciados),
|
|
1319
|
+
obrigatorio: campo.obrigatorio,
|
|
1320
|
+
}));
|
|
1321
|
+
}
|
|
1322
|
+
return [{
|
|
1323
|
+
nome: paraSnakeCase(nomeParametro),
|
|
1324
|
+
tipo: mapearTipoPythonParaSema(tipoTexto, tipos, entidadesReferenciadas, enumsReferenciados),
|
|
1325
|
+
obrigatorio,
|
|
1326
|
+
}];
|
|
1327
|
+
}
|
|
1328
|
+
function criarEntidadesPython(tipos, entidadesReferenciadas, enumsReferenciados) {
|
|
1329
|
+
const entities = [...entidadesReferenciadas]
|
|
1330
|
+
.map((nome) => tipos.get(nome))
|
|
1331
|
+
.filter((item) => Boolean(item?.tipo === "objeto" && item.campos))
|
|
1332
|
+
.map((item) => ({
|
|
1333
|
+
nome: item.nome,
|
|
1334
|
+
campos: item.campos.map((campo) => ({
|
|
1335
|
+
nome: paraSnakeCase(campo.nome),
|
|
1336
|
+
tipo: mapearTipoPythonParaSema(campo.tipoTexto, tipos, entidadesReferenciadas, enumsReferenciados),
|
|
1337
|
+
obrigatorio: campo.obrigatorio,
|
|
1338
|
+
})),
|
|
1339
|
+
}));
|
|
1340
|
+
const enums = [...enumsReferenciados]
|
|
1341
|
+
.map((nome) => tipos.get(nome))
|
|
1342
|
+
.filter((item) => Boolean(item?.tipo === "enum" && item.valores))
|
|
1343
|
+
.map((item) => ({
|
|
1344
|
+
nome: item.nome,
|
|
1345
|
+
valores: item.valores,
|
|
1346
|
+
}));
|
|
1347
|
+
return { entities: deduplicarEntidades(entities), enums: deduplicarEnums(enums) };
|
|
1348
|
+
}
|
|
1349
|
+
function extrairErrosPython(texto) {
|
|
1350
|
+
const erros = new Map();
|
|
1351
|
+
for (const match of texto.matchAll(/raise\s+(\w+)(?:\(([^)]*)\))?/g)) {
|
|
1352
|
+
const nomeBruto = match[1];
|
|
1353
|
+
const mensagem = (match[2] ?? "").match(/["']([^"']+)["']/)?.[1] ?? `Erro importado automaticamente de ${nomeBruto}.`;
|
|
1354
|
+
erros.set(normalizarNomeErroBruto(nomeBruto), mensagem);
|
|
1355
|
+
}
|
|
1356
|
+
return [...erros.entries()].map(([nome, mensagem]) => ({ nome, mensagem }));
|
|
1357
|
+
}
|
|
1358
|
+
function caminhoImplGenerico(diretorioBase, arquivo, simbolo, opcoes) {
|
|
1359
|
+
const relativo = path.relative(diretorioBase, arquivo).replace(/\.[^.]+$/, "");
|
|
1360
|
+
const segmentos = relativo.split(path.sep).map((segmento, indice, lista) => opcoes?.snakeCaseUltimoArquivo && indice === lista.length - 1
|
|
1361
|
+
? paraSnakeCase(segmento)
|
|
1362
|
+
: paraIdentificadorModulo(segmento))
|
|
1363
|
+
.filter(Boolean);
|
|
1364
|
+
return [...segmentos, simbolo].join(".");
|
|
1365
|
+
}
|
|
1366
|
+
function caminhoImplPython(diretorioBase, arquivo, simbolo) {
|
|
1367
|
+
return caminhoImplGenerico(diretorioBase, arquivo, simbolo);
|
|
1368
|
+
}
|
|
1369
|
+
function caminhoImplDart(diretorioBase, arquivo, simbolo) {
|
|
1370
|
+
return caminhoImplGenerico(diretorioBase, arquivo, simbolo);
|
|
1371
|
+
}
|
|
1372
|
+
function dividirParametrosPython(parametros) {
|
|
1373
|
+
const partes = [];
|
|
1374
|
+
let atual = "";
|
|
1375
|
+
let profundidade = 0;
|
|
1376
|
+
for (const caractere of parametros) {
|
|
1377
|
+
if (caractere === "," && profundidade === 0) {
|
|
1378
|
+
if (atual.trim()) {
|
|
1379
|
+
partes.push(atual.trim());
|
|
1380
|
+
}
|
|
1381
|
+
atual = "";
|
|
1382
|
+
continue;
|
|
1383
|
+
}
|
|
1384
|
+
if (["[", "(", "{", "<"].includes(caractere)) {
|
|
1385
|
+
profundidade += 1;
|
|
1386
|
+
}
|
|
1387
|
+
else if (["]", ")", "}", ">"].includes(caractere) && profundidade > 0) {
|
|
1388
|
+
profundidade -= 1;
|
|
1389
|
+
}
|
|
1390
|
+
atual += caractere;
|
|
1391
|
+
}
|
|
1392
|
+
if (atual.trim()) {
|
|
1393
|
+
partes.push(atual.trim());
|
|
1394
|
+
}
|
|
1395
|
+
return partes;
|
|
1396
|
+
}
|
|
1397
|
+
function extrairAssinaturaParametrosPython(parametros) {
|
|
1398
|
+
const assinatura = new Map();
|
|
1399
|
+
for (const item of dividirParametrosPython(parametros)) {
|
|
1400
|
+
if (!item || item.startsWith("self") || item.startsWith("cls") || item.startsWith("*")) {
|
|
1401
|
+
continue;
|
|
1402
|
+
}
|
|
1403
|
+
const obrigatorio = !item.includes("=");
|
|
1404
|
+
const semValorPadrao = item.split("=")[0]?.trim() ?? item.trim();
|
|
1405
|
+
const [nomeBruto, tipo] = semValorPadrao.split(":").map((parte) => parte.trim());
|
|
1406
|
+
const nome = nomeBruto?.replace(/^\*{1,2}/, "").trim();
|
|
1407
|
+
if (!nome) {
|
|
1408
|
+
continue;
|
|
1409
|
+
}
|
|
1410
|
+
assinatura.set(nome, {
|
|
1411
|
+
tipoTexto: tipo || undefined,
|
|
1412
|
+
obrigatorio,
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
return assinatura;
|
|
1416
|
+
}
|
|
1417
|
+
function mapearConversorFlaskParaSema(conversor) {
|
|
1418
|
+
switch ((conversor ?? "").toLowerCase()) {
|
|
1419
|
+
case "int":
|
|
1420
|
+
return "Inteiro";
|
|
1421
|
+
case "float":
|
|
1422
|
+
return "Decimal";
|
|
1423
|
+
case "uuid":
|
|
1424
|
+
return "Id";
|
|
1425
|
+
case "path":
|
|
1426
|
+
default:
|
|
1427
|
+
return "Texto";
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
function criarInputRotaFlask(caminho, parametros, tiposGlobais, entitiesRef, enumsRef) {
|
|
1431
|
+
const assinatura = extrairAssinaturaParametrosPython(parametros);
|
|
1432
|
+
return extrairParametrosCaminhoFlask(caminho).map((parametro) => {
|
|
1433
|
+
const correspondente = assinatura.get(parametro.nome);
|
|
1434
|
+
return {
|
|
1435
|
+
nome: paraSnakeCase(parametro.nome),
|
|
1436
|
+
tipo: correspondente?.tipoTexto
|
|
1437
|
+
? mapearTipoPythonParaSema(correspondente.tipoTexto, tiposGlobais, entitiesRef, enumsRef)
|
|
1438
|
+
: mapearConversorFlaskParaSema(parametro.conversor),
|
|
1439
|
+
obrigatorio: correspondente?.obrigatorio ?? true,
|
|
1440
|
+
};
|
|
1441
|
+
});
|
|
1442
|
+
}
|
|
1443
|
+
async function importarPythonBase(diretorio, namespaceBase, modoHttp = "nenhum") {
|
|
1444
|
+
const arquivos = (await listarArquivosRecursivos(diretorio, [".py"]))
|
|
1445
|
+
.filter((arquivo) => !arquivo.endsWith("__init__.py") && !/tests?[\\/]/i.test(arquivo));
|
|
1446
|
+
const textos = new Map();
|
|
1447
|
+
const tiposGlobais = new Map();
|
|
1448
|
+
for (const arquivo of arquivos) {
|
|
1449
|
+
const texto = await readFile(arquivo, "utf8");
|
|
1450
|
+
textos.set(arquivo, texto);
|
|
1451
|
+
for (const [nome, tipo] of extrairTiposPython(texto)) {
|
|
1452
|
+
if (!tiposGlobais.has(nome)) {
|
|
1453
|
+
tiposGlobais.set(nome, tipo);
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
const modulos = new Map();
|
|
1458
|
+
for (const arquivo of arquivos) {
|
|
1459
|
+
const texto = textos.get(arquivo);
|
|
1460
|
+
const relacao = path.relative(diretorio, arquivo);
|
|
1461
|
+
const contextoSegmentos = inferirContextoPorArquivo(relacao);
|
|
1462
|
+
const nomeModulo = [namespaceBase, ...contextoSegmentos].join(".");
|
|
1463
|
+
const entitiesRef = new Set();
|
|
1464
|
+
const enumsRef = new Set();
|
|
1465
|
+
const tasks = [];
|
|
1466
|
+
const routes = [];
|
|
1467
|
+
if (modoHttp === "fastapi") {
|
|
1468
|
+
const prefixo = texto.match(/APIRouter\s*\(\s*prefix\s*=\s*["']([^"']+)["']/)?.[1];
|
|
1469
|
+
const routeRegex = /@(?:router|app)\.(get|post|put|patch|delete)\(([^)]*)\)\s*\n(?:async\s+)?def\s+(\w+)\(([^)]*)\)(?:\s*->\s*([^:]+))?:/g;
|
|
1470
|
+
for (const match of texto.matchAll(routeRegex)) {
|
|
1471
|
+
const metodo = match[1].toUpperCase();
|
|
1472
|
+
const argumentoDecorator = match[2] ?? "";
|
|
1473
|
+
const sufixo = argumentoDecorator.match(/["']([^"']+)["']/)?.[1];
|
|
1474
|
+
const nomeFuncao = match[3];
|
|
1475
|
+
const parametros = match[4];
|
|
1476
|
+
const retorno = match[5]?.trim();
|
|
1477
|
+
const routeInput = parametros
|
|
1478
|
+
.split(",")
|
|
1479
|
+
.map((item) => item.trim())
|
|
1480
|
+
.filter(Boolean)
|
|
1481
|
+
.filter((item) => !item.startsWith("self") && !item.startsWith("cls"))
|
|
1482
|
+
.flatMap((item) => {
|
|
1483
|
+
const [nome, tipo] = item.split(":").map((parte) => parte.trim());
|
|
1484
|
+
const obrigatorio = !item.includes("=");
|
|
1485
|
+
return expandirCamposPython(nome, tipo, tiposGlobais, entitiesRef, enumsRef, obrigatorio);
|
|
1486
|
+
});
|
|
1487
|
+
const routeOutput = retorno && mapearTipoPrimitivo(retorno) === "Vazio"
|
|
1488
|
+
? []
|
|
1489
|
+
: deduplicarCampos(expandirCamposPython("resultado", retorno, tiposGlobais, entitiesRef, enumsRef, false));
|
|
1490
|
+
const taskNome = paraSnakeCase(nomeFuncao);
|
|
1491
|
+
routes.push({
|
|
1492
|
+
nome: `${taskNome}_publico`,
|
|
1493
|
+
resumo: `Rota FastAPI importada automaticamente de ${relacao}#${nomeFuncao}.`,
|
|
1494
|
+
metodo,
|
|
1495
|
+
caminho: juntarCaminhoHttp(prefixo, sufixo),
|
|
1496
|
+
task: taskNome,
|
|
1497
|
+
input: deduplicarCampos(routeInput),
|
|
1498
|
+
output: routeOutput,
|
|
1499
|
+
errors: [],
|
|
1500
|
+
});
|
|
1501
|
+
}
|
|
1502
|
+
}
|
|
1503
|
+
else if (modoHttp === "flask") {
|
|
1504
|
+
for (const rota of extrairRotasFlaskDecoradas(texto)) {
|
|
1505
|
+
const taskNome = paraSnakeCase(rota.nomeFuncao);
|
|
1506
|
+
const nomeBase = `${taskNome}_publico`;
|
|
1507
|
+
const nome = routes.some((route) => route.nome === nomeBase)
|
|
1508
|
+
? `${taskNome}_${rota.metodo.toLowerCase()}_publico`
|
|
1509
|
+
: nomeBase;
|
|
1510
|
+
routes.push({
|
|
1511
|
+
nome,
|
|
1512
|
+
resumo: `Rota Flask importada automaticamente de ${relacao}#${rota.nomeFuncao}.`,
|
|
1513
|
+
metodo: rota.metodo,
|
|
1514
|
+
caminho: rota.caminho,
|
|
1515
|
+
task: taskNome,
|
|
1516
|
+
input: deduplicarCampos(criarInputRotaFlask(rota.caminho, rota.parametros, tiposGlobais, entitiesRef, enumsRef)),
|
|
1517
|
+
output: [],
|
|
1518
|
+
errors: [],
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
const funcRegex = /^(async\s+def|def)\s+(\w+)\(([^)]*)\)(?:\s*->\s*([^:]+))?:/gm;
|
|
1523
|
+
for (const match of texto.matchAll(funcRegex)) {
|
|
1524
|
+
const nomeFuncao = match[2];
|
|
1525
|
+
if (nomeFuncao.startsWith("_")) {
|
|
1526
|
+
continue;
|
|
1527
|
+
}
|
|
1528
|
+
const parametros = match[3];
|
|
1529
|
+
const retorno = match[4]?.trim();
|
|
1530
|
+
const inicioCorpo = match.index ?? 0;
|
|
1531
|
+
const trecho = texto.slice(inicioCorpo, Math.min(texto.length, inicioCorpo + 1500));
|
|
1532
|
+
const input = parametros
|
|
1533
|
+
.split(",")
|
|
1534
|
+
.map((item) => item.trim())
|
|
1535
|
+
.filter(Boolean)
|
|
1536
|
+
.filter((item) => !item.startsWith("self") && !item.startsWith("cls"))
|
|
1537
|
+
.flatMap((item) => {
|
|
1538
|
+
const [nome, tipo] = item.split(":").map((parte) => parte.trim());
|
|
1539
|
+
const obrigatorio = !item.includes("=");
|
|
1540
|
+
return expandirCamposPython(nome, tipo, tiposGlobais, entitiesRef, enumsRef, obrigatorio);
|
|
1541
|
+
});
|
|
1542
|
+
const output = retorno && mapearTipoPrimitivo(retorno) === "Vazio"
|
|
1543
|
+
? []
|
|
1544
|
+
: deduplicarCampos(expandirCamposPython("resultado", retorno, tiposGlobais, entitiesRef, enumsRef, false));
|
|
1545
|
+
tasks.push({
|
|
1546
|
+
nome: paraSnakeCase(nomeFuncao),
|
|
1547
|
+
resumo: `Task importada automaticamente de ${relacao}#${nomeFuncao}.`,
|
|
1548
|
+
input: deduplicarCampos(input),
|
|
1549
|
+
output,
|
|
1550
|
+
errors: extrairErrosPython(trecho),
|
|
1551
|
+
effects: descreverEfeitosPorHeuristica(trecho),
|
|
1552
|
+
impl: { py: caminhoImplPython(diretorio, arquivo, nomeFuncao) },
|
|
1553
|
+
origemArquivo: relacao,
|
|
1554
|
+
origemSimbolo: nomeFuncao,
|
|
1555
|
+
});
|
|
1556
|
+
}
|
|
1557
|
+
const classRegex = /^class\s+(\w+)(?:\(([^)]*)\))?:\n((?:^[ \t].*(?:\n|$))*)/gm;
|
|
1558
|
+
for (const match of texto.matchAll(classRegex)) {
|
|
1559
|
+
const nomeClasse = match[1];
|
|
1560
|
+
const bases = match[2] ?? "";
|
|
1561
|
+
const corpo = match[3];
|
|
1562
|
+
if (/(BaseModel|Enum|StrEnum)/.test(bases)) {
|
|
1563
|
+
continue;
|
|
1564
|
+
}
|
|
1565
|
+
for (const metodo of corpo.matchAll(/^\s{4}(?:async\s+def|def)\s+(\w+)\(([^)]*)\)(?:\s*->\s*([^:]+))?:/gm)) {
|
|
1566
|
+
const nomeMetodo = metodo[1];
|
|
1567
|
+
if (nomeMetodo.startsWith("_")) {
|
|
1568
|
+
continue;
|
|
1569
|
+
}
|
|
1570
|
+
const parametros = metodo[2];
|
|
1571
|
+
const retorno = metodo[3]?.trim();
|
|
1572
|
+
const input = parametros
|
|
1573
|
+
.split(",")
|
|
1574
|
+
.map((item) => item.trim())
|
|
1575
|
+
.filter(Boolean)
|
|
1576
|
+
.filter((item) => !item.startsWith("self") && !item.startsWith("cls"))
|
|
1577
|
+
.flatMap((item) => {
|
|
1578
|
+
const [nome, tipo] = item.split(":").map((parte) => parte.trim());
|
|
1579
|
+
const obrigatorio = !item.includes("=");
|
|
1580
|
+
return expandirCamposPython(nome, tipo, tiposGlobais, entitiesRef, enumsRef, obrigatorio);
|
|
1581
|
+
});
|
|
1582
|
+
const output = retorno && mapearTipoPrimitivo(retorno) === "Vazio"
|
|
1583
|
+
? []
|
|
1584
|
+
: deduplicarCampos(expandirCamposPython("resultado", retorno, tiposGlobais, entitiesRef, enumsRef, false));
|
|
1585
|
+
tasks.push({
|
|
1586
|
+
nome: paraSnakeCase(nomeMetodo),
|
|
1587
|
+
resumo: `Task importada automaticamente de ${relacao}#${nomeClasse}.${nomeMetodo}.`,
|
|
1588
|
+
input: deduplicarCampos(input),
|
|
1589
|
+
output,
|
|
1590
|
+
errors: extrairErrosPython(corpo),
|
|
1591
|
+
effects: descreverEfeitosPorHeuristica(corpo),
|
|
1592
|
+
impl: { py: caminhoImplPython(diretorio, arquivo, `${nomeClasse}.${nomeMetodo}`) },
|
|
1593
|
+
origemArquivo: relacao,
|
|
1594
|
+
origemSimbolo: `${nomeClasse}.${nomeMetodo}`,
|
|
1595
|
+
});
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
if (!tasks.length && !routes.length) {
|
|
1599
|
+
continue;
|
|
1600
|
+
}
|
|
1601
|
+
const { entities, enums } = criarEntidadesPython(tiposGlobais, entitiesRef, enumsRef);
|
|
1602
|
+
sincronizarRotasComTasks(routes, tasks);
|
|
1603
|
+
modulos.set(nomeModulo, {
|
|
1604
|
+
nome: nomeModulo,
|
|
1605
|
+
resumo: `Rascunho Sema importado automaticamente de ${relacao}.`,
|
|
1606
|
+
tasks: deduplicarTarefas(tasks),
|
|
1607
|
+
routes: deduplicarRotas(routes),
|
|
1608
|
+
entities,
|
|
1609
|
+
enums,
|
|
1610
|
+
});
|
|
1611
|
+
}
|
|
1612
|
+
return [...modulos.values()];
|
|
1613
|
+
}
|
|
1614
|
+
async function importarDartBase(diretorio, namespaceBase) {
|
|
1615
|
+
const arquivos = (await listarArquivosRecursivos(diretorio, [".dart"]))
|
|
1616
|
+
.filter((arquivo) => !arquivo.endsWith(".g.dart") && !arquivo.endsWith(".freezed.dart"));
|
|
1617
|
+
const modulos = [];
|
|
1618
|
+
for (const arquivo of arquivos) {
|
|
1619
|
+
const texto = await readFile(arquivo, "utf8");
|
|
1620
|
+
const relacao = path.relative(diretorio, arquivo);
|
|
1621
|
+
const contextoSegmentos = inferirContextoPorArquivo(relacao);
|
|
1622
|
+
const nomeModulo = [namespaceBase, ...contextoSegmentos].join(".");
|
|
1623
|
+
const tasks = [];
|
|
1624
|
+
for (const match of texto.matchAll(/(?:Future<([^\n]+)>|([\w?<>.,\s]+))\s+(\w+)\(([^)]*)\)\s*(?:async\s*)?\{/g)) {
|
|
1625
|
+
const retorno = (match[1] ?? match[2] ?? "").trim();
|
|
1626
|
+
const nome = match[3];
|
|
1627
|
+
if (["build", "toString", "hashCode"].includes(nome)) {
|
|
1628
|
+
continue;
|
|
1629
|
+
}
|
|
1630
|
+
const parametros = match[4];
|
|
1631
|
+
const input = parametros
|
|
1632
|
+
.split(",")
|
|
1633
|
+
.map((item) => item.trim())
|
|
1634
|
+
.filter(Boolean)
|
|
1635
|
+
.map((item) => item.replace(/^(required|final)\s+/g, ""))
|
|
1636
|
+
.map((item) => {
|
|
1637
|
+
const partes = item.split(/\s+/);
|
|
1638
|
+
const nomeParametro = partes.at(-1) ?? "param";
|
|
1639
|
+
const tipoParametro = partes.length > 1 ? partes.slice(0, -1).join(" ") : undefined;
|
|
1640
|
+
return {
|
|
1641
|
+
nome: paraSnakeCase(nomeParametro),
|
|
1642
|
+
tipo: mapearTipoPrimitivo(tipoParametro ?? "Json"),
|
|
1643
|
+
obrigatorio: item.includes("required"),
|
|
1644
|
+
};
|
|
1645
|
+
});
|
|
1646
|
+
const output = retorno && mapearTipoPrimitivo(retorno) === "Vazio"
|
|
1647
|
+
? []
|
|
1648
|
+
: [{ nome: "resultado", tipo: mapearTipoPrimitivo(retorno || "Json"), obrigatorio: false }];
|
|
1649
|
+
tasks.push({
|
|
1650
|
+
nome: paraSnakeCase(nome),
|
|
1651
|
+
resumo: `Task importada automaticamente de ${relacao}#${nome}.`,
|
|
1652
|
+
input,
|
|
1653
|
+
output,
|
|
1654
|
+
errors: [],
|
|
1655
|
+
effects: descreverEfeitosPorHeuristica(texto),
|
|
1656
|
+
impl: { dart: caminhoImplDart(diretorio, arquivo, nome) },
|
|
1657
|
+
origemArquivo: relacao,
|
|
1658
|
+
origemSimbolo: nome,
|
|
1659
|
+
});
|
|
1660
|
+
}
|
|
1661
|
+
if (tasks.length === 0) {
|
|
1662
|
+
continue;
|
|
1663
|
+
}
|
|
1664
|
+
modulos.push({
|
|
1665
|
+
nome: nomeModulo,
|
|
1666
|
+
resumo: `Rascunho Sema importado automaticamente de ${relacao}.`,
|
|
1667
|
+
tasks: deduplicarTarefas(tasks),
|
|
1668
|
+
routes: [],
|
|
1669
|
+
entities: [],
|
|
1670
|
+
enums: [],
|
|
1671
|
+
});
|
|
1672
|
+
}
|
|
1673
|
+
return modulos;
|
|
1674
|
+
}
|
|
1675
|
+
function criarModuloImportadoSimples(nome, resumo, tasks, routes = []) {
|
|
1676
|
+
sincronizarRotasComTasks(routes, tasks);
|
|
1677
|
+
return {
|
|
1678
|
+
nome,
|
|
1679
|
+
resumo,
|
|
1680
|
+
tasks: deduplicarTarefas(tasks),
|
|
1681
|
+
routes: deduplicarRotas(routes),
|
|
1682
|
+
entities: [],
|
|
1683
|
+
enums: [],
|
|
1684
|
+
};
|
|
1685
|
+
}
|
|
1686
|
+
function acumularModuloImportado(modulos, modulo) {
|
|
1687
|
+
const existente = modulos.get(modulo.nome);
|
|
1688
|
+
if (!existente) {
|
|
1689
|
+
modulos.set(modulo.nome, modulo);
|
|
1690
|
+
return;
|
|
1691
|
+
}
|
|
1692
|
+
existente.tasks = deduplicarTarefas([...existente.tasks, ...modulo.tasks]);
|
|
1693
|
+
existente.routes = deduplicarRotas([...existente.routes, ...modulo.routes]);
|
|
1694
|
+
existente.entities = deduplicarEntidades([...existente.entities, ...modulo.entities]);
|
|
1695
|
+
existente.enums = deduplicarEnums([...existente.enums, ...modulo.enums]);
|
|
1696
|
+
}
|
|
1697
|
+
function selecionarSimbolosPreferidos(simbolos) {
|
|
1698
|
+
const mapa = new Map();
|
|
1699
|
+
for (const simbolo of simbolos) {
|
|
1700
|
+
const chave = simbolo.simbolo.split(".").at(-1) ?? simbolo.simbolo;
|
|
1701
|
+
const existente = mapa.get(chave);
|
|
1702
|
+
if (!existente) {
|
|
1703
|
+
mapa.set(chave, simbolo);
|
|
1704
|
+
continue;
|
|
1705
|
+
}
|
|
1706
|
+
const pontuacaoAtual = simbolo.simbolo.split(".").length;
|
|
1707
|
+
const pontuacaoExistente = existente.simbolo.split(".").length;
|
|
1708
|
+
if (pontuacaoAtual > pontuacaoExistente) {
|
|
1709
|
+
mapa.set(chave, simbolo);
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
return [...mapa.values()];
|
|
1713
|
+
}
|
|
1714
|
+
async function existeArquivo(caminho) {
|
|
1715
|
+
try {
|
|
1716
|
+
await access(caminho);
|
|
1717
|
+
return true;
|
|
1718
|
+
}
|
|
1719
|
+
catch {
|
|
1720
|
+
return false;
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
async function resolverArquivoRustParaSimbolo(diretorio, relacaoFonte, simbolo) {
|
|
1724
|
+
const partes = simbolo.split(".").filter(Boolean);
|
|
1725
|
+
if (partes.length <= 1) {
|
|
1726
|
+
return path.join(diretorio, relacaoFonte);
|
|
1727
|
+
}
|
|
1728
|
+
const moduloPartes = partes.slice(0, -1);
|
|
1729
|
+
const baseAtual = path.dirname(relacaoFonte);
|
|
1730
|
+
const candidatos = [
|
|
1731
|
+
path.join(baseAtual, ...moduloPartes) + ".rs",
|
|
1732
|
+
path.join(baseAtual, ...moduloPartes, "mod.rs"),
|
|
1733
|
+
path.join("src", ...moduloPartes) + ".rs",
|
|
1734
|
+
path.join("src", ...moduloPartes, "mod.rs"),
|
|
1735
|
+
];
|
|
1736
|
+
for (const candidato of candidatos) {
|
|
1737
|
+
const absoluto = path.join(diretorio, candidato);
|
|
1738
|
+
if (await existeArquivo(absoluto)) {
|
|
1739
|
+
return absoluto;
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
return path.join(diretorio, relacaoFonte);
|
|
1743
|
+
}
|
|
1744
|
+
async function importarDotnetBase(diretorio, namespaceBase) {
|
|
1745
|
+
const arquivos = (await listarArquivosRecursivos(diretorio, [".cs"]))
|
|
1746
|
+
.filter((arquivo) => !/(^|[\\/])(bin|obj|Test[s]?)([\\/]|$)/i.test(arquivo));
|
|
1747
|
+
const modulos = new Map();
|
|
1748
|
+
for (const arquivo of arquivos) {
|
|
1749
|
+
const texto = await readFile(arquivo, "utf8");
|
|
1750
|
+
const relacao = path.relative(diretorio, arquivo);
|
|
1751
|
+
const contextoSegmentos = inferirContextoPorArquivo(relacao, { preservarUltimo: true, snakeCaseUltimo: true });
|
|
1752
|
+
const nomeModulo = [namespaceBase, ...contextoSegmentos].join(".");
|
|
1753
|
+
const tasks = [];
|
|
1754
|
+
const routes = [];
|
|
1755
|
+
for (const simbolo of extrairSimbolosDotnet(texto)) {
|
|
1756
|
+
const taskNome = paraSnakeCase(simbolo.simbolo.split(".").at(-1) ?? simbolo.simbolo);
|
|
1757
|
+
tasks.push({
|
|
1758
|
+
nome: taskNome,
|
|
1759
|
+
resumo: `Task importada automaticamente de ${relacao}#${simbolo.simbolo}.`,
|
|
1760
|
+
input: simbolo.parametros.map((parametro) => ({
|
|
1761
|
+
nome: paraSnakeCase(parametro.nome),
|
|
1762
|
+
tipo: mapearTipoBackendParaSema(parametro.tipoTexto),
|
|
1763
|
+
obrigatorio: parametro.obrigatorio,
|
|
1764
|
+
})),
|
|
1765
|
+
output: criarCampoResultadoBackend(simbolo.retorno),
|
|
1766
|
+
errors: [],
|
|
1767
|
+
effects: descreverEfeitosPorHeuristica(texto),
|
|
1768
|
+
impl: { cs: caminhoImplGenerico(diretorio, arquivo, simbolo.simbolo, { snakeCaseUltimoArquivo: true }) },
|
|
1769
|
+
origemArquivo: relacao,
|
|
1770
|
+
origemSimbolo: simbolo.simbolo,
|
|
1771
|
+
});
|
|
1772
|
+
}
|
|
1773
|
+
for (const rota of extrairRotasDotnet(texto)) {
|
|
1774
|
+
const taskNome = paraSnakeCase(rota.simbolo.split(".").at(-1) ?? rota.simbolo);
|
|
1775
|
+
const output = criarCampoResultadoBackend(rota.retorno);
|
|
1776
|
+
tasks.push({
|
|
1777
|
+
nome: taskNome,
|
|
1778
|
+
resumo: `Task HTTP ASP.NET Core importada automaticamente de ${relacao}#${rota.simbolo}.`,
|
|
1779
|
+
input: camposDeParametrosRotaBackend(rota.parametros),
|
|
1780
|
+
output,
|
|
1781
|
+
errors: [],
|
|
1782
|
+
effects: [{ categoria: "consulta", alvo: "http", criticidade: "media" }],
|
|
1783
|
+
impl: { cs: caminhoImplGenerico(diretorio, arquivo, rota.simbolo, { snakeCaseUltimoArquivo: true }) },
|
|
1784
|
+
origemArquivo: relacao,
|
|
1785
|
+
origemSimbolo: rota.simbolo,
|
|
1786
|
+
});
|
|
1787
|
+
routes.push({
|
|
1788
|
+
nome: `${taskNome}_publico`,
|
|
1789
|
+
resumo: `Rota ASP.NET Core importada automaticamente de ${relacao}#${rota.simbolo}.`,
|
|
1790
|
+
metodo: rota.metodo,
|
|
1791
|
+
caminho: rota.caminho,
|
|
1792
|
+
task: taskNome,
|
|
1793
|
+
input: camposDeParametrosRotaBackend(rota.parametros),
|
|
1794
|
+
output,
|
|
1795
|
+
errors: [],
|
|
1796
|
+
});
|
|
1797
|
+
}
|
|
1798
|
+
if (tasks.length === 0 && routes.length === 0) {
|
|
1799
|
+
continue;
|
|
1800
|
+
}
|
|
1801
|
+
acumularModuloImportado(modulos, criarModuloImportadoSimples(nomeModulo, `Rascunho Sema importado automaticamente de ${relacao}.`, tasks, routes));
|
|
1802
|
+
}
|
|
1803
|
+
return [...modulos.values()];
|
|
1804
|
+
}
|
|
1805
|
+
async function importarJavaBase(diretorio, namespaceBase) {
|
|
1806
|
+
const arquivos = (await listarArquivosRecursivos(diretorio, [".java"]))
|
|
1807
|
+
.filter((arquivo) => !/(^|[\\/])(target|build|out|Test[s]?)([\\/]|$)/i.test(arquivo));
|
|
1808
|
+
const modulos = new Map();
|
|
1809
|
+
for (const arquivo of arquivos) {
|
|
1810
|
+
const texto = await readFile(arquivo, "utf8");
|
|
1811
|
+
const relacao = path.relative(diretorio, arquivo);
|
|
1812
|
+
const contextoSegmentos = inferirContextoPorArquivo(relacao, { preservarUltimo: true, snakeCaseUltimo: true });
|
|
1813
|
+
const nomeModulo = [namespaceBase, ...contextoSegmentos].join(".");
|
|
1814
|
+
const tasks = [];
|
|
1815
|
+
const routes = [];
|
|
1816
|
+
for (const simbolo of extrairSimbolosJava(texto)) {
|
|
1817
|
+
const taskNome = paraSnakeCase(simbolo.simbolo.split(".").at(-1) ?? simbolo.simbolo);
|
|
1818
|
+
tasks.push({
|
|
1819
|
+
nome: taskNome,
|
|
1820
|
+
resumo: `Task importada automaticamente de ${relacao}#${simbolo.simbolo}.`,
|
|
1821
|
+
input: simbolo.parametros.map((parametro) => ({
|
|
1822
|
+
nome: paraSnakeCase(parametro.nome),
|
|
1823
|
+
tipo: mapearTipoBackendParaSema(parametro.tipoTexto),
|
|
1824
|
+
obrigatorio: parametro.obrigatorio,
|
|
1825
|
+
})),
|
|
1826
|
+
output: criarCampoResultadoBackend(simbolo.retorno),
|
|
1827
|
+
errors: [],
|
|
1828
|
+
effects: descreverEfeitosPorHeuristica(texto),
|
|
1829
|
+
impl: { java: caminhoImplGenerico(diretorio, arquivo, simbolo.simbolo, { snakeCaseUltimoArquivo: true }) },
|
|
1830
|
+
origemArquivo: relacao,
|
|
1831
|
+
origemSimbolo: simbolo.simbolo,
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1834
|
+
for (const rota of extrairRotasJava(texto)) {
|
|
1835
|
+
const taskNome = paraSnakeCase(rota.simbolo.split(".").at(-1) ?? rota.simbolo);
|
|
1836
|
+
const output = criarCampoResultadoBackend(rota.retorno);
|
|
1837
|
+
tasks.push({
|
|
1838
|
+
nome: taskNome,
|
|
1839
|
+
resumo: `Task HTTP Spring Boot importada automaticamente de ${relacao}#${rota.simbolo}.`,
|
|
1840
|
+
input: camposDeParametrosRotaBackend(rota.parametros),
|
|
1841
|
+
output,
|
|
1842
|
+
errors: [],
|
|
1843
|
+
effects: [{ categoria: "consulta", alvo: "http", criticidade: "media" }],
|
|
1844
|
+
impl: { java: caminhoImplGenerico(diretorio, arquivo, rota.simbolo, { snakeCaseUltimoArquivo: true }) },
|
|
1845
|
+
origemArquivo: relacao,
|
|
1846
|
+
origemSimbolo: rota.simbolo,
|
|
1847
|
+
});
|
|
1848
|
+
routes.push({
|
|
1849
|
+
nome: `${taskNome}_publico`,
|
|
1850
|
+
resumo: `Rota Spring Boot importada automaticamente de ${relacao}#${rota.simbolo}.`,
|
|
1851
|
+
metodo: rota.metodo,
|
|
1852
|
+
caminho: rota.caminho,
|
|
1853
|
+
task: taskNome,
|
|
1854
|
+
input: camposDeParametrosRotaBackend(rota.parametros),
|
|
1855
|
+
output,
|
|
1856
|
+
errors: [],
|
|
1857
|
+
});
|
|
1858
|
+
}
|
|
1859
|
+
if (tasks.length === 0 && routes.length === 0) {
|
|
1860
|
+
continue;
|
|
1861
|
+
}
|
|
1862
|
+
acumularModuloImportado(modulos, criarModuloImportadoSimples(nomeModulo, `Rascunho Sema importado automaticamente de ${relacao}.`, tasks, routes));
|
|
1863
|
+
}
|
|
1864
|
+
return [...modulos.values()];
|
|
1865
|
+
}
|
|
1866
|
+
async function importarGoBase(diretorio, namespaceBase) {
|
|
1867
|
+
const arquivos = await listarArquivosRecursivos(diretorio, [".go"]);
|
|
1868
|
+
const modulos = new Map();
|
|
1869
|
+
for (const arquivo of arquivos) {
|
|
1870
|
+
const texto = await readFile(arquivo, "utf8");
|
|
1871
|
+
const relacao = path.relative(diretorio, arquivo);
|
|
1872
|
+
const contextoSegmentos = inferirContextoPorArquivo(relacao);
|
|
1873
|
+
const nomeModulo = [namespaceBase, ...contextoSegmentos].join(".");
|
|
1874
|
+
const tasks = [];
|
|
1875
|
+
const routes = [];
|
|
1876
|
+
for (const simbolo of extrairSimbolosGo(texto)) {
|
|
1877
|
+
const taskNome = paraSnakeCase(simbolo.simbolo.split(".").at(-1) ?? simbolo.simbolo);
|
|
1878
|
+
tasks.push({
|
|
1879
|
+
nome: taskNome,
|
|
1880
|
+
resumo: `Task importada automaticamente de ${relacao}#${simbolo.simbolo}.`,
|
|
1881
|
+
input: simbolo.parametros.map((parametro) => ({
|
|
1882
|
+
nome: paraSnakeCase(parametro.nome),
|
|
1883
|
+
tipo: mapearTipoBackendParaSema(parametro.tipoTexto),
|
|
1884
|
+
obrigatorio: parametro.obrigatorio,
|
|
1885
|
+
})),
|
|
1886
|
+
output: criarCampoResultadoBackend(simbolo.retorno),
|
|
1887
|
+
errors: [],
|
|
1888
|
+
effects: descreverEfeitosPorHeuristica(texto),
|
|
1889
|
+
impl: { go: caminhoImplGenerico(diretorio, arquivo, simbolo.simbolo) },
|
|
1890
|
+
origemArquivo: relacao,
|
|
1891
|
+
origemSimbolo: simbolo.simbolo,
|
|
1892
|
+
});
|
|
1893
|
+
}
|
|
1894
|
+
for (const rota of extrairRotasGo(texto)) {
|
|
1895
|
+
const taskNome = paraSnakeCase(rota.simbolo.split(".").at(-1) ?? rota.simbolo);
|
|
1896
|
+
tasks.push({
|
|
1897
|
+
nome: taskNome,
|
|
1898
|
+
resumo: `Task HTTP Go importada automaticamente de ${relacao}#${rota.simbolo}.`,
|
|
1899
|
+
input: camposDeParametrosRotaBackend(rota.parametros),
|
|
1900
|
+
output: [{ nome: "resultado", tipo: "Json", obrigatorio: false }],
|
|
1901
|
+
errors: [],
|
|
1902
|
+
effects: [{ categoria: "consulta", alvo: "http", criticidade: "media" }],
|
|
1903
|
+
impl: { go: caminhoImplGenerico(diretorio, arquivo, rota.simbolo) },
|
|
1904
|
+
origemArquivo: relacao,
|
|
1905
|
+
origemSimbolo: rota.simbolo,
|
|
1906
|
+
});
|
|
1907
|
+
routes.push({
|
|
1908
|
+
nome: `${taskNome}_publico`,
|
|
1909
|
+
resumo: `Rota Go importada automaticamente de ${relacao}#${rota.simbolo}.`,
|
|
1910
|
+
metodo: rota.metodo,
|
|
1911
|
+
caminho: rota.caminho,
|
|
1912
|
+
task: taskNome,
|
|
1913
|
+
input: camposDeParametrosRotaBackend(rota.parametros),
|
|
1914
|
+
output: [{ nome: "resultado", tipo: "Json", obrigatorio: false }],
|
|
1915
|
+
errors: [],
|
|
1916
|
+
});
|
|
1917
|
+
}
|
|
1918
|
+
if (tasks.length === 0 && routes.length === 0) {
|
|
1919
|
+
continue;
|
|
1920
|
+
}
|
|
1921
|
+
modulos.set(nomeModulo, criarModuloImportadoSimples(nomeModulo, `Rascunho Sema importado automaticamente de ${relacao}.`, tasks, routes));
|
|
1922
|
+
}
|
|
1923
|
+
return [...modulos.values()];
|
|
1924
|
+
}
|
|
1925
|
+
async function importarRustBase(diretorio, namespaceBase) {
|
|
1926
|
+
const arquivos = await listarArquivosRecursivos(diretorio, [".rs"]);
|
|
1927
|
+
const modulos = new Map();
|
|
1928
|
+
for (const arquivo of arquivos) {
|
|
1929
|
+
const texto = await readFile(arquivo, "utf8");
|
|
1930
|
+
const relacao = path.relative(diretorio, arquivo);
|
|
1931
|
+
const contextoSegmentos = inferirContextoPorArquivo(relacao);
|
|
1932
|
+
const nomeModulo = [namespaceBase, ...contextoSegmentos].join(".");
|
|
1933
|
+
const tasks = [];
|
|
1934
|
+
const routes = [];
|
|
1935
|
+
for (const simbolo of extrairSimbolosRust(texto)) {
|
|
1936
|
+
const taskNome = paraSnakeCase(simbolo.simbolo.split(".").at(-1) ?? simbolo.simbolo);
|
|
1937
|
+
tasks.push({
|
|
1938
|
+
nome: taskNome,
|
|
1939
|
+
resumo: `Task importada automaticamente de ${relacao}#${simbolo.simbolo}.`,
|
|
1940
|
+
input: simbolo.parametros.map((parametro) => ({
|
|
1941
|
+
nome: paraSnakeCase(parametro.nome),
|
|
1942
|
+
tipo: mapearTipoBackendParaSema(parametro.tipoTexto),
|
|
1943
|
+
obrigatorio: parametro.obrigatorio,
|
|
1944
|
+
})),
|
|
1945
|
+
output: criarCampoResultadoBackend(simbolo.retorno),
|
|
1946
|
+
errors: [],
|
|
1947
|
+
effects: descreverEfeitosPorHeuristica(texto),
|
|
1948
|
+
impl: { rust: caminhoImplGenerico(diretorio, arquivo, simbolo.simbolo) },
|
|
1949
|
+
origemArquivo: relacao,
|
|
1950
|
+
origemSimbolo: simbolo.simbolo,
|
|
1951
|
+
});
|
|
1952
|
+
}
|
|
1953
|
+
acumularModuloImportado(modulos, criarModuloImportadoSimples(nomeModulo, `Rascunho Sema importado automaticamente de ${relacao}.`, tasks, routes));
|
|
1954
|
+
for (const rota of extrairRotasRust(texto)) {
|
|
1955
|
+
const simboloLimpo = rota.simbolo.replace(/::/g, ".");
|
|
1956
|
+
const nomeSimbolo = simboloLimpo.split(".").at(-1) ?? simboloLimpo;
|
|
1957
|
+
const arquivoAlvo = await resolverArquivoRustParaSimbolo(diretorio, relacao, simboloLimpo);
|
|
1958
|
+
const relacaoAlvo = path.relative(diretorio, arquivoAlvo);
|
|
1959
|
+
const moduloAlvo = [namespaceBase, ...inferirContextoPorArquivo(relacaoAlvo)].join(".");
|
|
1960
|
+
const taskNome = paraSnakeCase(rota.simbolo.split(".").at(-1) ?? rota.simbolo);
|
|
1961
|
+
const task = {
|
|
1962
|
+
nome: taskNome,
|
|
1963
|
+
resumo: `Task HTTP Axum importada automaticamente de ${relacao}#${rota.simbolo}.`,
|
|
1964
|
+
input: camposDeParametrosRotaBackend(rota.parametros),
|
|
1965
|
+
output: [{ nome: "resultado", tipo: "Json", obrigatorio: false }],
|
|
1966
|
+
errors: [],
|
|
1967
|
+
effects: [{ categoria: "consulta", alvo: "http", criticidade: "media" }],
|
|
1968
|
+
impl: { rust: caminhoImplGenerico(diretorio, arquivoAlvo, nomeSimbolo) },
|
|
1969
|
+
origemArquivo: relacaoAlvo,
|
|
1970
|
+
origemSimbolo: nomeSimbolo,
|
|
1971
|
+
};
|
|
1972
|
+
const route = {
|
|
1973
|
+
nome: `${taskNome}_publico`,
|
|
1974
|
+
resumo: `Rota Axum importada automaticamente de ${relacao}#${rota.simbolo}.`,
|
|
1975
|
+
metodo: rota.metodo,
|
|
1976
|
+
caminho: rota.caminho,
|
|
1977
|
+
task: taskNome,
|
|
1978
|
+
input: camposDeParametrosRotaBackend(rota.parametros),
|
|
1979
|
+
output: [{ nome: "resultado", tipo: "Json", obrigatorio: false }],
|
|
1980
|
+
errors: [],
|
|
1981
|
+
};
|
|
1982
|
+
acumularModuloImportado(modulos, criarModuloImportadoSimples(moduloAlvo, `Rascunho Sema importado automaticamente de ${relacaoAlvo}.`, [task], [route]));
|
|
1983
|
+
}
|
|
1984
|
+
}
|
|
1985
|
+
return [...modulos.values()];
|
|
1986
|
+
}
|
|
1987
|
+
async function importarCppBase(diretorio, namespaceBase) {
|
|
1988
|
+
const arquivos = (await listarArquivosRecursivos(diretorio, [".cpp", ".cc", ".cxx", ".hpp", ".h"]))
|
|
1989
|
+
.filter((arquivo) => !/(^|[\\/])(windows|linux|macos|runner|flutter|ephemeral|build|vendor)([\\/]|$)/i.test(arquivo));
|
|
1990
|
+
const modulos = new Map();
|
|
1991
|
+
for (const arquivo of arquivos) {
|
|
1992
|
+
const texto = await readFile(arquivo, "utf8");
|
|
1993
|
+
const relacao = path.relative(diretorio, arquivo);
|
|
1994
|
+
const contextoSegmentos = inferirContextoPorArquivo(relacao);
|
|
1995
|
+
const nomeModulo = [namespaceBase, ...contextoSegmentos].join(".");
|
|
1996
|
+
const tasks = [];
|
|
1997
|
+
for (const simbolo of selecionarSimbolosPreferidos(extrairSimbolosCpp(texto))) {
|
|
1998
|
+
const taskNome = paraSnakeCase(simbolo.simbolo.split(".").at(-1) ?? simbolo.simbolo);
|
|
1999
|
+
tasks.push({
|
|
2000
|
+
nome: taskNome,
|
|
2001
|
+
resumo: `Task importada automaticamente de ${relacao}#${simbolo.simbolo}.`,
|
|
2002
|
+
input: simbolo.parametros.map((parametro) => ({
|
|
2003
|
+
nome: paraSnakeCase(parametro.nome),
|
|
2004
|
+
tipo: mapearTipoBackendParaSema(parametro.tipoTexto),
|
|
2005
|
+
obrigatorio: parametro.obrigatorio,
|
|
2006
|
+
})),
|
|
2007
|
+
output: [{ nome: "resultado", tipo: "Json", obrigatorio: false }],
|
|
2008
|
+
errors: [],
|
|
2009
|
+
effects: descreverEfeitosPorHeuristica(texto),
|
|
2010
|
+
impl: { cpp: caminhoImplGenerico(diretorio, arquivo, simbolo.simbolo) },
|
|
2011
|
+
origemArquivo: relacao,
|
|
2012
|
+
origemSimbolo: simbolo.simbolo,
|
|
2013
|
+
});
|
|
2014
|
+
}
|
|
2015
|
+
if (tasks.length === 0) {
|
|
2016
|
+
continue;
|
|
2017
|
+
}
|
|
2018
|
+
acumularModuloImportado(modulos, criarModuloImportadoSimples(nomeModulo, `Rascunho Sema importado automaticamente de ${relacao}.`, tasks));
|
|
2019
|
+
}
|
|
2020
|
+
return [...modulos.values()];
|
|
2021
|
+
}
|
|
2022
|
+
export async function importarProjetoLegado(fonte, diretorio, namespaceBase) {
|
|
2023
|
+
const base = path.resolve(diretorio);
|
|
2024
|
+
const namespace = inferirNamespaceBase(base, namespaceBase);
|
|
2025
|
+
let modulos = [];
|
|
2026
|
+
if (fonte === "nestjs") {
|
|
2027
|
+
modulos = await importarTypeScriptBase(base, namespace, true);
|
|
2028
|
+
}
|
|
2029
|
+
else if (fonte === "nextjs") {
|
|
2030
|
+
modulos = await importarNextJsBase(base, namespace);
|
|
2031
|
+
}
|
|
2032
|
+
else if (fonte === "firebase") {
|
|
2033
|
+
modulos = await importarFirebaseBase(base, namespace);
|
|
2034
|
+
}
|
|
2035
|
+
else if (fonte === "typescript") {
|
|
2036
|
+
modulos = await importarTypeScriptBase(base, namespace, false);
|
|
2037
|
+
}
|
|
2038
|
+
else if (fonte === "fastapi") {
|
|
2039
|
+
modulos = await importarPythonBase(base, namespace, "fastapi");
|
|
2040
|
+
}
|
|
2041
|
+
else if (fonte === "flask") {
|
|
2042
|
+
modulos = await importarPythonBase(base, namespace, "flask");
|
|
2043
|
+
}
|
|
2044
|
+
else if (fonte === "python") {
|
|
2045
|
+
modulos = await importarPythonBase(base, namespace, "nenhum");
|
|
2046
|
+
}
|
|
2047
|
+
else if (fonte === "dart") {
|
|
2048
|
+
modulos = await importarDartBase(base, namespace);
|
|
2049
|
+
}
|
|
2050
|
+
else if (fonte === "dotnet") {
|
|
2051
|
+
modulos = await importarDotnetBase(base, namespace);
|
|
2052
|
+
}
|
|
2053
|
+
else if (fonte === "java") {
|
|
2054
|
+
modulos = await importarJavaBase(base, namespace);
|
|
2055
|
+
}
|
|
2056
|
+
else if (fonte === "go") {
|
|
2057
|
+
modulos = await importarGoBase(base, namespace);
|
|
2058
|
+
}
|
|
2059
|
+
else if (fonte === "rust") {
|
|
2060
|
+
modulos = await importarRustBase(base, namespace);
|
|
2061
|
+
}
|
|
2062
|
+
else if (fonte === "cpp") {
|
|
2063
|
+
modulos = await importarCppBase(base, namespace);
|
|
2064
|
+
}
|
|
2065
|
+
const arquivos = [];
|
|
2066
|
+
for (const modulo of modulos) {
|
|
2067
|
+
const bruto = moduloParaCodigo(modulo);
|
|
2068
|
+
const formatado = await formatarModuloImportado(bruto, `${modulo.nome}.sema`);
|
|
2069
|
+
arquivos.push(montarArquivoImportado(modulo, namespace, formatado));
|
|
2070
|
+
}
|
|
2071
|
+
const diagnosticos = compilarProjeto(arquivos.map((arquivo) => ({
|
|
2072
|
+
caminho: path.join(base, ".tmp", "importado", arquivo.caminhoRelativo),
|
|
2073
|
+
codigo: arquivo.conteudo,
|
|
2074
|
+
}))).diagnosticos;
|
|
2075
|
+
return {
|
|
2076
|
+
fonte,
|
|
2077
|
+
diretorio: base,
|
|
2078
|
+
namespaceBase: namespace,
|
|
2079
|
+
arquivos,
|
|
2080
|
+
diagnosticos,
|
|
2081
|
+
};
|
|
2082
|
+
}
|
|
2083
|
+
export function resumoImportacao(resultado) {
|
|
2084
|
+
return {
|
|
2085
|
+
modulos: resultado.arquivos.length,
|
|
2086
|
+
tarefas: resultado.arquivos.reduce((total, arquivo) => total + arquivo.tarefas, 0),
|
|
2087
|
+
rotas: resultado.arquivos.reduce((total, arquivo) => total + arquivo.rotas, 0),
|
|
2088
|
+
entidades: resultado.arquivos.reduce((total, arquivo) => total + arquivo.entidades, 0),
|
|
2089
|
+
enums: resultado.arquivos.reduce((total, arquivo) => total + arquivo.enums, 0),
|
|
2090
|
+
diagnosticos: resultado.diagnosticos.length,
|
|
2091
|
+
sucesso: !temErros(resultado.diagnosticos),
|
|
2092
|
+
};
|
|
2093
|
+
}
|
|
2094
|
+
//# sourceMappingURL=importador.js.map
|