diff --git a/README.md b/README.md index 1936ce7b..16cc4f8c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# ERB Lint [![Build Status](https://github.com/Shopify/erb-lint/workflows/Tests/badge.svg)](https://github.com/Shopify/erb-lint/actions) +# ERB Lint [![Build Status](https://github.com/Shopify/erb_lint/workflows/Tests/badge.svg)](https://github.com/Shopify/erb_lint/actions) -`erb-lint` is a tool to help lint your ERB or HTML files using the included linters or by writing your own. +`erb_lint` is a tool to help lint your ERB or HTML files using the included linters or by writing your own. ## Requirements @@ -22,7 +22,7 @@ gem 'erb_lint', require: false ## Configuration -Create a `.erb-lint.yml` file in your project, with the following structure: +Create a `.erb_lint.yml` file in your project, with the following structure: ```yaml --- @@ -44,10 +44,10 @@ See below for linter-specific configuration options. This gem provides a command-line interface which can be run like so: -1. Run `erblint [options]` if the gem is installed standalone. -2. Run `bundle exec erblint [options]` if the gem is installed as a Gemfile dependency for your app. +1. Run `erb_lint [options]` if the gem is installed standalone. +2. Run `bundle exec erb_lint [options]` if the gem is installed as a Gemfile dependency for your app. -For example, `erblint --lint-all --enable-all-linters` will run all available +For example, `erb_lint --lint-all --enable-all-linters` will run all available linters on all ERB files in the current directory or its descendants (`**/*.html{+*,}.erb`). If you want to change the glob & exclude that is used, you can configure it by adding it to your config file as follows: @@ -221,8 +221,8 @@ Linter-Specific Option | Description ### Rubocop -Runs RuboCop on all ruby statements found in ERB templates. The RuboCop configuration that `erb-lint` uses can inherit from -the configuration that the rest of your application uses. `erb-lint` can be configured independently however, as it will often +Runs RuboCop on all ruby statements found in ERB templates. The RuboCop configuration that `erb_lint` uses can inherit from +the configuration that the rest of your application uses. `erb_lint` can be configured independently however, as it will often be necessary to disable specific RuboCop rules that do not apply to ERB files. **Note**: Each ruby statement (between ERB tags `<% ... %>`) is parsed and analyzed independently of each other. Any rule that requires a broader context can trigger false positives (e.g. `Lint/UselessAssignment` will complaint for an assignment even if used in a subsequent ERB tag). @@ -523,11 +523,11 @@ Good ✅ ## Custom Linters -`erb-lint` allows you to create custom linters specific to your project. It will load linters from the `.erb-linters` directory in the root of your +`erb_lint` allows you to create custom linters specific to your project. It will load linters from the `.erb_linters` directory in the root of your repository. See the [linters directory](lib/erb_lint/linters) for examples of how to write linters. ```ruby -# .erb-linters/custom_linter.rb +# .erb_linters/custom_linter.rb module ERBLint module Linters @@ -552,7 +552,7 @@ module ERBLint end ``` -By default, this linter would be disabled. You can enable it by adding an entry to `.erb-lint.yml`: +By default, this linter would be disabled. You can enable it by adding an entry to `.erb_lint.yml`: ```yaml --- @@ -562,10 +562,10 @@ linters: custom_message: We suggest you change this file. ``` -Test your linter by running `erblint`'s command-line interface: +Test your linter by running `erb_lint`'s command-line interface: ```bash -bundle exec erblint --enable-linters custom_linter --lint-all +bundle exec erb_lint --enable-linters custom_linter --lint-all ``` Running this on a random project might yield this output: @@ -582,7 +582,7 @@ Errors were found in ERB files To write a linter that can autocorrect offenses it detects, simply add an `autocorrect` method that returns a callable. The callable is called with an instance of [`RuboCop::Cop::Corrector`](http://www.rubydoc.info/github/bbatsov/RuboCop/RuboCop/Cop/Corrector) -as argument, and therefore erb-lint correctors work exactly as RuboCop correctors do. +as argument, and therefore erb_lint correctors work exactly as RuboCop correctors do. ```ruby def autocorrect(_processed_source, offense) @@ -599,7 +599,7 @@ You can change the output format of ERB Lint by specifying formatters with the ` ### Multiline (default) ```sh -$ erblint +$ erb_lint Linting 8 files with 12 linters... Remove multiple trailing newline at the end of the file. @@ -614,7 +614,7 @@ In file: app/views/subscriptions/index.html.erb:38 ### Compact ```sh -erblint --format compact +erb_lint --format compact Linting 8 files with 12 linters... app/views/users/show.html.erb:95:0: Remove multiple trailing newline at the end of the file. app/views/users/_graph.html.erb:27:37: Extra space detected where there should be no space @@ -624,9 +624,9 @@ app/views/users/_graph.html.erb:27:37: Extra space detected where there should b ### JUnit ```sh -erblint --format junit +erb_lint --format junit - + @@ -684,7 +684,7 @@ Quality](https://docs.gitlab.com/ee/ci/testing/code_quality.html#implement-a-cus The cache is currently opt-in - to turn it on, use the `--cache` option: ```sh -erblint --cache ./app +erb_lint --cache ./app Cache mode is on Linting 413 files with 15 linters... File names pruned from the cache will be logged @@ -693,9 +693,9 @@ No errors were found in ERB files ``` Cached lint results are stored in the `.erb-lint-cache` directory by default, though a custom directory can be provided -via the `--cache-dir` option. Cache filenames are computed with a hash of information about the file and `erb-lint` settings. +via the `--cache-dir` option. Cache filenames are computed with a hash of information about the file and `erb_lint` settings. These files store instance attributes of the `CachedOffense` object, which only contain the `Offense` attributes -necessary to restore the results of running `erb-lint` for output. The cache also automatically prunes outdated files each time it's run. +necessary to restore the results of running `erb_lint` for output. The cache also automatically prunes outdated files each time it's run. You can also use the `--clear-cache` option to delete the cache file directory. diff --git a/erb_lint.gemspec b/erb_lint.gemspec index 3c06a856..73313c0e 100644 --- a/erb_lint.gemspec +++ b/erb_lint.gemspec @@ -11,7 +11,7 @@ Gem::Specification.new do |s| s.email = ["ruby@shopify.com"] s.summary = "ERB lint tool" s.description = "ERB Linter tool." - s.homepage = "https://github.com/Shopify/erb-lint" + s.homepage = "https://github.com/Shopify/erb_lint" s.license = "MIT" s.files = Dir["lib/**/*.rb", "exe/*"] diff --git a/exe/erb_lint b/exe/erb_lint new file mode 100755 index 00000000..ce9491df --- /dev/null +++ b/exe/erb_lint @@ -0,0 +1,10 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +lib_path = File.expand_path("#{__dir__}/../lib") +$LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include?(lib_path) + +require "erb_lint/cli" + +cli = ERBLint::CLI.new +exit(cli.run) diff --git a/exe/erblint b/exe/erblint index 4cff68c9..05dac9cf 100755 --- a/exe/erblint +++ b/exe/erblint @@ -1,9 +1,10 @@ #!/usr/bin/env ruby # frozen_string_literal: true -$LOAD_PATH.unshift("#{__dir__}/../lib") +lib_path = File.expand_path("#{__dir__}/../lib") +$LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include?(lib_path) -require "erb_lint/cli" +require "rainbow" -cli = ERBLint::CLI.new -exit(cli.run) +warn(Rainbow("Calling `erblint` is deprecated, please call the renamed executable `erb_lint` instead.").yellow) +exec(File.join(__dir__, "erb_lint"), *ARGV) diff --git a/lib/erb_lint/cli.rb b/lib/erb_lint/cli.rb index 3e6a6b04..34b847ae 100644 --- a/lib/erb_lint/cli.rb +++ b/lib/erb_lint/cli.rb @@ -13,7 +13,8 @@ module ERBLint class CLI include Utils::SeverityLevels - DEFAULT_CONFIG_FILENAME = ".erb-lint.yml" + DEPRECATED_CONFIG_FILENAME = ".erb-lint.yml" + DEFAULT_CONFIG_FILENAME = ".erb_lint.yml" DEFAULT_LINT_ALL_GLOB = "**/*.html{+*,}.erb" class ExitWithFailure < RuntimeError; end @@ -87,7 +88,7 @@ def run(args = ARGV) rescue => e @stats.exceptions += 1 puts "Exception occurred when processing: #{relative_filename(filename)}" - puts "If this file cannot be processed by erb-lint, " \ + puts "If this file cannot be processed by erb_lint, " \ "you can exclude it in your configuration file." puts e.message puts Rainbow(e.backtrace.join("\n")).red @@ -215,6 +216,13 @@ def load_config if File.exist?(config_filename) config = RunnerConfig.new(file_loader.yaml(config_filename), file_loader) @config = RunnerConfig.default_for(config) + elsif File.exist?(DEPRECATED_CONFIG_FILENAME) + deprecation_message = "The config file has been renamed to `#{DEFAULT_CONFIG_FILENAME}` and " \ + "`#{DEPRECATED_CONFIG_FILENAME}` is deprecated. " \ + "Please rename your config file to `#{DEFAULT_CONFIG_FILENAME}`." + warn(Rainbow(deprecation_message).yellow) + config = RunnerConfig.new(file_loader.yaml(DEPRECATED_CONFIG_FILENAME), file_loader) + @config = RunnerConfig.default_for(config) else warn(Rainbow("#{config_filename} not found: using default config").yellow) @config = RunnerConfig.default diff --git a/lib/erb_lint/linter_registry.rb b/lib/erb_lint/linter_registry.rb index 3fff5b72..74948358 100644 --- a/lib/erb_lint/linter_registry.rb +++ b/lib/erb_lint/linter_registry.rb @@ -3,7 +3,8 @@ module ERBLint # Stores all linters available to the application. module LinterRegistry - CUSTOM_LINTERS_DIR = ".erb-linters" + DEPRECATED_CUSTOM_LINTERS_DIR = ".erb-linters" + CUSTOM_LINTERS_DIR = ".erb_linters" @loaded_linters = [] class << self @@ -28,6 +29,15 @@ def linters def load_custom_linters(directory = CUSTOM_LINTERS_DIR) ruby_files = Dir.glob(File.expand_path(File.join(directory, "**", "*.rb"))) + + deprecated_ruby_files = Dir.glob(File.expand_path(File.join(DEPRECATED_CUSTOM_LINTERS_DIR, "**", "*.rb"))) + if deprecated_ruby_files.any? + deprecation_message = "The '#{DEPRECATED_CUSTOM_LINTERS_DIR}' directory for custom linters is deprecated. " \ + "Please rename it to '#{CUSTOM_LINTERS_DIR}'" + warn(Rainbow(deprecation_message).yellow) + ruby_files.concat(deprecated_ruby_files) + end + ruby_files.each { |file| require file } end end diff --git a/lib/erb_lint/runner_config_resolver.rb b/lib/erb_lint/runner_config_resolver.rb index 93748664..289a9d31 100644 --- a/lib/erb_lint/runner_config_resolver.rb +++ b/lib/erb_lint/runner_config_resolver.rb @@ -37,7 +37,7 @@ def resolve_inheritance(hash, file_loader) def resolve_inheritance_from_gems(hash, gems) (gems || {}).each_pair do |gem_name, config_path| - raise(ArgumentError, "can't inherit configuration from the erb-lint gem") if gem_name == "erb-lint" + raise(ArgumentError, "can't inherit configuration from the erb_lint gem") if gem_name == "erb_lint" hash["inherit_from"] = Array(hash["inherit_from"]) Array(config_path).reverse_each do |path| diff --git a/spec/erb_lint/cli_spec.rb b/spec/erb_lint/cli_spec.rb index 4d9c680c..40e1678a 100644 --- a/spec/erb_lint/cli_spec.rb +++ b/spec/erb_lint/cli_spec.rb @@ -103,6 +103,22 @@ def run(processed_source) end end + context "with deprecated config file" do + let(:deprecated_config_filename) { ".erb-lint.yml" } + let(:config_file_content) { "---\nEnableDefaultLinters: true\n" } + + before do + FileUtils.mkdir_p(File.dirname(deprecated_config_filename)) + File.write(deprecated_config_filename, config_file_content) + end + + it "shows a warning but loads the deprecated config file" do + expect { subject }.to(output(/`#{Regexp.escape(deprecated_config_filename)}` is deprecated/).to_stderr) + config = cli.instance_variable_get(:@config) + expect(config).to(be_an_instance_of(ERBLint::RunnerConfig)) + end + end + context "with --disable-inline-configs" do module ERBLint module Linters @@ -227,7 +243,7 @@ def run(processed_source) context "without --config" do context "when default config does not exist" do - it { expect { subject }.to(output(/\.erb-lint\.yml not found: using default config/).to_stderr) } + it { expect { subject }.to(output(/\.erb_lint\.yml not found: using default config/).to_stderr) } end end @@ -444,7 +460,7 @@ def run(processed_source) context "without --config" do context "when default config does not exist" do - it { expect { subject }.to(output(/\.erb-lint\.yml not found: using default config/).to_stderr) } + it { expect { subject }.to(output(/\.erb_lint\.yml not found: using default config/).to_stderr) } end end @@ -603,7 +619,7 @@ def run(processed_source) context "without --config" do context "when default config does not exist" do - it { expect { subject }.to(output(/\.erb-lint\.yml not found: using default config/).to_stderr) } + it { expect { subject }.to(output(/\.erb_lint\.yml not found: using default config/).to_stderr) } end end diff --git a/spec/erb_lint/linter_registry_spec.rb b/spec/erb_lint/linter_registry_spec.rb index d9f9b63a..5f5b7b10 100644 --- a/spec/erb_lint/linter_registry_spec.rb +++ b/spec/erb_lint/linter_registry_spec.rb @@ -31,5 +31,14 @@ class FakeLinter < ERBLint::Linter .with(File.join(custom_directory, "custom_linter.rb")).once) described_class.load_custom_linters(custom_directory) end + + it "warns when using the deprecated custom linters directory" do + stub_const("ERBLint::LinterRegistry::DEPRECATED_CUSTOM_LINTERS_DIR", custom_directory) + + expected_warning = Rainbow("The '#{custom_directory}' directory for custom linters is deprecated. " \ + "Please rename it to '.erb_linters'").yellow + expect(described_class).to(receive(:warn).with(expected_warning).once) + described_class.load_custom_linters + end end end diff --git a/spec/erb_lint/runner_config_spec.rb b/spec/erb_lint/runner_config_spec.rb index 91bba348..684f42b1 100644 --- a/spec/erb_lint/runner_config_spec.rb +++ b/spec/erb_lint/runner_config_spec.rb @@ -223,7 +223,7 @@ class MySchema < ERBLint::LinterConfig after { FileUtils.rm_rf(tmp_root) } it "inherits from a gem and loads the config" do - create_file("#{gem_root}/gemone/config/erb-lint.yml", <<-YAML.strip_indent) + create_file("#{gem_root}/gemone/config/erb_lint.yml", <<-YAML.strip_indent) MyCustomLinter: my_option: custom value YAML @@ -238,7 +238,7 @@ class MySchema < ERBLint::LinterConfig runner_config = described_class.new( { "inherit_gem" => { - "gemone" => "config/erb-lint.yml", + "gemone" => "config/erb_lint.yml", }, }, ERBLint::FileLoader.new(Dir.pwd), @@ -248,7 +248,7 @@ class MySchema < ERBLint::LinterConfig end it "inherits from a gem and merges the config" do - create_file("#{gem_root}/gemone/config/erb-lint.yml", <<-YAML.strip_indent) + create_file("#{gem_root}/gemone/config/erb_lint.yml", <<-YAML.strip_indent) MyCustomLinter1: a: value to be overwritten b: value for b @@ -264,7 +264,7 @@ class MySchema < ERBLint::LinterConfig runner_config = described_class.new( { "inherit_gem" => { - "gemone" => "config/erb-lint.yml", + "gemone" => "config/erb_lint.yml", }, "MyCustomLinter1" => { "a" => "value for a", @@ -332,7 +332,7 @@ class MySchema < ERBLint::LinterConfig end it "inherits from a gem if file load is not provided" do - create_file("#{gem_root}/gemone/config/erb-lint.yml", <<-YAML.strip_indent) + create_file("#{gem_root}/gemone/config/erb_lint.yml", <<-YAML.strip_indent) MyCustomLinter: my_option: custom value YAML @@ -346,7 +346,7 @@ class MySchema < ERBLint::LinterConfig runner_config = described_class.new( "inherit_gem" => { - "gemone" => "config/erb-lint.yml", + "gemone" => "config/erb_lint.yml", }, )