def test_apply(self): uut = ShowAppliedPatchesAction() with make_temp() as f_a, make_temp() as f_b, make_temp() as f_c: file_dict = { f_a: ['1\n', '2\n', '3\n'], f_b: ['1\n', '2\n', '3\n'], f_c: ['1\n', '2\n', '3\n'] } expected_file_dict = { f_a: ['1\n', '3_changed\n'], f_b: ['1\n', '2\n', '3_changed\n'], f_c: ['1\n', '2\n', '3\n'] } with make_temp() as testfile_path: file_diff_dict = {} file_dict = {testfile_path: ['1\n', '2\n', '3\n']} diff = Diff(file_dict[testfile_path]) diff.delete_line(2) diff.change_line(3, '3\n', '3_changed\n') result = Result('origin', 'msg', diffs={f_a: diff}, applied_actions={'ApplyPatchAction': [Result( 'origin', 'message', diffs={testfile_path: diff}), file_dict, file_diff_dict, Section('')]}) self.assertTrue(uut.apply(result, file_dict, file_diff_dict))
def test_print_result_no_input(self): with make_temp() as testfile_path: file_dict = {testfile_path: ["1\n", "2\n", "3\n"]} diff = Diff(file_dict[testfile_path]) diff.delete_line(2) diff.change_line(3, "3\n", "3_changed\n") with simulate_console_inputs(1, 2, 3) as generator, retrieve_stdout() as stdout: ApplyPatchAction.is_applicable = staticmethod(lambda *args: True) print_results_no_input( self.log_printer, Section("someSection"), [Result("origin", "message", diffs={testfile_path: diff})], file_dict, self.file_diff_dict, color=False, ) self.assertEqual(generator.last_input, -1) self.assertEqual( stdout.getvalue(), """ Project wide: | | [NORMAL] origin: | | message """, )
def test_from_string_arrays(self): a = ['q', 'a', 'b', 'x', 'c', 'd'] b = ['a', 'b', 'y', 'c', 'd', 'f'] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b) a = ['first', 'fourth'] b = ['first', 'second', 'third', 'fourth'] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b) a = ['first', 'fourth'] b = ['first_changed', 'second', 'third', 'fourth'] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b) a = ['first', 'second', 'third', 'fourth'] b = ['first', 'fourth'] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b) a = ['first', 'second', 'third', 'fourth'] b = ['first_changed', 'second_changed', 'fourth'] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b)
def test_finalize_backup_fail(self): def raise_error(file, mode=""): raise PermissionException _open = builtins.open _copy2 = shutil.copy2 try: builtins.open = raise_error builtins.__dict__["open"] = raise_error shutil.copy2 = lambda src, dst: self.assertEqual(src+".orig", dst) diff = Diff() diff.delete_line(2) # Should catch the backup permission error during write-back. finalize({"f": diff}, {"f": ["1", "2", "3"]}) # Test logging output. finalize({"f": diff}, {"f": ["1", "2"]}, log_printer=None) logger = LogPrinter(StringPrinter()) finalize({"f": diff}, {"f": ["1"]}, log_printer=logger) self.assertIn("Can't backup, writing patch to file f failed.", logger.printer.string) finally: builtins.open = _open shutil.copy2 = _copy2
def test_redirect_threshold(self): long_url_redirect = """ https://bitbucket.org/api/301 https://bitbucket.org/api/302 """.splitlines() short_url_redirect = """ http://httpbin.org/status/301 """.splitlines() with requests_mock.Mocker() as m: m.add_matcher(custom_matcher) self.check_validity(self.uut, long_url_redirect, settings={'follow_redirects': 'true'}) with prepare_file(short_url_redirect, None, create_tempfile=False) as (lines, _): diff = Diff(lines) diff.modify_line(2, ' http://httpbin.org/get\n') self.check_results( self.uut, short_url_redirect, [Result.from_values( 'InvalidLinkBear', 'This link redirects to http://httpbin.org/get', severity=RESULT_SEVERITY.NORMAL, line=2, file='short_url_redirect_text', diffs={'short_url_redirect_text': diff}, )], settings={'follow_redirects': 'true'}, filename='short_url_redirect_text')
def run(self, filename, file, file_naming_convention: str="snake"): """ Checks whether the filename follows a certain naming-convention. :param file_naming_convention: The naming-convention. Supported values are: - ``camel`` (``thisIsCamelCase``) - ``pascal`` (``ThisIsPascalCase``) - ``snake`` (``this_is_snake_case``) """ head, tail = os.path.split(filename) filename_without_extension, extension = os.path.splitext(tail) try: new_name = self._naming_convention[file_naming_convention]( filename_without_extension) except KeyError: self.err("Invalid file-naming-convention provided: " + file_naming_convention) return if new_name != filename_without_extension: diff = Diff(file, rename=os.path.join(head, new_name + extension)) yield Result( self, "Filename does not follow {} naming-convention.".format( file_naming_convention), diff.affected_code(filename), diffs={filename: diff})
def process_output(self, output, filename, file): output = json.loads(output) for issue in output: diff = Diff(file) from_lines = issue['from'].splitlines() to_lines = issue['to'].splitlines() assert len(from_lines) == len(to_lines) for other_lines in range(1, len(from_lines)): assert from_lines[other_lines] == to_lines[other_lines] line_nr = issue['startLine'] line_to_change = file[line_nr-1] newline = line_to_change.replace(from_lines[0], to_lines[0]) diff.change_line(line_nr, line_to_change, newline) yield Result.from_values( origin=self, message=issue['hint'], file=filename, severity=self.severity_map[issue['severity']], line=issue['startLine'], column=issue['startColumn'], end_line=issue['endLine'], end_column=issue['endColumn'], diffs={filename: diff})
def test_equality(self): a = ['first', 'second', 'third'] b = ['first', 'third'] diff_1 = Diff.from_string_arrays(a, b) c = ['first', 'second', 'third'] d = ['first', 'third'] diff_2 = Diff.from_string_arrays(c, d) self.assertEqual(diff_1, diff_2) # changing the original array should not influence # the diff a[1] = 'else' self.assertEqual(diff_1, diff_2) diff_1.rename = 'abcd' self.assertNotEqual(diff_1, diff_2) diff_1.rename = False diff_1.delete = True self.assertNotEqual(diff_1, diff_2) diff_1.delete = False diff_1.add_lines(1, ['1']) self.assertNotEqual(diff_1, diff_2)
def test_from_string_arrays(self): a = ['q\n', 'a\n', 'b\n', 'x\n', 'c\n', 'd\n'] b = ['a\n', 'b\n', 'y\n', 'c\n', 'd\n', 'f\n'] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b) a = ['first\n', 'fourth\n'] b = ['first\n', 'second\n', 'third\n', 'fourth\n'] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b) a = ['first\n', 'fourth\n'] b = ['first_changed\n', 'second\n', 'third\n', 'fourth\n'] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b) a = ['first\n', 'second\n', 'third\n', 'fourth\n'] b = ['first\n', 'fourth\n'] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b) a = ['first\n', 'second\n', 'third\n', 'fourth\n'] b = ['first_changed\n', 'second_changed\n', 'fourth\n'] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b)
def apply(self, result, original_file_dict, file_diff_dict, language: str, no_orig: bool=False): """ Add ignore comment """ ignore_comment = self.get_ignore_comment(result.origin, language) if not ignore_comment: return file_diff_dict source_range = next(filter(lambda sr: exists(sr.file), result.affected_code)) filename = source_range.file ignore_diff = Diff(original_file_dict[filename]) ignore_diff.change_line( source_range.start.line, original_file_dict[filename][source_range.start.line-1], original_file_dict[filename][source_range.start.line-1].rstrip() + ' ' + ignore_comment) if filename in file_diff_dict: ignore_diff = file_diff_dict[filename] + ignore_diff else: if not no_orig and isfile(filename): shutil.copy2(filename, filename + '.orig') file_diff_dict[filename] = ignore_diff new_filename = ignore_diff.rename if ignore_diff.rename else filename with open(new_filename, mode='w', encoding='utf-8') as file: file.writelines(ignore_diff.modified) return file_diff_dict
def test_from_string_arrays(self): a = ["q", "a", "b", "x", "c", "d"] b = ["a", "b", "y", "c", "d", "f"] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b) a = ["first", "fourth"] b = ["first", "second", "third", "fourth"] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b) a = ["first", "fourth"] b = ["first_changed", "second", "third", "fourth"] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b) a = ["first", "second", "third", "fourth"] b = ["first", "fourth"] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b) a = ["first", "second", "third", "fourth"] b = ["first_changed", "second_changed", "fourth"] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b)
def test_apply_rename(self): # Initial file contents, *before* a patch was applied file_dict = { self.fa: ["1\n", "2\n", "3\n"]} # A patch that was applied for some reason to make things complicated file_diff_dict = {} diff = Diff(file_dict[self.fa], rename=self.fa+".renamed") diff.change_line(3, "3\n", "3_changed\n") ApplyPatchAction().apply( Result("origin", "msg", diffs={self.fa: diff}), file_dict, file_diff_dict) # End file contents after the patch and the OpenEditorAction was # applied expected_file_dict = { self.fa: ["1\n", "3_changed\n"]} section = Section("") section.append(Setting("editor", "")) uut = OpenEditorAction() subprocess.call = self.fake_edit diff_dict = uut.apply_from_section( Result.from_values("origin", "msg", self.fa), file_dict, file_diff_dict, section) for filename in diff_dict: file_dict[filename] = ( file_diff_dict[filename].modified) self.assertEqual(file_dict, expected_file_dict) open(self.fa, 'w').close()
def generate_spacing_diff(file, filename, line, line_number, match_object, required_spacing): """ Generate a diff for incorrectly spaced control or variable tags. :param match_object: A Match object containing the groups ``open``, ``close`` containing the opening and closing delimiters of a Jinja2 tag and ``content`` containing everything in between. :param required_spacing: The number of spaces expected after the ``open`` delimiter and before the ``close`` delimiter """ diff = Diff(file) content_before = line[:match_object.start('open')] content_after = line[match_object.end('close'):] spacing = ' ' * required_spacing replacement = ( '{before}{open}{spacing}{content}{spacing}{close}{after}'.format( before=content_before, spacing=spacing, after=content_after, content=match_object.group('content').strip(), open=match_object.group('open'), close=match_object.group('close'))) diff.change_line( line_number, line, replacement) return {filename: diff}
def test_is_applicable_conflict(self): diff = Diff(["1\n", "2\n", "3\n"]) diff.add_lines(2, ['a line']) conflict_result = Result("", "", diffs={'f': diff}) # Applying the same diff twice will result in a conflict self.assertFalse( ApplyPatchAction.is_applicable(conflict_result, {}, {'f': diff}))
def test_from_unified_diff_multiple_additions_different_orderings(self): source = ['A', 'B', 'C'] target = ['A', 'Y', 'Z', 'B', 'C'] diff = ['--- a/testfile', '+++ b/testfile', '@@ -1,3 +1,5 @@', ' A', '+Y', '+Z', ' B', ' C'] diff_string = '\n'.join(diff) self.uut = Diff.from_unified_diff(diff_string, source) self.assertEqual(self.uut.original, source) self.assertEqual(self.uut.modified, target) source = ['A', 'B', 'C'] target = ['A', 'Y', 'Z', 'C'] diff = ['--- a/testfile', '+++ b/testfile', '@@ -1,3 +1,5 @@', ' A', '+Y', '+Z', '-B', ' C'] diff_string = '\n'.join(diff) self.uut = Diff.from_unified_diff(diff_string, source) self.assertEqual(self.uut.original, source) self.assertEqual(self.uut.modified, target) source = ['A', 'B', 'C'] target = ['Y', 'Z', 'C'] diff = ['--- a/testfile', '+++ b/testfile', '@@ -1,3 +1,5 @@', '-A', '+Y', '+Z', '-B', ' C'] diff_string = '\n'.join(diff) self.uut = Diff.from_unified_diff(diff_string, source) self.assertEqual(self.uut.original, source) self.assertEqual(self.uut.modified, target) source = ['A', 'B', 'C'] target = ['A', 'B', 'C', 'Y', 'Z'] diff = ['--- a/testfile', '+++ b/testfile', '@@ -3 +3,3 @@', ' C', '+Y', '+Z'] diff_string = '\n'.join(diff) self.uut = Diff.from_unified_diff(diff_string, source) self.assertEqual(self.uut.original, source) self.assertEqual(self.uut.modified, target)
def run(self, filename, file, file_naming_convention: str=None, ignore_uppercase_filenames: bool=True): """ Checks whether the filename follows a certain naming-convention. :param file_naming_convention: The naming-convention. Supported values are: - ``auto`` to guess the correct convention. Defaults to ``snake`` if the correct convention cannot be guessed. - ``camel`` (``thisIsCamelCase``) - ``kebab`` (``this-is-kebab-case``) - ``pascal`` (``ThisIsPascalCase``) - ``snake`` (``this_is_snake_case``) - ``space`` (``This Is Space Case``) :param ignore_uppercase_filenames: Whether or not to ignore fully uppercase filenames completely, e.g. COPYING, LICENSE etc. """ head, tail = os.path.split(filename) filename_without_extension, extension = os.path.splitext(tail) if file_naming_convention is None: self.warn('Please specify a file naming convention explicitly' ' or use "auto".') file_naming_convention = 'auto' else: file_naming_convention = file_naming_convention.lower() if file_naming_convention == 'auto': if extension in self._language_naming_convention: file_naming_convention = self._language_naming_convention[ extension] else: self.warn('The file naming convention could not be guessed. ' 'Using the default "snake" naming convention.') file_naming_convention = 'snake' try: new_name = self._naming_convention[file_naming_convention]( filename_without_extension) except KeyError: self.err('Invalid file-naming-convention provided: ' + file_naming_convention) return if ignore_uppercase_filenames and filename_without_extension.isupper(): return if new_name != filename_without_extension: diff = Diff(file, rename=os.path.join(head, new_name + extension)) yield Result( self, 'Filename does not follow {} naming-convention.'.format( file_naming_convention), diff.affected_code(filename), diffs={filename: diff})
def run(self, filename, file, timeout: int=DEFAULT_TIMEOUT, ignore_regex: str="[.\/]example\.com"): """ Find links in any text file and check if they are valid. A link is considered valid if the server responds with a 2xx code. This bear can automatically fix redirects, but ignores redirect URLs that have a huge difference with the original URL. :param timeout: Request timeout period. :param ignore_regex: A regex for urls to ignore. """ for line_number, link, code in InvalidLinkBear.find_links_in_file( file, timeout, ignore_regex): if code is None: yield Result.from_values( origin=self, message=('Broken link - unable to connect to ' '{url}').format(url=link), file=filename, line=line_number, severity=RESULT_SEVERITY.MAJOR) elif not 200 <= code < 300: # HTTP status 404, 410 or 50x if code in (404, 410) or 500 <= code < 600: yield Result.from_values( origin=self, message=('Broken link - unable to connect to {url} ' '(HTTP Error: {code})' ).format(url=link, code=code), file=filename, line=line_number, severity=RESULT_SEVERITY.NORMAL) if 300 <= code < 400: # HTTP status 30x redirect_url = requests.head(link, allow_redirects=True).url matcher = SequenceMatcher( None, redirect_url, link) if (matcher.real_quick_ratio() > 0.7 and matcher.ratio()) > 0.7: diff = Diff(file) current_line = file[line_number - 1] start = current_line.find(link) end = start + len(link) replacement = current_line[:start] + \ redirect_url + current_line[end:] diff.change_line(line_number, current_line, replacement) yield Result.from_values( self, 'This link redirects to ' + redirect_url, diffs={filename: diff}, file=filename, line=line_number, severity=RESULT_SEVERITY.NORMAL)
def test_is_applicable_conflict(self): diff = Diff(['1\n', '2\n', '3\n']) diff.add_lines(2, ['a line']) conflict_result = Result('', '', diffs={'f': diff}) # Applying the same diff twice will result in a conflict self.assertIn( 'Two or more patches conflict with each other: ', ApplyPatchAction.is_applicable(conflict_result, {}, {'f': diff}) )
def test_add(self): file_dict = { "f_a": ["1", "2", "3"], "f_b": ["1", "2", "3"], "f_c": ["1", "2", "3"] } expected_file_dict = { "f_a": ["1", "3_changed"], "f_b": ["1", "2", "3_changed"], "f_c": ["1", "2", "3"] } diff = Diff() diff.delete_line(2) uut1 = PatchResult("origin", "msg", {"f_a": diff}) diff = Diff() diff.change_line(3, "3", "3_changed") uut2 = PatchResult("origin", "msg", {"f_a": diff}) diff = Diff() diff.change_line(3, "3", "3_changed") uut3 = PatchResult("origin", "msg", {"f_b": diff}) uut1 += uut2 + uut3 uut1.apply(file_dict) self.assertEqual(file_dict, expected_file_dict)
def test_apply(self): uut = ApplyPatchAction() file_dict = { "f_a": ["1", "2", "3"], "f_b": ["1", "2", "3"], "f_c": ["1", "2", "3"] } expected_file_dict = { "f_a": ["1", "3_changed"], "f_b": ["1", "2", "3_changed"], "f_c": ["1", "2", "3"] } file_diff_dict = {} diff = Diff() diff.delete_line(2) uut.apply_from_section(PatchResult("origin", "msg", {"f_a": diff}), file_dict, file_diff_dict, Section("t")) diff = Diff() diff.change_line(3, "3", "3_changed") uut.apply_from_section(PatchResult("origin", "msg", {"f_a": diff}), file_dict, file_diff_dict, Section("t")) diff = Diff() diff.change_line(3, "3", "3_changed") uut.apply(PatchResult("origin", "msg", {"f_b": diff}), file_dict, file_diff_dict) for filename in file_diff_dict: file_dict[filename] = file_diff_dict[filename].apply(file_dict[filename]) self.assertEqual(file_dict, expected_file_dict)
def test_apply(self): file_dict = {"f_a": ["1", "2", "3"], "f_b": ["1", "2", "3"]} expected_file_dict = {"f_a": ["1", "3_changed"], "f_b": ["1", "2", "3"]} diff = Diff(file_dict["f_a"]) diff.delete_line(2) diff.change_line(3, "3", "3_changed") uut = Result("origin", "msg", diffs={"f_a": diff}) uut.apply(file_dict) self.assertEqual(file_dict, expected_file_dict)
def test_equality(self): a = ["first", "second", "third"] b = ["first", "third"] diff_1 = Diff.from_string_arrays(a, b) a[1] = "else" diff_2 = Diff.from_string_arrays(a, b) self.assertEqual(diff_1, diff_2) diff_1.add_lines(1, ["1"]) self.assertNotEqual(diff_1, diff_2)
def test_apply(self): uut = ApplyPatchAction() fh_a, f_a = mkstemp() fh_b, f_b = mkstemp() fh_c, f_c = mkstemp() os.close(fh_a) os.close(fh_b) os.close(fh_c) file_dict = { f_a: ["1\n", "2\n", "3\n"], f_b: ["1\n", "2\n", "3\n"], f_c: ["1\n", "2\n", "3\n"] } expected_file_dict = { f_a: ["1\n", "3_changed\n"], f_b: ["1\n", "2\n", "3_changed\n"], f_c: ["1\n", "2\n", "3\n"] } file_diff_dict = {} diff = Diff(file_dict[f_a]) diff.delete_line(2) uut.apply_from_section(Result("origin", "msg", diffs={f_a: diff}), file_dict, file_diff_dict, Section("t")) diff = Diff(file_dict[f_a]) diff.change_line(3, "3\n", "3_changed\n") uut.apply_from_section(Result("origin", "msg", diffs={f_a: diff}), file_dict, file_diff_dict, Section("t")) diff = Diff(file_dict[f_b]) diff.change_line(3, "3\n", "3_changed\n") uut.apply(Result("origin", "msg", diffs={f_b: diff}), file_dict, file_diff_dict) for filename in file_diff_dict: file_dict[filename] = file_diff_dict[filename].modified self.assertEqual(file_dict, expected_file_dict) with open(f_a) as fa: self.assertEqual(file_dict[f_a], fa.readlines()) with open(f_b) as fb: self.assertEqual(file_dict[f_b], fb.readlines()) with open(f_c) as fc: # File c is unchanged and should be untouched self.assertEqual([], fc.readlines()) os.remove(f_a) os.remove(f_b) os.remove(f_c)
def test_acquire_actions_and_apply_single(self): with make_temp() as testfile_path: file_dict = {testfile_path: ['1\n', '2\n', '3\n']} diff = Diff(file_dict[testfile_path]) diff.delete_line(2) diff.change_line(3, '3\n', '3_changed\n') with simulate_console_inputs('a', 'n') as generator: with retrieve_stdout() as sio: ApplyPatchAction.is_applicable = staticmethod( lambda *args: True) acquire_actions_and_apply(self.console_printer, Section(''), self.file_diff_dict, Result( 'origin', 'message', diffs={testfile_path: diff}), file_dict, apply_single=True) self.assertEqual(generator.last_input, -1) self.assertIn('', sio.getvalue()) class InvalidateTestAction(ResultAction): is_applicable = staticmethod(lambda *args: True) def apply(*args, **kwargs): ApplyPatchAction.is_applicable = staticmethod( lambda *args: 'ApplyPatchAction cannot be applied.') old_applypatch_is_applicable = ApplyPatchAction.is_applicable ApplyPatchAction.is_applicable = staticmethod(lambda *args: True) cli_actions = [ApplyPatchAction(), InvalidateTestAction()] with simulate_console_inputs('a') as generator: with retrieve_stdout() as sio: acquire_actions_and_apply(self.console_printer, Section(''), self.file_diff_dict, Result( 'origin', 'message', diffs={testfile_path: diff}), file_dict, cli_actions=cli_actions, apply_single=True) self.assertEqual(generator.last_input, -1) action_fail = 'Failed to execute the action' self.assertNotIn(action_fail, sio.getvalue()) apply_path_desc = ApplyPatchAction().get_metadata().desc self.assertEqual(sio.getvalue().count(apply_path_desc), 0) ApplyPatchAction.is_applicable = old_applypatch_is_applicable
def test_addition_rename(self): uut = Diff(self.file, rename=False) other = Diff(self.file, rename=False) self.assertEqual((other + uut).rename, False) other.rename = 'some.py' self.assertEqual((other + uut).rename, 'some.py') uut.rename = 'some.py' self.assertEqual((other + uut).rename, 'some.py') uut.rename = 'other.py' self.assertRaises(ConflictError, other.__add__, uut)
def test_acquire_actions_and_apply(self): with make_temp() as testfile_path: file_dict = {testfile_path: ["1\n", "2\n", "3\n"]} diff = Diff(file_dict[testfile_path]) diff.delete_line(2) diff.change_line(3, "3\n", "3_changed\n") with simulate_console_inputs(1, 0) as generator, \ retrieve_stdout() as sio: ApplyPatchAction.is_applicable = staticmethod( lambda *args: True) acquire_actions_and_apply(self.console_printer, self.log_printer, Section(""), self.file_diff_dict, Result("origin", "message", diffs={ testfile_path: diff}), file_dict) self.assertEqual(generator.last_input, 1) self.assertIn(ApplyPatchAction.success_message, sio.getvalue()) class InvalidateTestAction(ResultAction): is_applicable = staticmethod(lambda *args: True) def apply(*args, **kwargs): ApplyPatchAction.is_applicable = staticmethod( lambda *args: False) old_applypatch_is_applicable = ApplyPatchAction.is_applicable ApplyPatchAction.is_applicable = staticmethod(lambda *args: True) cli_actions = [ApplyPatchAction(), InvalidateTestAction()] with simulate_console_inputs(2, 1, 0) as generator, \ retrieve_stdout() as sio: acquire_actions_and_apply(self.console_printer, self.log_printer, Section(""), self.file_diff_dict, Result("origin", "message", diffs={testfile_path: diff}), file_dict, cli_actions=cli_actions) self.assertEqual(generator.last_input, 2) action_fail = "Failed to execute the action" self.assertNotIn(action_fail, sio.getvalue()) apply_path_desc = ApplyPatchAction().get_metadata().desc self.assertEqual(sio.getvalue().count(apply_path_desc), 1) ApplyPatchAction.is_applicable = old_applypatch_is_applicable
def test_bool(self): self.assertFalse(self.uut) self.uut.add_line(4, '4') self.assertTrue(self.uut) self.uut.delete_line(4) self.assertFalse(self.uut) # test if it works with tuples. uutuple = Diff(('1', '2', '3', '4')) self.assertFalse(uutuple) uutuple.add_line(4, '4') self.assertTrue(uutuple) uutuple.delete_line(4) self.assertFalse(uutuple)
def process_output(self, output, filename, file): if not file: return output = json.loads(output) lines = "".join(file) assert len(output) == 1 for result in output[0]['messages']: if 'fix' not in result: diffs = None else: fix = result['fix'] start, end = fix['range'] replacement_text = fix['text'] new_output = lines[:start] + replacement_text + lines[end:] diffs = {filename: Diff.from_string_arrays( lines.splitlines(True), new_output.splitlines(True))} origin = ( "{class_name} ({rule})".format(class_name=type(self).__name__, rule=result['ruleId']) if result['ruleId'] is not None else self) yield Result.from_values( origin=origin, message=result['message'], file=filename, line=result['line'], diffs=diffs, severity=self.severity_map[result['severity']])
def run(self, filename, file, max_line_length: int=80, tab_width: int=SpacingHelper.DEFAULT_TAB_WIDTH, pep_ignore: typed_list(str)=(), pep_select: typed_list(str)=(), local_pep8_config: bool=False): """ Detects and fixes PEP8 incompliant code. This bear will not change functionality of the code in any way. :param max_line_length: Maximum number of characters for a line. :param tab_width: Number of spaces per indent level. :param pep_ignore: A list of errors/warnings to ignore. :param pep_select: A list of errors/warnings to exclusively apply. :param local_pep8_config: Set to true if autopep8 should use a config file as if run normally from this directory. """ options = {"ignore": pep_ignore, "select": pep_select, "max_line_length": max_line_length, "indent_size": tab_width} corrected = autopep8.fix_code(''.join(file), apply_config=local_pep8_config, options=options).splitlines(True) diffs = Diff.from_string_arrays(file, corrected).split_diff() for diff in diffs: yield Result(self, "The code does not comply to PEP8.", affected_code=(diff.range(filename),), diffs={filename: diff})
def run(self, filename, file, max_line_length: int = 79, indent_size: int = SpacingHelper.DEFAULT_TAB_WIDTH, allow_multiline_lambdas: bool = False, blank_line_before_nested_class_or_def: bool = False, continuation_tab_width: int = SpacingHelper.DEFAULT_TAB_WIDTH, dedent_closing_brackets: bool = False, indent_dictionary_value: bool = False, coalesce_brackets: bool = False, join_multiple_lines: bool = True, spaces_around_power_operator: bool = True, spaces_before_comment: int = 2, space_between_ending_comma_and_closing_bracket: bool = False, split_arguments_when_comma_terminated: bool = False, split_before_bitwise_operator: bool = False, split_before_first_argument: bool = False, split_before_logical_operator: bool = False, split_before_named_assigns: bool = True, use_spaces: bool = True, based_on_style: str = 'pep8'): """ :param max_line_length: Maximum number of characters for a line. :param indent_size: Number of spaces per indentation level. :param allow_multiline_lambdas: Allows lambdas to be formatted on more than one line. :param blank_line_before_nested_class_or_def: Inserts a blank line before a ``def`` or ``class`` immediately nested within another ``def`` or ``class``. :param continuation_tab_width: Indent width used for line continuations. :param dedent_closing_brackets: Puts closing brackets on a separate line, dedented, if the bracketed expression can't fit in a single line. Applies to all kinds of brackets, including function definitions and calls. :param indent_dictionary_value: Indents the dictionary value if it cannot fit on the same line as the dictionary key. :param coalesce_brackets: Prevents splitting consecutive brackets. Only relevant when ``dedent_closing_brackets`` is set. Example: If ``True``, ``` call_func_that_takes_a_dict( { 'key1': 'value1', 'key2': 'value2', } ) ``` would reformat to: ``` call_func_that_takes_a_dict({ 'key1': 'value1', 'key2': 'value2', }) ``` :param join_multiple_lines: Joins short lines into one line. :param spaces_around_power_operator: Set to ``True`` to prefer using spaces around ``**``. :param spaces_before_comment: The number of spaces required before a trailing comment. :param space_between_ending_comma_and_closing_bracket: Inserts a space between the ending comma and closing bracket of a list, etc. :param split_arguments_when_comma_terminated: Splits before arguments if the argument list is terminated by a comma. :param split_before_bitwise_operator: Set to ``True`` to prefer splitting before ``&``, ``|`` or ``^`` rather than after. :param split_before_first_argument: If an argument / parameter list is going to be split, then split before the first argument. :param split_before_logical_operator: Set to ``True`` to prefer splitting before ``and`` or ``or`` rather than after. :param split_before_named_assigns: Splits named assignments into individual lines. :param use_spaces: Uses spaces for indentation. :param based_on_style: The formatting style to be used as reference. """ options = """ [style] indent_width = {indent_size} column_limit = {max_line_length} allow_multiline_lambdas = {allow_multiline_lambdas} continuation_indent_width = {continuation_tab_width} dedent_closing_brackets = {dedent_closing_brackets} indent_dictionary_value = {indent_dictionary_value} join_multiple_lines = {join_multiple_lines} spaces_around_power_operator = {spaces_around_power_operator} spaces_before_comment = {spaces_before_comment} coalesce_brackets = {coalesce_brackets} split_before_bitwise_operator = {split_before_bitwise_operator} split_before_first_argument = {split_before_first_argument} split_before_logical_operator = {split_before_logical_operator} split_before_named_assigns = {split_before_named_assigns} based_on_style = {based_on_style} blank_line_before_nested_class_or_def = {blank_line_before_nested_class_or_def} split_arguments_when_comma_terminated = {split_arguments_when_comma_terminated} space_between_ending_comma_and_closing_bracket= \ {space_between_ending_comma_and_closing_bracket} """ options += 'use_tabs = ' + str(not use_spaces) options = options.format(**locals()) with prepare_file(options.splitlines(keepends=True), None) as (file_, fname): corrected = FormatFile(filename, style_config=fname)[0].splitlines(True) diffs = Diff.from_string_arrays(file, corrected).split_diff() for diff in diffs: yield Result(self, "The code does not comply with the settings " "provided.", affected_code=(diff.range(filename), ), diffs={filename: diff})
class DiffTest(unittest.TestCase): def setUp(self): self.file = ['1', '2', '3', '4'] self.uut = Diff(self.file) def test_add_lines(self): self.uut.add_lines(0, []) self.uut.add_lines(0, ['t']) self.uut.add_lines(0, []) def test_add_line(self): self.uut.add_line(0, 't') self.assertRaises(ConflictError, self.uut.add_line, 0, 't') self.assertEqual(self.uut.modified, ['t\n', '1\n', '2\n', '3\n', '4']) def test_double_addition(self): self.uut.add_lines(0, ['t']) # No double addition allowed self.assertRaises(ConflictError, self.uut.add_lines, 0, ['t']) self.assertRaises(IndexError, self.uut.add_lines, -1, ['t']) self.assertRaises(TypeError, self.uut.add_lines, 'str', ['t']) def test_delete_line(self): self.uut.delete_line(1) self.uut.delete_line(1) # Double deletion possible without conflict additions, deletions = self.uut.stats() self.assertEqual(deletions, 1) self.assertRaises(IndexError, self.uut.delete_line, 0) self.assertRaises(IndexError, self.uut.delete_line, 10) def test_delete_lines(self): self.uut.delete_lines(1, 2) self.uut.delete_lines(2, 3) additions, deletions = self.uut.stats() self.assertEqual(deletions, 3) self.assertRaises(IndexError, self.uut.delete_lines, 0, 2) self.assertRaises(IndexError, self.uut.delete_lines, 1, 6) def test_change_line(self): self.assertEqual(len(self.uut), 0) self.uut.change_line(2, '1', '2') self.assertEqual(len(self.uut), 2) self.assertRaises(ConflictError, self.uut.change_line, 2, '1', '3') self.assertRaises(IndexError, self.uut.change_line, 0, '1', '2') self.uut.delete_line(1) # Line was deleted, unchangeable self.assertRaises(ConflictError, self.uut.change_line, 1, '1', '2') def test_capture_warnings(self): """ Since this addresses the deprecated method, this testcase is temporary (until the old API is fully removed). """ logger = logging.getLogger() with self.assertLogs(logger, 'DEBUG') as log: self.assertEqual(len(self.uut), 0) self.uut.change_line(2, '1', '2') self.assertEqual(log.output, [ 'DEBUG:root:Use of change_line method is deprecated. Instead ' 'use modify_line method, without the original_line argument']) def test_double_changes_with_same_diff(self): self.uut.modify_line(2, '2') # Double addition when diff is equal is allowed try: self.uut.modify_line(2, '2') except Exception: self.fail('We should not have a conflict on same diff!') def test_affected_code(self): self.assertEqual(self.uut.affected_code('file'), []) self.uut.add_lines(0, ['test']) affected_code = [ SourceRange.from_values('file', start_line=1)] self.assertEqual(self.uut.affected_code('file'), affected_code) self.uut.delete_line(2) affected_code = [ SourceRange.from_values('file', start_line=1), SourceRange.from_values('file', start_line=2)] self.assertEqual(self.uut.affected_code('file'), affected_code) self.uut.delete_line(3) affected_code = [ SourceRange.from_values('file', start_line=1), SourceRange.from_values('file', start_line=2, end_line=3)] self.assertEqual(self.uut.affected_code('file'), affected_code) self.uut.delete_line(4) affected_code = [ SourceRange.from_values('file', start_line=1), SourceRange.from_values('file', start_line=2, end_line=4)] self.assertEqual(self.uut.affected_code('file'), affected_code) def test_len(self): self.uut.delete_line(2) self.assertEqual(len(self.uut), 1) self.uut.add_lines(2, ['2.3', '2.5', '2.6']) self.assertEqual(len(self.uut), 4) self.uut.modify_line(1, '1.1') self.assertEqual(len(self.uut), 6) def test_stats(self): self.uut.delete_line(2) self.assertEqual(self.uut.stats(), (0, 1)) self.uut.add_lines(2, ['2.3', '2.5', '2.6']) self.assertEqual(self.uut.stats(), (3, 1)) self.uut.modify_line(1, '1.1') self.assertEqual(self.uut.stats(), (4, 2)) def test_modified(self): result_file = ['0.1\n', '0.2\n', '1\n', '1.1\n', '3.changed\n', '4'] self.uut.delete_line(2) self.uut.add_lines(0, ['0.1', '0.2']) self.uut.add_lines(1, ['1.1']) self.uut.modify_line(3, '3.changed') self.assertEqual(self.uut.modified, result_file) self.uut.delete_line(len(self.file)) del result_file[-1] result_file[-1] = result_file[-1].rstrip('\n') self.assertEqual(self.uut.modified, result_file) self.uut.delete_line(1) del result_file[2] self.assertEqual(self.uut.modified, result_file) def test_bool(self): self.assertFalse(self.uut) self.uut.add_line(4, '4') self.assertTrue(self.uut) self.uut.delete_line(4) self.assertFalse(self.uut) self.uut.modify_line(1, '1\n') self.assertFalse(self.uut) # test if it works with tuples. uutuple = Diff(('1', '2', '3', '4')) self.assertFalse(uutuple) uutuple.add_line(4, '4') self.assertTrue(uutuple) uutuple.delete_line(4) self.assertFalse(uutuple) uutuple.modify_line(1, '1\n') self.assertFalse(uutuple) def test_addition(self): self.assertRaises(TypeError, self.uut.__add__, 5) result_file = ['1\n', '2\n', '2'] other = Diff(self.file) other.delete_line(1) other.modify_line(2, '2') other.add_lines(0, ['1']) self.uut.delete_line(1) self.uut.delete_line(3) self.uut.modify_line(4, '2') result = self.uut + other self.assertEqual(result.modified, result_file) # Make sure it didn't happen in place! self.assertNotEqual(self.uut.modified, result_file) def test_addition_rename(self): uut = Diff(self.file, rename=False) other = Diff(self.file, rename=False) self.assertEqual((other + uut).rename, False) other.rename = 'some.py' self.assertEqual((other + uut).rename, 'some.py') uut.rename = 'some.py' self.assertEqual((other + uut).rename, 'some.py') uut.rename = 'other.py' self.assertRaises(ConflictError, other.__add__, uut) def test_from_string_arrays(self): a = ['q\n', 'a\n', 'b\n', 'x\n', 'c\n', 'd\n'] b = ['a\n', 'b\n', 'y\n', 'c\n', 'd\n', 'f\n'] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b) a = ['first\n', 'fourth\n'] b = ['first\n', 'second\n', 'third\n', 'fourth\n'] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b) a = ['first\n', 'fourth\n'] b = ['first_changed\n', 'second\n', 'third\n', 'fourth\n'] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b) a = ['first\n', 'second\n', 'third\n', 'fourth\n'] b = ['first\n', 'fourth\n'] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b) a = ['first\n', 'second\n', 'third\n', 'fourth\n'] b = ['first_changed\n', 'second_changed\n', 'fourth\n'] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b) def test_from_unified_diff_single_addition(self): source = ['single line'] target = ['single line\n', 'another line added'] diff = ['--- a/testfile', '+++ b/testfile', '@@ -1 +1,2 @@', ' single line', '+another line added'] diff_string = '\n'.join(diff) self.uut = Diff.from_unified_diff(diff_string, source) self.assertEqual(self.uut.original, source) self.assertEqual(self.uut.modified, target) def test_from_unified_diff_single_deletion(self): source = ['two lines\n', 'to be removed'] target = ['two lines\n'] diff = ['--- a/testfile', '+++ b/testfile', '@@ -1,2 +1 @@', ' two lines', '-to be removed'] diff_string = '\n'.join(diff) self.uut = Diff.from_unified_diff(diff_string, source) self.assertEqual(self.uut.original, source) self.assertEqual(self.uut.modified, target) def test_from_unified_diff_single_modification(self): source = ['first\n', 'second'] target = ['only_first_changed\n', 'second'] diff = ['--- a/testfile', '+++ b/testfile', '@@ -1,2 +1,2 @@', '-first', '+only_first_changed', ' second'] diff_string = '\n'.join(diff) self.uut = Diff.from_unified_diff(diff_string, source) self.assertEqual(self.uut.original, source) self.assertEqual(self.uut.modified, target) def test_from_unified_diff_multiple_additions_different_orderings(self): source = ['A\n', 'B\n', 'C'] target = ['A\n', 'Y\n', 'Z\n', 'B\n', 'C'] diff = ['--- a/testfile', '+++ b/testfile', '@@ -1,3 +1,5 @@', ' A', '+Y', '+Z', ' B', ' C'] diff_string = '\n'.join(diff) self.uut = Diff.from_unified_diff(diff_string, source) self.assertEqual(self.uut.original, source) self.assertEqual(self.uut.modified, target) source = ['A\n', 'B\n', 'C'] target = ['A\n', 'Y\n', 'Z\n', 'C'] diff = ['--- a/testfile', '+++ b/testfile', '@@ -1,3 +1,4 @@', ' A', '+Y', '+Z', '-B', ' C'] diff_string = '\n'.join(diff) self.uut = Diff.from_unified_diff(diff_string, source) self.assertEqual(self.uut.original, source) self.assertEqual(self.uut.modified, target) source = ['A\n', 'B\n', 'C'] target = ['Y\n', 'Z\n', 'C'] diff = ['--- a/testfile', '+++ b/testfile', '@@ -1,3 +1,3 @@', '-A', '+Y', '+Z', '-B', ' C'] diff_string = '\n'.join(diff) self.uut = Diff.from_unified_diff(diff_string, source) self.assertEqual(self.uut.original, source) self.assertEqual(self.uut.modified, target) source = ['A\n', 'B\n', 'C'] target = ['A\n', 'B\n', 'C\n', 'Y\n', 'Z'] diff = ['--- a/testfile', '+++ b/testfile', '@@ -3 +3,3 @@', ' C', '+Y', '+Z'] diff_string = '\n'.join(diff) self.uut = Diff.from_unified_diff(diff_string, source) self.assertEqual(self.uut.original, source) self.assertEqual(self.uut.modified, target) def test_from_unified_diffrent_beginning_line_types(self): source = ['A\n', 'B\n', 'C'] target = ['A\n', 'Y\n', 'B\n', 'C'] diff = ['--- a/testfile', '+++ b/testfile', '@@ -1,3 +1,4 @@', ' A', '+Y', ' B', ' C'] diff_string = '\n'.join(diff) self.uut = Diff.from_unified_diff(diff_string, source) self.assertEqual(self.uut.original, source) self.assertEqual(self.uut.modified, target) source = ['A\n', 'B\n', 'C'] target = ['B\n', 'C'] diff = ['--- a/testfile', '+++ b/testfile', '@@ -1,3 +1,2 @@', '-A', ' B', ' C'] diff_string = '\n'.join(diff) self.uut = Diff.from_unified_diff(diff_string, source) self.assertEqual(self.uut.original, source) self.assertEqual(self.uut.modified, target) source = ['A\n', 'B\n', 'C'] target = ['Z\n', 'A\n', 'B\n', 'C'] diff = ['--- a/testfile', '+++ b/testfile', '@@ -1,2 +1,3 @@', '+Z', ' A', ' B'] diff_string = '\n'.join(diff) self.uut = Diff.from_unified_diff(diff_string, source) self.assertEqual(self.uut.original, source) self.assertEqual(self.uut.modified, target) def test_from_unified_diff_multiple_modifications(self): source = ['first\n', 'second'] target = ['first_changed\n', 'second_changed'] diff = ['--- a/testfile', '+++ b/testfile', '@@ -1,2 +1,2 @@', '-first', '-second', '+first_changed', '+second_changed'] diff_string = '\n'.join(diff) self.uut = Diff.from_unified_diff(diff_string, source) self.assertEqual(self.uut.original, source) self.assertEqual(self.uut.modified, target) def test_from_unified_diff_multiple_hunks(self): source = ['A\n', 'B\n', 'C\n', 'D\n', 'E\n', 'F\n', 'G'] target = ['A\n', 'C\n', 'D\n', 'E\n', 'F\n'] diff = ['--- a/testfile', '+++ b/testfile', '@@ -1,2 +1,1 @@', ' A', '-B', '@@ -3,5 +2,4 @@', ' C', ' D', ' E', ' F', '-G'] diff_string = '\n'.join(diff) self.uut = Diff.from_unified_diff(diff_string, source) self.assertEqual(self.uut.original, source) self.assertEqual(self.uut.modified, target) def test_from_unified_diff_incomplete_hunks_multiple_deletions(self): source = ['A\n', 'B\n', 'C\n', 'D\n', 'E\n', 'F\n', 'G'] target = ['A\n', 'C\n', 'D\n', 'E\n', 'F\n'] diff = ['--- a/testfile', '+++ b/testfile', '@@ -1,2 +1,1 @@', ' A', '-B', '@@ -5,3 +4,2 @@', ' E', ' F', '-G'] diff_string = '\n'.join(diff) self.uut = Diff.from_unified_diff(diff_string, source) self.assertEqual(self.uut.original, source) self.assertEqual(self.uut.modified, target) def test_from_unified_diff_incomplete_hunks_multiple_additions(self): source = ['A\n', 'C\n', 'D\n', 'E\n', 'G'] target = ['A\n', 'B\n', 'C\n', 'D\n', 'E\n', 'F\n', 'G'] diff = ['--- a/testfile', '+++ b/testfile', '@@ -1,1 +1,2 @@', ' A', '+B', '@@ -4,2 +5,3 @@', ' E', '+F', ' G'] diff_string = '\n'.join(diff) self.uut = Diff.from_unified_diff(diff_string, source) self.assertEqual(self.uut.original, source) self.assertEqual(self.uut.modified, target) def test_from_unified_diff_incomplete_hunks_multiple_modifications(self): source = ['A\n', 'B\n', 'C\n', 'D\n', 'E\n', 'F\n', 'G'] target = ['A\n', 'B\n', 'Z\n', 'D\n', 'E\n', 'F\n', 'K'] diff = ['--- a/testfile', '+++ b/testfile', '@@ -1,3 +1,3 @@', ' A', ' B', '-C', '+Z', '@@ -6,2 +5,2 @@', ' F', '-G', '+K'] diff_string = '\n'.join(diff) self.uut = Diff.from_unified_diff(diff_string, source) self.assertEqual(self.uut.original, source) self.assertEqual(self.uut.modified, target) def test_from_unified_diff_unmatched_line_to_delete(self): source = ['first', 'second'] diff = ['--- a/testfile', '+++ b/testfile', '@@ -1,2 +1,2 @@', '-line_to_be_deleted_is_not_same', '+only_first_changed', ' second'] diff_string = '\n'.join(diff) error_message = ('The line to delete does not match with ' 'the line in the original file. ' 'Line to delete: {!r}, ' 'Original line #{!r}: {!r}') with self.assertRaisesRegex( RuntimeError, error_message.format( 'line_to_be_deleted_is_not_same', 1, 'first')): Diff.from_unified_diff(diff_string, source) def test_from_unified_diff_unmatched_context_line(self): source = ['first', 'second'] diff = ['--- a/testfile', '+++ b/testfile', '@@ -1,2 +1,2 @@', ' context_line_is_not_same', ' second'] diff_string = '\n'.join(diff) error_message = ('Context lines do not match. ' 'Line from unified diff: {!r}, ' 'Original line #{!r}: {!r}') with self.assertRaisesRegex( RuntimeError, error_message.format( 'context_line_is_not_same', 1, 'first')): Diff.from_unified_diff(diff_string, source) def test_from_unified_diff_no_changes(self): source = ['first\n', 'second'] target = ['first\n', 'second'] diff = ['--- a/testfile', '+++ b/testfile', '@@ -1,2 +1,2 @@', ' first', ' second'] diff_string = '\n'.join(diff) self.uut = Diff.from_unified_diff(diff_string, source) self.assertEqual(self.uut.original, source) self.assertEqual(self.uut.modified, target) def test_from_unified_diff_no_changes_empty_diff(self): source = ['first\n', 'second'] target = ['first\n', 'second'] diff_string = '' self.uut = Diff.from_unified_diff(diff_string, source) self.assertEqual(self.uut.original, source) self.assertEqual(self.uut.modified, target) def test_from_unified_diff_invalid_line_type_character(self): source = ['first', 'invalid starting character'] diff = ['--- a/testfile', '+++ b/testfile', '@@ -1,2 +1,2 @@', ' first', '*invalid_starting_character'] diff_string = '\n'.join(diff) with self.assertRaises(UnidiffParseError): self.uut = Diff.from_unified_diff(diff_string, source) def test_from_unified_diff_invalid_hunk(self): source = ['A', 'B', 'C', 'D', 'E', 'F', 'G'] diff = ['--- a/testfile', '+++ b/testfile', '@@ -1,7 +1,5 @@', ' A', ' B', '-C', '+Z', '@@ -6,2 +5,2 @@', ' F', '-G', '+K'] diff_string = '\n'.join(diff) with self.assertRaises(UnidiffParseError): self.uut = Diff.from_unified_diff(diff_string, source) # def test_from_clang_fixit(self): # try: # from clang.cindex import Index, LibclangError # except ImportError as err: # raise unittest.case.SkipTest(str(err)) # # joined_file = 'struct { int f0; }\nx = { f0 :1 };\n' # file = joined_file.splitlines(True) # fixed_file = ['struct { int f0; }\n', 'x = { .f0 = 1 };\n'] # try: # tu = Index.create().parse('t.c', unsaved_files=[ # ('t.c', joined_file)]) # except LibclangError as err: # raise unittest.case.SkipTest(str(err)) # # fixit = tu.diagnostics[0].fixits[0] # clang_fixed_file = Diff.from_clang_fixit(fixit, file).modified # self.assertEqual(fixed_file, clang_fixed_file) def test_equality(self): a = ['first', 'second', 'third'] b = ['first', 'third'] diff_1 = Diff.from_string_arrays(a, b) c = ['first', 'second', 'third'] d = ['first', 'third'] diff_2 = Diff.from_string_arrays(c, d) self.assertEqual(diff_1, diff_2) # changing the original array should not influence # the diff a[1] = 'else' self.assertEqual(diff_1, diff_2) diff_1.rename = 'abcd' self.assertNotEqual(diff_1, diff_2) diff_1.rename = False diff_1.delete = True self.assertNotEqual(diff_1, diff_2) diff_1.delete = False diff_1.add_lines(1, ['1']) self.assertNotEqual(diff_1, diff_2) def test_json_export(self): JSONEncoder = create_json_encoder() a = ['first\n', 'second\n', 'third\n'] b = ['first\n', 'third\n'] diff = Diff.from_string_arrays(a, b) self.assertEqual( json.dumps(diff, cls=JSONEncoder, sort_keys=True), '"--- \\n' '+++ \\n' '@@ -1,3 +1,2 @@\\n' ' first\\n' '-second\\n' ' third\\n"') def test_rename(self): self.uut.rename = False self.uut.rename = '1234' with self.assertRaises(TypeError): self.uut.rename = True with self.assertRaises(TypeError): self.uut.rename = 1234 def test_delete(self): self.uut.delete = True self.uut.delete = False # Double deletion is allowed self.uut.delete = False with self.assertRaises(TypeError): self.uut.delete = 'abcd' # If delete is True then modified returns an empty list self.uut.delete = True self.assertEqual(self.uut.modified, []) self.uut.delete = False def test_add_linebreaks(self): expected = ['1\n', '2\n', '3\n'] self.assertEqual( Diff._add_linebreaks(['1', '2', '3']), expected) self.assertEqual( Diff._add_linebreaks(['1', '2\n', '3']), expected) self.assertEqual( Diff._add_linebreaks(expected), expected) self.assertEqual(Diff._add_linebreaks([]), []) def test_generate_linebreaks(self): eof_ln = ['1\n', '2\n', '3\n'] no_eof_ln = ['1\n', '2\n', '3'] self.assertEqual( Diff._generate_linebreaks(['1', '2', '3']), no_eof_ln) self.assertEqual( Diff._generate_linebreaks(['1', '2', '3\n']), eof_ln) self.assertEqual( Diff._generate_linebreaks(['1', '2\n', '3']), no_eof_ln) self.assertEqual( Diff._generate_linebreaks(no_eof_ln), no_eof_ln) self.assertEqual( Diff._generate_linebreaks(eof_ln), eof_ln) self.assertEqual(Diff._generate_linebreaks([]), [])
def run(self, filename, file, language: str, docstyle: str = 'default', allow_missing_func_desc: str = False, indent_size: int = 4): """ Checks for certain in-code documentation styles. It currently checks for the following style: :: The first line needs to have no indentation. - Following lines can have indentation :param x: 4 space indent :return: also 4 space indent following lines are also 4 space indented :param language: The programming language of the file(s). :param docstyle: The docstyle to use. For example ``default`` or ``doxygen``. Docstyles are language dependent, meaning not every language is supported by a certain docstyle. :param allow_missing_func_desc: When set ``True`` this will allow functions with missing descriptions, allowing functions to start with params. :param indent_size: Number of spaces per indentation level. """ for doc_comment in extract_documentation(file, language, docstyle): parsed = doc_comment.parse() metadata = iter(parsed) # Assuming that the first element is always the only main # description. main_description = next(metadata) if main_description.desc == '\n' and not allow_missing_func_desc: warning_desc = """ Missing function description. Please set allow_missing_func_desc = True to ignore this warning. """ else: warning_desc = 'Documentation does not have correct style.' # one empty line shall follow main description (except it's empty # or no annotations follow). if main_description.desc.strip() != '': main_description = main_description._replace( desc='\n' + main_description.desc.strip() + '\n' * (1 if len(parsed) == 1 else 2)) new_metadata = [main_description] for m in metadata: # Split newlines and remove leading and trailing whitespaces. stripped_desc = list(map(str.strip, m.desc.splitlines())) if len(stripped_desc) == 0: # Annotations should be on their own line, though no # further description follows. stripped_desc.append('') else: # Wrap parameter description onto next line if it follows # annotation directly. if stripped_desc[0] != '': stripped_desc.insert(0, '') # Indent with 4 spaces. stripped_desc = ('' if line == '' else ' ' * indent_size + line for line in stripped_desc) new_desc = '\n'.join(stripped_desc) # Strip away trailing whitespaces and obsolete newlines (except # one newline which is mandatory). new_desc = new_desc.rstrip() + '\n' new_metadata.append(m._replace(desc=new_desc.lstrip(' '))) new_comment = DocumentationComment.from_metadata( new_metadata, doc_comment.docstyle_definition, doc_comment.marker, doc_comment.indent, doc_comment.position) if new_comment != doc_comment: # Something changed, let's apply a result. diff = Diff(file) # We need to update old comment positions, as `assemble()` # prepends indentation for first line. old_range = TextRange.from_values(doc_comment.range.start.line, 1, doc_comment.range.end.line, doc_comment.range.end.column) diff.replace(old_range, new_comment.assemble()) yield Result(origin=self, message=warning_desc, affected_code=(diff.range(filename), ), diffs={filename: diff})
def run(self, filename, file, timeout: int = DEFAULT_TIMEOUT, link_ignore_regex: str = "([.\/]example\.com|\{|\$)", follow_redirects: bool = False): """ Find links in any text file and check if they are valid. A link is considered valid if the server responds with a 2xx code. This bear can automatically fix redirects, but ignores redirect URLs that have a huge difference with the original URL. Warning: This bear will make HEAD requests to all URLs mentioned in your codebase, which can potentially be destructive. As an example, this bear would naively just visit the URL from a line that goes like `do_not_ever_open = 'https://api.acme.inc/delete-all-data'` wiping out all your data. :param timeout: Request timeout period. :param link_ignore_regex: A regex for urls to ignore. :param follow_redirects: Set to true to autocorrect redirects. """ for line_number, link, code in InvalidLinkBear.find_links_in_file( file, timeout, link_ignore_regex): if code is None: yield Result.from_values( origin=self, message=('Broken link - unable to connect to ' '{url}').format(url=link), file=filename, line=line_number, severity=RESULT_SEVERITY.MAJOR) elif not 200 <= code < 300: # HTTP status 404, 410 or 50x if code in (404, 410) or 500 <= code < 600: yield Result.from_values( origin=self, message=('Broken link - unable to connect to {url} ' '(HTTP Error: {code})').format(url=link, code=code), file=filename, line=line_number, severity=RESULT_SEVERITY.NORMAL) if follow_redirects and 300 <= code < 400: # HTTP status 30x redirect_url = requests.head(link, allow_redirects=True).url matcher = SequenceMatcher(None, redirect_url, link) if (matcher.real_quick_ratio() > 0.7 and matcher.ratio()) > 0.7: diff = Diff(file) current_line = file[line_number - 1] start = current_line.find(link) end = start + len(link) replacement = current_line[:start] + \ redirect_url + current_line[end:] diff.change_line(line_number, current_line, replacement) yield Result.from_values( self, 'This link redirects to ' + redirect_url, diffs={filename: diff}, file=filename, line=line_number, severity=RESULT_SEVERITY.NORMAL)
class DiffTest(unittest.TestCase): def setUp(self): self.file = ['1', '2', '3', '4'] self.uut = Diff(self.file) def test_add_lines(self): self.uut.add_lines(0, []) self.uut.add_lines(0, ['t']) self.uut.add_lines(0, []) def test_add_line(self): self.uut.add_line(0, 't') self.assertRaises(ConflictError, self.uut.add_line, 0, 't') self.assertEqual(self.uut.modified, ['t', '1', '2', '3', '4']) def test_double_addition(self): self.uut.add_lines(0, ['t']) # No double addition allowed self.assertRaises(ConflictError, self.uut.add_lines, 0, ['t']) self.assertRaises(IndexError, self.uut.add_lines, -1, ['t']) self.assertRaises(TypeError, self.uut.add_lines, 'str', ['t']) def test_delete_line(self): self.uut.delete_line(1) self.uut.delete_line(1) # Double deletion possible without conflict additions, deletions = self.uut.stats() self.assertEqual(deletions, 1) self.assertRaises(IndexError, self.uut.delete_line, 0) self.assertRaises(IndexError, self.uut.delete_line, 10) def test_delete_lines(self): self.uut.delete_lines(1, 2) self.uut.delete_lines(2, 3) additions, deletions = self.uut.stats() self.assertEqual(deletions, 3) self.assertRaises(IndexError, self.uut.delete_lines, 0, 2) self.assertRaises(IndexError, self.uut.delete_lines, 1, 6) def test_change_line(self): self.assertEqual(len(self.uut), 0) self.uut.change_line(2, '1', '2') self.assertEqual(len(self.uut), 2) self.assertRaises(ConflictError, self.uut.change_line, 2, '1', '3') self.assertRaises(IndexError, self.uut.change_line, 0, '1', '2') self.uut.delete_line(1) # Line was deleted, unchangeable self.assertRaises(ConflictError, self.uut.change_line, 1, '1', '2') def test_capture_warnings(self): """ Since this addresses the deprecated method, this testcase is temporary (until the old API is fully removed). """ logger = logging.getLogger() with self.assertLogs(logger, 'DEBUG') as log: self.assertEqual(len(self.uut), 0) self.uut.change_line(2, '1', '2') self.assertEqual(log.output, [ 'DEBUG:root:Use of change_line method is deprecated. Instead ' 'use modify_line method, without the original_line argument' ]) def test_double_changes_with_same_diff(self): self.uut.change_line(2, '1', '2') # Double addition when diff is equal is allowed try: self.uut.change_line(2, '1', '2') except Exception: self.fail('We should not have a conflict on same diff!') def test_affected_code(self): self.assertEqual(self.uut.affected_code('file'), []) self.uut.add_lines(0, ['test']) affected_code = [SourceRange.from_values('file', start_line=1)] self.assertEqual(self.uut.affected_code('file'), affected_code) self.uut.delete_line(2) affected_code = [ SourceRange.from_values('file', start_line=1), SourceRange.from_values('file', start_line=2) ] self.assertEqual(self.uut.affected_code('file'), affected_code) self.uut.delete_line(3) affected_code = [ SourceRange.from_values('file', start_line=1), SourceRange.from_values('file', start_line=2, end_line=3) ] self.assertEqual(self.uut.affected_code('file'), affected_code) self.uut.delete_line(4) affected_code = [ SourceRange.from_values('file', start_line=1), SourceRange.from_values('file', start_line=2, end_line=4) ] self.assertEqual(self.uut.affected_code('file'), affected_code) def test_len(self): self.uut.delete_line(2) self.assertEqual(len(self.uut), 1) self.uut.add_lines(2, ['2.3', '2.5', '2.6']) self.assertEqual(len(self.uut), 4) self.uut.change_line(1, '1', '1.1') self.assertEqual(len(self.uut), 6) def test_stats(self): self.uut.delete_line(2) self.assertEqual(self.uut.stats(), (0, 1)) self.uut.add_lines(2, ['2.3', '2.5', '2.6']) self.assertEqual(self.uut.stats(), (3, 1)) self.uut.change_line(1, '1', '1.1') self.assertEqual(self.uut.stats(), (4, 2)) def test_modified(self): result_file = ['0.1', '0.2', '1', '1.1', '3.changed', '4'] self.uut.delete_line(2) self.uut.add_lines(0, ['0.1', '0.2']) self.uut.add_lines(1, ['1.1']) self.uut.change_line(3, '3', '3.changed') self.assertEqual(self.uut.modified, result_file) self.assertEqual(self.uut.original, self.file) self.uut.delete_line(len(self.file)) del result_file[len(result_file) - 1] self.assertEqual(self.uut.modified, result_file) self.uut.delete_line(1) del result_file[2] self.assertEqual(self.uut.modified, result_file) def test_addition(self): self.assertRaises(TypeError, self.uut.__add__, 5) result_file = ['1', '2', '2'] other = Diff(self.file) other.delete_line(1) other.change_line(2, '1', '2') other.add_lines(0, ['1']) self.uut.delete_line(1) self.uut.delete_line(3) self.uut.change_line(4, '4', '2') result = self.uut + other self.assertEqual(result.modified, result_file) # Make sure it didn't happen in place! self.assertNotEqual(self.uut.modified, result_file) def test_addition_rename(self): uut = Diff(self.file, rename=False) other = Diff(self.file, rename=False) self.assertEqual((other + uut).rename, False) other.rename = 'some.py' self.assertEqual((other + uut).rename, 'some.py') uut.rename = 'some.py' self.assertEqual((other + uut).rename, 'some.py') uut.rename = 'other.py' self.assertRaises(ConflictError, other.__add__, uut) def test_from_string_arrays(self): a = ['q', 'a', 'b', 'x', 'c', 'd'] b = ['a', 'b', 'y', 'c', 'd', 'f'] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b) a = ['first', 'fourth'] b = ['first', 'second', 'third', 'fourth'] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b) a = ['first', 'fourth'] b = ['first_changed', 'second', 'third', 'fourth'] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b) a = ['first', 'second', 'third', 'fourth'] b = ['first', 'fourth'] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b) a = ['first', 'second', 'third', 'fourth'] b = ['first_changed', 'second_changed', 'fourth'] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b) def test_from_clang_fixit(self): try: from clang.cindex import Index, LibclangError except ImportError as err: raise unittest.case.SkipTest(str(err)) joined_file = 'struct { int f0; }\nx = { f0 :1 };\n' file = joined_file.splitlines(True) fixed_file = ['struct { int f0; }\n', 'x = { .f0 = 1 };\n'] try: tu = Index.create().parse('t.c', unsaved_files=[('t.c', joined_file)]) except LibclangError as err: raise unittest.case.SkipTest(str(err)) fixit = tu.diagnostics[0].fixits[0] clang_fixed_file = Diff.from_clang_fixit(fixit, file).modified self.assertEqual(fixed_file, clang_fixed_file) def test_equality(self): a = ['first', 'second', 'third'] b = ['first', 'third'] diff_1 = Diff.from_string_arrays(a, b) a[1] = 'else' diff_2 = Diff.from_string_arrays(a, b) self.assertEqual(diff_1, diff_2) diff_1.rename = 'abcd' self.assertNotEqual(diff_1, diff_2) diff_1.rename = False diff_1.delete = True self.assertNotEqual(diff_1, diff_2) diff_1.delete = False diff_1.add_lines(1, ['1']) self.assertNotEqual(diff_1, diff_2) def test_json_export(self): JSONEncoder = create_json_encoder() a = ['first\n', 'second\n', 'third\n'] b = ['first\n', 'third\n'] diff = Diff.from_string_arrays(a, b) self.assertEqual( json.dumps(diff, cls=JSONEncoder, sort_keys=True), '"--- \\n' '+++ \\n' '@@ -1,3 +1,2 @@\\n' ' first\\n' '-second\\n' ' third\\n"') def test_rename(self): self.uut.rename = False self.uut.rename = '1234' with self.assertRaises(TypeError): self.uut.rename = True with self.assertRaises(TypeError): self.uut.rename = 1234 def test_delete(self): self.uut.delete = True self.uut.delete = False # Double deletion is allowed self.uut.delete = False with self.assertRaises(TypeError): self.uut.delete = 'abcd' # If delete is True then modified returns an empty list self.uut.delete = True self.assertEqual(self.uut.modified, []) self.uut.delete = False
class DiffTestCase(unittest.TestCase): def setUp(self): self.uut = Diff() def test_add_lines(self): self.uut.add_lines(0, []) self.uut.add_lines(0, ["t"]) self.uut.add_lines(0, []) self.assertRaises(ConflictError, self.uut.add_lines, 0, ["t"]) # No double addition allowed self.assertRaises(ValueError, self.uut.add_lines, -1, ["t"]) self.assertRaises(TypeError, self.uut.add_lines, "str", ["t"]) def test_delete_line(self): self.uut.delete_line(1) self.uut.delete_line(1) # Double deletion possible without conflict self.assertRaises(ValueError, self.uut.delete_line, 0) def test_change_line(self): self.assertEqual(len(self.uut), 0) self.uut.change_line(2, "1", "2") self.assertEqual(len(self.uut), 1) self.assertRaises(ConflictError, self.uut.change_line, 2, "1", "3") self.assertRaises(ValueError, self.uut.change_line, 0, "1", "2") self.uut.delete_line(1) self.assertRaises(AssertionError, self.uut.change_line, 1, "1", "2") # Line was deleted, unchangeable def test_apply(self): file = ["1", "2", "3", "4"] result_file = ["0.1", "0.2", "1", "1.1", "3.changed", "4"] self.uut.delete_line(2) self.uut.add_lines(0, ["0.1", "0.2"]) self.uut.add_lines(1, ["1.1"]) self.uut.change_line(3, "3", "3.changed") self.assertEqual(self.uut.apply(file), result_file) self.uut.delete_line(len(file)) del result_file[len(result_file) - 1] self.assertEqual(self.uut.apply(file), result_file) self.uut.delete_line(1) del result_file[2] self.assertEqual(self.uut.apply(file), result_file) def test_addition(self): self.assertRaises(TypeError, self.uut.__add__, 5) file = ["1", "1", "3", "4"] result_file = ["1", "2", "2"] other = Diff() other.delete_line(1) other.change_line(2, "1", "2") other.add_lines(0, ["1"]) self.uut.delete_line(1) self.uut.delete_line(3) self.uut.change_line(4, "4", "2") self.uut += other self.assertEqual(self.uut.apply(file), result_file)
def test_is_applicable(self): diff = Diff(["1\n", "2\n", "3\n"]) diff.delete_line(2) patch_result = Result("", "", diffs={'f': diff}) self.assertTrue( ApplyPatchAction.is_applicable(patch_result, {}, {}))
def run(self, filename, file, max_filename_length: int = 260, file_naming_convention: str = None, ignore_uppercase_filenames: bool = True, filename_prefix: str = '', filename_suffix: str = ''): """ Checks whether the filename follows a certain naming-convention. :param file_naming_convention: The naming-convention. Supported values are: - ``auto`` to guess the correct convention. Defaults to ``snake`` if the correct convention cannot be guessed. - ``camel`` (``thisIsCamelCase``) - ``kebab`` (``this-is-kebab-case``) - ``pascal`` (``ThisIsPascalCase``) - ``snake`` (``this_is_snake_case``) - ``space`` (``This Is Space Case``) :param max_filename_length: Maximum filename length on both Windows and Unix-like systems. :param ignore_uppercase_filenames: Whether or not to ignore fully uppercase filenames completely, e.g. COPYING, LICENSE etc. :param filename_prefix: Check whether the filename uses a certain prefix. The file's extension is ignored. :param filename_suffix: Check whether the filename uses a certain suffix. The file's extension is ignored. """ head, tail = os.path.split(filename) filename_without_extension, extension = os.path.splitext(tail) if file_naming_convention is None: self.warn('Please specify a file naming convention explicitly' ' or use "auto".') file_naming_convention = 'auto' else: file_naming_convention = file_naming_convention.lower() if file_naming_convention == 'auto': if extension in self._language_naming_convention: file_naming_convention = self._language_naming_convention[ extension] else: self.warn('The file naming convention could not be guessed. ' 'Using the default "snake" naming convention.') file_naming_convention = 'snake' messages = [] try: new_name = self._naming_convention[file_naming_convention]( filename_without_extension) except KeyError: self.err('Invalid file-naming-convention provided: ' + file_naming_convention) return if new_name != filename_without_extension: messages.append( 'Filename does not follow {} naming-convention.'.format( file_naming_convention)) if not filename_without_extension.startswith(filename_prefix): new_name = filename_prefix + new_name messages.append('Filename does not use the prefix {!r}.'.format( filename_prefix)) if not filename_without_extension.endswith(filename_suffix): new_name = new_name + filename_suffix messages.append('Filename does not use the suffix {!r}.'.format( filename_suffix)) if len(filename) > max_filename_length: messages.append('Filename is too long ({} > {}).'.format( len(filename), max_filename_length)) if ignore_uppercase_filenames and filename_without_extension.isupper(): return if messages: diff = Diff(file, rename=os.path.join(head, new_name + extension)) message = ('\n'.join( '- ' + mes for mes in messages) if len(messages) > 1 else messages[0]) yield Result(self, message, diff.affected_code(filename), diffs={filename: diff})
class DiffTest(unittest.TestCase): def setUp(self): self.file = ["1", "2", "3", "4"] self.uut = Diff(self.file) def test_add_lines(self): self.uut.add_lines(0, []) self.uut.add_lines(0, ["t"]) self.uut.add_lines(0, []) # No double addition allowed self.assertRaises(ConflictError, self.uut.add_lines, 0, ["t"]) self.assertRaises(ValueError, self.uut.add_lines, -1, ["t"]) self.assertRaises(TypeError, self.uut.add_lines, "str", ["t"]) def test_delete_line(self): self.uut.delete_line(1) self.uut.delete_line(1) # Double deletion possible without conflict self.assertRaises(ValueError, self.uut.delete_line, 0) def test_change_line(self): self.assertEqual(len(self.uut), 0) self.uut.change_line(2, "1", "2") self.assertEqual(len(self.uut), 1) self.assertRaises(ConflictError, self.uut.change_line, 2, "1", "3") self.assertRaises(ValueError, self.uut.change_line, 0, "1", "2") self.uut.delete_line(1) # Line was deleted, unchangeable self.assertRaises(AssertionError, self.uut.change_line, 1, "1", "2") def test_affected_code(self): self.assertEqual(self.uut.affected_code("file"), []) self.uut.add_lines(0, ["test"]) affected_code = [ SourceRange.from_values("file", start_line=1)] self.assertEqual(self.uut.affected_code("file"), affected_code) self.uut.delete_line(2) affected_code = [ SourceRange.from_values("file", start_line=1), SourceRange.from_values("file", start_line=2)] self.assertEqual(self.uut.affected_code("file"), affected_code) self.uut.delete_line(3) affected_code = [ SourceRange.from_values("file", start_line=1), SourceRange.from_values("file", start_line=2, end_line=3)] self.assertEqual(self.uut.affected_code("file"), affected_code) self.uut.delete_line(6) affected_code = [ SourceRange.from_values("file", start_line=1), SourceRange.from_values("file", start_line=2, end_line=3), SourceRange.from_values('file', start_line=6)] self.assertEqual(self.uut.affected_code("file"), affected_code) def test_modified(self): result_file = ["0.1", "0.2", "1", "1.1", "3.changed", "4"] self.uut.delete_line(2) self.uut.add_lines(0, ["0.1", "0.2"]) self.uut.add_lines(1, ["1.1"]) self.uut.change_line(3, "3", "3.changed") self.assertEqual(self.uut.modified, result_file) self.assertEqual(self.uut.original, self.file) self.uut.delete_line(len(self.file)) del result_file[len(result_file) - 1] self.assertEqual(self.uut.modified, result_file) self.uut.delete_line(1) del result_file[2] self.assertEqual(self.uut.modified, result_file) def test_addition(self): self.assertRaises(TypeError, self.uut.__add__, 5) result_file = ["1", "2", "2"] other = Diff(self.file) other.delete_line(1) other.change_line(2, "1", "2") other.add_lines(0, ["1"]) self.uut.delete_line(1) self.uut.delete_line(3) self.uut.change_line(4, "4", "2") result = self.uut + other self.assertEqual(result.modified, result_file) # Make sure it didn't happen in place! self.assertNotEqual(self.uut.modified, result_file) def test_from_string_arrays(self): a = ["q", "a", "b", "x", "c", "d"] b = ["a", "b", "y", "c", "d", "f"] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b) a = ["first", "fourth"] b = ["first", "second", "third", "fourth"] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b) a = ["first", "fourth"] b = ["first_changed", "second", "third", "fourth"] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b) a = ["first", "second", "third", "fourth"] b = ["first", "fourth"] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b) a = ["first", "second", "third", "fourth"] b = ["first_changed", "second_changed", "fourth"] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b) @skip_if_no_clang() def test_from_clang_fixit(self): joined_file = 'struct { int f0; }\nx = { f0 :1 };\n' file = joined_file.splitlines(True) fixed_file = ['struct { int f0; }\n', 'x = { .f0 = 1 };\n'] tu = Index.create().parse('t.c', unsaved_files=[ ('t.c', joined_file)]) fixit = tu.diagnostics[0].fixits[0] clang_fixed_file = Diff.from_clang_fixit(fixit, file).modified self.assertEqual(fixed_file, clang_fixed_file) def test_equality(self): a = ["first", "second", "third"] b = ["first", "third"] diff_1 = Diff.from_string_arrays(a, b) a[1] = "else" diff_2 = Diff.from_string_arrays(a, b) self.assertEqual(diff_1, diff_2) diff_1.add_lines(1, ["1"]) self.assertNotEqual(diff_1, diff_2) def test_json_export(self): a = ["first\n", "second\n", "third\n"] b = ["first\n", "third\n"] diff = Diff.from_string_arrays(a, b) self.assertEqual( json.dumps(diff, cls=JSONEncoder, sort_keys=True), '"--- \\n' '+++ \\n' '@@ -1,3 +1,2 @@\\n' ' first\\n' '-second\\n' ' third\\n"')
def setUp(self): diff0 = Diff(['coler']) diff0.modify_line(1, 'color') diff1 = Diff(['coler']) diff1.modify_line(1, 'colour') diff2 = Diff(['coler']) diff2.modify_line(1, 'cooler') diff3 = Diff(['coler']) diff3.modify_line(1, 'coder') self.original_diff = {'filename': diff0} self.alternate_diff1 = {'filename': diff1} self.alternate_diff2 = {'filename': diff2} self.alternate_diff3 = {'filename': diff3} self.alternate_diffs = [ self.alternate_diff1, self.alternate_diff2, self.alternate_diff3 ] self.result = Result('origin', 'message', diffs=self.original_diff, alternate_diffs=self.alternate_diffs) self.original_file_dict = {'filename': ['coler']} self.section = Section('name') self.section.append(Setting('no_color', 'True')) self.uut1 = AlternatePatchAction(self.alternate_diff1, 1) self.uut2 = AlternatePatchAction(self.alternate_diff2, 2) self.uut3 = AlternatePatchAction(self.alternate_diff3, 3)
class DiffTest(unittest.TestCase): def setUp(self): self.file = ["1", "2", "3", "4"] self.uut = Diff(self.file) def test_add_lines(self): self.uut.add_lines(0, []) self.uut.add_lines(0, ["t"]) self.uut.add_lines(0, []) # No double addition allowed self.assertRaises(ConflictError, self.uut.add_lines, 0, ["t"]) self.assertRaises(ValueError, self.uut.add_lines, -1, ["t"]) self.assertRaises(TypeError, self.uut.add_lines, "str", ["t"]) def test_delete_line(self): self.uut.delete_line(1) self.uut.delete_line(1) # Double deletion possible without conflict self.assertRaises(ValueError, self.uut.delete_line, 0) def test_change_line(self): self.assertEqual(len(self.uut), 0) self.uut.change_line(2, "1", "2") self.assertEqual(len(self.uut), 2) self.assertRaises(ConflictError, self.uut.change_line, 2, "1", "3") self.assertRaises(ValueError, self.uut.change_line, 0, "1", "2") self.uut.delete_line(1) # Line was deleted, unchangeable self.assertRaises(ConflictError, self.uut.change_line, 1, "1", "2") def test_affected_code(self): self.assertEqual(self.uut.affected_code("file"), []) self.uut.add_lines(0, ["test"]) affected_code = [ SourceRange.from_values("file", start_line=1)] self.assertEqual(self.uut.affected_code("file"), affected_code) self.uut.delete_line(2) affected_code = [ SourceRange.from_values("file", start_line=1), SourceRange.from_values("file", start_line=2)] self.assertEqual(self.uut.affected_code("file"), affected_code) self.uut.delete_line(3) affected_code = [ SourceRange.from_values("file", start_line=1), SourceRange.from_values("file", start_line=2, end_line=3)] self.assertEqual(self.uut.affected_code("file"), affected_code) self.uut.delete_line(6) affected_code = [ SourceRange.from_values("file", start_line=1), SourceRange.from_values("file", start_line=2, end_line=3), SourceRange.from_values('file', start_line=6)] self.assertEqual(self.uut.affected_code("file"), affected_code) def test_len(self): self.uut.delete_line(2) self.assertEqual(len(self.uut), 1) self.uut.add_lines(2, ["2.3", "2.5", "2.6"]) self.assertEqual(len(self.uut), 4) self.uut.change_line(1, "1", "1.1") self.assertEqual(len(self.uut), 6) def test_stats(self): self.uut.delete_line(2) self.assertEqual(self.uut.stats(), (0, 1)) self.uut.add_lines(2, ["2.3", "2.5", "2.6"]) self.assertEqual(self.uut.stats(), (3, 1)) self.uut.change_line(1, "1", "1.1") self.assertEqual(self.uut.stats(), (4, 2)) def test_modified(self): result_file = ["0.1", "0.2", "1", "1.1", "3.changed", "4"] self.uut.delete_line(2) self.uut.add_lines(0, ["0.1", "0.2"]) self.uut.add_lines(1, ["1.1"]) self.uut.change_line(3, "3", "3.changed") self.assertEqual(self.uut.modified, result_file) self.assertEqual(self.uut.original, self.file) self.uut.delete_line(len(self.file)) del result_file[len(result_file) - 1] self.assertEqual(self.uut.modified, result_file) self.uut.delete_line(1) del result_file[2] self.assertEqual(self.uut.modified, result_file) def test_addition(self): self.assertRaises(TypeError, self.uut.__add__, 5) result_file = ["1", "2", "2"] other = Diff(self.file) other.delete_line(1) other.change_line(2, "1", "2") other.add_lines(0, ["1"]) self.uut.delete_line(1) self.uut.delete_line(3) self.uut.change_line(4, "4", "2") result = self.uut + other self.assertEqual(result.modified, result_file) # Make sure it didn't happen in place! self.assertNotEqual(self.uut.modified, result_file) def test_addition_rename(self): uut = Diff(self.file, rename=False) other = Diff(self.file, rename=False) self.assertEqual((other + uut).rename, False) other.rename = "some.py" self.assertEqual((other + uut).rename, "some.py") uut.rename = "some.py" self.assertEqual((other + uut).rename, "some.py") uut.rename = "other.py" self.assertRaises(ConflictError, other.__add__, uut) def test_from_string_arrays(self): a = ["q", "a", "b", "x", "c", "d"] b = ["a", "b", "y", "c", "d", "f"] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b) a = ["first", "fourth"] b = ["first", "second", "third", "fourth"] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b) a = ["first", "fourth"] b = ["first_changed", "second", "third", "fourth"] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b) a = ["first", "second", "third", "fourth"] b = ["first", "fourth"] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b) a = ["first", "second", "third", "fourth"] b = ["first_changed", "second_changed", "fourth"] self.uut = Diff.from_string_arrays(a, b) self.assertEqual(self.uut.modified, b) def test_from_clang_fixit(self): try: from clang.cindex import Index, LibclangError except ImportError as err: raise SkipTest(str(err)) joined_file = 'struct { int f0; }\nx = { f0 :1 };\n' file = joined_file.splitlines(True) fixed_file = ['struct { int f0; }\n', 'x = { .f0 = 1 };\n'] try: tu = Index.create().parse('t.c', unsaved_files=[ ('t.c', joined_file)]) except LibclangError as err: raise SkipTest(str(err)) fixit = tu.diagnostics[0].fixits[0] clang_fixed_file = Diff.from_clang_fixit(fixit, file).modified self.assertEqual(fixed_file, clang_fixed_file) def test_equality(self): a = ["first", "second", "third"] b = ["first", "third"] diff_1 = Diff.from_string_arrays(a, b) a[1] = "else" diff_2 = Diff.from_string_arrays(a, b) self.assertEqual(diff_1, diff_2) diff_1.rename = "abcd" self.assertNotEqual(diff_1, diff_2) diff_1.rename = False diff_1.delete = True self.assertNotEqual(diff_1, diff_2) diff_1.delete = False diff_1.add_lines(1, ["1"]) self.assertNotEqual(diff_1, diff_2) def test_json_export(self): JSONEncoder = create_json_encoder() a = ["first\n", "second\n", "third\n"] b = ["first\n", "third\n"] diff = Diff.from_string_arrays(a, b) self.assertEqual( json.dumps(diff, cls=JSONEncoder, sort_keys=True), '"--- \\n' '+++ \\n' '@@ -1,3 +1,2 @@\\n' ' first\\n' '-second\\n' ' third\\n"') def test_rename(self): self.uut.rename = False self.uut.rename = "1234" with self.assertRaises(TypeError): self.uut.rename = True with self.assertRaises(TypeError): self.uut.rename = 1234 def test_delete(self): self.uut.delete = True self.uut.delete = False # Double deletion is allowed self.uut.delete = False with self.assertRaises(TypeError): self.uut.delete = "abcd" # If delete is True then modified returns an empty list self.uut.delete = True self.assertEqual(self.uut.modified, []) self.uut.delete = False
def run( self, filename, file, max_line_length: int = 0, json_sort: bool = False, indent_size: int = SpacingHelper.DEFAULT_TAB_WIDTH, escape_unicode: bool = True, ): """ Raises issues for any deviations from the pretty-printed JSON. :param max_line_length: Maximum number of characters for a line. When set to 0 allows infinite line length. :param json_sort: Whether or not keys should be sorted. :param indent_size: Number of spaces per indentation level. :param escape_unicode: Whether or not to escape unicode values using ASCII. """ if not max_line_length: max_line_length = sys.maxsize # Output a meaningful message if empty file given as input if len(file) == 0: yield Result.from_values(self, 'This file is empty.', file=filename) return try: json_content = json.loads(''.join(file), object_pairs_hook=OrderedDict) except JSONDecodeError as err: err_content = match(r'(.*): line (\d+) column (\d+)', str(err)) yield Result.from_values( self, 'This file does not contain parsable JSON. ' + err_content.group(1) + '.', file=filename, line=int(err_content.group(2)), column=int(err_content.group(3))) return for line_number, line in enumerate(file): line = line.expandtabs(indent_size) if len(line) > max_line_length + 1: yield Result.from_values( origin=self, message='Line is longer than allowed.' ' ({actual} > {maximum})'.format(actual=len(line) - 1, maximum=max_line_length), file=filename, line=line_number + 1, column=max_line_length + 1, end_line=line_number + 1, end_column=len(line), ) corrected = json.dumps(json_content, sort_keys=json_sort, indent=indent_size, ensure_ascii=escape_unicode).splitlines(True) # Because of a bug in several python versions we have to correct # whitespace here. corrected = tuple(line.rstrip(' \n') + '\n' for line in corrected) diff = Diff.from_string_arrays(file, corrected) if len(diff) > 0: yield Result(self, 'This file can be reformatted by sorting keys and ' 'following indentation.', affected_code=tuple( d.range(filename) for d in diff.split_diff()), diffs={filename: diff})
def test_is_applicable(self): diff = Diff([], rename='new_name') result = Result('', '', diffs={'f': diff}) self.assertTrue(self.uut.is_applicable(result, {}, {'f': diff}))
def setUp(self): self.file = ["1", "2", "3", "4"] self.uut = Diff(self.file)
def test_apply(self): uut = ApplyPatchAction() with make_temp() as f_a, make_temp() as f_b, make_temp() as f_c: file_dict = { f_a: ["1\n", "2\n", "3\n"], f_b: ["1\n", "2\n", "3\n"], f_c: ["1\n", "2\n", "3\n"] } expected_file_dict = { f_a: ["1\n", "3_changed\n"], f_b: ["1\n", "2\n", "3_changed\n"], f_c: ["1\n", "2\n", "3\n"] } file_diff_dict = {} diff = Diff(file_dict[f_a]) diff.delete_line(2) uut.apply_from_section(Result("origin", "msg", diffs={f_a: diff}), file_dict, file_diff_dict, Section("t")) diff = Diff(file_dict[f_a]) diff.change_line(3, "3\n", "3_changed\n") uut.apply_from_section(Result("origin", "msg", diffs={f_a: diff}), file_dict, file_diff_dict, Section("t")) diff = Diff(file_dict[f_b]) diff.change_line(3, "3\n", "3_changed\n") uut.apply(Result("origin", "msg", diffs={f_b: diff}), file_dict, file_diff_dict) for filename in file_diff_dict: file_dict[filename] = file_diff_dict[filename].modified self.assertEqual(file_dict, expected_file_dict) with open(f_a) as fa: self.assertEqual(file_dict[f_a], fa.readlines()) with open(f_b) as fb: self.assertEqual(file_dict[f_b], fb.readlines()) with open(f_c) as fc: # File c is unchanged and should be untouched self.assertEqual([], fc.readlines())
def setUp(self): self.uut = Diff()
def __yield_diffs(file, new_file): if tuple(new_file) != tuple(file): wholediff = Diff.from_string_arrays(file, new_file) for diff in wholediff.split_diff(): yield diff
def test_stdin_stderr_config_correction(self): create_arguments_mock = Mock() generate_config_mock = Mock() # `some_value_A` and `some_value_B` are used to test the different # delegation to `generate_config()` and `create_arguments()` # accordingly. class Handler: @staticmethod def generate_config(filename, file, some_value_A): generate_config_mock(filename, file, some_value_A) return '\n'.join(['use_stdin', 'use_stderr', 'correct']) @staticmethod def create_arguments(filename, file, config_file, some_value_B): create_arguments_mock(filename, file, config_file, some_value_B) return self.test_program_path, '--config', config_file uut = (linter(sys.executable, use_stdin=True, use_stdout=False, use_stderr=True, output_format='corrected', config_suffix='.conf')(Handler)(self.section, None)) results = list( uut.run(self.testfile2_path, self.testfile2_content, some_value_A=124, some_value_B=-78)) expected_correction = [s + '\n' for s in ['+', '/', '/', '-']] diffs = list( Diff.from_string_arrays(self.testfile2_content, expected_correction).split_diff()) expected = [ Result.from_values(uut, 'Inconsistency found.', self.testfile2_path, 1, None, 1, None, RESULT_SEVERITY.NORMAL, diffs={self.testfile2_path: diffs[0]}), Result.from_values(uut, 'Inconsistency found.', self.testfile2_path, 5, None, 5, None, RESULT_SEVERITY.NORMAL, diffs={self.testfile2_path: diffs[1]}) ] self.assertEqual(results, expected) create_arguments_mock.assert_called_once_with(self.testfile2_path, self.testfile2_content, ANY, -78) self.assertEqual(create_arguments_mock.call_args[0][2][-5:], '.conf') generate_config_mock.assert_called_once_with(self.testfile2_path, self.testfile2_content, 124)
def run(self, filename, file, dependency_results: dict, language: str, use_spaces: bool = True, indent_size: int = SpacingHelper.DEFAULT_TAB_WIDTH, coalang_dir: str = None, ): """ It is a generic indent bear, which looks for a start and end indent specifier, example: ``{ : }`` where "{" is the start indent specifier and "}" is the end indent specifier. If the end-specifier is not given, this bear looks for unindents within the code to correctly figure out indentation. It also figures out hanging indents and absolute indentation of function params or list elements. It does not however support indents based on keywords yet. for example: if(something) does not get indented undergoes no change. WARNING: The IndentationBear is experimental right now, you can report any issues found to https://github.com/coala/coala-bears :param filename: Name of the file that needs to be checked. :param file: File that needs to be checked in the form of a list of strings. :param dependency_results: Results given by the AnnotationBear. :param language: Language to be used for indentation. :param use_spaces: Insert spaces instead of tabs for indentation. :param indent_size: Number of spaces per indentation level. :param coalang_dir: Full path of external directory containing the coalang file for language. """ lang_settings_dict = LanguageDefinition( language, coalang_dir=coalang_dir) annotation_dict = dependency_results[AnnotationBear.name][0].contents # 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']) indent_types[':'] = '' else: indent_types = dict(lang_settings_dict['indent_types']) encapsulators = (dict(lang_settings_dict['encapsulators']) if 'encapsulators' in lang_settings_dict else {}) encaps_pos = [] for encapsulator in encapsulators: encaps_pos += self.get_specified_block_range( file, filename, encapsulator, encapsulators[encapsulator], annotation_dict) encaps_pos = tuple(sorted(encaps_pos, key=lambda x: x.start.line)) comments = dict(lang_settings_dict['comment_delimiters']) comments.update( dict(lang_settings_dict['multiline_comment_delimiters'])) try: indent_levels = self.get_indent_levels( file, filename, indent_types, annotation_dict, encaps_pos, comments) # This happens only in case of unmatched indents or # ExpectedIndentError. except (UnmatchedIndentError, ExpectedIndentError) as e: yield Result(self, str(e), severity=RESULT_SEVERITY.MAJOR) return absolute_indent_levels = self.get_absolute_indent_of_range( file, filename, encaps_pos, annotation_dict) insert = ' '*indent_size if use_spaces else '\t' no_indent_file = self._get_no_indent_file(file) new_file = self._get_basic_indent_file(no_indent_file, indent_levels, insert) new_file = self._get_absolute_indent_file(new_file, absolute_indent_levels, indent_levels, insert) if new_file != list(file): wholediff = Diff.from_string_arrays(file, new_file) for diff in wholediff.split_diff(): yield Result( self, 'The indentation could be changed to improve readability.', severity=RESULT_SEVERITY.INFO, affected_code=(diff.range(filename),), diffs={filename: diff})
def test_add(self): file_dict = { 'f_a': ['1', '2', '3'], 'f_b': ['1', '2', '3'], 'f_c': ['1', '2', '3'] } expected_file_dict = { 'f_a': ['1\n', '3_changed'], 'f_b': ['1\n', '2\n', '3_changed'], 'f_c': ['1', '2', '3'] } diff = Diff(file_dict['f_a']) diff.delete_line(2) uut1 = Result('origin', 'msg', diffs={'f_a': diff}) diff = Diff(file_dict['f_a']) diff.modify_line(3, '3_changed') uut2 = Result('origin', 'msg', diffs={'f_a': diff}) diff = Diff(file_dict['f_b']) diff.modify_line(3, '3_changed') uut3 = Result('origin', 'msg', diffs={'f_b': diff}) uut1 += uut2 + uut3 uut1.apply(file_dict) self.assertEqual(file_dict, expected_file_dict)
def test_add(self): file_dict = { "f_a": ["1", "2", "3"], "f_b": ["1", "2", "3"], "f_c": ["1", "2", "3"] } expected_file_dict = { "f_a": ["1", "3_changed"], "f_b": ["1", "2", "3_changed"], "f_c": ["1", "2", "3"] } diff = Diff(file_dict['f_a']) diff.delete_line(2) uut1 = Result("origin", "msg", diffs={"f_a": diff}) diff = Diff(file_dict['f_a']) diff.change_line(3, "3", "3_changed") uut2 = Result("origin", "msg", diffs={"f_a": diff}) diff = Diff(file_dict['f_b']) diff.change_line(3, "3", "3_changed") uut3 = Result("origin", "msg", diffs={"f_b": diff}) uut1 += uut2 + uut3 uut1.apply(file_dict) self.assertEqual(file_dict, expected_file_dict)
def run(self, filename, file, use_spaces: bool, allow_trailing_whitespace: bool=False, tab_width: int=SpacingHelper.DEFAULT_TAB_WIDTH, enforce_newline_at_EOF: bool=True): ''' Checks the space consistency for each line. :param use_spaces: True if spaces are to be used instead of tabs :param allow_trailing_whitespace: Whether to allow trailing whitespace or not :param tab_width: Number of spaces representing one tab :param enforce_newline_at_EOF: Whether to enforce a newline at the End Of File ''' spacing_helper = SpacingHelper(tab_width) result_texts = [] for line_number, line in enumerate(file, start=1): replacement = line if enforce_newline_at_EOF: # Since every line contains at the end at least one \n, only # the last line could potentially not have one. So we don't # need to check whether the current line_number is the last # one. if replacement[-1] != '\n': replacement += '\n' result_texts.append('No newline at EOF.') if not allow_trailing_whitespace: replacement = replacement.rstrip(' \t\n') + '\n' if replacement != line.rstrip('\n') + '\n': result_texts.append('Trailing whitespaces.') if use_spaces: pre_replacement = replacement replacement = spacing_helper.replace_tabs_with_spaces( replacement) if replacement != pre_replacement: result_texts.append('Tabs used instead of spaces.') else: pre_replacement = replacement replacement = spacing_helper.replace_spaces_with_tabs( replacement) if replacement != pre_replacement: result_texts.append('Spaces used instead of tabs.') if len(result_texts) > 0: diff = Diff(file) diff.change_line(line_number, line, replacement) inconsistencies = ''.join('\n- ' + string for string in result_texts) yield Result.from_values( self, 'Line contains following spacing inconsistencies:' + inconsistencies, diffs={filename: diff}, file=filename, line=line_number) result_texts = []
def setUp(self): self.file = ['1', '2', '3', '4'] self.uut = Diff(self.file)
def run(self, filename, file, use_parentheses_in_import: bool = True, force_alphabetical_sort_in_import: bool = False, force_sort_within_import_sections: bool = True, from_first_in_import: bool = False, include_trailing_comma_in_import: bool = False, combine_star_imports: bool = True, combine_as_imports: bool = True, lines_after_imports: int = -1, order_imports_by_type: bool = False, balanced_wrapping_in_imports: bool = False, import_heading_localfolder: str = '', import_heading_firstparty: str = '', import_heading_thirdparty: str = '', import_heading_stdlib: str = '', import_heading_future: str = '', default_import_section: str = 'FIRSTPARTY', force_grid_wrap_imports: bool = False, force_single_line_imports: bool = True, sort_imports_by_length: bool = False, use_spaces: bool = True, indent_size: int = SpacingHelper.DEFAULT_TAB_WIDTH, forced_separate_imports: typed_list(str) = (), isort_multi_line_output: int = 4, known_first_party_imports: typed_list(str) = (), known_third_party_imports: typed_list(str) = (), known_standard_library_imports: typed_list(str) = None, max_line_length: int = 79, imports_forced_to_top: typed_list(str) = ()): """ Raise issues related to sorting imports, segregating imports into various sections, and also adding comments on top of each import section based on the configurations provided. You can read more about ``isort`` at <https://isort.readthedocs.org/en/latest/>. :param use_parentheses_in_import: True if parenthesis are to be used in import statements. :param force_alphabetical_sort_in_import: If set, forces all imports to be sorted as a single section, instead of within other groups (such as straight vs from). :param force_sort_within_import_sections: If set, imports will be sorted within there section independent to the import_type. :param from_first_in_import: If set, imports using "from" will be displayed above normal (straight) imports. :param include_trailing_comma_in_import: If set, will automatically add a trailing comma to the end of "from" imports. Example: ``from abc import (a, b, c,)`` :param combine_star_imports: If set to true - ensures that if a star import is present, nothing else is imported from that namespace. :param combine_as_imports: If set to true - isort will combine as imports on the same line within for import statements. :param lines_after_imports: Forces a certain number of lines after the imports and before the first line of functional code. By default this is set to -1 which uses 2 lines if the first line of code is a class or function and 1 line if it's anything else. :param order_imports_by_type: If set to true - isort will create separate sections within "from" imports for CONSTANTS, Classes, and modules/functions. :param balanced_wrapping_in_imports: If set to true - for each multi-line import statement isort will dynamically change the import length to the one that produces the most balanced grid, while staying below the maximum import length defined. :param import_heading_localfolder: A comment to consistently place directly above imports that start with '.'. :param import_heading_firstparty: A comment to consistently place directly above imports from the current project. :param import_heading_thirdparty: A comment to consistently place directly above thirdparty imports. :param import_heading_stdlib: A comment to consistently place directly above imports from the standard library. :param import_heading_future: A comment to consistently place directly above future imports. :param default_import_section: The default section to place imports in, if their section can not be automatically determined. :param force_grid_wrap_imports: Force "from" imports to be grid wrapped regardless of line length. :param force_single_line_imports: If set to true - instead of wrapping multi-line from style imports, each import will be forced to display on its own line. :param sort_imports_by_length: Set to true to sort imports by length instead of alphabetically. :param use_spaces: True if spaces are to be used instead of tabs. :param indent_size: Number of spaces per indentation level. :param forced_separate_imports: A list of modules that you want to appear in their own separate section. :param isort_multi_line_output: An integer that represents how you want imports to be displayed by ``isort`` if they're long enough to span multiple lines. This value is passed to isort as the ``multi_line_output`` setting. Possible values are (0-grid, 1-vertical, 2-hanging, 3-vert-hanging, 4-vert-grid, 5-vert-grid-grouped) A full definition of all possible modes can be found at <https://github.com/timothycrosley/isort#multi-line-output-modes>. :param known_first_party_imports: A list of imports that will be forced to display within the standard library category of imports. :param known_third_party_imports: A list of imports that will be forced to display within the third party category of imports. :param known_standard_library_imports: A list of imports that will be forced to display within the first party category of imports. :param import_wrap_length: An integer that represents the longest line-length you want when wrapping. If not set will default to line_length. :param imports_forced_to_top: Forces a list of imports to the top of their respective section. This works well for handling the unfortunate cases of import dependencies that occur in many projects. :param max_line_length: Maximum number of characters for a line. """ isort_settings = dict( use_parentheses=use_parentheses_in_import, force_alphabetical_sort=force_alphabetical_sort_in_import, force_sort_within_sections=force_sort_within_import_sections, from_first=from_first_in_import, include_trailing_comma=include_trailing_comma_in_import, combine_star=combine_star_imports, lines_after_imports=lines_after_imports, order_by_type=order_imports_by_type, balanced_wrapping=balanced_wrapping_in_imports, import_heading_localfolder=import_heading_localfolder, import_heading_firstparty=import_heading_firstparty, import_heading_thirdparty=import_heading_thirdparty, import_heading_stdlib=import_heading_stdlib, import_heading_future=import_heading_future, default_section=default_import_section, force_grid_wrap=force_grid_wrap_imports, force_single_line=force_single_line_imports, length_sort=sort_imports_by_length, indent='Tab' if use_spaces == False else indent_size, forced_separate=forced_separate_imports, multi_line_output=isort_multi_line_output, known_first_party=known_first_party_imports, line_length=max_line_length, force_to_top=imports_forced_to_top) if known_standard_library_imports is not None: isort_settings['known_standard_library'] = ( known_standard_library_imports) sort_imports = SortImports(file_contents=''.join(file), **isort_settings) new_file = tuple(sort_imports.output.splitlines(True)) if new_file != tuple(file): diff = Diff.from_string_arrays(file, new_file) yield Result(self, 'Imports can be sorted.', affected_code=diff.affected_code(filename), diffs={filename: diff})
def run( self, filename, file, max_line_length: int = 79, indent_size: int = SpacingHelper.DEFAULT_TAB_WIDTH, allow_multiline_lambdas: bool = False, blank_line_before_nested_class_or_def: bool = False, continuation_tab_width: int = SpacingHelper.DEFAULT_TAB_WIDTH, dedent_closing_brackets: bool = False, indent_dictionary_value: bool = False, coalesce_brackets: bool = False, join_multiple_lines: bool = True, spaces_around_power_operator: bool = True, spaces_before_comment: int = 2, space_between_ending_comma_and_closing_bracket: bool = True, split_arguments_when_comma_terminated: bool = False, split_before_bitwise_operator: bool = False, split_before_first_argument: bool = False, split_before_logical_operator: bool = False, split_before_named_assigns: bool = True, use_spaces: bool = True, based_on_style: str = 'pep8', prefer_line_break_after_opening_bracket: bool = True, ): """ Check and correct formatting of Python code using ``yapf`` utility. See <https://github.com/google/yapf> for more information. :param max_line_length: Maximum number of characters for a line. :param indent_size: Number of spaces per indentation level. :param allow_multiline_lambdas: Allows lambdas to be formatted on more than one line. :param blank_line_before_nested_class_or_def: Inserts a blank line before a ``def`` or ``class`` immediately nested within another ``def`` or ``class``. :param continuation_tab_width: Indent width used for line continuations. :param dedent_closing_brackets: Puts closing brackets on a separate line, dedented, if the bracketed expression can't fit in a single line. Applies to all kinds of brackets, including function definitions and calls. :param indent_dictionary_value: Indents the dictionary value if it cannot fit on the same line as the dictionary key. :param coalesce_brackets: Prevents splitting consecutive brackets. Only relevant when ``dedent_closing_brackets`` is set. Example: If ``True``:: call_func_that_takes_a_dict( { 'key1': 'value1', 'key2': 'value2', } ) would reformat to:: call_func_that_takes_a_dict({ 'key1': 'value1', 'key2': 'value2', }) :param join_multiple_lines: Joins short lines into one line. :param spaces_around_power_operator: Set to ``True`` to prefer using spaces around ``**``. :param spaces_before_comment: The number of spaces required before a trailing comment. :param space_between_ending_comma_and_closing_bracket: Inserts a space between the ending comma and closing bracket of a list, etc. :param split_arguments_when_comma_terminated: Splits before arguments if the argument list is terminated by a comma. :param split_before_bitwise_operator: Set to ``True`` to prefer splitting before ``&``, ``|`` or ``^`` rather than after. :param split_before_first_argument: If an argument / parameter list is going to be split, then split before the first argument. :param split_before_logical_operator: Set to ``True`` to prefer splitting before ``and`` or ``or`` rather than after. :param split_before_named_assigns: Splits named assignments into individual lines. :param use_spaces: Uses spaces for indentation. :param based_on_style: The formatting style to be used as reference. :param prefer_line_break_after_opening_bracket: If True, splitting right after a open bracket will not be preferred. """ if not file: # Yapf cannot handle zero-byte files well, and adds a redundent # newline into the file. To avoid this, we don't parse zero-byte # files as they cannot have anything to format either. return options = """ [style] indent_width = {indent_size} column_limit = {max_line_length} allow_multiline_lambdas = {allow_multiline_lambdas} continuation_indent_width = {continuation_tab_width} dedent_closing_brackets = {dedent_closing_brackets} indent_dictionary_value = {indent_dictionary_value} join_multiple_lines = {join_multiple_lines} spaces_around_power_operator = {spaces_around_power_operator} spaces_before_comment = {spaces_before_comment} coalesce_brackets = {coalesce_brackets} split_before_bitwise_operator = {split_before_bitwise_operator} split_before_first_argument = {split_before_first_argument} split_before_logical_operator = {split_before_logical_operator} split_before_named_assigns = {split_before_named_assigns} based_on_style = {based_on_style} blank_line_before_nested_class_or_def = {blank_line_before_nested_class_or_def} split_arguments_when_comma_terminated = {split_arguments_when_comma_terminated} space_between_ending_comma_and_closing_bracket= \ {space_between_ending_comma_and_closing_bracket} """ options += 'use_tabs = ' + str(not use_spaces) + '\n' options += ( 'split_penalty_after_opening_bracket = ' + ('30' if prefer_line_break_after_opening_bracket else '0') + '\n') options = options.format(**locals()) try: with prepare_file(options.splitlines(keepends=True), None) as (file_, fname): corrected = FormatCode(''.join(file), style_config=fname)[0].splitlines(True) except SyntaxError as err: if isinstance(err, IndentationError): error_type = 'indentation errors (' + err.args[0] + ')' else: error_type = 'syntax errors' yield Result.from_values( self, 'The code cannot be parsed due to {0}.'.format(error_type), filename, line=err.lineno, column=err.offset) return diffs = Diff.from_string_arrays(file, corrected).split_diff() for diff in diffs: yield Result(self, 'The code does not comply with the settings ' 'provided.', affected_code=(diff.range(filename), ), diffs={filename: diff})
def test_apply(self): uut = ApplyPatchAction() file_dict = { "f_a": ["1", "2", "3"], "f_b": ["1", "2", "3"], "f_c": ["1", "2", "3"] } expected_file_dict = { "f_a": ["1", "3_changed"], "f_b": ["1", "2", "3_changed"], "f_c": ["1", "2", "3"] } file_diff_dict = {} diff = Diff() diff.delete_line(2) uut.apply_from_section(PatchResult("origin", "msg", {"f_a": diff}), file_dict, file_diff_dict, Section("t")) diff = Diff() diff.change_line(3, "3", "3_changed") uut.apply_from_section(PatchResult("origin", "msg", {"f_a": diff}), file_dict, file_diff_dict, Section("t")) diff = Diff() diff.change_line(3, "3", "3_changed") uut.apply(PatchResult("origin", "msg", {"f_b": diff}), file_dict, file_diff_dict) for filename in file_diff_dict: file_dict[filename] = file_diff_dict[filename].apply( file_dict[filename]) self.assertEqual(file_dict, expected_file_dict)
def test_print_result(self): print_result(self.console_printer, self.log_printer, None, self.file_diff_dict, "illegal value", {}) with simulate_console_inputs(0): print_result(self.console_printer, self.log_printer, self.section, self.file_diff_dict, Result("origin", "msg", diffs={}), {}) with make_temp() as testfile_path: file_dict = { testfile_path: ["1\n", "2\n", "3\n"], "f_b": ["1", "2", "3"] } diff = Diff(file_dict[testfile_path]) diff.delete_line(2) diff.change_line(3, "3\n", "3_changed\n") ApplyPatchAction.is_applicable = staticmethod(lambda *args: True) # Interaction must be closed by the user with `0` if it's not a # param with simulate_console_inputs("INVALID", -1, 1, 0, 3) as input_generator: curr_section = Section("") print_section_beginning(self.console_printer, curr_section) print_result( self.console_printer, self.log_printer, curr_section, self.file_diff_dict, Result("origin", "msg", diffs={testfile_path: diff}), file_dict) self.assertEqual(input_generator.last_input, 3) self.file_diff_dict.clear() with open(testfile_path) as f: self.assertEqual(f.readlines(), ["1\n", "3_changed\n"]) os.remove(testfile_path + ".orig") name, section = get_action_info(curr_section, TestAction().get_metadata(), failed_actions=set()) self.assertEqual(input_generator.last_input, 4) self.assertEqual(str(section), " {param : '3'}") self.assertEqual(name, "TestAction") # Check if the user is asked for the parameter only the first time. # Use OpenEditorAction that needs this parameter (editor command). with simulate_console_inputs(1, "test_editor", 0, 1, 0) as generator: OpenEditorAction.is_applicable = staticmethod(lambda *args: True) patch_result = Result("origin", "msg", diffs={testfile_path: diff}) patch_result.file = "f_b" print_result(self.console_printer, self.log_printer, curr_section, self.file_diff_dict, patch_result, file_dict) # choose action, choose editor, choose no action (-1 -> 2) self.assertEqual(generator.last_input, 2) # It shoudn't ask for parameter again print_result(self.console_printer, self.log_printer, curr_section, self.file_diff_dict, patch_result, file_dict) self.assertEqual(generator.last_input, 4)
def test_print_result(self): print_result(self.console_printer, None, self.file_diff_dict, 'illegal value', {}) with simulate_console_inputs('n'): print_result(self.console_printer, self.section, self.file_diff_dict, Result('origin', 'msg', diffs={}), {}) with make_temp() as testfile_path: file_dict = { testfile_path: ['1\n', '2\n', '3\n'], 'f_b': ['1', '2', '3'] } diff = Diff(file_dict[testfile_path]) diff.delete_line(2) diff.change_line(3, '3\n', '3_changed\n') ApplyPatchAction.is_applicable = staticmethod(lambda *args: True) # Interaction must be closed by the user with `0` if it's not a # param with simulate_console_inputs('INVALID', -1, 'a', 'n', 'm') as input_generator: curr_section = Section('') print_section_beginning(self.console_printer, curr_section) print_result( self.console_printer, curr_section, self.file_diff_dict, Result('origin', 'msg', diffs={testfile_path: diff}), file_dict) self.assertEqual(input_generator.last_input, 3) self.file_diff_dict.clear() with open(testfile_path) as f: self.assertEqual(f.readlines(), ['1\n', '3_changed\n']) os.remove(testfile_path + '.orig') name, section = get_action_info(curr_section, TestAction().get_metadata(), failed_actions=set()) self.assertEqual(input_generator.last_input, 4) self.assertEqual(str(section), " {param : 'm'}") self.assertEqual(name, 'TestAction') # Check if the user is asked for the parameter only the first time. # Use OpenEditorAction that needs this parameter (editor command). with simulate_console_inputs('o', 'test_editor', 'n', 'o', 'n') as generator: OpenEditorAction.is_applicable = staticmethod(lambda *args: True) patch_result = Result('origin', 'msg', diffs={testfile_path: diff}) patch_result.file = 'f_b' print_result(self.console_printer, curr_section, self.file_diff_dict, patch_result, file_dict) # choose action, choose editor, choose no action (-1 -> 2) self.assertEqual(generator.last_input, 2) # It shoudn't ask for parameter again print_result(self.console_printer, curr_section, self.file_diff_dict, patch_result, file_dict) self.assertEqual(generator.last_input, 4)
def run( self, filename, file, use_spaces: bool, allow_trailing_whitespace: bool = False, indent_size: int = SpacingHelper.DEFAULT_TAB_WIDTH, enforce_newline_at_EOF: bool = True, ): ''' Check and correct spacing for all textual data. This includes usage of tabs vs. spaces, trailing whitespace and (missing) newlines before the end of the file. :param use_spaces: True if spaces are to be used instead of tabs. :param allow_trailing_whitespace: Whether to allow trailing whitespace or not. :param indent_size: Number of spaces per indentation level. :param enforce_newline_at_EOF: Whether to enforce a newline at the End Of File. ''' spacing_helper = SpacingHelper(indent_size) result_texts = [] additional_info_texts = [] for line_number, line in enumerate(file, start=1): replacement = line if enforce_newline_at_EOF: # Since every line contains at the end at least one \n, only # the last line could potentially not have one. So we don't # need to check whether the current line_number is the last # one. if replacement[-1] != '\n': replacement += '\n' result_texts.append('No newline at EOF.') additional_info_texts.append( "A trailing newline character ('\\n') is missing from " 'your file. ' '<http://stackoverflow.com/a/5813359/3212182> gives ' 'more information about why you might need one.') if not allow_trailing_whitespace: replacement = replacement.rstrip(' \t\n') + '\n' if replacement != line.rstrip('\n') + '\n': result_texts.append('Trailing whitespaces.') additional_info_texts.append( 'Your source code contains trailing whitespaces. ' 'Those usually have no meaning. Please consider ' 'removing them.') if use_spaces: pre_replacement = replacement replacement = replacement.expandtabs(indent_size) if replacement != pre_replacement: result_texts.append('Tabs used instead of spaces.') else: pre_replacement = replacement replacement = spacing_helper.replace_spaces_with_tabs( replacement) if replacement != pre_replacement: result_texts.append('Spaces used instead of tabs.') if len(result_texts) > 0: diff = Diff(file) diff.change_line(line_number, line, replacement) inconsistencies = ''.join('\n- ' + string for string in result_texts) yield Result.from_values( self, 'Line contains following spacing inconsistencies:' + inconsistencies, diffs={filename: diff}, file=filename, line=line_number, additional_info='\n\n'.join(additional_info_texts)) result_texts = [] additional_info_texts = []
def test_is_applicable(self): diff = Diff(['1\n', '2\n', '3\n']) diff.delete_line(2) patch_result = Result('', '', diffs={'f': diff}) self.assertTrue(ApplyPatchAction.is_applicable(patch_result, {}, {}))