diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..2fcedcc --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,102 @@ +name: CI + +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + +jobs: + test: + runs-on: ubuntu-latest + timeout-minutes: 15 + + strategy: + fail-fast: false + matrix: + ruby: ['3.0', '3.1', '3.2', '3.3'] + gemfile: + - rails_6.1_active_admin_2.9 + - rails_7.0_active_admin_2.x + - rails_7.0_active_admin_3.x + - rails_7.1_active_admin_3.x + - rails_7.2_active_admin_3.x + - rails_7.2_active_admin_4.x + - rails_8.0_active_admin_4.x + exclude: + # Rails 8 requires Ruby 3.2+ + - ruby: '3.0' + gemfile: rails_8.0_active_admin_4.x + - ruby: '3.1' + gemfile: rails_8.0_active_admin_4.x + # ActiveAdmin 4 requires Ruby 3.2+ + - ruby: '3.0' + gemfile: rails_7.2_active_admin_4.x + - ruby: '3.1' + gemfile: rails_7.2_active_admin_4.x + + env: + BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile + RAILS_ENV: test + + steps: + - uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: spec/dummy/package-lock.json + + - name: Install npm dependencies for dummy app + run: | + cd spec/dummy + npm install + + - name: Build assets for dummy app + run: | + cd spec/dummy + # Build JavaScript with esbuild + npm run build:js || true + # Build CSS with Tailwind for AA4 + if [[ "${{ matrix.gemfile }}" == *"4.x"* ]]; then + npm run build:css || true + fi + + - name: Database setup + run: | + cd spec/dummy + bundle exec rails db:create db:migrate db:test:prepare + + - name: Run tests + run: bundle exec rspec --format progress + + - name: Upload screenshots on failure + uses: actions/upload-artifact@v4 + if: failure() + with: + name: test-screenshots-${{ matrix.ruby }}-${{ matrix.gemfile }} + path: spec/dummy/tmp/capybara + if-no-files-found: ignore + + lint: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.3' + bundler-cache: true + + - name: Run RuboCop + run: bundle exec rubocop --force-exclusion \ No newline at end of file diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml deleted file mode 100644 index 9185aa3..0000000 --- a/.github/workflows/linters.yml +++ /dev/null @@ -1,39 +0,0 @@ ---- -name: Linters - -on: - pull_request: - branches: [main] - push: - branches: [main] - -jobs: - reviewdog: - name: Reviewdog - runs-on: ubuntu-latest - - env: - RAILS_VERSION: 7.0 - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: 3.0 - bundler-cache: true - - - name: Set up Reviewdog - uses: reviewdog/action-setup@v1 - with: - reviewdog_version: latest - - - name: Run Reviewdog - env: - REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - reviewdog -fail-on-error -reporter=github-pr-review -runners=fasterer,rubocop - - # NOTE: check with: reviewdog -fail-on-error -reporter=github-pr-review -runners=fasterer -diff="git diff" -tee diff --git a/.github/workflows/specs.yml b/.github/workflows/specs.yml deleted file mode 100644 index b97097b..0000000 --- a/.github/workflows/specs.yml +++ /dev/null @@ -1,148 +0,0 @@ ---- -name: Specs - -on: - pull_request: - branches: [main, master] - push: - branches: [main, master] - -jobs: - test_legacy: - runs-on: ubuntu-latest - name: Ruby ${{ matrix.ruby }} / Rails ${{ matrix.rails }} / AA ${{ matrix.activeadmin }} - - strategy: - fail-fast: false - matrix: - include: - # Legacy ActiveAdmin 2.x/3.x support - - ruby: '3.0' - rails: '7.0' - activeadmin: '~> 2.9' - - ruby: '3.1' - rails: '7.1' - activeadmin: '~> 3.0' - - ruby: '3.2' - rails: '7.2' - activeadmin: '~> 3.0' - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ matrix.ruby }} - bundler-cache: false - - - name: Install dependencies - run: | - gem install bundler - bundle config set --local without 'development' - bundle install --jobs 4 --retry 3 - env: - RAILS_VERSION: ${{ matrix.rails }} - ACTIVEADMIN_VERSION: ${{ matrix.activeadmin }} - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - - - name: Install NPM dependencies - run: | - cd spec/dummy - npm install - - - name: Build assets - run: | - cd spec/dummy - npm run build - - - name: Run tests - run: bundle exec rspec --profile - env: - RAILS_VERSION: ${{ matrix.rails }} - ACTIVEADMIN_VERSION: ${{ matrix.activeadmin }} - - - name: Archive screenshots on failure - uses: actions/upload-artifact@v4 - if: failure() - with: - name: test-screenshots-legacy-${{ matrix.ruby }}-${{ matrix.rails }} - path: spec/dummy/tmp/capybara - - test_activeadmin_4: - runs-on: ubuntu-latest - name: Ruby ${{ matrix.ruby }} / Rails ${{ matrix.rails }} / AA 4.x - - strategy: - fail-fast: false - matrix: - ruby: ['3.2', '3.3'] - rails: ['7.2', '8.0'] - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ matrix.ruby }} - bundler-cache: false - - - name: Install dependencies for AA4 - run: | - gem install bundler - bundle config set --local without 'development' - bundle install --jobs 4 --retry 3 - env: - RAILS_VERSION: ${{ matrix.rails }} - ACTIVEADMIN_VERSION: '' - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - - - name: Install NPM dependencies - run: | - cd spec/dummy - npm install - - - name: Build assets - run: | - cd spec/dummy - npm run build - - - name: Run tests - run: bundle exec rspec --profile - env: - RAILS_VERSION: ${{ matrix.rails }} - ACTIVEADMIN_VERSION: '4.0' - - - name: Archive screenshots on failure - uses: actions/upload-artifact@v4 - if: failure() - with: - name: test-screenshots-aa4-${{ matrix.ruby }}-${{ matrix.rails }} - path: spec/dummy/tmp/capybara - - lint: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: '3.2' - bundler-cache: true - - - name: Run RuboCop - run: bundle exec rubocop --parallel \ No newline at end of file diff --git a/.github/workflows/specs_rails61.yml b/.github/workflows/specs_rails61.yml deleted file mode 100644 index 5fd1541..0000000 --- a/.github/workflows/specs_rails61.yml +++ /dev/null @@ -1,43 +0,0 @@ ---- -name: Specs Rails 6.1 with ActiveAdmin 2.9 - -on: - pull_request: - branches: [main] - push: - branches: [main] - -jobs: - test: - runs-on: ubuntu-latest - - strategy: - matrix: - ruby: ['3.0', '3.1', '3.2'] - - env: - RAILS_VERSION: 6.0 - ACTIVEADMIN_VERSION: 2.9.0 - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ matrix.ruby }} - bundler-cache: true - - - name: Database setup - run: bin/rails db:create db:migrate db:test:prepare - - - name: Run tests - run: bundle exec rspec --profile - - - name: On failure, archive screenshots as artifacts - uses: actions/upload-artifact@v4 - if: failure() - with: - name: test-failed-screenshots - path: spec/dummy/tmp/screenshots diff --git a/.github/workflows/specs_rails70.yml b/.github/workflows/specs_rails70.yml deleted file mode 100644 index 293c8b4..0000000 --- a/.github/workflows/specs_rails70.yml +++ /dev/null @@ -1,42 +0,0 @@ ---- -name: Specs Rails 7.0 - -on: - pull_request: - branches: [main] - push: - branches: [main] - -jobs: - test: - runs-on: ubuntu-latest - - strategy: - matrix: - ruby: ['3.0', '3.1', '3.2'] - - env: - RAILS_VERSION: 7.0 - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ matrix.ruby }} - bundler-cache: true - - - name: Database setup - run: bin/rails db:create db:migrate db:test:prepare - - - name: Run tests - run: bundle exec rspec --profile - - - name: On failure, archive screenshots as artifacts - uses: actions/upload-artifact@v4 - if: failure() - with: - name: test-failed-screenshots - path: spec/dummy/tmp/screenshots diff --git a/.github/workflows/specs_rails71.yml b/.github/workflows/specs_rails71.yml deleted file mode 100644 index 6817c31..0000000 --- a/.github/workflows/specs_rails71.yml +++ /dev/null @@ -1,42 +0,0 @@ ---- -name: Specs Rails 7.1 - -on: - pull_request: - branches: [main] - push: - branches: [main] - -jobs: - test: - runs-on: ubuntu-latest - - strategy: - matrix: - ruby: ['3.2', '3.4'] - - env: - RAILS_VERSION: 7.1 - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ matrix.ruby }} - bundler-cache: true - - - name: Database setup - run: bin/rails db:create db:migrate db:test:prepare - - - name: Run tests - run: bundle exec rspec --profile - - - name: On failure, archive screenshots as artifacts - uses: actions/upload-artifact@v4 - if: failure() - with: - name: test-failed-screenshots - path: spec/dummy/tmp/screenshots diff --git a/.github/workflows/specs_rails72.yml b/.github/workflows/specs_rails72.yml deleted file mode 100644 index 0937f06..0000000 --- a/.github/workflows/specs_rails72.yml +++ /dev/null @@ -1,42 +0,0 @@ ---- -name: Specs Rails 7.2 - -on: - pull_request: - branches: [main] - push: - branches: [main] - -jobs: - test: - runs-on: ubuntu-latest - - strategy: - matrix: - ruby: ['3.2', '3.4'] - - env: - RAILS_VERSION: 7.2 - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ matrix.ruby }} - bundler-cache: true - - - name: Database setup - run: bin/rails db:create db:migrate db:test:prepare - - - name: Run tests - run: bundle exec rspec --profile - - - name: On failure, archive screenshots as artifacts - uses: actions/upload-artifact@v4 - if: failure() - with: - name: test-failed-screenshots - path: spec/dummy/tmp/screenshots diff --git a/.github/workflows/specs_rails80.yml b/.github/workflows/specs_rails80.yml deleted file mode 100644 index 1c00379..0000000 --- a/.github/workflows/specs_rails80.yml +++ /dev/null @@ -1,64 +0,0 @@ ---- -name: Specs Rails 8.0 - -on: - pull_request: - branches: [main] - push: - branches: [main] - -jobs: - test: - runs-on: ubuntu-latest - - strategy: - matrix: - ruby: ['3.2', '3.3'] - - env: - RAILS_VERSION: 8.0 - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: ${{ matrix.ruby }} - bundler-cache: false - - - name: Install dependencies - run: | - gem install bundler - bundle install --jobs 4 --retry 3 - - - name: Set up Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - - - name: Install NPM dependencies - run: | - cd spec/dummy - npm install - - - name: Build assets - run: | - cd spec/dummy - npm run build - - - name: Database setup - run: | - cd spec/dummy - bundle exec rails db:create db:migrate db:test:prepare - - - name: Run tests - run: bundle exec rspec --profile - - - name: On failure, archive screenshots as artifacts - uses: actions/upload-artifact@v4 - if: failure() - with: - name: test-failed-screenshots-rails8-${{ matrix.ruby }} - path: spec/dummy/tmp/capybara diff --git a/.gitignore b/.gitignore index 2adff09..c22b4bd 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,6 @@ /spec/dummy/log/ /spec/dummy/storage/ /spec/dummy/tmp/ +tmp/ /spec/dummy/node_modules/ +/gemfiles/ diff --git a/.rubocop.yml b/.rubocop.yml index e51b9ac..a967615 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,7 +1,4 @@ --- -inherit_from: - - https://relaxed.ruby.style/rubocop.yml - plugins: - rubocop-capybara - rubocop-packaging @@ -10,24 +7,64 @@ plugins: - rubocop-rspec - rubocop-rspec_rails +inherit_mode: + merge: + - Exclude + AllCops: + NewCops: enable + TargetRubyVersion: 3.0 + SuggestExtensions: false + DisplayCopNames: true + CacheRootDirectory: tmp/rubocop-cache Exclude: - bin/* + - coverage/**/* - db/schema.rb + - docs/**/* + - examples/**/* - gemfiles/**/* - spec/dummy/**/* - vendor/**/* - NewCops: enable - TargetRubyVersion: 3.0 + +Layout/LineLength: + Max: 120 + +Metrics/BlockLength: + Enabled: true + Exclude: + - spec/**/* + - docs/**/* + +Metrics/MethodLength: + Max: 20 + Exclude: + - spec/**/* + +Style/Documentation: + Enabled: false + +Style/StringLiterals: + EnforcedStyle: single_quotes + +Style/StringLiteralsInInterpolation: + EnforcedStyle: double_quotes RSpec/ExampleLength: - # default 5 Max: 12 + Exclude: + - spec/system/**/* RSpec/MultipleExpectations: - # default 1 Max: 4 + Exclude: + - spec/system/**/* RSpec/MultipleMemoizedHelpers: - # default 5 Max: 10 + +Rails/SkipsModelValidations: + Enabled: true + +Rails/ActiveRecordAliases: + Enabled: true diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..6fd944c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,19 @@ +# Repository Guidelines + +## Project Structure & Module Organization +The gem code lives in `lib/activeadmin` and `lib/formtastic`, with the public entry point in `lib/activeadmin_quill_editor.rb`. Ruby assets and templates that Active Admin mounts ship from `app/assets` and `app/views`. Front-end bundles exposed to consuming apps sit in `index.js` and the `app/assets/javascripts/activeadmin/quill_editor` tree. System specs exercise the dummy Rails app under `spec/dummy`, while reusable page helpers reside in `spec/page_objects`. + +## Build, Test, and Development Commands +`make up` builds the Docker environment defined in `extra/docker-compose.yml` and prepares the dummy app; add `RUBY`, `RAILS`, or `ACTIVEADMIN` env vars to test matrix combinations. Run `make specs` (or directly `bin/rspec --fail-fast`) to execute the suite, and `make lint` to invoke `bin/rubocop` inside the container. Use `make shell` for interactive debugging and `make server` to boot the dummy Rails instance at `SERVER_PORT`. + +## Coding Style & Naming Conventions +Follow the `.rubocop.yml` rules: two-space indentation, trailing commas on multiline literals, double quotes where interpolation occurs, and `snake_case` for Ruby identifiers. Use `CamelCase` for classes and modules under the `ActiveadminQuillEditor` namespace, mirroring existing files. JavaScript additions in `app/assets/javascripts` should align with the lintable ES module style in `index.js`; run `npx eslint index.js` if you modify the package entry point. + +## Testing Guidelines +RSpec powers the suite; place feature-level coverage in `spec/system`, supporting components in `spec/page_objects`, and pure Ruby behaviour in `spec`. Prefer descriptive example names (`it "renders toolbar buttons"`) and keep factories in the dummy app to mirror real usage. Start tests with `make specs`; use `bin/rspec spec/system/editor_spec.rb` to target a file. + +## Commit & Pull Request Guidelines +Recent history follows conventional prefixes (`fix:`, `ci:`, `chore:`); keep messages in the imperative mood and limit the subject to 72 characters. Every pull request should link related GitHub issues, summarize user-facing changes, and note any docs or assets touched. Include screenshots or GIFs when altering the editor UI, and confirm that both `make specs` and `make lint` pass in the PR description. Request review from a maintainer and re-run the pipeline after rebases. + +## Security & Configuration Tips +Store local-only overrides in `extra/.env` (kept out of version control) and avoid committing credentials to the dummy app. The docker-compose services mount the repository directly, so prefer editing files via your host editor rather than inside the container to prevent permission drift. diff --git a/Appraisals b/Appraisals new file mode 100644 index 0000000..113f860 --- /dev/null +++ b/Appraisals @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +# Rails 6.1 with ActiveAdmin 2.9 +appraise 'rails-6.1-active-admin-2.9' do + gem 'rails', '~> 6.1.0' + gem 'activeadmin', '~> 2.9.0' + gem 'sassc' + gem 'sprockets-rails' + remove_gem 'propshaft' + remove_gem 'importmap-rails' +end + +# Rails 7.0 with ActiveAdmin 2.x +appraise 'rails-7.0-active-admin-2.x' do + gem 'rails', '~> 7.0.0' + gem 'activeadmin', '~> 2.14' + gem 'sassc' + gem 'sprockets-rails' + remove_gem 'propshaft' + remove_gem 'importmap-rails' +end + +# Rails 7.0 with ActiveAdmin 3.x +appraise 'rails-7.0-active-admin-3.x' do + gem 'rails', '~> 7.0.0' + gem 'activeadmin', '~> 3.2' + gem 'sassc' + gem 'sprockets-rails' + remove_gem 'propshaft' + remove_gem 'importmap-rails' +end + +# Rails 7.1 with ActiveAdmin 3.x +appraise 'rails-7.1-active-admin-3.x' do + gem 'rails', '~> 7.1.0' + gem 'activeadmin', '~> 3.2' + gem 'sassc' + gem 'sprockets-rails' + remove_gem 'propshaft' + remove_gem 'importmap-rails' +end + +# Rails 7.2 with ActiveAdmin 3.x +appraise 'rails-7.2-active-admin-3.x' do + gem 'rails', '~> 7.2.0' + gem 'activeadmin', '~> 3.2' + gem 'sassc' + gem 'sprockets-rails' + remove_gem 'propshaft' + remove_gem 'importmap-rails' +end + +# Rails 7.2 with ActiveAdmin 4.x beta +appraise 'rails-7.2-active-admin-4.x' do + gem 'rails', '~> 7.2.0' + gem 'activeadmin', '4.0.0.beta16' + gem 'importmap-rails' + gem 'propshaft' +end + +# Rails 8.0 with ActiveAdmin 4.x beta +appraise 'rails-8.0-active-admin-4.x' do + gem 'rails', '~> 8.0.0' + gem 'activeadmin', '4.0.0.beta16' + gem 'importmap-rails' + gem 'propshaft' +end \ No newline at end of file diff --git a/Gemfile b/Gemfile index 6be1646..b3d69dd 100644 --- a/Gemfile +++ b/Gemfile @@ -27,13 +27,21 @@ rails = eval_version('rails', rails_ver) gem(*rails) active_admin_ver = ENV.fetch('ACTIVEADMIN_VERSION', '') -if active_admin_ver.empty? - # Use ActiveAdmin 4 beta by default for development - gem 'activeadmin', '~> 4.0.0.beta' -else - active_admin = eval_version('activeadmin', active_admin_ver) - gem(*active_admin) -end +active_admin_requirement = + if active_admin_ver.empty? + '~> 4.0.0.beta16' + elsif active_admin_ver.match?(/[a-zA-Z]/) || active_admin_ver.match?(/^[~<>=]/) + active_admin_ver + else + target = Gem::Version.new(active_admin_ver) + if target >= Gem::Version.new('4.0') + '~> 4.0.0.beta16' + else + active_admin_ver.count('.') < 2 ? "~> #{active_admin_ver}.0" : "~> #{active_admin_ver}" + end + end + +gem 'activeadmin', active_admin_requirement ruby32 = ruby_ver.empty? || Gem::Version.new(ruby_ver) >= Gem::Version.new('3.2') rails72 = rails_ver.empty? || Gem::Version.new(rails_ver) >= Gem::Version.new('7.2') @@ -54,8 +62,8 @@ gem 'puma' # Asset pipeline - use Propshaft for Rails 8, Sprockets for older versions rails80 = rails_ver.empty? || Gem::Version.new(rails_ver) >= Gem::Version.new('8.0') if rails80 + gem 'importmap-rails' # Required for ActiveAdmin 4 gem 'propshaft' - gem 'importmap-rails' # Required for ActiveAdmin 4 else gem 'sassc' gem 'sprockets-rails' @@ -80,4 +88,5 @@ gem 'rubocop-rspec' gem 'rubocop-rspec_rails' # Tools +gem 'appraisal' gem 'pry-rails' diff --git a/Rakefile b/Rakefile index dbb5eaf..430cab5 100644 --- a/Rakefile +++ b/Rakefile @@ -6,7 +6,7 @@ rescue LoadError puts 'You must `gem install bundler` and `bundle install` to run rake tasks' end -APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__) +APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__) load 'rails/tasks/engine.rake' load 'rails/tasks/statistics.rake' diff --git a/activeadmin_quill_editor.gemspec b/activeadmin-quill_editor.gemspec similarity index 83% rename from activeadmin_quill_editor.gemspec rename to activeadmin-quill_editor.gemspec index 083f786..948b191 100644 --- a/activeadmin_quill_editor.gemspec +++ b/activeadmin-quill_editor.gemspec @@ -5,7 +5,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'activeadmin/quill_editor/version' Gem::Specification.new do |spec| - spec.name = 'activeadmin_quill_editor' + spec.name = 'activeadmin-quill_editor' spec.version = ActiveAdmin::QuillEditor::VERSION spec.summary = 'Quill Editor for ActiveAdmin' spec.description = 'An Active Admin plugin to use Quill Rich Text Editor' @@ -25,6 +25,6 @@ Gem::Specification.new do |spec| spec.files = Dir['{app,lib,vendor}/**/*', 'LICENSE.txt', 'Rakefile', 'README.md'] spec.require_paths = ['lib'] - spec.add_runtime_dependency 'activeadmin', '>= 2.9', '< 5' # rubocop:disable Gemspec/AddRuntimeDependency - spec.add_runtime_dependency 'rails', '>= 6.0' + spec.add_dependency 'activeadmin', '>= 2.9', '< 5' + spec.add_dependency 'rails', '>= 6.0' end diff --git a/docs/rubocop-configuration.adoc.txt b/docs/rubocop-configuration.adoc.txt new file mode 100644 index 0000000..727fbf8 --- /dev/null +++ b/docs/rubocop-configuration.adoc.txt @@ -0,0 +1,1028 @@ += Configuration + +The behavior of RuboCop can be controlled via the +https://github.com/rubocop/rubocop/blob/master/.rubocop.yml[.rubocop.yml] +configuration file. It makes it possible to enable/disable certain cops +(checks) and to alter their behavior if they accept any parameters. The file +can be placed in your home directory, XDG config directory, or in some project +directory. + +The file has the following format: + +[source,yaml] +---- +inherit_from: ../.rubocop.yml + +Style/Encoding: + Enabled: false + +Layout/LineLength: + Max: 99 +---- + +NOTE: Qualifying cop name with its type, e.g., `Style`, is recommended, +but not necessary as long as the cop name is unique across all types. + +== Config file locations + +RuboCop will start looking for the configuration file in the directory +where the inspected file is and continue its way up to the root directory. + +If it cannot be found until reaching the project's root directory, then it will +be searched for in the https://dot-config.github.io[.config directory of the project root] +and the user's global config locations. The user's global config locations consist of a +dotfile or a config file inside the https://specifications.freedesktop.org/basedir-spec/latest/index.html[XDG Base Directory +specification]. + +* `.config/.rubocop.yml` or `.config/rubocop/config.yml` at the project root +* `~/.rubocop.yml` +* `$XDG_CONFIG_HOME/rubocop/config.yml` (expands to `~/.config/rubocop/config.yml` +if `$XDG_CONFIG_HOME` is not set) + +If both files exist, the dotfile will be selected. + +As an example, if RuboCop is invoked from inside `/path/to/project/lib/utils`, +then RuboCop will use the config as specified inside the first of the following +files: + +* `/path/to/project/lib/utils/.rubocop.yml` +* `/path/to/project/lib/.rubocop.yml` +* `/path/to/project/.rubocop.yml` +* `/path/to/project/.config/.rubocop.yml` +* `/path/to/project/.config/rubocop/config.yml` +* `~/.rubocop.yml` +* `~/.config/rubocop/config.yml` +* https://github.com/rubocop/rubocop/blob/master/config/default.yml[RuboCop's default configuration] + +NOTE: All the previous logic does not apply if a specific configuration file is passed +on the command line through the `--config` flag. In that case, the resolved +configuration file will be the one passed to the CLI. + +== Inheritance + +All configuration inherits from https://github.com/rubocop/rubocop/blob/master/config/default.yml[RuboCop's default configuration] (See +"Defaults"). + +RuboCop also supports inheritance in user's configuration files. The most common +example would be the `.rubocop_todo.yml` file (See "Automatically Generated +Configuration" below). + +Settings in the child file (that which inherits) override those in the parent +(that which is inherited), with the following caveats. + +=== Inheritance of hashes vs. other types + +Configuration parameters that are hashes, for example `PreferredMethods` in +`Style/CollectionMethods`, are merged with the same parameter in the parent +configuration. This means that any key-value pairs given in child configuration +override the same keys in parent configuration. Giving `~`, YAML's +representation of `nil`, as a value cancels the setting of the corresponding +key in the parent configuration. For example: + +[source,yaml] +---- +Style/CollectionMethods: + Enabled: true + PreferredMethods: + # No preference for collect, keep all others from default config. + collect: ~ +---- + +Other types, such as `AllCops` / `Include` (an array), are overridden by the +child setting. + +Arrays override because if they were merged, there would be no way to +remove elements in child files. + +However, advanced users can still merge arrays using the `inherit_mode` setting. +See "Merging arrays using inherit_mode" below. + +=== Inheriting from another configuration file in the project + +The optional `inherit_from` directive is used to include configuration +from one or more files. This makes it possible to have the common +project settings in the `.rubocop.yml` file at the project root, and +then only the deviations from those rules in the subdirectories. The +files can be given with absolute paths or paths relative to the file +where they are referenced. The settings after an `inherit_from` +directive override any settings in the file(s) inherited from. When +multiple files are included, the first file in the list has the lowest +precedence and the last one has the highest. The format for multiple +inheritance is: + +[source,yaml] +---- +inherit_from: + - ../.rubocop.yml + - ../conf/.rubocop.yml +---- + +`inherit_from` also accepts a glob, for example: + +[source,yaml] +---- +inherit_from: + - packages/*/.rubocop_todo.yml +---- + +The example above is one potential use-case: allowing components within your repo to organize their own `.rubocop_todo.yml` files. + +== Inheriting configuration from a remote URL + +The optional `inherit_from` directive can contain a full url to a remote +file. This makes it possible to have common project settings stored on a http +server and shared between many projects. + +The remote config file is cached locally and is only updated if: + +* The file does not exist. +* The file has not been updated in the last 24 hours. +* The remote copy has a newer modification time than the local copy. + +You can inherit from both remote and local files in the same config and the +same inheritance rules apply to remote URLs and inheriting from local +files where the first file in the list has the lowest precedence and the +last one has the highest. The format for multiple inheritance using URLs is: + +[source,yaml] +---- +inherit_from: + - http://www.example.com/rubocop.yml + - ../.rubocop.yml +---- + +You can inherit from a repo with basic auth that is authorized to access the repo as follows: + +[source,yaml] +---- +inherit_from: + - http://:@raw.github.com/example/rubocop.yml +---- + +A link:https://docs.github.com/en/developers/apps/about-apps#personal-access-token[GitHub personal access token] +can also be configured as follows: + +[source,yaml] +---- +inherit_from: + - http://@raw.github.com/example/rubocop.yml +---- + +=== Inheriting configuration from a dependency gem + +The optional `inherit_gem` directive is used to include configuration from +one or more gems external to the current project. This makes it possible to +inherit a shared dependency's RuboCop configuration that can be used from +multiple disparate projects. + +Configurations inherited in this way will be essentially _prepended_ to the +`inherit_from` directive, such that the `inherit_gem` configurations will be +loaded first, then the `inherit_from` relative file paths will be loaded +(overriding the configurations from the gems), and finally the remaining +directives in the configuration file will supersede any of the inherited +configurations. This means the configurations inherited from one or more gems +have the lowest precedence of inheritance. + +The directive should be formatted as a YAML Hash using the gem name as the +key and the relative path within the gem as the value: + +[source,yaml] +---- +inherit_gem: + my-shared-gem: .rubocop.yml + cucumber: conf/rubocop.yml +---- + +An array can also be used as the value to include multiple configuration files +from a single gem: + +[source,yaml] +---- +inherit_gem: + my-shared-gem: + - default.yml + - strict.yml +---- + +NOTE: If the shared dependency is declared using a https://bundler.io/[Bundler] +Gemfile and the gem was installed using `bundle install`, it would be +necessary to also invoke RuboCop using Bundler in order to find the +dependency's installation path at runtime: + +[source,sh] +---- +$ bundle exec rubocop +---- + +=== Merging arrays using inherit_mode + +The optional directive `inherit_mode` specifies which configuration keys that +have array values should be merged together instead of overriding the inherited +value. + +This applies to explicit inheritance using `inherit_from` as well as implicit +inheritance from https://github.com/rubocop/rubocop/blob/master/config/default.yml[the default configuration]. + +Given the following config: + +[source,yaml] +---- +# .rubocop.yml +inherit_from: + - shared.yml + +inherit_mode: + merge: + - Exclude + +AllCops: + Exclude: + - 'generated/**/*.rb' + +Style/For: + Exclude: + - bar.rb +---- + +[source,yaml] +---- +# .shared.yml +Style/For: + Exclude: + - foo.rb +---- + +The list of ``Exclude``s for the `Style/For` cop in this example will be +`['foo.rb', 'bar.rb']`. Similarly, the `AllCops:Exclude` list will contain all +the default patterns plus the `+generated/**/*.rb+` entry that was added locally. + +The directive can also be used on individual cop configurations to override +the global setting. + +[source,yaml] +---- +inherit_from: + - shared.yml + +inherit_mode: + merge: + - Exclude + +Style/For: + inherit_mode: + override: + - Exclude + Exclude: + - bar.rb +---- + +In this example the `Exclude` would only include `bar.rb`. + +== Pre-processing + +Configuration files are pre-processed using the ERB templating mechanism. This +makes it possible to add dynamic content that will be evaluated when the +configuration file is read. For example, you could let RuboCop ignore all files +ignored by Git. + +[source,yaml] +---- +AllCops: + Exclude: + <% `git status --ignored --porcelain`.lines.grep(/^!! /).each do |path| %> + - <%= path.sub(/^!! /, '').sub(/\/$/, '/**/*') %> + <% end %> +---- + +== Defaults + +The file https://github.com/rubocop/rubocop/blob/master/config/default.yml[config/default.yml] under the RuboCop home directory contains the +default settings that all configurations inherit from. Project and personal +`.rubocop.yml` files need only make settings that are different from the +default ones. If there is no `.rubocop.yml` file in the project, home or XDG +directories, `config/default.yml` will be used. + +== Including/Excluding files + +RuboCop does a recursive file search starting from the directory it is +run in, or directories given as command line arguments. Files that +match any pattern listed under `AllCops`/`Include` and extensionless +files with a hash-bang (`#!`) declaration containing one of the known +ruby interpreters listed under `AllCops`/`RubyInterpreters` are +inspected, unless the file also matches a pattern in +`AllCops`/`Exclude`. Hidden directories (i.e., directories whose names +start with a dot) are not searched by default. + +Here is an example that might be used for a Rails project: + +[source,yaml] +---- +AllCops: + Exclude: + - 'db/**/*' + - 'config/**/*' + - 'script/**/*' + - 'bin/{rails,rake}' + - !ruby/regexp /old_and_unused\.rb$/ + +# other configuration +# ... +---- + +NOTE: When inspecting a certain directory(or file) +given as RuboCop's command line arguments, +patterns listed under `AllCops` / `Exclude` are also inspected. +If you want to apply `AllCops` / `Exclude` rules in this circumstance, +add `--force-exclusion` to the command line argument. + +Here is an example: + +[source,yaml] +---- +# .rubocop.yml +AllCops: + Exclude: + - foo.rb +---- + +If `foo.rb` is specified as a RuboCop's command line argument, the result is: + +[source,sh] +---- +# RuboCop inspects foo.rb. +$ bundle exec rubocop foo.rb + +# RuboCop does not inspect foo.rb. +$ bundle exec rubocop --force-exclusion foo.rb +---- + +=== Path relativity + +In `.rubocop.yml` and any other configuration file beginning with `.rubocop`, +files, and directories are specified relative to the directory where the +configuration file is. In configuration files that don't begin with `.rubocop`, +e.g. `our_company_defaults.yml`, paths are relative to the directory where +`rubocop` is run. + +This affects cops that have customisable paths: if the default is `db/migrate/\*.rb`, +and the cop is enabled in `db/migrate/.rubocop.yml`, the path will need to be +explicitly set as `*.rb`, as the default will look for `db/migrate/db/migrate/*.rb`. +This is unlikely to be what you wanted. + +=== Unusual files, that would not be included by default + +RuboCop comes with a comprehensive list of common ruby file names and +extensions. But, if you'd like RuboCop to check files that are not included by +default, you'll need to pass them in on the command line, or add entries for +them under `AllCops`/`Include`. Remember that your configuration files override +https://github.com/rubocop/rubocop/blob/master/config/default.yml[RuboCops's defaults]. In the following example, we want to include +`foo.unusual_extension`, but we also must copy any other patterns we need from +the overridden `default.yml`. + +[source,yaml] +---- +AllCops: + Include: + - foo.unusual_extension + - '**/*.rb' + - '**/*.gemfile' + - '**/*.gemspec' + - '**/*.rake' + - '**/*.ru' + - '**/Gemfile' + - '**/Rakefile' +---- + +This behavior of `Include` (overriding `default.yml`) was introduced in +https://github.com/rubocop/rubocop/releases/tag/v0.56.0[0.56.0] +via https://github.com/rubocop/rubocop/pull/5882[#5882]. This change allows +people to include/exclude precisely what they need to, without the defaults +getting in the way. + +==== Another example, using `inherit_mode` + +[source,yaml] +---- +inherit_mode: + merge: + - Include + +AllCops: + Include: + - foo.unusual_extension +---- + +See "Merging arrays using inherit_mode" above. + +=== Deprecated patterns + +Patterns that are just a file name, e.g. `Rakefile`, will match +that file name in any directory, but this pattern style is deprecated. The +correct way to match the file in any directory, including the current, is +`+**/Rakefile+`. + +The pattern `+config/**+` will match any file recursively under +`config`, but this pattern style is deprecated and should be replaced by +`+config/**/*+`. + +==== `Include` and `Exclude` are relative to their directory + +The `Include` and `Exclude` parameters are special. They are +valid for the directory tree starting where they are defined. They are not +shadowed by the setting of `Include` and `Exclude` in other `.rubocop.yml` +files in subdirectories. This is different from all other parameters, who +follow RuboCop's general principle that configuration for an inspected file +is taken from the nearest `.rubocop.yml`, searching upwards. + +NOTE: This behavior +will be overridden if you specify the `--ignore-parent-exclusion` command line +argument. + +=== Cop-specific `Include` and `Exclude` + +Cops can be run only on specific sets of files when that's needed (for +instance you might want to run some Rails model checks only on files whose +paths match `app/models/*.rb`). All cops support the +`Include` param. + +[source,yaml] +---- +Rails/HasAndBelongsToMany: + Include: + - app/models/*.rb +---- + +Cops can also exclude only specific sets of files when that's needed (for +instance you might want to run some cop only on a specific file). All cops support the +`Exclude` param. + +[source,yaml] +---- +Rails/HasAndBelongsToMany: + Exclude: + - app/models/problematic.rb +---- + +== Generic configuration parameters + +In addition to `Include` and `Exclude`, the following parameters are available +for every cop. + +=== Enabled + +Specific cops can be disabled by setting `Enabled` to `false` for that specific cop. + +[source,yaml] +---- +Layout/LineLength: + Enabled: false +---- + +Most cops are enabled by default. Cops, introduced or significantly updated +between major versions, are in a special pending status (read more in +xref:versioning.adoc["Versioning"]). Some cops, configured the above `Enabled: false` +in https://github.com/rubocop/rubocop/blob/master/config/default.yml[config/default.yml], +are disabled by default. + +The cop enabling process can be altered by setting `DisabledByDefault` or +`EnabledByDefault` (but not both) to `true`. These settings override the default for *all* +cops to disabled or enabled, except `Lint/Syntax` which is always enabled, +regardless of the cops' default values (whether enabled, disabled or pending). + +[source,yaml] +---- +AllCops: + DisabledByDefault: true +---- + +All cops except `Lint/Syntax` are then disabled by default. Only cops appearing in user +configuration files with `Enabled: true` will be enabled; every other cop will +be disabled without having to explicitly disable them in configuration. It is +also possible to enable entire departments by adding for example + +[source,yaml] +---- +Style: + Enabled: true +---- + +All cops in the `Style` department are then enabled. In this case, only the cops +in the `Style` department that are enabled by default will be enabled. +The cops in the `Style` department that are disabled by default will remain disabled. + +If a department is disabled, cops in that department can still be individually +enabled, and that setting overrides the setting for its department in the same +configuration file and in any inherited file. + +[source,yaml] +---- +inherit_from: config_that_disables_the_metrics_department.yml + +Metrics/MethodLength: + Enabled: true + +Style: + Enabled: false + +Style/Alias: + Enabled: true +---- + +=== Severity + +Each cop has a default severity level based on which department it belongs +to. The level is normally `warning` for `Lint` and `convention` for all the +others, but this can be changed in user configuration. Cops can customize their +severity level. Allowed values are `info`, `refactor`, `convention`, `warning`, `error` +and `fatal`. + +Cops with severity `info` will be reported but will not cause `rubocop` to return +a non-zero value. + +There is one exception from the general rule above and that is `Lint/Syntax`, a +special cop that checks for syntax errors before the other cops are invoked. It +cannot be disabled and its severity (`fatal`) cannot be changed in +configuration. + +[source,yaml] +---- +Lint: + Severity: error + +Metrics/CyclomaticComplexity: + Severity: warning +---- + +=== Details + +Individual cops can be embellished with extra details in offense messages: + +[source,yaml] +---- +Layout/LineLength: + Details: >- + If lines are too short, text becomes hard to read because you must + constantly jump from one line to the next while reading. If lines are too + long, the line jumping becomes too hard because you "lose the line" while + going back to the start of the next line. 80 characters is a good + compromise. +---- + +These details will only be seen when RuboCop is run with the `--extra-details` flag or if `ExtraDetails` is set to true in your global RuboCop configuration. + +=== AutoCorrect + +Cops that support the `--autocorrect` option offer flexible settings for autocorrection. +These settings can be specified in the configuration file as follows: + +- `always` +- `contextual` +- `disabled` + +==== `always (Default)` + +This setting enables autocorrection always by default. For backward compatibility, `true` is treated the same as `always`. + +[source,yaml] +---- +Style/PerlBackrefs: + AutoCorrect: always # or true +---- + +==== `contextual` + +This setting enables autocorrection when launched from the `rubocop` command, but it is not available through LSP. +e.g., `rubocop --lsp`, `rubocop --editor-mode`, or a program where `RuboCop::LSP.enable` has been applied. + +Inspections via the command line are treated as code that has been finalized. + +[source,yaml] +---- +Style/PerlBackrefs: + AutoCorrect: contextual +---- + +This setting prevents autocorrection during editing in the editor. e.g, with `textDocument/formatting` LSP method. +However `workspace/executeCommand` LSP method, which is triggered by intentional user actions, respects the user's intention for autocorrection. + +Additionally, for cases like `Metrics` cops where the highlight range extends over the entire body of classes, modules, methods, or blocks +offending range will be confined to only name. This approach helps to avoid redundant and noisy offenses in editor display. + +==== `disabled` + +This setting disables autocorrection. For backward compatibility, `false` is treated the same as `disabled`. + +[source,yaml] +---- +Style/PerlBackrefs: + AutoCorrect: disabled # or false +---- + +== Common configuration parameters +There are some configuration parameters that are shared by many cops, with the same behavior. + +=== IgnoredMethods + +Cops that evaluate methods can often be configured to ignore certain methods. Both strings and +regular expressions can be used. For example: + +[source,yaml] +---- +Metrics/BlockLength: + IgnoredMethods: + - refine + - !ruby/regexp /\b(class|instance)_methods\b/ +---- + +== Setting the target Ruby version + +Some checks are dependent on the version of the Ruby interpreter which the +inspected code must run on. For example, enforcing using Ruby 2.6+ endless +ranges `foo[n..]` rather than `foo[n..-1]` can help make your code shorter and +more consistent... _unless_ it must run on e.g. Ruby 2.5. + +Users may let RuboCop know the oldest version of Ruby which your project +supports with: + +[source,yaml] +---- +AllCops: + TargetRubyVersion: 2.5 +---- + +If a `TargetRubyVersion` is not specified in your config, then RuboCop will +check your project for a series of other files where the Ruby version may be +specified already. The files that will be checked are (in this order): +`*.gemspec`, `.ruby-version`, `.tool-versions`, and `Gemfile.lock`. + +The target ruby version may also be specified by setting the +`RUBOCOP_TARGET_RUBY_VERSION` environment variable to the desired version: for +example, running `RUBOCOP_TARGET_RUBY_VERSION=3.3 rubocop` will +run rubocop with a target ruby version of 3.3. Using this environment variable +will override all other sources of version information, including +`.rubocop.yml`. + +If a target Ruby version cannot be found via any of the above sources, then a +default target Ruby version will be used. + +=== Finding target Ruby in a `*.gemspec` file + +In order for RuboCop to parse a `*.gemspec` file's `required_ruby_version`, the +Ruby version must be specified using one of these syntaxes: + +1. a string range, e.g. `'~> 3.2.0'` or `'>= 3.2.2'` +2. an array of strings, e.g. `['>= 3.0.0', '< 3.4.0']` +3. a `Gem::Requirement`, e.g. `Gem::Requirement.new('>= 3.1.2')` + +If a `*.gemspec` file specifies a range of supported Ruby versions via any of +these means, then the greater of the following Ruby versions will be used: + +- the lowest Ruby version that is compatible with your specified range +- the lowest version of Ruby that is still supported by your version of RuboCop + +If a `*.gemspec` file defines its `required_ruby_version` dynamically (e.g. by +reading from a `.ruby-version` file, via an environment variable, referencing a +constant or local variable, etc), then RuboCop will _not_ detect that Ruby +version, and will instead try to find a target Ruby version elsewhere. + +== Setting the parser engine + +NOTE: The parser engine configuration was introduced in RuboCop 1.62. Since RuboCop 1.75, RuboCop chooses the parser engine automatically, so you don't need to configure it yourself. + +RuboCop allows switching the backend parser by specifying either +`parser_whitequark` or `parser_prism` as the value for the `ParserEngine`. + +Here are the parsers used as backends for each value: + +- `ParserEngine: default` +- `ParserEngine: parser_whitequark` ... https://github.com/whitequark/parser +- `ParserEngine: parser_prism` ... https://github.com/ruby/prism (`Prism::Translation::Parser`) + +`parser_whitequark` can analyze source code from Ruby 2.0 until Ruby 3.4: + +[source,yaml] +---- +AllCops: + ParserEngine: parser_whitequark +---- + +`parser_prism` can analyze source code from Ruby 3.3 and above: + +[source,yaml] +---- +AllCops: + ParserEngine: parser_prism + TargetRubyVersion: 3.3 +---- + +`parser_prism` tends to perform analysis faster than `parser_whitequark`. + +== Automatically Generated Configuration + +If you have a code base with an overwhelming amount of offenses, it can +be a good idea to use `rubocop --auto-gen-config`, which creates +`.rubocop_todo.yml` and adds `inherit_from: .rubocop_todo.yml` in your +`.rubocop.yml`. The generated file `.rubocop_todo.yml` contains +configuration to disable cops that currently detect an offense in the +code by changing the configuration for the cop, excluding the offending +files, or disabling the cop altogether once a file count limit has been +reached. + +By adding the option `--exclude-limit COUNT`, e.g., `rubocop +--auto-gen-config --exclude-limit 5`, you can change how many files are +excluded before the cop is entirely disabled. The default COUNT is 15. +If you don't want the cop to be entirely disabled regardless of the +number of files, use the `--no-exclude-limit` option, e.g., +`rubocop --auto-gen-config --no-exclude-limit`. + +The next step is to cut and paste configuration from `.rubocop_todo.yml` +into `.rubocop.yml` for everything that you think is in line with your +(organization's) code style and not a good fit for a todo list. Pay +attention to the comments above each entry. They can reveal configuration +parameters such as `EnforcedStyle`, which can be used to modify the +behavior of a cop instead of disabling it completely. + +Then you can start removing the entries in the generated +`.rubocop_todo.yml` file one by one as you work through all the offenses +in the code. You can also regenerate your `.rubocop_todo.yml` using +the same options by running `rubocop --regenerate-todo`. + +Another way of silencing offense reports, aside from configuration, is +through source code comments. These can be added manually or +automatically. See "Disabling Cops within Source Code" below. + +The cops in the `Metrics` department will by default get `Max` parameters +generated in `.rubocop_todo.yml`. The value of these will be just high enough +so that no offenses are reported the next time you run `rubocop`. If you +prefer to exclude files, like for other cops, add `--auto-gen-only-exclude` +when running with `--auto-gen-config`. It will still change the maximum if the +number of excluded files is higher than the exclude limit. + +Some cops have a configurable option named `EnforcedStyle`. +By default, when generating the `.rubocop_todo.yml`, if one style is used +for all files, these cops will add the settings for the style being used. +If you want to exclude on a file-by-file basis, +add the `--no-auto-gen-enforced-style` option along with `--auto-gen-config`. + +== Updating the configuration file + +When you update RuboCop version, sometimes you need to change `.rubocop.yml`. +If you use https://github.com/pocke/mry[mry], you can update `.rubocop.yml` +to latest version automatically. + +[source,sh] +---- +$ gem install mry +# Update to latest version +$ mry .rubocop.yml +# Update to specified version +$ mry --target=0.48.0 .rubocop.yml +---- + +See https://github.com/pocke/mry for more information. + +== Disabling Cops within Source Code + +One or more individual cops can be disabled locally in a section of a +file by adding a comment such as + +[source,ruby] +---- +# rubocop:disable Layout/LineLength, Style/StringLiterals +[...] +# rubocop:enable Layout/LineLength, Style/StringLiterals +---- + +You can also disable entire departments by giving a department name in the comment. + +[source,ruby] +---- +# rubocop:disable Metrics, Layout/LineLength +[...] +# rubocop:enable Metrics, Layout/LineLength +---- + +You can also disable _all_ cops with + +[source,ruby] +---- +# rubocop:disable all +[...] +# rubocop:enable all +---- + +In cases where you want to differentiate intentionally-disabled cops vs. cops +you'd like to revisit later, you can use `rubocop:todo` as an alias of +`rubocop:disable`. + +[source,ruby] +---- +# rubocop:todo Layout/LineLength, Style/StringLiterals +[...] +# rubocop:enable Layout/LineLength, Style/StringLiterals +---- + +One or more cops can be disabled on a single line with an end-of-line +comment. + +[source,ruby] +---- +for x in (0..19) # rubocop:disable Style/For +---- + +If you want to disable a cop that inspects comments, you can do so by +adding an "inner comment" on the comment line. + +[source,ruby] +---- +# coding: utf-8 # rubocop:disable Style/Encoding +---- + +Running `rubocop --autocorrect --disable-uncorrectable` will +create comments to disable all offenses that can't be automatically +corrected. + +You can add a comment to the disabling/enabling directive by prefixing it with `--`. For example: + +[source,ruby] +---- +# rubocop:disable Layout/LineLength -- A comment explaining why the cop is disabled +---- + +The syntax of directives can be checked using the cop `Lint/CopDirectiveSyntax`. + +== Temporarily enabling cops in source code + +In a similar way to disabling cops within source code, you can also temporarily enable specific +cops if you want to enforce specific rules for part of the totality of a file. + +Let's use the cop `Style/AsciiComments`, which is by default `Enabled: false`. If you want a +specific file to have ASCII-only comments to be compatible with some specific post-processing. + +[source,ruby] +---- +# rubocop:enable Style/AsciiComments +# If applicable, leave a comment to others explaining the rationale: +# We need the comments to remain ASCII only for compatibility with lib/post_processor.rb + +class Restaurant + # This comment has to be ASCII-only because of the rubocop:enable directive + def menu + return dishes.map(&:humanize) + end +end +---- + +You can also enforce the same for part of a file by disabling the cop afterwards + +[source,ruby] +---- +class Dish + def humanize + return [ + "Delicious #{self.name}" + *ingredients + ].join("\n") + end +end + +# rubocop:enable Style/AsciiComments +# If applicable, leave a comment to others explaining the rationale: +# We need the comments to remain ASCII only for compatibility with lib/post_processor.rb + +class Restaurant + # This comment has to be ASCII-only because of the rubocop:enable directive + def menu + return dishes.map(&:humanize) + end +end + +# rubocop:disable Style/AsciiComments + +class Ingredient + # Notice how the comment below is non-ASCII + # Gets rid of odd characters like 😀, ͸ + def sanitize + self.name.gsub(/[^a-z]/, '') + end +end +---- + +== Setting the style guide URL + +You can specify the base URL of the style guide using `StyleGuideBaseURL`. +If specified under `AllCops`, all cops are targeted. + +[source,yaml] +---- +AllCops: + StyleGuideBaseURL: https://rubystyle.guide +---- + +`StyleGuideBaseURL` is combined with `StyleGuide` specified to the cop. + +[source,yaml] +---- +Lint/UselessAssignment: + StyleGuide: '#underscore-unused-vars' +---- + +The style guide URL is https://rubystyle.guide#underscore-unused-vars. + +If specified under a specific department, it takes precedence over `AllCops`. +The following is an example of specifying `Rails` department. + +[source,yaml] +---- +Rails: + StyleGuideBaseURL: https://rails.rubystyle.guide +---- + +[source,yaml] +---- +Rails/TimeZone: + StyleGuide: '#time' +---- + +The style guide URL is https://rails.rubystyle.guide#time. + +== Setting the documentation URL + +You can specify the base URL of the documentation using `DocumentationBaseURL`. +If specified under `AllCops`, all cops are targeted. + +[source,yaml] +---- +AllCops: + DocumentationBaseURL: https://docs.rubocop.org/rubocop +---- + +If specified under a specific department, it takes precedence over `AllCops`. +The following is an example of specifying `Rails` department. + +[source,yaml] +---- +Rails: + DocumentationBaseURL: https://docs.rubocop.org/rubocop-rails +---- + +By default, documentation is expected to be served as HTML but if you prefer +to use something else like markdown you can set `DocumentationExtension`. + +With markdown as the documentation format you are able to host it directly through +GitHub without having to own a domain or using GitHub Pages. The `rubocop-sorbet` +extension is an example of this, its docs are available +https://github.com/Shopify/rubocop-sorbet/blob/main/manual[here]. + +[source,yaml] +---- +Sorbet: + DocumentationBaseURL: https://github.com/Shopify/rubocop-sorbet/blob/main/manual + DocumentationExtension: .md +---- + +== Setting the version tracking metadata for cops + +This configuration is particularly useful when custom cops are distributed as a gem. + +Each cop can have the following additional metadata: + +* `VersionAdded` - the RuboCop version in which it was added +* `VersionChanged` (optional) - the latest RuboCop version in which it was changed in a user-impacting way (new config, updated defaults, etc) + +[source,yaml] +---- +Style/HashSyntax: + VersionAdded: '0.9' + VersionChanged: '1.67' +---- + +NOTE: These values do not include patch versions. + +Those will be pretty useful for the documentation (so the manual generation has to be enhanced to include them) and keeping track of changes. + +== Enable checking Active Support extensions + +Some cops for checking specified methods (e.g. `Style/HashExcept`) support Active Support extensions. +This is off by default, but can be enabled by the `ActiveSupportExtensionsEnabled` option. + +[source,yaml] +---- +AllCops: + ActiveSupportExtensionsEnabled: true +---- + +== Opting into globally frozen string literals + +Ruby continues to move into the direction of having all string literals frozen by default. +Ruby 3.4 for example will show a warning if a non-frozen string literal from a file without +the frozen string literal magic comment gets modified. By starting ruby with the environment +variable `RUBYOPT` set to `--enable=frozen-string-literal` you can opt into that behaviour today. +For RuboCop to provide accurate analysis you must also configure the `StringLiteralsFrozenByDefault` +option. + +[source,yaml] +---- +AllCops: + StringLiteralsFrozenByDefault: true +---- diff --git a/lib/activeadmin/quill_editor.rb b/lib/activeadmin/quill_editor.rb index 82c8a0d..20189c0 100644 --- a/lib/activeadmin/quill_editor.rb +++ b/lib/activeadmin/quill_editor.rb @@ -1,3 +1,4 @@ # frozen_string_literal: true require 'activeadmin/quill_editor/engine' +require 'formtastic/inputs/quill_editor_input' diff --git a/lib/activeadmin/quill_editor/engine.rb b/lib/activeadmin/quill_editor/engine.rb index 5010455..3fc5f36 100644 --- a/lib/activeadmin/quill_editor/engine.rb +++ b/lib/activeadmin/quill_editor/engine.rb @@ -32,4 +32,4 @@ module ActiveAdmin end end end -end \ No newline at end of file +end diff --git a/spec/page_objects/shared/html_editor.rb b/spec/page_objects/shared/html_editor.rb index d74b934..c31e759 100644 --- a/spec/page_objects/shared/html_editor.rb +++ b/spec/page_objects/shared/html_editor.rb @@ -18,7 +18,7 @@ module Shared end def select_all - content_element.send_keys([:control, "a"]) + content_element.send_keys([:control, 'a']) self end diff --git a/spec/support/javascript_helper.rb b/spec/support/javascript_helper.rb index 62735c8..4753765 100644 --- a/spec/support/javascript_helper.rb +++ b/spec/support/javascript_helper.rb @@ -7,6 +7,7 @@ module JavaScriptHelper loop do quill_loaded = page.evaluate_script('typeof Quill !== "undefined"') break if quill_loaded + sleep 0.1 end end @@ -20,6 +21,7 @@ module JavaScriptHelper typeof getQuillEditorByElementId === 'function' JS break if init_loaded + sleep 0.1 end end @@ -93,6 +95,7 @@ module JavaScriptHelper def quill_has_content?(text, index = 0) content = get_quill_content(index) return false unless content + content.include?(text) end @@ -115,4 +118,4 @@ end RSpec.configure do |config| config.include JavaScriptHelper, type: :system -end \ No newline at end of file +end diff --git a/spec/support/string_clean_multiline.rb b/spec/support/string_clean_multiline.rb index 16fc0db..8499c24 100644 --- a/spec/support/string_clean_multiline.rb +++ b/spec/support/string_clean_multiline.rb @@ -4,7 +4,7 @@ module StringCleanMultiline refine String do def clean_multiline # Get rid of newlines and indentation spaces - strip.gsub(/\s*\n\s*/, "") + strip.gsub(/\s*\n\s*/, '') end end end diff --git a/spec/system/css_loading_spec.rb b/spec/system/css_loading_spec.rb index e9ea1d5..ddbcdf4 100644 --- a/spec/system/css_loading_spec.rb +++ b/spec/system/css_loading_spec.rb @@ -2,12 +2,10 @@ require 'rails_helper' -RSpec.describe 'CSS Loading', type: :system do +RSpec.describe 'CSS Loading' do before do # Create an admin user if authentication is enabled - if defined?(AdminUser) - AdminUser.create!(email: 'admin@example.com', password: 'password') - end + AdminUser.create!(email: 'admin@example.com', password: 'password') if defined?(AdminUser) end context 'when loading Quill editor CSS' do @@ -32,10 +30,11 @@ RSpec.describe 'CSS Loading', type: :system do # Ensure no CDN URLs quill_stylesheets.each do |href| next if href == 'inline' + expect(href).not_to match(%r{https?://cdn}) - expect(href).not_to match(%r{jsdelivr}) - expect(href).not_to match(%r{unpkg}) - expect(href).to match(%r{/assets/}) if href != 'inline' + expect(href).not_to include('jsdelivr') + expect(href).not_to include('unpkg') + expect(href).to include('/assets/') end end @@ -79,20 +78,27 @@ RSpec.describe 'CSS Loading', type: :system do # Check that Quill styles are applied editor_styles = page.evaluate_script(<<~JS) (function() { - const editor = document.querySelector('.ql-container'); - if (!editor) return null; - const styles = window.getComputedStyle(editor); + const container = document.querySelector('.ql-container'); + const editor = document.querySelector('.ql-editor'); + if (!container || !editor) return null; + + const containerStyles = window.getComputedStyle(container); + const editorStyles = window.getComputedStyle(editor); + return { - position: styles.position, - display: styles.display, - hasBackground: styles.backgroundColor !== '' && styles.backgroundColor !== 'rgba(0, 0, 0, 0)' + containerDisplay: containerStyles.display, + containerBorderWidth: parseFloat(containerStyles.borderTopWidth || '0'), + editorBackground: editorStyles.backgroundColor, + editorPadding: editorStyles.paddingTop }; })() JS expect(editor_styles).not_to be_nil - expect(editor_styles['display']).not_to eq('none') - expect(editor_styles['hasBackground']).to be true + expect(editor_styles['containerDisplay']).not_to eq('none') + expect(editor_styles['containerBorderWidth']).to be > 0 + expect(editor_styles['editorBackground']).not_to eq('rgba(0, 0, 0, 0)') + expect(editor_styles['editorPadding']).not_to eq('0px') # Check toolbar styles toolbar_visible = page.evaluate_script(<<~JS) @@ -124,7 +130,7 @@ RSpec.describe 'CSS Loading', type: :system do JS # Check for bubble theme CSS (if included) - bubble_theme_loaded = page.evaluate_script(<<~JS) + _bubble_theme_loaded = page.evaluate_script(<<~JS) Array.from(document.styleSheets).some(sheet => { try { return sheet.cssRules && Array.from(sheet.cssRules).some(rule => @@ -159,4 +165,4 @@ RSpec.describe 'CSS Loading', type: :system do expect(quill_css_count).to be <= 1 end end -end \ No newline at end of file +end diff --git a/spec/system/quill_editor_spec.rb b/spec/system/quill_editor_spec.rb index e572ddb..f8ad03d 100644 --- a/spec/system/quill_editor_spec.rb +++ b/spec/system/quill_editor_spec.rb @@ -31,7 +31,7 @@ RSpec.describe 'Quill editor' do it 'edits some content using the editor' do editor.select_all editor.toggle_link - editor.tooltip_editing.send_keys(["https://blocknot.es", :return]) + editor.tooltip_editing.send_keys(['https://blocknot.es', :return]) editor << :right << :return << 'More content' editor.toggle_bold @@ -49,18 +49,18 @@ RSpec.describe 'Quill editor' do editor << 'code block enabled' << :return editor.toggle_code_block - editor << "Some text" + editor << 'Some text' editor.toggle_sub - editor << "sub text" + editor << 'sub text' editor.toggle_sub - editor << " More text" + editor << ' More text' editor.toggle_super - editor << "sup text" + editor << 'sup text' editor.toggle_super editor << :return editor.open_dropdown(:align).toggle_align_right - editor << "Text aligned on the right" + editor << 'Text aligned on the right' expect(editor.content).to eq <<~HTML.clean_multiline

Some content

diff --git a/spec/system/quill_js_spec.rb b/spec/system/quill_js_spec.rb index ee2e650..1fa4095 100644 --- a/spec/system/quill_js_spec.rb +++ b/spec/system/quill_js_spec.rb @@ -17,7 +17,7 @@ RSpec.describe 'Quill JS' do Admin::Posts::EditPage.new(path: path).load end - it "returns the available editors", :aggregate_failures do + it 'returns the available editors', :aggregate_failures do editors_count = page.evaluate_script('window.getQuillEditors().length') expect(editors_count).to eq 2 @@ -40,7 +40,7 @@ RSpec.describe 'Quill JS' do Admin::Posts::EditPage.new(path: path).load end - it "returns the expected editor instance" do + it 'returns the expected editor instance' do expected_element = find('#post_description > .ql-container') editor = page.evaluate_script('window.getQuillEditorByIndex(1).container') expect(editor).to eq expected_element @@ -56,7 +56,7 @@ RSpec.describe 'Quill JS' do Admin::Posts::EditPage.new(path: path).load end - it "returns the expected editor instance" do + it 'returns the expected editor instance' do expected_element = find('#post_description > .ql-container') editor = page.evaluate_script('window.getQuillEditorByElementId("post_description").container') expect(editor).to eq expected_element