ordit 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 573d6f3d0536da9b396366f3c794b28b5c74161792ed2b107b9da4b4edc5c268
4
+ data.tar.gz: a806b40aaefc894a2cfea8caebc4f2c94f19ce87dcab0be01671bf9fd00f8c74
5
+ SHA512:
6
+ metadata.gz: 4c3a5d96f70d1423f748db50052b37767eec553b5b6577ba18866040cb25d78457835aacbc478ed6169bcf379dea64ed4f743426144ce7e3288a3e854f9097ce
7
+ data.tar.gz: 0e30d715b13e47c0dd7c3362ee5917f6b1dfd40e9bdbffe1b590352af65791fd53383ddf222ebf982e41ae28d90ca3852557ff83fa03e873f9d44a757d15e73b
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2024-12-12
4
+
5
+ - Initial release
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 TODO: Write your name
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.
data/README.md ADDED
@@ -0,0 +1,122 @@
1
+ # Ordit
2
+
3
+ A Ruby gem to analyze Stimulus.js controller usage in your Rails application. Find unused controllers, undefined controllers, and audit your Stimulus controller usage.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'ordit'
11
+ ```
12
+
13
+ And then execute:
14
+ ```bash
15
+ bundle install
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ### Audit All Controllers
21
+
22
+ Run an audit to see all defined and used controllers in your application:
23
+
24
+ ```bash
25
+ rails ordit:stimulus
26
+ ```
27
+
28
+ This will show:
29
+ - Controllers that are defined but never used
30
+ - Controllers that are used but don't have corresponding files
31
+ - Active controllers and where they're being used
32
+ - Summary statistics
33
+
34
+ Example output:
35
+ ```
36
+ 📊 Stimulus Controller Audit
37
+
38
+ ❌ Defined but unused controllers:
39
+ unused_feature
40
+ └─ app/javascript/controllers/unused_feature_controller.js
41
+
42
+ ⚠️ Used but undefined controllers:
43
+ missing_controller
44
+ └─ app/views/products/show.html.erb (lines: 15, 23)
45
+
46
+ ✅ Active controllers:
47
+ products
48
+ └─ Defined in: app/javascript/controllers/products_controller.js
49
+ └─ Used in:
50
+ └─ app/views/products/index.html.erb (lines: 10, 45)
51
+ └─ app/components/product_card/component.html.erb (lines: 3)
52
+ ```
53
+
54
+ ### Scan for Specific Controller Usage
55
+
56
+ Find all uses of a specific controller:
57
+
58
+ ```bash
59
+ rails audit:scan[controller_name]
60
+ ```
61
+
62
+ Example:
63
+ ```bash
64
+ rails audit:scan[products]
65
+ rails audit:scan[users--name] # For namespaced controllers
66
+ ```
67
+
68
+ ### Configuration
69
+
70
+ You can customize the paths that are scanned in an initializer (`config/initializers/ordit.rb`):
71
+
72
+ ```ruby
73
+ Ordit.configure do |config|
74
+ config.view_paths = [
75
+ Rails.root.join('app/views/**/*.{html,erb,haml}'),
76
+ Rails.root.join('app/javascript/**/*.{js,jsx}'),
77
+ Rails.root.join('app/components/**/*.{html,erb,haml,rb}')
78
+ ]
79
+
80
+ config.controller_paths = [
81
+ Rails.root.join('app/javascript/controllers/**/*.{js,ts}')
82
+ ]
83
+ end
84
+ ```
85
+
86
+ ## Features
87
+
88
+ - Finds unused Stimulus controllers
89
+ - Detects controllers used in views but missing controller files
90
+ - Supports namespaced controllers (e.g., `users--name`)
91
+ - Handles multiple syntax styles:
92
+ ```ruby
93
+ # HTML attribute syntax
94
+ <div data-controller="products">
95
+
96
+ # Ruby hash syntax
97
+ <%= f.submit 'Save', data: { controller: 'products' } %>
98
+
99
+ # Hash rocket syntax
100
+ <%= f.submit 'Save', data: { :controller => 'products' } %>
101
+ ```
102
+ - Scans ERB, HTML, and HAML files
103
+ - Works with both JavaScript and TypeScript controller files
104
+ - Supports component-based architectures
105
+
106
+ ## Development
107
+
108
+ After checking out the repo:
109
+
110
+ 1. Run `bundle install` to install dependencies
111
+ 2. Run `rake test` to run the tests
112
+ 3. Create a branch for your changes (`git checkout -b my-new-feature`)
113
+ 4. Make your changes and add tests
114
+ 5. Ensure tests pass
115
+
116
+ ## Contributing
117
+
118
+ Bug reports and pull requests are welcome on GitHub. This project is intended to be a safe, welcoming space for collaboration.
119
+
120
+ ## License
121
+
122
+ The gem is available as open source under the terms of the MIT License.
@@ -0,0 +1,175 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ordit
4
+ class Auditor
5
+ def initialize(config = Ordit.configuration)
6
+ @config = config
7
+ end
8
+
9
+ def audit
10
+ defined_controllers = find_defined_controllers
11
+ used_controllers = find_used_controllers
12
+
13
+ AuditResult.new(
14
+ defined_controllers: defined_controllers,
15
+ used_controllers: used_controllers,
16
+ controller_locations: find_controller_locations,
17
+ usage_locations: find_usage_locations
18
+ )
19
+ end
20
+
21
+ private
22
+
23
+ def find_defined_controllers
24
+ controllers = Set.new
25
+ @config.controller_paths.each do |path|
26
+ Dir.glob(path.to_s).each do |file|
27
+ # Extract relative path from controllers directory
28
+ full_path = Pathname.new(file)
29
+ controllers_dir = full_path.each_filename.find_index("controllers")
30
+
31
+ next unless controllers_dir
32
+
33
+ # Get path components after 'controllers'
34
+ controller_path = full_path.each_filename.to_a[(controllers_dir + 1)..]
35
+ # Remove _controller.js from the last component
36
+ controller_path[-1] = controller_path[-1].sub(/_controller\.(js|ts)$/, "")
37
+ # Join with -- for namespacing and convert underscores to hyphens
38
+ name = controller_path.join("--").gsub("_", "-")
39
+ controllers << name
40
+ end
41
+ end
42
+ controllers
43
+ end
44
+
45
+ def find_used_controllers
46
+ controllers = Set.new
47
+ patterns = [
48
+ /data-controller=["']([^"']+)["']/, # HTML attribute syntax
49
+ /data:\s*{\s*(?:controller:|:controller\s*=>)\s*["']([^"']+)["']/ # Both hash syntaxes
50
+ ]
51
+
52
+ @config.view_paths.each do |path|
53
+ Dir.glob(path.to_s).each do |file|
54
+ content = File.read(file)
55
+ patterns.each do |pattern|
56
+ content.scan(pattern) do |match|
57
+ # Split in case of multiple controllers
58
+ match[0].split(/\s+/).each do |controller|
59
+ # Store controller names exactly as they appear in the view
60
+ # (they should already have hyphens as per Stimulus conventions)
61
+ controllers << controller
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ controllers
68
+ end
69
+
70
+ def find_controller_locations
71
+ locations = {}
72
+ @config.controller_paths.each do |path_pattern|
73
+ Dir.glob(path_pattern).each do |file|
74
+ relative_path = Pathname.new(file).relative_path_from(Dir.pwd)
75
+ controller_path = relative_path.to_s.gsub(%r{^app/javascript/controllers/|_controller\.(js|ts)$}, "")
76
+ name = controller_path.gsub("/", "--")
77
+ locations[name] = relative_path
78
+ end
79
+ end
80
+ locations
81
+ end
82
+
83
+ def find_usage_locations
84
+ locations = Hash.new { |h, k| h[k] = {} }
85
+ patterns = [
86
+ /data-controller=["']([^"']+)["']/,
87
+ /data:\s*{(?:[^}]*\s)?controller:\s*["']([^"']+)["']/,
88
+ /data:\s*{(?:[^}]*\s)?controller\s*=>\s*["']([^"']+)["']/
89
+ ]
90
+
91
+ @config.view_paths.each do |path_pattern|
92
+ Dir.glob(path_pattern).each do |file|
93
+ File.readlines(file).each_with_index do |line, index|
94
+ patterns.each do |pattern|
95
+ line.scan(pattern) do |match|
96
+ match[0].split(/\s+/).each do |controller|
97
+ relative_path = Pathname.new(file).relative_path_from(Dir.pwd)
98
+ locations[controller][relative_path] ||= []
99
+ locations[controller][relative_path] << index + 1
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ locations
107
+ end
108
+ end
109
+
110
+ class AuditResult
111
+ attr_reader :defined_controllers, :used_controllers,
112
+ :controller_locations, :usage_locations
113
+
114
+ def initialize(defined_controllers:, used_controllers:,
115
+ controller_locations:, usage_locations:)
116
+ @defined_controllers = defined_controllers
117
+ @used_controllers = used_controllers
118
+ @controller_locations = controller_locations
119
+ @usage_locations = usage_locations
120
+ end
121
+
122
+ def unused_controllers
123
+ defined_controllers - used_controllers
124
+ end
125
+
126
+ def undefined_controllers
127
+ used_controllers - defined_controllers
128
+ end
129
+
130
+ def active_controllers
131
+ defined_controllers & used_controllers
132
+ end
133
+
134
+ def to_console
135
+ puts "\n📊 Stimulus Controller Audit\n"
136
+
137
+ if unused_controllers.any?
138
+ puts "\n❌ Defined but unused controllers:"
139
+ unused_controllers.sort.each do |controller|
140
+ puts " #{controller}"
141
+ puts " └─ #{controller_locations[controller]}"
142
+ end
143
+ end
144
+
145
+ if undefined_controllers.any?
146
+ puts "\n⚠️ Used but undefined controllers:"
147
+ undefined_controllers.sort.each do |controller|
148
+ puts " #{controller}"
149
+ usage_locations[controller].each do |file, lines|
150
+ puts " └─ #{file} (lines: #{lines.join(", ")})"
151
+ end
152
+ end
153
+ end
154
+
155
+ if active_controllers.any?
156
+ puts "\n✅ Active controllers:"
157
+ active_controllers.sort.each do |controller|
158
+ puts " #{controller}"
159
+ puts " └─ Defined in: #{controller_locations[controller]}"
160
+ puts " └─ Used in:"
161
+ usage_locations[controller].each do |file, lines|
162
+ puts " └─ #{file} (lines: #{lines.join(", ")})"
163
+ end
164
+ end
165
+ end
166
+
167
+ puts "\n📈 Summary:"
168
+ puts " Total controllers defined: #{defined_controllers.size}"
169
+ puts " Total controllers in use: #{used_controllers.size}"
170
+ puts " Unused controllers: #{unused_controllers.size}"
171
+ puts " Undefined controllers: #{undefined_controllers.size}"
172
+ puts " Properly paired: #{active_controllers.size}"
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ordit
4
+ class Configuration
5
+ attr_accessor :view_paths, :controller_paths
6
+
7
+ def initialize
8
+ reset
9
+ end
10
+
11
+ def reset
12
+ base_path = Ordit.root
13
+
14
+ @view_paths = [
15
+ base_path.join("app/views/**/*.{html,erb,haml}").to_s,
16
+ base_path.join("app/javascript/**/*.{js,jsx}").to_s,
17
+ base_path.join("app/components/**/*.{html,erb,haml,rb}").to_s
18
+ ]
19
+
20
+ @controller_paths = [
21
+ base_path.join("app/javascript/controllers/**/*.{js,ts}").to_s
22
+ ]
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ordit
4
+ class Railtie < Rails::Railtie
5
+ rake_tasks do
6
+ load "tasks/ordit.rake"
7
+ end
8
+
9
+ initializer "ordit.setup" do
10
+ require "ordit"
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ordit
4
+ class Scanner
5
+ def initialize(config = Ordit.configuration)
6
+ @config = config
7
+ end
8
+
9
+ def scan(controller)
10
+ matches = find_matches(controller)
11
+ print_results(controller, matches)
12
+ end
13
+
14
+ private
15
+
16
+ def find_matches(controller)
17
+ matches = []
18
+ patterns = [
19
+ /data-controller=["'](?:[^"']*\s)?#{Regexp.escape(controller)}(?:\s[^"']*)?["']/, # HTML attribute
20
+ /data:\s*{\s*(?:controller:|:controller\s*=>)\s*["'](?:[^"']*\s)?#{Regexp.escape(controller)}(?:\s[^"']*)?["']/ # Both hash syntaxes
21
+ ]
22
+
23
+ @config.view_paths.each do |path|
24
+ Dir.glob(path.to_s).each do |file|
25
+ content = File.readlines(file)
26
+ content.each_with_index do |line, index|
27
+ next unless patterns.any? { |pattern| line.match?(pattern) }
28
+
29
+ matches << {
30
+ file: Pathname.new(file).relative_path_from(Pathname.new(Dir.pwd)),
31
+ line_number: index + 1,
32
+ content: line.strip
33
+ }
34
+ end
35
+ end
36
+ end
37
+ matches
38
+ end
39
+
40
+ def print_results(controller, matches)
41
+ puts "\nSearching for stimulus controller: '#{controller}'\n\n"
42
+
43
+ if matches.empty?
44
+ puts "No matches found."
45
+ return
46
+ end
47
+
48
+ current_file = nil
49
+ matches.each do |match|
50
+ if current_file != match[:file]
51
+ puts "📁 #{match[:file]}"
52
+ current_file = match[:file]
53
+ end
54
+ puts " Line #{match[:line_number]}:"
55
+ puts " #{match[:content]}\n\n"
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Ordit
4
+ VERSION = "0.1.0"
5
+ end
data/lib/ordit.rb ADDED
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+ require "pathname"
5
+ require_relative "ordit/version"
6
+ require_relative "ordit/configuration"
7
+ require_relative "ordit/auditor"
8
+ require_relative "ordit/scanner"
9
+
10
+ if defined?(Rails)
11
+ require "rails"
12
+ require 'ordit'
13
+ require "ordit/railtie"
14
+ end
15
+
16
+ module Ordit
17
+ class Error < StandardError; end
18
+
19
+ class << self
20
+ def configuration
21
+ @configuration ||= Configuration.new
22
+ end
23
+
24
+ def configure
25
+ yield(configuration)
26
+ end
27
+
28
+ def reset_configuration!
29
+ @configuration = Configuration.new
30
+ end
31
+
32
+ def root
33
+ if defined?(Rails)
34
+ Rails.root
35
+ else
36
+ Pathname.new(Dir.pwd)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :audit do
4
+ desc "Audit Stimulus controllers usage and find orphaned controllers"
5
+ task stimulus: :environment do
6
+ StimulusAudit::Auditor.new.audit.to_console
7
+ end
8
+
9
+ desc "Scan files for stimulus controller usage (e.g., rake audit:scan[products])"
10
+ task :scan, [:controller] => :environment do |_, args|
11
+ controller = args[:controller]
12
+
13
+ if controller.nil? || controller.empty?
14
+ puts "Please provide a controller name: rake audit:scan[controller_name]"
15
+ next
16
+ end
17
+
18
+ StimulusAudit::Scanner.new.scan(controller)
19
+ end
20
+ end
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ordit
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Toby
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-12-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '13.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '13.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '5.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '5.0'
41
+ description: A Ruby gem to analyze usage of Stimulus controllers, finding unused controllers
42
+ and undefined controllers
43
+ email:
44
+ - toby@darkroom.tech
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - CHANGELOG.md
50
+ - LICENSE.txt
51
+ - README.md
52
+ - lib/ordit.rb
53
+ - lib/ordit/auditor.rb
54
+ - lib/ordit/configuration.rb
55
+ - lib/ordit/railtie.rb
56
+ - lib/ordit/scanner.rb
57
+ - lib/ordit/version.rb
58
+ - lib/tasks/ordit.rake
59
+ homepage: https://github.com/tobyond/ordit
60
+ licenses:
61
+ - MIT
62
+ metadata:
63
+ homepage_uri: https://github.com/tobyond/ordit
64
+ changelog_uri: https://github.com/tobyond/ordit/blob/main/CHANGELOG.md
65
+ source_code_uri: https://github.com/tobyond/ordit
66
+ bug_tracker_uri: https://github.com/tobyond/ordit/issues
67
+ post_install_message:
68
+ rdoc_options: []
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 3.3.0
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ requirements: []
82
+ rubygems_version: 3.5.3
83
+ signing_key:
84
+ specification_version: 4
85
+ summary: Audit Stimulus.js controllers in your Rails application
86
+ test_files: []