Module: RuboCop::Cop::ProjectIndexHelp
| Relationships & Source Files | |
| Extension / Inclusion / Inheritance Descendants | |
|
Included In:
Lint::ConstantReassignment,
Lint::ConstantResolution,
Lint::DeprecatedReference,
Lint::DuplicateMethods,
Lint::InheritException,
Lint::MissingSuper,
Lint::NameTypo,
Lint::UnusedPrivateMethod,
Naming::AccessorMethodName,
Naming::PredicatePrefix,
Style::ClassAndModuleChildren,
Style::Documentation,
Style::MissingRespondToMissing,
Style::RedundantConstantBase,
Style::StaticClass
| |
| Defined in: | lib/rubocop/cop/mixin/project_index_help.rb |
Overview
Common helpers for cops that consult the project-wide static-analysis index via Base#project_index.
Mixed-in cops gain the #external_dependency_checksum override that invalidates
the ::RuboCop::ResultCache whenever the indexed project files change on disk.
To run index-backed analysis, cops should simply check whether project_index is non-nil;
the runner only exposes a non-nil index when the user opted in via AllCops/UseProjectIndex
and the underlying gem is available.
Constant Summary
-
BUILTIN_DOCUMENT_URI =
# File 'lib/rubocop/cop/mixin/project_index_help.rb', line 14'rubydex:built-in' -
FILE_URI_PREFIX =
# File 'lib/rubocop/cop/mixin/project_index_help.rb', line 15'file://' -
WINDOWS_DRIVE_PREFIX =
# File 'lib/rubocop/cop/mixin/project_index_help.rb', line 18
Matches the spurious leading slash before a Windows drive letter that remains after stripping file:// from a file:///C:/… URI.
%r{\A/(?=[A-Za-z]:[/\\])}.freeze
Class Attribute Summary
-
.cached_index_signature
rw
The signature is a property of the index, not of the cop, and computing it stats every indexed file, so all cops sharing an index share one computation.
Instance Method Summary
- #external_dependency_checksum
- #compute_project_index_signature private
-
#definitions_in_other_files(definitions)
private
Returns the definitions among
definitionsthat live in a file other than the one being inspected, ordered by path and line. -
#fully_resolved_index_ancestry?(declaration, ignore_extend: false) ⇒ Boolean
private
Whether every link of the declaration’s ancestry is resolved in the index: no definition of any ancestor has an unresolved superclass or mixin reference.
-
#indexed_singleton_member(namespace, member_name)
private
A namespace without any singleton method has no singleton-class declaration of its own, so the lookup starts from the first ancestor that has one; its
find_membercovers the rest of the chain. -
#indexed_singleton_of(declaration)
private
The declaration of `declaration’s singleton class, or nil when no singleton method is defined on it anywhere in the project.
-
#inherited_index_member?(scope, member_name) ⇒ Boolean
private
Whether an ancestor of
scopeother thanscopeitself definesmember_name. -
#lexical_nesting_of(node)
private
The lexical nesting the node’s constants resolve through, outermost first.
- #prior_definition_in_other_file(definitions) private
- #project_index_signature private
-
#resolve_constant_in_index(const_node)
private
Resolves a constant node the way Ruby does: the first segment through the lexical nesting and every following segment inside the previous one.
- #resolved_mixin_references?(definition, ignore_extend: false) ⇒ Boolean private
- #resolved_superclass_reference?(definition) ⇒ Boolean private
- #same_file?(path, other) ⇒ Boolean private
Class Attribute Details
.cached_index_signature (rw)
The signature is a property of the index, not of the cop, and computing it stats every indexed file, so all cops sharing an index share one computation. A single-entry cache (instead of a hash keyed by index) avoids retaining stale graphs in long-lived processes.
# File 'lib/rubocop/cop/mixin/project_index_help.rb', line 25
attr_accessor :cached_index_signature
Instance Method Details
#compute_project_index_signature (private)
[ GitHub ]# File 'lib/rubocop/cop/mixin/project_index_help.rb', line 170
def compute_project_index_signature project_index.documents.filter_map do |doc| uri = doc.uri next if uri == BUILTIN_DOCUMENT_URI path = uri.delete_prefix(FILE_URI_PREFIX).sub(WINDOWS_DRIVE_PREFIX, '') mtime, size = begin stat = File.stat(path) [stat.mtime.to_f, stat.size] rescue StandardError [0, 0] end "#{path}:#{mtime}:#{size}" end.sort end
#definitions_in_other_files(definitions) (private)
Returns the definitions among definitions that live in a file other than the
one being inspected, ordered by path and line. Definitions without a file://
URI (e.g. Rubydex’s built-in declarations) are ignored.
# File 'lib/rubocop/cop/mixin/project_index_help.rb', line 41
def definitions_in_other_files(definitions) current = processed_source.file_path definitions .select { |definition| definition.location.uri.start_with?(FILE_URI_PREFIX) } .reject { |definition| File.identical?(definition.location.to_file_path, current) } .sort_by do |definition| [definition.location.to_file_path, definition.location.start_line] end end
#external_dependency_checksum
[ GitHub ]# File 'lib/rubocop/cop/mixin/project_index_help.rb', line 28
def external_dependency_checksum return nil unless project_index @external_dependency_checksum ||= Digest::SHA1.hexdigest( project_index_signature.join("\n") ) end
#fully_resolved_index_ancestry?(declaration, ignore_extend: false) ⇒ Boolean (private)
Whether every link of the declaration’s ancestry is resolved in the
index: no definition of any ancestor has an unresolved superclass or
mixin reference. Unresolved links silently vanish from ancestors,
so this is the completeness check that must pass before reasoning
about what an ancestry does or does not define. extend mixins only
affect the singleton class; pass ignore_extend: true when reasoning
about instance-side ancestry only.
# File 'lib/rubocop/cop/mixin/project_index_help.rb', line 129
def fully_resolved_index_ancestry?(declaration, ignore_extend: false) declaration.ancestors.all? do |ancestor| ancestor.definitions.all? do |definition| resolved_superclass_reference?(definition) && resolved_mixin_references?(definition, ignore_extend: ignore_extend) end end end
#indexed_singleton_member(namespace, member_name) (private)
A namespace without any singleton method has no singleton-class
declaration of its own, so the lookup starts from the first ancestor
that has one; its find_member covers the rest of the chain.
# File 'lib/rubocop/cop/mixin/project_index_help.rb', line 105
def indexed_singleton_member(namespace, member_name) namespace.ancestors.each do |ancestor| singleton = indexed_singleton_of(ancestor) return singleton.find_member(member_name) if singleton end nil end
#indexed_singleton_of(declaration) (private)
The declaration of `declaration’s singleton class, or nil when no singleton method is defined on it anywhere in the project.
# File 'lib/rubocop/cop/mixin/project_index_help.rb', line 98
def indexed_singleton_of(declaration) project_index["#{declaration.name}::<#{declaration.name.split('::').last}>"] end
#inherited_index_member?(scope, member_name) ⇒ Boolean (private)
Whether an ancestor of scope other than scope itself defines
member_name.
# File 'lib/rubocop/cop/mixin/project_index_help.rb', line 116
def inherited_index_member?(scope, member_name) scope.ancestors.any? do |ancestor| ancestor.name != scope.name && ancestor.member(member_name) end end
#lexical_nesting_of(node) (private)
The lexical nesting the node’s constants resolve through, outermost first. Only scopes whose body contains the node count: a class or module’s identifier and superclass expression are evaluated before its scope exists, so ancestors reached through them are excluded.
# File 'lib/rubocop/cop/mixin/project_index_help.rb', line 82
def lexical_nesting_of(node) nesting = [] child = node node.each_ancestor do |ancestor| if ancestor.type?(:class, :module) && child.equal?(ancestor.body) nesting << ancestor.identifier.const_name end child = ancestor end nesting.reverse end
#prior_definition_in_other_file(definitions) (private)
[ GitHub ]# File 'lib/rubocop/cop/mixin/project_index_help.rb', line 53
def prior_definition_in_other_file(definitions) definitions_in_other_files(definitions).first end
#project_index_signature (private)
[ GitHub ]# File 'lib/rubocop/cop/mixin/project_index_help.rb', line 161
def project_index_signature index, signature = ProjectIndexHelp.cached_index_signature return signature if index.equal?(project_index) compute_project_index_signature.tap do |computed| ProjectIndexHelp.cached_index_signature = [project_index, computed] end end
#resolve_constant_in_index(const_node) (private)
Resolves a constant node the way Ruby does: the first segment through
the lexical nesting and every following segment inside the previous
one. (Qualified names cannot be passed to resolve_constant as a
whole, since it only applies the nesting to the full name. Later
segments use find_member — the namespace and its ancestors — since
resolve_constant cannot take an already-resolved namespace as the
scope, only names as they appear in source.)
# File 'lib/rubocop/cop/mixin/project_index_help.rb', line 64
def resolve_constant_in_index(const_node) segments = const_node.const_name.split('::') nesting = const_node.absolute? ? [] : lexical_nesting_of(const_node) declaration = project_index.resolve_constant(segments.first, nesting) segments.drop(1).each do |segment| return nil unless declaration.is_a?(Rubydex::Namespace) declaration = declaration.find_member(segment) end declaration end
#resolved_mixin_references?(definition, ignore_extend: false) ⇒ Boolean (private)
# File 'lib/rubocop/cop/mixin/project_index_help.rb', line 144
def resolved_mixin_references?(definition, ignore_extend: false) return true unless definition.respond_to?(:mixins) definition.mixins.none? do |mixin| next false if ignore_extend && mixin.is_a?(Rubydex::Extend) mixin.constant_reference.is_a?(Rubydex::UnresolvedConstantReference) end end
#resolved_superclass_reference?(definition) ⇒ Boolean (private)
# File 'lib/rubocop/cop/mixin/project_index_help.rb', line 138
def resolved_superclass_reference?(definition) return true unless definition.is_a?(Rubydex::ClassDefinition) !definition.superclass.is_a?(Rubydex::UnresolvedConstantReference) end