From f8bfdd26b1629824c845d6312bc79babe952131d Mon Sep 17 00:00:00 2001 From: David Crosby Date: Tue, 13 Jan 2026 14:50:00 -0800 Subject: [PATCH] Add JSON recipe literals (rule) Summary: Now that JSON recipes support including recipes, we need to take those literals into account in downstream reports. Differential Revision: D90605017 --- lib/bookworm/rules/IncludeRecipeLiterals.rb | 31 ++++++++++++-------- spec/rules/IncludeRecipeLiterals_spec.rb | 32 +++++++++++++++++++++ 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/lib/bookworm/rules/IncludeRecipeLiterals.rb b/lib/bookworm/rules/IncludeRecipeLiterals.rb index c3e9f04..54fe449 100644 --- a/lib/bookworm/rules/IncludeRecipeLiterals.rb +++ b/lib/bookworm/rules/IncludeRecipeLiterals.rb @@ -13,25 +13,32 @@ # See the License for the specific language governing permissions and # limitations under the License. description 'Extracts recipes that are used by include_recipe with string literals' -keys ['recipe'] +keys ['recipe', 'recipejson'] def_node_search :include_recipe_string_literals, '`(send nil? :include_recipe (str $_))' def to_a arr = [] - include_recipe_string_literals(@metadata['ast']).each do |x| - arr << x - end - return [] if arr.empty? - arr.map! do |x| - if x.start_with?('::') - "#{@metadata['cookbook']}#{x}" - elsif !x.include?('::') - "#{x}::default" - else - x + if @metadata['ast'] # Ruby recipe + include_recipe_string_literals(@metadata['ast']).each do |x| + arr << x + end + return [] if arr.empty? + arr.map! do |x| + if x.start_with?('::') + "#{@metadata['cookbook']}#{x}" + elsif !x.include?('::') + "#{x}::default" + else + x + end + end + elsif @metadata['object'] # JSON recipe + if @metadata['object'].key?('include_recipes') + arr = @metadata['object']['include_recipes'] end end + arr.uniq! arr.sort! end diff --git a/spec/rules/IncludeRecipeLiterals_spec.rb b/spec/rules/IncludeRecipeLiterals_spec.rb index 565d047..7a0d626 100644 --- a/spec/rules/IncludeRecipeLiterals_spec.rb +++ b/spec/rules/IncludeRecipeLiterals_spec.rb @@ -56,4 +56,36 @@ # Note - output is sorted expect(rule.output).to eq(['fake_cookbook::bar', 'fake_cookbook::foo']) end + + context 'with JSON recipes' do + it 'extracts include_recipes from JSON object' do + rule = described_class.new({ + 'object' => { 'include_recipes' => ['foo::bar', 'baz::qux'] }, + }) + expect(rule.output).to eq(['baz::qux', 'foo::bar']) + end + + it 'returns empty array when no include_recipes key' do + rule = described_class.new({ + 'object' => { 'some_other_key' => 'value' }, + }) + expect(rule.output).to eq([]) + end + + it 'returns empty array when include_recipes is empty' do + rule = described_class.new({ + 'object' => { 'include_recipes' => [] }, + }) + expect(rule.output).to eq([]) + end + + it 'deduplicates include_recipes' do + rule = described_class.new({ + 'object' => { + 'include_recipes' => ['foo::bar', 'foo::bar', 'baz::qux'], + }, + }) + expect(rule.output).to eq(['baz::qux', 'foo::bar']) + end + end end