Skip to content

Commit a97b8e9

Browse files
committed
Modernize with zeitwerk, rubocop-gusto, gemspec deps
1 parent 56e40d8 commit a97b8e9

File tree

9 files changed

+171
-90
lines changed

9 files changed

+171
-90
lines changed

.rubocop.yml

Lines changed: 52 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
inherit_gem:
2+
rubocop-gusto:
3+
- config/default.yml
4+
5+
plugins:
6+
- rubocop-gusto
7+
- rubocop-rspec
8+
- rubocop-performance
9+
- rubocop-rake
10+
111
# The behavior of RuboCop can be controlled via the .rubocop.yml
212
# configuration file. It makes it possible to enable/disable
313
# certain cops (checks) and to alter their behavior if they accept
@@ -14,66 +24,69 @@ AllCops:
1424
- vendor/bundle/**/**
1525
TargetRubyVersion: 2.6
1626

17-
Metrics/ParameterLists:
27+
Gemspec/RequireMFA:
1828
Enabled: false
1929

20-
# This cop is annoying with typed configuration
21-
Style/TrivialAccessors:
30+
# This leads to code that is not very readable at times (very long lines)
31+
Layout/ArgumentAlignment:
2232
Enabled: false
2333

24-
# This rubocop is annoying when we use interfaces a lot
25-
Lint/UnusedMethodArgument:
34+
# This leads to code that is not very readable at times (very long lines)
35+
Layout/FirstArgumentIndentation:
2636
Enabled: false
2737

28-
Gemspec/RequireMFA:
38+
Layout/LineLength:
2939
Enabled: false
3040

31-
Lint/DuplicateBranch:
41+
# This leads to code that is not very readable at times (very long lines)
42+
Layout/MultilineMethodCallIndentation:
3243
Enabled: false
3344

34-
# If is sometimes easier to think about than unless sometimes
35-
Style/NegatedIf:
45+
Lint/DuplicateBranch:
3646
Enabled: false
3747

38-
# Disabling for now until it's clearer why we want this
39-
Style/FrozenStringLiteralComment:
48+
# This rubocop is annoying when we use interfaces a lot
49+
Lint/UnusedMethodArgument:
4050
Enabled: false
4151

42-
# It's nice to be able to read the condition first before reading the code within the condition
43-
Style/GuardClause:
52+
Metrics/AbcSize:
4453
Enabled: false
4554

46-
#
47-
# Leaving length metrics to human judgment for now
48-
#
49-
Metrics/ModuleLength:
55+
Metrics/BlockLength:
5056
Enabled: false
5157

52-
Layout/LineLength:
58+
Metrics/ClassLength:
5359
Enabled: false
5460

55-
Metrics/BlockLength:
61+
# This doesn't feel useful
62+
Metrics/CyclomaticComplexity:
5663
Enabled: false
5764

5865
Metrics/MethodLength:
5966
Enabled: false
6067

61-
Metrics/AbcSize:
68+
#
69+
# Leaving length metrics to human judgment for now
70+
#
71+
Metrics/ModuleLength:
6272
Enabled: false
6373

64-
Metrics/ClassLength:
74+
Metrics/ParameterLists:
6575
Enabled: false
6676

6777
# This doesn't feel useful
68-
Metrics/CyclomaticComplexity:
78+
Metrics/PerceivedComplexity:
6979
Enabled: false
7080

71-
# This doesn't feel useful
72-
Metrics/PerceivedComplexity:
81+
# Sometimes we like methods like `get_packages`
82+
Naming/AccessorMethodName:
7383
Enabled: false
7484

75-
# It's nice to be able to read the condition first before reading the code within the condition
76-
Style/IfUnlessModifier:
85+
Style/AccessorGrouping:
86+
Enabled: false
87+
88+
# Blocks across lines are okay sometimes
89+
Style/BlockDelimiters:
7790
Enabled: false
7891

7992
# This leads to code that is not very readable at times (very long lines)
@@ -88,36 +101,29 @@ Style/Documentation:
88101
Style/EmptyElse:
89102
Enabled: false
90103

91-
# Sometimes we want to more explicitly list out a condition
92-
Style/RedundantCondition:
93-
Enabled: false
94-
95-
# This leads to code that is not very readable at times (very long lines)
96-
Layout/MultilineMethodCallIndentation:
104+
# Disabling for now until it's clearer why we want this
105+
Style/FrozenStringLiteralComment:
97106
Enabled: false
98107

99-
# Blocks across lines are okay sometimes
100-
Style/BlockDelimiters:
108+
# It's nice to be able to read the condition first before reading the code within the condition
109+
Style/GuardClause:
101110
Enabled: false
102111

103-
# Sometimes we like methods like `get_packages`
104-
Naming/AccessorMethodName:
112+
Style/HashSyntax:
105113
Enabled: false
106114

107-
# This leads to code that is not very readable at times (very long lines)
108-
Layout/FirstArgumentIndentation:
115+
# It's nice to be able to read the condition first before reading the code within the condition
116+
Style/IfUnlessModifier:
109117
Enabled: false
110118

111-
# This leads to code that is not very readable at times (very long lines)
112-
Layout/ArgumentAlignment:
119+
# If is sometimes easier to think about than unless sometimes
120+
Style/NegatedIf:
113121
Enabled: false
114122

115-
Style/AccessorGrouping:
123+
# Sometimes we want to more explicitly list out a condition
124+
Style/RedundantCondition:
116125
Enabled: false
117126

118-
Style/HashSyntax:
127+
# This cop is annoying with typed configuration
128+
Style/TrivialAccessors:
119129
Enabled: false
120-
121-
Gemspec/DevelopmentDependencies:
122-
Enabled: true
123-
EnforcedStyle: gemspec

.rubocop_todo.yml

Whitespace-only changes.

Gemfile

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1-
source 'https://rubygems.org'
1+
source "https://rubygems.org"
22

33
gemspec
4+
5+
gem "debug"
6+
gem "packwerk"
7+
gem "railties"
8+
gem "rake"
9+
gem "rspec"
10+
gem "rubocop"
11+
gem "rubocop-gusto", "~> 10.0"
12+
gem "sorbet"
13+
gem "tapioca"

bin/rubocop

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/usr/bin/env ruby
2+
# frozen_string_literal: true
3+
4+
#
5+
# This file was generated by Bundler.
6+
#
7+
# The application 'rubocop' 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+
require "rubygems"
14+
require "bundler/setup"
15+
16+
load Gem.bin_path("rubocop", "rubocop")

code_ownership.gemspec

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,5 @@ Gem::Specification.new do |spec|
3030
spec.add_dependency 'code_teams', '~> 1.0'
3131
spec.add_dependency 'packs-specification'
3232
spec.add_dependency 'sorbet-runtime', '>= 0.5.11249'
33-
34-
spec.add_development_dependency 'debug'
35-
spec.add_development_dependency 'packwerk'
36-
spec.add_development_dependency 'railties'
37-
spec.add_development_dependency 'rake'
38-
spec.add_development_dependency 'rspec', '~> 3.0'
39-
spec.add_development_dependency 'rubocop'
40-
spec.add_development_dependency 'sorbet'
41-
spec.add_development_dependency 'tapioca'
33+
spec.add_dependency 'zeitwerk'
4234
end

lib/code_ownership.rb

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,13 @@
77
require 'sorbet-runtime'
88
require 'json'
99
require 'packs-specification'
10-
require 'code_ownership/mapper'
11-
require 'code_ownership/validator'
12-
require 'code_ownership/private'
13-
require 'code_ownership/cli'
14-
require 'code_ownership/configuration'
10+
require 'zeitwerk'
11+
12+
loader = Zeitwerk::Loader.for_gem
13+
loader.setup
1514

1615
if defined?(Packwerk)
17-
require 'code_ownership/private/permit_pack_owner_top_level_key'
16+
require 'code_ownership/private/pack_ownership_validator'
1817
end
1918

2019
module CodeOwnership
@@ -97,10 +96,10 @@ def validate!(
9796
Private.load_configuration!
9897

9998
tracked_file_subset = if files
100-
files.select { |f| Private.file_tracked?(f) }
101-
else
102-
Private.tracked_files
103-
end
99+
files.select { |f| Private.file_tracked?(f) }
100+
else
101+
Private.tracked_files
102+
end
104103

105104
Private.validate!(files: tracked_file_subset, autocorrect: autocorrect, stage_changes: stage_changes)
106105
end
@@ -162,7 +161,7 @@ def backtrace_with_ownership(backtrace)
162161

163162
[
164163
CodeOwnership.for_file(file),
165-
file
164+
file,
166165
]
167166
end
168167
end

lib/code_ownership/private.rb

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,5 @@
1-
# frozen_string_literal: true
2-
31
# typed: strict
4-
5-
require 'code_ownership/private/extension_loader'
6-
require 'code_ownership/private/team_plugins/ownership'
7-
require 'code_ownership/private/team_plugins/github'
8-
require 'code_ownership/private/codeowners_file'
9-
require 'code_ownership/private/parse_js_packages'
10-
require 'code_ownership/private/glob_cache'
11-
require 'code_ownership/private/owner_assigner'
12-
require 'code_ownership/private/validations/files_have_owners'
13-
require 'code_ownership/private/validations/github_codeowners_up_to_date'
14-
require 'code_ownership/private/validations/files_have_unique_owners'
15-
require 'code_ownership/private/ownership_mappers/file_annotations'
16-
require 'code_ownership/private/ownership_mappers/team_globs'
17-
require 'code_ownership/private/ownership_mappers/directory_ownership'
18-
require 'code_ownership/private/ownership_mappers/package_ownership'
19-
require 'code_ownership/private/ownership_mappers/js_package_ownership'
20-
require 'code_ownership/private/ownership_mappers/team_yml_ownership'
2+
# frozen_string_literal: true
213

224
module CodeOwnership
235
module Private
@@ -117,10 +99,10 @@ def self.find_team!(team_name, location_of_reference)
11799
def self.glob_cache
118100
@glob_cache ||= T.let(@glob_cache, T.nilable(GlobCache))
119101
@glob_cache ||= if CodeownersFile.use_codeowners_cache?
120-
CodeownersFile.to_glob_cache
121-
else
122-
Mapper.to_glob_cache
123-
end
102+
CodeownersFile.to_glob_cache
103+
else
104+
Mapper.to_glob_cache
105+
end
124106
end
125107
end
126108

lib/code_ownership/private/permit_pack_owner_top_level_key.rb renamed to lib/code_ownership/private/pack_ownership_validator.rb

File renamed without changes.

spec/zeitwerk_spec.rb

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# typed: false
2+
# frozen_string_literal: true
3+
4+
# Zeitwerk Compliance Smoke Test
5+
#
6+
# This test serves as a Zeitwerk compliance smoke test that validates the gem's
7+
# file structure and naming conventions. It ensures that all files in the gem
8+
# follow Zeitwerk's strict naming conventions by forcing the autoloader to
9+
# eagerly load every constant and file in the gem.
10+
#
11+
# How it works:
12+
# 1. Eager Loading: Forces Zeitwerk to immediately load all files and constants
13+
# in the gem, rather than loading them on-demand
14+
# 2. Error Detection: If there are any naming convention violations, Zeitwerk
15+
# will raise an error during this process
16+
# 3. Validation: The test passes only if no errors are raised
17+
#
18+
# What it catches:
19+
# - Misnamed files (e.g., my_class.rb should define MyClass)
20+
# - Incorrect directory structure relative to module nesting
21+
# - Missing constants (files that exist but don't define expected constants)
22+
# - Extra or orphaned files that don't follow naming patterns
23+
# - Namespace violations (constants defined in wrong namespace for their location)
24+
25+
RSpec.describe "zeitwerk autoloader" do
26+
it "werks (successfully loads the gem)" do
27+
Zeitwerk::Loader.eager_load_namespace(CodeOwnership)
28+
rescue => error
29+
# Enhance the error message with more specific information
30+
enhanced_message = build_enhanced_error_message(error)
31+
raise enhanced_message
32+
end
33+
34+
private
35+
36+
def build_enhanced_error_message(error)
37+
message_parts = [
38+
"Zeitwerk eager loading failed with the following error:",
39+
"",
40+
"Original Error: #{error.class}: #{error.message}",
41+
"",
42+
]
43+
44+
# Add backtrace information to help identify the problematic file
45+
if error.backtrace
46+
gem_related_trace = error.backtrace.select do |line|
47+
line.include?('lib/') || line.include?('zeitwerk')
48+
end
49+
50+
if gem_related_trace.any?
51+
message_parts << "Relevant backtrace:"
52+
gem_related_trace.first(5).each do |line|
53+
message_parts << " #{line}"
54+
end
55+
message_parts << ""
56+
end
57+
end
58+
59+
# Try to identify which file might be causing the issue
60+
if /(?:wrong constant name|uninitialized constant|expected.*to define)/i.match?(error.message)
61+
message_parts << "This error typically indicates:"
62+
message_parts << "- A file name doesn't match its constant name"
63+
message_parts << "- A constant is defined in the wrong namespace"
64+
message_parts << "- A file exists but doesn't define the expected constant"
65+
message_parts << ""
66+
end
67+
68+
# Add helpful debugging information
69+
message_parts << "To debug this issue:"
70+
message_parts << "1. Check that all files in lib/ follow zeitwerk naming conventions"
71+
message_parts << "2. Ensure each file defines a constant matching its file path"
72+
message_parts << "3. Verify modules/classes are in the correct namespace"
73+
74+
message_parts.join("\n")
75+
end
76+
end

0 commit comments

Comments
 (0)