even_better_specs 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1654cb014ec4bfbe5ac31e7f75259ff4f161041cd1fb15e2658a279b4ceb1f01
4
- data.tar.gz: f5f45ee95234cf0631612cb9a4f48946fff2df2415fcd80aceb5b295b619dba3
3
+ metadata.gz: d351b776d1b0ae7a8ade491c02fc817eb10fa9609bced214c22f3eadbf98b44a
4
+ data.tar.gz: 23429a0a67b2ff0213e79d951821c98132d8d53dfe8467b22a44036215c61451
5
5
  SHA512:
6
- metadata.gz: 672498e77b41332d9f1d8510505b5edde0bfb227cd5168fc858beaa21a0d506d2e6dd536d06fce90d24c2399e52fed993ac84e6a46250a7e5d7894878e9887c8
7
- data.tar.gz: 6c38c8e78159a86f6358dae83faacd41fe246c70104ba0700b7d2a5b9ccfd3db36cbd90acf1c4ca636e5f86a43ec4f4ab447f1c69d0f23dace9873a938adc8b4
6
+ metadata.gz: fcb236582d722386c56855e48e7300c51fdf4c513ab6b19d1dd5275add4f11740587e77c01ad1f6282396dbd659728340709d62a76314bcc48877814391f3def
7
+ data.tar.gz: a87a96aa03ffcf87e0fe6426e5b750db360140f33f0b77b1b9247e53128f7b61956c101d32b4401cffb6dbc8a48a5fa7000508781187d25d7e94c2540d3583ba
data/CHANGELOG.md CHANGED
@@ -6,6 +6,12 @@ and this project follows [Semantic Versioning](https://semver.org/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.2.0]
10
+
11
+ ### Added
12
+
13
+ - `--diff[=BASE]` option: report only offenses on lines changed since a git revision (default `HEAD`).
14
+
9
15
  ## [0.1.0] - 2026-10-30
10
16
 
11
17
  ### Added
data/README.md CHANGED
@@ -12,11 +12,19 @@ gem install even_better_specs
12
12
  even_better_specs # checks every *_spec.rb
13
13
  even_better_specs spec/models
14
14
  even_better_specs -a # autocorrect what can be autocorrected
15
+ even_better_specs --diff # only lines changed since HEAD, plus untracked specs
16
+ even_better_specs --diff=develop # only lines changed since develop (e.g. a whole branch)
15
17
  ```
16
18
 
17
19
  Do not use `bundle exec`, because the gem is not in the Gemfile. The CLI accepts all `rubocop` options.
18
20
  It runs with its own config only, so the project's `.rubocop.yml` (if any) is ignored.
19
21
 
22
+ `--diff[=BASE]` reports only offenses that start on a line added or changed since `BASE` (default `HEAD`),
23
+ so you are judged on what you wrote, not on the file's legacy. Without paths it inspects the changed spec
24
+ files; with paths it inspects those and still filters by the diff. The exit code counts only the reported
25
+ offenses. An offense that starts on an unchanged line (e.g. an existing `describe` around your new code) is
26
+ not reported. It cannot be combined with autocorrect, which would rewrite lines outside the diff.
27
+
20
28
  ## Rules
21
29
 
22
30
  | Guideline | Cop |
@@ -3,5 +3,4 @@
3
3
 
4
4
  require "even_better_specs"
5
5
 
6
- # --config makes RuboCop ignore the project's own .rubocop.yml (if any).
7
- exit RuboCop::CLI.new.run(["--config", EvenBetterSpecs::CONFIG, *ARGV])
6
+ exit EvenBetterSpecs::CLI.run(ARGV)
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pathname"
4
+
5
+ module EvenBetterSpecs
6
+ # Runs RuboCop with the gem's config, plus the `--diff[=BASE]` option.
7
+ module CLI
8
+ AUTOCORRECT_FLAGS = %w[-a -A -x --autocorrect --autocorrect-all --fix-layout].freeze
9
+
10
+ def self.run(argv)
11
+ args = argv.dup
12
+ flag = args.find { |arg| arg == "--diff" || arg.start_with?("--diff=") }
13
+ return rubocop(args) unless flag
14
+
15
+ args.delete(flag)
16
+ if args.intersect?(AUTOCORRECT_FLAGS)
17
+ warn "--diff cannot be combined with autocorrect: it would rewrite lines outside the diff."
18
+ return 2
19
+ end
20
+
21
+ base = flag.include?("=") ? flag.split("=", 2).last : "HEAD"
22
+ Diff.changed_lines = Diff.compute(base)
23
+ if Diff.changed_lines.empty?
24
+ warn "No changes since #{base}."
25
+ return 0
26
+ end
27
+
28
+ # Without explicit paths, inspect only the changed files that the config's Include matches.
29
+ if args.none? { |arg| File.exist?(arg) }
30
+ cwd = Pathname.new(File.realpath(Dir.pwd))
31
+ files = Diff.changed_lines.keys.map { |file| Pathname.new(file).relative_path_from(cwd).to_s }
32
+ args += ["--only-recognized-file-types", *files]
33
+ end
34
+ rubocop(args)
35
+ rescue ArgumentError => e
36
+ warn e.message
37
+ 2
38
+ ensure
39
+ Diff.changed_lines = nil
40
+ end
41
+
42
+ def self.rubocop(args)
43
+ # --config makes RuboCop ignore the project's own .rubocop.yml (if any).
44
+ RuboCop::CLI.new.run(["--config", CONFIG, *args])
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+
5
+ module EvenBetterSpecs
6
+ # Limits a run to the lines added or changed since a git revision (`--diff`).
7
+ module Diff
8
+ HUNK = /\A@@ -\S+ \+(\d+)(?:,(\d+))? @@/
9
+ WHOLE_FILE = [1..].freeze
10
+
11
+ class << self
12
+ # { absolute path => [line ranges] } while a `--diff` run is active, nil otherwise.
13
+ attr_accessor :changed_lines
14
+
15
+ # Changed line ranges per file since `base`. Untracked files count as changed in full,
16
+ # like RuboCop's `--changed`.
17
+ def compute(base)
18
+ root = git("rev-parse", "--show-toplevel").chomp
19
+ lines = parse(git("diff", "-U0", "--no-color", "--no-ext-diff", "--src-prefix=a/", "--dst-prefix=b/", base), root)
20
+ git("ls-files", "--others", "--exclude-standard", "--full-name", chdir: root).each_line do |path|
21
+ lines[File.join(root, path.chomp)] = WHOLE_FILE
22
+ end
23
+ lines
24
+ end
25
+
26
+ def parse(diff, root)
27
+ file = nil
28
+ diff.each_line.with_object({}) do |line, lines|
29
+ if line.start_with?("+++ ")
30
+ path = line.chomp.chomp("\t").delete_prefix("+++ ")
31
+ file = path == "/dev/null" ? nil : File.join(root, path.delete_prefix("b/"))
32
+ elsif file && (hunk = HUNK.match(line))
33
+ start = hunk[1].to_i
34
+ count = (hunk[2] || 1).to_i
35
+ (lines[file] ||= []) << (start...start + count) if count.positive?
36
+ end
37
+ end
38
+ end
39
+
40
+ def changed?(file, line)
41
+ # git reports real paths; RuboCop may hold a symlinked one (e.g. /tmp on macOS).
42
+ changed_lines.fetch(File.realpath(file), []).any? { |range| range.cover?(line) }
43
+ end
44
+
45
+ private
46
+
47
+ def git(*args, chdir: Dir.pwd)
48
+ output, error, status = Open3.capture3("git", *args, chdir: chdir)
49
+ raise ArgumentError, "git #{args.first} failed: #{error.strip}" unless status.success?
50
+
51
+ output
52
+ end
53
+ end
54
+
55
+ # Drops offenses outside the changed lines. Hooked after RuboCop's result cache, so the cache
56
+ # keeps full results, and before reporting, so every formatter and the exit code agree.
57
+ module RunnerFilter
58
+ private
59
+
60
+ def process_file(file)
61
+ offenses = super
62
+ return offenses unless Diff.changed_lines
63
+
64
+ offenses.select { |offense| Diff.changed?(file, offense.line) }.freeze
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ RuboCop::Runner.prepend(EvenBetterSpecs::Diff::RunnerFilter)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EvenBetterSpecs
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -3,6 +3,8 @@
3
3
  require "rubocop"
4
4
  require "rubocop-rspec"
5
5
  require_relative "even_better_specs/version"
6
+ require_relative "even_better_specs/diff"
7
+ require_relative "even_better_specs/cli"
6
8
 
7
9
  module EvenBetterSpecs
8
10
  CONFIG = File.expand_path("../config/default.yml", __dir__)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: even_better_specs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Glauco Custodio
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-10-30 00:00:00.000000000 Z
11
+ date: 2026-11-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubocop
@@ -52,6 +52,8 @@ files:
52
52
  - config/default.yml
53
53
  - exe/even_better_specs
54
54
  - lib/even_better_specs.rb
55
+ - lib/even_better_specs/cli.rb
56
+ - lib/even_better_specs/diff.rb
55
57
  - lib/even_better_specs/version.rb
56
58
  - lib/rubocop/cop/even_better_specs/all_possible_cases.rb
57
59
  - lib/rubocop/cop/even_better_specs/create_only_needed_data.rb