├── .github └── workflows │ └── test.yml ├── .gitignore ├── .standard.yml ├── CHANGELOG.md ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── lib ├── lint_roller.rb └── lint_roller │ ├── about.rb │ ├── context.rb │ ├── error.rb │ ├── plugin.rb │ ├── rules.rb │ ├── support │ └── merges_upstream_metadata.rb │ └── version.rb ├── lint_roller.gemspec └── test ├── lib ├── plugin_test.rb └── support │ └── merges_upstream_metadata_test.rb ├── lint_roller_test.rb └── test_helper.rb /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | workflow_run: 9 | workflows: ["Update"] 10 | types: 11 | - completed 12 | 13 | jobs: 14 | test: 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest] 18 | ruby-version: 19 | - "2.7" 20 | - "3.0" 21 | - "3.1" 22 | - "3.2" 23 | - "3.3" 24 | 25 | runs-on: ${{ matrix.os }} 26 | 27 | steps: 28 | - uses: actions/checkout@v4 29 | - name: Set up Ruby ${{ matrix.ruby-version }} 30 | uses: ruby/setup-ruby@v1 31 | with: 32 | ruby-version: ${{ matrix.ruby-version }} 33 | bundler-cache: true 34 | - name: Run tests for Ruby ${{ matrix.ruby-version }} on ${{ matrix.os }} 35 | run: bundle exec rake 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /_yardoc/ 4 | /coverage/ 5 | /doc/ 6 | /pkg/ 7 | /spec/reports/ 8 | /tmp/ 9 | -------------------------------------------------------------------------------- /.standard.yml: -------------------------------------------------------------------------------- 1 | # For available configuration options, see: 2 | # https://github.com/standardrb/standard 3 | ruby_version: 2.7 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [Unreleased] 2 | 3 | ## [1.1.0] 4 | 5 | - Add `LintRoller::Support` module of classes designed to make it a little 6 | easier to author plugins. `MergesUpstreamMetadata#merge` will allow a minimal 7 | YAML config (say, `standard-sorbet`'s, which only contains `Enabled` values for 8 | each rule) to merge in any other defaults from a source YAML (e.g. 9 | `rubocop-sorbet`'s which includes `Description`, `VersionAdded`, and so on). 10 | This way that metadata is neither absent at runtime nor duplicated in a standard 11 | plugin that mirrors a rubocop extension 12 | 13 | ## [1.0.0] 14 | 15 | - Initial release 16 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | # Specify your gem's dependencies in lint_roller.gemspec 4 | gemspec 5 | 6 | gem "m" 7 | gem "minitest" 8 | gem "rake" 9 | gem "simplecov" 10 | gem "standard" 11 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | lint_roller (1.1.0) 5 | 6 | GEM 7 | remote: https://rubygems.org/ 8 | specs: 9 | ast (2.4.2) 10 | docile (1.4.1) 11 | json (2.8.1) 12 | language_server-protocol (3.17.0.3) 13 | m (1.6.1) 14 | method_source (>= 0.6.7) 15 | rake (>= 0.9.2.2) 16 | method_source (1.0.0) 17 | minitest (5.18.0) 18 | parallel (1.26.3) 19 | parser (3.3.6.0) 20 | ast (~> 2.4.1) 21 | racc 22 | racc (1.8.1) 23 | rainbow (3.1.1) 24 | rake (13.0.6) 25 | regexp_parser (2.9.2) 26 | rexml (3.3.9) 27 | rubocop (1.64.1) 28 | json (~> 2.3) 29 | language_server-protocol (>= 3.17.0) 30 | parallel (~> 1.10) 31 | parser (>= 3.3.0.2) 32 | rainbow (>= 2.2.2, < 4.0) 33 | regexp_parser (>= 1.8, < 3.0) 34 | rexml (>= 3.2.5, < 4.0) 35 | rubocop-ast (>= 1.31.1, < 2.0) 36 | ruby-progressbar (~> 1.7) 37 | unicode-display_width (>= 2.4.0, < 3.0) 38 | rubocop-ast (1.34.1) 39 | parser (>= 3.3.1.0) 40 | rubocop-performance (1.21.1) 41 | rubocop (>= 1.48.1, < 2.0) 42 | rubocop-ast (>= 1.31.1, < 2.0) 43 | ruby-progressbar (1.13.0) 44 | simplecov (0.22.0) 45 | docile (~> 1.1) 46 | simplecov-html (~> 0.11) 47 | simplecov_json_formatter (~> 0.1) 48 | simplecov-html (0.13.1) 49 | simplecov_json_formatter (0.1.4) 50 | standard (1.37.0) 51 | language_server-protocol (~> 3.17.0.2) 52 | lint_roller (~> 1.0) 53 | rubocop (~> 1.64.0) 54 | standard-custom (~> 1.0.0) 55 | standard-performance (~> 1.4) 56 | standard-custom (1.0.2) 57 | lint_roller (~> 1.0) 58 | rubocop (~> 1.50) 59 | standard-performance (1.4.0) 60 | lint_roller (~> 1.1) 61 | rubocop-performance (~> 1.21.0) 62 | unicode-display_width (2.6.0) 63 | 64 | PLATFORMS 65 | arm64-darwin-22 66 | arm64-darwin-24 67 | x86_64-linux 68 | 69 | DEPENDENCIES 70 | lint_roller! 71 | m 72 | minitest 73 | rake 74 | simplecov 75 | standard 76 | 77 | BUNDLED WITH 78 | 2.4.22 79 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023 Test Double, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lint_roller - A plugin specification for linters 2 | 3 | `lint_roller` is an itty-bitty plugin API for code analysis tools like linters 4 | and formatters. It provides plugins for those tools to load extensions and 5 | specify custom rulesets. 6 | 7 | As of April 2025, both [Standard Ruby](https://github.com/standardrb/standard) and [RuboCop](https://github.com/rubocop/rubocop) rely on this gem for publishing plugins. And because `lint_roller` only specifies the basic lifecycle hooks a plugin system might need (as opposed to configuration formats or protocols), there's nothing preventing other tools like [rufo](https://github.com/ruby-formatter/rufo) or [brakeman](https://github.com/presidentbeef/brakeman) from adopting it. With broader support, a single gem could theoretically bundle separate configurations for many different analysis tools at a single version release (imagine a `seattle-style` gem, an official set of Sorbet recommendations, or an easy-to-distribute set of organization-wide styles). 8 | 9 | ## How to make a plugin 10 | 11 | If you want to make a plugin, the first thing you should do is extend 12 | [LintRoller::Plugin](/lib/lint_roller/plugin.rb) with a custom class. Let's take 13 | this example plugin for banana-related static analysis: 14 | 15 | ```ruby 16 | module BananaRoller 17 | class Plugin < LintRoller::Plugin 18 | # `config' is a Hash of options passed to the plugin by the user 19 | def initialize(config = {}) 20 | @alternative = config["alternative"] ||= "chocolate" 21 | end 22 | 23 | def about 24 | LintRoller::About.new( 25 | name: "banana_roller", 26 | version: "1.0", # or, in a gem, probably BananaRoller::VERSION 27 | homepage: "https://github.com/example/banana_roller", 28 | description: "Configuration of banana-related code" 29 | ) 30 | end 31 | 32 | # `context' is an instance of LintRoller::Context provided by the runner 33 | def supported?(context) 34 | context.engine == :rubocop 35 | end 36 | 37 | # `context' is an instance of LintRoller::Context provided by the runner 38 | def rules(context) 39 | LintRoller::Rules.new( 40 | type: :path, 41 | config_format: :rubocop, 42 | value: Pathname.new(__dir__).join("../../config/default.yml") 43 | ) 44 | end 45 | end 46 | end 47 | ``` 48 | 49 | And that's pretty much it. Just a declarative way to identify your plugin, 50 | detect whether it supports the given 51 | [LintRoller::Context](/lib/lint_roller/context.rb) (e.g. the current `runner` 52 | and `engine` and their respective `_version`s), for which the plugin will 53 | ultimately its configuration as a [LintRoller::Rules](/lib/lint_roller/rules.rb) 54 | object. 55 | 56 | ## Packaging a plugin in a gem 57 | 58 | In order for a formatter to use your plugin, it needs to know what path to 59 | require as well as the name of the plugin class to instantiate and invoke. 60 | 61 | To make this work seamlessly for your users without additional configuration of 62 | their own, all you need to do is specify a metadata attribute called 63 | `default_lint_roller_plugin` in your gemspec. 64 | 65 | Taking [standard-custom](https://github.com/standardrb/standard-custom) as an 66 | example, its gemspec contains: 67 | 68 | ```ruby 69 | Gem::Specification.new do |spec| 70 | # … 71 | spec.metadata["default_lint_roller_plugin"] = "Standard::Custom::Plugin" 72 | # … 73 | end 74 | ``` 75 | 76 | Because gem specs are loaded by RubyGems and Bundler very early, remember to 77 | specify the plugin as a string representation of the constant, as load order 78 | usually matters, and most tools will need to be loaded before any custom 79 | extensions. Hence, `"Standard::Custom::Plugin"` instead of 80 | `Standard::Custom::Plugin`. 81 | 82 | ## Using your plugin 83 | 84 | Once you've made your plugin, here's how it's configured from a Standard Ruby 85 | `.standard.yml` file. 86 | 87 | ### If your plugin is packaged as a gem 88 | 89 | Packaging your plugin in a gem is the golden path, both because distributing 90 | code via [RubyGems.org](https://rubygems.org) is very neat, but also because it 91 | makes the least work for your users. 92 | 93 | If your gem name is `banana_roller` and you've set 94 | `spec.metadata["default_lint_roller_plugin"]` to `"BananaRoller::Plugin"`, then 95 | your users could just add this to their `.standard.yml` file: 96 | 97 | ```yaml 98 | plugins: 99 | - banana_roller 100 | ``` 101 | 102 | And that's it! During initialization, `standardrb` will `require 103 | "banana_roller"` and know to call `BananaRoller::Plugin.new(config)` to 104 | instantiate it. 105 | 106 | ### If your plugin ISN'T in a gem 107 | 108 | If you're developing a plugin for internal use or in conjunction with a single 109 | project, you may want it to live in the same repo as opposed to packaging it in 110 | a gem. 111 | 112 | To do this, then—in lieu of a gem name—provide the path you want to be required 113 | as its name, and (since there is no `spec.metadata` to learn of your plugin's 114 | class name), specify it as an option on the plugin: 115 | 116 | ```yaml 117 | plugins: 118 | - lib/banana_roller/plugin: 119 | plugin_class_name: BananaRoller::Plugin 120 | ``` 121 | 122 | (Be careful with the indentation here! Any configuration under a plugin must be 123 | indented in order for it to be parsed as a hash under the 124 | `"lib/banana_roller/plugin"` key.) 125 | 126 | Additionally, if you want the plugin's name to make more sense, you can give 127 | it whatever name you like in the configuration and specify the `require_path` 128 | explicitly: 129 | 130 | ```yaml 131 | plugins: 132 | - banana_roller: 133 | require_path: lib/banana_roller/plugin 134 | plugin_class_name: BananaRoller::Plugin 135 | ``` 136 | 137 | ### Passing user configuration to the plugin 138 | 139 | When a `LintRoller::Plugin` is instantiated, users can pass a configuration hash 140 | that tells your plugin how to behave. 141 | 142 | To illustrate how this works in Standard Ruby, anything passed as a hash beneath 143 | a plugin will be passed to the class's `initialize` method: 144 | 145 | ```yaml 146 | plugins: 147 | - apple_roller 148 | - banana_roller: 149 | require_path: lib/banana_roller/plugin 150 | plugin_class_name: BananaRoller::Plugin 151 | alternative: "apples" 152 | - orange_roller: 153 | rind: false 154 | ``` 155 | 156 | In the above case, `apple_roller`'s plugin class will be instantiated with 157 | `new({})`, `banana_roller` will get all 3 of those parameters passed 158 | `BananaRoller::Plugin.new({require_path…})`, and `orange_roller`'s class will be 159 | called with `new({rind: false})`. 160 | 161 | ## Code of Conduct 162 | 163 | This project follows Test Double's [code of 164 | conduct](https://testdouble.com/code-of-conduct) for all community interactions, 165 | including (but not limited to) one-on-one communications, public posts/comments, 166 | code reviews, pull requests, and GitHub issues. If violations occur, Test Double 167 | will take any action they deem appropriate for the infraction, up to and 168 | including blocking a user from the organization's repositories. 169 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rake/testtask" 3 | 4 | Rake::TestTask.new(:test) do |t| 5 | t.libs << "test" 6 | t.libs << "lib" 7 | t.test_files = FileList["test/**/*_test.rb"] 8 | end 9 | 10 | require "standard/rake" 11 | 12 | task default: %i[test standard:fix] 13 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "lint_roller" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | require "irb" 10 | IRB.start(__FILE__) 11 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /lib/lint_roller.rb: -------------------------------------------------------------------------------- 1 | require_relative "lint_roller/version" 2 | 3 | require_relative "lint_roller/about" 4 | require_relative "lint_roller/context" 5 | require_relative "lint_roller/rules" 6 | 7 | require_relative "lint_roller/plugin" 8 | require_relative "lint_roller/error" 9 | 10 | require_relative "lint_roller/support/merges_upstream_metadata" 11 | 12 | module LintRoller 13 | end 14 | -------------------------------------------------------------------------------- /lib/lint_roller/about.rb: -------------------------------------------------------------------------------- 1 | module LintRoller 2 | About = Struct.new( 3 | :name, # "standard-performance" 4 | :version, # "1.2.3" 5 | :homepage, # "https://github.com/testdouble/standard-performance" 6 | :description, # "A configuration of rubocop-performance rules to make Ruby go faster" 7 | keyword_init: true 8 | ) 9 | end 10 | -------------------------------------------------------------------------------- /lib/lint_roller/context.rb: -------------------------------------------------------------------------------- 1 | module LintRoller 2 | Context = Struct.new( 3 | :runner, # :standard, :rubocop 4 | :runner_version, # "1.2.3" 5 | :engine, # :rubocop 6 | :engine_version, # "2.3.4", 7 | :rule_format, # :rubocop 8 | :target_ruby_version, # Gem::Version.new("2.7.0") 9 | keyword_init: true 10 | ) 11 | end 12 | -------------------------------------------------------------------------------- /lib/lint_roller/error.rb: -------------------------------------------------------------------------------- 1 | module LintRoller 2 | class Error < StandardError 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /lib/lint_roller/plugin.rb: -------------------------------------------------------------------------------- 1 | module LintRoller 2 | class Plugin 3 | # `config' is a Hash of options passed to the plugin by the user 4 | def initialize(config = {}) 5 | @config = config 6 | end 7 | 8 | def about 9 | raise Error.new("Please implement `about` and return an instance of LintRoller::About") 10 | end 11 | 12 | # `context' is an instance of LintRoller::Context provided by the runner 13 | def supported?(context) 14 | true 15 | end 16 | 17 | # `context' is an instance of LintRoller::Context provided by the runner 18 | def rules(context) 19 | raise Error.new("Please implement `rules(context)` and return an instance of LintRoller::Rules") 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/lint_roller/rules.rb: -------------------------------------------------------------------------------- 1 | module LintRoller 2 | Rules = Struct.new( 3 | # Valid values: :path, :object, :error 4 | :type, 5 | # Only known value right now is :rubocop but nothing would stop rufo or 6 | # rubyfmt or prettier from adding support without a change to the gem 7 | :config_format, 8 | # If type is :path, String or Pathname. Otherwise, whatever :object type 9 | # makes sense given :config_format (e.g. for RuboCop, a Hash loaded from a 10 | # YAML file; note that providing a hash will prevent the use of RuboCop features 11 | # like `inherit_from' and `require'!) 12 | :value, 13 | # If something went wrong an Error for the runner to deal with appropriately 14 | :error, 15 | keyword_init: true 16 | ) 17 | end 18 | -------------------------------------------------------------------------------- /lib/lint_roller/support/merges_upstream_metadata.rb: -------------------------------------------------------------------------------- 1 | module LintRoller 2 | module Support 3 | class MergesUpstreamMetadata 4 | def merge(plugin_yaml, upstream_yaml) 5 | common_upstream_values = upstream_yaml.select { |key| plugin_yaml.key?(key) } 6 | 7 | plugin_yaml.merge(common_upstream_values) { |key, plugin_value, upstream_value| 8 | if plugin_value.is_a?(Hash) && upstream_value.is_a?(Hash) 9 | plugin_value.merge(upstream_value) { |sub_key, plugin_sub_value, upstream_sub_value| 10 | if plugin_value.key?(sub_key) 11 | plugin_sub_value 12 | else 13 | upstream_sub_value 14 | end 15 | } 16 | else 17 | plugin_value 18 | end 19 | } 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/lint_roller/version.rb: -------------------------------------------------------------------------------- 1 | module LintRoller 2 | VERSION = "1.1.0" 3 | end 4 | -------------------------------------------------------------------------------- /lint_roller.gemspec: -------------------------------------------------------------------------------- 1 | require_relative "lib/lint_roller/version" 2 | 3 | Gem::Specification.new do |spec| 4 | spec.name = "lint_roller" 5 | spec.version = LintRoller::VERSION 6 | spec.authors = ["Justin Searls"] 7 | spec.email = ["searls@gmail.com"] 8 | 9 | spec.summary = "A plugin specification for linter and formatter rulesets" 10 | spec.homepage = "https://github.com/standardrb/lint_roller" 11 | spec.license = "MIT" 12 | spec.required_ruby_version = ">= 2.7.0" 13 | 14 | spec.metadata["homepage_uri"] = spec.homepage 15 | spec.metadata["source_code_uri"] = spec.homepage 16 | spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md" 17 | 18 | # Specify which files should be added to the gem when it is released. 19 | # The `git ls-files -z` loads the files in the RubyGem that have been added into git. 20 | spec.files = Dir.chdir(__dir__) do 21 | `git ls-files -z`.split("\x0").reject do |f| 22 | (File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor]) 23 | end 24 | end 25 | spec.bindir = "exe" 26 | spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) } 27 | spec.require_paths = ["lib"] 28 | end 29 | -------------------------------------------------------------------------------- /test/lib/plugin_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | module LintRoller 4 | class PluginTest < Minitest::Test 5 | def setup 6 | @subject = Plugin.new 7 | end 8 | 9 | def test_default_methods 10 | error = assert_raises(Error) { @subject.about } 11 | assert_equal "Please implement `about` and return an instance of LintRoller::About", error.message 12 | 13 | assert @subject.supported?(:some_context) 14 | 15 | error = assert_raises(Error) { @subject.rules(:some_context) } 16 | assert_equal "Please implement `rules(context)` and return an instance of LintRoller::Rules", error.message 17 | end 18 | 19 | class SampleRoller < Plugin 20 | ABOUT = About.new( 21 | name: "sample-roller", 22 | version: "1.2.3", 23 | homepage: "https://example.com", 24 | description: "A sample lint roller" 25 | ).freeze 26 | 27 | def about 28 | ABOUT 29 | end 30 | 31 | def supported?(context) 32 | [:standard, :rubocop].include?(context.runner) 33 | end 34 | 35 | def rules(context) 36 | if @config[:💥] == true 37 | Rules.new(error: Error.new("Unexpected Boom")) 38 | else 39 | Rules.new( 40 | type: :path, 41 | config_format: :rubocop, 42 | value: "/some/path/to/a/place" 43 | ) 44 | end 45 | end 46 | end 47 | 48 | def test_sample_roller 49 | sample_roller = SampleRoller.new(:some_config) 50 | 51 | assert_equal About.new( 52 | name: "sample-roller", 53 | version: "1.2.3", 54 | homepage: "https://example.com", 55 | description: "A sample lint roller" 56 | ), sample_roller.about 57 | 58 | assert sample_roller.supported?(Context.new(runner: :standard)) 59 | refute sample_roller.supported?(Context.new(runner: :rufo)) 60 | 61 | assert_equal Rules.new( 62 | type: :path, 63 | config_format: :rubocop, 64 | value: "/some/path/to/a/place" 65 | ), SampleRoller.new.rules(Context.new) 66 | assert_equal Rules.new( 67 | error: Error.new("Unexpected Boom") 68 | ), SampleRoller.new({:💥 => true}).rules(Context.new) 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /test/lib/support/merges_upstream_metadata_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | module LintRoller 4 | module Support 5 | class MergesUpstreamMetadataTest < Minitest::Test 6 | def setup 7 | @subject = MergesUpstreamMetadata.new 8 | end 9 | 10 | def test_no_op 11 | assert_equal({}, @subject.merge({}, {})) 12 | assert_equal({}, @subject.merge({}, {:junk => {}, "Some/Rule" => {}})) 13 | assert_equal({foo: "berry"}, @subject.merge({foo: "berry"}, {:junk => {}, "Some/Rule" => {}})) 14 | end 15 | 16 | def test_merges_undefined_sub_keys 17 | assert_equal( 18 | {"Some/Rule" => {"Enabled" => true, "Description" => "foo"}}, 19 | @subject.merge( 20 | {"Some/Rule" => {"Enabled" => true}}, 21 | {"Some/Rule" => {"Description" => "foo"}} 22 | ) 23 | ) 24 | end 25 | 26 | def test_doesnt_merge_nil_values 27 | assert_equal( 28 | {"Some/Rule" => {"Description" => nil}}, 29 | @subject.merge( 30 | {"Some/Rule" => {"Description" => nil}}, 31 | {"Some/Rule" => {"Description" => "foo"}} 32 | ) 33 | ) 34 | end 35 | 36 | def test_doesnt_override_defined_values 37 | assert_equal( 38 | {"Some/Rule" => {"Enabled" => false}}, 39 | @subject.merge( 40 | {"Some/Rule" => {"Enabled" => false}}, 41 | {"Some/Rule" => {"Enabled" => true}} 42 | ) 43 | ) 44 | end 45 | 46 | def test_doesnt_munge_arrays 47 | assert_equal( 48 | {"Some/Rule" => {"Included" => ["lol"]}}, 49 | @subject.merge( 50 | {"Some/Rule" => {"Included" => ["lol"]}}, 51 | {"Some/Rule" => {"Included" => ["kek"]}} 52 | ) 53 | ) 54 | end 55 | 56 | def test_doesnt_munge_hashes 57 | assert_equal( 58 | {"Some/Rule" => {"Conf" => {a: 1}}}, 59 | @subject.merge( 60 | {"Some/Rule" => {"Conf" => {a: 1}}}, 61 | {"Some/Rule" => {"Conf" => {a: 2, b: 3}}} 62 | ) 63 | ) 64 | end 65 | end 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /test/lint_roller_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class LintRollerTest < Minitest::Test 4 | def test_that_it_has_a_version_number 5 | refute_nil ::LintRoller::VERSION 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path("../lib", __dir__) 2 | begin 3 | require "simplecov" 4 | SimpleCov.start do 5 | load_profile "test_frameworks" 6 | end 7 | rescue LoadError 8 | end 9 | 10 | require "lint_roller" 11 | 12 | require "minitest/autorun" 13 | --------------------------------------------------------------------------------