%PDF- %PDF-
| Direktori : /home/qgbqkvz/www/wp-content/plugins/wp-scss/scssphp/src/Ast/Selector/ |
| Current File : /home/qgbqkvz/www/wp-content/plugins/wp-scss/scssphp/src/Ast/Selector/PseudoSelector.php |
<?php
/**
* SCSSPHP
*
* @copyright 2012-2020 Leaf Corcoran
*
* @license http://opensource.org/licenses/MIT MIT
*
* @link http://scssphp.github.io/scssphp
*/
namespace ScssPhp\ScssPhp\Ast\Selector;
use ScssPhp\ScssPhp\Util;
use ScssPhp\ScssPhp\Util\EquatableUtil;
use ScssPhp\ScssPhp\Visitor\SelectorVisitor;
/**
* A pseudo-class or pseudo-element selector.
*
* The semantics of a specific pseudo selector depends on its name. Some
* selectors take arguments, including other selectors. Sass manually encodes
* logic for each pseudo selector that takes a selector as an argument, to
* ensure that extension and other selector operations work properly.
*/
final class PseudoSelector extends SimpleSelector
{
/**
* The name of this selector.
*
* @var string
* @readonly
*/
private $name;
/**
* Like {@see name}, but without any vendor prefixes.
*
* @var string
* @readonly
*/
private $normalizedName;
/**
* @var bool
* @readonly
*/
private $isClass;
/**
* @var bool
* @readonly
*/
private $isSyntacticClass;
/**
* The non-selector argument passed to this selector.
*
* This is `null` if there's no argument. If {@see argument} and {@see selector} are
* both non-`null`, the selector follows the argument.
*
* @var string|null
* @readonly
*/
private $argument;
/**
* The selector argument passed to this selector.
*
* This is `null` if there's no selector. If {@see argument} and {@see selector} are
* both non-`null`, the selector follows the argument.
*
* @var SelectorList|null
* @readonly
*/
private $selector;
/**
* @var int|null
*/
private $specificity;
public function __construct(string $name, bool $element = false, ?string $argument = null, ?SelectorList $selector = null)
{
$this->name = $name;
$this->isClass = !$element && !self::isFakePseudoElement($name);
$this->isSyntacticClass = !$element;
$this->argument = $argument;
$this->selector = $selector;
$this->normalizedName = Util::unvendor($name);
}
/**
* Returns whether $name is the name of a pseudo-element that can be written
* with pseudo-class syntax (`:before`, `:after`, `:first-line`, or
* `:first-letter`)
*/
private static function isFakePseudoElement(string $name): bool
{
if ($name === '') {
return false;
}
switch ($name[0]) {
case 'a':
case 'A':
return strtolower($name) === 'after';
case 'b':
case 'B':
return strtolower($name) === 'before';
case 'f':
case 'F':
$lowerCasedName = strtolower($name);
return $lowerCasedName === 'first-line' || $lowerCasedName === 'first-letter';
default:
return false;
}
}
public function getName(): string
{
return $this->name;
}
public function getNormalizedName(): string
{
return $this->normalizedName;
}
/**
* Whether this is a pseudo-class selector.
*
* This is `true` if and only if {@see isElement} is `false`.
*/
public function isClass(): bool
{
return $this->isClass;
}
/**
* Whether this is a pseudo-element selector.
*
* This is `true` if and only if {@see isClass} is `false`.
*/
public function isElement(): bool
{
return !$this->isClass;
}
/**
* Whether this is syntactically a pseudo-class selector.
*
* This is the same as {@see isClass} unless this selector is a pseudo-element
* that was written syntactically as a pseudo-class (`:before`, `:after`,
* `:first-line`, or `:first-letter`).
*
* This is `true` if and only if {@see isSyntacticElement} is `false`.
*/
public function isSyntacticClass(): bool
{
return $this->isSyntacticClass;
}
/**
* Whether this is syntactically a pseudo-element selector.
*
* This is `true` if and only if {@see isSyntacticClass} is `false`.
*/
public function isSyntacticElement(): bool
{
return !$this->isSyntacticClass;
}
/**
* Whether this is a valid `:host` selector.
*
* @internal
*/
public function isHost(): bool
{
return $this->isClass && $this->name === 'host';
}
/**
* Whether this is a valid `:host-context` selector.
*
* @internal
*/
public function isHostContext(): bool
{
return $this->isClass && $this->name === 'host-context' && $this->selector !== null;
}
public function getArgument(): ?string
{
return $this->argument;
}
public function getSelector(): ?SelectorList
{
return $this->selector;
}
public function getSpecificity(): int
{
if ($this->specificity === null) {
$this->specificity = $this->computeSpecificity();
}
return $this->specificity;
}
private function computeSpecificity(): int
{
if ($this->isElement()) {
return 1;
}
$selector = $this->selector;
if ($selector === null) {
return parent::getSpecificity();
}
// https://www.w3.org/TR/selectors-4/#specificity-rules
switch ($this->normalizedName) {
case 'where':
return 0;
case 'is':
case 'not':
case 'has':
case 'matches':
$maxSpecificity = 0;
foreach ($selector->getComponents() as $complex) {
$maxSpecificity = max($maxSpecificity, $complex->getSpecificity());
}
return $maxSpecificity;
case 'nth-child':
case 'nth-last-child':
$maxSpecificity = 0;
foreach ($selector->getComponents() as $complex) {
$maxSpecificity = max($maxSpecificity, $complex->getSpecificity());
}
return parent::getSpecificity() + $maxSpecificity;
default:
return parent::getSpecificity();
}
}
public function withSelector(SelectorList $selector): PseudoSelector
{
return new PseudoSelector($this->name, $this->isElement(), $this->argument, $selector);
}
public function addSuffix(string $suffix): SimpleSelector
{
if ($this->argument !== null || $this->selector !== null) {
parent::addSuffix($suffix);
}
return new PseudoSelector($this->name . $suffix, $this->isElement());
}
public function unify(array $compound): ?array
{
if ($this->name === 'host' || $this->name === 'host-context') {
foreach ($compound as $simple) {
if (!$simple instanceof PseudoSelector || (!$simple->isHost() && $simple->selector === null)) {
return null;
}
}
} elseif (\count($compound) === 1) {
$other = $compound[0];
if ($other instanceof UniversalSelector || $other instanceof PseudoSelector && ($other->isHost() || $other->isHostContext())) {
return $other->unify([$this]);
}
}
if (EquatableUtil::listContains($compound, $this)) {
return $compound;
}
$result = [];
$addedThis = false;
foreach ($compound as $simple) {
if ($simple instanceof PseudoSelector && $simple->isElement()) {
// A given compound selector may only contain one pseudo element. If
// $compound has a different one than $this, unification fails.
if ($this->isElement()) {
return null;
}
// Otherwise, this is a pseudo selector and should come before pseudo
// elements.
$result[] = $this;
$addedThis = true;
}
$result[] = $simple;
}
if (!$addedThis) {
$result[] = $this;
}
return $result;
}
public function isSuperselector(SimpleSelector $other): bool
{
if (parent::isSuperselector($other)) {
return true;
}
$selector = $this->selector;
if ($selector === null) {
return $this === $other || $this->equals($other);
}
if ($other instanceof PseudoSelector && $this->isElement() && $other->isElement() && $this->normalizedName === 'slotted' && $other->name === $this->name) {
if ($other->getSelector() !== null) {
return $selector->isSuperselector($other->getSelector());
}
return false;
}
// Fall back to the logic defined in ExtendUtil, which knows how to
// compare selector pseudoclasses against raw selectors.
return (new CompoundSelector([$this]))->isSuperselector(new CompoundSelector([$other]));
}
public function accept(SelectorVisitor $visitor)
{
return $visitor->visitPseudoSelector($this);
}
public function equals(object $other): bool
{
return $other instanceof PseudoSelector &&
$other->name === $this->name &&
$other->isClass === $this->isClass &&
$other->argument === $this->argument &&
($this->selector === $other->selector || ($this->selector !== null && $other->selector !== null && $this->selector->equals($other->selector)));
}
}