build: replace luacheck with selene, add nix devshell and pre-commit (#20)

* build: replace luacheck with selene

Problem: luacheck is unmaintained (last release 2018) and required
suppressing four warning classes to avoid false positives. It also
lacks first-class vim/neovim awareness.

Solution: switch to selene with std='vim' for vim-aware linting.
Replace the luacheck CI job with selene, update the Makefile lint
target, and delete .luacheckrc.

* build: add nix devshell and pre-commit hooks

Problem: oil.nvim had no reproducible dev environment. The .envrc
set up a Python venv for the now-removed docgen pipeline, and there
were no pre-commit hooks for local formatting checks.

Solution: add flake.nix with stylua, selene, and prettier in the
devshell. Replace the stale Python .envrc with 'use flake'. Add
.pre-commit-config.yaml with stylua and prettier hooks matching
other plugins in the repo collection.

* fix: format with stylua

* build(selene): configure lints and add inline suppressions

Problem: selene fails on 5 errors and 3 warnings from upstream code
patterns that are intentional (mixed tables in config API, unused
callback parameters, identical if branches for readability).

Solution: globally allow mixed_table and unused_variable (high volume,
inherent to the codebase design). Add inline selene:allow directives
for the 8 remaining issues: if_same_then_else (4), mismatched_arg_count
(1), empty_if (2), global_usage (1). Remove .envrc from tracking.

* build: switch typecheck action to mrcjkb/lua-typecheck-action

Problem: oil.nvim used stevearc/nvim-typecheck-action, which required
cloning the action repo locally for the Makefile lint target. All
other plugins in the collection use mrcjkb/lua-typecheck-action.

Solution: swap to mrcjkb/lua-typecheck-action@v0 for consistency.
Remove the nvim-typecheck-action git clone from the Makefile and
.gitignore. Drop LuaLS from the local lint target since it requires
a full language server install — CI handles it.
This commit is contained in:
Barrett Ruth 2026-02-21 23:52:27 -05:00 committed by GitHub
parent df53b172a9
commit 86f553cd0a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
72 changed files with 2762 additions and 2649 deletions

View file

@ -1,6 +1,6 @@
local config = require("oil.config")
local constants = require("oil.constants")
local util = require("oil.util")
local config = require('oil.config')
local constants = require('oil.constants')
local util = require('oil.util')
local M = {}
local FIELD_NAME = constants.FIELD_NAME
@ -38,7 +38,7 @@ end
---@return oil.ColumnSpec[]
M.get_supported_columns = function(adapter_or_scheme)
local adapter
if type(adapter_or_scheme) == "string" then
if type(adapter_or_scheme) == 'string' then
adapter = config.get_adapter_by_scheme(adapter_or_scheme)
else
adapter = adapter_or_scheme
@ -53,7 +53,7 @@ M.get_supported_columns = function(adapter_or_scheme)
return ret
end
local EMPTY = { "-", "OilEmpty" }
local EMPTY = { '-', 'OilEmpty' }
M.EMPTY = EMPTY
@ -71,17 +71,17 @@ M.render_col = function(adapter, col_def, entry, bufnr)
end
local chunk = column.render(entry, conf, bufnr)
if type(chunk) == "table" then
if chunk[1]:match("^%s*$") then
if type(chunk) == 'table' then
if chunk[1]:match('^%s*$') then
return EMPTY
end
else
if not chunk or chunk:match("^%s*$") then
if not chunk or chunk:match('^%s*$') then
return EMPTY
end
if conf and conf.highlight then
local highlight = conf.highlight
if type(highlight) == "function" then
if type(highlight) == 'function' then
highlight = conf.highlight(chunk)
end
return { chunk, highlight }
@ -98,13 +98,13 @@ end
M.parse_col = function(adapter, line, col_def)
local name, conf = util.split_config(col_def)
-- If rendering failed, there will just be a "-"
local empty_col, rem = line:match("^%s*(-%s+)(.*)$")
local empty_col, rem = line:match('^%s*(-%s+)(.*)$')
if empty_col then
return nil, rem
end
local column = M.get_column(adapter, name)
if column then
return column.parse(line:gsub("^%s+", ""), conf)
return column.parse(line:gsub('^%s+', ''), conf)
end
end
@ -128,12 +128,12 @@ end
M.render_change_action = function(adapter, action)
local column = M.get_column(adapter, action.column)
if not column then
error(string.format("Received change action for nonexistant column %s", action.column))
error(string.format('Received change action for nonexistant column %s', action.column))
end
if column.render_action then
return column.render_action(action)
else
return string.format("CHANGE %s %s = %s", action.url, action.column, action.value)
return string.format('CHANGE %s %s = %s', action.url, action.column, action.value)
end
end
@ -144,7 +144,7 @@ M.perform_change_action = function(adapter, action, callback)
local column = M.get_column(adapter, action.column)
if not column then
return callback(
string.format("Received change action for nonexistant column %s", action.column)
string.format('Received change action for nonexistant column %s', action.column)
)
end
column.perform_action(action, callback)
@ -152,12 +152,12 @@ end
local icon_provider = util.get_icon_provider()
if icon_provider then
M.register("icon", {
M.register('icon', {
render = function(entry, conf, bufnr)
local field_type = entry[FIELD_TYPE]
local name = entry[FIELD_NAME]
local meta = entry[FIELD_META]
if field_type == "link" and meta then
if field_type == 'link' and meta then
if meta.link then
name = meta.link
end
@ -170,11 +170,11 @@ if icon_provider then
end
local ft = nil
if conf and conf.use_slow_filetype_detection and field_type == "file" then
if conf and conf.use_slow_filetype_detection and field_type == 'file' then
local bufname = vim.api.nvim_buf_get_name(bufnr)
local _, path = util.parse_url(bufname)
if path then
local lines = vim.fn.readfile(path .. name, "", 16)
local lines = vim.fn.readfile(path .. name, '', 16)
if lines and #lines > 0 then
ft = vim.filetype.match({ filename = name, contents = lines })
end
@ -183,10 +183,10 @@ if icon_provider then
local icon, hl = icon_provider(field_type, name, conf, ft)
if not conf or conf.add_padding ~= false then
icon = icon .. " "
icon = icon .. ' '
end
if conf and conf.highlight then
if type(conf.highlight) == "function" then
if type(conf.highlight) == 'function' then
hl = conf.highlight(icon)
else
hl = conf.highlight
@ -196,29 +196,29 @@ if icon_provider then
end,
parse = function(line, conf)
return line:match("^(%S+)%s+(.*)$")
return line:match('^(%S+)%s+(.*)$')
end,
})
end
local default_type_icons = {
directory = "dir",
socket = "sock",
directory = 'dir',
socket = 'sock',
}
---@param entry oil.InternalEntry
---@return boolean
local function is_entry_directory(entry)
local type = entry[FIELD_TYPE]
if type == "directory" then
if type == 'directory' then
return true
elseif type == "link" then
elseif type == 'link' then
local meta = entry[FIELD_META]
return (meta and meta.link_stat and meta.link_stat.type == "directory") == true
return (meta and meta.link_stat and meta.link_stat.type == 'directory') == true
else
return false
end
end
M.register("type", {
M.register('type', {
render = function(entry, conf)
local entry_type = entry[FIELD_TYPE]
if conf and conf.icons then
@ -229,7 +229,7 @@ M.register("type", {
end,
parse = function(line, conf)
return line:match("^(%S+)%s+(.*)$")
return line:match('^(%S+)%s+(.*)$')
end,
get_sort_value = function(entry)
@ -242,22 +242,22 @@ M.register("type", {
})
local function adjust_number(int)
return string.format("%03d%s", #int, int)
return string.format('%03d%s', #int, int)
end
M.register("name", {
M.register('name', {
render = function(entry, conf)
error("Do not use the name column. It is for sorting only")
error('Do not use the name column. It is for sorting only')
end,
parse = function(line, conf)
error("Do not use the name column. It is for sorting only")
error('Do not use the name column. It is for sorting only')
end,
create_sort_value_factory = function(num_entries)
if
config.view_options.natural_order == false
or (config.view_options.natural_order == "fast" and num_entries > 5000)
or (config.view_options.natural_order == 'fast' and num_entries > 5000)
then
if config.view_options.case_insensitive then
return function(entry)
@ -272,7 +272,7 @@ M.register("name", {
local memo = {}
return function(entry)
if memo[entry] == nil then
local name = entry[FIELD_NAME]:gsub("0*(%d+)", adjust_number)
local name = entry[FIELD_NAME]:gsub('0*(%d+)', adjust_number)
if config.view_options.case_insensitive then
name = name:lower()
end