Class: RuboCop::Cop::Naming::FileName
Relationships & Source Files | |
Super Chains via Extension / Inclusion / Inheritance | |
Class Chain:
self,
::RuboCop::Cop::Base ,
::RuboCop::ExcludeLimit ,
NodePattern::Macros,
RuboCop::AST::Sexp
|
|
Instance Chain:
self,
::RuboCop::Cop::Base ,
::RuboCop::Cop::AutocorrectLogic ,
::RuboCop::Cop::IgnoredNode ,
::RuboCop::Util ,
RuboCop::AST::Sexp
|
|
Inherits: |
RuboCop::Cop::Base
|
Defined in: | lib/rubocop/cop/naming/file_name.rb |
Overview
Makes sure that Ruby source files have snake_case names. Ruby scripts (i.e. source files with a shebang in the first line) are ignored.
The cop also ignores .gemspec
files, because Bundler
recommends using dashes to separate namespaces in nested gems
(i.e. bundler-console
becomes Bundler::Console
). As such, the
gemspec is supposed to be named bundler-console.gemspec
.
When ExpectMatchingDefinition
(default: false
) is true
, the cop requires
each file to have a class, module or Struct
defined in it that matches
the filename. This can be further configured using
CheckDefinitionPathHierarchy
(default: true
) to determine whether the
path should match the namespace of the above definition.
When IgnoreExecutableScripts
(default: true
) is true
, files that start
with a shebang line are not considered by the cop.
When Regex
is set, the cop will flag any filename that does not match
the regular expression.
Constant Summary
-
MSG_NO_DEFINITION =
# File 'lib/rubocop/cop/naming/file_name.rb', line 41'`%<basename>s` should define a class or module called `%<namespace>s`.'
-
MSG_REGEX =
# File 'lib/rubocop/cop/naming/file_name.rb', line 42'`%<basename>s` should match `%<regex>s`.'
-
MSG_SNAKE_CASE =
# File 'lib/rubocop/cop/naming/file_name.rb', line 40'The name of this source file (`%<basename>s`) should use snake_case.'
-
SNAKE_CASE =
# File 'lib/rubocop/cop/naming/file_name.rb', line 44/^[\d[[:lower:]]_.?!]+$/.freeze
::RuboCop::Cop::Base
- Inherited
Class Attribute Summary
::RuboCop::Cop::Base
- Inherited
.gem_requirements, .lint?, | |
.support_autocorrect? | Returns if class supports autocorrect. |
.support_multiple_source? | Override if your cop should be called repeatedly for multiple investigations Between calls to #on_new_investigation and |
Class Method Summary
::RuboCop::Cop::Base
- Inherited
.autocorrect_incompatible_with | List of cops that should not try to autocorrect at the same time as this cop. |
.badge | Naming. |
.callbacks_needed, .cop_name, .department, | |
.documentation_url | Returns a url to view this cops documentation online. |
.exclude_from_registry | Call for abstract Cop classes. |
.inherited, | |
.joining_forces | Override and return the Force class(es) you need to join. |
.match? | Returns true if the cop name or the cop namespace matches any of the given names. |
.new, | |
.requires_gem | Register a version requirement for the given gem name. |
.restrict_on_send |
::RuboCop::ExcludeLimit
- Extended
exclude_limit | Sets up a configuration option to have an exclude limit tracked. |
transform |
Instance Attribute Summary
- #bad_filename_allowed? ⇒ Boolean readonly private
- #check_definition_path_hierarchy? ⇒ Boolean readonly private
- #expect_matching_definition? ⇒ Boolean readonly private
- #ignore_executable_scripts? ⇒ Boolean readonly private
::RuboCop::Cop::Base
- Inherited
::RuboCop::Cop::AutocorrectLogic
- Included
Instance Method Summary
- #on_new_investigation
- #struct_definition(node)
- #allowed_acronyms private
- #defined_struct(node) private
- #definition_path_hierarchy_roots private
- #filename_good?(basename) ⇒ Boolean private
- #find_class_or_module(node, namespace) private
- #find_definition(node) private
- #for_bad_filename(file_path) private
- #match?(expected) ⇒ Boolean private
- #match_acronym?(expected, name) ⇒ Boolean private
- #match_namespace(node, namespace, expected) private
- #matching_class?(file_name) ⇒ Boolean private
- #matching_definition?(file_path) ⇒ Boolean private
- #no_definition_message(basename, file_path) private
- #other_message(basename) private
- #partial_matcher!(expected) private
- #perform_class_and_module_naming_checks(file_path, basename) private
- #regex private
- #to_module_name(basename) private
- #to_namespace(path) private
::RuboCop::Cop::Base
- Inherited
#add_global_offense | Adds an offense that has no particular location. |
#add_offense | Adds an offense on the specified range (or node with an expression) Unless that offense is disabled for this range, a corrector will be yielded to provide the cop the opportunity to autocorrect the offense. |
#begin_investigation | Called before any investigation. |
#callbacks_needed, | |
#cop_config | Configuration Helpers. |
#cop_name, #excluded_file?, | |
#external_dependency_checksum | This method should be overridden when a cop’s behavior depends on state that lives outside of these locations: |
#inspect, | |
#message | Gets called if no message is specified when calling |
#name | Alias for Base#cop_name. |
#offenses, | |
#on_investigation_end | Called after all on_… |
#on_new_investigation | Called before all on_… |
#on_other_file | Called instead of all on_… |
#parse | There should be very limited reasons for a Cop to do it’s own parsing. |
#parser_engine, | |
#ready | Called between investigations. |
#relevant_file?, #target_rails_version, #target_ruby_version, #annotate, #apply_correction, #attempt_correction, | |
#callback_argument | Reserved for Cop::Cop. |
#complete_investigation | Called to complete an investigation. |
#correct, #current_corrector, | |
#current_offense_locations | Reserved for Commissioner: |
#current_offenses, #currently_disabled_lines, #custom_severity, #default_severity, #disable_uncorrectable, #enabled_line?, #file_name_matches_any?, #find_message, #find_severity, #range_for_original, #range_from_node_or_range, | |
#reset_investigation | Actually private methods. |
#use_corrector |
::RuboCop::Cop::AutocorrectLogic
- Included
#disable_offense, #disable_offense_at_end_of_line, #disable_offense_before_and_after, #disable_offense_with_eol_or_surround_comment, #max_line_length, | |
#range_by_lines | Expand the given range to include all of any lines it covers. |
#range_of_first_line, #range_overlaps_offense?, #string_continuation, #string_continuation?, #surrounding_heredoc, #surrounding_percent_array |
::RuboCop::Cop::IgnoredNode
- Included
Constructor Details
This class inherits a constructor from RuboCop::Cop::Base
Instance Attribute Details
#bad_filename_allowed? ⇒ Boolean
(readonly, private)
[ GitHub ]
# File 'lib/rubocop/cop/naming/file_name.rb', line 94
def bad_filename_allowed? ignore_executable_scripts? && processed_source.start_with?('#!') end
#check_definition_path_hierarchy? ⇒ Boolean
(readonly, private)
[ GitHub ]
# File 'lib/rubocop/cop/naming/file_name.rb', line 120
def check_definition_path_hierarchy? cop_config['CheckDefinitionPathHierarchy'] end
#expect_matching_definition? ⇒ Boolean
(readonly, private)
[ GitHub ]
# File 'lib/rubocop/cop/naming/file_name.rb', line 116
def expect_matching_definition? cop_config['ExpectMatchingDefinition'] end
#ignore_executable_scripts? ⇒ Boolean
(readonly, private)
[ GitHub ]
# File 'lib/rubocop/cop/naming/file_name.rb', line 112
def ignore_executable_scripts? cop_config['IgnoreExecutableScripts'] end
Instance Method Details
#allowed_acronyms (private)
[ GitHub ]# File 'lib/rubocop/cop/naming/file_name.rb', line 132
def allowed_acronyms cop_config['AllowedAcronyms'] || [] end
#defined_struct(node) (private)
[ GitHub ]# File 'lib/rubocop/cop/naming/file_name.rb', line 167
def defined_struct(node) namespace, name = *struct_definition(node) s(:const, namespace, name) if name end
#definition_path_hierarchy_roots (private)
[ GitHub ]# File 'lib/rubocop/cop/naming/file_name.rb', line 124
def definition_path_hierarchy_roots cop_config['CheckDefinitionPathHierarchyRoots'] || [] end
#filename_good?(basename) ⇒ Boolean
(private)
# File 'lib/rubocop/cop/naming/file_name.rb', line 136
def filename_good?(basename) basename = basename.delete_prefix('.') basename = basename.sub(/\.[^.]+$/, '') # special handling for Action Pack Variants file names like # some_file.xlsx+mobile.axlsx basename = basename.sub('+', '_') basename.match?(regex || SNAKE_CASE) end
#find_class_or_module(node, namespace) (private)
[ GitHub ]# File 'lib/rubocop/cop/naming/file_name.rb', line 145
def find_class_or_module(node, namespace) return nil unless node name = namespace.pop on_node(%i[class module casgn], node) do |child| next unless (const = find_definition(child)) const_namespace, const_name = *const next if name != const_name && !match_acronym?(name, const_name) next unless namespace.empty? || match_namespace(child, const_namespace, namespace) return node end nil end
#find_definition(node) (private)
[ GitHub ]# File 'lib/rubocop/cop/naming/file_name.rb', line 163
def find_definition(node) node.defined_module || defined_struct(node) end
#for_bad_filename(file_path) (private)
[ GitHub ]# File 'lib/rubocop/cop/naming/file_name.rb', line 63
def for_bad_filename(file_path) basename = File.basename(file_path) if filename_good?(basename) msg = perform_class_and_module_naming_checks(file_path, basename) else msg = (basename) unless bad_filename_allowed? end add_global_offense(msg) if msg end
#match?(expected) ⇒ Boolean
(private)
# File 'lib/rubocop/cop/naming/file_name.rb', line 200
def match?(expected) expected.empty? || expected == [:Object] end
#match_acronym?(expected, name) ⇒ Boolean
(private)
# File 'lib/rubocop/cop/naming/file_name.rb', line 204
def match_acronym?(expected, name) expected = expected.to_s name = name.to_s allowed_acronyms.any? { |acronym| expected.gsub(acronym.capitalize, acronym) == name } end
#match_namespace(node, namespace, expected) (private)
[ GitHub ]# File 'lib/rubocop/cop/naming/file_name.rb', line 172
def match_namespace(node, namespace, expected) match_partial = partial_matcher!(expected) match_partial.call(namespace) node.each_ancestor(:class, :module, :sclass, :casgn) do |ancestor| return false if ancestor.sclass_type? match_partial.call(ancestor.defined_module) end match?(expected) end
#matching_class?(file_name) ⇒ Boolean
(private)
# File 'lib/rubocop/cop/naming/file_name.rb', line 90
def matching_class?(file_name) find_class_or_module(processed_source.ast, to_namespace(file_name)) end
#matching_definition?(file_path) ⇒ Boolean
(private)
# File 'lib/rubocop/cop/naming/file_name.rb', line 86
def matching_definition?(file_path) find_class_or_module(processed_source.ast, to_namespace(file_path)) end
#no_definition_message(basename, file_path) (private)
[ GitHub ]# File 'lib/rubocop/cop/naming/file_name.rb', line 98
def (basename, file_path) format(MSG_NO_DEFINITION, basename: basename, namespace: to_namespace(file_path).join('::')) end
#on_new_investigation
[ GitHub ]# File 'lib/rubocop/cop/naming/file_name.rb', line 54
def on_new_investigation file_path = processed_source.file_path return if config.file_to_exclude?(file_path) || config.allowed_camel_case_file?(file_path) for_bad_filename(file_path) end
#other_message(basename) (private)
[ GitHub ]# File 'lib/rubocop/cop/naming/file_name.rb', line 104
def (basename) if regex format(MSG_REGEX, basename: basename, regex: regex) else format(MSG_SNAKE_CASE, basename: basename) end end
#partial_matcher!(expected) (private)
[ GitHub ]# File 'lib/rubocop/cop/naming/file_name.rb', line 186
def partial_matcher!(expected) lambda do |namespace| while namespace return match?(expected) if namespace.cbase_type? namespace, name = *namespace expected.pop if name == expected.last || match_acronym?(expected.last, name) end false end end
#perform_class_and_module_naming_checks(file_path, basename) (private)
[ GitHub ]# File 'lib/rubocop/cop/naming/file_name.rb', line 75
def perform_class_and_module_naming_checks(file_path, basename) return unless expect_matching_definition? if check_definition_path_hierarchy? && !matching_definition?(file_path) msg = (basename, file_path) elsif !matching_class?(basename) msg = (basename, basename) end msg end
#regex (private)
[ GitHub ]# File 'lib/rubocop/cop/naming/file_name.rb', line 128
def regex cop_config['Regex'] end
#struct_definition(node)
[ GitHub ]# File 'lib/rubocop/cop/naming/file_name.rb', line 47
def_node_matcher :struct_definition, <<~PATTERN { (casgn $_ $_ (send (const {nil? cbase} :Struct) :new ...)) (casgn $_ $_ (block (send (const {nil? cbase} :Struct) :new ...) ...)) } PATTERN
#to_module_name(basename) (private)
[ GitHub ]# File 'lib/rubocop/cop/naming/file_name.rb', line 238
def to_module_name(basename) words = basename.sub(/\..*/, '').split('_') words.map(&:capitalize).join.to_sym end
#to_namespace(path) (private)
[ GitHub ]# File 'lib/rubocop/cop/naming/file_name.rb', line 211
def to_namespace(path) # rubocop:disable Metrics/AbcSize components = Pathname(path).each_filename.to_a # To convert a pathname to a Ruby namespace, we need a starting point # But RC can be run from any working directory, and can check any path # We can't assume that the working directory, or any other, is the # "starting point" to build a namespace. start = definition_path_hierarchy_roots start_index = nil # To find the closest namespace root take the path components, and # then work through them backwards until we find a candidate. This # makes sure we work from the actual root in the case of a path like # /home/user/src/project_name/lib. components.reverse.each_with_index do |c, i| if start.include?(c) start_index = components.size - i break end end if start_index.nil? [to_module_name(components.last)] else components[start_index..].map { |c| to_module_name(c) } end end