From 7110f7ba41bf7f077794bdab0825cbe7e6420476 Mon Sep 17 00:00:00 2001 From: Soumya Ray Date: Mon, 24 Nov 2025 18:01:45 +0800 Subject: [PATCH] refactor: implement self-registration pattern for language support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor language filtering architecture to use self-registration pattern, eliminating hardcoded constants and improving maintainability. Domain layer changes: * CodeLanguage: Add self-registration system with @registry - Each language class declares extension (e.g., extension 'rb') - Auto-registers via ClassMethods.extension hook - Remove hardcoded LANGUAGE_EXTENSION hash - Add wanted_extensions public interface * Streamline wanted?/unwanted? predicates - Single source of truth in ClassMethods - Instance methods delegate to class methods - Remove WANTED_LANGUAGES constant from FileContributions * FilePath improvements - Add wanted? method for domain-level file filtering - Fix extension method with safe navigation for files without extensions - Add guard in language method to return Unknown for missing extensions Infrastructure layer changes: * LocalGitRepo: Remove utility function code smell - Remove hardcoded TEXT_FILES constant - Replace wanted_code_file? with Value::FilePath#wanted? - Infrastructure now queries domain for language support Benefits: - Single source of truth for language definitions - Self-documenting: languages declare their own extensions - Infrastructure automatically stays in sync with domain - Eliminates utility function smell (reek clean) - Domain logic properly encapsulated in domain layer Tests: All passing (35 runs, 132 assertions, 0 failures) Code quality: RuboCop and Reek clean 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../entities/file_contributions.rb | 22 +-- .../contributions/values/code_language.rb | 78 +++++++-- app/domain/contributions/values/file_path.rb | 8 +- .../git/repositories/local_repo.rb | 4 +- coverage/.resultset.json | 156 +++++++++++------- 5 files changed, 172 insertions(+), 96 deletions(-) diff --git a/app/domain/contributions/entities/file_contributions.rb b/app/domain/contributions/entities/file_contributions.rb index a34735f..e68bd73 100644 --- a/app/domain/contributions/entities/file_contributions.rb +++ b/app/domain/contributions/entities/file_contributions.rb @@ -9,17 +9,6 @@ module Entity class FileContributions include Mixins::ContributionsCalculator - WANTED_LANGUAGES = [ - Value::CodeLanguage::Ruby, - Value::CodeLanguage::Python, - Value::CodeLanguage::Javascript, - Value::CodeLanguage::Css, - Value::CodeLanguage::Html, - Value::CodeLanguage::Slim, - Value::CodeLanguage::Erb, - Value::CodeLanguage::Markdown - ].freeze - attr_reader :file_path, :lines def initialize(file_path:, lines:) @@ -36,7 +25,7 @@ def total_credits end def credit_share - return Value::CreditShare.new if not_wanted + return Value::CreditShare.new if unwanted? @credit_share ||= lines.each_with_object(Value::CreditShare.new) do |line, credit| @@ -50,13 +39,8 @@ def contributors private - def not_wanted - !wanted - end - - def wanted - WANTED_LANGUAGES.include?(language) - end + def unwanted? = language.unwanted? + def wanted? = language.wanted? end end end diff --git a/app/domain/contributions/values/code_language.rb b/app/domain/contributions/values/code_language.rb index e2a3ad1..3ef4e42 100644 --- a/app/domain/contributions/values/code_language.rb +++ b/app/domain/contributions/values/code_language.rb @@ -7,9 +7,28 @@ module CodeLanguage WHITESPACE = '[ \t]' LINE_END = '$' + # Registry for language classes + @registry = {} + + def self.register(extension, language_class) + @registry[extension] = language_class + end + + def self.extension_language(file_extension) + @registry.fetch(file_extension) { Unknown } + end + + def self.wanted_extensions + @registry.keys.freeze + end + module LanguageMethods attr_reader :code + def self.included(base) + base.extend(ClassMethods) + end + def setup(code) @code = code end @@ -21,10 +40,31 @@ def name def useless? code.match?(self.class.const_get(:USELESS)) end + + # Delegate to class methods + def wanted? = self.class.wanted? + def unwanted? = self.class.unwanted? + + module ClassMethods + def extension(ext = nil) + if ext + @extension = ext + CodeLanguage.register(ext, self) + end + @extension + end + + # Single source of truth for wanted/unwanted at class level + def wanted? = true + def unwanted? = !wanted? + end end class Ruby include LanguageMethods + + extension 'rb' + def initialize(code) = setup(code) COMMENT = '[#\/]' USELESS = /^#{WHITESPACE}*(#{COMMENT}|#{LINE_END})/ @@ -32,6 +72,9 @@ def initialize(code) = setup(code) class Python include LanguageMethods + + extension 'py' + def initialize(code) = setup(code) COMMENT = '[#\/]' USELESS = /^#{WHITESPACE}*(#{COMMENT}|#{LINE_END})/ @@ -39,6 +82,9 @@ def initialize(code) = setup(code) class Javascript include LanguageMethods + + extension 'js' + def initialize(code) = setup(code) COMMENT = '//' USELESS = /^#{WHITESPACE}*(#{COMMENT}|#{LINE_END})/ @@ -46,30 +92,45 @@ def initialize(code) = setup(code) class Html include LanguageMethods + + extension 'html' + def initialize(code) = setup(code) USELESS = /^#{WHITESPACE}*#{LINE_END}/ end class Erb include LanguageMethods + + extension 'erb' + def initialize(code) = setup(code) USELESS = /^#{WHITESPACE}*#{LINE_END}/ end class Slim include LanguageMethods + + extension 'slim' + def initialize(code) = setup(code) USELESS = /^#{WHITESPACE}*#{LINE_END}/ end class Css include LanguageMethods + + extension 'css' + def initialize(code) = setup(code) USELESS = /^#{WHITESPACE}*#{LINE_END}/ end class Markdown include LanguageMethods + + extension 'md' + def initialize(code) = setup(code) USELESS = /^#{WHITESPACE}*#{LINE_END}/ end @@ -78,25 +139,14 @@ class Unknown include LanguageMethods def initialize(code) = setup(code) + # Override: all lines are useless def useless? = true def self.lang_name = 'not recognized' + # Override: class is not wanted + def self.wanted? = false end - LANGUAGE_EXTENSION = { - 'rb' => Ruby, - 'py' => Python, - 'js' => Javascript, - 'css' => Css, - 'html' => Html, - 'erb' => Erb, - 'slim' => Slim, - 'md' => Markdown - }.freeze UNKNOWN_LANGUAGE = CodeLanguage::Unknown.freeze - - def self.extension_language(file_extension) - LANGUAGE_EXTENSION[file_extension] || UNKNOWN_LANGUAGE - end end end end diff --git a/app/domain/contributions/values/file_path.rb b/app/domain/contributions/values/file_path.rb index b63dbed..2557606 100644 --- a/app/domain/contributions/values/file_path.rb +++ b/app/domain/contributions/values/file_path.rb @@ -17,13 +17,19 @@ def initialize(filepath) end def extension - @extension ||= filename.match(/\.([a-zA-Z0-9]+$)/).captures.first + @extension ||= filename.match(/\.([a-zA-Z0-9]+$)/)&.captures&.first end def language + return CodeLanguage::Unknown unless extension + CodeLanguage.extension_language(extension) end + def wanted? + language.wanted? + end + def folder_after(root) raise(ArgumentError, 'Path mismatch') unless self.start_with?(root) || root.empty? diff --git a/app/infrastructure/git/repositories/local_repo.rb b/app/infrastructure/git/repositories/local_repo.rb index 91d35b4..ab1e94d 100644 --- a/app/infrastructure/git/repositories/local_repo.rb +++ b/app/infrastructure/git/repositories/local_repo.rb @@ -13,8 +13,6 @@ module Errors class LocalGitRepo ONLY_FOLDERS = '**/' FILES_AND_FOLDERS = '**/*' - TEXT_FILES = %w[rb py js css html slim md erb].join('|') - CODE_FILENAME_MATCH = /\.(#{TEXT_FILES})$/ attr_reader :git_repo_path @@ -33,7 +31,7 @@ def files @files ||= in_repo do Dir.glob(FILES_AND_FOLDERS).select do |path| - File.file?(path) && (path =~ CODE_FILENAME_MATCH) + File.file?(path) && Value::FilePath.new(path).wanted? end end end diff --git a/coverage/.resultset.json b/coverage/.resultset.json index c2f1e83..166a4d1 100644 --- a/coverage/.resultset.json +++ b/coverage/.resultset.json @@ -153,17 +153,6 @@ 1, 1, null, - null, - 1, - null, - null, - null, - null, - null, - null, - null, - null, - null, 1, null, 1, @@ -195,12 +184,7 @@ 1, null, 1, - 711, - null, - null, 1, - 711, - null, null, null, null @@ -251,8 +235,27 @@ 1, 1, null, + null, + 1, + null, 1, + 8, + null, + null, 1, + 6581, + null, + null, + 1, + 0, + null, + null, + 1, + 1, + null, + 1, + 9, + null, null, 1, 5360, @@ -269,26 +272,37 @@ null, 1, 1, + null, 1, 1, - 1, + 8, + 8, + 8, + null, + 8, + null, null, null, 1, 1, - 1, + null, + null, + null, 1, 1, null, + 1, null, 1, 1, 1, + null, + null, 1, 1, null, - null, 1, + null, 1, 1, 1, @@ -296,11 +310,9 @@ null, 1, 1, - 1, - 1, - null, null, 1, + null, 1, 1, 1, @@ -308,12 +320,18 @@ null, 1, 1, + null, + 1, + null, 1, 1, null, null, 1, 1, + null, + 1, + null, 1, 1, null, @@ -322,26 +340,42 @@ 1, null, 1, + null, 1, 1, null, null, + 1, + 1, + null, + 1, null, 1, + 1, null, null, + 1, + 1, null, + 1, null, + 1, + 1, null, null, + 1, + 1, null, + 1, null, 1, + 1, null, 1, - 6071, null, null, + 1, + null, null, null, null @@ -656,16 +690,22 @@ 1, null, 1, - 380, - 380, + 836, + 836, + null, + null, + 1, + 13036, null, null, 1, - 6071, + 6527, + null, + 6509, null, null, 1, - 6071, + 456, null, null, 1, @@ -679,9 +719,9 @@ 1, null, 1, - 380, - 380, - 380, + 836, + 836, + 836, null, null, null, @@ -746,7 +786,7 @@ null, null, 1, - 1, + 3, null, null, 1, @@ -1043,16 +1083,16 @@ 1, null, 1, - 192, - 192, - 192, - 192, + 194, + 194, + 194, + 194, null, null, 1, - 1, - 1, - 1, + 3, + 3, + 3, null, null, 1, @@ -1067,21 +1107,21 @@ null, null, 1, - 1, - 1, + 3, + 3, null, null, 1, - 1, - 1, + 3, + 3, null, null, 1, - 384, + 388, null, null, 1, - 192, + 194, null, null, null, @@ -1092,8 +1132,8 @@ null, null, 1, - 0, - 0, + 2, + 10, null, null, null, @@ -1428,15 +1468,15 @@ null, null, 1, - 14, + 16, null, null, null, 1, - 1, - 0, + 3, + 2, null, - 0, + 12, null, null, null @@ -1459,8 +1499,6 @@ 1, 1, 1, - 1, - 1, null, 1, null, @@ -1470,8 +1508,8 @@ null, null, 1, - 0, - 0, + 12, + 2, null, null, 1, @@ -1490,7 +1528,7 @@ null, null, 1, - 31, + 33, null, null, null, @@ -1538,7 +1576,7 @@ null, null, 1, - 0, + 2, null, null, null, @@ -2563,8 +2601,8 @@ null, 1, 30, - 2070, - 2070, + 2015, + 2015, null, null, 30, @@ -3235,6 +3273,6 @@ ] } }, - "timestamp": 1764424989 + "timestamp": 1764473876 } }