@@ -43,6 +43,8 @@ graph TB
4343 PartialDetector[Partial Detection]
4444 ChangeDetector[Change Detector]
4545 Cache[Page Scanning Cache]
46+ StaticScanner[Static File Scanner]
47+ FileChangeTracker[File Change Tracker]
4648 end
4749
4850 subgraph "Configuration"
@@ -91,12 +93,22 @@ graph TB
9193 CLI --> RuleEngine
9294 Routes --> ViewDetector
9395
96+ StaticScanner --> FileChangeTracker
97+ StaticScanner --> ErbExtractor[ErbExtractor]
98+ ErbExtractor --> StaticAdapter[StaticPageAdapter]
99+ StaticAdapter --> RuleEngine
100+ StaticScanner --> LineFinder[LineNumberFinder]
101+ StaticScanner --> ViolationConverter[ViolationConverter]
102+ ViolationConverter --> ErrorBuilder
103+
94104 style Entry fill:#ff6b6b
95105 style RuleEngine fill:#4ecdc4
96106 style Checks fill:#45b7d1
97107 style ViewDetector fill:#96ceb4
98108 style ErrorBuilder fill:#ffeaa7
99109 style Cache fill:#a29bfe
110+ style StaticScanner fill:#feca57
111+ style FileChangeTracker fill:#ff9ff3
100112```
101113
102114---
@@ -529,5 +541,145 @@ The Rails Accessibility Testing gem provides:
529541
530542** For more details, see the [ ARCHITECTURE.md] ( https://github.com/rayraycodes/rails-accessibility-testing/blob/main/ARCHITECTURE.md ) file in the repository.**
531543
532- ** Version** : 1.5.0
544+ ---
545+
546+ ## Static Scanning System (NEW in 1.5.3+)
547+
548+ The static scanning system allows scanning view files directly without browser rendering, providing fast feedback during development. It's the recommended approach for continuous development testing via ` bin/dev ` .
549+
550+ ### Architecture Overview
551+
552+ The static scanner uses a modular, pipeline-based architecture:
553+
554+ ```
555+ ERB Template → ErbExtractor → HTML → StaticPageAdapter → RuleEngine → Checks → Violations → ViolationConverter → Errors/Warnings
556+ ```
557+
558+ ### Components
559+
560+ 1 . ** StaticFileScanner** - Main orchestrator
561+ - Reads ERB template files from ` app/views/**/*.html.erb `
562+ - Coordinates the entire scanning pipeline
563+ - Loads configuration via ` YamlLoader `
564+ - Creates ` RuleEngine ` instance with config
565+ - Returns structured hash: ` { errors: [...], warnings: [...] } `
566+
567+ 2 . ** FileChangeTracker** - Change detection for static files
568+ - ** State File** : ` tmp/.rails_a11y_scanned_files.json `
569+ - ** Purpose** : Tracks file modification times (mtime) to detect changes
570+ - ** Methods** :
571+ - ` load_state ` : Reads JSON state file, returns hash of ` { file_path => mtime } `
572+ - ` changed_files(files) ` : Compares current mtimes with stored state, returns changed/new files
573+ - ` update_state(files) ` : Updates state file with current mtimes (atomic write)
574+ - ` clear_state ` : Clears state file (forces full rescan)
575+ - ** Atomic Writes** : Uses temp file + ` mv ` to prevent partial writes
576+
577+ 3 . ** ErbExtractor** - ERB to HTML conversion
578+ - ** Purpose** : Converts Rails helpers to HTML placeholders for static analysis
579+ - ** Supported Helpers** : Form helpers, images, links, buttons (15+ helpers)
580+ - ** Process** : Convert helpers → Remove ERB tags → Clean whitespace
581+
582+ 4 . ** StaticPageAdapter** - Capybara compatibility layer
583+ - ** Purpose** : Makes Nokogiri documents look like Capybara pages
584+ - ** Key Methods** : ` all(selector) ` , ` has_css?(selector) ` , etc.
585+ - ** Benefit** : Allows reuse of existing checks without modification
586+
587+ 5 . ** LineNumberFinder** - Precise error location
588+ - ** Purpose** : Maps HTML elements back to original ERB line numbers
589+ - ** Matching Strategy** : id → src → href → type → tag name
590+ - ** Returns** : 1-indexed line number or ` nil `
591+
592+ 6 . ** ViolationConverter** - Result formatting
593+ - ** Purpose** : Converts raw ` Violation ` objects to structured errors/warnings
594+ - ** Process** : Find line numbers → Categorize → Filter warnings if ` ignore_warnings: true `
595+ - ** Warning Detection** : Skip links and ARIA landmarks → warnings
596+
597+ ### Static Scanner Flow
598+
599+ ``` mermaid
600+ graph TB
601+ Start[a11y_static_scanner Startup] --> LoadConfig[Load accessibility.yml]
602+ LoadConfig --> LoadState[FileChangeTracker.load_state]
603+ LoadState --> GetFiles[Get all view files]
604+ GetFiles --> Decision{scan_changed_only?}
605+
606+ Decision -->|false| ScanAll[Scan ALL files]
607+ Decision -->|true| CheckStartup{full_scan_on_startup?}
608+
609+ CheckStartup -->|true| ScanAll
610+ CheckStartup -->|false| CheckChanged[FileChangeTracker.changed_files]
611+
612+ CheckChanged --> ChangedEmpty{Changed files empty?}
613+ ChangedEmpty -->|yes| WatchLoop[Enter watch loop]
614+ ChangedEmpty -->|no| ScanChanged[Scan changed files only]
615+
616+ ScanAll --> ScanFile[For each file: StaticFileScanner.scan]
617+ ScanChanged --> ScanFile
618+
619+ ScanFile --> ReadFile[Read ERB file content]
620+ ReadFile --> Extract[ErbExtractor.extract_html]
621+ Extract --> Parse[Nokogiri.parse HTML]
622+ Parse --> Adapter[StaticPageAdapter.new]
623+ Adapter --> Engine[RuleEngine.new]
624+ Engine --> RunChecks[Run all 11 enabled checks]
625+ RunChecks --> Convert[ViolationConverter.convert]
626+ Convert --> FindLines[LineNumberFinder.find_line_number]
627+ FindLines --> Filter{ignore_warnings?}
628+ Filter -->|true| RemoveWarnings[Remove warnings]
629+ Filter -->|false| KeepAll[Keep errors + warnings]
630+ RemoveWarnings --> Format[Format errors/warnings]
631+ KeepAll --> Format
632+ Format --> UpdateState[FileChangeTracker.update_state]
633+ UpdateState --> Output[Display results]
634+
635+ Output --> Continuous{scan_changed_only?}
636+ Continuous -->|true| WatchLoop
637+ Continuous -->|false| Exit[Exit]
638+
639+ WatchLoop --> Sleep[sleep check_interval]
640+ Sleep --> CheckAgain[FileChangeTracker.changed_files]
641+ CheckAgain --> HasChanges{Has changes?}
642+ HasChanges -->|yes| ScanChanged
643+ HasChanges -->|no| Sleep
644+
645+ style Start fill:#ff6b6b
646+ style ScanFile fill:#4ecdc4
647+ style Extract fill:#ff9ff3
648+ style Adapter fill:#48dbfb
649+ style Engine fill:#feca57
650+ style FindLines fill:#ff6b6b
651+ style Format fill:#ffeaa7
652+ ```
653+
654+ ### Configuration Flags
655+
656+ All configuration is done via ` config/accessibility.yml ` :
657+
658+ #### Static Scanner Configuration (` static_scanner ` )
659+
660+ | Flag | Type | Default | Description |
661+ | ------| ------| ---------| -------------|
662+ | ` scan_changed_only ` | Boolean | ` true ` | Only scan files that have changed since last scan |
663+ | ` check_interval ` | Integer | ` 3 ` | Seconds between file change checks when running continuously |
664+ | ` full_scan_on_startup ` | Boolean | ` true ` | Force full scan of all files on startup |
665+
666+ #### Summary Configuration (` summary ` )
667+
668+ | Flag | Type | Default | Description |
669+ | ------| ------| ---------| -------------|
670+ | ` ignore_warnings ` | Boolean | ` false ` | Filter out warnings completely - only show errors |
671+ | ` show_summary ` | Boolean | ` true ` | Show summary at end of test suite (RSpec only) |
672+ | ` errors_only ` | Boolean | ` false ` | Show only errors in summary, hide warnings (RSpec only) |
673+ | ` show_fixes ` | Boolean | ` true ` | Show fix suggestions in error messages |
674+
675+ ### Benefits
676+
677+ - ** Fast** : No browser needed - scans ERB templates directly (~ 10-100x faster)
678+ - ** Precise** : Reports exact file locations and line numbers
679+ - ** Efficient** : Only scans changed files using modification time tracking
680+ - ** Continuous** : Runs continuously, watching for file changes
681+ - ** Reusable** : Leverages existing RuleEngine and all 11 checks
682+ - ** Configurable** : Full control via YAML with profile support
683+
684+ ** Version** : 1.5.5
533685** Last Updated** : 2025-11-20
0 commit comments