Skip to content

fix: support @import string #113

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 55 additions & 24 deletions src/CssLint/Linter.php
Original file line number Diff line number Diff line change
Expand Up @@ -194,34 +194,39 @@
return true;
}

if (is_bool($bLintCommentChar = $this->lintCommentChar($charValue))) {
if (is_bool($lintImportChar = $this->lintImportChar($charValue))) {
$this->setPreviousChar($charValue);
return $bLintCommentChar;
return $lintImportChar;
}

if (is_bool($bLintSelectorChar = $this->lintSelectorChar($charValue))) {
if (is_bool($lintCommentChar = $this->lintCommentChar($charValue))) {
$this->setPreviousChar($charValue);
return $bLintSelectorChar;
return $lintCommentChar;
}

if (is_bool($bLintSelectorContentChar = $this->lintSelectorContentChar($charValue))) {
if (is_bool($lintSelectorChar = $this->lintSelectorChar($charValue))) {
$this->setPreviousChar($charValue);
return $bLintSelectorContentChar;
return $lintSelectorChar;
}

if (is_bool($bLintPropertyNameChar = $this->lintPropertyNameChar($charValue))) {
if (is_bool($lintSelectorContentChar = $this->lintSelectorContentChar($charValue))) {
$this->setPreviousChar($charValue);
return $bLintPropertyNameChar;
return $lintSelectorContentChar;
}

if (is_bool($bLintPropertyContentChar = $this->lintPropertyContentChar($charValue))) {
if (is_bool($lintPropertyNameChar = $this->lintPropertyNameChar($charValue))) {
$this->setPreviousChar($charValue);
return $bLintPropertyContentChar;
return $lintPropertyNameChar;
}

if (is_bool($bLintNestedSelectorChar = $this->lintNestedSelectorChar($charValue))) {
if (is_bool($lintPropertyContentChar = $this->lintPropertyContentChar($charValue))) {
$this->setPreviousChar($charValue);
return $bLintNestedSelectorChar;
return $lintPropertyContentChar;
}

if (is_bool($lintNestedSelectorChar = $this->lintNestedSelectorChar($charValue))) {
$this->setPreviousChar($charValue);
return $lintNestedSelectorChar;
}

$this->addError('Unexpected char ' . json_encode($charValue));
Expand Down Expand Up @@ -292,14 +297,14 @@
// Start of selector content
if ($charValue === '{') {
// Check if selector if valid
$sSelector = trim($this->getContextContent());
$selector = trim($this->getContextContent());

// @nested is a specific selector content
if (
// @media selector
preg_match('/^@media.+/', $sSelector)
preg_match('/^@media.+/', $selector)
// Keyframes selector
|| preg_match('/^@.*keyframes.+/', $sSelector)
|| preg_match('/^@.*keyframes.+/', $selector)
) {
$this->setNestedSelector(true);
$this->resetContext();
Expand All @@ -313,41 +318,41 @@

// There cannot have two following commas
if ($charValue === ',') {
$sSelector = $this->getContextContent();
if ($sSelector === '' || $sSelector === '0' || in_array(preg_match('/, *$/', $sSelector), [0, false], true)) {
$selector = $this->getContextContent();
if ($selector === '' || $selector === '0' || in_array(preg_match('/, *$/', $selector), [0, false], true)) {
$this->addContextContent($charValue);
return true;
}

$this->addError(sprintf(
'Selector token %s cannot be preceded by "%s"',
json_encode($charValue),
$sSelector
$selector
));
return false;
}

// Wildcard and hash
if (in_array($charValue, ['*', '#'], true)) {
$sSelector = $this->getContextContent();
if ($sSelector === '' || $sSelector === '0' || preg_match('/[a-zA-Z>,\'"] *$/', $sSelector)) {
$selector = $this->getContextContent();
if ($selector === '' || $selector === '0' || preg_match('/[a-zA-Z>,\'"] *$/', $selector)) {
$this->addContextContent($charValue);
return true;
}

$this->addError('Selector token "' . $charValue . '" cannot be preceded by "' . $sSelector . '"');
$this->addError('Selector token "' . $charValue . '" cannot be preceded by "' . $selector . '"');
return true;
}

// Dot
if ($charValue === '.') {
$sSelector = $this->getContextContent();
if ($sSelector === '' || $sSelector === '0' || preg_match('/(, |[a-zA-Z]).*$/', $sSelector)) {
$selector = $this->getContextContent();
if ($selector === '' || $selector === '0' || preg_match('/(, |[a-zA-Z]).*$/', $selector)) {
$this->addContextContent($charValue);
return true;
}

$this->addError('Selector token "' . $charValue . '" cannot be preceded by "' . $sSelector . '"');
$this->addError('Selector token "' . $charValue . '" cannot be preceded by "' . $selector . '"');

Check warning on line 355 in src/CssLint/Linter.php

View check run for this annotation

Codecov / codecov/patch

src/CssLint/Linter.php#L355

Added line #L355 was not covered by tests
return true;
}

Expand Down Expand Up @@ -485,6 +490,32 @@
return null;
}

/**
* Performs lint for a given char, check @import rules
* @return bool|null : true if the process should continue, else false, null if this char is not an @import rule
*/
protected function lintImportChar(string $charValue): ?bool
{
if ($this->assertContext(null) && $charValue === '@') {
$this->setContext(self::CONTEXT_SELECTOR);
$this->addContextContent($charValue);
return true;
}

if ($this->assertContext(self::CONTEXT_SELECTOR) && str_starts_with($this->getContextContent(), '@import')) {
$this->addContextContent($charValue);

if ($charValue === ';') {
$this->resetContext();
return true;
}

return true;
}

return null;
}

/**
* Check if a given char is an end of line token
* @return boolean : true if the char is an end of line token, else false
Expand Down
8 changes: 8 additions & 0 deletions tests/TestSuite/LinterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -194,4 +194,12 @@ public function testLintNotValidCssFile()
'Unterminated "selector content" (line: 17, char: 0)',
], $this->linter->getErrors());
}

public function testLintValidImportRule()
{
$this->assertTrue(
$this->linter->lintString("@import url('https://fonts.googleapis.com/css2?family=Poppins&display=swap');"),
print_r($this->linter->getErrors(), true)
);
}
}
114 changes: 57 additions & 57 deletions tests/TestSuite/PropertiesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,149 +9,149 @@ class PropertiesTest extends TestCase
{
public function testShouldReturnTrueWhenGivenStandardPropertyExists()
{
$oProperties = new Properties();
$this->assertTrue($oProperties->propertyExists('align-content'));
$properties = new Properties();
$this->assertTrue($properties->propertyExists('align-content'));
}

public function testShouldReturnTrueWhenGivenConstructorStandardPropertyExists()
{
$oProperties = new Properties();
$this->assertTrue($oProperties->propertyExists('-moz-align-content'));
$properties = new Properties();
$this->assertTrue($properties->propertyExists('-moz-align-content'));
}

public function testShouldReturnTrueWhenGivenConstructorNonStandardPropertyExists()
{
$oProperties = new Properties();
$this->assertTrue($oProperties->propertyExists('-moz-font-smoothing'));
$properties = new Properties();
$this->assertTrue($properties->propertyExists('-moz-font-smoothing'));
}

public function testShouldReturnTrueWhenGivenPropertyDoesNotExist()
{
$oProperties = new Properties();
$this->assertFalse($oProperties->propertyExists('-wrong-font-smoothing'));
$properties = new Properties();
$this->assertFalse($properties->propertyExists('-wrong-font-smoothing'));
}

public function testGetAllowedIndentationChars()
{
$oProperties = new Properties();
$this->assertEquals([" "], $oProperties->getAllowedIndentationChars());
$properties = new Properties();
$this->assertEquals([" "], $properties->getAllowedIndentationChars());
}

public function testSetAllowedIndentationChars()
{
$oProperties = new Properties();
$properties = new Properties();
$aAllowedIndentationChars = ["\t"];
$oProperties->setAllowedIndentationChars($aAllowedIndentationChars);
$this->assertEquals($aAllowedIndentationChars, $oProperties->getAllowedIndentationChars());
$properties->setAllowedIndentationChars($aAllowedIndentationChars);
$this->assertEquals($aAllowedIndentationChars, $properties->getAllowedIndentationChars());
}

public function testShouldReturnTrueWhenGivenCharIsAnAllowedIndentationChar()
{
$oProperties = new Properties();
$this->assertTrue($oProperties->isAllowedIndentationChar(" "));
$properties = new Properties();
$this->assertTrue($properties->isAllowedIndentationChar(" "));
}

public function testShouldReturnTrueWhenGivenCharIsNotAnAllowedIndentationChar()
{
$oProperties = new Properties();
$this->assertFalse($oProperties->isAllowedIndentationChar("\t"));
$properties = new Properties();
$this->assertFalse($properties->isAllowedIndentationChar("\t"));
}

public function testMergeConstructorsShouldDisableAContructor()
{
$oProperties = new Properties();
$this->assertTrue($oProperties->propertyExists('-moz-font-smoothing'));
$properties = new Properties();
$this->assertTrue($properties->propertyExists('-moz-font-smoothing'));

$oProperties->mergeConstructors(['moz' => false]);
$this->assertFalse($oProperties->propertyExists('-moz-font-smoothing'));
$properties->mergeConstructors(['moz' => false]);
$this->assertFalse($properties->propertyExists('-moz-font-smoothing'));
}

public function testMergeConstructorsShouldAddAContructor()
{
$oProperties = new Properties();
$this->assertFalse($oProperties->propertyExists('-new-font-smoothing'));
$properties = new Properties();
$this->assertFalse($properties->propertyExists('-new-font-smoothing'));

$oProperties->mergeConstructors(['new' => true]);
$this->assertTrue($oProperties->propertyExists('-new-font-smoothing'));
$properties->mergeConstructors(['new' => true]);
$this->assertTrue($properties->propertyExists('-new-font-smoothing'));
}

public function testMergeStandardsShouldDisableAContructor()
{
$oProperties = new Properties();
$this->assertTrue($oProperties->propertyExists('align-content'));
$properties = new Properties();
$this->assertTrue($properties->propertyExists('align-content'));

$oProperties->mergeStandards(['align-content' => false]);
$this->assertFalse($oProperties->propertyExists('align-content'));
$properties->mergeStandards(['align-content' => false]);
$this->assertFalse($properties->propertyExists('align-content'));
}

public function testMergeStandardsShouldAddAContructor()
{
$oProperties = new Properties();
$this->assertFalse($oProperties->propertyExists('new-content'));
$properties = new Properties();
$this->assertFalse($properties->propertyExists('new-content'));

$oProperties->mergeStandards(['new-content' => true]);
$this->assertTrue($oProperties->propertyExists('new-content'));
$properties->mergeStandards(['new-content' => true]);
$this->assertTrue($properties->propertyExists('new-content'));
}

public function testMergeNonStandardsShouldDisableAContructor()
{
$oProperties = new Properties();
$this->assertTrue($oProperties->propertyExists('-moz-font-smoothing'));
$properties = new Properties();
$this->assertTrue($properties->propertyExists('-moz-font-smoothing'));

$oProperties->mergeNonStandards(['font-smoothing' => false]);
$this->assertFalse($oProperties->propertyExists('-moz-font-smoothing'));
$properties->mergeNonStandards(['font-smoothing' => false]);
$this->assertFalse($properties->propertyExists('-moz-font-smoothing'));
}

public function testMergeNonStandardsShouldAddAContructor()
{
$oProperties = new Properties();
$this->assertFalse($oProperties->propertyExists('-moz-new-content'));
$properties = new Properties();
$this->assertFalse($properties->propertyExists('-moz-new-content'));

$oProperties->mergeNonStandards(['new-content' => true]);
$this->assertTrue($oProperties->propertyExists('-moz-new-content'));
$properties->mergeNonStandards(['new-content' => true]);
$this->assertTrue($properties->propertyExists('-moz-new-content'));
}

public function testSetOptionsAllowedIndentationChars()
{
$oProperties = new Properties();
$this->assertFalse($oProperties->isAllowedIndentationChar("\t"));
$properties = new Properties();
$this->assertFalse($properties->isAllowedIndentationChar("\t"));

$oProperties->setOptions([
$properties->setOptions([
'allowedIndentationChars' => ["\t"],
]);
$this->assertTrue($oProperties->isAllowedIndentationChar("\t"));
$this->assertTrue($properties->isAllowedIndentationChar("\t"));
}

public function testSetOptionsConstructors()
{
$oProperties = new Properties();
$this->assertFalse($oProperties->propertyExists('-new-font-smoothing'));
$properties = new Properties();
$this->assertFalse($properties->propertyExists('-new-font-smoothing'));

$oProperties->setOptions([
$properties->setOptions([
'constructors' => ['new' => true],
]);
$this->assertTrue($oProperties->propertyExists('-new-font-smoothing'));
$this->assertTrue($properties->propertyExists('-new-font-smoothing'));
}

public function testSetOptionsStandards()
{
$oProperties = new Properties();
$this->assertFalse($oProperties->propertyExists('new-content'));
$properties = new Properties();
$this->assertFalse($properties->propertyExists('new-content'));

$oProperties->setOptions([
$properties->setOptions([
'standards' => ['new-content' => true],
]);
$this->assertTrue($oProperties->propertyExists('new-content'));
$this->assertTrue($properties->propertyExists('new-content'));
}

public function testSetOptionsNonStandards()
{
$oProperties = new Properties();
$this->assertFalse($oProperties->propertyExists('-moz-new-content'));
$properties = new Properties();
$this->assertFalse($properties->propertyExists('-moz-new-content'));

$oProperties->setOptions([
$properties->setOptions([
'nonStandards' => ['new-content' => true],
]);
$this->assertTrue($oProperties->propertyExists('-moz-new-content'));
$this->assertTrue($properties->propertyExists('-moz-new-content'));
}
}