]> git.lizzy.rs Git - micro.git/blobdiff - runtime/plugins/linter/linter.lua
Update linter to include eslint
[micro.git] / runtime / plugins / linter / linter.lua
index 46be745bfd54a360518488177e4536af2306a87d..0bb312ec0579e9a2aa58ee4f9a6c7bc41e653e5a 100644 (file)
@@ -1,8 +1,13 @@
+VERSION = "1.0.0"
+
+local micro = import("micro")
 local runtime = import("runtime")
 local filepath = import("path/filepath")
 local shell = import("micro/shell")
 local buffer = import("micro/buffer")
-local micro = import("micro")
+local config = import("micro/config")
+local util = import("micro/util")
+local os = import("os")
 
 local linters = {}
 
@@ -18,14 +23,23 @@ local linters = {}
 -- errorformat: how to parse the linter/compiler process output
 --     %f: file, %l: line number, %m: error/warning message
 -- os: list of OSs this linter is supported or unsupported on
---     optional param, default: []
+--     optional param, default: {}
 -- whitelist: should the OS list be a blacklist (do not run the linter for these OSs)
--- or a whitelist (only run the linter for these OSs)
+--            or a whitelist (only run the linter for these OSs)
 --     optional param, default: false (should blacklist)
 -- domatch: should the filetype be interpreted as a lua pattern to match with
--- the actual filetype, or should the linter only activate on an exact match
+--          the actual filetype, or should the linter only activate on an exact match
 --     optional param, default: false (require exact match)
-function makeLinter(name, filetype, cmd, args, errorformat, os, whitelist, domatch)
+-- loffset: line offset will be added to the line number returned by the linter
+--          useful if the linter returns 0-indexed lines
+--     optional param, default: 0
+-- coffset: column offset will be added to the col number returned by the linter
+--          useful if the linter returns 0-indexed columns
+--     optional param, default: 0
+-- callback: function to call before executing the linter, if it returns
+--           false the lint is canceled. The callback is passed the buf.
+--     optional param, default: nil
+function makeLinter(name, filetype, cmd, args, errorformat, os, whitelist, domatch, loffset, coffset, callback)
     if linters[name] == nil then
         linters[name] = {}
         linters[name].filetype = filetype
@@ -35,6 +49,9 @@ function makeLinter(name, filetype, cmd, args, errorformat, os, whitelist, domat
         linters[name].os = os or {}
         linters[name].whitelist = whitelist or false
         linters[name].domatch = domatch or false
+        linters[name].loffset = loffset or 0
+        linters[name].coffset = coffset or 0
+        linters[name].callback = callback or nil
     end
 end
 
@@ -48,24 +65,33 @@ function init()
         devnull = "NUL"
     end
 
-    makeLinter("gcc", "c", "gcc", {"-fsyntax-only", "-Wall", "-Wextra", "%f"}, "%f:%l:%d+:.+: %m")
-    makeLinter("gcc", "c++", "gcc", {"-fsyntax-only","-std=c++14", "-Wall", "-Wextra", "%f"}, "%f:%l:%d+:.+: %m")
+    makeLinter("gcc", "c", "gcc", {"-fsyntax-only", "-Wall", "-Wextra", "%f"}, "%f:%l:%c:.+: %m")
+    makeLinter("g++", "c++", "gcc", {"-fsyntax-only","-std=c++14", "-Wall", "-Wextra", "%f"}, "%f:%l:%c:.+: %m")
     makeLinter("dmd", "d", "dmd", {"-color=off", "-o-", "-w", "-wi", "-c", "%f"}, "%f%(%l%):.+: %m")
-    makeLinter("gobuild", "go", "go", {"build", "-o", devnull}, "%f:%l: %m")
-    makeLinter("golint", "go", "golint", {"%f"}, "%f:%l:%d+: %m")
+    makeLinter("eslint", "javascript", "eslint", {"-f","compact","%f"}, "%f: line %l, col %c, %m")
+    makeLinter("gobuild", "go", "go", {"build", "-o", devnull, "%d"}, "%f:%l:%c:? %m")
+    -- makeLinter("golint", "go", "golint", {"%f"}, "%f:%l:%c: %m")
+    makeLinter("hlint", "haskell", "hlint", {"%f"}, "%f:%l:%c.-: %m")
     makeLinter("javac", "java", "javac", {"-d", "%d", "%f"}, "%f:%l: error: %m")
     makeLinter("jshint", "javascript", "jshint", {"%f"}, "%f: line %l,.+, %m")
     makeLinter("literate", "literate", "lit", {"-c", "%f"}, "%f:%l:%m", {}, false, true)
-    makeLinter("luacheck", "lua", "luacheck", {"--no-color", "%f"}, "%f:%l:%d+: %m")
-    makeLinter("nim", "nim", "nim", {"check", "--listFullPaths", "--stdout", "--hints:off", "%f"}, "%f.%l, %d+. %m")
-    makeLinter("clang", "objective-c", "xcrun", {"clang", "-fsyntax-only", "-Wall", "-Wextra", "%f"}, "%f:%l:%d+:.+: %m")
+    makeLinter("luacheck", "lua", "luacheck", {"--no-color", "%f"}, "%f:%l:%c: %m")
+    makeLinter("nim", "nim", "nim", {"check", "--listFullPaths", "--stdout", "--hints:off", "%f"}, "%f.%l, %c. %m")
+    makeLinter("clang", "objective-c", "xcrun", {"clang", "-fsyntax-only", "-Wall", "-Wextra", "%f"}, "%f:%l:%c:.+: %m")
     makeLinter("pyflakes", "python", "pyflakes", {"%f"}, "%f:%l:.-:? %m")
     makeLinter("mypy", "python", "mypy", {"%f"}, "%f:%l: %m")
     makeLinter("pylint", "python", "pylint", {"--output-format=parseable", "--reports=no", "%f"}, "%f:%l: %m")
-    makeLinter("shfmt", "shell", "shfmt", {"%f"}, "%f:%l:%d+: %m")
-    makeLinter("switfc", "swift", "xcrun", {"swiftc", "%f"}, "%f:%l:%d+:.+: %m", {"darwin"}, true)
-    makeLinter("switfc", "swiftc", {"%f"}, "%f:%l:%d+:.+: %m", {"linux"}, true)
-    makeLinter("yaml", "yaml", "yamllint", {"--format", "parsable", "%f"}, "%f:%l:%d+:.+ %m")
+    makeLinter("shfmt", "shell", "shfmt", {"%f"}, "%f:%l:%c: %m")
+    makeLinter("swiftc", "swift", "xcrun", {"swiftc", "%f"}, "%f:%l:%c:.+: %m", {"darwin"}, true)
+    makeLinter("swiftc", "swiftc", {"%f"}, "%f:%l:%c:.+: %m", {"linux"}, true)
+    makeLinter("yaml", "yaml", "yamllint", {"--format", "parsable", "%f"}, "%f:%l:%c:.+ %m")
+
+    config.MakeCommand("lint", function(bp, args)
+        bp:Save()
+        runLinter(bp.Buf)
+    end, config.NoComplete)
+
+    config.AddRuntimeFile("linter", config.RTHelp, "help/linter.md")
 end
 
 function contains(list, element)
@@ -80,7 +106,7 @@ end
 function runLinter(buf)
     local ft = buf:FileType()
     local file = buf.Path
-    local dir = filepath.Dir(file)
+    local dir = "." .. util.RuneStr(os.PathSeparator) .. filepath.Dir(file)
 
     for k, v in pairs(linters) do
         local ftmatch = ft == v.filetype
@@ -96,41 +122,61 @@ function runLinter(buf)
             ftmatch = false
         end
 
+        local args = {}
         for k, arg in pairs(v.args) do
-            v.args[k] = arg:gsub("%%f", file):gsub("%%d", dir)
+            args[k] = arg:gsub("%%f", file):gsub("%%d", dir)
         end
 
         if ftmatch then
-            lint(buf, k, v.cmd, v.args, v.errorformat)
+            lint(buf, k, v.cmd, args, v.errorformat, v.loffset, v.coffset, v.callback)
         end
     end
 end
 
 function onSave(bp)
-    micro.Log("SAVE")
     runLinter(bp.Buf)
-    return false
+    return true
 end
 
-function lint(buf, linter, cmd, args, errorformat)
-    buf:ClearMessages("linter")
+function lint(buf, linter, cmd, args, errorformat, loff, coff, callback)
+    buf:ClearMessages(linter)
 
-    shell.JobSpawn(cmd, args, "", "", "linter.onExit", buf, linter, errorformat)
+    if callback ~= nil then
+        if not callback(buf) then
+            return
+        end
+    end
+
+    shell.JobSpawn(cmd, args, nil, nil, onExit, buf, linter, errorformat, loff, coff)
 end
 
-function onExit(output, buf, linter, errorformat)
-    micro.Log("ONEXIT")
-    micro.Log(output)
+function onExit(output, args)
+    local buf, linter, errorformat, loff, coff = args[1], args[2], args[3], args[4], args[5]
     local lines = split(output, "\n")
 
-    local regex = errorformat:gsub("%%f", "(..-)"):gsub("%%l", "(%d+)"):gsub("%%m", "(.+)")
+    local regex = errorformat:gsub("%%f", "(..-)"):gsub("%%l", "(%d+)"):gsub("%%c", "(%d+)"):gsub("%%m", "(.+)")
     for _,line in ipairs(lines) do
         -- Trim whitespace
         line = line:match("^%s*(.+)%s*$")
         if string.find(line, regex) then
-            local file, line, msg = string.match(line, regex)
+            local file, line, col, msg = string.match(line, regex)
+            local hascol = true
+            if not string.find(errorformat, "%%c") then
+                hascol = false
+                msg = col
+            elseif col == nil then
+                hascol = false
+            end
+            micro.Log(basename(buf.Path), basename(file))
             if basename(buf.Path) == basename(file) then
-                local bmsg = buffer.NewMessageAtLine("linter", msg, tonumber(line), buffer.MTError)
+                local bmsg = nil
+                if hascol then
+                    local mstart = buffer.Loc(tonumber(col-1+coff), tonumber(line-1+loff))
+                    local mend = buffer.Loc(tonumber(col+coff), tonumber(line-1+loff))
+                    bmsg = buffer.NewMessage(linter, msg, mstart, mend, buffer.MTError)
+                else
+                    bmsg = buffer.NewMessageAtLine(linter, msg, tonumber(line+loff), buffer.MTError)
+                end
                 buf:AddMessage(bmsg)
             end
         end