danger-logging_lint 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+ }