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.
Files changed (121) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +22 -0
  3. data/README.md +94 -0
  4. data/bin/danger +5 -0
  5. data/lib/assets/DangerfileTemplate +13 -0
  6. data/lib/danger.rb +44 -0
  7. data/lib/danger/ci_source/appcenter.rb +55 -0
  8. data/lib/danger/ci_source/appveyor.rb +60 -0
  9. data/lib/danger/ci_source/azure_pipelines.rb +44 -0
  10. data/lib/danger/ci_source/bamboo.rb +41 -0
  11. data/lib/danger/ci_source/bitbucket_pipelines.rb +37 -0
  12. data/lib/danger/ci_source/bitrise.rb +65 -0
  13. data/lib/danger/ci_source/buddybuild.rb +62 -0
  14. data/lib/danger/ci_source/buildkite.rb +51 -0
  15. data/lib/danger/ci_source/ci_source.rb +37 -0
  16. data/lib/danger/ci_source/circle.rb +94 -0
  17. data/lib/danger/ci_source/circle_api.rb +51 -0
  18. data/lib/danger/ci_source/cirrus.rb +31 -0
  19. data/lib/danger/ci_source/code_build.rb +57 -0
  20. data/lib/danger/ci_source/codefresh.rb +53 -0
  21. data/lib/danger/ci_source/codeship.rb +44 -0
  22. data/lib/danger/ci_source/dotci.rb +52 -0
  23. data/lib/danger/ci_source/drone.rb +71 -0
  24. data/lib/danger/ci_source/github_actions.rb +43 -0
  25. data/lib/danger/ci_source/gitlab_ci.rb +86 -0
  26. data/lib/danger/ci_source/jenkins.rb +149 -0
  27. data/lib/danger/ci_source/local_git_repo.rb +119 -0
  28. data/lib/danger/ci_source/local_only_git_repo.rb +47 -0
  29. data/lib/danger/ci_source/screwdriver.rb +47 -0
  30. data/lib/danger/ci_source/semaphore.rb +37 -0
  31. data/lib/danger/ci_source/support/commits.rb +17 -0
  32. data/lib/danger/ci_source/support/find_repo_info_from_logs.rb +35 -0
  33. data/lib/danger/ci_source/support/find_repo_info_from_url.rb +42 -0
  34. data/lib/danger/ci_source/support/local_pull_request.rb +14 -0
  35. data/lib/danger/ci_source/support/no_pull_request.rb +7 -0
  36. data/lib/danger/ci_source/support/no_repo_info.rb +5 -0
  37. data/lib/danger/ci_source/support/pull_request_finder.rb +179 -0
  38. data/lib/danger/ci_source/support/remote_pull_request.rb +15 -0
  39. data/lib/danger/ci_source/support/repo_info.rb +10 -0
  40. data/lib/danger/ci_source/surf.rb +37 -0
  41. data/lib/danger/ci_source/teamcity.rb +161 -0
  42. data/lib/danger/ci_source/travis.rb +51 -0
  43. data/lib/danger/ci_source/vsts.rb +73 -0
  44. data/lib/danger/ci_source/xcode_server.rb +48 -0
  45. data/lib/danger/clients/rubygems_client.rb +14 -0
  46. data/lib/danger/commands/dangerfile/gem.rb +43 -0
  47. data/lib/danger/commands/dangerfile/init.rb +30 -0
  48. data/lib/danger/commands/dry_run.rb +54 -0
  49. data/lib/danger/commands/init.rb +297 -0
  50. data/lib/danger/commands/init_helpers/interviewer.rb +92 -0
  51. data/lib/danger/commands/local.rb +83 -0
  52. data/lib/danger/commands/local_helpers/http_cache.rb +36 -0
  53. data/lib/danger/commands/local_helpers/local_setup.rb +46 -0
  54. data/lib/danger/commands/local_helpers/pry_setup.rb +31 -0
  55. data/lib/danger/commands/plugins/plugin_json.rb +46 -0
  56. data/lib/danger/commands/plugins/plugin_lint.rb +54 -0
  57. data/lib/danger/commands/plugins/plugin_readme.rb +45 -0
  58. data/lib/danger/commands/pr.rb +92 -0
  59. data/lib/danger/commands/runner.rb +94 -0
  60. data/lib/danger/commands/staging.rb +53 -0
  61. data/lib/danger/commands/systems.rb +43 -0
  62. data/lib/danger/comment_generators/bitbucket_server.md.erb +20 -0
  63. data/lib/danger/comment_generators/bitbucket_server_inline.md.erb +15 -0
  64. data/lib/danger/comment_generators/bitbucket_server_message_group.md.erb +12 -0
  65. data/lib/danger/comment_generators/github.md.erb +55 -0
  66. data/lib/danger/comment_generators/github_inline.md.erb +26 -0
  67. data/lib/danger/comment_generators/gitlab.md.erb +40 -0
  68. data/lib/danger/comment_generators/gitlab_inline.md.erb +26 -0
  69. data/lib/danger/comment_generators/vsts.md.erb +20 -0
  70. data/lib/danger/core_ext/file_list.rb +18 -0
  71. data/lib/danger/core_ext/string.rb +20 -0
  72. data/lib/danger/danger_core/dangerfile.rb +341 -0
  73. data/lib/danger/danger_core/dangerfile_dsl.rb +29 -0
  74. data/lib/danger/danger_core/dangerfile_generator.rb +11 -0
  75. data/lib/danger/danger_core/environment_manager.rb +123 -0
  76. data/lib/danger/danger_core/executor.rb +92 -0
  77. data/lib/danger/danger_core/message_aggregator.rb +49 -0
  78. data/lib/danger/danger_core/message_group.rb +68 -0
  79. data/lib/danger/danger_core/messages/base.rb +56 -0
  80. data/lib/danger/danger_core/messages/markdown.rb +42 -0
  81. data/lib/danger/danger_core/messages/violation.rb +54 -0
  82. data/lib/danger/danger_core/plugins/dangerfile_bitbucket_cloud_plugin.rb +144 -0
  83. data/lib/danger/danger_core/plugins/dangerfile_bitbucket_server_plugin.rb +211 -0
  84. data/lib/danger/danger_core/plugins/dangerfile_danger_plugin.rb +248 -0
  85. data/lib/danger/danger_core/plugins/dangerfile_git_plugin.rb +158 -0
  86. data/lib/danger/danger_core/plugins/dangerfile_github_plugin.rb +254 -0
  87. data/lib/danger/danger_core/plugins/dangerfile_gitlab_plugin.rb +240 -0
  88. data/lib/danger/danger_core/plugins/dangerfile_local_only_plugin.rb +42 -0
  89. data/lib/danger/danger_core/plugins/dangerfile_messaging_plugin.rb +218 -0
  90. data/lib/danger/danger_core/plugins/dangerfile_vsts_plugin.rb +191 -0
  91. data/lib/danger/danger_core/standard_error.rb +143 -0
  92. data/lib/danger/helpers/array_subclass.rb +61 -0
  93. data/lib/danger/helpers/comment.rb +32 -0
  94. data/lib/danger/helpers/comments_helper.rb +178 -0
  95. data/lib/danger/helpers/comments_parsing_helper.rb +70 -0
  96. data/lib/danger/helpers/emoji_mapper.rb +41 -0
  97. data/lib/danger/helpers/find_max_num_violations.rb +31 -0
  98. data/lib/danger/helpers/message_groups_array_helper.rb +31 -0
  99. data/lib/danger/plugin_support/gems_resolver.rb +77 -0
  100. data/lib/danger/plugin_support/plugin.rb +49 -0
  101. data/lib/danger/plugin_support/plugin_file_resolver.rb +30 -0
  102. data/lib/danger/plugin_support/plugin_linter.rb +161 -0
  103. data/lib/danger/plugin_support/plugin_parser.rb +199 -0
  104. data/lib/danger/plugin_support/templates/readme_table.html.erb +26 -0
  105. data/lib/danger/request_sources/bitbucket_cloud.rb +171 -0
  106. data/lib/danger/request_sources/bitbucket_cloud_api.rb +181 -0
  107. data/lib/danger/request_sources/bitbucket_server.rb +105 -0
  108. data/lib/danger/request_sources/bitbucket_server_api.rb +117 -0
  109. data/lib/danger/request_sources/github/github.rb +530 -0
  110. data/lib/danger/request_sources/github/github_review.rb +126 -0
  111. data/lib/danger/request_sources/github/github_review_resolver.rb +19 -0
  112. data/lib/danger/request_sources/github/github_review_unsupported.rb +25 -0
  113. data/lib/danger/request_sources/gitlab.rb +525 -0
  114. data/lib/danger/request_sources/local_only.rb +53 -0
  115. data/lib/danger/request_sources/request_source.rb +85 -0
  116. data/lib/danger/request_sources/support/get_ignored_violation.rb +17 -0
  117. data/lib/danger/request_sources/vsts.rb +118 -0
  118. data/lib/danger/request_sources/vsts_api.rb +138 -0
  119. data/lib/danger/scm_source/git_repo.rb +181 -0
  120. data/lib/danger/version.rb +4 -0
  121. 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