From b58a0116c3a171d46067b9b60a9240b40b86da0d Mon Sep 17 00:00:00 2001 From: Jakub Wegner Date: Mon, 22 Aug 2022 15:05:06 +0200 Subject: [PATCH 01/14] Add twigcs support and basic ruleset --- composer.json | 3 +- src/Twigcs/Dto/HtmlTagDto.php | 60 +++++++++++++++++ src/Twigcs/Dto/OffsetDto.php | 45 +++++++++++++ src/Twigcs/Rule/MacroRule.php | 99 +++++++++++++++++++++++++++++ src/Twigcs/Rule/VoidHtmlTagRule.php | 70 ++++++++++++++++++++ src/Twigcs/Rule/WhitespaceRule.php | 66 +++++++++++++++++++ src/Twigcs/Ruleset/Ruleset.php | 63 ++++++++++++++++++ src/Twigcs/Util/HtmlUtil.php | 88 +++++++++++++++++++++++++ 8 files changed, 493 insertions(+), 1 deletion(-) create mode 100644 src/Twigcs/Dto/HtmlTagDto.php create mode 100644 src/Twigcs/Dto/OffsetDto.php create mode 100644 src/Twigcs/Rule/MacroRule.php create mode 100644 src/Twigcs/Rule/VoidHtmlTagRule.php create mode 100644 src/Twigcs/Rule/WhitespaceRule.php create mode 100644 src/Twigcs/Ruleset/Ruleset.php create mode 100644 src/Twigcs/Util/HtmlUtil.php diff --git a/composer.json b/composer.json index 593d256..c7a4aba 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,8 @@ "phpstan/phpstan-webmozart-assert": "^1.0", "symplify/easy-coding-standard": "^8.3 || ^9.4 || ^10.0 || ^11.0", "friendsofphp/php-cs-fixer": "^3.0", - "slevomat/coding-standard": "^7.0" + "slevomat/coding-standard": "^7.0", + "friendsoftwig/twigcs": "^6.0" }, "autoload": { "psr-4": { diff --git a/src/Twigcs/Dto/HtmlTagDto.php b/src/Twigcs/Dto/HtmlTagDto.php new file mode 100644 index 0000000..ac74227 --- /dev/null +++ b/src/Twigcs/Dto/HtmlTagDto.php @@ -0,0 +1,60 @@ +tag; + } + + public function setTag(string $tag): self + { + $this->tag = $tag; + + return $this; + } + + public function getHtmlLine(): string + { + return $this->htmlLine; + } + + public function setHtmlLine(string $htmlLine): self + { + $this->htmlLine = $htmlLine; + + return $this; + } + + public function getOffset(): int + { + return $this->offset; + } + + public function setOffset(int $offset): self + { + $this->offset = $offset; + + return $this; + } +} diff --git a/src/Twigcs/Dto/OffsetDto.php b/src/Twigcs/Dto/OffsetDto.php new file mode 100644 index 0000000..18be115 --- /dev/null +++ b/src/Twigcs/Dto/OffsetDto.php @@ -0,0 +1,45 @@ +line; + } + + public function setLine(int $line): self + { + $this->line = $line; + + return $this; + } + + public function getColumn(): int + { + return $this->column; + } + + public function setColumn(int $column): self + { + $this->column = $column; + + return $this; + } +} diff --git a/src/Twigcs/Rule/MacroRule.php b/src/Twigcs/Rule/MacroRule.php new file mode 100644 index 0000000..3bc9c02 --- /dev/null +++ b/src/Twigcs/Rule/MacroRule.php @@ -0,0 +1,99 @@ +isMacro = false; + $path = $tokens->getSourceContext()->getPath(); + + while (!$tokens->isEOF()) { + $token = $tokens->getCurrent(); + + if ($violation = $this->checkMultipleMacros($token, $path)) { + $violations[] = $violation; + } + if ($violation = $this->checkSelfMacro($tokens, $token, $path)) { + $violations[] = $violation; + } + + $tokens->next(); + } + + return $violations; + } + + private function checkMultipleMacros(Token $token, string $filename): ?Violation + { + if ( + Token::NAME_TYPE === $token->getType() + && self::TWIG_TAG_MACRO === $token->getValue() + ) { + if (!$this->isMacro) { + $this->isMacro = true; + } else { + return $this->createViolation( + $filename, + $token->getLine(), + $token->getColumn(), + Ruleset::MULTIPLE_MACROS_IN_THE_SAME_FILE + ); + } + } + + return null; + } + + private function checkSelfMacro(TokenStream $tokens, Token $token, string $filename): ?Violation + { + if ($this->isSelfTagUsedForMacro($tokens, $token)) { + return $this->createViolation( + $filename, + $token->getLine(), + $token->getColumn(), + Ruleset::MACRO_IN_THE_TEMPLATE + ); + } + + return null; + } + + private function isSelfTagUsedForMacro(TokenStream $tokens, Token $token): bool + { + return Token::NAME_TYPE === $token->getType() + && self::TWIG_TAG_SELF === $token->getValue() + + && Token::PUNCTUATION_TYPE === $tokens->look(1)->getType() + && '.' === $tokens->look(1)->getValue() + + && Token::NAME_TYPE === $tokens->look(2)->getType() + + && Token::PUNCTUATION_TYPE === $tokens->look(3)->getType() + && '(' === $tokens->look(3)->getValue(); + } +} diff --git a/src/Twigcs/Rule/VoidHtmlTagRule.php b/src/Twigcs/Rule/VoidHtmlTagRule.php new file mode 100644 index 0000000..c2452ff --- /dev/null +++ b/src/Twigcs/Rule/VoidHtmlTagRule.php @@ -0,0 +1,70 @@ +htmlUtil = $htmlUtil; + } + + public function check(TokenStream $tokens) + { + $violations = []; + + while (!$tokens->isEOF()) { + $token = $tokens->getCurrent(); + + if ($content = $this->htmlUtil->getHtmlContentOnly($token)) { + foreach ($this->htmlUtil->getParsedHtmlTags($content) as $tag) { + if ($this->isViolation($tag)) { + $offset = $this->htmlUtil->getTwigcsOffset($content, $tag->getOffset()); + + $violations[] = $this->createViolation( + $tokens->getSourceContext()->getPath(), + $token->getLine() + $offset->getLine(), + $offset->getColumn(), + sprintf(Ruleset::UNCLOSED_VOID_HTML_TAG, $tag->getTag()) + ); + } + } + } + + $tokens->next(); + } + + return $violations; + } + + private function isViolation(HtmlTagDto $tag): bool + { + return in_array($tag->getTag(), self::VOID_HTML_TAGS) + && !preg_match('#/\s*>$#', $tag->getHtmlLine()); + } +} diff --git a/src/Twigcs/Rule/WhitespaceRule.php b/src/Twigcs/Rule/WhitespaceRule.php new file mode 100644 index 0000000..d43f870 --- /dev/null +++ b/src/Twigcs/Rule/WhitespaceRule.php @@ -0,0 +1,66 @@ +htmlUtil = $htmlUtil; + } + + public function check(TokenStream $tokens) + { + $violations = []; + + while (!$tokens->isEOF()) { + $token = $tokens->getCurrent(); + + if ($content = $this->htmlUtil->getHtmlContentOnly($token)) { + foreach ($this->htmlUtil->getParsedHtmlTags($content) as $tag) { + foreach ($this->getViolationSpaces($tag->getHtmlLine()) as $space) { + $offset = $this->htmlUtil->getTwigcsOffset($content, $tag->getOffset() + $space[0][1]); + + $violations[] = $this->createViolation( + $tokens->getSourceContext()->getPath(), + $token->getLine() + $offset->getLine(), + $offset->getColumn(), + sprintf(Ruleset::MULTIPLE_WHITESPACES, $tag->getTag()) + ); + } + } + } + + $tokens->next(); + } + + return $violations; + } + + private function getViolationSpaces(string $html): array + { + return preg_match_all('#\s{2,}#', $html, $spaces, PREG_OFFSET_CAPTURE | PREG_SET_ORDER) + ? $spaces + : []; + } +} diff --git a/src/Twigcs/Ruleset/Ruleset.php b/src/Twigcs/Ruleset/Ruleset.php new file mode 100644 index 0000000..bd64d8d --- /dev/null +++ b/src/Twigcs/Ruleset/Ruleset.php @@ -0,0 +1,63 @@ +twigMajorVersion = $twigMajorVersion; + } + + public function getRules() + { + $rulesetConfigurator = (new RulesetConfigurator()) + ->setTwigMajorVersion($this->twigMajorVersion); + $twigcsRulesetBuilder = new RulesetBuilder($rulesetConfigurator); + + $htmlUtil = new HtmlUtil(); + + return [ + new BitBagRule\VoidHtmlTagRule(Violation::SEVERITY_ERROR, $htmlUtil), + new BitBagRule\MacroRule(Violation::SEVERITY_ERROR), + new BitBagRule\WhitespaceRule(Violation::SEVERITY_ERROR, $htmlUtil), + + new TwigcsRule\RegEngineRule(Violation::SEVERITY_ERROR, $twigcsRulesetBuilder->build()), + new TwigcsRule\LowerCaseVariable(Violation::SEVERITY_ERROR), + new TwigcsRule\ForbiddenFunctions(Violation::SEVERITY_ERROR, $this->forbiddenTwigFunctions), + new TwigcsRule\TrailingSpace(Violation::SEVERITY_ERROR), + new TwigcsRule\UnusedMacro(Violation::SEVERITY_ERROR), + new TwigcsRule\UnusedVariable(Violation::SEVERITY_ERROR), + ]; + } +} diff --git a/src/Twigcs/Util/HtmlUtil.php b/src/Twigcs/Util/HtmlUtil.php new file mode 100644 index 0000000..03fcef8 --- /dev/null +++ b/src/Twigcs/Util/HtmlUtil.php @@ -0,0 +1,88 @@ +#s', + '##s', + '#<\s*script[^>]*[^/]>(.*?)<\s*/\s*script\s*>#s', + '#<\s*script\s*>(.*?)<\s*/\s*script\s*>#s', + '#<\s*style[^>]*[^/]>(.*?)<\s*/\s*style\s*>#s', + '#<\s*style\s*>(.*?)<\s*/\s*style\s*>#s', + ]; + + private const HTML_TAG_PATTERN = '#\w+)[^>]*>#s'; + + public function getHtmlContentOnly(Token $token): ?string + { + return Token::TEXT_TYPE === $token->getType() + ? $this->stripUnnecessaryTags($token->getValue()) + : null; + } + + /** + * @return HtmlTagDto[] + */ + public function getParsedHtmlTags(string $html): array + { + $ret = []; + + if (preg_match_all(self::HTML_TAG_PATTERN, $html, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) { + foreach ($matches as $match) { + $ret[] = (new HtmlTagDto()) + ->setTag($match['tag'][0]) + ->setHtmlLine($match[0][0]) + ->setOffset($match[0][1]); + } + } + + return $ret; + } + + public function getTwigcsOffset(string $html, int $length): OffsetDto + { + $substr = mb_substr($html, 0, $length); + $lines = explode("\n", $substr); + $linesCount = count($lines) - 1; + + return (new OffsetDto()) + ->setLine($linesCount) + ->setColumn(mb_strlen($lines[$linesCount])); + } + + private function stripUnnecessaryTags(string $html): string + { + $tags = []; + + foreach (self::UNNECESSARY_TAGS as $tag) { + $tags[$tag] = function ($m) { return $this->replaceToTwigcsStringPad($m[0]); }; + } + + return preg_replace_callback_array( + $tags, + str_replace("\r", '', $html) + ); + } + + private function replaceToTwigcsStringPad(string $match): string + { + $newLinesCount = mb_substr_count($match, "\n"); + + return str_pad('', mb_strlen($match), 'A') . str_repeat("\n", $newLinesCount); + } +} From 4bdd7819bf95abf4a3086131b2d490cac4fe6907 Mon Sep 17 00:00:00 2001 From: Jakub Wegner Date: Mon, 22 Aug 2022 15:19:48 +0200 Subject: [PATCH 02/14] Add quote html tag attribute rule --- src/Twigcs/Rule/MacroRule.php | 4 +- src/Twigcs/Rule/QuoteHtmlTagAttributeRule.php | 66 +++++++++++++++++++ src/Twigcs/Rule/VoidHtmlTagRule.php | 2 +- src/Twigcs/Rule/WhitespaceRule.php | 2 +- src/Twigcs/Ruleset/Ruleset.php | 10 +-- 5 files changed, 76 insertions(+), 8 deletions(-) create mode 100644 src/Twigcs/Rule/QuoteHtmlTagAttributeRule.php diff --git a/src/Twigcs/Rule/MacroRule.php b/src/Twigcs/Rule/MacroRule.php index 3bc9c02..e943554 100644 --- a/src/Twigcs/Rule/MacroRule.php +++ b/src/Twigcs/Rule/MacroRule.php @@ -61,7 +61,7 @@ private function checkMultipleMacros(Token $token, string $filename): ?Violation $filename, $token->getLine(), $token->getColumn(), - Ruleset::MULTIPLE_MACROS_IN_THE_SAME_FILE + Ruleset::ERROR_MULTIPLE_MACROS ); } } @@ -76,7 +76,7 @@ private function checkSelfMacro(TokenStream $tokens, Token $token, string $filen $filename, $token->getLine(), $token->getColumn(), - Ruleset::MACRO_IN_THE_TEMPLATE + Ruleset::ERROR_MACRO_IN_TEMPLATE ); } diff --git a/src/Twigcs/Rule/QuoteHtmlTagAttributeRule.php b/src/Twigcs/Rule/QuoteHtmlTagAttributeRule.php new file mode 100644 index 0000000..e54834b --- /dev/null +++ b/src/Twigcs/Rule/QuoteHtmlTagAttributeRule.php @@ -0,0 +1,66 @@ +htmlUtil = $htmlUtil; + } + + public function check(TokenStream $tokens) + { + $violations = []; + + while (!$tokens->isEOF()) { + $token = $tokens->getCurrent(); + + if ($content = $this->htmlUtil->getHtmlContentOnly($token)) { + foreach ($this->htmlUtil->getParsedHtmlTags($content) as $tag) { + foreach ($this->getViolationQuotes($tag->getHtmlLine()) as $quote) { + $offset = $this->htmlUtil->getTwigcsOffset($content, $tag->getOffset() + $quote[1][1]); + + $violations[] = $this->createViolation( + $tokens->getSourceContext()->getPath(), + $token->getLine() + $offset->getLine(), + $offset->getColumn(), + sprintf(Ruleset::ERROR_APOSTROPHE_IN_ATTRIBUTE, $tag->getTag()) + ); + } + } + } + + $tokens->next(); + } + + return $violations; + } + + private function getViolationQuotes(string $html): array + { + return preg_match_all("#=\s*(')#", $html, $quotes, PREG_OFFSET_CAPTURE | PREG_SET_ORDER) + ? $quotes + : []; + } +} diff --git a/src/Twigcs/Rule/VoidHtmlTagRule.php b/src/Twigcs/Rule/VoidHtmlTagRule.php index c2452ff..5f36552 100644 --- a/src/Twigcs/Rule/VoidHtmlTagRule.php +++ b/src/Twigcs/Rule/VoidHtmlTagRule.php @@ -50,7 +50,7 @@ public function check(TokenStream $tokens) $tokens->getSourceContext()->getPath(), $token->getLine() + $offset->getLine(), $offset->getColumn(), - sprintf(Ruleset::UNCLOSED_VOID_HTML_TAG, $tag->getTag()) + sprintf(Ruleset::ERROR_UNCLOSED_VOID_HTML_TAG, $tag->getTag()) ); } } diff --git a/src/Twigcs/Rule/WhitespaceRule.php b/src/Twigcs/Rule/WhitespaceRule.php index d43f870..9cc085d 100644 --- a/src/Twigcs/Rule/WhitespaceRule.php +++ b/src/Twigcs/Rule/WhitespaceRule.php @@ -45,7 +45,7 @@ public function check(TokenStream $tokens) $tokens->getSourceContext()->getPath(), $token->getLine() + $offset->getLine(), $offset->getColumn(), - sprintf(Ruleset::MULTIPLE_WHITESPACES, $tag->getTag()) + sprintf(Ruleset::ERROR_MULTIPLE_WHITESPACES, $tag->getTag()) ); } } diff --git a/src/Twigcs/Ruleset/Ruleset.php b/src/Twigcs/Ruleset/Ruleset.php index bd64d8d..a496749 100644 --- a/src/Twigcs/Ruleset/Ruleset.php +++ b/src/Twigcs/Ruleset/Ruleset.php @@ -21,10 +21,11 @@ final class Ruleset implements RulesetInterface { - public const UNCLOSED_VOID_HTML_TAG = 'Unclosed %s HTML void tag'; - public const MULTIPLE_MACROS_IN_THE_SAME_FILE = 'Multiple macros in the same file'; - public const MACRO_IN_THE_TEMPLATE = 'Macro in the template file'; - public const MULTIPLE_WHITESPACES = 'Multiple whitespaces in %s HTML tag'; + public const ERROR_UNCLOSED_VOID_HTML_TAG = 'Unclosed <%s> HTML void tag'; + public const ERROR_MULTIPLE_MACROS = 'Multiple macros in the same file'; + public const ERROR_MACRO_IN_TEMPLATE = 'Macro in the template file'; + public const ERROR_MULTIPLE_WHITESPACES = 'Multiple whitespaces in <%s> HTML tag'; + public const ERROR_APOSTROPHE_IN_ATTRIBUTE = 'Apostrophe instead of quote in <%s> HTML tag attributes'; /** @var int */ private $twigMajorVersion; @@ -51,6 +52,7 @@ public function getRules() new BitBagRule\VoidHtmlTagRule(Violation::SEVERITY_ERROR, $htmlUtil), new BitBagRule\MacroRule(Violation::SEVERITY_ERROR), new BitBagRule\WhitespaceRule(Violation::SEVERITY_ERROR, $htmlUtil), + new BitBagRule\QuoteHtmlTagAttributeRule(Violation::SEVERITY_ERROR, $htmlUtil), new TwigcsRule\RegEngineRule(Violation::SEVERITY_ERROR, $twigcsRulesetBuilder->build()), new TwigcsRule\LowerCaseVariable(Violation::SEVERITY_ERROR), From f39948c15012394ce9c7f5367b7c7ad6f5217cb9 Mon Sep 17 00:00:00 2001 From: Jakub Wegner Date: Mon, 22 Aug 2022 15:55:35 +0200 Subject: [PATCH 03/14] Add no space between html tag attributes rule --- ... => MultipleWhitespaceInAttributeRule.php} | 9 ++- src/Twigcs/Rule/NoSpaceInAttributeRule.php | 69 +++++++++++++++++++ ...ibuteRule.php => QuoteInAttributeRule.php} | 9 ++- .../{VoidHtmlTagRule.php => VoidTagRule.php} | 12 ++-- src/Twigcs/Ruleset/Ruleset.php | 8 ++- src/Twigcs/Util/HtmlUtil.php | 2 + 6 files changed, 96 insertions(+), 13 deletions(-) rename src/Twigcs/Rule/{WhitespaceRule.php => MultipleWhitespaceInAttributeRule.php} (85%) create mode 100644 src/Twigcs/Rule/NoSpaceInAttributeRule.php rename src/Twigcs/Rule/{QuoteHtmlTagAttributeRule.php => QuoteInAttributeRule.php} (86%) rename src/Twigcs/Rule/{VoidHtmlTagRule.php => VoidTagRule.php} (87%) diff --git a/src/Twigcs/Rule/WhitespaceRule.php b/src/Twigcs/Rule/MultipleWhitespaceInAttributeRule.php similarity index 85% rename from src/Twigcs/Rule/WhitespaceRule.php rename to src/Twigcs/Rule/MultipleWhitespaceInAttributeRule.php index 9cc085d..6dad3c0 100644 --- a/src/Twigcs/Rule/WhitespaceRule.php +++ b/src/Twigcs/Rule/MultipleWhitespaceInAttributeRule.php @@ -17,8 +17,11 @@ use FriendsOfTwig\Twigcs\Rule\RuleInterface; use FriendsOfTwig\Twigcs\TwigPort\TokenStream; -final class WhitespaceRule extends AbstractRule implements RuleInterface +final class MultipleWhitespaceInAttributeRule extends AbstractRule implements RuleInterface { + /** @var string */ + private $pattern = '#(?\s{2,})#'; + /** @var HtmlUtil */ private $htmlUtil; @@ -39,7 +42,7 @@ public function check(TokenStream $tokens) if ($content = $this->htmlUtil->getHtmlContentOnly($token)) { foreach ($this->htmlUtil->getParsedHtmlTags($content) as $tag) { foreach ($this->getViolationSpaces($tag->getHtmlLine()) as $space) { - $offset = $this->htmlUtil->getTwigcsOffset($content, $tag->getOffset() + $space[0][1]); + $offset = $this->htmlUtil->getTwigcsOffset($content, $tag->getOffset() + $space['offset'][1]); $violations[] = $this->createViolation( $tokens->getSourceContext()->getPath(), @@ -59,7 +62,7 @@ public function check(TokenStream $tokens) private function getViolationSpaces(string $html): array { - return preg_match_all('#\s{2,}#', $html, $spaces, PREG_OFFSET_CAPTURE | PREG_SET_ORDER) + return preg_match_all($this->pattern, $html, $spaces, HtmlUtil::REGEX_FLAGS) ? $spaces : []; } diff --git a/src/Twigcs/Rule/NoSpaceInAttributeRule.php b/src/Twigcs/Rule/NoSpaceInAttributeRule.php new file mode 100644 index 0000000..08bc200 --- /dev/null +++ b/src/Twigcs/Rule/NoSpaceInAttributeRule.php @@ -0,0 +1,69 @@ +[^\s/>])#s'; + + /** @var HtmlUtil */ + private $htmlUtil; + + public function __construct($severity, HtmlUtil $htmlUtil) + { + parent::__construct($severity); + + $this->htmlUtil = $htmlUtil; + } + + public function check(TokenStream $tokens) + { + $violations = []; + + while (!$tokens->isEOF()) { + $token = $tokens->getCurrent(); + + if ($content = $this->htmlUtil->getHtmlContentOnly($token)) { + foreach ($this->htmlUtil->getParsedHtmlTags($content) as $tag) { + foreach ($this->getViolationNoSpaces($tag->getHtmlLine()) as $noSpace) { + $offset = $this->htmlUtil->getTwigcsOffset($content, $tag->getOffset() + $noSpace['offset'][1]); + + $violations[] = $this->createViolation( + $tokens->getSourceContext()->getPath(), + $token->getLine() + $offset->getLine(), + $offset->getColumn(), + sprintf(Ruleset::ERROR_NO_SPACE_BETWEEN_ATTRIBUTES, $tag->getTag()) + ); + } + } + } + + $tokens->next(); + } + + return $violations; + } + + private function getViolationNoSpaces(string $html): array + { + return preg_match_all($this->pattern, $html, $noSpaces, HtmlUtil::REGEX_FLAGS) + ? $noSpaces + : []; + } +} diff --git a/src/Twigcs/Rule/QuoteHtmlTagAttributeRule.php b/src/Twigcs/Rule/QuoteInAttributeRule.php similarity index 86% rename from src/Twigcs/Rule/QuoteHtmlTagAttributeRule.php rename to src/Twigcs/Rule/QuoteInAttributeRule.php index e54834b..e9fa32b 100644 --- a/src/Twigcs/Rule/QuoteHtmlTagAttributeRule.php +++ b/src/Twigcs/Rule/QuoteInAttributeRule.php @@ -17,8 +17,11 @@ use FriendsOfTwig\Twigcs\Rule\RuleInterface; use FriendsOfTwig\Twigcs\TwigPort\TokenStream; -final class QuoteHtmlTagAttributeRule extends AbstractRule implements RuleInterface +final class QuoteInAttributeRule extends AbstractRule implements RuleInterface { + /** @var string */ + private $pattern = "#=\s*(?')#"; + /** @var HtmlUtil */ private $htmlUtil; @@ -39,7 +42,7 @@ public function check(TokenStream $tokens) if ($content = $this->htmlUtil->getHtmlContentOnly($token)) { foreach ($this->htmlUtil->getParsedHtmlTags($content) as $tag) { foreach ($this->getViolationQuotes($tag->getHtmlLine()) as $quote) { - $offset = $this->htmlUtil->getTwigcsOffset($content, $tag->getOffset() + $quote[1][1]); + $offset = $this->htmlUtil->getTwigcsOffset($content, $tag->getOffset() + $quote['offset'][1]); $violations[] = $this->createViolation( $tokens->getSourceContext()->getPath(), @@ -59,7 +62,7 @@ public function check(TokenStream $tokens) private function getViolationQuotes(string $html): array { - return preg_match_all("#=\s*(')#", $html, $quotes, PREG_OFFSET_CAPTURE | PREG_SET_ORDER) + return preg_match_all($this->pattern, $html, $quotes, HtmlUtil::REGEX_FLAGS) ? $quotes : []; } diff --git a/src/Twigcs/Rule/VoidHtmlTagRule.php b/src/Twigcs/Rule/VoidTagRule.php similarity index 87% rename from src/Twigcs/Rule/VoidHtmlTagRule.php rename to src/Twigcs/Rule/VoidTagRule.php index 5f36552..d14042a 100644 --- a/src/Twigcs/Rule/VoidHtmlTagRule.php +++ b/src/Twigcs/Rule/VoidTagRule.php @@ -18,12 +18,16 @@ use FriendsOfTwig\Twigcs\Rule\RuleInterface; use FriendsOfTwig\Twigcs\TwigPort\TokenStream; -final class VoidHtmlTagRule extends AbstractRule implements RuleInterface +final class VoidTagRule extends AbstractRule implements RuleInterface { - private const VOID_HTML_TAGS = [ + /** @var string[] */ + private $tags = [ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr' ]; + /** @var string */ + private $pattern = '#/\s*>$#'; + /** @var HtmlUtil */ private $htmlUtil; @@ -64,7 +68,7 @@ public function check(TokenStream $tokens) private function isViolation(HtmlTagDto $tag): bool { - return in_array($tag->getTag(), self::VOID_HTML_TAGS) - && !preg_match('#/\s*>$#', $tag->getHtmlLine()); + return in_array($tag->getTag(), $this->tags) + && !preg_match($this->pattern, $tag->getHtmlLine()); } } diff --git a/src/Twigcs/Ruleset/Ruleset.php b/src/Twigcs/Ruleset/Ruleset.php index a496749..88163c5 100644 --- a/src/Twigcs/Ruleset/Ruleset.php +++ b/src/Twigcs/Ruleset/Ruleset.php @@ -26,6 +26,7 @@ final class Ruleset implements RulesetInterface public const ERROR_MACRO_IN_TEMPLATE = 'Macro in the template file'; public const ERROR_MULTIPLE_WHITESPACES = 'Multiple whitespaces in <%s> HTML tag'; public const ERROR_APOSTROPHE_IN_ATTRIBUTE = 'Apostrophe instead of quote in <%s> HTML tag attributes'; + public const ERROR_NO_SPACE_BETWEEN_ATTRIBUTES = 'No space between attributes in <%s> HTML tag'; /** @var int */ private $twigMajorVersion; @@ -49,10 +50,11 @@ public function getRules() $htmlUtil = new HtmlUtil(); return [ - new BitBagRule\VoidHtmlTagRule(Violation::SEVERITY_ERROR, $htmlUtil), + new BitBagRule\VoidTagRule(Violation::SEVERITY_ERROR, $htmlUtil), new BitBagRule\MacroRule(Violation::SEVERITY_ERROR), - new BitBagRule\WhitespaceRule(Violation::SEVERITY_ERROR, $htmlUtil), - new BitBagRule\QuoteHtmlTagAttributeRule(Violation::SEVERITY_ERROR, $htmlUtil), + new BitBagRule\MultipleWhitespaceInAttributeRule(Violation::SEVERITY_ERROR, $htmlUtil), + new BitBagRule\QuoteInAttributeRule(Violation::SEVERITY_ERROR, $htmlUtil), + new BitBagRule\NoSpaceInAttributeRule(Violation::SEVERITY_ERROR, $htmlUtil), new TwigcsRule\RegEngineRule(Violation::SEVERITY_ERROR, $twigcsRulesetBuilder->build()), new TwigcsRule\LowerCaseVariable(Violation::SEVERITY_ERROR), diff --git a/src/Twigcs/Util/HtmlUtil.php b/src/Twigcs/Util/HtmlUtil.php index 03fcef8..25ea259 100644 --- a/src/Twigcs/Util/HtmlUtil.php +++ b/src/Twigcs/Util/HtmlUtil.php @@ -17,6 +17,8 @@ final class HtmlUtil { + public const REGEX_FLAGS = PREG_OFFSET_CAPTURE | PREG_SET_ORDER; + private const UNNECESSARY_TAGS = [ '##s', '##s', From 178ffc5e9b1ccc86edad86e19fda7b9ec52302ea Mon Sep 17 00:00:00 2001 From: Jakub Wegner Date: Mon, 22 Aug 2022 16:24:41 +0200 Subject: [PATCH 04/14] Add no newline at the end of the file rule --- src/Twigcs/Rule/NoNewlineAtTheEndRule.php | 56 +++++++++++++++++++++++ src/Twigcs/Ruleset/Ruleset.php | 2 + src/Twigcs/Util/HtmlUtil.php | 2 +- 3 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 src/Twigcs/Rule/NoNewlineAtTheEndRule.php diff --git a/src/Twigcs/Rule/NoNewlineAtTheEndRule.php b/src/Twigcs/Rule/NoNewlineAtTheEndRule.php new file mode 100644 index 0000000..f80865c --- /dev/null +++ b/src/Twigcs/Rule/NoNewlineAtTheEndRule.php @@ -0,0 +1,56 @@ +htmlUtil = $htmlUtil; + } + + public function check(TokenStream $tokens) + { + $violations = []; + + $content = str_replace("\r", '', $tokens->getSourceContext()->getCode()); + + if ($this->isNoNewline($content)) { + $offset = $this->htmlUtil->getTwigcsOffset($content, mb_strlen($content)); + + $violations[] = $this->createViolation( + $tokens->getSourceContext()->getPath(), + $offset->getLine() + 1, + $offset->getColumn(), + Ruleset::ERROR_NO_NEWLINE_AT_THE_END + ); + } + + return $violations; + } + + private function isNoNewline(string $content): bool + { + return $content && "\n" !== mb_substr($content, -1); + } +} diff --git a/src/Twigcs/Ruleset/Ruleset.php b/src/Twigcs/Ruleset/Ruleset.php index 88163c5..90888c8 100644 --- a/src/Twigcs/Ruleset/Ruleset.php +++ b/src/Twigcs/Ruleset/Ruleset.php @@ -27,6 +27,7 @@ final class Ruleset implements RulesetInterface public const ERROR_MULTIPLE_WHITESPACES = 'Multiple whitespaces in <%s> HTML tag'; public const ERROR_APOSTROPHE_IN_ATTRIBUTE = 'Apostrophe instead of quote in <%s> HTML tag attributes'; public const ERROR_NO_SPACE_BETWEEN_ATTRIBUTES = 'No space between attributes in <%s> HTML tag'; + public const ERROR_NO_NEWLINE_AT_THE_END = 'No newline at the end of the file'; /** @var int */ private $twigMajorVersion; @@ -55,6 +56,7 @@ public function getRules() new BitBagRule\MultipleWhitespaceInAttributeRule(Violation::SEVERITY_ERROR, $htmlUtil), new BitBagRule\QuoteInAttributeRule(Violation::SEVERITY_ERROR, $htmlUtil), new BitBagRule\NoSpaceInAttributeRule(Violation::SEVERITY_ERROR, $htmlUtil), + new BitBagRule\NoNewlineAtTheEndRule(Violation::SEVERITY_ERROR, $htmlUtil), new TwigcsRule\RegEngineRule(Violation::SEVERITY_ERROR, $twigcsRulesetBuilder->build()), new TwigcsRule\LowerCaseVariable(Violation::SEVERITY_ERROR), diff --git a/src/Twigcs/Util/HtmlUtil.php b/src/Twigcs/Util/HtmlUtil.php index 25ea259..2d1bdde 100644 --- a/src/Twigcs/Util/HtmlUtil.php +++ b/src/Twigcs/Util/HtmlUtil.php @@ -44,7 +44,7 @@ public function getParsedHtmlTags(string $html): array { $ret = []; - if (preg_match_all(self::HTML_TAG_PATTERN, $html, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) { + if (preg_match_all(self::HTML_TAG_PATTERN, $html, $matches, self::REGEX_FLAGS)) { foreach ($matches as $match) { $ret[] = (new HtmlTagDto()) ->setTag($match['tag'][0]) From d3860f3a62d402308be74147aa636b627af16b3c Mon Sep 17 00:00:00 2001 From: Jakub Wegner Date: Mon, 22 Aug 2022 16:43:50 +0200 Subject: [PATCH 05/14] Add line length rule --- src/Twigcs/Rule/LineLengthRule.php | 49 ++++++++++++++++++++++++++++++ src/Twigcs/Ruleset/Ruleset.php | 2 ++ 2 files changed, 51 insertions(+) create mode 100644 src/Twigcs/Rule/LineLengthRule.php diff --git a/src/Twigcs/Rule/LineLengthRule.php b/src/Twigcs/Rule/LineLengthRule.php new file mode 100644 index 0000000..4059831 --- /dev/null +++ b/src/Twigcs/Rule/LineLengthRule.php @@ -0,0 +1,49 @@ +getSourceContext()->getCode()); + $lines = explode("\n", $content); + + foreach ($lines as $lineNumber => $line) { + if ($this->lineIsTooLong($line)) { + $violations[] = $this->createViolation( + $tokens->getSourceContext()->getPath(), + $lineNumber + 1, + $this->maxLineLength - 1, + Ruleset::ERROR_LINE_TOO_LONG + ); + } + } + + return $violations; + } + + private function lineIsTooLong(string $line): bool + { + return mb_strlen($line) > $this->maxLineLength; + } +} diff --git a/src/Twigcs/Ruleset/Ruleset.php b/src/Twigcs/Ruleset/Ruleset.php index 90888c8..c36e73d 100644 --- a/src/Twigcs/Ruleset/Ruleset.php +++ b/src/Twigcs/Ruleset/Ruleset.php @@ -28,6 +28,7 @@ final class Ruleset implements RulesetInterface public const ERROR_APOSTROPHE_IN_ATTRIBUTE = 'Apostrophe instead of quote in <%s> HTML tag attributes'; public const ERROR_NO_SPACE_BETWEEN_ATTRIBUTES = 'No space between attributes in <%s> HTML tag'; public const ERROR_NO_NEWLINE_AT_THE_END = 'No newline at the end of the file'; + public const ERROR_LINE_TOO_LONG = 'Line is too long'; /** @var int */ private $twigMajorVersion; @@ -57,6 +58,7 @@ public function getRules() new BitBagRule\QuoteInAttributeRule(Violation::SEVERITY_ERROR, $htmlUtil), new BitBagRule\NoSpaceInAttributeRule(Violation::SEVERITY_ERROR, $htmlUtil), new BitBagRule\NoNewlineAtTheEndRule(Violation::SEVERITY_ERROR, $htmlUtil), + new BitBagRule\LineLengthRule(Violation::SEVERITY_ERROR), new TwigcsRule\RegEngineRule(Violation::SEVERITY_ERROR, $twigcsRulesetBuilder->build()), new TwigcsRule\LowerCaseVariable(Violation::SEVERITY_ERROR), From f071a8b6822e92cdc58bd13fba3f99bba8ece138 Mon Sep 17 00:00:00 2001 From: Jakub Wegner Date: Tue, 23 Aug 2022 11:19:57 +0200 Subject: [PATCH 06/14] Add multiple empty line rule --- src/Twigcs/Rule/MultipleEmptyLineRule.php | 66 +++++++++++++++++++++++ src/Twigcs/Ruleset/Ruleset.php | 10 ++-- src/Twigcs/Util/HtmlUtil.php | 38 ++++++++++--- 3 files changed, 104 insertions(+), 10 deletions(-) create mode 100644 src/Twigcs/Rule/MultipleEmptyLineRule.php diff --git a/src/Twigcs/Rule/MultipleEmptyLineRule.php b/src/Twigcs/Rule/MultipleEmptyLineRule.php new file mode 100644 index 0000000..1bf8ffd --- /dev/null +++ b/src/Twigcs/Rule/MultipleEmptyLineRule.php @@ -0,0 +1,66 @@ +\n{3,})#s"; + + /** @var HtmlUtil */ + private $htmlUtil; + + public function __construct($severity, HtmlUtil $htmlUtil) + { + parent::__construct($severity); + + $this->htmlUtil = $htmlUtil; + } + + public function check(TokenStream $tokens) + { + $violations = []; + + $content = str_replace("\r", '', $tokens->getSourceContext()->getCode()); + $this->htmlUtil->stripUnnecessaryTagsAndSavePositions($content); + + foreach ($this->getMultilines($content) as $multiline) { + if ($this->htmlUtil->isInsideUnnecessaryTag($multiline['offset'][1])) { + continue; + } + + $offset = $this->htmlUtil->getTwigcsOffset($content, $multiline['offset'][1] + 3); + + $violations[] = $this->createViolation( + $tokens->getSourceContext()->getPath(), + $offset->getLine(), + 0, + Ruleset::ERROR_MULTIPLE_EMPTY_LINES + ); + } + + return $violations; + } + + private function getMultilines(string $content): array + { + return preg_match_all($this->pattern, $content, $multilines, HtmlUtil::REGEX_FLAGS) + ? $multilines + : []; + } +} diff --git a/src/Twigcs/Ruleset/Ruleset.php b/src/Twigcs/Ruleset/Ruleset.php index c36e73d..367fc9e 100644 --- a/src/Twigcs/Ruleset/Ruleset.php +++ b/src/Twigcs/Ruleset/Ruleset.php @@ -29,6 +29,7 @@ final class Ruleset implements RulesetInterface public const ERROR_NO_SPACE_BETWEEN_ATTRIBUTES = 'No space between attributes in <%s> HTML tag'; public const ERROR_NO_NEWLINE_AT_THE_END = 'No newline at the end of the file'; public const ERROR_LINE_TOO_LONG = 'Line is too long'; + public const ERROR_MULTIPLE_EMPTY_LINES = 'Multiple empty lines'; /** @var int */ private $twigMajorVersion; @@ -59,13 +60,14 @@ public function getRules() new BitBagRule\NoSpaceInAttributeRule(Violation::SEVERITY_ERROR, $htmlUtil), new BitBagRule\NoNewlineAtTheEndRule(Violation::SEVERITY_ERROR, $htmlUtil), new BitBagRule\LineLengthRule(Violation::SEVERITY_ERROR), + new BitBagRule\MultipleEmptyLineRule(Violation::SEVERITY_ERROR, $htmlUtil), - new TwigcsRule\RegEngineRule(Violation::SEVERITY_ERROR, $twigcsRulesetBuilder->build()), new TwigcsRule\LowerCaseVariable(Violation::SEVERITY_ERROR), - new TwigcsRule\ForbiddenFunctions(Violation::SEVERITY_ERROR, $this->forbiddenTwigFunctions), + new TwigcsRule\RegEngineRule(Violation::SEVERITY_ERROR, $twigcsRulesetBuilder->build()), new TwigcsRule\TrailingSpace(Violation::SEVERITY_ERROR), - new TwigcsRule\UnusedMacro(Violation::SEVERITY_ERROR), - new TwigcsRule\UnusedVariable(Violation::SEVERITY_ERROR), + new TwigcsRule\ForbiddenFunctions(Violation::SEVERITY_ERROR, $this->forbiddenTwigFunctions), + new TwigcsRule\UnusedMacro(Violation::SEVERITY_WARNING), + new TwigcsRule\UnusedVariable(Violation::SEVERITY_WARNING), ]; } } diff --git a/src/Twigcs/Util/HtmlUtil.php b/src/Twigcs/Util/HtmlUtil.php index 2d1bdde..3432d49 100644 --- a/src/Twigcs/Util/HtmlUtil.php +++ b/src/Twigcs/Util/HtmlUtil.php @@ -30,10 +30,13 @@ final class HtmlUtil private const HTML_TAG_PATTERN = '#\w+)[^>]*>#s'; + /** @var array */ + private $unnecessaryTagsPositions = []; + public function getHtmlContentOnly(Token $token): ?string { return Token::TEXT_TYPE === $token->getType() - ? $this->stripUnnecessaryTags($token->getValue()) + ? $this->stripUnnecessaryTagsAndSavePositions($token->getValue()) : null; } @@ -67,9 +70,21 @@ public function getTwigcsOffset(string $html, int $length): OffsetDto ->setColumn(mb_strlen($lines[$linesCount])); } - private function stripUnnecessaryTags(string $html): string + public function isInsideUnnecessaryTag(int $position): bool + { + foreach ($this->unnecessaryTagsPositions as $tagsPosition) { + if ($tagsPosition[0] <= $position && $position < $tagsPosition[1]) { + return true; + } + } + + return false; + } + + public function stripUnnecessaryTagsAndSavePositions(string $html): string { $tags = []; + $this->unnecessaryTagsPositions = []; foreach (self::UNNECESSARY_TAGS as $tag) { $tags[$tag] = function ($m) { return $this->replaceToTwigcsStringPad($m[0]); }; @@ -77,14 +92,25 @@ private function stripUnnecessaryTags(string $html): string return preg_replace_callback_array( $tags, - str_replace("\r", '', $html) + str_replace("\r", '', $html), + -1, + $count, + self::REGEX_FLAGS ); } - private function replaceToTwigcsStringPad(string $match): string + private function replaceToTwigcsStringPad(array $match): string { - $newLinesCount = mb_substr_count($match, "\n"); + $html = $match[0]; + $offset = $match[1]; + + $matchLength = mb_strlen($html); + $matchLinesCount = mb_substr_count($html, "\n"); + + $this->unnecessaryTagsPositions[] = [ + $offset, $offset + $matchLength, + ]; - return str_pad('', mb_strlen($match), 'A') . str_repeat("\n", $newLinesCount); + return str_pad('', $matchLength, 'A') . str_repeat("\n", $matchLinesCount); } } From 06c75751fe1ea2fcdb2ded23d3f82a638ca4f415 Mon Sep 17 00:00:00 2001 From: Jakub Wegner Date: Tue, 23 Aug 2022 12:24:16 +0200 Subject: [PATCH 07/14] Better error messages --- src/Twigcs/Rule/LineLengthRule.php | 2 +- src/Twigcs/Ruleset/Ruleset.php | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Twigcs/Rule/LineLengthRule.php b/src/Twigcs/Rule/LineLengthRule.php index 4059831..64e60a1 100644 --- a/src/Twigcs/Rule/LineLengthRule.php +++ b/src/Twigcs/Rule/LineLengthRule.php @@ -34,7 +34,7 @@ public function check(TokenStream $tokens) $tokens->getSourceContext()->getPath(), $lineNumber + 1, $this->maxLineLength - 1, - Ruleset::ERROR_LINE_TOO_LONG + sprintf(Ruleset::ERROR_LINE_TOO_LONG, $this->maxLineLength) ); } } diff --git a/src/Twigcs/Ruleset/Ruleset.php b/src/Twigcs/Ruleset/Ruleset.php index 367fc9e..7d6f3d2 100644 --- a/src/Twigcs/Ruleset/Ruleset.php +++ b/src/Twigcs/Ruleset/Ruleset.php @@ -21,15 +21,15 @@ final class Ruleset implements RulesetInterface { - public const ERROR_UNCLOSED_VOID_HTML_TAG = 'Unclosed <%s> HTML void tag'; - public const ERROR_MULTIPLE_MACROS = 'Multiple macros in the same file'; - public const ERROR_MACRO_IN_TEMPLATE = 'Macro in the template file'; - public const ERROR_MULTIPLE_WHITESPACES = 'Multiple whitespaces in <%s> HTML tag'; - public const ERROR_APOSTROPHE_IN_ATTRIBUTE = 'Apostrophe instead of quote in <%s> HTML tag attributes'; - public const ERROR_NO_SPACE_BETWEEN_ATTRIBUTES = 'No space between attributes in <%s> HTML tag'; - public const ERROR_NO_NEWLINE_AT_THE_END = 'No newline at the end of the file'; - public const ERROR_LINE_TOO_LONG = 'Line is too long'; - public const ERROR_MULTIPLE_EMPTY_LINES = 'Multiple empty lines'; + public const ERROR_UNCLOSED_VOID_HTML_TAG = '<%s> HTML void tag should be closed.'; + public const ERROR_MULTIPLE_MACROS = 'There should not be many macros in the same file.'; + public const ERROR_MACRO_IN_TEMPLATE = 'There should not be macro in the template file.'; + public const ERROR_MULTIPLE_WHITESPACES = 'There should not be many whitespaces in <%s> HTML tag attributes.'; + public const ERROR_APOSTROPHE_IN_ATTRIBUTE = 'A quote should be used instead of apostrophe in <%s> HTML tag attributes.'; + public const ERROR_NO_SPACE_BETWEEN_ATTRIBUTES = 'There should be a space between attributes in <%s> HTML tag.'; + public const ERROR_NO_NEWLINE_AT_THE_END = 'There should be a newline at the end of the file.'; + public const ERROR_LINE_TOO_LONG = 'Line should be up to %d characters long.'; + public const ERROR_MULTIPLE_EMPTY_LINES = 'There should not be so many empty lines.'; /** @var int */ private $twigMajorVersion; From c77b8817870fcff5f490adab7c75e786b81169d7 Mon Sep 17 00:00:00 2001 From: Jakub Wegner Date: Tue, 23 Aug 2022 12:55:08 +0200 Subject: [PATCH 08/14] Add new line after twig set rule --- src/Twigcs/Rule/NewlineAfterSetRule.php | 66 +++++++++++++++++++++++++ src/Twigcs/Ruleset/Ruleset.php | 2 + 2 files changed, 68 insertions(+) create mode 100644 src/Twigcs/Rule/NewlineAfterSetRule.php diff --git a/src/Twigcs/Rule/NewlineAfterSetRule.php b/src/Twigcs/Rule/NewlineAfterSetRule.php new file mode 100644 index 0000000..a311275 --- /dev/null +++ b/src/Twigcs/Rule/NewlineAfterSetRule.php @@ -0,0 +1,66 @@ +[^\n])#s"; + + /** @var HtmlUtil */ + private $htmlUtil; + + public function __construct($severity, HtmlUtil $htmlUtil) + { + parent::__construct($severity); + + $this->htmlUtil = $htmlUtil; + } + + public function check(TokenStream $tokens) + { + $violations = []; + + $content = str_replace("\r", '', $tokens->getSourceContext()->getCode()); + $this->htmlUtil->stripUnnecessaryTagsAndSavePositions($content); + + foreach ($this->getNoNewlinesAfterSet($content) as $noNewLine) { + if ($this->htmlUtil->isInsideUnnecessaryTag($noNewLine['offset'][1])) { + continue; + } + + $offset = $this->htmlUtil->getTwigcsOffset($content, $noNewLine['offset'][1]); + + $violations[] = $this->createViolation( + $tokens->getSourceContext()->getPath(), + $offset->getLine(), + $offset->getColumn(), + Ruleset::ERROR_NEW_LINE_AFTER_SET + ); + } + + return $violations; + } + + private function getNoNewlinesAfterSet(string $content): array + { + return preg_match_all($this->pattern, $content, $noNewLines, HtmlUtil::REGEX_FLAGS) + ? $noNewLines + : []; + } +} diff --git a/src/Twigcs/Ruleset/Ruleset.php b/src/Twigcs/Ruleset/Ruleset.php index 7d6f3d2..6d6fb69 100644 --- a/src/Twigcs/Ruleset/Ruleset.php +++ b/src/Twigcs/Ruleset/Ruleset.php @@ -30,6 +30,7 @@ final class Ruleset implements RulesetInterface public const ERROR_NO_NEWLINE_AT_THE_END = 'There should be a newline at the end of the file.'; public const ERROR_LINE_TOO_LONG = 'Line should be up to %d characters long.'; public const ERROR_MULTIPLE_EMPTY_LINES = 'There should not be so many empty lines.'; + public const ERROR_NEW_LINE_AFTER_SET = 'There should be a new line after twig {% set %} declaration.'; /** @var int */ private $twigMajorVersion; @@ -61,6 +62,7 @@ public function getRules() new BitBagRule\NoNewlineAtTheEndRule(Violation::SEVERITY_ERROR, $htmlUtil), new BitBagRule\LineLengthRule(Violation::SEVERITY_ERROR), new BitBagRule\MultipleEmptyLineRule(Violation::SEVERITY_ERROR, $htmlUtil), + new BitBagRule\NewlineAfterSetRule(Violation::SEVERITY_ERROR, $htmlUtil), new TwigcsRule\LowerCaseVariable(Violation::SEVERITY_ERROR), new TwigcsRule\RegEngineRule(Violation::SEVERITY_ERROR, $twigcsRulesetBuilder->build()), From 5b4212b5cf6dfcf8fc037a1e42477f9b1774ff6e Mon Sep 17 00:00:00 2001 From: Jakub Wegner Date: Wed, 24 Aug 2022 12:19:29 +0200 Subject: [PATCH 09/14] Add apostrophe in twig rule, fix bug with twigcs tokens value --- src/Twigcs/Rule/ApostropheInTwigRule.php | 70 +++++++++++++++++++ src/Twigcs/Rule/LineLengthRule.php | 27 +++++-- src/Twigcs/Rule/MultipleEmptyLineRule.php | 6 +- .../MultipleWhitespaceInAttributeRule.php | 31 ++++---- src/Twigcs/Rule/NewlineAfterSetRule.php | 2 +- src/Twigcs/Rule/NoSpaceInAttributeRule.php | 31 ++++---- src/Twigcs/Rule/QuoteInAttributeRule.php | 31 ++++---- src/Twigcs/Rule/VoidTagRule.php | 33 ++++----- src/Twigcs/Ruleset/Ruleset.php | 12 ++-- src/Twigcs/Util/HtmlUtil.php | 17 ++--- 10 files changed, 159 insertions(+), 101 deletions(-) create mode 100644 src/Twigcs/Rule/ApostropheInTwigRule.php diff --git a/src/Twigcs/Rule/ApostropheInTwigRule.php b/src/Twigcs/Rule/ApostropheInTwigRule.php new file mode 100644 index 0000000..4f6a20f --- /dev/null +++ b/src/Twigcs/Rule/ApostropheInTwigRule.php @@ -0,0 +1,70 @@ +")[^"]*"#s'; + + /** @var HtmlUtil */ + private $htmlUtil; + + public function __construct($severity, HtmlUtil $htmlUtil) + { + parent::__construct($severity); + + $this->htmlUtil = $htmlUtil; + } + + public function check(TokenStream $tokens) + { + $violations = []; + + $content = $this->htmlUtil->stripUnnecessaryTagsAndSavePositions($tokens->getSourceContext()->getCode()); + + foreach ($this->getMatches($this->patternTwigTags, $content) as $tag) { + if ($this->htmlUtil->isInsideUnnecessaryTag($tag[0][1])) { + continue; + } + + foreach ($this->getMatches($this->patternQuotes, $tag[0][0]) as $quote) { + $offset = $this->htmlUtil->getTwigcsOffset($content, $tag[0][1] + $quote['offset'][1]); + + $violations[] = $this->createViolation( + $tokens->getSourceContext()->getPath(), + $offset->getLine(), + $offset->getColumn(), + Ruleset::ERROR_QUOTE_IN_TWIG + ); + } + } + + return $violations; + } + + private function getMatches(string $pattern, string $content): array + { + return preg_match_all($pattern, $content, $matches, HtmlUtil::REGEX_FLAGS) + ? $matches + : []; + } +} diff --git a/src/Twigcs/Rule/LineLengthRule.php b/src/Twigcs/Rule/LineLengthRule.php index 64e60a1..b3dda89 100644 --- a/src/Twigcs/Rule/LineLengthRule.php +++ b/src/Twigcs/Rule/LineLengthRule.php @@ -12,6 +12,7 @@ namespace BitBag\CodingStandard\Twigcs\Rule; use BitBag\CodingStandard\Twigcs\Ruleset\Ruleset; +use BitBag\CodingStandard\Twigcs\Util\HtmlUtil; use FriendsOfTwig\Twigcs\Rule\AbstractRule; use FriendsOfTwig\Twigcs\Rule\RuleInterface; use FriendsOfTwig\Twigcs\TwigPort\TokenStream; @@ -21,6 +22,16 @@ final class LineLengthRule extends AbstractRule implements RuleInterface /** @var int */ private $maxLineLength = 120; + /** @var HtmlUtil */ + private $htmlUtil; + + public function __construct($severity, HtmlUtil $htmlUtil) + { + parent::__construct($severity); + + $this->htmlUtil = $htmlUtil; + } + public function check(TokenStream $tokens) { $violations = []; @@ -28,22 +39,30 @@ public function check(TokenStream $tokens) $content = str_replace("\r", '', $tokens->getSourceContext()->getCode()); $lines = explode("\n", $content); + $this->htmlUtil->stripUnnecessaryTagsAndSavePositions($content); + $currentPosition = 0; + foreach ($lines as $lineNumber => $line) { - if ($this->lineIsTooLong($line)) { + $lineLength = mb_strlen($line); + $currentPosition += $lineLength; + + if ($this->isLineTooLong($lineLength) && !$this->htmlUtil->isInsideUnnecessaryTag($currentPosition)) { $violations[] = $this->createViolation( $tokens->getSourceContext()->getPath(), $lineNumber + 1, - $this->maxLineLength - 1, + $this->maxLineLength, sprintf(Ruleset::ERROR_LINE_TOO_LONG, $this->maxLineLength) ); } + + ++$currentPosition; } return $violations; } - private function lineIsTooLong(string $line): bool + private function isLineTooLong(int $lineLength): bool { - return mb_strlen($line) > $this->maxLineLength; + return $lineLength > $this->maxLineLength; } } diff --git a/src/Twigcs/Rule/MultipleEmptyLineRule.php b/src/Twigcs/Rule/MultipleEmptyLineRule.php index 1bf8ffd..0934487 100644 --- a/src/Twigcs/Rule/MultipleEmptyLineRule.php +++ b/src/Twigcs/Rule/MultipleEmptyLineRule.php @@ -20,11 +20,13 @@ final class MultipleEmptyLineRule extends AbstractRule implements RuleInterface { /** @var string */ - private $pattern = "#(?\n{3,})#s"; + private $pattern = '#(?\n{3,})#s'; /** @var HtmlUtil */ private $htmlUtil; + private $lineNumberOffset = 2; + public function __construct($severity, HtmlUtil $htmlUtil) { parent::__construct($severity); @@ -44,7 +46,7 @@ public function check(TokenStream $tokens) continue; } - $offset = $this->htmlUtil->getTwigcsOffset($content, $multiline['offset'][1] + 3); + $offset = $this->htmlUtil->getTwigcsOffset($content, $multiline['offset'][1] + $this->lineNumberOffset); $violations[] = $this->createViolation( $tokens->getSourceContext()->getPath(), diff --git a/src/Twigcs/Rule/MultipleWhitespaceInAttributeRule.php b/src/Twigcs/Rule/MultipleWhitespaceInAttributeRule.php index 6dad3c0..4dad981 100644 --- a/src/Twigcs/Rule/MultipleWhitespaceInAttributeRule.php +++ b/src/Twigcs/Rule/MultipleWhitespaceInAttributeRule.php @@ -35,26 +35,19 @@ public function __construct($severity, HtmlUtil $htmlUtil) public function check(TokenStream $tokens) { $violations = []; - - while (!$tokens->isEOF()) { - $token = $tokens->getCurrent(); - - if ($content = $this->htmlUtil->getHtmlContentOnly($token)) { - foreach ($this->htmlUtil->getParsedHtmlTags($content) as $tag) { - foreach ($this->getViolationSpaces($tag->getHtmlLine()) as $space) { - $offset = $this->htmlUtil->getTwigcsOffset($content, $tag->getOffset() + $space['offset'][1]); - - $violations[] = $this->createViolation( - $tokens->getSourceContext()->getPath(), - $token->getLine() + $offset->getLine(), - $offset->getColumn(), - sprintf(Ruleset::ERROR_MULTIPLE_WHITESPACES, $tag->getTag()) - ); - } - } + $content = $this->htmlUtil->stripUnnecessaryTagsAndSavePositions($tokens->getSourceContext()->getCode()); + + foreach ($this->htmlUtil->getParsedHtmlTags($content) as $tag) { + foreach ($this->getViolationSpaces($tag->getHtmlLine()) as $space) { + $offset = $this->htmlUtil->getTwigcsOffset($content, $tag->getOffset() + $space['offset'][1]); + + $violations[] = $this->createViolation( + $tokens->getSourceContext()->getPath(), + $offset->getLine(), + $offset->getColumn(), + sprintf(Ruleset::ERROR_MULTIPLE_WHITESPACES, $tag->getTag()) + ); } - - $tokens->next(); } return $violations; diff --git a/src/Twigcs/Rule/NewlineAfterSetRule.php b/src/Twigcs/Rule/NewlineAfterSetRule.php index a311275..337d333 100644 --- a/src/Twigcs/Rule/NewlineAfterSetRule.php +++ b/src/Twigcs/Rule/NewlineAfterSetRule.php @@ -20,7 +20,7 @@ final class NewlineAfterSetRule extends AbstractRule implements RuleInterface { /** @var string */ - private $pattern = "#set[^%}]+%\s*}[^\S\n]*(?[^\n])#s"; + private $pattern = '#set[^%}]+%\s*}[^\S\n]*(?[^\n])#s'; /** @var HtmlUtil */ private $htmlUtil; diff --git a/src/Twigcs/Rule/NoSpaceInAttributeRule.php b/src/Twigcs/Rule/NoSpaceInAttributeRule.php index 08bc200..797849e 100644 --- a/src/Twigcs/Rule/NoSpaceInAttributeRule.php +++ b/src/Twigcs/Rule/NoSpaceInAttributeRule.php @@ -35,26 +35,19 @@ public function __construct($severity, HtmlUtil $htmlUtil) public function check(TokenStream $tokens) { $violations = []; - - while (!$tokens->isEOF()) { - $token = $tokens->getCurrent(); - - if ($content = $this->htmlUtil->getHtmlContentOnly($token)) { - foreach ($this->htmlUtil->getParsedHtmlTags($content) as $tag) { - foreach ($this->getViolationNoSpaces($tag->getHtmlLine()) as $noSpace) { - $offset = $this->htmlUtil->getTwigcsOffset($content, $tag->getOffset() + $noSpace['offset'][1]); - - $violations[] = $this->createViolation( - $tokens->getSourceContext()->getPath(), - $token->getLine() + $offset->getLine(), - $offset->getColumn(), - sprintf(Ruleset::ERROR_NO_SPACE_BETWEEN_ATTRIBUTES, $tag->getTag()) - ); - } - } + $content = $this->htmlUtil->stripUnnecessaryTagsAndSavePositions($tokens->getSourceContext()->getCode()); + + foreach ($this->htmlUtil->getParsedHtmlTags($content) as $tag) { + foreach ($this->getViolationNoSpaces($tag->getHtmlLine()) as $noSpace) { + $offset = $this->htmlUtil->getTwigcsOffset($content, $tag->getOffset() + $noSpace['offset'][1]); + + $violations[] = $this->createViolation( + $tokens->getSourceContext()->getPath(), + $offset->getLine(), + $offset->getColumn(), + sprintf(Ruleset::ERROR_NO_SPACE_BETWEEN_ATTRIBUTES, $tag->getTag()) + ); } - - $tokens->next(); } return $violations; diff --git a/src/Twigcs/Rule/QuoteInAttributeRule.php b/src/Twigcs/Rule/QuoteInAttributeRule.php index e9fa32b..7177c2c 100644 --- a/src/Twigcs/Rule/QuoteInAttributeRule.php +++ b/src/Twigcs/Rule/QuoteInAttributeRule.php @@ -35,26 +35,19 @@ public function __construct($severity, HtmlUtil $htmlUtil) public function check(TokenStream $tokens) { $violations = []; - - while (!$tokens->isEOF()) { - $token = $tokens->getCurrent(); - - if ($content = $this->htmlUtil->getHtmlContentOnly($token)) { - foreach ($this->htmlUtil->getParsedHtmlTags($content) as $tag) { - foreach ($this->getViolationQuotes($tag->getHtmlLine()) as $quote) { - $offset = $this->htmlUtil->getTwigcsOffset($content, $tag->getOffset() + $quote['offset'][1]); - - $violations[] = $this->createViolation( - $tokens->getSourceContext()->getPath(), - $token->getLine() + $offset->getLine(), - $offset->getColumn(), - sprintf(Ruleset::ERROR_APOSTROPHE_IN_ATTRIBUTE, $tag->getTag()) - ); - } - } + $content = $this->htmlUtil->stripUnnecessaryTagsAndSavePositions($tokens->getSourceContext()->getCode()); + + foreach ($this->htmlUtil->getParsedHtmlTags($content) as $tag) { + foreach ($this->getViolationQuotes($tag->getHtmlLine()) as $quote) { + $offset = $this->htmlUtil->getTwigcsOffset($content, $tag->getOffset() + $quote['offset'][1]); + + $violations[] = $this->createViolation( + $tokens->getSourceContext()->getPath(), + $offset->getLine(), + $offset->getColumn(), + sprintf(Ruleset::ERROR_APOSTROPHE_IN_ATTRIBUTE, $tag->getTag()) + ); } - - $tokens->next(); } return $violations; diff --git a/src/Twigcs/Rule/VoidTagRule.php b/src/Twigcs/Rule/VoidTagRule.php index d14042a..1ee88d6 100644 --- a/src/Twigcs/Rule/VoidTagRule.php +++ b/src/Twigcs/Rule/VoidTagRule.php @@ -41,32 +41,25 @@ public function __construct($severity, HtmlUtil $htmlUtil) public function check(TokenStream $tokens) { $violations = []; - - while (!$tokens->isEOF()) { - $token = $tokens->getCurrent(); - - if ($content = $this->htmlUtil->getHtmlContentOnly($token)) { - foreach ($this->htmlUtil->getParsedHtmlTags($content) as $tag) { - if ($this->isViolation($tag)) { - $offset = $this->htmlUtil->getTwigcsOffset($content, $tag->getOffset()); - - $violations[] = $this->createViolation( - $tokens->getSourceContext()->getPath(), - $token->getLine() + $offset->getLine(), - $offset->getColumn(), - sprintf(Ruleset::ERROR_UNCLOSED_VOID_HTML_TAG, $tag->getTag()) - ); - } - } + $content = $this->htmlUtil->stripUnnecessaryTagsAndSavePositions($tokens->getSourceContext()->getCode()); + + foreach ($this->htmlUtil->getParsedHtmlTags($content) as $tag) { + if ($this->isTagUnclosed($tag)) { + $offset = $this->htmlUtil->getTwigcsOffset($content, $tag->getOffset()); + + $violations[] = $this->createViolation( + $tokens->getSourceContext()->getPath(), + $offset->getLine(), + $offset->getColumn(), + sprintf(Ruleset::ERROR_UNCLOSED_VOID_HTML_TAG, $tag->getTag()) + ); } - - $tokens->next(); } return $violations; } - private function isViolation(HtmlTagDto $tag): bool + private function isTagUnclosed(HtmlTagDto $tag): bool { return in_array($tag->getTag(), $this->tags) && !preg_match($this->pattern, $tag->getHtmlLine()); diff --git a/src/Twigcs/Ruleset/Ruleset.php b/src/Twigcs/Ruleset/Ruleset.php index 6d6fb69..23bad8b 100644 --- a/src/Twigcs/Ruleset/Ruleset.php +++ b/src/Twigcs/Ruleset/Ruleset.php @@ -22,15 +22,16 @@ final class Ruleset implements RulesetInterface { public const ERROR_UNCLOSED_VOID_HTML_TAG = '<%s> HTML void tag should be closed.'; - public const ERROR_MULTIPLE_MACROS = 'There should not be many macros in the same file.'; - public const ERROR_MACRO_IN_TEMPLATE = 'There should not be macro in the template file.'; - public const ERROR_MULTIPLE_WHITESPACES = 'There should not be many whitespaces in <%s> HTML tag attributes.'; + public const ERROR_MULTIPLE_MACROS = 'There should only be one macro in the same file.'; + public const ERROR_MACRO_IN_TEMPLATE = 'There should not be a macro in the template file.'; + public const ERROR_MULTIPLE_WHITESPACES = 'There should not be so many whitespaces in <%s> HTML tag attributes.'; public const ERROR_APOSTROPHE_IN_ATTRIBUTE = 'A quote should be used instead of apostrophe in <%s> HTML tag attributes.'; - public const ERROR_NO_SPACE_BETWEEN_ATTRIBUTES = 'There should be a space between attributes in <%s> HTML tag.'; + public const ERROR_NO_SPACE_BETWEEN_ATTRIBUTES = 'There should be a whitespace between attributes in <%s> HTML tag.'; public const ERROR_NO_NEWLINE_AT_THE_END = 'There should be a newline at the end of the file.'; public const ERROR_LINE_TOO_LONG = 'Line should be up to %d characters long.'; public const ERROR_MULTIPLE_EMPTY_LINES = 'There should not be so many empty lines.'; public const ERROR_NEW_LINE_AFTER_SET = 'There should be a new line after twig {% set %} declaration.'; + public const ERROR_QUOTE_IN_TWIG = 'An apostrophe should be used instead of quote in Twig tag.'; /** @var int */ private $twigMajorVersion; @@ -60,9 +61,10 @@ public function getRules() new BitBagRule\QuoteInAttributeRule(Violation::SEVERITY_ERROR, $htmlUtil), new BitBagRule\NoSpaceInAttributeRule(Violation::SEVERITY_ERROR, $htmlUtil), new BitBagRule\NoNewlineAtTheEndRule(Violation::SEVERITY_ERROR, $htmlUtil), - new BitBagRule\LineLengthRule(Violation::SEVERITY_ERROR), + new BitBagRule\LineLengthRule(Violation::SEVERITY_ERROR, $htmlUtil), new BitBagRule\MultipleEmptyLineRule(Violation::SEVERITY_ERROR, $htmlUtil), new BitBagRule\NewlineAfterSetRule(Violation::SEVERITY_ERROR, $htmlUtil), + new BitBagRule\ApostropheInTwigRule(Violation::SEVERITY_ERROR, $htmlUtil), new TwigcsRule\LowerCaseVariable(Violation::SEVERITY_ERROR), new TwigcsRule\RegEngineRule(Violation::SEVERITY_ERROR, $twigcsRulesetBuilder->build()), diff --git a/src/Twigcs/Util/HtmlUtil.php b/src/Twigcs/Util/HtmlUtil.php index 3432d49..27c2e46 100644 --- a/src/Twigcs/Util/HtmlUtil.php +++ b/src/Twigcs/Util/HtmlUtil.php @@ -13,13 +13,13 @@ use BitBag\CodingStandard\Twigcs\Dto\HtmlTagDto; use BitBag\CodingStandard\Twigcs\Dto\OffsetDto; -use FriendsOfTwig\Twigcs\TwigPort\Token; final class HtmlUtil { public const REGEX_FLAGS = PREG_OFFSET_CAPTURE | PREG_SET_ORDER; private const UNNECESSARY_TAGS = [ + '|{#(.*?)#}|s', '##s', '##s', '#<\s*script[^>]*[^/]>(.*?)<\s*/\s*script\s*>#s', @@ -28,18 +28,11 @@ final class HtmlUtil '#<\s*style\s*>(.*?)<\s*/\s*style\s*>#s', ]; - private const HTML_TAG_PATTERN = '#\w+)[^>]*>#s'; + private const HTML_TAG_PATTERN = '#\w+).*?>#s'; /** @var array */ private $unnecessaryTagsPositions = []; - public function getHtmlContentOnly(Token $token): ?string - { - return Token::TEXT_TYPE === $token->getType() - ? $this->stripUnnecessaryTagsAndSavePositions($token->getValue()) - : null; - } - /** * @return HtmlTagDto[] */ @@ -63,11 +56,11 @@ public function getTwigcsOffset(string $html, int $length): OffsetDto { $substr = mb_substr($html, 0, $length); $lines = explode("\n", $substr); - $linesCount = count($lines) - 1; + $linesCount = count($lines); return (new OffsetDto()) ->setLine($linesCount) - ->setColumn(mb_strlen($lines[$linesCount])); + ->setColumn(mb_strlen($lines[$linesCount - 1]) + 1); } public function isInsideUnnecessaryTag(int $position): bool @@ -111,6 +104,6 @@ private function replaceToTwigcsStringPad(array $match): string $offset, $offset + $matchLength, ]; - return str_pad('', $matchLength, 'A') . str_repeat("\n", $matchLinesCount); + return str_pad('', $matchLength - $matchLinesCount, 'A') . str_repeat("\n", $matchLinesCount); } } From 639093b760d0639a7cf1f746397b06377f0aa379 Mon Sep 17 00:00:00 2001 From: Jakub Wegner Date: Wed, 24 Aug 2022 16:35:43 +0200 Subject: [PATCH 10/14] Add twigcs conf to readme --- README.md | 44 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fad8c6d..c0a324d 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,9 @@ Like what we do? Want to join us? Check out our job listings on our [career page ---- [BitBag](https://bitbag.io/) coding standard helps you produce solid and maintainable code. At [BitBag Coding Bible](https://github.com/BitBagCommerce/BitBagBible) you can get familiar with standard we have -implemented in our library. [ECS](https://github.com/symplify/easy-coding-standard) -and [PHPStan](https://github.com/phpstan/phpstan) are responsible for keeping your code in order. +implemented in our library. [ECS](https://github.com/symplify/easy-coding-standard), +[PHPStan](https://github.com/phpstan/phpstan) +and [Twigcs](https://github.com/friendsoftwig/twigcs) are responsible for keeping your code in order. ## We are here to help This **open-source library was developed to help the community**. If you have any additional questions, would like help with installing or configuring the plugin, or need any assistance with your project - let us know! @@ -68,6 +69,23 @@ return static function (ContainerConfigurator $containerConfigurator): void { ``` +For Twigcs create `.twig_cs.dist` file with following lines +```php + +setName('bitbag_config') + ->setRuleSet(Ruleset::class) +; + +``` + ## Usage @@ -79,6 +97,11 @@ If ECS found any standard violations, you can fix it by: ```bash ./vendor/bin/ecs check src --fix ``` +To verify you Twig templates in /templates dir: +```bash +./vendor/bin/twigcs templates +``` + ## Customization #### ECS @@ -98,6 +121,23 @@ You can set PHPStan rule level with following commands ./vendor/bin/phpstan analyze --configuration=vendor/bitbag/coding-standard/phpstan.neon tests --level=5 ``` +#### Twigcs +You can set path to your Twig templates in the configuration file so you don't have to provide it in the twigcs command +```php +setName('bitbag_config') + ->setRuleSet(Ruleset::class) + ->addFinder(TemplateFinder::create()->in(__DIR__.'/templates')) +; +``` # About us From c6dbbcbbd5c2d095e3c9ba4d5a4f010b2d770a1a Mon Sep 17 00:00:00 2001 From: Jakub Wegner Date: Thu, 25 Aug 2022 10:02:42 +0200 Subject: [PATCH 11/14] Clean code --- src/Twigcs/Dto/HtmlTagDto.php | 10 +++---- ...leEmptyLineRule.php => EmptyLinesRule.php} | 7 ++--- .../ApostropheInAttributesRule.php} | 10 +++---- .../MultiWhitespaceInAttributesRule.php} | 8 +++--- .../UnclosedVoidTagsRule.php} | 6 ++--- .../WhitespaceInAttributesRule.php} | 8 +++--- ...TheEndRule.php => NewlineAtTheEndRule.php} | 4 +-- src/Twigcs/Rule/{ => Twig}/MacroRule.php | 13 ++++++---- .../Rule/{ => Twig}/NewlineAfterSetRule.php | 4 +-- .../QuoteInTwigRule.php} | 4 +-- src/Twigcs/Ruleset/Ruleset.php | 26 ++++++++++--------- src/Twigcs/Util/HtmlUtil.php | 2 +- 12 files changed, 54 insertions(+), 48 deletions(-) rename src/Twigcs/Rule/{MultipleEmptyLineRule.php => EmptyLinesRule.php} (89%) rename src/Twigcs/Rule/{QuoteInAttributeRule.php => Html/ApostropheInAttributesRule.php} (83%) rename src/Twigcs/Rule/{MultipleWhitespaceInAttributeRule.php => Html/MultiWhitespaceInAttributesRule.php} (85%) rename src/Twigcs/Rule/{VoidTagRule.php => Html/UnclosedVoidTagsRule.php} (90%) rename src/Twigcs/Rule/{NoSpaceInAttributeRule.php => Html/WhitespaceInAttributesRule.php} (86%) rename src/Twigcs/Rule/{NoNewlineAtTheEndRule.php => NewlineAtTheEndRule.php} (91%) rename src/Twigcs/Rule/{ => Twig}/MacroRule.php (90%) rename src/Twigcs/Rule/{ => Twig}/NewlineAfterSetRule.php (94%) rename src/Twigcs/Rule/{ApostropheInTwigRule.php => Twig/QuoteInTwigRule.php} (94%) diff --git a/src/Twigcs/Dto/HtmlTagDto.php b/src/Twigcs/Dto/HtmlTagDto.php index ac74227..767a1b3 100644 --- a/src/Twigcs/Dto/HtmlTagDto.php +++ b/src/Twigcs/Dto/HtmlTagDto.php @@ -17,7 +17,7 @@ final class HtmlTagDto private $tag = ''; /** @var string */ - private $htmlLine = ''; + private $html = ''; /** @var int */ private $offset = 0; @@ -34,14 +34,14 @@ public function setTag(string $tag): self return $this; } - public function getHtmlLine(): string + public function getHtml(): string { - return $this->htmlLine; + return $this->html; } - public function setHtmlLine(string $htmlLine): self + public function setHtml(string $html): self { - $this->htmlLine = $htmlLine; + $this->html = $html; return $this; } diff --git a/src/Twigcs/Rule/MultipleEmptyLineRule.php b/src/Twigcs/Rule/EmptyLinesRule.php similarity index 89% rename from src/Twigcs/Rule/MultipleEmptyLineRule.php rename to src/Twigcs/Rule/EmptyLinesRule.php index 0934487..b71a0ca 100644 --- a/src/Twigcs/Rule/MultipleEmptyLineRule.php +++ b/src/Twigcs/Rule/EmptyLinesRule.php @@ -17,7 +17,7 @@ use FriendsOfTwig\Twigcs\Rule\RuleInterface; use FriendsOfTwig\Twigcs\TwigPort\TokenStream; -final class MultipleEmptyLineRule extends AbstractRule implements RuleInterface +final class EmptyLinesRule extends AbstractRule implements RuleInterface { /** @var string */ private $pattern = '#(?\n{3,})#s'; @@ -25,6 +25,7 @@ final class MultipleEmptyLineRule extends AbstractRule implements RuleInterface /** @var HtmlUtil */ private $htmlUtil; + /** @var int */ private $lineNumberOffset = 2; public function __construct($severity, HtmlUtil $htmlUtil) @@ -41,7 +42,7 @@ public function check(TokenStream $tokens) $content = str_replace("\r", '', $tokens->getSourceContext()->getCode()); $this->htmlUtil->stripUnnecessaryTagsAndSavePositions($content); - foreach ($this->getMultilines($content) as $multiline) { + foreach ($this->getMultiLines($content) as $multiline) { if ($this->htmlUtil->isInsideUnnecessaryTag($multiline['offset'][1])) { continue; } @@ -59,7 +60,7 @@ public function check(TokenStream $tokens) return $violations; } - private function getMultilines(string $content): array + private function getMultiLines(string $content): array { return preg_match_all($this->pattern, $content, $multilines, HtmlUtil::REGEX_FLAGS) ? $multilines diff --git a/src/Twigcs/Rule/QuoteInAttributeRule.php b/src/Twigcs/Rule/Html/ApostropheInAttributesRule.php similarity index 83% rename from src/Twigcs/Rule/QuoteInAttributeRule.php rename to src/Twigcs/Rule/Html/ApostropheInAttributesRule.php index 7177c2c..c4ea50d 100644 --- a/src/Twigcs/Rule/QuoteInAttributeRule.php +++ b/src/Twigcs/Rule/Html/ApostropheInAttributesRule.php @@ -9,7 +9,7 @@ declare(strict_types=1); -namespace BitBag\CodingStandard\Twigcs\Rule; +namespace BitBag\CodingStandard\Twigcs\Rule\Html; use BitBag\CodingStandard\Twigcs\Ruleset\Ruleset; use BitBag\CodingStandard\Twigcs\Util\HtmlUtil; @@ -17,7 +17,7 @@ use FriendsOfTwig\Twigcs\Rule\RuleInterface; use FriendsOfTwig\Twigcs\TwigPort\TokenStream; -final class QuoteInAttributeRule extends AbstractRule implements RuleInterface +final class ApostropheInAttributesRule extends AbstractRule implements RuleInterface { /** @var string */ private $pattern = "#=\s*(?')#"; @@ -38,8 +38,8 @@ public function check(TokenStream $tokens) $content = $this->htmlUtil->stripUnnecessaryTagsAndSavePositions($tokens->getSourceContext()->getCode()); foreach ($this->htmlUtil->getParsedHtmlTags($content) as $tag) { - foreach ($this->getViolationQuotes($tag->getHtmlLine()) as $quote) { - $offset = $this->htmlUtil->getTwigcsOffset($content, $tag->getOffset() + $quote['offset'][1]); + foreach ($this->getApostrophes($tag->getHtml()) as $apostrophe) { + $offset = $this->htmlUtil->getTwigcsOffset($content, $tag->getOffset() + $apostrophe['offset'][1]); $violations[] = $this->createViolation( $tokens->getSourceContext()->getPath(), @@ -53,7 +53,7 @@ public function check(TokenStream $tokens) return $violations; } - private function getViolationQuotes(string $html): array + private function getApostrophes(string $html): array { return preg_match_all($this->pattern, $html, $quotes, HtmlUtil::REGEX_FLAGS) ? $quotes diff --git a/src/Twigcs/Rule/MultipleWhitespaceInAttributeRule.php b/src/Twigcs/Rule/Html/MultiWhitespaceInAttributesRule.php similarity index 85% rename from src/Twigcs/Rule/MultipleWhitespaceInAttributeRule.php rename to src/Twigcs/Rule/Html/MultiWhitespaceInAttributesRule.php index 4dad981..ceeae76 100644 --- a/src/Twigcs/Rule/MultipleWhitespaceInAttributeRule.php +++ b/src/Twigcs/Rule/Html/MultiWhitespaceInAttributesRule.php @@ -9,7 +9,7 @@ declare(strict_types=1); -namespace BitBag\CodingStandard\Twigcs\Rule; +namespace BitBag\CodingStandard\Twigcs\Rule\Html; use BitBag\CodingStandard\Twigcs\Ruleset\Ruleset; use BitBag\CodingStandard\Twigcs\Util\HtmlUtil; @@ -17,7 +17,7 @@ use FriendsOfTwig\Twigcs\Rule\RuleInterface; use FriendsOfTwig\Twigcs\TwigPort\TokenStream; -final class MultipleWhitespaceInAttributeRule extends AbstractRule implements RuleInterface +final class MultiWhitespaceInAttributesRule extends AbstractRule implements RuleInterface { /** @var string */ private $pattern = '#(?\s{2,})#'; @@ -38,7 +38,7 @@ public function check(TokenStream $tokens) $content = $this->htmlUtil->stripUnnecessaryTagsAndSavePositions($tokens->getSourceContext()->getCode()); foreach ($this->htmlUtil->getParsedHtmlTags($content) as $tag) { - foreach ($this->getViolationSpaces($tag->getHtmlLine()) as $space) { + foreach ($this->getMultiSpaces($tag->getHtml()) as $space) { $offset = $this->htmlUtil->getTwigcsOffset($content, $tag->getOffset() + $space['offset'][1]); $violations[] = $this->createViolation( @@ -53,7 +53,7 @@ public function check(TokenStream $tokens) return $violations; } - private function getViolationSpaces(string $html): array + private function getMultiSpaces(string $html): array { return preg_match_all($this->pattern, $html, $spaces, HtmlUtil::REGEX_FLAGS) ? $spaces diff --git a/src/Twigcs/Rule/VoidTagRule.php b/src/Twigcs/Rule/Html/UnclosedVoidTagsRule.php similarity index 90% rename from src/Twigcs/Rule/VoidTagRule.php rename to src/Twigcs/Rule/Html/UnclosedVoidTagsRule.php index 1ee88d6..fcf1cc7 100644 --- a/src/Twigcs/Rule/VoidTagRule.php +++ b/src/Twigcs/Rule/Html/UnclosedVoidTagsRule.php @@ -9,7 +9,7 @@ declare(strict_types=1); -namespace BitBag\CodingStandard\Twigcs\Rule; +namespace BitBag\CodingStandard\Twigcs\Rule\Html; use BitBag\CodingStandard\Twigcs\Dto\HtmlTagDto; use BitBag\CodingStandard\Twigcs\Ruleset\Ruleset; @@ -18,7 +18,7 @@ use FriendsOfTwig\Twigcs\Rule\RuleInterface; use FriendsOfTwig\Twigcs\TwigPort\TokenStream; -final class VoidTagRule extends AbstractRule implements RuleInterface +final class UnclosedVoidTagsRule extends AbstractRule implements RuleInterface { /** @var string[] */ private $tags = [ @@ -62,6 +62,6 @@ public function check(TokenStream $tokens) private function isTagUnclosed(HtmlTagDto $tag): bool { return in_array($tag->getTag(), $this->tags) - && !preg_match($this->pattern, $tag->getHtmlLine()); + && !preg_match($this->pattern, $tag->getHtml()); } } diff --git a/src/Twigcs/Rule/NoSpaceInAttributeRule.php b/src/Twigcs/Rule/Html/WhitespaceInAttributesRule.php similarity index 86% rename from src/Twigcs/Rule/NoSpaceInAttributeRule.php rename to src/Twigcs/Rule/Html/WhitespaceInAttributesRule.php index 797849e..13186cc 100644 --- a/src/Twigcs/Rule/NoSpaceInAttributeRule.php +++ b/src/Twigcs/Rule/Html/WhitespaceInAttributesRule.php @@ -9,7 +9,7 @@ declare(strict_types=1); -namespace BitBag\CodingStandard\Twigcs\Rule; +namespace BitBag\CodingStandard\Twigcs\Rule\Html; use BitBag\CodingStandard\Twigcs\Ruleset\Ruleset; use BitBag\CodingStandard\Twigcs\Util\HtmlUtil; @@ -17,7 +17,7 @@ use FriendsOfTwig\Twigcs\Rule\RuleInterface; use FriendsOfTwig\Twigcs\TwigPort\TokenStream; -final class NoSpaceInAttributeRule extends AbstractRule implements RuleInterface +final class WhitespaceInAttributesRule extends AbstractRule implements RuleInterface { /** @var string */ private $pattern = '#=\s*("[^"]*"|\'[^\']*\')(?[^\s/>])#s'; @@ -38,7 +38,7 @@ public function check(TokenStream $tokens) $content = $this->htmlUtil->stripUnnecessaryTagsAndSavePositions($tokens->getSourceContext()->getCode()); foreach ($this->htmlUtil->getParsedHtmlTags($content) as $tag) { - foreach ($this->getViolationNoSpaces($tag->getHtmlLine()) as $noSpace) { + foreach ($this->getNoSpaces($tag->getHtml()) as $noSpace) { $offset = $this->htmlUtil->getTwigcsOffset($content, $tag->getOffset() + $noSpace['offset'][1]); $violations[] = $this->createViolation( @@ -53,7 +53,7 @@ public function check(TokenStream $tokens) return $violations; } - private function getViolationNoSpaces(string $html): array + private function getNoSpaces(string $html): array { return preg_match_all($this->pattern, $html, $noSpaces, HtmlUtil::REGEX_FLAGS) ? $noSpaces diff --git a/src/Twigcs/Rule/NoNewlineAtTheEndRule.php b/src/Twigcs/Rule/NewlineAtTheEndRule.php similarity index 91% rename from src/Twigcs/Rule/NoNewlineAtTheEndRule.php rename to src/Twigcs/Rule/NewlineAtTheEndRule.php index f80865c..2f9fc9f 100644 --- a/src/Twigcs/Rule/NoNewlineAtTheEndRule.php +++ b/src/Twigcs/Rule/NewlineAtTheEndRule.php @@ -17,7 +17,7 @@ use FriendsOfTwig\Twigcs\Rule\RuleInterface; use FriendsOfTwig\Twigcs\TwigPort\TokenStream; -final class NoNewlineAtTheEndRule extends AbstractRule implements RuleInterface +final class NewlineAtTheEndRule extends AbstractRule implements RuleInterface { /** @var HtmlUtil */ private $htmlUtil; @@ -42,7 +42,7 @@ public function check(TokenStream $tokens) $tokens->getSourceContext()->getPath(), $offset->getLine() + 1, $offset->getColumn(), - Ruleset::ERROR_NO_NEWLINE_AT_THE_END + Ruleset::ERROR_NO_NEW_LINE_AT_THE_END ); } diff --git a/src/Twigcs/Rule/MacroRule.php b/src/Twigcs/Rule/Twig/MacroRule.php similarity index 90% rename from src/Twigcs/Rule/MacroRule.php rename to src/Twigcs/Rule/Twig/MacroRule.php index e943554..ea98982 100644 --- a/src/Twigcs/Rule/MacroRule.php +++ b/src/Twigcs/Rule/Twig/MacroRule.php @@ -9,7 +9,7 @@ declare(strict_types=1); -namespace BitBag\CodingStandard\Twigcs\Rule; +namespace BitBag\CodingStandard\Twigcs\Rule\Twig; use BitBag\CodingStandard\Twigcs\Ruleset\Ruleset; use FriendsOfTwig\Twigcs\Rule\AbstractRule; @@ -20,8 +20,11 @@ final class MacroRule extends AbstractRule implements RuleInterface { - private const TWIG_TAG_MACRO = 'macro'; - private const TWIG_TAG_SELF = '_self'; + /** @var string */ + private $twigTagMacro = 'macro'; + + /** @var string */ + private $twigTagSelfMacro = '_self'; /** @var bool */ private $isMacro; @@ -52,7 +55,7 @@ private function checkMultipleMacros(Token $token, string $filename): ?Violation { if ( Token::NAME_TYPE === $token->getType() - && self::TWIG_TAG_MACRO === $token->getValue() + && $this->twigTagMacro === $token->getValue() ) { if (!$this->isMacro) { $this->isMacro = true; @@ -86,7 +89,7 @@ private function checkSelfMacro(TokenStream $tokens, Token $token, string $filen private function isSelfTagUsedForMacro(TokenStream $tokens, Token $token): bool { return Token::NAME_TYPE === $token->getType() - && self::TWIG_TAG_SELF === $token->getValue() + && $this->twigTagSelfMacro === $token->getValue() && Token::PUNCTUATION_TYPE === $tokens->look(1)->getType() && '.' === $tokens->look(1)->getValue() diff --git a/src/Twigcs/Rule/NewlineAfterSetRule.php b/src/Twigcs/Rule/Twig/NewlineAfterSetRule.php similarity index 94% rename from src/Twigcs/Rule/NewlineAfterSetRule.php rename to src/Twigcs/Rule/Twig/NewlineAfterSetRule.php index 337d333..03c50fb 100644 --- a/src/Twigcs/Rule/NewlineAfterSetRule.php +++ b/src/Twigcs/Rule/Twig/NewlineAfterSetRule.php @@ -9,7 +9,7 @@ declare(strict_types=1); -namespace BitBag\CodingStandard\Twigcs\Rule; +namespace BitBag\CodingStandard\Twigcs\Rule\Twig; use BitBag\CodingStandard\Twigcs\Ruleset\Ruleset; use BitBag\CodingStandard\Twigcs\Util\HtmlUtil; @@ -50,7 +50,7 @@ public function check(TokenStream $tokens) $tokens->getSourceContext()->getPath(), $offset->getLine(), $offset->getColumn(), - Ruleset::ERROR_NEW_LINE_AFTER_SET + Ruleset::ERROR_NO_NEW_LINE_AFTER_SET ); } diff --git a/src/Twigcs/Rule/ApostropheInTwigRule.php b/src/Twigcs/Rule/Twig/QuoteInTwigRule.php similarity index 94% rename from src/Twigcs/Rule/ApostropheInTwigRule.php rename to src/Twigcs/Rule/Twig/QuoteInTwigRule.php index 4f6a20f..59321b0 100644 --- a/src/Twigcs/Rule/ApostropheInTwigRule.php +++ b/src/Twigcs/Rule/Twig/QuoteInTwigRule.php @@ -9,7 +9,7 @@ declare(strict_types=1); -namespace BitBag\CodingStandard\Twigcs\Rule; +namespace BitBag\CodingStandard\Twigcs\Rule\Twig; use BitBag\CodingStandard\Twigcs\Ruleset\Ruleset; use BitBag\CodingStandard\Twigcs\Util\HtmlUtil; @@ -17,7 +17,7 @@ use FriendsOfTwig\Twigcs\Rule\RuleInterface; use FriendsOfTwig\Twigcs\TwigPort\TokenStream; -final class ApostropheInTwigRule extends AbstractRule implements RuleInterface +final class QuoteInTwigRule extends AbstractRule implements RuleInterface { /** @var string */ private $patternTwigTags = '#\{(\{|%).*?(\}|%)\}#s'; diff --git a/src/Twigcs/Ruleset/Ruleset.php b/src/Twigcs/Ruleset/Ruleset.php index 23bad8b..3e9bfb8 100644 --- a/src/Twigcs/Ruleset/Ruleset.php +++ b/src/Twigcs/Ruleset/Ruleset.php @@ -27,10 +27,10 @@ final class Ruleset implements RulesetInterface public const ERROR_MULTIPLE_WHITESPACES = 'There should not be so many whitespaces in <%s> HTML tag attributes.'; public const ERROR_APOSTROPHE_IN_ATTRIBUTE = 'A quote should be used instead of apostrophe in <%s> HTML tag attributes.'; public const ERROR_NO_SPACE_BETWEEN_ATTRIBUTES = 'There should be a whitespace between attributes in <%s> HTML tag.'; - public const ERROR_NO_NEWLINE_AT_THE_END = 'There should be a newline at the end of the file.'; + public const ERROR_NO_NEW_LINE_AT_THE_END = 'There should be a new line at the end of the file.'; public const ERROR_LINE_TOO_LONG = 'Line should be up to %d characters long.'; public const ERROR_MULTIPLE_EMPTY_LINES = 'There should not be so many empty lines.'; - public const ERROR_NEW_LINE_AFTER_SET = 'There should be a new line after twig {% set %} declaration.'; + public const ERROR_NO_NEW_LINE_AFTER_SET = 'There should be a new line after twig {% set %} declaration.'; public const ERROR_QUOTE_IN_TWIG = 'An apostrophe should be used instead of quote in Twig tag.'; /** @var int */ @@ -55,21 +55,23 @@ public function getRules() $htmlUtil = new HtmlUtil(); return [ - new BitBagRule\VoidTagRule(Violation::SEVERITY_ERROR, $htmlUtil), - new BitBagRule\MacroRule(Violation::SEVERITY_ERROR), - new BitBagRule\MultipleWhitespaceInAttributeRule(Violation::SEVERITY_ERROR, $htmlUtil), - new BitBagRule\QuoteInAttributeRule(Violation::SEVERITY_ERROR, $htmlUtil), - new BitBagRule\NoSpaceInAttributeRule(Violation::SEVERITY_ERROR, $htmlUtil), - new BitBagRule\NoNewlineAtTheEndRule(Violation::SEVERITY_ERROR, $htmlUtil), + new BitBagRule\EmptyLinesRule(Violation::SEVERITY_ERROR, $htmlUtil), new BitBagRule\LineLengthRule(Violation::SEVERITY_ERROR, $htmlUtil), - new BitBagRule\MultipleEmptyLineRule(Violation::SEVERITY_ERROR, $htmlUtil), - new BitBagRule\NewlineAfterSetRule(Violation::SEVERITY_ERROR, $htmlUtil), - new BitBagRule\ApostropheInTwigRule(Violation::SEVERITY_ERROR, $htmlUtil), + new BitBagRule\NewlineAtTheEndRule(Violation::SEVERITY_ERROR, $htmlUtil), + new BitBagRule\Html\ApostropheInAttributesRule(Violation::SEVERITY_ERROR, $htmlUtil), + new BitBagRule\Html\MultiWhitespaceInAttributesRule(Violation::SEVERITY_ERROR, $htmlUtil), + new BitBagRule\Html\UnclosedVoidTagsRule(Violation::SEVERITY_ERROR, $htmlUtil), + new BitBagRule\Html\WhitespaceInAttributesRule(Violation::SEVERITY_ERROR, $htmlUtil), + + new BitBagRule\Twig\MacroRule(Violation::SEVERITY_ERROR), + new BitBagRule\Twig\NewlineAfterSetRule(Violation::SEVERITY_ERROR, $htmlUtil), + new BitBagRule\Twig\QuoteInTwigRule(Violation::SEVERITY_ERROR, $htmlUtil), + + new TwigcsRule\ForbiddenFunctions(Violation::SEVERITY_ERROR, $this->forbiddenTwigFunctions), new TwigcsRule\LowerCaseVariable(Violation::SEVERITY_ERROR), new TwigcsRule\RegEngineRule(Violation::SEVERITY_ERROR, $twigcsRulesetBuilder->build()), new TwigcsRule\TrailingSpace(Violation::SEVERITY_ERROR), - new TwigcsRule\ForbiddenFunctions(Violation::SEVERITY_ERROR, $this->forbiddenTwigFunctions), new TwigcsRule\UnusedMacro(Violation::SEVERITY_WARNING), new TwigcsRule\UnusedVariable(Violation::SEVERITY_WARNING), ]; diff --git a/src/Twigcs/Util/HtmlUtil.php b/src/Twigcs/Util/HtmlUtil.php index 27c2e46..4b8acdf 100644 --- a/src/Twigcs/Util/HtmlUtil.php +++ b/src/Twigcs/Util/HtmlUtil.php @@ -44,7 +44,7 @@ public function getParsedHtmlTags(string $html): array foreach ($matches as $match) { $ret[] = (new HtmlTagDto()) ->setTag($match['tag'][0]) - ->setHtmlLine($match[0][0]) + ->setHtml($match[0][0]) ->setOffset($match[0][1]); } } From 2816b954b8bdd07259ce3cdbb4564f51e1bd3c4d Mon Sep 17 00:00:00 2001 From: Jakub Wegner Date: Thu, 25 Aug 2022 15:23:35 +0200 Subject: [PATCH 12/14] Fix readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c0a324d..79dc50f 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ return static function (ContainerConfigurator $containerConfigurator): void { ``` -For Twigcs create `.twig_cs.dist` file with following lines +For Twigcs create `.twig_cs.dist` file with the following lines ```php Date: Tue, 30 Aug 2022 12:03:21 +0200 Subject: [PATCH 13/14] Clean code after review --- src/Twigcs/Rule/EmptyLinesRule.php | 8 ++++++-- src/Twigcs/Rule/Html/ApostropheInAttributesRule.php | 8 ++++++-- .../Rule/Html/MultiWhitespaceInAttributesRule.php | 8 ++++++-- src/Twigcs/Rule/Html/UnclosedVoidTagsRule.php | 8 ++++++-- src/Twigcs/Rule/Html/WhitespaceInAttributesRule.php | 8 ++++++-- src/Twigcs/Rule/LineLengthRule.php | 8 ++++++-- src/Twigcs/Rule/NewlineAtTheEndRule.php | 10 +++++++--- src/Twigcs/Rule/Twig/MacroRule.php | 5 ++++- src/Twigcs/Rule/Twig/NewlineAfterSetRule.php | 8 ++++++-- src/Twigcs/Rule/Twig/QuoteInTwigRule.php | 8 ++++++-- 10 files changed, 59 insertions(+), 20 deletions(-) diff --git a/src/Twigcs/Rule/EmptyLinesRule.php b/src/Twigcs/Rule/EmptyLinesRule.php index b71a0ca..c48fae3 100644 --- a/src/Twigcs/Rule/EmptyLinesRule.php +++ b/src/Twigcs/Rule/EmptyLinesRule.php @@ -16,6 +16,7 @@ use FriendsOfTwig\Twigcs\Rule\AbstractRule; use FriendsOfTwig\Twigcs\Rule\RuleInterface; use FriendsOfTwig\Twigcs\TwigPort\TokenStream; +use FriendsOfTwig\Twigcs\Validator\Violation; final class EmptyLinesRule extends AbstractRule implements RuleInterface { @@ -28,14 +29,17 @@ final class EmptyLinesRule extends AbstractRule implements RuleInterface /** @var int */ private $lineNumberOffset = 2; - public function __construct($severity, HtmlUtil $htmlUtil) + public function __construct(int $severity, HtmlUtil $htmlUtil) { parent::__construct($severity); $this->htmlUtil = $htmlUtil; } - public function check(TokenStream $tokens) + /** + * @return Violation[] + */ + public function check(TokenStream $tokens): array { $violations = []; diff --git a/src/Twigcs/Rule/Html/ApostropheInAttributesRule.php b/src/Twigcs/Rule/Html/ApostropheInAttributesRule.php index c4ea50d..997a0e8 100644 --- a/src/Twigcs/Rule/Html/ApostropheInAttributesRule.php +++ b/src/Twigcs/Rule/Html/ApostropheInAttributesRule.php @@ -16,6 +16,7 @@ use FriendsOfTwig\Twigcs\Rule\AbstractRule; use FriendsOfTwig\Twigcs\Rule\RuleInterface; use FriendsOfTwig\Twigcs\TwigPort\TokenStream; +use FriendsOfTwig\Twigcs\Validator\Violation; final class ApostropheInAttributesRule extends AbstractRule implements RuleInterface { @@ -25,14 +26,17 @@ final class ApostropheInAttributesRule extends AbstractRule implements RuleInter /** @var HtmlUtil */ private $htmlUtil; - public function __construct($severity, HtmlUtil $htmlUtil) + public function __construct(int $severity, HtmlUtil $htmlUtil) { parent::__construct($severity); $this->htmlUtil = $htmlUtil; } - public function check(TokenStream $tokens) + /** + * @return Violation[] + */ + public function check(TokenStream $tokens): array { $violations = []; $content = $this->htmlUtil->stripUnnecessaryTagsAndSavePositions($tokens->getSourceContext()->getCode()); diff --git a/src/Twigcs/Rule/Html/MultiWhitespaceInAttributesRule.php b/src/Twigcs/Rule/Html/MultiWhitespaceInAttributesRule.php index ceeae76..ad50792 100644 --- a/src/Twigcs/Rule/Html/MultiWhitespaceInAttributesRule.php +++ b/src/Twigcs/Rule/Html/MultiWhitespaceInAttributesRule.php @@ -16,6 +16,7 @@ use FriendsOfTwig\Twigcs\Rule\AbstractRule; use FriendsOfTwig\Twigcs\Rule\RuleInterface; use FriendsOfTwig\Twigcs\TwigPort\TokenStream; +use FriendsOfTwig\Twigcs\Validator\Violation; final class MultiWhitespaceInAttributesRule extends AbstractRule implements RuleInterface { @@ -25,14 +26,17 @@ final class MultiWhitespaceInAttributesRule extends AbstractRule implements Rule /** @var HtmlUtil */ private $htmlUtil; - public function __construct($severity, HtmlUtil $htmlUtil) + public function __construct(int $severity, HtmlUtil $htmlUtil) { parent::__construct($severity); $this->htmlUtil = $htmlUtil; } - public function check(TokenStream $tokens) + /** + * @return Violation[] + */ + public function check(TokenStream $tokens): array { $violations = []; $content = $this->htmlUtil->stripUnnecessaryTagsAndSavePositions($tokens->getSourceContext()->getCode()); diff --git a/src/Twigcs/Rule/Html/UnclosedVoidTagsRule.php b/src/Twigcs/Rule/Html/UnclosedVoidTagsRule.php index fcf1cc7..357b7f1 100644 --- a/src/Twigcs/Rule/Html/UnclosedVoidTagsRule.php +++ b/src/Twigcs/Rule/Html/UnclosedVoidTagsRule.php @@ -17,6 +17,7 @@ use FriendsOfTwig\Twigcs\Rule\AbstractRule; use FriendsOfTwig\Twigcs\Rule\RuleInterface; use FriendsOfTwig\Twigcs\TwigPort\TokenStream; +use FriendsOfTwig\Twigcs\Validator\Violation; final class UnclosedVoidTagsRule extends AbstractRule implements RuleInterface { @@ -31,14 +32,17 @@ final class UnclosedVoidTagsRule extends AbstractRule implements RuleInterface /** @var HtmlUtil */ private $htmlUtil; - public function __construct($severity, HtmlUtil $htmlUtil) + public function __construct(int $severity, HtmlUtil $htmlUtil) { parent::__construct($severity); $this->htmlUtil = $htmlUtil; } - public function check(TokenStream $tokens) + /** + * @return Violation[] + */ + public function check(TokenStream $tokens): array { $violations = []; $content = $this->htmlUtil->stripUnnecessaryTagsAndSavePositions($tokens->getSourceContext()->getCode()); diff --git a/src/Twigcs/Rule/Html/WhitespaceInAttributesRule.php b/src/Twigcs/Rule/Html/WhitespaceInAttributesRule.php index 13186cc..5d43b59 100644 --- a/src/Twigcs/Rule/Html/WhitespaceInAttributesRule.php +++ b/src/Twigcs/Rule/Html/WhitespaceInAttributesRule.php @@ -16,6 +16,7 @@ use FriendsOfTwig\Twigcs\Rule\AbstractRule; use FriendsOfTwig\Twigcs\Rule\RuleInterface; use FriendsOfTwig\Twigcs\TwigPort\TokenStream; +use FriendsOfTwig\Twigcs\Validator\Violation; final class WhitespaceInAttributesRule extends AbstractRule implements RuleInterface { @@ -25,14 +26,17 @@ final class WhitespaceInAttributesRule extends AbstractRule implements RuleInter /** @var HtmlUtil */ private $htmlUtil; - public function __construct($severity, HtmlUtil $htmlUtil) + public function __construct(int $severity, HtmlUtil $htmlUtil) { parent::__construct($severity); $this->htmlUtil = $htmlUtil; } - public function check(TokenStream $tokens) + /** + * @return Violation[] + */ + public function check(TokenStream $tokens): array { $violations = []; $content = $this->htmlUtil->stripUnnecessaryTagsAndSavePositions($tokens->getSourceContext()->getCode()); diff --git a/src/Twigcs/Rule/LineLengthRule.php b/src/Twigcs/Rule/LineLengthRule.php index b3dda89..7ca3e29 100644 --- a/src/Twigcs/Rule/LineLengthRule.php +++ b/src/Twigcs/Rule/LineLengthRule.php @@ -16,6 +16,7 @@ use FriendsOfTwig\Twigcs\Rule\AbstractRule; use FriendsOfTwig\Twigcs\Rule\RuleInterface; use FriendsOfTwig\Twigcs\TwigPort\TokenStream; +use FriendsOfTwig\Twigcs\Validator\Violation; final class LineLengthRule extends AbstractRule implements RuleInterface { @@ -25,14 +26,17 @@ final class LineLengthRule extends AbstractRule implements RuleInterface /** @var HtmlUtil */ private $htmlUtil; - public function __construct($severity, HtmlUtil $htmlUtil) + public function __construct(int $severity, HtmlUtil $htmlUtil) { parent::__construct($severity); $this->htmlUtil = $htmlUtil; } - public function check(TokenStream $tokens) + /** + * @return Violation[] + */ + public function check(TokenStream $tokens): array { $violations = []; diff --git a/src/Twigcs/Rule/NewlineAtTheEndRule.php b/src/Twigcs/Rule/NewlineAtTheEndRule.php index 2f9fc9f..5c91b66 100644 --- a/src/Twigcs/Rule/NewlineAtTheEndRule.php +++ b/src/Twigcs/Rule/NewlineAtTheEndRule.php @@ -16,20 +16,24 @@ use FriendsOfTwig\Twigcs\Rule\AbstractRule; use FriendsOfTwig\Twigcs\Rule\RuleInterface; use FriendsOfTwig\Twigcs\TwigPort\TokenStream; +use FriendsOfTwig\Twigcs\Validator\Violation; final class NewlineAtTheEndRule extends AbstractRule implements RuleInterface { /** @var HtmlUtil */ private $htmlUtil; - public function __construct($severity, HtmlUtil $htmlUtil) + public function __construct(int $severity, HtmlUtil $htmlUtil) { parent::__construct($severity); $this->htmlUtil = $htmlUtil; } - public function check(TokenStream $tokens) + /** + * @return Violation[] + */ + public function check(TokenStream $tokens): array { $violations = []; @@ -40,7 +44,7 @@ public function check(TokenStream $tokens) $violations[] = $this->createViolation( $tokens->getSourceContext()->getPath(), - $offset->getLine() + 1, + $offset->getLine(), $offset->getColumn(), Ruleset::ERROR_NO_NEW_LINE_AT_THE_END ); diff --git a/src/Twigcs/Rule/Twig/MacroRule.php b/src/Twigcs/Rule/Twig/MacroRule.php index ea98982..cd8495b 100644 --- a/src/Twigcs/Rule/Twig/MacroRule.php +++ b/src/Twigcs/Rule/Twig/MacroRule.php @@ -29,7 +29,10 @@ final class MacroRule extends AbstractRule implements RuleInterface /** @var bool */ private $isMacro; - public function check(TokenStream $tokens) + /** + * @return Violation[] + */ + public function check(TokenStream $tokens): array { $violations = []; $this->isMacro = false; diff --git a/src/Twigcs/Rule/Twig/NewlineAfterSetRule.php b/src/Twigcs/Rule/Twig/NewlineAfterSetRule.php index 03c50fb..e8de9fe 100644 --- a/src/Twigcs/Rule/Twig/NewlineAfterSetRule.php +++ b/src/Twigcs/Rule/Twig/NewlineAfterSetRule.php @@ -16,6 +16,7 @@ use FriendsOfTwig\Twigcs\Rule\AbstractRule; use FriendsOfTwig\Twigcs\Rule\RuleInterface; use FriendsOfTwig\Twigcs\TwigPort\TokenStream; +use FriendsOfTwig\Twigcs\Validator\Violation; final class NewlineAfterSetRule extends AbstractRule implements RuleInterface { @@ -25,14 +26,17 @@ final class NewlineAfterSetRule extends AbstractRule implements RuleInterface /** @var HtmlUtil */ private $htmlUtil; - public function __construct($severity, HtmlUtil $htmlUtil) + public function __construct(int $severity, HtmlUtil $htmlUtil) { parent::__construct($severity); $this->htmlUtil = $htmlUtil; } - public function check(TokenStream $tokens) + /** + * @return Violation[] + */ + public function check(TokenStream $tokens): array { $violations = []; diff --git a/src/Twigcs/Rule/Twig/QuoteInTwigRule.php b/src/Twigcs/Rule/Twig/QuoteInTwigRule.php index 59321b0..e665619 100644 --- a/src/Twigcs/Rule/Twig/QuoteInTwigRule.php +++ b/src/Twigcs/Rule/Twig/QuoteInTwigRule.php @@ -16,6 +16,7 @@ use FriendsOfTwig\Twigcs\Rule\AbstractRule; use FriendsOfTwig\Twigcs\Rule\RuleInterface; use FriendsOfTwig\Twigcs\TwigPort\TokenStream; +use FriendsOfTwig\Twigcs\Validator\Violation; final class QuoteInTwigRule extends AbstractRule implements RuleInterface { @@ -28,14 +29,17 @@ final class QuoteInTwigRule extends AbstractRule implements RuleInterface /** @var HtmlUtil */ private $htmlUtil; - public function __construct($severity, HtmlUtil $htmlUtil) + public function __construct(int $severity, HtmlUtil $htmlUtil) { parent::__construct($severity); $this->htmlUtil = $htmlUtil; } - public function check(TokenStream $tokens) + /** + * @return Violation[] + */ + public function check(TokenStream $tokens): array { $violations = []; From 1ed025d0eaf3f835aef6713b8be10532aa3a784e Mon Sep 17 00:00:00 2001 From: Jakub Wegner Date: Tue, 30 Aug 2022 12:10:46 +0200 Subject: [PATCH 14/14] Add integration tests for Twigcs custom rules --- composer.json | 8 ++ phpunit.xml.dist | 19 ++++ tests/Integration/BaseIntegrationTest.php | 27 +++++ .../Integration/Twigcs/EmptyLinesRuleTest.php | 64 +++++++++++ .../Html/ApostropheInAttributesRuleTest.php | 58 ++++++++++ .../MultiWhitespaceInAttributesRuleTest.php | 58 ++++++++++ .../Twigcs/Html/UnclosedVoidTagsRuleTest.php | 59 ++++++++++ .../Html/WhitespaceInAttributesRuleTest.php | 58 ++++++++++ .../Integration/Twigcs/LineLengthRuleTest.php | 60 ++++++++++ .../Twigcs/NewlineAtTheEndRuleTest.php | 59 ++++++++++ .../Integration/Twigcs/Twig/MacroRuleTest.php | 107 ++++++++++++++++++ .../Twigcs/Twig/NewlineAfterSetRuleTest.php | 59 ++++++++++ .../Twigcs/Twig/QuoteInTwigRuleTest.php | 58 ++++++++++ 13 files changed, 694 insertions(+) create mode 100644 phpunit.xml.dist create mode 100644 tests/Integration/BaseIntegrationTest.php create mode 100644 tests/Integration/Twigcs/EmptyLinesRuleTest.php create mode 100644 tests/Integration/Twigcs/Html/ApostropheInAttributesRuleTest.php create mode 100644 tests/Integration/Twigcs/Html/MultiWhitespaceInAttributesRuleTest.php create mode 100644 tests/Integration/Twigcs/Html/UnclosedVoidTagsRuleTest.php create mode 100644 tests/Integration/Twigcs/Html/WhitespaceInAttributesRuleTest.php create mode 100644 tests/Integration/Twigcs/LineLengthRuleTest.php create mode 100644 tests/Integration/Twigcs/NewlineAtTheEndRuleTest.php create mode 100644 tests/Integration/Twigcs/Twig/MacroRuleTest.php create mode 100644 tests/Integration/Twigcs/Twig/NewlineAfterSetRuleTest.php create mode 100644 tests/Integration/Twigcs/Twig/QuoteInTwigRuleTest.php diff --git a/composer.json b/composer.json index c7a4aba..19e5e37 100644 --- a/composer.json +++ b/composer.json @@ -15,9 +15,17 @@ "slevomat/coding-standard": "^7.0", "friendsoftwig/twigcs": "^6.0" }, + "require-dev": { + "phpunit/phpunit": "^9" + }, "autoload": { "psr-4": { "BitBag\\CodingStandard\\": "src/" } + }, + "autoload-dev": { + "psr-4": { + "BitBag\\CodingStandard\\Tests": "tests/" + } } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..0dbcd21 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,19 @@ + + + + + + tests/Integration + + + + + + + + + diff --git a/tests/Integration/BaseIntegrationTest.php b/tests/Integration/BaseIntegrationTest.php new file mode 100644 index 0000000..0569d53 --- /dev/null +++ b/tests/Integration/BaseIntegrationTest.php @@ -0,0 +1,27 @@ +rule = new EmptyLinesRule(Violation::SEVERITY_ERROR, new HtmlUtil()); + } + + public function test_it_returns_violation_when_are_multi_empty_lines() + { + $html = '
+ + + '; + $tokenStream = $this->getFinalTokenStream($html); + + $violations = $this->rule->check($tokenStream); + + self::assertIsArray($violations); + self::assertCount(1, $violations); + self::assertInstanceOf(Violation::class, $violations[0]); + + self::assertEquals(3, $violations[0]->getLine()); + self::assertEquals(0, $violations[0]->getColumn()); + self::assertEquals(Ruleset::ERROR_MULTIPLE_EMPTY_LINES, $violations[0]->getReason()); + } + + public function test_its_ok_when_there_are_not_multi_empty_lines() + { + $html = '
+ + +
'; + $tokenStream = $this->getFinalTokenStream($html); + + $violations = $this->rule->check($tokenStream); + + self::assertIsArray($violations); + self::assertCount(0, $violations); + } +} diff --git a/tests/Integration/Twigcs/Html/ApostropheInAttributesRuleTest.php b/tests/Integration/Twigcs/Html/ApostropheInAttributesRuleTest.php new file mode 100644 index 0000000..3df87f0 --- /dev/null +++ b/tests/Integration/Twigcs/Html/ApostropheInAttributesRuleTest.php @@ -0,0 +1,58 @@ +rule = new ApostropheInAttributesRule(Violation::SEVERITY_ERROR, new HtmlUtil()); + } + + public function test_it_returns_violation_when_is_apostrophe_in_attributes() + { + $html = "
some content
"; + $tokenStream = $this->getFinalTokenStream($html); + + $violations = $this->rule->check($tokenStream); + + self::assertIsArray($violations); + self::assertCount(1, $violations); + self::assertInstanceOf(Violation::class, $violations[0]); + + self::assertEquals(1, $violations[0]->getLine()); + self::assertEquals(12, $violations[0]->getColumn()); + self::assertEquals(sprintf(Ruleset::ERROR_APOSTROPHE_IN_ATTRIBUTE, 'div'), $violations[0]->getReason()); + } + + public function test_its_ok_when_is_quote_in_attributes() + { + $html = '
some content
some content'; + $tokenStream = $this->getFinalTokenStream($html); + + $violations = $this->rule->check($tokenStream); + + self::assertIsArray($violations); + self::assertCount(0, $violations); + } +} diff --git a/tests/Integration/Twigcs/Html/MultiWhitespaceInAttributesRuleTest.php b/tests/Integration/Twigcs/Html/MultiWhitespaceInAttributesRuleTest.php new file mode 100644 index 0000000..c437d86 --- /dev/null +++ b/tests/Integration/Twigcs/Html/MultiWhitespaceInAttributesRuleTest.php @@ -0,0 +1,58 @@ +rule = new MultiWhitespaceInAttributesRule(Violation::SEVERITY_ERROR, new HtmlUtil()); + } + + public function test_it_returns_violation_when_are_multiple_spaces_in_attributes() + { + $html = '
some content
'; + $tokenStream = $this->getFinalTokenStream($html); + + $violations = $this->rule->check($tokenStream); + + self::assertIsArray($violations); + self::assertCount(1, $violations); + self::assertInstanceOf(Violation::class, $violations[0]); + + self::assertEquals(1, $violations[0]->getLine()); + self::assertEquals(23, $violations[0]->getColumn()); + self::assertEquals(sprintf(Ruleset::ERROR_MULTIPLE_WHITESPACES, 'div'), $violations[0]->getReason()); + } + + public function test_its_ok_when_there_are_not_multiple_spaces_in_attributes() + { + $html = '
some content
some content'; + $tokenStream = $this->getFinalTokenStream($html); + + $violations = $this->rule->check($tokenStream); + + self::assertIsArray($violations); + self::assertCount(0, $violations); + } +} diff --git a/tests/Integration/Twigcs/Html/UnclosedVoidTagsRuleTest.php b/tests/Integration/Twigcs/Html/UnclosedVoidTagsRuleTest.php new file mode 100644 index 0000000..2876400 --- /dev/null +++ b/tests/Integration/Twigcs/Html/UnclosedVoidTagsRuleTest.php @@ -0,0 +1,59 @@ +rule = new UnclosedVoidTagsRule(Violation::SEVERITY_ERROR, new HtmlUtil()); + } + + public function test_it_returns_violation_when_tag_is_unclosed() + { + $html = 'some content '; + $tokenStream = $this->getFinalTokenStream($html); + + $violations = $this->rule->check($tokenStream); + + self::assertIsArray($violations); + self::assertCount(1, $violations); + self::assertInstanceOf(Violation::class, $violations[0]); + + self::assertEquals(1, $violations[0]->getLine()); + self::assertEquals(14, $violations[0]->getColumn()); + self::assertEquals(sprintf(Ruleset::ERROR_UNCLOSED_VOID_HTML_TAG, 'img'), $violations[0]->getReason()); + } + + public function test_its_ok_when_tags_are_closed() + { + $html = 'content

'. + ' '; + $tokenStream = $this->getFinalTokenStream($html); + + $violations = $this->rule->check($tokenStream); + + self::assertIsArray($violations); + self::assertCount(0, $violations); + } +} diff --git a/tests/Integration/Twigcs/Html/WhitespaceInAttributesRuleTest.php b/tests/Integration/Twigcs/Html/WhitespaceInAttributesRuleTest.php new file mode 100644 index 0000000..2ab1ba7 --- /dev/null +++ b/tests/Integration/Twigcs/Html/WhitespaceInAttributesRuleTest.php @@ -0,0 +1,58 @@ +rule = new WhitespaceInAttributesRule(Violation::SEVERITY_ERROR, new HtmlUtil()); + } + + public function test_it_returns_violation_when_is_no_space_between_attributes() + { + $html = 'content content '; + $tokenStream = $this->getFinalTokenStream($html); + + $violations = $this->rule->check($tokenStream); + + self::assertIsArray($violations); + self::assertCount(1, $violations); + self::assertInstanceOf(Violation::class, $violations[0]); + + self::assertEquals(1, $violations[0]->getLine()); + self::assertEquals(32, $violations[0]->getColumn()); + self::assertEquals(sprintf(Ruleset::ERROR_NO_SPACE_BETWEEN_ATTRIBUTES, 'span'), $violations[0]->getReason()); + } + + public function test_its_ok_when_is_exactly_one_space_between_attributes() + { + $html = 'content content '; + $tokenStream = $this->getFinalTokenStream($html); + + $violations = $this->rule->check($tokenStream); + + self::assertIsArray($violations); + self::assertCount(0, $violations); + } +} diff --git a/tests/Integration/Twigcs/LineLengthRuleTest.php b/tests/Integration/Twigcs/LineLengthRuleTest.php new file mode 100644 index 0000000..0705617 --- /dev/null +++ b/tests/Integration/Twigcs/LineLengthRuleTest.php @@ -0,0 +1,60 @@ +rule = new LineLengthRule(Violation::SEVERITY_ERROR, new HtmlUtil()); + } + + public function test_it_returns_violation_when_line_is_too_long() + { + $html = str_repeat('line', 31); + $tokenStream = $this->getFinalTokenStream($html); + + $violations = $this->rule->check($tokenStream); + + self::assertIsArray($violations); + self::assertCount(1, $violations); + self::assertInstanceOf(Violation::class, $violations[0]); + + self::assertEquals(1, $violations[0]->getLine()); + self::assertEquals(120, $violations[0]->getColumn()); + self::assertEquals(sprintf(Ruleset::ERROR_LINE_TOO_LONG, 120), $violations[0]->getReason()); + } + + public function test_its_ok_when_there_are_not_too_long_lines() + { + $html = '
+ +
'; + $tokenStream = $this->getFinalTokenStream($html); + + $violations = $this->rule->check($tokenStream); + + self::assertIsArray($violations); + self::assertCount(0, $violations); + } +} diff --git a/tests/Integration/Twigcs/NewlineAtTheEndRuleTest.php b/tests/Integration/Twigcs/NewlineAtTheEndRuleTest.php new file mode 100644 index 0000000..7762917 --- /dev/null +++ b/tests/Integration/Twigcs/NewlineAtTheEndRuleTest.php @@ -0,0 +1,59 @@ +rule = new NewlineAtTheEndRule(Violation::SEVERITY_ERROR, new HtmlUtil()); + } + + public function test_it_returns_violation_when_there_is_no_new_line_at_the_end() + { + $html = 'content
'; + $tokenStream = $this->getFinalTokenStream($html); + + $violations = $this->rule->check($tokenStream); + + self::assertIsArray($violations); + self::assertCount(1, $violations); + self::assertInstanceOf(Violation::class, $violations[0]); + + self::assertEquals(1, $violations[0]->getLine()); + self::assertEquals(20, $violations[0]->getColumn()); + self::assertEquals(Ruleset::ERROR_NO_NEW_LINE_AT_THE_END, $violations[0]->getReason()); + } + + public function test_its_ok_when_there_is_new_line_at_the_end() + { + $html = 'content
+'; + $tokenStream = $this->getFinalTokenStream($html); + + $violations = $this->rule->check($tokenStream); + + self::assertIsArray($violations); + self::assertCount(0, $violations); + } +} diff --git a/tests/Integration/Twigcs/Twig/MacroRuleTest.php b/tests/Integration/Twigcs/Twig/MacroRuleTest.php new file mode 100644 index 0000000..18009a1 --- /dev/null +++ b/tests/Integration/Twigcs/Twig/MacroRuleTest.php @@ -0,0 +1,107 @@ +rule = new MacroRule(Violation::SEVERITY_ERROR); + } + + public function test_it_returns_violation_when_are_multiple_macros_in_the_same_file() + { + $html = "{% macro button(name, value, type='text', size=20) %} +
+{% macro textarea(name, value, type='text', size=20) %}"; + + $tokenStream = $this->getFinalTokenStream($html, [ + new Token(Token::BLOCK_START_TYPE, '', 1, 1), + new Token(Token::NAME_TYPE, 'macro', 1, 4), + new Token(Token::BLOCK_END_TYPE, '', 1, 52), + + new Token(Token::BLOCK_START_TYPE, '', 3, 1), + new Token(Token::NAME_TYPE, 'macro', 3, 4), + new Token(Token::BLOCK_END_TYPE, '', 3, 54), + + new Token(Token::EOF_TYPE, '', 3, 56), + ]); + + $violations = $this->rule->check($tokenStream); + + self::assertIsArray($violations); + self::assertCount(1, $violations); + self::assertInstanceOf(Violation::class, $violations[0]); + + self::assertEquals(3, $violations[0]->getLine()); + self::assertEquals(4, $violations[0]->getColumn()); + self::assertEquals(Ruleset::ERROR_MULTIPLE_MACROS, $violations[0]->getReason()); + } + + public function test_it_returns_violation_when_is_macro_in_the_template_file() + { + $html = "{% macro button(name, value, type='text', size=20) %} +
+{% _self.button('someName', 'someValue') %}"; + + $tokenStream = $this->getFinalTokenStream($html, [ + new Token(Token::BLOCK_START_TYPE, '', 3, 1), + new Token(Token::NAME_TYPE, '_self', 3, 4), + new Token(Token::PUNCTUATION_TYPE, '.', 3, 9), + new Token(Token::NAME_TYPE, 'button', 3, 10), + new Token(Token::PUNCTUATION_TYPE, '(', 3, 16), + new Token(Token::PUNCTUATION_TYPE, ')', 3, 40), + new Token(Token::BLOCK_END_TYPE, '', 3, 42), + + new Token(Token::EOF_TYPE, '', 3, 44), + ]); + + $violations = $this->rule->check($tokenStream); + + self::assertIsArray($violations); + self::assertCount(1, $violations); + self::assertInstanceOf(Violation::class, $violations[0]); + + self::assertEquals(3, $violations[0]->getLine()); + self::assertEquals(4, $violations[0]->getColumn()); + self::assertEquals(Ruleset::ERROR_MACRO_IN_TEMPLATE, $violations[0]->getReason()); + } + + public function test_its_ok_when_macro_is_in_the_separated_file() + { + $html = "{% macro button(name, value, type='text', size=20) %}"; + + $tokenStream = $this->getFinalTokenStream($html, [ + new Token(Token::BLOCK_START_TYPE, '', 1, 1), + new Token(Token::NAME_TYPE, 'macro', 1, 4), + new Token(Token::BLOCK_END_TYPE, '', 1, 52), + + new Token(Token::EOF_TYPE, '', 1, 54), + ]); + + $violations = $this->rule->check($tokenStream); + + self::assertIsArray($violations); + self::assertCount(0, $violations); + } +} diff --git a/tests/Integration/Twigcs/Twig/NewlineAfterSetRuleTest.php b/tests/Integration/Twigcs/Twig/NewlineAfterSetRuleTest.php new file mode 100644 index 0000000..d2f8225 --- /dev/null +++ b/tests/Integration/Twigcs/Twig/NewlineAfterSetRuleTest.php @@ -0,0 +1,59 @@ +rule = new NewlineAfterSetRule(Violation::SEVERITY_ERROR, new HtmlUtil()); + } + + public function test_it_returns_violation_when_is_no_new_line_after_set() + { + $html = "
{% set var = 'value' %} {{ var }} "; + $tokenStream = $this->getFinalTokenStream($html); + + $violations = $this->rule->check($tokenStream); + + self::assertIsArray($violations); + self::assertCount(1, $violations); + self::assertInstanceOf(Violation::class, $violations[0]); + + self::assertEquals(1, $violations[0]->getLine()); + self::assertEquals(37, $violations[0]->getColumn()); + self::assertEquals(Ruleset::ERROR_NO_NEW_LINE_AFTER_SET, $violations[0]->getReason()); + } + + public function test_its_ok_when_is_new_line_after_set() + { + $html = "
{% set var = 'value' %} + {{ var }} "; + $tokenStream = $this->getFinalTokenStream($html); + + $violations = $this->rule->check($tokenStream); + + self::assertIsArray($violations); + self::assertCount(0, $violations); + } +} diff --git a/tests/Integration/Twigcs/Twig/QuoteInTwigRuleTest.php b/tests/Integration/Twigcs/Twig/QuoteInTwigRuleTest.php new file mode 100644 index 0000000..bd0ea96 --- /dev/null +++ b/tests/Integration/Twigcs/Twig/QuoteInTwigRuleTest.php @@ -0,0 +1,58 @@ +rule = new QuoteInTwigRule(Violation::SEVERITY_ERROR, new HtmlUtil()); + } + + public function test_it_returns_violation_when_is_quote_in_twig() + { + $html = '
{% set var = "value" %}'; + $tokenStream = $this->getFinalTokenStream($html); + + $violations = $this->rule->check($tokenStream); + + self::assertIsArray($violations); + self::assertCount(1, $violations); + self::assertInstanceOf(Violation::class, $violations[0]); + + self::assertEquals(1, $violations[0]->getLine()); + self::assertEquals(26, $violations[0]->getColumn()); + self::assertEquals(Ruleset::ERROR_QUOTE_IN_TWIG, $violations[0]->getReason()); + } + + public function test_its_ok_when_is_apostrophe_in_twig() + { + $html = "
{% set var = 'value' %}"; + $tokenStream = $this->getFinalTokenStream($html); + + $violations = $this->rule->check($tokenStream); + + self::assertIsArray($violations); + self::assertCount(0, $violations); + } +}