diff --git a/.gitignore b/.gitignore index de4a392..8cab567 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /vendor /composer.lock +.idea diff --git a/composer.json b/composer.json index 61bf3d6..a75a72c 100644 --- a/composer.json +++ b/composer.json @@ -17,5 +17,18 @@ "nette/neon": "~2.3", "latte/latte": "~2.3" }, + "require-dev": { + "nette/tester": "1.4.0" + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "autoload-dev": { + "classmap": [ + "tests/" + ] + }, "bin": ["src/code-checker.php"] } diff --git a/src/CodeCheckers/EntityChecker.php b/src/CodeCheckers/EntityChecker.php new file mode 100644 index 0000000..29716e0 --- /dev/null +++ b/src/CodeCheckers/EntityChecker.php @@ -0,0 +1,38 @@ +is('php')) { + return; + } + + if (Strings::contains($s, '@ORM\Entity') && !Strings::contains($s, '@ORM\Entity(')) { + $checker->fix('Missing Entity`()`'); + $s = str_replace('@ORM\Entity', '@ORM\Entity()', $s); + } + + if (Strings::contains($s, '@ORM\Table()')) { + $checker->fix('Missing `name="table_name"`'); + $s = str_replace('@ORM\Table()', '@ORM\Table(name="")', $s); + } + + if (Strings::contains($s, '@ORM\Table') && !Strings::contains($s, '@ORM\Table(')) { + $checker->fix('Missing `name="table_name"`'); + $s = str_replace('@ORM\Table', '@ORM\Table(name="")', $s); + } + + return $s; + }; + } +} diff --git a/src/CodeCheckers/Html5Checker.php b/src/CodeCheckers/Html5Checker.php new file mode 100644 index 0000000..d383dd1 --- /dev/null +++ b/src/CodeCheckers/Html5Checker.php @@ -0,0 +1,25 @@ +is('latte')) { + return; + } + + if (Strings::match($s, '##')) { + $checker->error('contains XHTML'); + } + }; + } +} diff --git a/src/CodeCheckers/LineLengthChecker.php b/src/CodeCheckers/LineLengthChecker.php new file mode 100644 index 0000000..a9275ea --- /dev/null +++ b/src/CodeCheckers/LineLengthChecker.php @@ -0,0 +1,37 @@ +is('php') && !$checker->is('phpt')) { + return; + } + + $i = 0; + foreach (explode("\n", $s) as $line) { + $i++; + $line = str_replace("\t", str_repeat(' ', 4), $line); + $lineLength = Strings::length($line); + $message = sprintf('Line %s have %d characters', Strings::truncate(Strings::trim($line), 30), $lineLength); + + if ($lineLength > $errorLineLength) { + $checker->error($message, $i); + continue; + } + if ($warningLineLength !== NULL && $lineLength > $warningLineLength) { + $checker->warning($message, $i); + } + } + }; + } +} diff --git a/src/CodeCheckers/NeonChecker.php b/src/CodeCheckers/NeonChecker.php new file mode 100644 index 0000000..4d96a7c --- /dev/null +++ b/src/CodeCheckers/NeonChecker.php @@ -0,0 +1,32 @@ +is('neon')) { + return; + } + + $lines = explode("\n", $s); + foreach ($lines as &$line) { + if (preg_match('~^(.*):( )?(yes|on|no|off)$~i', $line)) { + $message = sprintf('Boolean values should be true/false: %s', $line); + $checker->fix($message); + $line = preg_replace('~:( )?(yes|on)$~i', ': true', $line); + $line = preg_replace('~:( )?(no|off)$~i', ': false', $line); + } + } + + return implode("\n", $lines); + }; + } +} diff --git a/src/code-checker.php b/src/code-checker.php old mode 100644 new mode 100755 index 06f7d17..62f5ddc --- a/src/code-checker.php +++ b/src/code-checker.php @@ -6,10 +6,19 @@ * This file is part of the Nette Framework (https://nette.org) */ -use Nette\Utils\Strings; +use CodeCheckers\EntityChecker; +use CodeCheckers\LineLengthChecker; +use CodeCheckers\Html5Checker; +use CodeCheckers\NeonChecker; use Nette\CommandLine\Parser; +use Nette\Utils\Strings; + +$loaders = array_filter([ + __DIR__ . '/../vendor/autoload.php', + __DIR__ . '/../../../autoload.php', +], 'file_exists'); -if (@!include __DIR__ . '/../vendor/autoload.php') { +if (@!include reset($loaders)) { echo('Install packages using `composer update`'); exit(1); } @@ -373,6 +382,27 @@ public function is($extensions) } }; +// cs, missing @testCase in phpt files +$checker->tasks[] = function (CodeChecker $checker, $s) { + if ($checker->is('phpt')) { + if (!Strings::contains($s, ' * @testCase')) { + $checker->error('Missing @testCase'); + } + } +}; + +// cs, entity +$checker->tasks[] = EntityChecker::createAnnotationsChecker(); + +//max line length +$checker->tasks[] = LineLengthChecker::createLineLengthChecker(NULL, 360); + +//html5
+$checker->tasks[] = Html5Checker::createHtml5CheckerChecker(); + +//boolean (yes, on -> true, no, off -> false) +$checker->tasks[] = NeonChecker::createBooleanValuesChecker(); + $ok = $checker->run($options['-d']); exit($ok ? 0 : 1); diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000..a7ffcfd --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1,3 @@ +/*/output +/test.log +/tmp diff --git a/tests/CodeCheckers/EntityChecker.phpt b/tests/CodeCheckers/EntityChecker.phpt new file mode 100644 index 0000000..a597daa --- /dev/null +++ b/tests/CodeCheckers/EntityChecker.phpt @@ -0,0 +1,100 @@ +fixMessages[] = $message; + } + + + + public function is($file) + { + return TRUE; + } +} + + + +class EntityCheckerTest extends TestCase +{ + /** + * @var FakeChecker + */ + private $fakeChecker; + + + + public function setup() + { + $this->fakeChecker = new FakeChecker(); + } + + + + /** + * @dataProvider getDataForTestEntity + */ + public function testEntity($input, $output, $countOfFixMessages) + { + $annotationChecker = EntityChecker::createAnnotationsChecker(); + Assert::same($output, $annotationChecker($this->fakeChecker, $input)); + Assert::count($countOfFixMessages, $this->fakeChecker->fixMessages); + } + + + + public function getDataForTestEntity() + { + return [ + ['@ORM\Entity', '@ORM\Entity()', 1], + ['@ORM\Entity()', '@ORM\Entity()', 0], + ]; + } + + + + /** + * @dataProvider getDataForTestTableName + */ + public function testTableName($input, $output, $countOfFixMessages) + { + $annotationChecker = EntityChecker::createAnnotationsChecker(); + Assert::same($output, $annotationChecker($this->fakeChecker, $input)); + Assert::count($countOfFixMessages, $this->fakeChecker->fixMessages); + } + + + + public function getDataForTestTableName() + { + return [ + ['@ORM\Table()', '@ORM\Table(name="")', 1], + ['@ORM\Table', '@ORM\Table(name="")', 1], + ['@ORM\Table(name="table_name")', '@ORM\Table(name="table_name")', 0], + ]; + } +} + + + +\run(new EntityCheckerTest()); diff --git a/tests/CodeCheckers/Html5Checker.phpt b/tests/CodeCheckers/Html5Checker.phpt new file mode 100644 index 0000000..b6c93f2 --- /dev/null +++ b/tests/CodeCheckers/Html5Checker.phpt @@ -0,0 +1,106 @@ +errors[] = $message; + } + + + + public function is($file) + { + return TRUE; + } +} + + + +class Html5CheckerTest extends Tester\TestCase +{ + + /** + * @var FakeChecker + */ + private $fakeChecker; + + + + public function setup() + { + $this->fakeChecker = new FakeChecker(); + } + + + + /** + * @dataProvider getDataForTestPass + */ + public function testPass($s) + { + $html5Checker = Html5Checker::createHtml5CheckerChecker(); + $html5Checker($this->fakeChecker, $s); + Assert::count(0, $this->fakeChecker->errors); + } + + + + public function getDataForTestPass() + { + return [ + [NULL], + ['

lol

'], + ['
lol

'], + ]; + } + + + + /** + * @dataProvider getDataForTestFail + */ + public function testFail($s) + { + $html5Checker = Html5Checker::createHtml5CheckerChecker(); + $html5Checker($this->fakeChecker, $s); + Assert::count(1, $this->fakeChecker->errors); + } + + + + public function getDataForTestFail() + { + return [ + ['lol

'], + ['

lol

'], + ['
lol

'], + ["
"], + [""], + ]; + } +} + + + +\run(new Html5CheckerTest()); diff --git a/tests/CodeCheckers/LineLengthChecker.phpt b/tests/CodeCheckers/LineLengthChecker.phpt new file mode 100644 index 0000000..6abef3f --- /dev/null +++ b/tests/CodeCheckers/LineLengthChecker.phpt @@ -0,0 +1,179 @@ +warning[] = $message; + } + + + + public function error($message) + { + $this->error[] = $message; + } + + + + public function is($file) + { + return TRUE; + } +} + + + +class LineLengthCheckerTest extends Tester\TestCase +{ + + /** + * @var FakeChecker + */ + private $fakeChecker; + + + + public function setup() + { + $this->fakeChecker = new FakeChecker(); + } + + + + /** + * @dataProvider getDataForTestTooLengthFiles + */ + public function testTooLengthFiles($s, $countOfMessages) + { + $checker = LineLengthChecker::createLineLengthChecker(30); + + $checker($this->fakeChecker, $s); + Assert::count($countOfMessages, $this->fakeChecker->warning); + foreach ($this->fakeChecker->warning as $message) { + Assert::match('%A%have%A%characters', $message); + } + } + + + + public function getDataForTestTooLengthFiles() + { + $s = str_repeat('a', 32); + + return [ + [$s, 1], + ["\n\n\n\n$s\n$s", 2], + ["foo\nbar\nfoo\n$s\n$s\n$s", 3], + ["__construct(foo$s,$s", 1], + ]; + } + + + + /** + * @dataProvider getDataForTestPass + */ + public function testPass($s) + { + $checker = LineLengthChecker::createLineLengthChecker(30); + + $checker($this->fakeChecker, $s); + Assert::count(0, $this->fakeChecker->warning); + } + + + + public function getDataForTestPass() + { + $s = str_repeat('a', 15); + + return [ + [$s], + ["\n\n\n\n$s\n$s"], + ["foo\nbar\nfoo\n$s\n$s\n$s"], + ]; + } + + + + public function testTabsToSpaces() + { + $checker = LineLengthChecker::createLineLengthChecker(10); + + $checker($this->fakeChecker, "\t\t"); + Assert::count(0, $this->fakeChecker->warning); + + $checker($this->fakeChecker, "\t\t\t"); //3 tabs = 12 characters + Assert::count(1, $this->fakeChecker->warning); + + foreach ($this->fakeChecker->warning as $message) { + Assert::match('%A%have%A%characters', $message); + } + } + + + public function testErrorAndWarning() + { + $checker = LineLengthChecker::createLineLengthChecker(10, 20); + $warningLine = str_repeat('a', 12); + $errorLine = str_repeat('a', 22); + + Assert::count(0, $this->fakeChecker->warning); + Assert::count(0, $this->fakeChecker->error); + + $checker($this->fakeChecker, $warningLine); + Assert::count(1, $this->fakeChecker->warning); + Assert::count(0, $this->fakeChecker->error); + + $checker($this->fakeChecker, $errorLine); + Assert::count(1, $this->fakeChecker->warning); + Assert::count(1, $this->fakeChecker->error); + } + + + + public function testDisableWarning() + { + $checker = LineLengthChecker::createLineLengthChecker(NULL, 20); + $warningLine = str_repeat('a', 12); + $errorLine = str_repeat('a', 22); + + Assert::count(0, $this->fakeChecker->warning); + Assert::count(0, $this->fakeChecker->error); + + $checker($this->fakeChecker, $warningLine); + Assert::count(0, $this->fakeChecker->warning); //zero, disabled warning + Assert::count(0, $this->fakeChecker->error); + + $checker($this->fakeChecker, $errorLine); + Assert::count(0, $this->fakeChecker->warning); + Assert::count(1, $this->fakeChecker->error); + } + +} + + + +\run(new LineLengthCheckerTest()); diff --git a/tests/CodeCheckers/NeonChecker.phpt b/tests/CodeCheckers/NeonChecker.phpt new file mode 100644 index 0000000..5e41984 --- /dev/null +++ b/tests/CodeCheckers/NeonChecker.phpt @@ -0,0 +1,119 @@ +fixMessages[] = $message; + } + + + + public function is($file) + { + return TRUE; + } +} + + + +class NeonCheckerTest extends TestCase +{ + + /** + * @var FakeChecker + */ + private $fakeChecker; + + /** + * @var array + */ + private $input = [ + 'common: + php: + include_path: %libsDir% + date.timezone: Europe/Prague + auto_detect_line_endings: no + # zlib.output_compression: yes +', + 'common: + php: + include_path: %libsDir% + date.timezone: Europe/Prague + auto_detect_line_endings: true + # zlib.output_compression: true +', + ]; + + /** + * @var array + */ + private $output = [ + 'common: + php: + include_path: %libsDir% + date.timezone: Europe/Prague + auto_detect_line_endings: false + # zlib.output_compression: true +', + 'common: + php: + include_path: %libsDir% + date.timezone: Europe/Prague + auto_detect_line_endings: true + # zlib.output_compression: true +', + ]; + + + + public function setup() + { + $this->fakeChecker = new FakeChecker(); + } + + + + /** + * @dataProvider getDataForTestBooleanValues + */ + public function testBooleanValues($input, $output, $countOfFixMessages) + { + $booleanValuesChecker = NeonChecker::createBooleanValuesChecker(); + Assert::same($output, $booleanValuesChecker($this->fakeChecker, $input)); + Assert::count($countOfFixMessages, $this->fakeChecker->fixMessages); + } + + + + public function getDataForTestBooleanValues() + { + return [ + [$this->input[0], $this->output[0], 2], + [$this->input[1], $this->output[1], 0], + ]; + } + +} + + + +\run(new NeonCheckerTest()); diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..87b48f5 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,34 @@ +run(isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : NULL); +}