diff --git a/ale_linters/cs/csc.vim b/ale_linters/cs/csc.vim new file mode 100644 index 00000000..308abc77 --- /dev/null +++ b/ale_linters/cs/csc.vim @@ -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':'', + \ '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 +\}) diff --git a/ale_linters/cs/mcsc.vim b/ale_linters/cs/mcsc.vim index dd067eba..0e4e5667 100644 --- a/ale_linters/cs/mcsc.vim +++ b/ale_linters/cs/mcsc.vim @@ -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':'', + \ '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 diff --git a/doc/ale-cs.txt b/doc/ale-cs.txt index 01e6348f..abcc43eb 100644 --- a/doc/ale-cs.txt +++ b/doc/ale-cs.txt @@ -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* diff --git a/doc/ale-supported-languages-and-tools.txt b/doc/ale-supported-languages-and-tools.txt index 87e1ecde..ec04d175 100644 --- a/doc/ale-supported-languages-and-tools.txt +++ b/doc/ale-supported-languages-and-tools.txt @@ -53,6 +53,7 @@ Notes: * `gcc` * `uncrustify` * C# + * `csc`!! * `mcs` * `mcsc`!! * `uncrustify` diff --git a/doc/ale.txt b/doc/ale.txt index eea3d102..c1dab120 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -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| diff --git a/supported-tools.md b/supported-tools.md index f96f49a3..802054cd 100644 --- a/supported-tools.md +++ b/supported-tools.md @@ -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) diff --git a/test/command_callback/test_cs_csc_command_callbacks.vader b/test/command_callback/test_cs_csc_command_callbacks.vader new file mode 100644 index 00000000..c21ce209 --- /dev/null +++ b/test/command_callback/test_cs_csc_command_callbacks.vader @@ -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') diff --git a/test/handler/test_csc_handler.vader b/test/handler/test_csc_handler.vader new file mode 100644 index 00000000..3db5b6fd --- /dev/null +++ b/test/handler/test_csc_handler.vader @@ -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': '', + \ }, + \ { + \ 'lnum': -1, + \ 'col': -1, + \ 'text': 'Outputs without source must have the /out option specified', + \ 'code': 'CS1562', + \ 'type': 'E', + \ 'filename': '', + \ }, + \ ], + \ 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', + \ ]) diff --git a/test/handler/test_mcsc_handler.vader b/test/handler/test_mcsc_handler.vader index 8ae47357..c04f7d27 100644 --- a/test/handler/test_mcsc_handler.vader +++ b/test/handler/test_mcsc_handler.vader @@ -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': '', + \ }, + \ ], + \ ale_linters#cs#mcsc#Handle(bufnr(''), [ + \ 'error CS2008: No files to compile were specified', + \ 'Compilation failed: 1 error(s), 0 warnings', + \ ])