From 076aecd482aebd64260ca6001b9c6039d973f821 Mon Sep 17 00:00:00 2001 From: Ruben De Smet Date: Mon, 27 Feb 2017 18:50:19 +0100 Subject: [PATCH] RustClippyLintBear: Linter support for Rust code Closes https://github.com/coala/coala-bears/issues/50 --- .gitignore | 1 + bears/rust/RustClippyLintBear.py | 77 ++++++++++++++++++++++++++++ bears/rust/__init__.py | 0 tests/rust/.gitignore | 2 + tests/rust/RustClippyLintBearTest.py | 40 +++++++++++++++ tests/rust/__init__.py | 0 tests/rust/test_bad/Cargo.toml | 5 ++ tests/rust/test_bad/src/lib.rs | 24 +++++++++ tests/rust/test_ok/Cargo.toml | 5 ++ tests/rust/test_ok/src/lib.rs | 3 ++ 10 files changed, 157 insertions(+) create mode 100644 bears/rust/RustClippyLintBear.py create mode 100644 bears/rust/__init__.py create mode 100644 tests/rust/.gitignore create mode 100644 tests/rust/RustClippyLintBearTest.py create mode 100644 tests/rust/__init__.py create mode 100644 tests/rust/test_bad/Cargo.toml create mode 100644 tests/rust/test_bad/src/lib.rs create mode 100644 tests/rust/test_ok/Cargo.toml create mode 100644 tests/rust/test_ok/src/lib.rs diff --git a/.gitignore b/.gitignore index 24d6ce89bf..0e44cd7b02 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ htmlcov *.egg-info .coverage.* src +!tests/rust/test_*/src site .zanata-cache *.exe diff --git a/bears/rust/RustClippyLintBear.py b/bears/rust/RustClippyLintBear.py new file mode 100644 index 0000000000..1d0a436434 --- /dev/null +++ b/bears/rust/RustClippyLintBear.py @@ -0,0 +1,77 @@ +import json +import os + +from coalib.bears.GlobalBear import GlobalBear +from coalib.misc.Shell import run_shell_command +from coalib.results.Result import Result +from coalib.results.RESULT_SEVERITY import RESULT_SEVERITY +from dependency_management.requirements.CargoRequirement import ( + CargoRequirement) + + +class RustClippyLintBear(GlobalBear): + LANGUAGES = {'Rust'} + AUTHORS = {'The coala developers'} + AUTHORS_EMAILS = {'coala-devel@googlegroups.com'} + LICENSE = 'AGPL-3.0' + CAN_DETECT = { + 'Formatting', + 'Unused Code', + 'Syntax', + 'Unreachable Code', + 'Smell', + 'Code Simplification', + } + EXECUTABLE = 'cargo.exe' if os.name == 'nt' else 'cargo' + REQUIREMENTS = { + CargoRequirement('clippy') + } + SEVERITY_MAP = { + 'warning': RESULT_SEVERITY.NORMAL, + 'error': RESULT_SEVERITY.MAJOR, + } + + def run(self): + args = ('clippy', '--quiet', '--color', 'never', + '--', '-Z', 'unstable-options', + '--error-format', 'json', + '--test') + # In the future, we might want to use --manifest-path, which + # should point to the Cargo.toml file, instead of relying on + # the config directory. + # Currently bugged: + # https://github.com/Manishearth/rust-clippy/issues/1611 + + _, stderr_output = run_shell_command( + (self.EXECUTABLE,) + args, + cwd=self.get_config_dir(), + universal_newlines=True) + + # Rust outputs \n's, instead of the system default. + for line in stderr_output.split('\n'): + # cargo still outputs some text, even when in quiet mode, + # when a project does not build. We treat it as + # a major concern. + if line.startswith('error: Could not compile '): + yield Result( + origin=self.__class__.__name__, + message=line, + severity=RESULT_SEVERITY.MAJOR) + continue + if not line: + continue + if line.startswith('To learn more, run the command again'): + continue + yield self.new_result(json.loads(line)) + + def new_result(self, issue): + span = issue['spans'][0] + return Result.from_values( + origin=self.__class__.__name__, + message=issue['message'], + file=span['file_name'], + line=span['line_start'], + end_line=span['line_end'], + column=span['column_start'], + end_column=span['column_end'], + severity=self.SEVERITY_MAP[issue['level']]) diff --git a/bears/rust/__init__.py b/bears/rust/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/rust/.gitignore b/tests/rust/.gitignore new file mode 100644 index 0000000000..a9d37c560c --- /dev/null +++ b/tests/rust/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/tests/rust/RustClippyLintBearTest.py b/tests/rust/RustClippyLintBearTest.py new file mode 100644 index 0000000000..7c2a7e28c6 --- /dev/null +++ b/tests/rust/RustClippyLintBearTest.py @@ -0,0 +1,40 @@ +import unittest +import os +from queue import Queue +from shutil import which +from unittest.case import skipIf + +from coalib.settings.Section import Section + +from bears.rust.RustClippyLintBear import RustClippyLintBear + + +@skipIf(which('cargo') is None, 'Cargo is not installed') +class RustClippyLintBearTest(unittest.TestCase): + + def setUp(self): + self.section = Section('name') + self.queue = Queue() + self.file_dict = {} + self.uut = RustClippyLintBear(self.file_dict, self.section, self.queue) + + def change_directory(self, directory_name): + test_path = os.path.abspath(os.path.join(os.path.dirname(__file__), + directory_name)) + os.chdir(test_path) + + def set_config_dir(self, directory): + # Work around https://github.com/coala/coala/issues/3867 + test_path = os.path.abspath(os.path.join( + os.path.dirname(__file__), directory)) + self.uut.get_config_dir = lambda *args, **kwargs: test_path + + def test_ok_source(self): + self.set_config_dir('test_ok') + results = list(self.uut.run()) + self.assertTrue(len(results) == 0) + + def test_bad_source(self): + self.set_config_dir('test_bad') + results = list(self.uut.run()) + self.assertTrue(len(results) >= 3) diff --git a/tests/rust/__init__.py b/tests/rust/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/rust/test_bad/Cargo.toml b/tests/rust/test_bad/Cargo.toml new file mode 100644 index 0000000000..ccaca8ccad --- /dev/null +++ b/tests/rust/test_bad/Cargo.toml @@ -0,0 +1,5 @@ +[package] +name = "coala_test_files" +version = "0.1.0" + +[dependencies] diff --git a/tests/rust/test_bad/src/lib.rs b/tests/rust/test_bad/src/lib.rs new file mode 100644 index 0000000000..c767679a79 --- /dev/null +++ b/tests/rust/test_bad/src/lib.rs @@ -0,0 +1,24 @@ +// This function should trigger unused code (warning level) +fn testssss() { + // This comparison should trigger a `deny` level lint + let mut x: f64 = 0.0; + if x == std::f64::NAN { + } + x += 1.; // Triggers allow style lint + println!("{}", x); +} + + +// We test whether bad code is found in (unit) tests +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + let x = Some(1u8); + // This match triggers a `warning` level lint + match x { + Some(y) => println!("{:?}", y), + _ => () + } + } +} diff --git a/tests/rust/test_ok/Cargo.toml b/tests/rust/test_ok/Cargo.toml new file mode 100644 index 0000000000..ccaca8ccad --- /dev/null +++ b/tests/rust/test_ok/Cargo.toml @@ -0,0 +1,5 @@ +[package] +name = "coala_test_files" +version = "0.1.0" + +[dependencies] diff --git a/tests/rust/test_ok/src/lib.rs b/tests/rust/test_ok/src/lib.rs new file mode 100644 index 0000000000..a93251b65d --- /dev/null +++ b/tests/rust/test_ok/src/lib.rs @@ -0,0 +1,3 @@ +#[test] +fn it_works() { +}