Support csc, update mcsc (#2586)

* Added a new csc linter for C# code.
* More output is now handled for mcsc.
This commit is contained in:
hernot 2019-07-02 09:18:17 +02:00 committed by w0rp
parent 8700586890
commit 46ab7c5904
9 changed files with 374 additions and 12 deletions

95
ale_linters/cs/csc.vim Normal file
View file

@ -0,0 +1,95 @@
call ale#Set('cs_csc_options', '')
call ale#Set('cs_csc_source', '')
call ale#Set('cs_csc_assembly_path', [])
call ale#Set('cs_csc_assemblies', [])
function! s:GetWorkingDirectory(buffer) abort
let l:working_directory = ale#Var(a:buffer, 'cs_csc_source')
if !empty(l:working_directory)
return l:working_directory
endif
return expand('#' . a:buffer . ':p:h')
endfunction
function! ale_linters#cs#csc#GetCommand(buffer) abort
" Pass assembly paths via the -lib: parameter.
let l:path_list = ale#Var(a:buffer, 'cs_csc_assembly_path')
let l:lib_option = !empty(l:path_list)
\ ? '/lib:' . join(map(copy(l:path_list), 'ale#Escape(v:val)'), ',')
\ : ''
" Pass paths to DLL files via the -r: parameter.
let l:assembly_list = ale#Var(a:buffer, 'cs_csc_assemblies')
let l:r_option = !empty(l:assembly_list)
\ ? '/r:' . join(map(copy(l:assembly_list), 'ale#Escape(v:val)'), ',')
\ : ''
" register temporary module target file with ale
" register temporary module target file with ALE.
let l:out = ale#command#CreateFile(a:buffer)
" The code is compiled as a module and the output is redirected to a
" temporary file.
return ale#path#CdString(s:GetWorkingDirectory(a:buffer))
\ . 'csc /unsafe'
\ . ale#Pad(ale#Var(a:buffer, 'cs_csc_options'))
\ . ale#Pad(l:lib_option)
\ . ale#Pad(l:r_option)
\ . ' /out:' . l:out
\ . ' /t:module'
\ . ' /recurse:' . ale#Escape('*.cs')
endfunction
function! ale_linters#cs#csc#Handle(buffer, lines) abort
" Look for lines like the following.
"
" Tests.cs(12,29): error CSXXXX: ; expected
"
" NOTE: pattern also captures file name as linter compiles all
" files within the source tree rooted at the specified source
" path and not just the file loaded in the buffer
let l:patterns = [
\ '^\v(.+\.cs)\((\d+),(\d+)\)\:\s+([^ ]+)\s+([cC][sS][^ ]+):\s(.+)$',
\ '^\v([^ ]+)\s+([Cc][sS][^ ]+):\s+(.+)$',
\]
let l:output = []
let l:dir = s:GetWorkingDirectory(a:buffer)
for l:match in ale#util#GetMatches(a:lines, l:patterns)
if len(l:match) > 6 && strlen(l:match[5]) > 2 && l:match[5][:1] is? 'CS'
call add(l:output, {
\ 'filename': ale#path#GetAbsPath(l:dir, l:match[1]),
\ 'lnum': l:match[2] + 0,
\ 'col': l:match[3] + 0,
\ 'type': l:match[4] is# 'error' ? 'E' : 'W',
\ 'code': l:match[5],
\ 'text': l:match[6] ,
\})
elseif strlen(l:match[2]) > 2 && l:match[2][:1] is? 'CS'
call add(l:output, {
\ 'filename':'<csc>',
\ 'lnum': -1,
\ 'col': -1,
\ 'type': l:match[1] is# 'error' ? 'E' : 'W',
\ 'code': l:match[2],
\ 'text': l:match[3],
\})
endif
endfor
return l:output
endfunction
call ale#linter#Define('cs',{
\ 'name': 'csc',
\ 'output_stream': 'stdout',
\ 'executable': 'csc',
\ 'command': function('ale_linters#cs#csc#GetCommand'),
\ 'callback': 'ale_linters#cs#csc#Handle',
\ 'lint_file': 1
\})

View file

@ -52,20 +52,34 @@ function! ale_linters#cs#mcsc#Handle(buffer, lines) abort
" NOTE: pattern also captures file name as linter compiles all
" files within the source tree rooted at the specified source
" path and not just the file loaded in the buffer
let l:pattern = '^\v(.+\.cs)\((\d+),(\d+)\)\: ([^ ]+) ([^ ]+): (.+)$'
let l:patterns = [
\ '^\v(.+\.cs)\((\d+),(\d+)\)\:\s+([^ ]+)\s+([cC][sS][^ ]+):\s(.+)$',
\ '^\v([^ ]+)\s+([Cc][sS][^ ]+):\s+(.+)$',
\]
let l:output = []
let l:dir = s:GetWorkingDirectory(a:buffer)
for l:match in ale#util#GetMatches(a:lines, l:pattern)
call add(l:output, {
\ 'filename': ale#path#GetAbsPath(l:dir, l:match[1]),
\ 'lnum': l:match[2] + 0,
\ 'col': l:match[3] + 0,
\ 'type': l:match[4] is# 'error' ? 'E' : 'W',
\ 'code': l:match[5],
\ 'text': l:match[6],
\})
for l:match in ale#util#GetMatches(a:lines, l:patterns)
if len(l:match) > 6 && strlen(l:match[5]) > 2 && l:match[5][:1] is? 'CS'
call add(l:output, {
\ 'filename': ale#path#GetAbsPath(l:dir, l:match[1]),
\ 'lnum': l:match[2] + 0,
\ 'col': l:match[3] + 0,
\ 'type': l:match[4] is# 'error' ? 'E' : 'W',
\ 'code': l:match[5],
\ 'text': l:match[6] ,
\})
elseif strlen(l:match[2]) > 2 && l:match[2][:1] is? 'CS'
call add(l:output, {
\ 'filename':'<mcs>',
\ 'lnum': -1,
\ 'col': -1,
\ 'type': l:match[1] is# 'error' ? 'E' : 'W',
\ 'code': l:match[2],
\ 'text': l:match[3],
\})
endif
endfor
return l:output

View file

@ -6,11 +6,97 @@ In addition to the linters that are provided with ALE, C# code can be checked
with the OmniSharp plugin. See here: https://github.com/OmniSharp/omnisharp-vim
===============================================================================
csc *ale-cs-csc*
The |ale-cs-csc| linter checks for semantic errors when files are opened or
saved.
See |ale-lint-file-linters| for more information on linters which do not
check for problems while you type.
The csc linter uses the mono csc compiler providing full c# 7 and newer
support to generate a temporary module target file (/t:module). The module
includes including all '*.cs' files contained in the directory tree rooted
at the path defined by the |g:ale_cs_csc_source| or |b:ale_cs_csc_source|
variabl and all sub directories.
It will in future replace the |ale-cs-mcs| and |ale-cs-mcsc| linters as both
utilizer the mcsc compiler which according to mono porject ist further
developed and as of writint these lines only receives maintenance updates.
The down is that the csc compiler does not support the -sytax option any more
and therefore |ale-cs-csc| linter doese not offer any as you type syntax
checking like the |ale-cs-mcsc| linter doesn't.
The paths to search for additional assembly files can be specified using the
|g:ale_cs_csc_assembly_path| or |b:ale_cs_csc_assembly_path| variables.
NOTE: ALE will not find any errors in files apart from syntax errors if any
one of the source files contains a syntax error. Syntax errors must be fixed
first before other errors will be shown.
g:ale_cs_csc_options *g:ale_cs_csc_options*
*b:ale_cs_csc_options*
Type: |String|
Default: `''`
This option can be set to pass additional arguments to the `csc` compiler.
For example, to add the dotnet package which is not added per default: >
let g:ale_cs_mcs_options = ' /warn:4 /langversion:7.2'
<
NOTE: the `/unsafe` option is always passed to `csc`.
g:ale_cs_csc_source *g:ale_cs_csc_source*
*b:ale_cs_csc_source*
Type: |String|
Default: `''`
This variable defines the root path of the directory tree searched for the
'*.cs' files to be linted. If this option is empty, the source file's
directory will be used.
NOTE: Currently it is not possible to specify sub directories and
directory sub trees which shall not be searched for *.cs files.
g:ale_cs_csc_assembly_path *g:ale_cs_csc_assembly_path*
*b:ale_cs_csc_assembly_path*
Type: |List|
Default: `[]`
This variable defines a list of path strings to be searched for external
assembly files. The list is passed to the csc compiler using the `/lib:`
flag.
g:ale_cs_csc_assemblies *g:ale_cs_csc_assemblies*
*b:ale_cs_csc_assemblies*
Type: |List|
Default: `[]`
This variable defines a list of external assembly (*.dll) files required
by the mono mcs compiler to generate a valid module target. The list is
passed the csc compiler using the `/r:` flag.
For example: >
" Compile C# programs with the Unity engine DLL file on Mac.
let g:ale_cs_mcsc_assemblies = [
\ '/Applications/Unity/Unity.app/Contents/Frameworks/Managed/UnityEngine.dll',
\ 'path-to-unityproject/obj/Debug',
\]
<
===============================================================================
mcs *ale-cs-mcs*
The `mcs` linter looks only for syntax errors while you type. See |ale-cs-mcsc|
for the separately configured linter for checking for semantic errors.
The `mcs` linter looks only for syntax errors while you type. See
|ale-cs-mcsc| for the separately configured linter for checking for semantic
errors.
g:ale_cs_mcs_options *g:ale_cs_mcs_options*

View file

@ -53,6 +53,7 @@ Notes:
* `gcc`
* `uncrustify`
* C#
* `csc`!!
* `mcs`
* `mcsc`!!
* `uncrustify`

View file

@ -1975,6 +1975,7 @@ documented in additional help files.
uncrustify............................|ale-cpp-uncrustify|
ccls..................................|ale-cpp-ccls|
c#......................................|ale-cs-options|
csc...................................|ale-cs-csc|
mcs...................................|ale-cs-mcs|
mcsc..................................|ale-cs-mcsc|
uncrustify............................|ale-cs-uncrustify|

View file

@ -62,6 +62,7 @@ formatting.
* [gcc](https://gcc.gnu.org/)
* [uncrustify](https://github.com/uncrustify/uncrustify)
* C#
* [csc](http://www.mono-project.com/docs/about-mono/languages/csharp/) :floppy_disk: see:`help ale-cs-csc` for details and configuration
* [mcs](http://www.mono-project.com/docs/about-mono/languages/csharp/) see:`help ale-cs-mcs` for details
* [mcsc](http://www.mono-project.com/docs/about-mono/languages/csharp/) :floppy_disk: see:`help ale-cs-mcsc` for details and configuration
* [uncrustify](https://github.com/uncrustify/uncrustify)

View file

@ -0,0 +1,47 @@
Before:
call ale#assert#SetUpLinterTest('cs', 'csc')
After:
call ale#assert#TearDownLinterTest()
Execute(The csc linter should return the correct default command):
AssertLinter 'csc', ale#path#CdString(g:dir)
\ . 'csc /unsafe /out:TEMP /t:module /recurse:' . ale#Escape('*.cs')
Execute(The options should be be used in the command):
let g:ale_cs_csc_options = ''
AssertLinter 'csc', ale#path#CdString(g:dir)
\ . 'csc /unsafe /out:TEMP /t:module /recurse:' . ale#Escape('*.cs')
Execute(The souce path should be be used in the command):
let g:ale_cs_csc_source = '../foo/bar'
AssertLinter 'csc', ale#path#CdString('../foo/bar')
\ . 'csc /unsafe /out:TEMP /t:module /recurse:' . ale#Escape('*.cs')
Execute(The list of search pathes for assemblies should be be used in the command if not empty):
let g:ale_cs_csc_assembly_path = ['/usr/lib/mono', '../foo/bar']
AssertLinter 'csc', ale#path#CdString(g:dir)
\ . 'csc /unsafe'
\ . ' /lib:' . ale#Escape('/usr/lib/mono') . ',' . ale#Escape('../foo/bar')
\ . ' /out:TEMP /t:module /recurse:' . ale#Escape('*.cs')
let g:ale_cs_csc_assembly_path = []
AssertLinter 'csc', ale#path#CdString(g:dir)
\ . 'csc /unsafe /out:TEMP /t:module /recurse:' . ale#Escape('*.cs')
Execute(The list of assemblies should be be used in the command if not empty):
let g:ale_cs_csc_assemblies = ['foo.dll', 'bar.dll']
AssertLinter 'csc', ale#path#CdString(g:dir)
\ . 'csc /unsafe'
\ . ' /r:' . ale#Escape('foo.dll') . ',' . ale#Escape('bar.dll')
\ . ' /out:TEMP /t:module /recurse:' . ale#Escape('*.cs')
let g:ale_cs_csc_assemblies = []
AssertLinter 'csc', ale#path#CdString(g:dir)
\ . 'csc /unsafe /out:TEMP /t:module /recurse:' . ale#Escape('*.cs')

View file

@ -0,0 +1,98 @@
Before:
Save g:ale_cs_csc_source
unlet! g:ale_cs_csc_source
call ale#test#SetDirectory('/testplugin/test/handler')
call ale#test#SetFilename('Test.cs')
runtime ale_linters/cs/csc.vim
After:
unlet! g:ale_cs_csc_source
call ale#test#RestoreDirectory()
call ale#linter#Reset()
Execute(The csc handler should work with the default of the buffer's directory):
AssertEqual
\ [
\ {
\ 'lnum': 12,
\ 'col' : 29,
\ 'text': '; expected',
\ 'code': 'CS1001',
\ 'type': 'E',
\ 'filename': ale#path#Simplify(g:dir . '/Test.cs'),
\ },
\ ],
\ ale_linters#cs#csc#Handle(bufnr(''), [
\ 'Test.cs(12,29): error CS1001: ; expected',
\ 'Compilation failed: 2 error(s), 1 warnings',
\ ])
Execute(The csc handler should handle cannot find symbol errors):
let g:ale_cs_csc_source = '/home/foo/project/bar'
AssertEqual
\ [
\ {
\ 'lnum': 12,
\ 'col' : 29,
\ 'text': '; expected',
\ 'code': 'CS1001',
\ 'type': 'E',
\ 'filename': ale#path#Simplify('/home/foo/project/bar/Test.cs'),
\ },
\ {
\ 'lnum': 101,
\ 'col': 0,
\ 'text': 'Unexpected processor directive (no #if for this #endif)',
\ 'code': 'CS1028',
\ 'type': 'E',
\ 'filename': ale#path#Simplify('/home/foo/project/bar/Test.cs'),
\ },
\ {
\ 'lnum': 10,
\ 'col': 12,
\ 'text': 'some warning',
\ 'code': 'CS0123',
\ 'type': 'W',
\ 'filename': ale#path#Simplify('/home/foo/project/bar/Test.cs'),
\ },
\ ],
\ ale_linters#cs#csc#Handle(bufnr(''), [
\ 'Test.cs(12,29): error CS1001: ; expected',
\ 'Test.cs(101,0): error CS1028: Unexpected processor directive (no #if for this #endif)',
\ 'Test.cs(10,12): warning CS0123: some warning',
\ 'Compilation failed: 2 error(s), 1 warnings',
\ ])
Execute(The csc handler should handle non file specific compiler errors without reporting overal status report as error):
let g:ale_cs_csc_source = '/home/foo/project/bar'
AssertEqual
\ [
\ {
\ 'lnum': -1,
\ 'col' : -1,
\ 'text': 'No source files specified.',
\ 'code': 'CS2008',
\ 'type': 'W',
\ 'filename': '<csc>',
\ },
\ {
\ 'lnum': -1,
\ 'col': -1,
\ 'text': 'Outputs without source must have the /out option specified',
\ 'code': 'CS1562',
\ 'type': 'E',
\ 'filename': '<csc>',
\ },
\ ],
\ ale_linters#cs#csc#Handle(bufnr(''), [
\ 'Microsoft (R) Visual C# Compiler version 2.8.2.62916 (2ad4aabc)',
\ 'Copyright (C) Microsoft Corporation. All rights reserved.',
\ 'warning CS2008: No source files specified.',
\ 'error CS1562: Outputs without source must have the /out option specified',
\ ])

View file

@ -67,3 +67,22 @@ Execute(The mcs handler should handle cannot find symbol errors):
\ 'Test.cs(10,12): warning CS0123: some warning',
\ 'Compilation failed: 2 error(s), 1 warnings',
\ ])
Execute(The mcsc handler should handle non file specific compiler errors without reporting overal status report as error):
let g:ale_cs_mcsc_source = '/home/foo/project/bar'
AssertEqual
\ [
\ {
\ 'lnum': -1,
\ 'col' : -1,
\ 'text': 'No files to compile were specified',
\ 'code': 'CS2008',
\ 'type': 'E',
\ 'filename': '<mcs>',
\ },
\ ],
\ ale_linters#cs#mcsc#Handle(bufnr(''), [
\ 'error CS2008: No files to compile were specified',
\ 'Compilation failed: 1 error(s), 0 warnings',
\ ])