From a3348df042e15bb7cb3e648ea75f80be3aa4017e Mon Sep 17 00:00:00 2001 From: aptrishu Date: Sat, 8 Apr 2017 22:11:06 +0530 Subject: [PATCH] AnnotationBear: Return precise string/comment info Earlier the return used to be either a tuple of source ranges of strings/comments now it is a tuple of dicts with keys 'start_delimiter_range', 'end_delimiter_range' 'content_range', 'full_range' of singleline strings/multiline strings/singleline comments/multiline comments. --- bear-requirements.txt | 1 + bears/general/AnnotationBear.py | 278 +++++++++++++++------- bears/general/IndentationBear.py | 52 +++-- bears/general/KeywordBear.py | 8 +- bears/general/QuotesBear.py | 22 +- tests/general/AnnotationBearTest.py | 343 ++++++++++++++++++++++++---- tests/general/KeywordBearTest.py | 77 ++++--- tests/general/QuotesBearTest.py | 30 ++- 8 files changed, 608 insertions(+), 203 deletions(-) diff --git a/bear-requirements.txt b/bear-requirements.txt index 93fa45fb9c..ff53c371ed 100644 --- a/bear-requirements.txt +++ b/bear-requirements.txt @@ -1,6 +1,7 @@ HTTPolice~=0.5.2 aenum~=2.0.8 apertium-lint~=0.29 +attrs==17.2.0 autoflake~=0.7 autopep8~=1.2 bandit~=1.2 diff --git a/bears/general/AnnotationBear.py b/bears/general/AnnotationBear.py index 17dc53a897..b96da802b9 100644 --- a/bears/general/AnnotationBear.py +++ b/bears/general/AnnotationBear.py @@ -1,3 +1,5 @@ +import attr + from coalib.bearlib.languages.LanguageDefinition import LanguageDefinition from coalib.bears.LocalBear import LocalBear from coalib.results.HiddenResult import HiddenResult @@ -23,10 +25,12 @@ def run(self, filename, file, language: str, coalang_dir: str = None): :param coalang_dir: External directory for coalang file. :return: - One HiddenResult containing a dictionary with keys being 'strings' - or 'comments' and values being a tuple of SourceRanges pointing to - the strings and a tuple of SourceRanges pointing to all comments - respectively. The ranges do include string quotes or the comment + One HiddenResult containing a dictionary with keys being + 'singleline strings', 'multiline strings', 'singleline comments' + and 'multiline comments' and values being a dictionary with + keys ```'start_delimiter_range', 'end_delimiter_range' + 'content_range', 'full_range'```. + The ranges do include string quotes or the comment starting separator but not anything before (e.g. when using ``u"string"``, the ``u`` will not be in the source range). """ @@ -44,9 +48,9 @@ def run(self, filename, file, language: str, coalang_dir: str = None): multiline_comment_delimiters = dict( lang_dict['multiline_comment_delimiters']) comment_delimiter = dict(lang_dict['comment_delimiter']) - string_ranges = comment_ranges = () + ranges = [] try: - string_ranges, comment_ranges = self.find_annotation_ranges( + ranges = self.find_annotation_ranges( file, filename, string_delimiters, @@ -58,7 +62,7 @@ def run(self, filename, file, language: str, coalang_dir: str = None): yield Result(self, str(e), severity=RESULT_SEVERITY.MAJOR, affected_code=(e.code,)) - content = {'strings': string_ranges, 'comments': comment_ranges} + content = AnnotationContent(ranges) yield HiddenResult(self, content) def find_annotation_ranges(self, @@ -88,66 +92,99 @@ def find_annotation_ranges(self, A dictionary containing the various ways to define multi-line comments in a language. :return: - Two tuples first containing a tuple of strings, the second a tuple - of comments. + Four tuples containing dictionary for singleline strings, + multiline strings, singleline comments and multiline comments + respectively. """ text = ''.join(file) - strings_range = [] - comments_range = [] + annotations_range = [] position = 0 + while position <= len(text): def get_new_position(): - _range, end_position = self.get_range_end_position( - file, - filename, - text, - multiline_string_delimiters, - position, - self.get_multiline) - if end_position and _range: - strings_range.append(_range) + end_position, start_delim, end_delim = ( + self.get_range_end_position( + file, + filename, + text, + multiline_string_delimiters, + position, + self.get_multiline)) + if end_position: + seperate_ranges = get_seperate_ranges(file, + filename, + start_delim, + end_delim, + position, + end_position) + annotations_range.append(AnnotationRange( + 'multiline_string', seperate_ranges)) return end_position + 1 - _range, end_position = self.get_range_end_position( - file, - filename, - text, - string_delimiters, - position, - self.get_singleline_strings) - if end_position and _range: - strings_range.append(_range) + end_position, start_delim, end_delim = ( + self.get_range_end_position( + file, + filename, + text, + string_delimiters, + position, + self.get_singleline_strings)) + if end_position: + seperate_ranges = get_seperate_ranges(file, + filename, + start_delim, + end_delim, + position, + end_position) + annotations_range.append(AnnotationRange( + 'singleline_string', seperate_ranges)) return end_position + 1 - _range, end_position = self.get_range_end_position( - file, - filename, - text, - multiline_comment_delimiters, - position, - self.get_multiline) - if end_position and _range: - comments_range.append(_range) + end_position, start_delim, end_delim = ( + self.get_range_end_position( + file, + filename, + text, + multiline_comment_delimiters, + position, + self.get_multiline)) + if end_position: + seperate_ranges = get_seperate_ranges(file, + filename, + start_delim, + end_delim, + position, + end_position) + annotations_range.append(AnnotationRange( + 'multiline_comment', seperate_ranges)) return end_position + 1 - _range, end_position = self.get_range_end_position( - file, - filename, - text, - comment_delimiter, - position, - self.get_singleline_comment, - single_comment=True) - if end_position and _range: - comments_range.append(_range) + end_position, start_delim, end_delim = ( + self.get_range_end_position( + file, + filename, + text, + comment_delimiter, + position, + self.get_singleline_comment, + single_comment=True)) + if end_position: + seperate_ranges = get_seperate_ranges(file, + filename, + start_delim, + end_delim, + position, + end_position) + annotations_range.append(AnnotationRange( + 'singleline_comment', seperate_ranges)) return end_position + 1 return position + 1 position = get_new_position() - return tuple(strings_range), tuple(comments_range) + return annotations_range @staticmethod def get_range_end_position(file, @@ -157,26 +194,27 @@ def get_range_end_position(file, position, func, single_comment=False): - _range = end_position = None + selected_annotation = end_position = selected_end_annotation = None for annotation in annotations.keys(): if text[position:].startswith(annotation): + selected_annotation = annotation if not single_comment: - ret_val = func(file, - filename, - text, - annotation, - annotations[annotation], - position) + selected_end_annotation = annotations[selected_annotation] + end_position = func(file, + filename, + text, + annotation, + annotations[annotation], + position) else: - ret_val = func(file, - filename, - text, - annotation, - position) - if ret_val: - _range, end_position = ret_val[0], ret_val[1] + selected_end_annotation = '\n' + end_position = func(file, + filename, + text, + annotation, + position) - return _range, end_position + return end_position, selected_annotation, selected_end_annotation @staticmethod def get_multiline(file, @@ -200,8 +238,7 @@ def get_multiline(file, :param position: An integer identifying the position where the annotation started. :return: - A SourceRange object holding the range of the multi-line annotation - and the end_position of the annotation as an integer. + The end_position of the annotation as an integer. """ end_end = get_end_position(annotation_end, text, @@ -212,11 +249,7 @@ def get_multiline(file, AbsolutePosition(file, position)) raise NoCloseError(annotation_start, _range) - return (SourceRange.from_absolute_position( - filename, - AbsolutePosition(file, position), - AbsolutePosition(file, end_end)), - end_end) + return end_end @staticmethod def get_singleline_strings(file, @@ -239,8 +272,7 @@ def get_singleline_strings(file, :position: An integer identifying the position where the string started. :return: - A SourceRange object identifying the range of the single-line - string and the end_position of the string as an integer. + The end_position of the string as an integer. """ end_position = get_end_position(string_end, text, @@ -254,11 +286,7 @@ def get_singleline_strings(file, AbsolutePosition(file, position)) raise NoCloseError(string_start, _range) if newline > end_position: - return (SourceRange.from_absolute_position( - filename, - AbsolutePosition(file, position), - AbsolutePosition(file, end_position)), - end_position) + return end_position @staticmethod def get_singleline_comment(file, filename, text, comment, position): @@ -275,19 +303,14 @@ def get_singleline_comment(file, filename, text, comment, position): :position: An integer identifying the position where the string started. :return: - A SourceRange object identifying the range of the single-line - comment and the end_position of the comment as an integer. + The end_position of the comment as an integer. """ end_position = get_end_position('\n', text, position + len(comment) - 1) if end_position == -1: end_position = len(text) - 1 - return (SourceRange.from_absolute_position( - filename, - AbsolutePosition(file, position), - AbsolutePosition(file, end_position)), - end_position) + return end_position def get_end_position(end_marker, text, position): @@ -300,8 +323,93 @@ def get_end_position(end_marker, text, position): return end_position +def get_seperate_ranges(file, + filename, + start_delim, + end_delim, + start_position, + end_position): + ranges = [] + ranges.append(SourceRange.from_absolute_position( + filename, + AbsolutePosition(file, start_position), + AbsolutePosition(file, start_position + len(start_delim) - 1))) + + ranges.append(SourceRange.from_absolute_position( + filename, + AbsolutePosition(file, end_position - len(end_delim) + 1), + AbsolutePosition(file, end_position))) + + if start_position + 1 == end_position: # empty string so no content + ranges.append([]) + else: + ranges.append(SourceRange.from_absolute_position( + filename, + AbsolutePosition(file, start_position + len(start_delim)), + AbsolutePosition(file, end_position - len(end_delim)))) + + ranges.append(SourceRange.from_absolute_position( + filename, + AbsolutePosition(file, start_position), + AbsolutePosition(file, end_position))) + + return ranges + + class NoCloseError(Exception): def __init__(self, annotation, code): Exception.__init__(self, annotation + ' has no closure') self.code = code + + +@attr.s +class AnnotationRange: + + AnnotationType = attr.ib(convert=str) + range = attr.ib() + start_delimiter_range = attr.ib(init=False) + end_delimiter_range = attr.ib(init=False) + content_range = attr.ib(init=False) + full_range = attr.ib(init=False) + + def __attrs_post_init__(self): + if len(self.range) == 4: + self.start_delimiter_range = self.range[0] + self.end_delimiter_range = self.range[1] + self.content_range = self.range[2] + self.full_range = self.range[3] + else: + self.start_delimiter_range = [] + self.end_delimiter_range = [] + self.content_range = [] + self.full_range = [] + + +@attr.s +class AnnotationContent: + + singleline_strings = attr.ib(init=False) + multiline_strings = attr.ib(init=False) + singleline_comments = attr.ib(init=False) + multiline_comments = attr.ib(init=False) + ranges = attr.ib() + + def __attrs_post_init__(self): + single_strings = [] + multi_strings = [] + single_comments = [] + multi_comments = [] + for range in self.ranges: + if range.AnnotationType == 'singleline_string': + single_strings.append(range) + elif range.AnnotationType == 'multiline_string': + multi_strings.append(range) + elif range.AnnotationType == 'multiline_comment': + multi_comments.append(range) + elif range.AnnotationType == 'singleline_comment': + single_comments.append(range) + self.singleline_strings = single_strings + self.multiline_strings = multi_strings + self.singleline_comments = single_comments + self.multiline_comments = multi_comments diff --git a/bears/general/IndentationBear.py b/bears/general/IndentationBear.py index 6933a67058..d9e1e5e38f 100644 --- a/bears/general/IndentationBear.py +++ b/bears/general/IndentationBear.py @@ -67,7 +67,12 @@ def run(self, """ lang_settings_dict = LanguageDefinition( language, coalang_dir=coalang_dir) - annotation_dict = dependency_results[AnnotationBear.name][0].contents + dep_contents = dependency_results[AnnotationBear.name][0].contents + annotation_dict = {} + annotation_dict['strings'] = (dep_contents.singleline_strings + + dep_contents.multiline_strings) + annotation_dict['comments'] = (dep_contents.singleline_comments + + dep_contents.multiline_comments) # sometimes can't convert strings with ':' to dict correctly if ':' in dict(lang_settings_dict['indent_types']).keys(): indent_types = dict(lang_settings_dict['indent_types']) @@ -151,8 +156,8 @@ def _get_absolute_indent_file(self, new_line = (prev_indent + ' '*indent + insert*(indent_levels[line] - prev_indent_level) + indented_file[line].lstrip()) - indented_file[line] = new_line if new_line.strip() != ''\ - else '\n' + indented_file[line] = (new_line if new_line.strip() != '' + else '\n') return indented_file def get_absolute_indent_of_range(self, @@ -168,7 +173,7 @@ def get_absolute_indent_of_range(self, :param encaps_pos: A tuple ofSourceRanges of code regions trapped in between a matching pair of encapsulators. - :param annotation_dict: A dictionary containing sourceranges of all the + :param annotation_dict: A dictionary containing dicts of all the strings and comments within a file. :return: A tuple of tuples with first element as the range of encapsulator and second element as the @@ -196,7 +201,7 @@ def get_indent_levels(self, :param filename: Name of the file that needs to be checked. :param indent_types: A dictionary with keys as start of indent and values as their corresponding closing indents. - :param annotation_dict: A dictionary containing sourceranges of all the + :param annotation_dict: A dictionary containing dicts of all the strings and comments within a file. :param encapsulators: A tuple of sourceranges of all encapsulators of a language. @@ -255,7 +260,7 @@ def get_specified_block_range(self, block has begun. :param close_specifier: A character or string indicating that the block has ended. - :param annotation_dict: A dictionary containing sourceranges of all the + :param annotation_dict: A dictionary containing dicts of all the strings and comments within a file. :return: A tuple with the first source range being the range of the outermost indentation while @@ -324,7 +329,7 @@ def get_unspecified_block_range(self, :param filename: Name of the file that needs to be checked. :param indent_specifier: A character or string indicating that the indentation should begin. - :param annotation_dict: A dictionary containing sourceranges of all the + :param annotation_dict: A dictionary containing dicts of all the strings and comments within a file. :param encapsulators: A tuple of sourceranges of all encapsulators of a language. @@ -372,7 +377,7 @@ def get_valid_sequences(file, :param file: File that needs to be checked in the form of a list of strings. :param sequence: Sequence whose validity is to be checked. - :param annotation_dict: A dictionary containing sourceranges of all the + :param annotation_dict: A dictionary containing dicts of all the strings and comments within a file. :param encapsulators: A tuple of SourceRanges of code regions trapped in between a matching pair of @@ -394,22 +399,23 @@ def get_valid_sequences(file, # ignore if within string for string in annotation_dict['strings']: - if(gt_eq(sequence_position, string.start) and - lt_eq(sequence_position, string.end)): + if(gt_eq(sequence_position, string.full_range.start) and + lt_eq(sequence_position, string.full_range.end)): valid = False # ignore if within comments for comment in annotation_dict['comments']: - if(gt_eq(sequence_position, comment.start) and - lt_eq(sequence_position, comment.end)): + if(gt_eq(sequence_position, comment.full_range.start) and + lt_eq(sequence_position, comment.full_range.end)): valid = False - if(comment.start.line == sequence_position.line and - comment.end.line == sequence_position.line and - check_ending): - sequence_line_text = sequence_line_text[ - :comment.start.column - 1] + sequence_line_text[ - comment.end.column-1:] + if(comment.full_range.start.line == sequence_position.line + and comment.full_range.end.line == + sequence_position.line and check_ending): + sequence_line_text = (sequence_line_text[ + :comment.full_range.start.column - 1] + + sequence_line_text[comment.full_range.end.column-1: + ]) if encapsulators: for encapsulator in encapsulators: @@ -462,7 +468,7 @@ def get_first_unindent(indent, :param file: A tuple of strings. :param start_line: The line from where to start searching for unindent. - :param annotation_dict: A dictionary containing sourceranges of all the + :param annotation_dict: A dictionary containing dicts of all the strings and comments within a file. :param encapsulators: A tuple of SourceRanges of code regions trapped in between a matching pair of encapsulators. @@ -476,12 +482,12 @@ def get_first_unindent(indent, valid = True for comment in annotation_dict['comments']: - if(comment.start.line < line_nr + 1 and - comment.end.line >= line_nr + 1): + if(comment.full_range.start.line < line_nr + 1 and + comment.full_range.end.line >= line_nr + 1): valid = False - first_char = file[line_nr].lstrip()[0] if file[line_nr].strip()\ - else '' + first_char = (file[line_nr].lstrip()[0] if file[line_nr].strip() + else '') if first_char in comments: valid = False diff --git a/bears/general/KeywordBear.py b/bears/general/KeywordBear.py index 08890aed6f..172297ecb9 100644 --- a/bears/general/KeywordBear.py +++ b/bears/general/KeywordBear.py @@ -20,7 +20,13 @@ def _get_comments(dependency_results): if isinstance(result.contents, str): logging.error(result.contents) else: - yield from result.contents.get('comments', []) + dep_contents = result.contents + annotation_dict = {} + annotation_dict['comments'] = \ + (dep_contents.singleline_comments + + dep_contents.multiline_comments) + yield from [range.full_range + for range in annotation_dict['comments']] def generate_diff(comments, file, filename, diff --git a/bears/general/QuotesBear.py b/bears/general/QuotesBear.py index f15806eb7f..55df52031c 100644 --- a/bears/general/QuotesBear.py +++ b/bears/general/QuotesBear.py @@ -62,21 +62,27 @@ def run(self, filename, file, dependency_results, :param preferred_quotation: Your preferred quotation character, e.g. ``"`` or ``'``. """ - if not isinstance(dependency_results[AnnotationBear.name][0], - HiddenResult): + if (not dependency_results or + not isinstance(dependency_results[AnnotationBear.name], + HiddenResult)): return - if isinstance(dependency_results[AnnotationBear.name][0].contents, + if isinstance(dependency_results[AnnotationBear.name].contents, str): - self.err(dependency_results[AnnotationBear.name][0].contents) + self.err(dependency_results[AnnotationBear.name].contents) return - ranges = dependency_results[AnnotationBear.name][0].contents['strings'] + dep_contents = dependency_results[AnnotationBear.name].contents + annotation_dict = {} + annotation_dict['strings'] = (dep_contents.singleline_strings + + dep_contents.multiline_strings) + ranges = annotation_dict['strings'] for string_range in ranges: - if (file[string_range.start.line-1][string_range.start.column-1] == + temp_range = string_range.full_range + if (file[temp_range.start.line-1][temp_range.start.column-1] == preferred_quotation): continue - if string_range.start.line == string_range.end.line: + if temp_range.start.line == temp_range.end.line: yield from self.correct_single_line_str( - filename, file, string_range, preferred_quotation) + filename, file, temp_range, preferred_quotation) diff --git a/tests/general/AnnotationBearTest.py b/tests/general/AnnotationBearTest.py index c26d7b154c..48e6e87d00 100644 --- a/tests/general/AnnotationBearTest.py +++ b/tests/general/AnnotationBearTest.py @@ -22,47 +22,157 @@ def setUp(self): def test_single_line_string(self): text = ["'from start till the end with #comments'\n", ] - compare = (SourceRange.from_absolute_position( - 'F', - AbsolutePosition(text, 0), - AbsolutePosition(text, len(text[0]) - 2)),) + compare_full = SourceRange.from_absolute_position( + 'F', + AbsolutePosition(text, 0), + AbsolutePosition(text, len(text[0]) - 2)) + + compare_content = SourceRange.from_absolute_position( + 'F', + AbsolutePosition(text, 1), + AbsolutePosition(text, len(text[0]) - 3)) + + compare_start = SourceRange.from_absolute_position( + 'F', + AbsolutePosition(text, 0), + AbsolutePosition(text, 0)) + + compare_end = SourceRange.from_absolute_position( + 'F', + AbsolutePosition(text, len(text[0]) - 2), + AbsolutePosition(text, len(text[0]) - 2)) + with execute_bear(self.python_uut, 'F', text) as result: - self.assertEqual(result[0].contents['strings'], compare) - self.assertEqual(result[0].contents['comments'], ()) + for _result in result[0].contents.singleline_strings: + self.assertEqual(_result.full_range, compare_full) + self.assertEqual(_result.content_range, compare_content) + self.assertEqual( + _result.start_delimiter_range, compare_start) + self.assertEqual(_result.end_delimiter_range, compare_end) + self.assertEqual(result[0].contents.singleline_comments, []) text = ["a'\n", "b'\n"] with execute_bear(self.python_uut, 'F', text) as result: self.assertEqual(result[0].message, "' has no closure") + def test_empty_string(self): + text = ["''\n", ] + compare_full = SourceRange.from_absolute_position( + 'F', + AbsolutePosition(text, 0), + AbsolutePosition(text, len(text[0]) - 2)) + + compare_start = SourceRange.from_absolute_position( + 'F', + AbsolutePosition(text, 0), + AbsolutePosition(text, 0)) + + compare_end = SourceRange.from_absolute_position( + 'F', + AbsolutePosition(text, len(text[0]) - 2), + AbsolutePosition(text, len(text[0]) - 2)) + + with execute_bear(self.python_uut, 'F', text) as result: + for _result in result[0].contents.singleline_strings: + self.assertEqual(_result.full_range, compare_full) + self.assertEqual(_result.content_range, []) + self.assertEqual( + _result.start_delimiter_range, compare_start) + self.assertEqual(_result.end_delimiter_range, compare_end) + self.assertEqual(result[0].contents.singleline_comments, []) + def test_multiline_string(self): text = ["'''multiline string, #comment within it'''\n"] - compare = (SourceRange.from_absolute_position( - 'F', - AbsolutePosition(text, 0), - AbsolutePosition(text, len(text[0])-2)),) + compare_full = SourceRange.from_absolute_position( + 'F', + AbsolutePosition(text, 0), + AbsolutePosition(text, len(text[0]) - 2)) + + compare_content = SourceRange.from_absolute_position( + 'F', + AbsolutePosition(text, 3), + AbsolutePosition(text, len(text[0]) - 5)) + + compare_start = SourceRange.from_absolute_position( + 'F', + AbsolutePosition(text, 0), + AbsolutePosition(text, 2)) + + compare_end = SourceRange.from_absolute_position( + 'F', + AbsolutePosition(text, len(text[0]) - 4), + AbsolutePosition(text, len(text[0]) - 2)) + with execute_bear(self.python_uut, 'F', text) as result: - self.assertEqual(result[0].contents['strings'], compare) - self.assertEqual(result[0].contents['comments'], ()) + for _result in result[0].contents.multiline_strings: + self.assertEqual(_result.full_range, compare_full) + self.assertEqual(_result.content_range, compare_content) + self.assertEqual( + _result.start_delimiter_range, compare_start) + self.assertEqual(_result.end_delimiter_range, compare_end) + self.assertEqual(result[0].contents.singleline_comments, []) def test_single_line_comment(self): text = ["some #coment with 'string'\n", 'and next line'] - compare = (SourceRange.from_absolute_position( - 'F', - AbsolutePosition(text, text[0].find('#')), - AbsolutePosition(text, len(text[0]) - 1)),) + compare_full = SourceRange.from_absolute_position( + 'F', + AbsolutePosition(text, text[0].find('#')), + AbsolutePosition(text, len(text[0]) - 1)) + + compare_content = SourceRange.from_absolute_position( + 'F', + AbsolutePosition(text, text[0].find('#')+1), + AbsolutePosition(text, len(text[0]) - 2)) + + compare_start = SourceRange.from_absolute_position( + 'F', + AbsolutePosition(text, text[0].find('#')), + AbsolutePosition(text, text[0].find('#'))) + + compare_end = SourceRange.from_absolute_position( + 'F', + AbsolutePosition(text, len(text[0]) - 1), + AbsolutePosition(text, len(text[0]) - 1)) + with execute_bear(self.python_uut, 'F', text) as result: - self.assertEqual(result[0].contents['strings'], ()) - self.assertEqual(result[0].contents['comments'], compare) + self.assertEqual(result[0].contents.singleline_strings, []) + for _result in result[0].contents.singleline_comments: + self.assertEqual(_result.full_range, compare_full) + self.assertEqual(_result.content_range, compare_content) + self.assertEqual( + _result.start_delimiter_range, compare_start) + self.assertEqual(_result.end_delimiter_range, compare_end) def test_multiline_comment(self): text = ['some string /*within \n', "'multiline comment'*/"] - compare = (SourceRange.from_absolute_position( - 'F', - AbsolutePosition(text, text[0].find('/*')), - AbsolutePosition(text, len(''.join(text)) - 1)),) + compare_full = SourceRange.from_absolute_position( + 'F', + AbsolutePosition(text, text[0].find('/*')), + AbsolutePosition(text, len(''.join(text)) - 1)) + + compare_content = SourceRange.from_absolute_position( + 'F', + AbsolutePosition(text, text[0].find('/*')+2), + AbsolutePosition(text, len(''.join(text)) - 3)) + + compare_start = SourceRange.from_absolute_position( + 'F', + AbsolutePosition(text, text[0].find('/*')), + AbsolutePosition(text, text[0].find('/*')+1)) + + compare_end = SourceRange.from_absolute_position( + 'F', + AbsolutePosition(text, len(''.join(text)) - 2), + AbsolutePosition(text, len(''.join(text)) - 1)) + with execute_bear(self.c_uut, 'F', text) as result: - self.assertEqual(result[0].contents['strings'], ()) - self.assertEqual(result[0].contents['comments'], compare) + self.assertEqual(result[0].contents.singleline_strings, []) + for _result in result[0].contents.multiline_comments: + self.assertEqual(_result.full_range, compare_full) + self.assertEqual(_result.content_range, compare_content) + self.assertEqual( + _result.start_delimiter_range, compare_start) + self.assertEqual(_result.end_delimiter_range, compare_end) text = ['/*Multiline which does not end'] with execute_bear(self.c_uut, 'F', text) as result: @@ -74,17 +184,58 @@ def test_string_with_comments(self): comment_end = len(text[0]) - 1 string_start = ''.join(text).find("'") string_end = ''.join(text).find("'", string_start + 1) - compare = [(SourceRange.from_absolute_position( - 'F', - AbsolutePosition(text, string_start), - AbsolutePosition(text, string_end)),), - (SourceRange.from_absolute_position( - 'F', - AbsolutePosition(text, comment_start), - AbsolutePosition(text, comment_end)),)] + compare_full = [SourceRange.from_absolute_position( + 'F', + AbsolutePosition(text, string_start), + AbsolutePosition(text, string_end)), + SourceRange.from_absolute_position( + 'F', + AbsolutePosition(text, comment_start), + AbsolutePosition(text, comment_end))] + + compare_content = [SourceRange.from_absolute_position( + 'F', + AbsolutePosition(text, string_start+1), + AbsolutePosition(text, string_end-1)), + SourceRange.from_absolute_position( + 'F', + AbsolutePosition(text, comment_start+1), + AbsolutePosition(text, comment_end-1))] + + compare_start = [SourceRange.from_absolute_position( + 'F', + AbsolutePosition(text, string_start), + AbsolutePosition(text, string_start)), + SourceRange.from_absolute_position( + 'F', + AbsolutePosition(text, comment_start), + AbsolutePosition(text, comment_start))] + + compare_end = [SourceRange.from_absolute_position( + 'F', + AbsolutePosition(text, string_end), + AbsolutePosition(text, string_end)), + SourceRange.from_absolute_position( + 'F', + AbsolutePosition(text, comment_end), + AbsolutePosition(text, comment_end))] + with execute_bear(self.python_uut, 'F', text) as result: - self.assertEqual(result[0].contents['strings'], compare[0]) - self.assertEqual(result[0].contents['comments'], compare[1]) + for _result in result[0].contents.singleline_strings: + self.assertEqual(_result.full_range, compare_full[0]) + self.assertEqual(_result.content_range, compare_content[0]) + self.assertEqual( + _result.start_delimiter_range, compare_start[0]) + self.assertEqual( + _result.end_delimiter_range, compare_end[0]) + + for _result in result[0].contents.singleline_comments: + self.assertEqual(_result.full_range, compare_full[1]) + self.assertEqual(_result.content_range, compare_content[1]) + self.assertEqual( + _result.start_delimiter_range, compare_start[1]) + self.assertEqual( + _result.end_delimiter_range, compare_end[1]) def test_combined_strings(self): file_text = ['"some string #with comment"\n', @@ -95,11 +246,28 @@ def test_combined_strings(self): 'another comment # rather harmless\n', '"""\n'] string1_start = 0 - string1_end = len(file_text[0]) - 2 + string1_end = len(file_text[0]) - 2 # 2 string1 = SourceRange.from_absolute_position( 'F', AbsolutePosition(file_text, string1_start), AbsolutePosition(file_text, string1_end)) + + string_c1 = SourceRange.from_absolute_position( + 'F', + AbsolutePosition( + file_text, string1_start+1), + AbsolutePosition(file_text, string1_end-1)) + + string_s1 = SourceRange.from_absolute_position( + 'F', + AbsolutePosition(file_text, string1_start), + AbsolutePosition(file_text, string1_start)) + + string_e1 = SourceRange.from_absolute_position( + 'F', + AbsolutePosition(file_text, string1_end), + AbsolutePosition(file_text, string1_end)) + string2_start = string1_end+2 text = ''.join(file_text) string2_end = text.find('"""', string2_start + 1) + 2 @@ -108,6 +276,24 @@ def test_combined_strings(self): 'F', AbsolutePosition(file_text, string2_start), AbsolutePosition(file_text, string2_end)) + + string_c2 = SourceRange.from_absolute_position( + 'F', + AbsolutePosition( + file_text, string2_start+3), + AbsolutePosition(file_text, string2_end-3)) + + string_s2 = SourceRange.from_absolute_position( + 'F', + AbsolutePosition(file_text, string2_start), + AbsolutePosition(file_text, + string2_start+2)) + + string_e2 = SourceRange.from_absolute_position( + 'F', + AbsolutePosition(file_text, string2_end-2), + AbsolutePosition(file_text, string2_end)) + string3_start = text.find('"""', string2_end + 1) string3_end = text.find('"""', string3_start + 1) + 2 # +2 for length of """ @@ -115,11 +301,54 @@ def test_combined_strings(self): 'F', AbsolutePosition(file_text, string3_start), AbsolutePosition(file_text, string3_end)) + + string_c3 = SourceRange.from_absolute_position( + 'F', + AbsolutePosition( + file_text, string3_start+3), + AbsolutePosition(file_text, string3_end-3)) + + string_s3 = SourceRange.from_absolute_position( + 'F', + AbsolutePosition(file_text, string3_start), + AbsolutePosition(file_text, + string3_start+2)) + + string_e3 = SourceRange.from_absolute_position( + 'F', + AbsolutePosition(file_text, string3_end-2), + AbsolutePosition(file_text, string3_end)) + with execute_bear(self.python_uut, 'F', file_text) as results: - self.assertIn(string1, results[0].contents['strings']) - self.assertIn(string2, results[0].contents['strings']) - self.assertIn(string3, results[0].contents['strings']) - self.assertEqual(results[0].contents['comments'], ()) + full_ranges = [] + content_ranges = [] + start_delimiter_ranges = [] + end_delimiter_ranges = [] + for _result in (results[0].contents.singleline_strings + + results[0].contents.multiline_strings): + full_ranges.append(_result.full_range) + content_ranges.append(_result.content_range) + start_delimiter_ranges.append(_result.start_delimiter_range) + end_delimiter_ranges.append(_result.end_delimiter_range) + + self.assertIn(string1, full_ranges) + self.assertIn(string2, full_ranges) + self.assertIn(string3, full_ranges) + + self.assertIn(string_c1, content_ranges) + self.assertIn(string_c2, content_ranges) + self.assertIn(string_c3, content_ranges) + + self.assertIn(string_s1, start_delimiter_ranges) + self.assertIn(string_s2, start_delimiter_ranges) + self.assertIn(string_s3, start_delimiter_ranges) + + self.assertIn(string_e1, end_delimiter_ranges) + self.assertIn(string_e2, end_delimiter_ranges) + self.assertIn(string_e3, end_delimiter_ranges) + + self.assertEqual(results[0].contents.multiline_comments, []) + self.assertEqual(results[0].contents.singleline_comments, []) def test_no_coalang(self): self.section1.append(Setting('language', 'Valyrian')) @@ -132,12 +361,44 @@ def test_no_coalang(self): def test_escape_strings(self): text = [r"'I\'ll be back' -T1000"] uut = AnnotationBear(self.section1, Queue()) - test_range = SourceRange.from_absolute_position( + test_full_range = SourceRange.from_absolute_position( 'F', AbsolutePosition(text, 0), AbsolutePosition(text, text[0].find("'", 4))) + + test_content_range = SourceRange.from_absolute_position( + 'F', + AbsolutePosition(text, 1), + AbsolutePosition(text, text[0].find("'", 4)-1)) + + test_start_range = SourceRange.from_absolute_position( + 'F', + AbsolutePosition(text, 0), + AbsolutePosition(text, 0)) + + test_end_range = SourceRange.from_absolute_position( + 'F', + AbsolutePosition(text, text[0].find("'", 4)), + AbsolutePosition(text, text[0].find("'", 4))) + with execute_bear(uut, 'F', text) as result: - self.assertEqual(result[0].contents['strings'], (test_range,)) + self.assertEqual( + result[0].contents.singleline_strings[0].full_range, + test_full_range) + + self.assertEqual( + result[0].contents.singleline_strings[0].content_range, + test_content_range) + + self.assertEqual( + result[0].contents.singleline_strings[ + 0].start_delimiter_range, + test_start_range) + + self.assertEqual( + result[0].contents.singleline_strings[ + 0].end_delimiter_range, + test_end_range) text = [''' """"quoting inside quoting" diff --git a/tests/general/KeywordBearTest.py b/tests/general/KeywordBearTest.py index aa863fed24..bcc1759985 100644 --- a/tests/general/KeywordBearTest.py +++ b/tests/general/KeywordBearTest.py @@ -4,6 +4,7 @@ import logging from bears.general.KeywordBear import KeywordBear +from bears.general.AnnotationBear import AnnotationContent, AnnotationRange from coalib.results.HiddenResult import HiddenResult from coalib.results.SourceRange import SourceRange from coalib.settings.Section import Section @@ -36,7 +37,8 @@ def setUp(self): self.annotation_bear_result_type = namedtuple('result', ['contents']) self.dep_results = {'AnnotationBear': HiddenResult( - 'AnnotationBear', {'comments': ()})} + 'AnnotationBear', AnnotationContent([AnnotationRange( + 'singleline_comment', [])]))} def test_empty_keyword(self): text = ['a == b'] @@ -57,8 +59,9 @@ def test_keyword_in_comment(self): self.assertEqual(len(result), 1) dep_results = {'AnnotationBear': HiddenResult( - 'AnnotationBear', {'comments': 123})} - with execute_bear(self.uut, filename='F', file=text, + 'AnnotationBear', AnnotationContent( + [AnnotationRange('singleline_comment', [[], [], [], 123])]))} + with execute_bear(self.uut, 'F', text, dependency_results=dep_results) as result: self.assertEqual(result[0].diffs, {}) self.assertEqual(result[0].affected_code[0].start.line, 1) @@ -74,12 +77,13 @@ def test_keyword_in_comment(self): def test_keyword_not_in_comment(self): text = ['# comment 123\n', 'a = "TODO"\n'] - comments = [SourceRange.from_values('F', 1, 1, 1, 40)] + comments = SourceRange.from_values('F', 1, 1, 1, 40) dep_results = { - 'AnnotationBear': [ - self.annotation_bear_result_type({'comments': comments}) - ] - } + 'AnnotationBear': + [self.annotation_bear_result_type( + AnnotationContent([AnnotationRange( + 'singleline_comment', [[], [], [], comments])])) + ]} with execute_bear(self.uut, filename='F', file=text, dependency_results=dep_results) as result: @@ -87,12 +91,13 @@ def test_keyword_not_in_comment(self): def test_keyword_diff(self): text = ['# todo 123\n'] - comments = [SourceRange.from_values('F', 1, 1, 1, 10)] + comments = SourceRange.from_values('F', 1, 1, 1, 10) dep_results = { - 'AnnotationBear': [ - self.annotation_bear_result_type({'comments': comments}) - ] - } + 'AnnotationBear': + [self.annotation_bear_result_type( + AnnotationContent([AnnotationRange( + 'singleline_comment', [[], [], [], comments])]) + )]} with execute_bear(self.uut, filename='F', file=text, dependency_results=dep_results) as result: @@ -103,12 +108,13 @@ def test_keyword_diff(self): '-# todo 123\n') text = ['test = 55 # todo 123\n'] - comments = [SourceRange.from_values('F', 1, 11, 1, 23)] + comments = SourceRange.from_values('F', 1, 11, 1, 23) dep_results = { - 'AnnotationBear': [ - self.annotation_bear_result_type({'comments': comments}) - ] - } + 'AnnotationBear': + [self.annotation_bear_result_type( + AnnotationContent([AnnotationRange( + 'singleline_comment', [[], [], [], comments])]) + )]} with execute_bear(self.uut, filename='F', file=text, dependency_results=dep_results) as result: self.assertEqual(result[0].diffs['F'].unified_diff, @@ -130,12 +136,13 @@ def test_keyword_between_code(self): text = ['int a=0; /* TODO: Test */ int b=1;\n'] - comments = [SourceRange.from_values('F', 1, 10, 1, 25)] + comments = SourceRange.from_values('F', 1, 10, 1, 25) dep_results = { - 'AnnotationBear': [ - self.annotation_bear_result_type({'comments': comments}) - ] - } + 'AnnotationBear': + [self.annotation_bear_result_type( + AnnotationContent([AnnotationRange( + 'singleline_comment', [[], [], [], comments])]) + )]} with execute_bear(self.uut, filename='F', file=text, dependency_results=dep_results) as result: @@ -149,12 +156,13 @@ def test_keyword_between_code(self): text = ['int a = 0; /* TODO test\n', 'another test\n', '*/\n'] - comments = [SourceRange.from_values('F', 1, 12, 3, 2)] + comments = SourceRange.from_values('F', 1, 12, 3, 2) dep_results = { - 'AnnotationBear': [ - self.annotation_bear_result_type({'comments': comments}) - ] - } + 'AnnotationBear': + [self.annotation_bear_result_type( + AnnotationContent([AnnotationRange( + 'multiline_comment', [[], [], [], comments])]) + )]} with execute_bear(self.uut, filename='F', file=text, dependency_results=dep_results) as result: @@ -170,12 +178,13 @@ def test_keyword_between_code(self): text = ['/* TODO\n', 'test\n', '*/\n'] - comments = [SourceRange.from_values('F', 1, 1, 3, 2)] + comments = SourceRange.from_values('F', 1, 1, 3, 2) dep_results = { - 'AnnotationBear': [ - self.annotation_bear_result_type({'comments': comments}) - ] - } + 'AnnotationBear': + [self.annotation_bear_result_type( + AnnotationContent([AnnotationRange( + 'multiline_comment', [[], [], [], comments])]) + )]} with execute_bear(self.uut, filename='F', file=text, dependency_results=dep_results) as result: @@ -224,7 +233,7 @@ def test_wrong_language(self): dep_results = { 'AnnotationBear': [ annotation_bear_result_type( - 'coalang specification for anything not found.') + 'coalang specification for anything not found.') ] } diff --git a/tests/general/QuotesBearTest.py b/tests/general/QuotesBearTest.py index e7d4f9ddba..73599a0889 100644 --- a/tests/general/QuotesBearTest.py +++ b/tests/general/QuotesBearTest.py @@ -3,9 +3,11 @@ from textwrap import dedent from coalib.results.HiddenResult import HiddenResult, Result +from bears.general.AnnotationBear import AnnotationContent, AnnotationRange from bears.general.QuotesBear import QuotesBear from coalib.results.SourceRange import SourceRange from coalib.settings.Section import Section +from bears.general.AnnotationBear import AnnotationBear from coalib.testing.LocalBearTestHelper import execute_bear @@ -32,26 +34,32 @@ def setUp(self): """).splitlines(True) self.filename = 'f' - + ranges = [] + ranges.append( + AnnotationRange( + 'multiline_string', [[], [], [], SourceRange.from_values( + self.filename, 2, 1, 4, 3)])) + ranges.append(AnnotationRange('singleline_string', [ + [], [], [], SourceRange.from_values( + self.filename, 5, 1, 5, 30)])) + ranges.append(AnnotationRange('singleline_string', [ + [], [], [], SourceRange.from_values( + self.filename, 6, 1, 6, 37)])) self.dep_results = { - 'AnnotationBear': - [HiddenResult( + AnnotationBear.name: + HiddenResult( 'AnnotationBear', - {'comments': (), 'strings': ( - SourceRange.from_values(self.filename, 2, 1, 4, 3), - SourceRange.from_values(self.filename, 5, 1, 5, 30), - SourceRange.from_values(self.filename, 6, 1, 6, 37)) - } - )] + AnnotationContent(ranges) + ) } def test_error_handling(self): - dep_results = {'AnnotationBear': [Result('test', 'test')]} + dep_results = {AnnotationBear.name: Result('test', 'test')} with execute_bear(self.uut, self.filename, self.double_quote_file, dependency_results=dep_results) as results: self.assertEqual(len(results), 0) - dep_results = {'AnnotationBear': [HiddenResult('a', 'error!')]} + dep_results = {AnnotationBear.name: HiddenResult('a', 'error!')} with execute_bear(self.uut, self.filename, self.double_quote_file, dependency_results=dep_results) as results: self.assertEqual(len(results), 0)