mirror of https://github.com/dense-analysis/ale
Linter for powershell syntax errors (#2413)
* Linter for powershell syntax errors
This commit is contained in:
parent
d7395906ba
commit
2ed53108c4
|
@ -0,0 +1,91 @@
|
||||||
|
" Author: Jesse Harris - https://github.com/zigford
|
||||||
|
" Description: This file adds support for powershell scripts synatax errors
|
||||||
|
|
||||||
|
call ale#Set('powershell_powershell_executable', 'pwsh')
|
||||||
|
|
||||||
|
function! ale_linters#powershell#powershell#GetExecutable(buffer) abort
|
||||||
|
return ale#Var(a:buffer, 'powershell_powershell_executable')
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
" Some powershell magic to show syntax errors without executing the script
|
||||||
|
" thanks to keith hill:
|
||||||
|
" https://rkeithhill.wordpress.com/2007/10/30/powershell-quicktip-preparsing-scripts-to-check-for-syntax-errors/
|
||||||
|
function! ale_linters#powershell#powershell#GetCommand(buffer) abort
|
||||||
|
let l:script = ['Param($Script);
|
||||||
|
\ trap {$_;continue} & {
|
||||||
|
\ $Contents = Get-Content -Path $Script;
|
||||||
|
\ $Contents = [string]::Join([Environment]::NewLine, $Contents);
|
||||||
|
\ [void]$ExecutionContext.InvokeCommand.NewScriptBlock($Contents);
|
||||||
|
\ };']
|
||||||
|
|
||||||
|
return ale#powershell#RunPowerShell(
|
||||||
|
\ a:buffer, 'powershell_powershell', l:script)
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
" Parse powershell error output using regex into a list of dicts
|
||||||
|
function! ale_linters#powershell#powershell#Handle(buffer, lines) abort
|
||||||
|
let l:output = []
|
||||||
|
" Our 3 patterns we need to scrape the data for the dicts
|
||||||
|
let l:patterns = [
|
||||||
|
\ '\v^At line:(\d+) char:(\d+)',
|
||||||
|
\ '\v^(At|\+| )@!.*',
|
||||||
|
\ '\vFullyQualifiedErrorId : (\w+)',
|
||||||
|
\]
|
||||||
|
|
||||||
|
let l:matchcount = 0
|
||||||
|
|
||||||
|
for l:match in ale#util#GetMatches(a:lines, l:patterns)
|
||||||
|
" We want to work with 3 matches per syntax error
|
||||||
|
let l:matchcount = l:matchcount + 1
|
||||||
|
|
||||||
|
if l:matchcount == 1 || str2nr(l:match[1])
|
||||||
|
" First match consists of 2 capture groups, and
|
||||||
|
" can capture the line and col
|
||||||
|
if exists('l:item')
|
||||||
|
" We may be here because the last syntax
|
||||||
|
" didn't emit a code, and so only had 2
|
||||||
|
" matches
|
||||||
|
call add(l:output, l:item)
|
||||||
|
let l:matchcount = 1
|
||||||
|
endif
|
||||||
|
|
||||||
|
let l:item = {
|
||||||
|
\ 'lnum': str2nr(l:match[1]),
|
||||||
|
\ 'col': str2nr(l:match[2]),
|
||||||
|
\ 'type': 'E',
|
||||||
|
\}
|
||||||
|
elseif l:matchcount == 2
|
||||||
|
" Second match[0] grabs the full line in order
|
||||||
|
" to handles the text
|
||||||
|
let l:item['text'] = l:match[0]
|
||||||
|
else
|
||||||
|
" Final match handles the code, however
|
||||||
|
" powershell only emits 1 code for all errors
|
||||||
|
" so, we get the final code on the last error
|
||||||
|
" and loop over the previously added items to
|
||||||
|
" append the code we now know
|
||||||
|
call add(l:output, l:item)
|
||||||
|
unlet l:item
|
||||||
|
|
||||||
|
if len(l:match[1]) > 0
|
||||||
|
for l:i in l:output
|
||||||
|
let l:i['code'] = l:match[1]
|
||||||
|
endfor
|
||||||
|
endif
|
||||||
|
|
||||||
|
" Reset the matchcount so we can begin gathering
|
||||||
|
" matches for the next syntax error
|
||||||
|
let l:matchcount = 0
|
||||||
|
endif
|
||||||
|
endfor
|
||||||
|
|
||||||
|
return l:output
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
call ale#linter#Define('powershell', {
|
||||||
|
\ 'name': 'powershell',
|
||||||
|
\ 'executable_callback': 'ale_linters#powershell#powershell#GetExecutable',
|
||||||
|
\ 'command_callback': 'ale_linters#powershell#powershell#GetCommand',
|
||||||
|
\ 'output_stream': 'stdout',
|
||||||
|
\ 'callback': 'ale_linters#powershell#powershell#Handle',
|
||||||
|
\})
|
|
@ -13,37 +13,6 @@ function! ale_linters#powershell#psscriptanalyzer#GetExecutable(buffer) abort
|
||||||
return ale#Var(a:buffer, 'powershell_psscriptanalyzer_executable')
|
return ale#Var(a:buffer, 'powershell_psscriptanalyzer_executable')
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
" Write a powershell script to a temp file for execution
|
|
||||||
" return the command used to execute it
|
|
||||||
function! s:TemporaryPSScript(buffer, input) abort
|
|
||||||
let l:filename = 'script.ps1'
|
|
||||||
" Create a temp dir to house our temp .ps1 script
|
|
||||||
" a temp dir is needed as powershell needs the .ps1
|
|
||||||
" extension
|
|
||||||
let l:tempdir = ale#util#Tempname() . (has('win32') ? '\' : '/')
|
|
||||||
let l:tempscript = l:tempdir . l:filename
|
|
||||||
" Create the temporary directory for the file, unreadable by 'other'
|
|
||||||
" users.
|
|
||||||
call mkdir(l:tempdir, '', 0750)
|
|
||||||
" Automatically delete the directory later.
|
|
||||||
call ale#command#ManageDirectory(a:buffer, l:tempdir)
|
|
||||||
" Write the script input out to a file.
|
|
||||||
call ale#util#Writefile(a:buffer, a:input, l:tempscript)
|
|
||||||
|
|
||||||
return l:tempscript
|
|
||||||
endfunction
|
|
||||||
|
|
||||||
function! ale_linters#powershell#psscriptanalyzer#RunPowerShell(buffer, command) abort
|
|
||||||
let l:executable = ale_linters#powershell#psscriptanalyzer#GetExecutable(
|
|
||||||
\ a:buffer)
|
|
||||||
let l:tempscript = s:TemporaryPSScript(a:buffer, a:command)
|
|
||||||
|
|
||||||
return ale#Escape(l:executable)
|
|
||||||
\ . ' -Exe Bypass -NoProfile -File '
|
|
||||||
\ . ale#Escape(l:tempscript)
|
|
||||||
\ . ' %t'
|
|
||||||
endfunction
|
|
||||||
|
|
||||||
" Run Invoke-ScriptAnalyzer and output each linting message as 4 seperate lines
|
" Run Invoke-ScriptAnalyzer and output each linting message as 4 seperate lines
|
||||||
" for each parsing
|
" for each parsing
|
||||||
function! ale_linters#powershell#psscriptanalyzer#GetCommand(buffer) abort
|
function! ale_linters#powershell#psscriptanalyzer#GetCommand(buffer) abort
|
||||||
|
@ -60,8 +29,10 @@ function! ale_linters#powershell#psscriptanalyzer#GetCommand(buffer) abort
|
||||||
\ $_.Message;
|
\ $_.Message;
|
||||||
\ $_.RuleName}']
|
\ $_.RuleName}']
|
||||||
|
|
||||||
return ale_linters#powershell#psscriptanalyzer#RunPowerShell(
|
return ale#powershell#RunPowerShell(
|
||||||
\ a:buffer, l:script)
|
\ a:buffer,
|
||||||
|
\ 'powershell_psscriptanalyzer',
|
||||||
|
\ l:script)
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
" add every 4 lines to an item(Dict) and every item to a list
|
" add every 4 lines to an item(Dict) and every item to a list
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
" Author: zigford <zigford@gmail.com>
|
||||||
|
" Description: Functions for integrating with Powershell linters.
|
||||||
|
|
||||||
|
" Write a powershell script to a temp file for execution
|
||||||
|
" return the command used to execute it
|
||||||
|
function! s:TemporaryPSScript(buffer, input) abort
|
||||||
|
let l:filename = 'script.ps1'
|
||||||
|
" Create a temp dir to house our temp .ps1 script
|
||||||
|
" a temp dir is needed as powershell needs the .ps1
|
||||||
|
" extension
|
||||||
|
let l:tempdir = ale#util#Tempname() . (has('win32') ? '\' : '/')
|
||||||
|
let l:tempscript = l:tempdir . l:filename
|
||||||
|
" Create the temporary directory for the file, unreadable by 'other'
|
||||||
|
" users.
|
||||||
|
call mkdir(l:tempdir, '', 0750)
|
||||||
|
" Automatically delete the directory later.
|
||||||
|
call ale#command#ManageDirectory(a:buffer, l:tempdir)
|
||||||
|
" Write the script input out to a file.
|
||||||
|
call ale#util#Writefile(a:buffer, a:input, l:tempscript)
|
||||||
|
|
||||||
|
return l:tempscript
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
function! ale#powershell#RunPowerShell(buffer, base_var_name, command) abort
|
||||||
|
let l:executable = ale#Var(a:buffer, a:base_var_name . '_executable')
|
||||||
|
let l:tempscript = s:TemporaryPSScript(a:buffer, a:command)
|
||||||
|
|
||||||
|
return ale#Escape(l:executable)
|
||||||
|
\ . ' -Exe Bypass -NoProfile -File '
|
||||||
|
\ . ale#Escape(l:tempscript)
|
||||||
|
\ . ' %t'
|
||||||
|
endfunction
|
|
@ -2,6 +2,21 @@
|
||||||
ALE PowerShell Integration *ale-powershell-options*
|
ALE PowerShell Integration *ale-powershell-options*
|
||||||
|
|
||||||
|
|
||||||
|
===============================================================================
|
||||||
|
powershell *ale-powershell-powershell*
|
||||||
|
|
||||||
|
g:ale_powershell_powershell_executable *g:ale_powershell_powershell_executable*
|
||||||
|
*b:ale_powershell_powershell_executable*
|
||||||
|
Type: String
|
||||||
|
Default: `'pwsh'`
|
||||||
|
|
||||||
|
This variable can be changed to use a different executable for powershell.
|
||||||
|
|
||||||
|
>
|
||||||
|
" Use powershell.exe rather than the default pwsh
|
||||||
|
let g:ale_powershell_powershell_executable = 'powershell.exe'
|
||||||
|
>
|
||||||
|
|
||||||
===============================================================================
|
===============================================================================
|
||||||
psscriptanalyzer *ale-powershell-psscriptanalyzer*
|
psscriptanalyzer *ale-powershell-psscriptanalyzer*
|
||||||
|
|
||||||
|
|
|
@ -319,6 +319,7 @@ Notes:
|
||||||
* Pony
|
* Pony
|
||||||
* `ponyc`
|
* `ponyc`
|
||||||
* PowerShell
|
* PowerShell
|
||||||
|
* `powershell`
|
||||||
* `psscriptanalyzer`
|
* `psscriptanalyzer`
|
||||||
* Prolog
|
* Prolog
|
||||||
* `swipl`
|
* `swipl`
|
||||||
|
|
|
@ -2070,6 +2070,7 @@ documented in additional help files.
|
||||||
pony....................................|ale-pony-options|
|
pony....................................|ale-pony-options|
|
||||||
ponyc.................................|ale-pony-ponyc|
|
ponyc.................................|ale-pony-ponyc|
|
||||||
powershell............................|ale-powershell-options|
|
powershell............................|ale-powershell-options|
|
||||||
|
powershell..........................|ale-powershell-powershell|
|
||||||
psscriptanalyzer....................|ale-powershell-psscriptanalyzer|
|
psscriptanalyzer....................|ale-powershell-psscriptanalyzer|
|
||||||
prolog..................................|ale-prolog-options|
|
prolog..................................|ale-prolog-options|
|
||||||
swipl.................................|ale-prolog-swipl|
|
swipl.................................|ale-prolog-swipl|
|
||||||
|
|
|
@ -328,6 +328,7 @@ formatting.
|
||||||
* Pony
|
* Pony
|
||||||
* [ponyc](https://github.com/ponylang/ponyc)
|
* [ponyc](https://github.com/ponylang/ponyc)
|
||||||
* PowerShell
|
* PowerShell
|
||||||
|
* [powershell](https://github.com/PowerShell/PowerShell) :floppy_disk
|
||||||
* [psscriptanalyzer](https://github.com/PowerShell/PSScriptAnalyzer) :floppy_disk
|
* [psscriptanalyzer](https://github.com/PowerShell/PSScriptAnalyzer) :floppy_disk
|
||||||
* Prolog
|
* Prolog
|
||||||
* [swipl](https://github.com/SWI-Prolog/swipl-devel)
|
* [swipl](https://github.com/SWI-Prolog/swipl-devel)
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
Before:
|
||||||
|
runtime ale_linters/powershell/powershell.vim
|
||||||
|
|
||||||
|
After:
|
||||||
|
call ale#linter#Reset()
|
||||||
|
|
||||||
|
Execute(The powershell handler should process syntax errors from parsing a powershell script):
|
||||||
|
AssertEqual
|
||||||
|
\ [
|
||||||
|
\ {
|
||||||
|
\ 'lnum': 8,
|
||||||
|
\ 'col': 29,
|
||||||
|
\ 'type': 'E',
|
||||||
|
\ 'text': 'Missing closing ''}'' in statement block or type definition.',
|
||||||
|
\ 'code': 'ParseException',
|
||||||
|
\ },
|
||||||
|
\ ],
|
||||||
|
\ ale_linters#powershell#powershell#Handle(bufnr(''), [
|
||||||
|
\ "At line:8 char:29",
|
||||||
|
\ "+ Invoke-Command -ScriptBlock {",
|
||||||
|
\ "+ ~",
|
||||||
|
\ "Missing closing '}' in statement block or type definition.",
|
||||||
|
\ "At /home/harrisj/tester.ps1:5 char:5",
|
||||||
|
\ "+ [void]$ExecutionContext.InvokeCommand.NewScriptBlock($Contents);",
|
||||||
|
\ "+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~",
|
||||||
|
\ "+ CategoryInfo : NotSpecified: (:) [], ParseException",
|
||||||
|
\ "+ FullyQualifiedErrorId : ParseException"
|
||||||
|
\ ])
|
||||||
|
|
||||||
|
Execute(The powershell handler should process multiple syntax errors from parsing a powershell script):
|
||||||
|
AssertEqual
|
||||||
|
\ [
|
||||||
|
\ {
|
||||||
|
\ 'lnum': 11,
|
||||||
|
\ 'col': 31,
|
||||||
|
\ 'type': 'E',
|
||||||
|
\ 'text': 'The string is missing the terminator: ".',
|
||||||
|
\ 'code': 'ParseException'
|
||||||
|
\ },
|
||||||
|
\ {
|
||||||
|
\ 'lnum': 3,
|
||||||
|
\ 'col': 16,
|
||||||
|
\ 'type': 'E',
|
||||||
|
\ 'text': 'Missing closing ''}'' in statement block or type definition.',
|
||||||
|
\ 'code': 'ParseException'
|
||||||
|
\ },
|
||||||
|
\ ],
|
||||||
|
\ ale_linters#powershell#powershell#Handle(bufnr(''), [
|
||||||
|
\ 'At line:11 char:31',
|
||||||
|
\ '+ write-verbose ''deleted''',
|
||||||
|
\ '+ ~',
|
||||||
|
\ 'The string is missing the terminator: ".',
|
||||||
|
\ 'At line:3 char:16',
|
||||||
|
\ '+ invoke-command {',
|
||||||
|
\ '+ ~',
|
||||||
|
\ 'Missing closing ''}'' in statement block or type definition.',
|
||||||
|
\ 'At /var/folders/qv/15ybvt050v9cgwrm7c95x4r4zc4qsg/T/vwhzIc8/1/script.ps1:1 char:150',
|
||||||
|
\ '+ ... ontents); [void]$ExecutionContext.InvokeCommand.NewScriptBlock($Con ...',
|
||||||
|
\ '+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~',
|
||||||
|
\ '+ CategoryInfo : NotSpecified: (:) [], ParseException',
|
||||||
|
\ '+ FullyQualifiedErrorId : ParseException'
|
||||||
|
\ ])
|
Loading…
Reference in New Issue