Skip to content
This repository was archived by the owner on Sep 20, 2026. It is now read-only.

fix: native/forward multilinha e doc comment após #pragma deprecated - #37

Merged
NullSablex merged 19 commits into
masterfrom
fix/multiline-native-e-doc-apos-pragma
Sep 1, 2026
Merged

NullSablex merged 19 commits into
masterfrom
fix/multiline-native-e-doc-apos-pragma

Conversation

@NullSablex

Copy link
Copy Markdown
Owner

Duas correções de parsing que se somavam justamente nos includes do open.mp.

native/forward com assinatura multilinha eram ignorados

Ao fechar o ), o parser só criava o símbolo se o resto da linha trouxesse {; sem isso, guardava em pending_plain esperando um corpo na linha seguinte. Mas native e forward declaram sem corpo e terminam em ; — a espera nunca terminava e o símbolo se perdia por completo: sem hover, sem autocomplete, sem signature help.

O impacto é grande nos includes do open.mp, onde assinaturas longas quebradas em várias linhas são a norma. Um ApplyActorAnimation do omp_actor, com nove parâmetros em nove linhas, era simplesmente invisível para a engine.

native bool:ApplyActorAnimation(
    actorid,
    const animationLibrary[],
    ...
    time);

Doc comment perdido quando #pragma deprecated ficava no meio

extract_doc caminha para cima a partir da declaração e para na primeira linha que não é comentário — a diretiva cortava o caminho:

/**
 * Bane com motivo.
 */
#pragma deprecated Use BanPlayerFor
native BanEx(playerid, const reason[]);   // doc: None

Regressão da mudança anterior, que trouxe o #pragma: com @DEPRECATED o marcador era um comentário e não interrompia a varredura. Passa a pular a diretiva.

Verificação

202 testes (5 novos cobrindo os dois defeitos), cargo fmt limpo, clippy pedantic sem avisos. Validado contra uma amostra real do omp-stdlib: o símbolo multilinha é reconhecido, com os 9 parâmetros e o doc preservado.

Entra na 1.4.0, que ainda não foi lançada — o changelog dela já registra as duas correções.

Dois defeitos que se somavam justamente nos includes do open.mp, onde
assinaturas longas quebradas em várias linhas são comuns.

**Assinatura multilinha descartada.** Ao fechar o `)`, o parser só criava o
símbolo se o resto da linha trouxesse `{`; sem isso, guardava em
`pending_plain` à espera de um corpo na linha seguinte. `native` e `forward`
declaram sem corpo e terminam em `;`, então a espera nunca terminava e o
símbolo se perdia — sem hover, sem autocomplete, sem signature help. Um
`ApplyActorAnimation` do omp_actor, com nove parâmetros em nove linhas, era
invisível para a engine.

**Doc perdido quando a diretiva estava no meio.** `extract_doc` caminha para
cima a partir da declaração e para na primeira linha que não é comentário —
`#pragma deprecated` entre o bloco e a declaração cortava o caminho. Passa a
pular a diretiva. Regressão da mudança anterior, que trouxe o `#pragma`: com
`@DEPRECATED` o marcador era um comentário e não interrompia a varredura.
`pragma_deprecated_message` monta uma `String` e era chamada em toda linha do
arquivo — tanto no laço principal quanto na varredura para cima do
`extract_doc` — só para responder se a linha é a diretiva.

Separa o reconhecimento (`is_pragma_deprecated`, sem alocação) da extração da
mensagem, e faz o `extract_doc` acumular fatias em vez de `String` por linha.

Num arquivo com 600 nativas documentadas, ~2,5 ms → ~2,1 ms; com a diretiva e
sem doc, ~2,1 ms → ~1,96 ms. Medido com 20 repetições após aquecimento —
medições únicas variam demais para servir de base.
O compilador rejeita uma diretiva que não conheça (erro 207), mas só na
compilação. O diagnóstico antecipa isso e oferece a correção.

Dois casos, ambos com quick fix:

- **nome não reconhecido** — comparado com a lista de `sc2.c`; havendo uma
  diretiva próxima (distância de edição pequena, mais estrita para nomes
  curtos), ela é sugerida;
- **mensagem de `deprecated` entre aspas** — a diretiva toma o resto da linha
  como texto livre (`strdupwithouta` sobre `lptr`, em `sc2.c:1240`), então as
  aspas entrariam na mensagem em vez de delimitá-la. Aspas no meio do texto
  são literais legítimas e não são sinalizadas.

A forma correta, confirmada no compilador, é sem aspas:

    #pragma deprecated Use BanPlayerFor
Estende as correções rápidas aos diagnósticos cuja correção é
determinística:

- `PP0002`/`PP0003` — remove o corpo `{ … }` que o compilador não aceita em
  `native`/`forward`. Por ser erro de sintaxe, é oferecido também em `.inc`,
  ao contrário das remoções por "não utilizado";
- `PP0004` — duas ações: dar um corpo vazio, ou converter `public`/`stock`
  em `forward`, que é a forma de declarar sem corpo;
- `PP0010` — troca por um símbolo conhecido de nome parecido, entre os do
  arquivo e de todos os includes transitivos. É o mesmo universo do
  autocomplete, então a sugestão nunca aponta para algo invisível ao arquivo;
- `PP0011`/`PP0012` — remove a linha da diretiva, arrastando continuações
  com `\` no fim;
- `PP0017` — reindenta a linha pelo formatador, com o estilo configurado no
  workspace (preset e overrides), em vez de uma indentação fixa.

`PP0014`/`PP0015` ficam de fora de propósito: remover uma `native` ou um
`forward` de uma include quebraria quem a consome.

A distância de edição que o PP0019 usava sai de `pragmas.rs` para
`similar.rs`, agora compartilhada com o PP0010, com tolerância proporcional
ao tamanho do nome — um `max` fixo trata mal os extremos.
O `sort_text` punha os símbolos globais em `0_` e as variáveis locais em
`1_`: dentro de uma função, os parâmetros e as locais apareciam **abaixo** de
milhares de nativas dos includes — o inverso do útil.

A ordem passa a ser explícita, num `Rank`: locais e parâmetros, símbolos do
próprio arquivo, símbolos dos includes, palavras-chave, e por último os
marcados com `#pragma deprecated`. Um descontinuado continua na lista, mas
nunca à frente de uma alternativa viva. Dentro do grupo, a ordem é alfabética
sem diferenciar caixa — sem isso "Zebra" viria antes de "alfa".

A lista também era enviada inteira a cada tecla. Passa a ser cortada em 1000
itens com `isIncomplete`, o mecanismo do LSP para listas grandes: o editor
pede de novo conforme o prefixo cresce. A ordenação acontece antes do corte,
então o que se descarta são os itens mais distantes do cursor.
**Doc de um símbolo no hover de outro.** Ao ver uma linha terminada em `*/`,
`extract_doc` empurrava a linha e procurava o `/*` de abertura a partir da
**anterior** — mas um bloco de uma linha (`/** … */`) já está completo. A
busca então atravessava o código acima até casar com o `/**` de outro
comentário, e o hover de um `#define` saía com a documentação da função
anterior mais o código do meio.

Um bloco de uma linha passa a encerrar ali mesmo, e um `*/` sem abertura
devolve `None` em vez de arrastar o arquivo até o topo.

**Hover desalinhado.** O aviso de depreciado era um blockquote (`> …`): o
editor recuava o bloco e lia o `---` seguinte como continuação, desalinhando
a documentação. Vira texto normal, e passa a mostrar a mensagem do
`#pragma deprecated` — que até então só aparecia no aviso de uso.
Um cabeçalho de seção acima de uma função era tratado como documentação
dela, e o hover mostrava a régua mais o texto da seção:

    // ---------------------------------------------------
    // 9. Símbolos vindos dos includes
    // ---------------------------------------------------

    stock TesteIncludes(playerid)   // hover exibia o bloco acima

Uma linha de comentário composta só de ornamento (`-`, `=`, `*`, `_`, `#`,
`~`) encerra a varredura: além de não documentar nada, o que está acima dela
pertence a outra seção do arquivo.

Comentários `//` com texto seguem valendo como documentação — é convenção
legítima em Pawn, e só a régua sai.
Explicavam o mecanismo onde bastava dizer o que fazer.

  "`#pragma x` não é reconhecido pelo compilador"  → "`#pragma x` não existe"
  "… — você quis dizer `deprecated`?"              → "… — use `deprecated`"
  "toma o resto da linha como texto — as aspas
   entrariam na mensagem"                          → "A mensagem não leva
                                                      aspas — elas entrariam
                                                      no texto"
  "\"x\" está marcado como depreciado"             → "\"x\" está depreciado"

Nos títulos das ações, o alvo já está no contexto do cursor: "Remover o corpo
da declaração" → "Remover o corpo"; "Corrigir a indentação desta linha" →
"Corrigir a indentação"; "Renomear \"a\" para \"b\"" → "Renomear para \"b\"".

Nos cinco idiomas.
A correção anterior só reconhecia a régua pura (`// --------`). O cabeçalho
que traz texto entre ornamentos continuava virando documentação:

    // --- PP0004: `stock` sem corpo ---------------------
    stock FuncaoSemCorpo(playerid);   // hover exibia a linha acima

Passa a valer também quando a linha abre e fecha com uma corrida de três ou
mais ornamentos. O limite de três é o que separa cabeçalho de prosa: um
hífen isolado é comum em texto (`// vale -1 quando ausente`) e segue sendo
documentação.
A varredura subia o arquivo acumulando linhas de comentário, e trazia para o
hover réguas, cabeçalhos de seção e o texto de outras funções. As correções
anteriores atacavam cada sintoma — reconhecer régua pura, depois cabeçalho
com texto entre ornamentos — quando o problema era a varredura não ter fim.

Passa a valer a regra do Javadoc/PHPDoc: **um bloco só, o imediatamente
acima da declaração**. Um `/* … */` colado nela, ou uma sequência contígua
de linhas `//`. Linha em branco ou código entre os dois separa.

Com isso saem `is_comment_rule` e a heurística de ornamentos: não é mais
preciso adivinhar o que é separador, porque nada além do bloco vizinho é
lido. O `#pragma deprecated` entre o comentário e a declaração continua
sendo pulado — é a diretiva que marca aquele símbolo.
As categorias aceitavam só os cinco estilos embutidos. Quem tem convenção
própria (prefixo de global, notação húngara) não tinha como descrevê-la.

Um item da lista no formato /padrão/ passa a ser lido como regex. Convive
com os embutidos pela regra que já existia: o nome passa se casar com
QUALQUER critério da categoria.

O regex é âncorado como ^(?:...)$ — o usuário descreve o nome inteiro, e o
agrupamento evita que uma alternância no topo ancore só os extremos. Um
padrão inválido é ignorado em vez de derrubar a análise, e o '_' inicial
não é removido: quem escreve o padrão decide se o aceita.

O critério vira o tipo Rule em vez de uma variante de Case, que é Copy e
alimenta o gerador de renomeação: de um regex dá para saber se o nome
passa, não como reescrevê-lo. Por isso Rule::builtin devolve None para
regex e o quick fix não oferece sugestão inválida.

WrongStyle passa a carregar String: o rótulo de um regex só existe em
runtime.
Cobre o caminho completo, do item de configuração ao diagnóstico: o padrão
aceitando a convenção própria, sinalizando o que não casa, convivendo com
um estilo embutido na mesma categoria, e um padrão inválido sendo ignorado
sem levar junto os critérios válidos ao lado.
A crate regex garante tempo linear — não há backtracking, então (a+)+ com
entrada que não casa termina de imediato. O teste fixa essa propriedade,
que é o motivo de um padrão do usuário poder ser aceito sem limite de
forma.
O changelog da 1.4.0 não cobria o padrão por expressão regular, que entrou
depois de ele ter sido fechado.

No guia do assistente, o exemplo de configuração mostrava os estilos como
texto e citava uma categoria 'enums' que não existe — são listas, e as
categorias são constants e macros. A seção de estilo passa a descrever o
tipo Rule: por que o regex não é uma variante de Case (Case é Copy e
alimenta o suggest, que gera nomes), a âncora implícita, o tratamento do
'_' inicial e do padrão inválido, e a mudança de WrongStyle para
Vec<String>.

Contagem de diagnósticos corrigida: o CLAUDE.md dizia PP0013 e o
CONTRIBUTING dizia PP0017; são 19 desde o PP0019.
- Afirmava que a extensão 'recua para o modo TypeScript como fallback' se o
  binário faltar. Esse fallback não existe no código da extensão.
- Dizia 17 códigos de diagnóstico; são 19, e 11 têm correção automática.
- Faltavam nas capacidades o assistente de nomenclatura e as code actions.

A abertura passa a dizer por que o motor é um processo separado — analisar
o gamemode com todos os includes sem travar a digitação — e que a análise é
real, não realce por expressão regular.
Contei só os códigos citados em server.rs; os de remoção de código não
usado (PP0006 e PP0016) ficam em intellisense/quickfix.rs e tinham ficado
de fora. O docs/diagnostics.md já listava os 13 corretamente.
@NullSablex
NullSablex merged commit f4b4301 into master Sep 1, 2026
5 checks passed
@NullSablex
NullSablex deleted the fix/multiline-native-e-doc-apos-pragma branch September 1, 2026 15:16
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant