UNPKG

26.1 kBPlain TextView Raw
1import { Include } from './include';
2import { Erro, Severity } from './models/Erro';
3import { Fonte, Tipos } from './fonte';
4import { version } from './package.json';
5
6export class ValidaAdvpl {
7 public comentFontPad: string[];
8 public error: number;
9 public warning: number;
10 public information: number;
11 public hint: number;
12 public includes: any[];
13 public aErros: Erro[];
14 public ownerDb: string[];
15 public empresas: string[];
16 public fonte: Fonte;
17 public version: string;
18 public conteudoFonte: string;
19 private local;
20 constructor(
21 comentFontePad: string[],
22 local: string,
23 private log: boolean = true
24 ) {
25 this.local = local;
26 this.aErros = [];
27 this.includes = [];
28 this.error = 0;
29 this.warning = 0;
30 this.information = 0;
31 this.hint = 0;
32 //Se não está preenchido seta com valor padrão
33 this.comentFontPad = comentFontePad;
34 this.ownerDb = [];
35 this.empresas = [];
36 this.version = version;
37 }
38
39 public validacao(texto: string, path: string): Promise<ValidaAdvpl> {
40 return new Promise((resolve: Function, reject: Function) => {
41 try {
42 let objeto: ValidaAdvpl = this;
43
44 objeto.conteudoFonte = texto;
45 objeto.aErros = [];
46 objeto.includes = [];
47 objeto.fonte = new Fonte(path);
48 let conteudoSComentario: string = '';
49 let linhas: String[] = texto.split('\n');
50 //Pega as linhas do documento ativo e separa o array por linha
51
52 let comentFuncoes: any[] = new Array();
53 let funcoes: any[] = new Array();
54 let cBeginSql: boolean = false;
55 let FromQuery: boolean = false;
56 let JoinQuery: boolean = false;
57 let cSelect: boolean = false;
58 let ProtheusDoc: boolean = false;
59 let emComentario: boolean = false;
60 //Percorre todas as linhas
61 for (var key in linhas) {
62 //seta linha atual em caixa alta
63 let linha: String = linhas[key].toLocaleUpperCase();
64 let linhaClean: String = '';
65 //se estiver no PotheusDoc vê se está fechando
66 if (ProtheusDoc && linha.match(/(\*\/)/i)) {
67 ProtheusDoc = false;
68 }
69 //verifica se é protheusDoc
70 if (linha.match(/^(\s*)(\/\*\/(.*)?\{Protheus.doc\}(.*)?)/i)) {
71 ProtheusDoc = true;
72 //reseta todas as ariáveis de controle pois se entrou em ProtheusDoc está fora de qualquer função
73 cBeginSql = false;
74 FromQuery = false;
75 JoinQuery = false;
76 cSelect = false;
77 //verifica se é um comentário de função e adiciona no array
78 comentFuncoes.push([
79 linha
80 .trim()
81 .replace(/^(\s*)(\/\*\/(.*)?\{Protheus.doc\}( |\t)*)/i, '')
82 .trim()
83 .toLocaleUpperCase(),
84 key
85 ]);
86 }
87
88 //verifica se a linha está toda comentada
89 let posComentLinha: number = linha.search(/\/\//);
90 let posComentBloco: number = linha.search(/\/\*/);
91 posComentBloco = posComentBloco === -1 ? 999999 : posComentBloco;
92 posComentLinha = posComentLinha === -1 ? 999999 : posComentLinha;
93 if (!emComentario && posComentLinha < posComentBloco) {
94 linha = linha.split('//')[0];
95 }
96
97 //Verifica se está em comentário de bloco
98 //trata comentários dentro da linha
99 linha = linha.replace(/\/\*+.+\*\//, '');
100 if (linha.match(/(\*\/)/i) && emComentario) {
101 emComentario = false;
102 linha = linha.split('*/')[1];
103 }
104
105 //se não estiver dentro do Protheus DOC valida linha
106 if (!emComentario) {
107 if (
108 linha
109 .replace(/\"+.+\"/, '')
110 .replace(/\'+.+\'/, '')
111 .search(/\/\*/) !== -1
112 ) {
113 emComentario = true;
114 linha = linha.split(/\/\*/)[0];
115 }
116
117 //Se não estiver em comentário verifica se o último caracter da linha é ;
118 if (!emComentario && linha.charAt(linha.length - 1) === ';') {
119 linhas[parseInt(key) + 1] =
120 linha + ' ' + linhas[parseInt(key) + 1];
121 linha = '';
122 }
123
124 //trata comentários em linha ou strings em aspas simples ou duplas
125 //não remove aspas quando for include
126 linha = linha.split('//')[0];
127 linhaClean = linha;
128 if (linha.match(/^(\s*)#INCLUDE/)) {
129 while (
130 linhaClean.match(/\"+.+\"/) ||
131 linhaClean.match(/\'+.+\'/)
132 ) {
133 let colunaDupla: number = linhaClean.search(/\"+.+\"/);
134 let colunaSimples: number = linhaClean.search(/\'+.+\'/);
135 //se a primeira for a dupla
136 if (
137 colunaDupla !== -1 &&
138 (colunaDupla < colunaSimples || colunaSimples === -1)
139 ) {
140 let quebra: string[] = linhaClean.split('"');
141 linhaClean = linhaClean.replace('"' + quebra[1] + '"', '');
142 } else {
143 let quebra: string[] = linhaClean.split("'");
144 linhaClean = linhaClean.replace("'" + quebra[1] + "'", '');
145 }
146 }
147 }
148
149 //Remove espaços ou tabulações seguidas
150 linhaClean = linhaClean.replace(/\t/g, ' ');
151 linhaClean = linhaClean.replace(/\:\=/g, ' :=');
152 linhaClean = linhaClean.replace(/\r/g, '');
153 let conteudos: string[] = linhaClean.split(' ');
154 linhaClean = '';
155 for (const key in conteudos) {
156 if (conteudos[key]) {
157 linhaClean += conteudos[key] + ' ';
158 }
159 }
160
161 conteudoSComentario = conteudoSComentario + linhaClean + '\n';
162 let firstWord: string = linhaClean.split(' ')[0].split('\t')[0];
163
164 // só analisa se tiver conteúdo
165 if (conteudoSComentario.trim()) {
166 //verifica se é função e adiciona no array
167 if (
168 linhaClean.match(
169 /^(\s*)((user|static)(\ |\t)*)?(function)(\s+)(\w+)/i
170 )
171 ) {
172 //reseta todas as ariáveis de controle pois está fora de qualquer função
173 cBeginSql = false;
174 FromQuery = false;
175 JoinQuery = false;
176 cSelect = false;
177 let nomeFuncao: string = linhaClean
178 .replace(
179 /^(\s*)((user|static)(\ |\t)*)?(function)(\ |\t)+/gi,
180 ''
181 )
182 .split('(')[0];
183 //verifica se é um função e adiciona no array
184 funcoes.push([nomeFuncao.trim(), key]);
185 //verifica o TIPO
186 if (
187 linhaClean.match(
188 /^(\s*)((user)(\ |\t)*)?(function)(\s+)(\w+)/i
189 )
190 ) {
191 objeto.fonte.addFunction(
192 Tipos['User Function'],
193 nomeFuncao,
194 parseInt(key)
195 );
196 } else if (
197 linhaClean.match(
198 /^(\s*)((static)(\ |\t)*)?(function)(\s+)(\w+)/i
199 )
200 ) {
201 //verifica se a primeira palavra é FUNCTION
202 objeto.fonte.addFunction(
203 Tipos['Static Function'],
204 nomeFuncao,
205 parseInt(key)
206 );
207 } else if (firstWord === 'FUNCTION') {
208 //verifica se a primeira palavra é FUNCTION
209 objeto.fonte.addFunction(
210 Tipos.Function,
211 nomeFuncao,
212 parseInt(key)
213 );
214 }
215 }
216 //Verifica se é CLASSE ou WEBSERVICE
217 if (
218 linhaClean.search('METHOD\\ .*?CLASS') !== -1 ||
219 firstWord === 'CLASS' ||
220 linhaClean.search('WSMETHOD.*?WSSERVICE') !== -1 ||
221 firstWord === 'WSSERVICE' ||
222 firstWord === 'WSRESTFUL' ||
223 firstWord === 'WSSTRUCT'
224 ) {
225 //reseta todas as ariáveis de controle pois está fora de qualquer função
226 cBeginSql = false;
227 FromQuery = false;
228 JoinQuery = false;
229 cSelect = false;
230 //verifica se é um função e adiciona no array
231 try {
232 funcoes.push([
233 linhaClean
234 .trim()
235 .split(' ')[1]
236 .split('(')[0],
237 key
238 ]);
239 } catch {
240 console.log('Erro na captura de função da linha ');
241 console.log(linhaClean);
242 }
243
244 if (firstWord === 'CLASS') {
245 objeto.fonte.addFunction(
246 Tipos.Class,
247 linhaClean
248 .trim()
249 .split(' ')[1]
250 .split('(')[0],
251 parseInt(key)
252 );
253 }
254 if (firstWord.match(/METHOD/)) {
255 let palavras: string[] = linhaClean.split(/,| |\t|\(/);
256 let metodo: string = palavras[1];
257 let classe: string;
258 for (var i = 0; i < palavras.length; i++) {
259 let key2 = palavras[i];
260 if (key2 === 'WSSERVICE' || key2 === 'CLASS') {
261 classe = palavras[i + 1];
262 break;
263 }
264 }
265
266 objeto.fonte.addFunction(
267 Tipos.Method,
268 classe + '|' + metodo,
269 parseInt(key)
270 );
271 }
272 }
273 //Adiciona no objeto as variáveis locais
274 if (firstWord === 'LOCAL') {
275 //remove o LOCAL
276 let variaveis: string[] = linhaClean.split(/,| |\t|\r/);
277 for (var key2 of variaveis) {
278 if (key2 !== 'LOCAL' && key2 !== '') {
279 // se terminar as variáveis
280 if (key2.match(/\:\=/)) {
281 break;
282 }
283 //objeto.fonte.addVariavel(key2);
284 }
285 }
286 }
287
288 //Verifica se adicionou o include TOTVS.CH
289 if (linha.search(/^(\s*)#INCLUDE/i) !== -1) {
290 //REMOVE as aspas a palavra #include e os espacos e tabulações
291 objeto.includes.push({
292 include: linha
293 .replace(/^(\s*)#INCLUDE/gi, '')
294 .replace(/\t/g, '')
295 .replace(/\'/g, '')
296 .replace(/\"/g, '')
297 .trim(),
298 linha: parseInt(key)
299 });
300 }
301 if (linhaClean.search(/^(\s*)BEGIN(\s*)ALIAS/) !== -1) {
302 cBeginSql = true;
303 }
304 if (
305 linha.match(/(\ |\t|\'|\"|)+(SELECT|DELETE|UPDATE)(\ |\t)+/)
306 ) {
307 cSelect = true;
308 }
309 if (
310 !cBeginSql &&
311 (linha.search(
312 /(\ |\t|\'|\"|)+DBUSEAREA+(\ |\t|)+\(+.+TOPCONN+.+TCGENQRY/
313 ) !== -1 ||
314 linhaClean.search(/TCQUERY+(\ |\t)/) !== -1)
315 ) {
316 objeto.aErros.push(
317 new Erro(
318 parseInt(key),
319 parseInt(key),
320 traduz('validaAdvpl.queryNoEmbedded', objeto.local),
321 Severity.Warning
322 )
323 );
324 FromQuery = false;
325 cSelect = false;
326 }
327 if (
328 linha.search(/(\ |\t|\'|\")+DELETE+(\ |\t)+FROM+(\ |\t)/) !== -1
329 ) {
330 objeto.aErros.push(
331 new Erro(
332 parseInt(key),
333 parseInt(key),
334 traduz('validaAdvpl.deleteFrom', objeto.local),
335 Severity.Warning
336 )
337 );
338 }
339 if (linhaClean.search(/MSGBOX\(/) !== -1) {
340 objeto.aErros.push(
341 new Erro(
342 parseInt(key),
343 parseInt(key),
344 traduz('validaAdvpl.msgBox', objeto.local),
345 Severity.Information
346 )
347 );
348 }
349 if (linha.match(/GETMV(\ |\t|\()+(\"|\')+MV_FOLMES+(\"|\')/gi)) {
350 objeto.aErros.push(
351 new Erro(
352 parseInt(key),
353 parseInt(key),
354 traduz('validaAdvpl.folMes', objeto.local),
355 Severity.Information
356 )
357 );
358 }
359 if (linha.search('\\<\\<\\<\\<\\<\\<\\<\\ HEAD') !== -1) {
360 //Verifica linha onde terminou o conflito
361 let nFim: string = key;
362 for (var key2 in linhas) {
363 if (
364 linhas[key2].search('\\>\\>\\>\\>\\>\\>\\>') !== -1 &&
365 nFim === key &&
366 key2 > key
367 ) {
368 nFim = key2;
369 }
370 }
371 objeto.aErros.push(
372 new Erro(
373 parseInt(key),
374 parseInt(nFim),
375 traduz('validaAdvpl.conflictMerge', objeto.local),
376 Severity.Error
377 )
378 );
379 }
380 if (
381 linha.search(/(\ |\t|\'|\"|)+SELECT+(\ |\t)/) !== -1 &&
382 linha.search('\\ \\*\\ ') !== -1
383 ) {
384 objeto.aErros.push(
385 new Erro(
386 parseInt(key),
387 parseInt(key),
388 traduz('validaAdvpl.selectAll', objeto.local),
389 Severity.Warning
390 )
391 );
392 }
393 if (
394 linha.search('CHR\\(13\\)') !== -1 &&
395 linha.search('CHR\\(10\\)') !== -1
396 ) {
397 objeto.aErros.push(
398 new Erro(
399 parseInt(key),
400 parseInt(key),
401 traduz('validaAdvpl.crlf', objeto.local),
402 Severity.Warning
403 )
404 );
405 }
406 if (cSelect && linha.search('FROM') !== -1) {
407 FromQuery = true;
408 }
409 if (cSelect && FromQuery && linha.search('JOIN') !== -1) {
410 JoinQuery = true;
411 }
412 if (
413 linha.search('ENDSQL') !== -1 ||
414 linha.search('WHERE') !== -1 ||
415 linha.search('TCQUERY') !== -1
416 ) {
417 FromQuery = false;
418 cSelect = false;
419 }
420 //Implementação para aceitar vários bancos de dados
421 for (var idb = 0; idb < objeto.ownerDb.length; idb++) {
422 let banco: string = objeto.ownerDb[idb];
423 if (cSelect && FromQuery && linha.search(banco) !== -1) {
424 objeto.aErros.push(
425 new Erro(
426 parseInt(key),
427 parseInt(key),
428 traduz('validaAdvpl.noSchema', objeto.local) +
429 banco +
430 traduz('validaAdvpl.inQuery', objeto.local),
431 Severity.Error
432 )
433 );
434 }
435 }
436 if (
437 cSelect &&
438 (FromQuery || JoinQuery || linha.search('SET') !== -1) &&
439 linha.search('exp:cTable') === -1
440 ) {
441 //procura códigos de empresas nas queryes
442 for (var idb = 0; idb < objeto.empresas.length; idb++) {
443 let empresa: string = objeto.empresas[idb];
444 //para melhorar a análise vou quebrar a string por espaços
445 //e removendo as quebras de linhas, vou varrer os itens do array e verificar o tamanho
446 //e o código da empresa chumbado
447 let palavras: string[] = linha
448 .replace(/\r/g, '')
449 .replace(/\t/g, '')
450 .split(' ');
451 for (var idb2 = 0; idb2 < palavras.length; idb2++) {
452 let palavra: string = palavras[idb2];
453 if (
454 palavra.search(empresa + '0') !== -1 &&
455 palavra.length === 6
456 ) {
457 objeto.aErros.push(
458 new Erro(
459 parseInt(key),
460 parseInt(key),
461 traduz('validaAdvpl.tableFixed', objeto.local),
462 Severity.Error
463 )
464 );
465 }
466 }
467 }
468 }
469 if (cSelect && JoinQuery && linha.search('ON') !== -1) {
470 JoinQuery = false;
471 }
472 if (linhaClean.search(/CONOUT(\ |\t)*\(/) !== -1) {
473 objeto.aErros.push(
474 new Erro(
475 parseInt(key),
476 parseInt(key),
477 traduz('validaAdvpl.conout', objeto.local),
478 Severity.Warning
479 )
480 );
481 }
482 // PUTSX1
483 if (linhaClean.search(/PUTSX1(\ |\t)*\(/) !== -1) {
484 objeto.aErros.push(
485 new Erro(
486 parseInt(key),
487 parseInt(key),
488 traduz('validaAdvpl.PutSX1', objeto.local),
489 Severity.Error
490 )
491 );
492 }
493 // Uso de Dicionários Fora do BeginSql
494 let posicaoDic: number = (' ' + linhaClean).search(
495 /(,| |\t|\>|\()+X+(1|2|3|5|6|7|9|A|B|D|G)+\_/gim
496 );
497 if (
498 !cBeginSql &&
499 posicaoDic !== -1 &&
500 (' ' + linhaClean)
501 .substring(posicaoDic + 1)
502 .split(' ')[0]
503 .split('\t')[0]
504 .search(/\(/) === -1
505 ) {
506 objeto.aErros.push(
507 new Erro(
508 parseInt(key),
509 parseInt(key),
510 traduz('validaAdvpl.Dictionary', objeto.local),
511 Severity.Error
512 )
513 );
514 }
515 if (
516 linhaClean.search(
517 /(,| |\t||\()*(MSFILE|MSFILE|DBCREATE|DBUSEAREA|CRIATRAB)+( \(|\t\(|\()+/gim
518 ) !== -1 ||
519 linhaClean.search(
520 /( |)*(MSCOPYFILE|MSERASE|COPY TO)+( |\t)+/gim
521 ) !== -1
522 ) {
523 objeto.aErros.push(
524 new Erro(
525 parseInt(key),
526 parseInt(key),
527 traduz('validaAdvpl.Isam', objeto.local),
528 Severity.Error
529 )
530 );
531 }
532 //recomendação para melhorar identificação de problemas em queryes
533 if (
534 (linha.match(/(\ |\t|)+SELECT+(\ |\t)/) ||
535 linha.match(/(\ |\t|)+DELETE+(\ |\t)/) ||
536 linha.match(/(\ |\t|)+UPDATE+(\ |\t)/) ||
537 linha.match(/(\ |\t|)+JOIN+(\ |\t)/)) &&
538 (linha.match(/(\ |\t|)+FROM+(\ |\t)/) ||
539 linha.match(/(\ |\t|)+ON+(\ |\t)/) ||
540 linha.match(/(\ |\t|)+WHERE+(\ |\t)/)) &&
541 linha.search(/(\ |\t)+TCSQLEXEC+\(/) === -1
542 ) {
543 //verifica o caracter anterior tem que ser ou ESPACO ou ' ou " ou nada
544 let itens1: string[] = ['FROM', 'ON', 'WHERE'];
545 let addErro: boolean = false;
546 for (var idx3 = 0; idx3 < itens1.length; idx3++) {
547 let item: string = itens1[idx3];
548
549 addErro = addErro || linha.search("\\'" + item) !== -1;
550 addErro = addErro || linha.search('\\"' + item) !== -1;
551 addErro = addErro || linha.search('\\ ' + item) !== -1;
552 }
553
554 if (addErro) {
555 objeto.aErros.push(
556 new Erro(
557 parseInt(key),
558 parseInt(key),
559 traduz('validaAdvpl.bestAnalitc', objeto.local) +
560 ' SELECT, DELETE, UPDATE, JOIN, FROM, ON, WHERE.',
561 Severity.Information
562 )
563 );
564 }
565 }
566 }
567 } else {
568 conteudoSComentario += '\n';
569 }
570 }
571
572 //Validação de padrão de comentáris de fontes
573 let comentariosFonte: boolean = true;
574 for (var _i = 0; _i < objeto.comentFontPad.length; _i++) {
575 let cExpressao: string = objeto.comentFontPad[_i] as string;
576 let linha: string = linhas[_i] as string;
577 comentariosFonte =
578 comentariosFonte && linha.search(cExpressao) !== -1;
579 }
580
581 if (!comentariosFonte) {
582 objeto.aErros.push(
583 new Erro(
584 0,
585 0,
586 traduz('validaAdvpl.padComment', objeto.local),
587 Severity.Information
588 )
589 );
590 }
591
592 //Validação funções sem comentários
593 for (var idx = 0; idx < funcoes.length; idx++) {
594 let funcao: string = funcoes[idx];
595 let achou: boolean = false;
596 for (var idx4 = 0; idx4 < comentFuncoes.length; idx4++) {
597 let comentario: string = comentFuncoes[idx4];
598 achou = achou || comentario[0] === funcao[0];
599 }
600
601 if (!achou) {
602 objeto.aErros.push(
603 new Erro(
604 parseInt(funcao[1]),
605 parseInt(funcao[1]),
606 traduz('validaAdvpl.functionNoCommented', objeto.local),
607 Severity.Warning
608 )
609 );
610 }
611 }
612 //Validação comentários sem funções
613 for (var idx = 0; idx < comentFuncoes.length; idx++) {
614 let comentario: string = comentFuncoes[idx];
615 let achou: boolean = false;
616 for (var idx4 = 0; idx4 < funcoes.length; idx4++) {
617 let funcao: string = funcoes[idx4];
618 achou = achou || comentario[0] === funcao[0];
619 }
620
621 if (!achou) {
622 objeto.aErros.push(
623 new Erro(
624 parseInt(comentario[1]),
625 parseInt(comentario[1]),
626 traduz('validaAdvpl.CommentNoFunction', objeto.local),
627 Severity.Warning
628 )
629 );
630 }
631 }
632
633 //Validador de includes
634 let oInclude: Include = new Include(objeto.local);
635 oInclude.valida(objeto, conteudoSComentario);
636 //Conta os erros por tipo e totaliza no objeto
637 objeto.hint = 0;
638 objeto.information = 0;
639 objeto.warning = 0;
640 objeto.error = 0;
641 for (var idx = 0; idx < objeto.aErros.length; idx++) {
642 let erro: any = objeto.aErros[idx];
643 if (erro.severity === Severity.Hint) {
644 objeto.hint++;
645 }
646 if (erro.severity === Severity.Information) {
647 objeto.information++;
648 }
649 if (erro.severity === Severity.Warning) {
650 objeto.warning++;
651 }
652 if (erro.severity === Severity.Error) {
653 objeto.error++;
654 }
655 }
656 if (
657 objeto.error + objeto.hint + objeto.warning + objeto.information >
658 0 &&
659 this.log
660 ) {
661 if (objeto.error > 0) {
662 console.log(`\t\t${objeto.error} Errors .`);
663 }
664 if (objeto.warning > 0) {
665 console.log(`\t\t${objeto.warning} Warnings .`);
666 }
667 if (objeto.information > 0) {
668 console.log(`\t\t${objeto.information} Informations .`);
669 }
670 if (objeto.hint > 0) {
671 console.log(`\t\t${objeto.hint} Hints .`);
672 }
673 }
674 resolve(objeto);
675 } catch (e) {
676 reject(e);
677 }
678 });
679 }
680}
681
682function traduz(key, local) {
683 let locales: string[] = ['en', 'pt-br'];
684 let i18n = require('i18n');
685 i18n.configure({
686 locales: locales,
687 directory: __dirname + '/locales'
688 });
689 i18n.setLocale(locales.indexOf(local) + 1 ? local : 'en');
690 return i18n.__(key);
691}