Como usar o sonarqube no neovim
Este é um artigo que eu queria ter lido muitos anos atrás, mas ninguém nunca escreveu nada parecido. Eu levei diversos anos para configurar o SonarQube no Neovim e agora que tenho ele funcionando, nada mais justo do que ensinar o passo a passo e todos os detalhes que precisam ser ditos.
Só para constar, o SonarQube é um programa mal documentado e o que salva ele é o fato de ele ser de código aberto. Eu mesmo já tive que ler o código-fonte para descobrir como ele funciona sendo que tudo isso poderia estar documentado, estou falando de modo conectado, configurações iniciais e extensões de cliente.
O modo conectado foi a última coisa que consegui configurar antes de sair do Soluções Digitais e agora nada mais justo do que passar esse conhecimento oculto adiante.
Instalação
Antes de tudo é preciso instalar o SonarQube no seu sistema operacional, você
pode instalar como um programa local em /home/user/.local ou como um programa
global em /usr/local/bin, a preferência é sua.
Clone o projeto sonarqube-makefile para a sua máquina e faça a instalação do programa.
git clone https://codeberg.org/cizordj/sonarqube-makefile.git
cd sonarqube-makefile
make
Para instalar o SonarQube como um programa local rode o comando:
env PREFIX="$HOME/.local" DESTDIR="$HOME/.local/share/sonarqube" make install
Para desinstalar rode o comando:
env PREFIX="$HOME/.local" DESTDIR="$HOME/.local/share/sonarqube" make uninstall
Para instalar o SonarQube como um programa global rode o comando:
sudo make install
Para desinstalar rode o comando:
sudo make uninstall
Configuração
É aqui que vem o pulo do gato! O SonarQube é um programa que exige MUITAS extensões por parte do cliente e é por isso que ele não funciona fora da caixa. Essas extensões você mesmo pode implementar ou você pode usar plugins prontos que já fazem isso por você, caso você queira usar algum plugin eu recomendo o Sonarlint.nvim que é muito bem implementado. Este artigo, no entanto, não ensina a trabalhar com o plugin supracitado, por isso usaremos uma configuração mais manual.
Instale o plugin
nvim-lspconfig e coloque o
seguinte no seu arquivo init.lua.
local function get_snake_case_current_folder()
local cwd = vim.fn.getcwd()
local folder_name = cwd:match("^.+/(.+)$") or cwd
local snake_case = folder_name:gsub("%s+", "_"):gsub("[^%w_]", ""):lower()
return snake_case
end
local lspconfig = require('lspconfig')
local configs = require('lspconfig.configs')
--- @class lspconfig.Config
configs.sonarqube = {
default_config = {
cmd = { "sonarqube-lsp" },
autostart = true,
handlers = {
["window/logMessage"] = function(_, params)
vim.print(params.message)
end,
["sonarlint/readyForTests"] = function()
vim.notify("Sonarqube has started", vim.log.levels.INFO)
end,
["sonarlint/reportConnectionCheckResult"] = function(_, params)
local connectionId = params.connectionId
local reason = params.reason
local success = params.success
if success then
vim.notify(
string.format(
"Connected successfully to %s!",
connectionId
),
vim.log.levels.INFO
)
else
vim.notify(
string.format(
"Connection to %s failed! Reason: %s",
connectionId,
reason
),
vim.log.levels.ERROR
)
end
end,
["sonarlint/isOpenInEditor"] = function(_, params)
local file_path = vim.uri_to_fname(params[1])
for _, buf in ipairs(vim.api.nvim_list_bufs()) do
if vim.api.nvim_buf_is_loaded(buf) and vim.api.nvim_buf_get_name(buf) == file_path then
return true
end
end
return false
end,
["sonarlint/filterOutExcludedFiles"] = function(_, params)
return params
end,
["sonarlint/getTokenForServer"] = function(_, params)
local serverUrl = params[1]
-- Return the token based on the serverUrl
return serverUrl
end,
["sonarlint/listFilesInFolder"] = function(_, params)
local folderUri = vim.uri_to_fname(params.folderUri)
local files = {
foundFiles = {}
}
local uv = vim.loop
-- Open the folder
local handle = uv.fs_scandir(folderUri)
if not handle then
vim.notify("Cannot open folder: " .. folderUri, vim.log.levels.ERROR)
return files
end
while true do
local name, type = uv.fs_scandir_next(handle)
if not name then
break
end
if type == "file" then
table.insert(files.foundFiles,
{
fileName = name,
filePath = folderUri
}
)
end
end
return files
end
},
detached = false,
filetypes = {
'java',
'javascript',
'javascriptreact',
'typescript',
'typescriptreact',
'css',
'html',
'python',
'cpp',
'c',
'php',
'dockerfile'
},
root_dir = function(fname)
if vim.version()['minor'] == 9
then
return lspconfig.util.find_git_ancestor(fname)
else
return vim.fs.root(0, '.git')
end
end,
settings = {
sonarlint = {
-- connectedMode = {
-- project = {
-- projectKey = "",
-- connectionId = ""
-- },
-- connections = {
-- sonarqube = {
-- {
-- serverUrl = "",
-- token = "",
-- connectionId = ""
-- },
-- }
-- }
-- },
showAnalyzerLogs = false,
showVerboseLogs = false,
disableTelemetry = false,
["files.exclude"] = {
["**/.git"] = true,
["**/node_modules"] = true,
},
rules = vim.empty_dict()
}
},
init_options = {
productKey = "neovim",
productName = "Neovim",
productVersion = tostring(vim.version()),
workspaceName = get_snake_case_current_folder(),
firstSecretDetected = false,
showVerboseLogs = false,
platform = vim.loop.os_uname().sysname,
architecture = vim.loop.os_uname().machine,
},
single_file_support = false
},
docs = {
description = 'An advanced linter in your IDE for Clean Code',
}
}
lspconfig.sonarqube.setup({
on_attach = function(client, bufnr)
common.setKeymaps(bufnr, client.name)
end,
flags = {
debounce_text_changes = 3000
},
})
É uma configuração bem grande, não? Isso acontece porque o SonarQube exige muita implementação no lado do cliente e por isso muita coisa precisa ser programada. A primeira coisa que o servidor de linguagem exige é a configuração de inicialização (init_options), nela você passa algumas informações sobre a IDE que você está usando e o nome do espaço de trabalho (workspaceName), o SonarQube usa isso para criar cache para o projeto e alimentar sua telemetria.
Por isso é importante que bastante gente habilite a telemetria, pois assim a SonarSource pode oferecer melhor suporte ao Neovim.
A segunda parte mais importante reside na chave “handlers”. Perceba que o Neovim precisa lidar com muitos eventos específicos como, por exemplo, sonarlint/isOpenInEditor. Se você não implementar essas respostas, o SonarQube simplesmente não funciona. Há inúmeras requisições que o Sonar pode fazer para o seu editor, porém nem todas estão documentadas e você só consegue descobrir elas ao rodar em modo verboso enquanto lê os logs.
Modo conectado
Para ativar o modo conectado do SonarQube você precisa preencher o seguinte dentro da chave settings.
local settings = {
sonarlint = {
connectedMode = {
project = {
projectKey = "",
connectionId = ""
},
connections = {
sonarqube = {
{
serverUrl = "",
token = "",
connectionId = ""
},
}
}
}
}
}
As informações como projectKey, serverUrl e token você obtém direto do site onde está hospedado a instância do SonarQube Cloud. Já o connectionId é um valor único que serve para identificar essa conexão e é você quem decide o que vai nele.
Lembrando também que você precisa implementar uma resposta para o evento sonarlint/getTokenForServer.
local handlers = {
["sonarlint/getTokenForServer"] = function(_, params)
-- Retorne o seu token aqui
return "1234"
end
}
Apesar do token já estar na configuração estática, você ainda precisa retorná-lo durante a execução do SonarQube. O token da configuração estática não possui efeito, mas é exigido pelo Sonar.
Conclusão
Essa configuração não é perfeita e abre espaço para muitas melhorias, no entanto, você já consegue trabalhar no dia-a-dia com ela e entregar suas demandas. É possível criar um ecossistema inteiro ao redor do SonarQube, como, por exemplo, um sistema de armazenamento de credenciais, um assistente para configurar o modo conectado e um instalador automático. No entanto, isso demanda muito tempo e disposição; por isso, existem projetos como o do Marc que procura implementar todos os eventos possíveis e deixar a configuração do SonarQube mais suave.