@semacode/cli 2.0.1 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +39 -39
- package/README.md +125 -121
- package/dist/agentEntryPoints.js +499 -499
- package/dist/doctorCommand.js +28 -0
- package/dist/geracaoCore.js +97 -1
- package/dist/iaPrompts.d.ts +1 -1
- package/dist/iaPrompts.js +307 -307
- package/dist/index.part01.js +4 -3
- package/dist/index.part02.js +21 -21
- package/dist/index.part04.js +96 -96
- package/dist/index.part06.js +1 -1
- package/dist/index.part07.js +35 -24
- package/dist/init/templates.part01.js +393 -393
- package/dist/init/templates.part02.js +189 -189
- package/dist/initTemplatesBackend.js +73 -73
- package/dist/initTemplatesBase.js +81 -81
- package/dist/initTemplatesFlutter.js +137 -137
- package/dist/initTemplatesRuntime.js +350 -350
- package/dist/initTemplatesWeb.js +309 -309
- package/dist/nativeToolchains.d.ts +15 -0
- package/dist/nativeToolchains.js +132 -0
- package/dist/projetoConfig.js +7 -1
- package/dist/projetoResolucao.js +21 -6
- package/dist/sync/index.js +35 -35
- package/docs/README.md +25 -25
- package/docs/ai-onboarding.md +62 -62
- package/docs/ai-workflow.md +1 -1
- package/docs/auth.md +26 -26
- package/docs/cli.md +79 -72
- package/docs/commands.md +1 -1
- package/docs/database.md +15 -15
- package/docs/deploy.md +41 -36
- package/docs/documentation.md +39 -39
- package/docs/env.md +16 -16
- package/docs/frontend.md +41 -41
- package/docs/getting-started.md +57 -57
- package/docs/i18n.md +15 -15
- package/docs/profiles.md +45 -43
- package/docs/repositories.md +43 -43
- package/docs/rollback.md +31 -31
- package/docs/security.md +32 -32
- package/docs/support.md +8 -8
- package/docs/syntax.md +1 -1
- package/docs/testing.md +35 -28
- package/docs/vocabulary.md +29 -29
- package/exemplos/agendamento.sema +1 -1
- package/exemplos/assinatura.sema +1 -1
- package/exemplos/auditoria.sema +1 -1
- package/exemplos/autenticacao.sema +1 -1
- package/exemplos/author_obra_comum.sema +1 -1
- package/exemplos/author_tema_sensivel.sema +1 -1
- package/exemplos/automacao.sema +1 -1
- package/exemplos/cadastro_usuario.sema +1 -1
- package/exemplos/calculadora.sema +1 -1
- package/exemplos/crud_simples.sema +1 -1
- package/exemplos/estoque.sema +1 -1
- package/exemplos/exportacao.sema +1 -1
- package/exemplos/fila.sema +1 -1
- package/exemplos/integracao_externa.sema +1 -1
- package/exemplos/multi_tenant.sema +1 -1
- package/exemplos/notificacao.sema +1 -1
- package/exemplos/operacao_estrategia.sema +132 -132
- package/exemplos/operacao_estrategia_discovery.sema +220 -220
- package/exemplos/operacao_estrategia_fluxo.sema +114 -114
- package/exemplos/operacao_estrategia_gates.sema +106 -106
- package/exemplos/operacao_estrategia_quarentena.sema +133 -133
- package/exemplos/operacao_estrategia_rollout.sema +124 -124
- package/exemplos/pagamento.sema +1 -1
- package/exemplos/pagamento_dominio.sema +1 -1
- package/exemplos/pedido.sema +1 -1
- package/exemplos/permissao.sema +1 -1
- package/exemplos/persistencia_vendor_first.sema +1 -1
- package/exemplos/profile_conversas.sema +165 -165
- package/exemplos/profile_game.sema +1 -1
- package/exemplos/profile_legal.sema +1 -1
- package/exemplos/profile_ops.sema +1 -1
- package/exemplos/profile_research.sema +1 -1
- package/exemplos/profile_software.sema +1 -1
- package/exemplos/profile_workflow_n8n.sema +1 -1
- package/exemplos/relatorio.sema +1 -1
- package/exemplos/replica_analitica_erp.sema +1 -1
- package/exemplos/testes_embutidos.sema +1 -1
- package/exemplos/tratamento_erro.sema +1 -1
- package/exemplos/upload_arquivo.sema +1 -1
- package/exemplos/webhook.sema +1 -1
- package/node_modules/@sema/gerador-cpp/dist/index.d.ts +3 -0
- package/node_modules/@sema/gerador-cpp/dist/index.js +859 -0
- package/node_modules/@sema/gerador-cpp/dist/index.js.map +1 -0
- package/node_modules/@sema/gerador-cpp/package.json +7 -0
- package/node_modules/@sema/gerador-css/dist/index.part01.js +10 -10
- package/node_modules/@sema/gerador-css/dist/index.part02.js +460 -460
- package/node_modules/@sema/gerador-dotnet/dist/index.d.ts +3 -0
- package/node_modules/@sema/gerador-dotnet/dist/index.js +828 -0
- package/node_modules/@sema/gerador-dotnet/dist/index.js.map +1 -0
- package/node_modules/@sema/gerador-dotnet/package.json +7 -0
- package/node_modules/@sema/gerador-html/dist/index.js +17 -17
- package/node_modules/@sema/gerador-lua/dist/index.js +5 -5
- package/node_modules/@sema/gerador-php/dist/index.js +113 -113
- package/node_modules/@sema/gerador-python/dist/index.part02.js +3 -3
- package/node_modules/@sema/gerador-typescript/dist/index.part02.js +11 -11
- package/node_modules/@sema/nucleo/dist/diagnosticos/melhorador.js +42 -42
- package/node_modules/@sema/padroes/dist/index.d.ts +1 -1
- package/package.json +5 -1
|
@@ -0,0 +1,828 @@
|
|
|
1
|
+
// SEMA-GOVERNED: sema.produto.geradores_nativos
|
|
2
|
+
// Contrato: contratos/sema/geradores_nativos.sema
|
|
3
|
+
// Descrição: gera projetos C#/.NET autocontidos e testes executáveis sem dependências NuGet externas.
|
|
4
|
+
import { extrairTiposNomeados, normalizarNomeModulo, normalizarNomeParaSimbolo, } from "@sema/padroes";
|
|
5
|
+
const PALAVRAS_RESERVADAS_CSHARP = new Set([
|
|
6
|
+
"abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked",
|
|
7
|
+
"class", "const", "continue", "decimal", "default", "delegate", "do", "double", "else",
|
|
8
|
+
"enum", "event", "explicit", "extern", "false", "finally", "fixed", "float", "for", "foreach",
|
|
9
|
+
"goto", "if", "implicit", "in", "int", "interface", "internal", "is", "lock", "long",
|
|
10
|
+
"namespace", "new", "null", "object", "operator", "out", "override", "params", "private",
|
|
11
|
+
"protected", "public", "readonly", "ref", "return", "sbyte", "sealed", "short", "sizeof",
|
|
12
|
+
"stackalloc", "static", "string", "struct", "switch", "this", "throw", "true", "try",
|
|
13
|
+
"typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using", "virtual", "void",
|
|
14
|
+
"volatile", "while",
|
|
15
|
+
]);
|
|
16
|
+
const TIPOS_VALOR_CSHARP = new Set([
|
|
17
|
+
"bool", "byte", "decimal", "double", "float", "int", "long", "sbyte", "short", "uint", "ulong",
|
|
18
|
+
"ushort", "DateOnly", "DateTimeOffset", "Guid",
|
|
19
|
+
]);
|
|
20
|
+
function escaparComentario(valor) {
|
|
21
|
+
return valor.replace(/\r?\n/g, " ").replace(/\*\//g, "* /").trim();
|
|
22
|
+
}
|
|
23
|
+
function literalString(valor) {
|
|
24
|
+
return JSON.stringify(valor)
|
|
25
|
+
.replace(/\u2028/g, "\\u2028")
|
|
26
|
+
.replace(/\u2029/g, "\\u2029");
|
|
27
|
+
}
|
|
28
|
+
function paraPascalCase(valor, fallback = "Contrato") {
|
|
29
|
+
const normalizado = normalizarNomeParaSimbolo(valor);
|
|
30
|
+
const partes = normalizado.split(/_+/).filter(Boolean);
|
|
31
|
+
let resultado = partes
|
|
32
|
+
.map((parte) => parte.length > 0 ? parte[0].toUpperCase() + parte.slice(1) : "")
|
|
33
|
+
.join("");
|
|
34
|
+
resultado = resultado.replace(/[^A-Za-z0-9_]/g, "");
|
|
35
|
+
if (!resultado) {
|
|
36
|
+
resultado = fallback;
|
|
37
|
+
}
|
|
38
|
+
if (/^[0-9]/.test(resultado)) {
|
|
39
|
+
resultado = `N${resultado}`;
|
|
40
|
+
}
|
|
41
|
+
if (PALAVRAS_RESERVADAS_CSHARP.has(resultado.toLowerCase())) {
|
|
42
|
+
resultado = `${resultado}Value`;
|
|
43
|
+
}
|
|
44
|
+
return resultado;
|
|
45
|
+
}
|
|
46
|
+
function nomePropriedade(valor) {
|
|
47
|
+
return paraPascalCase(valor, "Valor");
|
|
48
|
+
}
|
|
49
|
+
function nomeNamespace(nomeModulo) {
|
|
50
|
+
const segmentos = nomeModulo.split(".").map((segmento) => paraPascalCase(segmento, "Modulo"));
|
|
51
|
+
return segmentos.length > 0 ? segmentos.join(".") : "Sema.Generated";
|
|
52
|
+
}
|
|
53
|
+
function nomeProjeto(nomeModulo) {
|
|
54
|
+
return nomeModulo.split(".").map((segmento) => paraPascalCase(segmento, "Modulo")).join(".") || "Sema.Generated";
|
|
55
|
+
}
|
|
56
|
+
function tipoCSharp(tipo, contexto) {
|
|
57
|
+
const limpo = tipo.trim();
|
|
58
|
+
if (/^Opcional<.+>$/.test(limpo)) {
|
|
59
|
+
return tornarOpcional(tipoCSharp(limpo.slice("Opcional<".length, -1), contexto), contexto);
|
|
60
|
+
}
|
|
61
|
+
const uniao = dividirTipoRaiz(limpo, "|");
|
|
62
|
+
if (uniao.length > 1) {
|
|
63
|
+
const tipos = [...new Set(uniao.map((item) => tipoCSharp(item, contexto)))];
|
|
64
|
+
return tipos.length === 1 ? tipos[0] : "object?";
|
|
65
|
+
}
|
|
66
|
+
if (limpo.endsWith("[]")) {
|
|
67
|
+
return `List<${tipoCSharp(limpo.slice(0, -2), contexto)}>`;
|
|
68
|
+
}
|
|
69
|
+
if (/^Lista<.+>$/.test(limpo)) {
|
|
70
|
+
return `List<${tipoCSharp(limpo.slice("Lista<".length, -1), contexto)}>`;
|
|
71
|
+
}
|
|
72
|
+
if (/^Mapa<.+>$/.test(limpo)) {
|
|
73
|
+
const partes = dividirTipoRaiz(limpo.slice("Mapa<".length, -1), ",");
|
|
74
|
+
const chave = tipoCSharp(partes[0] ?? "Texto", contexto);
|
|
75
|
+
const valor = tipoCSharp(partes[1] ?? "Json", contexto);
|
|
76
|
+
return `Dictionary<${chave}, ${valor}>`;
|
|
77
|
+
}
|
|
78
|
+
const primitivos = {
|
|
79
|
+
Texto: "string",
|
|
80
|
+
Numero: "decimal",
|
|
81
|
+
Inteiro: "long",
|
|
82
|
+
Decimal: "decimal",
|
|
83
|
+
Booleano: "bool",
|
|
84
|
+
Data: "DateOnly",
|
|
85
|
+
DataHora: "DateTimeOffset",
|
|
86
|
+
Id: "string",
|
|
87
|
+
Email: "string",
|
|
88
|
+
Url: "string",
|
|
89
|
+
Json: "Dictionary<string, object?>",
|
|
90
|
+
Vazio: "object?",
|
|
91
|
+
};
|
|
92
|
+
return primitivos[limpo] ?? paraPascalCase(limpo, "ExternalValue");
|
|
93
|
+
}
|
|
94
|
+
function dividirTipoRaiz(valor, separador) {
|
|
95
|
+
const partes = [];
|
|
96
|
+
let atual = "";
|
|
97
|
+
let profundidade = 0;
|
|
98
|
+
for (const caractere of valor) {
|
|
99
|
+
if (caractere === "<") {
|
|
100
|
+
profundidade += 1;
|
|
101
|
+
}
|
|
102
|
+
else if (caractere === ">") {
|
|
103
|
+
profundidade = Math.max(0, profundidade - 1);
|
|
104
|
+
}
|
|
105
|
+
if (caractere === separador && profundidade === 0) {
|
|
106
|
+
if (atual.trim()) {
|
|
107
|
+
partes.push(atual.trim());
|
|
108
|
+
}
|
|
109
|
+
atual = "";
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
atual += caractere;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (atual.trim()) {
|
|
116
|
+
partes.push(atual.trim());
|
|
117
|
+
}
|
|
118
|
+
return partes;
|
|
119
|
+
}
|
|
120
|
+
function tipoEhValor(tipo, contexto) {
|
|
121
|
+
const base = tipo.replace(/\?$/, "");
|
|
122
|
+
return TIPOS_VALOR_CSHARP.has(base) || contexto.enums.has(base);
|
|
123
|
+
}
|
|
124
|
+
function tornarOpcional(tipo, contexto) {
|
|
125
|
+
if (tipo.endsWith("?") || tipo === "object?") {
|
|
126
|
+
return tipo;
|
|
127
|
+
}
|
|
128
|
+
if (tipoEhValor(tipo, contexto) || tipo === "string" || tipo.startsWith("List<") || tipo.startsWith("Dictionary<") || contexto.tiposLocais.has(tipo)) {
|
|
129
|
+
return `${tipo}?`;
|
|
130
|
+
}
|
|
131
|
+
return `${tipo}?`;
|
|
132
|
+
}
|
|
133
|
+
function tipoCampoCSharp(campo, contexto) {
|
|
134
|
+
let tipo = tipoCSharp(campo.tipoOriginal || campo.tipo, contexto);
|
|
135
|
+
if (campo.opcional && !tipo.endsWith("?")) {
|
|
136
|
+
tipo = tornarOpcional(tipo, contexto);
|
|
137
|
+
}
|
|
138
|
+
return tipo;
|
|
139
|
+
}
|
|
140
|
+
function valorPadraoTipo(tipo, contexto) {
|
|
141
|
+
if (tipo.endsWith("?")) {
|
|
142
|
+
return "null";
|
|
143
|
+
}
|
|
144
|
+
if (tipo === "string") {
|
|
145
|
+
return "string.Empty";
|
|
146
|
+
}
|
|
147
|
+
if (tipo === "bool") {
|
|
148
|
+
return "false";
|
|
149
|
+
}
|
|
150
|
+
if (["byte", "decimal", "double", "float", "int", "long", "sbyte", "short", "uint", "ulong", "ushort"].includes(tipo)) {
|
|
151
|
+
return "0";
|
|
152
|
+
}
|
|
153
|
+
if (tipo === "DateOnly" || tipo === "DateTimeOffset" || tipo === "Guid" || contexto.enums.has(tipo)) {
|
|
154
|
+
return "default";
|
|
155
|
+
}
|
|
156
|
+
if (tipo.startsWith("List<") || tipo.startsWith("Dictionary<")) {
|
|
157
|
+
return "new()";
|
|
158
|
+
}
|
|
159
|
+
if (tipo === "object?") {
|
|
160
|
+
return "null";
|
|
161
|
+
}
|
|
162
|
+
return "new()";
|
|
163
|
+
}
|
|
164
|
+
function valorSaidaCampo(campo, contexto) {
|
|
165
|
+
const tipoDeclarado = tipoCampoCSharp(campo, contexto);
|
|
166
|
+
const tipo = tipoDeclarado.replace(/\?$/, "");
|
|
167
|
+
if (tipo === "string") {
|
|
168
|
+
return literalString(`${campo.nome}_gerado`);
|
|
169
|
+
}
|
|
170
|
+
if (tipo === "object") {
|
|
171
|
+
return "new object()";
|
|
172
|
+
}
|
|
173
|
+
if (tipo.startsWith("List<") || tipo.startsWith("Dictionary<")) {
|
|
174
|
+
return "new()";
|
|
175
|
+
}
|
|
176
|
+
if (contexto.tiposLocais.has(tipo)) {
|
|
177
|
+
return "new()";
|
|
178
|
+
}
|
|
179
|
+
return valorPadraoTipo(tipo, contexto);
|
|
180
|
+
}
|
|
181
|
+
function valorAlternativoComparacao(campo, literal, contexto) {
|
|
182
|
+
const tipo = tipoCampoCSharp(campo, contexto).replace(/\?$/, "");
|
|
183
|
+
if (tipo === "bool") {
|
|
184
|
+
return literal === "true" ? "false" : "true";
|
|
185
|
+
}
|
|
186
|
+
if (tipo === "string") {
|
|
187
|
+
return literalString(`${campo.nome}_alternativo`);
|
|
188
|
+
}
|
|
189
|
+
if (["byte", "decimal", "double", "float", "int", "long", "sbyte", "short", "uint", "ulong", "ushort"].includes(tipo)) {
|
|
190
|
+
const incremento = tipo === "decimal" ? "1m" : tipo === "float" ? "1f" : tipo === "double" ? "1d" : "1";
|
|
191
|
+
return `(${literal} + ${incremento})`;
|
|
192
|
+
}
|
|
193
|
+
if (tipo === "DateOnly") {
|
|
194
|
+
return `${literal}.AddDays(1)`;
|
|
195
|
+
}
|
|
196
|
+
if (tipo === "DateTimeOffset") {
|
|
197
|
+
return `${literal}.AddTicks(1)`;
|
|
198
|
+
}
|
|
199
|
+
const valoresEnum = contexto.valoresEnums.get(tipo);
|
|
200
|
+
if (valoresEnum) {
|
|
201
|
+
const alternativo = valoresEnum
|
|
202
|
+
.map((valor) => `${tipo}.${paraPascalCase(valor, "Desconhecido")}`)
|
|
203
|
+
.find((valor) => valor !== literal);
|
|
204
|
+
return alternativo;
|
|
205
|
+
}
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
function valorParaComparacaoGarantida(expressao, campo, contexto) {
|
|
209
|
+
if (expressao.valorLiteral === false) {
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
const literal = literalComparacao(expressao.valor, campo, contexto);
|
|
213
|
+
if (["==", ">=", "<="].includes(expressao.operador)) {
|
|
214
|
+
return literal;
|
|
215
|
+
}
|
|
216
|
+
if (expressao.operador === "!=") {
|
|
217
|
+
return valorAlternativoComparacao(campo, literal, contexto);
|
|
218
|
+
}
|
|
219
|
+
const tipo = tipoCampoCSharp(campo, contexto).replace(/\?$/, "");
|
|
220
|
+
if (["byte", "decimal", "double", "float", "int", "long", "sbyte", "short", "uint", "ulong", "ushort"].includes(tipo)) {
|
|
221
|
+
const passo = tipo === "decimal" ? "1m" : tipo === "float" ? "1f" : tipo === "double" ? "1d" : "1";
|
|
222
|
+
return `(${literal} ${expressao.operador === ">" ? "+" : "-"} ${passo})`;
|
|
223
|
+
}
|
|
224
|
+
if (tipo === "DateOnly") {
|
|
225
|
+
return `${literal}.AddDays(${expressao.operador === ">" ? "1" : "-1"})`;
|
|
226
|
+
}
|
|
227
|
+
if (tipo === "DateTimeOffset") {
|
|
228
|
+
return `${literal}.AddTicks(${expressao.operador === ">" ? "1" : "-1"})`;
|
|
229
|
+
}
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
function valorSaidaGarantido(task, campo, contexto) {
|
|
233
|
+
const valoresEsperados = task.tests
|
|
234
|
+
.filter((caso) => !caso.error)
|
|
235
|
+
.flatMap((caso) => caso.expect.campos.filter((item) => item.nome === campo.nome))
|
|
236
|
+
.map((item) => item.tipoOriginal || item.tipo);
|
|
237
|
+
const valoresEsperadosUnicos = [...new Set(valoresEsperados)];
|
|
238
|
+
if (valoresEsperadosUnicos.length === 1) {
|
|
239
|
+
return literalCasoTeste(valoresEsperadosUnicos[0], campo, contexto);
|
|
240
|
+
}
|
|
241
|
+
for (const garantia of task.garantiasEstruturadas) {
|
|
242
|
+
if (!("alvo" in garantia) || garantia.alvo !== campo.nome) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
if (garantia.tipo === "pertencimento" && garantia.valores.length > 0) {
|
|
246
|
+
return literalComparacao(garantia.valores[0], campo, contexto);
|
|
247
|
+
}
|
|
248
|
+
if (garantia.tipo === "comparacao") {
|
|
249
|
+
const valor = valorParaComparacaoGarantida(garantia, campo, contexto);
|
|
250
|
+
if (valor) {
|
|
251
|
+
return valor;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (garantia.tipo === "predicado") {
|
|
255
|
+
const predicado = garantia.predicadoCanonico ?? garantia.predicado;
|
|
256
|
+
if (predicado === "positivo" || predicado === "diferente_de_zero") {
|
|
257
|
+
return literalComparacao("1", campo, contexto);
|
|
258
|
+
}
|
|
259
|
+
if (predicado === "email_valido") {
|
|
260
|
+
return literalString(`${campo.nome}@sema.local`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return valorSaidaCampo(campo, contexto);
|
|
265
|
+
}
|
|
266
|
+
function gerarCabecalhoCSharp(modulo, descricao) {
|
|
267
|
+
return [
|
|
268
|
+
"// SEMA-GOVERNED",
|
|
269
|
+
`// Módulo de origem: ${modulo.nome}`,
|
|
270
|
+
"// Contrato: consulte o arquivo .sema aplicável antes de editar este artefato.",
|
|
271
|
+
"// Rastreabilidade: outros contratos .sema podem governar este mesmo arquivo via vinculos.",
|
|
272
|
+
"// Para IA fraca/média/forte: chame Sema, rode docs-impacto e drift antes de alterar código.",
|
|
273
|
+
`// Descrição: ${descricao}`,
|
|
274
|
+
"",
|
|
275
|
+
].join("\n");
|
|
276
|
+
}
|
|
277
|
+
function gerarCabecalhoProjeto(modulo, descricao) {
|
|
278
|
+
return [
|
|
279
|
+
"<!-- SEMA-GOVERNED -->",
|
|
280
|
+
`<!-- Módulo de origem: ${modulo.nome} -->`,
|
|
281
|
+
"<!-- Consulte o contrato .sema aplicável antes de editar este projeto. -->",
|
|
282
|
+
`<!-- Descrição: ${descricao} -->`,
|
|
283
|
+
].join("\n");
|
|
284
|
+
}
|
|
285
|
+
function gerarPropriedades(campos, contexto) {
|
|
286
|
+
if (campos.length === 0) {
|
|
287
|
+
return " // Sem campos declarados no contrato.";
|
|
288
|
+
}
|
|
289
|
+
return campos.map((campo) => {
|
|
290
|
+
const tipo = tipoCampoCSharp(campo, contexto);
|
|
291
|
+
const modificadores = campo.modificadores.join(", ") || "nenhum";
|
|
292
|
+
return [
|
|
293
|
+
` // SEMA-FIELD: ${escaparComentario(campo.nome)} tipo=${escaparComentario(campo.tipoOriginal || campo.tipo)} modificadores=${escaparComentario(modificadores)}`,
|
|
294
|
+
` public ${tipo} ${nomePropriedade(campo.nome)} { get; init; } = ${valorPadraoTipo(tipo, contexto)};`,
|
|
295
|
+
].join("\n");
|
|
296
|
+
}).join("\n\n");
|
|
297
|
+
}
|
|
298
|
+
function gerarModelo(nome, campos, invariantes, contexto, categoria) {
|
|
299
|
+
const comentarios = invariantes.map((item) => `// SEMA-INVARIANT: ${escaparComentario(item.textoOriginal)}`).join("\n");
|
|
300
|
+
return [
|
|
301
|
+
`// SEMA-${categoria.toUpperCase()}: ${escaparComentario(nome)}`,
|
|
302
|
+
comentarios,
|
|
303
|
+
`public sealed record class ${paraPascalCase(nome)}`,
|
|
304
|
+
"{",
|
|
305
|
+
gerarPropriedades(campos, contexto),
|
|
306
|
+
"}",
|
|
307
|
+
].filter(Boolean).join("\n");
|
|
308
|
+
}
|
|
309
|
+
function gerarEnum(nome, valores) {
|
|
310
|
+
const usados = new Map();
|
|
311
|
+
const membros = (valores.length > 0 ? valores : ["desconhecido"]).map((valor, indice) => {
|
|
312
|
+
const base = paraPascalCase(valor, "Desconhecido");
|
|
313
|
+
const ocorrencia = (usados.get(base) ?? 0) + 1;
|
|
314
|
+
usados.set(base, ocorrencia);
|
|
315
|
+
const membro = ocorrencia === 1 ? base : `${base}${ocorrencia}`;
|
|
316
|
+
return ` ${membro} = ${indice},`;
|
|
317
|
+
});
|
|
318
|
+
return `// SEMA-ENUM: ${escaparComentario(nome)}\npublic enum ${paraPascalCase(nome)}\n{\n${membros.join("\n")}\n}`;
|
|
319
|
+
}
|
|
320
|
+
function coletarTiposExternos(modulo) {
|
|
321
|
+
const locaisOriginais = new Set([
|
|
322
|
+
...modulo.types.map((item) => item.nome),
|
|
323
|
+
...modulo.entities.map((item) => item.nome),
|
|
324
|
+
...modulo.enums.map((item) => item.nome),
|
|
325
|
+
]);
|
|
326
|
+
const campos = [
|
|
327
|
+
...modulo.types.flatMap((item) => item.definicao.campos),
|
|
328
|
+
...modulo.entities.flatMap((item) => item.campos),
|
|
329
|
+
...modulo.tasks.flatMap((item) => [...item.input, ...item.output]),
|
|
330
|
+
...modulo.routes.flatMap((item) => [...item.inputPublico, ...item.outputPublico]),
|
|
331
|
+
...modulo.states.flatMap((item) => item.campos),
|
|
332
|
+
];
|
|
333
|
+
const externos = new Set();
|
|
334
|
+
for (const campo of campos) {
|
|
335
|
+
for (const tipo of extrairTiposNomeados(campo.tipoOriginal || campo.tipo)) {
|
|
336
|
+
if (!locaisOriginais.has(tipo)) {
|
|
337
|
+
externos.add(tipo);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return [...externos].sort((a, b) => a.localeCompare(b, "pt-BR"));
|
|
342
|
+
}
|
|
343
|
+
function acessoCampo(referencia, variavel, campos) {
|
|
344
|
+
const segmentos = referencia.split(".").map((item) => item.trim()).filter(Boolean);
|
|
345
|
+
if (segmentos.length !== 1 || !campos.some((campo) => campo.nome === segmentos[0])) {
|
|
346
|
+
return undefined;
|
|
347
|
+
}
|
|
348
|
+
return `${variavel}.${segmentos.map(nomePropriedade).join(".")}`;
|
|
349
|
+
}
|
|
350
|
+
function tipoAceitaLiteral(campo, contexto) {
|
|
351
|
+
if (!campo) {
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
const tipo = tipoCampoCSharp(campo, contexto).replace(/\?$/, "");
|
|
355
|
+
return tipo === "string"
|
|
356
|
+
|| tipo === "bool"
|
|
357
|
+
|| TIPOS_VALOR_CSHARP.has(tipo)
|
|
358
|
+
|| contexto.enums.has(tipo);
|
|
359
|
+
}
|
|
360
|
+
function condicaoExiste(acesso, campo, contexto) {
|
|
361
|
+
if (!campo) {
|
|
362
|
+
return `${acesso} is not null`;
|
|
363
|
+
}
|
|
364
|
+
const tipo = tipoCampoCSharp(campo, contexto);
|
|
365
|
+
if (tipo === "string" || tipo === "string?") {
|
|
366
|
+
return `!string.IsNullOrWhiteSpace(${acesso})`;
|
|
367
|
+
}
|
|
368
|
+
if (tipo.startsWith("List<") || tipo.startsWith("Dictionary<")) {
|
|
369
|
+
return `${acesso} is not null`;
|
|
370
|
+
}
|
|
371
|
+
if (tipoEhValor(tipo, contexto) && !tipo.endsWith("?")) {
|
|
372
|
+
return "true";
|
|
373
|
+
}
|
|
374
|
+
return `${acesso} is not null`;
|
|
375
|
+
}
|
|
376
|
+
function literalComparacao(valor, campo, contexto) {
|
|
377
|
+
const limpo = valor.trim().replace(/^['\"]|['\"]$/g, "");
|
|
378
|
+
if (/^(verdadeiro|true)$/i.test(limpo)) {
|
|
379
|
+
return "true";
|
|
380
|
+
}
|
|
381
|
+
if (/^(falso|false)$/i.test(limpo)) {
|
|
382
|
+
return "false";
|
|
383
|
+
}
|
|
384
|
+
if (/^(nulo|null)$/i.test(limpo)) {
|
|
385
|
+
return "null";
|
|
386
|
+
}
|
|
387
|
+
const tipo = campo ? tipoCampoCSharp(campo, contexto).replace(/\?$/, "") : "";
|
|
388
|
+
if (/^-?\d+(?:\.\d+)?$/.test(limpo)) {
|
|
389
|
+
return tipo === "decimal" ? `${limpo}m` : limpo;
|
|
390
|
+
}
|
|
391
|
+
if (tipo === "DateOnly") {
|
|
392
|
+
return `DateOnly.Parse(${literalString(limpo)}, CultureInfo.InvariantCulture)`;
|
|
393
|
+
}
|
|
394
|
+
if (tipo === "DateTimeOffset") {
|
|
395
|
+
return `DateTimeOffset.Parse(${literalString(limpo)}, CultureInfo.InvariantCulture)`;
|
|
396
|
+
}
|
|
397
|
+
if (contexto.enums.has(tipo)) {
|
|
398
|
+
return `${tipo}.${paraPascalCase(limpo, "Desconhecido")}`;
|
|
399
|
+
}
|
|
400
|
+
return literalString(limpo);
|
|
401
|
+
}
|
|
402
|
+
function traduzirExpressao(expressao, variavel, campos, contexto) {
|
|
403
|
+
if (expressao.tipo === "composta") {
|
|
404
|
+
const termos = expressao.termos.map((termo) => traduzirExpressao(termo, variavel, campos, contexto));
|
|
405
|
+
if (termos.some((termo) => !termo)) {
|
|
406
|
+
return undefined;
|
|
407
|
+
}
|
|
408
|
+
return `(${termos.join(expressao.operadorLogico === "e" ? " && " : " || ")})`;
|
|
409
|
+
}
|
|
410
|
+
if (expressao.tipo === "negacao") {
|
|
411
|
+
const termo = traduzirExpressao(expressao.termo, variavel, campos, contexto);
|
|
412
|
+
return termo ? `!(${termo})` : undefined;
|
|
413
|
+
}
|
|
414
|
+
const alvo = acessoCampo(expressao.alvo, variavel, campos);
|
|
415
|
+
const campo = campos.find((item) => item.nome === expressao.alvo.split(".")[0]);
|
|
416
|
+
if (!alvo) {
|
|
417
|
+
return undefined;
|
|
418
|
+
}
|
|
419
|
+
if (expressao.tipo === "existe") {
|
|
420
|
+
return condicaoExiste(alvo, campo, contexto);
|
|
421
|
+
}
|
|
422
|
+
if (expressao.tipo === "comparacao") {
|
|
423
|
+
if (expressao.valorLiteral === false) {
|
|
424
|
+
const outraReferencia = acessoCampo(expressao.valor, variavel, campos);
|
|
425
|
+
return outraReferencia ? `${alvo} ${expressao.operador} ${outraReferencia}` : undefined;
|
|
426
|
+
}
|
|
427
|
+
if (!tipoAceitaLiteral(campo, contexto)) {
|
|
428
|
+
return undefined;
|
|
429
|
+
}
|
|
430
|
+
return `${alvo} ${expressao.operador} ${literalComparacao(expressao.valor, campo, contexto)}`;
|
|
431
|
+
}
|
|
432
|
+
if (expressao.tipo === "pertencimento") {
|
|
433
|
+
if (!tipoAceitaLiteral(campo, contexto)) {
|
|
434
|
+
return undefined;
|
|
435
|
+
}
|
|
436
|
+
const valores = expressao.valores.map((valor) => literalComparacao(valor, campo, contexto));
|
|
437
|
+
if (valores.length === 0) {
|
|
438
|
+
return undefined;
|
|
439
|
+
}
|
|
440
|
+
return `new[] { ${valores.join(", ")} }.Contains(${alvo})`;
|
|
441
|
+
}
|
|
442
|
+
const predicado = expressao.predicadoCanonico ?? expressao.predicado;
|
|
443
|
+
const tipo = campo ? tipoCampoCSharp(campo, contexto).replace(/\?$/, "") : "";
|
|
444
|
+
if (["preenchido", "nao_vazio", "valido"].includes(predicado)) {
|
|
445
|
+
return condicaoExiste(alvo, campo, contexto);
|
|
446
|
+
}
|
|
447
|
+
if (predicado === "numero_valido") {
|
|
448
|
+
return tipo === "double" || tipo === "float" ? `!double.IsNaN(${alvo}) && !double.IsInfinity(${alvo})` : "true";
|
|
449
|
+
}
|
|
450
|
+
if (predicado === "positivo") {
|
|
451
|
+
return `${alvo} > 0`;
|
|
452
|
+
}
|
|
453
|
+
if (predicado === "diferente_de_zero") {
|
|
454
|
+
return `${alvo} != 0`;
|
|
455
|
+
}
|
|
456
|
+
if (predicado === "email_valido") {
|
|
457
|
+
return `!string.IsNullOrWhiteSpace(${alvo}) && ${alvo}.Contains('@', StringComparison.Ordinal)`;
|
|
458
|
+
}
|
|
459
|
+
return undefined;
|
|
460
|
+
}
|
|
461
|
+
function gerarValidacoes(task, contexto) {
|
|
462
|
+
const linhas = [];
|
|
463
|
+
for (let indice = 0; indice < task.rules.length; indice += 1) {
|
|
464
|
+
const regra = task.rules[indice];
|
|
465
|
+
const expressao = task.regrasEstruturadas.find((item) => item.textoOriginal === regra);
|
|
466
|
+
linhas.push(` // SEMA-RULE: ${escaparComentario(regra)}`);
|
|
467
|
+
const condicao = expressao ? traduzirExpressao(expressao, "input", task.input, contexto) : undefined;
|
|
468
|
+
if (condicao && condicao !== "true") {
|
|
469
|
+
linhas.push(` if (!(${condicao}))`);
|
|
470
|
+
linhas.push(" {");
|
|
471
|
+
linhas.push(` throw new SemaValidationException(${literalString(regra)});`);
|
|
472
|
+
linhas.push(" }");
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return linhas.length > 0 ? linhas.join("\n") : " // Nenhuma regra declarada.";
|
|
476
|
+
}
|
|
477
|
+
function gerarVerificacaoGarantias(task, contexto) {
|
|
478
|
+
const linhas = [];
|
|
479
|
+
for (let indice = 0; indice < task.guarantees.length; indice += 1) {
|
|
480
|
+
const garantia = task.guarantees[indice];
|
|
481
|
+
const expressao = task.garantiasEstruturadas.find((item) => item.textoOriginal === garantia);
|
|
482
|
+
linhas.push(` // SEMA-GUARANTEE: ${escaparComentario(garantia)}`);
|
|
483
|
+
const condicao = expressao ? traduzirExpressao(expressao, "output", task.output, contexto) : undefined;
|
|
484
|
+
if (condicao && condicao !== "true") {
|
|
485
|
+
linhas.push(` if (!(${condicao}))`);
|
|
486
|
+
linhas.push(" {");
|
|
487
|
+
linhas.push(` throw new SemaGuaranteeException(${literalString(garantia)});`);
|
|
488
|
+
linhas.push(" }");
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
return linhas.length > 0 ? linhas.join("\n") : " // Nenhuma garantia declarada.";
|
|
492
|
+
}
|
|
493
|
+
function gerarInicializadorSaida(task, contexto) {
|
|
494
|
+
if (task.output.length === 0) {
|
|
495
|
+
return `new ${paraPascalCase(`${task.nome}_saida`)}()`;
|
|
496
|
+
}
|
|
497
|
+
const propriedades = task.output.map((campo) => {
|
|
498
|
+
return ` ${nomePropriedade(campo.nome)} = ${valorSaidaGarantido(task, campo, contexto)},`;
|
|
499
|
+
}).join("\n");
|
|
500
|
+
return `new ${paraPascalCase(`${task.nome}_saida`)}\n {\n${propriedades}\n }`;
|
|
501
|
+
}
|
|
502
|
+
function gerarTask(task, contexto) {
|
|
503
|
+
const nome = paraPascalCase(task.nome);
|
|
504
|
+
const nomeEntrada = paraPascalCase(`${task.nome}_entrada`);
|
|
505
|
+
const nomeSaida = paraPascalCase(`${task.nome}_saida`);
|
|
506
|
+
const regras = task.rules.length > 0 ? `new[] { ${task.rules.map(literalString).join(", ")} }` : "Array.Empty<string>()";
|
|
507
|
+
const garantias = task.guarantees.length > 0 ? `new[] { ${task.guarantees.map(literalString).join(", ")} }` : "Array.Empty<string>()";
|
|
508
|
+
const efeitos = task.effects.length > 0 ? `new[] { ${task.effects.map(literalString).join(", ")} }` : "Array.Empty<string>()";
|
|
509
|
+
const erros = Object.entries(task.errors)
|
|
510
|
+
.map(([codigo, mensagem]) => ` [${literalString(codigo)}] = ${literalString(mensagem)},`)
|
|
511
|
+
.join("\n");
|
|
512
|
+
return `// SEMA-TASK: ${escaparComentario(task.nome)}
|
|
513
|
+
public sealed record class ${nomeEntrada}
|
|
514
|
+
{
|
|
515
|
+
${gerarPropriedades(task.input, contexto)}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
public sealed record class ${nomeSaida}
|
|
519
|
+
{
|
|
520
|
+
${gerarPropriedades(task.output, contexto)}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
public static class ${nome}Contract
|
|
524
|
+
{
|
|
525
|
+
public static IReadOnlyList<string> Rules { get; } = ${regras};
|
|
526
|
+
public static IReadOnlyList<string> Guarantees { get; } = ${garantias};
|
|
527
|
+
public static IReadOnlyList<string> Effects { get; } = ${efeitos};
|
|
528
|
+
public static IReadOnlyDictionary<string, string> Errors { get; } = new Dictionary<string, string>
|
|
529
|
+
{
|
|
530
|
+
${erros}
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
public static void Validate(${nomeEntrada} input)
|
|
534
|
+
{
|
|
535
|
+
ArgumentNullException.ThrowIfNull(input);
|
|
536
|
+
${gerarValidacoes(task, contexto)}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
public static ${nomeSaida} Execute(${nomeEntrada} input, string? expectedErrorCode = null)
|
|
540
|
+
{
|
|
541
|
+
Validate(input);
|
|
542
|
+
if (!string.IsNullOrWhiteSpace(expectedErrorCode))
|
|
543
|
+
{
|
|
544
|
+
var message = Errors.TryGetValue(expectedErrorCode, out var declaredMessage)
|
|
545
|
+
? declaredMessage
|
|
546
|
+
: "Erro esperado pelo caso Sema.";
|
|
547
|
+
throw new SemaContractException(expectedErrorCode, message);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
var output = ${gerarInicializadorSaida(task, contexto)};
|
|
551
|
+
VerifyGuarantees(output);
|
|
552
|
+
return output;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
public static void VerifyGuarantees(${nomeSaida} output)
|
|
556
|
+
{
|
|
557
|
+
ArgumentNullException.ThrowIfNull(output);
|
|
558
|
+
${gerarVerificacaoGarantias(task, contexto)}
|
|
559
|
+
}
|
|
560
|
+
}`;
|
|
561
|
+
}
|
|
562
|
+
function removerAspas(valor) {
|
|
563
|
+
const limpo = valor.trim();
|
|
564
|
+
if ((limpo.startsWith("\"") && limpo.endsWith("\"")) || (limpo.startsWith("'") && limpo.endsWith("'"))) {
|
|
565
|
+
return limpo.slice(1, -1);
|
|
566
|
+
}
|
|
567
|
+
return limpo;
|
|
568
|
+
}
|
|
569
|
+
function literalCasoTeste(valor, campo, contexto) {
|
|
570
|
+
const limpo = removerAspas(valor);
|
|
571
|
+
const tipo = tipoCampoCSharp(campo, contexto).replace(/\?$/, "");
|
|
572
|
+
if (/^(nulo|null)$/i.test(limpo)) {
|
|
573
|
+
return "null";
|
|
574
|
+
}
|
|
575
|
+
if (tipo === "bool") {
|
|
576
|
+
return /^(verdadeiro|true)$/i.test(limpo) ? "true" : "false";
|
|
577
|
+
}
|
|
578
|
+
if (["byte", "double", "float", "int", "long", "sbyte", "short", "uint", "ulong", "ushort"].includes(tipo) && /^-?\d+(?:\.\d+)?$/.test(limpo)) {
|
|
579
|
+
return limpo;
|
|
580
|
+
}
|
|
581
|
+
if (tipo === "decimal" && /^-?\d+(?:\.\d+)?$/.test(limpo)) {
|
|
582
|
+
return `${limpo}m`;
|
|
583
|
+
}
|
|
584
|
+
if (tipo === "DateOnly") {
|
|
585
|
+
return `DateOnly.Parse(${literalString(limpo)}, CultureInfo.InvariantCulture)`;
|
|
586
|
+
}
|
|
587
|
+
if (tipo === "DateTimeOffset") {
|
|
588
|
+
return `DateTimeOffset.Parse(${literalString(limpo)}, CultureInfo.InvariantCulture)`;
|
|
589
|
+
}
|
|
590
|
+
if (contexto.enums.has(tipo)) {
|
|
591
|
+
return `${tipo}.${paraPascalCase(limpo, "Desconhecido")}`;
|
|
592
|
+
}
|
|
593
|
+
if (tipo.startsWith("List<") || tipo.startsWith("Dictionary<") || contexto.tiposLocais.has(tipo)) {
|
|
594
|
+
return "new()";
|
|
595
|
+
}
|
|
596
|
+
return literalString(limpo);
|
|
597
|
+
}
|
|
598
|
+
function gerarAssercoesEsperadas(task, caso, contexto) {
|
|
599
|
+
const camposSaida = new Map(task.output.map((campo) => [campo.nome, campo]));
|
|
600
|
+
return caso.expect.campos.flatMap((esperado) => {
|
|
601
|
+
if (esperado.nome === "sucesso") {
|
|
602
|
+
return [];
|
|
603
|
+
}
|
|
604
|
+
const campo = camposSaida.get(esperado.nome);
|
|
605
|
+
if (!campo) {
|
|
606
|
+
return [];
|
|
607
|
+
}
|
|
608
|
+
const literal = literalCasoTeste(esperado.tipoOriginal || esperado.tipo, campo, contexto);
|
|
609
|
+
return [` SemaAssert.Equal(${literal}, output.${nomePropriedade(campo.nome)}, ${literalString(`Saída inesperada para ${campo.nome}.`)});`];
|
|
610
|
+
}).join("\n");
|
|
611
|
+
}
|
|
612
|
+
function gerarEntradaCaso(task, caso, contexto) {
|
|
613
|
+
const dados = new Map(caso.given.campos.map((campo) => [campo.nome, campo.tipoOriginal || campo.tipo]));
|
|
614
|
+
if (task.input.length === 0) {
|
|
615
|
+
return `new ${paraPascalCase(`${task.nome}_entrada`)}()`;
|
|
616
|
+
}
|
|
617
|
+
const propriedades = task.input.map((campo) => {
|
|
618
|
+
const literal = dados.has(campo.nome)
|
|
619
|
+
? literalCasoTeste(dados.get(campo.nome), campo, contexto)
|
|
620
|
+
: valorPadraoTipo(tipoCampoCSharp(campo, contexto), contexto);
|
|
621
|
+
return ` ${nomePropriedade(campo.nome)} = ${literal},`;
|
|
622
|
+
}).join("\n");
|
|
623
|
+
return `new ${paraPascalCase(`${task.nome}_entrada`)}\n {\n${propriedades}\n }`;
|
|
624
|
+
}
|
|
625
|
+
function codigoErroEsperado(caso) {
|
|
626
|
+
const campoTipo = caso.error?.campos.find((campo) => campo.nome === "tipo") ?? caso.error?.campos[0];
|
|
627
|
+
return campoTipo ? removerAspas(campoTipo.tipoOriginal || campoTipo.tipo) : undefined;
|
|
628
|
+
}
|
|
629
|
+
function gerarMetodoTeste(task, caso, indice, contexto) {
|
|
630
|
+
const nome = `Test${paraPascalCase(task.nome)}${indice + 1}`;
|
|
631
|
+
const nomeTask = `${paraPascalCase(task.nome)}Contract`;
|
|
632
|
+
const entrada = gerarEntradaCaso(task, caso, contexto);
|
|
633
|
+
const erro = codigoErroEsperado(caso);
|
|
634
|
+
const detalhesEsperados = caso.expect.campos
|
|
635
|
+
.map((campo) => `${campo.nome}=${campo.tipoOriginal || campo.tipo}`)
|
|
636
|
+
.join(", ");
|
|
637
|
+
const assercoesEsperadas = gerarAssercoesEsperadas(task, caso, contexto);
|
|
638
|
+
const corpo = erro
|
|
639
|
+
? ` try
|
|
640
|
+
{
|
|
641
|
+
${nomeTask}.Execute(input, ${literalString(erro)});
|
|
642
|
+
throw new InvalidOperationException(${literalString(`O caso ${caso.nome} deveria falhar.`)});
|
|
643
|
+
}
|
|
644
|
+
catch (SemaContractException exception) when (exception.Code == ${literalString(erro)})
|
|
645
|
+
{
|
|
646
|
+
// Erro declarado e observado conforme o contrato.
|
|
647
|
+
}`
|
|
648
|
+
: ` var output = ${nomeTask}.Execute(input);
|
|
649
|
+
SemaAssert.NotNull(output, ${literalString(`O caso ${caso.nome} não retornou saída.`)});
|
|
650
|
+
${nomeTask}.VerifyGuarantees(output);
|
|
651
|
+
${assercoesEsperadas || " // O caso não declarou campos de saída verificáveis."}`;
|
|
652
|
+
return {
|
|
653
|
+
nome,
|
|
654
|
+
codigo: ` // SEMA-TEST: ${escaparComentario(task.nome)} :: ${escaparComentario(caso.nome)}
|
|
655
|
+
private static void ${nome}()
|
|
656
|
+
{
|
|
657
|
+
// SEMA-EXPECT: ${escaparComentario(detalhesEsperados || "sucesso")}
|
|
658
|
+
var input = ${entrada};
|
|
659
|
+
${corpo}
|
|
660
|
+
}`,
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
function gerarCodigoFonte(modulo, contexto) {
|
|
664
|
+
const namespace = nomeNamespace(modulo.nome);
|
|
665
|
+
const externos = coletarTiposExternos(modulo).map((tipo) => `// SEMA-EXTERNAL-TYPE: ${escaparComentario(tipo)}\npublic sealed record class ${paraPascalCase(tipo)};`).join("\n\n");
|
|
666
|
+
const tipos = modulo.types.map((tipo) => gerarModelo(tipo.nome, tipo.definicao.campos, tipo.invariantes, contexto, "type")).join("\n\n");
|
|
667
|
+
const entidades = modulo.entities.map((entidade) => gerarModelo(entidade.nome, entidade.campos, entidade.invariantes, contexto, "entity")).join("\n\n");
|
|
668
|
+
const enums = modulo.enums.map((item) => gerarEnum(item.nome, item.valores)).join("\n\n");
|
|
669
|
+
const tasks = modulo.tasks.map((task) => gerarTask(task, contexto)).join("\n\n");
|
|
670
|
+
const interops = modulo.interoperabilidades.map((item) => `// SEMA-INTEROP: ${item.origem}:${escaparComentario(item.caminho)}`).join("\n");
|
|
671
|
+
const flows = modulo.flows.map((item) => `// SEMA-FLOW: ${escaparComentario(item.nome)} tasks=${escaparComentario(item.tasksReferenciadas.join(", ") || "nenhuma")}`).join("\n");
|
|
672
|
+
const routes = modulo.routes.map((item) => `// SEMA-ROUTE: ${escaparComentario(item.nome)} metodo=${escaparComentario(item.metodo ?? "nao_definido")} caminho=${escaparComentario(item.caminho ?? "nao_definido")} task=${escaparComentario(item.task ?? "nao_definida")}`).join("\n");
|
|
673
|
+
const states = modulo.states.map((item) => `// SEMA-STATE: ${escaparComentario(item.nome ?? "anonimo")} campos=${item.campos.length} invariantes=${item.invariantes.length} transicoes=${item.transicoes.length}`).join("\n");
|
|
674
|
+
return `${gerarCabecalhoCSharp(modulo, "código C# gerado a partir do IR Sema, incluindo modelos, regras, garantias e metadados operacionais.")}using System;
|
|
675
|
+
using System.Collections.Generic;
|
|
676
|
+
using System.Globalization;
|
|
677
|
+
using System.Linq;
|
|
678
|
+
|
|
679
|
+
namespace ${namespace};
|
|
680
|
+
|
|
681
|
+
public class SemaContractException : Exception
|
|
682
|
+
{
|
|
683
|
+
public SemaContractException(string code, string message) : base(message) => Code = code;
|
|
684
|
+
public string Code { get; }
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
public sealed class SemaValidationException : SemaContractException
|
|
688
|
+
{
|
|
689
|
+
public SemaValidationException(string rule) : base("regra_invalida", $"Regra Sema violada: {rule}") { }
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
public sealed class SemaGuaranteeException : SemaContractException
|
|
693
|
+
{
|
|
694
|
+
public SemaGuaranteeException(string guarantee) : base("garantia_violada", $"Garantia Sema violada: {guarantee}") { }
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
${interops ? `${interops}\n\n` : ""}${externos ? `${externos}\n\n` : ""}${enums ? `${enums}\n\n` : ""}${tipos ? `${tipos}\n\n` : ""}${entidades ? `${entidades}\n\n` : ""}${tasks}${states ? `\n\n${states}` : ""}${flows ? `\n${flows}` : ""}${routes ? `\n${routes}` : ""}
|
|
698
|
+
`;
|
|
699
|
+
}
|
|
700
|
+
function gerarCodigoTestes(modulo, contexto) {
|
|
701
|
+
const namespace = nomeNamespace(modulo.nome);
|
|
702
|
+
const metodos = modulo.tasks.flatMap((task) => task.tests.map((caso, indice) => gerarMetodoTeste(task, caso, indice, contexto)));
|
|
703
|
+
if (metodos.length === 0) {
|
|
704
|
+
metodos.push({
|
|
705
|
+
nome: "TestEstruturaGerada",
|
|
706
|
+
codigo: ` // SEMA-TEST: estrutura gerada :: módulo sem casos declarados
|
|
707
|
+
private static void TestEstruturaGerada()
|
|
708
|
+
{
|
|
709
|
+
SemaAssert.True(true, "A estrutura .NET gerada deve ser executavel.");
|
|
710
|
+
}`,
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
const registros = metodos.map((metodo) => ` (${literalString(metodo.nome)}, ${metodo.nome}),`).join("\n");
|
|
714
|
+
return `${gerarCabecalhoCSharp(modulo, "runner de testes .NET gerado sem framework ou pacote NuGet externo.")}using System;
|
|
715
|
+
using System.Collections.Generic;
|
|
716
|
+
using System.Globalization;
|
|
717
|
+
using ${namespace};
|
|
718
|
+
|
|
719
|
+
internal static class SemaAssert
|
|
720
|
+
{
|
|
721
|
+
public static void True(bool condition, string message)
|
|
722
|
+
{
|
|
723
|
+
if (!condition) throw new InvalidOperationException(message);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
public static void NotNull(object? value, string message) => True(value is not null, message);
|
|
727
|
+
|
|
728
|
+
public static void Equal<T>(T expected, T actual, string message)
|
|
729
|
+
{
|
|
730
|
+
if (!EqualityComparer<T>.Default.Equals(expected, actual))
|
|
731
|
+
{
|
|
732
|
+
throw new InvalidOperationException($"{message} Esperado={expected}; Atual={actual}.");
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
internal static class Program
|
|
738
|
+
{
|
|
739
|
+
private static int Main()
|
|
740
|
+
{
|
|
741
|
+
var tests = new (string Name, Action Run)[]
|
|
742
|
+
{
|
|
743
|
+
${registros}
|
|
744
|
+
};
|
|
745
|
+
|
|
746
|
+
foreach (var test in tests)
|
|
747
|
+
{
|
|
748
|
+
Console.WriteLine($"test {test.Name}");
|
|
749
|
+
test.Run();
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
Console.WriteLine($"ok {tests.Length} testes");
|
|
753
|
+
return 0;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
${metodos.map((metodo) => metodo.codigo).join("\n\n")}
|
|
757
|
+
}
|
|
758
|
+
`;
|
|
759
|
+
}
|
|
760
|
+
function gerarProjetoFonte(modulo, base, assemblyName) {
|
|
761
|
+
return `${gerarCabecalhoProjeto(modulo, "biblioteca .NET gerada sem dependências NuGet externas.")}
|
|
762
|
+
<Project Sdk="Microsoft.NET.Sdk">
|
|
763
|
+
<PropertyGroup>
|
|
764
|
+
<TargetFramework>net8.0</TargetFramework>
|
|
765
|
+
<ImplicitUsings>enable</ImplicitUsings>
|
|
766
|
+
<Nullable>enable</Nullable>
|
|
767
|
+
<LangVersion>latest</LangVersion>
|
|
768
|
+
<AssemblyName>${assemblyName}</AssemblyName>
|
|
769
|
+
<RootNamespace>${assemblyName}</RootNamespace>
|
|
770
|
+
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
|
|
771
|
+
</PropertyGroup>
|
|
772
|
+
<ItemGroup>
|
|
773
|
+
<Compile Include="${base}.cs" />
|
|
774
|
+
</ItemGroup>
|
|
775
|
+
</Project>
|
|
776
|
+
`;
|
|
777
|
+
}
|
|
778
|
+
function gerarProjetoTestes(modulo, base, assemblyName) {
|
|
779
|
+
return `${gerarCabecalhoProjeto(modulo, "executavel de testes .NET autocontido, sem xUnit, MSTest, NUnit ou outro pacote NuGet.")}
|
|
780
|
+
<Project Sdk="Microsoft.NET.Sdk">
|
|
781
|
+
<PropertyGroup>
|
|
782
|
+
<OutputType>Exe</OutputType>
|
|
783
|
+
<TargetFramework>net8.0</TargetFramework>
|
|
784
|
+
<ImplicitUsings>enable</ImplicitUsings>
|
|
785
|
+
<Nullable>enable</Nullable>
|
|
786
|
+
<LangVersion>latest</LangVersion>
|
|
787
|
+
<AssemblyName>${assemblyName}.Tests</AssemblyName>
|
|
788
|
+
<RootNamespace>${assemblyName}.Tests</RootNamespace>
|
|
789
|
+
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
|
|
790
|
+
</PropertyGroup>
|
|
791
|
+
<ItemGroup>
|
|
792
|
+
<Compile Include="${base}.Tests.cs" />
|
|
793
|
+
<ProjectReference Include="${base}.csproj" />
|
|
794
|
+
</ItemGroup>
|
|
795
|
+
</Project>
|
|
796
|
+
`;
|
|
797
|
+
}
|
|
798
|
+
export function gerarDotNet(modulo) {
|
|
799
|
+
const base = normalizarNomeModulo(modulo.nome).replace(/\./g, "_") || "sema_generated";
|
|
800
|
+
const assemblyName = nomeProjeto(modulo.nome);
|
|
801
|
+
const enums = new Set(modulo.enums.map((item) => paraPascalCase(item.nome)));
|
|
802
|
+
const valoresEnums = new Map(modulo.enums.map((item) => [paraPascalCase(item.nome), item.valores]));
|
|
803
|
+
const tiposLocais = new Set([
|
|
804
|
+
...modulo.types.map((item) => paraPascalCase(item.nome)),
|
|
805
|
+
...modulo.entities.map((item) => paraPascalCase(item.nome)),
|
|
806
|
+
...coletarTiposExternos(modulo).map((item) => paraPascalCase(item)),
|
|
807
|
+
]);
|
|
808
|
+
const contexto = { enums, valoresEnums, tiposLocais };
|
|
809
|
+
return [
|
|
810
|
+
{
|
|
811
|
+
caminhoRelativo: `${base}.csproj`,
|
|
812
|
+
conteudo: gerarProjetoFonte(modulo, base, assemblyName),
|
|
813
|
+
},
|
|
814
|
+
{
|
|
815
|
+
caminhoRelativo: `${base}.cs`,
|
|
816
|
+
conteudo: gerarCodigoFonte(modulo, contexto),
|
|
817
|
+
},
|
|
818
|
+
{
|
|
819
|
+
caminhoRelativo: `${base}.Tests.csproj`,
|
|
820
|
+
conteudo: gerarProjetoTestes(modulo, base, assemblyName),
|
|
821
|
+
},
|
|
822
|
+
{
|
|
823
|
+
caminhoRelativo: `${base}.Tests.cs`,
|
|
824
|
+
conteudo: gerarCodigoTestes(modulo, contexto),
|
|
825
|
+
},
|
|
826
|
+
];
|
|
827
|
+
}
|
|
828
|
+
//# sourceMappingURL=index.js.map
|