diff --git a/test/asserting/policy.py b/test/asserting/policy.py index f8e07bd..c69c441 100644 --- a/test/asserting/policy.py +++ b/test/asserting/policy.py @@ -65,7 +65,7 @@ def assertFoundViolationsEqual(self, path, Policy, expected_violations, policy_o violations = linter.lint_file(path) pprint(violations) - assert len(violations) == len(expected_violations) + self.assertEqual(len(violations), len(expected_violations)) for violation, expected_violation in zip_longest(violations, expected_violations): self.assertViolation(violation, expected_violation) @@ -77,9 +77,9 @@ def assertViolation(self, actual_violation, expected_violation): pprint(actual_violation) - assert actual_violation['name'] == expected_violation['name'] - assert actual_violation['position'] == expected_violation['position'] - assert actual_violation['level'] == expected_violation['level'] + self.assertEqual(actual_violation['name'], expected_violation['name'], 'name') + self.assertEqual(actual_violation['position'], expected_violation['position'], 'position') + self.assertEqual(actual_violation['level'], expected_violation['level'], 'level') self.assertIsInstance(actual_violation['description'], str) diff --git a/test/fixture/ast/fixture_traversing_find_all.vim b/test/fixture/ast/fixture_traversing_find_all.vim new file mode 100644 index 0000000..78079ed --- /dev/null +++ b/test/fixture/ast/fixture_traversing_find_all.vim @@ -0,0 +1,6 @@ +echo 0 +echoerr 0 +echo 1 +echoerr 1 +echo 2 +echoerr 2 diff --git a/test/unit/vint/ast/dictionary/__init__.py b/test/unit/vint/ast/dictionary/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/unit/vint/ast/dictionary/test_autocmd_events.py b/test/unit/vint/ast/dictionary/test_autocmd_events.py new file mode 100644 index 0000000..3f33b86 --- /dev/null +++ b/test/unit/vint/ast/dictionary/test_autocmd_events.py @@ -0,0 +1,25 @@ +import unittest +from vint.ast.dictionary.autocmd_events import is_autocmd_event + + +class TestAutocmdEvent(unittest.TestCase): + def test_is_autocmd_event(self): + test_cases = [ + ('BufReadPost', True), + ('FileType', True), + ('FILETYPE', True), + ('filetype', True), + ('*', True), + ('INVALID', False), + ] + + for (event_name, expected) in test_cases: + self.assertEqual( + is_autocmd_event(event_name), + expected, + msg="{event_name} should be {expected}".format(event_name=event_name, expected=expected) + ) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/unit/vint/ast/plugin/fixture_autocmd_parser_orthodox.vim b/test/unit/vint/ast/plugin/fixture_autocmd_parser_orthodox.vim new file mode 100644 index 0000000..6a52f06 --- /dev/null +++ b/test/unit/vint/ast/plugin/fixture_autocmd_parser_orthodox.vim @@ -0,0 +1 @@ +autocmd! bufwritepost vimrc diff --git a/test/unit/vint/ast/plugin/fixture_autocmd_parser_with_line_continuation.vim b/test/unit/vint/ast/plugin/fixture_autocmd_parser_with_line_continuation.vim new file mode 100644 index 0000000..b9336eb --- /dev/null +++ b/test/unit/vint/ast/plugin/fixture_autocmd_parser_with_line_continuation.vim @@ -0,0 +1,5 @@ +" https://github.com/amix/vimrc +autocmd BufReadPost * + \ if line("'\"") > 0 && line("'\"") <= line("$") | + \ exe "normal! g`\"" | + \ endif diff --git a/test/unit/vint/ast/plugin/scope_plugin/test_scope_detector.py b/test/unit/vint/ast/plugin/scope_plugin/test_scope_detector.py index 6c75a75..06bf19f 100644 --- a/test/unit/vint/ast/plugin/scope_plugin/test_scope_detector.py +++ b/test/unit/vint/ast/plugin/scope_plugin/test_scope_detector.py @@ -1,4 +1,4 @@ -import pytest +import unittest from vint.ast.node_type import NodeType from vint.ast.plugin.scope_plugin.identifier_classifier import ( IDENTIFIER_ATTRIBUTE, @@ -20,6 +20,193 @@ ) +class TestScopeDetector(unittest.TestCase): + def test_detect_scope_visibility(self): + test_cases = [ + # Declarative variable test + (Vis.SCRIPT_LOCAL, create_id('g:explicit_global'), Vis.GLOBAL_LIKE, False), + (Vis.SCRIPT_LOCAL, create_id('implicit_global'), Vis.GLOBAL_LIKE, True), + (Vis.FUNCTION_LOCAL, create_id('g:explicit_global'), Vis.GLOBAL_LIKE, False), + + (Vis.SCRIPT_LOCAL, create_id('b:buffer_local'), Vis.GLOBAL_LIKE, False), + (Vis.FUNCTION_LOCAL, create_id('b:buffer_local'), Vis.GLOBAL_LIKE, False), + + (Vis.SCRIPT_LOCAL, create_id('w:window_local'), Vis.GLOBAL_LIKE, False), + (Vis.FUNCTION_LOCAL, create_id('w:window_local'), Vis.GLOBAL_LIKE, False), + + (Vis.SCRIPT_LOCAL, create_id('s:script_local'), Vis.SCRIPT_LOCAL, False), + (Vis.FUNCTION_LOCAL, create_id('s:script_local'), Vis.SCRIPT_LOCAL, False), + + (Vis.FUNCTION_LOCAL, create_id('l:explicit_function_local'), Vis.FUNCTION_LOCAL, False), + (Vis.FUNCTION_LOCAL, create_id('implicit_function_local'), Vis.FUNCTION_LOCAL, True), + + (Vis.FUNCTION_LOCAL, create_id('param', is_declarative=True, is_declarative_parameter=True), Vis.FUNCTION_LOCAL, False), + + (Vis.SCRIPT_LOCAL, create_id('v:count'), Vis.BUILTIN, False), + (Vis.FUNCTION_LOCAL, create_id('v:count'), Vis.BUILTIN, False), + (Vis.FUNCTION_LOCAL, create_id('count'), Vis.BUILTIN, True), + + (Vis.SCRIPT_LOCAL, create_curlyname(), Vis.UNANALYZABLE, False), + (Vis.FUNCTION_LOCAL, create_curlyname(), Vis.UNANALYZABLE, False), + + (Vis.SCRIPT_LOCAL, create_subscript_member(), Vis.UNANALYZABLE, False), + (Vis.FUNCTION_LOCAL, create_subscript_member(), Vis.UNANALYZABLE, False), + + (Vis.SCRIPT_LOCAL, create_id('g:ExplicitGlobalFunc', is_function=True), Vis.GLOBAL_LIKE, False), + (Vis.SCRIPT_LOCAL, create_id('ImplicitGlobalFunc', is_function=True), Vis.GLOBAL_LIKE, True), + + (Vis.SCRIPT_LOCAL, create_id('g:file#explicit_global_func', is_function=True, is_autoload=True), Vis.GLOBAL_LIKE, False), + (Vis.SCRIPT_LOCAL, create_id('file#implicit_global_func', is_function=True, is_autoload=True), Vis.GLOBAL_LIKE, True), + + (Vis.FUNCTION_LOCAL, create_id('g:ExplicitGlobalFunc', is_function=True), Vis.GLOBAL_LIKE, False), + (Vis.FUNCTION_LOCAL, create_id('ImplicitGlobalFunc', is_function=True), Vis.GLOBAL_LIKE, True), + + (Vis.FUNCTION_LOCAL, create_id('g:file#explicit_global_func', is_function=True, is_autoload=True), Vis.GLOBAL_LIKE, False), + (Vis.FUNCTION_LOCAL, create_id('file#implicit_global_func', is_function=True, is_autoload=True), Vis.GLOBAL_LIKE, True), + + (Vis.SCRIPT_LOCAL, create_id('s:ScriptLocalFunc', is_function=True), Vis.SCRIPT_LOCAL, False), + (Vis.FUNCTION_LOCAL, create_id('s:ScriptLocalFunc', is_function=True), Vis.SCRIPT_LOCAL, False), + + (Vis.SCRIPT_LOCAL, create_id('t:InvalidScopeFunc', is_function=True), Vis.INVALID, False), + + # Referencing variable test + (Vis.SCRIPT_LOCAL, create_id('g:explicit_global', is_declarative=False), Vis.GLOBAL_LIKE, False), + (Vis.SCRIPT_LOCAL, create_id('implicit_global', is_declarative=False), Vis.GLOBAL_LIKE, True), + (Vis.FUNCTION_LOCAL, create_id('g:explicit_global', is_declarative=False), Vis.GLOBAL_LIKE, False), + + (Vis.SCRIPT_LOCAL, create_id('b:buffer_local', is_declarative=False), Vis.GLOBAL_LIKE, False), + (Vis.FUNCTION_LOCAL, create_id('b:buffer_local', is_declarative=False), Vis.GLOBAL_LIKE, False), + + (Vis.SCRIPT_LOCAL, create_id('w:window_local', is_declarative=False), Vis.GLOBAL_LIKE, False), + (Vis.FUNCTION_LOCAL, create_id('w:window_local', is_declarative=False), Vis.GLOBAL_LIKE, False), + + (Vis.SCRIPT_LOCAL, create_id('s:script_local', is_declarative=False), Vis.SCRIPT_LOCAL, False), + (Vis.FUNCTION_LOCAL, create_id('s:script_local', is_declarative=False), Vis.SCRIPT_LOCAL, False), + + (Vis.FUNCTION_LOCAL, create_id('l:explicit_function_local', is_declarative=False), Vis.FUNCTION_LOCAL, False), + (Vis.FUNCTION_LOCAL, create_id('implicit_function_local', is_declarative=False), Vis.FUNCTION_LOCAL, True), + + (Vis.FUNCTION_LOCAL, create_id('a:param', is_declarative=False), Vis.FUNCTION_LOCAL, False), + (Vis.FUNCTION_LOCAL, create_id('a:000', is_declarative=False), Vis.FUNCTION_LOCAL, False), + (Vis.FUNCTION_LOCAL, create_id('a:1', is_declarative=False), Vis.FUNCTION_LOCAL, False), + + (Vis.SCRIPT_LOCAL, create_id('v:count', is_declarative=False), Vis.BUILTIN, False), + (Vis.FUNCTION_LOCAL, create_id('v:count', is_declarative=False), Vis.BUILTIN, False), + (Vis.FUNCTION_LOCAL, create_id('count', is_declarative=False), Vis.BUILTIN, True), + (Vis.SCRIPT_LOCAL, create_id('localtime', is_declarative=False, is_function=True), Vis.BUILTIN, False), + + (Vis.SCRIPT_LOCAL, create_curlyname(is_declarative=False), Vis.UNANALYZABLE, False), + (Vis.FUNCTION_LOCAL, create_curlyname(is_declarative=False), Vis.UNANALYZABLE, False), + + (Vis.SCRIPT_LOCAL, create_subscript_member(is_declarative=False), Vis.UNANALYZABLE, False), + (Vis.FUNCTION_LOCAL, create_subscript_member(is_declarative=False), Vis.UNANALYZABLE, False), + + (Vis.SCRIPT_LOCAL, create_id('g:ExplicitGlobalFunc', is_declarative=False, is_function=True), Vis.GLOBAL_LIKE, False), + (Vis.SCRIPT_LOCAL, create_id('ImplicitGlobalFunc', is_declarative=False, is_function=True), Vis.GLOBAL_LIKE, True), + + (Vis.SCRIPT_LOCAL, create_id('g:file#explicit_global_func', is_declarative=False, is_function=True, is_autoload=True), Vis.GLOBAL_LIKE, False), + (Vis.SCRIPT_LOCAL, create_id('file#implicit_global_func', is_declarative=False, is_function=False, is_autoload=True), Vis.GLOBAL_LIKE, True), + + (Vis.FUNCTION_LOCAL, create_id('g:ExplicitGlobalFunc', is_declarative=False, is_function=True), Vis.GLOBAL_LIKE, False), + (Vis.FUNCTION_LOCAL, create_id('ImplicitGlobalFunc', is_declarative=False, is_function=True), Vis.GLOBAL_LIKE, True), + + (Vis.FUNCTION_LOCAL, create_id('g:file#explicit_global_func', is_declarative=False, is_function=True, is_autoload=True), Vis.GLOBAL_LIKE, False), + (Vis.FUNCTION_LOCAL, create_id('file#implicit_global_func', is_declarative=False, is_function=True, is_autoload=True), Vis.GLOBAL_LIKE, True), + + (Vis.SCRIPT_LOCAL, create_id('s:ScriptLocalFunc', is_declarative=False, is_function=True), Vis.SCRIPT_LOCAL, False), + (Vis.FUNCTION_LOCAL, create_id('s:ScriptLocalFunc', is_declarative=False, is_function=True), Vis.SCRIPT_LOCAL, False), + + (Vis.SCRIPT_LOCAL, create_id('t:TabLocalFuncRef', is_declarative=False, is_function=True), Vis.GLOBAL_LIKE, False), + ] + + for (context_scope_visibility, id_node, expected_scope_visibility, expected_implicity) in test_cases: + scope = create_scope(context_scope_visibility) + scope_visibility_hint = detect_scope_visibility(id_node, scope) + + expected_scope_visibility_hint = create_scope_visibility_hint( + expected_scope_visibility, + is_implicit=expected_implicity + ) + self.assertEqual(expected_scope_visibility_hint, scope_visibility_hint, id_node) + + + def test_normalize_variable_name(self): + test_cases = [ + (Vis.SCRIPT_LOCAL, create_id('g:explicit_global'), 'g:explicit_global'), + (Vis.SCRIPT_LOCAL, create_id('implicit_global'), 'g:implicit_global'), + (Vis.SCRIPT_LOCAL, create_id('implicit_global', is_declarative=False), 'g:implicit_global'), + + (Vis.FUNCTION_LOCAL, create_id('l:explicit_function_local'), 'l:explicit_function_local'), + (Vis.FUNCTION_LOCAL, create_id('implicit_function_local'), 'l:implicit_function_local'), + (Vis.FUNCTION_LOCAL, create_id('implicit_function_local', is_declarative=False), 'l:implicit_function_local'), + + (Vis.SCRIPT_LOCAL, create_id('g:ExplicitGlobalFunc', is_function=True), 'g:ExplicitGlobalFunc'), + (Vis.SCRIPT_LOCAL, create_id('s:ExplicitScriptLocalFunc', is_function=True), 's:ExplicitScriptLocalFunc'), + (Vis.SCRIPT_LOCAL, create_id('ImplicitGlobalFunc', is_function=True), 'g:ImplicitGlobalFunc'), + (Vis.FUNCTION_LOCAL, create_id('ImplicitGlobalFunc', is_function=True), 'g:ImplicitGlobalFunc'), + (Vis.FUNCTION_LOCAL, create_id('s:ExplicitScriptLocalFunc', is_function=True), 's:ExplicitScriptLocalFunc'), + (Vis.SCRIPT_LOCAL, create_id('ImplicitGlobalFunc', is_declarative=False, is_function=True), 'g:ImplicitGlobalFunc'), + + (Vis.FUNCTION_LOCAL, create_id('l:explicit_function_local'), 'l:explicit_function_local'), + (Vis.FUNCTION_LOCAL, create_id('implicit_function_local'), 'l:implicit_function_local'), + (Vis.FUNCTION_LOCAL, create_id('implicit_function_local', is_declarative=False), 'l:implicit_function_local'), + + (Vis.FUNCTION_LOCAL, create_id('param', is_declarative=False, is_declarative_parameter=True), 'param'), + + (Vis.SCRIPT_LOCAL, create_id('v:count'), 'v:count'), + (Vis.FUNCTION_LOCAL, create_id('v:count'), 'v:count'), + (Vis.FUNCTION_LOCAL, create_id('count'), 'v:count'), + (Vis.SCRIPT_LOCAL, create_id('localtime', is_declarative=False, is_function=True), 'localtime'), + + (Vis.SCRIPT_LOCAL, create_env('$ENV'), '$ENV'), + (Vis.SCRIPT_LOCAL, create_option('&OPT'), '&OPT'), + (Vis.SCRIPT_LOCAL, create_reg('@"'), '@"'), + ] + + for (context_scope_visibility, node, expected_variable_name) in test_cases: + scope = create_scope(context_scope_visibility) + normalized_variable_name = normalize_variable_name(node, scope) + + self.assertEqual(expected_variable_name, normalized_variable_name) + + + def test_is_builtin_variable(self): + test_cases = [ + ('my_var', False, False), + ('count', False, True), + ('v:count', False, True), + + # It is available on only map() or filter(). + ('key', False, False), + ('val', False, False), + + ('MyFunc', True, False), + ('localtime', True, True), + ] + + for (id_value, is_function, expected_result) in test_cases: + id_node = create_id(id_value, is_function=is_function) + result = is_builtin_variable(id_node) + + self.assertEqual(expected_result, result, id_value) + + + def test_get_explicity_of_scope_visibility(self): + test_cases = [ + (create_id('my_var'), ExplicityOfScopeVisibility.IMPLICIT), + (create_id('g:my_var'), ExplicityOfScopeVisibility.EXPLICIT), + + (create_id('param', is_declarative=True, is_declarative_parameter=True), ExplicityOfScopeVisibility.EXPLICIT), + + (create_id('localtime', is_declarative=False, is_function=True), ExplicityOfScopeVisibility.EXPLICIT), + + (create_curlyname(), ExplicityOfScopeVisibility.UNANALYZABLE), + ] + for (node, expected_result) in test_cases: + result = get_explicity_of_scope_visibility(node) + self.assertEqual(expected_result, result) + + def create_scope(visibility): return { 'scope_visibility': visibility, @@ -144,189 +331,5 @@ def create_subscript_member(is_declarative=True): } -@pytest.mark.parametrize( - 'context_scope_visibility, id_node, expected_scope_visibility, expected_implicity', [ - # Declarative variable test - (Vis.SCRIPT_LOCAL, create_id('g:explicit_global'), Vis.GLOBAL_LIKE, False), - (Vis.SCRIPT_LOCAL, create_id('implicit_global'), Vis.GLOBAL_LIKE, True), - (Vis.FUNCTION_LOCAL, create_id('g:explicit_global'), Vis.GLOBAL_LIKE, False), - - (Vis.SCRIPT_LOCAL, create_id('b:buffer_local'), Vis.GLOBAL_LIKE, False), - (Vis.FUNCTION_LOCAL, create_id('b:buffer_local'), Vis.GLOBAL_LIKE, False), - - (Vis.SCRIPT_LOCAL, create_id('w:window_local'), Vis.GLOBAL_LIKE, False), - (Vis.FUNCTION_LOCAL, create_id('w:window_local'), Vis.GLOBAL_LIKE, False), - - (Vis.SCRIPT_LOCAL, create_id('s:script_local'), Vis.SCRIPT_LOCAL, False), - (Vis.FUNCTION_LOCAL, create_id('s:script_local'), Vis.SCRIPT_LOCAL, False), - - (Vis.FUNCTION_LOCAL, create_id('l:explicit_function_local'), Vis.FUNCTION_LOCAL, False), - (Vis.FUNCTION_LOCAL, create_id('implicit_function_local'), Vis.FUNCTION_LOCAL, True), - - (Vis.FUNCTION_LOCAL, create_id('param', is_declarative=True, is_declarative_parameter=True), Vis.FUNCTION_LOCAL, False), - - (Vis.SCRIPT_LOCAL, create_id('v:count'), Vis.BUILTIN, False), - (Vis.FUNCTION_LOCAL, create_id('v:count'), Vis.BUILTIN, False), - (Vis.FUNCTION_LOCAL, create_id('count'), Vis.BUILTIN, True), - - (Vis.SCRIPT_LOCAL, create_curlyname(), Vis.UNANALYZABLE, False), - (Vis.FUNCTION_LOCAL, create_curlyname(), Vis.UNANALYZABLE, False), - - (Vis.SCRIPT_LOCAL, create_subscript_member(), Vis.UNANALYZABLE, False), - (Vis.FUNCTION_LOCAL, create_subscript_member(), Vis.UNANALYZABLE, False), - - (Vis.SCRIPT_LOCAL, create_id('g:ExplicitGlobalFunc', is_function=True), Vis.GLOBAL_LIKE, False), - (Vis.SCRIPT_LOCAL, create_id('ImplicitGlobalFunc', is_function=True), Vis.GLOBAL_LIKE, True), - - (Vis.SCRIPT_LOCAL, create_id('g:file#explicit_global_func', is_function=True, is_autoload=True), Vis.GLOBAL_LIKE, False), - (Vis.SCRIPT_LOCAL, create_id('file#implicit_global_func', is_function=True, is_autoload=True), Vis.GLOBAL_LIKE, True), - - (Vis.FUNCTION_LOCAL, create_id('g:ExplicitGlobalFunc', is_function=True), Vis.GLOBAL_LIKE, False), - (Vis.FUNCTION_LOCAL, create_id('ImplicitGlobalFunc', is_function=True), Vis.GLOBAL_LIKE, True), - - (Vis.FUNCTION_LOCAL, create_id('g:file#explicit_global_func', is_function=True, is_autoload=True), Vis.GLOBAL_LIKE, False), - (Vis.FUNCTION_LOCAL, create_id('file#implicit_global_func', is_function=True, is_autoload=True), Vis.GLOBAL_LIKE, True), - - (Vis.SCRIPT_LOCAL, create_id('s:ScriptLocalFunc', is_function=True), Vis.SCRIPT_LOCAL, False), - (Vis.FUNCTION_LOCAL, create_id('s:ScriptLocalFunc', is_function=True), Vis.SCRIPT_LOCAL, False), - - (Vis.SCRIPT_LOCAL, create_id('t:InvalidScopeFunc', is_function=True), Vis.INVALID, False), - - # Referencing variable test - (Vis.SCRIPT_LOCAL, create_id('g:explicit_global', is_declarative=False), Vis.GLOBAL_LIKE, False), - (Vis.SCRIPT_LOCAL, create_id('implicit_global', is_declarative=False), Vis.GLOBAL_LIKE, True), - (Vis.FUNCTION_LOCAL, create_id('g:explicit_global', is_declarative=False), Vis.GLOBAL_LIKE, False), - - (Vis.SCRIPT_LOCAL, create_id('b:buffer_local', is_declarative=False), Vis.GLOBAL_LIKE, False), - (Vis.FUNCTION_LOCAL, create_id('b:buffer_local', is_declarative=False), Vis.GLOBAL_LIKE, False), - - (Vis.SCRIPT_LOCAL, create_id('w:window_local', is_declarative=False), Vis.GLOBAL_LIKE, False), - (Vis.FUNCTION_LOCAL, create_id('w:window_local', is_declarative=False), Vis.GLOBAL_LIKE, False), - - (Vis.SCRIPT_LOCAL, create_id('s:script_local', is_declarative=False), Vis.SCRIPT_LOCAL, False), - (Vis.FUNCTION_LOCAL, create_id('s:script_local', is_declarative=False), Vis.SCRIPT_LOCAL, False), - - (Vis.FUNCTION_LOCAL, create_id('l:explicit_function_local', is_declarative=False), Vis.FUNCTION_LOCAL, False), - (Vis.FUNCTION_LOCAL, create_id('implicit_function_local', is_declarative=False), Vis.FUNCTION_LOCAL, True), - - (Vis.FUNCTION_LOCAL, create_id('a:param', is_declarative=False), Vis.FUNCTION_LOCAL, False), - (Vis.FUNCTION_LOCAL, create_id('a:000', is_declarative=False), Vis.FUNCTION_LOCAL, False), - (Vis.FUNCTION_LOCAL, create_id('a:1', is_declarative=False), Vis.FUNCTION_LOCAL, False), - - (Vis.SCRIPT_LOCAL, create_id('v:count', is_declarative=False), Vis.BUILTIN, False), - (Vis.FUNCTION_LOCAL, create_id('v:count', is_declarative=False), Vis.BUILTIN, False), - (Vis.FUNCTION_LOCAL, create_id('count', is_declarative=False), Vis.BUILTIN, True), - (Vis.SCRIPT_LOCAL, create_id('localtime', is_declarative=False, is_function=True), Vis.BUILTIN, False), - - (Vis.SCRIPT_LOCAL, create_curlyname(is_declarative=False), Vis.UNANALYZABLE, False), - (Vis.FUNCTION_LOCAL, create_curlyname(is_declarative=False), Vis.UNANALYZABLE, False), - - (Vis.SCRIPT_LOCAL, create_subscript_member(is_declarative=False), Vis.UNANALYZABLE, False), - (Vis.FUNCTION_LOCAL, create_subscript_member(is_declarative=False), Vis.UNANALYZABLE, False), - - (Vis.SCRIPT_LOCAL, create_id('g:ExplicitGlobalFunc', is_declarative=False, is_function=True), Vis.GLOBAL_LIKE, False), - (Vis.SCRIPT_LOCAL, create_id('ImplicitGlobalFunc', is_declarative=False, is_function=True), Vis.GLOBAL_LIKE, True), - - (Vis.SCRIPT_LOCAL, create_id('g:file#explicit_global_func', is_declarative=False, is_function=True, is_autoload=True), Vis.GLOBAL_LIKE, False), - (Vis.SCRIPT_LOCAL, create_id('file#implicit_global_func', is_declarative=False, is_function=False, is_autoload=True), Vis.GLOBAL_LIKE, True), - - (Vis.FUNCTION_LOCAL, create_id('g:ExplicitGlobalFunc', is_declarative=False, is_function=True), Vis.GLOBAL_LIKE, False), - (Vis.FUNCTION_LOCAL, create_id('ImplicitGlobalFunc', is_declarative=False, is_function=True), Vis.GLOBAL_LIKE, True), - - (Vis.FUNCTION_LOCAL, create_id('g:file#explicit_global_func', is_declarative=False, is_function=True, is_autoload=True), Vis.GLOBAL_LIKE, False), - (Vis.FUNCTION_LOCAL, create_id('file#implicit_global_func', is_declarative=False, is_function=True, is_autoload=True), Vis.GLOBAL_LIKE, True), - - (Vis.SCRIPT_LOCAL, create_id('s:ScriptLocalFunc', is_declarative=False, is_function=True), Vis.SCRIPT_LOCAL, False), - (Vis.FUNCTION_LOCAL, create_id('s:ScriptLocalFunc', is_declarative=False, is_function=True), Vis.SCRIPT_LOCAL, False), - - (Vis.SCRIPT_LOCAL, create_id('t:TabLocalFuncRef', is_declarative=False, is_function=True), Vis.GLOBAL_LIKE, False), - ] -) -def test_detect_scope_visibility(context_scope_visibility, id_node, expected_scope_visibility, expected_implicity): - scope = create_scope(context_scope_visibility) - scope_visibility_hint = detect_scope_visibility(id_node, scope) - - expected_scope_visibility_hint = create_scope_visibility_hint(expected_scope_visibility, - is_implicit=expected_implicity) - assert expected_scope_visibility_hint == scope_visibility_hint - - - -@pytest.mark.parametrize( - 'context_scope_visibility, node, expected_variable_name', [ - (Vis.SCRIPT_LOCAL, create_id('g:explicit_global'), 'g:explicit_global'), - (Vis.SCRIPT_LOCAL, create_id('implicit_global'), 'g:implicit_global'), - (Vis.SCRIPT_LOCAL, create_id('implicit_global', is_declarative=False), 'g:implicit_global'), - - (Vis.FUNCTION_LOCAL, create_id('l:explicit_function_local'), 'l:explicit_function_local'), - (Vis.FUNCTION_LOCAL, create_id('implicit_function_local'), 'l:implicit_function_local'), - (Vis.FUNCTION_LOCAL, create_id('implicit_function_local', is_declarative=False), 'l:implicit_function_local'), - - (Vis.SCRIPT_LOCAL, create_id('g:ExplicitGlobalFunc', is_function=True), 'g:ExplicitGlobalFunc'), - (Vis.SCRIPT_LOCAL, create_id('s:ExplicitScriptLocalFunc', is_function=True), 's:ExplicitScriptLocalFunc'), - (Vis.SCRIPT_LOCAL, create_id('ImplicitGlobalFunc', is_function=True), 'g:ImplicitGlobalFunc'), - (Vis.FUNCTION_LOCAL, create_id('ImplicitGlobalFunc', is_function=True), 'g:ImplicitGlobalFunc'), - (Vis.FUNCTION_LOCAL, create_id('s:ExplicitScriptLocalFunc', is_function=True), 's:ExplicitScriptLocalFunc'), - (Vis.SCRIPT_LOCAL, create_id('ImplicitGlobalFunc', is_declarative=False, is_function=True), 'g:ImplicitGlobalFunc'), - - (Vis.FUNCTION_LOCAL, create_id('l:explicit_function_local'), 'l:explicit_function_local'), - (Vis.FUNCTION_LOCAL, create_id('implicit_function_local'), 'l:implicit_function_local'), - (Vis.FUNCTION_LOCAL, create_id('implicit_function_local', is_declarative=False), 'l:implicit_function_local'), - - (Vis.FUNCTION_LOCAL, create_id('param', is_declarative=False, is_declarative_parameter=True), 'param'), - - (Vis.SCRIPT_LOCAL, create_id('v:count'), 'v:count'), - (Vis.FUNCTION_LOCAL, create_id('v:count'), 'v:count'), - (Vis.FUNCTION_LOCAL, create_id('count'), 'v:count'), - (Vis.SCRIPT_LOCAL, create_id('localtime', is_declarative=False, is_function=True), 'localtime'), - - (Vis.SCRIPT_LOCAL, create_env('$ENV'), '$ENV'), - (Vis.SCRIPT_LOCAL, create_option('&OPT'), '&OPT'), - (Vis.SCRIPT_LOCAL, create_reg('@"'), '@"'), - ] -) -def test_normalize_variable_name(context_scope_visibility, node, expected_variable_name): - scope = create_scope(context_scope_visibility) - normalized_variable_name = normalize_variable_name(node, scope) - - assert expected_variable_name == normalized_variable_name - - - -@pytest.mark.parametrize( - 'id_value, is_function, expected_result', [ - ('my_var', False, False), - ('count', False, True), - ('v:count', False, True), - - # It is available on only map() or filter(). - ('key', False, False), - ('val', False, False), - - ('MyFunc', True, False), - ('localtime', True, True), - ] -) -def test_is_builtin_variable(id_value, is_function, expected_result): - id_node = create_id(id_value, is_function=is_function) - result = is_builtin_variable(id_node) - - assert expected_result == result - - - -@pytest.mark.parametrize( - 'node, expected_result', [ - (create_id('my_var'), ExplicityOfScopeVisibility.IMPLICIT), - (create_id('g:my_var'), ExplicityOfScopeVisibility.EXPLICIT), - - (create_id('param', is_declarative=True, is_declarative_parameter=True), ExplicityOfScopeVisibility.EXPLICIT), - - (create_id('localtime', is_declarative=False, is_function=True), ExplicityOfScopeVisibility.EXPLICIT), - - (create_curlyname(), ExplicityOfScopeVisibility.UNANALYZABLE), - ] -) -def test_get_explicity_of_scope_visibility(node, expected_result): - result = get_explicity_of_scope_visibility(node) - assert expected_result == result +if __name__ == '__main__': + unittest.main() diff --git a/test/unit/vint/ast/plugin/test_autocmd_parser.py b/test/unit/vint/ast/plugin/test_autocmd_parser.py new file mode 100644 index 0000000..30458c1 --- /dev/null +++ b/test/unit/vint/ast/plugin/test_autocmd_parser.py @@ -0,0 +1,180 @@ +import unittest +from vint.ast.dictionary.autocmd_events import AutocmdEvents +from vint.ast.plugin.autocmd_parser import parse_autocmd + + +def create_echo_cmd_node(): + return ':echo' + + +def create_autocmd_node(autocmd_str): + return {'str': autocmd_str} + + +class TestAutocmdParser(unittest.TestCase): + def test_parse_autocmd(self): + test_cases = [ + (create_autocmd_node('autocmd'), { + 'group': None, + 'event': [], + 'pat': None, + 'nested': False, + 'cmd': None, + 'bang': False, + }), + (create_autocmd_node('autocmd!'), { + 'group': None, + 'event': [], + 'pat': None, + 'nested': False, + 'cmd': None, + 'bang': True, + }), + (create_autocmd_node('autocmd FileType'), { + 'group': None, + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': None, + 'nested': False, + 'cmd': None, + 'bang': False, + }), + (create_autocmd_node('autocmd BufNew,BufRead'), { + 'group': None, + 'event': [AutocmdEvents.BUF_NEW, AutocmdEvents.BUF_READ], + 'pat': None, + 'nested': False, + 'cmd': None, + 'bang': False, + }), + (create_autocmd_node('autocmd! FileType'), { + 'group': None, + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': None, + 'nested': False, + 'cmd': None, + 'bang': True, + }), + (create_autocmd_node('autocmd FileType *'), { + 'group': None, + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': '*', + 'nested': False, + 'cmd': None, + 'bang': False, + }), + (create_autocmd_node('autocmd! FileType *'), { + 'group': None, + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': '*', + 'nested': False, + 'cmd': None, + 'bang': True, + }), + (create_autocmd_node('autocmd FileType * nested :echo'), { + 'group': None, + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': '*', + 'nested': True, + 'cmd': create_echo_cmd_node(), + 'bang': False, + }), + (create_autocmd_node('autocmd! FileType * nested :echo'), { + 'group': None, + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': '*', + 'nested': True, + 'cmd': create_echo_cmd_node(), + 'bang': True, + }), + (create_autocmd_node('autocmd Group'), { + 'group': 'Group', + 'event': [], + 'pat': None, + 'nested': False, + 'cmd': None, + 'bang': False, + }), + (create_autocmd_node('autocmd! Group'), { + 'group': 'Group', + 'event': [], + 'pat': None, + 'nested': False, + 'cmd': None, + 'bang': True, + }), + (create_autocmd_node('autocmd Group *'), { + 'group': 'Group', + 'event': [AutocmdEvents.ANY], + 'pat': None, + 'nested': False, + 'cmd': None, + 'bang': False, + }), + (create_autocmd_node('autocmd! Group *'), { + 'group': 'Group', + 'event': [AutocmdEvents.ANY], + 'pat': None, + 'nested': False, + 'cmd': None, + 'bang': True, + }), + (create_autocmd_node('autocmd Group FileType'), { + 'group': 'Group', + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': None, + 'nested': False, + 'cmd': None, + 'bang': False, + }), + (create_autocmd_node('autocmd! Group FileType'), { + 'group': 'Group', + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': None, + 'nested': False, + 'cmd': None, + 'bang': True, + }), + (create_autocmd_node('autocmd Group FileType *'), { + 'group': 'Group', + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': '*', + 'nested': False, + 'cmd': None, + 'bang': False, + }), + (create_autocmd_node('autocmd! Group FileType *'), { + 'group': 'Group', + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': '*', + 'nested': False, + 'cmd': None, + 'bang': True, + }), + (create_autocmd_node('autocmd Group FileType * nested :echo'), { + 'group': 'Group', + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': '*', + 'nested': True, + 'cmd': create_echo_cmd_node(), + 'bang': False, + }), + (create_autocmd_node('autocmd! Group FileType * nested :echo'), { + 'group': 'Group', + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': '*', + 'nested': True, + 'cmd': create_echo_cmd_node(), + 'bang': True, + }), + ] + + for (autocmd_str, expected) in test_cases: + self.assertEqual( + parse_autocmd(autocmd_str), + expected, + msg=autocmd_str + ) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/unit/vint/ast/test_node_type.py b/test/unit/vint/ast/test_node_type.py index 77a3514..11c90d6 100644 --- a/test/unit/vint/ast/test_node_type.py +++ b/test/unit/vint/ast/test_node_type.py @@ -1,5 +1,5 @@ import unittest -from vint.ast.node_type import NodeType +from vint.ast.node_type import NodeType, get_node_type, is_node_type_of class TestNodeType(unittest.TestCase): @@ -7,5 +7,19 @@ def test_get_node_type_name(self): self.assertIs(NodeType(1), NodeType.TOPLEVEL) self.assertIs(NodeType(89), NodeType.REG) + + def test_get_node_type(self): + node_stub = {'type': NodeType.TOPLEVEL.value} + + self.assertEqual(get_node_type(node_stub), NodeType.TOPLEVEL) + + + def test_is_node_type_of(self): + node_stub = {'type': NodeType.TOPLEVEL.value} + + self.assertTrue(is_node_type_of(NodeType.TOPLEVEL, node_stub)) + self.assertFalse(is_node_type_of(NodeType.ECHO, node_stub)) + + if __name__ == '__main__': unittest.main() diff --git a/test/unit/vint/ast/test_traversing.py b/test/unit/vint/ast/test_traversing.py index f5b37d5..9f34079 100644 --- a/test/unit/vint/ast/test_traversing.py +++ b/test/unit/vint/ast/test_traversing.py @@ -1,19 +1,28 @@ import unittest +import enum +from functools import partial from test.asserting.ast import get_fixture_path from vint.ast.parsing import Parser -from vint.ast.node_type import NodeType -from vint.ast.traversing import traverse, SKIP_CHILDREN +from vint.ast.node_type import NodeType, is_node_type_of +from vint.ast.traversing import traverse, SKIP_CHILDREN, find_all, find_first -FIXTURE_FILE = get_fixture_path('fixture_to_traverse.vim') + + +class Fixtures(enum.Enum): + FIND_ALL = get_fixture_path('fixture_traversing_find_all.vim') + TRAVERSING = get_fixture_path('fixture_to_traverse.vim') class TestTraverse(unittest.TestCase): - def setUp(self): + def create_ast(self, filepath): parser = Parser() - self.ast = parser.parse_file(FIXTURE_FILE) + return parser.parse_file(filepath.value) + def test_traverse(self): + ast = self.create_ast(Fixtures.TRAVERSING) + expected_order_of_events = [ {'node_type': NodeType.TOPLEVEL, 'handler': 'enter'}, {'node_type': NodeType.LET, 'handler': 'enter'}, @@ -49,7 +58,7 @@ def test_traverse(self): # Records visit node type name in order actual_order_of_events = [] - traverse(self.ast, + traverse(ast, on_enter=lambda node: actual_order_of_events.append({ 'node_type': NodeType(node['type']), 'handler': 'enter', @@ -64,6 +73,8 @@ def test_traverse(self): def test_traverse_ignoring_while_children(self): + ast = self.create_ast(Fixtures.TRAVERSING) + expected_order_of_events = [ {'node_type': NodeType.TOPLEVEL, 'handler': 'enter'}, {'node_type': NodeType.LET, 'handler': 'enter'}, @@ -86,10 +97,9 @@ def on_enter(node): if NodeType(node['type']) is NodeType.WHILE: return SKIP_CHILDREN - # Records visit node type name in order actual_order_of_events = [] - traverse(self.ast, + traverse(ast, on_enter=on_enter, on_leave=lambda node: actual_order_of_events.append({ 'node_type': NodeType(node['type']), @@ -100,5 +110,33 @@ def on_enter(node): self.assertEqual(actual_order_of_events, expected_order_of_events) + def test_find_all(self): + ast = self.create_ast(Fixtures.FIND_ALL) + + found_nodes = find_all(NodeType.ECHO, ast) + + is_let_node = partial(is_node_type_of, NodeType.ECHO) + are_let_nodes = all(map(is_let_node, found_nodes)) + + self.assertEqual(3, len(found_nodes)) + self.assertTrue(are_let_nodes) + + + def test_find_with_existent_node_type(self): + ast = self.create_ast(Fixtures.FIND_ALL) + + found_node = find_first(NodeType.ECHO, ast) + + self.assertTrue(is_node_type_of(NodeType.ECHO, found_node)) + + + def test_find_with_unexistent_node_type(self): + ast = self.create_ast(Fixtures.FIND_ALL) + + found_node = find_first(NodeType.WHILE, ast) + + self.assertEqual(found_node, None) + + if __name__ == '__main__': unittest.main() diff --git a/test/unit/vint/ast/test_traversing_handler.py b/test/unit/vint/ast/test_traversing_handler.py new file mode 100644 index 0000000..408401c --- /dev/null +++ b/test/unit/vint/ast/test_traversing_handler.py @@ -0,0 +1,101 @@ +import unittest +from vint.compat.unittest import mock +import enum +from pathlib import Path + +from vint.ast.node_type import get_node_type, NodeType +from vint.ast.traversing_handler import traverse_by_handler, compose_handlers +from vint.ast.parsing import Parser + + +FIXTURE_PATH_BASE = Path('test', 'fixture', 'ast') + + +def get_fixture_path(file_path): + return Path(FIXTURE_PATH_BASE).joinpath(file_path) + + +class Fixtures(enum.Enum): + TRAVERSING = get_fixture_path('fixture_to_traverse.vim') + + +class TraversingHandlerSpy(): + def __init__(self): + self.on_enter = {node_type: self._create_logger('on_enter') for node_type in NodeType} + self.on_leave = {node_type: self._create_logger('on_leave') for node_type in NodeType} + self.handled_events = [] + + def _create_logger(self, event_name): + def logger(node): + node_type = get_node_type(node) + self.handled_events.append((event_name, node_type)) + + return logger + + +class TestTraversingHandler(unittest.TestCase): + def create_ast(self, filepath): + parser = Parser() + return parser.parse_file(filepath.value) + + def test_traverse_by_handler(self): + ast = self.create_ast(Fixtures.TRAVERSING) + spy = TraversingHandlerSpy() + + traverse_by_handler(ast, on_enter=spy.on_enter, on_leave=spy.on_leave) + + expected_order_of_events = [ + ('on_enter', NodeType.TOPLEVEL), + ('on_enter', NodeType.LET), + ('on_enter', NodeType.IDENTIFIER), + ('on_leave', NodeType.IDENTIFIER), + ('on_enter', NodeType.NUMBER), + ('on_leave', NodeType.NUMBER), + ('on_leave', NodeType.LET), + ('on_enter', NodeType.WHILE), + ('on_enter', NodeType.SMALLER), + ('on_enter', NodeType.IDENTIFIER), + ('on_leave', NodeType.IDENTIFIER), + ('on_enter', NodeType.NUMBER), + ('on_leave', NodeType.NUMBER), + ('on_leave', NodeType.SMALLER), + ('on_enter', NodeType.ECHO), + ('on_enter', NodeType.STRING), + ('on_leave', NodeType.STRING), + ('on_enter', NodeType.IDENTIFIER), + ('on_leave', NodeType.IDENTIFIER), + ('on_leave', NodeType.ECHO), + ('on_enter', NodeType.LET), + ('on_enter', NodeType.IDENTIFIER), + ('on_leave', NodeType.IDENTIFIER), + ('on_enter', NodeType.NUMBER), + ('on_leave', NodeType.NUMBER), + ('on_leave', NodeType.LET), + ('on_enter', NodeType.ENDWHILE), + ('on_leave', NodeType.ENDWHILE), + ('on_leave', NodeType.WHILE), + ('on_leave', NodeType.TOPLEVEL), + ] + self.maxDiff = 2000 + self.assertEqual(spy.handled_events, expected_order_of_events) + + + def test_compose_handlers(self): + mockA = mock.Mock() + stub_node_type = NodeType.IDENTIFIER + stub_node = {'type': stub_node_type} + + handlerA = {stub_node_type: mockA} + + mockB = mock.Mock() + handlerB = {stub_node_type: mockB} + + composed_handler = compose_handlers(handlerA, handlerB) + composed_handler[stub_node_type](stub_node) + + self.assertEqual(mockA.call_count, 1) + self.assertEqual(mockB.call_count, 1) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/unit/vint/linting/test_policy_set.py b/test/unit/vint/linting/test_policy_set.py index 7cded13..db74f13 100644 --- a/test/unit/vint/linting/test_policy_set.py +++ b/test/unit/vint/linting/test_policy_set.py @@ -31,7 +31,7 @@ def assertEnabledPolicies(self, expected_enabled_policy_classes, actual_enabled_ actual_enabled_policy_classes[actual_enabled_policy.__class__] = True pprint(actual_enabled_policy_classes) - assert all(actual_enabled_policy_classes.values()) + self.assertTrue(all(actual_enabled_policy_classes.values())) def test_get_enabled_policies_with_a_disabled_option(self): diff --git a/vint/ast/dictionary/autocmd_events.py b/vint/ast/dictionary/autocmd_events.py index 873dc2c..ea645b7 100644 --- a/vint/ast/dictionary/autocmd_events.py +++ b/vint/ast/dictionary/autocmd_events.py @@ -1,88 +1,101 @@ -AutoCmdEvents = { - 'BufAdd': True, - 'BufCreate': True, - 'BufDelete': True, - 'BufEnter': True, - 'BufFilePost': True, - 'BufFilePre': True, - 'BufHidden': True, - 'BufLeave': True, - 'BufNew': True, - 'BufNewFile': True, - 'BufRead': True, - 'BufReadCmd': True, - 'BufReadPost': True, - 'BufReadPre': True, - 'BufUnload': True, - 'BufWinEnter': True, - 'BufWinLeave': True, - 'BufWipeout': True, - 'BufWrite': True, - 'BufWriteCmd': True, - 'BufWritePost': True, - 'BufWritePre': True, - 'CmdUndefined': True, - 'CmdwinEnter': True, - 'CmdwinLeave': True, - 'ColorScheme': True, - 'CompleteDone': True, - 'CursorHold': True, - 'CursorHoldI': True, - 'CursorMoved': True, - 'CursorMovedI': True, - 'EncodingChanged': True, - 'FileAppendCmd': True, - 'FileAppendPost': True, - 'FileAppendPre': True, - 'FileChangedRO': True, - 'FileChangedShell': True, - 'FileChangedShellPost': True, - 'FileReadCmd': True, - 'FileReadPost': True, - 'FileReadPre': True, - 'FileType': True, - 'FileWriteCmd': True, - 'FileWritePost': True, - 'FileWritePre': True, - 'FilterReadPost': True, - 'FilterReadPre': True, - 'FilterWritePost': True, - 'FilterWritePre': True, - 'FocusGained': True, - 'FocusLost': True, - 'FuncUndefined': True, - 'GUIEnter': True, - 'GUIFailed': True, - 'InsertChange': True, - 'InsertCharPre': True, - 'InsertEnter': True, - 'InsertLeave': True, - 'MenuPopup': True, - 'QuickFixCmdPost': True, - 'QuickFixCmdPre': True, - 'QuitPre': True, - 'RemoteReply': True, - 'SessionLoadPost': True, - 'ShellCmdPost': True, - 'ShellFilterPost': True, - 'SourceCmd': True, - 'SourcePre': True, - 'SpellFileMissing': True, - 'StdinReadPost': True, - 'StdinReadPre': True, - 'SwapExists': True, - 'Syntax': True, - 'TabEnter': True, - 'TabLeave': True, - 'TermChanged': True, - 'TermResponse': True, - 'TextChanged': True, - 'TextChangedI': True, - 'User': True, - 'VimEnter': True, - 'VimLeave': True, - 'VimLeavePre': True, - 'VimResized': True, - 'WinEnter': True, - 'WinLeave': True, -} +from enum import Enum, unique + + +@unique +class AutocmdEvents(Enum): + BUF_ADD = 'BufAdd' + BUF_CREATE = 'BufCreate' + BUF_DELETE = 'BufDelete' + BUF_ENTER = 'BufEnter' + BUF_FILE_POST = 'BufFilePost' + BUF_FILE_PRE = 'BufFilePre' + BUF_HIDDEN = 'BufHidden' + BUF_LEAVE = 'BufLeave' + BUF_NEW = 'BufNew' + BUF_NEW_FILE = 'BufNewFile' + BUF_READ = 'BufRead' + BUF_READ_CMD = 'BufReadCmd' + BUF_READ_POST = 'BufReadPost' + BUF_READ_PRE = 'BufReadPre' + BUF_UNLOAD = 'BufUnload' + BUF_WIN_ENTER = 'BufWinEnter' + BUF_WIN_LEAVE = 'BufWinLeave' + BUF_WIPEOUT = 'BufWipeout' + BUF_WRITE = 'BufWrite' + BUF_WRITE_CMD = 'BufWriteCmd' + BUF_WRITE_POST = 'BufWritePost' + BUF_WRITE_PRE = 'BufWritePre' + CMD_UNDEFINED = 'CmdUndefined' + CMDWIN_ENTER = 'CmdwinEnter' + CMDWIN_LEAVE = 'CmdwinLeave' + COLOR_SCHEME = 'ColorScheme' + COMPLETE_DONE = 'CompleteDone' + CURSOR_HOLD = 'CursorHold' + CURSOR_HOLD_I = 'CursorHoldI' + CURSOR_MOVED = 'CursorMoved' + CURSOR_MOVED_I = 'CursorMovedI' + ENCODING_CHANGED = 'EncodingChanged' + FILE_APPEND_CMD = 'FileAppendCmd' + FILE_APPEND_POST = 'FileAppendPost' + FILE_APPEND_PRE = 'FileAppendPre' + FILE_CHANGED_RO = 'FileChangedRO' + FILE_CHANGED_SHELL = 'FileChangedShell' + FILE_CHANGED_SHELL_POST = 'FileChangedShellPost' + FILE_READ_CMD = 'FileReadCmd' + FILE_READ_POST = 'FileReadPost' + FILE_READ_PRE = 'FileReadPre' + FILE_TYPE = 'FileType' + FILE_WRITE_CMD = 'FileWriteCmd' + FILE_WRITE_POST = 'FileWritePost' + FILE_WRITE_PRE = 'FileWritePre' + FILTER_READ_POST = 'FilterReadPost' + FILTER_READ_PRE = 'FilterReadPre' + FILTER_WRITE_POST = 'FilterWritePost' + FILTER_WRITE_PRE = 'FilterWritePre' + FOCUS_GAINED = 'FocusGained' + FOCUS_LOST = 'FocusLost' + FUNC_UNDEFINED = 'FuncUndefined' + GUI_ENTER = 'GUIEnter' + GUI_FAILED = 'GUIFailed' + INSERT_CHANGE = 'InsertChange' + INSERT_CHAR_PRE = 'InsertCharPre' + INSERT_ENTER = 'InsertEnter' + INSERT_LEAVE = 'InsertLeave' + MENU_POPUP = 'MenuPopup' + QUICK_FIX_CMD_POST = 'QuickFixCmdPost' + QUICK_FIX_CMD_PRE = 'QuickFixCmdPre' + QUIT_PRE = 'QuitPre' + REMOTE_REPLY = 'RemoteReply' + SESSION_LOAD_POST = 'SessionLoadPost' + SHELL_CMD_POST = 'ShellCmdPost' + SHELL_FILTER_POST = 'ShellFilterPost' + SOURCE_CMD = 'SourceCmd' + SOURCE_PRE = 'SourcePre' + SPELL_FILE_MISSING = 'SpellFileMissing' + STDIN_READ_POST = 'StdinReadPost' + STDIN_READ_PRE = 'StdinReadPre' + SWAP_EXISTS = 'SwapExists' + SYNTAX = 'Syntax' + TAB_ENTER = 'TabEnter' + TAB_LEAVE = 'TabLeave' + TERM_CHANGED = 'TermChanged' + TERM_RESPONSE = 'TermResponse' + TEXT_CHANGED = 'TextChanged' + TEXT_CHANGED_I = 'TextChangedI' + USER = 'User' + VIM_ENTER = 'VimEnter' + VIM_LEAVE = 'VimLeave' + VIM_LEAVE_PRE = 'VimLeavePre' + VIM_RESIZED = 'VimResized' + WIN_ENTER = 'WinEnter' + WIN_LEAVE = 'WinLeave' + ANY = '*' + + +_autocmd_existence_map = {event.value.lower(): True for event in AutocmdEvents} + + +def is_autocmd_event(event_name): + """ Whether the specified string is an autocmd event. + """ + return _autocmd_existence_map.get(event_name.lower(), False) diff --git a/vint/ast/helpers.py b/vint/ast/helpers.py new file mode 100644 index 0000000..50a156f --- /dev/null +++ b/vint/ast/helpers.py @@ -0,0 +1,3 @@ +def get_cmd_name_from_excmd_node(excmd_node): + # noed.ea.cmd is empty when line jump command such as 1 + return excmd_node['ea']['cmd'].get('name', None) diff --git a/vint/ast/node_type.py b/vint/ast/node_type.py index cb13212..f8f7e5c 100644 --- a/vint/ast/node_type.py +++ b/vint/ast/node_type.py @@ -1,6 +1,16 @@ from enum import Enum, unique +def get_node_type(node): + """ Returns a node type of the specified node. """ + return NodeType(node['type']) + + +def is_node_type_of(node_type, node): + """ Whether the specified type is the node type of the node. """ + return get_node_type(node) is node_type + + @unique class NodeType(Enum): TOPLEVEL = 1 diff --git a/vint/ast/plugin/autocmd_parser.py b/vint/ast/plugin/autocmd_parser.py new file mode 100644 index 0000000..4643b59 --- /dev/null +++ b/vint/ast/plugin/autocmd_parser.py @@ -0,0 +1,176 @@ +from vint.ast.node_type import NodeType, get_node_type +from vint.ast.dictionary.autocmd_events import AutocmdEvents, is_autocmd_event +from vint.ast.plugin.abstract_ast_plugin import AbstractASTPlugin +from vint.ast.traversing import traverse, register_traverser_extension +from vint.ast.helpers import get_cmd_name_from_excmd_node + +AUTOCMD_CONTENT = 'VINT:autocmd_content' + +_autocmd_event_map = {event.value.lower(): event for event in AutocmdEvents} + + +class AutocmdParser(AbstractASTPlugin): + """ A class for :autocmd parsers. + Syntax: + :au[tocmd][!] [group] {event} {pat} [nested] {cmd} + :au[tocmd][!] [group] {event} {pat} + :au[tocmd][!] [group] * {pat} + :au[tocmd][!] [group] {event} + :au[tocmd][!] [group] + """ + def process(self, ast): + def enter_handler(node): + if get_node_type(node) is not NodeType.EXCMD or \ + get_cmd_name_from_excmd_node(node) != 'autocmd': + return + + node[AUTOCMD_CONTENT] = parse_autocmd(node) + + traverse(ast, on_enter=enter_handler) + + +def parse_autocmd(autocmd_node): + autocmd_info = { + 'group': None, + 'event': [], + 'pat': None, + 'nested': False, + 'cmd': None, + 'bang': False + } + + # type: str + autocmd_str = autocmd_node.get('str') + + # This tokens may be broken, because the cmd part can have + # whitespaces. + # type: [str] + tokens = autocmd_str.split(None, 2) + + if len(tokens) > 0: + autocmd_info['bang'] = tokens[0].endswith('!') + + if len(tokens) == 1: + # Examples: + # :au[tocmd][!] + return autocmd_info + + # Examples: + # :au[tocmd][!] [group] {event} {pat} [nested] {cmd} + # :au[tocmd][!] [group] {event} {pat} + # :au[tocmd][!] [group] * {pat} + # :au[tocmd][!] [group] {event} + # :au[tocmd][!] {group} + # ^ + # tokens[1] + has_group = not is_autocmd_event_like(tokens[1]) + + if has_group: + # Examples: + # :au[tocmd][!] {group} {event} {pat} [nested] {cmd} + # :au[tocmd][!] {group} {event} {pat} + # :au[tocmd][!] {group} * {pat} + # :au[tocmd][!] {group} {event} + # :au[tocmd][!] {group} + # ^ + # tokens[1] + autocmd_info['group'] = tokens[1] + + if len(tokens) == 2: + # Examples: + # :au[tocmd][!] {group} + return autocmd_info + + # Examples: + # :au[tocmd][!] {group} {event} {pat} [nested] {cmd} + # :au[tocmd][!] {group} {event} {pat} + # :au[tocmd][!] {group} * {pat} + # :au[tocmd][!] {group} {event} + # <-------- tokens[2] -------> + rest_str = tokens[2] + + rest_tokens = rest_str.split(None, 1) + autocmd_info['event'] = _parse_events(rest_tokens[0]) + + if len(rest_tokens) == 1: + # Examples: + # :au[tocmd][!] {group} {event} + return autocmd_info + + rest_str = rest_tokens[1] + else: + # Examples: + # :au[tocmd][!] {event} {pat} [nested] {cmd} + # :au[tocmd][!] {event} {pat} + # :au[tocmd][!] * {pat} + # :au[tocmd][!] {event} + # ^ + # tokens[1] + autocmd_info['event'] = _parse_events(tokens[1]) + + if len(tokens) == 2: + # Examples: + # :au[tocmd][!] {event} + return autocmd_info + + # Examples: + # :au[tocmd][!] {event} {pat} [nested] {cmd} + # :au[tocmd][!] {event} {pat} + # :au[tocmd][!] * {pat} + # <---- tokens[2] ---> + rest_str = tokens[2] + + # Examples: + # :au[tocmd][!] {event} {pat} [nested] {cmd} + # :au[tocmd][!] {event} {pat} + # :au[tocmd][!] * {pat} + # <---- rest_str ----> + rest_tokens = rest_str.split(None, 1) + autocmd_info['pat'] = rest_tokens[0] + + if len(rest_tokens) == 1: + # Examples: + # :au[tocmd][!] {event} {pat} + # :au[tocmd][!] * {pat} + return autocmd_info + + # :au[tocmd][!] {event} {pat} [nested] {cmd} + # :au[tocmd][!] {event} {pat} + # :au[tocmd][!] * {pat} + # ^ + # rest_tokens[1] + rest_str = rest_tokens[1] + has_nested_token = rest_str.startswith('nested ') + if not has_nested_token: + # Examples: + # :au[tocmd][!] {event} {pat} {cmd} + # ^ + # rest_str + autocmd_info['cmd'] = _parse_cmd(rest_str) + return autocmd_info + + # Examples: + # :au[tocmd][!] {event} {pat} nested {cmd} + # <- rest_str -> + rest_str = rest_str.split(None, 1)[1] + autocmd_info['cmd'] = _parse_cmd(rest_str) + autocmd_info['nested'] = True + return autocmd_info + + +def get_autocmd_content(node): + return node.get(AUTOCMD_CONTENT) + + +def _parse_cmd(cmd_str): + # TODO: Implement it + return cmd_str + + +def _parse_events(token): + parts = token.split(',') + return [_autocmd_event_map.get(event_name.lower()) for event_name in parts] + + +def is_autocmd_event_like(token): + return all([is_autocmd_event(part) for part in token.split(',')]) diff --git a/vint/ast/traversing.py b/vint/ast/traversing.py index a640d8f..577d364 100644 --- a/vint/ast/traversing.py +++ b/vint/ast/traversing.py @@ -215,6 +215,7 @@ def __str__(self): def register_traverser_extension(handler): """ Registers the specified function to traverse into extended child nodes. + You should register your traverser extension if you extend AST. """ _traverser_extensions.append(handler) @@ -248,3 +249,26 @@ def traverse(node, on_enter=None, on_leave=None): if on_leave: on_leave(node) + + +def find_first(node_type, node_to_find): + """ Returns a first node by the specified node type. """ + all_nodes = find_all(node_type, node_to_find) + + if len(all_nodes) > 0: + return all_nodes[0] + + return None + + +def find_all(node_type, node_to_find): + """ Returns all nodes by the specified node type. """ + + nodes = [] + + def node_collector(node): + if NodeType(node['type']) is node_type: + nodes.append(node) + + traverse(node_to_find, on_enter=node_collector) + return nodes diff --git a/vint/ast/traversing_handler.py b/vint/ast/traversing_handler.py new file mode 100644 index 0000000..b34924a --- /dev/null +++ b/vint/ast/traversing_handler.py @@ -0,0 +1,46 @@ +from vint.ast.node_type import get_node_type, NodeType +from vint.ast.traversing import traverse + + +def _combine(funcs): + def combined_funcs(node): + for method in funcs: + method(node) + return combined_funcs + + +def compose_handlers(*handlers): + composed_handler = {} + + for node_type in NodeType: + funcs = [handler[node_type] for handler in handlers if node_type in handler] + composed_handler[node_type] = _combine(funcs) + + return composed_handler + + +def traverse_by_handler(ast, on_enter=None, on_leave=None): + """ Traverse the specified AST by NodeType-to-function dictionary. + + USAGE: + traverse_by_handler(ast, on_enter={ NodeType.ECHO: handlerFn }) + """ + + def create_caller(handler): + if handler is None: + return None + + def caller(node): + node_type = get_node_type(node) + + if node_type not in handler: + return + + method = handler[node_type] + method(node) + + return caller + + traverse(ast, + on_enter=create_caller(on_enter), + on_leave=create_caller(on_leave)) diff --git a/vint/linting/linter.py b/vint/linting/linter.py index b5ed55e..26a3757 100644 --- a/vint/linting/linter.py +++ b/vint/linting/linter.py @@ -7,6 +7,7 @@ from vint.ast.node_type import NodeType from vint.ast.traversing import traverse from vint.ast.plugin.scope_plugin import ScopePlugin +from vint.ast.plugin.autocmd_parser import AutocmdParser from vint.linting.config.config_container import ConfigContainer from vint.linting.config.config_dict_source import ConfigDictSource from vint.linting.config.config_comment_source import ConfigCommentSource @@ -32,6 +33,7 @@ class Linter(object): def __init__(self, policy_set, config_dict_global): self._plugins = { 'scope': ScopePlugin(), + 'autocmd': AutocmdParser(), } self._policy_set = policy_set diff --git a/vint/linting/policy/prohibit_autocmd_with_no_group.py b/vint/linting/policy/prohibit_autocmd_with_no_group.py index 713c6ba..315a1c2 100644 --- a/vint/linting/policy/prohibit_autocmd_with_no_group.py +++ b/vint/linting/policy/prohibit_autocmd_with_no_group.py @@ -3,7 +3,9 @@ from vint.linting.level import Level from vint.linting.policy.abstract_policy import AbstractPolicy from vint.linting.policy_registry import register_policy -from vint.ast.dictionary.autocmd_events import AutoCmdEvents +from vint.ast.dictionary.autocmd_events import AutocmdEvents +from vint.ast.plugin.autocmd_parser import AUTOCMD_CONTENT +from pprint import pprint @register_policy @@ -27,19 +29,21 @@ def is_valid(self, node, lint_context): autocmd family should be called with any groups. """ - # noed.ea.cmd is empty when line jump command such as 1 + # node.ea.cmd is empty when line jump command such as 1 cmd_name = node['ea']['cmd'].get('name', None) is_autocmd = cmd_name == 'autocmd' if is_autocmd and not self.is_inside_of_augroup: - matched = re.match(r'au(?:tocmd)?!?\s+(\S+)', node['str']) - - if not matched: + autocmd_attr = node[AUTOCMD_CONTENT] + if autocmd_attr['bang']: # Looks like autocmd with a bang return True - has_no_group = matched.group(1) in AutoCmdEvents - return not has_no_group + has_group = autocmd_attr.get('group') is not None + if has_group: + return True + + return self.is_inside_of_augroup is_augroup = cmd_name == 'augroup' if is_augroup: