danger 8.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +22 -0
- data/README.md +94 -0
- data/bin/danger +5 -0
- data/lib/assets/DangerfileTemplate +13 -0
- data/lib/danger.rb +44 -0
- data/lib/danger/ci_source/appcenter.rb +55 -0
- data/lib/danger/ci_source/appveyor.rb +60 -0
- data/lib/danger/ci_source/azure_pipelines.rb +44 -0
- data/lib/danger/ci_source/bamboo.rb +41 -0
- data/lib/danger/ci_source/bitbucket_pipelines.rb +37 -0
- data/lib/danger/ci_source/bitrise.rb +65 -0
- data/lib/danger/ci_source/buddybuild.rb +62 -0
- data/lib/danger/ci_source/buildkite.rb +51 -0
- data/lib/danger/ci_source/ci_source.rb +37 -0
- data/lib/danger/ci_source/circle.rb +94 -0
- data/lib/danger/ci_source/circle_api.rb +51 -0
- data/lib/danger/ci_source/cirrus.rb +31 -0
- data/lib/danger/ci_source/code_build.rb +57 -0
- data/lib/danger/ci_source/codefresh.rb +53 -0
- data/lib/danger/ci_source/codeship.rb +44 -0
- data/lib/danger/ci_source/dotci.rb +52 -0
- data/lib/danger/ci_source/drone.rb +71 -0
- data/lib/danger/ci_source/github_actions.rb +43 -0
- data/lib/danger/ci_source/gitlab_ci.rb +86 -0
- data/lib/danger/ci_source/jenkins.rb +149 -0
- data/lib/danger/ci_source/local_git_repo.rb +119 -0
- data/lib/danger/ci_source/local_only_git_repo.rb +47 -0
- data/lib/danger/ci_source/screwdriver.rb +47 -0
- data/lib/danger/ci_source/semaphore.rb +37 -0
- data/lib/danger/ci_source/support/commits.rb +17 -0
- data/lib/danger/ci_source/support/find_repo_info_from_logs.rb +35 -0
- data/lib/danger/ci_source/support/find_repo_info_from_url.rb +42 -0
- data/lib/danger/ci_source/support/local_pull_request.rb +14 -0
- data/lib/danger/ci_source/support/no_pull_request.rb +7 -0
- data/lib/danger/ci_source/support/no_repo_info.rb +5 -0
- data/lib/danger/ci_source/support/pull_request_finder.rb +179 -0
- data/lib/danger/ci_source/support/remote_pull_request.rb +15 -0
- data/lib/danger/ci_source/support/repo_info.rb +10 -0
- data/lib/danger/ci_source/surf.rb +37 -0
- data/lib/danger/ci_source/teamcity.rb +161 -0
- data/lib/danger/ci_source/travis.rb +51 -0
- data/lib/danger/ci_source/vsts.rb +73 -0
- data/lib/danger/ci_source/xcode_server.rb +48 -0
- data/lib/danger/clients/rubygems_client.rb +14 -0
- data/lib/danger/commands/dangerfile/gem.rb +43 -0
- data/lib/danger/commands/dangerfile/init.rb +30 -0
- data/lib/danger/commands/dry_run.rb +54 -0
- data/lib/danger/commands/init.rb +297 -0
- data/lib/danger/commands/init_helpers/interviewer.rb +92 -0
- data/lib/danger/commands/local.rb +83 -0
- data/lib/danger/commands/local_helpers/http_cache.rb +36 -0
- data/lib/danger/commands/local_helpers/local_setup.rb +46 -0
- data/lib/danger/commands/local_helpers/pry_setup.rb +31 -0
- data/lib/danger/commands/plugins/plugin_json.rb +46 -0
- data/lib/danger/commands/plugins/plugin_lint.rb +54 -0
- data/lib/danger/commands/plugins/plugin_readme.rb +45 -0
- data/lib/danger/commands/pr.rb +92 -0
- data/lib/danger/commands/runner.rb +94 -0
- data/lib/danger/commands/staging.rb +53 -0
- data/lib/danger/commands/systems.rb +43 -0
- data/lib/danger/comment_generators/bitbucket_server.md.erb +20 -0
- data/lib/danger/comment_generators/bitbucket_server_inline.md.erb +15 -0
- data/lib/danger/comment_generators/bitbucket_server_message_group.md.erb +12 -0
- data/lib/danger/comment_generators/github.md.erb +55 -0
- data/lib/danger/comment_generators/github_inline.md.erb +26 -0
- data/lib/danger/comment_generators/gitlab.md.erb +40 -0
- data/lib/danger/comment_generators/gitlab_inline.md.erb +26 -0
- data/lib/danger/comment_generators/vsts.md.erb +20 -0
- data/lib/danger/core_ext/file_list.rb +18 -0
- data/lib/danger/core_ext/string.rb +20 -0
- data/lib/danger/danger_core/dangerfile.rb +341 -0
- data/lib/danger/danger_core/dangerfile_dsl.rb +29 -0
- data/lib/danger/danger_core/dangerfile_generator.rb +11 -0
- data/lib/danger/danger_core/environment_manager.rb +123 -0
- data/lib/danger/danger_core/executor.rb +92 -0
- data/lib/danger/danger_core/message_aggregator.rb +49 -0
- data/lib/danger/danger_core/message_group.rb +68 -0
- data/lib/danger/danger_core/messages/base.rb +56 -0
- data/lib/danger/danger_core/messages/markdown.rb +42 -0
- data/lib/danger/danger_core/messages/violation.rb +54 -0
- data/lib/danger/danger_core/plugins/dangerfile_bitbucket_cloud_plugin.rb +144 -0
- data/lib/danger/danger_core/plugins/dangerfile_bitbucket_server_plugin.rb +211 -0
- data/lib/danger/danger_core/plugins/dangerfile_danger_plugin.rb +248 -0
- data/lib/danger/danger_core/plugins/dangerfile_git_plugin.rb +158 -0
- data/lib/danger/danger_core/plugins/dangerfile_github_plugin.rb +254 -0
- data/lib/danger/danger_core/plugins/dangerfile_gitlab_plugin.rb +240 -0
- data/lib/danger/danger_core/plugins/dangerfile_local_only_plugin.rb +42 -0
- data/lib/danger/danger_core/plugins/dangerfile_messaging_plugin.rb +218 -0
- data/lib/danger/danger_core/plugins/dangerfile_vsts_plugin.rb +191 -0
- data/lib/danger/danger_core/standard_error.rb +143 -0
- data/lib/danger/helpers/array_subclass.rb +61 -0
- data/lib/danger/helpers/comment.rb +32 -0
- data/lib/danger/helpers/comments_helper.rb +178 -0
- data/lib/danger/helpers/comments_parsing_helper.rb +70 -0
- data/lib/danger/helpers/emoji_mapper.rb +41 -0
- data/lib/danger/helpers/find_max_num_violations.rb +31 -0
- data/lib/danger/helpers/message_groups_array_helper.rb +31 -0
- data/lib/danger/plugin_support/gems_resolver.rb +77 -0
- data/lib/danger/plugin_support/plugin.rb +49 -0
- data/lib/danger/plugin_support/plugin_file_resolver.rb +30 -0
- data/lib/danger/plugin_support/plugin_linter.rb +161 -0
- data/lib/danger/plugin_support/plugin_parser.rb +199 -0
- data/lib/danger/plugin_support/templates/readme_table.html.erb +26 -0
- data/lib/danger/request_sources/bitbucket_cloud.rb +171 -0
- data/lib/danger/request_sources/bitbucket_cloud_api.rb +181 -0
- data/lib/danger/request_sources/bitbucket_server.rb +105 -0
- data/lib/danger/request_sources/bitbucket_server_api.rb +117 -0
- data/lib/danger/request_sources/github/github.rb +530 -0
- data/lib/danger/request_sources/github/github_review.rb +126 -0
- data/lib/danger/request_sources/github/github_review_resolver.rb +19 -0
- data/lib/danger/request_sources/github/github_review_unsupported.rb +25 -0
- data/lib/danger/request_sources/gitlab.rb +525 -0
- data/lib/danger/request_sources/local_only.rb +53 -0
- data/lib/danger/request_sources/request_source.rb +85 -0
- data/lib/danger/request_sources/support/get_ignored_violation.rb +17 -0
- data/lib/danger/request_sources/vsts.rb +118 -0
- data/lib/danger/request_sources/vsts_api.rb +138 -0
- data/lib/danger/scm_source/git_repo.rb +181 -0
- data/lib/danger/version.rb +4 -0
- metadata +339 -0
@@ -0,0 +1,41 @@
|
|
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" => ":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
|
@@ -0,0 +1,31 @@
|
|
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
|
@@ -0,0 +1,31 @@
|
|
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
|
@@ -0,0 +1,77 @@
|
|
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
|
@@ -0,0 +1,49 @@
|
|
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
|
+
# rubocop:disable Style/MethodMissing
|
22
|
+
def method_missing(method_sym, *arguments, &block)
|
23
|
+
@dangerfile.send(method_sym, *arguments, &block)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.all_plugins
|
27
|
+
@all_plugins ||= []
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.clear_external_plugins
|
31
|
+
@all_plugins = @all_plugins.select { |plugin| Dangerfile.essential_plugin_classes.include? plugin }
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.inherited(plugin)
|
35
|
+
Plugin.all_plugins.push(plugin)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
# When using `danger local --pry`, every plugin had an unreasonable
|
41
|
+
# amount of text output due to the Dangerfile reference in every
|
42
|
+
# plugin. So, it is filtered out. Users will start out in the context
|
43
|
+
# of the Dangerfile, and can view it by just typing `self` into the REPL.
|
44
|
+
#
|
45
|
+
def pretty_print_instance_variables
|
46
|
+
super - [:@dangerfile]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,30 @@
|
|
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
|
@@ -0,0 +1,161 @@
|
|
1
|
+
module Danger
|
2
|
+
class PluginLinter
|
3
|
+
# An internal class that is used to represent a rule for the linter.
|
4
|
+
class Rule
|
5
|
+
attr_accessor :modifier, :description, :title, :function, :ref, :metadata, :type
|
6
|
+
|
7
|
+
def initialize(modifier, ref, title, description, function)
|
8
|
+
@modifier = modifier
|
9
|
+
@title = title
|
10
|
+
@description = description
|
11
|
+
@function = function
|
12
|
+
@ref = ref
|
13
|
+
end
|
14
|
+
|
15
|
+
def object_applied_to
|
16
|
+
metadata[:name].to_s.bold + " (" + type + ")"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_accessor :json, :warnings, :errors
|
21
|
+
|
22
|
+
def initialize(json)
|
23
|
+
@json = json
|
24
|
+
@warnings = []
|
25
|
+
@errors = []
|
26
|
+
end
|
27
|
+
|
28
|
+
# Lints the current JSON, looking at:
|
29
|
+
# * Class rules
|
30
|
+
# * Method rules
|
31
|
+
# * Attribute rules
|
32
|
+
#
|
33
|
+
def lint
|
34
|
+
json.each do |plugin|
|
35
|
+
apply_rules(plugin, "class", class_rules)
|
36
|
+
|
37
|
+
plugin[:methods].each do |method|
|
38
|
+
apply_rules(method, "method", method_rules)
|
39
|
+
end
|
40
|
+
|
41
|
+
plugin[:attributes].each do |method_hash|
|
42
|
+
method_name = method_hash.keys.first
|
43
|
+
method = method_hash[method_name]
|
44
|
+
|
45
|
+
value = method[:write] || method[:read]
|
46
|
+
apply_rules(value, "attribute", method_rules)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Did the linter pass/fail?
|
52
|
+
#
|
53
|
+
def failed?
|
54
|
+
errors.count > 0
|
55
|
+
end
|
56
|
+
|
57
|
+
# Prints a summary of the errors and warnings.
|
58
|
+
#
|
59
|
+
def print_summary(ui)
|
60
|
+
# Print whether it passed/failed at the top
|
61
|
+
if failed?
|
62
|
+
ui.puts "\n[!] Failed\n".red
|
63
|
+
else
|
64
|
+
ui.notice "Passed"
|
65
|
+
end
|
66
|
+
|
67
|
+
# A generic proc to handle the similarities between
|
68
|
+
# errors and warnings.
|
69
|
+
do_rules = proc do |name, rules|
|
70
|
+
unless rules.empty?
|
71
|
+
ui.puts ""
|
72
|
+
ui.section(name.bold) do
|
73
|
+
rules.each do |rule|
|
74
|
+
title = rule.title.bold + " - #{rule.object_applied_to}"
|
75
|
+
subtitles = [rule.description, link(rule.ref)]
|
76
|
+
subtitles += [rule.metadata[:files].join(":")] if rule.metadata[:files]
|
77
|
+
ui.labeled(title, subtitles)
|
78
|
+
ui.puts ""
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Run the rules
|
85
|
+
do_rules.call("Errors".red, errors)
|
86
|
+
do_rules.call("Warnings".yellow, warnings)
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
# Rules that apply to a class
|
92
|
+
#
|
93
|
+
def class_rules
|
94
|
+
[
|
95
|
+
Rule.new(:error, 4..6, "Description Markdown", "Above your class you need documentation that covers the scope, and the usage of your plugin.", proc do |json|
|
96
|
+
json[:body_md] && json[:body_md].empty?
|
97
|
+
end),
|
98
|
+
Rule.new(:warning, 30, "Tags", "This plugin does not include `@tags tag1, tag2` and thus will be harder to find in search.", proc do |json|
|
99
|
+
json[:tags] && json[:tags].empty?
|
100
|
+
end),
|
101
|
+
Rule.new(:warning, 29, "References", "Ideally, you have a reference implementation of your plugin that you can show to people, add `@see org/repo` to have the site auto link it.", proc do |json|
|
102
|
+
json[:see] && json[:see].empty?
|
103
|
+
end),
|
104
|
+
Rule.new(:error, 8..27, "Examples", "You should include some examples of common use-cases for your plugin.", proc do |json|
|
105
|
+
json[:example_code] && json[:example_code].empty?
|
106
|
+
end)
|
107
|
+
]
|
108
|
+
end
|
109
|
+
|
110
|
+
# Rules that apply to individual methods, and attributes
|
111
|
+
#
|
112
|
+
def method_rules
|
113
|
+
[
|
114
|
+
Rule.new(:error, 40..41, "Description", "You should include a description for your method.", proc do |json|
|
115
|
+
json[:body_md] && json[:body_md].empty?
|
116
|
+
end),
|
117
|
+
Rule.new(:warning, 43..45, "Params", "You should give a 'type' for the param, yes, ruby is duck-typey but it's useful for newbies to the language, use `@param [Type] name`.", proc do |json|
|
118
|
+
json[:param_couplets] && json[:param_couplets].flat_map(&:values).include?(nil)
|
119
|
+
end),
|
120
|
+
Rule.new(:warning, 43..45, "Unknown Param", "You should give a 'type' for the param, yes, ruby is duck-typey but it's useful for newbies to the language, use `@param [Type] name`.", proc do |json|
|
121
|
+
json[:param_couplets] && json[:param_couplets].flat_map(&:values).include?("Unknown")
|
122
|
+
end),
|
123
|
+
Rule.new(:warning, 46, "Return Type", "If the function has no useful return value, use ` @return [void]` - this will be ignored by documentation generators.", proc do |json|
|
124
|
+
return_hash = json[:tags].find { |tag| tag[:name] == "return" }
|
125
|
+
!(return_hash && return_hash[:types] && !return_hash[:types].first.empty?)
|
126
|
+
end)
|
127
|
+
]
|
128
|
+
end
|
129
|
+
|
130
|
+
# Generates a link to see an example of said rule
|
131
|
+
#
|
132
|
+
def link(ref)
|
133
|
+
if ref.kind_of?(Range)
|
134
|
+
"@see - " + "https://github.com/dbgrandi/danger-prose/blob/v2.0.0/lib/danger_plugin.rb#L#{ref.min}#-L#{ref.max}".blue
|
135
|
+
elsif ref.kind_of?(Integer)
|
136
|
+
"@see - " + "https://github.com/dbgrandi/danger-prose/blob/v2.0.0/lib/danger_plugin.rb#L#{ref}".blue
|
137
|
+
else
|
138
|
+
"@see - " + "https://github.com/dbgrandi/danger-prose/blob/v2.0.0/lib/danger_plugin.rb".blue
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Runs the rule, if it fails then additional metadata
|
143
|
+
# is added to the rule (for printing later) and it's
|
144
|
+
# added to either `warnings` or `errors`.
|
145
|
+
#
|
146
|
+
def apply_rules(json, type, rules)
|
147
|
+
rules.each do |rule|
|
148
|
+
next unless rule.function.call(json)
|
149
|
+
rule.metadata = json
|
150
|
+
rule.type = type
|
151
|
+
|
152
|
+
case rule.modifier
|
153
|
+
when :warning
|
154
|
+
warnings << rule
|
155
|
+
when :error
|
156
|
+
errors << rule
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,199 @@
|
|
1
|
+
require "json"
|
2
|
+
|
3
|
+
=begin
|
4
|
+
|
5
|
+
So you want to improve this? Great. Hard thing is getting yourself into a position where you
|
6
|
+
have access to all the tokens, so here's something you should run in `bundle exec pry` to dig in:
|
7
|
+
|
8
|
+
require 'danger'
|
9
|
+
require 'yard'
|
10
|
+
parser = Danger::PluginParser.new "spec/fixtures/plugins/example_fully_documented.rb"
|
11
|
+
parser.parse
|
12
|
+
plugins = parser.plugins_from_classes(parser.classes_in_file)
|
13
|
+
git = plugins.first
|
14
|
+
klass = git
|
15
|
+
parser.to_dict(plugins)
|
16
|
+
|
17
|
+
Then some helpers
|
18
|
+
|
19
|
+
attribute_meths = klass.attributes[:instance].values.map(&:values).flatten
|
20
|
+
|
21
|
+
methods = klass.meths - klass.inherited_meths - attribute_meths
|
22
|
+
usable_methods = methods.select { |m| m.visibility == :public }.reject { |m| m.name == :initialize }
|
23
|
+
|
24
|
+
the alternative, is to add
|
25
|
+
|
26
|
+
require 'pry'
|
27
|
+
binding.pry
|
28
|
+
|
29
|
+
anywhere inside the source code below.
|
30
|
+
|
31
|
+
=end
|
32
|
+
|
33
|
+
module Danger
|
34
|
+
class PluginParser
|
35
|
+
attr_accessor :registry
|
36
|
+
|
37
|
+
def initialize(paths, verbose = false)
|
38
|
+
raise "Path cannot be empty" if paths.empty?
|
39
|
+
|
40
|
+
setup_yard(verbose)
|
41
|
+
|
42
|
+
if paths.kind_of? String
|
43
|
+
@paths = [File.expand_path(paths)]
|
44
|
+
else
|
45
|
+
@paths = paths
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def setup_yard(verbose)
|
50
|
+
require 'yard'
|
51
|
+
|
52
|
+
# Unless specifically asked, don't output anything.
|
53
|
+
unless verbose
|
54
|
+
YARD::Logger.instance.level = YARD::Logger::FATAL
|
55
|
+
end
|
56
|
+
|
57
|
+
# Add some of our custom tags
|
58
|
+
YARD::Tags::Library.define_tag('tags', :tags)
|
59
|
+
YARD::Tags::Library.define_tag('availability', :availability)
|
60
|
+
end
|
61
|
+
|
62
|
+
def parse
|
63
|
+
files = ["lib/danger/plugin_support/plugin.rb"] + @paths
|
64
|
+
|
65
|
+
# This turns on YARD debugging
|
66
|
+
# $DEBUG = true
|
67
|
+
|
68
|
+
self.registry = YARD::Registry.load(files, true)
|
69
|
+
end
|
70
|
+
|
71
|
+
def classes_in_file
|
72
|
+
registry.all(:class).select { |klass| @paths.include? klass.file }
|
73
|
+
end
|
74
|
+
|
75
|
+
def plugins_from_classes(classes)
|
76
|
+
classes.select { |klass| klass.inheritance_tree.map(&:name).include? :Plugin }
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_json
|
80
|
+
plugins = plugins_from_classes(classes_in_file)
|
81
|
+
to_h(plugins)
|
82
|
+
end
|
83
|
+
|
84
|
+
def to_json_string
|
85
|
+
plugins = plugins_from_classes(classes_in_file)
|
86
|
+
to_h(plugins).to_json
|
87
|
+
end
|
88
|
+
|
89
|
+
# rubocop:disable Metrics/AbcSize
|
90
|
+
|
91
|
+
def method_return_string(meth)
|
92
|
+
return "" unless meth[:tags]
|
93
|
+
|
94
|
+
return_value = meth[:tags].find { |t| t[:name] == "return" && t[:types] }
|
95
|
+
return "" if return_value.nil?
|
96
|
+
return "" if return_value[:types].nil?
|
97
|
+
return "" unless return_value[:types].kind_of? Array
|
98
|
+
|
99
|
+
unless return_value.empty?
|
100
|
+
return "" if return_value[:types].first == "void"
|
101
|
+
return return_value[:types].first
|
102
|
+
end
|
103
|
+
""
|
104
|
+
end
|
105
|
+
|
106
|
+
def method_params(method)
|
107
|
+
return {} unless method[:params]
|
108
|
+
|
109
|
+
params_names = method[:params].map { |param| param.compact.join("=").strip }
|
110
|
+
params_values = method[:tags].select { |t| t[:name] == "param" }
|
111
|
+
|
112
|
+
return {} if params_values.empty?
|
113
|
+
return {} if params_values.select { |p| p[:types] }.empty?
|
114
|
+
|
115
|
+
return params_names.map.with_index do |name, index|
|
116
|
+
name = name.delete ":"
|
117
|
+
if index < params_values.length
|
118
|
+
type = params_values[index][:types]
|
119
|
+
{ name => type ? type.first : "Unknown" }
|
120
|
+
else
|
121
|
+
{ name => "Unknown" }
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def method_parser(gem_path, meth)
|
127
|
+
return nil if meth.nil?
|
128
|
+
method = {
|
129
|
+
name: meth.name,
|
130
|
+
body_md: meth.docstring,
|
131
|
+
params: meth.parameters,
|
132
|
+
files: meth.files.map { |item| [item.first.gsub(gem_path, ""), item.last] },
|
133
|
+
tags: meth.tags.map { |t| { name: t.tag_name, types: t.types } }
|
134
|
+
}
|
135
|
+
|
136
|
+
|
137
|
+
return_v = method_return_string(method)
|
138
|
+
params_v = method_params(method)
|
139
|
+
|
140
|
+
# Pull out some derived data
|
141
|
+
method[:param_couplets] = params_v
|
142
|
+
method[:return] = return_v
|
143
|
+
|
144
|
+
# Create the center params, `thing: OK, other: Thing`
|
145
|
+
string_params = params_v.map do |param|
|
146
|
+
name = param.keys.first
|
147
|
+
param[name].nil? ? name : name + ": " + param[name]
|
148
|
+
end.join ", "
|
149
|
+
|
150
|
+
# Wrap it in () if there _are_ params
|
151
|
+
string_params = "(" + string_params + ")" unless string_params.empty?
|
152
|
+
# Append the return type if we have one
|
153
|
+
suffix = return_v.empty? ? "" : " -> #{return_v}"
|
154
|
+
|
155
|
+
method[:one_liner] = meth.name.to_s + string_params + suffix
|
156
|
+
method
|
157
|
+
end
|
158
|
+
|
159
|
+
def attribute_parser(gem_path, attribute)
|
160
|
+
{
|
161
|
+
read: method_parser(gem_path, attribute[:read]),
|
162
|
+
write: method_parser(gem_path, attribute[:write])
|
163
|
+
}
|
164
|
+
end
|
165
|
+
|
166
|
+
def to_h(classes)
|
167
|
+
classes.map do |klass|
|
168
|
+
# Adds the class being parsed into the ruby runtime, so that we can access it's instance_name
|
169
|
+
require klass.file
|
170
|
+
real_klass = Danger.const_get klass.name
|
171
|
+
attribute_meths = klass.attributes[:instance].values.map(&:values).flatten
|
172
|
+
|
173
|
+
methods = klass.meths - klass.inherited_meths - attribute_meths
|
174
|
+
usable_methods = methods.select { |m| m.visibility == :public }.reject { |m| m.name == :initialize || m.name == :instance_name || m.name == :new }
|
175
|
+
|
176
|
+
plugin_gem = klass.file.include?("gems") ? klass.file.split("gems/").last.split("-")[0..-2].join("-") : nil
|
177
|
+
# Pull out the gem's path ( to make relative file paths )
|
178
|
+
# if no gem is found, index = 0, making gem_path = ""
|
179
|
+
index_of_gem_in_path = plugin_gem ? klass.file.split("/").index { |component| component.include? plugin_gem } : 0
|
180
|
+
gem_path = klass.file.split("/")[0..index_of_gem_in_path].join("/")
|
181
|
+
|
182
|
+
{
|
183
|
+
name: klass.name.to_s,
|
184
|
+
body_md: klass.docstring,
|
185
|
+
instance_name: real_klass.instance_name,
|
186
|
+
gem: plugin_gem,
|
187
|
+
gem_path: gem_path,
|
188
|
+
files: klass.files.map { |item| [item.first.gsub(gem_path, ""), item.last] },
|
189
|
+
example_code: klass.tags.select { |t| t.tag_name == "example" }.map { |tag| { title: tag.name, text: tag.text } }.compact,
|
190
|
+
attributes: klass.attributes[:instance].map { |pair| { pair.first => attribute_parser(gem_path, pair.last) } },
|
191
|
+
methods: usable_methods.map { |m| method_parser(gem_path, m) },
|
192
|
+
tags: klass.tags.select { |t| t.tag_name == "tags" }.map(&:text).compact,
|
193
|
+
see: klass.tags.select { |t| t.tag_name == "see" }.map(&:name).map(&:split).flatten.compact,
|
194
|
+
}
|
195
|
+
end
|
196
|
+
end
|
197
|
+
# rubocop:enable Metrics/AbcSize
|
198
|
+
end
|
199
|
+
end
|