danger-logging_lint 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,297 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Danger
4
+ # This danger plugin can be used to check log lines in modified (added) files. It heavily relies on regex
5
+ # configuration which can be modified to search all kinds of parts of code in the files. Default configuration is set
6
+ # to support Kotlin eMan Logger Library https://github.com/eManPrague/logger-ktx. Ex: logInfo { "Info message $var" }.
7
+ #
8
+ # It works in two steps. First it searches for all log lines (multilines) in files. And then it applies line variable
9
+ # regex combined with line remove regex. Check [check_files] function for more information.
10
+ #
11
+ # @example Log linter with its basic configuration (searches for logInfo { "Message with $var" } and it's combinations)
12
+ #
13
+ # logging_lint.log_lint
14
+ #
15
+ # @example Log linter with multiple log functions
16
+ #
17
+ # # Linting multiple log functions
18
+ # logging_lint.log_functions = ["logInfo", "logWarn", "logError"]
19
+ # logging_lint.log_lint
20
+ #
21
+ # @example Log linter with completely custom functionality
22
+ #
23
+ # # Linting only kotlin files (extensions without dot or star)
24
+ # logging_lint.file_extensions = ["kt"]
25
+ # # Linting multiple log functions
26
+ # logging_lint.log_functions = ["logInfo", "logWarn", "logError"]
27
+ # # Custom warning text and description
28
+ # logging_lint.warning_text = "You should really check this!"
29
+ # logging_lint.warning_description = "May be a security issue. Check this link: ...."
30
+ # # Custom log regex (searches for "foo $ bar")
31
+ # logging_lint.log_regex = '(\".*\$.*\")'
32
+ # # Custom log variable regex (searches for "$" and "${message}" in the log)
33
+ # logging_lint.line_variable_regex = ['\$', '${message}']
34
+ # # Custom log remove regex (removes nothing from the log lines)
35
+ # logging_lint.line_remove_regex = []
36
+ # # Marks start of the log when variable was found in it
37
+ # logging_lint.line_index_position = "start"
38
+ # logging_lint.log_lint
39
+ #
40
+ # @see eManPrague/danger-logging_lint
41
+ # @tags log, logging, security, lint, kotlin
42
+ #
43
+ class DangerLoggingLint < Plugin
44
+ DEFAULT_LOG_FUNCTIONS = %w(logInfo).freeze
45
+ DEFAULT_LOG_REGEX = '[ ]?[{(](?:\n?|.)["]?(?:\n?|.)["]?(?:\n?|.)+(?:[)}][ ]?\n)'
46
+ DEFAULT_LINE_VARIABLE_REGEX = ['[{(](\n| |\+)*([^\"]\w[^\"])+', '(\".*\$.*\")'].freeze
47
+ DEFAULT_LINE_REMOVE_REGEX = ['(\+ )?\".*\"'].freeze
48
+ DEFAULT_WARNING_TEXT = "Does this log comply with security rules?"
49
+
50
+ #
51
+ # File extensions are used to limit the number of files checked based on their extension. For example for Kotlin
52
+ # language we want to check only .kt files and no other.
53
+ #
54
+ # This variable is optional. When it is not set the plugin will automatically check all files.
55
+ #
56
+ # @return [Array<String>] with file extensions
57
+ #
58
+ attr_accessor :file_extensions
59
+
60
+ #
61
+ # Warning description can be used to extend warning text. It can be used to provide more context for the log warning
62
+ # such as more description, link with security rules and other.
63
+ #
64
+ # @return [String] with warning description
65
+ #
66
+ attr_accessor :warning_description
67
+
68
+ #
69
+ # Unfortunately due to line modification in function `contains_variable` it is not possible to accurately pinpoint
70
+ # variable in the log. That is why there are three options for the offset to identity the line.
71
+ #
72
+ # Options are (set by `line_index_position`):
73
+ # - "start" which means 0 offset and start of the log,
74
+ # - "middle" which means length of the log divided by two,
75
+ # - else ("end") which means length - 1 and end of the log. Used by default.
76
+ #
77
+ # @return [String] with line index position
78
+ #
79
+ attr_accessor :line_index_position
80
+
81
+ #
82
+ # Log functions are functions which define logging. They usually identify logging function that is being used. For
83
+ # example logInfo, logWarn or logError. Each of these values is checked in a file combined with log_regex.
84
+ #
85
+ # @return [Array<String>] with log functions
86
+ #
87
+ attr_writer :log_functions
88
+
89
+ #
90
+ # Gets `log_functions` array from configuration or default `DEFAULT_LOG_FUNCTIONS` when null.
91
+ #
92
+ # @return [Array<String>] with log functions
93
+ #
94
+ def log_functions
95
+ return DEFAULT_LOG_FUNCTIONS if @log_functions.nil?
96
+
97
+ @log_functions
98
+ end
99
+
100
+ #
101
+ # Warning text is used to modify the text displayed in the Danger report. It is a message with which the Danger
102
+ # warning for specific log is created.
103
+ #
104
+ # @return [String] with warning text
105
+ #
106
+ attr_writer :warning_text
107
+
108
+ #
109
+ # Gets `warning_text` string from configuration or default `DEFAULT_WARNING_TEXT` when null.
110
+ #
111
+ # @return [String] with warning text
112
+ #
113
+ def warning_text
114
+ return DEFAULT_WARNING_TEXT if @warning_text.nil?
115
+
116
+ @warning_text
117
+ end
118
+
119
+ #
120
+ # This regex is used to search for all log lines in a file. It does not check if there are variables in it. It just
121
+ # searches for all logs. These results are used later to filter in them.
122
+ #
123
+ # @return [String] with log regex
124
+ #
125
+ attr_writer :log_regex
126
+
127
+ #
128
+ # Gets `log_regex` string from configuration or default `DEFAULT_LOG_REGEX` when null.
129
+ #
130
+ # @return [String] with log regex
131
+ #
132
+ def log_regex
133
+ return DEFAULT_LOG_REGEX if @log_regex.nil?
134
+
135
+ @log_regex
136
+ end
137
+
138
+ #
139
+ # This regex is used to check log lines for variables. Since it is not always possible to find all variables using
140
+ # one single regex it is represented as an array.
141
+ #
142
+ # This array cannot be null or empty for the script to function.
143
+ #
144
+ # @return [Array<String>] with regex array
145
+ #
146
+ attr_writer :line_variable_regex
147
+
148
+ #
149
+ # Gets `line_variable_regex` array from configuration or default `DEFAULT_LINE_VARIABLE_REGEX` when null or empty.
150
+ #
151
+ # @return [Array<String>] with regex array
152
+ #
153
+ def line_variable_regex
154
+ return DEFAULT_LINE_VARIABLE_REGEX if @line_variable_regex.nil? || @line_variable_regex.size <= 0
155
+
156
+ @line_variable_regex
157
+ end
158
+
159
+ #
160
+ # This regex is used to clear the log line before variable regex is applied. It allows us to clear values that would
161
+ # interfere with variable searching.
162
+ #
163
+ # This array cannot be null but it can be empty for this script to function.
164
+ #
165
+ # @return [Array<String>] with regex array
166
+ #
167
+ attr_writer :line_remove_regex
168
+
169
+ #
170
+ # Gets `line_remove_regex` array from configuration or default `DEFAULT_LINE_REMOVE_REGEX` when null or empty.
171
+ #
172
+ # @return [Array<String>] with regex array
173
+ #
174
+ def line_remove_regex
175
+ return DEFAULT_LINE_REMOVE_REGEX if @line_remove_regex.nil?
176
+
177
+ @line_remove_regex
178
+ end
179
+
180
+ #
181
+ # Triggers file linting on specific target files. But first it does few checks if it actually needs to run.
182
+ # 1) Checks if `log_functions` have size at least 1. If they are not then this script send Danger fail and cancels.
183
+ # 2) Checks if `line_variable_regex` have size at least 1. If they are not then this script send Danger fail and
184
+ # cancels.
185
+ # 3) Filters target files based on `file_extensions` and if there are no files to check it will send Danger message
186
+ # and cancels.
187
+ #
188
+ # If all of these checks pass then it will trigger linter on target files (filtered) using `check_files`.
189
+ #
190
+ # @return [void]
191
+ #
192
+ def log_lint
193
+ if log_functions.nil? || log_functions.size <= 0
194
+ self.fail("No log functions are defined. Please check your Danger file.")
195
+ return
196
+ end
197
+
198
+ if line_variable_regex.nil? || line_variable_regex.size <= 0
199
+ message("At least one variable index must be defined (using default). Please check your Danger file.")
200
+ end
201
+
202
+ target_files = (git.modified_files - git.deleted_files) + git.added_files
203
+ if !file_extensions.nil? && file_extensions.size >= 0
204
+ file_extensions_regex = "(.#{file_extensions.join('|.')})"
205
+ target_files = target_files.grep(/#{file_extensions_regex}/)
206
+ end
207
+
208
+ if target_files.empty?
209
+ message("No files to check.")
210
+ return
211
+ end
212
+
213
+ check_files(target_files)
214
+ end
215
+
216
+ #
217
+ # Checks all files for log violations based on log regex and log function. Each log function id extended by log
218
+ # regex and searched for (format: #log_function#log_regex). Each of such found line is then checked if it contains a
219
+ # variable. If it does it is warned with a specific line index and warning text. Uses Danger warn level with sticky
220
+ # option.
221
+ #
222
+ # @return [void]
223
+ #
224
+ def check_files(files)
225
+ raw_file = ""
226
+ files.each do |filename|
227
+ raw_file = File.read(filename)
228
+ log_functions.each do |log_function|
229
+ raw_file.scan(/#{log_function}#{log_regex}/m) do |c|
230
+ if contains_variable(c)
231
+ char_index = $~.offset(0)[0] + line_offset(c)
232
+ line_index = raw_file[0..char_index].lines.count
233
+ warn(compose_warning_text(warning_text), true, filename, line_index)
234
+ end
235
+ end
236
+ end
237
+ end
238
+ end
239
+
240
+ #
241
+ # Checks if log contains variable or not. Requires `line_variable_regex` variable to be configured. For each of this
242
+ # regex is searches value in `line_remove_regex`. If it is found then it is used to replace parts of the log using
243
+ # `gsub` function. It makes sure variable regex can be used on complex logs like `logInfo(\n"TEST"\n+ message\n)`.
244
+ # After cleaning it will try to match the variable regex in modified log.
245
+ #
246
+ # @return [Boolean] true if contains regex else false
247
+ #
248
+ def contains_variable(log)
249
+ line_variable_regex.each_with_index do |regex, index|
250
+ next if regex.nil?
251
+
252
+ log_temp = log
253
+ remove_regex = line_remove_regex[index]
254
+ unless remove_regex.nil?
255
+ log_temp = log.gsub(/#{remove_regex}/, "")
256
+ end
257
+ return true if log_temp.match?(regex)
258
+ end
259
+ false
260
+ end
261
+
262
+ #
263
+ # Calculates line offset which is used to identify line to danger. Unfortunately due to line modification in
264
+ # `contains_variable` it is not possible to accurately pinpoint variable in the log. That is why there are three
265
+ # options for the offset to identity the line.
266
+ #
267
+ # Options are (set by `line_index_position`):
268
+ # - "start" which means 0 offset and start of the log,
269
+ # - "middle" which means length of the log divided by two,
270
+ # - else ("end") which means length - 1 and end of the log.
271
+ #
272
+ # @return [Integer] offset based on `line_index_position` and line length
273
+ #
274
+ def line_offset(line)
275
+ case line_index_position
276
+ when "start"
277
+ 0
278
+ when "middle"
279
+ (line.length - 1) / 1
280
+ else
281
+ line.length - 1
282
+ end
283
+ end
284
+
285
+ #
286
+ # Composes warning text. If `warning_description` is defined it will return a combination with `warning_text` else
287
+ # it will return only `warning_text`.
288
+ #
289
+ # @return [String] with warning text (and description)
290
+ #
291
+ def compose_warning_text(warning_text)
292
+ return warning_text if warning_description.nil?
293
+
294
+ "#{warning_text} Check: #{warning_description}"
295
+ end
296
+ end
297
+ end
@@ -0,0 +1,112 @@
1
+ package cz.eman.logging.lint
2
+
3
+ class ModifiedFile {
4
+
5
+ fun runLogDebug() {
6
+ "Text to ignore modified $ignoredVar"
7
+
8
+ logDebug { "Log debug modified" }
9
+ logDebug("Log debug modified")
10
+ logDebug { "Log debug modified $var" }
11
+ logDebug("Log debug modified $var")
12
+ logDebug {
13
+ "Log debug modified"
14
+ }
15
+ logDebug(
16
+ "Log debug modified"
17
+ )
18
+ logDebug {
19
+ "Log debug modified $var"
20
+ }
21
+ logDebug(
22
+ "Log debug modified $var"
23
+ )
24
+ logDebug {
25
+ "Log debug modified"
26
+ +"Second line modified"
27
+ }
28
+ logDebug(
29
+ "Log debug modified"
30
+ + "Second line modified"
31
+ )
32
+ logDebug {
33
+ "Log debug modified"
34
+ +"Second line modified debug $var"
35
+ }
36
+ logDebug(
37
+ "Log debug modified"
38
+ + "Second line modified debug $var"
39
+ )
40
+ logDebug(
41
+ "Log debug modifies"
42
+ + message
43
+ )
44
+ logDebug(message.var)
45
+ logDebug(
46
+ message.var
47
+ )
48
+ logDebug {
49
+ "Log debug modifies"
50
+ + message
51
+ }
52
+ logDebug { message.var }
53
+ logDebug {
54
+ message.var
55
+ }
56
+ }
57
+
58
+ fun runLogInfo() {
59
+ "Text to ignore modified $ignoredVar"
60
+
61
+ logInfo { "Log info modified" }
62
+ logInfo("Log info modified")
63
+ logInfo { "Log info modified $var" }
64
+ logInfo("Log info modified $var")
65
+ logInfo {
66
+ "Log info modified"
67
+ }
68
+ logInfo(
69
+ "Log info modified"
70
+ )
71
+ logInfo {
72
+ "Log info modified $var"
73
+ }
74
+ logInfo(
75
+ "Log info modified $var"
76
+ )
77
+ logInfo {
78
+ "Log info modified"
79
+ +"Second line modified"
80
+ }
81
+ logInfo(
82
+ "Log info modified"
83
+ + "Second line modified"
84
+ )
85
+ logInfo {
86
+ "Log info modified"
87
+ +"Second line modified info $var"
88
+ }
89
+ logInfo(
90
+ "Log info modified"
91
+ + "Second line modified info $var"
92
+ )
93
+ logInfo(
94
+ "Log debug modifies"
95
+ + message
96
+ + "another line"
97
+ )
98
+ logInfo(message.var)
99
+ logInfo(
100
+ message.var
101
+ )
102
+ logInfo {
103
+ "Log debug modifies {some text}"
104
+ + message
105
+ + "another line"
106
+ }
107
+ logInfo { message.var }
108
+ logInfo {
109
+ message.var
110
+ }
111
+ }
112
+ }
@@ -0,0 +1,112 @@
1
+ package cz.eman.logging.lint
2
+
3
+ class ModifiedFile {
4
+
5
+ fun runLogDebug() {
6
+ "Text to ignore modified $ignoredVar"
7
+
8
+ logDebug { "Log debug modified" }
9
+ logDebug("Log debug modified")
10
+ logDebug { "Log debug modified $var" }
11
+ logDebug("Log debug modified $var")
12
+ logDebug {
13
+ "Log debug modified"
14
+ }
15
+ logDebug(
16
+ "Log debug modified"
17
+ )
18
+ logDebug {
19
+ "Log debug modified $var"
20
+ }
21
+ logDebug(
22
+ "Log debug modified $var"
23
+ )
24
+ logDebug {
25
+ "Log debug modified"
26
+ +"Second line modified"
27
+ }
28
+ logDebug(
29
+ "Log debug modified"
30
+ + "Second line modified"
31
+ )
32
+ logDebug {
33
+ "Log debug modified"
34
+ +"Second line modified debug $var"
35
+ }
36
+ logDebug(
37
+ "Log debug modified"
38
+ + "Second line modified debug $var"
39
+ )
40
+ logDebug(
41
+ "Log debug modifies"
42
+ + message
43
+ )
44
+ logDebug(message.var)
45
+ logDebug(
46
+ message.var
47
+ )
48
+ logDebug {
49
+ "Log debug modifies"
50
+ + message
51
+ }
52
+ logDebug { message.var }
53
+ logDebug {
54
+ message.var
55
+ }
56
+ }
57
+
58
+ fun runLogInfo() {
59
+ "Text to ignore modified $ignoredVar"
60
+
61
+ logInfo { "Log info modified" }
62
+ logInfo("Log info modified")
63
+ logInfo { "Log info modified $var" }
64
+ logInfo("Log info modified $var")
65
+ logInfo {
66
+ "Log info modified"
67
+ }
68
+ logInfo(
69
+ "Log info modified"
70
+ )
71
+ logInfo {
72
+ "Log info modified $var"
73
+ }
74
+ logInfo(
75
+ "Log info modified $var"
76
+ )
77
+ logInfo {
78
+ "Log info modified"
79
+ +"Second line modified"
80
+ }
81
+ logInfo(
82
+ "Log info modified"
83
+ + "Second line modified"
84
+ )
85
+ logInfo {
86
+ "Log info modified"
87
+ +"Second line modified info $var"
88
+ }
89
+ logInfo(
90
+ "Log info modified"
91
+ + "Second line modified info $var"
92
+ )
93
+ logInfo(
94
+ "Log debug modifies"
95
+ + message
96
+ + "another line"
97
+ )
98
+ logInfo(message.var)
99
+ logInfo(
100
+ message.var
101
+ )
102
+ logInfo {
103
+ "Log debug modifies {some text}"
104
+ + message
105
+ + "another line"
106
+ }
107
+ logInfo { message.var }
108
+ logInfo {
109
+ message.var
110
+ }
111
+ }
112
+ }
@@ -0,0 +1,78 @@
1
+ package cz.eman.logging.lint
2
+
3
+ class NewFile {
4
+
5
+ fun runLogDebug() {
6
+ "Text to ignore new $ignoredVar"
7
+
8
+ logDebug { "Log debug new" }
9
+ logDebug("Log debug new")
10
+ logDebug { "Log debug new $var" }
11
+ logDebug("Log debug new $var")
12
+ logDebug {
13
+ "Log debug new"
14
+ }
15
+ logDebug(
16
+ "Log debug new"
17
+ )
18
+ logDebug {
19
+ "Log debug new $var"
20
+ }
21
+ logDebug(
22
+ "Log debug new $var"
23
+ )
24
+ logDebug {
25
+ "Log debug new"
26
+ +"Second line new"
27
+ }
28
+ logDebug(
29
+ "Log debug new"
30
+ + "Second line new"
31
+ )
32
+ logDebug {
33
+ "Log debug new"
34
+ +"Second line new $var"
35
+ }
36
+ logDebug(
37
+ "Log debug new"
38
+ + "Second line new $var"
39
+ )
40
+ }
41
+
42
+ fun runLogInfo() {
43
+ "Text to ignore modified $ignoredVar"
44
+
45
+ logInfo { "Log info new" }
46
+ logInfo("Log info new")
47
+ logInfo { "Log info new $var" }
48
+ logInfo("Log info new $var")
49
+ logInfo {
50
+ "Log info new"
51
+ }
52
+ logInfo(
53
+ "Log info new"
54
+ )
55
+ logInfo {
56
+ "Log info new $var"
57
+ }
58
+ logInfo(
59
+ "Log info new $var"
60
+ )
61
+ logInfo {
62
+ "Log info new"
63
+ +"Second line new"
64
+ }
65
+ logInfo(
66
+ "Log info new"
67
+ + "Second line new"
68
+ )
69
+ logInfo {
70
+ "Log info new"
71
+ +"Second line new $var"
72
+ }
73
+ logInfo(
74
+ "Log info new"
75
+ + "Second line new $var"
76
+ )
77
+ }
78
+ }