docquet 1.0.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 +7 -0
- data/CHANGELOG.md +20 -0
- data/LICENSE +21 -0
- data/README.md +205 -0
- data/config/cops/bundler.yml +19 -0
- data/config/cops/capybara.yml +1 -0
- data/config/cops/capybara_rspec.yml +1 -0
- data/config/cops/gemspec.yml +4 -0
- data/config/cops/i18n_gettext.yml +2 -0
- data/config/cops/i18n_railsi18n.yml +1 -0
- data/config/cops/layout.yml +79 -0
- data/config/cops/lint.yml +7 -0
- data/config/cops/metrics.yml +14 -0
- data/config/cops/migration.yml +2 -0
- data/config/cops/naming.yml +4 -0
- data/config/cops/performance.yml +7 -0
- data/config/cops/rake.yml +5 -0
- data/config/cops/rspec.yml +41 -0
- data/config/cops/security.yml +1 -0
- data/config/cops/sequel.yml +2 -0
- data/config/cops/style.yml +153 -0
- data/config/cops/thread_safety.yml +5 -0
- data/config/defaults/bundler.yml +97 -0
- data/config/defaults/capybara.yml +103 -0
- data/config/defaults/capybara_rspec.yml +24 -0
- data/config/defaults/gemspec.yml +121 -0
- data/config/defaults/i18n_gettext.yml +20 -0
- data/config/defaults/i18n_railsi18n.yml +9 -0
- data/config/defaults/layout.yml +1121 -0
- data/config/defaults/lint.yml +1315 -0
- data/config/defaults/metrics.yml +119 -0
- data/config/defaults/migration.yml +9 -0
- data/config/defaults/naming.yml +332 -0
- data/config/defaults/performance.yml +445 -0
- data/config/defaults/rake.yml +34 -0
- data/config/defaults/rspec.yml +1081 -0
- data/config/defaults/security.yml +67 -0
- data/config/defaults/sequel.yml +59 -0
- data/config/defaults/style.yml +2985 -0
- data/config/defaults/thread_safety.yml +45 -0
- data/exe/docquet +7 -0
- data/lib/docquet/cli/base.rb +21 -0
- data/lib/docquet/cli/install_config.rb +75 -0
- data/lib/docquet/cli/regenerate_todo.rb +46 -0
- data/lib/docquet/cli.rb +6 -0
- data/lib/docquet/commands.rb +12 -0
- data/lib/docquet/config_processor.rb +52 -0
- data/lib/docquet/generators/rubocop_yml_generator.rb +78 -0
- data/lib/docquet/inflector.rb +22 -0
- data/lib/docquet/plugin_detector.rb +29 -0
- data/lib/docquet/rake_task.rb +126 -0
- data/lib/docquet/version.rb +6 -0
- data/lib/docquet.rb +17 -0
- data/templates/rubocop.yml.erb +35 -0
- metadata +172 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Department 'ThreadSafety' (6):
|
|
2
|
+
# https://docs.rubocop.org/rubocop-thread_safety/cops_thread_safety.html#threadsafetyclassandmoduleattributes
|
|
3
|
+
ThreadSafety/ClassAndModuleAttributes:
|
|
4
|
+
Description: Avoid mutating class and module attributes.
|
|
5
|
+
Enabled: true
|
|
6
|
+
ActiveSupportClassAttributeAllowed: false
|
|
7
|
+
|
|
8
|
+
# https://docs.rubocop.org/rubocop-thread_safety/cops_thread_safety.html#threadsafetyclassinstancevariable
|
|
9
|
+
ThreadSafety/ClassInstanceVariable:
|
|
10
|
+
Description: Avoid class instance variables.
|
|
11
|
+
Enabled: true
|
|
12
|
+
|
|
13
|
+
# https://docs.rubocop.org/rubocop-thread_safety/cops_thread_safety.html#threadsafetydirchdir
|
|
14
|
+
ThreadSafety/DirChdir:
|
|
15
|
+
Description: Avoid using `Dir.chdir` due to its process-wide effect.
|
|
16
|
+
Enabled: true
|
|
17
|
+
AllowCallWithBlock: false
|
|
18
|
+
|
|
19
|
+
# Supports --autocorrect
|
|
20
|
+
# https://docs.rubocop.org/rubocop-thread_safety/cops_thread_safety.html#threadsafetymutableclassinstancevariable
|
|
21
|
+
ThreadSafety/MutableClassInstanceVariable:
|
|
22
|
+
Description: Do not assign mutable objects to class instance variables.
|
|
23
|
+
Enabled: true
|
|
24
|
+
EnforcedStyle: literals
|
|
25
|
+
SafeAutoCorrect: false
|
|
26
|
+
SupportedStyles:
|
|
27
|
+
- literals
|
|
28
|
+
- strict
|
|
29
|
+
|
|
30
|
+
# https://docs.rubocop.org/rubocop-thread_safety/cops_thread_safety.html#threadsafetynewthread
|
|
31
|
+
ThreadSafety/NewThread:
|
|
32
|
+
Description: Avoid starting new threads. Let a framework like Sidekiq handle the threads.
|
|
33
|
+
Enabled: true
|
|
34
|
+
|
|
35
|
+
# https://docs.rubocop.org/rubocop-thread_safety/cops_thread_safety.html#threadsafetyrackmiddlewareinstancevariable
|
|
36
|
+
ThreadSafety/RackMiddlewareInstanceVariable:
|
|
37
|
+
Description: Avoid instance variables in Rack middleware.
|
|
38
|
+
Enabled: true
|
|
39
|
+
Include:
|
|
40
|
+
- app/middleware/**/*.rb
|
|
41
|
+
- lib/middleware/**/*.rb
|
|
42
|
+
- app/middlewares/**/*.rb
|
|
43
|
+
- lib/middlewares/**/*.rb
|
|
44
|
+
AllowedIdentifiers: []
|
|
45
|
+
|
data/exe/docquet
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/cli"
|
|
4
|
+
|
|
5
|
+
module Docquet
|
|
6
|
+
module CLI
|
|
7
|
+
class Base < Dry::CLI::Command
|
|
8
|
+
private def rubocop_yml_exists?
|
|
9
|
+
File.exist?(".rubocop.yml")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private def rubocop_command
|
|
13
|
+
bundle_exec_available? ? "bundle exec rubocop" : "rubocop"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private def bundle_exec_available?
|
|
17
|
+
system("which bundle > /dev/null 2>&1")
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docquet
|
|
4
|
+
module CLI
|
|
5
|
+
class InstallConfig < Base
|
|
6
|
+
desc "Install RuboCop configuration and generate TODO file"
|
|
7
|
+
|
|
8
|
+
option :force, type: :boolean, default: false, desc: "Overwrite existing files"
|
|
9
|
+
|
|
10
|
+
def call(force: false, **)
|
|
11
|
+
check_existing_files(force)
|
|
12
|
+
|
|
13
|
+
# Processing order is important for correct TODO file generation:
|
|
14
|
+
# 1. Create empty .rubocop_todo.yml first to prevent reference errors
|
|
15
|
+
# 2. Generate .rubocop.yml with gem configuration
|
|
16
|
+
# 3. Generate actual .rubocop_todo.yml with gem rules applied
|
|
17
|
+
# This ensures TODO file reflects violations against gem configuration
|
|
18
|
+
|
|
19
|
+
create_empty_todo_file
|
|
20
|
+
|
|
21
|
+
generator = Generators::RuboCopYMLGenerator.new
|
|
22
|
+
generator.generate
|
|
23
|
+
puts "✓ Generated .rubocop.yml"
|
|
24
|
+
|
|
25
|
+
generate_todo_file
|
|
26
|
+
|
|
27
|
+
show_completion_message
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private def check_existing_files(force)
|
|
31
|
+
existing_files = %w[.rubocop.yml .rubocop_todo.yml].select {|file| File.exist?(file) }
|
|
32
|
+
|
|
33
|
+
return if existing_files.none? || force
|
|
34
|
+
|
|
35
|
+
puts "Error: Files already exist: #{existing_files.join(", ")}. Use --force to overwrite."
|
|
36
|
+
exit 1
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private def create_empty_todo_file
|
|
40
|
+
File.write(".rubocop_todo.yml", <<~TODO)
|
|
41
|
+
# This file contains configuration to temporarily disable cops
|
|
42
|
+
# Configuration will be generated by running: rubocop --regenerate-todo
|
|
43
|
+
TODO
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private def generate_todo_file
|
|
47
|
+
puts "Generating .rubocop_todo.yml..."
|
|
48
|
+
|
|
49
|
+
command = build_todo_command
|
|
50
|
+
if system(command)
|
|
51
|
+
puts "✓ Generated .rubocop_todo.yml"
|
|
52
|
+
else
|
|
53
|
+
puts "Error: Failed to generate .rubocop_todo.yml"
|
|
54
|
+
exit 1
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private def build_todo_command
|
|
59
|
+
"#{rubocop_command} --regenerate-todo --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private def show_completion_message
|
|
63
|
+
puts <<~MESSAGE
|
|
64
|
+
|
|
65
|
+
✓ RuboCop setup complete!
|
|
66
|
+
|
|
67
|
+
Next steps:
|
|
68
|
+
1. Review .rubocop_todo.yml and gradually fix violations
|
|
69
|
+
2. Use 'docquet regenerate-todo' for future updates
|
|
70
|
+
3. Run 'bundle exec rubocop' to check your code
|
|
71
|
+
MESSAGE
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
|
|
5
|
+
module Docquet
|
|
6
|
+
module CLI
|
|
7
|
+
class RegenerateTodo < Base
|
|
8
|
+
desc "Regenerate .rubocop_todo.yml"
|
|
9
|
+
|
|
10
|
+
def call(**)
|
|
11
|
+
unless rubocop_yml_exists?
|
|
12
|
+
puts "Error: .rubocop.yml not found. Run 'docquet install-config' first."
|
|
13
|
+
exit 1
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
before_hash = calculate_file_hash(".rubocop_todo.yml")
|
|
17
|
+
command = build_command
|
|
18
|
+
|
|
19
|
+
puts "Running: #{command}"
|
|
20
|
+
if system(command)
|
|
21
|
+
after_hash = calculate_file_hash(".rubocop_todo.yml")
|
|
22
|
+
changed = before_hash != after_hash
|
|
23
|
+
|
|
24
|
+
puts <<~MESSAGE
|
|
25
|
+
✓ Regenerated .rubocop_todo.yml
|
|
26
|
+
#{changed ? "📝 TODO file was updated with changes" : "✅ TODO file unchanged (no new violations)"}
|
|
27
|
+
Review the updated TODO file and continue fixing violations.
|
|
28
|
+
MESSAGE
|
|
29
|
+
else
|
|
30
|
+
puts "Error: Failed to regenerate .rubocop_todo.yml"
|
|
31
|
+
exit 1
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private def build_command
|
|
36
|
+
"#{rubocop_command} --regenerate-todo --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private def calculate_file_hash(file_path)
|
|
40
|
+
return nil unless File.exist?(file_path)
|
|
41
|
+
|
|
42
|
+
Digest::SHA256.hexdigest(File.read(file_path))
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
data/lib/docquet/cli.rb
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module Docquet
|
|
6
|
+
class ConfigProcessor
|
|
7
|
+
def initialize(plugin_names)
|
|
8
|
+
@plugin_names = plugin_names
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def process(content, department, gem_name, base)
|
|
12
|
+
content = add_department_header(content, department)
|
|
13
|
+
content = enable_all_cops(content)
|
|
14
|
+
content = remove_deprecated_config(content)
|
|
15
|
+
content = add_documentation_links(content, department, gem_name, base)
|
|
16
|
+
content = normalize_paths(content)
|
|
17
|
+
remove_trailing_whitespace(content)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private def add_department_header(content, department)
|
|
21
|
+
cop_count = content.scan(%r{^#{Regexp.escape(department)}/}).length
|
|
22
|
+
header = "# Department '#{department}' (#{cop_count}):\n"
|
|
23
|
+
header + content
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private def enable_all_cops(content)
|
|
27
|
+
content.gsub(/(?<=^ )Enabled: (false|pending)$/) { "Enabled: true # was #{$1}" }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private def remove_deprecated_config(content)
|
|
31
|
+
content.gsub(/^\s+AllowOnlyRestArgument:.*\n/, "")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private def add_documentation_links(content, department, gem_name, base)
|
|
35
|
+
content.gsub(%r{(?=^#{department}/(.+):$)}) do
|
|
36
|
+
cop_name = $1
|
|
37
|
+
path = "/#{gem_name}/cops_#{base}.html"
|
|
38
|
+
fragment = "#{department}#{cop_name}".downcase.delete("/_")
|
|
39
|
+
|
|
40
|
+
"# #{URI::HTTPS.build(scheme: "https", host: "docs.rubocop.org", path:, fragment:)}\n"
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private def normalize_paths(content)
|
|
45
|
+
content.gsub("#{Dir.pwd}/", "")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private def remove_trailing_whitespace(content)
|
|
49
|
+
content.gsub(/ +$/, "")
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "erb"
|
|
4
|
+
|
|
5
|
+
module Docquet
|
|
6
|
+
module Generators
|
|
7
|
+
class RuboCopYMLGenerator
|
|
8
|
+
def initialize
|
|
9
|
+
@inflector = Inflector.instance
|
|
10
|
+
@detected_plugin_names = PluginDetector.detect_plugin_names
|
|
11
|
+
@filtered_configs = filtered_config_files
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def generate
|
|
15
|
+
content = ERB.new(File.read(template_path("rubocop.yml.erb")), trim_mode: "-").result(binding)
|
|
16
|
+
File.write(".rubocop.yml", content)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private def detected_ruby_version
|
|
20
|
+
# Detect from .ruby-version or Gemfile
|
|
21
|
+
if File.exist?(".ruby-version")
|
|
22
|
+
File.read(".ruby-version").strip
|
|
23
|
+
else
|
|
24
|
+
RUBY_VERSION[/\A\d+\.\d+/]
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private def template_path(filename)
|
|
29
|
+
File.join(File.dirname(__dir__, 3), "templates", filename)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private def detect_available_config_files
|
|
33
|
+
gem_config_dir = File.join(File.dirname(__dir__, 3), "config", "cops")
|
|
34
|
+
Dir.glob("#{gem_config_dir}/*.yml").map {|path| File.basename(path, ".yml") }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private def filtered_config_files
|
|
38
|
+
available_configs = detect_available_config_files
|
|
39
|
+
core_departments = %w[style layout lint metrics security gemspec bundler naming]
|
|
40
|
+
|
|
41
|
+
available_configs.select do |config|
|
|
42
|
+
# Extract department name from config file name
|
|
43
|
+
department = extract_department_from_config(config)
|
|
44
|
+
|
|
45
|
+
if core_departments.include?(@inflector.underscore(department))
|
|
46
|
+
true # Core departments are always included
|
|
47
|
+
else
|
|
48
|
+
# Check if corresponding plugin is detected
|
|
49
|
+
plugin_name = @inflector.underscore(department)
|
|
50
|
+
@detected_plugin_names.include?(plugin_name)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private def extract_department_from_config(config)
|
|
56
|
+
# Extract department name from the corresponding defaults file
|
|
57
|
+
cops_file = File.join(File.dirname(__dir__, 3), "config", "cops", "#{config}.yml")
|
|
58
|
+
|
|
59
|
+
if File.exist?(cops_file)
|
|
60
|
+
cops_content = File.read(cops_file)
|
|
61
|
+
if cops_content =~ %r{inherit_from:\s*\.\./defaults/(.+)\.yml}
|
|
62
|
+
defaults_file = File.join(File.dirname(__dir__, 3), "config", "defaults", "#{$1}.yml")
|
|
63
|
+
|
|
64
|
+
if File.exist?(defaults_file)
|
|
65
|
+
defaults_content = File.read(defaults_file)
|
|
66
|
+
if defaults_content =~ /^# Department '(.+)'/
|
|
67
|
+
return $1
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Fallback: use the simple split method
|
|
74
|
+
config.split("_").first
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dry/inflector"
|
|
4
|
+
|
|
5
|
+
module Docquet
|
|
6
|
+
# Provides a centralized inflector with consistent acronym rules.
|
|
7
|
+
#
|
|
8
|
+
# This module ensures all classes use the same inflection rules,
|
|
9
|
+
# preventing inconsistencies in department name transformations.
|
|
10
|
+
module Inflector
|
|
11
|
+
# Returns a configured Dry::Inflector instance with custom acronyms.
|
|
12
|
+
#
|
|
13
|
+
# @return [Dry::Inflector] configured inflector instance
|
|
14
|
+
def self.instance
|
|
15
|
+
@instance ||= Dry::Inflector.new do |inflections|
|
|
16
|
+
inflections.acronym("RSpec")
|
|
17
|
+
inflections.acronym("GetText")
|
|
18
|
+
inflections.acronym("RailsI18n")
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docquet
|
|
4
|
+
# Detects available RuboCop plugins and provides consistent naming.
|
|
5
|
+
#
|
|
6
|
+
# Terminology:
|
|
7
|
+
# - Plugin Gem Name: Full gem name (e.g., "rubocop-performance", "rubocop-rspec")
|
|
8
|
+
# - Plugin Name: Short name without prefix (e.g., "performance", "rspec")
|
|
9
|
+
#
|
|
10
|
+
# Both forms are used in different contexts:
|
|
11
|
+
# - Plugin Gem Names: For gem operations, --plugin CLI arguments
|
|
12
|
+
# - Plugin Names: For config file matching, department filtering
|
|
13
|
+
module PluginDetector
|
|
14
|
+
# Returns full gem names of detected RuboCop plugins.
|
|
15
|
+
#
|
|
16
|
+
# @return [Array<String>] plugin gem names (e.g., ["rubocop-performance", "rubocop-rspec"])
|
|
17
|
+
module_function def detect_plugin_gem_names
|
|
18
|
+
plugins = Gem::Specification.select {|spec| /\ARuboCop::.*::Plugin\z/ =~ spec.metadata["default_lint_roller_plugin"] }
|
|
19
|
+
plugins.map(&:name)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Returns short names of detected RuboCop plugins (without "rubocop-" prefix).
|
|
23
|
+
#
|
|
24
|
+
# @return [Array<String>] plugin names (e.g., ["performance", "rspec"])
|
|
25
|
+
module_function def detect_plugin_names
|
|
26
|
+
detect_plugin_gem_names.map {|name| name.delete_prefix("rubocop-") }
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Only load in docquet gem development environment
|
|
4
|
+
unless File.exist?("docquet.gemspec")
|
|
5
|
+
warn "Warning: docquet/rake_task loaded outside of docquet project - skipping loading"
|
|
6
|
+
return
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
# Standard library and framework requires
|
|
10
|
+
require "fileutils"
|
|
11
|
+
require "open3"
|
|
12
|
+
require "rake/tasklib"
|
|
13
|
+
require "uri"
|
|
14
|
+
require "yaml"
|
|
15
|
+
|
|
16
|
+
# RuboCop core
|
|
17
|
+
require "rubocop"
|
|
18
|
+
|
|
19
|
+
# RuboCop plugins
|
|
20
|
+
require "rubocop-capybara"
|
|
21
|
+
require "rubocop-i18n"
|
|
22
|
+
require "rubocop-performance"
|
|
23
|
+
require "rubocop-rake"
|
|
24
|
+
require "rubocop-rspec"
|
|
25
|
+
require "rubocop-sequel"
|
|
26
|
+
require "rubocop-thread_safety"
|
|
27
|
+
|
|
28
|
+
# Load docquet for zeitwerk autoloading
|
|
29
|
+
require_relative "../docquet"
|
|
30
|
+
|
|
31
|
+
module Docquet
|
|
32
|
+
class RakeTask < Rake::TaskLib
|
|
33
|
+
def initialize
|
|
34
|
+
super
|
|
35
|
+
@inflector = Inflector.instance
|
|
36
|
+
|
|
37
|
+
@plugin_gem_names = PluginDetector.detect_plugin_gem_names
|
|
38
|
+
departments = RuboCop::Cop::Registry.global.map {|c| c.department.to_s }
|
|
39
|
+
departments.sort!
|
|
40
|
+
departments.uniq!
|
|
41
|
+
@departments = departments
|
|
42
|
+
|
|
43
|
+
define_tasks
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private def define_tasks
|
|
47
|
+
desc "Create defaults directory"
|
|
48
|
+
directory "config/defaults"
|
|
49
|
+
|
|
50
|
+
desc "Generate default configuration files"
|
|
51
|
+
task generate_defaults: "config/defaults"
|
|
52
|
+
|
|
53
|
+
desc "Check cops configuration inheritance"
|
|
54
|
+
task check_cops: :generate_defaults do
|
|
55
|
+
check_cops_configurations
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
desc "Clean existing default configuration files"
|
|
59
|
+
task :clean_defaults do
|
|
60
|
+
FileUtils.rm_rf("config/defaults")
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
desc "Clean and regenerate all configuration files"
|
|
64
|
+
task regenerate: %i[clean_defaults generate_defaults check_cops]
|
|
65
|
+
|
|
66
|
+
# Department-specific file generation tasks
|
|
67
|
+
@departments.each do |department|
|
|
68
|
+
base = @inflector.underscore(department).tr("/", "_")
|
|
69
|
+
target_file = "config/defaults/#{base}.yml"
|
|
70
|
+
|
|
71
|
+
desc "Generate configuration for #{department}"
|
|
72
|
+
file target_file => "config/defaults" do |t|
|
|
73
|
+
generate_default_config(department, t.name)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
task generate_defaults: target_file
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private def generate_default_config(department, target_file)
|
|
81
|
+
puts "Generating #{department} configuration..."
|
|
82
|
+
|
|
83
|
+
base = @inflector.underscore(department).tr("/", "_")
|
|
84
|
+
plugin_name = @inflector.underscore(department.sub(%r{/.*}, ""))
|
|
85
|
+
gem_name = "rubocop-#{plugin_name}"
|
|
86
|
+
gem_name = "rubocop" unless @plugin_gem_names.include?(gem_name)
|
|
87
|
+
|
|
88
|
+
options = %W[--show-cops=#{department}/* --force-default-config --display-cop-names --extra-details --display-style-guide]
|
|
89
|
+
|
|
90
|
+
cmd = ["bin/rubocop", *options, *@plugin_gem_names.sort.flat_map {|name| %W[--plugin #{name}] }]
|
|
91
|
+
|
|
92
|
+
puts "Running: #{cmd.join(" ")}"
|
|
93
|
+
content, status = Open3.capture2e(*cmd)
|
|
94
|
+
|
|
95
|
+
if status.success?
|
|
96
|
+
processor = ConfigProcessor.new(@plugin_gem_names)
|
|
97
|
+
processed_content = processor.process(content, department, gem_name, base)
|
|
98
|
+
File.write(target_file, processed_content)
|
|
99
|
+
puts "✓ Generated #{target_file}"
|
|
100
|
+
else
|
|
101
|
+
puts "✗ Failed to generate #{department} configuration"
|
|
102
|
+
exit 1
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private def check_cops_configurations
|
|
107
|
+
puts "Checking cops configurations..."
|
|
108
|
+
|
|
109
|
+
Dir["config/defaults/*.yml"].each do |default_file|
|
|
110
|
+
base_name = File.basename(default_file)
|
|
111
|
+
cops_file = "config/cops/#{base_name}"
|
|
112
|
+
|
|
113
|
+
next unless File.exist?(cops_file)
|
|
114
|
+
|
|
115
|
+
puts "Checking #{cops_file}..."
|
|
116
|
+
|
|
117
|
+
cops_content = File.read(cops_file)
|
|
118
|
+
if cops_content.include?("inherit_from: ../defaults/#{base_name}")
|
|
119
|
+
puts " ✓ Inheritance looks good"
|
|
120
|
+
else
|
|
121
|
+
puts " Warning: #{cops_file} may need inherit_from update"
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
data/lib/docquet.rb
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "zeitwerk"
|
|
4
|
+
require_relative "docquet/version"
|
|
5
|
+
|
|
6
|
+
module Docquet
|
|
7
|
+
class Error < StandardError; end
|
|
8
|
+
|
|
9
|
+
loader = Zeitwerk::Loader.for_gem
|
|
10
|
+
loader.inflector.inflect(
|
|
11
|
+
"cli" => "CLI",
|
|
12
|
+
"rubocop_yml_generator" => "RuboCopYMLGenerator"
|
|
13
|
+
)
|
|
14
|
+
loader.ignore("#{__dir__}/docquet/version.rb")
|
|
15
|
+
loader.ignore("#{__dir__}/docquet/rake_task.rb")
|
|
16
|
+
loader.setup
|
|
17
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
DisplayCopNames: true
|
|
3
|
+
DisplayStyleGuide: true
|
|
4
|
+
EnabledByDefault: true
|
|
5
|
+
Exclude:
|
|
6
|
+
- bin/**/*
|
|
7
|
+
- vendor/**/*
|
|
8
|
+
ExtraDetails: true
|
|
9
|
+
NewCops: enable
|
|
10
|
+
TargetRubyVersion: <%= detected_ruby_version %>
|
|
11
|
+
UseCache: true
|
|
12
|
+
|
|
13
|
+
inherit_mode:
|
|
14
|
+
merge:
|
|
15
|
+
- Exclude
|
|
16
|
+
|
|
17
|
+
<% unless @detected_plugin_names.empty? -%>
|
|
18
|
+
plugins:
|
|
19
|
+
<% @detected_plugin_names.each do |plugin| -%>
|
|
20
|
+
- rubocop-<%= plugin %>
|
|
21
|
+
<% end -%>
|
|
22
|
+
|
|
23
|
+
<% end -%>
|
|
24
|
+
inherit_gem:
|
|
25
|
+
docquet:
|
|
26
|
+
<% @filtered_configs.each do |config| -%>
|
|
27
|
+
- config/cops/<%= config %>.yml
|
|
28
|
+
<% end -%>
|
|
29
|
+
|
|
30
|
+
inherit_from: .rubocop_todo.yml
|
|
31
|
+
|
|
32
|
+
# Project-specific settings can be added here
|
|
33
|
+
# Example:
|
|
34
|
+
# Style/Documentation:
|
|
35
|
+
# Enabled: false
|