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
|
# frozen_string_literal: true
module Tooling
module Danger
module Specs
SPEC_FILES_REGEX = 'spec/'
EE_PREFIX = 'ee/'
MATCH_WITH_ARRAY_REGEX = /(?<to>to\(?\s*)(?<matcher>match|eq)(?<expectation>[( ]?\[[^\]]+)/.freeze
MATCH_WITH_ARRAY_REPLACEMENT = '\k<to>match_array\k<expectation>'
PROJECT_FACTORIES = %w[
:project
:project_empty_repo
:forked_project_with_submodules
:project_with_design
].freeze
PROJECT_FACTORY_REGEX = /
^\+? # Start of the line, which may or may not have a `+`
(?<head>\s*) # 0-many leading whitespace captured in a group named head
let!? # Literal `let` which may or may not end in `!`
(?<tail> # capture group named tail
\([^)]+\) # Two parenthesis with any non-parenthesis characters between them
\s*\{\s* # Opening curly brace surrounded by 0-many whitespace characters
create\( # literal
(?:#{PROJECT_FACTORIES.join('|')}) # Any of the project factory names
\W # Non-word character, avoid matching factories like :project_authorization
) # end capture group named tail
/x.freeze
PROJECT_FACTORY_REPLACEMENT = '\k<head>let_it_be\k<tail>'
SUGGESTION_MARKDOWN = <<~SUGGESTION_MARKDOWN
```suggestion
%<suggested_line>s
```
SUGGESTION_MARKDOWN
MATCH_WITH_ARRAY_SUGGESTION = <<~SUGGEST_COMMENT
If order of the result is not important, please consider using `match_array` to avoid flakiness.
SUGGEST_COMMENT
PROJECT_FACTORY_SUGGESTION = <<~SUGGEST_COMMENT
Project creations are very slow. Use `let_it_be`, `build` or `build_stubbed` if possible.
See [testing best practices](https://docs.gitlab.com/ee/development/testing_guide/best_practices.html#optimize-factory-usage)
for background information and alternative options.
SUGGEST_COMMENT
FEATURE_CATEGORY_REGEX = /^\+.?RSpec\.describe(.+)(?!feature_category)/.freeze
FEATURE_CATEGORY_SUGGESTION = <<~SUGGESTION_MARKDOWN
Consider adding `feature_category: <feature_category_name>` for this example if it is not set already.
See [testing best practices](https://docs.gitlab.com/ee/development/testing_guide/best_practices.html#feature-category-metadata).
SUGGESTION_MARKDOWN
FEATURE_CATEGORY_EXCLUDE = 'feature_category'
def changed_specs_files(ee: :include)
changed_files = helper.all_changed_files
folder_prefix =
case ee
when :include
"(#{EE_PREFIX})?"
when :only
EE_PREFIX
when :exclude
nil
end
changed_files.grep(%r{\A#{folder_prefix}#{SPEC_FILES_REGEX}})
end
def add_suggestions_for_match_with_array(filename)
add_suggestion(
filename,
MATCH_WITH_ARRAY_REGEX,
MATCH_WITH_ARRAY_SUGGESTION,
MATCH_WITH_ARRAY_REPLACEMENT
)
end
def add_suggestions_for_project_factory_usage(filename)
add_suggestion(
filename,
PROJECT_FACTORY_REGEX,
PROJECT_FACTORY_SUGGESTION,
PROJECT_FACTORY_REPLACEMENT
)
end
def add_suggestions_for_feature_category(filename)
add_suggestion(
filename,
FEATURE_CATEGORY_REGEX,
FEATURE_CATEGORY_SUGGESTION,
nil,
FEATURE_CATEGORY_EXCLUDE
)
end
private
def added_lines_matching(filename, regex)
helper.changed_lines(filename).grep(/\A\+( )?/).grep(regex)
end
def add_suggestion(filename, regex, comment_text, replacement = nil, exclude = nil)
added_lines = added_lines_matching(filename, regex)
return if added_lines.empty?
spec_file_lines = project_helper.file_lines(filename)
added_lines.each_with_object([]) do |added_line, processed_line_numbers|
line_number = find_line_number(spec_file_lines, added_line.delete_prefix('+'), exclude_indexes: processed_line_numbers)
next unless line_number
next if !exclude.nil? && added_line.include?(exclude)
processed_line_numbers << line_number
suggested_line = spec_file_lines[line_number]
suggested_line = suggested_line.gsub(regex, replacement) unless replacement.nil?
text = format(comment(comment_text), suggested_line: suggested_line)
markdown(text, file: filename, line: line_number.succ)
end
end
def comment(comment_text)
<<~COMMENT_BODY.chomp
#{SUGGESTION_MARKDOWN}
#{comment_text}
COMMENT_BODY
end
def find_line_number(file_lines, searched_line, exclude_indexes: [])
_, index = file_lines.each_with_index.find do |file_line, index|
file_line == searched_line && !exclude_indexes.include?(index)
end
index
end
end
end
end
|