linter_changes 0.2.0 → 0.3.1

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: 9301ea21335f7cf36dcd50ec8006aca9c52e9a6df09a2560b7345b8807ae804f
4
- data.tar.gz: 2c860bef46256629516fbdb98cc2141bfe6f92cdbc41a0081a695ffbcf07cfa1
3
+ metadata.gz: 5724a288156947b1ab1eca983b1ad38fc831e4523a504f2f574fb59f15a3bf72
4
+ data.tar.gz: e5c14aeda697cd7e68dc0b8812c0289e6eb2396960ead4c5517599bd56fa94a9
5
5
  SHA512:
6
- metadata.gz: 19a5488a61b01ba6e917e70f63e9a9af87de3aecc7e8a8e13db493f2a0e3116c460371b2e10ba9d7ecc1f2c79d126b7abb4400940d0588eb9e068822a7f66f78
7
- data.tar.gz: 9d8ddf964fd9a130055bd754651de0eb72811456271b2051dd3b410099521d8eaf4cd84a66f6a8d60a62daf76ce3aa231e4206f7173be7ed6c978d186de794f8
6
+ metadata.gz: 1502bf26bea370f48f367107989f0cd51ccf0ee32df9f7df628808d126cd658f41d867c90cf951396ab93eb75a8e79ecac5da1de93f6962df51ea4ad16296c67
7
+ data.tar.gz: d1e82c6a623eddc5b784b95a61ebd379626473efb018bbf92ff0d9242228380bc50b3d78a25ba13d69f6638ac39161498d92ee9d089050930f673ec1d370c2ea
data/README.md CHANGED
@@ -4,7 +4,7 @@ LinterChanges is a Ruby gem that runs linters on files changed between your curr
4
4
 
5
5
  **What sets LinterChanges apart from other tools like Pronto is that it checks entire files rather than just the changed lines when raising errors. Additionally, if configuration changes for the linter occur, LinterChanges will run the linter on the entire repository, not just on the current changes.**
6
6
 
7
- Currently, **LinterChanges** supports **RuboCop** for Ruby code. Support for additional linters can be added in the future.
7
+ Currently, **LinterChanges** supports **RuboCop** for Ruby code and **ESLint** for JavaScript/Vue. Support for additional linters can be added in the future.
8
8
 
9
9
  ---
10
10
 
@@ -15,8 +15,8 @@ Currently, **LinterChanges** supports **RuboCop** for Ruby code. Support for add
15
15
  - [Installation](#installation)
16
16
  - [Usage](#usage)
17
17
  - [Basic Usage](#basic-usage)
18
- - [Specifying the Target Branch](#specifying-the-target-branch)
19
- - [Customizing RuboCop Configuration](#customizing-rubocop-configuration)
18
+ - [CLI](#cli)
19
+ - [Customizing Configuration](#customizing-configuration)
20
20
  - [Contributing](#contributing)
21
21
  - [Running the test suite](#running-the-test-suite)
22
22
  - [Acknowledgments](#acknowledgments)
@@ -34,75 +34,108 @@ gem 'linter_changes', git: 'https://github.com/bukhr/linter_changes.git'
34
34
  ```bash
35
35
  bundle install
36
36
  ```
37
+
37
38
  ---
38
39
 
39
40
  ## Usage
40
41
 
41
- LinterChanges provides a command-line interface (CLI) to run RuboCop on the files changed between your current branch and the target branch.
42
+ LinterChanges provides a command-line interface (CLI) to run Linters on the files changed between your current branch and the target branch. Also if you change a configuration file, it will run on the entire repository.
42
43
 
43
44
  ### Basic Usage
44
45
 
45
46
  By default, LinterChanges will:
46
47
 
47
- - Compare your current branch with the `main` branch.
48
- - Run the linters on the changed files that linter listen to.
48
+ - Run globally on all files applying all linters specify in configuration.
49
+ - The linters will only run on files that the linter listen to.
50
+ - Optionally, if you pass CHANGE_TARGET environment variable to run only on changed files between HEAD and the CHANGE_TARGET branch.
49
51
 
50
52
  **Command:**
51
53
 
52
54
  ```bash
53
- bin/linter_changes lint
55
+ CHANGE_TARGET=origin/master bin/linter_changes lint
54
56
  ```
55
57
 
56
58
  **Example Output:**
57
59
 
60
+ ```bash
61
+ ❯ CHANGE_TARGET=master ./Jenkins/test-scripts/rubocop.sh
62
+ [DEBUG] Using configuration file: .linter_changes.yml
63
+ [DEBUG] Running Rubocop linter
64
+ [DEBUG] Target branch: origin/master
65
+ [DEBUG] Executing command: git diff --name-only origin/master...HEAD
66
+ [DEBUG] Changed files: bin/linter_changes, packs/hcm/core/employee_management/app/models/employee.rb
67
+ [DEBUG] Executing command: bin/rubocop --list-target-files
68
+ [DEBUG] Linting files with [Rubocop]: packs/hcm/core/employee_management/app/models/employee.rb
69
+ [DEBUG] [Rubocop] Executing command: bin/rubocop --parallel --extra-details --display-style-guide --fail-level convention --display-only-fail-level-offenses --format clang packs/hcm/core/employee_management/app/models/employee.rb
70
+
71
+ 1 file inspected, no offenses detected
58
72
  ```
59
- Running RuboCop linter
60
- Linting files with RuboCop: app/models/user.rb, app/controllers/users_controller.rb
61
- Inspecting 2 files
62
- ..
63
-
64
- 2 files inspected, no offenses detected
65
- ```
66
-
67
- ### Specifying the Target Branch
68
73
 
69
- If you want to compare against a different branch, you can specify it using the `--target-branch` option.
74
+ ### CLI
70
75
 
71
- **Command:**
76
+ The `linter_changes` command provides several options to customize its behavior:
72
77
 
73
78
  ```bash
74
- bin/linter_changes lint --target-branch origin/master
79
+ bin/linter_changes lint [options]
75
80
  ```
76
81
 
77
- This will compare your current branch with the `origin/master` branch.
82
+ **Global Options:**
78
83
 
79
- ### Customizing RuboCop Configuration
84
+ - `--debug`: Enable debug mode to see detailed logging information
85
+ - `--force_global`: Force running linters on all files, ignoring git diff
80
86
 
81
- You can customize the RuboCop configuration files and command options.
87
+ **Lint Command Options:**
82
88
 
83
- **Specify Custom Config Files:**
89
+ - `--linters=rubocop,eslint,...`: Specify which linters to run (comma-separated list)
90
+ - `--config_file=PATH`: Path to the configuration file (default: `.linter_changes.yml`)
84
91
 
85
- Note: each config file passed is interpreted as regex on the full file path
92
+ **Environment Variables:**
86
93
 
87
- ```bash
88
- bin/linter_changes lint --config-files rubocop:rubocop,custom_rubocop.yml
89
- ```
94
+ - `CHANGE_TARGET`: Specify the target branch for comparison (e.g., `CHANGE_TARGET=origin/master`). If not specified, it will run on all files.
90
95
 
91
- **Specify Custom RuboCop Command:**
96
+ **Examples:**
92
97
 
93
98
  ```bash
94
- bin/linter_changes lint --linter-command rubocop:"rubocop --parallel"
95
- ```
99
+ # Run with debug logging
100
+ bin/linter_changes lint --debug
96
101
 
97
- **Combining Both:**
102
+ # Force global run regardless of changed files
103
+ bin/linter_changes lint --force_global
98
104
 
99
- ```bash
100
- bin/linter_changes lint \
101
- --config-files rubocop:.rubocop.yml,custom_rubocop.yml \
102
- --linter-command rubocop:"rubocop --parallel"
105
+ # Only run specific linters
106
+ bin/linter_changes lint --linters=rubocop,eslint
107
+
108
+ # Use a custom config file
109
+ bin/linter_changes lint --config_file=custom_linter_config.yml
110
+
111
+ # Combine options
112
+ CHANGE_TARGET=main bin/linter_changes lint --debug --linters=rubocop
103
113
  ```
104
114
 
115
+ ### Customizing Configuration
116
+
117
+ You can customize the configuration creating a file called `.linter_changes.yml` at the root of your proyect.
118
+
119
+ Example:
120
+
121
+ ```yml
105
122
  ---
123
+ rubocop:
124
+ linter_command: "bin/rubocop \
125
+ --parallel \
126
+ --extra-details \
127
+ --display-style-guide \
128
+ --fail-level convention \
129
+ --display-only-fail-level-offenses \
130
+ --format clang"
131
+ config_files:
132
+ - "rubocop"
133
+ ```
134
+
135
+ each key represents a linter (e.g., `rubocop`) and inside it you can specify the linter command and the files that trigger a global run of the linter.
136
+ In this example, if a file containing `rubocop` is edited, it would trigger a full run on all the proyect.
137
+
138
+ Also, if you want to specify another yml file, you can pass it though the cli with the option `--config_file`. The default is `.linter_changes.yml`.
106
139
 
107
140
  ## Contributing
108
141
 
@@ -154,4 +187,4 @@ bundle exec rake test
154
187
  - **Full File Linting:** Unlike tools like Pronto that only check the changed lines, LinterChanges lints the entire files that have been modified. This ensures that any issues in the modified files are caught, not just those in the changed lines.
155
188
  - **Configuration Change Detection:** If a configuration file for the linter (e.g., `.rubocop.yml`) has changed, LinterChanges will run the linter on the entire repository. This ensures that any new or altered linting rules are applied across all files.
156
189
 
157
- ---
190
+ ---
data/bin/linter_changes CHANGED
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require 'bundler/setup'
4
- require_relative '../lib/cli'
3
+ require_relative '../lib/linter_changes/cli'
5
4
 
6
5
  LinterChanges::CLI.start(ARGV)
data/bin/tapioca ADDED
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'tapioca' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
12
+
13
+ bundle_binstub = File.expand_path("bundle", __dir__)
14
+
15
+ if File.file?(bundle_binstub)
16
+ if File.read(bundle_binstub, 300).include?("This file was generated by Bundler")
17
+ load(bundle_binstub)
18
+ else
19
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
20
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
21
+ end
22
+ end
23
+
24
+ require "rubygems"
25
+ require "bundler/setup"
26
+
27
+ load Gem.bin_path("tapioca", "tapioca")
@@ -0,0 +1,75 @@
1
+ # typed: true
2
+
3
+ require 'bundler/setup'
4
+ require 'thor'
5
+ require 'sorbet-runtime'
6
+
7
+ require_relative 'git_diff'
8
+ require_relative 'logger'
9
+ require_relative 'linter/base'
10
+ require_relative 'linter/rubocop/adapter'
11
+ require_relative 'linter/eslint/adapter'
12
+ require_relative 'config'
13
+
14
+ module LinterChanges
15
+ class CLI < Thor
16
+ extend T::Sig
17
+
18
+ class_option :debug, type: :boolean, default: false, desc: 'Enable debug mode'
19
+ class_option :force_global, type: :boolean, default: false, desc: 'Run all linters on global configuration'
20
+
21
+ method_option :linters, type: :array, default: [],
22
+ desc: 'Specify linters to run (e.g., rubocop,eslint). If no option provided, will run everything at config file'
23
+ method_option :config_file, type: :string, default: LinterChanges::Config::USER_CONFIG_PATH,
24
+ desc: 'Path to the configuration file. Default: $(pwd)/.linter_changes.yml'
25
+ desc 'lint', 'Run linters on changed files'
26
+
27
+ sig { returns(T.noreturn) }
28
+ def lint
29
+ Logger.debug_mode = options[:debug]
30
+
31
+ Logger.debug "Using configuration file: #{options[:config_file]}"
32
+ @config = LinterChanges::Config.load(config_file: options[:config_file])
33
+ overall_success = T.let(true, T::Boolean)
34
+
35
+ select_linters.each do |linter|
36
+ Logger.debug "Running #{linter.name.capitalize} linter"
37
+ overall_success &&= linter.run
38
+ end
39
+
40
+ exit(overall_success ? 0 : 1)
41
+ end
42
+
43
+ sig { returns(T::Boolean) }
44
+ def self.exit_on_failure?
45
+ true
46
+ end
47
+
48
+ no_commands do
49
+ def select_linters
50
+ linter_names = @config.keys && options[:linters]
51
+ if linter_names.empty?
52
+ Logger.error 'No linters specified on configuration file.'
53
+ exit 1
54
+ end
55
+ linter_names.map do |name|
56
+ klass = AVAILABLE_LINTERS[name]
57
+ unless klass
58
+ Logger.error "Unknown linter specified: #{name}"
59
+ exit 1
60
+ end
61
+
62
+ # TODO: raise if no command found
63
+ config_files = @config[name]['config_files'] || []
64
+ command = @config[name]['linter_command']
65
+ klass.new(config_files:, command:, force_global: options[:force_global])
66
+ end
67
+ end
68
+ end
69
+
70
+ AVAILABLE_LINTERS = {
71
+ 'rubocop' => LinterChanges::Linter::RuboCop::Adapter,
72
+ 'eslint' => LinterChanges::Linter::Eslint::Adapter
73
+ }.freeze
74
+ end
75
+ end
@@ -0,0 +1,21 @@
1
+ # typed: true
2
+
3
+ require 'yaml'
4
+
5
+ module LinterChanges
6
+ class Config
7
+ extend T::Sig
8
+ USER_CONFIG_PATH = '.linter_changes.yml'
9
+
10
+ sig { params(config_file: String).returns(T::Hash[String, T.untyped]) }
11
+ def self.load config_file: USER_CONFIG_PATH
12
+ config_file = File.join(Dir.pwd, config_file)
13
+ user_config = File.exist?(config_file) ? YAML.load_file(config_file) : nil
14
+ unless user_config
15
+ raise StandardError.new 'No configuration file provided, you need the file .linter_changes.yml at the base of your proyect'
16
+ end
17
+
18
+ user_config
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,64 @@
1
+ # typed: true
2
+
3
+ require 'open3'
4
+
5
+ module LinterChanges
6
+ class GitDiff
7
+ extend T::Sig
8
+ sig { params(target_branch: String).void }
9
+ def initialize(target_branch:)
10
+ @target_branch = target_branch
11
+ end
12
+
13
+ sig { returns(T::Array[String]) }
14
+ def changed_files
15
+ return @changed_files if defined? @changed_files
16
+
17
+ Logger.debug "Target branch: #{@target_branch}"
18
+ cmd = "git diff --name-only #{@target_branch}...HEAD"
19
+ Logger.debug "Executing command: #{cmd}"
20
+
21
+ stdout, stderr, status = Open3.capture3(cmd)
22
+ unless status.success?
23
+ Logger.error "Error obtaining git changes: #{stderr}"
24
+ exit 1
25
+ end
26
+
27
+ @changed_files = stdout.strip.split("\n")
28
+ Logger.debug "Changed files: #{@changed_files.join(', ')}"
29
+ @changed_files
30
+ end
31
+
32
+ sig { params(file: String, pattern: String).returns(T::Boolean) }
33
+ def changed_lines_contains? file:, pattern:
34
+ cmd = "git diff #{@target_branch}...HEAD -- #{file}"
35
+ stdout, stderr, status = Open3.capture3(cmd)
36
+ unless status.success?
37
+ Logger.error "Error obtaining git diff for #{file}: #{stderr}"
38
+ exit 1
39
+ end
40
+ stdout.include? pattern
41
+ end
42
+
43
+ sig { params(ref: String).returns(T::Boolean) }
44
+ def reference_exists?(ref)
45
+ !!system("git rev-parse --verify #{ref} > /dev/null 2>&1")
46
+ end
47
+
48
+ sig { returns(T::Boolean) }
49
+ def references_exists?
50
+ return @references_exist if defined?(@references_exists)
51
+
52
+ @references_exist = if !reference_exists?(@target_branch)
53
+ Logger.debug("Reference for #{@target_branch} does not exists")
54
+ false
55
+ elsif !reference_exists?('HEAD')
56
+ Logger.debug('Reference for HEAD does not exists')
57
+ false
58
+ else
59
+ true
60
+ end
61
+ @references_exist
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,82 @@
1
+ # typed: true
2
+
3
+ module LinterChanges
4
+ module Linter
5
+ class Base
6
+ extend T::Sig
7
+ attr_reader :name
8
+
9
+ sig { params(config_files: T::Array[String], command: String, force_global: T::Boolean).void }
10
+ def initialize(config_files:, command:, force_global:)
11
+ @name = T.must(self.class.name).split('::')[-2].then do |adapter_name|
12
+ T.must(adapter_name).downcase
13
+ end
14
+ @config_files = config_files
15
+ @command = command
16
+ @base_command = @command.split(' ').first # used for listing files with in the adaptars
17
+ @target_branch = ENV['CHANGE_TARGET']
18
+ # We force origin if the target_branch is present
19
+ @target_branch = "origin/#{@target_branch}" if !@target_branch.nil? && !@target_branch['origin']
20
+ if @target_branch
21
+ @git_diff = GitDiff.new(target_branch: @target_branch)
22
+ end
23
+ @force_global = force_global || @target_branch.nil?
24
+ @force_global = true unless @git_diff&.references_exists?
25
+ end
26
+
27
+ sig { returns(T::Array[String]) }
28
+ def changed_files
29
+ @git_diff.changed_files
30
+ end
31
+
32
+ # Returns an array of files that the linter targets
33
+ sig { returns(T::Array[String]) }
34
+ def list_target_files
35
+ raise NotImplementedError, "#{self.class} must implement #list_target_files"
36
+ end
37
+
38
+ # Checks if any configuration files have changed
39
+ sig { returns(T::Boolean) }
40
+ def config_changed?
41
+ @config_files.any? do |pattern|
42
+ changed_files.any? { |file| file.match? Regexp.new(pattern) }
43
+ end
44
+ end
45
+
46
+ # Runs the linter on the specified files
47
+ sig { returns(T::Boolean) }
48
+ def run
49
+ if @force_global
50
+ if @target_branch.nil?
51
+ Logger.debug "[#{name.capitalize}] No git branch provided by CHANGE_TARGET enviroment variable, running globally."
52
+ elsif !@git_diff.references_exists?
53
+ Logger.debug "[#{name.capitalize}] Some git reference does not exists locally. Forced to run globally"
54
+ else
55
+ Logger.debug "[#{name.capitalize}] Forced to run globally."
56
+ end
57
+ execute_linter(@command)
58
+ elsif config_changed?
59
+ Logger.debug "[#{name.capitalize}] Configuration changed. Running linter globally."
60
+ execute_linter(@command)
61
+ else
62
+ files_to_lint = list_target_files & changed_files
63
+ if files_to_lint.empty?
64
+ Logger.debug "No files to lint for [#{name.capitalize}]."
65
+ true
66
+ else
67
+ Logger.debug "Linting files with [#{name.capitalize}]: #{files_to_lint.join(', ')}"
68
+ execute_linter("#{@command} #{files_to_lint.join(' ')}")
69
+ end
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ sig { params(command: String).returns(T::Boolean) }
76
+ def execute_linter(command)
77
+ Logger.debug "[#{name.capitalize}] Executing command: #{command}"
78
+ !!system(command)
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,23 @@
1
+ # typed: true
2
+
3
+ module LinterChanges
4
+ module Linter
5
+ module Eslint
6
+ class Adapter < Base
7
+ extend T::Sig
8
+
9
+ # ESLint no ofrece un equivalente a `bin/rubocop --list-target-files`,
10
+ # asi que derivamos los archivos objetivo filtrando los cambiados por
11
+ # extension. `Base#run` los intersecta con los archivos del diff, de
12
+ # modo que solo se linteara lo modificado. Los archivos en `.eslintignore`
13
+ # que pasen este filtro son descartados por el propio ESLint.
14
+ EXTENSIONS = T.let(%w[.js .jsx .ts .tsx .vue .mjs .cjs].freeze, T::Array[String])
15
+
16
+ sig { returns(T::Array[String]) }
17
+ def list_target_files
18
+ changed_files.select { |file| EXTENSIONS.include?(File.extname(file)) }
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -1,15 +1,14 @@
1
- # lib/linter_changes/linter/rubocop/adapter.rb
1
+ # typed: true
2
2
 
3
3
  module LinterChanges
4
4
  module Linter
5
5
  module RuboCop
6
6
  class Adapter < Base
7
- # By default, anything containing rubocop on the file path will be consider a config file
8
- DEFAULT_CONFIG_FILES = [/rubocop/].freeze
9
- DEFAULT_COMMAND = 'bin/rubocop'.freeze
7
+ extend T::Sig
10
8
 
9
+ sig { returns(T.any(T::Array[String], T.noreturn)) }
11
10
  def list_target_files
12
- cmd = "#{DEFAULT_COMMAND} --list-target-files"
11
+ cmd = "#{@base_command} --list-target-files"
13
12
  Logger.debug "Executing command: #{cmd}"
14
13
 
15
14
  stdout, stderr, status = Open3.capture3(cmd)
@@ -21,43 +20,20 @@ module LinterChanges
21
20
  stdout.strip.split("\n")
22
21
  end
23
22
 
24
- def run(files: [])
25
- cmd = if files.empty?
26
- @command
27
- else
28
- "#{@command} #{files.join(' ')}"
29
- end
30
- execute_linter(cmd)
31
- end
32
-
33
- def config_changed?(changed_files)
23
+ sig { returns(T::Boolean) }
24
+ def config_changed?
34
25
  # Check if any of the config files have changed
35
26
  return true if super
36
27
 
37
28
  # TODO: get the location of Gemfile.lock with bundle command
38
29
  # Check if Gemfile.lock has changed and contains something related to rubocop
39
30
  if changed_files.include?('Gemfile.lock') && @git_diff.changed_lines_contains?(file: 'Gemfile.lock',
40
- pattern: 'rubocop')
31
+ pattern: 'rubocop')
41
32
  Logger.debug 'Something related to rubocop gem version changed in Gemfile.lock'
42
33
  return true
43
34
  end
44
35
  false
45
36
  end
46
-
47
- private
48
-
49
- def default_config_files
50
- DEFAULT_CONFIG_FILES
51
- end
52
-
53
- def default_command
54
- DEFAULT_COMMAND
55
- end
56
-
57
- def execute_linter(command)
58
- Logger.debug "Executing RuboCop command: #{command}"
59
- system(command)
60
- end
61
37
  end
62
38
  end
63
39
  end
@@ -1,14 +1,17 @@
1
- # lib/your_linter_gem/logger.rb
1
+ # typed: true
2
2
 
3
3
  module LinterChanges
4
4
  class Logger
5
5
  class << self
6
+ extend T::Sig
6
7
  attr_accessor :debug_mode
7
8
 
9
+ sig { params(message: String).void }
8
10
  def debug(message)
9
11
  puts "[DEBUG] #{message}" if debug_mode
10
12
  end
11
13
 
14
+ sig { params(message: String).void }
12
15
  def error(message)
13
16
  puts "[ERROR] #{message}"
14
17
  end
@@ -1,5 +1,6 @@
1
+ # typed: true
1
2
  # frozen_string_literal: true
2
3
 
3
4
  module LinterChanges
4
- VERSION = '0.2.0'
5
+ VERSION = '0.3.1'
5
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: linter_changes
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jose Lara
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-11-20 00:00:00.000000000 Z
11
+ date: 2026-08-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -19,7 +19,7 @@ dependencies:
19
19
  version: '5.0'
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '8.0'
22
+ version: '9.0'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
@@ -29,21 +29,21 @@ dependencies:
29
29
  version: '5.0'
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '8.0'
32
+ version: '9.0'
33
33
  - !ruby/object:Gem::Dependency
34
- name: open3
34
+ name: sorbet-runtime
35
35
  requirement: !ruby/object:Gem::Requirement
36
36
  requirements:
37
- - - ">="
37
+ - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: '0'
39
+ version: '0.5'
40
40
  type: :runtime
41
41
  prerelease: false
42
42
  version_requirements: !ruby/object:Gem::Requirement
43
43
  requirements:
44
- - - ">="
44
+ - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: '0'
46
+ version: '0.5'
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: thor
49
49
  requirement: !ruby/object:Gem::Requirement
@@ -62,16 +62,22 @@ dependencies:
62
62
  name: activesupport
63
63
  requirement: !ruby/object:Gem::Requirement
64
64
  requirements:
65
- - - "~>"
65
+ - - ">="
66
66
  - !ruby/object:Gem::Version
67
67
  version: '7'
68
+ - - "<"
69
+ - !ruby/object:Gem::Version
70
+ version: '9.0'
68
71
  type: :development
69
72
  prerelease: false
70
73
  version_requirements: !ruby/object:Gem::Requirement
71
74
  requirements:
72
- - - "~>"
75
+ - - ">="
73
76
  - !ruby/object:Gem::Version
74
77
  version: '7'
78
+ - - "<"
79
+ - !ruby/object:Gem::Version
80
+ version: '9.0'
75
81
  - !ruby/object:Gem::Dependency
76
82
  name: minitest
77
83
  requirement: !ruby/object:Gem::Requirement
@@ -184,7 +190,35 @@ dependencies:
184
190
  - - "~>"
185
191
  - !ruby/object:Gem::Version
186
192
  version: '6.0'
187
- description:
193
+ - !ruby/object:Gem::Dependency
194
+ name: sorbet
195
+ requirement: !ruby/object:Gem::Requirement
196
+ requirements:
197
+ - - ">="
198
+ - !ruby/object:Gem::Version
199
+ version: '0'
200
+ type: :development
201
+ prerelease: false
202
+ version_requirements: !ruby/object:Gem::Requirement
203
+ requirements:
204
+ - - ">="
205
+ - !ruby/object:Gem::Version
206
+ version: '0'
207
+ - !ruby/object:Gem::Dependency
208
+ name: tapioca
209
+ requirement: !ruby/object:Gem::Requirement
210
+ requirements:
211
+ - - ">="
212
+ - !ruby/object:Gem::Version
213
+ version: '0'
214
+ type: :development
215
+ prerelease: false
216
+ version_requirements: !ruby/object:Gem::Requirement
217
+ requirements:
218
+ - - ">="
219
+ - !ruby/object:Gem::Version
220
+ version: '0'
221
+ description:
188
222
  email:
189
223
  - jvlara@uc.cl
190
224
  executables:
@@ -195,18 +229,20 @@ files:
195
229
  - README.md
196
230
  - bin/linter_changes
197
231
  - bin/setup
198
- - lib/cli.rb
199
- - lib/git_diff.rb
200
- - lib/linter/base.rb
201
- - lib/linter/rubocop/adapter.rb
202
- - lib/linter/rubocop/default_config.yml
203
- - lib/logger.rb
204
- - lib/version.rb
232
+ - bin/tapioca
233
+ - lib/linter_changes/cli.rb
234
+ - lib/linter_changes/config.rb
235
+ - lib/linter_changes/git_diff.rb
236
+ - lib/linter_changes/linter/base.rb
237
+ - lib/linter_changes/linter/eslint/adapter.rb
238
+ - lib/linter_changes/linter/rubocop/adapter.rb
239
+ - lib/linter_changes/logger.rb
240
+ - lib/linter_changes/version.rb
205
241
  homepage: https://github.com/bukhr/linter_changes
206
242
  licenses:
207
243
  - MIT
208
244
  metadata: {}
209
- post_install_message:
245
+ post_install_message:
210
246
  rdoc_options: []
211
247
  require_paths:
212
248
  - lib
@@ -224,8 +260,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
224
260
  - !ruby/object:Gem::Version
225
261
  version: '0'
226
262
  requirements: []
227
- rubygems_version: 3.5.15
228
- signing_key:
263
+ rubygems_version: 3.4.10
264
+ signing_key:
229
265
  specification_version: 4
230
266
  summary: Runs linters on code changes based on Git, either globally or only on modified
231
267
  files, depending on the changes.
data/lib/cli.rb DELETED
@@ -1,89 +0,0 @@
1
- # lib/linter_changes/cli.rb
2
-
3
- require 'bundler/setup'
4
- require 'thor'
5
- require_relative 'git_diff'
6
- require_relative 'logger'
7
- require_relative 'linter/base'
8
- require_relative 'linter/rubocop/adapter'
9
- require 'pry-byebug'
10
-
11
- module LinterChanges
12
- class CLI < Thor
13
- class_option :debug, type: :boolean, default: false, desc: 'Enable debug mode'
14
- class_option :target_branch, type: :string, default: nil, desc: 'Specify the target branch'
15
-
16
- desc 'lint', 'Run linters on changed files'
17
- method_option :linters, type: :array, default: [], desc: 'Specify linters to run (e.g., rubocop,eslint)'
18
- method_option :config_files, type: :hash, default: {},
19
- desc: 'Specify config files per linter (e.g., rubocop=.rubocop.yml)'
20
- method_option :linter_command, type: :hash, default: {},
21
- desc: 'Specify command per linter (e.g., rubocop="rubocop --parallel")'
22
- def lint
23
- Logger.debug_mode = options[:debug]
24
-
25
- git_diff = GitDiff.new(target_branch: options[:target_branch])
26
- changed_files = git_diff.changed_files
27
- linters = select_linters git_diff
28
- overall_success = true
29
-
30
- linters.each do |linter|
31
- Logger.debug "Running #{linter.name.capitalize} linter"
32
- if linter.config_changed?(changed_files)
33
- Logger.debug "#{linter.name.capitalize} configuration changed. Running linter globally."
34
- success = linter.run
35
- else
36
- files_to_lint = linter.list_target_files & changed_files
37
- if files_to_lint.empty?
38
- Logger.debug "No files to lint for #{linter.name.capitalize}."
39
- next
40
- else
41
- Logger.debug "Linting files with #{linter.name.capitalize}: #{files_to_lint.join(', ')}"
42
- success = linter.run(files: files_to_lint)
43
- end
44
- end
45
-
46
- overall_success &&= success
47
- end
48
-
49
- exit(overall_success ? 0 : 1)
50
- end
51
-
52
- def self.exit_on_failure?
53
- true
54
- end
55
-
56
- no_commands do
57
- def select_linters git_diff
58
- linter_names = if options[:linters].empty?
59
- AVAILABLE_LINTERS.keys
60
- else
61
- options[:linters]
62
- end
63
- linter_names.map do |name|
64
- klass = AVAILABLE_LINTERS[name]
65
- unless klass
66
- Logger.error "Unknown linter specified: #{name}"
67
- exit 1
68
- end
69
-
70
- # Pass custom config files and commands if provided
71
- config_files = parse_config_files_option(name)
72
- command = options[:linter_command][name]
73
- klass.new(config_files:, command:, git_diff:)
74
- end
75
- end
76
-
77
- def parse_config_files_option(linter_name)
78
- config_files_option = options[:config_files][linter_name]
79
- return nil unless config_files_option
80
-
81
- config_files_option.split(',')
82
- end
83
- end
84
-
85
- AVAILABLE_LINTERS = {
86
- 'rubocop' => LinterChanges::Linter::RuboCop::Adapter
87
- }.freeze
88
- end
89
- end
data/lib/git_diff.rb DELETED
@@ -1,39 +0,0 @@
1
- # lib/your_linter_gem/git_diff.rb
2
-
3
- require 'open3'
4
-
5
- module LinterChanges
6
- class GitDiff
7
- DEFAULT_TARGET_BRANCH = 'origin/master'
8
-
9
- def initialize(target_branch: nil)
10
- @target_branch = target_branch || ENV['CHANGE_TARGET'] || DEFAULT_TARGET_BRANCH
11
- Logger.debug "Target branch: #{@target_branch}"
12
- end
13
-
14
- def changed_files
15
- cmd = "git diff --name-only #{@target_branch}...HEAD"
16
- Logger.debug "Executing command: #{cmd}"
17
-
18
- stdout, stderr, status = Open3.capture3(cmd)
19
- unless status.success?
20
- Logger.error "Error obtaining git changes: #{stderr}"
21
- exit 1
22
- end
23
-
24
- files = stdout.strip.split("\n")
25
- Logger.debug "Changed files: #{files.join(', ')}"
26
- files
27
- end
28
-
29
- def changed_lines_contains? file:, pattern:
30
- cmd = "git diff #{@target_branch}...HEAD -- #{file}"
31
- stdout, stderr, status = Open3.capture3(cmd)
32
- unless status.success?
33
- Logger.error "Error obtaining git diff for #{file}: #{stderr}"
34
- exit 1
35
- end
36
- stdout.include? pattern
37
- end
38
- end
39
- end
data/lib/linter/base.rb DELETED
@@ -1,46 +0,0 @@
1
- # lib/linter_changes/linter/base.rb
2
-
3
- module LinterChanges
4
- module Linter
5
- class Base
6
- attr_reader :name
7
-
8
- def initialize(config_files: nil, command: nil, git_diff: nil)
9
- @name = self.class.name.split('::')[-2].downcase
10
- default_config_file = YAML.load_file("lib/linter/#{@name}/default_config.yml")
11
- @config_files = config_files || default_config_file['config_files']
12
- @command = command || default_config_file['command']
13
- @git_diff = git_diff
14
- end
15
-
16
- # Returns an array of files that the linter targets
17
- def list_target_files
18
- raise NotImplementedError, "#{self.class} must implement #list_target_files"
19
- end
20
-
21
- # Checks if any configuration files have changed
22
- def config_changed?(changed_files)
23
- changed = @config_files.any? do |pattern|
24
- changed_files.any? { |file| file.match? Regexp.new(pattern) }
25
- end
26
- Logger.debug "#{@name.capitalize} configuration changed: #{changed}"
27
- changed
28
- end
29
-
30
- # Runs the linter on the specified files
31
- def run(files: [])
32
- raise NotImplementedError, "#{self.class} must implement #run"
33
- end
34
-
35
- private
36
-
37
- def default_config_files
38
- raise NotImplementedError, "#{self.class} must implement #default_config_files"
39
- end
40
-
41
- def default_command
42
- raise NotImplementedError, "#{self.class} must implement #default_command"
43
- end
44
- end
45
- end
46
- end
@@ -1,5 +0,0 @@
1
- ---
2
- # Only regex supported
3
- config_files:
4
- - rubocop
5
- command: bin/rubocop