2021-03-13 22:46:45 +01:00
|
|
|
local M = {}
|
|
|
|
|
|
2021-03-13 23:56:29 +01:00
|
|
|
local assert = require('luassert')
|
2021-04-19 00:34:56 +02:00
|
|
|
local say = require('say')
|
2021-04-23 00:41:44 +02:00
|
|
|
local scan_dir = require('plenary.scandir').scan_dir
|
|
|
|
|
local Path = require('plenary.path')
|
2021-04-19 00:34:56 +02:00
|
|
|
|
|
|
|
|
local function same_indent(state, arguments)
|
|
|
|
|
local before = arguments[1]
|
|
|
|
|
local after = arguments[2]
|
|
|
|
|
|
|
|
|
|
local ok = true
|
2021-04-19 01:08:58 +02:00
|
|
|
local errors = { before = {}, after = {} }
|
2021-04-19 00:34:56 +02:00
|
|
|
for line = 1,#before do
|
|
|
|
|
if before[line] ~= after[line] then
|
2021-04-19 01:08:58 +02:00
|
|
|
-- store the actual indentation length for each line
|
2021-04-19 00:34:56 +02:00
|
|
|
errors.before[line] = #string.match(before[line], '^%s*')
|
|
|
|
|
errors.after[line] = #string.match(after[line], '^%s*')
|
|
|
|
|
ok = false
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2021-04-19 01:08:58 +02:00
|
|
|
-- we will always use only a single argument, passing the other one in fmtargs
|
|
|
|
|
arguments.fmtargs = { { errors = errors, other = after } }
|
|
|
|
|
arguments.fmtargs[2] = { errors = errors, other = after }
|
2021-04-19 00:34:56 +02:00
|
|
|
|
|
|
|
|
return ok
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local function format_indent(arg, fmtargs)
|
2021-04-19 01:08:58 +02:00
|
|
|
-- find minimal width if any line is longer
|
|
|
|
|
local width = 40
|
|
|
|
|
for _, line in ipairs(fmtargs.other) do
|
|
|
|
|
width = #line > width and #line or width
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
width = width + 3
|
|
|
|
|
local header_fmt = '%8s %2s%-' .. tostring(width + 1) .. 's %s'
|
|
|
|
|
local fmt = '%8s %2s |%-' .. tostring(width) .. 's |%s'
|
|
|
|
|
|
|
|
|
|
local output = {header_fmt:format('', '', 'Found:', 'Expected:')}
|
|
|
|
|
|
2021-04-19 00:34:56 +02:00
|
|
|
for i, line in ipairs(arg) do
|
2021-04-19 01:08:58 +02:00
|
|
|
if fmtargs.errors.before[i] then
|
|
|
|
|
local indents = string.format('%d vs %d', fmtargs.errors.after[i], fmtargs.errors.before[i])
|
|
|
|
|
table.insert(output, fmt:format(indents, '=>', fmtargs.other[i], line))
|
2021-04-19 00:34:56 +02:00
|
|
|
else
|
2021-04-19 01:08:58 +02:00
|
|
|
table.insert(output, fmt:format('', '', fmtargs.other[i], line))
|
2021-04-19 00:34:56 +02:00
|
|
|
end
|
|
|
|
|
end
|
2021-04-19 01:08:58 +02:00
|
|
|
|
2021-04-19 00:34:56 +02:00
|
|
|
return table.concat(output, '\n')
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
say:set_namespace('en')
|
2021-04-19 01:08:58 +02:00
|
|
|
say:set('assertion.same_indent.positive', 'Incorrect indentation\n%s')
|
|
|
|
|
say:set('assertion.same_indent.negative', 'Incorrect indentation\n%s')
|
2021-04-19 00:34:56 +02:00
|
|
|
assert:register('assertion', 'same_indent', same_indent,
|
|
|
|
|
'assertion.same_indent.positive', 'assert.same_indent.negative')
|
|
|
|
|
|
|
|
|
|
-- Custom assertion better suited for indentation diffs
|
|
|
|
|
local function compare_indent(before, after)
|
|
|
|
|
assert:add_formatter(format_indent)
|
|
|
|
|
assert.is.same_indent(before, after)
|
|
|
|
|
assert:remove_formatter(format_indent)
|
|
|
|
|
end
|
2021-03-13 23:56:29 +01:00
|
|
|
|
2021-04-22 23:46:30 +02:00
|
|
|
local function set_buf_indent_opts(opts)
|
2021-04-17 22:45:42 +02:00
|
|
|
local optnames = {'tabstop', 'shiftwidth', 'softtabstop', 'expandtab', 'filetype'}
|
|
|
|
|
for _, opt in ipairs(optnames) do
|
|
|
|
|
if opts[opt] ~= nil then
|
|
|
|
|
vim.bo[opt] = opts[opt]
|
2021-03-13 23:51:57 +01:00
|
|
|
end
|
2021-04-17 22:45:42 +02:00
|
|
|
end
|
2021-03-13 23:51:57 +01:00
|
|
|
end
|
|
|
|
|
|
2021-04-17 22:45:42 +02:00
|
|
|
function M.run_indent_test(file, runner, opts)
|
2021-04-23 00:41:44 +02:00
|
|
|
assert.are.same(1, vim.fn.filereadable(file), string.format('File "%s" not readable', file))
|
2021-03-13 22:46:45 +01:00
|
|
|
|
|
|
|
|
-- load reference file
|
|
|
|
|
vim.cmd(string.format('edit %s', file))
|
2021-04-17 22:45:42 +02:00
|
|
|
local before = vim.api.nvim_buf_get_lines(0, 0, -1, true)
|
2021-03-13 22:46:45 +01:00
|
|
|
|
|
|
|
|
assert.are.same('nvim_treesitter#indent()', vim.bo.indentexpr)
|
2021-04-22 23:46:30 +02:00
|
|
|
set_buf_indent_opts(opts)
|
2021-03-13 23:51:57 +01:00
|
|
|
|
2021-04-17 22:45:42 +02:00
|
|
|
-- perform the test
|
|
|
|
|
runner()
|
|
|
|
|
|
|
|
|
|
-- get file content after the test
|
|
|
|
|
local after = vim.api.nvim_buf_get_lines(0, 0, -1, true)
|
2021-03-13 22:46:45 +01:00
|
|
|
|
|
|
|
|
-- clear any changes to avoid 'No write since last change (add ! to override)'
|
|
|
|
|
vim.cmd 'edit!'
|
|
|
|
|
|
2021-04-17 22:45:42 +02:00
|
|
|
return before, after
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function M.indent_whole_file(file, opts)
|
|
|
|
|
local before, after = M.run_indent_test(file, function()
|
|
|
|
|
vim.cmd 'silent normal gg=G'
|
|
|
|
|
end, opts)
|
|
|
|
|
|
2021-04-19 00:34:56 +02:00
|
|
|
compare_indent(before, after)
|
2021-03-13 22:46:45 +01:00
|
|
|
end
|
|
|
|
|
|
2021-04-18 00:14:03 +02:00
|
|
|
-- Open a file, use `normal o` to insert a new line and compare results
|
|
|
|
|
-- @param file path to the initial file
|
|
|
|
|
-- @param spec a table with keys:
|
|
|
|
|
-- on_line: line on which `normal o` is executed
|
|
|
|
|
-- text: text inserted in the new line
|
|
|
|
|
-- indent: expected indent before the inserted text (string or int)
|
2021-04-22 23:46:30 +02:00
|
|
|
-- @param opts buffer options passed to set_buf_indent_opts
|
2021-04-18 00:14:03 +02:00
|
|
|
function M.indent_new_line(file, spec, opts)
|
|
|
|
|
local before, after = M.run_indent_test(file, function()
|
|
|
|
|
-- move to the line and input the new one
|
|
|
|
|
vim.cmd(string.format('normal! %dG', spec.on_line))
|
|
|
|
|
vim.cmd(string.format('normal! o%s', spec.text))
|
|
|
|
|
end, opts)
|
|
|
|
|
|
|
|
|
|
local indent = type(spec.indent) == 'string' and spec.indent or string.rep(' ', spec.indent)
|
|
|
|
|
table.insert(before, spec.on_line + 1, indent .. spec.text)
|
2021-04-19 00:34:56 +02:00
|
|
|
|
|
|
|
|
compare_indent(before, after)
|
2021-04-18 00:14:03 +02:00
|
|
|
end
|
|
|
|
|
|
2021-04-23 00:41:44 +02:00
|
|
|
local Runner = {}
|
|
|
|
|
Runner.__index = Runner
|
|
|
|
|
|
|
|
|
|
-- Helper to avoid boilerplate when defining tests
|
|
|
|
|
-- @param it the "it" function that busted defines globally in spec files
|
|
|
|
|
-- @param base_dir all other paths will be resolved relative to this directory
|
|
|
|
|
-- @param buf_opts buffer options passed to set_buf_indent_opts
|
|
|
|
|
function Runner:new(it, base_dir, buf_opts)
|
|
|
|
|
local runner = {}
|
|
|
|
|
runner.it = it
|
|
|
|
|
runner.base_dir = Path:new(base_dir)
|
|
|
|
|
runner.buf_opts = buf_opts
|
|
|
|
|
return setmetatable(runner, self)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Runner:whole_file(dirs)
|
|
|
|
|
dirs = type(dirs) == "table" and dirs or {dirs}
|
|
|
|
|
dirs = vim.tbl_map(function(dir)
|
|
|
|
|
dir = self.base_dir / Path:new(dir)
|
|
|
|
|
assert.is.same(1, vim.fn.isdirectory(dir.filename))
|
|
|
|
|
return dir.filename
|
|
|
|
|
end, dirs)
|
|
|
|
|
local files = vim.tbl_flatten(vim.tbl_map(scan_dir, dirs))
|
|
|
|
|
for _, file in ipairs(files) do
|
|
|
|
|
local relpath = Path:new(file):make_relative(self.base_dir.filename)
|
|
|
|
|
self.it(relpath, function()
|
|
|
|
|
M.indent_whole_file(file, self.buf_opts)
|
|
|
|
|
end)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function Runner:new_line(file, spec, title)
|
|
|
|
|
title = title and title or tostring(spec.on_line)
|
|
|
|
|
self.it(string.format('%s[%s]', file, title), function()
|
|
|
|
|
local path = self.base_dir / file
|
|
|
|
|
M.indent_new_line(path.filename, spec, self.buf_opts)
|
|
|
|
|
end)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
M.Runner = Runner
|
|
|
|
|
|
2021-03-13 22:46:45 +01:00
|
|
|
return M
|