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