diff --git a/autoload/ale/c.vim b/autoload/ale/c.vim index b9f94399..f6ad7deb 100644 --- a/autoload/ale/c.vim +++ b/autoload/ale/c.vim @@ -1,6 +1,8 @@ " Author: gagbo , w0rp " Description: Functions for integrating with C-family linters. +let s:sep = has('win32') ? '\' : '/' + function! ale#c#FindProjectRoot(buffer) abort for l:project_filename in ['.git/HEAD', 'configure', 'Makefile', 'CMakeLists.txt'] let l:full_path = ale#path#FindNearestFile(a:buffer, l:project_filename) @@ -47,7 +49,7 @@ function! ale#c#FindLocalHeaderPaths(buffer) abort " If we find an 'include' directory in the project root, then use that. if isdirectory(l:project_root . '/include') - return [ale#path#Simplify(l:project_root . '/include')] + return [ale#path#Simplify(l:project_root . s:sep . 'include')] endif return [] @@ -79,7 +81,7 @@ let g:ale_c_build_dir_names = get(g:, 'ale_c_build_dir_names', [ function! ale#c#FindCompileCommands(buffer) abort for l:path in ale#path#Upwards(expand('#' . a:buffer . ':p:h')) for l:dirname in ale#Var(a:buffer, 'c_build_dir_names') - let l:c_build_dir = l:path . '/' . l:dirname + let l:c_build_dir = l:path . s:sep . l:dirname if filereadable(l:c_build_dir . '/compile_commands.json') return l:c_build_dir diff --git a/autoload/ale/path.vim b/autoload/ale/path.vim index 57e607a5..b0f4dca6 100644 --- a/autoload/ale/path.vim +++ b/autoload/ale/path.vim @@ -1,14 +1,23 @@ " Author: w0rp " Description: Functions for working with paths in the filesystem. +" simplify a path, and fix annoying issues with paths on Windows. +" +" Forward slashes are changed to back slashes so path equality works better. +" +" Paths starting with more than one forward slash are changed to only one +" forward slash, to prevent the paths being treated as special MSYS paths. function! ale#path#Simplify(path) abort - " //foo is turned into /foo to stop Windows doing stupid things with - " search paths. - return substitute(simplify(a:path), '^//\+', '/', 'g') " no-custom-checks + if has('unix') + return substitute(simplify(a:path), '^//\+', '/', 'g') " no-custom-checks + endif + + let l:win_path = substitute(a:path, '/', '\\', 'g') + + return substitute(simplify(l:win_path), '^\\\+', '\', 'g') " no-custom-checks endfunction " This function is mainly used for testing. -" Simplify() a path, and change forward slashes to back slashes on Windows. " " If an additional 'add_drive' argument is given, the current drive letter " will be prefixed to any absolute paths on Windows. @@ -16,8 +25,6 @@ function! ale#path#Winify(path, ...) abort let l:new_path = ale#path#Simplify(a:path) if has('win32') - let l:new_path = substitute(l:new_path, '/', '\\', 'g') - " Add a drive letter to \foo\bar paths, if needed. if a:0 && a:1 is# 'add_drive' && l:new_path[:0] is# '\' let l:new_path = fnamemodify('.', ':p')[:1] . l:new_path @@ -86,6 +93,10 @@ endfunction " Return 1 if a path is an absolute path. function! ale#path#IsAbsolute(filename) abort + if has('win32') && a:filename[:0] is# '\' + return 1 + endif + " Check for /foo and C:\foo, etc. return a:filename[:0] is# '/' || a:filename[1:2] is# ':\' endfunction @@ -103,7 +114,7 @@ endfunction " directory, return the absolute path to the file. function! ale#path#GetAbsPath(base_directory, filename) abort if ale#path#IsAbsolute(a:filename) - return a:filename + return ale#path#Simplify(a:filename) endif let l:sep = has('win32') ? '\' : '/' @@ -145,8 +156,8 @@ endfunction " Given a path, return every component of the path, moving upwards. function! ale#path#Upwards(path) abort - let l:pattern = ale#Has('win32') ? '\v/+|\\+' : '\v/+' - let l:sep = ale#Has('win32') ? '\' : '/' + let l:pattern = has('win32') ? '\v/+|\\+' : '\v/+' + let l:sep = has('win32') ? '\' : '/' let l:parts = split(ale#path#Simplify(a:path), l:pattern) let l:path_list = [] @@ -155,7 +166,7 @@ function! ale#path#Upwards(path) abort let l:parts = l:parts[:-2] endwhile - if ale#Has('win32') && a:path =~# '^[a-zA-z]:\' + if has('win32') && a:path =~# '^[a-zA-z]:\' " Add \ to C: for C:\, etc. let l:path_list[-1] .= '\' elseif a:path[0] is# '/' diff --git a/test/handler/test_javac_handler.vader b/test/handler/test_javac_handler.vader index 3997b42c..6189e6e1 100644 --- a/test/handler/test_javac_handler.vader +++ b/test/handler/test_javac_handler.vader @@ -12,33 +12,33 @@ Execute(The javac handler should handle cannot find symbol errors): AssertEqual \ [ \ { - \ 'filename': '/tmp/vLPr4Q5/33/foo.java', + \ 'filename': ale#path#Simplify('/tmp/vLPr4Q5/33/foo.java'), \ 'lnum': 1, \ 'text': 'error: some error', \ 'type': 'E', \ }, \ { - \ 'filename': '/tmp/vLPr4Q5/33/foo.java', + \ 'filename': ale#path#Simplify('/tmp/vLPr4Q5/33/foo.java'), \ 'lnum': 2, \ 'col': 5, \ 'text': 'error: cannot find symbol: BadName', \ 'type': 'E', \ }, \ { - \ 'filename': '/tmp/vLPr4Q5/33/foo.java', + \ 'filename': ale#path#Simplify('/tmp/vLPr4Q5/33/foo.java'), \ 'lnum': 34, \ 'col': 5, \ 'text': 'error: cannot find symbol: BadName2', \ 'type': 'E', \ }, \ { - \ 'filename': '/tmp/vLPr4Q5/33/foo.java', + \ 'filename': ale#path#Simplify('/tmp/vLPr4Q5/33/foo.java'), \ 'lnum': 37, \ 'text': 'warning: some warning', \ 'type': 'W', \ }, \ { - \ 'filename': '/tmp/vLPr4Q5/33/foo.java', + \ 'filename': ale#path#Simplify('/tmp/vLPr4Q5/33/foo.java'), \ 'lnum': 42, \ 'col': 11, \ 'text': 'error: cannot find symbol: bar()', diff --git a/test/handler/test_tslint_handler.vader b/test/handler/test_tslint_handler.vader index bbaef837..8d263efa 100644 --- a/test/handler/test_tslint_handler.vader +++ b/test/handler/test_tslint_handler.vader @@ -287,9 +287,9 @@ Execute(The tslint handler should not report no-implicit-dependencies errors): Execute(The tslint handler should set filename keys for temporary files): " The temporay filename below is hacked into being a relative path so we can " test that we resolve the temporary filename first. - let b:relative_to_root = substitute(expand('%:p'), '\v[^/\\]*([/\\])[^/\\]*', has('win32') ? '..\' : '../', 'g') + let b:relative_to_root = substitute(expand('%:p'), '\v[^/\\]*([/\\])[^/\\]*', '../', 'g') let b:tempname_suffix = substitute(tempname(), '^\v([A-Z]:)?[/\\]', '', '') - let b:relative_tempname = b:relative_to_root . b:tempname_suffix + let b:relative_tempname = substitute(b:relative_to_root . b:tempname_suffix, '\\', '/', 'g') AssertEqual \ [ diff --git a/test/test_c_import_paths.vader b/test/test_c_import_paths.vader index af185eae..21e49a3a 100644 --- a/test/test_c_import_paths.vader +++ b/test/test_c_import_paths.vader @@ -40,7 +40,7 @@ Execute(The C GCC handler should include 'include' directories for projects with \ ale#Escape('gcc') \ . ' -S -x c -fsyntax-only ' \ . '-iquote ' . ale#Escape(ale#path#Winify(g:dir . '/test_c_projects/makefile_project/subdir')) . ' ' - \ . ' -I' . ale#Escape(ale#path#Winify(g:dir . '/test_c_projects/makefile_project') . '/include') . ' ' + \ . ' -I' . ale#Escape(ale#path#Winify(g:dir . '/test_c_projects/makefile_project/include')) . ' ' \ . ' -' \ , ale_linters#c#gcc#GetCommand(bufnr('')) @@ -53,7 +53,7 @@ Execute(The C GCC handler should include 'include' directories for projects with \ ale#Escape('gcc') \ . ' -S -x c -fsyntax-only ' \ . '-iquote ' . ale#Escape(ale#path#Winify(g:dir . '/test_c_projects/configure_project/subdir')) . ' ' - \ . ' -I' . ale#Escape(ale#path#Winify(g:dir . '/test_c_projects/configure_project') . '/include') . ' ' + \ . ' -I' . ale#Escape(ale#path#Winify(g:dir . '/test_c_projects/configure_project/include')) . ' ' \ . ' -' \ , ale_linters#c#gcc#GetCommand(bufnr('')) @@ -92,7 +92,7 @@ Execute(The C Clang handler should include 'include' directories for projects wi \ ale#Escape('clang') \ . ' -S -x c -fsyntax-only ' \ . '-iquote ' . ale#Escape(ale#path#Winify(g:dir . '/test_c_projects/makefile_project/subdir')) . ' ' - \ . ' -I' . ale#Escape(ale#path#Winify(g:dir . '/test_c_projects/makefile_project') . '/include') . ' ' + \ . ' -I' . ale#Escape(ale#path#Winify(g:dir . '/test_c_projects/makefile_project/include')) . ' ' \ . ' -' \ , ale_linters#c#clang#GetCommand(bufnr('')) @@ -144,7 +144,7 @@ Execute(The C++ GCC handler should include 'include' directories for projects wi \ ale#Escape('gcc') \ . ' -S -x c++ -fsyntax-only ' \ . '-iquote ' . ale#Escape(ale#path#Winify(g:dir . '/test_c_projects/makefile_project/subdir')) . ' ' - \ . ' -I' . ale#Escape(ale#path#Winify(g:dir . '/test_c_projects/makefile_project') . '/include') . ' ' + \ . ' -I' . ale#Escape(ale#path#Winify(g:dir . '/test_c_projects/makefile_project/include')) . ' ' \ . ' -' \ , ale_linters#cpp#gcc#GetCommand(bufnr('')) @@ -157,7 +157,7 @@ Execute(The C++ GCC handler should include 'include' directories for projects wi \ ale#Escape('gcc') \ . ' -S -x c++ -fsyntax-only ' \ . '-iquote ' . ale#Escape(ale#path#Winify(g:dir . '/test_c_projects/configure_project/subdir')) . ' ' - \ . ' -I' . ale#Escape(ale#path#Winify(g:dir . '/test_c_projects/configure_project') . '/include') . ' ' + \ . ' -I' . ale#Escape(ale#path#Winify(g:dir . '/test_c_projects/configure_project/include')) . ' ' \ . ' -' \ , ale_linters#cpp#gcc#GetCommand(bufnr('')) @@ -196,7 +196,7 @@ Execute(The C++ Clang handler should include 'include' directories for projects \ ale#Escape('clang++') \ . ' -S -x c++ -fsyntax-only ' \ . '-iquote ' . ale#Escape(ale#path#Winify(g:dir . '/test_c_projects/makefile_project/subdir')) . ' ' - \ . ' -I' . ale#Escape(ale#path#Winify(g:dir . '/test_c_projects/makefile_project') . '/include') . ' ' + \ . ' -I' . ale#Escape(ale#path#Winify(g:dir . '/test_c_projects/makefile_project/include')) . ' ' \ . ' -' \ , ale_linters#cpp#clang#GetCommand(bufnr('')) @@ -209,7 +209,7 @@ Execute(The C++ Clang handler should include 'include' directories for projects \ ale#Escape('clang++') \ . ' -S -x c++ -fsyntax-only ' \ . '-iquote ' . ale#Escape(ale#path#Winify(g:dir . '/test_c_projects/configure_project/subdir')) . ' ' - \ . ' -I' . ale#Escape(ale#path#Winify(g:dir . '/test_c_projects/configure_project') . '/include') . ' ' + \ . ' -I' . ale#Escape(ale#path#Winify(g:dir . '/test_c_projects/configure_project/include')) . ' ' \ . ' -' \ , ale_linters#cpp#clang#GetCommand(bufnr('')) @@ -256,7 +256,7 @@ Execute(The C++ Clang handler shoud use the include directory based on the .git \ ale#Escape('clang++') \ . ' -S -x c++ -fsyntax-only ' \ . '-iquote ' . ale#Escape(ale#path#Winify(g:dir . '/test_c_projects/git_and_nested_makefiles/src')) . ' ' - \ . ' -I' . ale#Escape(ale#path#Winify(g:dir . '/test_c_projects/git_and_nested_makefiles') . '/include') . ' ' + \ . ' -I' . ale#Escape(ale#path#Winify(g:dir . '/test_c_projects/git_and_nested_makefiles/include')) . ' ' \ . ' -' \ , ale_linters#cpp#clang#GetCommand(bufnr('')) @@ -268,7 +268,7 @@ Execute(The C++ ClangTidy handler should include json folders for projects with AssertEqual \ ale#Escape('clang-tidy') \ . ' -checks=' . ale#Escape('*') . ' %s ' - \ . '-p ' . ale#Escape(ale#path#Winify(g:dir . '/test_c_projects/json_project') . '/build') + \ . '-p ' . ale#Escape(ale#path#Winify(g:dir . '/test_c_projects/json_project/build')) \ , ale_linters#cpp#clangtidy#GetCommand(bufnr('')) Execute(Move .git/HEAD back): diff --git a/test/test_get_abspath.vader b/test/test_get_abspath.vader index 5f813804..7e1b5930 100644 --- a/test/test_get_abspath.vader +++ b/test/test_get_abspath.vader @@ -1,15 +1,29 @@ Execute(Relative paths should be resolved correctly): AssertEqual - \ '/foo/bar/baz/whatever.txt', + \ has('win32') ? '\foo\bar\baz\whatever.txt' : '/foo/bar/baz/whatever.txt', \ ale#path#GetAbsPath('/foo/bar/xyz', '../baz/whatever.txt') AssertEqual - \ has('win32') ? '/foo/bar/xyz\whatever.txt' : '/foo/bar/xyz/whatever.txt', + \ has('win32') ? '\foo\bar\xyz\whatever.txt' : '/foo/bar/xyz/whatever.txt', \ ale#path#GetAbsPath('/foo/bar/xyz', './whatever.txt') AssertEqual - \ has('win32') ? '/foo/bar/xyz\whatever.txt' : '/foo/bar/xyz/whatever.txt', + \ has('win32') ? '\foo\bar\xyz\whatever.txt' : '/foo/bar/xyz/whatever.txt', \ ale#path#GetAbsPath('/foo/bar/xyz', 'whatever.txt') + if has('win32') + AssertEqual + \ 'C:\foo\bar\baz\whatever.txt', + \ ale#path#GetAbsPath('C:\foo\bar\baz\xyz', '../whatever.txt') + endif + Execute(Absolute paths should be resolved correctly): AssertEqual - \ '/ding/dong', + \ has('win32') ? '\ding\dong' : '/ding/dong', \ ale#path#GetAbsPath('/foo/bar/xyz', '/ding/dong') + + AssertEqual + \ has('win32') ? '\ding\dong' : '/ding/dong', + \ ale#path#GetAbsPath('/foo/bar/xyz', '//ding/dong') + + if has('win32') + AssertEqual '\ding', ale#path#GetAbsPath('/foo/bar/xyz', '\\ding') + endif diff --git a/test/test_path_upwards.vader b/test/test_path_upwards.vader index 8b81a109..cd461a28 100644 --- a/test/test_path_upwards.vader +++ b/test/test_path_upwards.vader @@ -1,52 +1,48 @@ -After: - let g:ale_has_override = {} +Execute(ale#path#Upwards should return the correct path components): + if has('unix') + " Absolute paths should include / on the end. + AssertEqual + \ ['/foo/bar/baz', '/foo/bar', '/foo', '/'], + \ ale#path#Upwards('/foo/bar/baz') + AssertEqual + \ ['/foo/bar/baz', '/foo/bar', '/foo', '/'], + \ ale#path#Upwards('/foo/bar/baz///') + " Relative paths do not. + AssertEqual + \ ['foo/bar/baz', 'foo/bar', 'foo'], + \ ale#path#Upwards('foo/bar/baz') + AssertEqual + \ ['foo2/bar', 'foo2'], + \ ale#path#Upwards('foo//..////foo2////bar') + " Expect an empty List for empty strings. + AssertEqual [], ale#path#Upwards('') + endif -Execute(ale#path#Upwards should return the correct path components for Unix): - let g:ale_has_override = {'win32': 0} - - " Absolute paths should include / on the end. - AssertEqual - \ ['/foo/bar/baz', '/foo/bar', '/foo', '/'], - \ ale#path#Upwards('/foo/bar/baz') - AssertEqual - \ ['/foo/bar/baz', '/foo/bar', '/foo', '/'], - \ ale#path#Upwards('/foo/bar/baz///') - " Relative paths do not. - AssertEqual - \ ['foo/bar/baz', 'foo/bar', 'foo'], - \ ale#path#Upwards('foo/bar/baz') - AssertEqual - \ ['foo2/bar', 'foo2'], - \ ale#path#Upwards('foo//..////foo2////bar') - " Expect an empty List for empty strings. - AssertEqual [], ale#path#Upwards('') - -Execute(ale#path#Upwards should return the correct path components for Windows): - let g:ale_has_override = {'win32': 1} - - AssertEqual - \ ['C:\foo\bar\baz', 'C:\foo\bar', 'C:\foo', 'C:\'], - \ ale#path#Upwards('C:\foo\bar\baz') - AssertEqual - \ ['C:\foo\bar\baz', 'C:\foo\bar', 'C:\foo', 'C:\'], - \ ale#path#Upwards('C:\foo\bar\baz\\\') - AssertEqual - \ ['/foo\bar\baz', '/foo\bar', '/foo', '/'], - \ ale#path#Upwards('/foo/bar/baz') - AssertEqual - \ ['foo\bar\baz', 'foo\bar', 'foo'], - \ ale#path#Upwards('foo/bar/baz') - AssertEqual - \ ['foo\bar\baz', 'foo\bar', 'foo'], - \ ale#path#Upwards('foo\bar\baz') - " simplify() is used internally, and should sort out \ paths when actually - " running Windows, which we can't test here. - AssertEqual - \ ['foo2\bar', 'foo2'], - \ ale#path#Upwards('foo//..///foo2////bar') - " Expect an empty List for empty strings. - AssertEqual [], ale#path#Upwards('') - " Paths starting with // return / - AssertEqual - \ ['/foo2\bar', '/foo2', '/'], - \ ale#path#Upwards('//foo//..///foo2////bar') + if has('win32') + AssertEqual + \ ['C:\foo\bar\baz', 'C:\foo\bar', 'C:\foo', 'C:\'], + \ ale#path#Upwards('C:\foo\bar\baz') + AssertEqual + \ ['C:\foo\bar\baz', 'C:\foo\bar', 'C:\foo', 'C:\'], + \ ale#path#Upwards('C:\foo\bar\baz\\\') + AssertEqual + \ ['/foo\bar\baz', '/foo\bar', '/foo', '/'], + \ ale#path#Upwards('/foo/bar/baz') + AssertEqual + \ ['foo\bar\baz', 'foo\bar', 'foo'], + \ ale#path#Upwards('foo/bar/baz') + AssertEqual + \ ['foo\bar\baz', 'foo\bar', 'foo'], + \ ale#path#Upwards('foo\bar\baz') + " simplify() is used internally, and should sort out \ paths when actually + " running Windows, which we can't test here. + AssertEqual + \ ['foo2\bar', 'foo2'], + \ ale#path#Upwards('foo//..///foo2////bar') + " Expect an empty List for empty strings. + AssertEqual [], ale#path#Upwards('') + " Paths starting with // return / + AssertEqual + \ ['/foo2\bar', '/foo2', '/'], + \ ale#path#Upwards('//foo//..///foo2////bar') + endif