From 308f206a8c75cc332c7248bf4e2ec484cabeeaab Mon Sep 17 00:00:00 2001 From: Alex Dowad Date: Tue, 29 Dec 2015 23:02:37 +0200 Subject: [PATCH] [Fix #1545] Add Regex config parameter to Style/FileName --- CHANGELOG.md | 1 + config/default.yml | 6 ++++- lib/rubocop/cop/style/file_name.rb | 21 +++++++++++++----- spec/rubocop/cop/style/file_name_spec.rb | 28 +++++++++++++++++++++++- 4 files changed, 49 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 336204afc42a..d57c0cbb5e81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,7 @@ * `Style/SymbolArray` has both `percent` and `brackets` (which enforces the user of bracketed arrays for symbols) styles. ([@alexdowad][]) * [#2343](https://github.com/bbatsov/rubocop/issues/2343): Entire cop types (or "departments") can be disabled using in .rubocop.yml using config like `Style: Enabled: false`. ([@alexdowad][]) * [#2399](https://github.com/bbatsov/rubocop/issues/2399): New `start_of_line` style for `Lint/EndAlignment` aligns a closing `end` keyword with the start of the line where the opening keyword appears. ([@alexdowad][]) +* [#1545](https://github.com/bbatsov/rubocop/issues/1545): New `Regex` config parameter for `Style/FileName` allows user to provide their own regex for validating file names. ([@alexdowad][]) ### Bug Fixes diff --git a/config/default.yml b/config/default.yml index c829ad234f81..96542fb64058 100644 --- a/config/default.yml +++ b/config/default.yml @@ -423,7 +423,11 @@ Style/FileName: # It further expects it to be nested inside modules which match the names # of subdirectories in its path. ExpectMatchingDefinition: false - + # If non-nil, expect all source file names to match the following regex. + # Only the file name itself is matched, not the entire file path. + # Use anchors as necessary if you want to match the entire name rather than + # just a part of it. + Regex: ~ # With `IgnoreExecutableScripts` set to `true`, this cop does not # report offending filenames for executable scripts (i.e. source # files with a shebang in the first line). diff --git a/lib/rubocop/cop/style/file_name.rb b/lib/rubocop/cop/style/file_name.rb index 1a10d7de3915..0b1d04be43be 100644 --- a/lib/rubocop/cop/style/file_name.rb +++ b/lib/rubocop/cop/style/file_name.rb @@ -11,6 +11,7 @@ module Style class FileName < Cop MSG_SNAKE_CASE = 'Use snake_case for source file names.' MSG_NO_DEFINITION = '%s should define a class or module called `%s`.' + MSG_REGEX = '`%s` should match `%s`.' SNAKE_CASE = /^[\da-z_]+$/ @@ -18,24 +19,25 @@ def investigate(processed_source) file_path = processed_source.buffer.name return if config.file_to_include?(file_path) - basename = File.basename(file_path).sub(/\.[^\.]+$/, '') - if snake_case?(basename) + basename = File.basename(file_path) + if filename_good?(basename) return unless expect_matching_definition? return if find_class_or_module(processed_source.ast, to_namespace(file_path)) range = source_range(processed_source.buffer, 1, 0) msg = format(MSG_NO_DEFINITION, - File.basename(file_path), + basename, to_namespace(file_path).join('::')) - add_offense(nil, range, msg) else first_line = processed_source.lines.first return if cop_config['IgnoreExecutableScripts'] && shebang?(first_line) range = source_range(processed_source.buffer, 1, 0) - add_offense(nil, range, MSG_SNAKE_CASE) + msg = regex ? format(MSG_REGEX, basename, regex) : MSG_SNAKE_CASE end + + add_offense(nil, range, msg) end private @@ -52,6 +54,15 @@ def expect_matching_definition? cop_config['ExpectMatchingDefinition'] end + def regex + cop_config['Regex'] + end + + def filename_good?(basename) + basename = basename.sub(/\.[^\.]+$/, '') + regex ? basename =~ regex : snake_case?(basename) + end + def find_class_or_module(node, namespace) name = namespace.pop diff --git a/spec/rubocop/cop/style/file_name_spec.rb b/spec/rubocop/cop/style/file_name_spec.rb index e8d05d46c3e5..c13f952d1f47 100644 --- a/spec/rubocop/cop/style/file_name_spec.rb +++ b/spec/rubocop/cop/style/file_name_spec.rb @@ -13,7 +13,11 @@ ) end let(:cop_config) do - { 'IgnoreExecutableScripts' => true, 'ExpectMatchingDefinition' => false } + { + 'IgnoreExecutableScripts' => true, + 'ExpectMatchingDefinition' => false, + 'Regex' => nil + } end let(:includes) { [] } @@ -220,4 +224,26 @@ include_examples 'matching module or class' end end + + context 'when Regex is set' do + let(:cop_config) { { 'Regex' => /\A[aeiou]\z/i } } + + context 'with a matching name' do + let(:filename) { 'a.rb' } + + it 'does not register an offense' do + expect(cop.offenses).to be_empty + end + end + + context 'with a non-matching name' do + let(:filename) { 'z.rb' } + + it 'registers an offense' do + expect(cop.offenses.size).to eq(1) + expect(cop.messages).to eq( + ['`z.rb` should match `(?i-mx:\\A[aeiou]\\z)`.']) + end + end + end end