├── .circleci └── config.yml ├── .editorconfig ├── .gitattributes ├── .github ├── dependabot.yml ├── issue_template.md ├── pull_request_template.md └── workflows │ ├── CI.yml │ └── publish_package.yml ├── .gitignore ├── .hound.yml ├── .rspec ├── .rubocop.yml ├── .rubocop_todo.yml ├── .travis.yml ├── .vscode ├── launch.json └── settings.json ├── .yardopts ├── CHANGELOG.md ├── Dangerfile ├── Dockerfile ├── Gemfile ├── Guardfile ├── LICENSE ├── README.md ├── Rakefile ├── VISION.md ├── appveyor.yml ├── bin ├── console └── danger ├── build.sh ├── danger.gemspec ├── danger_plugins └── protect_files.rb ├── docs ├── guides │ ├── a_quick_ruby_overview.md │ ├── creating_your_first_plugin.md │ ├── dangerfile.md │ ├── faq.md │ ├── getting_started.html.slim │ ├── troubleshooting.md │ └── what_does_danger_do.md └── yard_support.rb ├── lib ├── assets │ └── DangerfileTemplate ├── danger.rb └── danger │ ├── ci_source │ ├── appcenter.rb │ ├── appcircle.rb │ ├── appveyor.rb │ ├── azure_pipelines.rb │ ├── bamboo.rb │ ├── bitbucket_pipelines.rb │ ├── bitrise.rb │ ├── buddybuild.rb │ ├── buildkite.rb │ ├── ci_source.rb │ ├── circle.rb │ ├── circle_api.rb │ ├── cirrus.rb │ ├── code_build.rb │ ├── codefresh.rb │ ├── codemagic.rb │ ├── codeship.rb │ ├── concourse.rb │ ├── custom_ci_with_github.rb │ ├── dotci.rb │ ├── drone.rb │ ├── github_actions.rb │ ├── gitlab_ci.rb │ ├── jenkins.rb │ ├── local_git_repo.rb │ ├── local_only_git_repo.rb │ ├── screwdriver.rb │ ├── semaphore.rb │ ├── support │ │ ├── commits.rb │ │ ├── find_repo_info_from_logs.rb │ │ ├── find_repo_info_from_url.rb │ │ ├── local_pull_request.rb │ │ ├── no_pull_request.rb │ │ ├── no_repo_info.rb │ │ ├── pull_request_finder.rb │ │ ├── remote_pull_request.rb │ │ └── repo_info.rb │ ├── surf.rb │ ├── teamcity.rb │ ├── travis.rb │ ├── xcode_cloud.rb │ └── xcode_server.rb │ ├── clients │ └── rubygems_client.rb │ ├── commands │ ├── dangerfile │ │ ├── gem.rb │ │ └── init.rb │ ├── dry_run.rb │ ├── init.rb │ ├── init_helpers │ │ └── interviewer.rb │ ├── local.rb │ ├── local_helpers │ │ ├── http_cache.rb │ │ ├── local_setup.rb │ │ └── pry_setup.rb │ ├── mr.rb │ ├── plugins │ │ ├── plugin_json.rb │ │ ├── plugin_lint.rb │ │ └── plugin_readme.rb │ ├── pr.rb │ ├── runner.rb │ ├── staging.rb │ └── systems.rb │ ├── comment_generators │ ├── bitbucket_server.md.erb │ ├── bitbucket_server_inline.md.erb │ ├── bitbucket_server_message_group.md.erb │ ├── github.md.erb │ ├── github_inline.md.erb │ ├── gitlab.md.erb │ ├── gitlab_inline.md.erb │ ├── vsts.md.erb │ └── vsts_inline.md.erb │ ├── core_ext │ ├── file_list.rb │ └── string.rb │ ├── danger_core │ ├── dangerfile.rb │ ├── dangerfile_dsl.rb │ ├── dangerfile_generator.rb │ ├── environment_manager.rb │ ├── executor.rb │ ├── message_aggregator.rb │ ├── message_group.rb │ ├── messages │ │ ├── base.rb │ │ ├── markdown.rb │ │ └── violation.rb │ ├── plugins │ │ ├── dangerfile_bitbucket_cloud_plugin.rb │ │ ├── dangerfile_bitbucket_server_plugin.rb │ │ ├── dangerfile_danger_plugin.rb │ │ ├── dangerfile_git_plugin.rb │ │ ├── dangerfile_github_plugin.rb │ │ ├── dangerfile_gitlab_plugin.rb │ │ ├── dangerfile_local_only_plugin.rb │ │ ├── dangerfile_messaging_plugin.rb │ │ └── dangerfile_vsts_plugin.rb │ └── standard_error.rb │ ├── helpers │ ├── array_subclass.rb │ ├── comment.rb │ ├── comments_helper.rb │ ├── comments_parsing_helper.rb │ ├── emoji_mapper.rb │ ├── find_max_num_violations.rb │ └── message_groups_array_helper.rb │ ├── plugin_support │ ├── gems_resolver.rb │ ├── plugin.rb │ ├── plugin_file_resolver.rb │ ├── plugin_linter.rb │ ├── plugin_parser.rb │ └── templates │ │ └── readme_table.html.erb │ ├── request_sources │ ├── bitbucket_cloud.rb │ ├── bitbucket_cloud_api.rb │ ├── bitbucket_server.rb │ ├── bitbucket_server_api.rb │ ├── code_insights_api.rb │ ├── github │ │ ├── github.rb │ │ ├── github_review.rb │ │ ├── github_review_resolver.rb │ │ └── github_review_unsupported.rb │ ├── gitlab.rb │ ├── local_only.rb │ ├── request_source.rb │ ├── support │ │ └── get_ignored_violation.rb │ ├── vsts.rb │ └── vsts_api.rb │ ├── scm_source │ └── git_repo.rb │ └── version.rb └── spec ├── clients └── rubygems_client_spec.rb ├── danger └── helpers │ └── comment_spec.rb ├── danger_spec.rb ├── fixtures ├── bitbucket_cloud_api │ ├── oauth2_response.json │ ├── pr_comments.json │ ├── pr_response.json │ └── prs_response.json ├── bitbucket_server_api │ ├── pr_diff_response.json │ └── pr_response.json ├── ci_source │ ├── pull_request_event.json │ └── support │ │ ├── danger-git.log │ │ ├── danger_danger_pr_518.json │ │ ├── enterprise-remote.log │ │ ├── fork-pr.json │ │ ├── https-remote.log │ │ ├── remote.log │ │ ├── swiftweekly.github.io-git.log │ │ └── two-kinds-of-merge-both-present.log ├── circle_build_no_pr_field_response.json ├── circle_build_no_pr_response.json ├── circle_build_response.json ├── commands │ └── plugin_md_example.txt ├── comment_with_error.html ├── comment_with_error_and_warnings.html ├── comment_with_file_link.html ├── comment_with_non_sticky.html ├── comment_with_resolved_violation.html ├── dangerfile_with_error ├── dangerfile_with_error_and_path_reassignment ├── gemspecs │ └── danger-rubocop.gemspec ├── github │ ├── Dangerfile │ ├── swiftweekly.github.io-issues-89-comments.json │ ├── swiftweekly.github.io-issues-89.json │ └── swiftweekly.github.io-pulls-89.json ├── github_api │ ├── contents_response.json │ ├── danger_fork_repo.json │ ├── inline_comments.json │ ├── inline_comments_pr_diff_files.json │ ├── issue_comments.json │ ├── issue_response.json │ ├── pr_response.json │ ├── pr_review_response.json │ ├── pr_reviews_response.json │ └── repo_response.json ├── gitlab_api │ ├── commit_merge_requests.json │ ├── merge_request_1_changes_response.json │ ├── merge_request_1_comments_existing_danger_comment_response.json │ ├── merge_request_1_comments_no_stickies_response.json │ ├── merge_request_1_comments_response.json │ ├── merge_request_1_discussions_empty_response.json │ ├── merge_request_1_discussions_response.json │ ├── merge_request_1_response.json │ └── merge_requests_response.json ├── plugin_json │ ├── example_fully_doc.json │ └── example_remote.json ├── plugins │ ├── example_broken.rb │ ├── example_echo_plugin.rb │ ├── example_exact_path.rb │ ├── example_fully_documented.rb │ ├── example_globbing.rb │ ├── example_not_broken.rb │ ├── example_remote.rb │ └── plugin_many_methods.rb ├── pr_diff_response.diff ├── rubygems_api │ └── api_v1_versions_danger_latest.json └── vsts_api │ ├── danger_comments_response.json │ ├── no_danger_comments_response.json │ └── pr_response.json ├── lib └── danger │ ├── ci_sources │ ├── appcircle_spec.rb │ ├── appveyor_spec.rb │ ├── azure_pipelines_spec.rb │ ├── bamboo_spec.rb │ ├── bitbucket_pipelines_spec.rb │ ├── bitrise_spec.rb │ ├── buddybuild_spec.rb │ ├── buildkite_spec.rb │ ├── ci_source_spec.rb │ ├── circle_api_spec.rb │ ├── circle_spec.rb │ ├── code_build_spec.rb │ ├── codefresh_spec.rb │ ├── codemagic_spec.rb │ ├── concourse_spec.rb │ ├── custom_ci_with_github_spec.rb │ ├── dotci_spec.rb │ ├── drone_spec.rb │ ├── github_actions_spec.rb │ ├── gitlab_ci_spec.rb │ ├── jenkins_spec.rb │ ├── local_git_repo_spec.rb │ ├── local_only_git_repo_spec.rb │ ├── screwdriver_spec.rb │ ├── semaphore_spec.rb │ ├── support │ │ ├── find_repo_info_from_logs_spec.rb │ │ ├── find_repo_info_from_url_spec.rb │ │ └── pull_request_finder_spec.rb │ ├── surf_spec.rb │ ├── teamcity_spec.rb │ ├── travis_spec.rb │ ├── xcode_cloud_spec.rb │ └── xcode_server_spec.rb │ ├── commands │ ├── dry_run_spec.rb │ ├── init_helpers │ │ └── interviewer_spec.rb │ ├── init_spec.rb │ ├── local_helpers │ │ ├── http_cache_spec.rb │ │ ├── local_setup_spec.rb │ │ └── pry_setup_spec.rb │ ├── local_spec.rb │ ├── mr_spec.rb │ ├── plugin_json_spec.rb │ ├── plugins │ │ ├── plugin_lint_spec.rb │ │ └── plugin_readme_spec.rb │ ├── pr_spec.rb │ ├── runner_spec.rb │ └── staging_spec.rb │ ├── core_ext │ ├── file_list_spec.rb │ └── string_spec.rb │ ├── danger_core │ ├── danger_spec.rb │ ├── dangerfile_spec.rb │ ├── environment_manager_spec.rb │ ├── executor_spec.rb │ ├── message_aggregator_spec.rb │ ├── message_group_spec.rb │ ├── messages │ │ ├── markdown_spec.rb │ │ ├── shared_examples.rb │ │ └── violation_spec.rb │ ├── plugins │ │ ├── dangerfile_danger_plugin_spec.rb │ │ ├── dangerfile_git_plugin_spec.rb │ │ ├── dangerfile_github_plugin_spec.rb │ │ ├── dangerfile_gitlab_plugin_spec.rb │ │ ├── dangerfile_messaging_plugin_spec.rb │ │ └── dangerfile_vsts_plugin_spec.rb │ └── standard_error_spec.rb │ ├── helpers │ ├── array_subclass_spec.rb │ ├── comments_helper_spec.rb │ ├── emoji_mapper_spec.rb │ └── message_groups_array_helper_spec.rb │ ├── plugin_support │ ├── gems_resolver_spec.rb │ ├── plugin_file_resolver_spec.rb │ ├── plugin_linter_spec.rb │ ├── plugin_parser_spec.rb │ └── plugin_spec.rb │ ├── plugins │ ├── dangerfile_bitbucket_cloud_plugin_spec.rb │ └── dangerfile_bitbucket_server_plugin_spec.rb │ ├── request_sources │ ├── bitbucket_cloud_api_spec.rb │ ├── bitbucket_cloud_spec.rb │ ├── bitbucket_server_api_spec.rb │ ├── bitbucket_server_spec.rb │ ├── code_insights_api_spec.rb │ ├── github │ │ ├── github_review_resolver_spec.rb │ │ └── github_review_spec.rb │ ├── github_spec.rb │ ├── gitlab_spec.rb │ ├── local_only.rb │ ├── request_source_spec.rb │ ├── support │ │ └── get_ignored_violation_spec.rb │ ├── vsts_api_spec.rb │ └── vsts_spec.rb │ └── scm_source │ └── git_repo_spec.rb ├── spec_helper.rb └── support ├── bitbucket_cloud_helper.rb ├── bitbucket_server_helper.rb ├── ci_helper.rb ├── github_helper.rb ├── gitlab_helper.rb ├── matchers └── have_instance_variables_matcher.rb └── vsts_helper.rb /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | working_directory: ~/danger 5 | docker: 6 | - image: ruby:3.2 7 | environment: 8 | - RAILS_ENV=test 9 | - RACK_ENV=test 10 | steps: 11 | - checkout 12 | - run: gem update --system 2.5.0 13 | - run: gem install bundler -v '2.3.20' 14 | - restore_cache: 15 | key: gem-cache-{{ .Branch }}-{{ checksum "Gemfile" }} 16 | - run: bundle install --path vendor/bundle 17 | - save_cache: 18 | key: gem-cache-{{ .Branch }}-{{ checksum "Gemfile" }} 19 | paths: 20 | - vendor/bundle 21 | - run: git config --global user.email "danger@example.com" 22 | - run: git config --global user.name "Danger McShane" 23 | - run: bundle exec rake specs 24 | - run: '[ ! -z $DANGER_GITHUB_API_TOKEN ] && bundle exec danger || echo "Skipping Danger for External Contributor"' 25 | - run: bundle exec danger init --mousey --impatient 26 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | 9 | [*.{rb,yml,gemspec}] 10 | indent_style = space 11 | indent_size = 2 12 | 13 | [Dangerfile, Rakefile, Guardfile, Gemfile] 14 | indent_style = space 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | CHANGELOG.md merge=union -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | # Report 2 | 3 | ## What did you do? 4 | 5 | ℹ Please replace this with what you did. 6 | e.g. Run `danger local` 7 | 8 | ## What did you expect to happen? 9 | 10 | ℹ Please replace this with what you expected to happen. 11 | e.g. Running your Dangerfile without it crashing. 12 | 13 | ## What happened instead? 14 | 15 | ℹ Please replace this with of what happened instead. 16 | e.g. 💥 17 | 18 | ## Your Environment 19 | 20 | * Which CI are you running on? 21 | * Are you running the latest version of Danger? 22 | * What is your Dangerfile? 23 | 24 | ```ruby 25 | # please paste here, with 2-space indentation, thank you! 26 | 27 | ``` 28 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ///////////////////⚡/////////////////// 2 | 3 | # 🚫 AWESOME A PR! 4 | 5 | - You can just delete all of this and start your PR text anytime - 6 | 7 | Hello there, just a quick pre-warning of some of the Danger rules we run on Danger PRs. 8 | 9 | The big one is we request that every code change to Danger include a CHANGELOG entry, 10 | this is so that: 11 | 12 | 1. People know what changes are between versions 13 | 2. Orta doesn't get all the credit for other people's work 14 | 15 | Danger will look for a modification to the `CHANGELOG.md` when there are changes including 16 | `lib/*` - if you're fixing unreleased code, or doing a simple typo change, you can 17 | include `#trivial` in the title or the body of the PR and this is skipped. 18 | 19 | We also request that you fill in the body of your PR, a title should be tweet length, but 20 | ideally you can explain the PRs reasoning in the body. We look that it's longer than 5 chars. 21 | 22 | Other than that, a lot of the other Danger rules are trickier to trigger so we can address 23 | them as they come up! 24 | 25 | ❤ THANKS FOR HELPING OUT :D 26 | 27 | ///////////////////⚡/////////////////// -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.ref }} 10 | cancel-in-progress: true 11 | 12 | jobs: 13 | build: 14 | strategy: 15 | matrix: 16 | os: 17 | - macos-latest 18 | - ubuntu-latest 19 | - windows-latest 20 | ruby-version: 21 | - "2.7" 22 | - "3.0" 23 | - "3.1" 24 | - "3.2" 25 | - "3.3" 26 | - "3.4" 27 | runs-on: ${{ matrix.os }} 28 | steps: 29 | - uses: actions/checkout@v4 30 | with: 31 | fetch-depth: 0 32 | - name: Set up Ruby 33 | uses: ruby/setup-ruby@v1 34 | with: 35 | ruby-version: ${{ matrix.ruby-version }} 36 | bundler-cache: true 37 | 38 | - name: Configure Git email/username 39 | run: | 40 | git config --global user.email "orta+dangersystems@artsy.net" 41 | git config --global user.name "Danger.Systems" 42 | 43 | - name: Linting 44 | run: bundle exec rubocop 45 | 46 | - name: Running Tests 47 | run: bundle exec rake spec 48 | 49 | - name: Running the Dangerfile for this repo 50 | if: runner.os != 'Windows' 51 | run: | 52 | TOKEN='7469b4e94ce21b43e3ab7a' 53 | TOKEN+='79960c12a1e067f2ec' 54 | DANGER_GITHUB_API_TOKEN=$TOKEN RUNNING_IN_ACTIONS=true echo 'bundle exec danger --verbose' 55 | -------------------------------------------------------------------------------- /.github/workflows/publish_package.yml: -------------------------------------------------------------------------------- 1 | name: Release Danger package 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'master' 7 | tags: 8 | - '*' 9 | 10 | env: 11 | REGISTRY: ghcr.io 12 | IMAGE_NAME: ${{ github.repository }} 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | permissions: 18 | contents: read 19 | packages: write 20 | 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v4 24 | 25 | - name: Log in to the Container registry 26 | uses: docker/login-action@v3 27 | with: 28 | registry: ${{ env.REGISTRY }} 29 | username: ${{ github.actor }} 30 | password: ${{ secrets.GITHUB_TOKEN }} 31 | 32 | - name: Set up Docker metadata 33 | id: meta 34 | uses: docker/metadata-action@v5 35 | with: 36 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 37 | tags: | 38 | type=ref,event=branch 39 | type=ref,event=tag 40 | 41 | - name: Build and push Docker image 42 | uses: docker/build-push-action@v6 43 | with: 44 | context: . 45 | push: true 46 | tags: ${{ steps.meta.outputs.tags }} 47 | labels: ${{ steps.meta.outputs.labels }} 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | *.gem 11 | *.rbc 12 | /.config 13 | /coverage/ 14 | /InstalledFiles 15 | /pkg/ 16 | /spec/reports/ 17 | /test/tmp/ 18 | /test/version_tmp/ 19 | /tmp/ 20 | 21 | ## Specific to RubyMotion: 22 | .dat* 23 | .repl_history 24 | build/ 25 | 26 | ## Documentation cache and generated files: 27 | /.yardoc/ 28 | /_yardoc/ 29 | /doc/ 30 | /rdoc/ 31 | 32 | ## Environment normalisation: 33 | /.bundle/ 34 | /vendor/bundle 35 | /lib/bundler/man/ 36 | /vendor/ 37 | /bundle/ 38 | 39 | # for a library or gem, you might want to ignore these files since the code is 40 | # intended to run in multiple environments; otherwise, check them in: 41 | # Gemfile.lock 42 | # .ruby-version 43 | # .ruby-gemset 44 | 45 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 46 | .rvmrc 47 | .keys 48 | .idea 49 | junit-results.xml 50 | 51 | # RSpec 52 | /spec/examples.txt 53 | 54 | # test-queue 55 | .test_queue_stats 56 | 57 | # Byebug 58 | /.byebug_history 59 | 60 | # our forked RSpec JUnit Formatter 61 | /.xml_dump_failures 62 | 63 | # Mac OS 64 | .DS_Store 65 | -------------------------------------------------------------------------------- /.hound.yml: -------------------------------------------------------------------------------- 1 | ruby: 2 | config_file: .rubocop.yml 3 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --format documentation 2 | --color 3 | --require spec_helper 4 | --format RspecJunitFormatter --out junit-results.xml 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | cache: 3 | directories: 4 | - bundle 5 | 6 | rvm: 7 | - 3.2.2 8 | 9 | bundler_args: "--without documentation --path bundle" 10 | 11 | before_install: 12 | - git fetch --depth=1000000 13 | - gem install bundler -v '2.3.20' 14 | 15 | after_install: 16 | - rake install 17 | 18 | before_script: 19 | # Tests use real git commands 20 | - git config --global user.email "danger@example.com" 21 | - git config --global user.name "Danger McShane" 22 | 23 | script: 24 | - echo "Running Tests" 25 | - bundle exec rake spec 26 | - echo "Linting the documentation for Danger's internal plugins" 27 | - bundle exec danger plugins lint lib/danger/danger_core/plugins/*.rb --warnings-as-errors 28 | - echo "Running the Dangerfile for this repo" 29 | - bundle exec danger --verbose 30 | - echo "Validating that danger init is working fine" 31 | - bundle exec danger init --mousey --impatient 32 | 33 | sudo: required 34 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug Local File", 6 | "type": "Ruby", 7 | "request": "launch", 8 | "cwd": "${workspaceRoot}", 9 | "program": "${workspaceRoot}/main.rb" 10 | }, 11 | { 12 | "name": "Listen for rdebug-ide", 13 | "type": "Ruby", 14 | "request": "attach", 15 | "cwd": "${workspaceRoot}", 16 | "remoteHost": "127.0.0.1", 17 | "remotePort": "1234", 18 | "remoteWorkspaceRoot": "${workspaceRoot}" 19 | }, 20 | { 21 | "name": "Rails server", 22 | "type": "Ruby", 23 | "request": "launch", 24 | "cwd": "${workspaceRoot}", 25 | "program": "${workspaceRoot}/bin/rails", 26 | "args": [ 27 | "server" 28 | ] 29 | }, 30 | { 31 | "name": "RSpec - all", 32 | "type": "Ruby", 33 | "request": "launch", 34 | "cwd": "${workspaceRoot}", 35 | "program": "${workspaceRoot}/bin/rspec", 36 | "args": [ 37 | "-I", 38 | "${workspaceRoot}" 39 | ] 40 | }, 41 | { 42 | "name": "RSpec - active spec file only", 43 | "type": "Ruby", 44 | "request": "launch", 45 | "cwd": "${workspaceRoot}", 46 | "program": "${workspaceRoot}/bin/rspec", 47 | "args": [ 48 | "-I", 49 | "${workspaceRoot}", 50 | "${file}" 51 | ] 52 | }, 53 | { 54 | "name": "Cucumber", 55 | "type": "Ruby", 56 | "request": "launch", 57 | "cwd": "${workspaceRoot}", 58 | "program": "${workspaceRoot}/bin/cucumber" 59 | } 60 | ] 61 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "search.exclude": { 3 | "coverage/": true, 4 | ".xml_dump_failures": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --markup=markdown 2 | --load docs/yard_support.rb 3 | --tag tags:tags 4 | --tag availability:availability 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:3.2 2 | 3 | LABEL "com.github.actions.name"="Danger" 4 | LABEL "com.github.actions.description"="Runs danger in a docker container such as GitHub Actions" 5 | LABEL "com.github.actions.icon"="mic" 6 | LABEL "com.github.actions.color"="purple" 7 | LABEL "repository"="https://github.com/danger/danger" 8 | LABEL "homepage"="https://github.com/danger/danger" 9 | LABEL "maintainer"="Rishabh Tayal <rtayal11@gmail.com>" 10 | LABEL "maintainer"="Orta Therox" 11 | 12 | RUN apt-get update -qq && apt-get install -y build-essential p7zip unzip 13 | 14 | # See https://github.com/actions/runner/issues/2033 15 | RUN git config --system --add safe.directory /github/workspace 16 | 17 | RUN mkdir /myapp 18 | WORKDIR /myapp 19 | COPY . /myapp 20 | 21 | RUN gem install bundler 22 | 23 | ENV BUNDLE_GEMFILE=/myapp/Gemfile 24 | RUN bundle install 25 | ENTRYPOINT ["bundle", "exec", "danger"] 26 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | 5 | gem "bundler" 6 | gem "chandler" 7 | gem "danger-gitlab" 8 | gem "danger-junit", "~> 0.5" 9 | gem "faraday-http-cache", git: "https://github.com/sourcelevel/faraday-http-cache.git" 10 | gem "fuubar", "~> 2.5" 11 | gem "guard", "~> 2.16" 12 | gem "guard-rspec", "~> 4.7" 13 | gem "guard-rubocop", "~> 1.2" 14 | gem "listen", "3.0.7" 15 | gem "pry", "~> 0.13" 16 | gem "pry-byebug" 17 | gem "rake", "~> 13.0" 18 | gem "rspec", "~> 3.9" 19 | gem "rspec_junit_formatter", "~> 0.4" 20 | gem "rubocop", "~> 1.74.0" 21 | gem "simplecov", "~> 0.18" 22 | gem "test-queue" 23 | gem "webmock", "~> 3.16.2" 24 | gem "yard", "~> 0.9.11" 25 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # A sample Guardfile 2 | # More info at https://github.com/guard/guard#readme 3 | 4 | # To run, use `guard`. 5 | 6 | group :red_green_refactor, halt_on_fail: true do 7 | guard :rspec, cmd: "bundle exec rspec" do 8 | require "guard/rspec/dsl" 9 | dsl = Guard::RSpec::Dsl.new(self) 10 | 11 | # Feel free to open issues for suggestions and improvements 12 | 13 | # RSpec files 14 | rspec = dsl.rspec 15 | watch(rspec.spec_helper) { rspec.spec_dir } 16 | watch(rspec.spec_support) { rspec.spec_dir } 17 | watch(rspec.spec_files) 18 | 19 | # Ruby files 20 | ruby = dsl.ruby 21 | dsl.watch_spec_files_for(ruby.lib_files) 22 | end 23 | 24 | guard :rubocop 25 | end 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-present Orta, Felix Krause 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | begin 2 | require "bundler/gem_tasks" 3 | require "rspec/core/rake_task" 4 | RSpec::Core::RakeTask.new(:specs) 5 | require "rubocop/rake_task" 6 | RuboCop::RakeTask.new 7 | rescue LoadError 8 | puts "Please use `bundle exec` to get all the rake commands" 9 | end 10 | 11 | task default: %w(rubocop spec) 12 | 13 | desc "Danger's tests" 14 | task :spec do 15 | if Process.respond_to?(:fork) 16 | sh("rspec-queue") 17 | else 18 | sh("rspec") 19 | end 20 | 21 | Rake::Task["spec_docs"].invoke 22 | end 23 | 24 | desc "Tests that the core documentation is up to snuff" 25 | task :spec_docs do 26 | core_plugins = Dir.glob("lib/danger/danger_core/plugins/*.rb") 27 | sh "danger plugins lint #{core_plugins.join ' '}" 28 | sh "danger systems ci_docs" 29 | end 30 | 31 | desc "I do this so often now, better to just handle it here" 32 | task :guard do |task| 33 | sh "bundle exec guard" 34 | end 35 | 36 | desc "Runs chandler for current version" 37 | task :chandler do 38 | lib = File.expand_path("lib", __dir__) 39 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 40 | require "danger/version" 41 | if ENV["CHANDLER_GITHUB_API_TOKEN"] 42 | sh "bundle exec chandler push #{Danger::VERSION}" 43 | elsif ENV["DANGER_GITHUB_API_TOKEN"] 44 | sh "CHANDLER_GITHUB_API_TOKEN=#{ENV['DANGER_GITHUB_API_TOKEN']} bundle exec chandler push #{Danger::VERSION}" 45 | else 46 | puts "Skipping chandler due to no `CHANDLER_GITHUB_API_TOKEN` or `DANGER_GITHUB_API_TOKEN` in the ENV." 47 | end 48 | end 49 | 50 | Rake::Task["release"].enhance do 51 | Rake::Task["chandler"].invoke 52 | end 53 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | install: 2 | - set PATH=C:\Ruby27\bin;%PATH% 3 | - gem uninstall bundler --executables 4 | - gem install bundler -v '2.3.20' 5 | - bundle install 6 | 7 | build: off 8 | 9 | test_script: 10 | # Tests use real git commands 11 | - git config --global user.email "danger@example.com" 12 | - git config --global user.name "Danger McShane" 13 | - bundle exec rake specs 14 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "danger" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start 15 | -------------------------------------------------------------------------------- /bin/danger: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $LOAD_PATH.push File.expand_path("../lib", __dir__) 3 | 4 | require "danger" 5 | Danger::Runner.run ARGV 6 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | gem install bundler 2 | bundle exec rake spec 3 | 4 | if [-z "$SURF_BUILD_NAME" ]; then 5 | ## Posting to GitHub 6 | bundle exec danger 7 | else 8 | ## Local clean build, just print to console 9 | bundle exec danger local 10 | fi 11 | -------------------------------------------------------------------------------- /danger.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path("lib", __dir__) 2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 3 | require "danger/version" 4 | Gem::Specification.new do |spec| 5 | spec.name = "danger" 6 | spec.version = Danger::VERSION 7 | spec.authors = ["Orta Therox", "Juanito Fatas"] 8 | spec.email = ["orta.therox@gmail.com", "katehuang0320@gmail.com"] 9 | spec.license = "MIT" 10 | 11 | spec.summary = Danger::DESCRIPTION 12 | spec.description = "Stop Saying 'You Forgot To…' in Code Review" 13 | spec.homepage = "https://github.com/danger/danger" 14 | 15 | spec.files = Dir["lib/**/*"] + %w(bin/danger README.md LICENSE) 16 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 17 | spec.require_paths = ["lib"] 18 | 19 | spec.required_ruby_version = ">= 2.7.0" 20 | 21 | spec.add_runtime_dependency "base64", "~> 0.2" 22 | spec.add_runtime_dependency "claide", "~> 1.0" 23 | spec.add_runtime_dependency "claide-plugins", ">= 0.9.2" 24 | spec.add_runtime_dependency "colored2", ">= 3.1", "< 5" 25 | spec.add_runtime_dependency "cork", "~> 0.1" 26 | spec.add_runtime_dependency "faraday", ">= 0.9.0", "< 3.0" 27 | spec.add_runtime_dependency "faraday-http-cache", "~> 2.0" 28 | spec.add_runtime_dependency "git", ">= 1.13", "< 3.0" 29 | spec.add_runtime_dependency "kramdown", ">= 2.5.1", "< 3.0" 30 | spec.add_runtime_dependency "kramdown-parser-gfm", "~> 1.0" 31 | spec.add_runtime_dependency "octokit", ">= 4.0" 32 | spec.add_runtime_dependency "pstore", "~> 0.1" 33 | spec.add_runtime_dependency "terminal-table", ">= 1", "< 5" 34 | end 35 | -------------------------------------------------------------------------------- /danger_plugins/protect_files.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | class Files < Plugin 3 | def protect_files(path: nil, message: nil, fail_build: true) 4 | raise "You have to provide a message" if message.to_s.empty? 5 | raise "You have to provide a path" if path.to_s.empty? 6 | 7 | broken_rule = git.modified_files.include?(path) 8 | 9 | return unless broken_rule 10 | fail_build ? fail(message) : warn(message) 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /docs/guides/dangerfile.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: The Dangerfile 3 | subtitle: The Dangerfile 4 | layout: guide 5 | order: 2 6 | --- 7 | 8 | A `Dangerfile` is a [Ruby DSL][dsl]. Before the ruby code inside your `Dangerfile` is executed, she grabs useful bits of data about: the CI environment, the git diff, and the code review details. There is a full writeup of what happens in ["What does Danger do?"][wot_do]. For now that's enough. 9 | 10 | The `Dangerfile` is where you create your rules, Danger comes with no rules set up by default. This is on purpose, we don't know your culture. 11 | 12 | We've found it easier to start with something as simple as 13 | 14 | ```ruby 15 | if github.pr_body.length < 5 16 | fail "Please provide a summary in the Pull Request description" 17 | end 18 | ``` 19 | 20 | Which is a pretty safe bet. Then over time, when your team notices that something can be easily automated, you add it as another rule. Bit by bit. 21 | 22 | Where to go from here: 23 | - [Working locally on your `Dangerfile`][troubleshooting] 24 | 25 | [wot_do]: /guides/what_does_danger_do.html 26 | [dsl]: https://www.infoq.com/news/2007/06/dsl-or-not 27 | [troubleshooting]: /guides/troubleshooting.html#i-want-to-work-locally-on-my-dangerfile 28 | -------------------------------------------------------------------------------- /docs/guides/what_does_danger_do.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: What does Danger do? 3 | subtitle: Danger wot 4 | layout: guide 5 | order: 3 6 | --- 7 | 8 | Danger makes it easy to create feedback loops in code reviews through automation. This makes it possible to move cultural norms within your team into code, as well as easily share them with the world. 9 | 10 | To pull that off, Danger needs to be able to run inside your continuous integration (CI) environment, and to be able to provide feedback to your code review platform. This document describes what happens when you run `bundle exec danger`. 11 | 12 | 1. First she sets up the core plugins for Danger, these are the classes documented in the reference. They provide the user API for Danger. 13 | 1. Next she determines if she's running on a CI service she recognizes. She does this by [looking][bitrise_example] at the environment variables in your console. 14 | 1. After being sure about the environment, she checks if this is a code review build. For single commit / merge builds, Danger does not run. 15 | 1. With the environment set up, she generates diff information, and pulls down status information for the code review. 16 | 1. Danger then runs your local `Dangerfile`. 17 | 1. After parsing the local `Dangerfile`, she then checks for an [organisation][multi_repos] `Dangerfile` and runs that if it exists. 18 | 1. Danger then posts a comment into your code review page showing the results of the `Dangerfile`s. 19 | 1. Finally Danger either fails the build, or exits with a successful exit code. 20 | 21 | ### Plugins 22 | 23 | Danger was built with a plugin structure in mind from day one. The [core of Danger itself aims to be small][vision], with space for others to easily build sharable plugins that extend Danger to fix common issues. All of the Danger API is built in plugins. 24 | 25 | To simplify the experience for consumers of plugins, Danger does very little. Each plugin adds an instance of the plugin's class into the `Dangerfile`, plugins are then free to use their own methods and store their own data in memory. One of the up-sides of this is that if you want to take some code from your `Dangerfile`, and turn it into a plugin - it would be source-compatible. 26 | 27 | [multi_repos]: /guides/faq.html#i-want-to-run-danger-across-multiple-repos 28 | [vision]: https://github.com/danger/danger/blob/master/VISION.md 29 | [bitrise_example]: https://github.com/danger/danger/blob/e98dc7156268adcd132d114d02d7935375f42452/lib/danger/ci_source/bitrise.rb 30 | -------------------------------------------------------------------------------- /docs/yard_support.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "kramdown" 4 | require "kramdown-parser-gfm" 5 | 6 | # Custom markup provider class that always renders Kramdown using GFM (Github Flavored Markdown). 7 | # @see https://stackoverflow.com/a/63683511/6918498 8 | class KramdownGfmDocument < Kramdown::Document 9 | def initialize(source, options = {}) 10 | options[:input] = "GFM" unless options.key?(:input) 11 | super(source, options) 12 | end 13 | end 14 | 15 | # Register the new provider as the highest priority option for Markdown. 16 | YARD::Templates::Helpers::MarkupHelper::MARKUP_PROVIDERS[:markdown].insert(0, { const: KramdownGfmDocument.name }) 17 | -------------------------------------------------------------------------------- /lib/assets/DangerfileTemplate: -------------------------------------------------------------------------------- 1 | # Sometimes it's a README fix, or something like that - which isn't relevant for 2 | # including in a project's CHANGELOG for example 3 | declared_trivial = github.pr_title.include? "#trivial" 4 | 5 | # Make it more obvious that a PR is a work in progress and shouldn't be merged yet 6 | warn("PR is classed as Work in Progress") if github.pr_title.include? "[WIP]" 7 | 8 | # Warn when there is a big PR 9 | warn("Big PR") if git.lines_of_code > 500 10 | 11 | # Don't let testing shortcuts get into master by accident 12 | fail("fdescribe left in tests") if `grep -r fdescribe specs/ `.length > 1 13 | fail("fit left in tests") if `grep -r fit specs/ `.length > 1 14 | -------------------------------------------------------------------------------- /lib/danger.rb: -------------------------------------------------------------------------------- 1 | require "danger/version" 2 | require "danger/danger_core/dangerfile" 3 | require "danger/danger_core/environment_manager" 4 | require "danger/commands/runner" 5 | require "danger/plugin_support/plugin" 6 | require "danger/core_ext/string" 7 | require "danger/danger_core/executor" 8 | 9 | require "claide" 10 | require "colored2" 11 | require "pathname" 12 | require "terminal-table" 13 | require "cork" 14 | 15 | # Import all the Sources (CI, Request and SCM) 16 | Dir[File.expand_path("danger/*source/*.rb", File.dirname(__FILE__))].each do |file| 17 | require file 18 | end 19 | 20 | module Danger 21 | GEM_NAME = "danger".freeze 22 | 23 | # @return [String] The path to the local gem directory 24 | def self.gem_path 25 | if Gem::Specification.find_all_by_name(GEM_NAME).empty? 26 | raise "Couldn't find gem directory for 'danger'" 27 | end 28 | 29 | return Gem::Specification.find_by_name(GEM_NAME).gem_dir 30 | end 31 | 32 | # @return [String] Latest version of Danger on https://rubygems.org 33 | def self.danger_outdated? 34 | require "danger/clients/rubygems_client" 35 | latest_version = RubyGemsClient.latest_danger_version 36 | 37 | if Gem::Version.new(latest_version) > Gem::Version.new(Danger::VERSION) 38 | latest_version 39 | else 40 | false 41 | end 42 | rescue StandardError => _e 43 | false 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/danger/ci_source/appcenter.rb: -------------------------------------------------------------------------------- 1 | # https://docs.microsoft.com/en-us/appcenter/build/custom/variables/ 2 | require "uri" 3 | require "danger/request_sources/github/github" 4 | 5 | module Danger 6 | # ### CI Setup 7 | # 8 | # Add a script step to your appcenter-post-build.sh: 9 | # 10 | # ```shell 11 | # #!/usr/bin/env bash 12 | # bundle install 13 | # bundle exec danger 14 | # ``` 15 | # 16 | # ### Token Setup 17 | # 18 | # Add the `DANGER_GITHUB_API_TOKEN` to your environment variables. 19 | # 20 | class Appcenter < CI 21 | def self.validates_as_ci?(env) 22 | env.key? "APPCENTER_BUILD_ID" 23 | end 24 | 25 | def self.validates_as_pr?(env) 26 | return env["BUILD_REASON"] == "PullRequest" 27 | end 28 | 29 | def self.owner_for_github(env) 30 | URI.parse(env["BUILD_REPOSITORY_URI"]).path.split("/")[1] 31 | end 32 | 33 | def self.repo_identifier_for_github(env) 34 | repo_name = env["BUILD_REPOSITORY_NAME"] 35 | owner = owner_for_github(env) 36 | "#{owner}/#{repo_name}" 37 | end 38 | 39 | # Hopefully it's a temporary workaround (same as in Codeship integration) because App Center 40 | # doesn't expose PR's ID. There's a future request https://github.com/Microsoft/appcenter/issues/79 41 | def self.pr_from_env(env) 42 | Danger::RequestSources::GitHub.new(nil, env).get_pr_from_branch(repo_identifier_for_github(env), env["BUILD_SOURCEBRANCHNAME"], owner_for_github(env)) 43 | end 44 | 45 | def supported_request_sources 46 | @supported_request_sources ||= [Danger::RequestSources::GitHub] 47 | end 48 | 49 | def initialize(env) 50 | self.pull_request_id = self.class.pr_from_env(env) 51 | self.repo_url = env["BUILD_REPOSITORY_URI"] 52 | self.repo_slug = self.class.repo_identifier_for_github(env) 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/danger/ci_source/appveyor.rb: -------------------------------------------------------------------------------- 1 | # https://www.appveyor.com/docs/build-configuration/ 2 | module Danger 3 | # ### CI Setup 4 | # 5 | # Install dependencies and add a danger step to your `appveyor.yml`. 6 | # 7 | # ```yaml 8 | # install: 9 | # - cmd: >- 10 | # set PATH=C:\Ruby25-x64\bin;%PATH% 11 | # 12 | # bundle install 13 | # after_test: 14 | # - cmd: >- 15 | # bundle exec danger 16 | # ``` 17 | # 18 | # ### Token Setup 19 | # 20 | # For public repositories, add your plain token to environment variables in `appveyor.yml`. 21 | # Encrypted environment variables will not be decrypted on PR builds. 22 | # see here: https://www.appveyor.com/docs/build-configuration/#secure-variables 23 | # 24 | # ```yaml 25 | # environment: 26 | # DANGER_GITHUB_API_TOKEN: <YOUR_TOKEN_HERE> 27 | # ``` 28 | # 29 | # For private repositories, enter your token in `Settings>Environment>Environment variables>Add variable` and turn on `variable encryption`. 30 | # You will see encrypted variable text in `Settings>Export YAML` so just copy to your `appveyor.yml`. 31 | # 32 | # ```yaml 33 | # environment: 34 | # DANGER_GITHUB_API_TOKEN: 35 | # secure: <YOUR_ENCRYPTED_TOKEN_HERE> 36 | # ``` 37 | # 38 | class AppVeyor < CI 39 | def self.validates_as_ci?(env) 40 | env.key? "APPVEYOR" 41 | end 42 | 43 | def self.validates_as_pr?(env) 44 | return false unless env.key? "APPVEYOR_PULL_REQUEST_NUMBER" 45 | 46 | env["APPVEYOR_PULL_REQUEST_NUMBER"].to_i > 0 47 | end 48 | 49 | def initialize(env) 50 | self.repo_slug = env["APPVEYOR_REPO_NAME"] 51 | self.pull_request_id = env["APPVEYOR_PULL_REQUEST_NUMBER"] 52 | self.repo_url = GitRepo.new.origins # AppVeyor doesn't provide a repo url env variable for now 53 | end 54 | 55 | def supported_request_sources 56 | @supported_request_sources ||= [ 57 | Danger::RequestSources::GitHub, 58 | Danger::RequestSources::BitbucketCloud, 59 | Danger::RequestSources::BitbucketServer, 60 | Danger::RequestSources::GitLab 61 | ] 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/danger/ci_source/bamboo.rb: -------------------------------------------------------------------------------- 1 | require "set" 2 | 3 | module Danger 4 | # ### CI Setup 5 | # 6 | # Add a Run Script task that executes `danger` (or `bundle exec danger` if you're using Bundler 7 | # to manage your gems) as your as part of your Bamboo plan. 8 | # The minimum supported version is Bamboo 6.9. 9 | # 10 | # ### Token Setup 11 | # 12 | # IMPORTANT: All required Bamboo environment variables will be available 13 | # only if the plan is run as part of a pull request. This can be achieved by selecting: 14 | # Configure plan -> Branches -> Create plan branch: "When pull request is created". 15 | # Otherwise, `bamboo_repository_pr_key` and `bamboo_planRepository_repositoryUrl` 16 | # will not be available. 17 | # 18 | class Bamboo < CI 19 | def supported_request_sources 20 | @supported_request_sources ||= [ 21 | Danger::RequestSources::BitbucketServer 22 | ] 23 | end 24 | 25 | def self.validates_as_ci?(env) 26 | env.key? "bamboo_buildKey" 27 | end 28 | 29 | def self.validates_as_pr?(env) 30 | exists = ["bamboo_repository_pr_key", "bamboo_planRepository_repositoryUrl"].all? { |x| env[x] && !env[x].empty? } 31 | exists && env["bamboo_repository_pr_key"].to_i > 0 32 | end 33 | 34 | def initialize(env) 35 | self.repo_url = env["bamboo_planRepository_repositoryUrl"] 36 | self.pull_request_id = env["bamboo_repository_pr_key"] 37 | repo_matches = self.repo_url.match(%r{([/:])([^/]+/[^/]+?)(\.git$|$)}) 38 | self.repo_slug = repo_matches[2] unless repo_matches.nil? 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/danger/ci_source/bitbucket_pipelines.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | # ### CI Setup 3 | # 4 | # Install dependencies and add a danger step to your `bitbucket-pipelines.yml`. 5 | # 6 | # ```yaml 7 | # script: 8 | # - bundle exec danger --verbose 9 | # ``` 10 | # 11 | # ### Token Setup 12 | # 13 | # For username and password, you need to set. 14 | # 15 | # - `DANGER_BITBUCKETCLOUD_USERNAME` = The username for the account used to comment, as shown on 16 | # https://bitbucket.org/account/ 17 | # - `DANGER_BITBUCKETCLOUD_PASSWORD` = The password for the account used to comment, you could use 18 | # [App passwords](https://confluence.atlassian.com/bitbucket/app-passwords-828781300.html#Apppasswords-Aboutapppasswords) 19 | # with Read Pull Requests and Read Account Permissions. 20 | # 21 | # For OAuth key and OAuth secret, you can get them from. 22 | # 23 | # - Open [BitBucket Cloud Website](https://bitbucket.org) 24 | # - Navigate to Settings > OAuth > Add consumer 25 | # - Put `https://bitbucket.org/site/oauth2/authorize` for `Callback URL`, and enable Read Pull requests, and Read Account 26 | # Permission. 27 | # 28 | # - `DANGER_BITBUCKETCLOUD_OAUTH_KEY` = The consumer key for the account used to comment, as show as `Key` on the website. 29 | # - `DANGER_BITBUCKETCLOUD_OAUTH_SECRET` = The consumer secret for the account used to comment, as show as `Secret` on the 30 | # website. 31 | # 32 | # For [repository access token](https://support.atlassian.com/bitbucket-cloud/docs/repository-access-tokens/), what you 33 | # need to create one is: 34 | # 35 | # - Open your repository URL 36 | # - Navigate to Settings > Security > Access Tokens > Create Repository Access Token 37 | # - Give it a name and set Pull requests write scope 38 | 39 | class BitbucketPipelines < CI 40 | def self.validates_as_ci?(env) 41 | env.key? "BITBUCKET_BUILD_NUMBER" 42 | end 43 | 44 | def self.validates_as_pr?(env) 45 | env.key? "BITBUCKET_PR_ID" 46 | end 47 | 48 | def supported_request_sources 49 | @supported_request_sources ||= [Danger::RequestSources::BitbucketCloud] 50 | end 51 | 52 | def initialize(env) 53 | self.repo_url = env["BITBUCKET_GIT_HTTP_ORIGIN"] 54 | self.repo_slug = "#{env['BITBUCKET_REPO_OWNER']}/#{env['BITBUCKET_REPO_SLUG']}" 55 | self.pull_request_id = env["BITBUCKET_PR_ID"] 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/danger/ci_source/buildkite.rb: -------------------------------------------------------------------------------- 1 | # https://buildkite.com/docs/agent/osx 2 | # https://buildkite.com/docs/guides/environment-variables 3 | require "danger/request_sources/github/github" 4 | require "danger/request_sources/gitlab" 5 | 6 | module Danger 7 | # ### CI Setup 8 | # 9 | # With BuildKite you run the server yourself, so you will want to run it as a part of your build process. 10 | # It is common to have build steps, so we would recommend adding this to your script: 11 | # 12 | # ```shell 13 | # echo "--- Running Danger" 14 | # bundle exec danger 15 | # ``` 16 | # 17 | # ### Token Setup 18 | # 19 | # #### GitHub 20 | # 21 | # As this is self-hosted, you will need to add the `DANGER_GITHUB_API_TOKEN` to your build user's ENV. The alternative 22 | # is to pass in the token as a prefix to the command `DANGER_GITHUB_API_TOKEN="123" bundle exec danger`. 23 | # 24 | # #### GitLab 25 | # 26 | # As this is self-hosted, you will need to add the `DANGER_GITLAB_API_TOKEN` to your build user's ENV. The alternative 27 | # is to pass in the token as a prefix to the command `DANGER_GITLAB_API_TOKEN="123" bundle exec danger`. 28 | # 29 | class Buildkite < CI 30 | def self.validates_as_ci?(env) 31 | env.key? "BUILDKITE" 32 | end 33 | 34 | def self.validates_as_pr?(env) 35 | exists = ["BUILDKITE_PULL_REQUEST_REPO", "BUILDKITE_PULL_REQUEST"].all? { |x| env[x] } 36 | exists && !env["BUILDKITE_PULL_REQUEST_REPO"].empty? 37 | end 38 | 39 | def initialize(env) 40 | self.repo_url = env["BUILDKITE_REPO"] 41 | self.pull_request_id = env["BUILDKITE_PULL_REQUEST"] 42 | 43 | repo_matches = self.repo_url.match(%r{([/:])([^/]+/[^/]+?)(\.git$|$)}) 44 | self.repo_slug = repo_matches[2] unless repo_matches.nil? 45 | end 46 | 47 | def supported_request_sources 48 | @supported_request_sources ||= [Danger::RequestSources::GitHub, Danger::RequestSources::GitLab, Danger::RequestSources::BitbucketServer] 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/danger/ci_source/ci_source.rb: -------------------------------------------------------------------------------- 1 | require "set" 2 | 3 | module Danger 4 | # "abstract" CI class 5 | class CI 6 | attr_accessor :repo_slug, :pull_request_id, :repo_url, :supported_request_sources 7 | 8 | def self.inherited(child_class) 9 | available_ci_sources.add child_class 10 | super 11 | end 12 | 13 | def self.available_ci_sources 14 | @available_ci_sources ||= Set.new 15 | end 16 | 17 | def supported_request_sources 18 | raise "CISource subclass must specify the supported request sources" 19 | end 20 | 21 | def supports?(request_source) 22 | supported_request_sources.include?(request_source) 23 | end 24 | 25 | def self.validates_as_ci?(_env) 26 | abort "You need to include a function for #{self} for validates_as_ci?" 27 | end 28 | 29 | def self.validates_as_pr?(_env) 30 | abort "You need to include a function for #{self} for validates_as_pr?" 31 | end 32 | 33 | def initialize(_env) 34 | raise "Subclass and overwrite initialize" if method(__method__).owner == Danger::CI 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/danger/ci_source/circle_api.rb: -------------------------------------------------------------------------------- 1 | require "faraday" 2 | 3 | module Danger 4 | class CircleAPI 5 | # Determine if there's a PR attached to this commit, 6 | # and return a bool 7 | def pull_request?(env) 8 | url = pull_request_url(env) 9 | return !url.nil? 10 | end 11 | 12 | # Determine if there's a PR attached to this commit, 13 | # and return the url if so 14 | def pull_request_url(env) 15 | url = env["CI_PULL_REQUEST"] 16 | 17 | if url.nil? && !env["CIRCLE_PROJECT_USERNAME"].nil? && !env["CIRCLE_PROJECT_REPONAME"].nil? 18 | repo_slug = env["CIRCLE_PROJECT_USERNAME"] + "/" + env["CIRCLE_PROJECT_REPONAME"] 19 | if !env["CIRCLE_PR_NUMBER"].nil? 20 | host = env["DANGER_GITHUB_HOST"] || "github.com" 21 | url = "https://" + host + "/" + repo_slug + "/pull/" + env["CIRCLE_PR_NUMBER"] 22 | else 23 | token = env["DANGER_CIRCLE_CI_API_TOKEN"] 24 | url = fetch_pull_request_url(repo_slug, env["CIRCLE_BUILD_NUM"], token) 25 | end 26 | end 27 | url 28 | end 29 | 30 | def client 31 | @client ||= Faraday.new(url: "https://circleci.com/api/v1") 32 | end 33 | 34 | # Ask the API if the commit is inside a PR 35 | def fetch_pull_request_url(repo_slug, build_number, token) 36 | build_json = fetch_build(repo_slug, build_number, token) 37 | pull_requests = build_json[:pull_requests] 38 | return nil unless pull_requests && pull_requests.first 39 | 40 | pull_requests.first[:url] 41 | end 42 | 43 | # Make the API call, and parse the JSON 44 | def fetch_build(repo_slug, build_number, token) 45 | url = "project/#{repo_slug}/#{build_number}" 46 | params = { "circle-token" => token } 47 | response = client.get url, params, accept: "application/json" 48 | JSON.parse(response.body, symbolize_names: true) 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/danger/ci_source/cirrus.rb: -------------------------------------------------------------------------------- 1 | require "danger/request_sources/github/github" 2 | 3 | module Danger 4 | # ### CI Setup 5 | # You need to edit your `.cirrus.yml` to include `bundler exec danger`. 6 | # 7 | # Adding this to your `.cirrus.yml` allows Danger to fail your build, both on the Cirrus CI website and within your Pull Request. 8 | # With that set up, you can edit your task to add `bundler exec danger` in any script instruction. 9 | class Cirrus < CI 10 | def self.validates_as_ci?(env) 11 | env.key? "CIRRUS_CI" 12 | end 13 | 14 | def self.validates_as_pr?(env) 15 | exists = ["CIRRUS_PR", "CIRRUS_REPO_FULL_NAME"].all? { |x| env[x] && !env[x].empty? } 16 | exists && env["CIRRUS_PR"].to_i > 0 17 | end 18 | 19 | def supported_request_sources 20 | @supported_request_sources ||= [Danger::RequestSources::GitHub] 21 | end 22 | 23 | def initialize(env) 24 | self.repo_slug = env["CIRRUS_REPO_FULL_NAME"] 25 | if env["CIRRUS_PR"].to_i > 0 26 | self.pull_request_id = env["CIRRUS_PR"] 27 | end 28 | self.repo_url = env["CIRRUS_GIT_CLONE_URL"] 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/danger/ci_source/codefresh.rb: -------------------------------------------------------------------------------- 1 | # https://semaphoreci.com/docs/available-environment-variables.html 2 | require "danger/request_sources/github/github" 3 | 4 | module Danger 5 | # ### CI Setup 6 | # 7 | # To set up Danger on Codefresh, create a freestyle step in your Codefresh yaml configuration: 8 | # 9 | # ```yml 10 | # Danger: 11 | # title: Run Danger 12 | # image: alpine/bundle 13 | # working_directory: ${{main_clone}} 14 | # commands: 15 | # - bundle install --deployment 16 | # - bundle exec danger --verbose 17 | # ``` 18 | # 19 | # Don't forget to add the `DANGER_GITHUB_API_TOKEN` variable to your pipeline settings so that Danger can properly post comments to your pull request. 20 | # 21 | class Codefresh < CI 22 | def self.validates_as_ci?(env) 23 | env.key?("CF_BUILD_ID") && env.key?("CF_BUILD_URL") 24 | end 25 | 26 | def self.validates_as_pr?(env) 27 | return !env["CF_PULL_REQUEST_NUMBER"].to_s.empty? 28 | end 29 | 30 | def supported_request_sources 31 | @supported_request_sources ||= [Danger::RequestSources::GitHub] 32 | end 33 | 34 | def self.slug_from(env) 35 | return "" if env["CF_REPO_OWNER"].to_s.empty? 36 | return "" if env["CF_REPO_NAME"].to_s.empty? 37 | 38 | "#{env['CF_REPO_OWNER']}/#{env['CF_REPO_NAME']}".downcase! 39 | end 40 | 41 | def initialize(env) 42 | self.repo_url = env["CF_COMMIT_URL"].to_s.gsub(%r{/commit.+$}, "") 43 | self.repo_slug = self.class.slug_from(env) 44 | self.pull_request_id = env["CF_PULL_REQUEST_NUMBER"] 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/danger/ci_source/codemagic.rb: -------------------------------------------------------------------------------- 1 | # https://docs.codemagic.io/building/environment-variables/ 2 | 3 | module Danger 4 | # ### CI Setup 5 | # 6 | # Add a script step to your workflow: 7 | # 8 | # ``` 9 | # - name: Running Danger 10 | # script: | 11 | # bundle install 12 | # bundle exec danger 13 | # ``` 14 | # 15 | # ### Token Setup 16 | # 17 | # Add the following environment variables to your workflow's environment configuration. 18 | # https://docs.codemagic.io/getting-started/yaml/ 19 | # 20 | # #### GitHub 21 | # Add the `DANGER_GITHUB_API_TOKEN` to your build user's ENV. 22 | # 23 | # #### GitLab 24 | # Add the `DANGER_GITLAB_API_TOKEN` to your build user's ENV. 25 | # 26 | # #### Bitbucket Cloud 27 | # Add the `DANGER_BITBUCKETSERVER_USERNAME`, `DANGER_BITBUCKETSERVER_PASSWORD` 28 | # to your build user's ENV. 29 | # 30 | # #### Bitbucket server 31 | # Add the `DANGER_BITBUCKETSERVER_USERNAME`, `DANGER_BITBUCKETSERVER_PASSWORD` 32 | # and `DANGER_BITBUCKETSERVER_HOST` to your build user's ENV. 33 | # 34 | class Codemagic < CI 35 | def self.validates_as_ci?(env) 36 | env.key? "FCI_PROJECT_ID" 37 | end 38 | 39 | def self.validates_as_pr?(env) 40 | return !env["FCI_PULL_REQUEST_NUMBER"].to_s.empty? 41 | end 42 | 43 | def supported_request_sources 44 | @supported_request_sources ||= [ 45 | Danger::RequestSources::GitHub, 46 | Danger::RequestSources::GitLab, 47 | Danger::RequestSources::BitbucketServer, 48 | Danger::RequestSources::BitbucketCloud 49 | ] 50 | end 51 | 52 | def initialize(env) 53 | self.pull_request_id = env["FCI_PULL_REQUEST_NUMBER"] 54 | self.repo_slug = env["FCI_REPO_SLUG"] 55 | self.repo_url = GitRepo.new.origins # Codemagic doesn't provide a repo url env variable for n 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/danger/ci_source/codeship.rb: -------------------------------------------------------------------------------- 1 | # https://semaphoreci.com/docs/available-environment-variables.html 2 | require "danger/request_sources/github/github" 3 | 4 | module Danger 5 | # ### CI Setup 6 | # 7 | # In Codeship, go to your "Project Settings", then add `bundle exec danger` as a test step inside 8 | # one of your pipelines. 9 | # 10 | # ### Token Setup 11 | # 12 | # Add your `DANGER_GITHUB_API_TOKEN` to "Environment" section in "Project Settings". 13 | # 14 | class Codeship < CI 15 | def self.validates_as_ci?(env) 16 | env["CI_NAME"] == "codeship" 17 | end 18 | 19 | def self.validates_as_pr?(env) 20 | return false unless env["CI_BRANCH"] && !env["CI_BRANCH"].empty? 21 | 22 | !pr_from_env(env).nil? 23 | end 24 | 25 | def self.owner_for_github(env) 26 | env["CI_REPO_NAME"].split("/").first 27 | end 28 | 29 | # this is fairly hacky, see https://github.com/danger/danger/pull/892#issuecomment-329030616 for why 30 | def self.pr_from_env(env) 31 | Danger::RequestSources::GitHub.new(nil, env).get_pr_from_branch(env["CI_REPO_NAME"], env["CI_BRANCH"], owner_for_github(env)) 32 | end 33 | 34 | def supported_request_sources 35 | @supported_request_sources ||= [Danger::RequestSources::GitHub] 36 | end 37 | 38 | def initialize(env) 39 | self.repo_slug = env["CI_REPO_NAME"] 40 | self.pull_request_id = self.class.pr_from_env(env) 41 | self.repo_url = GitRepo.new.origins 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/danger/ci_source/concourse.rb: -------------------------------------------------------------------------------- 1 | require "git" 2 | require "danger/request_sources/local_only" 3 | 4 | module Danger 5 | # Concourse CI Integration 6 | # 7 | # https://concourse-ci.org/ 8 | # 9 | # ### CI Setup 10 | # 11 | # With Concourse, you run the docker images yourself, so you will want to add `yarn danger ci` within one of your build jobs. 12 | # 13 | # ```shell 14 | # build: 15 | # image: golang 16 | # commands: 17 | # - ... 18 | # - yarn danger ci 19 | # ``` 20 | # 21 | # ### Environment Variable Setup 22 | # 23 | # As this is self-hosted, you will need to add the `CONCOURSE` environment variable `export CONCOURSE=true` to your build environment, 24 | # as well as setting environment variables for `PULL_REQUEST_ID` and `REPO_SLUG`. Assuming you are using the github pull request resource 25 | # https://github.com/jtarchie/github-pullrequest-resource the id of the PR can be accessed from `git config --get pullrequest.id`. 26 | # 27 | # ### Token Setup 28 | # 29 | # Once again as this is self-hosted, you will need to add `DANGER_GITHUB_API_TOKEN` environment variable to the build environment. 30 | # The suggested method of storing the token is within the vault - https://concourse-ci.org/creds.html 31 | 32 | class Concourse < CI 33 | def self.validates_as_ci?(env) 34 | env.key? "CONCOURSE" 35 | end 36 | 37 | def self.validates_as_pr?(env) 38 | exists = ["PULL_REQUEST_ID", "REPO_SLUG"].all? { |x| env[x] && !env[x].empty? } 39 | exists && env["PULL_REQUEST_ID"].to_i > 0 40 | end 41 | 42 | def supported_request_sources 43 | @supported_request_sources ||= [ 44 | Danger::RequestSources::GitHub, 45 | Danger::RequestSources::GitLab, 46 | Danger::RequestSources::BitbucketServer, 47 | Danger::RequestSources::BitbucketCloud 48 | ] 49 | end 50 | 51 | def initialize(env) 52 | self.repo_slug = env["REPO_SLUG"] 53 | 54 | if env["PULL_REQUEST_ID"].to_i > 0 55 | self.pull_request_id = env["PULL_REQUEST_ID"] 56 | end 57 | self.repo_url = GitRepo.new.origins 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/danger/ci_source/custom_ci_with_github.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "danger/request_sources/github/github" 4 | 5 | module Danger 6 | # ### CI Setup 7 | # 8 | # Custom CI with GitHub 9 | # 10 | # This CI source is for custom, most likely internal, CI systems that are use GitHub as source control. 11 | # An example could be argo-workflows or tekton hosted in your own Kubernetes cluster. 12 | # 13 | # The following environment variables are required: 14 | # - `CUSTOM_CI_WITH_GITHUB` - Set to any value to indicate that this is a custom CI with GitHub 15 | # 16 | # ### Token Setup 17 | # 18 | # #### GitHub 19 | # As you own the setup, it's up to you to add the environment variable for the `DANGER_GITHUB_API_TOKEN`. 20 | # 21 | class CustomCIWithGithub < CI 22 | def self.validates_as_ci?(env) 23 | env.key? "CUSTOM_CI_WITH_GITHUB" 24 | end 25 | 26 | def self.validates_as_pr?(env) 27 | value = env["GITHUB_EVENT_NAME"] 28 | ["pull_request", "pull_request_target"].include?(value) 29 | end 30 | 31 | def supported_request_sources 32 | @supported_request_sources ||= [Danger::RequestSources::GitHub] 33 | end 34 | 35 | def initialize(env) 36 | super 37 | 38 | self.repo_slug = env["GITHUB_REPOSITORY"] 39 | pull_request_event = JSON.parse(File.read(env["GITHUB_EVENT_PATH"])) 40 | self.pull_request_id = pull_request_event["number"] 41 | self.repo_url = pull_request_event["repository"]["clone_url"] 42 | 43 | # if environment variable DANGER_GITHUB_API_TOKEN is not set, use env GITHUB_TOKEN 44 | if (env.key? "CUSTOM_CI_WITH_GITHUB") && (!env.key? "DANGER_GITHUB_API_TOKEN") 45 | env["DANGER_GITHUB_API_TOKEN"] = env["GITHUB_TOKEN"] 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/danger/ci_source/dotci.rb: -------------------------------------------------------------------------------- 1 | require "danger/request_sources/github/github" 2 | 3 | module Danger 4 | # https://groupon.github.io/DotCi 5 | 6 | # ### CI Setup 7 | # DotCi is a layer on top of jenkins. So, if you're using DotCi, you're hosting your own environment. 8 | # 9 | # ### Token Setup 10 | # 11 | # #### GitHub 12 | # As you own the machine, it's up to you to add the environment variable for the `DANGER_GITHUB_API_TOKEN`. 13 | # 14 | class DotCi < CI 15 | def self.validates_as_ci?(env) 16 | env.key? "DOTCI" 17 | end 18 | 19 | def self.validates_as_pr?(env) 20 | !env["DOTCI_PULL_REQUEST"].nil? && !env["DOTCI_PULL_REQUEST"].match(/^[0-9]+$/).nil? 21 | end 22 | 23 | def supported_request_sources 24 | @supported_request_sources ||= [ 25 | Danger::RequestSources::GitHub 26 | ] 27 | end 28 | 29 | def initialize(env) 30 | self.repo_url = self.class.repo_url(env) 31 | self.pull_request_id = self.class.pull_request_id(env) 32 | repo_matches = self.repo_url.match(%r{([/:])([^/]+/[^/]+)$}) 33 | self.repo_slug = repo_matches[2].gsub(/\.git$/, "") unless repo_matches.nil? 34 | end 35 | 36 | def self.pull_request_id(env) 37 | env["DOTCI_PULL_REQUEST"] 38 | end 39 | 40 | def self.repo_url(env) 41 | if env["DOTCI_INSTALL_PACKAGES_GIT_CLONE_URL"] 42 | env["DOTCI_INSTALL_PACKAGES_GIT_CLONE_URL"] 43 | elsif env["DOTCI_DOCKER_COMPOSE_GIT_CLONE_URL"] 44 | env["DOTCI_DOCKER_COMPOSE_GIT_CLONE_URL"] 45 | else 46 | env["GIT_URL"] 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/danger/ci_source/drone.rb: -------------------------------------------------------------------------------- 1 | # http://readme.drone.io/usage/variables/ 2 | require "danger/request_sources/github/github" 3 | require "danger/request_sources/gitlab" 4 | 5 | module Danger 6 | # ### CI Setup 7 | # 8 | # With Drone you run the docker images yourself, so you will want to add `bundle exec danger` at the end of 9 | # your `.drone.yml`. 10 | # 11 | # ```shell 12 | # build: 13 | # image: golang 14 | # commands: 15 | # - ... 16 | # - bundle exec danger 17 | # ``` 18 | # 19 | # ### Token Setup 20 | # 21 | # As this is self-hosted, you will need to expose the `DANGER_GITHUB_API_TOKEN` as a secret to your 22 | # builds: 23 | # 24 | # Drone secrets: http://readme.drone.io/usage/secret-guide/ 25 | # NOTE: This is a new syntax in DroneCI 0.6+ 26 | # 27 | # ```yml 28 | # build: 29 | # image: golang 30 | # secrets: 31 | # - DANGER_GITHUB_API_TOKEN 32 | # commands: 33 | # - ... 34 | # - bundle exec danger 35 | # ``` 36 | class Drone < CI 37 | def self.validates_as_ci?(env) 38 | validates_as_ci_post_06?(env) or validates_as_ci_pre_06?(env) 39 | end 40 | 41 | def self.validates_as_pr?(env) 42 | env["DRONE_PULL_REQUEST"].to_i > 0 43 | end 44 | 45 | def supported_request_sources 46 | @supported_request_sources ||= [Danger::RequestSources::GitHub, Danger::RequestSources::GitLab] 47 | end 48 | 49 | def initialize(env) 50 | if self.class.validates_as_ci_post_06?(env) 51 | self.repo_slug = "#{env['DRONE_REPO_OWNER']}/#{env['DRONE_REPO_NAME']}" 52 | self.repo_url = env["DRONE_REPO_LINK"] if self.class.validates_as_ci_post_06?(env) 53 | elsif self.class.validates_as_ci_pre_06?(env) 54 | self.repo_slug = env["DRONE_REPO"] 55 | self.repo_url = GitRepo.new.origins 56 | end 57 | 58 | self.pull_request_id = env["DRONE_PULL_REQUEST"] 59 | end 60 | 61 | # Check if this build is valid for CI with drone 0.6 or later 62 | def self.validates_as_ci_post_06?(env) 63 | env.key? "DRONE_REPO_OWNER" and env.key? "DRONE_REPO_NAME" 64 | end 65 | 66 | # Checks if this build is valid for CI with drone 0.5 or earlier 67 | def self.validates_as_ci_pre_06?(env) 68 | env.key? "DRONE_REPO" 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /lib/danger/ci_source/github_actions.rb: -------------------------------------------------------------------------------- 1 | require "danger/request_sources/github/github" 2 | 3 | module Danger 4 | # ### CI Setup 5 | # 6 | # You can use `danger/danger` Action in your `.github/workflows/xxx.yml`. 7 | # And so, you can use GITHUB_TOKEN secret as `DANGER_GITHUB_API_TOKEN` environment variable. 8 | # 9 | # ```yml 10 | # ... 11 | # steps: 12 | # - uses: actions/checkout@v3 13 | # - uses: danger/danger@master 14 | # env: 15 | # DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} 16 | # ``` 17 | # 18 | class GitHubActions < CI 19 | def self.validates_as_ci?(env) 20 | env.key? "GITHUB_ACTION" 21 | end 22 | 23 | def self.validates_as_pr?(env) 24 | value = env["GITHUB_EVENT_NAME"] 25 | ["pull_request", "pull_request_target"].include?(value) 26 | end 27 | 28 | def supported_request_sources 29 | @supported_request_sources ||= [Danger::RequestSources::GitHub] 30 | end 31 | 32 | def initialize(env) 33 | self.repo_slug = env["GITHUB_REPOSITORY"] 34 | pull_request_event = JSON.parse(File.read(env["GITHUB_EVENT_PATH"])) 35 | self.pull_request_id = pull_request_event["number"] 36 | self.repo_url = pull_request_event["repository"]["clone_url"] 37 | 38 | # if environment variable DANGER_GITHUB_API_TOKEN is not set, use env GITHUB_TOKEN 39 | if (env.key? "GITHUB_ACTION") && (!env.key? "DANGER_GITHUB_API_TOKEN") 40 | env["DANGER_GITHUB_API_TOKEN"] = env["GITHUB_TOKEN"] 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/danger/ci_source/local_only_git_repo.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "git" 4 | require "danger/request_sources/local_only" 5 | 6 | module Danger 7 | # ### CI Setup 8 | # 9 | # For setting up LocalOnlyGitRepo there is not much needed. Either `--base` and `--head` need to be specified or 10 | # origin/master is expected for base and HEAD for head 11 | # 12 | class LocalOnlyGitRepo < CI 13 | attr_accessor :base_commit, :head_commit 14 | 15 | HEAD_VAR = "DANGER_LOCAL_HEAD" 16 | BASE_VAR = "DANGER_LOCAL_BASE" 17 | 18 | def self.validates_as_ci?(env) 19 | env.key? "DANGER_USE_LOCAL_ONLY_GIT" 20 | end 21 | 22 | def self.validates_as_pr?(_env) 23 | false 24 | end 25 | 26 | def git 27 | @git ||= GitRepo.new 28 | end 29 | 30 | def run_git(command) 31 | git.exec(command).encode("UTF-8", "binary", invalid: :replace, undef: :replace, replace: "") 32 | end 33 | 34 | def supported_request_sources 35 | @supported_request_sources ||= [Danger::RequestSources::LocalOnly] 36 | end 37 | 38 | def initialize(env = {}) 39 | # expects --base/--head specified OR origin/master to be base and HEAD head 40 | self.base_commit = env[BASE_VAR] || run_git("rev-parse --abbrev-ref origin/master") 41 | self.head_commit = env[HEAD_VAR] || run_git("rev-parse --abbrev-ref HEAD") 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/danger/ci_source/screwdriver.rb: -------------------------------------------------------------------------------- 1 | # http://screwdriver.cd 2 | # https://docs.screwdriver.cd/user-guide/environment-variables 3 | require "danger/request_sources/github/github" 4 | 5 | module Danger 6 | # ### CI Setup 7 | # 8 | # Install dependencies and add a danger step to your screwdriver.yaml: 9 | # 10 | # ```yml 11 | # jobs: 12 | # danger: 13 | # requires: [~pr, ~commit] 14 | # steps: 15 | # - setup: bundle install --path vendor 16 | # - danger: bundle exec danger 17 | # secrets: 18 | # - DANGER_GITHUB_API_TOKEN 19 | # ``` 20 | # 21 | # ### Token Setup 22 | # 23 | # Add the `DANGER_GITHUB_API_TOKEN` to your pipeline env as a 24 | # [build secret](https://docs.screwdriver.cd/user-guide/configuration/secrets) 25 | # 26 | class Screwdriver < CI 27 | def self.validates_as_ci?(env) 28 | env.key? "SCREWDRIVER" 29 | end 30 | 31 | def self.validates_as_pr?(env) 32 | exists = ["SD_PULL_REQUEST", "SCM_URL"].all? { |x| env[x] && !env[x].empty? } 33 | exists && env["SD_PULL_REQUEST"].to_i > 0 34 | end 35 | 36 | def supported_request_sources 37 | @supported_request_sources ||= [Danger::RequestSources::GitHub] 38 | end 39 | 40 | def initialize(env) 41 | self.repo_slug = env["SCM_URL"].split(":").last.gsub(".git", "").split("#", 2).first 42 | self.repo_url = env["SCM_URL"].split("#", 2).first 43 | if env["SD_PULL_REQUEST"].to_i > 0 44 | self.pull_request_id = env["SD_PULL_REQUEST"] 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/danger/ci_source/semaphore.rb: -------------------------------------------------------------------------------- 1 | # https://docs.semaphoreci.com/article/12-environment-variables 2 | require "danger/request_sources/github/github" 3 | 4 | module Danger 5 | # ### CI Setup 6 | # 7 | # For Semaphore you will want to go to the settings page of the project. Inside "Build Settings" 8 | # you should add `bundle exec danger` to the Setup thread. Note that Semaphore only provides 9 | # the build environment variables necessary for Danger on PRs across forks. 10 | # 11 | # ### Token Setup 12 | # 13 | # You can add your `DANGER_GITHUB_API_TOKEN` inside the "Environment Variables" section in the settings. 14 | # 15 | class Semaphore < CI 16 | def self.validates_as_ci?(env) 17 | env.key? "SEMAPHORE" 18 | end 19 | 20 | def self.validates_as_pr?(env) 21 | one = ["SEMAPHORE_REPO_SLUG", "PULL_REQUEST_NUMBER"].all? { |x| env[x] && !env[x].empty? } 22 | two = ["SEMAPHORE_GIT_REPO_SLUG", "SEMAPHORE_GIT_PR_NUMBER"].all? { |x| env[x] && !env[x].empty? } 23 | 24 | one || two 25 | end 26 | 27 | def supported_request_sources 28 | @supported_request_sources ||= [Danger::RequestSources::GitHub] 29 | end 30 | 31 | def initialize(env) 32 | self.repo_slug = env["SEMAPHORE_GIT_REPO_SLUG"] || env["SEMAPHORE_REPO_SLUG"] 33 | self.pull_request_id = env["SEMAPHORE_GIT_PR_NUMBER"] || env["PULL_REQUEST_NUMBER"] 34 | self.repo_url = env["SEMAPHORE_GIT_URL"] || GitRepo.new.origins 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/danger/ci_source/support/commits.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | class Commits 3 | def initialize(base_head) 4 | @base_head = base_head.strip.split(" ".freeze) 5 | end 6 | 7 | def base 8 | base_head.first 9 | end 10 | 11 | def head 12 | base_head.last 13 | end 14 | 15 | private 16 | 17 | attr_reader :base_head 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/danger/ci_source/support/find_repo_info_from_logs.rb: -------------------------------------------------------------------------------- 1 | require "danger/ci_source/support/repo_info" 2 | 3 | module Danger 4 | class FindRepoInfoFromLogs 5 | def initialize(github_host, remote_logs) 6 | @github_host = github_host 7 | @remote_logs = remote_logs 8 | end 9 | 10 | def call 11 | matched = remote.match(regexp) 12 | 13 | if matched 14 | RepoInfo.new(matched["repo_slug"], nil) 15 | end 16 | end 17 | 18 | private 19 | 20 | attr_reader :remote_logs, :github_host 21 | 22 | def remote 23 | remote_logs.lines.grep(/Fetch URL/)[0].split(": ".freeze, 2)[1] 24 | end 25 | 26 | def regexp 27 | %r{ 28 | #{Regexp.escape(github_host)} 29 | (:|/|(:/)) 30 | (?<repo_slug>[^/]+/.+?) 31 | (?:\.git)?$ 32 | }x 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/danger/ci_source/support/find_repo_info_from_url.rb: -------------------------------------------------------------------------------- 1 | require "danger/ci_source/support/repo_info" 2 | 3 | module Danger 4 | class FindRepoInfoFromURL 5 | REGEXP = %r{ 6 | ://[^/]+/ 7 | (([^/]+/){1,2}_git/)? 8 | (?<slug>[^/]+(/[^/]+){0,2}) 9 | (/(pull|pullrequest|merge_requests|pull-requests)/) 10 | (?<id>\d+) 11 | }x.freeze 12 | 13 | # Regex used to extract info from Bitbucket server URLs, as they use a quite different format 14 | REGEXPBB = %r{ 15 | (?:[/:])projects 16 | /([^/.]+) 17 | /repos/([^/.]+) 18 | /pull-requests 19 | /(\d+) 20 | }x.freeze 21 | 22 | def initialize(url) 23 | @url = url 24 | end 25 | 26 | def call 27 | matched = url.match(REGEXPBB) 28 | 29 | if matched 30 | RepoInfo.new("#{matched[1]}/#{matched[2]}", matched[3]) 31 | else 32 | matched = url.match(REGEXP) 33 | if matched 34 | # Clean up the slug to remove any trailing dashes or slashes that might be part of the GitLab URL format 35 | clean_slug = matched[:slug].gsub(%r{[-/]+$}, "") 36 | RepoInfo.new(clean_slug, matched[:id]) 37 | end 38 | end 39 | end 40 | 41 | private 42 | 43 | attr_reader :url 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/danger/ci_source/support/local_pull_request.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | class LocalPullRequest 3 | attr_reader :pull_request_id, :sha 4 | 5 | def initialize(log_line) 6 | @pull_request_id = log_line.match(/#(?<id>[0-9]+)/)[:id] 7 | @sha = log_line.split(" ".freeze).first 8 | end 9 | 10 | def valid? 11 | pull_request_id && sha 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/danger/ci_source/support/no_pull_request.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | class NoPullRequest 3 | def valid? 4 | false 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/danger/ci_source/support/no_repo_info.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | class NoRepoInfo 3 | attr_reader :slug, :id 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/danger/ci_source/support/remote_pull_request.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | class RemotePullRequest 3 | attr_reader :pull_request_id, :sha, :head, :base 4 | 5 | def initialize(pull_request_id, head, base) 6 | @pull_request_id = pull_request_id 7 | @head = head 8 | @base = base 9 | end 10 | 11 | def valid? 12 | pull_request_id && head && base 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/danger/ci_source/support/repo_info.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | class RepoInfo 3 | attr_reader :slug, :id 4 | 5 | def initialize(slug, id) 6 | @slug = slug 7 | @id = id 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/danger/ci_source/surf.rb: -------------------------------------------------------------------------------- 1 | # http://github.com/surf-build/surf 2 | require "danger/request_sources/github/github" 3 | 4 | module Danger 5 | # ### CI Setup 6 | # 7 | # You want to add `bundle exec danger` to your `build.sh` file to run Danger at the 8 | # end of your build. 9 | # 10 | # ### Token Setup 11 | # 12 | # As this is self-hosted, you will need to add the `DANGER_GITHUB_API_TOKEN` to your build user's ENV. The alternative 13 | # is to pass in the token as a prefix to the command `DANGER_GITHUB_API_TOKEN="123" bundle exec danger`. 14 | # 15 | class Surf < CI 16 | def self.validates_as_ci?(env) 17 | return ["SURF_REPO", "SURF_NWO"].all? { |x| env[x] && !env[x].empty? } 18 | end 19 | 20 | def self.validates_as_pr?(env) 21 | validates_as_ci?(env) 22 | end 23 | 24 | def supported_request_sources 25 | @supported_request_sources ||= [Danger::RequestSources::GitHub] 26 | end 27 | 28 | def initialize(env) 29 | self.repo_slug = env["SURF_NWO"] 30 | if env["SURF_PR_NUM"].to_i > 0 31 | self.pull_request_id = env["SURF_PR_NUM"] 32 | end 33 | 34 | self.repo_url = env["SURF_REPO"] 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/danger/ci_source/travis.rb: -------------------------------------------------------------------------------- 1 | # http://docs.travis-ci.com/user/osx-ci-environment/ 2 | # http://docs.travis-ci.com/user/environment-variables/ 3 | require "danger/request_sources/github/github" 4 | 5 | module Danger 6 | # ### CI Setup 7 | # You need to edit your `.travis.yml` to include `bundle exec danger`. If you already have 8 | # a `script:` section then we recommend adding this command at the end of the script step: `- bundle exec danger`. 9 | # 10 | # Otherwise, add a `before_script` step to the root of the `.travis.yml` with `bundle exec danger` 11 | # 12 | # ```ruby 13 | # before_script: 14 | # - bundle exec danger 15 | # ``` 16 | # 17 | # Adding this to your `.travis.yml` allows Danger to fail your build, both on the TravisCI website and within your Pull Request. 18 | # With that set up, you can edit your job to add `bundle exec danger` at the build action. 19 | # 20 | # _Note:_ Travis CI defaults to using an older version of Ruby, so you may need to add `rvm: 2.0.0` to the root your `.travis.yml`. 21 | # 22 | # ### Token Setup 23 | # 24 | # You need to add the `DANGER_GITHUB_API_TOKEN` environment variable, to do this, 25 | # go to your repo's settings, which should look like: `https://travis-ci.org/[user]/[repo]/settings`. 26 | # 27 | # If you have an open source project, you should ensure "Display value in build log" enabled, so that PRs from forks work. 28 | # 29 | class Travis < CI 30 | def self.validates_as_ci?(env) 31 | env.key? "HAS_JOSH_K_SEAL_OF_APPROVAL" 32 | end 33 | 34 | def self.validates_as_pr?(env) 35 | exists = ["TRAVIS_PULL_REQUEST", "TRAVIS_REPO_SLUG"].all? { |x| env[x] && !env[x].empty? } 36 | exists && env["TRAVIS_PULL_REQUEST"].to_i > 0 37 | end 38 | 39 | def supported_request_sources 40 | @supported_request_sources ||= [Danger::RequestSources::GitHub] 41 | end 42 | 43 | def initialize(env) 44 | self.repo_slug = env["TRAVIS_REPO_SLUG"] 45 | if env["TRAVIS_PULL_REQUEST"].to_i > 0 46 | self.pull_request_id = env["TRAVIS_PULL_REQUEST"] 47 | end 48 | self.repo_url = GitRepo.new.origins # Travis doesn't provide a repo url env variable :/ 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/danger/ci_source/xcode_cloud.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | # ### CI Setup 3 | # 4 | # In order to work with Xcode Cloud and Danger, you will need to add `bundle exec danger` to 5 | # the `ci_scripts/ci_post_xcodebuild.sh` (Xcode Cloud's expected filename for a post-action build script). 6 | # More details and documentation on Xcode Cloud configuration can be found [here](https://developer.apple.com/documentation/xcode/writing-custom-build-scripts). 7 | # 8 | # ### Token Setup 9 | # 10 | # You will need to add the `DANGER_GITHUB_API_TOKEN` to your build environment. 11 | # If running on GitHub Enterprise, make sure you also set the expected values for 12 | # both `DANGER_GITHUB_API_HOST` and `DANGER_GITHUB_HOST`. 13 | # 14 | class XcodeCloud < CI 15 | def self.validates_as_ci?(env) 16 | env.key? "CI_XCODEBUILD_ACTION" 17 | end 18 | 19 | def self.validates_as_pr?(env) 20 | env.key? "CI_PULL_REQUEST_NUMBER" 21 | end 22 | 23 | def supported_request_sources 24 | @supported_request_sources ||= [ 25 | Danger::RequestSources::GitHub, 26 | Danger::RequestSources::GitLab, 27 | Danger::RequestSources::BitbucketCloud, 28 | Danger::RequestSources::BitbucketServer 29 | ] 30 | end 31 | 32 | def initialize(env) 33 | self.repo_slug = env["CI_PULL_REQUEST_SOURCE_REPO"] 34 | self.pull_request_id = env["CI_PULL_REQUEST_NUMBER"] 35 | self.repo_url = env["CI_PULL_REQUEST_HTML_URL"] 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/danger/ci_source/xcode_server.rb: -------------------------------------------------------------------------------- 1 | # Following the advice from @czechboy0 https://github.com/danger/danger/issues/171 2 | # https://github.com/czechboy0/Buildasaur 3 | require "danger/request_sources/github/github" 4 | 5 | module Danger 6 | # ### CI Setup 7 | # 8 | # If you're bold enough to use Xcode Bots. You will need to use [Buildasaur](https://github.com/czechboy0/Buildasaur) 9 | # in order to work with Danger. This will set up your build environment for you, as the name of the bot contains all 10 | # of the environment variables that Danger needs to work. 11 | # 12 | # With Buildasaur set up, you can edit your job to add `bundle exec danger` as a post-action build script. 13 | # 14 | # ### Token Setup 15 | # 16 | # As this is self-hosted, you will need to add the `DANGER_GITHUB_API_TOKEN` to your build user's ENV. The alternative 17 | # is to pass in the token as a prefix to the command `DANGER_GITHUB_API_TOKEN="123" bundle exec danger`.`. 18 | # 19 | class XcodeServer < CI 20 | def self.validates_as_ci?(env) 21 | env.key? "XCS_BOT_NAME" 22 | end 23 | 24 | def self.validates_as_pr?(env) 25 | value = env["XCS_BOT_NAME"] 26 | !value.nil? && value.include?("BuildaBot") 27 | end 28 | 29 | def supported_request_sources 30 | @supported_request_sources ||= [ 31 | Danger::RequestSources::GitHub, 32 | Danger::RequestSources::BitbucketServer, 33 | Danger::RequestSources::BitbucketCloud 34 | ] 35 | end 36 | 37 | def initialize(env) 38 | bot_name = env["XCS_BOT_NAME"] 39 | return if bot_name.nil? 40 | 41 | repo_matches = bot_name.match(/\[(.+)\]/) 42 | self.repo_slug = repo_matches[1] unless repo_matches.nil? 43 | pull_request_id_matches = bot_name.match(/#(\d+)/) 44 | self.pull_request_id = pull_request_id_matches[1] unless pull_request_id_matches.nil? 45 | self.repo_url = GitRepo.new.origins # Xcode Server doesn't provide a repo url env variable :/ 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/danger/clients/rubygems_client.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | class RubyGemsClient 3 | API_URL = "https://rubygems.org/api/v1/versions/danger/latest.json".freeze 4 | DUMMY_VERSION = "0.0.0".freeze 5 | 6 | def self.latest_danger_version 7 | require "json" 8 | json = JSON.parse(Faraday.get(API_URL).body) 9 | json.fetch("version") { DUMMY_VERSION } 10 | rescue StandardError => _e 11 | DUMMY_VERSION 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/danger/commands/dangerfile/gem.rb: -------------------------------------------------------------------------------- 1 | require "claide_plugin" 2 | require "danger/commands/dangerfile/init" 3 | 4 | module Danger 5 | class DangerfileGem < DangerfileCommand 6 | self.summary = "Create a gem-based Dangerfile quickly." 7 | def self.description 8 | <<-DESC 9 | Creates a scaffold for the development of a new gem based Dangerfile 10 | named `NAME` according to the best practices. 11 | DESC 12 | end 13 | self.command = "gem" 14 | self.arguments = [ 15 | CLAide::Argument.new("NAME", true) 16 | ] 17 | 18 | def initialize(argv) 19 | @name = argv.shift_argument 20 | prefix = "dangerfile" + "-" 21 | unless @name.nil? || @name.empty? || @name.start_with?(prefix) 22 | @name = prefix + @name.dup 23 | end 24 | @template_url = argv.shift_argument 25 | super 26 | end 27 | 28 | def validate! 29 | super 30 | if @name.nil? || @name.empty? 31 | help! "A name for the plugin is required." 32 | end 33 | 34 | help! "The plugin name cannot contain spaces." if @name =~ /\s/ 35 | end 36 | 37 | def run 38 | runner = CLAide::TemplateRunner.new(@name, "https://github.com/danger/dangerfile-gem-template") 39 | runner.clone_template 40 | runner.configure_template 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/danger/commands/dangerfile/init.rb: -------------------------------------------------------------------------------- 1 | require "danger/danger_core/dangerfile_generator" 2 | 3 | # Mainly so we can have a nice structure for commands 4 | 5 | module Danger 6 | class DangerfileCommand < Runner 7 | self.summary = "Easily create your Dangerfiles." 8 | self.command = "dangerfile" 9 | 10 | self.abstract_command = true 11 | def self.options 12 | [] 13 | end 14 | end 15 | end 16 | 17 | # Just a less verbose way of doing the Dangerfile from `danger init`. 18 | 19 | module Danger 20 | class DangerfileInit < DangerfileCommand 21 | self.summary = "Create an example Dangerfile." 22 | self.command = "init" 23 | 24 | def run 25 | content = DangerfileGenerator.create_dangerfile(".", cork) 26 | File.write("Dangerfile", content) 27 | cork.puts "Created" + "./Dangerfile".green 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/danger/commands/dry_run.rb: -------------------------------------------------------------------------------- 1 | require "danger/commands/local_helpers/pry_setup" 2 | require "fileutils" 3 | 4 | module Danger 5 | class DryRun < Runner 6 | self.summary = "Dry-Run the Dangerfile locally, so you could check some violations before sending real PR/MR." 7 | self.command = "dry_run" 8 | 9 | def self.options 10 | [ 11 | ["--pry", "Drop into a Pry shell after evaluating the Dangerfile."] 12 | ] 13 | end 14 | 15 | def initialize(argv) 16 | show_help = true if argv.arguments == ["-h"] 17 | 18 | # Currently CLAide doesn't support short option like -h https://github.com/CocoaPods/CLAide/pull/60 19 | # when user pass in -h, mimic the behavior of passing in --help. 20 | argv = CLAide::ARGV.new ["--help"] if show_help 21 | 22 | super 23 | 24 | if argv.flag?("pry", false) 25 | @dangerfile_path = PrySetup.new(cork).setup_pry(@dangerfile_path, DryRun.command) 26 | end 27 | end 28 | 29 | def validate! 30 | super 31 | unless @dangerfile_path 32 | help! "Could not find a Dangerfile." 33 | end 34 | end 35 | 36 | def run 37 | ENV["DANGER_USE_LOCAL_ONLY_GIT"] = "YES" 38 | ENV["DANGER_LOCAL_HEAD"] = @head if @head 39 | ENV["DANGER_LOCAL_BASE"] = @base if @base 40 | 41 | env = EnvironmentManager.new(ENV, cork) 42 | dm = Dangerfile.new(env, cork) 43 | 44 | exit 1 if dm.run( 45 | Danger::EnvironmentManager.danger_base_branch, 46 | Danger::EnvironmentManager.danger_head_branch, 47 | @dangerfile_path, 48 | nil, 49 | nil, 50 | nil 51 | ) 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /lib/danger/commands/init_helpers/interviewer.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | class Interviewer 3 | attr_accessor :no_delay, :no_waiting, :ui 4 | 5 | def initialize(cork_board) 6 | @ui = cork_board 7 | end 8 | 9 | def show_prompt 10 | ui.print "> ".bold.green 11 | end 12 | 13 | def yellow_bang 14 | "! ".yellow 15 | end 16 | 17 | def green_bang 18 | "! ".green 19 | end 20 | 21 | def red_bang 22 | "! ".red 23 | end 24 | 25 | def say(output) 26 | ui.puts output 27 | end 28 | 29 | def header(title) 30 | say title.yellow 31 | say "" 32 | pause 0.6 33 | end 34 | 35 | def link(url) 36 | say " -> " + url.underlined + "\n" 37 | end 38 | 39 | def pause(time) 40 | sleep(time) unless @no_waiting 41 | end 42 | 43 | def wait_for_return 44 | STDOUT.flush 45 | STDIN.gets unless @no_delay 46 | ui.puts 47 | end 48 | 49 | def run_command(command, output_command = nil) 50 | output_command ||= command 51 | ui.puts " " + output_command.magenta 52 | system command 53 | end 54 | 55 | def ask_with_answers(question, possible_answers) 56 | ui.print "\n#{question}? [" 57 | 58 | print_info = proc do 59 | possible_answers.each_with_index do |answer, i| 60 | the_answer = i.zero? ? answer.underlined : answer 61 | ui.print " " + the_answer 62 | ui.print(" /") if i != possible_answers.length - 1 63 | end 64 | ui.print " ]\n" 65 | end 66 | print_info.call 67 | 68 | answer = "" 69 | 70 | loop do 71 | show_prompt 72 | answer = @no_waiting ? possible_answers[0].downcase : STDIN.gets.downcase.chomp 73 | 74 | answer = "yes" if answer == "y" 75 | answer = "no" if answer == "n" 76 | 77 | # default to first answer 78 | if answer == "" 79 | answer = possible_answers[0].downcase 80 | ui.puts "Using: " + answer.yellow 81 | end 82 | 83 | break if possible_answers.map(&:downcase).include? answer 84 | 85 | ui.print "\nPossible answers are [" 86 | print_info.call 87 | end 88 | 89 | answer 90 | end 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /lib/danger/commands/local_helpers/http_cache.rb: -------------------------------------------------------------------------------- 1 | require "pstore" 2 | 3 | module Danger 4 | class HTTPCache 5 | attr_reader :expires_in 6 | 7 | def initialize(cache_file = nil, options = {}) 8 | File.delete(cache_file) if options[:clear_cache] 9 | @store = PStore.new(cache_file) 10 | @expires_in = options[:expires_in] || 300 # 5 minutes 11 | end 12 | 13 | def read(key) 14 | @store.transaction do 15 | entry = @store[key] 16 | return nil unless entry 17 | return entry[:value] unless entry_has_expired(entry, @expires_in) 18 | 19 | @store.delete key 20 | return nil 21 | end 22 | end 23 | 24 | def delete(key) 25 | @store.transaction { @store.delete key } 26 | end 27 | 28 | def write(key, value) 29 | @store.transaction do 30 | @store[key] = { updated_at: Time.now.to_i, value: value } 31 | end 32 | end 33 | 34 | def entry_has_expired(entry, ttl) 35 | Time.now.to_i > entry[:updated_at].to_i + ttl.to_i 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/danger/commands/local_helpers/local_setup.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | class LocalSetup 3 | attr_reader :dm, :cork 4 | 5 | def initialize(dangerfile, cork) 6 | @dm = dangerfile 7 | @cork = cork 8 | end 9 | 10 | def setup(verbose: false) 11 | source = dm.env.ci_source 12 | if source.nil? or source.repo_slug.empty? 13 | cork.puts "danger local failed because it only works with GitHub and Bitbucket server projects at the moment. Sorry.".red 14 | exit 0 15 | end 16 | gh = dm.env.request_source 17 | # We can use tokenless here, as it's running on someone's computer 18 | # and is IP locked, as opposed to on the CI. Only for Github PRs 19 | if gh.instance_of? Danger::RequestSources::GitHub 20 | gh.support_tokenless_auth = true 21 | end 22 | 23 | if gh.instance_of? Danger::RequestSources::BitbucketServer 24 | cork.puts "Running your Dangerfile against this PR - #{gh.host}/projects/#{source.repo_slug.split('/').first}/repos/#{source.repo_slug.split('/').last}/pull-requests/#{source.pull_request_id}" 25 | elsif gh.instance_of? Danger::RequestSources::VSTS 26 | cork.puts "Running your Dangerfile against this PR - #{gh.client.pr_api_endpoint}" 27 | else 28 | cork.puts "Running your Dangerfile against this PR - https://#{gh.host}/#{source.repo_slug}/pull/#{source.pull_request_id}" 29 | end 30 | 31 | unless verbose 32 | cork.puts "Turning on --verbose" 33 | dm.verbose = true 34 | end 35 | 36 | cork.puts 37 | 38 | begin 39 | gh.fetch_details 40 | rescue Octokit::NotFound 41 | cork.puts "Local repository was not found on GitHub. If you're trying to test a private repository please provide a valid API token through " + "DANGER_GITHUB_API_TOKEN".yellow + " environment variable." 42 | return 43 | end 44 | 45 | yield 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/danger/commands/local_helpers/pry_setup.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | class PrySetup 3 | def initialize(cork) 4 | @cork = cork 5 | end 6 | 7 | def setup_pry(dangerfile_path, command) 8 | return dangerfile_path if dangerfile_path.empty? 9 | 10 | validate_pry_available(command) 11 | FileUtils.cp dangerfile_path, DANGERFILE_COPY 12 | File.open(DANGERFILE_COPY, "a") do |f| 13 | f.write("\nbinding.pry; File.delete(\"#{DANGERFILE_COPY}\")") 14 | end 15 | DANGERFILE_COPY 16 | end 17 | 18 | private 19 | 20 | attr_reader :cork 21 | 22 | DANGERFILE_COPY = "_Dangerfile.tmp".freeze 23 | 24 | def validate_pry_available(command) 25 | Kernel.require "pry" 26 | rescue LoadError 27 | cork.warn "Pry was not found, and is required for 'danger #{command} --pry'." 28 | cork.print_warnings 29 | abort 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/danger/commands/plugins/plugin_json.rb: -------------------------------------------------------------------------------- 1 | require "danger/plugin_support/plugin_parser" 2 | require "danger/plugin_support/plugin_file_resolver" 3 | 4 | module Danger 5 | class PluginJSON < CLAide::Command::Plugins 6 | self.summary = "Lint plugins from files, gems or the current folder. Outputs JSON array representation of Plugins on success." 7 | self.command = "json" 8 | 9 | attr_accessor :cork 10 | 11 | def initialize(argv) 12 | @refs = argv.arguments! unless argv.arguments.empty? 13 | @cork = Cork::Board.new(silent: argv.option("silent", false), 14 | verbose: argv.option("verbose", false)) 15 | super 16 | end 17 | 18 | self.description = <<-DESC 19 | Converts a collection of file paths of Danger plugins into a JSON format. 20 | DESC 21 | 22 | self.arguments = [ 23 | CLAide::Argument.new("Paths, Gems or Nothing", false, true) 24 | ] 25 | 26 | def run 27 | file_resolver = PluginFileResolver.new(@refs) 28 | data = file_resolver.resolve 29 | 30 | parser = PluginParser.new(data[:paths]) 31 | parser.parse 32 | json = parser.to_json 33 | 34 | # Append gem metadata into every plugin 35 | data[:gems].each do |gem_data| 36 | json.each do |plugin| 37 | plugin[:gem_metadata] = gem_data if plugin[:gem] == gem_data[:gem] 38 | end 39 | end 40 | 41 | cork.puts json.to_json 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/danger/commands/plugins/plugin_lint.rb: -------------------------------------------------------------------------------- 1 | require "danger/plugin_support/plugin_parser" 2 | require "danger/plugin_support/plugin_file_resolver" 3 | require "danger/plugin_support/plugin_linter" 4 | 5 | module Danger 6 | class PluginLint < CLAide::Command::Plugins 7 | self.summary = "Lints a plugin" 8 | self.command = "lint" 9 | 10 | attr_accessor :cork 11 | 12 | def initialize(argv) 13 | @warnings_as_errors = argv.flag?("warnings-as-errors", false) 14 | @refs = argv.arguments! unless argv.arguments.empty? 15 | @cork = Cork::Board.new(silent: argv.option("silent", false), 16 | verbose: argv.option("verbose", false)) 17 | super 18 | end 19 | 20 | self.description = <<-DESC 21 | Converts a collection of file paths of Danger plugins into a JSON format. 22 | Note: Before 1.0, it will also parse the represented JSON to validate whether https://danger.systems would 23 | show the plugin on the website. 24 | DESC 25 | 26 | self.arguments = [ 27 | CLAide::Argument.new("Paths, Gems or Nothing", false, true) 28 | ] 29 | 30 | def self.options 31 | [ 32 | ["--warnings-as-errors", "Ensure strict linting."] 33 | ].concat(super) 34 | end 35 | 36 | def run 37 | file_resolver = PluginFileResolver.new(@refs) 38 | data = file_resolver.resolve 39 | 40 | parser = PluginParser.new(data[:paths], verbose: true) 41 | parser.parse 42 | json = parser.to_json 43 | 44 | linter = PluginLinter.new(json) 45 | linter.lint 46 | linter.print_summary(cork) 47 | 48 | abort("Failing due to errors\n".red) if linter.failed? 49 | abort("Failing due to warnings as errors\n".red) if @warnings_as_errors && !linter.warnings.empty? 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/danger/commands/plugins/plugin_readme.rb: -------------------------------------------------------------------------------- 1 | require "danger/plugin_support/plugin_parser" 2 | require "danger/plugin_support/plugin_file_resolver" 3 | require "json" 4 | require "erb" 5 | 6 | module Danger 7 | class PluginReadme < CLAide::Command::Plugins 8 | self.summary = "Generates a README from a set of plugins" 9 | self.command = "readme" 10 | 11 | attr_accessor :cork, :json, :markdown 12 | 13 | def initialize(argv) 14 | @refs = argv.arguments! unless argv.arguments.empty? 15 | @cork = Cork::Board.new(silent: argv.option("silent", false), 16 | verbose: argv.option("verbose", false)) 17 | super 18 | end 19 | 20 | self.description = <<-DESC 21 | Converts a collection of file paths of Danger plugins into a format usable in a README. 22 | This is useful for Danger itself, but also for any plugins wanting to showcase their API. 23 | DESC 24 | 25 | self.arguments = [ 26 | CLAide::Argument.new("Paths, Gems or Nothing", false, true) 27 | ] 28 | 29 | def run 30 | file_resolver = PluginFileResolver.new(@refs) 31 | data = file_resolver.resolve 32 | 33 | parser = PluginParser.new(data[:paths]) 34 | parser.parse 35 | 36 | self.json = JSON.parse(parser.to_json_string) 37 | 38 | template = File.join(Danger.gem_path, "lib/danger/plugin_support/templates/readme_table.html.erb") 39 | cork.puts ERB.new(File.read(template), trim_mode: "-").result(binding) 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/danger/commands/staging.rb: -------------------------------------------------------------------------------- 1 | require "danger/commands/local_helpers/pry_setup" 2 | require "fileutils" 3 | require "tmpdir" 4 | 5 | module Danger 6 | class Staging < Runner 7 | self.summary = "Run the Dangerfile locally against local master" 8 | self.command = "staging" 9 | 10 | def self.options 11 | [ 12 | ["--pry", "Drop into a Pry shell after evaluating the Dangerfile."] 13 | ] 14 | end 15 | 16 | def initialize(argv) 17 | show_help = true if argv.arguments == ["-h"] 18 | 19 | # Currently CLAide doesn't support short option like -h https://github.com/CocoaPods/CLAide/pull/60 20 | # when user pass in -h, mimic the behavior of passing in --help. 21 | argv = CLAide::ARGV.new ["--help"] if show_help 22 | 23 | super 24 | 25 | if argv.flag?("pry", false) 26 | @dangerfile_path = PrySetup.new(cork).setup_pry(@dangerfile_path, Staging.command) 27 | end 28 | end 29 | 30 | def validate! 31 | super 32 | unless @dangerfile_path 33 | help! "Could not find a Dangerfile." 34 | end 35 | end 36 | 37 | def run 38 | ENV["DANGER_USE_LOCAL_ONLY_GIT"] = "YES" 39 | 40 | env = EnvironmentManager.new(ENV, cork) 41 | dm = Dangerfile.new(env, cork) 42 | 43 | dm.run( 44 | Danger::EnvironmentManager.danger_base_branch, 45 | Danger::EnvironmentManager.danger_head_branch, 46 | @dangerfile_path, 47 | nil, 48 | nil, 49 | nil 50 | ) 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/danger/commands/systems.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | class Systems < Runner 3 | self.abstract_command = true 4 | self.description = "For commands related to passing information from Danger to Danger.Systems." 5 | self.summary = "Create data for Danger.Systems." 6 | end 7 | 8 | class CIDocs < Systems 9 | self.command = "ci_docs" 10 | self.summary = "Outputs the up-to-date CI documentation for Danger." 11 | 12 | def run 13 | here = File.dirname(__FILE__) 14 | ci_source_paths = Dir.glob(here + "/../ci_source/*.rb") 15 | 16 | require "yard" 17 | # Pull out all the Danger::CI subclasses docs 18 | registry = YARD::Registry.load(ci_source_paths, true) 19 | ci_sources = registry.all(:class) 20 | .select { |klass| klass.inheritance_tree.map(&:name).include? :CI } 21 | .reject { |source| source.name == :CI } 22 | .reject { |source| source.name == :LocalGitRepo } 23 | 24 | # Fail if anything is added and not documented 25 | cis_without_docs = ci_sources.select { |source| source.base_docstring.empty? } 26 | unless cis_without_docs.empty? 27 | cork.puts "Please add docs to: #{cis_without_docs.map(&:name).join(', ')}" 28 | abort("Failed.".red) 29 | end 30 | 31 | # Output a JSON array of name and details 32 | require "json" 33 | cork.puts ci_sources.map { |ci| 34 | { 35 | name: ci.name, 36 | docs: ci.docstring 37 | } 38 | }.to_json 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/danger/comment_generators/bitbucket_server.md.erb: -------------------------------------------------------------------------------- 1 | <%- @tables.each do |table| -%> 2 | <%- if table[:content].any? || table[:resolved].any? -%> 3 | | | <%= table[:count] %> <%= table[:name] %><%= "s" unless table[:count] == 1 %> | 4 | |---|---| 5 | <%- table[:content].each do |violation| -%> 6 | | <%= @emoji_mapper.map(table[:emoji]) %> | <%= violation.message %> | 7 | <%- end -%> 8 | <%- table[:resolved].each do |message| -%> 9 | | @emoji_mapper.map("white_check_mark") | <%= message %> | 10 | <%- end -%> 11 | 12 | <%- end -%> 13 | <%- end -%> 14 | 15 | <%- @markdowns.each do |current| -%> 16 | <%= current %> 17 | <%# the previous line has to be aligned far to the left, otherwise markdown can break easily %> 18 | <%- end -%> 19 | 20 | Generated by :no_entry_sign: [Danger](https://danger.systems/ "generated_by_<%= @danger_id %>") 21 | -------------------------------------------------------------------------------- /lib/danger/comment_generators/bitbucket_server_inline.md.erb: -------------------------------------------------------------------------------- 1 | <%- @tables.each do |table| -%> 2 | <%- table[:content].each do |violation| -%> 3 | <%= @emoji_mapper.map(table[:emoji]) if table[:emoji] %> <%= violation.message %> 4 | <%- end -%> 5 | <%- table[:resolved] && table[:resolved].each do |message| -%> 6 | <%= @emoji_mapper.map("white_check_mark") %> <%= message %> 7 | <%- end -%> 8 | <%- end -%> 9 | 10 | <%- @markdowns.each do |current| -%> 11 | <%= current %> 12 | <%# the previous line has to be aligned far to the left, otherwise markdown can break easily %> 13 | <%- end -%> 14 | 15 | Generated by :no_entry_sign: [Danger](https://danger.systems/ "generated_by_<%= @danger_id %>") 16 | -------------------------------------------------------------------------------- /lib/danger/comment_generators/bitbucket_server_message_group.md.erb: -------------------------------------------------------------------------------- 1 | <%- @message_group.messages.each do |message| -%> 2 | <%- next if message.type == :markdown -%> 3 | <%= @emoji_mapper.from_type(message.type) -%> <%= message.message %> 4 | 5 | <%- end -%> 6 | <%- @resolved.each do |message| -%> 7 | <%= @emoji_mapper.map("white_check_mark") %> <%= message %> 8 | <%- end -%> 9 | 10 | <%= @message_group.markdowns.map(&:message).join("\n\n") %> 11 | 12 | Generated by :no_entry_sign: [Danger](https://danger.systems/ "generated_by_<%= @danger_id %>") 13 | -------------------------------------------------------------------------------- /lib/danger/comment_generators/github.md.erb: -------------------------------------------------------------------------------- 1 | <!-- 2 | <%- @tables.each do |table| -%> 3 | <%= pluralize(table[:name], table[:count]) %><%= ": #{truncate(table[:content].first.message)}" if table[:count] > 0 %> 4 | <%- end -%> 5 | <%= pluralize("Markdown", @markdowns.size) %> 6 | --> 7 | <%- @tables.each do |table| -%> 8 | <%- if table[:content].any? || table[:resolved].any? -%> 9 | <table> 10 | <thead> 11 | <tr> 12 | <th width="50"></th> 13 | <th width="100%" data-danger-table="true" data-kind="<%= table[:name] %>"> 14 | <%- if table[:count] > 0 -%> 15 | <%= pluralize(table[:name], table[:count]) %> 16 | <%- else -%> 17 | :white_check_mark: <%= random_compliment %> 18 | <%- end -%> 19 | </th> 20 | </tr> 21 | </thead> 22 | <tbody> 23 | <%- max_num_violations = FindMaxNumViolations.new(table[:content]).call -%> 24 | <%- table[:content].take(max_num_violations).each do |violation| -%> 25 | <tr> 26 | <td>:<%= table[:emoji] %>:</td> 27 | <td data-sticky="<%= violation.sticky %>"><%= violation.message %></td> 28 | </tr> 29 | <%- end -%> 30 | <%- if table[:content].length > max_num_violations -%> 31 | <tr> 32 | <td>:warning:</td> 33 | <td> 34 | Danger found <%= table[:content].length %> violations with this PR. Due to GitHub's max issue comment size, the number shown has been truncated to <%= max_num_violations %>. 35 | </td> 36 | </tr> 37 | <%- end -%> 38 | <%- table[:resolved].each do |message| -%> 39 | <tr> 40 | <td>:white_check_mark:</td> 41 | <td data-sticky="true"><del><%= message %></del></td> 42 | </tr> 43 | <%- end -%> 44 | </tbody> 45 | </table> 46 | <%- end -%> 47 | <%- end -%> 48 | 49 | <%- @markdowns.each do |current| -%> 50 | <%= current %> 51 | <%# the previous line has to be aligned far to the left, otherwise markdown can break easily %> 52 | <%- end -%> 53 | <p align="right" data-meta="generated_by_<%= @danger_id %>"> 54 | Generated by :no_entry_sign: <a href="https://danger.systems/">Danger</a> 55 | </p> 56 | -------------------------------------------------------------------------------- /lib/danger/comment_generators/github_inline.md.erb: -------------------------------------------------------------------------------- 1 | <%- @tables.each do |table| -%> 2 | <%- if table[:content].any? -%> 3 | <table data-meta="generated_by_<%= @danger_id %>"> 4 | <tbody> 5 | <%- table[:content].each do |violation| -%> 6 | <tr> 7 | <td>:<%= table[:emoji] %>:</td> 8 | <td width="100%" data-sticky="<%= violation.sticky %>"><%= "<del>" if table[:resolved] %><%= violation.message %><%= "</del>" if table[:resolved] %></td> 9 | </tr> 10 | <%- end -%> 11 | </tbody> 12 | </table> 13 | <%- end -%> 14 | <%- end -%> 15 | 16 | <%- @markdowns.each do |current| -%> 17 | <%= current %> 18 | <%# the previous line has to be aligned far to the left, otherwise markdown can break easily %> 19 | <%- end -%> 20 | <%# We need to add the generated_by_ to identify comments from danger. But with inlines %> 21 | <%# it might be a little annoying, so we set on the table, but if we have markdown we add the footer anyway %> 22 | <%- if @markdowns.count > 0 -%> 23 | <p align="right" data-meta="generated_by_<%= @danger_id %>"> 24 | Generated by :no_entry_sign: <a href="http://danger.systems/">Danger</a> 25 | </p> 26 | <%- end -%> 27 | -------------------------------------------------------------------------------- /lib/danger/comment_generators/gitlab.md.erb: -------------------------------------------------------------------------------- 1 | <%- @tables.each do |table| -%> 2 | <%- if table[:content].any? || table[:resolved].any? -%> 3 | <table> 4 | <thead> 5 | <tr> 6 | <th width="5%"></th> 7 | <th width="95%" data-danger-table="true" data-kind="<%= table[:name] %>"> 8 | <%- if table[:count] > 0 -%> 9 | <%= table[:count] %> <%= table[:name] %><%= "s" unless table[:count] == 1 %> 10 | <%- else -%> 11 | :white_check_mark: <%= random_compliment %> 12 | <%- end -%> 13 | </th> 14 | </tr> 15 | </thead> 16 | <tbody> 17 | <%- table[:content].each do |violation| -%> 18 | <tr> 19 | <td>:<%= table[:emoji] %>:</td> 20 | <td data-sticky="<%= violation.sticky %>"><%= violation.message %></td> 21 | </tr> 22 | <%- end -%> 23 | <%- table[:resolved].each do |message| -%> 24 | <tr> 25 | <td>:white_check_mark:</td> 26 | <td data-sticky="true"><del><%= message %></del></td> 27 | </tr> 28 | <%- end -%> 29 | </tbody> 30 | </table> 31 | <%- end -%> 32 | <%- end -%> 33 | 34 | <%- @markdowns.each do |current| -%> 35 | <%= current %> 36 | <%# the previous line has to be aligned far to the left, otherwise markdown can break easily %> 37 | <%- end -%> 38 | <p align="right" data-meta="generated_by_<%= @danger_id %>"> 39 | Generated by :no_entry_sign: <a href="https://github.com/danger/danger/">Danger</a> 40 | </p> 41 | -------------------------------------------------------------------------------- /lib/danger/comment_generators/gitlab_inline.md.erb: -------------------------------------------------------------------------------- 1 | <%- @tables.each do |table| -%> 2 | <%- if table[:content].any? -%> 3 | <table data-meta="generated_by_<%= @danger_id %>"> 4 | <tbody> 5 | <%- table[:content].each do |violation| -%> 6 | <tr> 7 | <td>:<%= table[:emoji] %>:</td> 8 | <td width="100%" data-sticky="<%= violation.sticky %>"><%= "<del>" if table[:resolved] %><%= violation.message %><%= "</del>" if table[:resolved] %></td> 9 | </tr> 10 | <%- end -%> 11 | </tbody> 12 | </table> 13 | <%- end -%> 14 | <%- end -%> 15 | 16 | <%- @markdowns.each do |current| -%> 17 | <%= current %> 18 | <%# the previous line has to be aligned far to the left, otherwise markdown can break easily %> 19 | <%- end -%> 20 | <%# Add the generated_by_ as a html comment to identify comments from danger. %> 21 | <!-- "generated_by_<%= @danger_id %>" --> 22 | -------------------------------------------------------------------------------- /lib/danger/comment_generators/vsts.md.erb: -------------------------------------------------------------------------------- 1 | <%- @tables.each do |table| -%> 2 | <%- if table[:content].any? || table[:resolved].any? -%> 3 | | | <%= table[:count] %> <%= table[:name] %><%= "s" unless table[:count] == 1 %> | 4 | |---|---| 5 | <%- table[:content].each do |violation| -%> 6 | | <%= @emoji_mapper.map(table[:emoji]) %> | <%= violation.message %> | 7 | <%- end -%> 8 | <%- table[:resolved].each do |message| -%> 9 | | @emoji_mapper.map("white_check_mark") | <%= message %> | 10 | <%- end -%> 11 | 12 | <%- end -%> 13 | <%- end -%> 14 | 15 | <%- @markdowns.each do |current| -%> 16 | <%= current %> 17 | <%# the previous line has to be aligned far to the left, otherwise markdown can break easily %> 18 | <%- end -%> 19 | 20 | Generated by :no_entry_sign: [Danger](https://danger.systems/ "generated_by_<%= @danger_id %>") 21 | -------------------------------------------------------------------------------- /lib/danger/comment_generators/vsts_inline.md.erb: -------------------------------------------------------------------------------- 1 | <%- @tables.each do |table| -%> 2 | <%- if table[:content].any? || table[:resolved].any? -%> 3 | | | | 4 | |---|---| 5 | <%- table[:content].each do |violation| -%> 6 | | <%= @emoji_mapper.map(table[:emoji]) %> | <%= "~~" if table[:resolved] %><%= violation.message %><%= "~~" if table[:resolved] %> | 7 | <%- end -%> 8 | 9 | <%- end -%> 10 | <%- end -%> 11 | 12 | <%- @markdowns.each do |current| -%> 13 | <%= current %> 14 | <%# the previous line has to be aligned far to the left, otherwise markdown can break easily %> 15 | <%- end -%> 16 | 17 | Generated by :no_entry_sign: [Danger](https://danger.systems/ "generated_by_<%= @danger_id %>") 18 | -------------------------------------------------------------------------------- /lib/danger/core_ext/file_list.rb: -------------------------------------------------------------------------------- 1 | require "danger/helpers/array_subclass" 2 | 3 | module Danger 4 | class FileList 5 | include Helpers::ArraySubclass 6 | 7 | # Information about pattern: http://ruby-doc.org/core-2.2.0/File.html#method-c-fnmatch 8 | # e.g. "**/something.*" for any file called something with any extension 9 | def include?(pattern) 10 | self.each do |current| 11 | if !current.nil? && (File.fnmatch(pattern, current, File::FNM_EXTGLOB) || pattern == current) 12 | return true 13 | end 14 | end 15 | return false 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /lib/danger/core_ext/string.rb: -------------------------------------------------------------------------------- 1 | class String 2 | # @return [String] the plural form of self determined by count 3 | def danger_pluralize(count) 4 | "#{count} #{self}#{'s' unless count == 1}" 5 | end 6 | 7 | # @return [String] converts to underscored, lowercase form 8 | def danger_underscore 9 | self.gsub(/::/, "/".freeze). 10 | gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2'.freeze). 11 | gsub(/([a-z\d])([A-Z])/, '\1_\2'.freeze). 12 | tr("-".freeze, "_".freeze). 13 | downcase 14 | end 15 | 16 | # @return [String] truncates string with ellipsis when exceeding the limit 17 | def danger_truncate(limit) 18 | length > limit ? "#{self[0...limit]}..." : self 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/danger/danger_core/dangerfile_dsl.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | class Dangerfile 3 | # Anything inside this module is considered public API, and in the future 4 | # documentation will be generated from it via rdoc. 5 | 6 | module DSL 7 | # @!group Danger Zone 8 | # Provides access to the raw Travis/Circle/Buildkite/GitHub objects, which 9 | # you can use to pull out extra bits of information. _Warning_ 10 | # the interfaces of these objects is **not** considered a part of the Dangerfile public 11 | # API, and is viable to change occasionally on the whims of developers. 12 | # @return [EnvironmentManager] 13 | 14 | attr_reader :env 15 | 16 | private 17 | 18 | def initialize 19 | load_default_plugins 20 | end 21 | 22 | def load_default_plugins 23 | Dir["./danger_plugins/*.rb"].each do |file| 24 | require File.expand_path(file) 25 | end 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/danger/danger_core/dangerfile_generator.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | class DangerfileGenerator 3 | # returns the string for a Dangerfile based on a folder's contents' 4 | def self.create_dangerfile(_path, _ui) 5 | # Use this template for now, but this is a really ripe place to 6 | # improve now! 7 | dir = Danger.gem_path 8 | File.read(File.join(dir, "lib", "assets", "DangerfileTemplate")) 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/danger/danger_core/message_aggregator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "danger/danger_core/message_group" 4 | require "danger/helpers/message_groups_array_helper" 5 | 6 | module Danger 7 | class MessageAggregator 8 | def self.aggregate(*args, **kwargs) 9 | new(*args, **kwargs).aggregate 10 | end 11 | 12 | def initialize(warnings: [], 13 | errors: [], 14 | messages: [], 15 | markdowns: [], 16 | danger_id: "danger") 17 | @messages = warnings + errors + messages + markdowns 18 | @danger_id = danger_id 19 | end 20 | 21 | # aggregates the messages into an array of MessageGroups 22 | # @return [[MessageGroup]] 23 | def aggregate 24 | # oookay I took some shortcuts with this one. 25 | # first, sort messages by file and line 26 | @messages.sort! { |a, b| a.compare_by_file_and_line(b) } 27 | 28 | # now create an initial empty message group 29 | first_group = MessageGroup.new(file: nil, 30 | line: nil) 31 | @message_groups = @messages.reduce([first_group]) do |groups, msg| 32 | # We get to take a shortcut because we sorted the messages earlier - only 33 | # have to see if we can append msg to the last group in the list 34 | if groups.last << msg 35 | # we appended it, so return groups unchanged 36 | groups 37 | else 38 | # have to create a new group since msg wasn't appended to the other 39 | # group 40 | new_group = MessageGroup.new(file: msg.file, 41 | line: msg.line) 42 | new_group << msg 43 | groups << new_group 44 | end 45 | end 46 | 47 | @message_groups.extend(Helpers::MessageGroupsArrayHelper) 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/danger/danger_core/message_group.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Danger 4 | class MessageGroup 5 | def initialize(file: nil, line: nil) 6 | @file = file 7 | @line = line 8 | end 9 | 10 | # Returns whether this `MessageGroup` is for the same line of code as 11 | # `other`, taking which file they are in to account. 12 | # @param other [MessageGroup, Markdown, Violation] 13 | # @return [Boolean] whether this `MessageGroup` is for the same line of code 14 | def same_line?(other) 15 | other.file == file && other.line == line 16 | end 17 | 18 | # Merges two `MessageGroup`s that represent the same line of code 19 | # In future, perhaps `MessageGroup` will be able to represent a group of 20 | # messages for multiple lines. 21 | def merge(other) 22 | raise ArgumentError, "Cannot merge with MessageGroup for a different line" unless same_line?(other) 23 | 24 | @messages = (messages + other.messages).uniq 25 | end 26 | 27 | # Adds a message to the group. 28 | # @param message [Markdown, Violation] the message to add 29 | def <<(message) 30 | # TODO: insertion sort 31 | return nil unless same_line?(message) 32 | 33 | inserted = false 34 | messages.each.with_index do |other, idx| 35 | if (message <=> other) == -1 36 | inserted = true 37 | messages.insert(idx, message) 38 | break 39 | end 40 | end 41 | messages << message unless inserted 42 | messages 43 | end 44 | 45 | # The list of messages in this group. This list will be sorted in decreasing 46 | # order of severity (error, warning, message, markdown) 47 | def messages 48 | @messages ||= [] 49 | end 50 | 51 | attr_reader :file, :line 52 | 53 | # @return a hash of statistics. Currently only :warnings_count and 54 | # :errors_count 55 | def stats 56 | stats = { warnings_count: 0, errors_count: 0 } 57 | messages.each do |msg| 58 | stats[:warnings_count] += 1 if msg.type == :warning 59 | stats[:errors_count] += 1 if msg.type == :error 60 | end 61 | stats 62 | end 63 | 64 | def markdowns 65 | messages.select { |x| x.type == :markdown } 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lib/danger/danger_core/messages/base.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | class BaseMessage 3 | attr_accessor :message, :file, :line, :type 4 | 5 | def initialize(type:, message:, file: nil, line: nil) 6 | @type = type 7 | @message = message 8 | @file = file 9 | @line = line 10 | end 11 | 12 | def compare_by_file_and_line(other) 13 | order = cmp_nils(file, other.file) 14 | return order unless order.nil? 15 | 16 | order = file <=> other.file 17 | return order unless order.zero? 18 | 19 | order = cmp_nils(line, other.line) 20 | return order unless order.nil? 21 | 22 | line <=> other.line 23 | end 24 | 25 | # compares a and b based entirely on whether one or the other is nil 26 | # arguments are in the same order as `a <=> b` 27 | # nil is sorted earlier - so cmp_nils(nil, 1) => -1 28 | # 29 | # If neither are nil, rather than returning `a <=> b` which would seem 30 | # like the obvious shortcut, `nil` is returned. 31 | # This allows us to distinguish between cmp_nils returning 0 for a 32 | # comparison of filenames, which means "a comparison on the lines is 33 | # meaningless - you cannot have a line number for a nil file - so they 34 | # should be sorted the same", and a <=> b returning 0, which means "the 35 | # files are the same, so compare on the lines" 36 | # 37 | # @return 0, 1, -1, or nil 38 | def cmp_nils(a, b) 39 | if a.nil? && b.nil? 40 | 0 41 | elsif a.nil? 42 | -1 43 | elsif b.nil? 44 | 1 45 | end 46 | end 47 | 48 | def eql?(other) 49 | return self == other 50 | end 51 | 52 | # @return [Boolean] returns true if is a file or line, false otherwise 53 | def inline? 54 | file || line 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/danger/danger_core/messages/markdown.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "danger/danger_core/messages/base" 4 | 5 | module Danger 6 | class Markdown < BaseMessage 7 | def initialize(message, file = nil, line = nil) 8 | super(type: :markdown, message: message, file: file, line: line) 9 | end 10 | 11 | def ==(other) 12 | return false if other.nil? 13 | return false unless other.kind_of? self.class 14 | 15 | other.message == message && 16 | other.file == file && 17 | other.line == line 18 | end 19 | 20 | def hash 21 | h = 1 22 | h = h * 31 + message.hash 23 | h = h * 17 + file.hash 24 | h * 17 + line.hash 25 | end 26 | 27 | def to_s 28 | extra = [] 29 | extra << "file: #{file}" unless file 30 | extra << "line: #{line}" unless line 31 | 32 | "Markdown #{message} { #{extra.join ', '} }" 33 | end 34 | 35 | def <=>(other) 36 | return 1 if other.type != :markdown 37 | 38 | compare_by_file_and_line(other) 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/danger/danger_core/messages/violation.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "danger/danger_core/messages/base" 4 | 5 | module Danger 6 | class Violation < BaseMessage 7 | VALID_TYPES = %I[error warning message].freeze 8 | attr_accessor :sticky 9 | 10 | def initialize(message, sticky, file = nil, line = nil, type: :warning) 11 | raise ArgumentError unless VALID_TYPES.include?(type) 12 | 13 | super(type: type, message: message, file: file, line: line) 14 | self.sticky = sticky 15 | end 16 | 17 | def ==(other) 18 | return false if other.nil? 19 | return false unless other.kind_of? self.class 20 | 21 | other.message == message && 22 | other.sticky == sticky && 23 | other.file == file && 24 | other.line == line 25 | end 26 | 27 | def hash 28 | h = 1 29 | h = h * 31 + message.hash 30 | h = h * 13 + sticky.hash 31 | h = h * 17 + file.hash 32 | h * 17 + line.hash 33 | end 34 | 35 | def <=>(other) 36 | types = VALID_TYPES + [:markdown] 37 | order = types.index(type) <=> types.index(other.type) 38 | return order unless order.zero? 39 | 40 | compare_by_file_and_line(other) 41 | end 42 | 43 | def to_s 44 | extra = [] 45 | extra << "sticky: #{sticky}" 46 | extra << "file: #{file}" if file 47 | extra << "line: #{line}" if line 48 | extra << "type: #{type}" 49 | 50 | "Violation #{message} { #{extra.join ', '} }" 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /lib/danger/danger_core/plugins/dangerfile_local_only_plugin.rb: -------------------------------------------------------------------------------- 1 | require "danger/plugin_support/plugin" 2 | 3 | # Danger 4 | module Danger 5 | # Handles interacting with local only plugin inside a Dangerfile. 6 | # It is support plugin for dry_run command and does not expose any methods. 7 | # But you can still use other plugins like git 8 | # 9 | # @example Check that added lines contains agreed form of words 10 | # 11 | # git.diff.each do |chunk| 12 | # chunk.patch.lines.grep(/^\+/).each do |added_line| 13 | # if added_line.gsub!(/(?<cancel>cancel)(?<rest>[^l[[:space:]][[:punct:]]]+)/i, '>>\k<cancel>-l-\k<rest><<') 14 | # fail "Single 'L' for cancellation-alike words in '#{added_line}'" 15 | # end 16 | # end 17 | # end 18 | # 19 | # @see danger/danger 20 | # @tags core, local_only 21 | # 22 | class DangerfileLocalOnlyPlugin < Plugin 23 | # So that this init can fail. 24 | def self.new(dangerfile) 25 | return nil if dangerfile.env.request_source.class != Danger::RequestSources::LocalOnly 26 | 27 | super 28 | end 29 | 30 | def initialize(dangerfile) 31 | super(dangerfile) 32 | 33 | @local_repo = dangerfile.env.request_source 34 | end 35 | 36 | # The instance name used in the Dangerfile 37 | # @return [String] 38 | # 39 | def self.instance_name 40 | "local_repo" 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/danger/helpers/array_subclass.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | module Helpers 3 | module ArraySubclass 4 | include Comparable 5 | 6 | def initialize(array) 7 | @__array__ = array 8 | end 9 | 10 | def kind_of?(compare_class) 11 | return true if compare_class == self.class 12 | 13 | dummy.kind_of?(compare_class) 14 | end 15 | 16 | def method_missing(name, *args, &block) 17 | super unless __array__.respond_to?(name) 18 | 19 | respond_to_method(name, *args, &block) 20 | end 21 | 22 | def respond_to_missing?(name, include_all) 23 | __array__.respond_to?(name, include_all) || super 24 | end 25 | 26 | def to_a 27 | __array__ 28 | end 29 | 30 | def to_ary 31 | __array__ 32 | end 33 | 34 | def <=>(other) 35 | return unless other.kind_of?(self.class) 36 | 37 | __array__ <=> other.instance_variable_get(:@__array__) 38 | end 39 | 40 | private 41 | 42 | attr_accessor :__array__ 43 | 44 | def dummy 45 | Class.new(Array).new 46 | end 47 | 48 | def respond_to_method(name, *args, &block) 49 | result = __array__.send(name, *args, &block) 50 | return result unless result.kind_of?(Array) 51 | 52 | if name =~ /!/ 53 | __array__ = result 54 | self 55 | else 56 | self.class.new(result) 57 | end 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/danger/helpers/comment.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | class Comment 3 | attr_reader :id, :body 4 | 5 | def initialize(id, body, inline = nil) 6 | @id = id 7 | @body = body 8 | @inline = inline 9 | end 10 | 11 | def self.from_github(comment) 12 | self.new(comment["id"], comment["body"]) 13 | end 14 | 15 | def self.from_gitlab(comment) 16 | if comment.respond_to?(:id) && comment.respond_to?(:body) 17 | type = comment.respond_to?(:type) ? comment.type : nil 18 | self.new(comment.id, comment.body, type == "DiffNote") 19 | else 20 | self.new(comment["id"], comment["body"], comment["type"] == "DiffNote") 21 | end 22 | end 23 | 24 | def generated_by_danger?(danger_id) 25 | body.include?("\"generated_by_#{danger_id}\"") 26 | end 27 | 28 | def inline? 29 | @inline.nil? ? body.include?("") : @inline 30 | end 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /lib/danger/helpers/comments_parsing_helper.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | module Helpers 3 | module CommentsParsingHelper 4 | # @!group Extension points 5 | # Produces a message-like from a row in a comment table 6 | # 7 | # @param [String] row 8 | # The content of the row in the table 9 | # 10 | # @return [Violation or Markdown] the extracted message 11 | def parse_message_from_row(row) 12 | Violation.new(row, true) 13 | end 14 | 15 | # @endgroup 16 | 17 | def parse_tables_from_comment(comment) 18 | comment.split("</table>") 19 | end 20 | 21 | def violations_from_table(table) 22 | row_regex = %r{<td data-sticky="true">(?:<del>)?(.*?)(?:</del>)?\s*</td>}im 23 | table.scan(row_regex).flatten.map do |row| 24 | parse_message_from_row(row.strip) 25 | end 26 | end 27 | 28 | def parse_comment(comment) 29 | tables = parse_tables_from_comment(comment) 30 | violations = {} 31 | tables.each do |table| 32 | match = danger_table?(table) 33 | next unless match 34 | 35 | title = match[1] 36 | kind = table_kind_from_title(title) 37 | next unless kind 38 | 39 | violations[kind] = violations_from_table(table) 40 | end 41 | 42 | violations.reject { |_, v| v.empty? } 43 | end 44 | 45 | def table_kind_from_title(title) 46 | if title =~ /error/i 47 | :error 48 | elsif title =~ /warning/i 49 | :warning 50 | elsif title =~ /message/i 51 | :message 52 | end 53 | end 54 | 55 | private 56 | 57 | GITHUB_OLD_REGEX = %r{<th width="100%"(.*?)</th>}im.freeze 58 | NEW_REGEX = %r{<th.*data-danger-table="true"(.*?)</th>}im.freeze 59 | 60 | def danger_table?(table) 61 | # The old GitHub specific method relied on 62 | # the width of a `th` element to find the table 63 | # title and determine if it was a danger table. 64 | # The new method uses a more robust data-danger-table 65 | # tag instead. 66 | match = GITHUB_OLD_REGEX.match(table) 67 | return match if match 68 | 69 | return NEW_REGEX.match(table) 70 | end 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /lib/danger/helpers/emoji_mapper.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | class EmojiMapper 3 | DATA = { 4 | "github" => { 5 | "no_entry_sign" => "🚫", 6 | "warning" => "⚠️", 7 | "book" => "📖", 8 | "white_check_mark" => "✅" 9 | }, 10 | "bitbucket_server" => { 11 | "no_entry_sign" => ":no_entry_sign:", 12 | "warning" => ":warning:", 13 | "book" => ":blue_book:", 14 | "white_check_mark" => ":white_check_mark:" 15 | } 16 | }.freeze 17 | 18 | TYPE_TO_EMOJI = { 19 | error: "no_entry_sign", 20 | warning: "warning", 21 | message: "book" 22 | }.freeze 23 | 24 | def initialize(template) 25 | @template = DATA.has_key?(template) ? template : "github" 26 | end 27 | 28 | def map(emoji) 29 | emoji&.delete! ":" 30 | DATA[template][emoji] 31 | end 32 | 33 | def from_type(type) 34 | map(TYPE_TO_EMOJI[type]) 35 | end 36 | 37 | private 38 | 39 | attr_reader :template 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/danger/helpers/find_max_num_violations.rb: -------------------------------------------------------------------------------- 1 | # Find max_num_violations in lib/danger/comment_generators/github.md.erb. 2 | class FindMaxNumViolations 3 | # Save ~ 5000 for contents other than violations to avoid exceeded 65536 max comment length limit. 4 | LIMIT = 60_000 5 | 6 | def initialize(violations) 7 | @violations = violations 8 | end 9 | 10 | def call 11 | total = 0 12 | num_of_violations_allowed = 0 13 | 14 | violations.each do |violation| 15 | message_length = violation.message.length + 80 # 80 is ~ the size of html wraps violation message. 16 | 17 | if total + message_length < LIMIT 18 | total += message_length 19 | num_of_violations_allowed += 1 20 | else 21 | break 22 | end 23 | end 24 | 25 | num_of_violations_allowed 26 | end 27 | 28 | private 29 | 30 | attr_reader :violations 31 | end 32 | -------------------------------------------------------------------------------- /lib/danger/helpers/message_groups_array_helper.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | module Helpers 3 | module MessageGroupsArrayHelper 4 | FakeArray = Struct.new(:count) do 5 | def empty? 6 | count.zero? 7 | end 8 | end 9 | 10 | def fake_warnings_array 11 | FakeArray.new(counts[:warnings]) 12 | end 13 | 14 | def fake_errors_array 15 | FakeArray.new(counts[:errors]) 16 | end 17 | 18 | def counts 19 | return @counts if @counts 20 | 21 | @counts = { warnings: 0, errors: 0 } 22 | each do |message_group, counts| 23 | group_stats = message_group.stats 24 | @counts[:warnings] += group_stats[:warnings_count] 25 | @counts[:errors] += group_stats[:errors_count] 26 | end 27 | @counts 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/danger/plugin_support/gems_resolver.rb: -------------------------------------------------------------------------------- 1 | require "bundler" 2 | 3 | module Danger 4 | class GemsResolver 5 | def initialize(gem_names) 6 | @gem_names = gem_names 7 | @dir = Dir.mktmpdir # We want it to persist until OS cleans it on reboot 8 | end 9 | 10 | # Returns an Array of paths (plugin lib file paths) and gems (of metadata) 11 | def call 12 | path_gems = [] 13 | 14 | Bundler.with_clean_env do 15 | Dir.chdir(dir) do 16 | create_gemfile_from_gem_names 17 | `bundle install --path vendor/gems` 18 | path_gems = all_gems_metadata 19 | end 20 | end 21 | 22 | return path_gems 23 | end 24 | 25 | private 26 | 27 | attr_reader :gem_names, :dir 28 | 29 | def all_gems_metadata 30 | return paths, gems 31 | end 32 | 33 | def create_gemfile_from_gem_names 34 | gemfile = File.new("Gemfile", "w") 35 | gemfile.write "source 'https://rubygems.org'" 36 | 37 | gem_names.each do |plugin| 38 | gemfile.write "\ngem '#{plugin}'" 39 | end 40 | 41 | gemfile.close 42 | end 43 | 44 | # The paths are relative to dir. 45 | def paths 46 | relative_paths = gem_names.flat_map do |plugin| 47 | Dir.glob("vendor/gems/ruby/*/gems/#{plugin}*/lib/**/**/**/**.rb") 48 | end 49 | 50 | relative_paths.map { |path| File.join(dir, path) } 51 | end 52 | 53 | def gems 54 | real_gems.map { |gem| gem_metadata(gem) } 55 | end 56 | 57 | def real_gems 58 | spec_paths = gem_names.flat_map do |plugin| 59 | Dir.glob("vendor/gems/ruby/*/specifications/#{plugin}*.gemspec").first 60 | end 61 | 62 | spec_paths.map { |path| Gem::Specification.load path } 63 | end 64 | 65 | def gem_metadata(gem) 66 | { 67 | name: gem.name, 68 | gem: gem.name, 69 | author: gem.authors, 70 | url: gem.homepage, 71 | description: gem.summary, 72 | license: gem.license || "Unknown", 73 | version: gem.version.to_s 74 | } 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /lib/danger/plugin_support/plugin.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | class Plugin 3 | def initialize(dangerfile) 4 | @dangerfile = dangerfile 5 | end 6 | 7 | def self.instance_name 8 | to_s.gsub("Danger", "").danger_underscore.split("/").last 9 | end 10 | 11 | # Both of these methods exist on all objects 12 | # http://ruby-doc.org/core-2.2.3/Kernel.html#method-i-warn 13 | # http://ruby-doc.org/core-2.2.3/Kernel.html#method-i-fail 14 | # However, as we're using using them in the DSL, they won't 15 | # get method_missing called correctly. 16 | undef :warn, :fail 17 | 18 | # Since we have a reference to the Dangerfile containing all the information 19 | # We need to redirect the self calls to the Dangerfile 20 | 21 | def method_missing(method_sym, *arguments, **keyword_arguments, &block) 22 | if keyword_arguments.empty? 23 | @dangerfile.send(method_sym, *arguments, &block) 24 | else 25 | @dangerfile.send(method_sym, *arguments, **keyword_arguments, &block) 26 | end 27 | end 28 | 29 | def self.all_plugins 30 | @all_plugins ||= [] 31 | end 32 | 33 | def self.clear_external_plugins 34 | @all_plugins = @all_plugins.select { |plugin| Dangerfile.essential_plugin_classes.include? plugin } 35 | end 36 | 37 | def self.inherited(plugin) 38 | Plugin.all_plugins.push(plugin) 39 | end 40 | 41 | private 42 | 43 | # When using `danger local --pry`, every plugin had an unreasonable 44 | # amount of text output due to the Dangerfile reference in every 45 | # plugin. So, it is filtered out. Users will start out in the context 46 | # of the Dangerfile, and can view it by just typing `self` into the REPL. 47 | # 48 | def pretty_print_instance_variables 49 | super - [:@dangerfile] 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/danger/plugin_support/plugin_file_resolver.rb: -------------------------------------------------------------------------------- 1 | require "danger/plugin_support/gems_resolver" 2 | 3 | module Danger 4 | class PluginFileResolver 5 | # Takes an array of files, gems or nothing, then resolves them into 6 | # paths that should be sent into the documentation parser 7 | def initialize(references) 8 | @refs = references 9 | end 10 | 11 | # When given existing paths, map to absolute & existing paths 12 | # When given a list of gems, resolve for list of gems 13 | # When empty, imply you want to test the current lib folder as a plugin 14 | def resolve 15 | if !refs.nil? and refs.select { |ref| File.file? ref }.any? 16 | paths = refs.select { |ref| File.file? ref }.map { |path| File.expand_path(path) } 17 | elsif refs and refs.kind_of? Array 18 | paths, gems = GemsResolver.new(refs).call 19 | else 20 | paths = Dir.glob(File.join(".", "lib/**/*.rb")).map { |path| File.expand_path(path) } 21 | end 22 | 23 | { paths: paths, gems: gems || [] } 24 | end 25 | 26 | private 27 | 28 | attr_reader :refs 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/danger/plugin_support/templates/readme_table.html.erb: -------------------------------------------------------------------------------- 1 | <% json.each do |plugin| %> 2 | 3 | ### <%= plugin["instance_name"] %> 4 | 5 | <%= plugin["body_md"] %> 6 | <%- plugin["example_code"].each do |example| %> 7 | <blockquote><%= example["title"] %> 8 | <pre><%= example["text"] %></pre> 9 | </blockquote> 10 | <%- end %> 11 | 12 | <%- unless plugin["attributes"].empty? %> 13 | #### Attributes 14 | <%- plugin["attributes"].each do |attribute| %> 15 | `<%= attribute.keys.first %>` - <%= attribute.values.first["write"]["body_md"] %> 16 | <%- end %> 17 | <%- end %> 18 | 19 | <%- unless plugin["methods"].empty? %> 20 | #### Methods 21 | <%- plugin["methods"].each do |method| %> 22 | `<%= method["name"] %>` - <%= method["body_md"] %> 23 | <%- end %> 24 | <%- end %> 25 | 26 | <% end %> 27 | -------------------------------------------------------------------------------- /lib/danger/request_sources/github/github_review_resolver.rb: -------------------------------------------------------------------------------- 1 | require "danger/request_sources/github/github_review" 2 | 3 | module Danger 4 | module RequestSources 5 | module GitHubSource 6 | class ReviewResolver 7 | def self.should_submit?(review, body) 8 | return !same_body?(body, review.body) 9 | end 10 | 11 | def self.same_body?(body1, body2) 12 | return !body1.nil? && !body2.nil? && body1 == body2 13 | end 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/danger/request_sources/github/github_review_unsupported.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | module RequestSources 3 | module GitHubSource 4 | class ReviewUnsupported 5 | attr_reader :id, :body, :status, :review_json 6 | 7 | def initialize; end 8 | 9 | def start; end 10 | 11 | def submit; end 12 | 13 | def message(message, sticky = true, file = nil, line = nil); end 14 | 15 | def warn(message, sticky = true, file = nil, line = nil); end 16 | 17 | def fail(message, sticky = true, file = nil, line = nil); end 18 | 19 | def markdown(message, file = nil, line = nil); end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/danger/request_sources/local_only.rb: -------------------------------------------------------------------------------- 1 | require "danger/helpers/comments_helper" 2 | require "danger/helpers/comment" 3 | 4 | module Danger 5 | module RequestSources 6 | class LocalOnly < RequestSource 7 | include Danger::Helpers::CommentsHelper 8 | attr_accessor :mr_json, :commits_json 9 | 10 | def self.env_vars 11 | ["DANGER_LOCAL_ONLY"] 12 | end 13 | 14 | def initialize(ci_source, _environment) 15 | self.ci_source = ci_source 16 | end 17 | 18 | def validates_as_ci? 19 | true 20 | end 21 | 22 | def validates_as_api_source? 23 | true 24 | end 25 | 26 | def scm 27 | @scm ||= GitRepo.new 28 | end 29 | 30 | def setup_danger_branches 31 | # Check that discovered values really exists 32 | [ci_source.base_commit, ci_source.head_commit].each do |commit| 33 | raise "Specified commit '#{commit}' not found" if scm.exec("rev-parse --quiet --verify #{commit}").empty? 34 | end 35 | 36 | self.scm.exec "branch #{EnvironmentManager.danger_base_branch} #{ci_source.base_commit}" 37 | self.scm.exec "branch #{EnvironmentManager.danger_head_branch} #{ci_source.head_commit}" 38 | end 39 | 40 | def fetch_details; end 41 | 42 | def update_pull_request!(_hash_needed); end 43 | 44 | # @return [String] The organisation name, is nil if it can't be detected 45 | def organisation 46 | nil 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /lib/danger/request_sources/support/get_ignored_violation.rb: -------------------------------------------------------------------------------- 1 | class GetIgnoredViolation 2 | IGNORE_REGEXP = />*\s*danger\s*:\s*ignore\s*"(?<error>[^"]*)"/i.freeze 3 | 4 | def initialize(body) 5 | @body = body 6 | end 7 | 8 | def call 9 | return [] unless body 10 | 11 | body.chomp.scan(IGNORE_REGEXP).flatten 12 | end 13 | 14 | private 15 | 16 | attr_reader :body 17 | end 18 | -------------------------------------------------------------------------------- /lib/danger/version.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | VERSION = "9.5.3".freeze 3 | DESCRIPTION = "Like Unit Tests, but for your Team Culture.".freeze 4 | end 5 | -------------------------------------------------------------------------------- /spec/clients/rubygems_client_spec.rb: -------------------------------------------------------------------------------- 1 | require "danger/clients/rubygems_client" 2 | 3 | RSpec.describe Danger::RubyGemsClient do 4 | describe ".latest_danger_version" do 5 | context "rubygems.org is operational" do 6 | it "returns latest danger version" do 7 | latest_version_json = IO.read("spec/fixtures/rubygems_api/api_v1_versions_danger_latest.json") 8 | allow(Faraday).to receive_message_chain(:get, :body) { latest_version_json } 9 | 10 | result = described_class.latest_danger_version 11 | 12 | expect(result).to eq "3.1.1" 13 | end 14 | end 15 | 16 | context "user does not have network connection" do 17 | it "returns dummy version" do 18 | allow(Faraday).to receive_message_chain(:get, :body) { raise Faraday::ConnectionFailed } 19 | 20 | result = described_class.latest_danger_version 21 | 22 | expect(result).to eq described_class.const_get(:DUMMY_VERSION) 23 | end 24 | end 25 | 26 | context "rubygems.org is not operational" do 27 | it "returns dummy version" do 28 | allow(Faraday).to receive_message_chain(:get, :body) { raise "RubyGems.org is down 🔥" } 29 | 30 | result = described_class.latest_danger_version 31 | 32 | expect(result).to eq described_class.const_get(:DUMMY_VERSION) 33 | end 34 | end 35 | 36 | context "rubygems.org returns wrong data" do 37 | it "returns dummy version" do 38 | allow(Faraday).to receive_message_chain(:get, :body) { ["", nil].sample } 39 | 40 | result = described_class.latest_danger_version 41 | 42 | expect(result).to eq described_class.const_get(:DUMMY_VERSION) 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /spec/danger/helpers/comment_spec.rb: -------------------------------------------------------------------------------- 1 | require "danger/helpers/comment" 2 | 3 | RSpec.describe Danger::Comment do 4 | describe ".from_github" do 5 | it "initializes with GitHub comment data structure" do 6 | github_comment = { "id" => 42, "body" => "github comment" } 7 | 8 | result = described_class.from_github(github_comment) 9 | 10 | expect(result).to have_attributes(id: 42, body: "github comment") 11 | end 12 | end 13 | 14 | describe ".from_gitlab" do 15 | it "initializes with Gitlab comment data structure" do 16 | GitlabComment = Struct.new(:id, :body) 17 | gitlab_comment = GitlabComment.new(42, "gitlab comment") 18 | 19 | result = described_class.from_gitlab(gitlab_comment) 20 | 21 | expect(result).to have_attributes(id: 42, body: "gitlab comment") 22 | end 23 | end 24 | 25 | describe "#generated_by_danger?" do 26 | it "returns true when body contains generated_by_{identifier}" do 27 | comment = described_class.new(42, '"generated_by_orta"') 28 | 29 | expect(comment.generated_by_danger?("orta")).to be true 30 | end 31 | 32 | it "returns false when body NOT contains generated_by_{identifier}" do 33 | comment = described_class.new(42, '"generated_by_orta"') 34 | 35 | expect(comment.generated_by_danger?("artsy")).to be false 36 | end 37 | 38 | it "returns false when identifier is a substring of actual identifier" do 39 | comment = described_class.new(42, '"generated_by_danger2"') 40 | 41 | expect(comment.generated_by_danger?("danger")).to be false 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /spec/fixtures/bitbucket_cloud_api/oauth2_response.json: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | 3 | { 4 | "access_token": "a_token", 5 | "scopes": "pullrequest:write", 6 | "expires_in": 7200, 7 | "refresh_token": "a_refresh", 8 | "token_type": "bearer" 9 | } -------------------------------------------------------------------------------- /spec/fixtures/bitbucket_server_api/pr_response.json: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | 3 | {"id":2080,"version":1,"title":"This is a danger test","description":"This PR will be used for [Danger](http://danger.systems) tests.\r\n","state":"DECLINED","open":false,"closed":true,"createdDate":1470864800248,"updatedDate":1470864810102,"fromRef":{"id":"refs/heads/feature/Danger","displayId":"feature/Danger","latestCommit":"c50b3f61e90dac6a00b7d0c92e415a4348bb280a","repository":{"slug":"fancyapp","id":38,"name":"fancyapp","scmId":"git","state":"AVAILABLE","statusMessage":"Available","forkable":true,"project":{"key":"IOS","id":7,"name":"ios","public":false,"type":"NORMAL","links":{"self":[{"href":"https://stash.example.com/projects/IOS"}]}},"public":false,"links":{"clone":[{"href":"https://a.user@stash.example.com/scm/ios/fancyapp.git","name":"http"},{"href":"ssh://git@stash.example.com:7999/ios/fancyapp.git","name":"ssh"}],"self":[{"href":"https://stash.example.com/projects/IOS/repos/fancyapp/browse"}]}}},"toRef":{"id":"refs/heads/develop","displayId":"develop","latestCommit":"b366c9564ad57786f0e5c6b8333c7aa1e2e90b9a","repository":{"slug":"fancyapp","id":38,"name":"fancyapp","scmId":"git","state":"AVAILABLE","statusMessage":"Available","forkable":true,"project":{"key":"IOS","id":7,"name":"ios","public":false,"type":"NORMAL","links":{"self":[{"href":"https://stash.example.com/projects/IOS"}]}},"public":false,"links":{"clone":[{"href":"https://a.user@stash.example.com/scm/ios/fancyapp.git","name":"http"},{"href":"ssh://git@stash.example.com:7999/ios/fancyapp.git","name":"ssh"}],"self":[{"href":"https://stash.example.com/projects/IOS/repos/fancyapp/browse"}]}}},"locked":false,"author":{"user":{"name":"a.user","emailAddress":"a.user@example.com","id":101,"displayName":"A User","active":true,"slug":"a.user","type":"NORMAL","links":{"self":[{"href":"https://stash.example.com/users/a.user"}]}},"role":"AUTHOR","approved":false,"status":"UNAPPROVED"},"reviewers":[],"participants":[],"links":{"self":[{"href":"https://stash.example.com/projects/IOS/repos/fancyapp/pull-requests/2080"}]}} 4 | -------------------------------------------------------------------------------- /spec/fixtures/ci_source/support/danger-git.log: -------------------------------------------------------------------------------- 1 | bde9ea7 Merge pull request #557 from danger/hk-spec-improvements 2 | 4a86be0 fix ruby keyword argument syntax 3 | 028fecb silence git output in specs 4 | e2ccd73 extract `with_git_repo` to spec_helper, be more explicit about git origin 5 | 3f8645a use different url to allow spec to fail 6 | 49f08b9 remove useless local variable 7 | 9c84248 Update CHANGELOG.md 8 | 0cd9198 Prepare for 3.3.0 (#556) 9 | -------------------------------------------------------------------------------- /spec/fixtures/ci_source/support/enterprise-remote.log: -------------------------------------------------------------------------------- 1 | * remote origin 2 | Fetch URL: git@artsyhub.com:enterdanger/enterdanger.git 3 | Push URL: git@artsyhub.com:enterdanger/enterdanger.git 4 | HEAD branch: (not queried) 5 | Remote branches: (status not queried) 6 | add-scm-provider 7 | bitbucket_prep 8 | dangerfile 9 | doc/fix-changelog 10 | doc/fix-changelog-item 11 | doc/request_source/validates_as_ci 12 | enable-triming-whitespaces-for-all-files 13 | feature/inline_messaging 14 | get_local_started 15 | git-english-env 16 | gitlab 17 | hk-spec-improvements 18 | improve_warning_no_local 19 | issues 20 | js 21 | line_comment 22 | linter 23 | master 24 | merge_point 25 | patch/bitbucket-server-api-inspect-protection 26 | patch/comment 27 | patch/danger/gem_path 28 | patch/gemspec-homepage 29 | patch/remove-unused-danger-class-method 30 | patch/string-methods 31 | patch/volation-args 32 | readme_improvements 33 | release 34 | release_214 35 | release_303 36 | spec/orgnizations 37 | youre_a_local_command_harry 38 | youre_a_local_command_harry_two 39 | Local branch configured for 'git pull': 40 | master merges with remote master 41 | Local ref configured for 'git push' (status not queried): 42 | (matching) pushes to (matching) 43 | -------------------------------------------------------------------------------- /spec/fixtures/ci_source/support/https-remote.log: -------------------------------------------------------------------------------- 1 | * remote origin 2 | Fetch URL: https://github.com/danger/danger.git 3 | Push URL: https://github.com/danger/danger.git 4 | -------------------------------------------------------------------------------- /spec/fixtures/ci_source/support/remote.log: -------------------------------------------------------------------------------- 1 | * remote origin 2 | Fetch URL: git@github.com:danger/danger.git 3 | Push URL: git@github.com:danger/danger.git 4 | HEAD branch: (not queried) 5 | Remote branches: (status not queried) 6 | add-scm-provider 7 | bitbucket_prep 8 | dangerfile 9 | doc/fix-changelog 10 | doc/fix-changelog-item 11 | doc/request_source/validates_as_ci 12 | enable-triming-whitespaces-for-all-files 13 | feature/inline_messaging 14 | get_local_started 15 | git-english-env 16 | gitlab 17 | hk-spec-improvements 18 | improve_warning_no_local 19 | issues 20 | js 21 | line_comment 22 | linter 23 | master 24 | merge_point 25 | patch/bitbucket-server-api-inspect-protection 26 | patch/comment 27 | patch/danger/gem_path 28 | patch/gemspec-homepage 29 | patch/remove-unused-danger-class-method 30 | patch/string-methods 31 | patch/volation-args 32 | readme_improvements 33 | release 34 | release_214 35 | release_303 36 | spec/orgnizations 37 | youre_a_local_command_harry 38 | youre_a_local_command_harry_two 39 | Local branch configured for 'git pull': 40 | master merges with remote master 41 | Local ref configured for 'git push' (status not queried): 42 | (matching) pushes to (matching) 43 | -------------------------------------------------------------------------------- /spec/fixtures/ci_source/support/swiftweekly.github.io-git.log: -------------------------------------------------------------------------------- 1 | 129045f Flesh out issue 38 (#89) 2 | 300780c [38] draft update 3 | 8f0f2d2 [38] Add accepted proposals 0139 & 0140 (#87) 4 | 8c85645 Update 2016-09-15-issue-38.md 5 | 65dfb06 Update Dangerfile 6 | 684eeff Clarify publishing times in CONTRIBUTING.md (#81) 7 | 19369ac [38] Add ClangImporter refactor (#83) 8 | 81c2705 Update 2016-09-15-issue-38.md 9 | 10b1ec7 Update new_draft.sh 10 | 50b40c8 Add "TODO" and Apple projects to ignored Proselint words (#78) 11 | 038d71c [38] Add Jesse's work on the SE Status Page (#85) 12 | 9fb1cf5 [38] Add extended SE-0138 (#84) 13 | 86238a7 Merge branch 'master' of https://github.com/SwiftWeekly/swiftweekly.github.io 14 | 45a666b bundle install. fix/cleanup gems 15 | acc5a1b Fix code tag (#80) 16 | c4dde6d Typo fix (#82) 17 | 3f7047a [38] Add blurb on CI cross-testing (#77) 18 | 8f8d576 [38] Add cmpcodesize starter tasks (#76) 19 | 8178b7e Merge branch 'master' of https://github.com/SwiftWeekly/swiftweekly.github.io 20 | 2606da6 [37] oops. note iOS and OSX release dates 21 | bc865bb Update CONTRIBUTING.md 22 | 78a707f Update CONTRIBUTING.md 23 | e4af163 [38] initial draft 24 | c537b81 [37] publish issue 37 (#73) 25 | e08e251 update ignored words in proselint 26 | f674bd8 [37] draft update 27 | a2776ec update ignored words 28 | ae8c9e0 update danger setup 29 | 2e4da93 setup danger + proselint 30 | 74de0a0 implement Jekyll SEO tag. close #38 31 | 7849827 [37] draft update 32 | 9a217c7 [37] draft update 33 | 793517c [37] Remove returned proposals section (#69) 34 | 704cc04 Update CONTRIBUTING.md 35 | 6dac1a5 Update CONTRIBUTING.md 36 | b5fba45 [37] Add proposal SE-0140 (#67) 37 | d1e788b [37] Add two proposals in review (#66) 38 | 7b2e552 Update CONTRIBUTING.md 39 | 2901fbd [37] initial draft 40 | ef50aea publish issue 36 41 | 3ad7efb [36] draft update 42 | f7b8ae3 [36] draft update 43 | 9bae552 [36] draft update 44 | fb4bf4d [36] Add Apple event details (#65) 45 | e89f085 [36] initial draft 46 | 84fd626 publish issue 35 47 | 0fb9734 Update CONTRIBUTING.md 48 | 71e960e Added info on Robotary (#62) 49 | 76a1671 [35] draft update 50 | 5fc8803 [35] Add -driver-time-compilation (#61) 51 | -------------------------------------------------------------------------------- /spec/fixtures/ci_source/support/two-kinds-of-merge-both-present.log: -------------------------------------------------------------------------------- 1 | 1234567 This is fake commit 2 | 9f8c75a Fail on errors (#2) 3 | 8e9a3ab Fixes typo in init.rb #trivial (#588) 4 | f029131 Merge pull request #2 from orta/KrauseFx-patch-1 5 | 54bff64 Initial commit 6 | -------------------------------------------------------------------------------- /spec/fixtures/commands/plugin_md_example.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### proselint 4 | 5 | Lint markdown files inside your projects. 6 | This is done using the [proselint](http://proselint.com) python egg. 7 | Results are passed out as a table in markdown. 8 | 9 | <blockquote>Specifying custom CocoaPods installation options 10 | <pre> 11 | # Runs a linter with comma style disabled 12 | proselint.disable_linters = ["misc.scare_quotes", "misc.tense_present"] 13 | proselint.lint_files "_posts/*.md" 14 | 15 | # Runs a linter with all styles, on modified and added markpown files in this PR 16 | proselint.lint_files</pre> 17 | </blockquote> 18 | 19 | 20 | 21 | #### Attributes 22 | 23 | `disable_linters` - Allows you to disable a collection of linters from being ran. 24 | You can get a list of [them here](https://github.com/amperser/proselint#checks) 25 | 26 | 27 | 28 | 29 | #### Methods 30 | 31 | `lint_files` - Lints the globbed files, which can fail your build if 32 | 33 | `proselint_installed?` - Determine if proselint is currently installed in the system paths. 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /spec/fixtures/comment_with_error.html: -------------------------------------------------------------------------------- 1 | <table> 2 | <thead> 3 | <tr> 4 | <th width="50"></th> 5 | <th width="100%">1 Error</th> 6 | </tr> 7 | </thead> 8 | <tbody> 9 | <tr> 10 | <td>:no_entry_sign:</td> 11 | <td data-sticky="true">Some error 12 | </td> 13 | </tr> 14 | 15 | </tbody> 16 | </table> -------------------------------------------------------------------------------- /spec/fixtures/comment_with_error_and_warnings.html: -------------------------------------------------------------------------------- 1 | <table> 2 | <thead> 3 | <tr> 4 | <th width="50"></th> 5 | <th width="100%">1 Error</th> 6 | </tr> 7 | </thead> 8 | <tbody> 9 | <tr> 10 | <td>:no_entry_sign:</td> 11 | <td data-sticky="true">Some error 12 | </td> 13 | </tr> 14 | 15 | </tbody> 16 | </table> 17 | 18 | <table> 19 | <thead> 20 | <tr> 21 | <th width="50"></th> 22 | <th width="100%">2 Warnings</th> 23 | </tr> 24 | </thead> 25 | <tbody> 26 | <tr> 27 | <td>:warning:</td> 28 | <td data-sticky="true">First warning 29 | </td> 30 | <td data-sticky="true">Second warning</td> 31 | </tr> 32 | 33 | </tbody> 34 | </table> -------------------------------------------------------------------------------- /spec/fixtures/comment_with_file_link.html: -------------------------------------------------------------------------------- 1 | <table> 2 | <thead> 3 | <tr> 4 | <th width="50"></th> 5 | <th width="100%">1 Warning</th> 6 | </tr> 7 | </thead> 8 | <tbody> 9 | <tr> 10 | <td>:warning:</td> 11 | <td data-sticky="true"><a href="https://github.com/artsy/eigen/blob/13c4dc8bb61d/.gitignore#L10">.gitignore:10</a> - some warning</td> 12 | </tr> 13 | </tbody> 14 | </table> 15 | -------------------------------------------------------------------------------- /spec/fixtures/comment_with_non_sticky.html: -------------------------------------------------------------------------------- 1 | <table> 2 | <thead> 3 | <tr> 4 | <th width="50"></th> 5 | <th width="100%">1 Error</th> 6 | </tr> 7 | </thead> 8 | <tbody> 9 | <tr> 10 | <td>:no_entry_sign:</td> 11 | <td data-sticky="false">Some error 12 | </td> 13 | </tr> 14 | 15 | </tbody> 16 | </table> 17 | 18 | <table> 19 | <thead> 20 | <tr> 21 | <th width="50"></th> 22 | <th width="100%">2 Warnings</th> 23 | </tr> 24 | </thead> 25 | <tbody> 26 | <tr> 27 | <td>:warning:</td> 28 | <td data-sticky="true">First warning 29 | </td> 30 | <td>Second warning</td> 31 | </tr> 32 | 33 | </tbody> 34 | </table> -------------------------------------------------------------------------------- /spec/fixtures/comment_with_resolved_violation.html: -------------------------------------------------------------------------------- 1 | <table> 2 | <thead> 3 | <tr> 4 | <th width="50"></th> 5 | <th width="100%" data-kind="Error">:white_check_mark: Woo!</th> 6 | </tr> 7 | </thead> 8 | <tbody> 9 | <tr> 10 | <td>:white_check_mark:</td> 11 | <td data-sticky="true"><del>Some error</del> 12 | </td> 13 | </tr> 14 | 15 | </tbody> 16 | </table> 17 | 18 | <table> 19 | <thead> 20 | <tr> 21 | <th width="50"></th> 22 | <th width="100%">1 Warning</th> 23 | </tr> 24 | </thead> 25 | <tbody> 26 | <tr> 27 | <td>:warning:</td> 28 | <td data-sticky="true">First warning 29 | </td> 30 | <td data-sticky="true">Second warning</td> 31 | </tr> 32 | 33 | </tbody> 34 | </table> -------------------------------------------------------------------------------- /spec/fixtures/dangerfile_with_error: -------------------------------------------------------------------------------- 1 | # This will fail 2 | abc 3 | -------------------------------------------------------------------------------- /spec/fixtures/dangerfile_with_error_and_path_reassignment: -------------------------------------------------------------------------------- 1 | # This will fail 2 | path = 'asdf' 3 | abc 4 | -------------------------------------------------------------------------------- /spec/fixtures/github/Dangerfile: -------------------------------------------------------------------------------- 1 | # Look for prose issues 2 | prose.lint_files 3 | 4 | # Look for spelling issues 5 | prose.ignored_words = ["Swift", "iOS", "macOS", "watchOS", "tvOS", "iPhone", "iPad", "nonnull", "nullable", "nullability", "corelibs-foundation", "corelibs-libdispatch", "stdlib", "GCD", "SwiftPM", "Xcode", "TODO", "swift-evolution", "swift-package-manager", "swift-lldb", "swift-clang", "swift-llvm", "swift-corelibs-foundation", "swift-corelibs-libdispatch", "ClangImporter"] 6 | prose.check_spelling 7 | -------------------------------------------------------------------------------- /spec/fixtures/github_api/pr_review_response.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 15629060, 3 | "user": { 4 | "login": "conichi-ci", 5 | "id": 17313329, 6 | "avatar_url": "https://avatars3.githubusercontent.com/u/17313329?v=3", 7 | "gravatar_id": "", 8 | "url": "https://api.github.com/users/conichi-ci", 9 | "html_url": "https://github.com/conichi-ci", 10 | "followers_url": "https://api.github.com/users/conichi-ci/followers", 11 | "following_url": "https://api.github.com/users/conichi-ci/following{/other_user}", 12 | "gists_url": "https://api.github.com/users/conichi-ci/gists{/gist_id}", 13 | "starred_url": "https://api.github.com/users/conichi-ci/starred{/owner}{/repo}", 14 | "subscriptions_url": "https://api.github.com/users/conichi-ci/subscriptions", 15 | "organizations_url": "https://api.github.com/users/conichi-ci/orgs", 16 | "repos_url": "https://api.github.com/users/conichi-ci/repos", 17 | "events_url": "https://api.github.com/users/conichi-ci/events{/privacy}", 18 | "received_events_url": "https://api.github.com/users/conichi-ci/received_events", 19 | "type": "User", 20 | "site_admin": false 21 | }, 22 | "body": "Looks good", 23 | "commit_id": "d076f61a9eee9806bd8c272903c4182c033e0e7e", 24 | "state": "APPROVED", 25 | "html_url": "https://github.com/Antondomashnev/MyAmazingDangerPlayground/pull/2#pullrequestreview-15629060", 26 | "pull_request_url": "https://api.github.com/repos/Antondomashnev/MyAmazingDangerPlayground/pulls/2", 27 | "_links": { 28 | "html": { 29 | "href": "https://github.com/Antondomashnev/MyAmazingDangerPlayground/pull/2#pullrequestreview-15629060" 30 | }, 31 | "pull_request": { 32 | "href": "https://api.github.com/repos/Antondomashnev/MyAmazingDangerPlayground/pulls/2" 33 | } 34 | }, 35 | "submitted_at": "2017-01-08T17:37:23Z" 36 | } 37 | -------------------------------------------------------------------------------- /spec/fixtures/gitlab_api/merge_request_1_comments_response.json: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | Server: nginx 3 | Date: Sun, 17 Jul 2016 13:05:46 GMT 4 | Content-Type: application/json 5 | Content-Length: 1507 6 | Cache-Control: max-age=0, private, must-revalidate 7 | Etag: W/"e810a3b50890dd0a5823597abbbf9fc9" 8 | Link: <https://gitlab.com/api/v4/projects/k0nserv/danger-test/merge_requests/593728/notes?id=k0nserv/danger-test&merge_request_id=593728&page=1&per_page=20>; rel="first", <https://gitlab.com/api/v3/projects/k0nserv/danger-test/merge_requests/593728/notes?id=k0nserv/danger-test&merge_request_id=593728&page=1&per_page=20>; rel="last" 9 | Status: 200 OK 10 | Vary: Origin 11 | X-Next-Page: 12 | X-Page: 1 13 | X-Per-Page: 20 14 | X-Prev-Page: 15 | X-Request-Id: dee4a515-c1dd-4fec-8665-e6ce44bc6c41 16 | X-Runtime: 0.079596 17 | X-Total: 3 18 | X-Total-Pages: 1 19 | 20 | [{"id":12717719,"body":"Added 1 commit:\n\n* 345e74fa - add b","attachment":null,"author":{"name":"Hugo Tunius","username":"k0nserv","id":483414,"state":"active","avatar_url":"https://secure.gravatar.com/avatar/7300342f996dcc9d4e22418cc9a70b14?s=80\u0026d=identicon","web_url":"https://gitlab.com/u/k0nserv"},"created_at":"2016-06-27T12:34:05.086Z","updated_at":"2016-06-27T12:34:05.086Z","system":true,"noteable_id":593728,"noteable_type":"MergeRequest","upvote?":false,"downvote?":false},{"id":12717633,"body":"Added ~392754 label","attachment":null,"author":{"name":"Hugo Tunius","username":"k0nserv","id":483414,"state":"active","avatar_url":"https://secure.gravatar.com/avatar/7300342f996dcc9d4e22418cc9a70b14?s=80\u0026d=identicon","web_url":"https://gitlab.com/u/k0nserv"},"created_at":"2016-06-27T12:30:22.157Z","updated_at":"2016-06-27T12:30:22.157Z","system":true,"noteable_id":593728,"noteable_type":"MergeRequest","upvote?":false,"downvote?":false},{"id":12716187,"body":"Added 3 commits:\n\n* dd9ed2fe...0e4db308 - 2 commits from branch `master`\n* adae7c38 - Add a","attachment":null,"author":{"name":"Hugo Tunius","username":"k0nserv","id":483414,"state":"active","avatar_url":"https://secure.gravatar.com/avatar/7300342f996dcc9d4e22418cc9a70b14?s=80\u0026d=identicon","web_url":"https://gitlab.com/u/k0nserv"},"created_at":"2016-06-27T11:26:18.621Z","updated_at":"2016-06-27T11:26:18.621Z","system":true,"noteable_id":593728,"noteable_type":"MergeRequest","upvote?":false,"downvote?":false}] 21 | -------------------------------------------------------------------------------- /spec/fixtures/gitlab_api/merge_request_1_discussions_empty_response.json: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | Server: nginx 3 | Date: Thu, 14 Feb 2019 20:13:19 GMT 4 | Content-Type: application/json 5 | Content-Length: 7789 6 | Cache-Control: max-age=0, private, must-revalidate 7 | Etag: W/"5b77db1f9b56d74e705229cc87b3d5a6" 8 | Link: <https://gitlab.com/api/v4/projects/k0nserv%2Fdanger-test/merge_requests/1/discussions?id=k0nserv%2Fdanger-test¬eable_id=1&page=1&per_page=20>; rel="first", <https://gitlab.com/api/v4/projects/k0nserv%2Fdanger-test/merge_requests/1/discussions?id=k0nserv%2Fdanger-test¬eable_id=1&page=1&per_page=20>; rel="last" 9 | Vary: Origin 10 | X-Content-Type-Options: nosniff 11 | X-Frame-Options: SAMEORIGIN 12 | X-Next-Page: 13 | X-Page: 1 14 | X-Per-Page: 20 15 | X-Prev-Page: 16 | X-Request-Id: OCKNUgQKAc3 17 | X-Runtime: 0.189194 18 | X-Total: 9 19 | X-Total-Pages: 1 20 | Strict-Transport-Security: max-age=31536000 21 | RateLimit-Limit: 600 22 | RateLimit-Observed: 1 23 | RateLimit-Remaining: 599 24 | RateLimit-Reset: 1550175259 25 | RateLimit-ResetTime: Fri, 14 Feb 2019 20:14:19 GMT 26 | 27 | [] -------------------------------------------------------------------------------- /spec/fixtures/gitlab_api/merge_request_1_response.json: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | Server: nginx 3 | Date: Wed, 23 Jan 2019 16:54:22 GMT 4 | Content-Type: application/json 5 | Content-Length: 1658 6 | Cache-Control: max-age=0, private, must-revalidate 7 | Etag: W/"63fa6da2558d13827e3ce19a6c61f5a1" 8 | Vary: Origin 9 | X-Request-Id: Xq9HIN76754 10 | X-Runtime: 0.058471 11 | 12 | {"id":593728,"iid":1,"project_id":1342007,"title":"Add a","description":"The descriptions is here\r\n\r\n\u003e Danger: ignore \"Developer specific files shouldn't be changed\"\r\n\r\n\u003e Danger: ignore \"Testing\"","state":"opened","created_at":"2016-06-27T11:04:02.114Z","updated_at":"2016-09-19T20:25:31.077Z","merged_by":null,"merged_at":null,"closed_by":null,"closed_at":null,"target_branch":"master","source_branch":"mr-test","upvotes":0,"downvotes":0,"author":{"id":483414,"name":"Hugo Tunius","username":"k0nserv","state":"active","avatar_url":"https://secure.gravatar.com/avatar/7300342f996dcc9d4e22418cc9a70b14?s=80\u0026d=identicon","web_url":"https://gitlab.com/k0nserv"},"assignee":null,"source_project_id":1342007,"target_project_id":1342007,"labels":["test-label"],"work_in_progress":false,"milestone":null,"merge_when_pipeline_succeeds":false,"merge_status":"can_be_merged","sha":"04e58de1fa97502d7e28c1394d471bb8fb1fc4a8","merge_commit_sha":null,"user_notes_count":2,"discussion_locked":null,"should_remove_source_branch":null,"force_remove_source_branch":null,"web_url":"https://gitlab.com/k0nserv/danger-test/merge_requests/1","time_stats":{"time_estimate":0,"total_time_spent":0,"human_time_estimate":null,"human_total_time_spent":null},"squash":false,"subscribed":false,"changes_count":"3","latest_build_started_at":null,"latest_build_finished_at":null,"first_deployed_to_production_at":null,"pipeline":null,"diff_refs":{"base_sha":"0e4db308b6579f7cc733e5a354e026b272e1c076","head_sha":"04e58de1fa97502d7e28c1394d471bb8fb1fc4a8","start_sha":"0e4db308b6579f7cc733e5a354e026b272e1c076"},"merge_error":null,"approvals_before_merge":null} -------------------------------------------------------------------------------- /spec/fixtures/gitlab_api/merge_requests_response.json: -------------------------------------------------------------------------------- 1 | HTTP/1.1 200 OK 2 | Server: nginx 3 | Date: Sun, 17 Jul 2016 13:05:46 GMT 4 | Content-Type: application/json 5 | Content-Length: 1507 6 | Cache-Control: max-age=0, private, must-revalidate 7 | Etag: W/"e810a3b50890dd0a5823597abbbf9fc9" 8 | Link: <https://gitlab.com/api/v4/projects/k0nserv%2Fdanger-test/merge_requests?state=opened&page=1&per_page=20>; rel="first", <https://gitlab.com/api/v4/projects/k0nserv%2Fdanger-test/merge_requests?state=opened&page=1&per_page=20>; rel="last" 9 | Status: 200 OK 10 | Vary: Origin 11 | X-Next-Page: 12 | X-Page: 1 13 | X-Per-Page: 20 14 | X-Prev-Page: 15 | X-Request-Id: dee4a515-c1dd-4fec-8665-e6ce44bc6c41 16 | X-Runtime: 0.079596 17 | X-Total: 3 18 | X-Total-Pages: 1 19 | 20 | [ 21 | { 22 | "iid": 1, 23 | "sha": "1111111111111111111111111111111111111111" 24 | }, 25 | { 26 | "iid": 2, 27 | "sha": "2222222222222222222222222222222222222222" 28 | }, 29 | { 30 | "iid": 3, 31 | "sha": "3333333333333333333333333333333333333333" 32 | } 33 | ] 34 | -------------------------------------------------------------------------------- /spec/fixtures/plugin_json/example_remote.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "ExampleRemote", 4 | "body_md": "", 5 | "instance_name": "example_remote", 6 | "gem": null, 7 | "gem_path": "", 8 | "files": [ 9 | [ 10 | "/spec/fixtures/plugins/example_remote.rb", 11 | 2 12 | ] 13 | ], 14 | "example_code": [ 15 | 16 | ], 17 | "attributes": [ 18 | 19 | ], 20 | "methods": [ 21 | { 22 | "name": "echo", 23 | "body_md": "", 24 | "params": [ 25 | 26 | ], 27 | "files": [ 28 | [ 29 | "/spec/fixtures/plugins/example_remote.rb", 30 | 3 31 | ] 32 | ], 33 | "tags": [ 34 | 35 | ], 36 | "param_couplets": { 37 | }, 38 | "return": "", 39 | "one_liner": "echo" 40 | } 41 | ], 42 | "tags": [ 43 | 44 | ], 45 | "see": [ 46 | 47 | ] 48 | } 49 | ] -------------------------------------------------------------------------------- /spec/fixtures/plugins/example_broken.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | class Dangerfile 3 | class ExampleBroken # not a subclass < Plugin 4 | def run 5 | return "Hi there" 6 | end 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/example_echo_plugin.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | class ExamplePing < Plugin 3 | def echo 4 | return "Hi there 🎉" 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/example_exact_path.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | class Dangerfile 3 | module DSL 4 | class ExampleExactPath < Plugin 5 | def echo 6 | return "Hi there exact" 7 | end 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/example_globbing.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | class Dangerfile 3 | module DSL 4 | class ExampleGlobbing < Plugin 5 | def echo 6 | return "Hi there globbing" 7 | end 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/example_not_broken.rb: -------------------------------------------------------------------------------- 1 | class Dangerfile 2 | class ExampleBroken < Danger::Plugin 3 | def run 4 | return "Hi there" 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/example_remote.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | class ExampleRemote < Plugin 3 | def echo 4 | return "Hi there remote 🎉" 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/fixtures/plugins/plugin_many_methods.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | class ExampleManyMethodsPlugin < Plugin 3 | def one 4 | end 5 | 6 | # Thing two 7 | # 8 | def two(param1) 9 | end 10 | 11 | def two_point_five(param1 = nil) 12 | end 13 | 14 | # Thing three 15 | # 16 | # @param [String] param1 17 | # A thing thing, defaults to nil. 18 | # @return [void] 19 | # 20 | def three(param1 = nil) 21 | end 22 | 23 | # Thing four 24 | # 25 | # @param [Number] param1 26 | # A thing thing, defaults to nil. 27 | # @param [String] param2 28 | # Another param 29 | # @return [String] 30 | # 31 | def four(param1 = nil, param2) 32 | end 33 | 34 | # Thing five 35 | # 36 | # @param [Array<String>] param1 37 | # A thing thing. 38 | # @param [Filepath] param2 39 | # Another param 40 | # @return [String] 41 | # 42 | def five(param1 = [], param2, param3) 43 | end 44 | 45 | # Does six 46 | # @return [Bool] 47 | # 48 | def six? 49 | end 50 | 51 | # Attribute docs 52 | # 53 | # @return [Array<String>] 54 | attr_accessor :seven 55 | 56 | attr_accessor :eight 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /spec/fixtures/rubygems_api/api_v1_versions_danger_latest.json: -------------------------------------------------------------------------------- 1 | {"version":"3.1.1"} 2 | -------------------------------------------------------------------------------- /spec/lib/danger/ci_sources/appveyor_spec.rb: -------------------------------------------------------------------------------- 1 | require "danger/ci_source/appveyor" 2 | 3 | RSpec.describe Danger::AppVeyor do 4 | let(:valid_env) do 5 | { 6 | "APPVEYOR_PULL_REQUEST_NUMBER" => "2", 7 | "APPVEYOR" => "true", 8 | "APPVEYOR_REPO_NAME" => "artsy/eigen" 9 | } 10 | end 11 | 12 | let(:invalid_env) do 13 | { 14 | "BITRISE_IO" => "true" 15 | } 16 | end 17 | 18 | let(:source) { described_class.new(valid_env) } 19 | 20 | describe ".validates_as_pr?" do 21 | it "validates when the required env variables are set" do 22 | expect(described_class.validates_as_pr?(valid_env)).to be true 23 | end 24 | 25 | it "does not validate when the required env variables are not set" do 26 | expect(described_class.validates_as_pr?(invalid_env)).to be false 27 | end 28 | 29 | it "does not validate when there isn't a PR" do 30 | valid_env["APPVEYOR_PULL_REQUEST_NUMBER"] = nil 31 | expect(described_class.validates_as_pr?(valid_env)).to be false 32 | end 33 | end 34 | 35 | describe ".validates_as_ci?" do 36 | it "validates when the required env variables are set" do 37 | expect(described_class.validates_as_ci?(valid_env)).to be true 38 | end 39 | 40 | it "does not validate when the required env variables are not set" do 41 | expect(described_class.validates_as_ci?(invalid_env)).to be false 42 | end 43 | 44 | it "validates even when there is no PR" do 45 | valid_env["APPVEYOR_PULL_REQUEST_NUMBER"] = nil 46 | expect(described_class.validates_as_ci?(valid_env)).to be true 47 | end 48 | end 49 | 50 | describe "#new" do 51 | it "sets the repo_slug" do 52 | expect(source.repo_slug).to eq("artsy/eigen") 53 | end 54 | 55 | it "sets the pull_request_id" do 56 | expect(source.pull_request_id).to eq("2") 57 | end 58 | 59 | it "sets the repo_url", host: :github do 60 | with_git_repo(origin: "git@github.com:artsy/eigen") do 61 | expect(source.repo_url).to eq("git@github.com:artsy/eigen") 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /spec/lib/danger/ci_sources/bamboo_spec.rb: -------------------------------------------------------------------------------- 1 | # require "danger/ci_source/bamboo" 2 | 3 | RSpec.describe Danger::Bamboo do 4 | let(:valid_env) do 5 | { 6 | "bamboo_buildKey" => "1", 7 | "bamboo_repository_pr_key" => "33", 8 | "bamboo_planRepository_repositoryUrl" => "git@github.com:danger/danger.git" 9 | } 10 | end 11 | 12 | let(:source) { described_class.new(valid_env) } 13 | 14 | describe ".validates_as_ci?" do 15 | it "validates when the required env vars are set" do 16 | expect(described_class.validates_as_ci?(valid_env)).to be true 17 | end 18 | 19 | it "does not validate when the required env vars are not set" do 20 | valid_env.delete "bamboo_buildKey" 21 | expect(described_class.validates_as_ci?(valid_env)).to be false 22 | end 23 | end 24 | 25 | describe ".validates_as_pr?" do 26 | it "validates when the required env vars are set" do 27 | expect(described_class.validates_as_pr?(valid_env)).to be true 28 | end 29 | 30 | it "does not validate when the required pull request is not set" do 31 | valid_env["bamboo_repository_pr_key"] = nil 32 | expect(described_class.validates_as_pr?(valid_env)).to be false 33 | end 34 | 35 | it "does not validate when the required repo url is not set" do 36 | valid_env["bamboo_planRepository_repositoryUrl"] = nil 37 | expect(described_class.validates_as_pr?(valid_env)).to be false 38 | end 39 | end 40 | 41 | describe ".new" do 42 | it "sets the required attributes" do 43 | expect(source.repo_slug).to eq("danger/danger") 44 | expect(source.pull_request_id).to eq("33") 45 | expect(source.repo_url).to eq("git@github.com:danger/danger.git") 46 | end 47 | 48 | it "supports Bitbucket Server" do 49 | expect(source.supported_request_sources).to include(Danger::RequestSources::BitbucketServer) 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /spec/lib/danger/ci_sources/bitbucket_pipelines_spec.rb: -------------------------------------------------------------------------------- 1 | require "danger/ci_source/bitbucket_pipelines" 2 | 3 | RSpec.describe Danger::BitbucketPipelines do 4 | let(:valid_env) do 5 | { 6 | "BITBUCKET_BUILD_NUMBER" => "2", 7 | "BITBUCKET_PR_ID" => "4", 8 | "BITBUCKET_REPO_OWNER" => "foo", 9 | "BITBUCKET_REPO_SLUG" => "bar" 10 | } 11 | end 12 | 13 | let(:invalid_env) do 14 | { 15 | "BITRISE_IO" => "true" 16 | } 17 | end 18 | 19 | let(:source) { described_class.new(valid_env) } 20 | 21 | describe ".validates_as_ci?" do 22 | it "validates when the required env vars are set" do 23 | expect(described_class.validates_as_ci?(valid_env)).to be true 24 | end 25 | 26 | it "does not validate when the required env vars are not set" do 27 | expect(described_class.validates_as_ci?(invalid_env)).to be false 28 | end 29 | end 30 | 31 | describe ".validates_as_pr?" do 32 | it "validates when the required env vars are set" do 33 | expect(described_class.validates_as_pr?(valid_env)).to be true 34 | end 35 | 36 | it "does not validate when the required env vars are not set" do 37 | expect(described_class.validates_as_pr?(invalid_env)).to be false 38 | end 39 | end 40 | 41 | describe ".new" do 42 | it "sets the repository slug" do 43 | expect(source.repo_slug).to eq("foo/bar") 44 | expect(source.pull_request_id).to eq("4") 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/lib/danger/ci_sources/buddybuild_spec.rb: -------------------------------------------------------------------------------- 1 | require "danger/ci_source/buddybuild" 2 | 3 | RSpec.describe Danger::Buddybuild do 4 | let(:valid_env) do 5 | { 6 | "BUDDYBUILD_BUILD_ID" => "595be087b095370001d8e0b3", 7 | "BUDDYBUILD_PULL_REQUEST" => "4", 8 | "BUDDYBUILD_REPO_SLUG" => "palleas/Batman" 9 | } 10 | end 11 | 12 | let(:source) { described_class.new(valid_env) } 13 | 14 | describe ".validates_as_ci?" do 15 | it "validates when the required env vars are set" do 16 | expect(described_class.validates_as_ci?(valid_env)).to be true 17 | end 18 | 19 | it "does not validate when the required env vars are not set" do 20 | valid_env["BUDDYBUILD_BUILD_ID"] = nil 21 | expect(described_class.validates_as_ci?(valid_env)).to be false 22 | end 23 | end 24 | 25 | describe ".validates_as_pr?" do 26 | it "validates when the required env vars are set" do 27 | expect(described_class.validates_as_pr?(valid_env)).to be true 28 | end 29 | 30 | it "does not validate when the required env vars are not set" do 31 | valid_env["BUDDYBUILD_PULL_REQUEST"] = nil 32 | expect(described_class.validates_as_pr?(valid_env)).to be false 33 | end 34 | end 35 | 36 | describe ".new" do 37 | it "sets the repository slug" do 38 | expect(source.repo_slug).to eq("palleas/Batman") 39 | expect(source.pull_request_id).to eq("4") 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/lib/danger/ci_sources/ci_source_spec.rb: -------------------------------------------------------------------------------- 1 | require "danger/ci_source/ci_source" 2 | 3 | RSpec.describe Danger::CI do 4 | describe ".available_ci_sources" do 5 | it "returns list of CI subclasses" do 6 | expect(described_class.available_ci_sources.map(&:to_s)).to match_array( 7 | [ 8 | "Danger::Appcenter", 9 | "Danger::Appcircle", 10 | "Danger::AppVeyor", 11 | "Danger::AzurePipelines", 12 | "Danger::Bamboo", 13 | "Danger::BitbucketPipelines", 14 | "Danger::Bitrise", 15 | "Danger::Buddybuild", 16 | "Danger::Buildkite", 17 | "Danger::CircleCI", 18 | "Danger::Cirrus", 19 | "Danger::CodeBuild", 20 | "Danger::Codefresh", 21 | "Danger::Codemagic", 22 | "Danger::Codeship", 23 | "Danger::Concourse", 24 | "Danger::CustomCIWithGithub", 25 | "Danger::DotCi", 26 | "Danger::Drone", 27 | "Danger::GitHubActions", 28 | "Danger::GitLabCI", 29 | "Danger::Jenkins", 30 | "Danger::LocalGitRepo", 31 | "Danger::LocalOnlyGitRepo", 32 | "Danger::Screwdriver", 33 | "Danger::Semaphore", 34 | "Danger::Surf", 35 | "Danger::TeamCity", 36 | "Danger::Travis", 37 | "Danger::XcodeCloud", 38 | "Danger::XcodeServer" 39 | ] 40 | ) 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/lib/danger/ci_sources/local_only_git_repo_spec.rb: -------------------------------------------------------------------------------- 1 | require "spec_helper" 2 | require "danger/ci_source/local_only_git_repo" 3 | 4 | RSpec.describe Danger::LocalOnlyGitRepo do 5 | def run_in_repo 6 | Dir.mktmpdir do |dir| 7 | Dir.chdir dir do 8 | `git init -b master` 9 | `git remote add origin .` 10 | File.open(dir + "/file1", "w") {} 11 | `git add .` 12 | `git commit -m "adding file1"` 13 | `git fetch` 14 | `git checkout -b feature_branch` 15 | File.open(dir + "/file2", "w") {} 16 | `git add .` 17 | `git commit -m "adding file2"` 18 | 19 | yield 20 | end 21 | end 22 | end 23 | 24 | let(:valid_env) do 25 | { 26 | "DANGER_USE_LOCAL_ONLY_GIT" => "true" 27 | } 28 | end 29 | 30 | let(:invalid_env) do 31 | { 32 | "CIRCLE" => "true" 33 | } 34 | end 35 | 36 | def source(env) 37 | described_class.new(env) 38 | end 39 | 40 | describe "validates_as_ci?" do 41 | context "when run as danger dry_run" do 42 | it "validates as CI source" do 43 | expect(described_class.validates_as_ci?(valid_env)).to be true 44 | end 45 | end 46 | 47 | it "does not validate as CI source outside danger dry_run" do 48 | expect(described_class.validates_as_ci?(invalid_env)).to be false 49 | end 50 | end 51 | 52 | describe "#new" do 53 | it "sets base_commit" do 54 | run_in_repo do 55 | expect(source(valid_env).base_commit).to eq("origin/master") 56 | end 57 | end 58 | 59 | it "sets head_commit" do 60 | run_in_repo do 61 | expect(source(valid_env).head_commit).to eq("feature_branch") 62 | end 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /spec/lib/danger/ci_sources/screwdriver_spec.rb: -------------------------------------------------------------------------------- 1 | # require "danger/ci_source/screwdriver" 2 | 3 | RSpec.describe Danger::Screwdriver do 4 | let(:valid_env) do 5 | { 6 | "SCREWDRIVER" => "true", 7 | "SD_PULL_REQUEST" => "42", 8 | "SCM_URL" => "git@github.com:danger/danger.git#branch" 9 | } 10 | end 11 | 12 | let(:source) { described_class.new(valid_env) } 13 | 14 | describe ".validates_as_ci?" do 15 | it "validates when the required env vars are set" do 16 | expect(described_class.validates_as_ci?(valid_env)).to be true 17 | end 18 | 19 | it "does not validate when the required env vars are not set" do 20 | valid_env.delete "SCREWDRIVER" 21 | expect(described_class.validates_as_ci?(valid_env)).to be false 22 | end 23 | end 24 | 25 | describe ".validates_as_pr?" do 26 | it "validates when the required env vars are set" do 27 | expect(described_class.validates_as_pr?(valid_env)).to be true 28 | end 29 | 30 | it "does not validate when the required pull request is not set" do 31 | valid_env["SD_PULL_REQUEST"] = nil 32 | expect(described_class.validates_as_pr?(valid_env)).to be false 33 | end 34 | 35 | it "does not validate when the required repo url is not set" do 36 | valid_env["SCM_URL"] = nil 37 | expect(described_class.validates_as_pr?(valid_env)).to be false 38 | end 39 | end 40 | 41 | describe ".new" do 42 | it "sets the required attributes" do 43 | expect(source.repo_slug).to eq("danger/danger") 44 | expect(source.pull_request_id).to eq("42") 45 | expect(source.repo_url).to eq("git@github.com:danger/danger.git") 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/lib/danger/ci_sources/support/find_repo_info_from_logs_spec.rb: -------------------------------------------------------------------------------- 1 | require "danger/ci_source/support/find_repo_info_from_logs" 2 | 3 | RSpec.describe Danger::FindRepoInfoFromLogs do 4 | describe "#call" do 5 | it "returns repo slug from logs" do 6 | remote_logs = IO.read("spec/fixtures/ci_source/support/remote.log") 7 | finder = described_class.new("github.com", remote_logs) 8 | 9 | result = finder.call 10 | 11 | expect(result.slug).to eq "danger/danger" 12 | end 13 | 14 | context "specify GitHub Enterprise URL" do 15 | it "returns repo slug from logs" do 16 | remote_logs = IO.read("spec/fixtures/ci_source/support/enterprise-remote.log") 17 | finder = described_class.new("artsyhub.com", remote_logs) 18 | 19 | result = finder.call 20 | 21 | expect(result.slug).to eq "enterdanger/enterdanger" 22 | end 23 | end 24 | 25 | context "specify remote in https" do 26 | it "returns repo slug from logs" do 27 | remote_logs = IO.read("spec/fixtures/ci_source/support/https-remote.log") 28 | finder = described_class.new("github.com", remote_logs) 29 | 30 | result = finder.call 31 | 32 | expect(result.slug).to eq "danger/danger" 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/lib/danger/ci_sources/surf_spec.rb: -------------------------------------------------------------------------------- 1 | require "danger/ci_source/surf" 2 | 3 | RSpec.describe Danger::Surf do 4 | let(:valid_env) do 5 | { 6 | "SURF_REPO" => "https://github.com/surf-build/surf", 7 | "SURF_NWO" => "surf-build/surf", 8 | "SURF_PR_NUM" => "29" 9 | } 10 | end 11 | 12 | let(:invalid_env) do 13 | { 14 | "CIRCLE" => "true" 15 | } 16 | end 17 | 18 | let(:source) { described_class.new(valid_env) } 19 | 20 | describe ".validates_as_ci?" do 21 | it "validates when the expected valid_env variables are set" do 22 | expect(described_class.validates_as_ci?(valid_env)).to be true 23 | end 24 | 25 | it "does not validated when some expected valid_env variables are missing" do 26 | expect(described_class.validates_as_ci?(invalid_env)).to be false 27 | end 28 | end 29 | 30 | describe ".validates_as_pr?" do 31 | it "validates when the expected valid_env variables are set" do 32 | expect(described_class.validates_as_pr?(valid_env)).to be true 33 | end 34 | 35 | it "does not validated when some expected valid_env variables are missing" do 36 | expect(described_class.validates_as_pr?(invalid_env)).to be false 37 | end 38 | end 39 | 40 | describe "#new" do 41 | it "sets the pull_request_id" do 42 | expect(source.pull_request_id).to eq("29") 43 | end 44 | 45 | it "sets the repo_slug" do 46 | expect(source.repo_slug).to eq("surf-build/surf") 47 | end 48 | 49 | it "sets the repo_url" do 50 | expect(source.repo_url).to eq("https://github.com/surf-build/surf") 51 | end 52 | end 53 | 54 | describe "#supported_request_sources" do 55 | it "supports GitHub" do 56 | expect(source.supported_request_sources).to include(Danger::RequestSources::GitHub) 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /spec/lib/danger/commands/dry_run_spec.rb: -------------------------------------------------------------------------------- 1 | require "danger/commands/dry_run" 2 | require "open3" 3 | 4 | RSpec.describe Danger::DryRun do 5 | context "prints help" do 6 | it "danger dry_run --help flag prints help" do 7 | stdout, = Open3.capture3("danger dry_run -h") 8 | expect(stdout).to include "Usage" 9 | end 10 | 11 | it "danger dry_run -h prints help" do 12 | stdout, = Open3.capture3("danger dry-run -h") 13 | expect(stdout).to include "Usage" 14 | end 15 | end 16 | 17 | describe ".options" do 18 | it "contains extra options for local command" do 19 | result = described_class.options 20 | 21 | expect(result).to include ["--pry", "Drop into a Pry shell after evaluating the Dangerfile."] 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/lib/danger/commands/init_helpers/interviewer_spec.rb: -------------------------------------------------------------------------------- 1 | require "danger/commands/init_helpers/interviewer" 2 | 3 | RSpec.describe Danger::Interviewer do 4 | let(:cork) { double("cork") } 5 | let(:interviewer) { Danger::Interviewer.new(cork) } 6 | 7 | describe "#link" do 8 | before do 9 | allow(interviewer).to receive(:say) 10 | end 11 | 12 | it "link URL is decorated" do 13 | interviewer.link("http://danger.systems/") 14 | expect(interviewer).to have_received(:say).with(" -> \e[4mhttp://danger.systems/\e[0m\n") 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/lib/danger/commands/init_spec.rb: -------------------------------------------------------------------------------- 1 | require "danger/commands/init" 2 | 3 | RSpec.describe Danger::Init do 4 | describe "#current_repo_slug" do 5 | let(:command) { Danger::Init.new CLAide::ARGV.new([]) } 6 | 7 | context "with git url" do 8 | it "returns correct results" do 9 | url = "git@github.com:author/repo.git" 10 | 11 | allow_any_instance_of(Danger::GitRepo).to receive(:origins).and_return(url) 12 | 13 | expect(command.current_repo_slug).to eq "author/repo" 14 | end 15 | end 16 | 17 | context "with github pages url" do 18 | it "returns correct results" do 19 | url = "https://github.com/author/repo.github.io.git" 20 | 21 | allow_any_instance_of(Danger::GitRepo).to receive(:origins).and_return(url) 22 | 23 | expect(command.current_repo_slug).to eq "author/repo.github.io" 24 | end 25 | end 26 | 27 | context "with other url" do 28 | it "returns [Your/Repo]" do 29 | url = "http://example.com" 30 | 31 | allow_any_instance_of(Danger::GitRepo).to receive(:origins).and_return(url) 32 | 33 | expect(command.current_repo_slug).to eq "[Your/Repo]" 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /spec/lib/danger/commands/local_helpers/pry_setup_spec.rb: -------------------------------------------------------------------------------- 1 | require "danger/commands/local_helpers/pry_setup" 2 | 3 | RSpec.describe Danger::PrySetup do 4 | before { cleanup } 5 | after { cleanup } 6 | 7 | describe "#setup_pry" do 8 | it "copies the Dangerfile and appends bindings.pry" do 9 | Dir.mktmpdir do |dir| 10 | dangerfile_path = "#{dir}/Dangerfile" 11 | File.write(dangerfile_path, "") 12 | 13 | dangerfile_copy = described_class 14 | .new(testing_ui) 15 | .setup_pry(dangerfile_path, "pr") 16 | 17 | expect(File).to exist(dangerfile_copy) 18 | expect(File.read(dangerfile_copy)).to include("binding.pry; File.delete(\"_Dangerfile.tmp\")") 19 | end 20 | end 21 | 22 | it "doesn't copy a nonexistent Dangerfile" do 23 | described_class.new(testing_ui).setup_pry("", "pr") 24 | 25 | expect(File).not_to exist("_Dangerfile.tmp") 26 | end 27 | 28 | it "warns when the pry gem is not installed" do 29 | ui = testing_ui 30 | expect(Kernel).to receive(:require).with("pry").and_raise(LoadError) 31 | 32 | expect do 33 | described_class.new(ui).setup_pry("Dangerfile", "pr") 34 | end.to raise_error(SystemExit) 35 | expect(ui.err_string).to include("Pry was not found") 36 | end 37 | 38 | def cleanup 39 | File.delete "_Dangerfile.tmp" if File.exist? "_Dangerfile.tmp" 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /spec/lib/danger/commands/local_spec.rb: -------------------------------------------------------------------------------- 1 | require "danger/commands/local" 2 | require "open3" 3 | 4 | RSpec.describe Danger::Local do 5 | context "prints help" do 6 | it "danger local --help flag prints help" do 7 | stdout, = Open3.capture3("danger local -h") 8 | expect(stdout).to include "Usage" 9 | end 10 | 11 | it "danger local -h prints help" do 12 | stdout, = Open3.capture3("danger local -h") 13 | expect(stdout).to include "Usage" 14 | end 15 | end 16 | 17 | describe ".options" do 18 | it "contains extra options for local command" do 19 | result = described_class.options 20 | 21 | expect(result).to include ["--use-merged-pr=[#id]", "The ID of an already merged PR inside your history to use as a reference for the local run."] 22 | expect(result).to include ["--clear-http-cache", "Clear the local http cache before running Danger locally."] 23 | expect(result).to include ["--pry", "Drop into a Pry shell after evaluating the Dangerfile."] 24 | end 25 | end 26 | 27 | context "default options" do 28 | it "pr number is nil and clear_http_cache defaults to false" do 29 | argv = CLAide::ARGV.new([]) 30 | 31 | result = described_class.new(argv) 32 | 33 | expect(result.instance_variable_get(:"@pr_num")).to eq nil 34 | expect(result.instance_variable_get(:"@clear_http_cache")).to eq false 35 | end 36 | end 37 | 38 | describe "#run" do 39 | before do 40 | allow(Danger::EnvironmentManager).to receive(:new) 41 | 42 | @dm = instance_double(Danger::Dangerfile, run: nil) 43 | allow(Danger::Dangerfile).to receive(:new).and_return @dm 44 | 45 | local_setup = instance_double(Danger::LocalSetup) 46 | allow(local_setup).to receive(:setup).and_yield 47 | allow(Danger::LocalSetup).to receive(:new).and_return local_setup 48 | end 49 | 50 | it "passes danger_id to Dangerfile and its env" do 51 | argv = CLAide::ARGV.new(["--danger_id=DANGER_ID"]) 52 | described_class.new(argv).run 53 | expect(Danger::EnvironmentManager).to have_received(:new) 54 | .with(ENV, a_kind_of(Cork::Board), "DANGER_ID") 55 | expect(@dm).to have_received(:run) 56 | .with(anything, anything, anything, "DANGER_ID", nil, nil) 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /spec/lib/danger/commands/plugin_json_spec.rb: -------------------------------------------------------------------------------- 1 | require "danger/commands/plugins/plugin_json" 2 | 3 | RSpec.describe Danger::PluginJSON do 4 | after do 5 | Danger::Plugin.clear_external_plugins 6 | end 7 | 8 | it "outputs a plugins documentation as json" do 9 | expect do 10 | described_class.run(["spec/fixtures/plugins/example_fully_documented.rb"]) 11 | end.to output(/DangerProselint/).to_stdout 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/lib/danger/commands/plugins/plugin_lint_spec.rb: -------------------------------------------------------------------------------- 1 | require "danger/commands/plugins/plugin_lint" 2 | 3 | RSpec.describe Danger::PluginLint do 4 | after do 5 | Danger::Plugin.clear_external_plugins 6 | end 7 | 8 | it "runs the command" do 9 | allow(STDOUT).to receive(:puts) 10 | described_class.run(["spec/fixtures/plugins/example_fully_documented.rb"]) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/lib/danger/commands/plugins/plugin_readme_spec.rb: -------------------------------------------------------------------------------- 1 | require "danger/commands/plugins/plugin_readme" 2 | 3 | RSpec.describe Danger::PluginReadme do 4 | after do 5 | Danger::Plugin.clear_external_plugins 6 | end 7 | 8 | it "runs the command" do 9 | allow(STDOUT).to receive(:puts).with(fixture_txt("commands/plugin_md_example")) 10 | described_class.run(["spec/fixtures/plugins/example_fully_documented.rb"]) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /spec/lib/danger/commands/staging_spec.rb: -------------------------------------------------------------------------------- 1 | require "danger/commands/staging" 2 | require "open3" 3 | 4 | RSpec.describe Danger::Staging do 5 | context "prints help" do 6 | it "danger staging --help flag prints help" do 7 | stdout, = Open3.capture3("danger staging -h") 8 | expect(stdout).to include "Usage" 9 | end 10 | 11 | it "danger staging -h prints help" do 12 | stdout, = Open3.capture3("danger staging -h") 13 | expect(stdout).to include "Usage" 14 | end 15 | end 16 | 17 | describe ".options" do 18 | it "contains extra options for staging command" do 19 | result = described_class.options 20 | 21 | expect(result).to include ["--pry", "Drop into a Pry shell after evaluating the Dangerfile."] 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /spec/lib/danger/core_ext/file_list_spec.rb: -------------------------------------------------------------------------------- 1 | require "danger/core_ext/file_list" 2 | 3 | RSpec.describe Danger::FileList do 4 | describe "#include?" do 5 | before do 6 | paths = ["path1/file_name.txt", "path1/file_name1.txt", "path2/subfolder/example.json", "path1/file_name_with_[brackets].txt"] 7 | @filelist = Danger::FileList.new(paths) 8 | end 9 | 10 | it "supports exact matches" do 11 | expect(@filelist.include?("path1/file_name.txt")).to eq(true) 12 | expect(@filelist.include?("path1/file_name_with_[brackets].txt")).to eq(true) 13 | end 14 | 15 | it "supports * for wildcards" do 16 | expect(@filelist.include?("path1/*.txt")).to eq(true) 17 | end 18 | 19 | it "supports ? for single chars" do 20 | expect(@filelist.include?("path1/file_name.???")).to eq(true) 21 | expect(@filelist.include?("path1/file_name.?")).to eq(false) 22 | end 23 | 24 | it "returns false if nothing was found" do 25 | expect(@filelist.include?("notFound")).to eq(false) 26 | end 27 | 28 | it "returns false if file path is nil" do 29 | @filelist = Danger::FileList.new([nil]) 30 | expect(@filelist.include?("pattern")).to eq(false) 31 | end 32 | 33 | it "supports {a,b} as union of multiple patterns" do 34 | expect(@filelist.include?("{path1/file_name.txt,path3/file_name.rb}")).to eq(true) 35 | expect(@filelist.include?("{path1/file_name.rb,path1/file_name.js}")).to eq(false) 36 | expect(@filelist.include?("{path1/file_name.rb,path1/file_name.js,path2/*}")).to eq(true) 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /spec/lib/danger/core_ext/string_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe String do 2 | describe "#danger_pluralize" do 3 | examples = [ 4 | { count: 0, string: "0 errors" }, 5 | { count: 1, string: "1 error" }, 6 | { count: 2, string: "2 errors" } 7 | ] 8 | 9 | examples.each do |example| 10 | it "returns '#{example[:string]}' when count = #{example[:count]}" do 11 | expect("error".danger_pluralize(example[:count])).to eq(example[:string]) 12 | end 13 | end 14 | end 15 | 16 | describe "#danger_underscore" do 17 | it "converts properly" do 18 | expect("ExampleClass".danger_underscore).to eq("example_class") 19 | end 20 | end 21 | 22 | describe "#danger_truncate" do 23 | it "truncates strings exceeding the limit" do 24 | expect("super long string".danger_truncate(5)).to eq("super...") 25 | end 26 | 27 | it "does not truncate strings that are on the limit" do 28 | expect("12345".danger_truncate(5)).to eq("12345") 29 | end 30 | 31 | it "does not truncate strings that are within the limit" do 32 | expect("123".danger_truncate(5)).to eq("123") 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/lib/danger/danger_core/danger_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Danger do 2 | it "has a version number" do 3 | expect(Danger::VERSION).not_to be nil 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/lib/danger/danger_core/messages/markdown_spec.rb: -------------------------------------------------------------------------------- 1 | require_relative "./shared_examples" 2 | require "danger/danger_core/messages/violation" 3 | require "danger/danger_core/messages/markdown" 4 | 5 | RSpec.describe Danger::Markdown do 6 | subject(:markdown) { described_class.new(message, file, line) } 7 | let(:message) { "hello world" } 8 | let(:file) { nil } 9 | let(:line) { nil } 10 | 11 | describe "#initialize" do 12 | subject { described_class.new("hello world") } 13 | 14 | it "defaults file to nil" do 15 | expect(subject.file).to be nil 16 | end 17 | 18 | it "defaults line to nil" do 19 | expect(subject.line).to be nil 20 | end 21 | end 22 | 23 | describe "#<=>" do 24 | subject { markdown <=> other } 25 | context "when other is a Violation" do 26 | let(:other) { Danger::Violation.new("hello world", false, other_file, other_line) } 27 | let(:other_file) { "test" } 28 | let(:other_line) { rand(4000) } 29 | it { is_expected.to eq(1) } 30 | end 31 | 32 | context "when other is a Markdown" do 33 | let(:other) { Danger::Markdown.new("example message", other_file, other_line) } 34 | 35 | it_behaves_like "compares by file and line" 36 | end 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /spec/lib/danger/danger_core/messages/shared_examples.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This helper method and the examples are used for the specs for #<=> on Markdown and Violation 4 | 5 | def random_alphas(n) 6 | (0...n).map { ("a".."z").to_a[rand(26)] } 7 | end 8 | 9 | RSpec.shared_examples_for "compares by line" do 10 | context "when line is nil" do 11 | let(:line) { nil } 12 | 13 | context "when other_line is nil" do 14 | let(:other_line) { nil } 15 | it { is_expected.to eq(0) } 16 | end 17 | 18 | context "when other_line is not nil" do 19 | let(:other_line) { 1 } 20 | it { is_expected.to eq(-1) } 21 | end 22 | end 23 | 24 | context "when line is not nil" do 25 | let(:line) { rand(4000) } 26 | context "when other_line is nil" do 27 | let(:other_line) { nil } 28 | it { is_expected.to eq(1) } 29 | end 30 | 31 | context "when lines are the same" do 32 | let(:other_line) { line } 33 | it { is_expected.to eq 0 } 34 | end 35 | 36 | context "when line < other_line" do 37 | let(:other_line) { line + 10 } 38 | it { is_expected.to eq(-1) } 39 | end 40 | 41 | context "when line < other_line" do 42 | let(:other_line) { line - 10 } 43 | it { is_expected.to eq(1) } 44 | end 45 | end 46 | end 47 | 48 | RSpec.shared_examples_for "compares by file and line" do 49 | let(:other_line) { rand(4000) } 50 | context "when file is nil" do 51 | let(:file) { nil } 52 | 53 | context "when other_file is nil" do 54 | let(:other_file) { nil } 55 | it { is_expected.to eq(0) } 56 | end 57 | 58 | context "when other_file is not nil" do 59 | let(:other_file) { "world.txt" } 60 | it { is_expected.to eq(-1) } 61 | end 62 | end 63 | 64 | context "when file is not nil" do 65 | let(:file) { "hello.txt" } 66 | 67 | context "when other_file is nil" do 68 | let(:other_file) { nil } 69 | it { is_expected.to eq(1) } 70 | end 71 | 72 | context "when files are the same" do 73 | let(:other_file) { file } 74 | 75 | include_examples "compares by line" 76 | end 77 | 78 | context "when file < other_file" do 79 | let(:other_file) { "world.txt" } 80 | it { is_expected.to eq(-1) } 81 | end 82 | 83 | context "when file > other_file" do 84 | let(:other_file) { "aardvark.txt" } 85 | it { is_expected.to eq 1 } 86 | end 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /spec/lib/danger/helpers/array_subclass_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Danger::Helpers::ArraySubclass do 2 | class List; include Danger::Helpers::ArraySubclass; end 3 | class OtherList; include Danger::Helpers::ArraySubclass; end 4 | 5 | it "acts as array" do 6 | first_list = List.new([1, 2, 3]) 7 | second_list = List.new([4, 5, 6]) 8 | third_list = List.new([1, 2, 3]) 9 | fourth_list = List.new([7, 7]) 10 | 11 | mapped_list = first_list.map { |item| item + 1 } 12 | concated_list = first_list + second_list 13 | mapped_mutated_list = third_list.map! { |item| item + 10 } 14 | deleted_from_list = fourth_list.delete_at(0) 15 | reduced_list = first_list.each_with_object({}) do |el, accum| 16 | accum.store(el, el) 17 | end 18 | 19 | expect(first_list.length).to eq(3) 20 | expect(mapped_list).to eq(List.new([2, 3, 4])) 21 | expect(concated_list).to eq(List.new([1, 2, 3, 4, 5, 6])) 22 | expect(third_list).to eq(List.new([11, 12, 13])) 23 | expect(mapped_mutated_list).to eq(List.new([11, 12, 13])) 24 | expect(deleted_from_list).to eq(7) 25 | expect(fourth_list).to eq(List.new([7])) 26 | expect(reduced_list).to eq({ 1 => 1, 2 => 2, 3 => 3 }) 27 | end 28 | 29 | describe "equality" do 30 | it "equals with same class same size and same values" do 31 | first_list = List.new([1, 2, 3]) 32 | second_list = List.new([1, 2, 3]) 33 | third_list = List.new([4, 5, 6]) 34 | 35 | expect(first_list).to eq(second_list) 36 | expect(first_list).not_to eq(third_list) 37 | end 38 | 39 | it "not equals with other classes" do 40 | first_list = List.new([1, 2, 3]) 41 | second_list = OtherList.new([1, 2, 3]) 42 | third_list = [4, 5, 6] 43 | 44 | expect(first_list).not_to eq(second_list) 45 | expect(first_list).not_to eq(third_list) 46 | end 47 | end 48 | 49 | describe "#respond_to_missing?" do 50 | context "with missing method" do 51 | it "returns false" do 52 | list = List.new([]) 53 | expect(list.respond_to?(:missing_method)).to be(false) 54 | end 55 | end 56 | 57 | context "with existing method" do 58 | it "returns true" do 59 | list = List.new([]) 60 | expect(list.respond_to?(:to_a)).to be(true) 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /spec/lib/danger/helpers/message_groups_array_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require "danger/danger_core/message_group" 2 | require "danger/helpers/message_groups_array_helper" 3 | 4 | RSpec.describe Danger::Helpers::MessageGroupsArrayHelper do 5 | subject(:array) do 6 | class << message_groups 7 | include Danger::Helpers::MessageGroupsArrayHelper 8 | end 9 | message_groups 10 | end 11 | let(:message_groups) { [] } 12 | 13 | it { is_expected.to be_a Array } 14 | it { is_expected.to respond_to :fake_warnings_array } 15 | it { is_expected.to respond_to :fake_errors_array } 16 | 17 | shared_context "with two message groups" do 18 | let(:message_group_a) { double(Danger::MessageGroup.new) } 19 | let(:message_group_b) { double(Danger::MessageGroup.new) } 20 | let(:message_groups) { [message_group_a, message_group_b] } 21 | end 22 | 23 | describe "#fake_warnings_array" do 24 | subject { array.fake_warnings_array } 25 | 26 | context "with no message groups" do 27 | it "returns an fake array with a count method which returns 0" do 28 | expect(subject.count).to eq 0 29 | end 30 | end 31 | 32 | context "with two message groups" do 33 | include_context "with two message groups" 34 | 35 | before do 36 | allow(message_group_a).to receive(:stats).and_return(warnings_count: 10, errors_count: 35) 37 | allow(message_group_b).to receive(:stats).and_return(warnings_count: 6, errors_count: 9) 38 | end 39 | 40 | it "returns an fake array with a count method which returns 0" do 41 | expect(subject.count).to eq 16 42 | end 43 | end 44 | end 45 | 46 | describe "#fake_errors_array" do 47 | subject { array.fake_errors_array } 48 | 49 | context "with no message groups" do 50 | it "returns an fake array with a count method which returns 0" do 51 | expect(subject.count).to eq 0 52 | end 53 | end 54 | 55 | context "with two message groups" do 56 | include_context "with two message groups" 57 | 58 | before do 59 | allow(message_group_a).to receive(:stats).and_return(warnings_count: 10, errors_count: 35) 60 | allow(message_group_b).to receive(:stats).and_return(warnings_count: 6, errors_count: 9) 61 | end 62 | 63 | it "returns an fake array with a count method which returns 44" do 64 | expect(subject.count).to eq 44 65 | end 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /spec/lib/danger/plugin_support/gems_resolver_spec.rb: -------------------------------------------------------------------------------- 1 | require "lib/danger/plugin_support/gems_resolver" 2 | 3 | RSpec.describe Danger::GemsResolver do 4 | def expected_path(dir) 5 | [ 6 | "#{dir}/vendor/gems/ruby/2.3.0/gems/danger-rubocop-0.3.0/lib/danger_plugin.rb", 7 | "#{dir}/vendor/gems/ruby/2.3.0/gems/danger-rubocop-0.3.0/lib/version.rb" 8 | ] 9 | end 10 | 11 | def expected_gems 12 | [ 13 | { 14 | name: "danger-rubocop", 15 | gem: "danger-rubocop", 16 | author: ["Ash Furrow"], 17 | url: "https://github.com/ashfurrow/danger-rubocop", 18 | description: "A Danger plugin for running Ruby files through Rubocop.", 19 | license: "MIT", 20 | version: "0.3.0" 21 | } 22 | ] 23 | end 24 | 25 | # Mimic bundle install --path vendor/gems when install danger-rubocop 26 | def fake_bundle_install_path_vendor_gems_in(spec_root) 27 | FileUtils.mkdir_p("vendor/gems/ruby/2.3.0/gems/danger-rubocop-0.3.0/lib") 28 | FileUtils.touch("vendor/gems/ruby/2.3.0/gems/danger-rubocop-0.3.0/lib/version.rb") 29 | FileUtils.touch("vendor/gems/ruby/2.3.0/gems/danger-rubocop-0.3.0/lib/danger_plugin.rb") 30 | 31 | FileUtils.mkdir_p("vendor/gems/ruby/2.3.0/specifications") 32 | FileUtils.cp( 33 | "#{spec_root}/spec/fixtures/gemspecs/danger-rubocop.gemspec", 34 | "vendor/gems/ruby/2.3.0/specifications/danger-rubocop-0.3.0.gemspec" 35 | ) 36 | end 37 | 38 | describe "#call" do 39 | it "create gemfile, bundle, and returns" do 40 | spec_root = Dir.pwd 41 | gem_names = ["danger-rubocop"] 42 | tmpdir = Dir.mktmpdir 43 | 44 | # We want to control the temp dir created in gems_resolver 45 | # to compare in our test 46 | allow(Dir).to receive(:mktmpdir) { tmpdir } 47 | 48 | Dir.chdir(tmpdir) do 49 | expect(Bundler).to receive(:with_clean_env) do 50 | fake_bundle_install_path_vendor_gems_in(spec_root) 51 | end 52 | 53 | resolver = described_class.new(gem_names) 54 | resolver.call 55 | result = resolver.send(:all_gems_metadata) 56 | 57 | expect(result).to be_a Array 58 | expect(result.first).to match_array expected_path(tmpdir) 59 | expect(result.last).to match_array expected_gems 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /spec/lib/danger/plugin_support/plugin_file_resolver_spec.rb: -------------------------------------------------------------------------------- 1 | require "lib/danger/plugin_support/plugin_file_resolver" 2 | 3 | RSpec.describe Danger::PluginFileResolver do 4 | describe "#resolve" do 5 | context "Given list of gems" do 6 | it "resolves for gems" do 7 | resolver = Danger::PluginFileResolver.new(["danger", "rails"]) 8 | 9 | expect(Danger::GemsResolver).to receive_message_chain(:new, :call) 10 | 11 | resolver.resolve 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/lib/danger/plugin_support/plugin_linter_spec.rb: -------------------------------------------------------------------------------- 1 | require "danger/plugin_support/plugin_parser" 2 | require "danger/plugin_support/plugin_linter" 3 | 4 | def json_doc_for_path(path) 5 | parser = Danger::PluginParser.new path 6 | parser.parse 7 | parser.to_json 8 | end 9 | 10 | RSpec.describe Danger::PluginParser do 11 | it "creates a set of errors for fixtured plugins" do 12 | json = json_doc_for_path("spec/fixtures/plugins/plugin_many_methods.rb") 13 | linter = Danger::PluginLinter.new(json) 14 | linter.lint 15 | titles = ["Description Markdown", "Examples", "Description", "Description"] 16 | expect(linter.errors.map(&:title)).to eq(titles) 17 | end 18 | 19 | it "creates a set of warnings for fixtured plugins" do 20 | json = json_doc_for_path("spec/fixtures/plugins/plugin_many_methods.rb") 21 | linter = Danger::PluginLinter.new(json) 22 | linter.lint 23 | 24 | titles = ["Tags", "References", "Return Type", "Return Type", "Return Type", "Unknown Param", "Return Type"] 25 | expect(linter.warnings.map(&:title)).to eq(titles) 26 | end 27 | 28 | it "fails when there are errors" do 29 | linter = Danger::PluginLinter.new({}) 30 | expect(linter.failed?).to eq(false) 31 | 32 | linter.warnings = [1, 2, 3] 33 | expect(linter.failed?).to eq(false) 34 | 35 | linter.errors = [1, 2, 3] 36 | expect(linter.failed?).to eq(true) 37 | end 38 | 39 | it "handles outputting a warning" do 40 | ui = testing_ui 41 | linter = Danger::PluginLinter.new({}) 42 | warning = Danger::PluginLinter::Rule.new(:warning, 30, "Example Title", "Example Description", nil) 43 | warning.metadata = { name: "NameOfExample" } 44 | warning.type = "TypeOfThing" 45 | 46 | linter.warnings << warning 47 | 48 | linter.print_summary(ui) 49 | 50 | expect(ui.string).to eq("\n[!] Passed\n\nWarnings\n - Example Title - NameOfExample (TypeOfThing):\n - Example Description\n - @see - https://github.com/dbgrandi/danger-prose/blob/v2.0.0/lib/danger_plugin.rb#L30\n\n") 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /spec/lib/danger/plugin_support/plugin_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Danger::Plugin do 2 | it "creates an instance name based on the class name" do 3 | class DangerTestClassNamePlugin < Danger::Plugin; end 4 | expect(DangerTestClassNamePlugin.instance_name).to eq("test_class_name_plugin") 5 | end 6 | 7 | it "should forward unknown method calls to the dangerfile" do 8 | class DangerTestForwardPlugin < Danger::Plugin; end 9 | 10 | class DangerFileMock 11 | attr_accessor :pants 12 | 13 | def check(*args, **kargs); end 14 | end 15 | 16 | plugin = DangerTestForwardPlugin.new(DangerFileMock.new) 17 | expect do 18 | plugin.pants 19 | end.to_not raise_error 20 | 21 | expect do 22 | plugin.check("a", "b", verbose: true) 23 | end.to_not raise_error 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/lib/danger/plugins/dangerfile_bitbucket_cloud_plugin_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe Danger::DangerfileBitbucketCloudPlugin, host: :bitbucket_cloud do 2 | let(:dangerfile) { testing_dangerfile } 3 | let(:plugin) { described_class.new(dangerfile) } 4 | 5 | before do 6 | stub_pull_request 7 | end 8 | 9 | describe "plugin" do 10 | before do 11 | dangerfile.env.request_source.fetch_details 12 | end 13 | 14 | describe "#pr_json" do 15 | it "has a non empty json" do 16 | expect(plugin.pr_json).to be_truthy 17 | end 18 | end 19 | 20 | describe "#pr_title" do 21 | it "has a fetched title" do 22 | expect(plugin.pr_title).to eq("This is a danger test for bitbucket cloud") 23 | end 24 | end 25 | 26 | describe "#pr_author" do 27 | it "has a fetched author" do 28 | expect(plugin.pr_author).to eq("AName") 29 | end 30 | end 31 | 32 | describe "#pr_link" do 33 | it "has a fetched link to it self" do 34 | expect(plugin.pr_link).to eq("https://api.bitbucket.org/2.0/repositories/ios/fancyapp/pullrequests/2080") 35 | end 36 | end 37 | 38 | describe "#branch_for_base" do 39 | it "has a fetched branch for base" do 40 | expect(plugin.branch_for_base).to eq("develop") 41 | end 42 | end 43 | 44 | describe "#branch_for_head" do 45 | it "has a fetched branch for head" do 46 | expect(plugin.branch_for_head).to eq("feature/test_danger") 47 | end 48 | end 49 | 50 | describe "#base_commit" do 51 | it "has a fetched base commit" do 52 | expect(plugin.base_commit).to eq("9c823062cf99") 53 | end 54 | end 55 | 56 | describe "#head_commit" do 57 | it "has a fetched head commit" do 58 | expect(plugin.head_commit).to eq("b6f5656b6ac9") 59 | end 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /spec/lib/danger/request_sources/github/github_review_resolver_spec.rb: -------------------------------------------------------------------------------- 1 | require "danger/request_sources/github/github_review_resolver" 2 | require "danger/request_sources/github/github_review" 3 | 4 | RSpec.describe Danger::RequestSources::GitHubSource::ReviewResolver do 5 | let(:review) { double(Danger::RequestSources::GitHubSource::Review) } 6 | 7 | describe "should_submit?" do 8 | context "when submission body the same as review has" do 9 | before do 10 | allow(review).to receive(:body).and_return "super body" 11 | end 12 | 13 | it "returns false" do 14 | expect(described_class.should_submit?(review, "super body")).to be false 15 | end 16 | end 17 | 18 | context "when submission body is different to review body" do 19 | let(:submission_body) { "submission body" } 20 | 21 | before do 22 | allow(review).to receive(:body).and_return "unique body" 23 | end 24 | 25 | it "returns true" do 26 | expect(described_class.should_submit?(review, "super body")).to be true 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/lib/danger/request_sources/local_only.rb: -------------------------------------------------------------------------------- 1 | require "erb" 2 | 3 | require "danger/request_sources/local_only" 4 | 5 | RSpec.describe Danger::RequestSources::LocalOnly, host: :local do 6 | let(:ci) { instance_double("Danger::LocalOnlyGitRepo", base_commit: "origin/master", head_commit: "feature_branch") } 7 | let(:subject) { described_class.new(ci, {}) } 8 | 9 | describe "validation" do 10 | it "validates as an API source" do 11 | expect(subject.validates_as_api_source?).to be_truthy 12 | end 13 | 14 | it "validates as CI" do 15 | expect(subject.validates_as_ci?).to be_truthy 16 | end 17 | end 18 | 19 | describe "scm" do 20 | it "Sets up the scm" do 21 | expect(subject.scm).to be_kind_of(Danger::GitRepo) 22 | end 23 | end 24 | 25 | describe "#setup_danger_branches" do 26 | before do 27 | allow(subject.scm).to receive(:exec).and_return("found_some") 28 | end 29 | 30 | context "when specified head is missing" do 31 | before { expect(subject.scm).to receive(:exec).and_return("") } 32 | 33 | it "raises an error" do 34 | expect { subject.setup_danger_branches }.to raise_error("Specified commit 'origin/master' not found") 35 | end 36 | end 37 | 38 | it "sets danger_head to feature_branch" do 39 | expect(subject.scm).to receive(:exec).with(/^branch.*head.*feature_branch/).and_return("feature_branch") 40 | subject.setup_danger_branches 41 | end 42 | 43 | it "sets danger_base to origin/master" do 44 | expect(subject.scm).to receive(:exec).with(%r{^branch.*base.*origin/master}).and_return("origin/master") 45 | subject.setup_danger_branches 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/lib/danger/request_sources/support/get_ignored_violation_spec.rb: -------------------------------------------------------------------------------- 1 | RSpec.describe GetIgnoredViolation do 2 | describe "#call" do 3 | context "Without specific ignore sentence" do 4 | it "returns empty array" do 5 | result = described_class.new("No danger ignore").call 6 | 7 | expect(result).to eq [] 8 | end 9 | end 10 | 11 | context "With specific ignore sentence" do 12 | it "returns content in the quotes" do 13 | sentence = %(Danger: Ignore "This build didn't pass tests") 14 | result = described_class.new(sentence).call 15 | 16 | expect(result).to eq ["This build didn't pass tests"] 17 | end 18 | end 19 | 20 | context "With specific ignore sentence contains escapted quote" do 21 | it "returns content in the quotes" do 22 | sentence = %("ignoring this:\r\n>Danger: Ignore \"`TophatModels.v20` changed.\"") 23 | result = described_class.new(sentence).call 24 | 25 | expect(result).to eq [%(`TophatModels.v20` changed.)] 26 | end 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /spec/lib/danger/request_sources/vsts_api_spec.rb: -------------------------------------------------------------------------------- 1 | require "danger/request_sources/vsts_api" 2 | 3 | RSpec.describe Danger::RequestSources::VSTSAPI, host: :vsts do 4 | describe "#inspect" do 5 | it "masks personal access token on inspect" do 6 | allow(ENV).to receive(:[]).with("ENVDANGER_VSTS_API_TOKEN") { "supertopsecret" } 7 | api = described_class.new("danger", 1, stub_env) 8 | 9 | inspected = api.inspect 10 | 11 | expect(inspected).to include(%(@token="********")) 12 | end 13 | 14 | it "handles http hosts" do 15 | env = stub_env 16 | env["DANGER_VSTS_HOST"] = "http://my_url" 17 | api = described_class.new("danger", 1, env) 18 | expect(api.pr_api_endpoint).to eq("http://my_url/_apis/git/repositories/danger/pullRequests/1") 19 | env["DANGER_VSTS_HOST"] = "my_url" 20 | api = described_class.new("danger", 1, env) 21 | expect(api.pr_api_endpoint).to eq("https://my_url/_apis/git/repositories/danger/pullRequests/1") 22 | end 23 | 24 | it "checks uses ssl only for https urls" do 25 | env = stub_env 26 | env["DANGER_VSTS_HOST"] = "http://my_url" 27 | api = described_class.new("danger", 1, env) 28 | expect(api.send(:use_ssl)).to eq(false) 29 | 30 | env["DANGER_VSTS_HOST"] = "https://my_url" 31 | api = described_class.new("danger", 1, env) 32 | expect(api.send(:use_ssl)).to eq(true) 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/support/bitbucket_cloud_helper.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | module Support 3 | module BitbucketCloudHelper 4 | def stub_env 5 | { 6 | "DANGER_BITBUCKETCLOUD_USERNAME" => "a.name", 7 | "DANGER_BITBUCKETCLOUD_UUID" => "c91be865-efc6-49a6-93c5-24e1267c479b", 8 | "DANGER_BITBUCKETCLOUD_PASSWORD" => "a_password", 9 | "JENKINS_URL" => "http://jenkins.example.com/job/ios-check-pullrequest/", 10 | "GIT_URL" => "ssh://git@stash.example.com:7999/ios/fancyapp.git", 11 | "ghprbPullId" => "2080" 12 | } 13 | end 14 | 15 | def stub_ci 16 | Danger::Jenkins.new(stub_env) 17 | end 18 | 19 | def stub_request_source(env = stub_env) 20 | Danger::RequestSources::BitbucketCloud.new(stub_ci, env) 21 | end 22 | 23 | def stub_pull_request 24 | raw_file = File.new("spec/fixtures/bitbucket_cloud_api/pr_response.json") 25 | url = "https://api.bitbucket.org/2.0/repositories/ios/fancyapp/pullrequests/2080" 26 | WebMock.stub_request(:get, url).to_return(raw_file) 27 | end 28 | 29 | def stub_pull_requests 30 | raw_file = File.new("spec/fixtures/bitbucket_cloud_api/prs_response.json") 31 | url = "https://api.bitbucket.org/2.0/repositories/ios/fancyapp/pullrequests?q=source.branch.name=%22feature_branch%22" 32 | WebMock.stub_request(:get, url).to_return(raw_file) 33 | end 34 | 35 | def stub_access_token 36 | raw_file = File.new("spec/fixtures/bitbucket_cloud_api/oauth2_response.json") 37 | url = "https://bitbucket.org/site/oauth2/access_token" 38 | WebMock.stub_request(:post, url).to_return(raw_file) 39 | end 40 | 41 | def stub_pull_request_comment 42 | raw_file = File.new("spec/fixtures/bitbucket_cloud_api/pr_comments.json") 43 | url = "https://api.bitbucket.org/2.0/repositories/ios/fancyapp/pullrequests/2080/comments?pagelen=100&q=deleted+%7E+false+AND+user.uuid+%7E+%22c91be865-efc6-49a6-93c5-24e1267c479b%22" 44 | WebMock.stub_request(:get, url).to_return(raw_file) 45 | end 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /spec/support/bitbucket_server_helper.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | module Support 3 | module BitbucketServerHelper 4 | def stub_env 5 | { 6 | "DANGER_BITBUCKETSERVER_HOST" => "stash.example.com", 7 | "DANGER_BITBUCKETSERVER_USERNAME" => "a.name", 8 | "DANGER_BITBUCKETSERVER_PASSWORD" => "a_password", 9 | "JENKINS_URL" => "http://jenkins.example.com/job/ios-check-pullrequest/", 10 | "GIT_URL" => "ssh://git@stash.example.com:7999/ios/fancyapp.git", 11 | "ghprbPullId" => "2080" 12 | } 13 | end 14 | 15 | def stub_ci 16 | Danger::Jenkins.new(stub_env) 17 | end 18 | 19 | def stub_request_source 20 | Danger::RequestSources::GitLab.new(stub_ci, stub_env) 21 | end 22 | 23 | def stub_pull_request 24 | raw_file = File.new("spec/fixtures/bitbucket_server_api/pr_response.json") 25 | url = "https://stash.example.com/rest/api/1.0/projects/ios/repos/fancyapp/pull-requests/2080" 26 | WebMock.stub_request(:get, url).to_return(raw_file) 27 | end 28 | 29 | def stub_pull_request_diff 30 | raw_file = File.new("spec/fixtures/bitbucket_server_api/pr_diff_response.json") 31 | url = "https://stash.example.com/rest/api/1.0/projects/ios/repos/fancyapp/pull-requests/2080/diff?withComments=false" 32 | WebMock.stub_request(:get, url).to_return(raw_file) 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /spec/support/github_helper.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | module Support 3 | module GitHubHelper 4 | def expected_headers 5 | {} 6 | end 7 | 8 | def stub_env 9 | { 10 | "HAS_JOSH_K_SEAL_OF_APPROVAL" => "true", 11 | "TRAVIS_PULL_REQUEST" => "800", 12 | "TRAVIS_REPO_SLUG" => "artsy/eigen", 13 | "TRAVIS_COMMIT_RANGE" => "759adcbd0d8f...13c4dc8bb61d", 14 | "DANGER_GITHUB_API_TOKEN" => "hi" 15 | } 16 | end 17 | 18 | def stub_ci 19 | env = { "CI_PULL_REQUEST" => "https://github.com/artsy/eigen/pull/800" } 20 | Danger::CircleCI.new(env) 21 | end 22 | 23 | def stub_request_source 24 | Danger::RequestSources::GitHub.new(stub_ci, stub_env) 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/support/matchers/have_instance_variables_matcher.rb: -------------------------------------------------------------------------------- 1 | RSpec::Matchers.define(:have_instance_variables) do |expected| 2 | match do |actual| 3 | expected.each do |instance_variable, expected_value| 4 | expect(actual.instance_variable_get(instance_variable)).to eq(expected_value) 5 | end 6 | end 7 | 8 | failure_message do |actual| 9 | expected.each do |instance_variable, expected_value| 10 | actual_value = actual.instance_variable_get(instance_variable) 11 | if actual_value != expected_value 12 | return "expected #{actual}#{instance_variable} to match #{expected_value.inspect}, but got #{actual_value.inspect}." 13 | end 14 | end 15 | end 16 | 17 | failure_message_when_negated do |actual| 18 | expected.each do |instance_variable, expected_value| 19 | actual_value = actual.instance_variable_get(instance_variable) 20 | if actual_value == expected_value 21 | return "expected #{actual}#{instance_variable} not to match #{expected_value.inspect}, but got #{actual_value.inspect}." 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /spec/support/vsts_helper.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | module Support 3 | module VSTSHelper 4 | def stub_env 5 | { 6 | "AGENT_ID" => "4a4d3fa1-bae7-4e5f-a14a-a50b184c74aa", 7 | "DANGER_VSTS_HOST" => "https://example.visualstudio.com/example", 8 | "DANGER_VSTS_API_TOKEN" => "a_token", 9 | "SYSTEM_TEAMFOUNDATIONCOLLECTIONURI" => "https://example.visualstudio.com", 10 | "BUILD_SOURCEBRANCH" => "refs/pull/1/merge", 11 | "BUILD_REPOSITORY_URI" => "https://example.visualstudio.com/_git/example", 12 | "BUILD_REASON" => "PullRequest", 13 | "BUILD_REPOSITORY_NAME" => "example", 14 | "SYSTEM_TEAMPROJECT" => "example", 15 | "SYSTEM_PULLREQUEST_PULLREQUESTNUMBER" => 1, 16 | "BUILD_REPOSITORY_PROVIDER" => "TfsGit" 17 | } 18 | end 19 | 20 | def stub_ci 21 | Danger::AzurePipelines.new(stub_env) 22 | end 23 | 24 | def stub_request_source 25 | Danger::RequestSources::VSTS.new(stub_ci, stub_env) 26 | end 27 | 28 | def stub_pull_request 29 | raw_file = File.new("spec/fixtures/vsts_api/pr_response.json") 30 | url = "https://example.visualstudio.com/example/_apis/git/repositories/example/pullRequests/1?api-version=3.0" 31 | WebMock.stub_request(:get, url).to_return(raw_file) 32 | end 33 | 34 | def stub_get_comments_request_no_danger 35 | raw_file = File.new("spec/fixtures/vsts_api/no_danger_comments_response.json") 36 | url = "https://example.visualstudio.com/example/_apis/git/repositories/example/pullRequests/1/threads?api-version=3.0" 37 | WebMock.stub_request(:get, url).to_return(raw_file) 38 | end 39 | 40 | def stub_get_comments_request_with_danger 41 | raw_file = File.new("spec/fixtures/vsts_api/danger_comments_response.json") 42 | url = "https://example.visualstudio.com/example/_apis/git/repositories/example/pullRequests/1/threads?api-version=3.0" 43 | WebMock.stub_request(:get, url).to_return(raw_file) 44 | end 45 | end 46 | end 47 | end 48 | --------------------------------------------------------------------------------