Add basic Lua ALE functions and test coverage

Ensure that basic ALE functions `ale.var`, `ale.escape`, and `ale.env`
are available in Lua. Cover all Lua code so far with busted tests,
fixing bugs where ALE variables can be set with Boolean values instead
of numbers. Document all functionality so far.
This commit is contained in:
w0rp
2025-03-21 23:37:35 +00:00
parent 06f4b6fe25
commit bd591d47f2
16 changed files with 944 additions and 105 deletions

View File

@@ -30,6 +30,7 @@ jobs:
- '--vim-90-only' - '--vim-90-only'
- '--neovim-07-only' - '--neovim-07-only'
- '--neovim-08-only' - '--neovim-08-only'
- '--lua-only'
- '--linters-only' - '--linters-only'
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

19
.luarc.json Normal file
View File

@@ -0,0 +1,19 @@
{
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
"diagnostics.globals": [
"vim"
],
"workspace.ignoreDir": [
"test"
],
"workspace.library": [
"/usr/share/nvim/runtime/lua"
],
"runtime.pathStrict": true,
"runtime.path": [
"lua/?.lua",
"lua/?/init.lua"
],
"runtime.version": "LuaJIT",
"hint.enable": false
}

View File

@@ -2,12 +2,10 @@ ARG TESTBED_VIM_VERSION=24
FROM testbed/vim:${TESTBED_VIM_VERSION} FROM testbed/vim:${TESTBED_VIM_VERSION}
RUN install_vim -tag v8.0.0027 -build \
-tag v9.0.0297 -build \
-tag neovim:v0.7.0 -build \
-tag neovim:v0.8.0 -build
ENV PACKAGES="\ ENV PACKAGES="\
lua5.1 \
lua5.1-dev \
lua5.1-busted \
bash \ bash \
git \ git \
python2 \ python2 \
@@ -19,6 +17,11 @@ ENV PACKAGES="\
RUN apk --update add $PACKAGES && \ RUN apk --update add $PACKAGES && \
rm -rf /var/cache/apk/* /tmp/* /var/tmp/* rm -rf /var/cache/apk/* /tmp/* /var/tmp/*
RUN install_vim -tag v8.0.0027 -build \
-tag v9.0.0297 -build \
-tag neovim:v0.7.0 -build \
-tag neovim:v0.8.0 -build
RUN pip install vim-vint==0.3.21 RUN pip install vim-vint==0.3.21
RUN git clone https://github.com/junegunn/vader.vim vader && \ RUN git clone https://github.com/junegunn/vader.vim vader && \

View File

@@ -259,7 +259,7 @@ function! ale#engine#SendResultsToNeovimDiagnostics(buffer, loclist) abort
" Keep the Lua surface area really small in the VimL part of ALE, " Keep the Lua surface area really small in the VimL part of ALE,
" and just require the diagnostics.lua module on demand. " and just require the diagnostics.lua module on demand.
let l:SendDiagnostics = luaeval('require("ale.diagnostics").sendAleResultsToDiagnostics') let l:SendDiagnostics = luaeval('require("ale.diagnostics").send')
call l:SendDiagnostics(a:buffer, a:loclist) call l:SendDiagnostics(a:buffer, a:loclist)
endfunction endfunction

View File

@@ -4453,6 +4453,7 @@ Vim autocmd names `ale#Foo` are available in the Vim context, and functions
documented with dot names `ale.foo` are available in Lua scripts. documented with dot names `ale.foo` are available in Lua scripts.
ale.env(variable_name, value) *ale.env()*
ale#Env(variable_name, value) *ale#Env()* ale#Env(variable_name, value) *ale#Env()*
Given a variable name and a string value, produce a string for including in Given a variable name and a string value, produce a string for including in
@@ -4464,6 +4465,18 @@ ale#Env(variable_name, value) *ale#Env()*
'set VAR="some value" && command' # On Windows 'set VAR="some value" && command' # On Windows
ale.escape(str) *ale.escape()*
ale#Escape(str) *ale#Escape()*
Given a string, escape that string so it is ready for shell execution.
If the shell is detected to be `cmd.exe`, ALE will apply its own escaping
that tries to avoid escaping strings unless absolutely necessary to avoid
issues with Windows programs that do not properly handle quoted arguments.
In all other cases, ALE will call |shellescape|.
ale#GetFilenameMappings(buffer, name) *ale#GetFilenameMappings()* ale#GetFilenameMappings(buffer, name) *ale#GetFilenameMappings()*
Given a `buffer` and the `name` of either a linter for fixer, return a Given a `buffer` and the `name` of either a linter for fixer, return a
@@ -4509,7 +4522,7 @@ ale#Queue(delay, [linting_flag, buffer_number]) *ale#Queue()*
is broken, or when developing ALE itself. is broken, or when developing ALE itself.
ale.setup(config) *ale.setup* ale.setup(config) *ale.setup()*
Configure ALE global settings, which are documented in |ale-options|. For Configure ALE global settings, which are documented in |ale-options|. For
example: > example: >
@@ -4524,7 +4537,7 @@ ale.setup(config) *ale.setup*
ALE is being configured in less ambiguous if you like. ALE is being configured in less ambiguous if you like.
ale.setup.buffer(config) *ale.setup.buffer* ale.setup.buffer(config) *ale.setup.buffer()*
Configure ALE buffer-local settings, which are documented in |ale-options|. Configure ALE buffer-local settings, which are documented in |ale-options|.
For example: > For example: >
@@ -4534,6 +4547,18 @@ ale.setup.buffer(config) *ale.setup.buffer*
}) })
< <
ale.var(buffer, variable_name) *ale.var()*
ale#Var(buffer, variable_name) *ale#Var()*
Given a buffer number and an ALE variable name return the value of that
if defined in the buffer, and if not defined in the buffer return the
global value. The `ale_` prefix will be added to the Vim variable name.
The `ale#Var` Vim function will return errors if the variable is not defined
in either the buffer or globally. The `ale.var` Lua function will return
`nil` if the variable is not defined in either the buffer or globally.
ale#command#CreateDirectory(buffer) *ale#command#CreateDirectory()* ale#command#CreateDirectory(buffer) *ale#command#CreateDirectory()*
Create a new temporary directory with a unique name, and manage that Create a new temporary directory with a unique name, and manage that

View File

@@ -1,96 +1,95 @@
local ale = require("ale")
local module = {} local module = {}
local ale_type_to_diagnostic_severity = { local diagnostic_severity_map = {
E = vim.diagnostic.severity.ERROR, E = vim.diagnostic.severity.ERROR,
W = vim.diagnostic.severity.WARN, W = vim.diagnostic.severity.WARN,
I = vim.diagnostic.severity.INFO I = vim.diagnostic.severity.INFO
} }
-- Equivalent to ale#Var, only we can't error on missing global keys. -- A map of all possible values that we can consider virtualtext enabled for
module.aleVar = function(buffer, key) -- from ALE's setting.
key = "ale_" .. key local virtualtext_enabled_set = {
local exists, value = pcall(vim.api.nvim_buf_get_var, buffer, key) ["all"] = true,
["2"] = true,
if exists then
return value
end
return vim.g[key]
end
module.sendAleResultsToDiagnostics = function(buffer, loclist)
local diagnostics = {}
-- Convert all the ALE loclist items to the shape that Neovim's diagnostic
-- API is expecting.
for _, location in ipairs(loclist) do
if location.bufnr == buffer then
table.insert(
diagnostics,
-- All line numbers from ALE are 1-indexed, but all line numbers
-- in the diagnostics API are 0-indexed, so we have to subtract 1
-- to make this work.
{
lnum = location.lnum - 1,
-- Ending line number, or if we don't have one, just make it the same
-- as the starting line number
end_lnum = (location.end_lnum or location.lnum) - 1,
-- Which column does the error start on?
col = math.max((location.col or 1) - 1, 0),
-- end_col does *not* appear to need 1 subtracted, so we don't.
end_col = location.end_col,
-- Which severity: error, warning, or info?
severity = ale_type_to_diagnostic_severity[location.type] or "E",
-- An error code
code = location.code,
-- The error message
message = location.text,
-- e.g. "rubocop"
source = location.linter_name,
}
)
end
end
local virtualtext_enabled_set = {
['all'] = true,
['2'] = true,
[2] = true, [2] = true,
['current'] = true, ["current"] = true,
['1'] = true, ["1"] = true,
[1] = true, [1] = true,
} [true] = true,
}
local set_signs = module.aleVar(buffer, 'set_signs') ---Send diagnostics to the Neovim diagnostics API
local sign_priority = module.aleVar(buffer, 'sign_priority') ---@param buffer number The buffer number to retreive the variable for.
local signs ---@param loclist table The loclist array to report as diagnostics.
---@return nil
module.send = function(buffer, loclist)
local diagnostics = {}
if set_signs == 1 and sign_priority then -- Convert all the ALE loclist items to the shape that Neovim's diagnostic
-- If signs are enabled, set the priority for them. -- API is expecting.
local local_cfg = { priority = sign_priority } for _, location in ipairs(loclist) do
local global_cfg = vim.diagnostic.config().signs if location.bufnr == buffer then
table.insert(
if type(global_cfg) == 'boolean' then diagnostics,
signs = local_cfg -- All line numbers from ALE are 1-indexed, but all line
elseif type(global_cfg) == 'table' then -- numbers in the diagnostics API are 0-indexed, so we have to
signs = vim.tbl_extend('force', global_cfg, local_cfg) -- subtract 1 to make this work.
else {
signs = function(...) lnum = location.lnum - 1,
local calculated = global_cfg(...) -- Ending line number, or if we don't have one, just make
return vim.tbl_extend('force', calculated, local_cfg) -- it the same as the starting line number
end end_lnum = (location.end_lnum or location.lnum) - 1,
-- Which column does the error start on?
col = math.max((location.col or 1) - 1, 0),
-- end_col does not appear to need 1 subtracted.
end_col = location.end_col,
-- Which severity: error, warning, or info?
severity = diagnostic_severity_map[location.type] or "E",
-- An error code
code = location.code,
-- The error message
message = location.text,
-- e.g. "rubocop"
source = location.linter_name,
}
)
end
end end
end
vim.diagnostic.set( local set_signs = ale.var(buffer, "set_signs")
vim.api.nvim_create_namespace('ale'), local sign_priority = ale.var(buffer, "sign_priority")
buffer, local signs
diagnostics,
{ if (set_signs == 1 or set_signs == true) and sign_priority then
virtual_text = virtualtext_enabled_set[vim.g.ale_virtualtext_cursor] ~= nil, -- If signs are enabled, set the priority for them.
signs = signs, local local_cfg = { priority = sign_priority }
} local global_cfg = vim.diagnostic.config().signs
)
if type(global_cfg) == "boolean" then
signs = local_cfg
elseif type(global_cfg) == "table" then
signs = vim.tbl_extend("force", global_cfg, local_cfg)
else
-- If a global function is defined, then define a function
-- that calls that function when Neovim calls our function.
signs = function(...)
return vim.tbl_extend("force", global_cfg(...), local_cfg)
end
end
end
vim.diagnostic.set(
vim.api.nvim_create_namespace("ale"),
buffer,
diagnostics,
{
virtual_text =
virtualtext_enabled_set[vim.g.ale_virtualtext_cursor] ~= nil,
signs = signs,
}
)
end end
return module return module

View File

@@ -1,19 +1,19 @@
local ale = {} local ale = {}
local global_settings = setmetatable({}, { local global_settings = setmetatable({}, {
__index = function (_, key) __index = function(_, key)
return vim.g['ale_' .. key] return vim.g['ale_' .. key]
end, end,
__newindex = function (_, key, value) __newindex = function(_, key, value)
vim.g['ale_' .. key] = value vim.g['ale_' .. key] = value
end end
}) })
local buffer_settings = setmetatable({}, { local buffer_settings = setmetatable({}, {
__index = function (_, key) __index = function(_, key)
return vim.b['ale_' .. key] return vim.b['ale_' .. key]
end, end,
__newindex = function (_, key, value) __newindex = function(_, key, value)
vim.b['ale_' .. key] = value vim.b['ale_' .. key] = value
end end
}) })
@@ -30,17 +30,79 @@ ale.set_buffer = function(c)
end end
end end
---(when called) Set global ALE settings, just like ale.setup.global.
---@class ALESetup
---@field global fun(c: table): nil -- Set global ALE settings.
---@field buffer fun(c: table): nil -- Set buffer-local ALE settings.
---@overload fun(c: table): nil
---@type ALESetup
ale.setup = setmetatable({ ale.setup = setmetatable({
global = function(c) ---Set global ALE settings.
ale.set_global(c) ---@param c table The table of ALE settings to set.
end, ---@return nil
buffer = function(c) global = function(c)
ale.set_buffer(c) ale.set_global(c)
end, end,
---Set buffer-local ALE settings.
---@param c table The table of ALE settings to set.
---@return nil
buffer = function(c)
ale.set_buffer(c)
end,
}, { }, {
__call = function(self, c) __call = function(self, c)
self.global(c) self.global(c)
end, end,
}) })
---Get an ALE variable for a buffer (first) or globally (second)
---@param buffer number The buffer number to retreive the variable for.
---@param variable_name string The variable to retrieve.
---@return any value The value for the ALE variable
ale.var = function(buffer, variable_name)
variable_name = "ale_" .. variable_name
local exists, value = pcall(vim.api.nvim_buf_get_var, buffer, variable_name)
if exists then
return value
end
return vim.g[variable_name]
end
---Escape a string for use in a shell command
---@param str string The string to escape.
---@return string escaped The escaped string.
ale.escape = function(str)
local shell = vim.fn.fnamemodify(vim.o.shell, ":t")
if shell:lower() == "cmd.exe" then
local step1
if str:find(" ") then
step1 = '"' .. str:gsub('"', '""') .. '"'
else
step1 = str:gsub("([&|<>^])", "^%1")
end
local percent_subbed = step1:gsub("%%", "%%%%")
return percent_subbed
end
return vim.fn.shellescape(str)
end
---Create a prefix for a shell command for adding environment variables.
---@param variable_name string The environment variable name.
---@param value string The value to set for the environment variable.
---@return string prefix The shell code for prefixing a command.
ale.env = function(variable_name, value)
if vim.fn.has("win32") then
return "set " .. ale.escape(variable_name .. "=" .. value) .. " && "
end
return variable_name .. "=" .. ale.escape(value) .. " "
end
return ale return ale

View File

@@ -2,7 +2,9 @@ local module = {}
module.start = function(config) module.start = function(config)
-- Neovim's luaeval sometimes adds a Boolean key to table we need to remove. -- Neovim's luaeval sometimes adds a Boolean key to table we need to remove.
if config.init_options[true] ~= nil then if type(config.init_options) == "table"
and config.init_options[true] ~= nil
then
config.init_options[true] = nil config.init_options[true] = nil
end end

View File

@@ -29,6 +29,7 @@ run_neovim_07_tests=1
run_neovim_08_tests=1 run_neovim_08_tests=1
run_vim_80_tests=1 run_vim_80_tests=1
run_vim_90_tests=1 run_vim_90_tests=1
run_lua_tests=1
run_linters=1 run_linters=1
while [ $# -ne 0 ]; do while [ $# -ne 0 ]; do
@@ -46,12 +47,14 @@ while [ $# -ne 0 ]; do
run_vim_90_tests=0 run_vim_90_tests=0
run_neovim_07_tests=0 run_neovim_07_tests=0
run_neovim_08_tests=0 run_neovim_08_tests=0
run_lua_tests=0
run_linters=0 run_linters=0
shift shift
;; ;;
--neovim-only) --neovim-only)
run_vim_80_tests=0 run_vim_80_tests=0
run_vim_90_tests=0 run_vim_90_tests=0
run_lua_tests=0
run_linters=0 run_linters=0
shift shift
;; ;;
@@ -59,6 +62,7 @@ while [ $# -ne 0 ]; do
run_neovim_08_tests=0 run_neovim_08_tests=0
run_vim_80_tests=0 run_vim_80_tests=0
run_vim_90_tests=0 run_vim_90_tests=0
run_lua_tests=0
run_linters=0 run_linters=0
shift shift
;; ;;
@@ -66,12 +70,14 @@ while [ $# -ne 0 ]; do
run_neovim_07_tests=0 run_neovim_07_tests=0
run_vim_80_tests=0 run_vim_80_tests=0
run_vim_90_tests=0 run_vim_90_tests=0
run_lua_tests=0
run_linters=0 run_linters=0
shift shift
;; ;;
--vim-only) --vim-only)
run_neovim_07_tests=0 run_neovim_07_tests=0
run_neovim_08_tests=0 run_neovim_08_tests=0
run_lua_tests=0
run_linters=0 run_linters=0
shift shift
;; ;;
@@ -79,6 +85,7 @@ while [ $# -ne 0 ]; do
run_neovim_07_tests=0 run_neovim_07_tests=0
run_neovim_08_tests=0 run_neovim_08_tests=0
run_vim_90_tests=0 run_vim_90_tests=0
run_lua_tests=0
run_linters=0 run_linters=0
shift shift
;; ;;
@@ -86,6 +93,7 @@ while [ $# -ne 0 ]; do
run_neovim_07_tests=0 run_neovim_07_tests=0
run_neovim_08_tests=0 run_neovim_08_tests=0
run_vim_80_tests=0 run_vim_80_tests=0
run_lua_tests=0
run_linters=0 run_linters=0
shift shift
;; ;;
@@ -94,6 +102,15 @@ while [ $# -ne 0 ]; do
run_vim_90_tests=0 run_vim_90_tests=0
run_neovim_07_tests=0 run_neovim_07_tests=0
run_neovim_08_tests=0 run_neovim_08_tests=0
run_lua_tests=0
shift
;;
--lua-only)
run_vim_80_tests=0
run_vim_90_tests=0
run_neovim_07_tests=0
run_neovim_08_tests=0
run_linters=0
shift shift
;; ;;
--fast) --fast)
@@ -119,6 +136,7 @@ while [ $# -ne 0 ]; do
echo ' --vim-only Run tests only for Vim' echo ' --vim-only Run tests only for Vim'
echo ' --vim-80-only Run tests only for Vim 8.2' echo ' --vim-80-only Run tests only for Vim 8.2'
echo ' --vim-90-only Run tests only for Vim 9.0' echo ' --vim-90-only Run tests only for Vim 9.0'
echo ' --lua-only Run only Lua tests'
echo ' --linters-only Run only Vint and custom checks' echo ' --linters-only Run only Vint and custom checks'
echo ' --fast Run only the fastest Vim and custom checks' echo ' --fast Run only the fastest Vim and custom checks'
echo ' --help Show this help text' echo ' --help Show this help text'
@@ -147,6 +165,7 @@ if [ $# -ne 0 ]; then
# Don't run other tools when targeting tests. # Don't run other tools when targeting tests.
run_linters=0 run_linters=0
run_lua_tests=0
fi fi
# Delete .swp files in the test directory, which cause Vim 8 to hang. # Delete .swp files in the test directory, which cause Vim 8 to hang.
@@ -250,6 +269,13 @@ for vim in $("$DOCKER" run --rm "$DOCKER_RUN_IMAGE" ls /vim-build/bin | grep '^n
fi fi
done done
if ((run_lua_tests)); then
echo "Starting Lua tests..."
file_number=$((file_number+1))
test/script/run-lua-tests $quiet_flag > "$output_dir/$file_number" 2>&1 &
pid_list="$pid_list $!"
fi
if ((run_linters)); then if ((run_linters)); then
echo "Starting Vint..." echo "Starting Vint..."
file_number=$((file_number+1)) file_number=$((file_number+1))

View File

@@ -11,9 +11,30 @@
"pending", "pending",
"assert" "assert"
], ],
"workspace.checkThirdParty": false,
"workspace.library": [ "workspace.library": [
"${3rd}/busted/library",
"${env:HOME}/.luarocks/share/lua/5.4",
"${env:HOME}/.luarocks/share/lua/5.3",
"${env:HOME}/.luarocks/share/lua/5.2",
"${env:HOME}/.luarocks/share/lua/5.1",
"../../lua" "../../lua"
], ],
"runtime.pathStrict": true,
"runtime.path": [
"?.lua",
"?/init.lua",
"../../lua/?.lua",
"../../lua/?/init.lua",
"${env:HOME}/.luarocks/share/lua/5.4/?.lua",
"${env:HOME}/.luarocks/share/lua/5.4/?/init.lua",
"${env:HOME}/.luarocks/share/lua/5.3/?.lua",
"${env:HOME}/.luarocks/share/lua/5.3/?/init.lua",
"${env:HOME}/.luarocks/share/lua/5.2/?.lua",
"${env:HOME}/.luarocks/share/lua/5.2/?/init.lua",
"${env:HOME}/.luarocks/share/lua/5.1/?.lua",
"${env:HOME}/.luarocks/share/lua/5.1/?/init.lua"
],
"runtime.version": "LuaJIT", "runtime.version": "LuaJIT",
"hint.enable": false "hint.enable": false
} }

View File

@@ -0,0 +1,232 @@
local eq = assert.are.same
local diagnostics
describe("ale.diagnostics.send", function()
local buffer_map
local signs_config
local diagnostic_set_calls
setup(function()
_G.vim = {
api = {
nvim_buf_get_var = function(buffer, key)
local buffer_table = buffer_map[buffer] or {}
local value = buffer_table[key]
if value == nil then
error(key .. " is missing")
end
return value
end,
nvim_create_namespace = function()
return 42
end,
},
diagnostic = {
severity = {ERROR = 1, WARN = 2, INFO = 3},
config = function()
return {signs = signs_config}
end,
set = function(namespace, bufnr, _diagnostics, opts)
table.insert(diagnostic_set_calls, {
namespace = namespace,
bufnr = bufnr,
diagnostics = _diagnostics,
opts = opts,
})
end,
},
tbl_extend = function(behavior, ...)
assert(behavior == "force", "We should only use `force`")
local merged = {}
for _, arg in ipairs({...}) do
for key, value in pairs(arg) do
merged[key] = value
end
end
return merged
end,
g = {},
}
diagnostics = require("ale.diagnostics")
end)
teardown(function()
_G.vim = nil
end)
before_each(function()
buffer_map = {}
diagnostic_set_calls = {}
signs_config = false
_G.vim.g = {}
end)
it("should set an empty list of diagnostics correctly", function()
diagnostics.send(7, {})
eq(
{
{
namespace = 42,
bufnr = 7,
diagnostics = {},
opts = {virtual_text = false}
},
},
diagnostic_set_calls
)
end)
it("should handle basic case with all fields", function()
diagnostics.send(1, {
{
bufnr = 1,
lnum = 2,
end_lnum = 3,
col = 4,
end_col = 5,
type = "W",
code = "123",
text = "Warning message",
linter_name = "eslint",
},
})
eq({
{
lnum = 1,
end_lnum = 2,
col = 3,
end_col = 5,
severity = vim.diagnostic.severity.WARN,
code = "123",
message = "Warning message",
source = "eslint",
},
}, diagnostic_set_calls[1].diagnostics)
end)
it("should default end_lnum to lnum when missing", function()
diagnostics.send(1, {
{
bufnr = 1,
lnum = 5,
col = 2,
end_col = 8,
type = "E",
text = "Error message",
linter_name = "mylinter",
},
})
eq({
{
lnum = 4,
end_lnum = 4,
col = 1,
end_col = 8,
severity = vim.diagnostic.severity.ERROR,
code = nil,
message = "Error message",
source = "mylinter",
},
}, diagnostic_set_calls[1].diagnostics)
end)
it("should default col to 0 when missing", function()
diagnostics.send(1, {
{
bufnr = 1,
lnum = 10,
end_lnum = 12,
end_col = 6,
type = "I",
text = "Info message",
},
})
eq({
{
lnum = 9,
end_lnum = 11,
col = 0,
end_col = 6,
severity = vim.diagnostic.severity.INFO,
code = nil,
message = "Info message",
source = nil,
},
}, diagnostic_set_calls[1].diagnostics)
end)
it("should ignore non-matching buffers", function()
diagnostics.send(1, {
{
bufnr = 2,
lnum = 1,
end_lnum = 2,
col = 1,
end_col = 4,
type = "W",
text = "Message",
},
})
eq({}, diagnostic_set_calls[1].diagnostics)
end)
for _, set_signs_value in ipairs {1, true} do
describe("signs with setting set_signs = " .. tostring(set_signs_value), function()
before_each(function()
_G.vim.g.ale_set_signs = set_signs_value
_G.vim.g.ale_sign_priority = 10
end)
it("and global config as `false` should enable signs with the given priority", function()
diagnostics.send(7, {})
eq({priority = 10}, diagnostic_set_calls[1].opts.signs)
end)
it("and global config as a table should enable signs with the given priority", function()
signs_config = {foo = "bar", priority = 5}
diagnostics.send(7, {})
eq(
{foo = "bar", priority = 10},
diagnostic_set_calls[1].opts.signs
)
end)
it("and global config as a function should enable signs with the given priority", function()
signs_config = function()
return {foo = "bar", priority = 5}
end
diagnostics.send(7, {})
local local_signs = diagnostic_set_calls[1].opts.signs
eq("function", type(local_signs))
eq({foo = "bar", priority = 10}, local_signs())
end)
end)
end
it("should toggle virtual_text correctly", function()
for _, value in ipairs({"all", "2", 2, "current", "1", 1, true}) do
diagnostic_set_calls = {}
_G.vim.g.ale_virtualtext_cursor = value
diagnostics.send(7, {})
eq({virtual_text = true}, diagnostic_set_calls[1].opts)
end
for _, value in ipairs({"disabled", "0", 0, false, nil}) do
diagnostic_set_calls = {}
_G.vim.g.ale_virtualtext_cursor = value
diagnostics.send(7, {})
eq({virtual_text = false}, diagnostic_set_calls[1].opts)
end
end)
end)

56
test/lua/ale_env_spec.lua Normal file
View File

@@ -0,0 +1,56 @@
local eq = assert.are.same
local ale = require("ale")
describe("ale.env", function()
local is_win32 = false
setup(function()
_G.vim = {
o = setmetatable({}, {
__index = function(_, key)
if key == "shell" then
if is_win32 then
return "cmd.exe"
end
return "bash"
end
return nil
end
}),
fn = {
has = function(feature)
return feature == "win32" and is_win32
end,
-- Mock a very poor version of shellescape() for Unix
-- This shouldn't be called for Windows
shellescape = function(str)
return "'" .. str .. "'"
end,
fnamemodify = function(shell, _)
return shell
end
}
}
end)
teardown(function()
_G.vim = nil
end)
before_each(function()
is_win32 = false
end)
it("should escape values correctly on Unix", function()
eq("name='xxx' ", ale.env('name', 'xxx'))
eq("name='foo bar' ", ale.env('name', 'foo bar'))
end)
it("should escape values correctly on Windows", function()
is_win32 = true
eq('set name=xxx && ', ale.env('name', 'xxx'))
eq('set "name=foo bar" && ', ale.env('name', 'foo bar'))
end)
end)

224
test/lua/ale_lsp_spec.lua Normal file
View File

@@ -0,0 +1,224 @@
local eq = assert.are.same
local lsp = require("ale.lsp")
describe("ale.lsp.start", function()
local start_calls
local rpc_connect_calls
local vim_fn_calls
local defer_calls
setup(function()
_G.vim = {
defer_fn = function(func, delay)
table.insert(defer_calls, {func, delay})
end,
fn = setmetatable({}, {
__index = function(_, key)
return function(...)
table.insert(vim_fn_calls, {key, ...})
if key == "ale#lsp#GetLanguage" then
return "python"
end
if key ~= "ale#lsp_linter#HandleLSPResponse"
and key ~= "ale#lsp#UpdateCapabilities"
and key ~= "ale#lsp#CallInitCallbacks"
then
assert(false, "Invalid ALE function: " .. key)
end
return nil
end
end,
}),
lsp = {
rpc = {
connect = function(host, port)
return function(dispatch)
table.insert(rpc_connect_calls, {
host = host,
port = port,
dispatch = dispatch,
})
end
end,
},
start = function(...)
table.insert(start_calls, {...})
return 42
end,
},
}
end)
teardown(function()
_G.vim = nil
end)
before_each(function()
start_calls = {}
rpc_connect_calls = {}
vim_fn_calls = {}
defer_calls = {}
end)
it("should start lsp programs with the correct arguments", function()
lsp.start({
name = "server:/code",
cmd = "server",
root_dir = "/code",
-- This Boolean value somehow ends up in Dictionaries from
-- Vim for init_options, and we need to remove it.
init_options = {[true] = 123},
})
-- Remove arguments with functions we can't apply equality checks
-- for easily.
for _, args in pairs(start_calls) do
args[1].handlers = nil
args[1].on_init = nil
args[1].get_language_id = nil
end
eq({
{
{
cmd = "server",
name = "server:/code",
root_dir = "/code",
init_options = {},
},
{attach = false, silent = true}
}
}, start_calls)
eq({}, vim_fn_calls)
end)
it("should start lsp socket connections with the correct arguments", function()
lsp.start({
name = "localhost:1234:/code",
host = "localhost",
port = 1234,
root_dir = "/code",
init_options = {foo = "bar"},
})
local cmd
-- Remove arguments with functions we can't apply equality checks
-- for easily.
for _, args in pairs(start_calls) do
cmd = args[1].cmd
args[1].cmd = nil
args[1].handlers = nil
args[1].on_init = nil
args[1].get_language_id = nil
end
eq({
{
{
name = "localhost:1234:/code",
root_dir = "/code",
init_options = {foo = "bar"},
},
{attach = false, silent = true}
}
}, start_calls)
cmd("dispatch_value")
eq({
{dispatch = "dispatch_value", host = "localhost", port = 1234},
}, rpc_connect_calls)
eq({}, vim_fn_calls)
end)
it("should return the client_id value from vim.lsp.start", function()
eq(42, lsp.start({}))
end)
it("should implement get_language_id correctly", function()
lsp.start({name = "server:/code"})
eq(1, #start_calls)
eq("python", start_calls[1][1].get_language_id(347, "ftype"))
eq({{"ale#lsp#GetLanguage", "server:/code", 347}}, vim_fn_calls)
end)
it("should initialize clients with ALE correctly", function()
lsp.start({name = "server:/code"})
eq(1, #start_calls)
start_calls[1][1].on_init({server_capabilities = {cap = 1}})
eq({
{"ale#lsp#UpdateCapabilities", "server:/code", {cap = 1}},
}, vim_fn_calls)
eq(1, #defer_calls)
eq(2, #defer_calls[1])
eq("function", type(defer_calls[1][1]))
eq(0, defer_calls[1][2])
defer_calls[1][1]()
eq({
{"ale#lsp#UpdateCapabilities", "server:/code", {cap = 1}},
{"ale#lsp#CallInitCallbacks", "server:/code"},
}, vim_fn_calls)
end)
it("should configure handlers correctly", function()
lsp.start({name = "server:/code"})
eq(1, #start_calls)
local handlers = start_calls[1][1].handlers
local handler_names = {}
-- get keys from handlers
for key, _ in pairs(handlers) do
-- add key to handler_names mapping
handler_names[key] = true
end
eq({["textDocument/publishDiagnostics"] = true}, handler_names)
handlers["textDocument/publishDiagnostics"](nil, {
{
lnum = 1,
end_lnum = 2,
col = 3,
end_col = 5,
severity = 1,
code = "123",
message = "Warning message",
},
})
eq({
{
"ale#lsp_linter#HandleLSPResponse",
"server:/code",
{
jsonrpc = "2.0",
method = "textDocument/publishDiagnostics",
params = {
{
lnum = 1,
end_lnum = 2,
col = 3,
end_col = 5,
severity = 1,
code = "123",
message = "Warning message",
},
}
}
},
}, vim_fn_calls)
end)
end)

51
test/lua/ale_var_spec.lua Normal file
View File

@@ -0,0 +1,51 @@
local eq = assert.are.same
local ale = require("ale")
describe("ale.var", function()
local buffer_map
setup(function()
_G.vim = {
api = {
nvim_buf_get_var = function(buffer, key)
local buffer_table = buffer_map[buffer] or {}
local value = buffer_table[key]
if value == nil then
error(key .. " is missing")
end
return value
end,
},
g = {
},
}
end)
teardown(function()
_G.vim = nil
end)
before_each(function()
buffer_map = {}
_G.vim.g = {}
end)
it("should return nil for undefined variables", function()
eq(nil, ale.var(1, "foo"))
end)
it("should return buffer-local values, if set", function()
_G.vim.g.ale_foo = "global-value"
buffer_map[1] = {ale_foo = "buffer-value"}
eq("buffer-value", ale.var(1, "foo"))
end)
it("should return global values, if set", function()
_G.vim.g.ale_foo = "global-value"
eq("global-value", ale.var(1, "foo"))
end)
end)

View File

@@ -0,0 +1,61 @@
local eq = assert.are.same
local ale = require("ale")
describe("ale.escape for cmd.exe", function()
setup(function()
_G.vim = {
o = {
shell = "cmd.exe"
},
fn = {
fnamemodify = function(shell, _)
return shell
end
}
}
end)
teardown(function()
_G.vim = nil
end)
it("should allow not escape paths without special characters", function()
eq("C:", ale.escape("C:"))
eq("C:\\", ale.escape("C:\\"))
eq("python", ale.escape("python"))
eq("C:\\foo\\bar", ale.escape("C:\\foo\\bar"))
eq("/bar/baz", ale.escape("/bar/baz"))
eq("nul", ale.escape("nul"))
eq("'foo'", ale.escape("'foo'"))
end)
it("should escape Windows paths with spaces appropriately", function()
eq('"C:\\foo bar\\baz"', ale.escape('C:\\foo bar\\baz'))
eq('"^foo bar^"', ale.escape('^foo bar^'))
eq('"&foo bar&"', ale.escape('&foo bar&'))
eq('"|foo bar|"', ale.escape('|foo bar|'))
eq('"<foo bar<"', ale.escape('<foo bar<'))
eq('">foo bar>"', ale.escape('>foo bar>'))
eq('"^foo bar^"', ale.escape('^foo bar^'))
eq('"\'foo\' \'bar\'"', ale.escape('\'foo\' \'bar\''))
end)
it("should use caret escapes on special characters", function()
eq('^^foo^^', ale.escape('^foo^'))
eq('^&foo^&', ale.escape('&foo&'))
eq('^|foo^|', ale.escape('|foo|'))
eq('^<foo^<', ale.escape('<foo<'))
eq('^>foo^>', ale.escape('>foo>'))
eq('^^foo^^', ale.escape('^foo^'))
eq('\'foo\'^^\'bar\'', ale.escape('\'foo\'^\'bar\''))
end)
it("should escape percent characters", function()
eq('%%foo%%', ale.escape('%foo%'))
eq('C:\foo%%\bar\baz%%', ale.escape('C:\foo%\bar\baz%'))
eq('"C:\foo bar%%\baz%%"', ale.escape('C:\foo bar%\baz%'))
eq('^^%%foo%%', ale.escape('^%foo%'))
eq('"^%%foo%% %%bar%%"', ale.escape('^%foo% %bar%'))
eq('"^%%foo%% %%bar%% """""', ale.escape('^%foo% %bar% ""'))
end)
end)

57
test/script/run-lua-tests Executable file
View File

@@ -0,0 +1,57 @@
#!/usr/bin/env bash
set -e
set -u
docker_flags=(--rm -v "$PWD:/testplugin" -v "$PWD/test:/home" -w /testplugin/test/lua "$DOCKER_RUN_IMAGE")
quiet=0
while [ $# -ne 0 ]; do
case $1 in
-q)
quiet=1
shift
;;
--)
shift
break
;;
-?*)
echo "Invalid argument: $1" 1>&2
exit 1
;;
*)
break
;;
esac
done
function filter-busted-output() {
local hit_failure_line=0
while read -r; do
if ((quiet)); then
# If we're using the quiet flag, the filter out lines until we hit
# the first line with "Failure" and then print the rest.
if ((hit_failure_line)); then
echo "$REPLY"
elif [[ "$REPLY" = *'Failure'* ]]; then
hit_failure_line=1
echo "$REPLY"
fi
else
echo "$REPLY"
fi
done
}
exit_code=0
set -o pipefail
"$DOCKER" run -a stdout "${docker_flags[@]}" /usr/bin/busted-5.1 \
-m '../../lua/?.lua;../../lua/?/init.lua' . \
--output utfTerminal | filter-busted-output || exit_code=$?
set +o pipefail
exit "$exit_code"