Skip to content

Commit 79fa8f5

Browse files
dafyddcrosbyfacebook-github-bot
authored andcommitted
add multi-parser support, AllJsonRecipes report
Summary: With the addition of [JSON recipes in Chef](chef/chef#15094), we'll need to make Bookworm aware of other file types besides Ruby. For this diff, I'm focusing mostly on adding JSON parsing (ie I'll be updating the built-in rules+reports to handle JSON recipes in a subsequent diff). Differential Revision: D79741933 fbshipit-source-id: f8b3d4a1aa6667ca6ddd13267112a25f304e3e1f
1 parent cb121cb commit 79fa8f5

File tree

9 files changed

+169
-23
lines changed

9 files changed

+169
-23
lines changed

cookbooks/fb_bookworm/files/default/bookworm/lib/bookworm/crawler.rb

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,6 @@
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
1515
#
16-
require 'rubocop'
17-
require 'parser/current'
18-
19-
# TODO(dcrosby) Bookworm key should determine which AST parser is chosen. This
20-
# would allow multiple AST parsers (ie ripper) and a way of ingesting non-Ruby
21-
# files like JSON/YAML
2216

2317
module Bookworm
2418
class Crawler
@@ -47,23 +41,12 @@ def generate_file_queue(key)
4741
def process_paths(key)
4842
queue = @intake_queue[key]
4943
processed_files = {}
44+
parser = BOOKWORM_KEYS[key]['parser']
5045
until queue.empty?
5146
path = queue.pop
52-
processed_files[path] = generate_ast(File.read(path))
47+
processed_files[path] = parser.parse(File.read(path))
5348
end
5449
@processed_files[key] = processed_files
5550
end
56-
57-
# In order to keep rules from barfing on a nil value (when no AST is
58-
# generated at all from eg. an empty source code file), we supply a
59-
# single node called bookworm_found_nil. It's a magic value that is
60-
# 'unique enough' for our purposes
61-
EMPTY_RUBOCOP_AST = ::RuboCop::AST::Node.new('bookworm_found_nil').freeze
62-
def generate_rubocop_ast(code)
63-
::RuboCop::ProcessedSource.new(code, RUBY_VERSION.to_f)&.ast ||
64-
EMPTY_RUBOCOP_AST
65-
end
66-
67-
alias generate_ast generate_rubocop_ast
6851
end
6952
end

cookbooks/fb_bookworm/files/default/bookworm/lib/bookworm/keys.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
# limitations under the License.
1717
#
1818

19+
require 'bookworm/parsers/builtin_parsers'
20+
1921
module Bookworm
2022
# These keys are how we generalize and generate a lot of code within Bookworm
2123
#
@@ -27,6 +29,9 @@ module Bookworm
2729
# dont_init_kb_key: Don't initialize the keys on knowledge base creation
2830
# determine_cookbook_name: Determines the cookbook name from the given path
2931
# path_name_regex: A regex with a capture to determine the prettified name of the file
32+
#
33+
# TODO: handle cookbook root alias files like cookbook/attributes.rb
34+
# https://github.com/chef/chef/blob/d22fa4cdc46c58e450198427b86c4de4ed9a90e3/docs/dev/design_documents/cookbook_root_aliases.md?plain=1#L25-L28
3035
BOOKWORM_KEYS = {
3136
'cookbook' => {
3237
'metakey' => true,
@@ -46,6 +51,12 @@ module Bookworm
4651
'determine_cookbook_name' => true,
4752
'path_name_regex' => 'recipes/(.*)\.rb',
4853
},
54+
'recipejson' => {
55+
'determine_cookbook_name' => true,
56+
'glob_pattern' => '*/recipes/*.json',
57+
'path_name_regex' => 'recipes/(.*)\.json',
58+
'parser' => ::Bookworm::Parsers::JSON,
59+
},
4960
'attribute' => {
5061
'determine_cookbook_name' => true,
5162
'path_name_regex' => 'attributes/(.*)\.rb',
@@ -69,6 +80,7 @@ module Bookworm
6980
BOOKWORM_KEYS.each do |k, v|
7081
BOOKWORM_KEYS[k]['determine_cookbook_name'] ||= false
7182
BOOKWORM_KEYS[k]['plural'] ||= "#{k}s"
83+
BOOKWORM_KEYS[k]['parser'] ||= ::Bookworm::Parsers::RuboCop
7284
BOOKWORM_KEYS[k]['source_dirs'] ||= 'cookbook_dirs'
7385
BOOKWORM_KEYS[k]['glob_pattern'] ||= "*/#{v['plural']}/*.rb"
7486
end

cookbooks/fb_bookworm/files/default/bookworm/lib/bookworm/knowledge_base.rb

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,11 @@ def create_pluralized_getter(key)
8888
def init_key(key, files)
8989
self[key] = {}
9090
path_name_regex = BOOKWORM_KEYS[key]['path_name_regex']
91-
files.each do |path, ast|
91+
parser_output_key = BOOKWORM_KEYS[key]['parser'].parser_output_key
92+
files.each do |path, output|
9293
m = path.match(/#{path_name_regex}/)
9394
file_name = m[1]
94-
self[key][file_name] = { 'path' => path, 'ast' => ast }
95+
self[key][file_name] = { 'path' => path, parser_output_key => output }
9596
end
9697
end
9798

@@ -104,13 +105,14 @@ def init_key_with_cookbook_name(key, files)
104105
self[key] = {}
105106
self['cookbook'] ||= {}
106107
path_name_regex = BOOKWORM_KEYS[key]['path_name_regex']
107-
files.each do |path, ast|
108+
parser_output_key = BOOKWORM_KEYS[key]['parser'].parser_output_key
109+
files.each do |path, output|
108110
m = path.match(%r{/?([\w-]+)/#{path_name_regex}})
109111
cookbook_name = m[1]
110112
file_name = m[2]
111113
self['cookbook'][cookbook_name] ||= {}
112114
self[key]["#{cookbook_name}::#{file_name}"] =
113-
{ 'path' => path, 'cookbook' => cookbook_name, 'ast' => ast }
115+
{ 'path' => path, 'cookbook' => cookbook_name, parser_output_key => output }
114116
end
115117
end
116118
end
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# frozen_string_literal: true
2+
3+
# Copyright (c) 2025-present, Meta Platforms, Inc. and affiliates
4+
# All rights reserved.
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
19+
module Bookworm
20+
class KeyParserBase
21+
def self.parse(str)
22+
fail 'parse method required to inherit KeyParserBase class'
23+
end
24+
end
25+
end
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# frozen_string_literal: true
2+
3+
# Copyright (c) 2025-present, Meta Platforms, Inc. and affiliates
4+
# All rights reserved.
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
18+
require 'bookworm/parser_base'
19+
20+
require 'bookworm/parsers/rubocop'
21+
require 'bookworm/parsers/json'
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# frozen_string_literal: true
2+
3+
# Copyright (c) 2025-present, Meta Platforms, Inc. and affiliates
4+
# All rights reserved.
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
19+
require 'json'
20+
21+
module Bookworm
22+
class Parsers
23+
class JSON < ::Bookworm::KeyParserBase
24+
def self.parse(str)
25+
::JSON.parse(str)
26+
end
27+
28+
def self.parser_output_key
29+
'object'
30+
end
31+
end
32+
end
33+
end
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# frozen_string_literal: true
2+
3+
# Copyright (c) 2025-present, Meta Platforms, Inc. and affiliates
4+
# All rights reserved.
5+
#
6+
# Licensed under the Apache License, Version 2.0 (the "License");
7+
# you may not use this file except in compliance with the License.
8+
# You may obtain a copy of the License at
9+
#
10+
# http://www.apache.org/licenses/LICENSE-2.0
11+
#
12+
# Unless required by applicable law or agreed to in writing, software
13+
# distributed under the License is distributed on an "AS IS" BASIS,
14+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
# See the License for the specific language governing permissions and
16+
# limitations under the License.
17+
#
18+
19+
require 'rubocop'
20+
require 'parser/current'
21+
22+
module Bookworm
23+
class Parsers
24+
class RuboCop < ::Bookworm::KeyParserBase
25+
# In order to keep rules from barfing on a nil value (when no AST is
26+
# generated at all from eg. an empty source code file), we supply a
27+
# single node called bookworm_found_nil. It's a magic value that is
28+
# 'unique enough' for our purposes
29+
EMPTY_RUBOCOP_AST = ::RuboCop::AST::Node.new('bookworm_found_nil').freeze
30+
def self.parse(str)
31+
::RuboCop::ProcessedSource.new(str, RUBY_VERSION.to_f)&.ast ||
32+
EMPTY_RUBOCOP_AST
33+
end
34+
35+
def self.parser_output_key
36+
'ast'
37+
end
38+
end
39+
end
40+
41+
class Crawler
42+
# TODO: Fix references to use Bookworm::Parsers::RuboCop::EMPTY_RUBOCOP_AST
43+
EMPTY_RUBOCOP_AST = ::Bookworm::Parsers::RuboCop::EMPTY_RUBOCOP_AST
44+
end
45+
end
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Copyright (c) 2025-present, Meta Platforms, Inc. and affiliates
2+
# All rights reserved.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
description 'Lists all JSON Chef Recipes'
16+
needs_rules ['RecipeExists']
17+
18+
def to_a
19+
@kb.recipejsons.keys.sort
20+
end
21+
22+
def output
23+
to_a
24+
end

cookbooks/fb_bookworm/files/default/bookworm/lib/bookworm/rules/RecipeExists.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
description 'Helper rule to show existence of a recipe'
1616
keys %w{
1717
recipe
18+
recipejson
1819
}
1920

2021
def output

0 commit comments

Comments
 (0)