From 290199e22d8f9af9ab2c9751f810863154b988a5 Mon Sep 17 00:00:00 2001 From: Kuniwak Date: Tue, 3 Feb 2015 08:42:16 +0900 Subject: [PATCH 01/12] Implement shorthands for type getters --- test/unit/vint/ast/test_node_type.py | 16 +++++++++++++++- vint/ast/node_type.py | 10 ++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) 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/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 From 597a5b6a2acdb914d1483aa2bbae734c5753db99 Mon Sep 17 00:00:00 2001 From: Kuniwak Date: Tue, 3 Feb 2015 08:42:43 +0900 Subject: [PATCH 02/12] Implement a nodes collector --- .../ast/fixture_traversing_find_all.vim | 6 +++ test/unit/vint/ast/test_traversing.py | 45 ++++++++++++++++++- vint/ast/traversing.py | 23 ++++++++++ 3 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 test/fixture/ast/fixture_traversing_find_all.vim 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/test_traversing.py b/test/unit/vint/ast/test_traversing.py index f5b37d5..b8d615e 100644 --- a/test/unit/vint/ast/test_traversing.py +++ b/test/unit/vint/ast/test_traversing.py @@ -1,18 +1,31 @@ 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') + + class TestTraverse(unittest.TestCase): + def create_ast(self, filepath): + parser = Parser() + return parser.parse_file(filepath.value) + + def setUp(self): + # TODO: Remove that parser = Parser() self.ast = parser.parse_file(FIXTURE_FILE) + def test_traverse(self): expected_order_of_events = [ {'node_type': NodeType.TOPLEVEL, 'handler': 'enter'}, @@ -100,5 +113,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/vint/ast/traversing.py b/vint/ast/traversing.py index a640d8f..5382460 100644 --- a/vint/ast/traversing.py +++ b/vint/ast/traversing.py @@ -248,3 +248,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 From 52cf561eb057c000e8ed92307ce60207a32dfc63 Mon Sep 17 00:00:00 2001 From: Kuniwak Date: Tue, 3 Feb 2015 08:43:57 +0900 Subject: [PATCH 03/12] Add fixtures for AutocmdParser --- .../unit/vint/ast/plugin/fixture_autocmd_parser_orthodox.vim | 1 + .../plugin/fixture_autocmd_parser_with_line_continuation.vim | 5 +++++ 2 files changed, 6 insertions(+) create mode 100644 test/unit/vint/ast/plugin/fixture_autocmd_parser_orthodox.vim create mode 100644 test/unit/vint/ast/plugin/fixture_autocmd_parser_with_line_continuation.vim 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 From 35ad2a03938a8d7d05396e818da4c7a04134e7d5 Mon Sep 17 00:00:00 2001 From: Kuniwak Date: Sat, 7 Feb 2015 18:17:04 +0900 Subject: [PATCH 04/12] Refactoring tests by removing an unwanted common fixture --- test/unit/vint/ast/test_traversing.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/test/unit/vint/ast/test_traversing.py b/test/unit/vint/ast/test_traversing.py index b8d615e..9f34079 100644 --- a/test/unit/vint/ast/test_traversing.py +++ b/test/unit/vint/ast/test_traversing.py @@ -7,11 +7,11 @@ 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): @@ -20,13 +20,9 @@ def create_ast(self, filepath): return parser.parse_file(filepath.value) - def setUp(self): - # TODO: Remove that - parser = Parser() - self.ast = parser.parse_file(FIXTURE_FILE) - - 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'}, @@ -62,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', @@ -77,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'}, @@ -99,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']), From 0b5fefc8539864470fbefea7f18de8bc0af9b601 Mon Sep 17 00:00:00 2001 From: Kuniwak Date: Sat, 30 May 2015 17:52:48 +0900 Subject: [PATCH 05/12] Implement traversing_handler interface --- test/unit/vint/ast/test_traversing_handler.py | 83 +++++++++++++++++++ vint/ast/traversing_handler.py | 33 ++++++++ 2 files changed, 116 insertions(+) create mode 100644 test/unit/vint/ast/test_traversing_handler.py create mode 100644 vint/ast/traversing_handler.py 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..275cd21 --- /dev/null +++ b/test/unit/vint/ast/test_traversing_handler.py @@ -0,0 +1,83 @@ +import unittest +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 +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(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) + + +if __name__ == '__main__': + unittest.main() diff --git a/vint/ast/traversing_handler.py b/vint/ast/traversing_handler.py new file mode 100644 index 0000000..d7596e6 --- /dev/null +++ b/vint/ast/traversing_handler.py @@ -0,0 +1,33 @@ +from vint.ast.node_type import get_node_type, NodeType +from vint.ast.traversing import traverse + + +def compose_handlers(*handlers): + composed_handler = {} + + for node_type in NodeType: + composed_handler[node_type] = [handler for handler in handlers + if node_type in handler] + + return composed_handler + + +def traverse_by_handler(ast, on_enter=None, on_leave=None): + 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)) From 8a1bebbee3e434f88cf3c88a265b0b84424e35aa Mon Sep 17 00:00:00 2001 From: Kuniwak Date: Sat, 30 May 2015 18:56:08 +0900 Subject: [PATCH 06/12] Implement compose_handlers --- test/unit/vint/ast/test_traversing_handler.py | 22 +++++++++++++++++-- vint/ast/traversing_handler.py | 11 ++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/test/unit/vint/ast/test_traversing_handler.py b/test/unit/vint/ast/test_traversing_handler.py index 275cd21..f5eed0c 100644 --- a/test/unit/vint/ast/test_traversing_handler.py +++ b/test/unit/vint/ast/test_traversing_handler.py @@ -1,9 +1,10 @@ import unittest +from 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 +from vint.ast.traversing_handler import traverse_by_handler, compose_handlers from vint.ast.parsing import Parser @@ -37,7 +38,7 @@ def create_ast(self, filepath): parser = Parser() return parser.parse_file(filepath.value) - def test_traverse(self): + def test_traverse_by_handler(self): ast = self.create_ast(Fixtures.TRAVERSING) spy = TraversingHandlerSpy() @@ -79,5 +80,22 @@ def test_traverse(self): 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/vint/ast/traversing_handler.py b/vint/ast/traversing_handler.py index d7596e6..de17b7f 100644 --- a/vint/ast/traversing_handler.py +++ b/vint/ast/traversing_handler.py @@ -2,12 +2,19 @@ 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: - composed_handler[node_type] = [handler for handler in handlers - if node_type in handler] + funcs = [handler[node_type] for handler in handlers if node_type in handler] + composed_handler[node_type] = _combine(funcs) return composed_handler From db194f5c4bf1787a1b51d453a63d2f0a7b135491 Mon Sep 17 00:00:00 2001 From: Kuniwak Date: Fri, 21 Aug 2015 14:55:41 +0900 Subject: [PATCH 07/12] More document --- vint/ast/traversing.py | 1 + vint/ast/traversing_handler.py | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/vint/ast/traversing.py b/vint/ast/traversing.py index 5382460..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) diff --git a/vint/ast/traversing_handler.py b/vint/ast/traversing_handler.py index de17b7f..b34924a 100644 --- a/vint/ast/traversing_handler.py +++ b/vint/ast/traversing_handler.py @@ -20,6 +20,12 @@ def compose_handlers(*handlers): 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 From 9f8e3b75f0f08e160ab70845c6e9370e59b37b71 Mon Sep 17 00:00:00 2001 From: Kuniwak Date: Fri, 21 Aug 2015 14:56:22 +0900 Subject: [PATCH 08/12] Add autocmd parser (but not attached yet) --- test/unit/vint/ast/dictionary/__init__.py | 0 .../ast/dictionary/test_autocmd_events.py | 14 ++ test/unit/vint/ast/test_autocmd_patser.py | 150 ++++++++++++++ vint/ast/dictionary/autocmd_events.py | 189 ++++++++++-------- vint/ast/helpers.py | 3 + vint/ast/plugin/autocmd_parser.py | 178 +++++++++++++++++ 6 files changed, 446 insertions(+), 88 deletions(-) create mode 100644 test/unit/vint/ast/dictionary/__init__.py create mode 100644 test/unit/vint/ast/dictionary/test_autocmd_events.py create mode 100644 test/unit/vint/ast/test_autocmd_patser.py create mode 100644 vint/ast/helpers.py create mode 100644 vint/ast/plugin/autocmd_parser.py 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..4f44cfb --- /dev/null +++ b/test/unit/vint/ast/dictionary/test_autocmd_events.py @@ -0,0 +1,14 @@ +import pytest +from vint.ast.dictionary.autocmd_events import is_autocmd_event + + +@pytest.mark.parametrize(('event_name', 'expected_result'), [ + ('BufReadPost', True), + ('FileType', True), + ('FILETYPE', True), + ('filetype', True), + ('*', True), + ('INVALID', False), +]) +def test_is_autocmd_event(event_name, expected_result): + assert is_autocmd_event(event_name) is expected_result diff --git a/test/unit/vint/ast/test_autocmd_patser.py b/test/unit/vint/ast/test_autocmd_patser.py new file mode 100644 index 0000000..821fced --- /dev/null +++ b/test/unit/vint/ast/test_autocmd_patser.py @@ -0,0 +1,150 @@ +import pytest +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} + + +@pytest.mark.parametrize(('autocmd_str', 'expected_result'), [ + (create_autocmd_node('autocmd'), { + 'group': None, + 'event': [], + 'pat': None, + 'nested': False, + 'cmd': None, + }), + (create_autocmd_node('autocmd!'), { + 'group': None, + 'event': [], + 'pat': None, + 'nested': False, + 'cmd': None, + }), + (create_autocmd_node('autocmd FileType'), { + 'group': None, + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': None, + 'nested': False, + 'cmd': None, + }), + (create_autocmd_node('autocmd BufNew,BufRead'), { + 'group': None, + 'event': [AutocmdEvents.BUF_NEW, AutocmdEvents.BUF_READ], + 'pat': None, + 'nested': False, + 'cmd': None, + }), + (create_autocmd_node('autocmd! FileType'), { + 'group': None, + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': None, + 'nested': False, + 'cmd': None, + }), + (create_autocmd_node('autocmd FileType *'), { + 'group': None, + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': '*', + 'nested': False, + 'cmd': None, + }), + (create_autocmd_node('autocmd! FileType *'), { + 'group': None, + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': '*', + 'nested': False, + 'cmd': None, + }), + (create_autocmd_node('autocmd FileType * nested :echo'), { + 'group': None, + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': '*', + 'nested': True, + 'cmd': create_echo_cmd_node(), + }), + (create_autocmd_node('autocmd! FileType * nested :echo'), { + 'group': None, + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': '*', + 'nested': True, + 'cmd': create_echo_cmd_node(), + }), + (create_autocmd_node('autocmd Group'), { + 'group': 'Group', + 'event': [], + 'pat': None, + 'nested': False, + 'cmd': None, + }), + (create_autocmd_node('autocmd! Group'), { + 'group': 'Group', + 'event': [], + 'pat': None, + 'nested': False, + 'cmd': None, + }), + (create_autocmd_node('autocmd Group *'), { + 'group': 'Group', + 'event': [AutocmdEvents.ANY], + 'pat': None, + 'nested': False, + 'cmd': None, + }), + (create_autocmd_node('autocmd! Group *'), { + 'group': 'Group', + 'event': [AutocmdEvents.ANY], + 'pat': None, + 'nested': False, + 'cmd': None, + }), + (create_autocmd_node('autocmd Group FileType'), { + 'group': 'Group', + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': None, + 'nested': False, + 'cmd': None, + }), + (create_autocmd_node('autocmd! Group FileType'), { + 'group': 'Group', + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': None, + 'nested': False, + 'cmd': None, + }), + (create_autocmd_node('autocmd Group FileType *'), { + 'group': 'Group', + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': '*', + 'nested': False, + 'cmd': None, + }), + (create_autocmd_node('autocmd! Group FileType *'), { + 'group': 'Group', + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': '*', + 'nested': False, + 'cmd': None, + }), + (create_autocmd_node('autocmd Group FileType * nested :echo'), { + 'group': 'Group', + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': '*', + 'nested': True, + 'cmd': create_echo_cmd_node(), + }), + (create_autocmd_node('autocmd! Group FileType * nested :echo'), { + 'group': 'Group', + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': '*', + 'nested': True, + 'cmd': create_echo_cmd_node(), + }), +]) +def test_parse_autocmd(autocmd_str, expected_result): + assert parse_autocmd(autocmd_str) == expected_result 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/plugin/autocmd_parser.py b/vint/ast/plugin/autocmd_parser.py new file mode 100644 index 0000000..167b04f --- /dev/null +++ b/vint/ast/plugin/autocmd_parser.py @@ -0,0 +1,178 @@ +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, + } + + autocmd_str = autocmd_node.get('str') + + # This tokens may be broken, because the cmd part can have + # whitespaces. + tokens = autocmd_str.split(None, 2) + 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(',')]) + + +@register_traverser_extension +def traverse_autocmd(node, on_enter=None, on_leave=None): + autocmd_content = get_autocmd_content(node) + if autocmd_content is None: + return + + traverse(autocmd_content, on_enter=on_enter, on_leave=on_leave) From 738975f210dd201218ef22c8d697c059944fb152 Mon Sep 17 00:00:00 2001 From: Kuniwak Date: Sat, 31 Oct 2015 15:32:22 +0900 Subject: [PATCH 09/12] Rename autocmd parser --- .../ast/{test_autocmd_patser.py => plugin/test_autocmd_parser.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/unit/vint/ast/{test_autocmd_patser.py => plugin/test_autocmd_parser.py} (100%) diff --git a/test/unit/vint/ast/test_autocmd_patser.py b/test/unit/vint/ast/plugin/test_autocmd_parser.py similarity index 100% rename from test/unit/vint/ast/test_autocmd_patser.py rename to test/unit/vint/ast/plugin/test_autocmd_parser.py From 785d29e6a7b6c0d62e039f16e69f768d4be20e15 Mon Sep 17 00:00:00 2001 From: Kuniwak Date: Sun, 12 Nov 2017 10:13:09 +0900 Subject: [PATCH 10/12] Fix a typo --- vint/linting/policy/prohibit_autocmd_with_no_group.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vint/linting/policy/prohibit_autocmd_with_no_group.py b/vint/linting/policy/prohibit_autocmd_with_no_group.py index 713c6ba..18212d0 100644 --- a/vint/linting/policy/prohibit_autocmd_with_no_group.py +++ b/vint/linting/policy/prohibit_autocmd_with_no_group.py @@ -3,7 +3,7 @@ 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 @register_policy @@ -38,7 +38,7 @@ def is_valid(self, node, lint_context): # Looks like autocmd with a bang return True - has_no_group = matched.group(1) in AutoCmdEvents + has_no_group = matched.group(1) in AutocmdEvents return not has_no_group is_augroup = cmd_name == 'augroup' From 2c453ee8765ce58f36e05522265136c16fcb68bc Mon Sep 17 00:00:00 2001 From: Kuniwak Date: Sun, 12 Nov 2017 11:43:40 +0900 Subject: [PATCH 11/12] Remove pytest specific features --- test/asserting/policy.py | 8 +- .../ast/dictionary/test_autocmd_events.py | 33 +- .../scope_plugin/test_scope_detector.py | 377 +++++++++--------- .../vint/ast/plugin/test_autocmd_parser.py | 287 ++++++------- test/unit/vint/ast/test_traversing_handler.py | 2 +- test/unit/vint/linting/test_policy_set.py | 2 +- 6 files changed, 367 insertions(+), 342 deletions(-) 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/unit/vint/ast/dictionary/test_autocmd_events.py b/test/unit/vint/ast/dictionary/test_autocmd_events.py index 4f44cfb..3f33b86 100644 --- a/test/unit/vint/ast/dictionary/test_autocmd_events.py +++ b/test/unit/vint/ast/dictionary/test_autocmd_events.py @@ -1,14 +1,25 @@ -import pytest +import unittest from vint.ast.dictionary.autocmd_events import is_autocmd_event -@pytest.mark.parametrize(('event_name', 'expected_result'), [ - ('BufReadPost', True), - ('FileType', True), - ('FILETYPE', True), - ('filetype', True), - ('*', True), - ('INVALID', False), -]) -def test_is_autocmd_event(event_name, expected_result): - assert is_autocmd_event(event_name) is expected_result +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/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 index 821fced..fd894ce 100644 --- a/test/unit/vint/ast/plugin/test_autocmd_parser.py +++ b/test/unit/vint/ast/plugin/test_autocmd_parser.py @@ -1,4 +1,4 @@ -import pytest +import unittest from vint.ast.dictionary.autocmd_events import AutocmdEvents from vint.ast.plugin.autocmd_parser import parse_autocmd @@ -11,140 +11,151 @@ def create_autocmd_node(autocmd_str): return {'str': autocmd_str} -@pytest.mark.parametrize(('autocmd_str', 'expected_result'), [ - (create_autocmd_node('autocmd'), { - 'group': None, - 'event': [], - 'pat': None, - 'nested': False, - 'cmd': None, - }), - (create_autocmd_node('autocmd!'), { - 'group': None, - 'event': [], - 'pat': None, - 'nested': False, - 'cmd': None, - }), - (create_autocmd_node('autocmd FileType'), { - 'group': None, - 'event': [AutocmdEvents.FILE_TYPE], - 'pat': None, - 'nested': False, - 'cmd': None, - }), - (create_autocmd_node('autocmd BufNew,BufRead'), { - 'group': None, - 'event': [AutocmdEvents.BUF_NEW, AutocmdEvents.BUF_READ], - 'pat': None, - 'nested': False, - 'cmd': None, - }), - (create_autocmd_node('autocmd! FileType'), { - 'group': None, - 'event': [AutocmdEvents.FILE_TYPE], - 'pat': None, - 'nested': False, - 'cmd': None, - }), - (create_autocmd_node('autocmd FileType *'), { - 'group': None, - 'event': [AutocmdEvents.FILE_TYPE], - 'pat': '*', - 'nested': False, - 'cmd': None, - }), - (create_autocmd_node('autocmd! FileType *'), { - 'group': None, - 'event': [AutocmdEvents.FILE_TYPE], - 'pat': '*', - 'nested': False, - 'cmd': None, - }), - (create_autocmd_node('autocmd FileType * nested :echo'), { - 'group': None, - 'event': [AutocmdEvents.FILE_TYPE], - 'pat': '*', - 'nested': True, - 'cmd': create_echo_cmd_node(), - }), - (create_autocmd_node('autocmd! FileType * nested :echo'), { - 'group': None, - 'event': [AutocmdEvents.FILE_TYPE], - 'pat': '*', - 'nested': True, - 'cmd': create_echo_cmd_node(), - }), - (create_autocmd_node('autocmd Group'), { - 'group': 'Group', - 'event': [], - 'pat': None, - 'nested': False, - 'cmd': None, - }), - (create_autocmd_node('autocmd! Group'), { - 'group': 'Group', - 'event': [], - 'pat': None, - 'nested': False, - 'cmd': None, - }), - (create_autocmd_node('autocmd Group *'), { - 'group': 'Group', - 'event': [AutocmdEvents.ANY], - 'pat': None, - 'nested': False, - 'cmd': None, - }), - (create_autocmd_node('autocmd! Group *'), { - 'group': 'Group', - 'event': [AutocmdEvents.ANY], - 'pat': None, - 'nested': False, - 'cmd': None, - }), - (create_autocmd_node('autocmd Group FileType'), { - 'group': 'Group', - 'event': [AutocmdEvents.FILE_TYPE], - 'pat': None, - 'nested': False, - 'cmd': None, - }), - (create_autocmd_node('autocmd! Group FileType'), { - 'group': 'Group', - 'event': [AutocmdEvents.FILE_TYPE], - 'pat': None, - 'nested': False, - 'cmd': None, - }), - (create_autocmd_node('autocmd Group FileType *'), { - 'group': 'Group', - 'event': [AutocmdEvents.FILE_TYPE], - 'pat': '*', - 'nested': False, - 'cmd': None, - }), - (create_autocmd_node('autocmd! Group FileType *'), { - 'group': 'Group', - 'event': [AutocmdEvents.FILE_TYPE], - 'pat': '*', - 'nested': False, - 'cmd': None, - }), - (create_autocmd_node('autocmd Group FileType * nested :echo'), { - 'group': 'Group', - 'event': [AutocmdEvents.FILE_TYPE], - 'pat': '*', - 'nested': True, - 'cmd': create_echo_cmd_node(), - }), - (create_autocmd_node('autocmd! Group FileType * nested :echo'), { - 'group': 'Group', - 'event': [AutocmdEvents.FILE_TYPE], - 'pat': '*', - 'nested': True, - 'cmd': create_echo_cmd_node(), - }), -]) -def test_parse_autocmd(autocmd_str, expected_result): - assert parse_autocmd(autocmd_str) == expected_result +class TestAutocmdParser(unittest.TestCase): + def test_parse_autocmd(self): + test_cases = [ + (create_autocmd_node('autocmd'), { + 'group': None, + 'event': [], + 'pat': None, + 'nested': False, + 'cmd': None, + }), + (create_autocmd_node('autocmd!'), { + 'group': None, + 'event': [], + 'pat': None, + 'nested': False, + 'cmd': None, + }), + (create_autocmd_node('autocmd FileType'), { + 'group': None, + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': None, + 'nested': False, + 'cmd': None, + }), + (create_autocmd_node('autocmd BufNew,BufRead'), { + 'group': None, + 'event': [AutocmdEvents.BUF_NEW, AutocmdEvents.BUF_READ], + 'pat': None, + 'nested': False, + 'cmd': None, + }), + (create_autocmd_node('autocmd! FileType'), { + 'group': None, + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': None, + 'nested': False, + 'cmd': None, + }), + (create_autocmd_node('autocmd FileType *'), { + 'group': None, + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': '*', + 'nested': False, + 'cmd': None, + }), + (create_autocmd_node('autocmd! FileType *'), { + 'group': None, + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': '*', + 'nested': False, + 'cmd': None, + }), + (create_autocmd_node('autocmd FileType * nested :echo'), { + 'group': None, + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': '*', + 'nested': True, + 'cmd': create_echo_cmd_node(), + }), + (create_autocmd_node('autocmd! FileType * nested :echo'), { + 'group': None, + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': '*', + 'nested': True, + 'cmd': create_echo_cmd_node(), + }), + (create_autocmd_node('autocmd Group'), { + 'group': 'Group', + 'event': [], + 'pat': None, + 'nested': False, + 'cmd': None, + }), + (create_autocmd_node('autocmd! Group'), { + 'group': 'Group', + 'event': [], + 'pat': None, + 'nested': False, + 'cmd': None, + }), + (create_autocmd_node('autocmd Group *'), { + 'group': 'Group', + 'event': [AutocmdEvents.ANY], + 'pat': None, + 'nested': False, + 'cmd': None, + }), + (create_autocmd_node('autocmd! Group *'), { + 'group': 'Group', + 'event': [AutocmdEvents.ANY], + 'pat': None, + 'nested': False, + 'cmd': None, + }), + (create_autocmd_node('autocmd Group FileType'), { + 'group': 'Group', + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': None, + 'nested': False, + 'cmd': None, + }), + (create_autocmd_node('autocmd! Group FileType'), { + 'group': 'Group', + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': None, + 'nested': False, + 'cmd': None, + }), + (create_autocmd_node('autocmd Group FileType *'), { + 'group': 'Group', + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': '*', + 'nested': False, + 'cmd': None, + }), + (create_autocmd_node('autocmd! Group FileType *'), { + 'group': 'Group', + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': '*', + 'nested': False, + 'cmd': None, + }), + (create_autocmd_node('autocmd Group FileType * nested :echo'), { + 'group': 'Group', + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': '*', + 'nested': True, + 'cmd': create_echo_cmd_node(), + }), + (create_autocmd_node('autocmd! Group FileType * nested :echo'), { + 'group': 'Group', + 'event': [AutocmdEvents.FILE_TYPE], + 'pat': '*', + 'nested': True, + 'cmd': create_echo_cmd_node(), + }), + ] + + 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_traversing_handler.py b/test/unit/vint/ast/test_traversing_handler.py index f5eed0c..408401c 100644 --- a/test/unit/vint/ast/test_traversing_handler.py +++ b/test/unit/vint/ast/test_traversing_handler.py @@ -1,5 +1,5 @@ import unittest -from compat.unittest import mock +from vint.compat.unittest import mock import enum from pathlib import Path 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): From a0ca3c92103161f2c13ae73f67fbdb511f277ee9 Mon Sep 17 00:00:00 2001 From: Kuniwak Date: Sun, 12 Nov 2017 12:15:43 +0900 Subject: [PATCH 12/12] Make test pass --- .../vint/ast/plugin/test_autocmd_parser.py | 19 +++++++++++++++++++ vint/ast/plugin/autocmd_parser.py | 16 +++++++--------- vint/linting/linter.py | 2 ++ .../policy/prohibit_autocmd_with_no_group.py | 16 ++++++++++------ 4 files changed, 38 insertions(+), 15 deletions(-) diff --git a/test/unit/vint/ast/plugin/test_autocmd_parser.py b/test/unit/vint/ast/plugin/test_autocmd_parser.py index fd894ce..30458c1 100644 --- a/test/unit/vint/ast/plugin/test_autocmd_parser.py +++ b/test/unit/vint/ast/plugin/test_autocmd_parser.py @@ -20,6 +20,7 @@ def test_parse_autocmd(self): 'pat': None, 'nested': False, 'cmd': None, + 'bang': False, }), (create_autocmd_node('autocmd!'), { 'group': None, @@ -27,6 +28,7 @@ def test_parse_autocmd(self): 'pat': None, 'nested': False, 'cmd': None, + 'bang': True, }), (create_autocmd_node('autocmd FileType'), { 'group': None, @@ -34,6 +36,7 @@ def test_parse_autocmd(self): 'pat': None, 'nested': False, 'cmd': None, + 'bang': False, }), (create_autocmd_node('autocmd BufNew,BufRead'), { 'group': None, @@ -41,6 +44,7 @@ def test_parse_autocmd(self): 'pat': None, 'nested': False, 'cmd': None, + 'bang': False, }), (create_autocmd_node('autocmd! FileType'), { 'group': None, @@ -48,6 +52,7 @@ def test_parse_autocmd(self): 'pat': None, 'nested': False, 'cmd': None, + 'bang': True, }), (create_autocmd_node('autocmd FileType *'), { 'group': None, @@ -55,6 +60,7 @@ def test_parse_autocmd(self): 'pat': '*', 'nested': False, 'cmd': None, + 'bang': False, }), (create_autocmd_node('autocmd! FileType *'), { 'group': None, @@ -62,6 +68,7 @@ def test_parse_autocmd(self): 'pat': '*', 'nested': False, 'cmd': None, + 'bang': True, }), (create_autocmd_node('autocmd FileType * nested :echo'), { 'group': None, @@ -69,6 +76,7 @@ def test_parse_autocmd(self): 'pat': '*', 'nested': True, 'cmd': create_echo_cmd_node(), + 'bang': False, }), (create_autocmd_node('autocmd! FileType * nested :echo'), { 'group': None, @@ -76,6 +84,7 @@ def test_parse_autocmd(self): 'pat': '*', 'nested': True, 'cmd': create_echo_cmd_node(), + 'bang': True, }), (create_autocmd_node('autocmd Group'), { 'group': 'Group', @@ -83,6 +92,7 @@ def test_parse_autocmd(self): 'pat': None, 'nested': False, 'cmd': None, + 'bang': False, }), (create_autocmd_node('autocmd! Group'), { 'group': 'Group', @@ -90,6 +100,7 @@ def test_parse_autocmd(self): 'pat': None, 'nested': False, 'cmd': None, + 'bang': True, }), (create_autocmd_node('autocmd Group *'), { 'group': 'Group', @@ -97,6 +108,7 @@ def test_parse_autocmd(self): 'pat': None, 'nested': False, 'cmd': None, + 'bang': False, }), (create_autocmd_node('autocmd! Group *'), { 'group': 'Group', @@ -104,6 +116,7 @@ def test_parse_autocmd(self): 'pat': None, 'nested': False, 'cmd': None, + 'bang': True, }), (create_autocmd_node('autocmd Group FileType'), { 'group': 'Group', @@ -111,6 +124,7 @@ def test_parse_autocmd(self): 'pat': None, 'nested': False, 'cmd': None, + 'bang': False, }), (create_autocmd_node('autocmd! Group FileType'), { 'group': 'Group', @@ -118,6 +132,7 @@ def test_parse_autocmd(self): 'pat': None, 'nested': False, 'cmd': None, + 'bang': True, }), (create_autocmd_node('autocmd Group FileType *'), { 'group': 'Group', @@ -125,6 +140,7 @@ def test_parse_autocmd(self): 'pat': '*', 'nested': False, 'cmd': None, + 'bang': False, }), (create_autocmd_node('autocmd! Group FileType *'), { 'group': 'Group', @@ -132,6 +148,7 @@ def test_parse_autocmd(self): 'pat': '*', 'nested': False, 'cmd': None, + 'bang': True, }), (create_autocmd_node('autocmd Group FileType * nested :echo'), { 'group': 'Group', @@ -139,6 +156,7 @@ def test_parse_autocmd(self): 'pat': '*', 'nested': True, 'cmd': create_echo_cmd_node(), + 'bang': False, }), (create_autocmd_node('autocmd! Group FileType * nested :echo'), { 'group': 'Group', @@ -146,6 +164,7 @@ def test_parse_autocmd(self): 'pat': '*', 'nested': True, 'cmd': create_echo_cmd_node(), + 'bang': True, }), ] diff --git a/vint/ast/plugin/autocmd_parser.py b/vint/ast/plugin/autocmd_parser.py index 167b04f..4643b59 100644 --- a/vint/ast/plugin/autocmd_parser.py +++ b/vint/ast/plugin/autocmd_parser.py @@ -36,13 +36,20 @@ def parse_autocmd(autocmd_node): '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][!] @@ -167,12 +174,3 @@ def _parse_events(token): def is_autocmd_event_like(token): return all([is_autocmd_event(part) for part in token.split(',')]) - - -@register_traverser_extension -def traverse_autocmd(node, on_enter=None, on_leave=None): - autocmd_content = get_autocmd_content(node) - if autocmd_content is None: - return - - traverse(autocmd_content, on_enter=on_enter, on_leave=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 18212d0..315a1c2 100644 --- a/vint/linting/policy/prohibit_autocmd_with_no_group.py +++ b/vint/linting/policy/prohibit_autocmd_with_no_group.py @@ -4,6 +4,8 @@ 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.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: