diff --git a/.erb-lint.yml b/.erb-lint.yml new file mode 100644 index 0000000..be2bc7e --- /dev/null +++ b/.erb-lint.yml @@ -0,0 +1,4 @@ +--- +inherit_gem: + erblint-github: + - config/accessibility.yml diff --git a/.erb-linters/linters.rb b/.erb-linters/linters.rb new file mode 100644 index 0000000..c0e4b62 --- /dev/null +++ b/.erb-linters/linters.rb @@ -0,0 +1 @@ +require "erblint-github/linters" diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 7b3eb46..1b0fe54 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,7 +4,21 @@ updates: directory: "/" schedule: interval: weekly + groups: + all-dependencies: + patterns: + - "*" + update-types: + - "minor" + - "patch" - package-ecosystem: github-actions directory: "/" schedule: interval: weekly + groups: + all-dependencies: + patterns: + - "*" + update-types: + - "minor" + - "patch" \ No newline at end of file diff --git a/.github/workflows/accessibility-alt-text-bot.yml b/.github/workflows/accessibility-alt-text-bot.yml index 4b3c2b3..084a210 100644 --- a/.github/workflows/accessibility-alt-text-bot.yml +++ b/.github/workflows/accessibility-alt-text-bot.yml @@ -23,4 +23,4 @@ jobs: if: ${{ github.event.issue || github.event.pull_request || github.event.discussion }} steps: - name: Get action 'github/accessibility-alt-text-bot' - uses: github/accessibility-alt-text-bot@v1.2.0 + uses: github/accessibility-alt-text-bot@v1.7.1 diff --git a/.github/workflows/auto-approve-and-merge.yml b/.github/workflows/auto-approve-and-merge.yml new file mode 100644 index 0000000..2c7fa07 --- /dev/null +++ b/.github/workflows/auto-approve-and-merge.yml @@ -0,0 +1,28 @@ +name: Auto-approve and merge PR +on: pull_request + +permissions: + contents: write + pull-requests: write + +jobs: + auto_approve: + runs-on: ubuntu-latest + if: ${{ github.actor == 'dependabot[bot]' }} + steps: + - name: Approve PR + uses: actions/github-script@v7 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + github.rest.pulls.createReview({ + owner: context.payload.repository.owner.login, + repo: context.payload.repository.name, + pull_number: context.payload.pull_request.number, + event: "APPROVE", + }) + - name: Enable auto-merge for PR + run: gh pr merge --auto --merge "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index da478ad..bc3b0e2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,13 +2,17 @@ name: CI on: [push, pull_request] +permissions: + contents: write + pull-requests: write + jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Ruby - uses: ruby/setup-ruby@v1 + uses: ruby/setup-ruby@a4effe49ee8ee5b8b5091268c473a4628afb5651 with: ruby-version: '3.0' bundler-cache: true @@ -17,17 +21,13 @@ jobs: bundle install bundle exec rubocop test: - strategy: - fail-fast: false - matrix: - ruby_version: ["2.7", "3.0"] runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Ruby - uses: ruby/setup-ruby@v1 + uses: ruby/setup-ruby@a4effe49ee8ee5b8b5091268c473a4628afb5651 with: - ruby-version: ${{ matrix.ruby_version }} + ruby-version: '3.0' bundler-cache: true - name: Install dependencies run: bundle install @@ -36,9 +36,9 @@ jobs: docs-coverage: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Ruby - uses: ruby/setup-ruby@v1 + uses: ruby/setup-ruby@a4effe49ee8ee5b8b5091268c473a4628afb5651 with: ruby-version: '3.0' bundler-cache: true @@ -49,9 +49,9 @@ jobs: tests-coverage: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup Ruby - uses: ruby/setup-ruby@v1 + uses: ruby/setup-ruby@a4effe49ee8ee5b8b5091268c473a4628afb5651 with: ruby-version: '3.0' bundler-cache: true diff --git a/Gemfile.lock b/Gemfile.lock index eecdbe3..38f2342 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,110 +1,137 @@ PATH remote: . specs: - erblint-github (0.4.0) + erblint-github (1.0.1) GEM remote: https://rubygems.org/ specs: - actionview (7.0.5) - activesupport (= 7.0.5) + actionview (7.1.5.1) + activesupport (= 7.1.5.1) builder (~> 3.1) - erubi (~> 1.4) - rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.1, >= 1.2.0) - activesupport (7.0.5) + erubi (~> 1.11) + rails-dom-testing (~> 2.2) + rails-html-sanitizer (~> 1.6) + activesupport (7.1.5.1) + base64 + benchmark (>= 0.3) + bigdecimal concurrent-ruby (~> 1.0, >= 1.0.2) + connection_pool (>= 2.2.5) + drb i18n (>= 1.6, < 2) + logger (>= 1.4.2) minitest (>= 5.1) + mutex_m + securerandom (>= 0.3) tzinfo (~> 2.0) - ast (2.4.2) - better_html (2.0.1) + ast (2.4.3) + base64 (0.2.0) + benchmark (0.4.0) + better_html (2.1.1) actionview (>= 6.0) activesupport (>= 6.0) ast (~> 2.0) erubi (~> 1.4) parser (>= 2.4) smart_properties - builder (3.2.4) - concurrent-ruby (1.2.2) + bigdecimal (3.1.9) + builder (3.3.0) + concurrent-ruby (1.3.5) + connection_pool (2.5.0) crass (1.0.6) - erb_lint (0.4.0) + drb (2.2.1) + erb_lint (0.9.0) activesupport better_html (>= 2.0.1) parser (>= 2.7.1.4) rainbow - rubocop + rubocop (>= 1) smart_properties - erubi (1.12.0) - i18n (1.13.0) + erubi (1.13.1) + i18n (1.14.7) concurrent-ruby (~> 1.0) - json (2.6.3) - loofah (2.21.3) + json (2.12.2) + language_server-protocol (3.17.0.5) + lint_roller (1.1.0) + logger (1.6.6) + loofah (2.24.0) crass (~> 1.0.2) nokogiri (>= 1.12.0) - mini_portile2 (2.8.2) - minitest (5.18.0) - mocha (2.0.2) + mini_portile2 (2.8.8) + minitest (5.25.5) + mocha (2.7.1) ruby2_keywords (>= 0.0.5) - nokogiri (1.15.2) + mutex_m (0.3.0) + nokogiri (1.17.2) mini_portile2 (~> 2.8.2) racc (~> 1.4) - parallel (1.23.0) - parser (3.2.2.1) + parallel (1.27.0) + parser (3.3.8.0) ast (~> 2.4.1) - racc (1.6.2) - rack (3.0.4.1) - rails-dom-testing (2.0.3) - activesupport (>= 4.2.0) + racc + prism (1.4.0) + racc (1.8.1) + rack (3.1.16) + rails-dom-testing (2.2.0) + activesupport (>= 5.0.0) + minitest nokogiri (>= 1.6) - rails-html-sanitizer (1.6.0) + rails-html-sanitizer (1.6.2) loofah (~> 2.21) - nokogiri (~> 1.14) + nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0) rainbow (3.1.1) - rake (13.0.6) - regexp_parser (2.8.0) - rexml (3.2.5) - rubocop (1.52.0) + rake (13.3.0) + regexp_parser (2.10.0) + rubocop (1.77.0) json (~> 2.3) + language_server-protocol (~> 3.17.0.2) + lint_roller (~> 1.1.0) parallel (~> 1.10) - parser (>= 3.2.0.0) + parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.8, < 3.0) - rexml (>= 3.2.5, < 4.0) - rubocop-ast (>= 1.28.0, < 2.0) + regexp_parser (>= 2.9.3, < 3.0) + rubocop-ast (>= 1.45.1, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.29.0) - parser (>= 3.2.1.0) - rubocop-github (0.20.0) - rubocop (>= 1.37) - rubocop-performance (>= 1.15) - rubocop-rails (>= 2.17) - rubocop-performance (1.16.0) - rubocop (>= 1.7.0, < 2.0) - rubocop-ast (>= 0.4.0) - rubocop-rails (2.17.4) + unicode-display_width (>= 2.4.0, < 4.0) + rubocop-ast (1.45.1) + parser (>= 3.3.7.2) + prism (~> 1.4) + rubocop-github (0.23.0) + rubocop (>= 1.72) + rubocop-performance (>= 1.24) + rubocop-rails (>= 2.23) + rubocop-performance (1.24.0) + lint_roller (~> 1.1) + rubocop (>= 1.72.1, < 2.0) + rubocop-ast (>= 1.38.0, < 2.0) + rubocop-rails (2.30.3) activesupport (>= 4.2.0) + lint_roller (~> 1.1) rack (>= 1.1) - rubocop (>= 1.33.0, < 2.0) + rubocop (>= 1.72.1, < 2.0) + rubocop-ast (>= 1.38.0, < 2.0) ruby-progressbar (1.13.0) ruby2_keywords (0.0.5) + securerandom (0.3.2) smart_properties (1.17.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - unicode-display_width (2.4.2) + unicode-display_width (3.1.4) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) PLATFORMS ruby DEPENDENCIES - erb_lint (~> 0.4.0) + erb_lint (~> 0.9.0) erblint-github! - minitest (~> 5.18.0) - mocha (~> 2.0.2) - rake (~> 13.0.6) - rubocop (= 1.52.0) - rubocop-github (~> 0.20.0) + minitest (~> 5.25.1) + mocha (~> 2.7.0) + rake (~> 13.3.0) + rubocop (= 1.77.0) + rubocop-github (~> 0.23.0) BUNDLED WITH 2.3.26 diff --git a/README.md b/README.md index eb4b51d..a8452df 100644 --- a/README.md +++ b/README.md @@ -41,12 +41,13 @@ inherit_gem: - [GitHub::Accessibility::NoAriaLabelMisuse](./docs/rules/accessibility/no-aria-label-misuse.md) - [GitHub::Accessibility::NoPositiveTabIndex](./docs/rules/accessibility/no-positive-tab-index.md) - [GitHub::Accessibility::NoRedundantImageAlt](./docs/rules/accessibility/no-redundant-image-alt.md) +- [GitHub::Accessibility::NoVisuallyHiddenInteractiveElements](./docs/rules/accessibility/no-visually-hidden-interactive-elements.md) - [GitHub::Accessibility::NoTitleAttribute](./docs/rules/accessibility/no-title-attribute.md) - [GitHub::Accessibility::SvgHasAccessibleText](./docs/rules/accessibility/svg-has-accessible-text.md) ## Testing -``` +```sh bundle install bundle exec rake ``` diff --git a/bin/erblint-disable b/bin/erblint-disable new file mode 100755 index 0000000..d8ecf75 --- /dev/null +++ b/bin/erblint-disable @@ -0,0 +1,53 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "json" + +# Accepts comma-separated args with simple rule name +# e.g. script/erblint-disable SomeRule1,SomeRule2 +# e.g. script/erblint-disable GitHub::Accessibility::Rule1,SomeRule2 +rules = ARGV[0] + +rules_array = rules.split(",") +rules_map = {} +rules_array.each do |rule| + rule_underscored = rule.to_s.gsub("::", "/"). + gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2'). + gsub(/([a-z\d])([A-Z])/, '\1_\2'). + tr("-", "_"). + downcase. + gsub("git_hub", "github") # corrects rule names with `GitHub` name to align with erb-lint expectations + rules_map[rule] = rule_underscored +end + +rules_map.each do |disable_comment_name, command_line_name| + output = `bin/erblint --format json --enable-linters #{command_line_name} app/views app/components packages/**/app/components app/forms/**/` + hashed_output = JSON.parse(output) + hashed_output["files"].each do |file| + path = file["path"] + offenses = file["offenses"] + line_numbers = offenses.map do |offense| + offense["location"]["last_line"] + end + File.open(path, "r+") do |file| + lines = file.each_line.to_a + line_numbers.each do |line_number| + line = lines[line_number - 1] + unless line.match?(/erblint:disable (?.*#{disable_comment_name}).*/) + existing_disable = line.match(/(?<=# erblint:disable)(.*) (?=%>)/) + if existing_disable + existing_disable_string = existing_disable.captures[0] + add_new_disable = "#{existing_disable_string}, #{disable_comment_name}" + lines[line_number - 1] = lines[line_number - 1].gsub(existing_disable_string, add_new_disable) + else + lines[line_number - 1] = lines[line_number - 1].gsub("\n", "") + "<%# erblint:disable #{disable_comment_name} %>\n" + end + end + end + file.rewind + file.write(lines.join) + end + end +end + +exit 0 diff --git a/config/accessibility.yml b/config/accessibility.yml index e55a2cb..eeb9bc3 100644 --- a/config/accessibility.yml +++ b/config/accessibility.yml @@ -1,5 +1,7 @@ --- linters: + NoUnusedDisable: + enabled: true GitHub::Accessibility::AriaLabelIsWellFormatted: enabled: true GitHub::Accessibility::AvoidBothDisabledAndAriaDisabled: @@ -30,3 +32,5 @@ linters: enabled: true GitHub::Accessibility::SvgHasAccessibleText: enabled: true + GitHub::Accessibility::NoVisuallyHiddenInteractiveElements: + enabled: true diff --git a/docs/rules/accessibility/aria-label-is-well-formatted.md b/docs/rules/accessibility/aria-label-is-well-formatted.md index ff310d5..731b02d 100644 --- a/docs/rules/accessibility/aria-label-is-well-formatted.md +++ b/docs/rules/accessibility/aria-label-is-well-formatted.md @@ -11,6 +11,12 @@ Keep the following practices in mind: - Do not use line-break characters like ` `. An accessible name should be concise to start with. - Do not set the `aria-label` to a URL. Instead, use an appropriate human-friendly description. +### Be wary of `[aria-label]` on disallowed elements ⚠️ + +You may come across a scenario where `[aria-label]` is set on a generic `div` or `span`. This should also be flagged with the [NoAriaLabelMisuse](https://github.com/github/erblint-github/blob/main/docs/rules/accessibility/no-aria-label-misuse.md). + +In this scenario, prioritize removing the `aria-label`! + ## Resources - [Staff only: Guidance on naming controls](https://github.com/github/accessibility-playbook/blob/main/content/link-and-button-guidance.mdx#guidance-on-naming-controls) diff --git a/docs/rules/accessibility/navigation-has-label.md b/docs/rules/accessibility/navigation-has-label.md index a664b10..ccd48bb 100644 --- a/docs/rules/accessibility/navigation-has-label.md +++ b/docs/rules/accessibility/navigation-has-label.md @@ -2,9 +2,19 @@ ## Rule Details -This rule enforces that a navigation landmark (a `