Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for lpeg patterns and match functions to from_pattern #635

Merged
merged 1 commit into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 29 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,15 +277,26 @@ The function takes two arguments: `errorformat` and `skeleton` (optional).

### from_pattern

Creates a parser function from a pattern.

```lua
parser = require('lint.parser').from_pattern(pattern, groups, severity_map, defaults, opts)
```

The function allows to parse the linter's output using a Lua regular expression pattern.
### pattern

The function allows to parse the linter's output using a pattern which can be either:

- A Lua pattern. See `:help lua-patterns`.
- A LPEG pattern object. See `:help vim.lpeg`.
- A function (`fun(line: string):string[]`). It takes one parameter - a line
from the linter output and must return a string array with the matches. The
array should be empty if there was no match.


- pattern: The regular expression pattern applied on each line of the output
- groups: The groups specified by the pattern
### groups

The groups specify the result format of the pattern.
Available groups:

- `lnum`
Expand All @@ -305,7 +316,11 @@ local pattern = '[^:]+:(%d+):(%d+):(%w+):(.+)'
local groups = { 'lnum', 'col', 'code', 'message' }
```

- severity: A mapping from severity codes to diagnostic codes
The captures in the pattern correspond to the group at the same position.

### severity

A mapping from severity codes to diagnostic codes

``` lua
default_severity = {
Expand All @@ -316,18 +331,22 @@ default_severity = {
}
```

- defaults: The defaults diagnostic values
### defaults

The defaults diagnostic values

```lua
defaults = {["source"] = "mylint-name"}
```

- opts: Additional options
### opts

Additional options

- `lnum_offset`: Added to `lnum`. Defaults to 0
- `end_lnum_offset`: Added to `end_lnum`. Defaults to 0
- `end_col_offset`: offset added to `end_col`. Defaults to `-1`, assuming
that the end-column position is exclusive.
- `lnum_offset`: Added to `lnum`. Defaults to 0
- `end_lnum_offset`: Added to `end_lnum`. Defaults to 0
- `end_col_offset`: offset added to `end_col`. Defaults to `-1`, assuming
that the end-column position is exclusive.


## Customize built-in linters
Expand Down
23 changes: 21 additions & 2 deletions lua/lint/parser.lua
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ local normalize = (vim.fs ~= nil and vim.fs.normalize ~= nil)

--- Parse a linter's output using a Lua pattern
---
---@param pattern string
---@param pattern string|vim.lpeg.Pattern|fun(line: string):string[]
---@param groups string[]
---@param severity_map? table<string, vim.diagnostic.Severity>
---@param defaults? table
Expand All @@ -55,10 +55,29 @@ function M.from_pattern(pattern, groups, severity_map, defaults, opts)
defaults = defaults or {}
severity_map = severity_map or {}
opts = opts or {}

local type_ = type(pattern)
local matchline
if type_ == "string" then
matchline = function(line)
return { line:match(pattern) }
end
elseif type_ == "function" then
matchline = pattern
else
matchline = function(line)
return { pattern:match(line) }
end
end


-- Like vim.diagnostic.match but also checks if a `file` group matches the buffer path
-- Some linters produce diagnostics for the full project and this should only produce buffer diagnostics
local match = function(linter_cwd, buffer_path, line)
local matches = { line:match(pattern) }
local ok, matches = pcall(matchline, line)
if not ok then
error(string.format("pattern match failed on line: %s with error: %q", line, matches))
end
if not next(matches) then
return nil
end
Expand Down
54 changes: 54 additions & 0 deletions tests/parser_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,58 @@ bar:209:14 Bigger mistake
}
assert.are.same(expected, result)
end)

it("supports lpeg pattern", function()
if not vim.re then
return
end
local pattern = vim.re.compile("{[0-9]+} ':' { (.*) }")
local groups = { 'lnum', 'message' }
local parser = require('lint.parser').from_pattern(pattern, groups)
local output = [[
10:Big mistake
14:Bigger mistake
]]
local result = parser(output, 0)
local expected = {
{
message = 'Big mistake',
lnum = 9,
end_lnum = 9,
col = 0,
end_col = 0,
severity = vim.diagnostic.severity.ERROR,
},
{
message = 'Bigger mistake',
lnum = 13,
col = 0,
end_lnum = 13,
end_col = 0,
severity = vim.diagnostic.severity.ERROR,
},
}
assert.are.same(expected, result)
assert.are.same({}, parser("no-match", 0))
end)

it("supports match function", function()
local pattern = function(_)
return { 10, "hello" }
end
local groups = { 'lnum', 'message' }
local parser = require('lint.parser').from_pattern(pattern, groups)
local result = parser("foo", 0)
local expected = {
{
message = "hello",
lnum = 9,
col = 0,
end_lnum = 9,
end_col = 0,
severity = vim.diagnostic.severity.ERROR
},
}
assert.are.same(expected, result)
end)
end)
Loading