From 73003d18750adaf6797905fdb9e744c080305051 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81bel=20Katona?= Date: Wed, 30 May 2018 11:50:34 +0200 Subject: [PATCH 01/14] Update Application.php fix --- src/Application.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Application.php b/src/Application.php index 051d550..8ffd069 100755 --- a/src/Application.php +++ b/src/Application.php @@ -153,9 +153,8 @@ private function formatDocBody(string $doc, int $ident) foreach ($lines as $key => &$line) { $line = trim($line, "\r"); $line = str_repeat(' ', $ident).' * '.$line; + $line = rtrim($line); } - - $line = rtrim($line); $doc = implode($newLine, $lines); $doc = str_repeat(' ', $ident).'/**'.$newLine.$doc; From 9e4c77ae58f7e8972d43633ae5785d4bed881746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81bel=20Katona?= Date: Wed, 30 May 2018 11:50:51 +0200 Subject: [PATCH 02/14] Update VERSION --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 8a9ecc2..1750564 100755 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.1 \ No newline at end of file +0.0.6 From 2e213754a2b839bef6ea4cf904a02bc3e0b711e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81bel=20Katona?= Date: Wed, 30 May 2018 12:03:35 +0200 Subject: [PATCH 03/14] Update Application.php --- src/Application.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Application.php b/src/Application.php index 8ffd069..9ce5e44 100755 --- a/src/Application.php +++ b/src/Application.php @@ -136,10 +136,15 @@ private function normalizeDocBody(string $doc) if ($c > 0) { $line = str_repeat($this->config->getIdent(), $ident).$line; } else { - $line = str_repeat($this->config->getIdent(), $ident + $c).$line; + $i = $ident + $c > 0 ? $ident + $c : 0; + $line = str_repeat($this->config->getIdent(), $i).$line; } $ident += $c; + if($ident < 0) { + //TODO warning - possible open-close tag mismatch + $ident = 0; + } } return implode("\n", $lines); From d9e659b6c4110855997235377696a4474ac84ae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81bel=20Katona?= Date: Wed, 30 May 2018 12:31:32 +0200 Subject: [PATCH 04/14] Update Application.php --- src/Application.php | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/src/Application.php b/src/Application.php index 9ce5e44..702acf0 100755 --- a/src/Application.php +++ b/src/Application.php @@ -74,14 +74,32 @@ public function fixFiles() private function getFileDominantLineEnding(string $file) { - $n = preg_match_all('/(? $n) { - return "\r\n"; + static $eols = array( + "\0x000D000A", // [UNICODE] CR+LF: CR (U+000D) followed by LF (U+000A) + "\0x000A", // [UNICODE] LF: Line Feed, U+000A + "\0x000B", // [UNICODE] VT: Vertical Tab, U+000B + "\0x000C", // [UNICODE] FF: Form Feed, U+000C + "\0x000D", // [UNICODE] CR: Carriage Return, U+000D + "\0x0085", // [UNICODE] NEL: Next Line, U+0085 + "\0x2028", // [UNICODE] LS: Line Separator, U+2028 + "\0x2029", // [UNICODE] PS: Paragraph Separator, U+2029 + "\0x0D0A", // [ASCII] CR+LF: Windows, TOPS-10, RT-11, CP/M, MP/M, DOS, Atari TOS, OS/2, Symbian OS, Palm OS + "\0x0A0D", // [ASCII] LF+CR: BBC Acorn, RISC OS spooled text output. + "\0x0A", // [ASCII] LF: Multics, Unix, Unix-like, BeOS, Amiga, RISC OS + "\0x0D", // [ASCII] CR: Commodore 8-bit, BBC Acorn, TRS-80, Apple II, Mac OS <=v9, OS-9 + "\0x1E", // [ASCII] RS: QNX (pre-POSIX) + //"\0x76", // [?????] NEWLINE: ZX80, ZX81 [DEPRECATED] + "\0x15", // [EBCDEIC] NEL: OS/390, OS/400 + ); + $cur_cnt = 0; + $cur_eol = "\n"; + foreach($eols as $eol){ + if(($count = substr_count($file, $eol)) > $cur_cnt){ + $cur_cnt = $count; + $cur_eol = $eol; + } } - - return "\n"; + return $cur_eol; } private function getDocBodyIdent(string $doc) From 6eb5901da25339780e56e742e4972560e8405dd4 Mon Sep 17 00:00:00 2001 From: abel Date: Fri, 1 Jun 2018 15:28:25 +0200 Subject: [PATCH 05/14] tests --- .php_cs.dist | 9 +- php-doc-formatter | 11 ++ phpunit.xml.dist | 40 ++++ src/Application.php | 105 +++++++++-- src/Config.php | 51 +++-- tests/BasicFunctionalityTest.php | 311 +++++++++++++++++++++++++++++++ 6 files changed, 492 insertions(+), 35 deletions(-) create mode 100644 phpunit.xml.dist create mode 100644 tests/BasicFunctionalityTest.php diff --git a/.php_cs.dist b/.php_cs.dist index 087672f..d2911cb 100755 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -2,6 +2,7 @@ $fileHeaderComment = <<in(__DIR__.'/src') - ->in(__DIR__.'/tests') - ->exclude('Resources') - ->exclude('Resources2') + ->in(__DIR__) + ->exclude('tests/Resources') + ->exclude('tests/Resources2') + ->files() ; return PhpCsFixer\Config::create() diff --git a/php-doc-formatter b/php-doc-formatter index 5507e82..df63098 100755 --- a/php-doc-formatter +++ b/php-doc-formatter @@ -103,9 +103,14 @@ $autoloader = false; foreach (array(__DIR__.'/../../autoload.php', __DIR__.'/../vendor/autoload.php', __DIR__.'/vendor/autoload.php') as $file) { if (file_exists($file)) { require_once $file; + $autoloader = true; break; } } +if(!$autoloader) { + error_log('[ERROR] Could not locate the autoloader. Check if you have it.'); + exit(); +} use PhpDocFormatter\Application; use PhpDocFormatter\Config; @@ -132,6 +137,12 @@ if (is_array($exclude) && count($exclude) > 0) { } $config->setFinder($finder); +try { + $config->validate(); +} catch(\Exception $ex) { + error_log(sprintf('[ERROR] %s', $ex->getMessage())); + exit(); +} $app = new Application($config); $app->fixFiles(); diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..350aa8b --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,40 @@ + + + + + + + + + + + ./tests/ + + + + + + ./ + + ./tests + ./vendor + + + + + + + performance + + + diff --git a/src/Application.php b/src/Application.php index 702acf0..4e9930d 100755 --- a/src/Application.php +++ b/src/Application.php @@ -2,6 +2,7 @@ /* * This file is part of the PHPDoc Formatter application. + * https://github.com/SinSquare/phpdoc-formatter * * (c) Ábel Katona * @@ -12,15 +13,27 @@ use Symfony\Component\Stopwatch\Stopwatch; +/** + * @author Abel Katona + */ class Application { private $config; + /** + * @param Config $config + * + * @throws \Exception if the config is not valid + */ public function __construct(Config $config) { + $config->validate(); $this->config = $config; } + /** + * Fix all the files that the finder returns. + */ public function fixFiles() { $stopwatch = new Stopwatch(); @@ -48,18 +61,11 @@ public function fixFiles() $norm = $this->getDocBody($value); $norm = $this->normalizeDocBody($norm); - $norm = $this->formatDocBody($norm, $ident); + $norm = $this->reconstructDocBody($norm, $ident); $docComments[$key]['formatted'] = $norm; } - $newFile = ''; - $offset = 0; - foreach ($docComments as $key => $value) { - $newFile .= substr($content, $offset, $value['offset'] - $offset); - $offset = $value['offset'] + $value['length']; - $newFile .= $value['formatted']; - } - $newFile .= substr($content, $offset); + $newFile = $this->reconstructFile($file, $docComments); if (sha1($content) !== sha1($newFile)) { $d = file_put_contents($file, $newFile); @@ -72,6 +78,31 @@ public function fixFiles() } } + /** + * @param string $file File content + * @param string[] $docComments + */ + private function reconstructFile(string $content, array $docComments) + { + $newFile = ''; + $offset = 0; + foreach ($docComments as $key => $value) { + $newFile .= substr($content, $offset, $value['offset'] - $offset); + $offset = $value['offset'] + $value['length']; + $newFile .= $value['formatted']; + } + $newFile .= substr($content, $offset); + + return $newFile; + } + + /** + * Gets the file's dominant line ending. + * + * @param string $file File content + * + * @return string Dominant line ending + */ private function getFileDominantLineEnding(string $file) { static $eols = array( @@ -88,33 +119,47 @@ private function getFileDominantLineEnding(string $file) "\0x0A", // [ASCII] LF: Multics, Unix, Unix-like, BeOS, Amiga, RISC OS "\0x0D", // [ASCII] CR: Commodore 8-bit, BBC Acorn, TRS-80, Apple II, Mac OS <=v9, OS-9 "\0x1E", // [ASCII] RS: QNX (pre-POSIX) - //"\0x76", // [?????] NEWLINE: ZX80, ZX81 [DEPRECATED] "\0x15", // [EBCDEIC] NEL: OS/390, OS/400 ); $cur_cnt = 0; $cur_eol = "\n"; - foreach($eols as $eol){ - if(($count = substr_count($file, $eol)) > $cur_cnt){ + foreach ($eols as $eol) { + if (($count = substr_count($file, $eol)) > $cur_cnt) { $cur_cnt = $count; $cur_eol = $eol; } } + return $cur_eol; } + /** + * Gets the opening tag's ident. + * + * @param string $doc The whole unformatted PHPDoc block + * + * @return string The whitespace before the opening tag + */ private function getDocBodyIdent(string $doc) { $lines = explode("\n", $doc); foreach ($lines as $line) { - if (preg_match("#/\*\*#", $line, $matches, PREG_OFFSET_CAPTURE)) { - return $matches[0][1]; + if (preg_match("#([^/\*]*)/\*\*#", $line, $matches)) { + return $matches[1]; } } return null; } + /** + * Gets the body of the PHPDoc without the opening tags. + * + * @param string $doc The whole unformatted PHPDoc block + * + * @return string + */ private function getDocBody(string $doc) { $lines = explode("\n", $doc); @@ -136,6 +181,13 @@ private function getDocBody(string $doc) return $lines; } + /** + * Fixing the idention of the body. + * + * @param string $doc The PHPDoc body without the opening tags + * + * @return string + */ private function normalizeDocBody(string $doc) { $lines = explode("\n", $doc); @@ -159,7 +211,7 @@ private function normalizeDocBody(string $doc) } $ident += $c; - if($ident < 0) { + if ($ident < 0) { //TODO warning - possible open-close tag mismatch $ident = 0; } @@ -168,24 +220,39 @@ private function normalizeDocBody(string $doc) return implode("\n", $lines); } - private function formatDocBody(string $doc, int $ident) + /** + * Reconstruct the the body (adding opening and closing tag with correct idention). + * + * @param string $doc The PHPDoc body without the opening tags + * @param string $ident The opening tag's ident + * + * @return string The reconstructed PHPDoc + */ + private function reconstructDocBody(string $doc, string $ident) { $newLine = $this->config->getNewLine(); $lines = explode("\n", $doc); foreach ($lines as $key => &$line) { $line = trim($line, "\r"); - $line = str_repeat(' ', $ident).' * '.$line; + $line = $ident.' * '.$line; $line = rtrim($line); } $doc = implode($newLine, $lines); - $doc = str_repeat(' ', $ident).'/**'.$newLine.$doc; - $doc .= $newLine.str_repeat(' ', $ident).' */'.$newLine; + $doc = $ident.'/**'.$newLine.$doc; + $doc .= $newLine.$ident.' */'.$newLine; return $doc; } + /** + * Finds all the PHPDoc blocks in the file. + * + * @param string $file File content + * + * @return string[] Array of PHPDoc blocks + */ private function findAllDocDomment(string $file) { $regex = "#[\h]*/\*\*[\s]?([\s]*\*[^\n]*[\s]?)+[\h]*\*/[\s]?#"; diff --git a/src/Config.php b/src/Config.php index dd9a596..add1304 100755 --- a/src/Config.php +++ b/src/Config.php @@ -2,6 +2,7 @@ /* * This file is part of the PHPDoc Formatter application. + * https://github.com/SinSquare/phpdoc-formatter * * (c) Ábel Katona * @@ -10,6 +11,9 @@ namespace PhpDocFormatter; +/** + * @author Abel Katona + */ class Config { private $finder; @@ -19,18 +23,44 @@ class Config private function __construct() { + $this->rules = array(); + $this->ident = ' '; } + /** + * @return Config Default config + */ public static function create() { $c = new self(); - $c->setIdent(' '); return $c; } /** - * @return mixed + * Validates the configuration. + * + * @throws \Exception if the config is not valid + */ + public function validate() + { + if (preg_match('/^[^\h]*$/', $this->ident)) { + throw new \Exception('\'ident\' must contain whitespace only!'); + } + + if (null !== $this->newLine) { + if (preg_match('/^[^\n\r]*$/', $this->newLine)) { + throw new \Exception('\'newLine\' must contain new line and carriage return only!'); + } + } + + if (!is_array($this->finder) && !$this->finder instanceof \Traversable) { + throw new \Exception("'finder' must be an array or a Traversable object!"); + } + } + + /** + * @return Finder|Traversable */ public function getFinder() { @@ -38,22 +68,19 @@ public function getFinder() } /** - * @param mixed $finder + * @param Finder|Traversable $finder * * @return self */ public function setFinder($finder) { - if (!is_array($finder) && !$finder instanceof \Traversable) { - throw new \InvalidArgumentException("'finder' must be an array or a Traversable class"); - } $this->finder = $finder; return $this; } /** - * @return mixed + * @return array */ public function getRules() { @@ -61,7 +88,7 @@ public function getRules() } /** - * @param mixed $rules + * @param array $rules * * @return self */ @@ -73,7 +100,7 @@ public function setRules(array $rules) } /** - * @return mixed + * @return string */ public function getIdent() { @@ -81,7 +108,7 @@ public function getIdent() } /** - * @param mixed $ident + * @param string $ident * * @return self */ @@ -93,7 +120,7 @@ public function setIdent(string $ident) } /** - * @return mixed + * @return string|null */ public function getNewLine() { @@ -101,7 +128,7 @@ public function getNewLine() } /** - * @param mixed $newLine + * @param string $newLine * * @return self */ diff --git a/tests/BasicFunctionalityTest.php b/tests/BasicFunctionalityTest.php new file mode 100644 index 0000000..e1790ee --- /dev/null +++ b/tests/BasicFunctionalityTest.php @@ -0,0 +1,311 @@ +setFinder(array()); + $app = new Application($config); + + $docComments = $this->invokeMethod($app, 'findAllDocDomment', array($file)); + + $this->assertCount(3, $docComments); + + $fst = '/** + * Sample text here + */ +'; + $scnd = '/** + * @SWG\Info( + * title="API", + * version="1.0" + * ) + */ +'; + + $thrd = ' /** + * Sends a successfull(data key) JSON API response. + * + * + * @param array $data the data to send + * @param int $code HTTP response code + * @param array $headers array of headers + * + * @return JsonResponse + */ +'; + $expected = array($fst, $scnd, $thrd); + + foreach ($expected as $key => $value) { + $this->assertEquals($value, $docComments[$key]['match']); + $this->assertEquals(strlen($value), $docComments[$key]['length']); + + $pos = strpos($file, $value); + $this->assertEquals($pos, $docComments[$key]['offset']); + } + } + + public function testDocBodyIdent() + { + $values = array( + "/**\n" => '', + " /**\n" => ' ', + "\t/**\n" => "\t", + "###/**\n" => '###', + ); + + $config = Config::create(); + $config->setFinder(array()); + $app = new Application($config); + + foreach ($values as $key => $value) { + $ident = $this->invokeMethod($app, 'getDocBodyIdent', array($key)); + $this->assertEquals($value, $ident); + } + } + + public function testGetDocBody() + { + $in = ' + /** + * Sends a successfull(data key) JSON API response. + * + * +* + * + * + * + * + * @param array $data the data to send + * + * @return JsonResponse + */ +'; + $out = 'Sends a successfull(data key) JSON API response. + + +@param array $data the data to send + +@return JsonResponse'; + + $config = Config::create(); + $config->setFinder(array()); + $app = new Application($config); + + $o = $this->invokeMethod($app, 'getDocBody', array($in)); + $this->assertEquals($out, $o); + } + + public function testNormalizeDocBody() + { + $in = '{@inheritdoc} +@Route("/list", name="list") +@SWG\Swagger( +host=API_HOST +)'; + $out = "{@inheritdoc} +@Route(\"/list\", name=\"list\") +@SWG\Swagger( +\thost=API_HOST +)"; + $config = Config::create(); + $config->setFinder(array()); + $config->setIdent("\t"); + + $app = new Application($config); + + $o = $this->invokeMethod($app, 'normalizeDocBody', array($in)); + $this->assertEquals($out, $o); + } + + public function testReconstructDocBody() + { + $in = "{@inheritdoc} +@Route(\"/list\", name=\"list\") +@SWG\Swagger( +\thost=API_HOST +)"; + $out = "\t\t/** +\t\t * {@inheritdoc} +\t\t * @Route(\"/list\", name=\"list\") +\t\t * @SWG\Swagger( +\t\t * \thost=API_HOST +\t\t * ) +\t\t */ +"; + + $config = Config::create(); + $config->setFinder(array()); + $config->setIdent("\t"); + + $app = new Application($config); + $newLine = $this->invokeMethod($app, 'getFileDominantLineEnding', array($in)); + $config->setNewLine($newLine); + + $o = $this->invokeMethod($app, 'reconstructDocBody', array($in, "\t\t")); + $this->assertEquals($out, $o); + } + + public function testReconstructFile() + { + $file = ' +setFinder(array()); + $app = new Application($config); + + $newLine = $this->invokeMethod($app, 'getFileDominantLineEnding', array($file)); + $config->setNewLine($newLine); + + $docComments = $this->invokeMethod($app, 'findAllDocDomment', array($file)); + + foreach ($docComments as $key => $match) { + $value = $match['match']; + $ident = $this->invokeMethod($app, 'getDocBodyIdent', array($value)); + if (null === $ident) { + throw new \Exception('not good'); + } + + $norm = $this->invokeMethod($app, 'getDocBody', array($value)); + $norm = $this->invokeMethod($app, 'normalizeDocBody', array($norm)); + $norm = $this->invokeMethod($app, 'reconstructDocBody', array($norm, $ident)); + $docComments[$key]['formatted'] = $norm; + } + + $nF = $this->invokeMethod($app, 'reconstructFile', array($file, $docComments)); + + $this->assertEquals($newFile, $nF); + } + + public function invokeMethod(&$object, $methodName, array $parameters = array()) + { + $reflection = new \ReflectionClass(get_class($object)); + $method = $reflection->getMethod($methodName); + $method->setAccessible(true); + + return $method->invokeArgs($object, $parameters); + } +} From d003a5893ed9f55b5eb573fc1d7db08183d415ec Mon Sep 17 00:00:00 2001 From: abel Date: Fri, 1 Jun 2018 15:32:14 +0200 Subject: [PATCH 06/14] travis --- travis.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 travis.yml diff --git a/travis.yml b/travis.yml new file mode 100644 index 0000000..815505b --- /dev/null +++ b/travis.yml @@ -0,0 +1,14 @@ +language: php + +php: + - '5.6' + - '7.0' + - '7.1' + - '7.2' + +install: +- "composer require codeclimate/php-test-reporter --dev" +- "composer update --no-interaction --prefer-stable" + +script: +- vendor/bin/phpunit --coverage-clover build/logs/clover.xml \ No newline at end of file From a2274285d32af7976e6c083ac303b0a3652572cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81bel=20Katona?= Date: Fri, 1 Jun 2018 15:37:26 +0200 Subject: [PATCH 07/14] Update travis.yml --- travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/travis.yml b/travis.yml index 815505b..f4c8c75 100644 --- a/travis.yml +++ b/travis.yml @@ -11,4 +11,4 @@ install: - "composer update --no-interaction --prefer-stable" script: -- vendor/bin/phpunit --coverage-clover build/logs/clover.xml \ No newline at end of file +- vendor/bin/phpunit --coverage-clover build/logs/clover.xml From 138b02ede90c25dfaf281882243b6bb184368857 Mon Sep 17 00:00:00 2001 From: abel Date: Fri, 1 Jun 2018 15:38:50 +0200 Subject: [PATCH 08/14] travis --- travis.yml => .travis.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename travis.yml => .travis.yml (100%) diff --git a/travis.yml b/.travis.yml similarity index 100% rename from travis.yml rename to .travis.yml From 73ad0dacbcd86978b077b84b80aa56299c3c3efb Mon Sep 17 00:00:00 2001 From: abel Date: Fri, 1 Jun 2018 15:55:16 +0200 Subject: [PATCH 09/14] remove php 5.6 from travis --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f4c8c75..a281ab0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,6 @@ language: php php: - - '5.6' - '7.0' - '7.1' - '7.2' From 747ac72a5a1714b2276374d8e484537416c1cdb1 Mon Sep 17 00:00:00 2001 From: abel Date: Fri, 1 Jun 2018 16:11:28 +0200 Subject: [PATCH 10/14] readme --- README.md | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..3fdde20 --- /dev/null +++ b/README.md @@ -0,0 +1,72 @@ +[![Build Status](https://travis-ci.org/SinSquare/phpdoc-formatter.svg?branch=master)](https://travis-ci.org/SinSquare/phpdoc-formatter) + +A simple tool for formatting PHPDoc + +The tool currently fixes: +* Idention through multiline comments +* Removing more than 2 empty lines +* Fixing PHPDoc format + +Usage: +``` +phpdoc-formatter [--option value] [/path/to/project ...] +``` +Options: +``` + --exclude (-e) Exclude path(s) + ex: --exclude vendor,library + --version (-v) Displays the version + + --ident (-i) Sets the ident character(s) + ex: --ident " " + --newline (-n) Sets the newline character(s) + ex: --ident "\r\n" + --help (-h) Displays this message +``` + +Example: + +Before: +``` +/** + * @SWG\Info( +* title="API", + * version="1.0" + * ) + * + * @SWG\Swagger( + * host=API_HOST, + * basePath=API_BASE_PATH + * ) + * + * @SWG\SecurityScheme( + * securityDefinition="JWTTokenAuth", + * type="apiKey", + * in="header", + * name="Authorization", + * description=API_DEFAULT_TOKEN, + * ) + */ +``` +After: +``` +/** + * @SWG\Info( + * title="API", + * version="1.0" + * ) + * + * @SWG\Swagger( + * host=API_HOST, + * basePath=API_BASE_PATH + * ) + * + * @SWG\SecurityScheme( + * securityDefinition="JWTTokenAuth", + * type="apiKey", + * in="header", + * name="Authorization", + * description=API_DEFAULT_TOKEN, + * ) + */ +``` \ No newline at end of file From 212b33d8226ba396e827e6e1528fea129888e90e Mon Sep 17 00:00:00 2001 From: abel Date: Fri, 1 Jun 2018 16:11:52 +0200 Subject: [PATCH 11/14] version --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 1750564..6e8bf73 100755 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.6 +0.1.0 From 19b66e1e4773c82f6faff115e59db0f10088a02f Mon Sep 17 00:00:00 2001 From: "abel.katona" Date: Tue, 5 Jun 2018 16:20:17 +0200 Subject: [PATCH 12/14] modification --- php-doc-formatter | 143 ++++------------- src/Application.php | 2 +- src/CommandConfig.php | 172 +++++++++++++++++++++ src/Config.php | 4 +- tests/CliOptionsTest.php | 150 ++++++++++++++++++ tests/CommandTest.php | 79 ++++++++++ tests/Resources/.phpdoc | 13 ++ tests/Resources/.phpdoc.dist | 9 ++ tests/Resources/AbstractApiController.php | 30 ++-- tests/Resources/CurrencyController.php | 56 +++---- tests/Resources2/.phpdoc.dist | 8 + tests/Resources2/AbstractApiController.php | 28 ++-- 12 files changed, 520 insertions(+), 174 deletions(-) create mode 100644 src/CommandConfig.php create mode 100644 tests/CliOptionsTest.php create mode 100644 tests/CommandTest.php create mode 100644 tests/Resources/.phpdoc create mode 100644 tests/Resources/.phpdoc.dist create mode 100644 tests/Resources2/.phpdoc.dist diff --git a/php-doc-formatter b/php-doc-formatter index df63098..d0af2cb 100755 --- a/php-doc-formatter +++ b/php-doc-formatter @@ -2,69 +2,34 @@ [], - 'help' => false, - 'version' => false, - 'ident' => ' ', - 'newline' => null, -]; -$aliases = [ - 'e' => 'exclude', - 'v' => 'version', - 'h' => 'help', - 'i' => 'ident', - 'n' => 'newline', -]; -$needsArgument = [ - 'exclude', - 'ident', - 'newline', -]; +$autoloader = false; +foreach (array(__DIR__.'/../../autoload.php', __DIR__.'/../vendor/autoload.php', __DIR__.'/vendor/autoload.php') as $file) { + if (file_exists($file)) { + require_once $file; + $autoloader = true; + break; + } +} +if(!$autoloader) { + error_log('[ERROR] Could not locate the autoloader. Check if you have it.'); + exit(); +} -$paths = array(); -$error = false; +use PhpDocFormatter\Application; +use PhpDocFormatter\Config; +use Symfony\Component\Finder\Finder; +use PhpDocFormatter\CommandConfig; -try { - // Parse cli arguments - for ($i = 1; $i < $argc; ++$i) { - $arg = $argv[$i]; - if ('--' === substr($arg, 0, 2)) { // longopt - $option = substr($arg, 2); - } elseif ('-' === $arg[0]) { // shortopt - if (array_key_exists(substr($arg, 1), $aliases)) { - $option = $aliases[$arg[1]]; - } else { - throw new Exception('Unknown option: "'.$arg.'"'); - } - } else { - $paths[] = $arg; - continue; - } +$error = null; - if (false === array_key_exists($option, $options)) { - throw new Exception('Unknown option: "'.$arg.'"'); - } - if (in_array($option, $needsArgument)) { - if (empty($argv[$i + 1]) || $argv[$i + 1][0] === '-') { - throw new Exception('Missing argument for "'.$arg.'"'); - } - if (is_array($options[$option])) { - $options[$option][] = $argv[$i + 1]; - } else { - $options[$option] = $argv[$i + 1]; - } - ++$i; - } else { - $options[$option] = true; - } - } +try { + $options = CommandConfig::getCliOptions($argv); } catch (Exception $e) { $error = $e->getMessage(); } $version = trim(file_get_contents(__DIR__.'/VERSION')); -if ($options['version']) { +if ($options[CommandConfig::VERSION]) { echo $version, PHP_EOL; exit; } @@ -75,74 +40,22 @@ error_log('------------'.str_repeat('-', strlen($version))); if ($error) { error_log('[ERROR] '.$error); - $options['help'] = true; // Show help + $options[CommandConfig::HELP] = true; // Show help } -if ($options['help']) { +if ($options[CommandConfig::HELP]) { $help = trim(file_get_contents(__DIR__.'/HELP')); error_log($help); exit; } -$exclude = null; -if ($options['exclude']) { - $exclude = $options['exclude']; - if (false !== strpos($exclude[0], ',')) { - $exploded = explode(',', $exclude[0]); - error_log('[NOTICE] Comma-separated exclude paths are deprecated, use multiple --exclude statements: --exclude '.$exploded[0].' --exclude '.$exploded[1]); - $exclude[0] = array_shift($exploded); - $exclude = array_merge($exclude, $exploded); - } -} - -if (0 === count($paths)) { - $paths[] = getcwd(); - echo "Scanning files in '".$paths[0]."' ...\n"; -} - -$autoloader = false; -foreach (array(__DIR__.'/../../autoload.php', __DIR__.'/../vendor/autoload.php', __DIR__.'/vendor/autoload.php') as $file) { - if (file_exists($file)) { - require_once $file; - $autoloader = true; - break; - } -} -if(!$autoloader) { - error_log('[ERROR] Could not locate the autoloader. Check if you have it.'); - exit(); -} - -use PhpDocFormatter\Application; -use PhpDocFormatter\Config; -use Symfony\Component\Finder\Finder; - -$config = Config::create(); - -$config->setIdent($options['ident']); -if (null !== $options['newline']) { - $nl = $options['newline']; - $nl = str_replace('\\n', "\n", $nl); - $nl = str_replace('\\r', "\r", $nl); - $config->setNewLine($nl); -} - -$finder = new Finder(); -foreach ($paths as $p) { - $finder->in($p); -} -if (is_array($exclude) && count($exclude) > 0) { - foreach ($exclude as $p) { - $finder->exclude($p); - } -} +$config = CommandConfig::getOptionsFile(getcwd()); +CommandConfig::mergeOptions($config, $options); -$config->setFinder($finder); try { - $config->validate(); -} catch(\Exception $ex) { - error_log(sprintf('[ERROR] %s', $ex->getMessage())); + $app = new Application($config); +} catch (Exception $e) { + error_log('[ERROR] '.$error); exit(); } - -$app = new Application($config); $app->fixFiles(); + diff --git a/src/Application.php b/src/Application.php index 4e9930d..2011b39 100755 --- a/src/Application.php +++ b/src/Application.php @@ -65,7 +65,7 @@ public function fixFiles() $docComments[$key]['formatted'] = $norm; } - $newFile = $this->reconstructFile($file, $docComments); + $newFile = $this->reconstructFile($content, $docComments); if (sha1($content) !== sha1($newFile)) { $d = file_put_contents($file, $newFile); diff --git a/src/CommandConfig.php b/src/CommandConfig.php new file mode 100644 index 0000000..e41d9e0 --- /dev/null +++ b/src/CommandConfig.php @@ -0,0 +1,172 @@ + [], + self::EXCLUDE => [], + self::HELP => false, + self::VERSION => false, + self::IDENT => null, + self::NEWLINE => null, + ]; + const ALIASES = [ + 'e' => self::EXCLUDE, + 'v' => self::HELP, + 'h' => self::VERSION, + 'i' => self::IDENT, + 'n' => self::NEWLINE, + ]; + const NEEDSARGUMENT = [ + self::EXCLUDE, + self::IDENT, + self::NEWLINE, + ]; + + private $argc; + + public static function getCliOptions(array $argv) + { + $options = self::OPTIONS; + $options[self::PATHS] = array(); + + for ($i = 1; $i < count($argv); ++$i) { + $arg = $argv[$i]; + if ('--' === substr($arg, 0, 2)) { // longopt + $option = substr($arg, 2); + } elseif ('-' === $arg[0]) { // shortopt + if (array_key_exists(substr($arg, 1), self::ALIASES)) { + $option = self::ALIASES[$arg[1]]; + } else { + throw new Exception('Unknown option alias: "'.$arg.'"'); + } + } else { + $options[self::PATHS][] = $arg; + continue; + } + + if (false === array_key_exists($option, $options)) { + throw new \Exception('Unknown option: "'.$arg.'"'); + } + if (in_array($option, self::NEEDSARGUMENT)) { + if (empty($argv[$i + 1]) || '-' === $argv[$i + 1][0]) { + throw new \Exception('Missing argument for "'.$arg.'"'); + } + if (is_array($options[$option])) { + $options[$option][] = $argv[$i + 1]; + } else { + $options[$option] = $argv[$i + 1]; + } + ++$i; + } else { + $options[$option] = true; + } + } + + if (null !== $options[self::IDENT]) { + $i = $options[self::IDENT]; + $i = trim($i, "\"'"); + $i = str_replace('\\t', "\t", $i); + $options[self::IDENT] = $i; + } + + if (null !== $options[self::NEWLINE]) { + $nl = $options[self::NEWLINE]; + $nl = trim($nl, "\"'"); + $nl = str_replace('\\n', "\n", $nl); + $nl = str_replace('\\r', "\r", $nl); + $options[self::NEWLINE] = $nl; + } + + $exclude = array(); + if ($options[self::EXCLUDE]) { + $exclude = $options[self::EXCLUDE]; + $exclude = array_map(function ($item) {return trim($item, "\"'"); }, $exclude); + } + $options[self::EXCLUDE] = $exclude; + + if (0 === count($options[self::PATHS])) { + $options[self::PATHS][] = getcwd(); + } + + return $options; + } + + public static function getOptionsFile(string $path) + { + $path = rtrim($path, '/\\'); + $candidates = [ + $path.DIRECTORY_SEPARATOR.'.phpdoc', + $path.DIRECTORY_SEPARATOR.'.phpdoc.dist', + ]; + + foreach ($candidates as $path) { + if (file_exists($path) && is_readable($path)) { + $config = self::separatedContextLessInclude($path); + if ($config instanceof Config) { + return $config; + } + } + } + + return Config::create(); + } + + public static function mergeOptions(Config $config, array $options) + { + if (null === $config->getFinder()) { + $finder = new Finder(); + foreach ($options[self::PATHS] as $p) { + $finder->in($p); + } + if (is_array($options[self::EXCLUDE]) && count($options[self::EXCLUDE]) > 0) { + foreach ($options[self::EXCLUDE] as $p) { + $finder->exclude($p); + } + } + + $config->setFinder($finder); + } + + if (null !== $options[self::IDENT]) { + $i = $options[self::IDENT]; + $i = str_replace('\\t', "\t", $i); + $config->setIdent($i); + } + + if (null !== $options[self::NEWLINE]) { + $nl = $options[self::NEWLINE]; + $nl = str_replace('\\n', "\n", $nl); + $nl = str_replace('\\r', "\r", $nl); + $config->setNewLine($nl); + } + } + + private static function separatedContextLessInclude($path) + { + return include $path; + } +} diff --git a/src/Config.php b/src/Config.php index add1304..03d5eb7 100755 --- a/src/Config.php +++ b/src/Config.php @@ -32,9 +32,7 @@ private function __construct() */ public static function create() { - $c = new self(); - - return $c; + return new static(); } /** diff --git a/tests/CliOptionsTest.php b/tests/CliOptionsTest.php new file mode 100644 index 0000000..d75ec96 --- /dev/null +++ b/tests/CliOptionsTest.php @@ -0,0 +1,150 @@ +assertNotTrue($options[CommandConfig::HELP]); + $this->assertNotTrue($options[CommandConfig::VERSION]); + $this->assertNull($options[CommandConfig::IDENT]); + $this->assertNull($options[CommandConfig::NEWLINE]); + $this->assertCount(0, $options[CommandConfig::EXCLUDE]); + $this->assertCount(1, $options[CommandConfig::PATHS]); + } + + public function testLongCliOptions() + { + $argv = array( + 'vendor/bon/php-doc-formatter', + '--help', + '--version', + '--ident', + '"\\t\\t"', + '--newline', + '"\\n\\n"', + '--exclude', + '"somepath/1', + '--exclude', + 'somepath/2"', + 'path1', + 'path2', + ); + + $options = CommandConfig::getCliOptions($argv); + + $this->assertTrue($options[CommandConfig::HELP]); + $this->assertTrue($options[CommandConfig::VERSION]); + $this->assertEquals("\t\t", $options[CommandConfig::IDENT]); + $this->assertEquals("\n\n", $options[CommandConfig::NEWLINE]); + $this->assertEquals(array('somepath/1', 'somepath/2'), $options[CommandConfig::EXCLUDE]); + $this->assertEquals(array('path1', 'path2'), $options[CommandConfig::PATHS]); + } + + public function testShortCliOptions() + { + $argv = array( + 'vendor/bon/php-doc-formatter', + '-h', + '-v', + '-i', + '"\\t\\t"', + '-n', + '"\\n\\n"', + '-e', + '"somepath/1', + '-e', + 'somepath/2"', + 'path1', + 'path2', + ); + + $options = CommandConfig::getCliOptions($argv); + + $this->assertTrue($options[CommandConfig::HELP]); + $this->assertTrue($options[CommandConfig::VERSION]); + $this->assertEquals("\t\t", $options[CommandConfig::IDENT]); + $this->assertEquals("\n\n", $options[CommandConfig::NEWLINE]); + $this->assertEquals(array('somepath/1', 'somepath/2'), $options[CommandConfig::EXCLUDE]); + $this->assertEquals(array('path1', 'path2'), $options[CommandConfig::PATHS]); + } + + public function testOptionsFile1() + { + $config = CommandConfig::getOptionsFile(__DIR__.'/Resources'); + $this->assertInstanceOf(Config::class, $config); + + $this->assertEquals("\t", $config->getIdent()); + $this->assertInstanceOf(Finder::class, $config->getFinder()); + } + + public function testOptionsFile2() + { + $config = CommandConfig::getOptionsFile(__DIR__.'/Resources2'); + $this->assertInstanceOf(Config::class, $config); + + $this->assertEquals(' ', $config->getIdent()); + $this->assertInternalType('array', $config->getFinder()); + } + + public function testMerge() + { + $config = Config::create(); + $config->setIdent("\t\t\t"); + $config->setNewLine("\n\n\n\n"); + $config->setFinder(array('p1')); + + $options = array( + CommandConfig::IDENT => ' ', + CommandConfig::NEWLINE => "\r", + CommandConfig::PATHS => array('path1'), + ); + + CommandConfig::mergeOptions($config, $options); + + $this->assertEquals(' ', $config->getIdent()); + $this->assertEquals("\r", $config->getNewLine()); + $this->assertEquals(array('p1'), $config->getFinder()); + } + + public function testMergeFinder() + { + $config = Config::create(); + $config->setIdent("\t\t\t"); + $config->setNewLine("\n\n\n\n"); + + $options = array( + CommandConfig::IDENT => ' ', + CommandConfig::NEWLINE => "\r", + CommandConfig::EXCLUDE => array(), + CommandConfig::PATHS => array(__DIR__.'/Resources2'), + ); + + CommandConfig::mergeOptions($config, $options); + + $this->assertEquals(' ', $config->getIdent()); + $this->assertEquals("\r", $config->getNewLine()); + $this->assertInstanceOf(Finder::class, $config->getFinder()); + } +} diff --git a/tests/CommandTest.php b/tests/CommandTest.php new file mode 100644 index 0000000..d102da6 --- /dev/null +++ b/tests/CommandTest.php @@ -0,0 +1,79 @@ +rrmdir($path); + } + if (!mkdir($path, 0777, true)) { + throw new \Exception('Could not make temp folder'); + } + + $this->copydir(__DIR__.'/Resources', $path.'/Resources'); + $this->copydir(__DIR__.'/Resources2', $path.'/Resources2'); + + $this->path = $path; + } + + private function copydir($src, $dst) + { + $dir = opendir($src); + @mkdir($dst); + while (false !== ($file = readdir($dir))) { + if (('.' != $file) && ('..' != $file)) { + if (is_dir($src.'/'.$file)) { + $this->copydir($src.'/'.$file, $dst.'/'.$file); + } else { + copy($src.'/'.$file, $dst.'/'.$file); + } + } + } + closedir($dir); + } + + private function rrmdir($dir) + { + if (is_dir($dir)) { + $objects = scandir($dir); + foreach ($objects as $object) { + if ('.' != $object && '..' != $object) { + if ('dir' == filetype($dir.'/'.$object)) { + $this->rrmdir($dir.'/'.$object); + } else { + unlink($dir.'/'.$object); + } + } + } + reset($objects); + rmdir($dir); + } + } + + public function testMergeFinder() + { + exec(__DIR__.'/../php-doc-formatter '.escapeshellarg($this->path), $output, $retval); + $this->assertSame(0, $retval); + + $this->assertEquals('f290d79948f4337caf29371bbf88af0b93faeaa0', sha1_file($this->path.'/Resources/AbstractApiController.php')); + $this->assertEquals('a453e1ba0639b8d723b7974ab12b326e00022f0f', sha1_file($this->path.'/Resources/CurrencyController.php')); + $this->assertEquals('056405be2fc59df6ce27dc3ba5293cec1fcc95e3', sha1_file($this->path.'/Resources2/AbstractApiController.php')); + } +} diff --git a/tests/Resources/.phpdoc b/tests/Resources/.phpdoc new file mode 100644 index 0000000..b96e0ed --- /dev/null +++ b/tests/Resources/.phpdoc @@ -0,0 +1,13 @@ +in(__DIR__) + ->exclude('tests/Resources') + ->exclude('tests/Resources2') + ->files() +; + +return PhpDocFormatter\Config::create() + ->setIdent("\t") + ->setFinder($finder) +; diff --git a/tests/Resources/.phpdoc.dist b/tests/Resources/.phpdoc.dist new file mode 100644 index 0000000..eccdf28 --- /dev/null +++ b/tests/Resources/.phpdoc.dist @@ -0,0 +1,9 @@ +thisShouldNotRun + ->setIndent(" ") + ->setFinder($finder) +; diff --git a/tests/Resources/AbstractApiController.php b/tests/Resources/AbstractApiController.php index c47580d..86b285a 100755 --- a/tests/Resources/AbstractApiController.php +++ b/tests/Resources/AbstractApiController.php @@ -4,25 +4,25 @@ * Sample text here */ -namespace Controller; +namespace Resources; /** * @SWG\Info( - * title="API", - * version="1.0" + * title="API", + * version="1.0" * ) * * @SWG\Swagger( - * host=API_HOST, - * basePath=API_BASE_PATH + * host=API_HOST, + * basePath=API_BASE_PATH * ) * * @SWG\SecurityScheme( - * securityDefinition="JWTTokenAuth", - * type="apiKey", - * in="header", - * name="Authorization", - * description=API_DEFAULT_TOKEN, + * securityDefinition="JWTTokenAuth", + * type="apiKey", + * in="header", + * name="Authorization", + * description=API_DEFAULT_TOKEN, * ) */ abstract class AbstractApiController @@ -31,9 +31,13 @@ abstract class AbstractApiController * Sends a successfull(data key) JSON API response. * * - * @param array $data the data to send - * @param int $code HTTP response code - * @param array $headers array of headers + * + * + * + * + * @param array $data the data to send + * @param int $code HTTP response code + * @param array $headers array of headers * * @return JsonResponse */ diff --git a/tests/Resources/CurrencyController.php b/tests/Resources/CurrencyController.php index 7b4ee79..7cdeb42 100755 --- a/tests/Resources/CurrencyController.php +++ b/tests/Resources/CurrencyController.php @@ -4,7 +4,7 @@ * Sample text here */ -namespace Controller; +namespace Resources; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; @@ -21,33 +21,33 @@ class CurrencyController extends AbstractApiController * @Method({"GET"}) * * @SWG\Get( - * path="/currency/list", - * summary="Lists Currencys", - * description="Currency listing", - * security={{"JWTTokenAuth":{}}}, - * tags={"Currency"}, - * @SWG\Response( - * response=200, - * description="successful operation", - * @SWG\Schema( - * type="object", - * @SWG\Property( - * property="data", - * type="array", - * items=@SWG\Items(ref="#/definitions/currencyArrayDefinition") - * ), - * @SWG\Property( - * property="meta", - * type="object", - * ref="#/definitions/metaArrayDefinition" - * ), - * @SWG\Property( - * property="links", - * type="object", - * ref="#/definitions/linksArrayDefinition" - * ), - * ) - * ) + * path="/currency/list", + * summary="Lists Currencys", + * description="Currency listing", + * security={{"JWTTokenAuth":{}}}, + * tags={"Currency"}, + * @SWG\Response( + * response=200, + * description="successful operation", + * @SWG\Schema( + * type="object", + * @SWG\Property( + * property="data", + * type="array", + * items=@SWG\Items(ref="#/definitions/currencyArrayDefinition") + * ), + * @SWG\Property( + * property="meta", + * type="object", + * ref="#/definitions/metaArrayDefinition" + * ), + * @SWG\Property( + * property="links", + * type="object", + * ref="#/definitions/linksArrayDefinition" + * ) + * ) + * ) * ) */ public function listAction(Request $request): JsonResponse diff --git a/tests/Resources2/.phpdoc.dist b/tests/Resources2/.phpdoc.dist new file mode 100644 index 0000000..24853c0 --- /dev/null +++ b/tests/Resources2/.phpdoc.dist @@ -0,0 +1,8 @@ +setIdent(" ") + ->setFinder($finder) +; diff --git a/tests/Resources2/AbstractApiController.php b/tests/Resources2/AbstractApiController.php index c47580d..1eb9127 100755 --- a/tests/Resources2/AbstractApiController.php +++ b/tests/Resources2/AbstractApiController.php @@ -1,28 +1,28 @@ Date: Sun, 21 Mar 2021 16:30:03 +0100 Subject: [PATCH 13/14] Update composer.json --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 411afb7..4388013 100755 --- a/composer.json +++ b/composer.json @@ -4,7 +4,7 @@ "license": "MIT", "type": "application", "require": { - "php": "^5.6 || >=7.0 <7.3", + "php": "^5.6 || >=7.0", "symfony/finder": "^3.0 || ^4.0", "symfony/polyfill-php70": "^1.0", "symfony/polyfill-php72": "^1.4", From 7f5805f4734e653c971d01681a4442dcc7782f2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81bel=20Katona?= Date: Sun, 21 Mar 2021 16:37:10 +0100 Subject: [PATCH 14/14] Update composer.json --- composer.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/composer.json b/composer.json index 4388013..903c4d8 100755 --- a/composer.json +++ b/composer.json @@ -5,14 +5,14 @@ "type": "application", "require": { "php": "^5.6 || >=7.0", - "symfony/finder": "^3.0 || ^4.0", - "symfony/polyfill-php70": "^1.0", - "symfony/polyfill-php72": "^1.4", - "symfony/stopwatch": "^3.0 || ^4.0" + "symfony/finder": ">=4.0", + "symfony/polyfill-php70": ">=1.0", + "symfony/polyfill-php72": ">=1.4", + "symfony/stopwatch": ">=4.0" }, "require-dev": { - "phpunit/phpunit": "^6.0", - "friendsofphp/php-cs-fixer": "^2.0" + "phpunit/phpunit": ">=6.0", + "friendsofphp/php-cs-fixer": ">=2.0" }, "autoload": { "psr-4": { "PhpDocFormatter\\": "src/" }