diff options
| author | w0rp | 2026-05-16 12:32:08 +0100 |
|---|---|---|
| committer | w0rp | 2026-05-16 12:32:08 +0100 |
| commit | 2d5a15d501d842d7a03b5b7a531e27aa223ac100 (patch) | |
| tree | 1dba9988e621e123eae5276c43c7470d692648ed | |
| parent | f3d85691a540697f9a4f895d4a5dbd67d4dfd8dd (diff) | |
| download | ale-2d5a15d501d842d7a03b5b7a531e27aa223ac100.tar.gz | |
Close #4971 - Add support for ty via LSP
| -rw-r--r-- | ale_linters/python/ty.vim | 63 | ||||
| -rw-r--r-- | autoload/ale/linter.vim | 2 | ||||
| -rw-r--r-- | autoload/ale/python.vim | 1 | ||||
| -rw-r--r-- | doc/ale-python.txt | 74 | ||||
| -rw-r--r-- | doc/ale-supported-languages-and-tools.txt | 1 | ||||
| -rw-r--r-- | doc/ale.txt | 3 | ||||
| -rw-r--r-- | supported-tools.md | 1 | ||||
| -rw-r--r-- | test/linter/test_ty.vader | 91 | ||||
| -rw-r--r-- | test/test-files/python/namespace_package_ty/namespace/foo/bar.py | 0 | ||||
| -rw-r--r-- | test/test-files/python/namespace_package_ty/ty.toml | 0 | ||||
| -rw-r--r-- | test/test-files/python/with_virtualenv/env/Scripts/ty.exe | 0 | ||||
| -rwxr-xr-x | test/test-files/python/with_virtualenv/env/bin/ty | 0 | ||||
| -rw-r--r-- | test/test_filetype_linter_defaults.vader | 2 |
13 files changed, 235 insertions, 3 deletions
diff --git a/ale_linters/python/ty.vim b/ale_linters/python/ty.vim new file mode 100644 index 000000000..9d109893e --- /dev/null +++ b/ale_linters/python/ty.vim @@ -0,0 +1,63 @@ +" Author: w0rp <dev@w0rp.com> +" Description: Astral's Python type checker and language server + +call ale#Set('python_ty_executable', 'ty') +call ale#Set('python_ty_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('python_ty_auto_pipenv', 0) +call ale#Set('python_ty_auto_poetry', 0) +call ale#Set('python_ty_auto_uv', 0) +call ale#Set('python_ty_config', {}) + +function! ale_linters#python#ty#GetExecutable(buffer) abort + if (ale#Var(a:buffer, 'python_auto_pipenv') || ale#Var(a:buffer, 'python_ty_auto_pipenv')) + \ && ale#python#PipenvPresent(a:buffer) + return 'pipenv' + endif + + if (ale#Var(a:buffer, 'python_auto_poetry') || ale#Var(a:buffer, 'python_ty_auto_poetry')) + \ && ale#python#PoetryPresent(a:buffer) + return 'poetry' + endif + + if (ale#Var(a:buffer, 'python_auto_uv') || ale#Var(a:buffer, 'python_ty_auto_uv')) + \ && ale#python#UvPresent(a:buffer) + return 'uv' + endif + + return ale#python#FindExecutable(a:buffer, 'python_ty', ['ty']) +endfunction + +" Force the cwd of the server to be the same as the project root. +function! ale_linters#python#ty#GetCwd(buffer) abort + let l:fake_linter = { + \ 'name': 'ty', + \ 'project_root': function('ale#python#FindProjectRoot'), + \} + let l:root = ale#lsp_linter#FindProjectRoot(a:buffer, l:fake_linter) + + return !empty(l:root) ? l:root : v:null +endfunction + +function! ale_linters#python#ty#GetCommand(buffer) abort + let l:executable = ale_linters#python#ty#GetExecutable(a:buffer) + let l:exec_args = [ + \ ale#Escape(l:executable), + \] + \ + (l:executable =~? '\(pipenv\|poetry\|uv\)$' ? ['run', 'ty'] : []) + \ + [ + \ 'server', + \] + + return join(l:exec_args, ' ') +endfunction + +call ale#linter#Define('python', { +\ 'name': 'ty', +\ 'lsp': 'stdio', +\ 'executable': function('ale_linters#python#ty#GetExecutable'), +\ 'cwd': function('ale_linters#python#ty#GetCwd'), +\ 'command': function('ale_linters#python#ty#GetCommand'), +\ 'project_root': function('ale#python#FindProjectRoot'), +\ 'completion_filter': 'ale#completion#python#CompletionItemFilter', +\ 'lsp_config': {b -> ale#Var(b, 'python_ty_config')}, +\}) diff --git a/autoload/ale/linter.vim b/autoload/ale/linter.vim index 65c5dd0dd..e1f197e45 100644 --- a/autoload/ale/linter.vim +++ b/autoload/ale/linter.vim @@ -54,7 +54,7 @@ let s:default_ale_linters = { \ 'jsonc': ['biome'], \ 'perl': ['perlcritic'], \ 'perl6': [], -\ 'python': ['flake8', 'mypy', 'pylint', 'pyright', 'ruff'], +\ 'python': ['flake8', 'mypy', 'pylint', 'pyright', 'ruff', 'ty'], \ 'rust': ['analyzer', 'cargo'], \ 'spec': [], \ 'text': [], diff --git a/autoload/ale/python.vim b/autoload/ale/python.vim index 5799c7938..e35abe2b7 100644 --- a/autoload/ale/python.vim +++ b/autoload/ale/python.vim @@ -42,6 +42,7 @@ function! ale#python#FindProjectRootIni(buffer) abort \|| filereadable(l:path . '/Pipfile.lock') \|| filereadable(l:path . '/poetry.lock') \|| filereadable(l:path . '/pyproject.toml') + \|| filereadable(l:path . '/ty.toml') \|| filereadable(l:path . '/.tool-versions') \|| filereadable(l:path . '/uv.lock') return resolve(l:path) diff --git a/doc/ale-python.txt b/doc/ale-python.txt index eeea36d5f..c934506db 100644 --- a/doc/ale-python.txt +++ b/doc/ale-python.txt @@ -79,6 +79,7 @@ ALE will look for configuration files with the following filenames. > Pipfile.lock poetry.lock pyproject.toml + ty.toml .tool-versions uv.lock < @@ -2107,6 +2108,79 @@ g:ale_python_ruff_format_auto_uv =============================================================================== +ty *ale-python-ty* + +`ty` will be run from a detected project root, per |ale-python-root|. + + *ale-options.python_ty_executable* + *g:ale_python_ty_executable* + *b:ale_python_ty_executable* +python_ty_executable +g:ale_python_ty_executable + Type: |String| + Default: `'ty'` + + See |ale-integrations-local-executables| + + Set this to `'pipenv'` to invoke `'pipenv` `run` `ty'`. + Set this to `'poetry'` to invoke `'poetry` `run` `ty'`. + Set this to `'uv'` to invoke `'uv` `run` `ty'`. + + *ale-options.python_ty_use_global* + *g:ale_python_ty_use_global* + *b:ale_python_ty_use_global* +python_ty_use_global +g:ale_python_ty_use_global + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + *ale-options.python_ty_auto_pipenv* + *g:ale_python_ty_auto_pipenv* + *b:ale_python_ty_auto_pipenv* +python_ty_auto_pipenv +g:ale_python_ty_auto_pipenv + Type: |Number| + Default: `0` + + Detect whether the file is inside a pipenv, and set the executable to `pipenv` + if true. This is overridden by a manually-set executable. + + *ale-options.python_ty_auto_poetry* + *g:ale_python_ty_auto_poetry* + *b:ale_python_ty_auto_poetry* +python_ty_auto_poetry +g:ale_python_ty_auto_poetry + Type: |Number| + Default: `0` + + Detect whether the file is inside a poetry, and set the executable to `poetry` + if true. This is overridden by a manually-set executable. + + *ale-options.python_ty_auto_uv* + *g:ale_python_ty_auto_uv* + *b:ale_python_ty_auto_uv* +python_ty_auto_uv +g:ale_python_ty_auto_uv + Type: |Number| + Default: `0` + + Set the executable to `uv` if true. This is overridden by a manually-set + executable. + + *ale-options.python_ty_config* + *g:ale_python_ty_config* + *b:ale_python_ty_config* +python_ty_config +g:ale_python_ty_config + Type: |Dictionary| + Default: `{}` + + Dictionary with language server configuration settings for `ty`. + + +=============================================================================== unimport *ale-python-unimport* `unimport` will be run from a detected project root, per |ale-python-root|. diff --git a/doc/ale-supported-languages-and-tools.txt b/doc/ale-supported-languages-and-tools.txt index b53c5c915..90b470b93 100644 --- a/doc/ale-supported-languages-and-tools.txt +++ b/doc/ale-supported-languages-and-tools.txt @@ -563,6 +563,7 @@ Notes: * `reorder-python-imports` * ruff * ruff-format + * `ty` * `unimport` * `vulture`!! * `yapf` diff --git a/doc/ale.txt b/doc/ale.txt index d3f2b6978..53453b54f 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -1944,7 +1944,7 @@ g:ale_linters \ 'jsonc': [], \ 'perl': ['perlcritic'], \ 'perl6': [], - \ 'python': ['flake8', 'mypy', 'pylint', 'pyright', 'ruff'], + \ 'python': ['flake8', 'mypy', 'pylint', 'pyright', 'ruff', 'ty'], \ 'rust': ['analyzer', 'cargo'], \ 'spec': [], \ 'text': [], @@ -3882,6 +3882,7 @@ documented in additional help files. reorder-python-imports................|ale-python-reorder_python_imports| ruff..................................|ale-python-ruff| ruff-format...........................|ale-python-ruff-format| + ty....................................|ale-python-ty| unimport..............................|ale-python-unimport| vulture...............................|ale-python-vulture| yapf..................................|ale-python-yapf| diff --git a/supported-tools.md b/supported-tools.md index fad93f2ff..9e4a2cf2e 100644 --- a/supported-tools.md +++ b/supported-tools.md @@ -573,6 +573,7 @@ formatting. * [reorder-python-imports](https://github.com/asottile/reorder_python_imports) * [ruff](https://github.com/charliermarsh/ruff) * [ruff-format](https://docs.astral.sh/ruff/formatter/) + * [ty](https://github.com/astral-sh/ty) :speech_balloon: * [unimport](https://github.com/hakancelik96/unimport) * [vulture](https://github.com/jendrikseipp/vulture) :warning: :floppy_disk: * [yapf](https://github.com/google/yapf) diff --git a/test/linter/test_ty.vader b/test/linter/test_ty.vader new file mode 100644 index 000000000..ecd1d0ceb --- /dev/null +++ b/test/linter/test_ty.vader @@ -0,0 +1,91 @@ +Before: + call ale#assert#SetUpLinterTest('python', 'ty') + Save b:ale_python_auto_virtualenv + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + +After: + unlet! b:bin_dir + unlet! b:executable + + call ale#test#SetFilename('..') + call ale#assert#TearDownLinterTest() + +Execute(The default ty command should be correct): + call ale#test#SetFilename('./foo.py') + + AssertLinter 'ty', ale#Escape('ty') . ' server' + +Execute(The ty executable should be configurable): + let g:ale_python_ty_executable = '~/.local/bin/ty' + + AssertLinter '~/.local/bin/ty', + \ ale#Escape('~/.local/bin/ty') . ' server' + +Execute(The cwd and project root should be detected correctly): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + + AssertLinterCwd ale#test#GetFilename('../test-files/python/with_virtualenv/subdir') + AssertLSPProject ale#test#GetFilename('../test-files/python/with_virtualenv/subdir') + +Execute(FindProjectRoot should detect the project root directory via ty.toml): + call ale#test#SetFilename('../test-files/python/namespace_package_ty/namespace/foo/bar.py') + + AssertEqual + \ ale#path#Simplify(g:dir . '/../test-files/python/namespace_package_ty'), + \ ale#python#FindProjectRoot(bufnr('')) + +Execute(The ty executable should be run from the virtualenv path): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + + let b:executable = ale#path#Simplify( + \ g:dir . '/../test-files/python/with_virtualenv/env/' . b:bin_dir . '/ty' + \) + + AssertLinter b:executable, ale#Escape(b:executable) . ' server' + +Execute(You should be able to override the ty virtualenv lookup): + call ale#test#SetFilename('../test-files/python/with_virtualenv/subdir/foo/bar.py') + + let g:ale_python_ty_use_global = 1 + + AssertLinter 'ty', ale#Escape('ty') . ' server' + +Execute(Setting executable to 'pipenv' appends 'run ty'): + let g:ale_python_ty_executable = 'path/to/pipenv' + call ale#test#SetFilename('../test-files/dummy') + + AssertLinter 'path/to/pipenv', + \ ale#Escape('path/to/pipenv') . ' run ty server' + +Execute(Pipenv is detected when python_ty_auto_pipenv is set): + let g:ale_python_ty_auto_pipenv = 1 + call ale#test#SetFilename('../test-files/python/pipenv/whatever.py') + + AssertLinter 'pipenv', + \ ale#Escape('pipenv') . ' run ty server' + +Execute(Setting executable to 'poetry' appends 'run ty'): + let g:ale_python_ty_executable = 'path/to/poetry' + + AssertLinter 'path/to/poetry', + \ ale#Escape('path/to/poetry') . ' run ty server' + +Execute(Poetry is detected when python_ty_auto_poetry is set): + let g:ale_python_ty_auto_poetry = 1 + call ale#test#SetFilename('../test-files/python/poetry/whatever.py') + + AssertLinter 'poetry', + \ ale#Escape('poetry') . ' run ty server' + +Execute(uv is detected when python_ty_auto_uv is set): + let g:ale_python_ty_auto_uv = 1 + call ale#test#SetFilename('../test-files/python/uv/whatever.py') + + AssertLinter 'uv', + \ ale#Escape('uv') . ' run ty server' + +Execute(Should accept configuration settings): + AssertLSPConfig {} + let b:ale_python_ty_config = {'ty': {'diagnosticMode': 'workspace'}} + AssertLSPConfig {'ty': {'diagnosticMode': 'workspace'}} diff --git a/test/test-files/python/namespace_package_ty/namespace/foo/bar.py b/test/test-files/python/namespace_package_ty/namespace/foo/bar.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/test/test-files/python/namespace_package_ty/namespace/foo/bar.py diff --git a/test/test-files/python/namespace_package_ty/ty.toml b/test/test-files/python/namespace_package_ty/ty.toml new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/test/test-files/python/namespace_package_ty/ty.toml diff --git a/test/test-files/python/with_virtualenv/env/Scripts/ty.exe b/test/test-files/python/with_virtualenv/env/Scripts/ty.exe new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/test/test-files/python/with_virtualenv/env/Scripts/ty.exe diff --git a/test/test-files/python/with_virtualenv/env/bin/ty b/test/test-files/python/with_virtualenv/env/bin/ty new file mode 100755 index 000000000..e69de29bb --- /dev/null +++ b/test/test-files/python/with_virtualenv/env/bin/ty diff --git a/test/test_filetype_linter_defaults.vader b/test/test_filetype_linter_defaults.vader index ee4a5df00..180207fda 100644 --- a/test/test_filetype_linter_defaults.vader +++ b/test/test_filetype_linter_defaults.vader @@ -83,7 +83,7 @@ Execute(The defaults for the perl6 filetype should be correct): AssertEqual [], GetLinterNames('perl6') Execute(The defaults for the python filetype should be correct): - AssertEqual ['flake8', 'mypy', 'pylint', 'pyright', 'ruff'], GetLinterNames('python') + AssertEqual ['flake8', 'mypy', 'pylint', 'pyright', 'ruff', 'ty'], GetLinterNames('python') let g:ale_linters_explicit = 1 |