summaryrefslogtreecommitdiff
path: root/ale_linters
diff options
context:
space:
mode:
Diffstat (limited to 'ale_linters')
-rw-r--r--ale_linters/bats/shellcheck.vim4
-rw-r--r--ale_linters/dockerfile/dockerfile_lint.vim17
-rw-r--r--ale_linters/go/revive.vim21
-rw-r--r--ale_linters/java/eclipselsp.vim41
-rw-r--r--ale_linters/java/javac.vim35
-rw-r--r--ale_linters/kotlin/kotlinc.vim1
-rw-r--r--ale_linters/puppet/puppet.vim8
-rw-r--r--ale_linters/rust/analyzer.vim24
-rw-r--r--ale_linters/scala/metals.vim2
-rw-r--r--ale_linters/sh/shellcheck.vim107
-rw-r--r--ale_linters/terraform/terraform_lsp.vim25
-rw-r--r--ale_linters/verilog/verilator.vim27
-rw-r--r--ale_linters/vim/vimls.vim61
-rw-r--r--ale_linters/zig/zls.vim20
14 files changed, 264 insertions, 129 deletions
diff --git a/ale_linters/bats/shellcheck.vim b/ale_linters/bats/shellcheck.vim
new file mode 100644
index 00000000..5c2a0ea9
--- /dev/null
+++ b/ale_linters/bats/shellcheck.vim
@@ -0,0 +1,4 @@
+" Author: Ian2020 <https://github.com/Ian2020>
+" Description: shellcheck linter for bats scripts.
+
+call ale#handlers#shellcheck#DefineLinter('bats')
diff --git a/ale_linters/dockerfile/dockerfile_lint.vim b/ale_linters/dockerfile/dockerfile_lint.vim
index 95768b12..0c0ad533 100644
--- a/ale_linters/dockerfile/dockerfile_lint.vim
+++ b/ale_linters/dockerfile/dockerfile_lint.vim
@@ -32,14 +32,29 @@ function! ale_linters#dockerfile#dockerfile_lint#Handle(buffer, lines) abort
let l:line = get(l:object, 'line', -1)
let l:message = l:object['message']
+ let l:link = get(l:object, 'reference_url', '')
+
+ if type(l:link) == v:t_list
+ " Somehow, reference_url is returned as two-part list.
+ " Anchor markers in that list are sometimes duplicated.
+ " See https://github.com/projectatomic/dockerfile_lint/issues/134
+ let l:link = join(l:link, '')
+ let l:link = substitute(l:link, '##', '#', '')
+ endif
+
+ let l:detail = l:message
+
if get(l:object, 'description', 'None') isnot# 'None'
- let l:message = l:message . '. ' . l:object['description']
+ let l:detail .= "\n\n" . l:object['description']
endif
+ let l:detail .= "\n\n" . l:link
+
call add(l:messages, {
\ 'lnum': l:line,
\ 'text': l:message,
\ 'type': ale_linters#dockerfile#dockerfile_lint#GetType(l:type),
+ \ 'detail': l:detail,
\})
endfor
endfor
diff --git a/ale_linters/go/revive.vim b/ale_linters/go/revive.vim
new file mode 100644
index 00000000..b14b5ab9
--- /dev/null
+++ b/ale_linters/go/revive.vim
@@ -0,0 +1,21 @@
+" Author: Penghui Liao <liaoishere@gmail.com>
+" Description: Adds support for revive
+
+call ale#Set('go_revive_executable', 'revive')
+call ale#Set('go_revive_options', '')
+
+function! ale_linters#go#revive#GetCommand(buffer) abort
+ let l:options = ale#Var(a:buffer, 'go_revive_options')
+
+ return ale#go#EnvString(a:buffer) . '%e'
+ \ . (!empty(l:options) ? ' ' . l:options : '')
+ \ . ' %t'
+endfunction
+
+call ale#linter#Define('go', {
+\ 'name': 'revive',
+\ 'output_stream': 'both',
+\ 'executable': {b -> ale#Var(b, 'go_revive_executable')},
+\ 'command': function('ale_linters#go#revive#GetCommand'),
+\ 'callback': 'ale#handlers#unix#HandleAsWarning',
+\})
diff --git a/ale_linters/java/eclipselsp.vim b/ale_linters/java/eclipselsp.vim
index 2648893b..c981b749 100644
--- a/ale_linters/java/eclipselsp.vim
+++ b/ale_linters/java/eclipselsp.vim
@@ -7,6 +7,7 @@ call ale#Set('java_eclipselsp_path', ale#path#Simplify($HOME . '/eclipse.jdt.ls'
call ale#Set('java_eclipselsp_config_path', '')
call ale#Set('java_eclipselsp_workspace_path', '')
call ale#Set('java_eclipselsp_executable', 'java')
+call ale#Set('java_eclipselsp_javaagent', '')
function! ale_linters#java#eclipselsp#Executable(buffer) abort
return ale#Var(a:buffer, 'java_eclipselsp_executable')
@@ -19,25 +20,32 @@ endfunction
function! ale_linters#java#eclipselsp#JarPath(buffer) abort
let l:path = ale_linters#java#eclipselsp#TargetPath(a:buffer)
+ if has('win32')
+ let l:platform = 'win32'
+ elseif has('macunix')
+ let l:platform = 'macosx'
+ else
+ let l:platform = 'linux'
+ endif
+
" Search jar file within repository path when manually built using mvn
- let l:repo_path = l:path . '/org.eclipse.jdt.ls.product/target/repository'
- let l:files = globpath(l:repo_path, '**/plugins/org.eclipse.equinox.launcher_\d\.\d\.\d\d\d\.*\.jar', 1, 1)
+ let l:files = globpath(l:path, '**/'.l:platform.'/**/plugins/org.eclipse.equinox.launcher_\d\.\d\.\d\d\d\.*\.jar', 1, 1)
- if len(l:files) == 1
+ if len(l:files) > 1
return l:files[0]
endif
" Search jar file within VSCode extensions folder.
- let l:files = globpath(l:path, '**/plugins/org.eclipse.equinox.launcher_\d\.\d\.\d\d\d\.*\.jar', 1, 1)
+ let l:files = globpath(l:path, '**/'.l:platform.'/plugins/org.eclipse.equinox.launcher_\d\.\d\.\d\d\d\.*\.jar', 1, 1)
- if len(l:files) == 1
+ if len(l:files) > 1
return l:files[0]
endif
" Search jar file within system package path
let l:files = globpath('/usr/share/java/jdtls/plugins', 'org.eclipse.equinox.launcher_\d\.\d\.\d\d\d\.*\.jar', 1, 1)
- if len(l:files) == 1
+ if len(l:files) > 1
return l:files[0]
endif
@@ -100,12 +108,30 @@ function! ale_linters#java#eclipselsp#WorkspacePath(buffer) abort
return ale#path#Dirname(ale#java#FindProjectRoot(a:buffer))
endfunction
+function! ale_linters#java#eclipselsp#Javaagent(buffer) abort
+ let l:rets = []
+ let l:raw = ale#Var(a:buffer, 'java_eclipselsp_javaagent')
+
+ if empty(l:raw)
+ return ''
+ endif
+
+ let l:jars = split(l:raw)
+
+ for l:jar in l:jars
+ call add(l:rets, ale#Escape('-javaagent:' . l:jar))
+ endfor
+
+ return join(l:rets, ' ')
+endfunction
+
function! ale_linters#java#eclipselsp#Command(buffer, version) abort
let l:path = ale#Var(a:buffer, 'java_eclipselsp_path')
let l:executable = ale_linters#java#eclipselsp#Executable(a:buffer)
let l:cmd = [ ale#Escape(l:executable),
+ \ ale_linters#java#eclipselsp#Javaagent(a:buffer),
\ '-Declipse.application=org.eclipse.jdt.ls.core.id1',
\ '-Dosgi.bundles.defaultStartLevel=4',
\ '-Declipse.product=org.eclipse.jdt.ls.core.product',
@@ -147,7 +173,8 @@ function! ale_linters#java#eclipselsp#RunWithVersionCheck(buffer) abort
return ale#command#Run(
\ a:buffer,
\ l:command,
- \ function('ale_linters#java#eclipselsp#CommandWithVersion')
+ \ function('ale_linters#java#eclipselsp#CommandWithVersion'),
+ \ { 'output_stream': 'both' }
\)
endfunction
diff --git a/ale_linters/java/javac.vim b/ale_linters/java/javac.vim
index 8bb52c0b..f866eb09 100644
--- a/ale_linters/java/javac.vim
+++ b/ale_linters/java/javac.vim
@@ -6,6 +6,7 @@ let s:classpath_sep = has('unix') ? ':' : ';'
call ale#Set('java_javac_executable', 'javac')
call ale#Set('java_javac_options', '')
call ale#Set('java_javac_classpath', '')
+call ale#Set('java_javac_sourcepath', '')
function! ale_linters#java#javac#RunWithImportPaths(buffer) abort
let l:command = ''
@@ -40,10 +41,15 @@ endfunction
function! s:BuildClassPathOption(buffer, import_paths) abort
" Filter out lines like [INFO], etc.
let l:class_paths = filter(a:import_paths[:], 'v:val !~# ''[''')
- call extend(
- \ l:class_paths,
- \ split(ale#Var(a:buffer, 'java_javac_classpath'), s:classpath_sep),
- \)
+ let l:cls_path = ale#Var(a:buffer, 'java_javac_classpath')
+
+ if !empty(l:cls_path) && type(l:cls_path) is v:t_string
+ call extend(l:class_paths, split(l:cls_path, s:classpath_sep))
+ endif
+
+ if !empty(l:cls_path) && type(l:cls_path) is v:t_list
+ call extend(l:class_paths, l:cls_path)
+ endif
return !empty(l:class_paths)
\ ? '-cp ' . ale#Escape(join(l:class_paths, s:classpath_sep))
@@ -79,6 +85,27 @@ function! ale_linters#java#javac#GetCommand(buffer, import_paths, meta) abort
endif
endif
+ let l:source_paths = []
+ let l:source_path = ale#Var(a:buffer, 'java_javac_sourcepath')
+
+ if !empty(l:source_path) && type(l:source_path) is v:t_string
+ let l:source_paths = split(l:source_path, s:classpath_sep)
+ endif
+
+ if !empty(l:source_path) && type(l:source_path) is v:t_list
+ let l:source_paths = l:source_path
+ endif
+
+ if !empty(l:source_paths)
+ for l:path in l:source_paths
+ let l:sp_path = ale#path#FindNearestDirectory(a:buffer, l:path)
+
+ if !empty(l:sp_path)
+ call add(l:sp_dirs, l:sp_path)
+ endif
+ endfor
+ endif
+
if !empty(l:sp_dirs)
let l:sp_option = '-sourcepath '
\ . ale#Escape(join(l:sp_dirs, s:classpath_sep))
diff --git a/ale_linters/kotlin/kotlinc.vim b/ale_linters/kotlin/kotlinc.vim
index 3c6854fa..66c075be 100644
--- a/ale_linters/kotlin/kotlinc.vim
+++ b/ale_linters/kotlin/kotlinc.vim
@@ -174,6 +174,7 @@ endfunction
call ale#linter#Define('kotlin', {
\ 'name': 'kotlinc',
\ 'executable': 'kotlinc',
+\ 'output_stream': 'stderr',
\ 'command': function('ale_linters#kotlin#kotlinc#RunWithImportPaths'),
\ 'callback': 'ale_linters#kotlin#kotlinc#Handle',
\ 'lint_file': 1,
diff --git a/ale_linters/puppet/puppet.vim b/ale_linters/puppet/puppet.vim
index ae648615..59228dc8 100644
--- a/ale_linters/puppet/puppet.vim
+++ b/ale_linters/puppet/puppet.vim
@@ -8,13 +8,15 @@ function! ale_linters#puppet#puppet#Handle(buffer, lines) abort
" Error: Could not parse for environment production: Syntax error at ':' at /root/puppetcode/modules/nginx/manifests/init.pp:43:12
" Error: Could not parse for environment production: Syntax error at '='; expected '}' at /root/puppetcode/modules/pancakes/manifests/init.pp:5"
" Error: Could not parse for environment production: Syntax error at 'parameter1' (file: /tmp/modules/mariadb/manifests/slave.pp, line: 4, column: 5)
- let l:pattern = '^Error: .*: \(.\+\) \((file:\|at\) .\+\.pp\(, line: \|:\)\(\d\+\)\(, column: \|:\)\=\(\d*\)'
+ " Error: Illegal attempt to assign to 'a Name'. Not an assignable reference (file: /tmp/modules/waffles/manifests/syrup.pp, line: 5, column: 11)
+ " Error: Could not parse for environment production: Syntax error at end of input (file: /tmp/modules/bob/manifests/init.pp)
+ let l:pattern = '^Error:\%(.*:\)\? \(.\+\) \((file:\|at\) .\+\.pp\(\(, line: \|:\)\(\d\+\)\(, column: \|:\)\=\(\d*\)\|)$\)'
let l:output = []
for l:match in ale#util#GetMatches(a:lines, l:pattern)
call add(l:output, {
- \ 'lnum': l:match[4] + 0,
- \ 'col': l:match[6] + 0,
+ \ 'lnum': l:match[5] + 0,
+ \ 'col': l:match[7] + 0,
\ 'text': l:match[1],
\})
endfor
diff --git a/ale_linters/rust/analyzer.vim b/ale_linters/rust/analyzer.vim
new file mode 100644
index 00000000..3666ec03
--- /dev/null
+++ b/ale_linters/rust/analyzer.vim
@@ -0,0 +1,24 @@
+" Author: Jon Gjengset <jon@thesquareplanet.com>
+" Description: The next generation language server for Rust
+
+call ale#Set('rust_analyzer_executable', 'rust-analyzer')
+call ale#Set('rust_analyzer_config', {})
+
+function! ale_linters#rust#analyzer#GetCommand(buffer) abort
+ return '%e'
+endfunction
+
+function! ale_linters#rust#analyzer#GetProjectRoot(buffer) abort
+ let l:cargo_file = ale#path#FindNearestFile(a:buffer, 'Cargo.toml')
+
+ return !empty(l:cargo_file) ? fnamemodify(l:cargo_file, ':h') : ''
+endfunction
+
+call ale#linter#Define('rust', {
+\ 'name': 'analyzer',
+\ 'lsp': 'stdio',
+\ 'lsp_config': {b -> ale#Var(b, 'rust_analyzer_config')},
+\ 'executable': {b -> ale#Var(b, 'rust_analyzer_executable')},
+\ 'command': function('ale_linters#rust#analyzer#GetCommand'),
+\ 'project_root': function('ale_linters#rust#analyzer#GetProjectRoot'),
+\})
diff --git a/ale_linters/scala/metals.vim b/ale_linters/scala/metals.vim
index f78c7119..da9e855d 100644
--- a/ale_linters/scala/metals.vim
+++ b/ale_linters/scala/metals.vim
@@ -32,6 +32,8 @@ function! ale_linters#scala#metals#GetProjectRoot(buffer) abort
\)
endif
endfor
+
+ return ''
endfunction
function! ale_linters#scala#metals#GetCommand(buffer) abort
diff --git a/ale_linters/sh/shellcheck.vim b/ale_linters/sh/shellcheck.vim
index 1d8b6096..d9945126 100644
--- a/ale_linters/sh/shellcheck.vim
+++ b/ale_linters/sh/shellcheck.vim
@@ -1,107 +1,4 @@
" Author: w0rp <devw0rp@gmail.com>
-" Description: This file adds support for using the shellcheck linter with
-" shell scripts.
+" Description: shellcheck linter for shell scripts.
-" This global variable can be set with a string of comma-separated error
-" codes to exclude from shellcheck. For example:
-"
-" let g:ale_sh_shellcheck_exclusions = 'SC2002,SC2004'
-call ale#Set('sh_shellcheck_exclusions', get(g:, 'ale_linters_sh_shellcheck_exclusions', ''))
-call ale#Set('sh_shellcheck_executable', 'shellcheck')
-call ale#Set('sh_shellcheck_dialect', 'auto')
-call ale#Set('sh_shellcheck_options', '')
-call ale#Set('sh_shellcheck_change_directory', 1)
-
-function! ale_linters#sh#shellcheck#GetDialectArgument(buffer) abort
- let l:shell_type = ale#handlers#sh#GetShellType(a:buffer)
-
- if !empty(l:shell_type)
- " Use the dash dialect for /bin/ash, etc.
- if l:shell_type is# 'ash'
- return 'dash'
- endif
-
- return l:shell_type
- endif
-
- " If there's no hashbang, try using Vim's buffer variables.
- if getbufvar(a:buffer, 'is_bash', 0)
- return 'bash'
- elseif getbufvar(a:buffer, 'is_sh', 0)
- return 'sh'
- elseif getbufvar(a:buffer, 'is_kornshell', 0)
- return 'ksh'
- endif
-
- return ''
-endfunction
-
-function! ale_linters#sh#shellcheck#GetCommand(buffer, version) abort
- let l:options = ale#Var(a:buffer, 'sh_shellcheck_options')
- let l:exclude_option = ale#Var(a:buffer, 'sh_shellcheck_exclusions')
- let l:dialect = ale#Var(a:buffer, 'sh_shellcheck_dialect')
- let l:external_option = ale#semver#GTE(a:version, [0, 4, 0]) ? ' -x' : ''
- let l:cd_string = ale#Var(a:buffer, 'sh_shellcheck_change_directory')
- \ ? ale#path#BufferCdString(a:buffer)
- \ : ''
-
- if l:dialect is# 'auto'
- let l:dialect = ale_linters#sh#shellcheck#GetDialectArgument(a:buffer)
- endif
-
- return l:cd_string
- \ . '%e'
- \ . (!empty(l:dialect) ? ' -s ' . l:dialect : '')
- \ . (!empty(l:options) ? ' ' . l:options : '')
- \ . (!empty(l:exclude_option) ? ' -e ' . l:exclude_option : '')
- \ . l:external_option
- \ . ' -f gcc -'
-endfunction
-
-function! ale_linters#sh#shellcheck#Handle(buffer, lines) abort
- let l:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+):(\d+)?:? ([^:]+): (.+) \[([^\]]+)\]$'
- let l:output = []
-
- for l:match in ale#util#GetMatches(a:lines, l:pattern)
- if l:match[4] is# 'error'
- let l:type = 'E'
- elseif l:match[4] is# 'note'
- let l:type = 'I'
- else
- let l:type = 'W'
- endif
-
- let l:item = {
- \ 'lnum': str2nr(l:match[2]),
- \ 'type': l:type,
- \ 'text': l:match[5],
- \ 'code': l:match[6],
- \}
-
- if !empty(l:match[3])
- let l:item.col = str2nr(l:match[3])
- endif
-
- " If the filename is something like <stdin>, <nofile> or -, then
- " this is an error for the file we checked.
- if l:match[1] isnot# '-' && l:match[1][0] isnot# '<'
- let l:item['filename'] = l:match[1]
- endif
-
- call add(l:output, l:item)
- endfor
-
- return l:output
-endfunction
-
-call ale#linter#Define('sh', {
-\ 'name': 'shellcheck',
-\ 'executable': {buffer -> ale#Var(buffer, 'sh_shellcheck_executable')},
-\ 'command': {buffer -> ale#semver#RunWithVersionCheck(
-\ buffer,
-\ ale#Var(buffer, 'sh_shellcheck_executable'),
-\ '%e --version',
-\ function('ale_linters#sh#shellcheck#GetCommand'),
-\ )},
-\ 'callback': 'ale_linters#sh#shellcheck#Handle',
-\})
+call ale#handlers#shellcheck#DefineLinter('sh')
diff --git a/ale_linters/terraform/terraform_lsp.vim b/ale_linters/terraform/terraform_lsp.vim
new file mode 100644
index 00000000..e2408c15
--- /dev/null
+++ b/ale_linters/terraform/terraform_lsp.vim
@@ -0,0 +1,25 @@
+" Author: OJFord <dev@ojford.com>
+" Description: terraform-lsp integration for ALE (cf. https://github.com/juliosueiras/terraform-lsp)
+
+call ale#Set('terraform_langserver_executable', 'terraform-lsp')
+call ale#Set('terraform_langserver_options', '')
+
+function! ale_linters#terraform#terraform_lsp#GetCommand(buffer) abort
+ return '%e'
+ \ . ale#Pad(ale#Var(a:buffer, 'terraform_langserver_options'))
+endfunction
+
+function! ale_linters#terraform#terraform_lsp#GetProjectRoot(buffer) abort
+ let l:tf_dir = ale#path#FindNearestDirectory(a:buffer, '.terraform')
+
+ return !empty(l:tf_dir) ? fnamemodify(l:tf_dir, ':h:h') : ''
+endfunction
+
+call ale#linter#Define('terraform', {
+\ 'name': 'terraform_lsp',
+\ 'lsp': 'stdio',
+\ 'executable': {b -> ale#Var(b, 'terraform_langserver_executable')},
+\ 'command': function('ale_linters#terraform#terraform_lsp#GetCommand'),
+\ 'project_root': function('ale_linters#terraform#terraform_lsp#GetProjectRoot'),
+\ 'language': 'terraform',
+\})
diff --git a/ale_linters/verilog/verilator.vim b/ale_linters/verilog/verilator.vim
index 64bb6e41..029dd4c9 100644
--- a/ale_linters/verilog/verilator.vim
+++ b/ale_linters/verilog/verilator.vim
@@ -28,21 +28,30 @@ function! ale_linters#verilog#verilator#Handle(buffer, lines) abort
" %Warning-UNDRIVEN: test.v:3: Signal is not driven: clk
" %Warning-UNUSED: test.v:4: Signal is not used: dout
" %Warning-BLKSEQ: test.v:10: Blocking assignments (=) in sequential (flop or latch) block; suggest delayed assignments (<=).
- let l:pattern = '^%\(Warning\|Error\)[^:]*:\([^:]\+\):\(\d\+\): \(.\+\)$'
+ " Since version 4.032 (04/2020) verilator linter messages also contain the column number,
+ " and look like:
+ " %Error: /tmp/test.sv:3:1: syntax error, unexpected endmodule, expecting ';'
+ "
+ " to stay compatible with old versions of the tool, the column number is
+ " optional in the researched pattern
+ let l:pattern = '^%\(Warning\|Error\)[^:]*:\([^:]\+\):\(\d\+\):\(\d\+\)\?:\? \(.\+\)$'
let l:output = []
for l:match in ale#util#GetMatches(a:lines, l:pattern)
- let l:line = l:match[3] + 0
- let l:type = l:match[1] is# 'Error' ? 'E' : 'W'
- let l:text = l:match[4]
+ let l:item = {
+ \ 'lnum': str2nr(l:match[3]),
+ \ 'text': l:match[5],
+ \ 'type': l:match[1] is# 'Error' ? 'E' : 'W',
+ \}
+
+ if !empty(l:match[4])
+ let l:item.col = str2nr(l:match[4])
+ endif
+
let l:file = l:match[2]
if l:file =~# '_verilator_linted.v'
- call add(l:output, {
- \ 'lnum': l:line,
- \ 'text': l:text,
- \ 'type': l:type,
- \})
+ call add(l:output, l:item)
endif
endfor
diff --git a/ale_linters/vim/vimls.vim b/ale_linters/vim/vimls.vim
new file mode 100644
index 00000000..26014d66
--- /dev/null
+++ b/ale_linters/vim/vimls.vim
@@ -0,0 +1,61 @@
+" Author: Jeffrey Lau - https://github.com/zoonfafer
+" Description: Vim Language Server integration for ALE
+
+call ale#Set('vim_vimls_executable', 'vim-language-server')
+call ale#Set('vim_vimls_use_global', get(g:, 'ale_use_global_executables', 0))
+call ale#Set('vim_vimls_config', {})
+
+function! ale_linters#vim#vimls#GetProjectRoot(buffer) abort
+ let l:trigger_file_candidates = [
+ \ '.vimrc',
+ \ 'init.vim',
+ \]
+
+ for l:candidate in l:trigger_file_candidates
+ let l:trigger_file = fnamemodify(bufname(a:buffer), ':t')
+
+ if l:trigger_file is# l:candidate
+ return fnamemodify(
+ \ bufname(a:buffer),
+ \ ':h',
+ \)
+ endif
+ endfor
+
+ let l:trigger_dir_candidates = [
+ \ 'autoload',
+ \ 'plugin',
+ \ '.git',
+ \]
+
+ let l:path_upwards = ale#path#Upwards(fnamemodify(bufname(a:buffer), ':p:h'))
+
+ for l:path in l:path_upwards
+ for l:candidate in l:trigger_dir_candidates
+ let l:trigger_dir = ale#path#Simplify(
+ \ l:path . '/' . l:candidate,
+ \)
+
+ if isdirectory(l:trigger_dir)
+ return fnamemodify(
+ \ l:trigger_dir,
+ \ ':p:h:h',
+ \)
+ endif
+ endfor
+ endfor
+
+ return ''
+endfunction
+
+call ale#linter#Define('vim', {
+\ 'name': 'vimls',
+\ 'lsp': 'stdio',
+\ 'lsp_config': {b -> ale#Var(b, 'vim_vimls_config')},
+\ 'executable': {b -> ale#node#FindExecutable(b, 'vim_vimls', [
+\ 'node_modules/.bin/vim-language-server',
+\ ])},
+\ 'command': '%e --stdio',
+\ 'language': 'vim',
+\ 'project_root': function('ale_linters#vim#vimls#GetProjectRoot'),
+\})
diff --git a/ale_linters/zig/zls.vim b/ale_linters/zig/zls.vim
new file mode 100644
index 00000000..1390f6b1
--- /dev/null
+++ b/ale_linters/zig/zls.vim
@@ -0,0 +1,20 @@
+" Author: CherryMan <skipper308@hotmail.ca>
+" Description: A language server for Zig
+
+call ale#Set('zig_zls_executable', 'zls')
+call ale#Set('zig_zls_config', {})
+
+function! ale_linters#zig#zls#GetProjectRoot(buffer) abort
+ let l:build_rs = ale#path#FindNearestFile(a:buffer, 'build.zig')
+
+ return !empty(l:build_rs) ? fnamemodify(l:build_rs, ':h') : ''
+endfunction
+
+call ale#linter#Define('zig', {
+\ 'name': 'zls',
+\ 'lsp': 'stdio',
+\ 'lsp_config': {b -> ale#Var(b, 'zig_zls_config')},
+\ 'executable': {b -> ale#Var(b, 'zig_zls_executable')},
+\ 'command': '%e',
+\ 'project_root': function('ale_linters#zig#zls#GetProjectRoot'),
+\})