def __reindent_line_comments_after_keyword(self, args: List[str]) -> list: verifier = KeywordVerifier(self.__settings) for i in reversed(range(len(args))): if verifier.is_keyword(args[i]) and re.match( Tokens.get_reindent_regex(), args[i]): self.__try_reindent_all_previous_comments(args, i) return args
class KeywordStateUpdater: def __init__(self, state: dict, settings: dict): self.__state = state self.__verifier = KeywordVerifier(settings) def update_state(self, argument: str) -> None: if self.__verifier.is_first_class_keyword(argument): self.__state['has_first_class_keyword'] = True self.__state['indent'] += 1 elif self.__verifier.is_keyword( argument) or self.__should_indent_property(argument): if not self.__state['keyword_argument']: self.__state['indent'] += 1 self.__state['keyword_argument'] = True def __should_indent_property(self, argument) -> bool: return self.__state[ 'has_first_class_keyword'] and self.__verifier.is_property( argument)
class FormatUnquotedArgument: def __init__(self, state: dict, settings: dict): self.__verifier = KeywordVerifier(settings) self.__state_updater = KeywordStateUpdater(state, settings) self.__state = state self.__keyword_argument_already_found = False def __call__(self, data: str) -> str: self.__keyword_argument_already_found = self.__state[ 'keyword_argument'] self.__state_updater.update_state(data) return self.__format_data(data) def __format_data(self, data: str) -> str: if self.__keyword_argument_already_found and self.__is_reindent_needed( data): return Tokens.reindent(1) + data return data def __is_reindent_needed(self, data): return self.__verifier.is_keyword(data) or self.__verifier.is_property( data)
class CommandRealignModifier: __DIFF_BETWEEN_KEYWORD_AND_VALUE = 2 def __init__(self, state: dict, settings: dict): self.__state = state self.__verifier = KeywordVerifier(settings) self.__settings = settings def realign(self, invocation: dict) -> list: invocation['arguments'] = self.__realign_properties_if_needed(invocation['arguments']) invocation['arguments'] = self.__realign_double_keywords(invocation['arguments']) invocation['arguments'] = self.__realign_get_property(invocation) invocation['arguments'] = self.__realign_set_property(invocation) invocation['arguments'] = self.__realign_commands_if_needed(invocation['arguments']) return self.__realign_keyword_values_if_needed(invocation['arguments']) def __realign_commands_if_needed(self, args: List[str]) -> list: return self.__realign_commands(args) if self.__settings.get('keep_command_in_single_line') else args def __realign_commands(self, args: List[str]) -> list: diff = 0 if self.__settings.get('keyword_and_single_value_in_one_line') else CommandRealignModifier.__DIFF_BETWEEN_KEYWORD_AND_VALUE for i in range(len(args)): if KeywordVerifier.is_command_keyword(args[i]): for j in range(i + diff, len(args) - 1): if self.__verifier.is_keyword(args[j + 1]): break if Tokens.is_spacing_token(args[j]): args[j] = ' ' return args def __realign_properties_if_needed(self, args: List[str]) -> list: return self.__realign_properties(args) if self.__should_realign_properties() else args def __should_realign_properties(self) -> bool: return self.__settings['keep_property_and_value_in_one_line'] and self.__state['has_first_class_keyword'] def __realign_properties(self, args: List[str]) -> list: for i in range(len(args) - 2): if self.__is_property(args[i]) and self.__should_realign_value_after_keyword(args, i): args[i + 1] = ' ' return args def __is_property(self, argument: str) -> bool: return not self.__verifier.is_first_class_keyword(argument) and self.__verifier.is_keyword_or_property(argument) def __realign_double_keywords(self, args: List[str]) -> list: for i in range(len(args) - self.__DIFF_BETWEEN_KEYWORD_AND_VALUE): if self.__verifier.is_double_keyword(args[i], args[i + self.__DIFF_BETWEEN_KEYWORD_AND_VALUE]): args[i + 1] = ' ' return args def __realign_get_property(self, invocation: dict) -> list: if invocation['function_name'].startswith('get_property'): return self.__replace_newline_with_space_after_property_keyword(invocation['arguments']) return invocation['arguments'] def __realign_set_property(self, invocation: dict) -> list: if invocation['function_name'].startswith('set_property'): return self.__realign_property_in_set_function(invocation['arguments']) return invocation['arguments'] def __realign_property_in_set_function(self, args: List[str]) -> list: for i in range(len(args)): if KeywordVerifier.is_first_class_keyword(args[i]): args, position = self.__reindent_property_name(args, i) if self.__is_possible_to_realign_keyword_values(args) and self.__is_value_realignable(args, position): args[position + 1] = ' ' return args def __is_value_realignable(self, args, position): return not Tokens.is_line_comment(args[position + 2]) and \ self.__get_number_of_arguments(args, position) == 1 def __get_number_of_arguments(self, args: List[str], start: int) -> int: return sum([self.__is_argument(data) for data in args[start + 1:]]) def __reindent_property_name(self, args: list, start_index: int) -> Tuple[list, int]: for i in range(start_index + 1, len(args)): if self.__is_argument(args[i]): if not args[i].startswith(Tokens.reindent(1)): args[i] = Tokens.reindent(1) + args[i] return args, i return args, 0 @staticmethod def __is_argument(data: str) -> bool: return not (Tokens.is_line_comment(data) or Tokens.is_spacing_token(data)) def __replace_newline_with_space_after_property_keyword(self, args: List[str]) -> list: for i in range(len(args) - CommandRealignModifier.__DIFF_BETWEEN_KEYWORD_AND_VALUE): if self.__is_property_followed_by_name(args, i): args[i + 1] = ' ' return args def __realign_keyword_values_if_needed(self, args: List[str]) -> list: return self.__realign_keyword_values(args) if self.__is_possible_to_realign_keyword_values(args) else args def __is_possible_to_realign_keyword_values(self, args: List[str]) -> bool: return self.__settings['keyword_and_single_value_in_one_line'] and \ len(args) > CommandRealignModifier.__DIFF_BETWEEN_KEYWORD_AND_VALUE def __realign_keyword_values(self, args: List[str]) -> list: for i in range(len(args) - CommandRealignModifier.__DIFF_BETWEEN_KEYWORD_AND_VALUE): if self.__should_realign_value_after_keyword(args, i): args[i + 1] = ' ' return args def __should_realign_value_after_keyword(self, args: List[str], current_index: int) -> bool: return self.__verifier.is_keyword_or_property(args[current_index]) and \ Tokens.is_spacing_token(args[current_index + 1]) and \ not self.__verifier.is_keyword_or_property(args[current_index + 2]) and \ self.__is_single_value(args, current_index) def __is_single_value(self, args: List[str], current_index: int) -> bool: tokens_diff = 4 # keyword, space, value, space, keyword return current_index + tokens_diff >= len(args) or \ self.__verifier.is_keyword_or_property(args[current_index + tokens_diff]) @staticmethod def __is_property_followed_by_name(args: list, i: int) -> bool: return KeywordVerifier.is_first_class_keyword(args[i]) and not Tokens.is_line_comment(args[i + 2])
class TestKeywordVerifier(unittest.TestCase): def setUp(self) -> None: self.settings = { 'keywords': ['some'], 'unquoted_uppercase_as_keyword': False } self.verify = KeywordVerifier(self.settings) @mock.patch('builtins.open') def test_ensure_properties_are_read_only_once(self, mock_open: MagicMock): self.verify = KeywordVerifier(self.settings) self.assertFalse(mock_open.called) def test_should_accept_keyword_when_on_the_list(self): self.assertTrue(self.verify.is_keyword('some')) self.assertTrue(self.verify.is_keyword(Tokens.reindent(1) + 'some')) self.assertFalse(self.verify.is_keyword('some2')) self.assertFalse(self.verify.is_keyword('${some}')) def test_unquoted_arguments_with_uppercase_letters_only_are_keywords(self): self.settings['unquoted_uppercase_as_keyword'] = True self.verify = KeywordVerifier(self.settings) self.assertTrue(self.verify.is_keyword('OTHER')) self.assertTrue(self.verify.is_keyword(Tokens.reindent(1) + 'OTHER')) self.assertTrue(self.verify.is_keyword('WITH_SEPARATION')) self.assertFalse(self.verify.is_keyword('"$OTHER"')) self.assertFalse(self.verify.is_keyword('SOMeARG')) self.assertFalse(self.verify.is_keyword('a_ARGUMENT')) self.assertFalse(self.verify.is_keyword('NOT_')) self.assertFalse(self.verify.is_keyword('_SOME')) def test_whether_token_is_first_class_keyword(self): self.assertTrue(self.verify.is_first_class_keyword('PROPERTY')) self.assertTrue(self.verify.is_first_class_keyword('PROPERTIES')) self.assertTrue( self.verify.is_first_class_keyword( Tokens.reindent(1) + 'PROPERTY')) self.assertFalse(self.verify.is_first_class_keyword('PROPERTY2')) self.assertFalse(self.verify.is_first_class_keyword('proPERTY')) def test_available_properties_version(self): self.assertEqual('3.18.0', self.verify.get_cmake_properties_version()) def test_cmake_properties_matching_exactly(self): self.assertTrue(self.verify.is_property('LINK_DIRECTORIES')) self.assertTrue( self.verify.is_property('INSTALL_REMOVE_ENVIRONMENT_RPATH')) self.assertFalse( self.verify.is_property('1INSTALL_REMOVE_ENVIRONMENT_RPATH')) def test_cmake_properties_starting_with(self): self.assertTrue(self.verify.is_property('IMPORTED_NO_SONAME')) self.assertTrue(self.verify.is_property('IMPORTED_NO_SONAME_123')) self.assertFalse(self.verify.is_property('IMPORTED_NO_SONAME123')) self.assertFalse(self.verify.is_property('123_IMPORTED_NO_SONAME_123')) self.assertFalse(self.verify.is_property('IMPORTED_NO_SONAM')) def test_cmake_properties_ending_with(self): self.assertTrue(self.verify.is_property('_OUTPUT_NAME')) self.assertTrue(self.verify.is_property('VALUE_OUTPUT_NAME')) self.assertFalse(self.verify.is_property('_OUTPUT_NAME_VALUE')) def test_cmake_properties_with_reindent_token(self): self.assertTrue( self.verify.is_property(Tokens.reindent(1) + 'LINK_DIRECTORIES')) def test_double_keywords(self): self.assertTrue( self.verify.is_double_keyword( Tokens.reindent(1) + 'RUNTIME', 'DESTINATION')) self.assertTrue( self.verify.is_double_keyword('ARCHIVE', Tokens.reindent(1) + 'DESTINATION')) self.assertTrue(self.verify.is_double_keyword('LIBRARY', 'DESTINATION')) self.assertFalse(self.verify.is_double_keyword('OUTPUT', 'DESTINATION')) self.assertFalse(self.verify.is_double_keyword('LIBRARY', 'OUTPUT')) def test_recognition_of_conditional_invocation(self): self.assertTrue(KeywordVerifier.is_conditional_invocation('If(')) self.assertTrue(KeywordVerifier.is_conditional_invocation('while(')) self.assertTrue(KeywordVerifier.is_conditional_invocation('while (')) self.assertFalse( KeywordVerifier.is_conditional_invocation('foreach (')) self.assertFalse(KeywordVerifier.is_conditional_invocation('if2(')) self.assertFalse(KeywordVerifier.is_conditional_invocation('if*')) def test_is_command_keyword(self): self.assertTrue(KeywordVerifier.is_command_keyword('COMMAND')) self.assertTrue( KeywordVerifier.is_command_keyword(Tokens.reindent(1) + 'COMMAND')) self.assertTrue( KeywordVerifier.is_command_keyword(Tokens.reindent(1) + 'ARGS')) self.assertFalse(KeywordVerifier.is_command_keyword('CMD')) self.assertFalse( KeywordVerifier.is_command_keyword(Tokens.reindent(2) + 'COMMAND'))