# frozen_string_literal: truerequire'pathname'moduleRuboCopmoduleCopmoduleNaming# This cop 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`.## @example# # bad# lib/layoutManager.rb## anything/usingCamelCase## # good# lib/layout_manager.rb## anything/using_snake_case.rakeclassFileName<BaseincludeRangeHelpMSG_SNAKE_CASE='The name of this source file (`%<basename>s`) '\'should use snake_case.'MSG_NO_DEFINITION='%<basename>s should define a class or module '\'called `%<namespace>s`.'MSG_REGEX='`%<basename>s` should match `%<regex>s`.'SNAKE_CASE=/^[\d[[:lower:]]_.?!]+$/.freezedefon_new_investigationfile_path=processed_source.file_pathreturnifconfig.file_to_exclude?(file_path)||config.allowed_camel_case_file?(file_path)for_bad_filename(file_path)do|range,msg|add_offense(range,message: msg)endendprivatedeffor_bad_filename(file_path)basename=File.basename(file_path)iffilename_good?(basename)msg=perform_class_and_module_naming_checks(file_path,basename)elsemsg=other_message(basename)unlessbad_filename_allowed?endyieldsource_range(processed_source.buffer,1,0),msgifmsgenddefperform_class_and_module_naming_checks(file_path,basename)returnunlessexpect_matching_definition?ifcheck_definition_path_hierarchy?&&!matching_definition?(file_path)msg=no_definition_message(basename,file_path)elsif!matching_class?(basename)msg=no_definition_message(basename,basename)endmsgenddefmatching_definition?(file_path)find_class_or_module(processed_source.ast,to_namespace(file_path))enddefmatching_class?(file_name)find_class_or_module(processed_source.ast,to_namespace(file_name))enddefbad_filename_allowed?ignore_executable_scripts?&&processed_source.start_with?('#!')enddefno_definition_message(basename,file_path)format(MSG_NO_DEFINITION,basename: basename,namespace: to_namespace(file_path).join('::'))enddefother_message(basename)ifregexformat(MSG_REGEX,basename: basename,regex: regex)elseformat(MSG_SNAKE_CASE,basename: basename)endenddefignore_executable_scripts?cop_config['IgnoreExecutableScripts']enddefexpect_matching_definition?cop_config['ExpectMatchingDefinition']enddefcheck_definition_path_hierarchy?cop_config['CheckDefinitionPathHierarchy']enddefregexcop_config['Regex']enddefallowed_acronymscop_config['AllowedAcronyms']||[]enddeffilename_good?(basename)basename=basename.sub(/^\./,'')basename=basename.sub(/\.[^.]+$/,'')# special handling for Action Pack Variants file names like# some_file.xlsx+mobile.axlsxbasename=basename.sub('+','_')basename.match?(regex||SNAKE_CASE)enddeffind_class_or_module(node,namespace)returnnilunlessnodename=namespace.popon_node(%i[class module casgn],node)do|child|nextunless(const=child.defined_module)const_namespace,const_name=*constnextifname!=const_name&&!match_acronym?(name,const_name)nextunlessnamespace.empty?||match_namespace(child,const_namespace,namespace)returnnodeendnilenddefmatch_namespace(node,namespace,expected)match_partial=partial_matcher!(expected)match_partial.call(namespace)node.each_ancestor(:class,:module,:sclass,:casgn)do|ancestor|returnfalseifancestor.sclass_type?match_partial.call(ancestor.defined_module)endmatch?(expected)enddefpartial_matcher!(expected)lambdado|namespace|whilenamespacereturnmatch?(expected)ifnamespace.cbase_type?namespace,name=*namespaceexpected.popifname==expected.last||match_acronym?(expected.last,name)endfalseendenddefmatch?(expected)expected.empty?||expected==[:Object]enddefmatch_acronym?(expected,name)expected=expected.to_sname=name.to_sallowed_acronyms.any?do|acronym|expected.gsub(acronym.capitalize,acronym)==nameendenddefto_namespace(path)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=%w[lib spec test src]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_indexdo|c,i|ifstart.include?(c)start_index=components.size-ibreakendendifstart_index.nil?[to_module_name(components.last)]elsecomponents[start_index..-1].map{|c|to_module_name(c)}endenddefto_module_name(basename)words=basename.sub(/\..*/,'').split('_')words.map(&:capitalize).join.to_symendendendendend