Git Komut Rehberi & Terminal Asistanı
Sıfırdan Yeni Bir Depo Başlatın:
Mevcut Bir Git Deposu veya Klasörü Gönderin:
Hataları Geri Alma, İptal & Temizlik:
Son commit'i geri al (yapılan değişiklikleri korur):
git reset --soft HEAD~1
Yapılan yerel değişiklikleri iptal et (çalışma ağacını temizler):
git restore .
Takip edilmeyen gereksiz dosyaları sil:
git clean -fd
Değişiklikleri geçici sakla ve geri getir (stash):
git stash git stash pop
Dal (Branch) ve Sürüm Etiketleri:
Yeni dal aç ve geçiş yap:
git checkout -b yeni-ozellik git push -u origin yeni-ozellik
Sürüm etiketi (tag) oluştur ve gönder:
git tag -a v1.0.0 -m "Sürüm 1.0.0" git push origin --tags
feat: implement dynamic rule discovery and incremental level filtering
This commit is contained in:
@@ -14,6 +14,11 @@ abstract class AbstractRule implements RuleInterface
|
||||
abstract public function getStandard(): WCAGStandard;
|
||||
abstract public function getSeverity(): Severity;
|
||||
|
||||
public function getLevel(): int
|
||||
{
|
||||
return 4;
|
||||
}
|
||||
|
||||
protected function createViolation(DOMElement $element, string $message, ?string $suggestion = null): Violation
|
||||
{
|
||||
$doc = $element->ownerDocument;
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace YakNet\AccessibilityConsole\Rules;
|
||||
|
||||
use DOMElement;
|
||||
use YakNet\AccessibilityConsole\Core\Severity;
|
||||
use YakNet\AccessibilityConsole\Core\Violation;
|
||||
use YakNet\AccessibilityConsole\Core\WCAGStandard;
|
||||
|
||||
class AriaFocusable extends AbstractRule
|
||||
{
|
||||
/** @var array<int, string> */
|
||||
private static array $interactiveRoles = [
|
||||
'button', 'link', 'checkbox', 'menuitem', 'radio', 'textbox',
|
||||
'searchbox', 'slider', 'spinbutton', 'switch', 'tab'
|
||||
];
|
||||
|
||||
/** @var array<int, string> */
|
||||
private static array $nativeFocusable = [
|
||||
'a', 'button', 'input', 'select', 'textarea', 'iframe'
|
||||
];
|
||||
|
||||
public function getId(): string { return 'WCAG_2_1_1_ARIA_FOCUSABLE'; }
|
||||
public function getDescription(): string { return 'Elements with interactive ARIA roles must be focusable (e.g. have a tabindex attribute).'; }
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::WARNING; }
|
||||
public function getLevel(): int { return 3; }
|
||||
|
||||
public function check(DOMElement $element): ?Violation
|
||||
{
|
||||
if (!$element->hasAttribute('role')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$role = strtolower(trim($element->getAttribute('role')));
|
||||
if (!in_array($role, self::$interactiveRoles, true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$tagName = strtolower($element->tagName);
|
||||
if (in_array($tagName, self::$nativeFocusable, true)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$element->hasAttribute('tabindex')) {
|
||||
return $this->createViolation(
|
||||
$element,
|
||||
$this->getDescription(),
|
||||
'Add tabindex="0" to make the element focusable via keyboard navigation.'
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ class AriaHiddenFocusable extends AbstractRule
|
||||
public function getDescription(): string { return 'Focusable elements must not be hidden with aria-hidden="true".'; }
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::ERROR; }
|
||||
public function getLevel(): int { return 5; }
|
||||
|
||||
public function check(DOMElement $element): ?Violation
|
||||
{
|
||||
|
||||
@@ -78,4 +78,5 @@ class AriaRole extends AbstractRule
|
||||
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::ERROR; }
|
||||
public function getLevel(): int { return 3; }
|
||||
}
|
||||
@@ -13,6 +13,7 @@ class ButtonName extends AbstractRule
|
||||
public function getDescription(): string { return 'Buttons must have discernible text.'; }
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::ERROR; }
|
||||
public function getLevel(): int { return 1; }
|
||||
|
||||
public function check(DOMElement $element): ?Violation
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@ class ColorContrast extends AbstractRule
|
||||
public function getDescription(): string { return 'Text must have sufficient contrast against the background (4.5:1 minimum).'; }
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::AA; }
|
||||
public function getSeverity(): Severity { return Severity::WARNING; }
|
||||
public function getLevel(): int { return 5; }
|
||||
|
||||
public function check(DOMElement $element): ?Violation
|
||||
{
|
||||
|
||||
@@ -48,4 +48,5 @@ class DuplicateId extends AbstractRule
|
||||
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::ERROR; }
|
||||
public function getLevel(): int { return 3; }
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace YakNet\AccessibilityConsole\Rules;
|
||||
|
||||
use DOMElement;
|
||||
use YakNet\AccessibilityConsole\Core\Severity;
|
||||
use YakNet\AccessibilityConsole\Core\Violation;
|
||||
use YakNet\AccessibilityConsole\Core\WCAGStandard;
|
||||
|
||||
class EmptyHeading extends AbstractRule
|
||||
{
|
||||
public function getId(): string { return 'WCAG_1_3_1_EMPTY_HEADING'; }
|
||||
public function getDescription(): string { return 'Heading elements (h1-h6) must have discernible text content.'; }
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::ERROR; }
|
||||
public function getLevel(): int { return 2; }
|
||||
|
||||
public function check(DOMElement $element): ?Violation
|
||||
{
|
||||
if (!preg_match('/^h([1-6])$/i', $element->tagName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$text = trim($element->textContent);
|
||||
if ($text === '' && !$element->hasAttribute('aria-label') && !$element->hasAttribute('title')) {
|
||||
return $this->createViolation(
|
||||
$element,
|
||||
$this->getDescription(),
|
||||
'Add descriptive text or an aria-label to the heading element.'
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace YakNet\AccessibilityConsole\Rules;
|
||||
|
||||
use DOMElement;
|
||||
use YakNet\AccessibilityConsole\Core\Severity;
|
||||
use YakNet\AccessibilityConsole\Core\Violation;
|
||||
use YakNet\AccessibilityConsole\Core\WCAGStandard;
|
||||
|
||||
class EmptyLabel extends AbstractRule
|
||||
{
|
||||
public function getId(): string { return 'WCAG_1_3_1_EMPTY_LABEL'; }
|
||||
public function getDescription(): string { return 'Label elements must have discernible text content.'; }
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::ERROR; }
|
||||
public function getLevel(): int { return 2; }
|
||||
|
||||
public function check(DOMElement $element): ?Violation
|
||||
{
|
||||
if (strtolower($element->tagName) !== 'label') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$text = trim($element->textContent);
|
||||
if ($text === '') {
|
||||
return $this->createViolation(
|
||||
$element,
|
||||
$this->getDescription(),
|
||||
'Add text content to the label element to describe its associated input.'
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ class EmptyLink extends AbstractRule
|
||||
public function getDescription(): string { return 'Links must have discernible text.'; }
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::ERROR; }
|
||||
public function getLevel(): int { return 1; }
|
||||
|
||||
public function check(DOMElement $element): ?Violation
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@ class FieldsetLegend extends AbstractRule
|
||||
public function getDescription(): string { return 'Fieldsets should have a legend.'; }
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::WARNING; }
|
||||
public function getLevel(): int { return 2; }
|
||||
|
||||
public function check(DOMElement $element): ?Violation
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@ class FormLabel extends AbstractRule
|
||||
public function getDescription(): string { return 'Form inputs must have an associated label.'; }
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::ERROR; }
|
||||
public function getLevel(): int { return 2; }
|
||||
|
||||
public function check(DOMElement $element): ?Violation
|
||||
{
|
||||
|
||||
@@ -15,6 +15,7 @@ class HeadingOrder extends AbstractRule
|
||||
public function getDescription(): string { return 'Headings should follow a logical nesting order.'; }
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::WARNING; }
|
||||
public function getLevel(): int { return 2; }
|
||||
|
||||
public function check(DOMElement $element): ?Violation
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@ class HtmlHasLang extends AbstractRule
|
||||
public function getDescription(): string { return 'The html element must have a lang attribute.'; }
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::ERROR; }
|
||||
public function getLevel(): int { return 1; }
|
||||
|
||||
public function check(DOMElement $element): ?Violation
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@ class IframeTitle extends AbstractRule
|
||||
public function getDescription(): string { return 'Iframe elements must have a non-empty title attribute to describe their content.'; }
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::ERROR; }
|
||||
public function getLevel(): int { return 2; }
|
||||
|
||||
public function check(DOMElement $element): ?Violation
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@ class ImgAltText extends AbstractRule
|
||||
public function getDescription(): string { return 'Images must have an alt attribute.'; }
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::ERROR; }
|
||||
public function getLevel(): int { return 1; }
|
||||
|
||||
public function check(DOMElement $element): ?Violation
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@ class PageTitle extends AbstractRule
|
||||
public function getDescription(): string { return 'Web pages must have a non-empty title element in the head.'; }
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::ERROR; }
|
||||
public function getLevel(): int { return 1; }
|
||||
|
||||
public function check(DOMElement $element): ?Violation
|
||||
{
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace YakNet\AccessibilityConsole\Rules;
|
||||
|
||||
class RuleLevels
|
||||
{
|
||||
/**
|
||||
* Get rule instances for the specified level.
|
||||
* Levels are incremental: Level 5 includes all rules from Levels 1, 2, 3, and 4.
|
||||
*
|
||||
* @param int $level
|
||||
* @return array<int, \YakNet\AccessibilityConsole\Rules\RuleInterface|\YakNet\AccessibilityConsole\Core\AbstractRule>
|
||||
*/
|
||||
public static function getRulesForLevel(int $level): array
|
||||
{
|
||||
$rules = [];
|
||||
$files = glob(__DIR__ . '/*.php');
|
||||
if ($files === false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach ($files as $file) {
|
||||
$className = pathinfo($file, PATHINFO_FILENAME);
|
||||
$fqcn = 'YakNet\AccessibilityConsole\Rules\\' . $className;
|
||||
|
||||
if (class_exists($fqcn)) {
|
||||
$reflection = new \ReflectionClass($fqcn);
|
||||
|
||||
if ($reflection->isAbstract() || $reflection->isInterface() || $reflection->isTrait()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if it is a concrete rule
|
||||
$isRule = $reflection->isSubclassOf(\YakNet\AccessibilityConsole\Core\AbstractRule::class) ||
|
||||
$reflection->implementsInterface(\YakNet\AccessibilityConsole\Rules\RuleInterface::class);
|
||||
|
||||
if ($isRule) {
|
||||
// Exclude Turkish custom rules to match standard behavior
|
||||
if (in_array($className, ['EmptyLinkRule', 'HeadingOrderRule', 'ImageAltRule'], true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$instance = $reflection->newInstance();
|
||||
|
||||
// Check level
|
||||
$ruleLevel = 4; // default
|
||||
if (method_exists($instance, 'getLevel')) {
|
||||
/** @var mixed $levelVal */
|
||||
$levelVal = $instance->getLevel();
|
||||
if (is_int($levelVal)) {
|
||||
$ruleLevel = $levelVal;
|
||||
}
|
||||
}
|
||||
|
||||
if ($ruleLevel <= $level) {
|
||||
/** @var \YakNet\AccessibilityConsole\Rules\RuleInterface|\YakNet\AccessibilityConsole\Core\AbstractRule $instance */
|
||||
$rules[] = $instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort rules by class name to ensure deterministic order
|
||||
usort($rules, function ($a, $b) {
|
||||
return strcmp(get_class($a), get_class($b));
|
||||
});
|
||||
|
||||
return $rules;
|
||||
}
|
||||
}
|
||||
@@ -5,31 +5,10 @@ namespace YakNet\AccessibilityConsole\Rules;
|
||||
class StandardRuleSet
|
||||
{
|
||||
/**
|
||||
* @return RuleInterface[]
|
||||
* @return array<int, \YakNet\AccessibilityConsole\Rules\RuleInterface|\YakNet\AccessibilityConsole\Core\AbstractRule>
|
||||
*/
|
||||
public static function all(): array
|
||||
{
|
||||
/** @var RuleInterface[] $rules */
|
||||
$rules = [
|
||||
new HtmlHasLang(),
|
||||
new ImgAltText(),
|
||||
new EmptyLink(),
|
||||
new ButtonName(),
|
||||
new FormLabel(),
|
||||
new FieldsetLegend(),
|
||||
new HeadingOrder(),
|
||||
new MetaViewport(),
|
||||
new TabindexOrder(),
|
||||
new ColorContrast(),
|
||||
new DuplicateId(),
|
||||
new AriaRole(),
|
||||
new IframeTitle(),
|
||||
new LinkTextGeneric(),
|
||||
new PlaceholderAsLabel(),
|
||||
new ImageAltRedundant(),
|
||||
new AriaHiddenFocusable(),
|
||||
new PageTitle(),
|
||||
];
|
||||
return $rules;
|
||||
return RuleLevels::getRulesForLevel(5);
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ class TabindexOrder extends AbstractRule
|
||||
public function getDescription(): string { return 'Avoid positive tabindex values to maintain natural keyboard focus order.'; }
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::WARNING; }
|
||||
public function getLevel(): int { return 3; }
|
||||
|
||||
public function check(DOMElement $element): ?Violation
|
||||
{
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace YakNet\AccessibilityConsole\Rules;
|
||||
|
||||
use DOMElement;
|
||||
use YakNet\AccessibilityConsole\Core\Severity;
|
||||
use YakNet\AccessibilityConsole\Core\Violation;
|
||||
use YakNet\AccessibilityConsole\Core\WCAGStandard;
|
||||
|
||||
class VideoTrack extends AbstractRule
|
||||
{
|
||||
public function getId(): string { return 'WCAG_1_2_2_VIDEO_TRACK'; }
|
||||
public function getDescription(): string { return 'Video elements must contain at least one track element for captions or subtitles.'; }
|
||||
public function getStandard(): WCAGStandard { return WCAGStandard::A; }
|
||||
public function getSeverity(): Severity { return Severity::WARNING; }
|
||||
public function getLevel(): int { return 2; }
|
||||
|
||||
public function check(DOMElement $element): ?Violation
|
||||
{
|
||||
if (strtolower($element->tagName) !== 'video') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if there is a track element inside
|
||||
$tracks = $element->getElementsByTagName('track');
|
||||
if ($tracks->length === 0) {
|
||||
return $this->createViolation(
|
||||
$element,
|
||||
$this->getDescription(),
|
||||
'Add a <track kind="captions"> or <track kind="subtitles"> element inside the video element.'
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user