def test_jedi_rename(tmp_workspace, config): # pylint: disable=redefined-outer-name # rename the `Test1` class position = {'line': 0, 'character': 6} DOC_URI = uris.from_fs_path(os.path.join(tmp_workspace.root_path, DOC_NAME)) doc = Document(DOC_URI, tmp_workspace) result = pyls_rename(config, tmp_workspace, doc, position, 'ShouldBeRenamed') assert len(result.keys()) == 1 changes = result.get('documentChanges') assert len(changes) == 1 changes = changes[0] assert changes.get('edits') == [{ 'range': { 'start': { 'line': 0, 'character': 0 }, 'end': { 'line': 5, 'character': 0 }, }, 'newText': 'class ShouldBeRenamed():\n pass\n\nclass Test2(ShouldBeRenamed):\n pass\n', }]
def test_numpy_hover(workspace): # Over the blank line no_hov_position = {'line': 1, 'character': 0} # Over 'numpy' in import numpy as np numpy_hov_position_1 = {'line': 2, 'character': 8} # Over 'np' in import numpy as np numpy_hov_position_2 = {'line': 2, 'character': 17} # Over 'np' in np.sin numpy_hov_position_3 = {'line': 3, 'character': 1} # Over 'sin' in np.sin numpy_sin_hov_position = {'line': 3, 'character': 4} doc = Document(DOC_URI, workspace, NUMPY_DOC) contents = '' assert contents in pyls_hover(doc, no_hov_position)['contents'] contents = 'NumPy\n=====\n\nProvides\n' assert contents in pyls_hover(doc, numpy_hov_position_1)['contents'][0] contents = 'NumPy\n=====\n\nProvides\n' assert contents in pyls_hover(doc, numpy_hov_position_2)['contents'][0] contents = 'NumPy\n=====\n\nProvides\n' assert contents in pyls_hover(doc, numpy_hov_position_3)['contents'][0] contents = 'Trigonometric sine, element-wise.\n\n' assert contents in pyls_hover( doc, numpy_sin_hov_position)['contents'][0]
def test_per_file_caching(): # Ensure that diagnostics are cached per-file. with temp_document(DOC) as doc: assert pylint_lint.pyls_lint(doc, True) assert not pylint_lint.pyls_lint(Document(uris.from_fs_path(__file__)), False)
def test_per_file_caching(config, workspace): # Ensure that diagnostics are cached per-file. with temp_document(DOC, workspace) as doc: assert pylint_lint.pyls_lint(config, doc, True) assert not pylint_lint.pyls_lint( config, Document(uris.from_fs_path(__file__), workspace), False)
def test_builtin_definition(config): # Over 'i' in dict cursor_pos = {'line': 8, 'character': 24} # No go-to def for builtins doc = Document(DOC_URI, DOC) assert not pyls_definitions(config, doc, cursor_pos)
def test_flake8_no_checked_file(config): # A bad uri or a non-saved file may cause the flake8 linter to do nothing. # In this situtation, the linter will return an empty list. doc = Document('', DOC) diags = flake8_lint.pyls_lint(config, doc) assert diags == []
def test_rope_rename(tmp_workspace, config): # pylint: disable=redefined-outer-name position = {"line": 0, "character": 6} DOC_URI = uris.from_fs_path(os.path.join(tmp_workspace.root_path, DOC_NAME)) doc = Document(DOC_URI, tmp_workspace) result = pyls_rename(config, tmp_workspace, doc, position, "ShouldBeRenamed") assert len(result.keys()) == 1 changes = result.get("documentChanges") assert len(changes) == 1 changes = changes[0] # Note that this test differs from test_jedi_rename, because rope does not # seem to modify files that haven't been opened with textDocument/didOpen. assert changes.get("edits") == [{ "range": { "start": { "line": 0, "character": 0 }, "end": { "line": 5, "character": 0 }, }, "newText": "class ShouldBeRenamed():\n pass\n\nclass Test2(ShouldBeRenamed):\n pass\n", }]
def test_completion_with_class_objects(config, workspace): doc_text = 'class FOOBAR(Object): pass\nFOOB' com_position = {'line': 1, 'character': 4} doc = Document(DOC_URI, workspace, doc_text) config.capabilities['textDocument'] = { 'completion': { 'completionItem': { 'snippetSupport': True } } } config.update({ 'plugins': { 'jedi_completion': { 'include_params': True, 'include_class_objects': True, } } }) completions = pyls_jedi_completions(config, doc, com_position) assert len(completions) == 2 assert completions[0]['label'] == 'FOOBAR' assert completions[0]['kind'] == lsp.CompletionItemKind.Class assert completions[1]['label'] == 'FOOBAR object' assert completions[1]['kind'] == lsp.CompletionItemKind.TypeParameter
def test_pycodestyle(config): doc = Document(DOC_URI, DOC) diags = pycodestyle_lint.pyls_lint(config, doc) assert all([d['source'] == 'pycodestyle' for d in diags]) # One we're expecting is: msg = 'W191 indentation contains tabs' mod_import = [d for d in diags if d['message'] == msg][0] assert mod_import['code'] == 'W191' assert mod_import['severity'] == lsp.DiagnosticSeverity.Warning assert mod_import['range']['start'] == {'line': 3, 'character': 0} assert mod_import['range']['end'] == {'line': 3, 'character': 6} msg = 'W391 blank line at end of file' mod_import = [d for d in diags if d['message'] == msg][0] assert mod_import['code'] == 'W391' assert mod_import['severity'] == lsp.DiagnosticSeverity.Warning assert mod_import['range']['start'] == {'line': 7, 'character': 0} assert mod_import['range']['end'] == {'line': 7, 'character': 1} msg = "E201 whitespace after '('" mod_import = [d for d in diags if d['message'] == msg][0] assert mod_import['code'] == 'E201' assert mod_import['severity'] == lsp.DiagnosticSeverity.Warning assert mod_import['range']['start'] == {'line': 2, 'character': 10} assert mod_import['range']['end'] == {'line': 2, 'character': 14}
def test_numpy_hover(): # Over the blank line no_hov_position = {'line': 1, 'character': 0} # Over 'numpy' in import numpy as np numpy_hov_position_1 = {'line': 2, 'character': 8} # Over 'np' in import numpy as np numpy_hov_position_2 = {'line': 2, 'character': 17} # Over 'np' in np.sin numpy_hov_position_3 = {'line': 3, 'character': 1} # Over 'sin' in np.sin numpy_sin_hov_position = {'line': 3, 'character': 4} doc = Document(DOC_URI, NUMPY_DOC) if LooseVersion(_utils.JEDI_VERSION) >= LooseVersion('0.15.0'): contents = '' assert contents in pyls_hover(doc, no_hov_position)['contents'] contents = 'NumPy\n=====\n\nProvides\n' assert contents in pyls_hover(doc, numpy_hov_position_1)['contents'][0] contents = 'NumPy\n=====\n\nProvides\n' assert contents in pyls_hover(doc, numpy_hov_position_2)['contents'][0] contents = 'NumPy\n=====\n\nProvides\n' assert contents in pyls_hover(doc, numpy_hov_position_3)['contents'][0] contents = 'Trigonometric sine, element-wise.\n\n' assert contents in pyls_hover( doc, numpy_sin_hov_position)['contents'][0]
def test_undefined_name_pyflakes(): doc = Document(DOC_URI, DOC_UNDEFINED_NAME_ERR) diag = pyflakes_lint.pyls_lint(doc)[0] assert diag['message'] == 'undefined name \'b\'' assert diag['range']['start'] == {'line': 0, 'character': 4} assert diag['severity'] == lsp.DiagnosticSeverity.Error
def test_syntax_error_pyflakes(): doc = Document(DOC_URI, DOC_SYNTAX_ERR) diag = pyflakes_lint.pyls_lint(doc)[0] assert diag['message'] == 'invalid syntax' assert diag['range']['start'] == {'line': 0, 'character': 12} assert diag['severity'] == lsp.DiagnosticSeverity.Error
def test_folding(): doc = Document(DOC_URI, DOC) ranges = pyls_folding_range(doc) expected = [{'startLine': 1, 'endLine': 6}, {'startLine': 2, 'endLine': 3}, {'startLine': 5, 'endLine': 6}, {'startLine': 8, 'endLine': 11}, {'startLine': 12, 'endLine': 20}, {'startLine': 13, 'endLine': 14}, {'startLine': 15, 'endLine': 16}, {'startLine': 17, 'endLine': 18}, {'startLine': 19, 'endLine': 20}, {'startLine': 22, 'endLine': 35}, {'startLine': 23, 'endLine': 35}, {'startLine': 24, 'endLine': 25}, {'startLine': 27, 'endLine': 29}, {'startLine': 28, 'endLine': 29}, {'startLine': 30, 'endLine': 31}, {'startLine': 32, 'endLine': 34}, {'startLine': 33, 'endLine': 34}, {'startLine': 38, 'endLine': 39}, {'startLine': 41, 'endLine': 43}, {'startLine': 42, 'endLine': 43}, {'startLine': 45, 'endLine': 54}, {'startLine': 47, 'endLine': 51}, {'startLine': 49, 'endLine': 51}, {'startLine': 50, 'endLine': 51}, {'startLine': 52, 'endLine': 54}, {'startLine': 53, 'endLine': 54}, {'startLine': 56, 'endLine': 57}, {'startLine': 59, 'endLine': 65}, {'startLine': 60, 'endLine': 61}, {'startLine': 62, 'endLine': 63}, {'startLine': 64, 'endLine': 65}] assert ranges == expected
def test_highlight(): # Over 'a' in a.startswith cursor_pos = {'line': 1, 'character': 0} doc = Document(DOC_URI, DOC) assert pyls_document_highlight(doc, cursor_pos) == [ { 'range': { 'start': { 'line': 0, 'character': 0 }, 'end': { 'line': 0, 'character': 1 }, }, # The first usage is Write 'kind': lsp.DocumentHighlightKind.Write }, { 'range': { 'start': { 'line': 1, 'character': 0 }, 'end': { 'line': 1, 'character': 1 }, }, # The second usage is Read 'kind': lsp.DocumentHighlightKind.Read } ]
def test_no_signature(workspace): # Over blank line sig_position = {'line': 9, 'character': 0} doc = Document(DOC_URI, workspace, DOC) sigs = signature.pyls_signature_help(doc, sig_position)['signatures'] assert not sigs
def test_jedi_method_completion(config, workspace): # Over the 'y' in 'print Hello().every' com_position = {'line': 20, 'character': 19} doc = Document(DOC_URI, workspace, DOC) config.capabilities['textDocument'] = { 'completion': { 'completionItem': { 'snippetSupport': True } } } config.update({'plugins': {'jedi_completion': {'include_params': True}}}) completions = pyls_jedi_completions(config, doc, com_position) everyone_method = [ completion for completion in completions if completion['label'] == 'everyone(a, b, c, d)' ][0] # Ensure we only generate snippets for positional args assert everyone_method['insertTextFormat'] == lsp.InsertTextFormat.Snippet assert everyone_method['insertText'] == 'everyone(${1:a}, ${2:b})$0' # Disable param snippets config.update({'plugins': {'jedi_completion': {'include_params': False}}}) completions = pyls_jedi_completions(config, doc, com_position) everyone_method = [ completion for completion in completions if completion['label'] == 'everyone(a, b, c, d)' ][0] assert 'insertTextFormat' not in everyone_method assert everyone_method['insertText'] == 'everyone'
def test_option_prepend(tmpdir, monkeypatch): import sys from textwrap import dedent sentinel = tmpdir / 'ran' source = dedent("""\ #!/bin/sh touch {} exec {} "$@" """).format(sentinel, sys.executable) wrapper = tmpdir / 'bin/wrapper' wrapper.write(source, ensure=True) wrapper.chmod(0o700) monkeypatch.setattr( FakeConfig, 'plugin_settings', lambda *_: {'prepend': ['--python-executable', wrapper.strpath]}) assert not sentinel.exists() diags = plugin.pyls_lint( config=FakeConfig(), workspace=None, document=Document(DOC_URI, DOC_TYPE_ERR), is_saved=False, ) assert len(diags) == 1 assert sentinel.exists() assert sentinel.exists()
def test_references(tmp_workspace): # pylint: disable=redefined-outer-name # Over 'Test1' in class Test1(): position = {'line': 0, 'character': 8} DOC1_URI = uris.from_fs_path( os.path.join(tmp_workspace.root_path, DOC1_NAME)) doc1 = Document(DOC1_URI) refs = pyls_references(doc1, position) # Definition, the import and the instantiation assert len(refs) == 3 # Briefly check excluding the definitions (also excludes imports, only counts uses) no_def_refs = pyls_references(doc1, position, exclude_declaration=True) assert len(no_def_refs) == 1 # Make sure our definition is correctly located doc1_ref = [u for u in refs if u['uri'] == DOC1_URI][0] assert doc1_ref['range']['start'] == {'line': 0, 'character': 6} assert doc1_ref['range']['end'] == {'line': 0, 'character': 11} # Make sure our import is correctly located doc2_import_ref = [u for u in refs if u['uri'] != DOC1_URI][0] assert doc2_import_ref['range']['start'] == {'line': 0, 'character': 18} assert doc2_import_ref['range']['end'] == {'line': 0, 'character': 23} doc2_usage_ref = [u for u in refs if u['uri'] != DOC1_URI][1] assert doc2_usage_ref['range']['start'] == {'line': 3, 'character': 4} assert doc2_usage_ref['range']['end'] == {'line': 3, 'character': 9}
def test_parse_line_with_context(monkeypatch, word, bounds, workspace): doc = Document(DOC_URI, workspace) monkeypatch.setattr(Document, 'word_at_position', lambda *args: word) diag = plugin.parse_line(TEST_LINE, doc) assert diag['message'] == '"Request" has no attribute "id"' assert diag['range']['start'] == {'line': 278, 'character': bounds[0]} assert diag['range']['end'] == {'line': 278, 'character': bounds[1]}
def test_symbols(config, workspace): doc = Document(DOC_URI, workspace, DOC) config.update({'plugins': {'jedi_symbols': {'all_scopes': False}}}) symbols = pyls_document_symbols(config, doc) # All four symbols (import sys, a, B, main) # y is not in the root scope, it shouldn't be returned assert len(symbols) == 4 def sym(name): return [s for s in symbols if s['name'] == name][0] # Check we have some sane mappings to VSCode constants assert sym('a')['kind'] == SymbolKind.Variable assert sym('B')['kind'] == SymbolKind.Class assert sym('main')['kind'] == SymbolKind.Function # Not going to get too in-depth here else we're just testing Jedi assert sym('a')['location']['range']['start'] == { 'line': 2, 'character': 0 } # Ensure that the symbol range spans the whole definition assert sym('main')['location']['range']['start'] == { 'line': 9, 'character': 0 } assert sym('main')['location']['range']['end'] == { 'line': 12, 'character': 0 }
def test_jedi_method_completion(config): # Over the 'y' in 'print Hello().every' com_position = {'line': 20, 'character': 19} doc = Document(DOC_URI, DOC) completions = pyls_jedi_completions(config, doc, com_position) everyone_method = [ completion for completion in completions if completion['label'] == 'everyone(a, b, c, d)' ][0] assert everyone_method['insertTextFormat'] == lsp.InsertTextFormat.Snippet assert everyone_method[ 'insertText'] == 'everyone(${1:a}, ${2:b}, ${3:c}, ${4:d})$0' # Disable param snippets config.update({'plugins': {'jedi_completion': {'include_params': False}}}) completions = pyls_jedi_completions(config, doc, com_position) everyone_method = [ completion for completion in completions if completion['label'] == 'everyone(a, b, c, d)' ][0] assert 'insertTextFormat' not in everyone_method assert everyone_method['insertText'] == 'everyone'
def test_no_signature(): # Over blank line sig_position = {'line': 9, 'character': 0} doc = Document(DOC_URI, DOC) sigs = signature.pyls_signature_help(doc, sig_position)['signatures'] assert len(sigs) == 0
def test_flake8_no_checked_file(config, workspace): # A bad uri or a non-saved file may cause the flake8 linter to do nothing. # In this situtation, the linter will return an empty list. doc = Document('', workspace, DOC) diags = flake8_lint.pyls_lint(config, doc) assert 'Error' in diags[0]['message']
def test_jedi_completion_environment(config): # Content of doc to test completion doc_content = '''import logh ''' doc = Document(DOC_URI, MockWorkspace(), doc_content) # After 'import logh' with default environment com_position = {'line': 0, 'character': 11} assert os.path.isdir('/tmp/pyenv/') config.update({'plugins': {'jedi': {'environment': None}}}) doc.update_config(config) completions = pyls_jedi_completions(config, doc, com_position) assert completions is None # Update config extra environment env_path = '/tmp/pyenv/bin/python' config.update({'plugins': {'jedi': {'environment': env_path}}}) doc.update_config(config) # After 'import logh' with new environment completions = pyls_jedi_completions(config, doc, com_position) assert completions[0]['label'] == 'loghub' assert 'changelog generator' in completions[0]['documentation'].lower()
def test_jedi_completion_extra_paths(config, tmpdir, workspace): # Create a tempfile with some content and pass to extra_paths temp_doc_content = ''' def spam(): pass ''' p = tmpdir.mkdir("extra_path") extra_paths = [str(p)] p = p.join("foo.py") p.write(temp_doc_content) # Content of doc to test completion doc_content = """import foo foo.s""" doc = Document(DOC_URI, workspace, doc_content) # After 'foo.s' without extra paths com_position = {'line': 1, 'character': 5} completions = pyls_jedi_completions(config, doc, com_position) assert completions is None # Update config extra paths config.update({'plugins': {'jedi': {'extra_paths': extra_paths}}}) doc.update_config(config) # After 'foo.s' with extra paths com_position = {'line': 1, 'character': 5} completions = pyls_jedi_completions(config, doc, com_position) assert completions[0]['label'] == 'spam()'
def test_multistatement_snippet(config, workspace): config.capabilities['textDocument'] = { 'completion': {'completionItem': {'snippetSupport': True}}} config.update({'plugins': {'jedi_completion': {'include_params': True}}}) document = 'a = 1; from datetime import date' doc = Document(DOC_URI, workspace, document) position = {'line': 0, 'character': len(document)} completions = pyls_jedi_completions(config, doc, position) assert completions[0]['insertText'] == 'date' document = 'from datetime import date; a = date' doc = Document(DOC_URI, workspace, document) position = {'line': 0, 'character': len(document)} completions = pyls_jedi_completions(config, doc, position) assert completions[0]['insertText'] == 'date(${1:year}, ${2:month}, ${3:day})$0'
def temp_document(doc_text): temp_file = tempfile.NamedTemporaryFile(mode='w', delete=False) name = temp_file.name temp_file.write(doc_text) temp_file.close() doc = Document(uris.from_fs_path(name), MockWorkspace()) return name, doc
def test_completion(): # Over 'r' in sys.stdin.read() com_position = {'line': 1, 'character': 17} doc = Document(DOC_URI, DOC) items = pyls_completions(doc, com_position) assert len(items) > 0 assert items[0]['label'] == 'read'
def test_matplotlib_completions(config, workspace): doc_mpl = "import matplotlib.pyplot as plt; plt." com_position = {'line': 0, 'character': len(doc_mpl)} doc = Document(DOC_URI, workspace, doc_mpl) items = pyls_jedi_completions(config, doc, com_position) assert items assert any(['plot' in i['label'] for i in items])
def test_pandas_completions(config, workspace): doc_pandas = "import pandas as pd; pd." com_position = {'line': 0, 'character': len(doc_pandas)} doc = Document(DOC_URI, workspace, doc_pandas) items = pyls_jedi_completions(config, doc, com_position) assert items assert any(['DataFrame' in i['label'] for i in items])