diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 000000000..77f7a526a --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,277 @@ +# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json +language: "en-US" +early_access: false + +reviews: + profile: "chill" + request_changes_workflow: false + high_level_summary: true + high_level_summary_instructions: > + Evaluate this pull request against the legacy Expertiza Danger policy as well as normal Rails review quality. + Call out violations in pull request size, file scope, work-in-progress titles, TODO/FIXME markers, + debug statements, missing tests, schema-without-migration changes, config-file churn, vendor/factory changes, + and shallow RSpec patterns. + review_status: true + review_details: true + commit_status: true + fail_commit_status: false + collapse_walkthrough: false + changed_files_summary: true + sequence_diagrams: false + estimate_code_review_effort: true + assess_linked_issues: true + related_issues: true + related_prs: true + suggested_labels: true + suggested_reviewers: false + auto_apply_labels: false + auto_assign_reviewers: false + in_progress_fortune: false + poem: false + enable_prompt_for_ai_agents: false + + labeling_instructions: + - label: "api" + instructions: "Apply when controllers, routes, request specs, serializers, or Swagger docs change." + - label: "database" + instructions: "Apply when models, migrations, schema, associations, or data integrity rules change." + - label: "workflow" + instructions: "Apply when GitHub Actions, CI, bot behavior, or automation files change." + - label: "security" + instructions: "Apply when authentication, authorization, secrets handling, or workflow permissions change." + - label: "tests" + instructions: "Apply when RSpec coverage or test helpers are added or modified." + + path_filters: + - "!coverage/**" + - "!tmp/**" + - "!log/**" + - "!storage/**" + - "!public/assets/**" + - "!vendor/bundle/**" + - "!*.log" + - "!*.ibd" + + path_instructions: + - path: "app/controllers/**/*.rb" + instructions: | + Review authorization, strong parameters, HTTP status codes, and response shape consistency. + Flag N+1 queries, missing request-spec coverage, and behavior changes not reflected in Swagger docs. + + - path: "app/models/**/*.rb" + instructions: | + Focus on associations, validations, callbacks, transactions, STI and polymorphic behavior, + foreign keys, data integrity, and query efficiency. + + - path: "app/serializers/**/*.rb" + instructions: | + Check backward compatibility of response payloads and accidental exposure of internal or sensitive fields. + + - path: "app/mailers/**/*.rb" + instructions: | + Check mailer/template consistency, recipient safety, and avoid debug output or sensitive data leaks. + + - path: "config/routes.rb" + instructions: | + Flag duplicate or shadowed routes, surprising non-RESTful patterns, and route changes without matching request specs. + + - path: "db/migrate/**/*.rb" + instructions: | + Review for reversibility, null constraints, indexes, foreign keys, destructive data changes, + and alignment with db/schema.rb and affected model behavior. + + - path: "db/schema.rb" + instructions: | + If schema changes, verify there is a corresponding migration and that indexes, constraints, + and associated tests stay aligned. + + - path: "spec/models/**/*.rb" + instructions: | + Check that model specs cover validations, associations, callbacks, and important edge cases + with meaningful assertions instead of shallow examples. + Also apply the legacy Danger policy: discourage `create(` in unit tests when `build` or doubles are enough, + reject `.should`, skipped/focused specs, wildcard matcher overuse, missing expectations, matcher-less expectations, + and expectations that only prove values are not nil, empty, or zero. + + - path: "spec/controllers/**/*.rb" + instructions: | + Apply the same legacy RSpec policy used in Expertiza Danger checks: + avoid `create(` when lighter test setup would work, avoid `.should`, + avoid skipped/focused specs, and flag shallow controller expectations. + + - path: "spec/requests/**/*.rb" + instructions: | + Check authentication, authorization, invalid input, response payload assertions, and failure cases. + When endpoints change, expect Swagger docs to stay aligned. + Apply the legacy shallow-test rules: each new test should have a meaningful expectation, + avoid commented-out expectations, and do not rely only on page content or non-nil checks. + + - path: "swagger/**/*.yml" + instructions: | + Check for drift between documented endpoints and controller or request-spec behavior. + + - path: ".github/workflows/**/*.{yml,yaml}" + instructions: | + Review GitHub Actions for unsafe pull_request_target usage, overly broad permissions, secrets exposure, + skipped-job logic, artifact passing, and brittle PR-comment behavior. + + - path: "Dangerfile" + instructions: | + Preserve the legacy Expertiza Danger policy. If replacing or changing a Danger rule, + verify the equivalent behavior exists in CodeRabbit, GitHub Actions, or another documented enforcement path. + + - path: "Gemfile" + instructions: | + Non-maintainer Gemfile changes should be scrutinized closely. Prefer existing gems when possible + and call out dependency additions or lockfile churn without strong justification. + + - path: "Gemfile.lock" + instructions: | + Flag lockfile-only churn, IDE-generated noise, or dependency graph changes that are not explained in the PR. + + - path: "*.md" + instructions: | + Non-maintainer documentation changes should be questioned unless they are clearly part of the intended work. + + - path: "*.yml" + instructions: | + Treat YAML changes as sensitive, especially workflow, environment, and configuration files. + Call out changes that look unrelated to the stated task. + + - path: ".rspec" + instructions: | + Changing `.rspec` is sensitive. Flag it unless the PR clearly justifies altering the global test runner behavior. + + - path: "config.ru" + instructions: | + Treat Rack entrypoint changes as sensitive and require a clear reason in the PR description. + + - path: "setup.sh" + instructions: | + Treat setup script changes as sensitive and require a clear reason in the PR description. + + - path: "vendor/**" + instructions: | + Flag vendor directory changes unless the PR clearly explains why vendored files must change. + + - path: "spec/factories/**" + instructions: | + Non-maintainer changes to factories should be scrutinized. Ensure they are necessary and do not hide weak tests. + + - path: "Dockerfile" + instructions: | + Review base image choices, layer ordering, cleanup, and security-sensitive package installation. + + - path: "bin/*" + instructions: | + Check shell safety, idempotence, and environment assumptions for developer setup scripts. + + auto_review: + enabled: true + drafts: false + auto_incremental_review: true + auto_pause_after_reviewed_commits: 3 + ignore_title_keywords: + - "WIP" + - "DRAFT" + - "[WIP]" + + pre_merge_checks: + override_requested_reviewers_only: false + title: + mode: "warning" + requirements: "Use a concise, imperative title that names the subsystem changed." + custom_checks: + - name: "schema-without-migration" + mode: "warning" + instructions: | + Warn if db/schema.rb changes but no file under db/migrate/ changes in the same pull request. + + - name: "behavior-change-needs-tests" + mode: "warning" + instructions: | + Warn when files under app/controllers/, app/models/, config/routes.rb, or db/migrate/ change + without meaningful updates under spec/models/ or spec/requests/. + + - name: "workflow-security" + mode: "warning" + instructions: | + Warn on GitHub Actions changes that use pull_request_target with PR-head checkout, + overbroad write permissions, or token and secrets exposure patterns. + + - name: "config-and-setup-scrutiny" + mode: "warning" + instructions: | + Warn when config/database.yml, config/storage.yml, config/credentials.yml.enc, config.ru, setup.sh, + or workflow files change without a clear explanation in the pull request summary. + + - name: "todo-temp-debug-artifacts" + mode: "warning" + instructions: | + Warn when the pull request introduces TODO or FIXME markers, debug print statements, + temp or cache artifacts, or generated local-state files such as results.txt or safe.log. + + - name: "legacy-pr-scope-and-title" + mode: "warning" + instructions: | + Warn on pull requests larger than roughly 500 LoC, touching more than 30 files, + course-project PRs under roughly 50 LoC, duplicated commit messages, or WIP titles. + + - name: "legacy-config-file-guardrails" + mode: "warning" + instructions: | + For non-maintainers, warn on changes to Gemfile, Gemfile.lock, .gitignore, .rspec, Dangerfile, + Rakefile, config.ru, setup.sh, YAML files, Markdown files, vendor/**, and spec/factories/** unless clearly justified. + + - name: "legacy-rspec-hygiene" + mode: "warning" + instructions: | + Warn on skipped, pending, or focused specs; `.should`; unnecessary helper requires; + text fixtures under spec; and weak/shallow expectations such as wildcard matcher overuse, + no expectations, commented-out expectations, matcher-less expectations, or non-real-value assertions. + + - name: "legacy-global-debug-code" + mode: "warning" + instructions: | + Warn on newly introduced global variables, class variables, or debugging statements such as puts, print, + binding.pry, debugger, or console.log. + + tools: + github-checks: + enabled: true + timeout_ms: 300000 + rubocop: + enabled: true + brakeman: + enabled: true + actionlint: + enabled: true + gitleaks: + enabled: true + hadolint: + enabled: true + shellcheck: + enabled: true + markdownlint: + enabled: true + yamllint: + enabled: true + +chat: + auto_reply: true + allow_non_org_members: true + art: false + +knowledge_base: + web_search: + enabled: false + code_guidelines: + enabled: true + filePatterns: + - "CODERABBIT_GUIDELINES.md" + learnings: + scope: "local" + issues: + scope: "local" + pull_requests: + scope: "local" diff --git a/.github/workflows/CommentPR.yml b/.github/workflows/CommentPR.yml index 8f378c7f1..3e56f3ae2 100644 --- a/.github/workflows/CommentPR.yml +++ b/.github/workflows/CommentPR.yml @@ -40,5 +40,5 @@ jobs: ``` ${{ steps.read_files.outputs.test_output }} ``` - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b5c6344ed..df26ba359 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -155,12 +155,11 @@ jobs: name: code-coverage-artifacts path: coverage/ - - name: Upload code-coverage report to code-climate - run: | - export GIT_BRANCH="${GITHUB_REF/refs\/heads\//}" - gem install codeclimate-test-reporter - cc-test-reporter sum-coverage coverage/codeclimate.*.json - cc-test-reporter after-build -t simplecov -r ${{ secrets.CC_TEST_REPORTER_ID }} + - name: Upload code-coverage report to qlty + uses: qltysh/qlty-action/coverage@v2 + with: + token: ${{ secrets.CC_TEST_REPORTER_ID }} + files: coverage/coverage.json docker: needs: test diff --git a/CODERABBIT_GUIDELINES.md b/CODERABBIT_GUIDELINES.md new file mode 100644 index 000000000..d45b80846 --- /dev/null +++ b/CODERABBIT_GUIDELINES.md @@ -0,0 +1,252 @@ +# CodeRabbit Setup For This Repository + +This repository uses CodeRabbit as an AI pull request reviewer for the Expertiza backend reimplementation. The goal is to improve review quality and preserve project-specific policy without replacing the existing CI runners. + +## What CodeRabbit Should Do Here + +- Review pull requests automatically and re-review on new commits. +- Leave useful inline comments on Rails, API, database, workflow, and test changes. +- Summarize the impact of a pull request clearly for human reviewers. +- Surface policy issues that used to live in Danger-like rules, but start in warning mode instead of blocking merges. +- Read GitHub check results and use them as context when tests or workflows fail. + +## What CodeRabbit Should Not Replace Here + +- GitHub Actions still runs the real work. +- `main.yml` and `TestPR.yml` still execute RSpec, database setup, coverage generation, and Docker-related jobs. +- `lint.yml` still remains the source of truth for runnable spellcheck and lint workflows unless those checks are intentionally retired. +- CodeRabbit is a reviewer and explainer, not the test runner or deployment engine. + +## Repository Context + +- Language and framework: Ruby 3.4.5 with Rails 8. +- Test stack: RSpec, mainly under `spec/models` and `spec/requests`. +- API surface: controllers plus request specs, with Swagger documentation checked into `swagger/v1/swagger.yaml`. +- Database: MySQL-backed Rails app with a large migration history and `db/schema.rb` committed. +- Automation: GitHub Actions workflows in `.github/workflows`, including CI, PR test reporting, linting, and Danger. + +## Review Standards For This Codebase + +### Controllers + +- Verify authorization behavior and strong parameters. +- Check HTTP status codes and response payload consistency. +- Flag N+1 queries or heavy database work in request paths. +- Expect request-spec coverage for behavior changes. +- If an endpoint contract changes, expect Swagger documentation to stay in sync. + +### Models + +- Check associations, validations, callbacks, and transactions. +- Pay extra attention to STI, polymorphic associations, foreign keys, and null constraints. +- Prefer data integrity enforced in both application code and schema where appropriate. +- Flag query inefficiencies and fragile callback chains. + +### Serializers And Mailers + +- Watch for accidental exposure of internal fields or sensitive data. +- Keep serialized response shape stable unless the PR explicitly changes the API contract. +- For mailers, ensure template data and recipients are safe and consistent. + +### Routes And API Docs + +- Flag duplicate, shadowed, or surprising route definitions. +- Require controller, request-spec, and Swagger alignment when routes or endpoints change. + +### Migrations And Schema + +- Prefer reversible migrations. +- Check for missing indexes, foreign keys, null constraints, and destructive data changes. +- If `db/schema.rb` changes, expect a matching migration. +- Schema changes should usually have corresponding model and test updates. + +### Tests + +- Prefer meaningful request and model specs over shallow examples. +- Check authentication, authorization, invalid input, and failure paths. +- Flag behavior changes in `app/` or `config/routes.rb` that are not reflected in `spec/models` or `spec/requests`. +- Apply the legacy Expertiza Danger RSpec rules: + - avoid skipped, pending, or focused tests + - avoid `.should` + - avoid redundant helper `require` lines + - avoid overusing `create(` in unit-style specs when lighter setup would work + - flag shallow expectations such as wildcard matcher overuse, missing expectations, commented-out expectations, + matcher-less expectations, and assertions that only prove values are not nil, empty, or zero + +### Workflows And Bot Files + +- Treat workflow changes as security-sensitive. +- Flag `pull_request_target` combined with checkout of PR-head code, broad write permissions, unsafe token handling, brittle artifact passing, and comment spam patterns. +- If a Danger rule is removed, the replacement enforcement path must be explicit. + +## Legacy Danger Policy To Preserve + +The repository now carries a larger Expertiza-style Danger policy. CodeRabbit should mirror that policy in spirit even when the rule is heuristic rather than executable. + +### Pull Request Hygiene + +- Welcome first-time or non-maintainer contributors politely. +- Flag PRs over roughly 500 LoC. +- Flag course-project PRs under roughly 50 LoC. +- Flag PRs touching more than 30 files. +- Flag many duplicated commit messages. +- Flag WIP titles. + +### Change Content Rules + +- Flag newly added TODO or FIXME markers. +- Fail or strongly warn on temp, tmp, or cache artifacts committed into the PR. +- Flag newly introduced global variables, class variables, and obvious debug code. +- Flag application changes without corresponding test changes. + +### Spec Rules + +- Flag skipped, pending, or focused tests. +- Discourage `create(` inside model and controller specs when lighter setup would work. +- Reject `.should`. +- Reject redundant `require 'rspec'`, `spec_helper`, `rails_helper`, `test_helper`, or factory helper lines in specs. +- Warn on committed `.txt` or `.csv` files under spec paths unless clearly justified. + +### Sensitive Files + +- Non-maintainer changes to Markdown, YAML, helper files, Gemfile, Gemfile.lock, `.gitignore`, `.rspec`, + `Dangerfile`, `Rakefile`, `config.ru`, `setup.sh`, `vendor/**`, and `spec/factories/**` should be scrutinized closely. +- Schema changes should normally come with migrations. + +### Shallow Test Detection + +- Flag excessive wildcard argument matchers. +- Flag tests with no expectations. +- Flag commented-out expectations. +- Flag expectations without matchers. +- Flag expectations that only verify non-nil, non-empty, or non-zero conditions instead of real values. +- In page-oriented tests, encourage assertions beyond simple page content when deeper validation is possible. + +### Developer Scripts And Docker + +- `bin/*` and `setup.sh` should be idempotent and safe for repeated local use. +- `Dockerfile` changes should minimize layers, avoid unnecessary packages, and avoid leaving unsafe defaults behind. + +## How The `.coderabbit.yaml` Is Structured + +### Review Behavior + +- The profile is set to `chill` to reduce noise. +- `request_changes_workflow` is disabled so CodeRabbit does not block PRs on day one. +- Review details are enabled during rollout so reviewers can see ignored files and review context. +- Walkthroughs stay expanded to make the review easier to scan. +- Poems and fortune messages are disabled because this repository benefits more from direct signal than personality in review comments. + +### Auto Review + +- CodeRabbit reviews PRs automatically. +- It skips drafts. +- It re-reviews new commits incrementally. +- It pauses after a few reviewed commits so a very active PR does not get spammy. + +### Labels + +- The config narrows label suggestions to a repository-specific set: + - `api` + - `database` + - `workflow` + - `security` + - `tests` + +### Path Instructions + +The config teaches CodeRabbit that different parts of the repository need different review criteria. This is more useful than a single broad Rails instruction because this codebase has clear subsystems: + +- `app/controllers/**/*.rb` +- `app/models/**/*.rb` +- `app/serializers/**/*.rb` +- `app/mailers/**/*.rb` +- `config/routes.rb` +- `db/migrate/**/*.rb` +- `db/schema.rb` +- `spec/models/**/*.rb` +- `spec/requests/**/*.rb` +- `swagger/**/*.yml` +- `.github/workflows/**/*.{yml,yaml}` +- `Dangerfile` +- `Dockerfile` +- `bin/*` + +### Pre-Merge Checks + +These checks are configured as warnings first. They are meant to preserve policy signal from the current Danger setup without creating accidental merge blockers. + +- `schema-without-migration` +- `behavior-change-needs-tests` +- `workflow-security` +- `config-and-setup-scrutiny` +- `todo-temp-debug-artifacts` +- `legacy-pr-scope-and-title` +- `legacy-config-file-guardrails` +- `legacy-rspec-hygiene` +- `legacy-global-debug-code` + +These checks should only move from `warning` to `error` after the team is happy with the signal quality. + +### Tool Integrations + +The config enables tooling that fits this repository: + +- `github-checks` for reading CI outcomes and logs +- `rubocop` for Ruby style and common issues +- `brakeman` for Rails security review +- `actionlint` for workflow validation +- `gitleaks` for secrets scanning +- `hadolint` for Dockerfile review +- `shellcheck` for shell script review +- `markdownlint` for Markdown consistency +- `yamllint` for YAML sanity + +If the current CodeRabbit plan does not include tool integrations, the YAML can stay in place and those tools can be treated as future-ready configuration. + +### Knowledge Base + +- Web search is disabled so reviews stay grounded in repository context. +- Learnings, issues, and past PR context are scoped to `local` so this repository does not inherit unrelated preferences from other repositories. +- This file is explicitly included as a CodeRabbit guideline document through `knowledge_base.code_guidelines.filePatterns`. + +## Relationship To The Current Workflows + +### Keep + +- `.github/workflows/main.yml` +- `.github/workflows/TestPR.yml` +- `.github/workflows/lint.yml` + +These are execution workflows and should remain the source of truth for running tests, linting, coverage, and build logic. + +### Keep For Comparison During Rollout + +- `Dangerfile` +- `.github/workflows/danger.yml` +- `.github/workflows/danger_target.yml` + +Keep them temporarily while comparing CodeRabbit output against current customized review behavior. + +### Possible Future Cleanup + +Once CodeRabbit proves it is consistently covering the intended review policy, the repository can decide whether parts of the Danger setup are redundant. That should happen only after side-by-side comparison on real PRs. + +## Rollout Plan + +1. Commit `.coderabbit.yaml` and this document on a feature branch. +2. Open a small PR that touches one Rails file and, ideally, one spec or workflow file. +3. Confirm CodeRabbit posts an automatic review and walkthrough. +4. Compare its comments with Danger and CI output. +5. Tune noisy path instructions or checks before expanding enforcement. +6. Only after a few successful PRs, decide whether any Danger logic should be retired. + +## Practical Commands + +Common PR comment commands reviewers can use: + +- `@coderabbitai review` +- `@coderabbitai summary` +- `@coderabbitai resolve` + +Use comment chat to teach preferences gradually. If CodeRabbit learns something that should become a permanent repository rule, move it into this document or `.coderabbit.yaml`. diff --git a/Dangerfile b/Dangerfile index 6fd640064..334847f7f 100644 --- a/Dangerfile +++ b/Dangerfile @@ -1,59 +1,543 @@ -# Helper to safely read files in UTF-8 and avoid "invalid byte sequence" errors -def safe_read(path) - File.read(path, encoding: "UTF-8", invalid: :replace, undef: :replace) +# frozen_string_literal: true + +CURRENT_MAINTAINERS = %w[ + efg + Winbobob + TheAkbar + johnbumgardner + nnhimes +].freeze + +ADDED_FILES = git.added_files +DELETED_FILES = git.deleted_files +MODIFIED_FILES = git.modified_files +RENAMED_FILES = git.renamed_files +TOUCHED_FILES = ADDED_FILES + DELETED_FILES + MODIFIED_FILES + RENAMED_FILES +LOC = git.lines_of_code +COMMITS = git.commits + +PR_AUTHOR = github.respond_to?(:pr_author) ? github.pr_author.to_s : '' +PR_TITLE = github.respond_to?(:pr_title) ? github.pr_title.to_s : '' +PR_DIFF = github.respond_to?(:pr_diff) ? github.pr_diff.to_s : '' +PR_ADDED = PR_DIFF + .split("\n") + .select { |loc| loc.start_with?('+') && !loc.include?('+++ b') } + .join("\n") + +def added_lines_for(file) + diff = git.diff_for_file(file) + patch = diff&.patch.to_s + patch + .split("\n") + .select { |loc| loc.start_with?('+') && !loc.include?('+++ b') } end -# --- PR Size Checks --- -warn("Pull request is too big (more than 500 LoC).") if git.lines_of_code > 500 -warn("Pull request is too small (less than 50 LoC).") if git.lines_of_code < 50 +def warning_message_of_config_file_change(filename, regex) + return if CURRENT_MAINTAINERS.include?(PR_AUTHOR) + return unless TOUCHED_FILES.grep(regex).any? + + fail("You changed #{filename}; please double-check whether this is necessary.", sticky: true) +end + +# ------------------------------------------------------------------------------ +# 0. Welcome message +# ------------------------------------------------------------------------------ +unless CURRENT_MAINTAINERS.include?(PR_AUTHOR) + if PR_TITLE =~ /E[0-9]{4}/ + message( + markdown( + <<~MARKDOWN + Thanks for the pull request, and welcome! The Expertiza team is excited to review your changes, and you should hear from us soon. + + Please make sure the PR passes all checks and you have run `rubocop -a` to autocorrect issues before requesting a review. -# --- File Change Checks --- -warn("Pull request touches too many files (more than 30 files).") if git.modified_files.count > 30 + This repository is being automatically checked for code-quality issues using CodeRabbit, Danger, and CI workflows. Please address newly introduced issues before marking the PR ready for review. -# --- Commit Message Checks --- -commit_messages = git.commits.map(&:message) -duplicated_commits = commit_messages.group_by(&:itself).select { |_, v| v.size > 1 } -warn("Pull request has duplicated commit messages.") if duplicated_commits.any? + Also, please spend some time looking at the instructions at the top of your course project writeup. + If you have any questions, please send email to expertiza-support@lists.ncsu.edu. + MARKDOWN + ) + ) + else + message( + markdown( + <<~MARKDOWN + Thanks for the pull request, and welcome! The Expertiza team is excited to review your changes, and you should hear from us soon. + + Please make sure the PR passes all checks and you have run `rubocop -a` to autocorrect issues before requesting a review. + + This repository is being automatically checked for code-quality issues using CodeRabbit, Danger, and CI workflows. Please address newly introduced issues before marking the PR ready for review. + + If you have any questions, please send email to expertiza-support@lists.ncsu.edu. + MARKDOWN + ) + ) + end +end + +# ------------------------------------------------------------------------------ +# 1. Your pull request should not be too big (more than 500 LoC). +# ------------------------------------------------------------------------------ +if LOC > 500 + warn( + markdown( + <<~MARKDOWN + Your pull request is more than 500 LoC. + Please make sure you did not commit unnecessary changes, such as `schema.rb`, `node_modules`, or changelog noise. + MARKDOWN + ), + sticky: true + ) +end + +# ------------------------------------------------------------------------------ +# 2. Your pull request should not be too small (less than 50 LoC). +# ------------------------------------------------------------------------------ +if PR_TITLE =~ /E[0-9]{4}/ && LOC < 50 + warn( + markdown( + <<~MARKDOWN + Your pull request is less than 50 LoC. + If you are finished refactoring the code, please consider writing corresponding tests. + MARKDOWN + ), + sticky: true + ) +end -# --- TODO/FIXME Checks --- -todo_fixme = (git.modified_files + git.added_files).any? do |file| - File.exist?(file) && safe_read(file).match?(/\b(TODO|FIXME)\b/i) +# ------------------------------------------------------------------------------ +# 3. Your pull request should not touch too many files (more than 30 files). +# ------------------------------------------------------------------------------ +if TOUCHED_FILES.size > 30 + warn( + markdown( + <<~MARKDOWN + Your pull request touches more than 30 files. + Please make sure you did not commit unnecessary changes, such as `node_modules`, `vendor`, or workflow churn. + MARKDOWN + ), + sticky: true + ) end -warn("Pull request contains TODO or FIXME comments.") if todo_fixme -# --- Temp File Checks --- -temp_files = git.added_files.any? { |file| file.match?(/(tmp|temp|cache)/i) } -warn("Pull request includes temp, tmp, or cache files.") if temp_files +# ------------------------------------------------------------------------------ +# 4. Your pull request should not have too many duplicated commit messages. +# ------------------------------------------------------------------------------ +messages = COMMITS.map(&:message) +has_many_dup_commit_messages = messages.uniq.any? { |msg| messages.count(msg) >= 5 } -# --- Missing Test Checks --- -warn("There are no test changes in this PR.") if (git.modified_files + git.added_files).none? { |f| f.include?('spec/') || f.include?('test/') } +if has_many_dup_commit_messages + warn( + markdown( + <<~MARKDOWN + Your pull request has many duplicated commit messages. Please try to squash similar commits + and use meaningful commit messages later. + MARKDOWN + ), + sticky: true + ) +end + +# ------------------------------------------------------------------------------ +# 5. Your pull request is "work in progress" and it will not be merged. +# ------------------------------------------------------------------------------ +if PR_TITLE.match?(/\bWIP\b/i) + warn( + markdown( + <<~MARKDOWN + This pull request is classed as `Work in Progress`. It cannot be merged right now. + MARKDOWN + ), + sticky: true + ) +end + +# ------------------------------------------------------------------------------ +# 6. Your pull request should not contain "Todo" or "Fixme" keyword. +# ------------------------------------------------------------------------------ +if PR_ADDED.match?(/\b(TODO|FIXME)\b/i) + warn( + markdown( + <<~MARKDOWN + This pull request contains `TODO` or `FIXME` task(s); please fix them. + MARKDOWN + ), + sticky: true + ) +end + +# ------------------------------------------------------------------------------ +# 7. Your pull request should not include temp, tmp, cache file. +# ------------------------------------------------------------------------------ +if ADDED_FILES.grep(/temp|tmp|cache/i).any? + fail( + markdown( + <<~MARKDOWN + You committed `temp`, `tmp` or `cache` files. Please remove them. + MARKDOWN + ), + sticky: true + ) +end + +# ------------------------------------------------------------------------------ +# 8. Your pull request should avoid using global variables and/or class variables. +# ------------------------------------------------------------------------------ +(ADDED_FILES + MODIFIED_FILES + RENAMED_FILES).each do |file| + next unless file.end_with?('.rb') + + added_lines = added_lines_for(file).join("\n") + next unless added_lines.match?(/\$[A-Za-z0-9_]+/) || added_lines.match?(/@@[A-Za-z0-9_]+/) + + warn( + markdown( + <<~MARKDOWN + You are using global variables (`$`) or class variables (`@@`); please double-check whether this is necessary. + MARKDOWN + ), + sticky: true + ) + break +end + +# ------------------------------------------------------------------------------ +# 9. Your pull request should avoid keeping debugging code. +# ------------------------------------------------------------------------------ +if PR_ADDED.include?('puts ') || + PR_ADDED.include?('print ') || + PR_ADDED.include?('binding.pry') || + PR_ADDED.include?('debugger;') || + PR_ADDED.include?('console.log') + warn('You are including debug code in your pull request, please remove it.', sticky: true) +end + +# ------------------------------------------------------------------------------ +# 10. You should write tests after making changes to the application. +# ------------------------------------------------------------------------------ +if TOUCHED_FILES.grep(%r{^app/}).any? && TOUCHED_FILES.grep(%r{^spec/}).empty? + warn( + markdown( + <<~MARKDOWN + There are code changes, but no corresponding tests. + Please include tests if this PR introduces any modifications in behavior. + MARKDOWN + ), + sticky: true + ) +end -# --- .md File Changes --- -md_changes = git.modified_files.any? { |file| file.end_with?('.md') } -warn("Pull request modifies markdown files (*.md). Make sure you have a good reason.") if md_changes +# ------------------------------------------------------------------------------ +# 11. Your pull request should not include skipped/pending/focused test cases. +# ------------------------------------------------------------------------------ +(ADDED_FILES + MODIFIED_FILES + RENAMED_FILES).each do |file| + next unless file.end_with?('_spec.rb') -# --- DB Schema Changes --- -schema_changes = git.modified_files.any? { |file| file.match?(/db\/schema\.rb$/) } -unless git.modified_files.any? { |file| file.match?(/db\/migrate\//) } - warn("Schema changes detected without a corresponding DB migration.") if schema_changes + added_lines = added_lines_for(file).join("\n") + if added_lines.include?('xdescribe') || + added_lines.include?('xspecify') || + added_lines.include?('xexample') || + added_lines.include?('xit') || + added_lines.include?('skip(') || + added_lines.include?('skip ') || + added_lines.include?('pending(') || + added_lines.include?('fdescribe') || + added_lines.include?('fit') + warn( + markdown( + <<~MARKDOWN + There are one or more skipped, pending, or focused test cases in your pull request. Please fix them. + MARKDOWN + ), + sticky: true + ) + break + end end -# --- Config/Setup File Changes (selected ones not excluded) --- -config_files = %w[ - config/database.yml - config/secrets.yml - config/secrets.yml.example - config/settings.yml - config/settings.yml.example - setup.sh - config.ru -] -changed_config_files = git.modified_files.select { |file| config_files.include?(file) } -warn("Pull request modifies config or setup files: #{changed_config_files.join(', ')}.") if changed_config_files.any? +# ------------------------------------------------------------------------------ +# 12. Unit tests and integration tests should avoid using "create" keyword. +# ------------------------------------------------------------------------------ +(ADDED_FILES + MODIFIED_FILES + RENAMED_FILES).each do |file| + next unless file.match?(%r{spec/models}) || file.match?(%r{spec/controllers}) + + added_lines = added_lines_for(file).join("\n") + next unless added_lines.match?(/create\(/) + + warn( + markdown( + <<~MARKDOWN + Using `create` in unit tests or integration tests may be overkill. Try to use `build` or `double` instead. + MARKDOWN + ), + sticky: true + ) + break +end + +# ------------------------------------------------------------------------------ +# 13. RSpec tests should avoid using "should" keyword. +# ------------------------------------------------------------------------------ +(ADDED_FILES + MODIFIED_FILES + RENAMED_FILES).each do |file| + next unless file.end_with?('_spec.rb') + + added_lines = added_lines_for(file).join("\n") + next unless added_lines.include?('.should') + + warn( + markdown( + <<~MARKDOWN + The `should` syntax is deprecated in RSpec 3. Please use `expect` syntax instead. + Even in test descriptions, please avoid using `should`. + MARKDOWN + ), + sticky: true + ) + break +end + +# ------------------------------------------------------------------------------ +# 14. Your RSpec testing files do not need to require helper files. +# ------------------------------------------------------------------------------ +if PR_ADDED.include?("require 'rspec'") || + PR_ADDED.include?('require "rspec"') || + PR_ADDED.include?("require 'spec_helper'") || + PR_ADDED.include?('require "spec_helper"') || + PR_ADDED.include?("require 'rails_helper'") || + PR_ADDED.include?('require "rails_helper"') || + PR_ADDED.include?("require 'test_helper'") || + PR_ADDED.include?('require "test_helper"') || + PR_ADDED.include?("require 'factory_girl_rails'") || + PR_ADDED.include?('require "factory_girl_rails"') || + PR_ADDED.include?("require 'factory_bot_rails'") || + PR_ADDED.include?('require "factory_bot_rails"') + warn( + markdown( + <<~MARKDOWN + You are requiring `rspec`, fixture-related gems, or helper methods in RSpec tests. + These have already been included, so you do not need to require them again. Please remove them. + MARKDOWN + ), + sticky: true + ) +end + +# ------------------------------------------------------------------------------ +# 15. You should avoid committing text files for RSpec tests. +# ------------------------------------------------------------------------------ +if TOUCHED_FILES.grep(/spec.*\.(txt|csv)$/).any? + warn('You committed text files (`*.txt` or `*.csv`) for RSpec tests; please double-check whether this is necessary.', sticky: true) +end + +# ------------------------------------------------------------------------------ +# 16. Your pull request should not change or add *.md files unless you have a good reason. +# ------------------------------------------------------------------------------ +if !CURRENT_MAINTAINERS.include?(PR_AUTHOR) && TOUCHED_FILES.grep(/\.md$/).any? + warn( + markdown( + <<~MARKDOWN + You changed MARKDOWN (`*.md`) documents; please double-check whether it is necessary to do so. + Alternatively, you can insert project-related content in the description field of the pull request. + MARKDOWN + ), + sticky: true + ) +end + +# ------------------------------------------------------------------------------ +# 17. Your pull request should not change DB schema unless there is new DB migration. +# ------------------------------------------------------------------------------ +if !CURRENT_MAINTAINERS.include?(PR_AUTHOR) && + TOUCHED_FILES.grep(%r{db/migrate}).empty? && + (MODIFIED_FILES.grep(/schema\.rb$/).any? || TOUCHED_FILES.grep(/schema\.json$/).any?) + warn( + markdown( + <<~MARKDOWN + You should commit changes to the DB schema (`db/schema.rb`) only if you have created new DB migrations. + Please double check your code. If you did not aim to change the DB, please revert the DB schema changes. + MARKDOWN + ), + sticky: true + ) +end + +# ------------------------------------------------------------------------------ +# 18. Your pull request should not modify *.yml or *.yml.example file. +# ------------------------------------------------------------------------------ +if !CURRENT_MAINTAINERS.include?(PR_AUTHOR) && TOUCHED_FILES.grep(/\.ya?ml(\.example)?$/).any? + warn( + markdown( + <<~MARKDOWN + You changed YAML (`*.yml`, `*.yaml`) or example config files; please double-check whether this is necessary. + MARKDOWN + ), + sticky: true + ) +end + +# ------------------------------------------------------------------------------ +# 19. Your pull request should not modify test-related helper files. +# ------------------------------------------------------------------------------ +if !CURRENT_MAINTAINERS.include?(PR_AUTHOR) && + (MODIFIED_FILES.grep(/rails_helper\.rb$/).any? || MODIFIED_FILES.grep(/spec_helper\.rb$/).any?) + warn( + markdown( + <<~MARKDOWN + You should not change `rails_helper.rb` or `spec_helper.rb` without a strong reason; please double-check these changes. + MARKDOWN + ), + sticky: true + ) +end + +# ------------------------------------------------------------------------------ +# 20. Your pull request should not modify Gemfile, Gemfile.lock. +# ------------------------------------------------------------------------------ +if !CURRENT_MAINTAINERS.include?(PR_AUTHOR) && + (MODIFIED_FILES.include?('Gemfile') || MODIFIED_FILES.include?('Gemfile.lock')) + warn( + markdown( + <<~MARKDOWN + You are modifying `Gemfile` or `Gemfile.lock`, please double check whether it is necessary. + Add a new gem only if you have a very good reason, and please revert lockfile noise made by the IDE. + MARKDOWN + ), + sticky: true + ) +end + +# ------------------------------------------------------------------------------ +# 21-33. Configuration files should not change casually. +# ------------------------------------------------------------------------------ +warning_message_of_config_file_change('.bowerrc', /\.bowerrc$/) +warning_message_of_config_file_change('.gitignore', /\.gitignore$/) +warning_message_of_config_file_change('.mention-bot', /\.mention-bot$/) +warning_message_of_config_file_change('.rspec', /\.rspec$/) +warning_message_of_config_file_change('Capfile', /(^|\/)Capfile$/) +warning_message_of_config_file_change('Dangerfile', /(^|\/)Dangerfile$/) +warning_message_of_config_file_change('Guardfile', /(^|\/)Guardfile$/) +warning_message_of_config_file_change('LICENSE', /(^|\/)LICENSE$/) +warning_message_of_config_file_change('Procfile', /(^|\/)Procfile$/) +warning_message_of_config_file_change('Rakefile', /(^|\/)Rakefile$/) +warning_message_of_config_file_change('bower.json', /(^|\/)bower\.json$/) +warning_message_of_config_file_change('config.ru', /(^|\/)config\.ru$/) +warning_message_of_config_file_change('setup.sh', /(^|\/)setup\.sh$/) + +# ------------------------------------------------------------------------------ +# 34. The PR should not modify vendor folder. +# ------------------------------------------------------------------------------ +if !CURRENT_MAINTAINERS.include?(PR_AUTHOR) && TOUCHED_FILES.grep(%r{^vendor/}).any? + warn( + markdown( + <<~MARKDOWN + You modified the `vendor` folder; please double-check whether it is necessary. + MARKDOWN + ), + sticky: true + ) +end + +# ------------------------------------------------------------------------------ +# 35. You should not modify /spec/factories/ folder. +# ------------------------------------------------------------------------------ +if !CURRENT_MAINTAINERS.include?(PR_AUTHOR) && TOUCHED_FILES.grep(%r{^spec/factories/}).any? + warn( + markdown( + <<~MARKDOWN + You modified `spec/factories/`; please double-check whether it is necessary. + MARKDOWN + ), + sticky: true + ) +end + +# ------------------------------------------------------------------------------ +# 36. You should not commit .vscode folder to your pull request. +# ------------------------------------------------------------------------------ +if ADDED_FILES.grep(/\.vscode/).any? + warn( + markdown( + <<~MARKDOWN + You committed `.vscode` folder; please remove it. + MARKDOWN + ), + sticky: true + ) +end + +# ------------------------------------------------------------------------------ +# 37-41. RSpec tests should avoid shallow tests. +# ------------------------------------------------------------------------------ +(ADDED_FILES + MODIFIED_FILES + RENAMED_FILES).each do |file| + next unless file.end_with?('_spec.rb') + + added_lines_arr = added_lines_for(file) + added_lines = added_lines_arr.join("\n") + num_of_tests = added_lines.scan(/\+\s*it\s*['"]/).count + num_of_expect_key_words = added_lines.scan(/\+\s*expect\s*(\(|\{|do)/).count + num_of_commented_out_expect_key_words = added_lines.scan(/\+\s*#\s*expect\s*(\(|\{|do)/).count + num_of_expectation_without_matchers = added_lines_arr.count do |loc| + loc.match?(/^\+\s*expect\s*[\(\{]/) && !loc.match?(/\.(to|not_to|to_not)/) + end + num_of_expectation_not_focus_on_real_value = added_lines_arr.count do |loc| + loc.match?(/^\+\s*expect\s*[\(\{]/) && loc.match?(/\.(not_to|to_not)\s*(be_nil|be_empty|eq 0|eql 0|equal 0)/) + end + num_of_wildcard_argument_matchers = added_lines.scan(/\((anything|any_args)\)/).count + num_of_expectations_on_page = added_lines.scan(/\+\s*expect\s*\(page\)/).count -# --- Shallow Tests (RSpec) --- -shallow_test_files = git.modified_files.select { |file| file.include?('spec/') } -shallow_test_warning = shallow_test_files.any? do |file| - File.exist?(file) && safe_read(file).match?(/\bit\b|\bspecify\b/) + if num_of_wildcard_argument_matchers >= 5 + warn( + markdown( + <<~MARKDOWN + There are many wildcard argument matchers (e.g., `anything`, `any_args`) in your tests. + To avoid shallow tests, please avoid wildcard matchers. + MARKDOWN + ), + sticky: true + ) + break + elsif num_of_expect_key_words < num_of_tests || num_of_commented_out_expect_key_words.positive? + warn( + markdown( + <<~MARKDOWN + One or more of your tests do not have expectations or you commented out some expectations. + To avoid shallow tests, please write at least one expectation for each test and do not comment out expectations. + MARKDOWN + ), + sticky: true + ) + break + elsif num_of_expectation_without_matchers.positive? + warn( + markdown( + <<~MARKDOWN + One or more of your test expectations do not have matchers. + To avoid shallow tests, please include matchers such as comparisons, object state changes, or explicit error handling. + MARKDOWN + ), + sticky: true + ) + break + elsif num_of_expectation_not_focus_on_real_value.positive? + warn( + markdown( + <<~MARKDOWN + One or more of your test expectations only focus on the return value not being `nil`, `empty`, or `0` without testing the real value. + To avoid shallow tests, please write expectations that verify the real value. + MARKDOWN + ), + sticky: true + ) + break + elsif num_of_expect_key_words - num_of_expectations_on_page < num_of_tests + warn( + markdown( + <<~MARKDOWN + In your tests, there are many expectations of elements on pages, which is good. + To avoid shallow tests, please write more expectations to validate other things, such as database records or dynamically generated contents. + MARKDOWN + ), + sticky: true + ) + break + end end -warn("RSpec tests seem shallow (single `it` blocks or no context). Consider improving test structure.") if shallow_test_warning