UNPKG

@designliquido/delegua

Version:

Linguagem de programação simples e moderna usando português estruturado.

197 lines 7.33 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.QuebradorDeLinha = void 0; /** * Operadores lógicos reconhecidos como pontos de quebra. * Inclui as formas em português geradas pelo FormatadorDelegua. */ const OPERADORES_LOGICOS = [' && ', ' || ', ' e ', ' ou ', ' em ']; /** * Operadores binários multi-caractere reconhecidos como pontos de quebra. */ const OPERADORES_BINARIOS_MULTI = [' == ', ' != ', ' >= ', ' <= ']; /** * Verifica se a posição `pos` está dentro de um literal de texto na linha. * Suporta aspas simples e duplas, respeitando escapes com `\`. */ function estaEmString(linha, pos) { let emString = false; let delimitador = ''; for (let i = 0; i < pos; i++) { const c = linha[i]; if (emString) { if (c === '\\') { i++; // pula o próximo caractere (escape) continue; } if (c === delimitador) { emString = false; delimitador = ''; } } else { if (c === '"' || c === "'") { emString = true; delimitador = c; } } } return emString; } /** * Retorna o prefixo de espaços/tabs no início da linha. */ function obterIndentacao(linha) { const match = linha.match(/^(\s*)/); return match ? match[1] : ''; } /** * Detecta linhas de comentário que devem ser preservadas intactas. */ function ehLinhaComentario(linha) { const semIndentacao = linha.trimStart(); return (semIndentacao.startsWith('//') || semIndentacao.startsWith('#') || semIndentacao.startsWith('/*') || semIndentacao.startsWith('*') || semIndentacao.startsWith('*/')); } /** * Encontra todos os pontos de quebra válidos em uma linha, retornando os índices * onde a próxima linha de continuação deve começar (já avançado o espaço separador). * * Estratégia (em ordem de prioridade aplicada pela posição na linha): * - Após vírgulas fora de strings * - Antes de operadores lógicos fora de strings * - Antes de operadores binários multi-char fora de strings * * Retorna os índices ordenados do menor para o maior. */ function encontrarPontosDeQuebra(linha) { const pontos = new Set(); for (let i = 0; i < linha.length; i++) { if (estaEmString(linha, i)) continue; const c = linha[i]; // Após vírgula: a continuação começa logo depois (pulando o espaço separador) if (c === ',') { let pos = i + 1; if (pos < linha.length && linha[pos] === ' ') pos++; pontos.add(pos); continue; } // Antes de operadores lógicos: quebra logo antes do operador for (const op of OPERADORES_LOGICOS) { if (linha.slice(i).startsWith(op)) { pontos.add(i + 1); // mantém o espaço antes do operador na linha anterior break; } } // Antes de operadores binários multi-char for (const op of OPERADORES_BINARIOS_MULTI) { if (linha.slice(i).startsWith(op)) { pontos.add(i + 1); break; } } } return Array.from(pontos).sort((a, b) => a - b); } /** * Quebra uma única linha em múltiplas, tentando manter cada segmento * dentro de `maximo` caracteres usando uma estratégia gulosa. * * Para cada segmento, encontra o último ponto de quebra que ainda mantém * o conteúdo dentro do limite. Se não houver ponto de quebra viável, * usa o primeiro disponível (evitando truncamento arbitrário de tokens). * * Se a linha não puder ser melhorada (apenas 1 segmento), retorna a original intacta. */ function quebrarLinha(linha, maximo, indentacaoContinuacao) { if (ehLinhaComentario(linha)) return [linha]; if (linha.length <= maximo) return [linha]; const pontos = encontrarPontosDeQuebra(linha); if (pontos.length === 0) return [linha]; const segmentos = []; let inicio = 0; while (inicio < linha.length) { const prefixo = segmentos.length === 0 ? '' : indentacaoContinuacao; const restante = linha.slice(inicio); // O conteúdo restante cabe no limite → termina if ((prefixo + restante.trimEnd()).length <= maximo) { segmentos.push(prefixo + restante.trimEnd()); break; } // Filtra apenas pontos à frente de `inicio` const pontosDisponiveis = pontos.filter((p) => p > inicio); if (pontosDisponiveis.length === 0) { // Nenhum ponto restante; empurra o resto sem quebrar segmentos.push(prefixo + restante.trimEnd()); break; } // Encontra o maior ponto que ainda mantém o segmento dentro do limite let melhorPonto = -1; for (const ponto of pontosDisponiveis) { const conteudo = linha.slice(inicio, ponto).trimEnd(); if ((prefixo + conteudo).length <= maximo) { melhorPonto = ponto; } else { break; // pontos estão ordenados; excedeu o limite → para } } if (melhorPonto === -1) { // Nenhum ponto cabe → usa o primeiro disponível (soft break forçado) melhorPonto = pontosDisponiveis[0]; } const conteudo = linha.slice(inicio, melhorPonto).trimEnd(); segmentos.push(prefixo + conteudo); // Avança `inicio` pulando o espaço separador, se houver inicio = melhorPonto; if (inicio < linha.length && linha[inicio] === ' ') { inicio++; } } // Se não houve melhoria real, preserva a linha original if (segmentos.length <= 1) return [linha]; return segmentos; } /** * Aplica quebra de linha por limite de colunas a um bloco de código já formatado. * * Cada linha que ultrapasse `maximoCaracteres` é dividida em múltiplas linhas * em pontos semanticamente seguros (após vírgulas, antes de operadores lógicos * e binários), respeitando o conteúdo de strings literais. * * Quando não existe ponto de quebra viável, a linha é preservada intacta * ("soft break" — nunca trunca tokens arbitrariamente). */ class QuebradorDeLinha { constructor(maximoCaracteres, tamanhoIndentacao, separadorLinha) { this.maximoCaracteres = maximoCaracteres; this.tamanhoIndentacao = tamanhoIndentacao; this.separadorLinha = separadorLinha; } quebrar(codigo) { const linhas = codigo.split(this.separadorLinha); const resultado = []; for (const linha of linhas) { if (linha.length <= this.maximoCaracteres) { resultado.push(linha); } else { const indentacaoBase = obterIndentacao(linha); const indentacaoContinuacao = indentacaoBase + ' '.repeat(this.tamanhoIndentacao); resultado.push(...quebrarLinha(linha, this.maximoCaracteres, indentacaoContinuacao)); } } return resultado.join(this.separadorLinha); } } exports.QuebradorDeLinha = QuebradorDeLinha; //# sourceMappingURL=quebrador-linha.js.map