-
-
Notifications
You must be signed in to change notification settings - Fork 277
/
file_path.rb
151 lines (128 loc) · 4.42 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
# 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`.'
def_node_matcher :const_described, <<~PATTERN
(block
$(send #rspec? _example_group $(const ...) $...) ...
)
PATTERN
def_node_search :routing_metadata?, '(pair (sym :type) (sym :routing))'
def on_top_level_example_group(node)
return unless top_level_groups.one?
const_described(node) do |send_node, described_class, arguments|
next if routing_spec?(arguments)
ensure_correct_file_path(send_node, described_class, arguments)
end
end
private
def ensure_correct_file_path(send_node, described_class, arguments)
glob = glob_for(described_class, arguments.first)
return if filename_ends_with?(glob)
add_offense(send_node, message: format(MSG, suffix: glob))
end
def routing_spec?(args)
args.any?(&method(:routing_metadata?))
end
def glob_for(described_class, method_name)
return glob_for_spec_suffix_only? if spec_suffix_only?
"#{expected_path(described_class)}#{name_glob(method_name)}*_spec.rb"
end
def glob_for_spec_suffix_only?
'*_spec.rb'
end
def name_glob(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)
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?(glob)
filename =
RuboCop::PathUtil.relative_path(processed_source.buffer.name)
.gsub('../', '')
File.fnmatch?("*#{glob}", filename)
end
def relevant_rubocop_rspec_file?(_file)
true
end
def spec_suffix_only?
cop_config['SpecSuffixOnly']
end
end
end
end
end