puppet-strings 2.3.0 → 2.7.0

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 (135) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +102 -4
  3. data/COMMITTERS.md +17 -17
  4. data/CONTRIBUTING.md +37 -7
  5. data/README.md +17 -12
  6. data/lib/puppet-strings.rb +5 -3
  7. data/lib/puppet-strings/describe.rb +2 -0
  8. data/lib/puppet-strings/json.rb +2 -0
  9. data/lib/puppet-strings/markdown.rb +12 -10
  10. data/lib/puppet-strings/markdown/base.rb +30 -3
  11. data/lib/puppet-strings/markdown/data_type.rb +18 -0
  12. data/lib/puppet-strings/markdown/data_types.rb +3 -1
  13. data/lib/puppet-strings/markdown/defined_type.rb +2 -0
  14. data/lib/puppet-strings/markdown/defined_types.rb +3 -1
  15. data/lib/puppet-strings/markdown/function.rb +9 -7
  16. data/lib/puppet-strings/markdown/functions.rb +3 -1
  17. data/lib/puppet-strings/markdown/puppet_class.rb +2 -0
  18. data/lib/puppet-strings/markdown/puppet_classes.rb +3 -1
  19. data/lib/puppet-strings/markdown/puppet_plan.rb +2 -0
  20. data/lib/puppet-strings/markdown/puppet_plans.rb +3 -1
  21. data/lib/puppet-strings/markdown/puppet_task.rb +2 -0
  22. data/lib/puppet-strings/markdown/puppet_tasks.rb +3 -1
  23. data/lib/puppet-strings/markdown/resource_type.rb +21 -2
  24. data/lib/puppet-strings/markdown/resource_types.rb +3 -1
  25. data/lib/puppet-strings/markdown/table_of_contents.rb +3 -1
  26. data/lib/puppet-strings/markdown/templates/classes_and_defines.erb +18 -6
  27. data/lib/puppet-strings/markdown/templates/data_type.erb +30 -7
  28. data/lib/puppet-strings/markdown/templates/data_type_function.erb +67 -0
  29. data/lib/puppet-strings/markdown/templates/function.erb +10 -1
  30. data/lib/puppet-strings/markdown/templates/puppet_task.erb +1 -1
  31. data/lib/puppet-strings/markdown/templates/resource_type.erb +35 -13
  32. data/lib/puppet-strings/markdown/templates/table_of_contents.erb +6 -6
  33. data/lib/puppet-strings/monkey_patches/display_object_command.rb +2 -0
  34. data/lib/puppet-strings/tasks.rb +2 -0
  35. data/lib/puppet-strings/tasks/generate.rb +2 -0
  36. data/lib/puppet-strings/tasks/gh_pages.rb +3 -0
  37. data/lib/puppet-strings/version.rb +3 -1
  38. data/lib/puppet-strings/yard.rb +9 -0
  39. data/lib/puppet-strings/yard/code_objects.rb +2 -0
  40. data/lib/puppet-strings/yard/code_objects/base.rb +2 -0
  41. data/lib/puppet-strings/yard/code_objects/class.rb +4 -2
  42. data/lib/puppet-strings/yard/code_objects/data_type.rb +30 -8
  43. data/lib/puppet-strings/yard/code_objects/data_type_alias.rb +2 -0
  44. data/lib/puppet-strings/yard/code_objects/defined_type.rb +4 -2
  45. data/lib/puppet-strings/yard/code_objects/function.rb +8 -5
  46. data/lib/puppet-strings/yard/code_objects/group.rb +3 -0
  47. data/lib/puppet-strings/yard/code_objects/plan.rb +4 -2
  48. data/lib/puppet-strings/yard/code_objects/provider.rb +6 -0
  49. data/lib/puppet-strings/yard/code_objects/task.rb +3 -1
  50. data/lib/puppet-strings/yard/code_objects/type.rb +49 -5
  51. data/lib/puppet-strings/yard/handlers.rb +2 -0
  52. data/lib/puppet-strings/yard/handlers/helpers.rb +2 -0
  53. data/lib/puppet-strings/yard/handlers/json/base.rb +2 -0
  54. data/lib/puppet-strings/yard/handlers/json/task_handler.rb +2 -0
  55. data/lib/puppet-strings/yard/handlers/puppet/base.rb +3 -0
  56. data/lib/puppet-strings/yard/handlers/puppet/class_handler.rb +2 -0
  57. data/lib/puppet-strings/yard/handlers/puppet/data_type_alias_handler.rb +2 -0
  58. data/lib/puppet-strings/yard/handlers/puppet/defined_type_handler.rb +2 -0
  59. data/lib/puppet-strings/yard/handlers/puppet/function_handler.rb +3 -1
  60. data/lib/puppet-strings/yard/handlers/puppet/plan_handler.rb +2 -0
  61. data/lib/puppet-strings/yard/handlers/ruby/base.rb +5 -0
  62. data/lib/puppet-strings/yard/handlers/ruby/data_type_handler.rb +225 -52
  63. data/lib/puppet-strings/yard/handlers/ruby/function_handler.rb +9 -7
  64. data/lib/puppet-strings/yard/handlers/ruby/provider_handler.rb +13 -0
  65. data/lib/puppet-strings/yard/handlers/ruby/rsapi_handler.rb +4 -1
  66. data/lib/puppet-strings/yard/handlers/ruby/type_base.rb +20 -6
  67. data/lib/puppet-strings/yard/handlers/ruby/type_extras_handler.rb +6 -3
  68. data/lib/puppet-strings/yard/handlers/ruby/type_handler.rb +18 -1
  69. data/lib/puppet-strings/yard/parsers.rb +2 -0
  70. data/lib/puppet-strings/yard/parsers/json/parser.rb +2 -0
  71. data/lib/puppet-strings/yard/parsers/json/task_statement.rb +2 -0
  72. data/lib/puppet-strings/yard/parsers/puppet/parser.rb +18 -14
  73. data/lib/puppet-strings/yard/parsers/puppet/statement.rb +4 -0
  74. data/lib/puppet-strings/yard/tags.rb +4 -0
  75. data/lib/puppet-strings/yard/tags/enum_tag.rb +14 -0
  76. data/lib/puppet-strings/yard/tags/factory.rb +18 -0
  77. data/lib/puppet-strings/yard/tags/overload_tag.rb +4 -1
  78. data/lib/puppet-strings/yard/tags/parameter_directive.rb +5 -4
  79. data/lib/puppet-strings/yard/tags/property_directive.rb +5 -4
  80. data/lib/puppet-strings/yard/tags/summary_tag.rb +2 -0
  81. data/lib/puppet-strings/yard/templates/default/puppet_data_type/html/method_details_list.erb +6 -0
  82. data/lib/puppet-strings/yard/templates/default/puppet_data_type/html/setup.rb +9 -1
  83. data/lib/puppet-strings/yard/templates/default/puppet_function/html/setup.rb +1 -1
  84. data/lib/puppet-strings/yard/templates/default/puppet_type/html/setup.rb +3 -1
  85. data/lib/puppet-strings/yard/templates/default/tags/html/enum.erb +17 -0
  86. data/lib/puppet-strings/yard/templates/default/tags/setup.rb +6 -0
  87. data/lib/puppet-strings/yard/util.rb +7 -4
  88. data/lib/puppet/application/strings.rb +2 -0
  89. data/lib/puppet/face/strings.rb +4 -1
  90. data/lib/puppet/feature/rgen.rb +2 -0
  91. data/lib/puppet/feature/yard.rb +2 -0
  92. metadata +14 -51
  93. data/Gemfile +0 -47
  94. data/HISTORY.md +0 -218
  95. data/JSON.md +0 -832
  96. data/Rakefile +0 -168
  97. data/codecov.yml +0 -3
  98. data/misc/ANNOUNCEMENT_TEMPLATE.md +0 -40
  99. data/spec/acceptance/emit_json_options_spec.rb +0 -69
  100. data/spec/acceptance/generate_markdown_spec.rb +0 -47
  101. data/spec/acceptance/running_strings_generate_spec.rb +0 -78
  102. data/spec/fixtures/acceptance/modules/test/functions/add.pp +0 -9
  103. data/spec/fixtures/acceptance/modules/test/lib/puppet/functions/4x_function.rb +0 -5
  104. data/spec/fixtures/acceptance/modules/test/lib/puppet/parser/functions/function3x.rb +0 -2
  105. data/spec/fixtures/acceptance/modules/test/lib/puppet/provider/server/linux.rb +0 -9
  106. data/spec/fixtures/acceptance/modules/test/lib/puppet/type/database.rb +0 -15
  107. data/spec/fixtures/acceptance/modules/test/manifests/init.pp +0 -27
  108. data/spec/fixtures/acceptance/modules/test/manifests/triple_nested_classes.pp +0 -27
  109. data/spec/fixtures/acceptance/modules/test/metadata.json +0 -10
  110. data/spec/fixtures/acceptance/modules/test/types/elephant.pp +0 -2
  111. data/spec/fixtures/unit/markdown/output.md +0 -508
  112. data/spec/fixtures/unit/markdown/output_with_data_types.md +0 -553
  113. data/spec/fixtures/unit/markdown/output_with_plan.md +0 -542
  114. data/spec/spec_helper.rb +0 -49
  115. data/spec/spec_helper_acceptance.rb +0 -58
  116. data/spec/spec_helper_acceptance_local.rb +0 -10
  117. data/spec/unit/puppet-strings/describe_spec.rb +0 -141
  118. data/spec/unit/puppet-strings/json_spec.rb +0 -302
  119. data/spec/unit/puppet-strings/markdown/base_spec.rb +0 -146
  120. data/spec/unit/puppet-strings/markdown_spec.rb +0 -357
  121. data/spec/unit/puppet-strings/yard/code_objects/task_spec.rb +0 -92
  122. data/spec/unit/puppet-strings/yard/handlers/json/task_handler_spec.rb +0 -116
  123. data/spec/unit/puppet-strings/yard/handlers/puppet/class_handler_spec.rb +0 -217
  124. data/spec/unit/puppet-strings/yard/handlers/puppet/data_type_alias_handler_spec.rb +0 -65
  125. data/spec/unit/puppet-strings/yard/handlers/puppet/defined_type_handler_spec.rb +0 -231
  126. data/spec/unit/puppet-strings/yard/handlers/puppet/function_handler_spec.rb +0 -315
  127. data/spec/unit/puppet-strings/yard/handlers/ruby/data_type_handler_spec.rb +0 -232
  128. data/spec/unit/puppet-strings/yard/handlers/ruby/function_handler_spec.rb +0 -746
  129. data/spec/unit/puppet-strings/yard/handlers/ruby/provider_handler_spec.rb +0 -158
  130. data/spec/unit/puppet-strings/yard/handlers/ruby/rsapi_handler_spec.rb +0 -235
  131. data/spec/unit/puppet-strings/yard/handlers/ruby/type_handler_spec.rb +0 -311
  132. data/spec/unit/puppet-strings/yard/parsers/json/parser_spec.rb +0 -72
  133. data/spec/unit/puppet-strings/yard/parsers/json/task_statement_spec.rb +0 -56
  134. data/spec/unit/puppet-strings/yard/parsers/puppet/parser_spec.rb +0 -251
  135. data/spec/unit/puppet-strings/yard/util_spec.rb +0 -48
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puppet-strings/yard/code_objects/group'
2
4
 
3
5
  # Implements the group for Puppet functions.
@@ -61,11 +63,12 @@ class PuppetStrings::Yard::CodeObjects::Function < PuppetStrings::Yard::CodeObje
61
63
  # @return [String] Returns the Puppet signature of the function.
62
64
  def signature
63
65
  return '' if self.has_tag? :overload
66
+
64
67
  tags = self.tags(:param)
65
68
  args = @parameters.map do |parameter|
66
69
  name, default = parameter
67
70
  tag = tags.find { |t| t.name == name } if tags
68
- type = tag && tag.types ? "#{tag.type} " : 'Any '
71
+ type = tag&.types ? "#{tag.type} " : 'Any '
69
72
  prefix = "#{name[0]}" if name.start_with?('*', '&')
70
73
  name = name[1..-1] if prefix
71
74
  default = " = #{default}" if default
@@ -88,16 +91,16 @@ class PuppetStrings::Yard::CodeObjects::Function < PuppetStrings::Yard::CodeObje
88
91
  if self.has_tag? :overload
89
92
  # loop over overloads and append onto the signatures array
90
93
  self.tags(:overload).each do |o|
91
- hash[:signatures] << { :signature => o.signature, :docstring => PuppetStrings::Yard::Util.docstring_to_hash(o.docstring, %i[param option return example]) }
94
+ hash[:signatures] << { :signature => o.signature, :docstring => PuppetStrings::Yard::Util.docstring_to_hash(o.docstring, %i[param option enum return example]) }
92
95
  end
93
96
  else
94
- hash[:signatures] << { :signature => self.signature, :docstring => PuppetStrings::Yard::Util.docstring_to_hash(docstring, %i[param option return example]) }
97
+ hash[:signatures] << { :signature => self.signature, :docstring => PuppetStrings::Yard::Util.docstring_to_hash(docstring, %i[param option enum return example]) }
95
98
  end
96
99
 
97
100
  hash[:docstring] = PuppetStrings::Yard::Util.docstring_to_hash(docstring)
98
101
  defaults = Hash[*parameters.reject{ |p| p[1].nil? }.flatten]
99
- hash[:defaults] = defaults unless defaults.empty?
100
- hash[:source] = source unless source && source.empty?
102
+ hash[:defaults] = defaults unless defaults.nil? || defaults.empty?
103
+ hash[:source] = source unless source.nil? || source.empty?
101
104
  hash
102
105
  end
103
106
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puppet-strings/yard/code_objects/base'
2
4
 
3
5
  # Implements the base class for "groups".
@@ -10,6 +12,7 @@ class PuppetStrings::Yard::CodeObjects::Group < PuppetStrings::Yard::CodeObjects
10
12
  def self.instance(key)
11
13
  instance = P(:root, key)
12
14
  return instance unless instance.is_a?(YARD::CodeObjects::Proxy)
15
+
13
16
  instance = self.new(:root, key)
14
17
  instance.visibility = :hidden
15
18
  P(:root).children << instance
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puppet-strings/yard/code_objects/group'
2
4
 
3
5
  class PuppetStrings::Yard::CodeObjects::Plans < PuppetStrings::Yard::CodeObjects::Group
@@ -49,8 +51,8 @@ class PuppetStrings::Yard::CodeObjects::Plan < PuppetStrings::Yard::CodeObjects:
49
51
  hash[:line] = line
50
52
  hash[:docstring] = PuppetStrings::Yard::Util.docstring_to_hash(docstring)
51
53
  defaults = Hash[*parameters.reject{ |p| p[1].nil? }.flatten]
52
- hash[:defaults] = defaults unless defaults.empty?
53
- hash[:source] = source unless source && source.empty?
54
+ hash[:defaults] = defaults unless defaults.nil? || defaults.empty?
55
+ hash[:source] = source unless source.nil? || source.empty?
54
56
  hash
55
57
  end
56
58
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puppet-strings/yard/code_objects/group'
2
4
 
3
5
  # Implements the group for Puppet providers.
@@ -42,6 +44,7 @@ class PuppetStrings::Yard::CodeObjects::Provider < PuppetStrings::Yard::CodeObje
42
44
  # @return [void]
43
45
  def add_confine(key, value)
44
46
  return unless key && value
47
+
45
48
  @confines ||= {}
46
49
  @confines[key] = value
47
50
  end
@@ -51,6 +54,7 @@ class PuppetStrings::Yard::CodeObjects::Provider < PuppetStrings::Yard::CodeObje
51
54
  # @return [void]
52
55
  def add_feature(feature)
53
56
  return unless feature
57
+
54
58
  @features ||= []
55
59
  @features << feature
56
60
  end
@@ -60,6 +64,7 @@ class PuppetStrings::Yard::CodeObjects::Provider < PuppetStrings::Yard::CodeObje
60
64
  # @return [void]
61
65
  def add_default(constraints)
62
66
  return unless constraints
67
+
63
68
  @defaults ||= []
64
69
  @defaults << constraints
65
70
  end
@@ -70,6 +75,7 @@ class PuppetStrings::Yard::CodeObjects::Provider < PuppetStrings::Yard::CodeObje
70
75
  # @return [void]
71
76
  def add_command(key, value)
72
77
  return unless key && value
78
+
73
79
  @commands ||= {}
74
80
  @commands[key] = value
75
81
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puppet-strings/yard/code_objects/group'
2
4
 
3
5
  # Implements the group for Puppet tasks.
@@ -43,7 +45,7 @@ class PuppetStrings::Yard::CodeObjects::Task < PuppetStrings::Yard::CodeObjects:
43
45
 
44
46
  def parameters
45
47
  parameters = []
46
- statement.json['parameters'].each do |name,props|
48
+ statement.parameters.each do |name,props|
47
49
  parameters.push({ name: name.to_s,
48
50
  tag_name: 'param',
49
51
  text: props['description'] || "",
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puppet-strings/yard/code_objects/group'
2
4
  require 'puppet-strings/yard/util'
3
5
 
@@ -73,6 +75,9 @@ class PuppetStrings::Yard::CodeObjects::Type < PuppetStrings::Yard::CodeObjects:
73
75
  class Property < Parameter
74
76
  end
75
77
 
78
+ class Check < Parameter
79
+ end
80
+
76
81
  # Represents a resource type feature.
77
82
  class Feature
78
83
  attr_reader :name, :docstring
@@ -95,7 +100,7 @@ class PuppetStrings::Yard::CodeObjects::Type < PuppetStrings::Yard::CodeObjects:
95
100
  end
96
101
  end
97
102
 
98
- attr_reader :properties, :parameters, :features
103
+ attr_reader :properties, :features, :checks
99
104
 
100
105
  # Initializes a new resource type.
101
106
  # @param [String] name The resource type name.
@@ -134,17 +139,56 @@ class PuppetStrings::Yard::CodeObjects::Type < PuppetStrings::Yard::CodeObjects:
134
139
  @features << feature
135
140
  end
136
141
 
142
+ # Adds a check to the resource type.
143
+ # @param [PuppetStrings::Yard::CodeObjects::Type::Check] check The check to add.
144
+ # @return [void]
145
+ def add_check(check)
146
+ @checks ||= []
147
+ @checks << check
148
+ end
149
+
150
+ def parameters
151
+ # just return params if there are no providers
152
+ return @parameters if providers.empty?
153
+
154
+ # return existing params if we have already added provider
155
+ return @parameters if @parameters.any? { |p| p.name == 'provider' }
156
+
157
+ provider_param = Parameter.new(
158
+ 'provider',
159
+ "The specific backend to use for this `#{self.name.to_s}` resource. You will seldom need " + \
160
+ "to specify this --- Puppet will usually discover the appropriate provider for your platform."
161
+ )
162
+
163
+ @parameters << provider_param
164
+ end
165
+
166
+ # Not sure if this is where this belongs or if providers should only be resolved at
167
+ # render-time. For now, this should re-resolve on every call.
168
+ # may be able to memoize this
169
+ def providers
170
+ providers = YARD::Registry.all("puppet_providers_#{name}".intern)
171
+ return providers if providers.empty?
172
+
173
+ providers.first.children
174
+ end
175
+
137
176
  # Converts the code object to a hash representation.
138
177
  # @return [Hash] Returns a hash representation of the code object.
139
178
  def to_hash
140
179
  hash = {}
180
+
141
181
  hash[:name] = name
142
182
  hash[:file] = file
143
183
  hash[:line] = line
144
- hash[:docstring] = PuppetStrings::Yard::Util.docstring_to_hash(docstring)
145
- hash[:properties] = properties.map(&:to_hash) if properties && !properties.empty?
146
- hash[:parameters] = parameters.map(&:to_hash) if parameters && !parameters.empty?
147
- hash[:features] = features.map(&:to_hash) if features && !features.empty?
184
+
185
+ hash[:docstring] = PuppetStrings::Yard::Util.docstring_to_hash(docstring)
186
+ hash[:properties] = properties.sort_by { |p| p.name }.map(&:to_hash) if properties && !properties.empty?
187
+ hash[:parameters] = parameters.sort_by { |p| p.name }.map(&:to_hash) if parameters && !parameters.empty?
188
+ hash[:checks] = checks.sort_by { |c| c.name }.map(&:to_hash) if checks && !checks.empty?
189
+ hash[:features] = features.sort_by { |f| f.name }.map(&:to_hash) if features && !features.empty?
190
+ hash[:providers] = providers.sort_by { |p| p.name }.map(&:to_hash) if providers && !providers.empty?
191
+
148
192
  hash
149
193
  end
150
194
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # The module for custom YARD handlers.
2
4
  module PuppetStrings::Yard::Handlers
3
5
  # The module for custom Ruby YARD handlers.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PuppetStrings::Yard::Handlers::Helpers
2
4
  # Logs a warning if a summary tag has more than 140 characters
3
5
  def self.validate_summary_tag(object)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class PuppetStrings::Yard::Handlers::JSON::Base < YARD::Handlers::Base
2
4
  def self.handles?(statement)
3
5
  handlers.any? {|handler| statement.is_a?(handler)}
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puppet-strings/yard/handlers/json/base'
2
4
  require 'puppet-strings/yard/parsers'
3
5
  require 'puppet-strings/yard/parsers/json/parser'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Implements the base handler for Puppet language handlers.
2
4
  class PuppetStrings::Yard::Handlers::Puppet::Base < YARD::Handlers::Base
3
5
  # Determine sif the handler handles the given statement.
@@ -17,6 +19,7 @@ class PuppetStrings::Yard::Handlers::Puppet::Base < YARD::Handlers::Base
17
19
  tags = object.tags(:param)
18
20
  tags.each do |tag|
19
21
  next if statement.parameters.find { |p| tag.name == p.name }
22
+
20
23
  log.warn "The @param tag for parameter '#{tag.name}' has no matching parameter at #{statement.file}:#{statement.line}." unless tag.name == 'name' || tag.name == 'title'
21
24
  end
22
25
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puppet-strings/yard/handlers/helpers'
2
4
  require 'puppet-strings/yard/handlers/puppet/base'
3
5
  require 'puppet-strings/yard/parsers'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puppet-strings/yard/handlers/helpers'
2
4
  require 'puppet-strings/yard/handlers/puppet/base'
3
5
  require 'puppet-strings/yard/parsers'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puppet-strings/yard/handlers/helpers'
2
4
  require 'puppet-strings/yard/handlers/puppet/base'
3
5
  require 'puppet-strings/yard/parsers'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puppet-strings/yard/handlers/helpers'
2
4
  require 'puppet-strings/yard/handlers/puppet/base'
3
5
  require 'puppet-strings/yard/parsers'
@@ -37,7 +39,7 @@ class PuppetStrings::Yard::Handlers::Puppet::FunctionHandler < PuppetStrings::Ya
37
39
  def add_return_tag(object, type=nil)
38
40
  tag = object.tag(:return)
39
41
  if tag
40
- if (type && tag.types.first) && (type != tag.types.first)
42
+ if (type && tag.types && tag.types.first) && (type != tag.types.first)
41
43
  log.warn "Documented return type does not match return type in function definition near #{statement.file}:#{statement.line}."
42
44
  end
43
45
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puppet-strings/yard/handlers/helpers'
2
4
  require 'puppet-strings/yard/handlers/puppet/base'
3
5
  require 'puppet-strings/yard/parsers'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'ripper'
2
4
 
3
5
  # Implements the base handler for Ruby language handlers.
@@ -12,6 +14,7 @@ class PuppetStrings::Yard::Handlers::Ruby::Base < YARD::Handlers::Ruby::Base
12
14
  # @return [String] Returns a string representation of the node or nil if a string representation was not possible.
13
15
  def node_as_string(node)
14
16
  return nil unless node
17
+
15
18
  case node.type
16
19
  when :symbol, :symbol_literal
17
20
  node.source[1..-1]
@@ -42,8 +45,10 @@ class PuppetStrings::Yard::Handlers::Ruby::Base < YARD::Handlers::Ruby::Base
42
45
  def get_name(statementobject, statementtype)
43
46
  parameters = statementobject.parameters(false)
44
47
  raise YARD::Parser::UndocumentableError, "Expected at least one parameter to #{statementtype} at #{statementobject.file}:#{statementobject.line}." if parameters.empty?
48
+
45
49
  name = node_as_string(parameters.first)
46
50
  raise YARD::Parser::UndocumentableError, "Expected a symbol or string literal for first parameter but found '#{parameters.first.type}' at #{statement.file}:#{statement.line}." unless name
51
+
47
52
  name
48
53
  end
49
54
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'puppet-strings/yard/handlers/helpers'
2
4
  require 'puppet-strings/yard/handlers/ruby/base'
3
5
  require 'puppet-strings/yard/code_objects'
@@ -10,16 +12,21 @@ class PuppetStrings::Yard::Handlers::Ruby::DataTypeHandler < PuppetStrings::Yard
10
12
 
11
13
  process do
12
14
  return unless statement.count > 1
15
+
13
16
  ruby_module_name = statement[0].source
14
17
  return unless ruby_module_name == 'Puppet::DataTypes' || ruby_module_name == 'DataTypes' # rubocop:disable Style/MultipleComparison This reads better
15
- object = get_datatype_yard_object(get_name(statement, 'Puppet::DataTypes.create_type'))
16
18
 
17
- actual_params = extract_params_for_data_type # populate_data_type_data(object)
19
+ object = get_datatype_yard_object(get_name(statement, 'Puppet::DataTypes.create_type'))
20
+ # Extract the interface definition
21
+ type_interface = extract_data_type_interface
22
+ actual_params = extract_params(type_interface)
23
+ actual_funcs = extract_functions(object, type_interface)
18
24
 
19
25
  # Mark the data type as public if it doesn't already have an api tag
20
26
  object.add_tag YARD::Tags::Tag.new(:api, 'public') unless object.has_tag? :api
21
27
 
22
- validate_tags!(object, actual_params)
28
+ validate_param_tags!(object, actual_params)
29
+ validate_methods!(object, actual_funcs)
23
30
 
24
31
  # Set the default values for all parameters
25
32
  actual_params.each { |name, data| object.set_parameter_default(name, data[:default]) }
@@ -49,38 +56,64 @@ class PuppetStrings::Yard::Handlers::Ruby::DataTypeHandler < PuppetStrings::Yard
49
56
  object
50
57
  end
51
58
 
52
- def extract_params_for_data_type
53
- params = {}
54
- # Traverse the block looking for interface
59
+ # @return [Hash{Object => Object}] The Puppet DataType interface definition as a ruby Hash
60
+ def extract_data_type_interface
55
61
  block = statement.block
56
- return unless block && block.count >= 2
57
- block[1].children.each do |node|
58
- next unless node.is_a?(YARD::Parser::Ruby::MethodCallNode) &&
59
- node.method_name
62
+ return {} unless block
63
+
64
+ # Declare the parsed interface outside of the closure
65
+ parsed_interface = nil
66
+
67
+ # Recursively traverse the block looking for the first valid 'interface' call
68
+ interface_node = find_ruby_ast_node(block, true) do |node|
69
+ next false unless node.is_a?(YARD::Parser::Ruby::MethodCallNode) &&
70
+ node.method_name &&
71
+ node.method_name.source == 'interface'
60
72
 
61
- method_name = node.method_name.source
62
73
  parameters = node.parameters(false)
63
- if method_name == 'interface'
64
- next unless parameters.count >= 1
65
- interface_string = node_as_string(parameters[0])
66
- next unless interface_string
74
+ next false unless parameters.count >= 1
75
+
76
+ interface_string = node_as_string(parameters[0])
77
+ next false unless interface_string
78
+
79
+ begin
67
80
  # Ref - https://github.com/puppetlabs/puppet/blob/ba4d1a1aba0095d3c70b98fea5c67434a4876a61/lib/puppet/datatypes.rb#L159
68
81
  parsed_interface = Puppet::Pops::Parser::EvaluatingParser.new.parse_string("{ #{interface_string} }").body
69
- next unless parsed_interface
70
-
71
- # Now that we parsed the Puppet code (as a string) into a LiteralHash PCore type (Puppet AST),
72
- #
73
- # We need to convert the LiteralHash into a conventional ruby hash of strings. The
74
- # LazyLiteralEvaluator does this by traversing the AST tree can converting objects to strings
75
- # where possible and ignoring object types which cannot (thus the 'Lazy' name)
76
- #
77
- # Once we have it as a standard ruby hash we can then look at the keys and populate the YARD
78
- # Code object with the correct attributes etc.
79
- literal_eval = LazyLiteralEvaluator.new
80
- populate_data_type_params_from_literal_hash!(literal_eval.literal(parsed_interface), params)
82
+ rescue Puppet::Error => e
83
+ log.warn "Invalid datatype definition at #{statement.file}:#{statement.line}: #{e.basic_message}"
84
+ next false
81
85
  end
86
+ !parsed_interface.nil?
82
87
  end
83
- params
88
+
89
+ # Now that we parsed the Puppet code (as a string) into a LiteralHash PCore type (Puppet AST),
90
+ # We need to convert the LiteralHash into a conventional ruby hash of strings. The
91
+ # LazyLiteralEvaluator does this by traversing the AST tree can converting objects to strings
92
+ # where possible and ignoring object types which cannot (thus the 'Lazy' name)
93
+ literal_eval = LazyLiteralEvaluator.new
94
+ literal_eval.literal(parsed_interface)
95
+ end
96
+
97
+ # Find the first Ruby AST node within an AST Tree, optionally recursively. Returns nil of none could be found
98
+ #
99
+ # @param [YARD::Parser::Ruby::AstNode] ast_node The root AST node object to inspect
100
+ # @param [Boolean] recurse Whether to search the tree recursively. Defaults to false
101
+ # @yieldparam [YARD::Parser::Ruby::AstNode] ast_node The AST Node that should be checked
102
+ # @yieldreturn [Boolean] Whether the node was what was searched for
103
+ # @return [YARD::Parser::Ruby::AstNode, nil]
104
+ def find_ruby_ast_node(ast_node, recurse = false, &block)
105
+ raise ArgumentError, 'find_ruby_ast_node requires a block' unless block_given?
106
+
107
+ is_found = yield ast_node
108
+ return ast_node if is_found
109
+
110
+ if ast_node.respond_to?(:children)
111
+ ast_node.children.each do |child_node|
112
+ child_found = find_ruby_ast_node(child_node, recurse, &block)
113
+ return child_found unless child_found.nil?
114
+ end
115
+ end
116
+ nil
84
117
  end
85
118
 
86
119
  # Lazily evaluates a Pops object, ignoring any objects that cannot
@@ -103,7 +136,7 @@ class PuppetStrings::Yard::Handlers::Ruby::DataTypeHandler < PuppetStrings::Yard
103
136
  # Anything else is ignored
104
137
  class LazyLiteralEvaluator
105
138
  def initialize
106
- @literal_visitor ||= ::Puppet::Pops::Visitor.new(self, "literal", 0, 0)
139
+ @literal_visitor = ::Puppet::Pops::Visitor.new(self, "literal", 0, 0)
107
140
  end
108
141
 
109
142
  def literal(ast)
@@ -111,68 +144,72 @@ class PuppetStrings::Yard::Handlers::Ruby::DataTypeHandler < PuppetStrings::Yard
111
144
  end
112
145
 
113
146
  # ----- The following methods are different/additions from the original Literal_evaluator
114
- def literal_Object(o) # rubocop:disable Naming/UncommunicativeMethodParamName
147
+ def literal_Object(o)
115
148
  # Ignore any other object types
116
149
  end
117
150
 
118
- def literal_AccessExpression(o) # rubocop:disable Naming/UncommunicativeMethodParamName
151
+ def literal_AccessExpression(o)
119
152
  # Extract the raw text of the Access Expression
120
153
  ::Puppet::Pops::Adapters::SourcePosAdapter.adapt(o).extract_text
121
154
  end
122
155
 
123
- def literal_QualifiedReference(o) # rubocop:disable Naming/UncommunicativeMethodParamName
156
+ def literal_QualifiedReference(o)
124
157
  # Extract the raw text of the Qualified Reference
125
158
  ::Puppet::Pops::Adapters::SourcePosAdapter.adapt(o).extract_text
126
159
  end
127
160
 
128
161
  # ----- The following methods are the same as the original Literal_evaluator
129
- def literal_Factory(o) # rubocop:disable Naming/UncommunicativeMethodParamName
162
+ def literal_Factory(o)
130
163
  literal(o.model)
131
164
  end
132
165
 
133
- def literal_Program(o) # rubocop:disable Naming/UncommunicativeMethodParamName
166
+ def literal_Program(o)
134
167
  literal(o.body)
135
168
  end
136
169
 
137
- def literal_LiteralString(o) # rubocop:disable Naming/UncommunicativeMethodParamName
170
+ def literal_LiteralString(o)
138
171
  o.value
139
172
  end
140
173
 
141
- def literal_QualifiedName(o) # rubocop:disable Naming/UncommunicativeMethodParamName
174
+ def literal_QualifiedName(o)
142
175
  o.value
143
176
  end
144
177
 
145
- def literal_LiteralNumber(o) # rubocop:disable Naming/UncommunicativeMethodParamName
178
+ def literal_LiteralNumber(o)
146
179
  o.value
147
180
  end
148
181
 
149
- def literal_LiteralBoolean(o) # rubocop:disable Naming/UncommunicativeMethodParamName
182
+ def literal_UnaryMinusExpression(o)
183
+ -1 * literal(o.expr)
184
+ end
185
+
186
+ def literal_LiteralBoolean(o)
150
187
  o.value
151
188
  end
152
189
 
153
- def literal_LiteralUndef(o) # rubocop:disable Naming/UncommunicativeMethodParamName
190
+ def literal_LiteralUndef(o)
154
191
  nil
155
192
  end
156
193
 
157
- def literal_LiteralDefault(o) # rubocop:disable Naming/UncommunicativeMethodParamName
194
+ def literal_LiteralDefault(o)
158
195
  :default
159
196
  end
160
197
 
161
- def literal_LiteralRegularExpression(o) # rubocop:disable Naming/UncommunicativeMethodParamName
198
+ def literal_LiteralRegularExpression(o)
162
199
  o.value
163
200
  end
164
201
 
165
- def literal_ConcatenatedString(o) # rubocop:disable Naming/UncommunicativeMethodParamName
202
+ def literal_ConcatenatedString(o)
166
203
  # use double quoted string value if there is no interpolation
167
204
  throw :not_literal unless o.segments.size == 1 && o.segments[0].is_a?(Model::LiteralString)
168
205
  o.segments[0].value
169
206
  end
170
207
 
171
- def literal_LiteralList(o) # rubocop:disable Naming/UncommunicativeMethodParamName
172
- o.values.map {|v| literal(v) }
208
+ def literal_LiteralList(o)
209
+ o.values.map { |v| literal(v) }
173
210
  end
174
211
 
175
- def literal_LiteralHash(o) # rubocop:disable Naming/UncommunicativeMethodParamName
212
+ def literal_LiteralHash(o)
176
213
  o.entries.reduce({}) do |result, entry|
177
214
  result[literal(entry.key)] = literal(entry.value)
178
215
  result
@@ -180,27 +217,58 @@ class PuppetStrings::Yard::Handlers::Ruby::DataTypeHandler < PuppetStrings::Yard
180
217
  end
181
218
  end
182
219
 
183
- def populate_data_type_params_from_literal_hash!(hash, params_hash)
184
- return if hash.nil?
220
+ # Extracts the datatype attributes from a Puppet Data Type interface hash.
221
+ # Returns a Hash with a :types key (Array of data types for the parameter) and :default key (The default value of the parameter)
222
+ # @return Hash[Symbol => Hash] The Datatype Attributes as a hash
223
+ def extract_params(hash)
224
+ params_hash = {}
185
225
  # Exit early if there are no entries in the hash
186
- return if hash['attributes'].nil? || hash['attributes'].count.zero?
226
+ return params_hash if hash.nil? || hash['attributes'].nil? || hash['attributes'].empty?
187
227
 
188
228
  hash['attributes'].each do |key, value|
189
229
  data_type = nil
190
230
  default = nil
191
- case value
192
- when String
231
+ if value.is_a?(String)
193
232
  data_type = value
194
- when Hash
233
+ elsif value.is_a?(Hash)
195
234
  data_type = value['type'] unless value['type'].nil?
196
235
  default = value['value'] unless value['value'].nil?
197
236
  end
198
237
  data_type = [data_type] unless data_type.nil? || data_type.is_a?(Array)
199
238
  params_hash[key] = { :types => data_type, :default => default }
200
239
  end
240
+
241
+ params_hash
242
+ end
243
+
244
+ # Extracts the datatype functions from a Puppet Data Type interface hash.
245
+ # Returns a Hash with a :param_types key (Array of types for each parameter) and :return_type key (The return type of the function)
246
+ # @return Hash[Symbol => Hash] The Datatype Attributes as a hash
247
+ def extract_functions(object, hash)
248
+ funcs_hash = {}
249
+ # Exit early if there are no entries in the hash
250
+ return funcs_hash if hash.nil? || hash['functions'].nil? || hash['functions'].empty?
251
+
252
+ hash['functions'].each do |key, func_type|
253
+ func_hash = { :param_types => [], :return_type => nil }
254
+ begin
255
+ callable_type = Puppet::Pops::Types::TypeParser.singleton.parse(func_type)
256
+ if callable_type.is_a?(Puppet::Pops::Types::PCallableType)
257
+ func_hash[:param_types] = callable_type.param_types.map { |pt| pt.to_s }
258
+ func_hash[:return_type] = callable_type.return_type.to_s
259
+ else
260
+ log.warn "The function definition for '#{key}' near #{object.file}:#{object.line} is not a Callable type"
261
+ end
262
+ rescue Puppet::ParseError => e
263
+ log.warn "Unable to parse the function definition for '#{key}' near #{object.file}:#{object.line}. #{e}"
264
+ end
265
+ funcs_hash[key] = func_hash
266
+ end
267
+ funcs_hash
201
268
  end
202
269
 
203
- def validate_tags!(object, actual_params_hash)
270
+ # Validates and automatically fixes yard @param tags for the data type
271
+ def validate_param_tags!(object, actual_params_hash)
204
272
  actual_param_names = actual_params_hash.keys
205
273
  tagged_param_names = object.tags(:param).map(&:name)
206
274
  # Log any errors
@@ -215,8 +283,10 @@ class PuppetStrings::Yard::Handlers::Ruby::DataTypeHandler < PuppetStrings::Yard
215
283
  # Find param tags with a type that is different from the actual definition
216
284
  object.tags(:param).reject { |tag| tag.types.nil? }.each do |tag|
217
285
  next if actual_params_hash[tag.name].nil?
286
+
218
287
  actual_data_type = actual_params_hash[tag.name][:types]
219
288
  next if actual_data_type.nil?
289
+
220
290
  log.warn "The @param tag for '#{tag.name}' has a different type definition than the actual attribute near #{object.file}:#{object.line}." if tag.types != actual_data_type
221
291
  end
222
292
 
@@ -230,7 +300,110 @@ class PuppetStrings::Yard::Handlers::Ruby::DataTypeHandler < PuppetStrings::Yard
230
300
  # Set the type in the param tag
231
301
  object.tags(:param).each do |tag|
232
302
  next if actual_params_hash[tag.name].nil?
303
+
233
304
  tag.types = actual_params_hash[tag.name][:types]
234
305
  end
235
306
  end
307
+
308
+ # Validates and automatically fixes yard @method! tags for the data type
309
+ def validate_methods!(object, actual_functions_hash)
310
+ actual_func_names = actual_functions_hash.keys
311
+ tagged_func_names = object.meths.map { |meth| meth.name.to_s }
312
+
313
+ # Log any errors
314
+ # Find functions which are not documented
315
+ (actual_func_names - tagged_func_names).each do |item|
316
+ log.warn "Missing @!method tag for function '#{item}' near #{object.file}:#{object.line}."
317
+ end
318
+ # Find functions which are not defined
319
+ (tagged_func_names - actual_func_names).each do |item|
320
+ log.warn "The @!method tag for '#{item}' has no matching function definition near #{object.file}:#{object.line}."
321
+ end
322
+ # Functions with the wrong return type
323
+ object.meths.each do |meth|
324
+ next unless actual_func_names.include?(meth.name.to_s)
325
+
326
+ return_tag = meth.docstring.tag(:return)
327
+ next if return_tag.nil?
328
+
329
+ actual_return_types = [actual_functions_hash[meth.name.to_s][:return_type]]
330
+ next if return_tag.types == actual_return_types
331
+
332
+ log.warn "The @return tag for '#{meth.name}' has a different type definition than the actual function near #{object.file}:#{object.line}. Expected #{actual_return_types}"
333
+ return_tag.types = actual_return_types
334
+ end
335
+
336
+ # Automatically fix missing methods
337
+ (actual_func_names - tagged_func_names).each do |name|
338
+ object.add_function(name, actual_functions_hash[name][:return_type], actual_functions_hash[name][:param_types])
339
+ end
340
+ # Remove extra methods. Can't use `meths` as that's a derived property
341
+ object.children.reject! { |child| child.is_a?(YARD::CodeObjects::MethodObject) && !actual_func_names.include?(child.name.to_s) }
342
+
343
+ # Add the return type for the methods if missing
344
+ object.meths.each do |meth|
345
+ next unless meth.docstring.tag(:return).nil?
346
+
347
+ meth.docstring.add_tag(YARD::Tags::Tag.new(:return, '', actual_functions_hash[meth.name.to_s][:return_type]))
348
+ end
349
+
350
+ # Sync the method properties and add the return type for the methods if missing
351
+ object.meths.each do |meth|
352
+ validate_function_method!(object, meth, actual_functions_hash[meth.name.to_s])
353
+ next unless meth.docstring.tag(:return).nil?
354
+
355
+ meth.docstring.add_tag(YARD::Tags::Tag.new(:return, '', actual_functions_hash[meth.name.to_s][:return_type]))
356
+ end
357
+
358
+ # The default meth.signature assumes ruby invocation (e.g. def meth(...)) but this doesn't make sense for a
359
+ # Puppet Data Type function invocation. So instead we derive a signature from the method definition.
360
+ object.meths.each do |meth|
361
+ params = ''
362
+ unless meth.docstring.tags(:param).empty?
363
+ params += '(' + meth.docstring.tags(:param).map { |t| t.name }.join(', ') + ')'
364
+ end
365
+ meth.signature = "#{object.name}.#{meth.name}" + params
366
+ end
367
+
368
+ nil
369
+ end
370
+
371
+ # Validates and automatically fixes a single yard @method!
372
+ # Used by the validate_methods! method.
373
+ def validate_function_method!(object, meth, actual_function)
374
+ # Remove extra params
375
+ if meth.docstring.tags(:param).count > actual_function[:param_types].count
376
+ index = 0
377
+ meth.docstring.delete_tag_if do |tag|
378
+ if tag.tag_name == 'param'
379
+ index += 1
380
+ if index > actual_function[:param_types].count
381
+ log.warn "The @param tag for '#{tag.name}' should not exist for function '#{meth.name}' that is defined near #{object.file}:#{object.line}. Expected only #{actual_function[:param_types].count} parameter/s"
382
+ true
383
+ else
384
+ false
385
+ end
386
+ else
387
+ false
388
+ end
389
+ end
390
+ end
391
+
392
+ # Add missing params
393
+ if meth.docstring.tags(:param).count < actual_function[:param_types].count
394
+ start = meth.docstring.tags(:param).count + 1
395
+ (start..actual_function[:param_types].count).each do |index| # Using 1-based index here instead of usual zero
396
+ meth.add_tag(YARD::Tags::Tag.new(:param, '', actual_function[:param_types][index - 1], "param#{index}"))
397
+ end
398
+ end
399
+
400
+ # Ensure the parameter types are correct
401
+ meth.docstring.tags(:param).each_with_index do |tag, index|
402
+ actual_types = [actual_function[:param_types][index]]
403
+ if tag.types != actual_types
404
+ log.warn "The @param tag for '#{tag.name}' for function '#{meth.name}' has a different type definition than the actual function near #{object.file}:#{object.line}. Expected #{actual_types}"
405
+ tag.types = actual_types
406
+ end
407
+ end
408
+ end
236
409
  end