diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e0dde91..e0f1587 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,6 @@ name: CI on: [pull_request] jobs: test-skip-install-and-use-bundler: - name: runner / rubocop runs-on: ubuntu-latest defaults: run: @@ -10,10 +9,10 @@ jobs: env: BUNDLE_GEMFILE: ${{ github.workspace }}/test/using_bundler/Gemfile steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: - ruby-version: 2.6 + ruby-version: 3.3 bundler-cache: true - name: rubocop with skip install and using bundler uses: ./ @@ -21,4 +20,56 @@ jobs: github_token: ${{ secrets.github_token }} skip_install: 'true' use_bundler: 'true' - - run: test "$(bundle exec rubocop --version)" == "1.18.1" + - run: test "$(bundle exec rubocop --version)" == "1.65.0" + test-only_changed: + runs-on: ubuntu-latest + defaults: + run: + shell: bash + env: + INPUT_ONLY_CHANGED: 'true' + steps: + - uses: actions/checkout@v4 + - name: setup + run: | + git config user.email "workflow@github.com" + git config user.name "I am an automated workflow" + - name: Check when there are relevant files + run: | + git checkout ${{ github.sha }} + rm -f test/only_changed/reviewdog-was-called + + cp test/only_changed/few_relevant/files/* . + git add * + git commit -m auto + + export PATH=test/only_changed/few_relevant/mock_bins:test/only_changed/shared_mock_bins:$PATH + BASE_REF=$(git rev-parse HEAD~) HEAD_REF=$(git rev-parse HEAD) ./script.sh + + [ -f test/only_changed/reviewdog-was-called ] + - name: Check when there are no relevant files + run: | + git checkout ${{ github.sha }} + rm -f test/only_changed/reviewdog-was-called + + cp test/only_changed/nothing_relevant/files/* . + git add * + git commit -m auto + + export PATH=test/only_changed/nothing_relevant/mock_bins:test/only_changed/shared_mock_bins:$PATH + BASE_REF=$(git rev-parse HEAD~) HEAD_REF=$(git rev-parse HEAD) ./script.sh + + [ ! -f test/only_changed/reviewdog-was-called ] + - name: Check when there are too many relevant files + run: | + git checkout ${{ github.sha }} + rm -f test/only_changed/reviewdog-was-called + + touch a{00..100}.rb + git add * + git commit -m auto + + export PATH=test/only_changed/too_many_relevant/mock_bins:test/only_changed/shared_mock_bins:$PATH + BASE_REF=$(git rev-parse HEAD~) HEAD_REF=$(git rev-parse HEAD) ./script.sh + + [ -f test/only_changed/reviewdog-was-called ] diff --git a/.github/workflows/depup.yml b/.github/workflows/depup.yml index 89deb5a..3a9f433 100644 --- a/.github/workflows/depup.yml +++ b/.github/workflows/depup.yml @@ -9,7 +9,7 @@ jobs: reviewdog: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: haya14busa/action-depup@v1 id: depup with: @@ -18,7 +18,7 @@ jobs: repo: reviewdog/reviewdog - name: Create Pull Request - uses: peter-evans/create-pull-request@v3 + uses: peter-evans/create-pull-request@v6 with: token: ${{ secrets.GITHUB_TOKEN }} title: "chore(deps): update reviewdog to ${{ steps.depup.outputs.latest }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cf70348..fe431ac 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,7 @@ jobs: if: github.event.action != 'labeled' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 # Bump version on merging Pull Requests with specific labels. # (bump:major,bump:minor,bump:patch) @@ -38,22 +38,18 @@ jobs: if_false: ${{ steps.bumpr.outputs.next_version }} # Create release. - - uses: actions/create-release@v1 - if: "steps.tag.outputs.value != ''" + - if: "steps.tag.outputs.value != ''" env: - # This token is provided by Actions, you do not need to create your own token + TAG_NAME: ${{ steps.tag.outputs.value }} + BODY: ${{ steps.bumpr.outputs.message }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ steps.tag.outputs.value }} - release_name: Release ${{ steps.tag.outputs.value }} - body: ${{ steps.bumpr.outputs.message }} - draft: false - prerelease: false + run: | + gh release create "${TAG_NAME}" -t "Release ${TAG_NAME/refs\/tags\//}" --notes "${BODY}" release-check: if: github.event.action == 'labeled' runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Post bumpr status comment uses: haya14busa/action-bumpr@v1 diff --git a/.github/workflows/reviewdog.yml b/.github/workflows/reviewdog.yml index 90f66d6..8d6bccf 100644 --- a/.github/workflows/reviewdog.yml +++ b/.github/workflows/reviewdog.yml @@ -6,7 +6,7 @@ jobs: name: check / misspell runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: misspell uses: reviewdog/action-misspell@v1 with: @@ -19,7 +19,7 @@ jobs: name: runner / shellcheck runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: shellcheck uses: reviewdog/action-shellcheck@v1 with: @@ -33,7 +33,7 @@ jobs: name: check / yamllint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - name: yamllint uses: reviewdog/action-yamllint@v1 with: diff --git a/.github/workflows/test_rdjson_formatter.yml b/.github/workflows/test_rdjson_formatter.yml index 86d1b61..23190bc 100644 --- a/.github/workflows/test_rdjson_formatter.yml +++ b/.github/workflows/test_rdjson_formatter.yml @@ -8,7 +8,7 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: ruby-version: 3.0.2 diff --git a/.rubocop.yml b/.rubocop.yml index 2e515ce..b4afcae 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -2,3 +2,211 @@ AllCops: Exclude: - 'test/rdjson_formatter/**/*' - 'vendor/**/*' +Gemspec/AddRuntimeDependency: # new in 1.65 + Enabled: true +Gemspec/DeprecatedAttributeAssignment: # new in 1.30 + Enabled: true +Gemspec/DevelopmentDependencies: # new in 1.44 + Enabled: true +Gemspec/RequireMFA: # new in 1.23 + Enabled: true +Layout/LineContinuationLeadingSpace: # new in 1.31 + Enabled: true +Layout/LineContinuationSpacing: # new in 1.31 + Enabled: true +Layout/LineEndStringConcatenationIndentation: # new in 1.18 + Enabled: true +Layout/SpaceBeforeBrackets: # new in 1.7 + Enabled: true +Lint/AmbiguousAssignment: # new in 1.7 + Enabled: true +Lint/AmbiguousOperatorPrecedence: # new in 1.21 + Enabled: true +Lint/AmbiguousRange: # new in 1.19 + Enabled: true +Lint/ConstantOverwrittenInRescue: # new in 1.31 + Enabled: true +Lint/DeprecatedConstants: # new in 1.8 + Enabled: true +Lint/DuplicateBranch: # new in 1.3 + Enabled: true +Lint/DuplicateMagicComment: # new in 1.37 + Enabled: true +Lint/DuplicateMatchPattern: # new in 1.50 + Enabled: true +Lint/DuplicateRegexpCharacterClassElement: # new in 1.1 + Enabled: true +Lint/EmptyBlock: # new in 1.1 + Enabled: true +Lint/EmptyClass: # new in 1.3 + Enabled: true +Lint/EmptyInPattern: # new in 1.16 + Enabled: true +Lint/IncompatibleIoSelectWithFiberScheduler: # new in 1.21 + Enabled: true +Lint/ItWithoutArgumentsInBlock: # new in 1.59 + Enabled: true +Lint/LambdaWithoutLiteralBlock: # new in 1.8 + Enabled: true +Lint/LiteralAssignmentInCondition: # new in 1.58 + Enabled: true +Lint/MixedCaseRange: # new in 1.53 + Enabled: true +Style/NumberedParameters: # new in 1.22 + Enabled: true +Style/NumberedParametersLimit: # new in 1.22 + Enabled: true +Style/ObjectThen: # new in 1.28 + Enabled: true +Style/OpenStructUse: # new in 1.23 + Enabled: true +Style/OperatorMethodCall: # new in 1.37 + Enabled: true +Style/QuotedSymbols: # new in 1.16 + Enabled: true +Style/RedundantArgument: # new in 1.4 + Enabled: true +Style/RedundantArrayConstructor: # new in 1.52 + Enabled: true +Style/RedundantConstantBase: # new in 1.40 + Enabled: true +Style/RedundantCurrentDirectoryInPath: # new in 1.53 + Enabled: true +Style/RedundantDoubleSplatHashBraces: # new in 1.41 + Enabled: true +Style/RedundantEach: # new in 1.38 + Enabled: true +Style/RedundantFilterChain: # new in 1.52 + Enabled: true +Style/RedundantHeredocDelimiterQuotes: # new in 1.45 + Enabled: true +Style/RedundantInitialize: # new in 1.27 + Enabled: true +Style/RedundantLineContinuation: # new in 1.49 + Enabled: true +Style/RedundantRegexpArgument: # new in 1.53 + Enabled: true +Style/RedundantRegexpConstructor: # new in 1.52 + Enabled: true +Style/RedundantSelfAssignmentBranch: # new in 1.19 + Enabled: true +Style/RedundantStringEscape: # new in 1.37 + Enabled: true +Style/ReturnNilInPredicateMethodDefinition: # new in 1.53 + Enabled: true +Style/SelectByRegexp: # new in 1.22 + Enabled: true +Style/SendWithLiteralMethodName: # new in 1.64 + Enabled: true +Style/SingleLineDoEndBlock: # new in 1.57 + Enabled: true +Style/StringChars: # new in 1.12 + Enabled: true +Style/SuperArguments: # new in 1.64 + Enabled: true +Style/SuperWithArgsParentheses: # new in 1.58 + Enabled: true +Style/SwapValues: # new in 1.1 + Enabled: true +Style/YAMLFileRead: # new in 1.53 + Enabled: true +Lint/NoReturnInBeginEndBlocks: # new in 1.2 + Enabled: true +Lint/NonAtomicFileOperation: # new in 1.31 + Enabled: true +Lint/NumberedParameterAssignment: # new in 1.9 + Enabled: true +Lint/OrAssignmentToConstant: # new in 1.9 + Enabled: true +Lint/RedundantDirGlobSort: # new in 1.8 + Enabled: true +Lint/RedundantRegexpQuantifiers: # new in 1.53 + Enabled: true +Lint/RefinementImportMethods: # new in 1.27 + Enabled: true +Lint/RequireRangeParentheses: # new in 1.32 + Enabled: true +Lint/RequireRelativeSelfPath: # new in 1.22 + Enabled: true +Lint/SymbolConversion: # new in 1.9 + Enabled: true +Lint/ToEnumArguments: # new in 1.1 + Enabled: true +Lint/TripleQuotes: # new in 1.9 + Enabled: true +Lint/UnexpectedBlockArity: # new in 1.5 + Enabled: true +Lint/UnmodifiedReduceAccumulator: # new in 1.1 + Enabled: true +Lint/UselessRescue: # new in 1.43 + Enabled: true +Lint/UselessRuby2Keywords: # new in 1.23 + Enabled: true +Metrics/CollectionLiteralLength: # new in 1.47 + Enabled: true +Naming/BlockForwarding: # new in 1.24 + Enabled: true +Security/CompoundHash: # new in 1.28 + Enabled: true +Security/IoMethods: # new in 1.22 + Enabled: true +Style/ArgumentsForwarding: # new in 1.1 + Enabled: true +Style/ArrayIntersect: # new in 1.40 + Enabled: true +Style/CollectionCompact: # new in 1.2 + Enabled: true +Style/ComparableClamp: # new in 1.44 + Enabled: true +Style/ConcatArrayLiterals: # new in 1.41 + Enabled: true +Style/DataInheritance: # new in 1.49 + Enabled: true +Style/DirEmpty: # new in 1.48 + Enabled: true +Style/DocumentDynamicEvalDefinition: # new in 1.1 + Enabled: true +Style/EmptyHeredoc: # new in 1.32 + Enabled: true +Style/EndlessMethod: # new in 1.8 + Enabled: true +Style/EnvHome: # new in 1.29 + Enabled: true +Style/ExactRegexpMatch: # new in 1.51 + Enabled: true +Style/FetchEnvVar: # new in 1.28 + Enabled: true +Style/FileEmpty: # new in 1.48 + Enabled: true +Style/FileRead: # new in 1.24 + Enabled: true +Style/FileWrite: # new in 1.24 + Enabled: true +Style/HashConversion: # new in 1.10 + Enabled: true +Style/HashExcept: # new in 1.7 + Enabled: true +Style/IfWithBooleanLiteralBranches: # new in 1.9 + Enabled: true +Style/InPatternThen: # new in 1.16 + Enabled: true +Style/MagicCommentFormat: # new in 1.35 + Enabled: true +Style/MapCompactWithConditionalBlock: # new in 1.30 + Enabled: true +Style/MapIntoArray: # new in 1.63 + Enabled: true +Style/MapToHash: # new in 1.24 + Enabled: true +Style/MapToSet: # new in 1.42 + Enabled: true +Style/MinMaxComparison: # new in 1.42 + Enabled: true +Style/MultilineInPatternThen: # new in 1.16 + Enabled: true +Style/NegatedIfElseCondition: # new in 1.2 + Enabled: true +Style/NestedFileDirname: # new in 1.26 + Enabled: true +Style/NilLambda: # new in 1.3 + Enabled: true diff --git a/README.md b/README.md index 631baef..e45c49e 100644 --- a/README.md +++ b/README.md @@ -26,16 +26,50 @@ With `reporter: github-pr-review` a comment is added to the Pull Request Convers ## Inputs + + +### `fail_level` + +Optional. If set to `none`, always use exit code 0 for reviewdog. Otherwise, exit code 1 for reviewdog if it finds at least 1 issue with severity greater than or equal to the given level. +Possible values: [`none`, `any`, `info`, `warning`, `error`] +Default is `none`. + +### `fail_on_error` + +Deprecated, use `fail_level` instead. +Optional. Exit code for reviewdog when errors are found [`true`, `false`]. +Default is `false`. + +### `filter_mode` + +Optional. Filtering mode for the reviewdog command [`added`, `diff_context`, `file`, `nofilter`]. +Default is `added`. + ### `github_token` `GITHUB_TOKEN`. Default is `${{ github.token }}`. -### `rubocop_version` +### `level` -Optional. Set rubocop version. Possible values: -* empty or omit: install latest version -* `gemfile`: install version from Gemfile (`Gemfile.lock` should be presented, otherwise it will fallback to latest bundler version) -* version (e.g. `0.90.0`): install said version +Optional. Report level for reviewdog [`info`, `warning`, `error`]. +It's same as `-level` flag of reviewdog. + +### `only_changed` + +Optional. Run Rubocop only on changed (and added) files, for speedup [`true`, `false`]. +Default: `false`. + +Will fetch the tip of the base branch with depth 1 from remote origin if it is not available. +If you use different remote name or customize the checkout otherwise, make the tip of the base branch available before this action + +### `reporter` + +Optional. Reporter of reviewdog command [`github-pr-check`, `github-check`, `github-pr-review`]. +The default is `github-pr-check`. + +### `reviewdog_flags` + +Optional. Additional reviewdog flags. ### `rubocop_extensions` @@ -45,9 +79,10 @@ By default install `rubocop-rails`, `rubocop-performance`, `rubocop-rspec`, `rub Provide desired version delimited by `:` (e.g. `rubocop-rails:1.7.1`) Possible version values: -* empty or omit (`rubocop-rails rubocop-rspec`): install latest version -* `rubocop-rails:gemfile rubocop-rspec:gemfile`: install version from Gemfile (`Gemfile.lock` should be presented, otherwise it will fallback to latest bundler version) -* version (e.g. `rubocop-rails:1.7.1 rubocop-rspec:2.0.0`): install said version + +- empty or omit (`rubocop-rails rubocop-rspec`): install latest version +- `rubocop-rails:gemfile rubocop-rspec:gemfile`: install version from Gemfile (`Gemfile.lock` should be presented, otherwise it will fallback to latest bundler version) +- version (e.g. `rubocop-rails:1.7.1 rubocop-rspec:2.0.0`): install said version You can combine `gemfile`, fixed and latest bundle version as you want to. @@ -55,70 +90,71 @@ You can combine `gemfile`, fixed and latest bundle version as you want to. Optional. Rubocop flags. (rubocop ``). -### `tool_name` - -Optional. Tool name to use for reviewdog reporter. Useful when running multiple -actions with different config. - -### `level` +### `rubocop_version` -Optional. Report level for reviewdog [`info`, `warning`, `error`]. -It's same as `-level` flag of reviewdog. +Optional. Set rubocop version. Possible values: -### `reporter` +- empty or omit: install latest version +- `gemfile`: install version from Gemfile (`Gemfile.lock` should be presented, otherwise it will fallback to latest bundler version) +- version (e.g. `0.90.0`): install said version -Optional. Reporter of reviewdog command [`github-pr-check`, `github-check`, `github-pr-review`]. -The default is `github-pr-check`. - -### `filter_mode` +### `skip_install` -Optional. Filtering mode for the reviewdog command [`added`, `diff_context`, `file`, `nofilter`]. -Default is `added`. +Optional. Do not install Rubocop or its extensions. Default: `false`. -### `fail_on_error` +### `tool_name` -Optional. Exit code for reviewdog when errors are found [`true`, `false`]. -Default is `false`. +Optional. Tool name to use for reviewdog reporter. Useful when running multiple +actions with different config. -### `reviewdog_flags` +### `use_bundler` -Optional. Additional reviewdog flags. +Optional. Run Rubocop with bundle exec. Default: `false`. ### `workdir` Optional. The directory from which to look for and run Rubocop. Default `.`. -### `skip_install` - -Optional. Do not install Rubocop or its extensions. Default: `false`. +## Example usage -### `use_bundler` +This action will use your [RuboCop Configuration](https://docs.rubocop.org/rubocop/configuration.html) automatically. -Optional. Run Rubocop with bundle exec. Default: `false`. +In your `Gemfile`, ensure all Rubocop gems are in a named (e.g. rubocop) group: -## Example usage +```ruby +group :development, :rubocop do + gem 'rubocop', require: false + gem 'rubocop-rails', require: false + # ... +end +``` -You can create [RuboCop Configuration](https://docs.rubocop.org/rubocop/configuration.html) and this action uses that config too. +Create the following workflow. The `BUNDLE_ONLY` environment variable will tell Bundler to only install the specified group. ```yml name: reviewdog -on: [pull_request] +on: + pull_request: +permissions: + contents: read + pull-requests: write jobs: rubocop: name: runner / rubocop runs-on: ubuntu-latest + env: + BUNDLE_ONLY: rubocop steps: - - name: Check out code - uses: actions/checkout@v1 + - uses: actions/checkout@v4 - uses: ruby/setup-ruby@v1 with: - ruby-version: 3.0.0 - - name: rubocop - uses: reviewdog/action-rubocop@v2 + ruby-version: '3.3' + bundler-cache: true + - uses: reviewdog/action-rubocop@v2 with: - rubocop_version: gemfile - rubocop_extensions: rubocop-rails:gemfile rubocop-rspec:gemfile reporter: github-pr-review # Default is github-pr-check + skip_install: true + use_bundler: true ``` ## Sponsor diff --git a/action.yml b/action.yml index b9c66e0..accc82d 100644 --- a/action.yml +++ b/action.yml @@ -2,72 +2,92 @@ name: 'Run rubocop with reviewdog' description: '🐶 Run rubocop with reviewdog on pull requests to improve code review experience.' author: 'mgrachev (reviewdog)' inputs: + # Please maintain inputs in alphabetical order + fail_level: + description: | + If set to `none`, always use exit code 0 for reviewdog. Otherwise, exit code 1 for reviewdog if it finds at least 1 issue with severity greater than or equal to the given level. + Possible values: [none,any,info,warning,error] + Default is `none`. + default: 'none' + fail_on_error: + description: | + Deprecated, use `fail_level` instead. + Exit code for reviewdog when errors are found [true,false] + Default is `false`. + default: 'false' + deprecationMessage: Deprecated, use `fail_level` instead. + filter_mode: + description: | + Filtering mode for the reviewdog command [added,diff_context,file,nofilter]. + Default is added. + default: 'added' github_token: description: 'GITHUB_TOKEN' default: ${{ github.token }} - rubocop_version: - description: 'Rubocop version' - rubocop_extensions: - description: 'Rubocop extensions' - default: 'rubocop-rails rubocop-performance rubocop-rspec rubocop-i18n rubocop-rake' - rubocop_flags: - description: 'Rubocop flags. (rubocop )' - default: '' - tool_name: - description: 'Tool name to use for reviewdog reporter' - default: 'rubocop' level: description: 'Report level for reviewdog [info,warning,error]' default: 'error' + only_changed: + description: | + Run Rubocop only on changed (and added) files, for speedup [`true`, `false`]. + Will fetch the tip of the base branch with depth 1 from remote origin if it is not available. + If you use different remote name or customize the checkout otherwise, make the tip of the base branch available before this action. + default: 'false' reporter: description: | Reporter of reviewdog command [github-pr-check,github-check,github-pr-review]. Default is github-pr-check. default: 'github-pr-check' - filter_mode: - description: | - Filtering mode for the reviewdog command [added,diff_context,file,nofilter]. - Default is added. - default: 'added' - fail_on_error: - description: | - Exit code for reviewdog when errors are found [true,false] - Default is `false`. - default: 'false' reviewdog_flags: description: 'Additional reviewdog flags' default: '' - workdir: - description: "The directory from which to look for and run Rubocop. Default '.'" - default: '.' + rubocop_extensions: + description: 'Rubocop extensions' + default: 'rubocop-rails rubocop-performance rubocop-rspec rubocop-i18n rubocop-rake' + rubocop_flags: + description: 'Rubocop flags. (rubocop )' + default: '' + rubocop_version: + description: 'Rubocop version' skip_install: description: "Do not install Rubocop or its extensions. Default: `false`" default: 'false' + tool_name: + description: 'Tool name to use for reviewdog reporter' + default: 'rubocop' use_bundler: description: "Run Rubocop with bundle exec. Default: `false`" default: 'false' + workdir: + description: "The directory from which to look for and run Rubocop. Default '.'" + default: '.' runs: using: 'composite' steps: - run: $GITHUB_ACTION_PATH/script.sh shell: sh env: - REVIEWDOG_VERSION: v0.14.1 + REVIEWDOG_VERSION: v0.20.3 # INPUT_ is not available in Composite run steps # https://github.community/t/input-variable-name-is-not-available-in-composite-run-steps/127611 + # Please maintain inputs in alphabetical order + INPUT_FAIL_LEVEL: ${{ inputs.fail_level }} + INPUT_FAIL_ON_ERROR: ${{ inputs.fail_on_error }} + INPUT_FILTER_MODE: ${{ inputs.filter_mode }} INPUT_GITHUB_TOKEN: ${{ inputs.github_token }} - INPUT_RUBOCOP_VERSION: ${{ inputs.rubocop_version }} - INPUT_RUBOCOP_EXTENSIONS: ${{ inputs.rubocop_extensions }} - INPUT_RUBOCOP_FLAGS: ${{ inputs.rubocop_flags }} - INPUT_TOOL_NAME: ${{ inputs.tool_name }} INPUT_LEVEL: ${{ inputs.level }} + INPUT_ONLY_CHANGED: ${{ inputs.only_changed }} INPUT_REPORTER: ${{ inputs.reporter }} - INPUT_FILTER_MODE: ${{ inputs.filter_mode }} - INPUT_FAIL_ON_ERROR: ${{ inputs.fail_on_error }} INPUT_REVIEWDOG_FLAGS: ${{ inputs.reviewdog_flags }} - INPUT_WORKDIR: ${{ inputs.workdir }} + INPUT_RUBOCOP_EXTENSIONS: ${{ inputs.rubocop_extensions }} + INPUT_RUBOCOP_FLAGS: ${{ inputs.rubocop_flags }} + INPUT_RUBOCOP_VERSION: ${{ inputs.rubocop_version }} INPUT_SKIP_INSTALL: ${{ inputs.skip_install }} + INPUT_TOOL_NAME: ${{ inputs.tool_name }} INPUT_USE_BUNDLER: ${{ inputs.use_bundler }} + INPUT_WORKDIR: ${{ inputs.workdir }} + BASE_REF: ${{ github.event.pull_request.base.sha }} + HEAD_REF: ${{ github.sha }} branding: icon: 'check-circle' color: 'red' diff --git a/rdjson_formatter/rdjson_formatter.rb b/rdjson_formatter/rdjson_formatter.rb index c3607eb..14f1429 100644 --- a/rdjson_formatter/rdjson_formatter.rb +++ b/rdjson_formatter/rdjson_formatter.rb @@ -1,8 +1,10 @@ # frozen_string_literal: true # https://docs.rubocop.org/rubocop/formatters.html -# rubocop:disable Metrics/AbcSize, Metrics/MethodLength +# rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/ClassLength class RdjsonFormatter < RuboCop::Formatter::BaseFormatter + include RuboCop::PathUtil + def started(_target_files) @rdjson = { source: { @@ -16,10 +18,10 @@ def started(_target_files) def file_finished(file, offenses) offenses.each do |offense| - next if offense.location == RuboCop::Cop::Offense::NO_LOCATION - @rdjson[:diagnostics] << build_diagnostic(file, offense) end + + @rdjson[:severity] = overall_severity(offenses) end def finished(_inspected_files) @@ -28,6 +30,24 @@ def finished(_inspected_files) private + def overall_severity(offenses) + if offenses.any? { |o| o.severity >= minimum_severity_to_fail } + 'ERROR' + elsif offenses.all? { |o| convert_severity(o.severity) == 'INFO' } + 'INFO' + else + 'WARNING' + end + end + + def minimum_severity_to_fail + @minimum_severity_to_fail ||= begin + # Unless given explicitly as `fail_level`, `:info` severity offenses do not fail + name = options.fetch(:fail_level, :refactor) + RuboCop::Cop::Severity.new(name) + end + end + # @param [String] file # @param [RuboCop::Cop::Offense] offense # @return [Hash] @@ -37,30 +57,36 @@ def build_diagnostic(file, offense) diagnostic = { message: message, location: { - path: convert_path(file), - range: { - start: { - line: offense.location.begin.line, - column: offense.location.begin.column + 1 - }, - end: { - line: offense.location.end.line, - column: offense.location.end.column + 1 - } - } + path: convert_path(file) }, severity: convert_severity(offense.severity), code: { value: code }, - original_output: offense.to_s + original_output: build_original_output(file, offense) } + diagnostic[:location][:range] = build_range(offense) if offense.location != RuboCop::Cop::Offense::NO_LOCATION - diagnostic[:suggestions] = build_suggestions(offense) if offense.correctable? + diagnostic[:suggestions] = build_suggestions(offense) if offense.correctable? && offense.corrector diagnostic end + # @param [RuboCop::Cop::Offense] offense + # @return [Hash] + def build_range(offense) + { + start: { + line: offense.location.begin.line, + column: offense.location.begin.column + 1 + }, + end: { + line: offense.location.end.line, + column: offense.location.end.column + 1 + } + } + end + # @param [RuboCop::Cop::Offense] offense # @return [Array{Hash}] def build_suggestions(offense) @@ -89,12 +115,12 @@ def build_suggestions(offense) # @param [Symbol] severity # @return [String] def convert_severity(severity) - case severity - when :info + case severity.to_s + when 'info', 'refactor', 'convention' 'INFO' - when :warning + when 'warning' 'WARNING' - when :error + when 'error' 'ERROR' else 'UNKNOWN_SEVERITY' @@ -114,10 +140,24 @@ def convert_path(path) base_path = Dir.pwd begin - Pathname.new(File.expand_path(path)).relative_path_from(base_path).to_s + Pathname.new(File.expand_path(path)).relative_path_from(Pathname.new(base_path)).to_s rescue ArgumentError path end end + + # @param [String] file + # @param [RuboCop::Cop::Offense] offense + # @return [String] + def build_original_output(file, offense) + format( + '%s:%d:%d: %s: %s', + path: smart_path(file), + line: offense.line, + column: offense.real_column, + severity: offense.severity.code, + message: offense.message + ) + end end -# rubocop:enable Metrics/AbcSize, Metrics/MethodLength +# rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/ClassLength diff --git a/script.sh b/script.sh index 6d9e36b..2a69a4c 100755 --- a/script.sh +++ b/script.sh @@ -1,9 +1,7 @@ -#!/bin/sh -e -version() { - if [ -n "$1" ]; then - echo "-v $1" - fi -} +#!/usr/bin/env bash + +set -e +set -o pipefail cd "${GITHUB_WORKSPACE}/${INPUT_WORKDIR}" || exit export REVIEWDOG_GITHUB_API_TOKEN="${INPUT_GITHUB_TOKEN}" @@ -28,15 +26,15 @@ if [ "${INPUT_SKIP_INSTALL}" = "false" ]; then # left it empty otherwise, so no version will be passed if [ -n "$RUBOCOP_GEMFILE_VERSION" ]; then RUBOCOP_VERSION=$RUBOCOP_GEMFILE_VERSION - else - printf "Cannot get the rubocop's version from Gemfile.lock. The latest version will be installed." - fi else - printf 'Gemfile.lock not found. The latest version will be installed.' - fi + printf "Cannot get the rubocop's version from Gemfile.lock. The latest version will be installed." + fi else - # set desired rubocop version - RUBOCOP_VERSION=$INPUT_RUBOCOP_VERSION + printf 'Gemfile.lock not found. The latest version will be installed.' + fi + else + # set desired rubocop version + RUBOCOP_VERSION=$INPUT_RUBOCOP_VERSION fi gem install -N rubocop --version "${RUBOCOP_VERSION}" @@ -58,11 +56,11 @@ if [ "${INPUT_SKIP_INSTALL}" = "false" ]; then # left it empty otherwise, so no version will be passed if [ -n "$RUBOCOP_EXTENSION_GEMFILE_VERSION" ]; then RUBOCOP_EXTENSION_VERSION=$RUBOCOP_EXTENSION_GEMFILE_VERSION - else - printf "Cannot get the rubocop extension version from Gemfile.lock. The latest version will be installed." - fi else - printf 'Gemfile.lock not found. The latest version will be installed.' + printf "Cannot get the rubocop extension version from Gemfile.lock. The latest version will be installed." + fi + else + printf 'Gemfile.lock not found. The latest version will be installed.' fi else # set desired rubocop extension version @@ -90,13 +88,51 @@ else BUNDLE_EXEC="bundle exec " fi +if [ "${INPUT_ONLY_CHANGED}" = "true" ]; then + echo '::group:: Getting changed files list' + + # check if commit is present in repository, otherwise fetch it + if ! git cat-file -e "${BASE_REF}"; then + git fetch --depth 1 origin "${BASE_REF}" + fi + + # get intersection of changed files (excluding deleted) with target files for + # rubocop as an array + # shellcheck disable=SC2086 + readarray -t CHANGED_FILES < <( + comm -12 \ + <(git diff --diff-filter=d --name-only "${BASE_REF}..${HEAD_REF}" | sort || kill $$) \ + <(${BUNDLE_EXEC}rubocop --list-target-files | sort || kill $$) + ) + + if (( ${#CHANGED_FILES[@]} == 0 )); then + echo "No relevant files for rubocop, skipping" + exit 0 + fi + + printf '%s\n' "${CHANGED_FILES[@]}" + + if (( ${#CHANGED_FILES[@]} > 100 )); then + echo "More than 100 changed files (${#CHANGED_FILES[@]}), running rubocop on all files" + unset CHANGED_FILES + fi + + echo '::endgroup::' +fi + echo '::group:: Running rubocop with reviewdog 🐶 ...' # shellcheck disable=SC2086 -${BUNDLE_EXEC}rubocop ${INPUT_RUBOCOP_FLAGS} --require ${GITHUB_ACTION_PATH}/rdjson_formatter/rdjson_formatter.rb --format RdjsonFormatter \ +${BUNDLE_EXEC}rubocop \ + --require ${GITHUB_ACTION_PATH}/rdjson_formatter/rdjson_formatter.rb \ + --format RdjsonFormatter \ + --fail-level error \ + ${INPUT_RUBOCOP_FLAGS} \ + "${CHANGED_FILES[@]}" \ | reviewdog -f=rdjson \ -name="${INPUT_TOOL_NAME}" \ -reporter="${INPUT_REPORTER}" \ -filter-mode="${INPUT_FILTER_MODE}" \ + -fail-level="${INPUT_FAIL_LEVEL}" \ -fail-on-error="${INPUT_FAIL_ON_ERROR}" \ -level="${INPUT_LEVEL}" \ ${INPUT_REVIEWDOG_FLAGS} diff --git a/test/only_changed/few_relevant/files/a.rb b/test/only_changed/few_relevant/files/a.rb new file mode 100644 index 0000000..5460224 --- /dev/null +++ b/test/only_changed/few_relevant/files/a.rb @@ -0,0 +1 @@ +puts "Hello, " + "world!" diff --git a/test/only_changed/few_relevant/files/a.txt b/test/only_changed/few_relevant/files/a.txt new file mode 100644 index 0000000..5460224 --- /dev/null +++ b/test/only_changed/few_relevant/files/a.txt @@ -0,0 +1 @@ +puts "Hello, " + "world!" diff --git a/test/only_changed/few_relevant/mock_bins/rubocop b/test/only_changed/few_relevant/mock_bins/rubocop new file mode 100755 index 0000000..bcd0014 --- /dev/null +++ b/test/only_changed/few_relevant/mock_bins/rubocop @@ -0,0 +1,18 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +case ARGV +when %w[ + --list-target-files +] + puts Dir['**/*.rb'] +when %W[ + --require #{ENV['GITHUB_ACTION_PATH']}/rdjson_formatter/rdjson_formatter.rb + --format RdjsonFormatter + --fail-level error + a.rb +] + puts 'Mock message for reviewdog' +else + abort "rubocop mock called with unexpected arguments:\n#{ARGV.join("\n")}" +end diff --git a/test/only_changed/nothing_relevant/files/a.txt b/test/only_changed/nothing_relevant/files/a.txt new file mode 100644 index 0000000..5460224 --- /dev/null +++ b/test/only_changed/nothing_relevant/files/a.txt @@ -0,0 +1 @@ +puts "Hello, " + "world!" diff --git a/test/only_changed/nothing_relevant/mock_bins/rubocop b/test/only_changed/nothing_relevant/mock_bins/rubocop new file mode 100755 index 0000000..8b60d86 --- /dev/null +++ b/test/only_changed/nothing_relevant/mock_bins/rubocop @@ -0,0 +1,11 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +case ARGV +when %w[ + --list-target-files +] + puts Dir['**/*.rb'] +else + abort "rubocop mock called with unexpected arguments:\n#{ARGV.join("\n")}" +end diff --git a/test/only_changed/shared_mock_bins/bundle b/test/only_changed/shared_mock_bins/bundle new file mode 100755 index 0000000..0a28632 --- /dev/null +++ b/test/only_changed/shared_mock_bins/bundle @@ -0,0 +1,9 @@ +#!/bin/bash + +if [ "$1" = "exec" ]; then + shift + eval "$@" +else + echo "Only 'exec' command is supported" + exit 1 +fi diff --git a/test/only_changed/shared_mock_bins/curl b/test/only_changed/shared_mock_bins/curl new file mode 100755 index 0000000..3c5bc23 --- /dev/null +++ b/test/only_changed/shared_mock_bins/curl @@ -0,0 +1,8 @@ +#!/bin/bash + +arguments="$*" + +if [ "$arguments" != "-sfL https://raw.githubusercontent.com/reviewdog/reviewdog/master/install.sh" ]; then + echo "curl mock got unexpected arguments: $arguments" + exit 1 +fi diff --git a/test/only_changed/shared_mock_bins/reviewdog b/test/only_changed/shared_mock_bins/reviewdog new file mode 100755 index 0000000..5334ef7 --- /dev/null +++ b/test/only_changed/shared_mock_bins/reviewdog @@ -0,0 +1,10 @@ +#!/bin/bash + +touch test/only_changed/reviewdog-was-called + +read -r input + +if [ "$input" != "Mock message for reviewdog" ]; then + echo "reviewdog mock got unexpected input: $input" + exit 1 +fi diff --git a/test/only_changed/too_many_relevant/mock_bins/rubocop b/test/only_changed/too_many_relevant/mock_bins/rubocop new file mode 100755 index 0000000..1a2a540 --- /dev/null +++ b/test/only_changed/too_many_relevant/mock_bins/rubocop @@ -0,0 +1,17 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +case ARGV +when %w[ + --list-target-files +] + puts Dir['**/*.rb'] +when %W[ + --require #{ENV['GITHUB_ACTION_PATH']}/rdjson_formatter/rdjson_formatter.rb + --format RdjsonFormatter + --fail-level error +] + puts 'Mock message for reviewdog' +else + abort "rubocop mock called with unexpected arguments:\n#{ARGV.join("\n")}" +end diff --git a/test/rdjson_formatter/testdata/result.ok b/test/rdjson_formatter/testdata/result.ok index 314ca72..51116c8 100644 --- a/test/rdjson_formatter/testdata/result.ok +++ b/test/rdjson_formatter/testdata/result.ok @@ -19,11 +19,11 @@ } } }, - "severity": "UNKNOWN_SEVERITY", + "severity": "INFO", "code": { "value": "Style/FrozenStringLiteralComment" }, - "original_output": "C: 1: 1: Style/FrozenStringLiteralComment: Missing frozen string literal comment.", + "original_output": "test/rdjson_formatter/testdata/correctable_offenses.rb:1:1: C: Style/FrozenStringLiteralComment: Missing frozen string literal comment.", "suggestions": [ { "range": { @@ -55,11 +55,11 @@ } } }, - "severity": "UNKNOWN_SEVERITY", + "severity": "INFO", "code": { "value": "Layout/SpaceAfterComma" }, - "original_output": "C: 1: 12: Layout/SpaceAfterComma: Space missing after comma.", + "original_output": "test/rdjson_formatter/testdata/correctable_offenses.rb:1:12: C: Layout/SpaceAfterComma: Space missing after comma.", "suggestions": [ { "range": { @@ -91,11 +91,11 @@ } } }, - "severity": "UNKNOWN_SEVERITY", + "severity": "INFO", "code": { "value": "Layout/ExtraSpacing" }, - "original_output": "C: 2: 4: Layout/ExtraSpacing: Unnecessary spacing detected.", + "original_output": "test/rdjson_formatter/testdata/correctable_offenses.rb:2:4: C: Layout/ExtraSpacing: Unnecessary spacing detected.", "suggestions": [ { "range": { @@ -127,11 +127,11 @@ } } }, - "severity": "UNKNOWN_SEVERITY", + "severity": "INFO", "code": { "value": "Layout/SpaceAroundOperators" }, - "original_output": "C: 2: 13: Layout/SpaceAroundOperators: Surrounding space missing for operator `+`.", + "original_output": "test/rdjson_formatter/testdata/correctable_offenses.rb:2:13: C: Layout/SpaceAroundOperators: Surrounding space missing for operator `+`.", "suggestions": [ { "range": { @@ -163,11 +163,11 @@ } } }, - "severity": "UNKNOWN_SEVERITY", + "severity": "INFO", "code": { "value": "Layout/ExtraSpacing" }, - "original_output": "C: 2: 14: Layout/ExtraSpacing: Unnecessary spacing detected.", + "original_output": "test/rdjson_formatter/testdata/correctable_offenses.rb:2:14: C: Layout/ExtraSpacing: Unnecessary spacing detected.", "suggestions": [ { "range": { @@ -199,11 +199,11 @@ } } }, - "severity": "UNKNOWN_SEVERITY", + "severity": "INFO", "code": { "value": "Style/Semicolon" }, - "original_output": "C: 2: 20: Style/Semicolon: Do not use semicolons to terminate expressions.", + "original_output": "test/rdjson_formatter/testdata/correctable_offenses.rb:2:20: C: Style/Semicolon: Do not use semicolons to terminate expressions.", "suggestions": [ { "range": { @@ -235,11 +235,11 @@ } } }, - "severity": "UNKNOWN_SEVERITY", + "severity": "INFO", "code": { "value": "Style/RedundantReturn" }, - "original_output": "C: 3: 3: Style/RedundantReturn: Redundant `return` detected.", + "original_output": "test/rdjson_formatter/testdata/correctable_offenses.rb:3:3: C: Style/RedundantReturn: Redundant `return` detected.", "suggestions": [ { "range": { @@ -271,11 +271,11 @@ } } }, - "severity": "UNKNOWN_SEVERITY", + "severity": "INFO", "code": { "value": "Layout/ExtraSpacing" }, - "original_output": "C: 3: 9: Layout/ExtraSpacing: Unnecessary spacing detected.", + "original_output": "test/rdjson_formatter/testdata/correctable_offenses.rb:3:9: C: Layout/ExtraSpacing: Unnecessary spacing detected.", "suggestions": [ { "range": { @@ -292,6 +292,17 @@ } ] }, + { + "message": "Empty file detected.", + "location": { + "path": "test/rdjson_formatter/testdata/global_offenses.rb" + }, + "severity": "WARNING", + "code": { + "value": "Lint/EmptyFile" + }, + "original_output": "test/rdjson_formatter/testdata/global_offenses.rb:1:1: W: Lint/EmptyFile: Empty file detected." + }, { "message": "Method parameter must be at least 3 characters long.", "location": { @@ -307,11 +318,11 @@ } } }, - "severity": "UNKNOWN_SEVERITY", + "severity": "INFO", "code": { "value": "Naming/MethodParameterName" }, - "original_output": "C: 4: 15: Naming/MethodParameterName: Method parameter must be at least 3 characters long." + "original_output": "test/rdjson_formatter/testdata/not_correctable_offenses.rb:4:15: C: Naming/MethodParameterName: Method parameter must be at least 3 characters long." }, { "message": "Method parameter must be at least 3 characters long.", @@ -328,11 +339,11 @@ } } }, - "severity": "UNKNOWN_SEVERITY", + "severity": "INFO", "code": { "value": "Naming/MethodParameterName" }, - "original_output": "C: 4: 18: Naming/MethodParameterName: Method parameter must be at least 3 characters long." + "original_output": "test/rdjson_formatter/testdata/not_correctable_offenses.rb:4:18: C: Naming/MethodParameterName: Method parameter must be at least 3 characters long." }, { "message": "Useless assignment to variable - `c`.", @@ -349,11 +360,27 @@ } } }, - "severity": "UNKNOWN_SEVERITY", + "severity": "WARNING", "code": { "value": "Lint/UselessAssignment" }, - "original_output": "W: 5: 5: Lint/UselessAssignment: Useless assignment to variable - `c`." + "original_output": "test/rdjson_formatter/testdata/not_correctable_offenses.rb:5:5: W: Lint/UselessAssignment: Useless assignment to variable - `c`.", + "suggestions": [ + { + "range": { + "start": { + "line": 5, + "column": 5 + }, + "end": { + "line": 5, + "column": 14 + } + }, + "text": "a + b" + } + ] } - ] + ], + "severity": "ERROR" } diff --git a/test/using_bundler/Gemfile.lock b/test/using_bundler/Gemfile.lock index 8b9e7fe..2a75b52 100644 --- a/test/using_bundler/Gemfile.lock +++ b/test/using_bundler/Gemfile.lock @@ -2,25 +2,31 @@ GEM remote: https://rubygems.org/ specs: ast (2.4.2) - parallel (1.20.1) - parser (3.0.2.0) + json (2.7.2) + language_server-protocol (3.17.0.3) + parallel (1.25.1) + parser (3.3.4.0) ast (~> 2.4.1) - rainbow (3.0.0) - regexp_parser (2.1.1) - rexml (3.2.5) - rubocop (1.18.1) + racc + racc (1.8.0) + rainbow (3.1.1) + regexp_parser (2.9.2) + rexml (3.3.9) + rubocop (1.65.0) + json (~> 2.3) + language_server-protocol (>= 3.17.0) parallel (~> 1.10) - parser (>= 3.0.0.0) + parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) - regexp_parser (>= 1.8, < 3.0) - rexml - rubocop-ast (>= 1.7.0, < 2.0) + regexp_parser (>= 2.4, < 3.0) + rexml (>= 3.2.5, < 4.0) + rubocop-ast (>= 1.31.1, < 2.0) ruby-progressbar (~> 1.7) - unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.9.0) - parser (>= 3.0.1.1) - ruby-progressbar (1.11.0) - unicode-display_width (2.0.0) + unicode-display_width (>= 2.4.0, < 3.0) + rubocop-ast (1.31.3) + parser (>= 3.3.1.0) + ruby-progressbar (1.13.0) + unicode-display_width (2.5.0) PLATFORMS x86_64-linux @@ -29,4 +35,4 @@ DEPENDENCIES rubocop BUNDLED WITH - 2.2.25 + 2.5.11