puppet-strings 2.1.0 → 2.5.0

Sign up to get free protection for your applications and to get access to all the features.
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