-
-
Notifications
You must be signed in to change notification settings - Fork 269
/
file_path.rb
169 lines (145 loc) · 5.09 KB
/
file_path.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# frozen_string_literal: true
module RuboCop
module Cop
module RSpec
# Checks that spec file paths are consistent and well-formed.
#
# By default, this checks that spec file paths are consistent with the
# test subject and and enforces that it reflects the described
# class/module and its optionally called out method.
#
# With the configuration option `IgnoreMethods` the called out method will
# be ignored when determining the enforced path.
#
# With the configuration option `CustomTransform` modules or classes can
# be specified that should not as usual be transformed from CamelCase to
# snake_case (e.g. 'RuboCop' => 'rubocop' ).
#
# With the configuration option `SpecSuffixOnly` test files will only
# be checked to ensure they end in '_spec.rb'. This option disables
# checking for consistency in the test subject or test methods.
#
# @example
# # bad
# whatever_spec.rb # describe MyClass
#
# # bad
# my_class_spec.rb # describe MyClass, '#method'
#
# # good
# my_class_spec.rb # describe MyClass
#
# # good
# my_class_method_spec.rb # describe MyClass, '#method'
#
# # good
# my_class/method_spec.rb # describe MyClass, '#method'
#
# @example when configuration is `IgnoreMethods: true`
# # bad
# whatever_spec.rb # describe MyClass
#
# # good
# my_class_spec.rb # describe MyClass
#
# # good
# my_class_spec.rb # describe MyClass, '#method'
#
# @example when configuration is `SpecSuffixOnly: true`
# # good
# whatever_spec.rb # describe MyClass
#
# # good
# my_class_spec.rb # describe MyClass
#
# # good
# my_class_spec.rb # describe MyClass, '#method'
#
class FilePath < Base
include TopLevelGroup
MSG = 'Spec path should end with `%<suffix>s`.'
# @!method example_group(node)
def_node_matcher :example_group, <<~PATTERN
(block
$(send #rspec? _example_group $_ $...) ...
)
PATTERN
# @!method routing_metadata?(node)
def_node_search :routing_metadata?, '(pair (sym :type) (sym :routing))'
def on_top_level_example_group(node)
return unless top_level_groups.one?
example_group(node) do |send_node, example_group, arguments|
next if routing_spec?(arguments)
ensure_correct_file_path(send_node, example_group, arguments)
end
end
private
def ensure_correct_file_path(send_node, example_group, arguments)
pattern = pattern_for(example_group, arguments.first)
return if filename_ends_with?(pattern)
# For the suffix shown in the offense message, modify the regular
# expression pattern to resemble a glob pattern for clearer error
# messages.
offense_suffix = pattern.gsub('.*', '*').sub('[^/]', '')
.sub('\.', '.')
add_offense(send_node, message: format(MSG, suffix: offense_suffix))
end
def routing_spec?(args)
args.any?(&method(:routing_metadata?))
end
def pattern_for(example_group, method_name)
if spec_suffix_only? || !example_group.const_type?
return pattern_for_spec_suffix_only?
end
[
expected_path(example_group),
name_pattern(method_name),
'[^/]*_spec\.rb'
].join
end
def pattern_for_spec_suffix_only?
'.*_spec\.rb'
end
def name_pattern(method_name)
return unless method_name&.str_type?
".*#{method_name.str_content.gsub(/\W/, '')}" unless ignore_methods?
end
def expected_path(constant)
File.join(
constant.const_name.split('::').map do |name|
custom_transform.fetch(name) { camel_to_snake_case(name) }
end
)
end
def camel_to_snake_case(string)
if defined?(ActiveSupport::Inflector)
if File.exist?('./config/initializers/inflections.rb')
require './config/initializers/inflections'
end
return ActiveSupport::Inflector.underscore(string)
end
string
.gsub(/([^A-Z])([A-Z]+)/, '\1_\2')
.gsub(/([A-Z])([A-Z][^A-Z\d]+)/, '\1_\2')
.downcase
end
def custom_transform
cop_config.fetch('CustomTransform', {})
end
def ignore_methods?
cop_config['IgnoreMethods']
end
def filename_ends_with?(pattern)
filename = File.expand_path(processed_source.buffer.name)
filename.match?("#{pattern}$")
end
def relevant_rubocop_rspec_file?(_file)
true
end
def spec_suffix_only?
cop_config['SpecSuffixOnly']
end
end
end
end
end