Class: FoodCritic::Linter

Inherits:
Object
  • Object
show all
Includes:
Api
Defined in:
lib/foodcritic/linter.rb

Overview

The main entry point for linting your Chef cookbooks.

Constant Summary collapse

DEFAULT_CHEF_VERSION =

The default version that will be used to determine relevant rules. This can be over-ridden at the command line with the ‘–chef-version` option.

"15.1.36".freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Api

#ast_cache, #attribute_access, #cookbook_base_path, #cookbook_maintainer, #cookbook_maintainer_email, #cookbook_name, #declared_dependencies, #ensure_file_exists, #field, #field_value, #file_match, #find_resources, #gem_version, #included_recipes, #json_file_to_hash, #literal_searches, #match, #metadata_field, #read_ast, #resource_attribute, #resource_attributes, #resource_attributes_by_type, #resource_name, #resource_type, #resources_by_type, #ruby_code?, #searches, #standard_cookbook_subdirs, #supported_platforms, #template_file, #template_paths, #templates_included

Methods included from Notifications

#notifications

Methods included from Chef

#chef_dsl_methods, #chef_node_methods, #resource_action?, #resource_attribute?, #valid_query?

Instance Attribute Details

#chef_versionObject (readonly)

Returns the value of attribute chef_version.



13
14
15
# File 'lib/foodcritic/linter.rb', line 13

def chef_version
  @chef_version
end

Class Method Details

.run(cmd_line) ⇒ Object

Perform a lint check. This method is intended for use by the command-line wrapper. If you are programmatically using foodcritic you should use ‘#check` below.



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/foodcritic/linter.rb', line 18

def self.run(cmd_line)
  # The first item is the string output, the second is exit code.
  return [cmd_line.help, 0] if cmd_line.show_help?
  return [cmd_line.version, 0] if cmd_line.show_version?
  if !cmd_line.valid_grammar?
    [cmd_line.help, 4]
  elsif cmd_line.list_rules?
    listing = FoodCritic::Linter.new.list(cmd_line.options)
    [listing, 0]
  elsif cmd_line.valid_paths?
    review = FoodCritic::Linter.new.check(cmd_line.options)
    [review, review.failed? ? 3 : 0]
  else
    [cmd_line.help, 2]
  end
end

Instance Method Details

#check(options = {}) ⇒ Object

Review the cookbooks at the provided path, identifying potential improvements.

The ‘options` are a hash where the valid keys are:

  • ‘:cookbook_paths` - Cookbook paths to lint

  • ‘:role_paths` - Role paths to lint

  • ‘:include_rules` - Paths to additional rules to apply

  • ‘:search_gems - If true then search for custom rules in installed gems.

  • ‘:tags` - The tags to filter rules based on

  • ‘:fail_tags` - The tags to fail the build on

  • ‘:exclude_paths` - Paths to exclude from linting



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/foodcritic/linter.rb', line 67

def check(options = {})
  options = setup_defaults(options)
  @options = options
  @chef_version = options[:chef_version] || DEFAULT_CHEF_VERSION
  ast_cache(options[:ast_cache_size])

  warnings = []; last_dir = nil; matched_rule_tags = Set.new
  load_rules
  paths = specified_paths!(options)

  # Loop through each file to be processed and apply the rules
  files = files_to_process(paths)

  if options[:progress]
    puts "Checking #{files.count} files"
  end

  files.each do |p|
    relevant_tags = if options[:tags].any?
                      options[:tags]
                    else
                      rule_file_tags(p[:filename])
                    end

    progress = "."

    active_rules(relevant_tags).each do |rule|
      state = {
        path_type: p[:path_type],
        file: p[:filename],
        ast: read_ast(p[:filename]),
        rule: rule,
        last_dir: last_dir,
      }

      matches = if p[:path_type] == :cookbook
                  cookbook_matches(state)
                else
                  other_matches(state)
                end

      matches = remove_ignored(matches, state[:rule], state[:file])

      progress = "x" if matches.any?

      # Convert the matches into warnings
      matches.each do |match|
        warnings << Warning.new(state[:rule],
                                { filename: state[:file] }.merge(match),
                                options)
        matched_rule_tags << state[:rule].tags
      end
    end

    putc progress if options[:progress]

    last_dir = cookbook_dir(p[:filename])
  end

  puts "" if options[:progress]

  Review.new(paths, warnings)
end

#cookbook_matches(state) ⇒ Object



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/foodcritic/linter.rb', line 131

def cookbook_matches(state)
  cbk_matches = matches(state[:rule].recipe, state[:ast], state[:file])

  if dsl_method_for_file(state[:file])
    cbk_matches += matches(state[:rule].send(
      dsl_method_for_file(state[:file])), state[:ast], state[:file])
  end

  per_cookbook_rules(state[:last_dir], state[:file]) do
    if File.basename(state[:file]) == "metadata.rb"
      cbk_matches += matches(
        state[:rule]., state[:ast], state[:file])
    end
    cbk_matches += matches(
      state[:rule].cookbook, cookbook_dir(state[:file]))
  end

  cbk_matches
end

#list(options = {}) ⇒ Object

List the rules that are currently in effect.

The ‘options` are a hash where the valid keys are:

  • ‘:include_rules` - Paths to additional rules to apply

  • ‘:search_gems - If true then search for custom rules in installed gems.

  • ‘:tags` - The tags to filter rules based on



42
43
44
45
46
47
48
49
50
51
52
# File 'lib/foodcritic/linter.rb', line 42

def list(options = {})
  options = setup_defaults(options)
  @options = options
  load_rules

  if options[:tags].any?
    @rules = active_rules(options[:tags])
  end

  RuleList.new(@rules)
end

#load_rulesObject

Load the rules from the (fairly unnecessary) DSL.



156
157
158
# File 'lib/foodcritic/linter.rb', line 156

def load_rules
  load_rules!(@options) unless defined? @rules
end

#load_rules!(options) ⇒ Object



160
161
162
163
164
165
# File 'lib/foodcritic/linter.rb', line 160

def load_rules!(options)
  rule_files = Dir.glob(File.join(File.dirname(__FILE__), "rules", "*"))
  rule_files << options[:include_rules]
  rule_files << rule_files_in_gems if options[:search_gems]
  @rules = RuleDsl.load(rule_files.flatten.compact, chef_version)
end

#other_matches(state) ⇒ Object



151
152
153
# File 'lib/foodcritic/linter.rb', line 151

def other_matches(state)
  matches(state[:rule].send(state[:path_type]), state[:ast], state[:file])
end