Skip to content

Commit d4e3010

Browse files
committed
refactor: separate char linter by kind
Signed-off-by: Emilien Escalle <[email protected]>
1 parent 9ebec0c commit d4e3010

15 files changed

+859
-689
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace CssLint\CharLinter;
6+
7+
use CssLint\LintContext;
8+
9+
interface CharLinter
10+
{
11+
public function lintChar(string $charValue, LintContext $lintContext): ?bool;
12+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace CssLint\CharLinter;
6+
7+
use CssLint\LintContext;
8+
9+
class CommentCharLinter implements CharLinter
10+
{
11+
private static $COMMENT_DELIMITER = '/';
12+
13+
/**
14+
* Performs lint for a given char, check comment part
15+
* @return boolean|null : true if the process should continue, else false, null if this char is not about comment
16+
*/
17+
public function lintChar(string $charValue, LintContext $lintContext): ?bool
18+
{
19+
// Manage comment context
20+
if ($lintContext->isComment()) {
21+
if ($this->isCommentEnd($charValue, $lintContext)) {
22+
$lintContext->setComment(false);
23+
}
24+
$lintContext->setPreviousChar($charValue);
25+
return true;
26+
}
27+
28+
// First char for a comment
29+
if ($this->isCommentDelimiter($charValue)) {
30+
return true;
31+
}
32+
33+
// First char for a comment
34+
if ($this->isCommentStart($charValue, $lintContext)) {
35+
// End of comment
36+
$lintContext->setComment(true);
37+
return true;
38+
}
39+
40+
return null;
41+
}
42+
43+
/**
44+
* Check if the current char is a comment
45+
* @param string $charValue
46+
* @return bool
47+
*/
48+
private function isCommentDelimiter(string $charValue): bool
49+
{
50+
return $charValue === self::$COMMENT_DELIMITER;
51+
}
52+
53+
/**
54+
* Check if the current char is the end of a comment
55+
* @param string $charValue
56+
* @return bool
57+
*/
58+
private function isCommentEnd(string $charValue, LintContext $lintContext): bool
59+
{
60+
return $this->isCommentDelimiter($charValue) && $lintContext->assertPreviousChar('*');
61+
}
62+
63+
/**
64+
* Check if the current char is the start of a comment
65+
* @param string $charValue
66+
* @return bool
67+
*/
68+
private function isCommentStart(string $charValue, LintContext $lintContext): bool
69+
{
70+
return $charValue === '*' && $lintContext->assertPreviousChar(self::$COMMENT_DELIMITER);
71+
}
72+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace CssLint\CharLinter;
6+
7+
use CssLint\LintContext;
8+
9+
class EndOfLineCharLinter implements CharLinter
10+
{
11+
/**
12+
* Performs lint for a given char, check end fo line part
13+
* @return boolean|null : true if the process should continue, else false, null if this char is not about end of line
14+
*/
15+
public function lintChar(string $charValue, LintContext $lintContext): ?bool
16+
{
17+
if ($this->isEndOfLineChar($charValue)) {
18+
$lintContext->setPreviousChar($charValue);
19+
if ($charValue === "\n") {
20+
$lintContext
21+
->incrementLineNumber()
22+
->resetCharNumber();
23+
}
24+
return true;
25+
}
26+
return null;
27+
}
28+
29+
/**
30+
* Check if a given char is an end of line token
31+
* @return boolean : true if the char is an end of line token, else false
32+
*/
33+
protected function isEndOfLineChar(string $charValue): bool
34+
{
35+
return $charValue === "\r" || $charValue === "\n";
36+
}
37+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace CssLint\CharLinter;
6+
7+
use CssLint\LintContext;
8+
use CssLint\LintContextName;
9+
10+
class ImportCharLinter implements CharLinter
11+
{
12+
private static $IMPORT_RULE = '@import';
13+
14+
/**
15+
* Performs lint for a given char, check @import rules
16+
* @return bool|null : true if the process should continue, else false, null if this char is not an @import rule
17+
*/
18+
public function lintChar(string $charValue, LintContext $lintContext): ?bool
19+
{
20+
if ($this->isImportStart($charValue, $lintContext)) {
21+
$lintContext->setCurrentContext(LintContextName::CONTEXT_SELECTOR);
22+
$lintContext->appendCurrentContent($charValue);
23+
return true;
24+
}
25+
26+
if ($this->isImportContext($lintContext)) {
27+
$lintContext->appendCurrentContent($charValue);
28+
29+
if ($charValue === ';' && $lintContext->assertPreviousChar(')')) {
30+
$lintContext->resetCurrentContext();
31+
return true;
32+
}
33+
34+
return true;
35+
}
36+
37+
return null;
38+
}
39+
40+
private function isImportStart(string $charValue, LintContext $lintContext): bool
41+
{
42+
return $lintContext->assertCurrentContext(null) && $charValue === self::$IMPORT_RULE[0];
43+
}
44+
45+
private function isImportContext(LintContext $lintContext): bool
46+
{
47+
return $lintContext->assertCurrentContext(LintContextName::CONTEXT_SELECTOR) && str_starts_with($lintContext->getCurrentContent(), self::$IMPORT_RULE);
48+
}
49+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace CssLint\CharLinter;
6+
7+
use CssLint\LintContext;
8+
use CssLint\LintContextName;
9+
use CssLint\Properties;
10+
11+
class PropertyCharLinter implements CharLinter
12+
{
13+
public function __construct(
14+
protected readonly Properties $cssLintProperties,
15+
) {}
16+
17+
/**
18+
* Performs lint for a given char, check property part
19+
* @return boolean|null : true if the process should continue, else false, null if this char is not about selector
20+
*/
21+
public function lintChar(string $charValue, LintContext $lintContext): ?bool
22+
{
23+
if (is_bool($lintPropertyNameChar = $this->lintPropertyNameChar($charValue, $lintContext))) {
24+
return $lintPropertyNameChar;
25+
}
26+
27+
if (is_bool($lintPropertyContentChar = $this->lintPropertyContentChar($charValue, $lintContext))) {
28+
return $lintPropertyContentChar;
29+
}
30+
31+
return null;
32+
}
33+
/**
34+
* Performs lint for a given char, check property name part
35+
* @return bool|null : true if the process should continue, else false, null if this char is not a property name
36+
*/
37+
protected function lintPropertyNameChar(string $charValue, LintContext $lintContext): ?bool
38+
{
39+
if (!$lintContext->assertCurrentContext(LintContextName::CONTEXT_PROPERTY_NAME)) {
40+
return null;
41+
}
42+
43+
if ($charValue === ':') {
44+
$propertyName = trim($lintContext->getCurrentContent());
45+
46+
// Ignore CSS variables (names starting with --)
47+
if (str_starts_with($propertyName, '--')) {
48+
$lintContext->setCurrentContext(LintContextName::CONTEXT_PROPERTY_CONTENT);
49+
return true;
50+
}
51+
52+
// Check if property name exists
53+
if (!$this->cssLintProperties->propertyExists($propertyName)) {
54+
$lintContext->addError('Unknown CSS property "' . $propertyName . '"');
55+
}
56+
57+
$lintContext->setCurrentContext(LintContextName::CONTEXT_PROPERTY_CONTENT);
58+
return true;
59+
}
60+
61+
$lintContext->appendCurrentContent($charValue);
62+
63+
if ($charValue === ' ') {
64+
return true;
65+
}
66+
67+
if (in_array(preg_match('/[-a-zA-Z0-9]+/', $charValue), [0, false], true)) {
68+
$lintContext->addError('Unexpected property name token "' . $charValue . '"');
69+
}
70+
71+
return true;
72+
}
73+
74+
/**
75+
* Performs lint for a given char, check property content part
76+
* @return bool|null : true if the process should continue, else false, null if this char is not a property content
77+
*/
78+
protected function lintPropertyContentChar(string $charValue, LintContext $lintContext): ?bool
79+
{
80+
if (!$lintContext->assertCurrentContext(LintContextName::CONTEXT_PROPERTY_CONTENT)) {
81+
return null;
82+
}
83+
84+
$lintContext->appendCurrentContent($charValue);
85+
86+
// End of the property content
87+
if ($charValue === ';') {
88+
// Check if the ";" is not quoted
89+
$contextContent = $lintContext->getCurrentContent();
90+
if ((substr_count($contextContent, '"') & 1) === 0 && (substr_count($contextContent, "'") & 1) === 0) {
91+
$lintContext->setCurrentContext(LintContextName::CONTEXT_SELECTOR_CONTENT);
92+
}
93+
94+
if (trim($contextContent) !== '' && trim($contextContent) !== '0') {
95+
return true;
96+
}
97+
98+
$lintContext->addError('Property cannot be empty');
99+
return true;
100+
}
101+
102+
// No property content validation
103+
return true;
104+
}
105+
}

0 commit comments

Comments
 (0)