From 5292c2c8654e2db8ce05236be10cb6a70c024c41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enes=20Yak=C4=B1=C5=9Ft=C4=B1r?= Date: Mon, 13 Jul 2026 10:54:25 +0300 Subject: [PATCH] release: v3.1.0 premium smart core engines and 14 new accessibility rules --- .gitignore | 2 + bin/a11y | 14 +- composer.json | 12 +- demo/test_local_fix.html | 10 ++ phpunit.xml | 18 +++ src/AI/AiFixerManager.php | 73 ++++++++++ src/AI/AiProviderInterface.php | 23 +++ src/AI/PromptBuilder.php | 27 ++++ src/AI/Providers/ClaudeProvider.php | 62 ++++++++ src/AI/Providers/GeminiProvider.php | 45 ++++++ src/AI/Providers/MockAiProvider.php | 49 +++++++ src/AI/Providers/OpenAiProvider.php | 61 ++++++++ src/Console/Command/FixCommand.php | 77 +++++++--- src/Console/Command/ScanCommand.php | 23 +++ src/Core/Baseline/FuzzyBaselineMatcher.php | 66 +++++++++ src/Core/BaselineManager.php | 6 +- src/Core/Config/SmartPresetBuilder.php | 44 ++++++ src/Core/Container/ContainerFactory.php | 58 ++++++++ src/Core/Diff/VisualDiffGenerator.php | 46 ++++++ .../Metrics/AccessibilityScoreCalculator.php | 95 ++++++++++++ src/Core/SelfHealing/LocalHealer.php | 65 +++++++++ src/Parser/BladeParser.php | 20 +++ src/Parser/HtmlParser.php | 11 ++ src/Parser/ParserInterface.php | 15 ++ src/Parser/TwigParser.php | 20 +++ src/Reference/WcagCriterion.php | 32 +++++ src/Reference/WcagDatabase.php | 136 ++++++++++++++++++ src/Reference/WcagReference.php | 27 ++++ src/Reporting/CsvFormatter.php | 44 ++++++ src/Reporting/GitLabCodeQualityFormatter.php | 46 ++++++ src/Reporting/MarkdownFormatter.php | 46 ++++++ src/Reporting/XmlFormatter.php | 57 ++++++++ src/Rules/AriaLabelledbyExists.php | 57 ++++++++ src/Rules/AutocompleteValid.php | 56 ++++++++ src/Rules/AutofocusBanned.php | 30 ++++ src/Rules/BannedSelectors.php | 49 +++++++ src/Rules/BlinkTagBanned.php | 30 ++++ src/Rules/DuplicateH1.php | 38 +++++ src/Rules/FormSubmitButton.php | 56 ++++++++ src/Rules/ImageAltLong.php | 39 +++++ src/Rules/ImageAltPlaceholder.php | 59 ++++++++ src/Rules/LinkTextUrl.php | 35 +++++ src/Rules/ListStructure.php | 40 ++++++ src/Rules/PresentationTagsBanned.php | 32 +++++ src/Rules/ScrollableRegionFocusable.php | 33 +++++ src/Rules/SvgAltText.php | 49 +++++++ src/Rules/TableSummaryBanned.php | 34 +++++ src/accessibility-console.php | 77 ++++++++++ .../Core/AccessibilityScoreCalculatorTest.php | 29 ++++ tests/Core/FuzzyBaselineMatcherTest.php | 25 ++++ tests/Core/LocalHealerTest.php | 44 ++++++ tests/Core/VisualDiffGeneratorTest.php | 23 +++ tests/Rules/AriaLabelledbyExistsTest.php | 29 ++++ tests/Rules/AutocompleteValidTest.php | 38 +++++ tests/Rules/AutofocusBannedTest.php | 29 ++++ tests/Rules/BannedSelectorsTest.php | 31 ++++ tests/Rules/BlinkTagBannedTest.php | 29 ++++ tests/Rules/DuplicateH1Test.php | 29 ++++ tests/Rules/FormSubmitButtonTest.php | 38 +++++ tests/Rules/ImageAltLongTest.php | 30 ++++ tests/Rules/ImageAltPlaceholderTest.php | 48 +++++++ tests/Rules/LinkTextUrlTest.php | 29 ++++ tests/Rules/ListStructureTest.php | 29 ++++ tests/Rules/PresentationTagsBannedTest.php | 30 ++++ tests/Rules/ScrollableRegionFocusableTest.php | 38 +++++ tests/Rules/SvgAltTextTest.php | 47 ++++++ tests/Rules/TableSummaryBannedTest.php | 29 ++++ 67 files changed, 2639 insertions(+), 29 deletions(-) create mode 100644 demo/test_local_fix.html create mode 100644 phpunit.xml create mode 100644 src/AI/AiFixerManager.php create mode 100644 src/AI/AiProviderInterface.php create mode 100644 src/AI/PromptBuilder.php create mode 100644 src/AI/Providers/ClaudeProvider.php create mode 100644 src/AI/Providers/GeminiProvider.php create mode 100644 src/AI/Providers/MockAiProvider.php create mode 100644 src/AI/Providers/OpenAiProvider.php create mode 100644 src/Core/Baseline/FuzzyBaselineMatcher.php create mode 100644 src/Core/Config/SmartPresetBuilder.php create mode 100644 src/Core/Container/ContainerFactory.php create mode 100644 src/Core/Diff/VisualDiffGenerator.php create mode 100644 src/Core/Metrics/AccessibilityScoreCalculator.php create mode 100644 src/Core/SelfHealing/LocalHealer.php create mode 100644 src/Parser/BladeParser.php create mode 100644 src/Parser/HtmlParser.php create mode 100644 src/Parser/ParserInterface.php create mode 100644 src/Parser/TwigParser.php create mode 100644 src/Reference/WcagCriterion.php create mode 100644 src/Reference/WcagDatabase.php create mode 100644 src/Reference/WcagReference.php create mode 100644 src/Reporting/CsvFormatter.php create mode 100644 src/Reporting/GitLabCodeQualityFormatter.php create mode 100644 src/Reporting/MarkdownFormatter.php create mode 100644 src/Reporting/XmlFormatter.php create mode 100644 src/Rules/AriaLabelledbyExists.php create mode 100644 src/Rules/AutocompleteValid.php create mode 100644 src/Rules/AutofocusBanned.php create mode 100644 src/Rules/BannedSelectors.php create mode 100644 src/Rules/BlinkTagBanned.php create mode 100644 src/Rules/DuplicateH1.php create mode 100644 src/Rules/FormSubmitButton.php create mode 100644 src/Rules/ImageAltLong.php create mode 100644 src/Rules/ImageAltPlaceholder.php create mode 100644 src/Rules/LinkTextUrl.php create mode 100644 src/Rules/ListStructure.php create mode 100644 src/Rules/PresentationTagsBanned.php create mode 100644 src/Rules/ScrollableRegionFocusable.php create mode 100644 src/Rules/SvgAltText.php create mode 100644 src/Rules/TableSummaryBanned.php create mode 100644 src/accessibility-console.php create mode 100644 tests/Core/AccessibilityScoreCalculatorTest.php create mode 100644 tests/Core/FuzzyBaselineMatcherTest.php create mode 100644 tests/Core/LocalHealerTest.php create mode 100644 tests/Core/VisualDiffGeneratorTest.php create mode 100644 tests/Rules/AriaLabelledbyExistsTest.php create mode 100644 tests/Rules/AutocompleteValidTest.php create mode 100644 tests/Rules/AutofocusBannedTest.php create mode 100644 tests/Rules/BannedSelectorsTest.php create mode 100644 tests/Rules/BlinkTagBannedTest.php create mode 100644 tests/Rules/DuplicateH1Test.php create mode 100644 tests/Rules/FormSubmitButtonTest.php create mode 100644 tests/Rules/ImageAltLongTest.php create mode 100644 tests/Rules/ImageAltPlaceholderTest.php create mode 100644 tests/Rules/LinkTextUrlTest.php create mode 100644 tests/Rules/ListStructureTest.php create mode 100644 tests/Rules/PresentationTagsBannedTest.php create mode 100644 tests/Rules/ScrollableRegionFocusableTest.php create mode 100644 tests/Rules/SvgAltTextTest.php create mode 100644 tests/Rules/TableSummaryBannedTest.php diff --git a/.gitignore b/.gitignore index 9805767..ebc698b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ /rapor.html /.phpunit.result.cache /phpstan-baseline.neon +/.phpunit.cache/ + diff --git a/bin/a11y b/bin/a11y index 4ce6734..112780e 100644 --- a/bin/a11y +++ b/bin/a11y @@ -11,17 +11,19 @@ if (file_exists(__DIR__ . '/../vendor/autoload.php')) { } use Symfony\Component\Console\Application; +use YakNet\AccessibilityConsole\Core\Container\ContainerFactory; use YakNet\AccessibilityConsole\Console\Command\ScanCommand; use YakNet\AccessibilityConsole\Console\Command\FixCommand; use YakNet\AccessibilityConsole\Console\Command\InitCommand; use YakNet\AccessibilityConsole\Console\Command\RulesCommand; -$application = new Application('YakNet Accessibility Console', '3.0.1'); +$container = ContainerFactory::create(); +$application = new Application('YakNet Accessibility Console', '3.1.0'); -// Add commands -$application->addCommand(new ScanCommand()); -$application->addCommand(new FixCommand()); -$application->addCommand(new InitCommand()); -$application->addCommand(new RulesCommand()); +// Add commands resolved via DI Container +$application->add($container->get(ScanCommand::class)); +$application->add($container->get(FixCommand::class)); +$application->add($container->get(InitCommand::class)); +$application->add($container->get(RulesCommand::class)); $application->run(); diff --git a/composer.json b/composer.json index d5ae740..a5fb7e3 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,7 @@ "yaknet" ], "homepage": "https://forum.yak.net.tr", - "version": "3.0.5", + "version": "3.1.0", "type": "library", "license": "MIT", "authors": [ @@ -35,13 +35,20 @@ "guzzlehttp/guzzle": "^7.8", "masterminds/html5": "^2.8", "sabberworm/php-css-parser": "^9.4", - "chrome-php/chrome": "^1.15" + "chrome-php/chrome": "^1.15", + "sebastian/diff": "^5.1", + "php-di/php-di": "^7.1", + "symfony/css-selector": "^8.1", + "symfony/dom-crawler": "^8.1" }, "require-dev": { "phpunit/phpunit": "^10.5", "phpstan/phpstan": "^1.10" }, "autoload": { + "files": [ + "src/accessibility-console.php" + ], "psr-4": { "YakNet\\AccessibilityConsole\\": "src/" } @@ -57,4 +64,3 @@ } } } - diff --git a/demo/test_local_fix.html b/demo/test_local_fix.html new file mode 100644 index 0000000..a18986d --- /dev/null +++ b/demo/test_local_fix.html @@ -0,0 +1,10 @@ + + + + Test Local Fix + + + Hello blink + + + diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..a0d4146 --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,18 @@ + + + + + tests + + + diff --git a/src/AI/AiFixerManager.php b/src/AI/AiFixerManager.php new file mode 100644 index 0000000..9b86d1a --- /dev/null +++ b/src/AI/AiFixerManager.php @@ -0,0 +1,73 @@ + */ + private array $providers = []; + private ?string $activeProviderName = null; + + public function __construct(?string $geminiApiKey = null, ?string $openAiApiKey = null, ?string $claudeApiKey = null) + { + // Register default providers + if ($geminiApiKey) { + $this->registerProvider('gemini', new GeminiProvider($geminiApiKey)); + $this->activeProviderName = 'gemini'; + } + if ($openAiApiKey) { + $this->registerProvider('openai', new OpenAiProvider($openAiApiKey)); + if (!$this->activeProviderName) { + $this->activeProviderName = 'openai'; + } + } + if ($claudeApiKey) { + $this->registerProvider('claude', new ClaudeProvider($claudeApiKey)); + if (!$this->activeProviderName) { + $this->activeProviderName = 'claude'; + } + } + + // Always register mock provider as fallback/testing option + $this->registerProvider('mock', new MockAiProvider()); + if (!$this->activeProviderName) { + $this->activeProviderName = 'mock'; + } + } + + public function registerProvider(string $name, AiProviderInterface $provider): void + { + $this->providers[strtolower($name)] = $provider; + } + + public function setActiveProvider(string $name): void + { + $key = strtolower($name); + if (isset($this->providers[$key])) { + $this->activeProviderName = $key; + } + } + + public function getActiveProvider(): ?AiProviderInterface + { + if ($this->activeProviderName && isset($this->providers[$this->activeProviderName])) { + return $this->providers[$this->activeProviderName]; + } + return null; + } + + public function suggestFix(Violation $violation): ?string + { + $provider = $this->getActiveProvider(); + if ($provider) { + return $provider->suggestFix($violation); + } + return "No active AI provider configured."; + } +} diff --git a/src/AI/AiProviderInterface.php b/src/AI/AiProviderInterface.php new file mode 100644 index 0000000..aaf1d0c --- /dev/null +++ b/src/AI/AiProviderInterface.php @@ -0,0 +1,23 @@ +htmlSnippet} +Error Message: {$violation->message} +Rule ID: {$violation->ruleId} +Severity: {$violation->severity->value} +Standard: {$violation->standard->value} + +Please provide a short explanation of the fix in Turkish and the fixed version of the HTML snippet. +Use the following EXACT format: +EXPLANATION: [A short explanation of what you fixed and why, in Turkish] +FIX: [Only the corrected HTML snippet, no surrounding explanation] +PROMPT; + } +} diff --git a/src/AI/Providers/ClaudeProvider.php b/src/AI/Providers/ClaudeProvider.php new file mode 100644 index 0000000..b9a15e3 --- /dev/null +++ b/src/AI/Providers/ClaudeProvider.php @@ -0,0 +1,62 @@ +apiKey) { + $this->client = new GuzzleClient([ + 'base_uri' => 'https://api.anthropic.com/v1/', + 'headers' => [ + 'x-api-key' => $this->apiKey, + 'anthropic-version' => '2023-06-01', + 'Content-Type' => 'application/json', + ], + 'timeout' => 10.0, + ]); + } + } + + public function getName(): string + { + return 'Anthropic Claude'; + } + + public function suggestFix(Violation $violation): ?string + { + if (!$this->client) { + return null; + } + + $prompt = PromptBuilder::build($violation); + + try { + $response = $this->client->post('messages', [ + 'json' => [ + 'model' => 'claude-3-5-haiku-20241022', + 'max_tokens' => 1000, + 'messages' => [ + ['role' => 'user', 'content' => $prompt] + ], + ], + ]); + + $data = json_decode($response->getBody()->getContents(), true); + if (is_array($data) && isset($data['content'][0]['text']) && is_string($data['content'][0]['text'])) { + return $data['content'][0]['text']; + } + return null; + } catch (\Throwable $e) { + return "Claude Error: " . $e->getMessage(); + } + } +} diff --git a/src/AI/Providers/GeminiProvider.php b/src/AI/Providers/GeminiProvider.php new file mode 100644 index 0000000..0520e5e --- /dev/null +++ b/src/AI/Providers/GeminiProvider.php @@ -0,0 +1,45 @@ +apiKey) { + try { + $this->client = \Gemini::client($this->apiKey); + } catch (\Throwable) { + $this->client = null; + } + } + } + + public function getName(): string + { + return 'Google Gemini'; + } + + public function suggestFix(Violation $violation): ?string + { + if (!$this->apiKey || !$this->client) { + return null; + } + + $prompt = PromptBuilder::build($violation); + + try { + $result = $this->client->generativeModel('gemini-3.1-flash-lite')->generateContent($prompt); + return trim($result->text()); + } catch (\Throwable $e) { + return "Gemini Error: " . $e->getMessage(); + } + } +} diff --git a/src/AI/Providers/MockAiProvider.php b/src/AI/Providers/MockAiProvider.php new file mode 100644 index 0000000..700d956 --- /dev/null +++ b/src/AI/Providers/MockAiProvider.php @@ -0,0 +1,49 @@ +htmlSnippet; + $fixedSnippet = $snippet; + + // Simple mock fixes based on rule ID + if ($violation->ruleId === 'WCAG_2_2_2_BLINK') { + $fixedSnippet = str_replace(['', ''], ['', ''], $snippet); + } elseif ($violation->ruleId === 'WCAG_1_3_1_DUPLICATE_H1') { + $fixedSnippet = str_replace(['

', '

'], ['

', '

'], $snippet); + } elseif ($violation->ruleId === 'WCAG_1_3_5_AUTOCOMPLETE') { + $fixedSnippet = str_replace('ruleId === 'WCAG_2_1_1_SCROLLABLE_FOCUS') { + $fixedSnippet = str_replace('ruleId === 'WCAG_4_1_2_ARIA_LABELLEDBY') { + $fixedSnippet = str_replace('aria-labelledby="non_existent_label"', 'aria-labelledby="real_label"', $snippet); + } elseif ($violation->ruleId === 'WCAG_1_1_1_ALT_PLACEHOLDER') { + if (str_contains($snippet, 'alt="logo"')) { + $fixedSnippet = str_replace('alt="logo"', 'alt="YakNet Accessibility Console Company Logo"', $snippet); + } else { + $fixedSnippet = str_replace('alt="header_banner.jpg"', 'alt="YakNet Accessibility Console Marketing Banner"', $snippet); + } + } elseif ($violation->ruleId === 'WCAG_1_1_1_SVG') { + $fixedSnippet = str_replace('