Merge pull request #125 from crystal-ameba/config-globs

Ability to configure globs and globally excluded paths
This commit is contained in:
Vitalii Elenhaupt 2020-03-22 15:12:10 +02:00 committed by GitHub
commit 6a913be980
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 193 additions and 48 deletions

View file

@ -24,8 +24,9 @@
* [Docker](#docker) * [Docker](#docker)
* [From sources](#from-sources) * [From sources](#from-sources)
- [Configuration](#configuration) - [Configuration](#configuration)
* [Only/Except](#onlyexcept) * [Sources](#sources)
* [Explanation](#explanation) * [Rules](#rules)
* [Explain issues](#explain-issues)
* [Inline disabling](#inline-disabling) * [Inline disabling](#inline-disabling)
- [Editors & integrations](#editors--integrations) - [Editors & integrations](#editors--integrations)
- [Credits & inspirations](#credits--inspirations) - [Credits & inspirations](#credits--inspirations)
@ -150,7 +151,37 @@ It allows to configure rule properties, disable specific rules and exclude sourc
Generate new file by running `ameba --gen-config`. Generate new file by running `ameba --gen-config`.
### Only/Except ### Sources
**List of sources to run Ameba on can be configured globally via:**
- `Globs` section - an array of wildcards (or paths) to include to the
inspection. Defaults to `%w(**/*.cr !lib)`, meaning it includes all project
files with `*.cr` extension except those which exist in `lib` folder.
- `Excluded` section - an array of wildcards (or paths) to exclude from the
source list defined by `Globs`. Defaults to an empty array.
In this example we define default globs and exclude `src/compiler` folder:
``` yaml
Globs:
- **/*.cr
- !lib
Excluded:
- src/compiler
```
**Specific sources can be excluded at rule level**:
``` yaml
Style/RedundantBegin:
Excluded:
- src/server/processor.cr
- src/server/api.cr
```
### Rules
One or more rules, or a one or more group of rules can be included or excluded One or more rules, or a one or more group of rules can be included or excluded
via command line arguments: via command line arguments:
@ -162,7 +193,14 @@ $ ameba --except Lint/Syntax # runs all rules except Lint/Syntax
$ ameba --except Style,Lint # runs all rules except rules in Style and Lint groups $ ameba --except Style,Lint # runs all rules except rules in Style and Lint groups
``` ```
### Explanation Or through the configuration file:
``` yaml
Style/RedundantBegin:
Enabled: false
```
### Explain issues
Ameba allows you to dig deeper into an issue, by showing you details about the issue Ameba allows you to dig deeper into an issue, by showing you details about the issue
and the reasoning by it being reported. and the reasoning by it being reported.

View file

@ -8,6 +8,70 @@ module Ameba
Config::AVAILABLE_FORMATTERS.should_not be_nil Config::AVAILABLE_FORMATTERS.should_not be_nil
end end
describe ".new" do
it "loads default globs when config is empty" do
yml = YAML.parse "{}"
config = Config.new(yml)
config.globs.should eq Config::DEFAULT_GLOBS
end
it "initializes globs as string" do
yml = YAML.parse <<-CONFIG
---
Globs: src/*.cr
CONFIG
config = Config.new(yml)
config.globs.should eq %w(src/*.cr)
end
it "initializes globs as array" do
yml = YAML.parse <<-CONFIG
---
Globs:
- "src/*.cr"
- "!spec"
CONFIG
config = Config.new(yml)
config.globs.should eq %w(src/*.cr !spec)
end
it "raises if Globs has a wrong type" do
yml = YAML.parse <<-CONFIG
---
Globs: 100
CONFIG
expect_raises(Exception, "incorrect 'Globs' section in a config file") { Config.new(yml) }
end
it "initializes excluded as string" do
yml = YAML.parse <<-CONFIG
---
Excluded: spec
CONFIG
config = Config.new(yml)
config.excluded.should eq %w(spec)
end
it "initializes excluded as array" do
yml = YAML.parse <<-CONFIG
---
Excluded:
- spec
- lib/*.cr
CONFIG
config = Config.new(yml)
config.excluded.should eq %w(spec lib/*.cr)
end
it "raises if Excluded has a wrong type" do
yml = YAML.parse <<-CONFIG
---
Excluded: true
CONFIG
expect_raises(Exception, "incorrect 'Excluded' section in a config file") { Config.new(yml) }
end
end
describe ".load" do describe ".load" do
it "loads custom config" do it "loads custom config" do
config = Config.load config_sample config = Config.load config_sample
@ -28,7 +92,7 @@ module Ameba
config = Config.load config_sample config = Config.load config_sample
it "holds source globs" do it "holds source globs" do
config.globs.should contain "spec/ameba/config_spec.cr" config.globs.should eq Config::DEFAULT_GLOBS
end end
it "allows to set globs" do it "allows to set globs" do
@ -37,12 +101,36 @@ module Ameba
end end
end end
describe "#excluded, #excluded=" do
config = Config.load config_sample
it "defaults to empty array" do
config.excluded.should be_empty
end
it "allows to set excluded" do
config.excluded = ["spec"]
config.excluded.should eq ["spec"]
end
end
describe "#sources" do describe "#sources" do
config = Config.load config_sample config = Config.load config_sample
it "returns list of sources" do it "returns list of sources" do
config.sources.size.should be > 0 config.sources.size.should be > 0
config.sources.first.should be_a Source config.sources.first.should be_a Source
config.sources.any? { |s| s.fullpath == __FILE__ }.should be_true
end
it "returns a list of sources mathing globs" do
config.globs = %w(**/config_spec.cr)
config.sources.size.should eq(1)
end
it "returns a lisf of sources excluding 'Excluded'" do
config.excluded = %w(**/config_spec.cr)
config.sources.any? { |s| s.fullpath == __FILE__ }.should be_false
end end
end end

View file

@ -8,7 +8,7 @@ module Ameba::Cli
def run(args = ARGV) def run(args = ARGV)
opts = parse_args args opts = parse_args args
config = Config.load opts.config, opts.colors? config = Config.load opts.config, opts.colors?
config.globs = opts.globs config.globs = opts.globs.not_nil! if opts.globs
config.severity = opts.fail_level.not_nil! if opts.fail_level config.severity = opts.fail_level.not_nil! if opts.fail_level
configure_formatter(config, opts) configure_formatter(config, opts)

View file

@ -25,43 +25,16 @@ class Ameba::Config
} }
PATH = ".ameba.yml" PATH = ".ameba.yml"
DEFAULT_GLOBS = %w(
**/*.cr
!lib
)
setter formatter : Formatter::BaseFormatter? setter formatter : Formatter::BaseFormatter?
setter globs : Array(String)?
getter rules : Array(Rule::Base) getter rules : Array(Rule::Base)
property severity = Severity::Convention property severity = Severity::Convention
@rule_groups : Hash(String, Array(Rule::Base))
# Creates a new instance of `Ameba::Config` based on YAML parameters.
#
# `Config.load` uses this constructor to instantiate new config by YAML file.
protected def initialize(@config : YAML::Any)
@rules = Rule.rules.map &.new(config).as(Rule::Base)
@rule_groups = @rules.group_by &.group
if @config.as_h? && (name = @config["Formatter"]?.try &.["Name"]?)
self.formatter = name.to_s
end
end
# Loads YAML configuration file by `path`.
#
# ```
# config = Ameba::Config.load
# ```
#
def self.load(path = PATH, colors = true)
Colorize.enabled = colors
content = File.exists?(path) ? File.read path : ""
Config.new YAML.parse(content)
rescue e
raise "Config file is invalid: #{e.message}"
end
def self.formatter_names
AVAILABLE_FORMATTERS.keys.join("|")
end
# Returns a list of paths (with wildcards) to files. # Returns a list of paths (with wildcards) to files.
# Represents a list of sources to be inspected. # Represents a list of sources to be inspected.
# If globs are not set, it will return default list of files. # If globs are not set, it will return default list of files.
@ -71,22 +44,61 @@ class Ameba::Config
# config.globs = ["**/*.cr"] # config.globs = ["**/*.cr"]
# config.globs # config.globs
# ``` # ```
property globs : Array(String)
# Represents a list of paths to exclude from globs.
# Can have wildcards.
# #
def globs # ```
@globs ||= default_files # config = Ameba::Config.load
# config.excluded = ["spec", "src/server/*.cr"]
# ```
property excluded : Array(String)
@rule_groups : Hash(String, Array(Rule::Base))
# Creates a new instance of `Ameba::Config` based on YAML parameters.
#
# `Config.load` uses this constructor to instantiate new config by YAML file.
protected def initialize(config : YAML::Any)
@rules = Rule.rules.map &.new(config).as(Rule::Base)
@rule_groups = @rules.group_by &.group
@excluded = load_array_section(config, "Excluded")
@globs = load_array_section(config, "Globs", DEFAULT_GLOBS)
self.formatter = load_formatter_name(config)
end end
# Returns a list of sources. # Loads YAML configuration file by `path`.
#
# ```
# config = Ameba::Config.load
# ```
#
def self.load(path = PATH, colors = true)
Colorize.enabled = colors
content = File.exists?(path) ? File.read path : "{}"
Config.new YAML.parse(content)
rescue e
raise "Config file is invalid: #{e.message}"
end
def self.formatter_names
AVAILABLE_FORMATTERS.keys.join("|")
end
# Returns a list of sources matching globs and excluded sections.
# #
# ``` # ```
# config = Ameba::Config.load # config = Ameba::Config.load
# config.sources # => list of default sources # config.sources # => list of default sources
# config.globs = ["**/*.cr"] # config.globs = ["**/*.cr"]
# config.excluded = ["spec"]
# config.sources # => list of sources pointing to files found by the wildcards # config.sources # => list of sources pointing to files found by the wildcards
# ``` # ```
# #
def sources def sources
find_files_by_globs(globs) (find_files_by_globs(globs) - find_files_by_globs(excluded))
.map { |path| Source.new File.read(path), path } .map { |path| Source.new File.read(path), path }
end end
@ -100,7 +112,7 @@ class Ameba::Config
# ``` # ```
# #
def formatter def formatter
@formatter ||= default_formatter @formatter ||= Formatter::DotFormatter.new
end end
# Sets formatter by name. # Sets formatter by name.
@ -158,12 +170,19 @@ class Ameba::Config
end end
end end
private def default_files private def load_formatter_name(config)
Dir["**/*.cr"].reject(&.starts_with? "lib/") name = config["Formatter"]?.try &.["Name"]?
name ? name.to_s : nil
end end
private def default_formatter private def load_array_section(config, section_name, default = [] of String)
Formatter::DotFormatter.new case value = config[section_name]?
when .nil? then default
when .as_s? then [value.to_s]
when .as_a? then value.as_a.map(&.as_s)
else
raise "incorrect '#{section_name}' section in a config files"
end
end end
# :nodoc: # :nodoc: