diff --git a/.codeclimate.yml b/.codeclimate.yml index 1dfdc13a..a0073b71 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,27 +1,13 @@ ---- -engines: - duplication: - enabled: true - config: - languages: - - ruby - - javascript - - python - - php +version: "2" +checks: fixme: enabled: true phpmd: enabled: true config: rulesets: rulesets.xml -ratings: - paths: - - "**.inc" - - "**.js" - - "**.jsx" - - "**.module" - - "**.php" - - "**.py" - - "**.rb" + similar-code: + config: + threshold: 64 exclude_patterns: -- "**/tests/**" + - "**/tests/**" diff --git a/src/Phug/Lexer/Lexer.php b/src/Phug/Lexer/Lexer.php index a603b296..dd32a569 100644 --- a/src/Phug/Lexer/Lexer.php +++ b/src/Phug/Lexer/Lexer.php @@ -90,6 +90,7 @@ public function __construct($options = null) 'indent_style' => null, 'indent_width' => null, 'allow_mixed_indent' => true, + 'multiline_interpolation' => true, 'multiline_markup_enabled' => true, 'encoding' => null, 'lexer_modules' => [], @@ -216,6 +217,7 @@ public function lex($input, $path = null) 'indent_style' => $this->getOption('indent_style'), 'indent_width' => $this->getOption('indent_width'), 'allow_mixed_indent' => $this->getOption('allow_mixed_indent'), + 'multiline_interpolation' => $this->getOption('multiline_interpolation'), 'multiline_markup_enabled' => $this->getOption('multiline_markup_enabled'), 'level' => $this->getOption('level'), 'mixin_keyword' => $this->getRegExpOption('mixin_keyword'), diff --git a/src/Phug/Lexer/Lexer/Analyzer/LineAnalyzer.php b/src/Phug/Lexer/Lexer/Analyzer/LineAnalyzer.php index 3fc4d149..36ac9cc6 100644 --- a/src/Phug/Lexer/Lexer/Analyzer/LineAnalyzer.php +++ b/src/Phug/Lexer/Lexer/Analyzer/LineAnalyzer.php @@ -68,36 +68,6 @@ public function disallowInterpolation() $this->allowedInterpolation = false; } - protected function getLine() - { - $line = []; - $indent = $this->reader->match('[ \t]+(?=\S)') ? mb_strlen($this->reader->getMatch(0)) : INF; - if ($indent < $this->maxIndent) { - $this->maxIndent = $indent; - } - - if ($this->allowedInterpolation) { - foreach ($this->state->scan(InterpolationScanner::class) as $subToken) { - $line[] = $subToken instanceof TextToken ? $subToken->getValue() : $subToken; - } - } - - if (($text = $this->reader->readUntilNewLine()) !== null) { - $line[] = $text; - } - - return $line; - } - - protected function recordLine() - { - $this->lines[] = $this->getLine(); - - if ($this->newLine = $this->reader->peekNewLine()) { - $this->reader->consume(1); - } - } - public function analyze($quitOnOutdent, array $breakChars = []) { $this->outdent = false; @@ -105,26 +75,9 @@ public function analyze($quitOnOutdent, array $breakChars = []) $this->newLevel = $this->level; $breakChars = array_merge($breakChars, [' ', "\t", "\n"]); $this->newLine = false; - $first = true; - - while ($this->reader->hasLength()) { - $this->newLine = true; - $indentationScanner = new IndentationScanner(); - $newLevel = $indentationScanner->getIndentLevel($this->state, $this->level); - - if (!$first || $newLevel > $this->newLevel) { - $this->newLevel = $newLevel; - } - - $first = false; - - if (!$this->reader->peekChars($breakChars)) { - $this->outdent = $this->newLevel < $this->level; - - break; - } - if ($this->newLevel < $this->level && $this->reader->match('[ \t]*\n')) { + foreach ($this->hasChunksUntil($breakChars) as $lowerLevel) { + if ($lowerLevel && $this->reader->match('[ \t]*\n')) { $this->reader->consume(mb_strlen($this->reader->getMatch(0))); $this->lines[] = []; @@ -202,4 +155,69 @@ public function getNewLevel() { return $this->newLevel; } + + protected function getLineChunks() + { + $line = []; + + if ($this->allowedInterpolation) { + foreach ($this->state->scan(InterpolationScanner::class) as $subToken) { + $line[] = $subToken instanceof TextToken ? $subToken->getValue() : $subToken; + } + } + + if (($text = $this->reader->readUntilNewLine()) !== null) { + $line[] = $text; + } + + return $line; + } + + protected function getLine() + { + $indent = $this->reader->match('[ \t]+(?=\S)') ? mb_strlen($this->reader->getMatch(0)) : INF; + + if ($indent < $this->maxIndent) { + $this->maxIndent = $indent; + } + + return $this->getLineChunks(); + } + + protected function recordLine() + { + $this->lines[] = $this->getLine(); + + if ($this->newLine = $this->reader->peekNewLine()) { + $this->reader->consume(1); + } + } + + protected function setNewLevel($newLevel, $first = false) + { + if (!$first || $newLevel > $this->newLevel) { + $this->newLevel = $newLevel; + } + } + + protected function hasChunksUntil($breakChars) + { + $first = true; + + while ($this->reader->hasLength()) { + $this->newLine = true; + $indentationScanner = new IndentationScanner(); + $newLevel = $indentationScanner->getIndentLevel($this->state, $this->level); + $this->setNewLevel($newLevel, $first); + $first = false; + + if (!$this->reader->peekChars($breakChars)) { + $this->outdent = $this->newLevel < $this->level; + + break; + } + + yield $this->newLevel < $this->level; + } + } } diff --git a/src/Phug/Lexer/Lexer/Scanner/InterpolationScanner.php b/src/Phug/Lexer/Lexer/Scanner/InterpolationScanner.php index 04236e5e..0bc737c2 100644 --- a/src/Phug/Lexer/Lexer/Scanner/InterpolationScanner.php +++ b/src/Phug/Lexer/Lexer/Scanner/InterpolationScanner.php @@ -11,34 +11,45 @@ use Phug\Lexer\Token\ExpressionToken; use Phug\Lexer\Token\InterpolationEndToken; use Phug\Lexer\Token\InterpolationStartToken; +use Phug\Lexer\Token\NewLineToken; use Phug\Lexer\Token\TagInterpolationEndToken; use Phug\Lexer\Token\TagInterpolationStartToken; use Phug\Lexer\Token\TextToken; class InterpolationScanner implements ScannerInterface { - protected function scanInterpolation(State $state, $tagInterpolation, $interpolation, $escape) + protected function throwEndOfLineExceptionIf(State $state, $condition) { - if ($tagInterpolation) { - /** @var TagInterpolationStartToken $start */ - $start = $state->createToken(TagInterpolationStartToken::class); - /** @var TagInterpolationEndToken $end */ - $end = $state->createToken(TagInterpolationEndToken::class); + if ($condition) { + $state->throwException('End of line was reached with no closing bracket for interpolation.'); + } + } - $start->setEnd($end); - $end->setStart($start); + protected function scanTagInterpolation(State $state, $tagInterpolation) + { + /** @var TagInterpolationStartToken $start */ + $start = $state->createToken(TagInterpolationStartToken::class); + /** @var TagInterpolationEndToken $end */ + $end = $state->createToken(TagInterpolationEndToken::class); - $lexer = $state->getLexer(); + $start->setEnd($end); + $end->setStart($start); - yield $start; - foreach ($lexer->lex($tagInterpolation) as $token) { - yield $token; - } - yield $end; + $lexer = $state->getLexer(); + + yield $start; + + foreach ($lexer->lex($tagInterpolation) as $token) { + $this->throwEndOfLineExceptionIf($state, $token instanceof NewLineToken); - return; + yield $token; } + yield $end; + } + + protected function scanExpressionInterpolation(State $state, $interpolation, $escape) + { /** @var InterpolationStartToken $start */ $start = $state->createToken(InterpolationStartToken::class); /** @var InterpolationEndToken $end */ @@ -50,6 +61,7 @@ protected function scanInterpolation(State $state, $tagInterpolation, $interpola /** @var ExpressionToken $token */ $token = $state->createToken(ExpressionToken::class); $token->setValue($interpolation); + if ($escape === '#') { $token->escape(); } @@ -59,6 +71,20 @@ protected function scanInterpolation(State $state, $tagInterpolation, $interpola yield $end; } + protected function scanInterpolation(State $state, $tagInterpolation, $interpolation, $escape) + { + $this->throwEndOfLineExceptionIf( + $state, + !$state->getOption('multiline_interpolation') && strpos($interpolation, "\n") !== false + ); + + if ($tagInterpolation) { + return $this->scanTagInterpolation($state, $tagInterpolation); + } + + return $this->scanExpressionInterpolation($state, $interpolation, $escape); + } + protected function needSeparationBlankLine(State $state) { $reader = $state->getReader(); diff --git a/tests/Phug/Lexer/Scanner/CommentScannerTest.php b/tests/Phug/Lexer/Scanner/CommentScannerTest.php index 7851e04f..5153b635 100644 --- a/tests/Phug/Lexer/Scanner/CommentScannerTest.php +++ b/tests/Phug/Lexer/Scanner/CommentScannerTest.php @@ -19,9 +19,11 @@ class CommentScannerTest extends AbstractLexerTest * @covers \Phug\Lexer\Scanner\CommentScanner * @covers \Phug\Lexer\Scanner\CommentScanner::scan * @covers \Phug\Lexer\Analyzer\LineAnalyzer:: + * @covers \Phug\Lexer\Analyzer\LineAnalyzer::hasChunksUntil + * @covers \Phug\Lexer\Analyzer\LineAnalyzer::setNewLevel * @covers \Phug\Lexer\Analyzer\LineAnalyzer::recordLine * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLine - * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLine + * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLineChunks */ public function testVisibleSingleLineComment() { @@ -42,9 +44,11 @@ public function testVisibleSingleLineComment() * @covers \Phug\Lexer\Scanner\CommentScanner * @covers \Phug\Lexer\Scanner\CommentScanner::scan * @covers \Phug\Lexer\Analyzer\LineAnalyzer:: + * @covers \Phug\Lexer\Analyzer\LineAnalyzer::hasChunksUntil + * @covers \Phug\Lexer\Analyzer\LineAnalyzer::setNewLevel * @covers \Phug\Lexer\Analyzer\LineAnalyzer::recordLine * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLine - * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLine + * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLineChunks */ public function testInvisibleSingleLineComment() { @@ -67,7 +71,7 @@ public function testInvisibleSingleLineComment() * @covers \Phug\Lexer\Analyzer\LineAnalyzer:: * @covers \Phug\Lexer\Analyzer\LineAnalyzer::recordLine * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLine - * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLine + * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLineChunks */ public function testVisibleMultiLineComment() { @@ -93,7 +97,7 @@ public function testVisibleMultiLineComment() * @covers \Phug\Lexer\Analyzer\LineAnalyzer:: * @covers \Phug\Lexer\Analyzer\LineAnalyzer::recordLine * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLine - * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLine + * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLineChunks */ public function testInvisibleMultiLineComment() { @@ -119,7 +123,7 @@ public function testInvisibleMultiLineComment() * @covers \Phug\Lexer\Analyzer\LineAnalyzer:: * @covers \Phug\Lexer\Analyzer\LineAnalyzer::recordLine * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLine - * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLine + * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLineChunks */ public function testCommentInIndent() { @@ -148,7 +152,7 @@ public function testCommentInIndent() * @covers \Phug\Lexer\Analyzer\LineAnalyzer:: * @covers \Phug\Lexer\Analyzer\LineAnalyzer::recordLine * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLine - * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLine + * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLineChunks */ public function testCommentQuit() { diff --git a/tests/Phug/Lexer/Scanner/FilterScannerTest.php b/tests/Phug/Lexer/Scanner/FilterScannerTest.php index 8a60c6a6..eca6134c 100644 --- a/tests/Phug/Lexer/Scanner/FilterScannerTest.php +++ b/tests/Phug/Lexer/Scanner/FilterScannerTest.php @@ -22,9 +22,11 @@ class FilterScannerTest extends AbstractLexerTest * @covers \Phug\Lexer\Scanner\FilterScanner * @covers \Phug\Lexer\Scanner\FilterScanner::scan * @covers \Phug\Lexer\Analyzer\LineAnalyzer:: + * @covers \Phug\Lexer\Analyzer\LineAnalyzer::hasChunksUntil + * @covers \Phug\Lexer\Analyzer\LineAnalyzer::setNewLevel * @covers \Phug\Lexer\Analyzer\LineAnalyzer::recordLine * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLine - * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLine + * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLineChunks */ public function testFilter() { @@ -106,7 +108,7 @@ public function testFilter() * @covers \Phug\Lexer\Analyzer\LineAnalyzer:: * @covers \Phug\Lexer\Analyzer\LineAnalyzer::recordLine * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLine - * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLine + * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLineChunks */ public function testStylusFilter() { diff --git a/tests/Phug/Lexer/Scanner/InterpolationScannerTest.php b/tests/Phug/Lexer/Scanner/InterpolationScannerTest.php index 6c206567..2a27d0a9 100644 --- a/tests/Phug/Lexer/Scanner/InterpolationScannerTest.php +++ b/tests/Phug/Lexer/Scanner/InterpolationScannerTest.php @@ -181,4 +181,31 @@ public function testTokenInLineAnalyzer() ]); $analyzer->getFlatLines(); } + + /** + * @covers \Phug\Lexer\Scanner\InterpolationScanner::scanTagInterpolation + * @covers \Phug\Lexer\Scanner\InterpolationScanner::throwEndOfLineExceptionIf + * @covers \Phug\Lexer\Scanner\InterpolationScanner::scanInterpolation + */ + public function testNewLineInTagInterpolation() + { + $this->expectMessageToBeThrown('End of line was reached with no closing bracket for interpolation.'); + + $input = "p #[em\n]"; + $tokens = iterator_to_array($this->lexer->lex($input)); + } + + /** + * @covers \Phug\Lexer\Scanner\InterpolationScanner::scanExpressionInterpolation + * @covers \Phug\Lexer\Scanner\InterpolationScanner::throwEndOfLineExceptionIf + * @covers \Phug\Lexer\Scanner\InterpolationScanner::scanInterpolation + */ + public function testNewLineInInterpolation() + { + $this->expectMessageToBeThrown('End of line was reached with no closing bracket for interpolation.'); + + $this->lexer->setOption('multiline_interpolation', false); + $input = "p #{em\n}"; + iterator_to_array($this->lexer->lex($input)); + } } diff --git a/tests/Phug/Lexer/Scanner/MarkupScannerTest.php b/tests/Phug/Lexer/Scanner/MarkupScannerTest.php index 17dc476d..e235c9e1 100644 --- a/tests/Phug/Lexer/Scanner/MarkupScannerTest.php +++ b/tests/Phug/Lexer/Scanner/MarkupScannerTest.php @@ -24,9 +24,11 @@ class MarkupScannerTest extends AbstractLexerTest * @covers \Phug\Lexer\Scanner\MarkupScanner * @covers \Phug\Lexer\Scanner\MarkupScanner::scan * @covers \Phug\Lexer\Analyzer\LineAnalyzer:: + * @covers \Phug\Lexer\Analyzer\LineAnalyzer::hasChunksUntil + * @covers \Phug\Lexer\Analyzer\LineAnalyzer::setNewLevel * @covers \Phug\Lexer\Analyzer\LineAnalyzer::recordLine * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLine - * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLine + * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLineChunks */ public function testMarkupInCondition() { @@ -127,7 +129,7 @@ public function testMarkupInCondition() * @covers \Phug\Lexer\Analyzer\LineAnalyzer:: * @covers \Phug\Lexer\Analyzer\LineAnalyzer::recordLine * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLine - * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLine + * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLineChunks */ public function testRawMarkup() { @@ -275,7 +277,7 @@ public function testRawMarkup() * @covers \Phug\Lexer\Analyzer\LineAnalyzer:: * @covers \Phug\Lexer\Analyzer\LineAnalyzer::recordLine * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLine - * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLine + * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLineChunks */ public function testRawMarkupQuit() { diff --git a/tests/Phug/Lexer/Scanner/TextBlockScannerTest.php b/tests/Phug/Lexer/Scanner/TextBlockScannerTest.php index 5322daf7..1a3e583e 100644 --- a/tests/Phug/Lexer/Scanner/TextBlockScannerTest.php +++ b/tests/Phug/Lexer/Scanner/TextBlockScannerTest.php @@ -33,7 +33,7 @@ class TextBlockScannerTest extends AbstractLexerTest * @covers \Phug\Lexer\Analyzer\LineAnalyzer:: * @covers \Phug\Lexer\Analyzer\LineAnalyzer::recordLine * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLine - * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLine + * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLineChunks * @covers \Phug\Lexer\Scanner\InterpolationScanner * @covers \Phug\Lexer\Scanner\InterpolationScanner::scanInterpolation * @covers \Phug\Lexer\Scanner\InterpolationScanner::scan @@ -176,7 +176,7 @@ public function testScan() * @covers \Phug\Lexer\Analyzer\LineAnalyzer:: * @covers \Phug\Lexer\Analyzer\LineAnalyzer::recordLine * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLine - * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLine + * @covers \Phug\Lexer\Analyzer\LineAnalyzer::getLineChunks */ public function testScanWhiteSpaces() { diff --git a/tests/Phug/Lexer/Scanner/TextScannerTest.php b/tests/Phug/Lexer/Scanner/TextScannerTest.php index b6f56b15..bac5ee35 100644 --- a/tests/Phug/Lexer/Scanner/TextScannerTest.php +++ b/tests/Phug/Lexer/Scanner/TextScannerTest.php @@ -71,6 +71,8 @@ public function testTextQuit() /** * @covers \Phug\Lexer\Analyzer\LineAnalyzer:: + * @covers \Phug\Lexer\Analyzer\LineAnalyzer::hasChunksUntil + * @covers \Phug\Lexer\Analyzer\LineAnalyzer::setNewLevel */ public function testStartingWhitespace() {