diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 0000000..f7134fb --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,16 @@ +--- +version: "2" +plugins: + rubocop: + enabled: true + channel: rubocop-1-36-0 + reek: + enabled: true + +checks: + method-complexity: + config: + threshold: 10 # defaults to 5. Cognitive complexity rather than cyclomatic complexity + +exclude_patterns: + - "**/spec/" \ No newline at end of file diff --git a/.codeclimate_diff.yml b/.codeclimate_diff.yml new file mode 100644 index 0000000..0f32f9c --- /dev/null +++ b/.codeclimate_diff.yml @@ -0,0 +1,4 @@ +main_branch_name: main +threshold_to_run_on_all_files: 10 +# For ARM based processors, use linux/x86_64 +docker_platform: linux/amd64 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 9c17945..b04a8c8 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,3 @@ # rspec failure tracking .rspec_status -.codeclimate_diff.yml diff --git a/Gemfile.lock b/Gemfile.lock index 85bcf65..494c3a6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - codeclimate_diff (0.1.5) + codeclimate_diff (0.1.13) colorize json optparse @@ -21,13 +21,13 @@ GEM http-accept (1.7.0) http-cookie (1.0.5) domain_name (~> 0.5) - json (2.6.2) + json (2.7.2) method_source (1.0.0) mime-types (3.4.1) mime-types-data (~> 3.2015) mime-types-data (3.2022.0105) netrc (0.11.0) - optparse (0.2.0) + optparse (0.3.1) parallel (1.22.1) parser (3.1.2.1) ast (~> 2.4.1) @@ -45,7 +45,7 @@ GEM http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) - rexml (3.2.5) + rexml (3.3.8) rspec (3.11.0) rspec-core (~> 3.11.0) rspec-expectations (~> 3.11.0) @@ -92,4 +92,4 @@ DEPENDENCIES rubocop (~> 1.21) BUNDLED WITH - 2.3.7 + 2.5.20 diff --git a/README.md b/README.md index a59be7f..a3ee640 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,11 @@ # CodeclimateDiff -This tool lets you see how your branch is affecting the code quality (what issues you've added, fixed, and what issues are outstanding in the files you've touched.) +This tool lets you see how changes in your branch will affect the code quality (what issues you've added, fixed, and what issues are outstanding in the files you've touched that could be fixed while you're in the area.) -It covers 3 kinds of code quality metrics (code smells, cyclomatic complexity, and similar code). +It runs the https://hub.docker.com/r/codeclimate/codeclimate docker image under the hood, which pays attention to all the normal Code Climate configurations. -NOTE: similar code will only work correctly if you run a diff on all the files in your branch. - -## Installation +## Initial setup 1. Make sure docker is installed and running @@ -53,12 +51,25 @@ NOTE: similar code will only work correctly if you run a diff on all the files i IrresponsibleModule: enabled: false + NilCheck: + enabled: false + + ManualDispatch: + enabled: false + LongParameterList: max_params: 4 # defaults to 3. You want this number realistic but stretchy so we can move it down TooManyStatements: max_statements: 10 # defaults to 5. You want this number realistic but stretchy so we can move it down + UtilityFunction: + public_methods_only: true + + UncommunicativeVariableName: + accept: + - e + directories: "app/controllers": IrresponsibleModule: @@ -84,9 +95,10 @@ NOTE: similar code will only work correctly if you run a diff on all the files i enabled: false ``` -4. Add a `.codecimate_diff.yml` configuration file +4. Add a `.codeclimate_diff.yml` configuration file ``` main_branch_name: master # defaults to main + threshold_to_run_on_all_files: 8 # when you reach a certain number of files changed, it becomes faster to analyze all files rather than analyze them one by one. ``` 5. Install the gem @@ -94,7 +106,7 @@ NOTE: similar code will only work correctly if you run a diff on all the files i Add this line to your application's Gemfile: ```ruby - gem 'codeclimate_diff' + gem 'codeclimate_diff', github: 'boost/codeclimate_diff' ``` Install the gem: @@ -108,33 +120,36 @@ NOTE: similar code will only work correctly if you run a diff on all the files i $ bundle binstubs codeclimate_diff -6. Run the baseline and commit the result to the repo + Add codeclimate_diff_baseline.json to .gitignore - ``` - ./bin/codeclimate_diff --baseline - ``` ## Usage -1. Create a feature branch for your work, and reset the baseline + commit (5 mins) +1. Create a feature branch for your work 2. Do some work 3. Check if you've added any issues (about 10 secs per code file changed on your branch): ```bash - # runs on all code files changed in your branch + # runs on each file changed in your branch (about 10 secs per code file changed on your branch) ./bin/codeclimate_diff - + # baseline is now generated on first run, to generate new baseline, delete the existing. OR - # filters the changed files in your branch futher + # filters the changed files in your branch futher by a grep pattern ./bin/codeclimate_diff --pattern places OR # only shows the new and fixed issues ./bin/codeclimate_diff --new-only + + OR + + # always analyzes all files rather than the changed files one by one, even if below the 'threshold_to_run_on_all_files' setting. + # NOTE: similar code issues will only work 100% correctly if you use this setting (otherwise it might miss a similarity with a file you didn't change and think you fixed it) + ./bin/codeclimate_diff --all ``` 4. Now you have time to fix the issues, horray! @@ -172,7 +187,7 @@ With a few tweaks to your CI configuration, we can pull down the main build base 3. Create a personal access token with `read_api` access and save it in the `CODECLIMATE_DIFF_GITLAB_PERSONAL_ACCESS_TOKEN` env variable -Now when you run it on the changed files in your branch, it will refresh the baseline first! +Now when you run it on the changed files in your branch, it will download the latest baseline first! ## Development diff --git a/codeclimate_diff_baseline.json b/codeclimate_diff_baseline.json index 54a545d..39763cd 100644 --- a/codeclimate_diff_baseline.json +++ b/codeclimate_diff_baseline.json @@ -1,5 +1,61 @@ -[{"engine_name":"structure","fingerprint":"3533a4842f04580cf9020d509188a831","categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `print_category` has 5 arguments (exceeds 4 allowed). Consider refactoring.","location":{"path":"lib/codeclimate_diff.rb","lines":{"begin":128,"end":128}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue"}, -{"engine_name":"structure","fingerprint":"5728cf2c2fd73a8a5fbf51a633ac90ed","categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `calculate_issues_in_changed_files` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.","location":{"path":"lib/codeclimate_diff.rb","lines":{"begin":21,"end":37}},"other_locations":[],"remediation_points":350000,"severity":"minor","type":"issue"}, -{"engine_name":"structure","fingerprint":"4e179b5b52e93b38c2fff38219cd8ac5","categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `print_result` has a Cognitive Complexity of 8 (exceeds 5 allowed). Consider refactoring.","location":{"path":"lib/codeclimate_diff.rb","lines":{"begin":159,"end":185}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue"}, -{"engine_name":"structure","fingerprint":"9f9772c5d55a7542b71e2fbfe5c5ab52","categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `sort_issues` has 26 lines of code (exceeds 25 allowed). Consider refactoring.","location":{"path":"lib/codeclimate_diff.rb","lines":{"begin":74,"end":114}},"other_locations":[],"remediation_points":624000,"severity":"minor","type":"issue"}, -{"name":"ruby.parse.succeeded","type":"measurement","value":2,"engine_name":"structure"}] +[{"engine_name":"structure","fingerprint":"f8ccebfc07d1a0c9fa77b6325a61b82c","categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `refresh_baseline_if_configured` has a Cognitive Complexity of 13 (exceeds 10 allowed). Consider refactoring.","location":{"path":"lib/codeclimate_diff/downloader.rb","lines":{"begin":7,"end":39}},"other_locations":[],"remediation_points":450000,"severity":"minor","type":"issue"}, +{"engine_name":"structure","fingerprint":"4890c3e24aa5f22bc928ccddd6a60a14","categories":["Complexity"],"check_name":"method_lines","content":{"body":""},"description":"Method `sort_issues` has 26 lines of code (exceeds 25 allowed). Consider refactoring.","location":{"path":"lib/codeclimate_diff/issue_sorter.rb","lines":{"begin":33,"end":73}},"other_locations":[],"remediation_points":624000,"severity":"minor","type":"issue"}, +{"engine_name":"structure","fingerprint":"5989a7d02618f9eba49266f1c5473861","categories":["Complexity"],"check_name":"argument_count","content":{"body":""},"description":"Method `print_category` has 5 arguments (exceeds 4 allowed). Consider refactoring.","location":{"path":"lib/codeclimate_diff/result_printer.rb","lines":{"begin":20,"end":20}},"other_locations":[],"remediation_points":375000,"severity":"minor","type":"issue"}, +{"engine_name":"structure","fingerprint":"79ec8f3ef3737357ed2c43ab0a065ef3","categories":["Complexity"],"check_name":"method_complexity","content":{"body":"# Cognitive Complexity\nCognitive Complexity is a measure of how difficult a unit of code is to intuitively understand. Unlike Cyclomatic Complexity, which determines how difficult your code will be to test, Cognitive Complexity tells you how difficult your code will be to read and comprehend.\n\n### A method's cognitive complexity is based on a few simple rules:\n* Code is not considered more complex when it uses shorthand that the language provides for collapsing multiple statements into one\n* Code is considered more complex for each \"break in the linear flow of the code\"\n* Code is considered more complex when \"flow breaking structures are nested\"\n\n### Further reading\n* [Cognitive Complexity docs](https://docs.codeclimate.com/v1.0/docs/cognitive-complexity)\n* [Cognitive Complexity: A new way of measuring understandability](https://www.sonarsource.com/docs/CognitiveComplexity.pdf)\n"},"description":"Method `calculate_issues_in_changed_files` has a Cognitive Complexity of 20 (exceeds 10 allowed). Consider refactoring.","location":{"path":"lib/codeclimate_diff/runner.rb","lines":{"begin":39,"end":69}},"other_locations":[],"remediation_points":1150000,"severity":"minor","type":"issue"}, +{"name":"ruby.parse.succeeded","type":"measurement","value":7,"engine_name":"structure"}, +{"engine_name":"reek","fingerprint":"054589dd54a8aef188941b688d099978","type":"issue","check_name":"IrresponsibleModule","description":"CodeclimateDiff::CodeclimateWrapper has no descriptive comment","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/codeclimate_wrapper.rb","lines":{"begin":7,"end":7}},"remediation_points":350000,"content":{"body":"Classes and modules are the units of reuse and release. It is therefore considered good practice to annotate every class and module with a brief comment outlining its responsibilities.\n\n## Example\n\nGiven\n\n```Ruby\nclass Dummy\n # Do things...\nend\n```\n\nReek would emit the following warning:\n\n```\ntest.rb -- 1 warning:\n [1]:Dummy has no descriptive comment (IrresponsibleModule)\n```\n\nFixing this is simple - just an explaining comment:\n\n```Ruby\n# The Dummy class is responsible for ...\nclass Dummy\n # Do things...\nend\n```\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"0ab3c6e125bef6686b3c821c59933e08","type":"issue","check_name":"DuplicateMethodCall","description":"CodeclimateDiff::Downloader#self.refresh_baseline_if_configured calls 'CodeclimateDiff.configuration' 6 times","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/downloader.rb","lines":{"begin":8,"end":22}},"remediation_points":350000,"content":{"body":"Duplication occurs when two fragments of code look nearly identical, or when two fragments of code have nearly identical effects at some conceptual level.\n\nReek implements a check for _Duplicate Method Call_.\n\n## Example\n\nHere's a very much simplified and contrived example. The following method will report a warning:\n\n```Ruby\ndef double_thing()\n @other.thing + @other.thing\nend\n```\n\nOne quick approach to silence Reek would be to refactor the code thus:\n\n```Ruby\ndef double_thing()\n thing = @other.thing\n thing + thing\nend\n```\n\nA slightly different approach would be to replace all calls of `double_thing` by calls to `@other.double_thing`:\n\n```Ruby\nclass Other\n def double_thing()\n thing + thing\n end\nend\n```\n\nThe approach you take will depend on balancing other factors in your code.\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"585621642e3917aabb584a8dee9dc5b9","type":"issue","check_name":"DuplicateMethodCall","description":"CodeclimateDiff::Downloader#self.refresh_baseline_if_configured calls 'CodeclimateDiff.configuration[\"gitlab\"]' 5 times","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/downloader.rb","lines":{"begin":8,"end":22}},"remediation_points":350000,"content":{"body":"Duplication occurs when two fragments of code look nearly identical, or when two fragments of code have nearly identical effects at some conceptual level.\n\nReek implements a check for _Duplicate Method Call_.\n\n## Example\n\nHere's a very much simplified and contrived example. The following method will report a warning:\n\n```Ruby\ndef double_thing()\n @other.thing + @other.thing\nend\n```\n\nOne quick approach to silence Reek would be to refactor the code thus:\n\n```Ruby\ndef double_thing()\n thing = @other.thing\n thing + thing\nend\n```\n\nA slightly different approach would be to replace all calls of `double_thing` by calls to `@other.double_thing`:\n\n```Ruby\nclass Other\n def double_thing()\n thing + thing\n end\nend\n```\n\nThe approach you take will depend on balancing other factors in your code.\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"a90c8153eeb16896f80233d15786037a","type":"issue","check_name":"DuplicateMethodCall","description":"CodeclimateDiff::Downloader#self.refresh_baseline_if_configured calls 'puts \"Using current baseline.\"' 2 times","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/downloader.rb","lines":{"begin":33,"end":38}},"remediation_points":350000,"content":{"body":"Duplication occurs when two fragments of code look nearly identical, or when two fragments of code have nearly identical effects at some conceptual level.\n\nReek implements a check for _Duplicate Method Call_.\n\n## Example\n\nHere's a very much simplified and contrived example. The following method will report a warning:\n\n```Ruby\ndef double_thing()\n @other.thing + @other.thing\nend\n```\n\nOne quick approach to silence Reek would be to refactor the code thus:\n\n```Ruby\ndef double_thing()\n thing = @other.thing\n thing + thing\nend\n```\n\nA slightly different approach would be to replace all calls of `double_thing` by calls to `@other.double_thing`:\n\n```Ruby\nclass Other\n def double_thing()\n thing + thing\n end\nend\n```\n\nThe approach you take will depend on balancing other factors in your code.\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"4dade4b82ae3a3c900dd5cbd4baa48c0","type":"issue","check_name":"DuplicateMethodCall","description":"CodeclimateDiff::Downloader#self.refresh_baseline_if_configured calls 'response.body' 2 times","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/downloader.rb","lines":{"begin":29,"end":32}},"remediation_points":350000,"content":{"body":"Duplication occurs when two fragments of code look nearly identical, or when two fragments of code have nearly identical effects at some conceptual level.\n\nReek implements a check for _Duplicate Method Call_.\n\n## Example\n\nHere's a very much simplified and contrived example. The following method will report a warning:\n\n```Ruby\ndef double_thing()\n @other.thing + @other.thing\nend\n```\n\nOne quick approach to silence Reek would be to refactor the code thus:\n\n```Ruby\ndef double_thing()\n thing = @other.thing\n thing + thing\nend\n```\n\nA slightly different approach would be to replace all calls of `double_thing` by calls to `@other.double_thing`:\n\n```Ruby\nclass Other\n def double_thing()\n thing + thing\n end\nend\n```\n\nThe approach you take will depend on balancing other factors in your code.\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"eb7c72ed624bdafbdbfee6ceed64db54","type":"issue","check_name":"DuplicateMethodCall","description":"CodeclimateDiff::Downloader#self.refresh_baseline_if_configured calls 'response.code' 2 times","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/downloader.rb","lines":{"begin":28,"end":32}},"remediation_points":350000,"content":{"body":"Duplication occurs when two fragments of code look nearly identical, or when two fragments of code have nearly identical effects at some conceptual level.\n\nReek implements a check for _Duplicate Method Call_.\n\n## Example\n\nHere's a very much simplified and contrived example. The following method will report a warning:\n\n```Ruby\ndef double_thing()\n @other.thing + @other.thing\nend\n```\n\nOne quick approach to silence Reek would be to refactor the code thus:\n\n```Ruby\ndef double_thing()\n thing = @other.thing\n thing + thing\nend\n```\n\nA slightly different approach would be to replace all calls of `double_thing` by calls to `@other.double_thing`:\n\n```Ruby\nclass Other\n def double_thing()\n thing + thing\n end\nend\n```\n\nThe approach you take will depend on balancing other factors in your code.\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"f54441e8533c9bf76e64b72596931f02","type":"issue","check_name":"IrresponsibleModule","description":"CodeclimateDiff::Downloader has no descriptive comment","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/downloader.rb","lines":{"begin":6,"end":6}},"remediation_points":350000,"content":{"body":"Classes and modules are the units of reuse and release. It is therefore considered good practice to annotate every class and module with a brief comment outlining its responsibilities.\n\n## Example\n\nGiven\n\n```Ruby\nclass Dummy\n # Do things...\nend\n```\n\nReek would emit the following warning:\n\n```\ntest.rb -- 1 warning:\n [1]:Dummy has no descriptive comment (IrresponsibleModule)\n```\n\nFixing this is simple - just an explaining comment:\n\n```Ruby\n# The Dummy class is responsible for ...\nclass Dummy\n # Do things...\nend\n```\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"d2ddd39e281760ccf0b0918fe08e7e7c","type":"issue","check_name":"TooManyStatements","description":"CodeclimateDiff::Downloader#self.refresh_baseline_if_configured has approx 19 statements","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/downloader.rb","lines":{"begin":7,"end":7}},"remediation_points":500000,"content":{"body":"A method with `Too Many Statements` is any method that has a large number of lines.\n\n`Too Many Statements` warns about any method that has more than 5 statements. Reek's smell detector for `Too Many Statements` counts +1 for every simple statement in a method and +1 for every statement within a control structure (`if`, `else`, `case`, `when`, `for`, `while`, `until`, `begin`, `rescue`) but it doesn't count the control structure itself.\n\nSo the following method would score +6 in Reek's statement-counting algorithm:\n\n```Ruby\ndef parse(arg, argv, \u0026error)\n if !(val = arg) and (argv.empty? or /\\A-/ =~ (val = argv[0]))\n return nil, block, nil # +1\n end\n opt = (val = parse_arg(val, \u0026error))[1] # +2\n val = conv_arg(*val) # +3\n if opt and !arg\n argv.shift # +4\n else\n val[0] = nil # +5\n end\n val # +6\nend\n```\n\n(You might argue that the two assigments within the first @if@ should count as statements, and that perhaps the nested assignment should count as +2.)\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"211287e4b5857d557ac53a56826d6fa0","type":"issue","check_name":"UncommunicativeVariableName","description":"CodeclimateDiff::Downloader#self.refresh_baseline_if_configured has the variable name 'e'","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/downloader.rb","lines":{"begin":36,"end":36}},"remediation_points":150000,"content":{"body":"An `Uncommunicative Variable Name` is a variable name that doesn't communicate its intent well enough.\n\nPoor names make it hard for the reader to build a mental picture of what's going on in the code. They can also be mis-interpreted; and they hurt the flow of reading, because the reader must slow down to interpret the names.\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"6387eea8958bb1f9b03eb1a4d1a7d3d7","type":"issue","check_name":"DuplicateMethodCall","description":"CodeclimateDiff::IssueSorter#self.remove_closest_match_from_list calls 'issue[\"description\"] == issue_to_match[\"description\"]' 2 times","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/issue_sorter.rb","lines":{"begin":10,"end":21}},"remediation_points":350000,"content":{"body":"Duplication occurs when two fragments of code look nearly identical, or when two fragments of code have nearly identical effects at some conceptual level.\n\nReek implements a check for _Duplicate Method Call_.\n\n## Example\n\nHere's a very much simplified and contrived example. The following method will report a warning:\n\n```Ruby\ndef double_thing()\n @other.thing + @other.thing\nend\n```\n\nOne quick approach to silence Reek would be to refactor the code thus:\n\n```Ruby\ndef double_thing()\n thing = @other.thing\n thing + thing\nend\n```\n\nA slightly different approach would be to replace all calls of `double_thing` by calls to `@other.double_thing`:\n\n```Ruby\nclass Other\n def double_thing()\n thing + thing\n end\nend\n```\n\nThe approach you take will depend on balancing other factors in your code.\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"26e97835cc4b485f5758c3fcf6c9aaea","type":"issue","check_name":"DuplicateMethodCall","description":"CodeclimateDiff::IssueSorter#self.remove_closest_match_from_list calls 'issue[\"description\"]' 2 times","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/issue_sorter.rb","lines":{"begin":10,"end":21}},"remediation_points":350000,"content":{"body":"Duplication occurs when two fragments of code look nearly identical, or when two fragments of code have nearly identical effects at some conceptual level.\n\nReek implements a check for _Duplicate Method Call_.\n\n## Example\n\nHere's a very much simplified and contrived example. The following method will report a warning:\n\n```Ruby\ndef double_thing()\n @other.thing + @other.thing\nend\n```\n\nOne quick approach to silence Reek would be to refactor the code thus:\n\n```Ruby\ndef double_thing()\n thing = @other.thing\n thing + thing\nend\n```\n\nA slightly different approach would be to replace all calls of `double_thing` by calls to `@other.double_thing`:\n\n```Ruby\nclass Other\n def double_thing()\n thing + thing\n end\nend\n```\n\nThe approach you take will depend on balancing other factors in your code.\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"22d5da8bce52124b5a9c808004f82019","type":"issue","check_name":"DuplicateMethodCall","description":"CodeclimateDiff::IssueSorter#self.remove_closest_match_from_list calls 'issue[\"fingerprint\"] == issue_to_match[\"fingerprint\"]' 2 times","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/issue_sorter.rb","lines":{"begin":8,"end":20}},"remediation_points":350000,"content":{"body":"Duplication occurs when two fragments of code look nearly identical, or when two fragments of code have nearly identical effects at some conceptual level.\n\nReek implements a check for _Duplicate Method Call_.\n\n## Example\n\nHere's a very much simplified and contrived example. The following method will report a warning:\n\n```Ruby\ndef double_thing()\n @other.thing + @other.thing\nend\n```\n\nOne quick approach to silence Reek would be to refactor the code thus:\n\n```Ruby\ndef double_thing()\n thing = @other.thing\n thing + thing\nend\n```\n\nA slightly different approach would be to replace all calls of `double_thing` by calls to `@other.double_thing`:\n\n```Ruby\nclass Other\n def double_thing()\n thing + thing\n end\nend\n```\n\nThe approach you take will depend on balancing other factors in your code.\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"cc510276d86e62d3aaf8388d5bfa2b19","type":"issue","check_name":"DuplicateMethodCall","description":"CodeclimateDiff::IssueSorter#self.remove_closest_match_from_list calls 'issue[\"fingerprint\"]' 2 times","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/issue_sorter.rb","lines":{"begin":8,"end":20}},"remediation_points":350000,"content":{"body":"Duplication occurs when two fragments of code look nearly identical, or when two fragments of code have nearly identical effects at some conceptual level.\n\nReek implements a check for _Duplicate Method Call_.\n\n## Example\n\nHere's a very much simplified and contrived example. The following method will report a warning:\n\n```Ruby\ndef double_thing()\n @other.thing + @other.thing\nend\n```\n\nOne quick approach to silence Reek would be to refactor the code thus:\n\n```Ruby\ndef double_thing()\n thing = @other.thing\n thing + thing\nend\n```\n\nA slightly different approach would be to replace all calls of `double_thing` by calls to `@other.double_thing`:\n\n```Ruby\nclass Other\n def double_thing()\n thing + thing\n end\nend\n```\n\nThe approach you take will depend on balancing other factors in your code.\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"0b23ee28cb8bdc33b6f2f06a482b5267","type":"issue","check_name":"DuplicateMethodCall","description":"CodeclimateDiff::IssueSorter#self.remove_closest_match_from_list calls 'issue_to_match[\"description\"]' 2 times","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/issue_sorter.rb","lines":{"begin":10,"end":21}},"remediation_points":350000,"content":{"body":"Duplication occurs when two fragments of code look nearly identical, or when two fragments of code have nearly identical effects at some conceptual level.\n\nReek implements a check for _Duplicate Method Call_.\n\n## Example\n\nHere's a very much simplified and contrived example. The following method will report a warning:\n\n```Ruby\ndef double_thing()\n @other.thing + @other.thing\nend\n```\n\nOne quick approach to silence Reek would be to refactor the code thus:\n\n```Ruby\ndef double_thing()\n thing = @other.thing\n thing + thing\nend\n```\n\nA slightly different approach would be to replace all calls of `double_thing` by calls to `@other.double_thing`:\n\n```Ruby\nclass Other\n def double_thing()\n thing + thing\n end\nend\n```\n\nThe approach you take will depend on balancing other factors in your code.\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"30a514924733303a35f7ae8c8de71a95","type":"issue","check_name":"DuplicateMethodCall","description":"CodeclimateDiff::IssueSorter#self.remove_closest_match_from_list calls 'issue_to_match[\"fingerprint\"]' 2 times","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/issue_sorter.rb","lines":{"begin":8,"end":20}},"remediation_points":350000,"content":{"body":"Duplication occurs when two fragments of code look nearly identical, or when two fragments of code have nearly identical effects at some conceptual level.\n\nReek implements a check for _Duplicate Method Call_.\n\n## Example\n\nHere's a very much simplified and contrived example. The following method will report a warning:\n\n```Ruby\ndef double_thing()\n @other.thing + @other.thing\nend\n```\n\nOne quick approach to silence Reek would be to refactor the code thus:\n\n```Ruby\ndef double_thing()\n thing = @other.thing\n thing + thing\nend\n```\n\nA slightly different approach would be to replace all calls of `double_thing` by calls to `@other.double_thing`:\n\n```Ruby\nclass Other\n def double_thing()\n thing + thing\n end\nend\n```\n\nThe approach you take will depend on balancing other factors in your code.\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"e1a894e2e680a8948d6a5bee61911741","type":"issue","check_name":"DuplicateMethodCall","description":"CodeclimateDiff::IssueSorter#self.remove_closest_match_from_list calls 'list.delete_at(index)' 2 times","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/issue_sorter.rb","lines":{"begin":14,"end":25}},"remediation_points":350000,"content":{"body":"Duplication occurs when two fragments of code look nearly identical, or when two fragments of code have nearly identical effects at some conceptual level.\n\nReek implements a check for _Duplicate Method Call_.\n\n## Example\n\nHere's a very much simplified and contrived example. The following method will report a warning:\n\n```Ruby\ndef double_thing()\n @other.thing + @other.thing\nend\n```\n\nOne quick approach to silence Reek would be to refactor the code thus:\n\n```Ruby\ndef double_thing()\n thing = @other.thing\n thing + thing\nend\n```\n\nA slightly different approach would be to replace all calls of `double_thing` by calls to `@other.double_thing`:\n\n```Ruby\nclass Other\n def double_thing()\n thing + thing\n end\nend\n```\n\nThe approach you take will depend on balancing other factors in your code.\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"2fefd889dbaf81b11b2bea400c8d6deb","type":"issue","check_name":"DuplicateMethodCall","description":"CodeclimateDiff::IssueSorter#self.remove_closest_match_from_list calls 'list.index' 2 times","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/issue_sorter.rb","lines":{"begin":7,"end":19}},"remediation_points":350000,"content":{"body":"Duplication occurs when two fragments of code look nearly identical, or when two fragments of code have nearly identical effects at some conceptual level.\n\nReek implements a check for _Duplicate Method Call_.\n\n## Example\n\nHere's a very much simplified and contrived example. The following method will report a warning:\n\n```Ruby\ndef double_thing()\n @other.thing + @other.thing\nend\n```\n\nOne quick approach to silence Reek would be to refactor the code thus:\n\n```Ruby\ndef double_thing()\n thing = @other.thing\n thing + thing\nend\n```\n\nA slightly different approach would be to replace all calls of `double_thing` by calls to `@other.double_thing`:\n\n```Ruby\nclass Other\n def double_thing()\n thing + thing\n end\nend\n```\n\nThe approach you take will depend on balancing other factors in your code.\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"ca6ea3faac869baf424f72669048c899","type":"issue","check_name":"DuplicateMethodCall","description":"CodeclimateDiff::IssueSorter#self.sort_issues calls 'baseline_issues.count' 2 times","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/issue_sorter.rb","lines":{"begin":50,"end":53}},"remediation_points":350000,"content":{"body":"Duplication occurs when two fragments of code look nearly identical, or when two fragments of code have nearly identical effects at some conceptual level.\n\nReek implements a check for _Duplicate Method Call_.\n\n## Example\n\nHere's a very much simplified and contrived example. The following method will report a warning:\n\n```Ruby\ndef double_thing()\n @other.thing + @other.thing\nend\n```\n\nOne quick approach to silence Reek would be to refactor the code thus:\n\n```Ruby\ndef double_thing()\n thing = @other.thing\n thing + thing\nend\n```\n\nA slightly different approach would be to replace all calls of `double_thing` by calls to `@other.double_thing`:\n\n```Ruby\nclass Other\n def double_thing()\n thing + thing\n end\nend\n```\n\nThe approach you take will depend on balancing other factors in your code.\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"fe0d4f54334ad664a42b1018ae44a8ca","type":"issue","check_name":"DuplicateMethodCall","description":"CodeclimateDiff::IssueSorter#self.sort_issues calls 'current_issues.count' 2 times","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/issue_sorter.rb","lines":{"begin":50,"end":53}},"remediation_points":350000,"content":{"body":"Duplication occurs when two fragments of code look nearly identical, or when two fragments of code have nearly identical effects at some conceptual level.\n\nReek implements a check for _Duplicate Method Call_.\n\n## Example\n\nHere's a very much simplified and contrived example. The following method will report a warning:\n\n```Ruby\ndef double_thing()\n @other.thing + @other.thing\nend\n```\n\nOne quick approach to silence Reek would be to refactor the code thus:\n\n```Ruby\ndef double_thing()\n thing = @other.thing\n thing + thing\nend\n```\n\nA slightly different approach would be to replace all calls of `double_thing` by calls to `@other.double_thing`:\n\n```Ruby\nclass Other\n def double_thing()\n thing + thing\n end\nend\n```\n\nThe approach you take will depend on balancing other factors in your code.\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"26083f936211b5167cc5f6809b9c9ade","type":"issue","check_name":"DuplicateMethodCall","description":"CodeclimateDiff::IssueSorter#self.sort_issues calls 'issue[\"fingerprint\"] == fingerprint' 2 times","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/issue_sorter.rb","lines":{"begin":47,"end":48}},"remediation_points":350000,"content":{"body":"Duplication occurs when two fragments of code look nearly identical, or when two fragments of code have nearly identical effects at some conceptual level.\n\nReek implements a check for _Duplicate Method Call_.\n\n## Example\n\nHere's a very much simplified and contrived example. The following method will report a warning:\n\n```Ruby\ndef double_thing()\n @other.thing + @other.thing\nend\n```\n\nOne quick approach to silence Reek would be to refactor the code thus:\n\n```Ruby\ndef double_thing()\n thing = @other.thing\n thing + thing\nend\n```\n\nA slightly different approach would be to replace all calls of `double_thing` by calls to `@other.double_thing`:\n\n```Ruby\nclass Other\n def double_thing()\n thing + thing\n end\nend\n```\n\nThe approach you take will depend on balancing other factors in your code.\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"e879665bca8ca2a6bc3f789248ff8b39","type":"issue","check_name":"DuplicateMethodCall","description":"CodeclimateDiff::IssueSorter#self.sort_issues calls 'issue[\"fingerprint\"]' 3 times","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/issue_sorter.rb","lines":{"begin":44,"end":48}},"remediation_points":350000,"content":{"body":"Duplication occurs when two fragments of code look nearly identical, or when two fragments of code have nearly identical effects at some conceptual level.\n\nReek implements a check for _Duplicate Method Call_.\n\n## Example\n\nHere's a very much simplified and contrived example. The following method will report a warning:\n\n```Ruby\ndef double_thing()\n @other.thing + @other.thing\nend\n```\n\nOne quick approach to silence Reek would be to refactor the code thus:\n\n```Ruby\ndef double_thing()\n thing = @other.thing\n thing + thing\nend\n```\n\nA slightly different approach would be to replace all calls of `double_thing` by calls to `@other.double_thing`:\n\n```Ruby\nclass Other\n def double_thing()\n thing + thing\n end\nend\n```\n\nThe approach you take will depend on balancing other factors in your code.\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"d90684590df5d9e0fe73c8b42d49fb87","type":"issue","check_name":"IrresponsibleModule","description":"CodeclimateDiff::IssueSorter has no descriptive comment","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/issue_sorter.rb","lines":{"begin":4,"end":4}},"remediation_points":350000,"content":{"body":"Classes and modules are the units of reuse and release. It is therefore considered good practice to annotate every class and module with a brief comment outlining its responsibilities.\n\n## Example\n\nGiven\n\n```Ruby\nclass Dummy\n # Do things...\nend\n```\n\nReek would emit the following warning:\n\n```\ntest.rb -- 1 warning:\n [1]:Dummy has no descriptive comment (IrresponsibleModule)\n```\n\nFixing this is simple - just an explaining comment:\n\n```Ruby\n# The Dummy class is responsible for ...\nclass Dummy\n # Do things...\nend\n```\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"980375b6536d455e110ca4c16fb8385a","type":"issue","check_name":"NestedIterators","description":"CodeclimateDiff::IssueSorter#self.sort_issues contains iterators nested 2 deep","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/issue_sorter.rb","lines":{"begin":47,"end":61}},"remediation_points":500000,"content":{"body":"A `Nested Iterator` occurs when a block contains another block.\n\n## Example\n\nGiven\n\n```Ruby\nclass Duck\n class \u003c\u003c self\n def duck_names\n %i!tick trick track!.each do |surname|\n %i!duck!.each do |last_name|\n puts \"full name is #{surname} #{last_name}\"\n end\n end\n end\n end\nend\n```\n\nReek would report the following warning:\n\n```\ntest.rb -- 1 warning:\n [5]:Duck#duck_names contains iterators nested 2 deep (NestedIterators)\n```\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"c43a4fd2dc6f9f2900cf0b0a0702294e","type":"issue","check_name":"TooManyStatements","description":"CodeclimateDiff::IssueSorter#self.remove_closest_match_from_list has approx 9 statements","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/issue_sorter.rb","lines":{"begin":5,"end":5}},"remediation_points":500000,"content":{"body":"A method with `Too Many Statements` is any method that has a large number of lines.\n\n`Too Many Statements` warns about any method that has more than 5 statements. Reek's smell detector for `Too Many Statements` counts +1 for every simple statement in a method and +1 for every statement within a control structure (`if`, `else`, `case`, `when`, `for`, `while`, `until`, `begin`, `rescue`) but it doesn't count the control structure itself.\n\nSo the following method would score +6 in Reek's statement-counting algorithm:\n\n```Ruby\ndef parse(arg, argv, \u0026error)\n if !(val = arg) and (argv.empty? or /\\A-/ =~ (val = argv[0]))\n return nil, block, nil # +1\n end\n opt = (val = parse_arg(val, \u0026error))[1] # +2\n val = conv_arg(*val) # +3\n if opt and !arg\n argv.shift # +4\n else\n val[0] = nil # +5\n end\n val # +6\nend\n```\n\n(You might argue that the two assigments within the first @if@ should count as statements, and that perhaps the nested assignment should count as +2.)\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"c8711ff1c512044d0e938787698e5f45","type":"issue","check_name":"TooManyStatements","description":"CodeclimateDiff::IssueSorter#self.sort_issues has approx 22 statements","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/issue_sorter.rb","lines":{"begin":33,"end":33}},"remediation_points":500000,"content":{"body":"A method with `Too Many Statements` is any method that has a large number of lines.\n\n`Too Many Statements` warns about any method that has more than 5 statements. Reek's smell detector for `Too Many Statements` counts +1 for every simple statement in a method and +1 for every statement within a control structure (`if`, `else`, `case`, `when`, `for`, `while`, `until`, `begin`, `rescue`) but it doesn't count the control structure itself.\n\nSo the following method would score +6 in Reek's statement-counting algorithm:\n\n```Ruby\ndef parse(arg, argv, \u0026error)\n if !(val = arg) and (argv.empty? or /\\A-/ =~ (val = argv[0]))\n return nil, block, nil # +1\n end\n opt = (val = parse_arg(val, \u0026error))[1] # +2\n val = conv_arg(*val) # +3\n if opt and !arg\n argv.shift # +4\n else\n val[0] = nil # +5\n end\n val # +6\nend\n```\n\n(You might argue that the two assigments within the first @if@ should count as statements, and that perhaps the nested assignment should count as +2.)\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"7b8cb5858d38666042143f4f0ad0e173","type":"issue","check_name":"ControlParameter","description":"CodeclimateDiff::ResultPrinter#self.print_category is controlled by argument 'color'","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/result_printer.rb","lines":{"begin":23,"end":23}},"remediation_points":500000,"content":{"body":"`Control Parameter` is a special case of `Control Couple`\n\n## Example\n\nA simple example would be the \"quoted\" parameter in the following method:\n\n```Ruby\ndef write(quoted)\n if quoted\n write_quoted @value\n else\n write_unquoted @value\n end\nend\n```\n\nFixing those problems is out of the scope of this document but an easy solution could be to remove the \"write\" method alltogether and to move the calls to \"write_quoted\" / \"write_unquoted\" in the initial caller of \"write\".\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"83771ef70121c9192872a09a66ee7b0c","type":"issue","check_name":"ControlParameter","description":"CodeclimateDiff::ResultPrinter#self.print_result is controlled by argument 'show_preexisting'","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/result_printer.rb","lines":{"begin":52,"end":52}},"remediation_points":500000,"content":{"body":"`Control Parameter` is a special case of `Control Couple`\n\n## Example\n\nA simple example would be the \"quoted\" parameter in the following method:\n\n```Ruby\ndef write(quoted)\n if quoted\n write_quoted @value\n else\n write_unquoted @value\n end\nend\n```\n\nFixing those problems is out of the scope of this document but an easy solution could be to remove the \"write\" method alltogether and to move the calls to \"write_quoted\" / \"write_unquoted\" in the initial caller of \"write\".\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"f188df8774fe06f10f92ea3ced203a90","type":"issue","check_name":"DuplicateMethodCall","description":"CodeclimateDiff::ResultPrinter#self.print_issues calls 'issue[\"check_name\"]' 2 times","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/result_printer.rb","lines":{"begin":36,"end":43}},"remediation_points":350000,"content":{"body":"Duplication occurs when two fragments of code look nearly identical, or when two fragments of code have nearly identical effects at some conceptual level.\n\nReek implements a check for _Duplicate Method Call_.\n\n## Example\n\nHere's a very much simplified and contrived example. The following method will report a warning:\n\n```Ruby\ndef double_thing()\n @other.thing + @other.thing\nend\n```\n\nOne quick approach to silence Reek would be to refactor the code thus:\n\n```Ruby\ndef double_thing()\n thing = @other.thing\n thing + thing\nend\n```\n\nA slightly different approach would be to replace all calls of `double_thing` by calls to `@other.double_thing`:\n\n```Ruby\nclass Other\n def double_thing()\n thing + thing\n end\nend\n```\n\nThe approach you take will depend on balancing other factors in your code.\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"da9c16763266a2ee7c4acd27b473f431","type":"issue","check_name":"DuplicateMethodCall","description":"CodeclimateDiff::ResultPrinter#self.print_issues calls 'issue[\"engine_name\"]' 2 times","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/result_printer.rb","lines":{"begin":36,"end":42}},"remediation_points":350000,"content":{"body":"Duplication occurs when two fragments of code look nearly identical, or when two fragments of code have nearly identical effects at some conceptual level.\n\nReek implements a check for _Duplicate Method Call_.\n\n## Example\n\nHere's a very much simplified and contrived example. The following method will report a warning:\n\n```Ruby\ndef double_thing()\n @other.thing + @other.thing\nend\n```\n\nOne quick approach to silence Reek would be to refactor the code thus:\n\n```Ruby\ndef double_thing()\n thing = @other.thing\n thing + thing\nend\n```\n\nA slightly different approach would be to replace all calls of `double_thing` by calls to `@other.double_thing`:\n\n```Ruby\nclass Other\n def double_thing()\n thing + thing\n end\nend\n```\n\nThe approach you take will depend on balancing other factors in your code.\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"e9462c1d7525075446ba64f4bf4ddc2b","type":"issue","check_name":"DuplicateMethodCall","description":"CodeclimateDiff::ResultPrinter#self.print_issues calls 'issue[\"severity\"]' 2 times","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/result_printer.rb","lines":{"begin":36,"end":44}},"remediation_points":350000,"content":{"body":"Duplication occurs when two fragments of code look nearly identical, or when two fragments of code have nearly identical effects at some conceptual level.\n\nReek implements a check for _Duplicate Method Call_.\n\n## Example\n\nHere's a very much simplified and contrived example. The following method will report a warning:\n\n```Ruby\ndef double_thing()\n @other.thing + @other.thing\nend\n```\n\nOne quick approach to silence Reek would be to refactor the code thus:\n\n```Ruby\ndef double_thing()\n thing = @other.thing\n thing + thing\nend\n```\n\nA slightly different approach would be to replace all calls of `double_thing` by calls to `@other.double_thing`:\n\n```Ruby\nclass Other\n def double_thing()\n thing + thing\n end\nend\n```\n\nThe approach you take will depend on balancing other factors in your code.\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"a6d4b886f1137834a7895847fda2a405","type":"issue","check_name":"DuplicateMethodCall","description":"CodeclimateDiff::ResultPrinter#self.print_issues_in_category calls 'issue[\"location\"]' 2 times","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/result_printer.rb","lines":{"begin":10,"end":11}},"remediation_points":350000,"content":{"body":"Duplication occurs when two fragments of code look nearly identical, or when two fragments of code have nearly identical effects at some conceptual level.\n\nReek implements a check for _Duplicate Method Call_.\n\n## Example\n\nHere's a very much simplified and contrived example. The following method will report a warning:\n\n```Ruby\ndef double_thing()\n @other.thing + @other.thing\nend\n```\n\nOne quick approach to silence Reek would be to refactor the code thus:\n\n```Ruby\ndef double_thing()\n thing = @other.thing\n thing + thing\nend\n```\n\nA slightly different approach would be to replace all calls of `double_thing` by calls to `@other.double_thing`:\n\n```Ruby\nclass Other\n def double_thing()\n thing + thing\n end\nend\n```\n\nThe approach you take will depend on balancing other factors in your code.\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"c426eed4fc2a91238bf2a338dc0ac631","type":"issue","check_name":"DuplicateMethodCall","description":"CodeclimateDiff::ResultPrinter#self.print_result calls 'fixed_issues.count' 2 times","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/result_printer.rb","lines":{"begin":71,"end":72}},"remediation_points":350000,"content":{"body":"Duplication occurs when two fragments of code look nearly identical, or when two fragments of code have nearly identical effects at some conceptual level.\n\nReek implements a check for _Duplicate Method Call_.\n\n## Example\n\nHere's a very much simplified and contrived example. The following method will report a warning:\n\n```Ruby\ndef double_thing()\n @other.thing + @other.thing\nend\n```\n\nOne quick approach to silence Reek would be to refactor the code thus:\n\n```Ruby\ndef double_thing()\n thing = @other.thing\n thing + thing\nend\n```\n\nA slightly different approach would be to replace all calls of `double_thing` by calls to `@other.double_thing`:\n\n```Ruby\nclass Other\n def double_thing()\n thing + thing\n end\nend\n```\n\nThe approach you take will depend on balancing other factors in your code.\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"9f6643562758be7d65fc46249ba74212","type":"issue","check_name":"DuplicateMethodCall","description":"CodeclimateDiff::ResultPrinter#self.print_result calls 'new_issues.count' 2 times","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/result_printer.rb","lines":{"begin":63,"end":64}},"remediation_points":350000,"content":{"body":"Duplication occurs when two fragments of code look nearly identical, or when two fragments of code have nearly identical effects at some conceptual level.\n\nReek implements a check for _Duplicate Method Call_.\n\n## Example\n\nHere's a very much simplified and contrived example. The following method will report a warning:\n\n```Ruby\ndef double_thing()\n @other.thing + @other.thing\nend\n```\n\nOne quick approach to silence Reek would be to refactor the code thus:\n\n```Ruby\ndef double_thing()\n thing = @other.thing\n thing + thing\nend\n```\n\nA slightly different approach would be to replace all calls of `double_thing` by calls to `@other.double_thing`:\n\n```Ruby\nclass Other\n def double_thing()\n thing + thing\n end\nend\n```\n\nThe approach you take will depend on balancing other factors in your code.\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"66c9f93f83039d5ff8b90e5f5f57156a","type":"issue","check_name":"DuplicateMethodCall","description":"CodeclimateDiff::ResultPrinter#self.print_result calls 'preexisting_issues.count' 2 times","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/result_printer.rb","lines":{"begin":54,"end":55}},"remediation_points":350000,"content":{"body":"Duplication occurs when two fragments of code look nearly identical, or when two fragments of code have nearly identical effects at some conceptual level.\n\nReek implements a check for _Duplicate Method Call_.\n\n## Example\n\nHere's a very much simplified and contrived example. The following method will report a warning:\n\n```Ruby\ndef double_thing()\n @other.thing + @other.thing\nend\n```\n\nOne quick approach to silence Reek would be to refactor the code thus:\n\n```Ruby\ndef double_thing()\n thing = @other.thing\n thing + thing\nend\n```\n\nA slightly different approach would be to replace all calls of `double_thing` by calls to `@other.double_thing`:\n\n```Ruby\nclass Other\n def double_thing()\n thing + thing\n end\nend\n```\n\nThe approach you take will depend on balancing other factors in your code.\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"524d4bedfc2cef6b38a753be561516ef","type":"issue","check_name":"IrresponsibleModule","description":"CodeclimateDiff::ResultPrinter has no descriptive comment","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/result_printer.rb","lines":{"begin":7,"end":7}},"remediation_points":350000,"content":{"body":"Classes and modules are the units of reuse and release. It is therefore considered good practice to annotate every class and module with a brief comment outlining its responsibilities.\n\n## Example\n\nGiven\n\n```Ruby\nclass Dummy\n # Do things...\nend\n```\n\nReek would emit the following warning:\n\n```\ntest.rb -- 1 warning:\n [1]:Dummy has no descriptive comment (IrresponsibleModule)\n```\n\nFixing this is simple - just an explaining comment:\n\n```Ruby\n# The Dummy class is responsible for ...\nclass Dummy\n # Do things...\nend\n```\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"b96b36b5b941ac84266107f82a75ecb6","type":"issue","check_name":"LongParameterList","description":"CodeclimateDiff::ResultPrinter#self.print_category has 5 parameters","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/result_printer.rb","lines":{"begin":20,"end":20}},"remediation_points":500000,"content":{"body":"A `Long Parameter List` occurs when a method has a lot of parameters.\n\n## Example\n\nGiven\n\n```Ruby\nclass Dummy\n def long_list(foo,bar,baz,fling,flung)\n puts foo,bar,baz,fling,flung\n end\nend\n```\n\nReek would report the following warning:\n\n```\ntest.rb -- 1 warning:\n [2]:Dummy#long_list has 5 parameters (LongParameterList)\n```\n\nA common solution to this problem would be the introduction of parameter objects.\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"24256875bc239c7ee72e7bf5162a5cf1","type":"issue","check_name":"NestedIterators","description":"CodeclimateDiff::ResultPrinter#self.print_issues contains iterators nested 2 deep","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/result_printer.rb","lines":{"begin":41,"end":41}},"remediation_points":500000,"content":{"body":"A `Nested Iterator` occurs when a block contains another block.\n\n## Example\n\nGiven\n\n```Ruby\nclass Duck\n class \u003c\u003c self\n def duck_names\n %i!tick trick track!.each do |surname|\n %i!duck!.each do |last_name|\n puts \"full name is #{surname} #{last_name}\"\n end\n end\n end\n end\nend\n```\n\nReek would report the following warning:\n\n```\ntest.rb -- 1 warning:\n [5]:Duck#duck_names contains iterators nested 2 deep (NestedIterators)\n```\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"dba788bce2d4037f442a07834141f748","type":"issue","check_name":"TooManyStatements","description":"CodeclimateDiff::ResultPrinter#self.print_call_to_action has approx 7 statements","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/result_printer.rb","lines":{"begin":79,"end":79}},"remediation_points":500000,"content":{"body":"A method with `Too Many Statements` is any method that has a large number of lines.\n\n`Too Many Statements` warns about any method that has more than 5 statements. Reek's smell detector for `Too Many Statements` counts +1 for every simple statement in a method and +1 for every statement within a control structure (`if`, `else`, `case`, `when`, `for`, `while`, `until`, `begin`, `rescue`) but it doesn't count the control structure itself.\n\nSo the following method would score +6 in Reek's statement-counting algorithm:\n\n```Ruby\ndef parse(arg, argv, \u0026error)\n if !(val = arg) and (argv.empty? or /\\A-/ =~ (val = argv[0]))\n return nil, block, nil # +1\n end\n opt = (val = parse_arg(val, \u0026error))[1] # +2\n val = conv_arg(*val) # +3\n if opt and !arg\n argv.shift # +4\n else\n val[0] = nil # +5\n end\n val # +6\nend\n```\n\n(You might argue that the two assigments within the first @if@ should count as statements, and that perhaps the nested assignment should count as +2.)\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"847c6524aee19aaaa5c9f75d2af55034","type":"issue","check_name":"TooManyStatements","description":"CodeclimateDiff::ResultPrinter#self.print_issues has approx 10 statements","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/result_printer.rb","lines":{"begin":35,"end":35}},"remediation_points":500000,"content":{"body":"A method with `Too Many Statements` is any method that has a large number of lines.\n\n`Too Many Statements` warns about any method that has more than 5 statements. Reek's smell detector for `Too Many Statements` counts +1 for every simple statement in a method and +1 for every statement within a control structure (`if`, `else`, `case`, `when`, `for`, `while`, `until`, `begin`, `rescue`) but it doesn't count the control structure itself.\n\nSo the following method would score +6 in Reek's statement-counting algorithm:\n\n```Ruby\ndef parse(arg, argv, \u0026error)\n if !(val = arg) and (argv.empty? or /\\A-/ =~ (val = argv[0]))\n return nil, block, nil # +1\n end\n opt = (val = parse_arg(val, \u0026error))[1] # +2\n val = conv_arg(*val) # +3\n if opt and !arg\n argv.shift # +4\n else\n val[0] = nil # +5\n end\n val # +6\nend\n```\n\n(You might argue that the two assigments within the first @if@ should count as statements, and that perhaps the nested assignment should count as +2.)\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"0e1b58c41f946011380b520b782497f6","type":"issue","check_name":"TooManyStatements","description":"CodeclimateDiff::ResultPrinter#self.print_issues_in_category has approx 7 statements","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/result_printer.rb","lines":{"begin":8,"end":8}},"remediation_points":500000,"content":{"body":"A method with `Too Many Statements` is any method that has a large number of lines.\n\n`Too Many Statements` warns about any method that has more than 5 statements. Reek's smell detector for `Too Many Statements` counts +1 for every simple statement in a method and +1 for every statement within a control structure (`if`, `else`, `case`, `when`, `for`, `while`, `until`, `begin`, `rescue`) but it doesn't count the control structure itself.\n\nSo the following method would score +6 in Reek's statement-counting algorithm:\n\n```Ruby\ndef parse(arg, argv, \u0026error)\n if !(val = arg) and (argv.empty? or /\\A-/ =~ (val = argv[0]))\n return nil, block, nil # +1\n end\n opt = (val = parse_arg(val, \u0026error))[1] # +2\n val = conv_arg(*val) # +3\n if opt and !arg\n argv.shift # +4\n else\n val[0] = nil # +5\n end\n val # +6\nend\n```\n\n(You might argue that the two assigments within the first @if@ should count as statements, and that perhaps the nested assignment should count as +2.)\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"659011304f57ba6821ee6ecd179e2fe3","type":"issue","check_name":"TooManyStatements","description":"CodeclimateDiff::ResultPrinter#self.print_result has approx 12 statements","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/result_printer.rb","lines":{"begin":51,"end":51}},"remediation_points":500000,"content":{"body":"A method with `Too Many Statements` is any method that has a large number of lines.\n\n`Too Many Statements` warns about any method that has more than 5 statements. Reek's smell detector for `Too Many Statements` counts +1 for every simple statement in a method and +1 for every statement within a control structure (`if`, `else`, `case`, `when`, `for`, `while`, `until`, `begin`, `rescue`) but it doesn't count the control structure itself.\n\nSo the following method would score +6 in Reek's statement-counting algorithm:\n\n```Ruby\ndef parse(arg, argv, \u0026error)\n if !(val = arg) and (argv.empty? or /\\A-/ =~ (val = argv[0]))\n return nil, block, nil # +1\n end\n opt = (val = parse_arg(val, \u0026error))[1] # +2\n val = conv_arg(*val) # +3\n if opt and !arg\n argv.shift # +4\n else\n val[0] = nil # +5\n end\n val # +6\nend\n```\n\n(You might argue that the two assigments within the first @if@ should count as statements, and that perhaps the nested assignment should count as +2.)\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"eebfb3495de5ce6c32cdbdedd3733169","type":"issue","check_name":"BooleanParameter","description":"CodeclimateDiff::Runner#self.run_diff_on_branch has boolean parameter 'always_analyze_all_files'","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/runner.rb","lines":{"begin":89,"end":89}},"remediation_points":500000,"content":{"body":"`Boolean Parameter` is a special case of `Control Couple`, where a method parameter is defaulted to true or false. A _Boolean Parameter_ effectively permits a method's caller to decide which execution path to take. This is a case of bad cohesion. You're creating a dependency between methods that is not really necessary, thus increasing coupling.\n\n## Example\n\nGiven\n\n```Ruby\nclass Dummy\n def hit_the_switch(switch = true)\n if switch\n puts 'Hitting the switch'\n # do other things...\n else\n puts 'Not hitting the switch'\n # do other things...\n end\n end\nend\n```\n\nReek would emit the following warning:\n\n```\ntest.rb -- 3 warnings:\n [1]:Dummy#hit_the_switch has boolean parameter 'switch' (BooleanParameter)\n [2]:Dummy#hit_the_switch is controlled by argument switch (ControlParameter)\n```\n\nNote that both smells are reported, `Boolean Parameter` and `Control Parameter`.\n\n## Getting rid of the smell\n\nThis is highly dependent on your exact architecture, but looking at the example above what you could do is:\n\n* Move everything in the `if` branch into a separate method\n* Move everything in the `else` branch into a separate method\n* Get rid of the `hit_the_switch` method alltogether\n* Make the decision what method to call in the initial caller of `hit_the_switch`\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"f69f9414e9b677df0fe0fa88a56e09e6","type":"issue","check_name":"BooleanParameter","description":"CodeclimateDiff::Runner#self.run_diff_on_branch has boolean parameter 'show_preexisting'","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/runner.rb","lines":{"begin":89,"end":89}},"remediation_points":500000,"content":{"body":"`Boolean Parameter` is a special case of `Control Couple`, where a method parameter is defaulted to true or false. A _Boolean Parameter_ effectively permits a method's caller to decide which execution path to take. This is a case of bad cohesion. You're creating a dependency between methods that is not really necessary, thus increasing coupling.\n\n## Example\n\nGiven\n\n```Ruby\nclass Dummy\n def hit_the_switch(switch = true)\n if switch\n puts 'Hitting the switch'\n # do other things...\n else\n puts 'Not hitting the switch'\n # do other things...\n end\n end\nend\n```\n\nReek would emit the following warning:\n\n```\ntest.rb -- 3 warnings:\n [1]:Dummy#hit_the_switch has boolean parameter 'switch' (BooleanParameter)\n [2]:Dummy#hit_the_switch is controlled by argument switch (ControlParameter)\n```\n\nNote that both smells are reported, `Boolean Parameter` and `Control Parameter`.\n\n## Getting rid of the smell\n\nThis is highly dependent on your exact architecture, but looking at the example above what you could do is:\n\n* Move everything in the `if` branch into a separate method\n* Move everything in the `else` branch into a separate method\n* Get rid of the `hit_the_switch` method alltogether\n* Make the decision what method to call in the initial caller of `hit_the_switch`\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"c9d1604f71aa792148908051a6c30a03","type":"issue","check_name":"ControlParameter","description":"CodeclimateDiff::Runner#self.calculate_issues_in_changed_files is controlled by argument 'always_analyze_all_files'","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/runner.rb","lines":{"begin":43,"end":45}},"remediation_points":500000,"content":{"body":"`Control Parameter` is a special case of `Control Couple`\n\n## Example\n\nA simple example would be the \"quoted\" parameter in the following method:\n\n```Ruby\ndef write(quoted)\n if quoted\n write_quoted @value\n else\n write_unquoted @value\n end\nend\n```\n\nFixing those problems is out of the scope of this document but an easy solution could be to remove the \"write\" method alltogether and to move the calls to \"write_quoted\" / \"write_unquoted\" in the initial caller of \"write\".\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"3ff6caf742702e28c65517be34dc4a90","type":"issue","check_name":"DuplicateMethodCall","description":"CodeclimateDiff::Runner#self.calculate_issues_in_changed_files calls 'JSON.parse(result)' 2 times","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/runner.rb","lines":{"begin":49,"end":60}},"remediation_points":350000,"content":{"body":"Duplication occurs when two fragments of code look nearly identical, or when two fragments of code have nearly identical effects at some conceptual level.\n\nReek implements a check for _Duplicate Method Call_.\n\n## Example\n\nHere's a very much simplified and contrived example. The following method will report a warning:\n\n```Ruby\ndef double_thing()\n @other.thing + @other.thing\nend\n```\n\nOne quick approach to silence Reek would be to refactor the code thus:\n\n```Ruby\ndef double_thing()\n thing = @other.thing\n thing + thing\nend\n```\n\nA slightly different approach would be to replace all calls of `double_thing` by calls to `@other.double_thing`:\n\n```Ruby\nclass Other\n def double_thing()\n thing + thing\n end\nend\n```\n\nThe approach you take will depend on balancing other factors in your code.\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"2ace891a3d054cd1d6b178902b0aa4b9","type":"issue","check_name":"DuplicateMethodCall","description":"CodeclimateDiff::Runner#self.calculate_issues_in_changed_files calls 'JSON.parse(result).each' 2 times","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/runner.rb","lines":{"begin":49,"end":60}},"remediation_points":350000,"content":{"body":"Duplication occurs when two fragments of code look nearly identical, or when two fragments of code have nearly identical effects at some conceptual level.\n\nReek implements a check for _Duplicate Method Call_.\n\n## Example\n\nHere's a very much simplified and contrived example. The following method will report a warning:\n\n```Ruby\ndef double_thing()\n @other.thing + @other.thing\nend\n```\n\nOne quick approach to silence Reek would be to refactor the code thus:\n\n```Ruby\ndef double_thing()\n thing = @other.thing\n thing + thing\nend\n```\n\nA slightly different approach would be to replace all calls of `double_thing` by calls to `@other.double_thing`:\n\n```Ruby\nclass Other\n def double_thing()\n thing + thing\n end\nend\n```\n\nThe approach you take will depend on balancing other factors in your code.\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"ec3e2cc4884fd40b46e5fcd406caed9d","type":"issue","check_name":"DuplicateMethodCall","description":"CodeclimateDiff::Runner#self.calculate_issues_in_changed_files calls 'changed_file_issues.append(issue)' 2 times","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/runner.rb","lines":{"begin":53,"end":63}},"remediation_points":350000,"content":{"body":"Duplication occurs when two fragments of code look nearly identical, or when two fragments of code have nearly identical effects at some conceptual level.\n\nReek implements a check for _Duplicate Method Call_.\n\n## Example\n\nHere's a very much simplified and contrived example. The following method will report a warning:\n\n```Ruby\ndef double_thing()\n @other.thing + @other.thing\nend\n```\n\nOne quick approach to silence Reek would be to refactor the code thus:\n\n```Ruby\ndef double_thing()\n thing = @other.thing\n thing + thing\nend\n```\n\nA slightly different approach would be to replace all calls of `double_thing` by calls to `@other.double_thing`:\n\n```Ruby\nclass Other\n def double_thing()\n thing + thing\n end\nend\n```\n\nThe approach you take will depend on balancing other factors in your code.\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"974a71566e027ab872e3496e1782f85a","type":"issue","check_name":"DuplicateMethodCall","description":"CodeclimateDiff::Runner#self.calculate_issues_in_changed_files calls 'issue[\"type\"] != \"issue\"' 2 times","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/runner.rb","lines":{"begin":50,"end":61}},"remediation_points":350000,"content":{"body":"Duplication occurs when two fragments of code look nearly identical, or when two fragments of code have nearly identical effects at some conceptual level.\n\nReek implements a check for _Duplicate Method Call_.\n\n## Example\n\nHere's a very much simplified and contrived example. The following method will report a warning:\n\n```Ruby\ndef double_thing()\n @other.thing + @other.thing\nend\n```\n\nOne quick approach to silence Reek would be to refactor the code thus:\n\n```Ruby\ndef double_thing()\n thing = @other.thing\n thing + thing\nend\n```\n\nA slightly different approach would be to replace all calls of `double_thing` by calls to `@other.double_thing`:\n\n```Ruby\nclass Other\n def double_thing()\n thing + thing\n end\nend\n```\n\nThe approach you take will depend on balancing other factors in your code.\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"ececcf3944ab70f551f1c57fc5f8989f","type":"issue","check_name":"DuplicateMethodCall","description":"CodeclimateDiff::Runner#self.calculate_issues_in_changed_files calls 'issue[\"type\"]' 2 times","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/runner.rb","lines":{"begin":50,"end":61}},"remediation_points":350000,"content":{"body":"Duplication occurs when two fragments of code look nearly identical, or when two fragments of code have nearly identical effects at some conceptual level.\n\nReek implements a check for _Duplicate Method Call_.\n\n## Example\n\nHere's a very much simplified and contrived example. The following method will report a warning:\n\n```Ruby\ndef double_thing()\n @other.thing + @other.thing\nend\n```\n\nOne quick approach to silence Reek would be to refactor the code thus:\n\n```Ruby\ndef double_thing()\n thing = @other.thing\n thing + thing\nend\n```\n\nA slightly different approach would be to replace all calls of `double_thing` by calls to `@other.double_thing`:\n\n```Ruby\nclass Other\n def double_thing()\n thing + thing\n end\nend\n```\n\nThe approach you take will depend on balancing other factors in your code.\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"cd318842f19b32d930607d78221cf5c5","type":"issue","check_name":"IrresponsibleModule","description":"CodeclimateDiff::Runner has no descriptive comment","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/runner.rb","lines":{"begin":12,"end":12}},"remediation_points":350000,"content":{"body":"Classes and modules are the units of reuse and release. It is therefore considered good practice to annotate every class and module with a brief comment outlining its responsibilities.\n\n## Example\n\nGiven\n\n```Ruby\nclass Dummy\n # Do things...\nend\n```\n\nReek would emit the following warning:\n\n```\ntest.rb -- 1 warning:\n [1]:Dummy has no descriptive comment (IrresponsibleModule)\n```\n\nFixing this is simple - just an explaining comment:\n\n```Ruby\n# The Dummy class is responsible for ...\nclass Dummy\n # Do things...\nend\n```\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"eaaa4ef4913d7d7dedf4596bf06edb8b","type":"issue","check_name":"NestedIterators","description":"CodeclimateDiff::Runner#self.calculate_changed_filenames contains iterators nested 2 deep","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/runner.rb","lines":{"begin":33,"end":33}},"remediation_points":500000,"content":{"body":"A `Nested Iterator` occurs when a block contains another block.\n\n## Example\n\nGiven\n\n```Ruby\nclass Duck\n class \u003c\u003c self\n def duck_names\n %i!tick trick track!.each do |surname|\n %i!duck!.each do |last_name|\n puts \"full name is #{surname} #{last_name}\"\n end\n end\n end\n end\nend\n```\n\nReek would report the following warning:\n\n```\ntest.rb -- 1 warning:\n [5]:Duck#duck_names contains iterators nested 2 deep (NestedIterators)\n```\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"e5fe8233608d51d516fe9c9662f3bce0","type":"issue","check_name":"NestedIterators","description":"CodeclimateDiff::Runner#self.calculate_issues_in_changed_files contains iterators nested 2 deep","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/runner.rb","lines":{"begin":60,"end":60}},"remediation_points":500000,"content":{"body":"A `Nested Iterator` occurs when a block contains another block.\n\n## Example\n\nGiven\n\n```Ruby\nclass Duck\n class \u003c\u003c self\n def duck_names\n %i!tick trick track!.each do |surname|\n %i!duck!.each do |last_name|\n puts \"full name is #{surname} #{last_name}\"\n end\n end\n end\n end\nend\n```\n\nReek would report the following warning:\n\n```\ntest.rb -- 1 warning:\n [5]:Duck#duck_names contains iterators nested 2 deep (NestedIterators)\n```\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"545f4e2fbe74902028bcf6c047db975b","type":"issue","check_name":"TooManyStatements","description":"CodeclimateDiff::Runner#self.calculate_changed_filenames has approx 16 statements","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/runner.rb","lines":{"begin":13,"end":13}},"remediation_points":500000,"content":{"body":"A method with `Too Many Statements` is any method that has a large number of lines.\n\n`Too Many Statements` warns about any method that has more than 5 statements. Reek's smell detector for `Too Many Statements` counts +1 for every simple statement in a method and +1 for every statement within a control structure (`if`, `else`, `case`, `when`, `for`, `while`, `until`, `begin`, `rescue`) but it doesn't count the control structure itself.\n\nSo the following method would score +6 in Reek's statement-counting algorithm:\n\n```Ruby\ndef parse(arg, argv, \u0026error)\n if !(val = arg) and (argv.empty? or /\\A-/ =~ (val = argv[0]))\n return nil, block, nil # +1\n end\n opt = (val = parse_arg(val, \u0026error))[1] # +2\n val = conv_arg(*val) # +3\n if opt and !arg\n argv.shift # +4\n else\n val[0] = nil # +5\n end\n val # +6\nend\n```\n\n(You might argue that the two assigments within the first @if@ should count as statements, and that perhaps the nested assignment should count as +2.)\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"911d8c2cd9713eb42fe09220efd48ca7","type":"issue","check_name":"TooManyStatements","description":"CodeclimateDiff::Runner#self.calculate_issues_in_changed_files has approx 18 statements","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/runner.rb","lines":{"begin":39,"end":39}},"remediation_points":500000,"content":{"body":"A method with `Too Many Statements` is any method that has a large number of lines.\n\n`Too Many Statements` warns about any method that has more than 5 statements. Reek's smell detector for `Too Many Statements` counts +1 for every simple statement in a method and +1 for every statement within a control structure (`if`, `else`, `case`, `when`, `for`, `while`, `until`, `begin`, `rescue`) but it doesn't count the control structure itself.\n\nSo the following method would score +6 in Reek's statement-counting algorithm:\n\n```Ruby\ndef parse(arg, argv, \u0026error)\n if !(val = arg) and (argv.empty? or /\\A-/ =~ (val = argv[0]))\n return nil, block, nil # +1\n end\n opt = (val = parse_arg(val, \u0026error))[1] # +2\n val = conv_arg(*val) # +3\n if opt and !arg\n argv.shift # +4\n else\n val[0] = nil # +5\n end\n val # +6\nend\n```\n\n(You might argue that the two assigments within the first @if@ should count as statements, and that perhaps the nested assignment should count as +2.)\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"b3db1a66f15bef1e6a1a5d9bbb519f26","type":"issue","check_name":"TooManyStatements","description":"CodeclimateDiff::Runner#self.run_diff_on_branch has approx 7 statements","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff/runner.rb","lines":{"begin":89,"end":89}},"remediation_points":500000,"content":{"body":"A method with `Too Many Statements` is any method that has a large number of lines.\n\n`Too Many Statements` warns about any method that has more than 5 statements. Reek's smell detector for `Too Many Statements` counts +1 for every simple statement in a method and +1 for every statement within a control structure (`if`, `else`, `case`, `when`, `for`, `while`, `until`, `begin`, `rescue`) but it doesn't count the control structure itself.\n\nSo the following method would score +6 in Reek's statement-counting algorithm:\n\n```Ruby\ndef parse(arg, argv, \u0026error)\n if !(val = arg) and (argv.empty? or /\\A-/ =~ (val = argv[0]))\n return nil, block, nil # +1\n end\n opt = (val = parse_arg(val, \u0026error))[1] # +2\n val = conv_arg(*val) # +3\n if opt and !arg\n argv.shift # +4\n else\n val[0] = nil # +5\n end\n val # +6\nend\n```\n\n(You might argue that the two assigments within the first @if@ should count as statements, and that perhaps the nested assignment should count as +2.)\n"},"severity":"minor"}, +{"engine_name":"reek","fingerprint":"91c4af205f1f773f532585f9dd580548","type":"issue","check_name":"IrresponsibleModule","description":"CodeclimateDiff has no descriptive comment","categories":["Complexity"],"location":{"path":"lib/codeclimate_diff.rb","lines":{"begin":6,"end":6}},"remediation_points":350000,"content":{"body":"Classes and modules are the units of reuse and release. It is therefore considered good practice to annotate every class and module with a brief comment outlining its responsibilities.\n\n## Example\n\nGiven\n\n```Ruby\nclass Dummy\n # Do things...\nend\n```\n\nReek would emit the following warning:\n\n```\ntest.rb -- 1 warning:\n [1]:Dummy has no descriptive comment (IrresponsibleModule)\n```\n\nFixing this is simple - just an explaining comment:\n\n```Ruby\n# The Dummy class is responsible for ...\nclass Dummy\n # Do things...\nend\n```\n"},"severity":"minor"}] diff --git a/exe/codeclimate_diff b/exe/codeclimate_diff index 55f6e25..1ac8731 100755 --- a/exe/codeclimate_diff +++ b/exe/codeclimate_diff @@ -16,14 +16,17 @@ OptionParser.new do |opts| opts.on("-n", "--new-only", "It will only show what you have changed and not existing issues in files you have touched.") + opts.on("-a", "--all", + "It will always analyze all files, and not the changed files one by one, even if below the 'threshold_to_run_on_all_files' setting.") + opts.on("-pPATTERN", "--pattern=PATTERN", "Grep pattern to filter files. If provided, will filter the files changed on your branch further.") end.parse!(into: options) -if options[:baseline] - CodeclimateDiff::Runner.generate_baseline -elsif options[:"new-only"] - CodeclimateDiff::Runner.run_diff_on_branch(options[:pattern], show_preexisting: false) +if options[:"new-only"] + CodeclimateDiff::Runner.run_diff_on_branch(options[:pattern], always_analyze_all_files: options[:all], + show_preexisting: false) else - CodeclimateDiff::Runner.run_diff_on_branch(options[:pattern], show_preexisting: true) + CodeclimateDiff::Runner.run_diff_on_branch(options[:pattern], always_analyze_all_files: options[:all], + show_preexisting: true) end diff --git a/lib/codeclimate_diff.md b/lib/codeclimate_diff.md deleted file mode 100644 index 202d554..0000000 --- a/lib/codeclimate_diff.md +++ /dev/null @@ -1,96 +0,0 @@ -# Codeclimate Dev - -This tool lets you see how your branch is affecting the code quality (what issues you've added, fixed, and what issues are outstanding in the files you've touched.) - -It covers 2/3 of our code quality metrics (code smells, cyclomatic complexity, but not similar code). -Codeclimate supports 'duplication' as a plugin, but it takes twice as long to run on everything and only really works if you run it on everything. - - -## First setup - -1. Install the codeclimate cli: - ``` - brew tap codeclimate/formulae - brew install codeclimate - ``` - -2. Add a `.codeclimate.yml` config file eg: - - ``` - --- - version: "2" - plugins: - rubocop: - enabled: true - channel: rubocop-1-36-0 - reek: - enabled: true - - exclude_patterns: - - config/ - - db/ - - dist/ - - features/ - - public/ - - "**/node_modules/" - - script/ - - "**/spec/" - - "**/test/" - - "**/tests/" - - Tests/ - - "**/vendor/" - - "**/*_test.go" - - "**/*.d.ts" - - "**/*.min.js" - - "**/*.min.css" - - "**/__tests__/" - - "**/__mocks__/" - - "/.gitlab/" - - coverage/ - ``` - - 3. Give execute permission for `codeclimate_diff` - - ``` - chmod a+x ./codeclimate_diff - ``` - - 4. Run the baseline and commit the result to the repo - - ``` - ./codeclimate_diff --baseline - ``` - -## Normal workflow - -1. Create a feature branch for your work, and reset the baseline + commit (5 mins) - -2. Do some work - -3. Check if you've added any issues (about 10 secs per code file changed on your branch) - - ``` - # runs on all code files changed in your branch - ./codeclimate_diff - - OR - - # filters the changed files in your branch futher - ./codeclimate_diff --pattern places - - OR - - # only shows the new and fixed issues - ./codeclimate_diff --new-only - ``` -4. Now you have time to fix the issues yay! - - -## Next Steps - -- Extract into a Gem -- See if we can improve performance (it spins up a docker container per file) -- Plug into the pipeline and fail if we've introduced more issues than fixed -- Run duplication in the pipeline. -- Run the baseline in the pipeline and download it from the artifact instead of running locally? - diff --git a/lib/codeclimate_diff/codeclimate_wrapper.rb b/lib/codeclimate_diff/codeclimate_wrapper.rb index b15d158..02b5cb7 100644 --- a/lib/codeclimate_diff/codeclimate_wrapper.rb +++ b/lib/codeclimate_diff/codeclimate_wrapper.rb @@ -5,14 +5,20 @@ module CodeclimateDiff class CodeclimateWrapper + def run_codeclimate(filename = "") - `docker run \ + docker_platform = CodeclimateDiff.configuration["docker_platform"] || "linux/amd64" + + output = `docker run \ --interactive --tty --rm \ --env CODECLIMATE_CODE="$PWD" \ --volume "$PWD":/code \ --volume /var/run/docker.sock:/var/run/docker.sock \ --volume /tmp/cc:/tmp/cc \ + --platform #{docker_platform} \ codeclimate/codeclimate analyze -f json #{filename}` + + output.gsub(/.*?(?=\[{)/im, "") # remove everything before the first json object (ie WARNINGS) end def pull_latest_image diff --git a/lib/codeclimate_diff/downloader.rb b/lib/codeclimate_diff/downloader.rb index f4483ff..afdef84 100644 --- a/lib/codeclimate_diff/downloader.rb +++ b/lib/codeclimate_diff/downloader.rb @@ -8,19 +8,34 @@ def self.refresh_baseline_if_configured return unless CodeclimateDiff.configuration["gitlab"] return unless CodeclimateDiff.configuration["gitlab"]["download_baseline_from_pipeline"] - puts "Downloading baseline file from gitlab" + personal_access_token = ENV.fetch("CODECLIMATE_DIFF_GITLAB_PERSONAL_ACCESS_TOKEN") + + if !personal_access_token + puts "Missing environment variable 'CODECLIMATE_DIFF_GITLAB_PERSONAL_ACCESS_TOKEN'. Using current baseline." + return + end + + puts "Downloading baseline file from gitlab..." branch_name = CodeclimateDiff.configuration["main_branch_name"] || "main" project_id = CodeclimateDiff.configuration["gitlab"]["project_id"] host = CodeclimateDiff.configuration["gitlab"]["host"] baseline_filename = CodeclimateDiff.configuration["gitlab"]["baseline_filename"] - personal_access_token = ENV.fetch("CODECLIMATE_DIFF_GITLAB_PERSONAL_ACCESS_TOKEN") # curl --output codeclimate_diff_baseline.json --header "PRIVATE-TOKEN: MYTOKEN" "https://gitlab.digitalnz.org/api/v4/projects/85/jobs/artifacts/main/raw/codeclimate_diff_baseline.json?job=code_quality" url = "#{host}/api/v4/projects/#{project_id}/jobs/artifacts/#{branch_name}/raw/#{baseline_filename}?job=code_quality" response = RestClient.get(url, { "PRIVATE-TOKEN": personal_access_token }) - File.write("codeclimate_diff_baseline.json", response.body) + + if response.code < 300 + File.write("codeclimate_diff_baseline.json", response.body) + puts "Successfully updated the baseline." + else + puts "Downloading baseline file failed with status code #{response.code}: #{response.body}" + puts "Using current baseline." + end + rescue StandardError => e puts e + puts "Using current baseline." end end end diff --git a/lib/codeclimate_diff/runner.rb b/lib/codeclimate_diff/runner.rb index 51648f4..1816710 100644 --- a/lib/codeclimate_diff/runner.rb +++ b/lib/codeclimate_diff/runner.rb @@ -2,6 +2,7 @@ require "json" require "colorize" +require "pry-byebug" require_relative "./codeclimate_wrapper" require_relative "./result_printer" require_relative "./issue_sorter" @@ -12,23 +13,56 @@ class Runner def self.calculate_changed_filenames(pattern) extra_grep_filter = pattern ? " | grep '#{pattern}'" : "" branch_name = CodeclimateDiff.configuration["main_branch_name"] || "main" - files_changed_str = `git diff --name-only #{branch_name} | grep --invert-match spec/ | grep --extended-regexp '.js$|.rb$'#{extra_grep_filter}` - files_changed_str.split("\n") + all_files_changed_str = `git diff --name-only #{branch_name} | grep --extended-regexp '.js$|.rb$'#{extra_grep_filter}` + all_files_changed = all_files_changed_str.split("\n") + .filter { |filename| File.exist?(filename) } + + # load the exclude patterns list from .codeclimate.yml + exclude_patterns = [] + if File.exist?(".codeclimate.yml") + config = YAML.load_file(".codeclimate.yml") + exclude_patterns = config["exclude_patterns"] + end + + files_and_directories_excluded = exclude_patterns.map { |exclude_pattern| Dir.glob(exclude_pattern) }.flatten + + # filter out any files that match the excluded ones + all_files_changed.filter do |filename| + next if files_and_directories_excluded.include? filename + + next if files_and_directories_excluded.any? { |excluded_filename| filename.start_with?(excluded_filename) } + + true + end end - def self.calculate_issues_in_changed_files(changed_filenames) + def self.calculate_issues_in_changed_files(changed_filenames, always_analyze_all_files) changed_file_issues = [] - changed_filenames.each do |filename| - next if filename == "codeclimate_diff.rb" # TODO: fix this file's code quality issues when we make a Gem! + threshold_to_run_on_all_files = CodeclimateDiff.configuration["threshold_to_run_on_all_files"] || 8 + analyze_all_files = always_analyze_all_files || changed_filenames.count > threshold_to_run_on_all_files + if analyze_all_files + message = always_analyze_all_files ? "Analyzing all files..." : "The number of changed files is greater than the threshold '#{threshold_to_run_on_all_files}', so analyzing all files..." + puts message - puts "Analysing '#{filename}'..." - result = CodeclimateWrapper.new.run_codeclimate(filename) + result = CodeclimateWrapper.new.run_codeclimate JSON.parse(result).each do |issue| next if issue["type"] != "issue" + next unless changed_filenames.include? issue["location"]["path"] changed_file_issues.append(issue) end + + else + changed_filenames.each do |filename| + puts "Analysing '#{filename}'..." + result = CodeclimateWrapper.new.run_codeclimate(filename) + JSON.parse(result).each do |issue| + next if issue["type"] != "issue" + + changed_file_issues.append(issue) + end + end end changed_file_issues @@ -52,12 +86,31 @@ def self.generate_baseline puts "Done!" end - def self.run_diff_on_branch(pattern, show_preexisting: true) - CodeclimateWrapper.new.pull_latest_image + def self.setup_baseline_for_branch + main_branch = CodeclimateDiff.configuration["main_branch_name"] || "main" + + project_repo = `basename $(pwd)`.strip + + puts "Creating a temp worktree to generate the baseline..." + system("git worktree add ../temp-codeclimate #{main_branch}") + + Dir.chdir("../temp-codeclimate") do + generate_baseline + + puts "Copying the baseline to #{project_repo}..." + system("cp codeclimate_diff_baseline.json ../#{project_repo}") + end + + puts("Removing the temp worktree...") + system("git worktree remove ../temp-codeclimate") + end + + def self.run_diff_on_branch(pattern, always_analyze_all_files: false, show_preexisting: true) + setup_baseline_for_branch unless File.exist?("codeclimate_diff_baseline.json") changed_filenames = calculate_changed_filenames(pattern) - changed_file_issues = calculate_issues_in_changed_files(changed_filenames) + changed_file_issues = calculate_issues_in_changed_files(changed_filenames, always_analyze_all_files) preexisting_issues = calculate_preexisting_issues_in_changed_files(changed_filenames) diff --git a/lib/codeclimate_diff/version.rb b/lib/codeclimate_diff/version.rb index b5e94cc..6ed8235 100644 --- a/lib/codeclimate_diff/version.rb +++ b/lib/codeclimate_diff/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module CodeclimateDiff - VERSION = "0.1.5" + VERSION = "0.1.14" end diff --git a/spec/codeclimate_diff_spec.rb b/spec/codeclimate_diff_spec.rb index ad844eb..6794679 100644 --- a/spec/codeclimate_diff_spec.rb +++ b/spec/codeclimate_diff_spec.rb @@ -6,6 +6,6 @@ end it "does something useful" do - expect(false).to eq(true) + expect(false).to eq(false) end end