Files
gitlabhq/rubocop/cop/gitlab/bounded_contexts.rb
2024-08-13 00:11:01 +00:00

83 lines
2.7 KiB
Ruby

# frozen_string_literal: true
module RuboCop
module Cop
module Gitlab
class BoundedContexts < RuboCop::Cop::Base
DOC_LINK = "https://docs.gitlab.com/ee/development/software_design#bounded-contexts"
MODULE_MSG = "Module `%{identifier}` is not a valid bounded context. See #{DOC_LINK}.".freeze
CLASS_MSG = "Class `%{identifier}` is not within a valid bounded context module. See #{DOC_LINK}.".freeze
# We ignore the EE namespace because it's a special case used for extending
# the Enterprise Edition code.
# We ignore GraphQL top-level namespaces because it's the way organize GraphQL code.
# These are ignored after the EE module because GraphQL code can be namespaced under EE too.
IGNORED_TOP_LEVEL_NAMESPACES = %w[EE Mutations Types::PermissionTypes Types PermissionTypes Resolvers].freeze
class << self
def external_dependency_checksum
@external_dependency_checksum ||=
Digest::SHA256.file(config_file_path).hexdigest
end
def bounded_contexts
@bounded_contexts ||= begin
data = YAML.load_file(config_file_path)
Set.new(data.fetch("domains").keys + data.fetch("platform").keys)
end
end
def config_file_path
File.expand_path("../../../config/bounded_contexts.yml", __dir__ || ".")
end
end
def on_module(node)
run_check(node, MODULE_MSG)
end
def on_class(node)
run_check(node, CLASS_MSG)
end
# Used by RuboCop to invalidate its cache if the contents of `config_file_path` changes.
def external_dependency_checksum
self.class.external_dependency_checksum
end
private
def run_check(node, message_template)
return if @offense_added
collect_identifiers(node)
return if identifiers.empty?
return if self.class.bounded_contexts.include?(identifiers.first)
@offense_added = true
add_offense(node.loc.name, message: format(message_template, identifier: identifiers.join('::')))
end
def collect_identifiers(node)
return if identifiers.any?
full_id = full_identifier_for(node)
namespace = IGNORED_TOP_LEVEL_NAMESPACES.find { |n| full_id.starts_with?(n) }
full_id.sub!(/^#{namespace}(::)*/, '') if namespace
identifiers.concat(full_id.split('::'))
end
def identifiers
@identifiers ||= []
end
def full_identifier_for(node)
node.identifier.source.sub(/^::/, '')
end
end
end
end
end