fastlane-plugin-code_static_analyzer 0.1.1 → 0.2.0
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.
- checksums.yaml +4 -4
- data/README.md +45 -1
- data/lib/assets/formatter.rb +6 -1
- data/lib/assets/junit_parser.rb +220 -42
- data/lib/fastlane/plugin/code_static_analyzer/actions/clang_analyzer.rb +223 -0
- data/lib/fastlane/plugin/code_static_analyzer/actions/code_static_analyzer_action.rb +44 -7
- data/lib/fastlane/plugin/code_static_analyzer/actions/cpd_analyzer.rb +1 -1
- data/lib/fastlane/plugin/code_static_analyzer/version.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6530cd946311c49865759085970367cc7b80d49a
|
4
|
+
data.tar.gz: 1f25302307508f4869fd63ddb72ddac10a7c258a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b4574eea5e4578a9ff8d74fa66efe10602073c8a22af0bcf33089c0ec6736ab3b16ff867fd269c720389dada66909106d182d0d498045e40408fee43daf0650f
|
7
|
+
data.tar.gz: 11d10144c2966570d7c946bcc8de252fe92a11ac54a3560a5d3596af1f46fdf80b4ceebbb02088ee7e688400da086ddb1f894b13edfc7b949c7178c45d316080
|
data/README.md
CHANGED
@@ -22,6 +22,7 @@ This plugin can be used in pair with CI static code analysis plugins. Check out
|
|
22
22
|
## Important
|
23
23
|
- You can configure rubocop analyzer by creating configuration file `.rubocop.yml` in your project (more about rubocop configuration http://rubocop.readthedocs.io/en/latest/cops/)
|
24
24
|
- All paths should be relative to work directory.
|
25
|
+
- Clang-format tool have to be installed on your machine to use clang analyzer
|
25
26
|
|
26
27
|
### Specific for copy paste analyzer (CPD)
|
27
28
|
- PMD have to be installed on your machine (http://pmd.sourceforge.net/snapshot/usage/installing.html)
|
@@ -34,6 +35,7 @@ You may run each analyzer separate:<br />
|
|
34
35
|
`cpd_analyzer` - finds copy paste in code (based on CPD from PMD package) <br />
|
35
36
|
`ruby_analyzer` - checks your ruby files. Some offenses can be auto-corrected (if you want to save the changes do it manually) <br />
|
36
37
|
`warning_analyzer` - this analyzer uses Xcode built-in analyzer mainly to detect warnings<br />
|
38
|
+
`clang_analyzer` - this analyzer uses clang-format command to check and fix your code styling<br />
|
37
39
|
|
38
40
|
## Reference and Example
|
39
41
|
|
@@ -58,7 +60,12 @@ code_static_analyzer(
|
|
58
60
|
xcode_workspace_name: 'path/to/testWorkspace',
|
59
61
|
xcode_targets: ['TPClientTarget','TPServerTarget'],
|
60
62
|
ruby_files: 'fastlane/Fastfile',
|
61
|
-
disable_junit: 'all' # don't create any results in JUnit format
|
63
|
+
disable_junit: 'all', # don't create any results in JUnit format
|
64
|
+
autocorrect: true,
|
65
|
+
clang_dir_to_inspect: %w('path/to/myFiles/' 'path/to/testFiles/'),
|
66
|
+
clang_dir_to_exclude: %w(Pods ThirdParty Build 'path/to/otherDir'),
|
67
|
+
files_extention: %w(h cpp),
|
68
|
+
basic_style: 'custom'
|
62
69
|
)
|
63
70
|
````
|
64
71
|
Parameter | Description
|
@@ -74,6 +81,11 @@ Parameter | Description
|
|
74
81
|
`xcode_targets` | *(optional)* List of Xcode targets to inspect. By default used all targets which are available in project
|
75
82
|
`ruby_files` | *(optional)* List of paths to ruby files to be inspected
|
76
83
|
`disable_junit` | *(optional)* List of analysers for which you want to disable results in JUnit format.<br />Supported analyzers: "xcodeWar", "rubocop", "CPD", "all"<br />By default all results will be created in JUnit format.
|
84
|
+
`autocorrect` | *(optional)* If your code will be corrected basing on clang configuration.<br />Default value: false
|
85
|
+
`clang_dir_to_inspect` | *(optional)* List of directories which include files you want to inspect.<br />Default value: your work directory
|
86
|
+
`clang_dir_to_exclude` | *(optional)* List of directories which include files you don't want to inspect.
|
87
|
+
`files_extention` | *(optional)* List of file extensions you use. [!] Each extension set without point.<br />Default value:[m h]
|
88
|
+
`basic_style` | *(optional)* Basic Code Styling you want to use.<br />Supported styles: ['LLVM', 'Google', 'Chromium', 'Mozilla', 'WebKit', 'custom']. <br />By default analyzer uses custom (.clang-format) config file or create new one based on LLVM style in your work directory.<br />If you create configuration file based on one of the clang styling ('LLVM', 'Google', 'Chromium', 'Mozilla', 'WebKit') and change even one property than don't set this parameter or use 'custom'
|
77
89
|
|
78
90
|
### `code_static_analyzer` other examples (full configuration):
|
79
91
|
CPD:
|
@@ -114,6 +126,38 @@ code_static_analyzer(
|
|
114
126
|
xcode_targets: ['TPClientTarget','TPServerTarget'],
|
115
127
|
)
|
116
128
|
````
|
129
|
+
|
130
|
+
## Clang Format
|
131
|
+
If you need to run clang_analyzer separately (for example as build step) you may call next:
|
132
|
+
|
133
|
+
````ruby
|
134
|
+
# minimum configuration
|
135
|
+
clang_analyzer
|
136
|
+
|
137
|
+
# minimum configuration
|
138
|
+
clang_analyzer(
|
139
|
+
autocorrect: true,
|
140
|
+
clang_dir_to_inspect: %w(Classes/UI),
|
141
|
+
clang_dir_to_exclude: %w(Pods ThirdParty Build),
|
142
|
+
files_extention: %w(m h),
|
143
|
+
basic_style: 'custom'
|
144
|
+
)
|
145
|
+
````
|
146
|
+
All parameters are optional and described in previous part.
|
147
|
+
Minimum configuration means that will be checked all files with extension *.m & *.h in work directory and it subdirectories by using custom (if exists) or llvm formatting rules.
|
148
|
+
As result we get junit formatted file with failed tests (autocorrect = false) or skipped tests (autocorrect = true, + changed files).
|
149
|
+
Each test message include: <br />
|
150
|
+
|
151
|
+
Message parts | Description
|
152
|
+
--------- | -----------
|
153
|
+
`Code-fragment` | fragment of code (N lines) with clang format issue +- 5 lines (before /after line with clang issue)
|
154
|
+
`Clang-fix` | clang replacement
|
155
|
+
`Code-fragment-after fix` | the same lines of code as for `Code-fragment` after clang made fix.<br /> Pay attention that this part not always show the current clang fix (due to clang fix the entire file).
|
156
|
+
|
157
|
+
Recommendations: don't use autocorrect currently before making build due to some fixes can cause warnings.<br />
|
158
|
+
For example Objective C method declaration `-(void)methodName:(type1)parameter1 :(type2)method2;`
|
159
|
+
after fix will cause warnings due to this declaration is not under Objective C code convention (it should be `-(void)methodName:(type1)parameter1 withParamTwo:(type2)method2;`)
|
160
|
+
|
117
161
|
## Using CI plugins
|
118
162
|
|
119
163
|
If you want to use CI static code analysis plugins pay attention on type of file which they use.
|
data/lib/assets/formatter.rb
CHANGED
@@ -22,7 +22,12 @@ module Formatter
|
|
22
22
|
puts 'format : xml'
|
23
23
|
puts "output_file : #{result_file}"
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
|
+
def self.clang_format(file, count)
|
27
|
+
puts ">>>Made #{count} replacements in #{file}"
|
28
|
+
end
|
29
|
+
|
30
|
+
|
26
31
|
# String colorization
|
27
32
|
# call UI.message Actions::FormatterAction.light_blue(text)
|
28
33
|
def self.light_blue(mytext)
|
data/lib/assets/junit_parser.rb
CHANGED
@@ -5,10 +5,13 @@
|
|
5
5
|
# 1. Parser 1. Xcode analyze result (.log)
|
6
6
|
# 2. Parser 2. Rubocop result (.json)
|
7
7
|
# 3. Parser 3. CPD result (.xml)
|
8
|
+
# 4. Parser 4. Any result as required Hash (ruby hash)
|
8
9
|
|
9
10
|
require 'crack'
|
10
11
|
|
11
12
|
module JunitParser
|
13
|
+
SKIP_STR_IN_MATCH = ['*', '\n', '\\', '//']
|
14
|
+
|
12
15
|
#####################################################
|
13
16
|
# ================= For all parsers =================
|
14
17
|
#####################################################
|
@@ -36,8 +39,14 @@ module JunitParser
|
|
36
39
|
"#{failures}#{xml_level(2)}</testcase>"
|
37
40
|
end
|
38
41
|
|
39
|
-
def self.add_success_testcase(name)
|
40
|
-
"#{xml_level(2)}<testcase name='#{name}' status='success'/>"
|
42
|
+
def self.add_success_testcase(name, assertions)
|
43
|
+
"#{xml_level(2)}<testcase name='#{name}' #{insert_attribute('assertions', assertions)} status='success'/>"
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.add_skipped_testcase(name, assertions, message)
|
47
|
+
out = message == '' ? '' : add_system_output(message)
|
48
|
+
"#{xml_level(2)}<testcase #{insert_attribute('name', name)} #{insert_attribute('assertions', assertions)}>" \
|
49
|
+
"#{out}#{xml_level(3)}<skipped/>#{xml_level(2)}</testcase>"
|
41
50
|
end
|
42
51
|
|
43
52
|
def self.add_failure(message, type, text)
|
@@ -47,6 +56,10 @@ module JunitParser
|
|
47
56
|
"#{xml_level(3)}</failure>"
|
48
57
|
end
|
49
58
|
|
59
|
+
def self.add_system_output(text)
|
60
|
+
"#{xml_level(3)}<system-out>#{text}#{xml_level(3)}</system-out>"
|
61
|
+
end
|
62
|
+
|
50
63
|
def self.add_properties(property_array, value_array)
|
51
64
|
properties = "#{xml_level(2)}<properties>"
|
52
65
|
property_array.each_with_index do |property, index|
|
@@ -68,7 +81,7 @@ module JunitParser
|
|
68
81
|
end
|
69
82
|
|
70
83
|
def self.insert_attribute(attribute, value)
|
71
|
-
value == '' ? '' : "#{attribute}='#{value}'"
|
84
|
+
(value == '' or value.nil?) ? '' : "#{attribute}='#{value}'"
|
72
85
|
end
|
73
86
|
|
74
87
|
def self.add_code(codefragment)
|
@@ -106,55 +119,54 @@ module JunitParser
|
|
106
119
|
error_text = ''
|
107
120
|
temp_testcase = ''
|
108
121
|
File.open(file).each do |line|
|
109
|
-
|
122
|
+
next unless line =~ /warning:|error:|BCEROR/
|
110
123
|
failure_type = get_failure_type(line)
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
end
|
130
|
-
failures = add_failure('', '', error_text)
|
131
|
-
temp_testcase += add_failed_testcase(testcase_name, failures)
|
124
|
+
warning_params = line.split(':')
|
125
|
+
if warning_params.count == 5
|
126
|
+
error_text = construct_failure_mes(
|
127
|
+
['Error ClassType', 'Error Message'],
|
128
|
+
[failure_type, warning_params[4].tr("\n", '')]
|
129
|
+
)
|
130
|
+
testcase_name = project + ':' + warning_params[0].tr('<', '').tr('>', '') +
|
131
|
+
":#{warning_params[1]}:#{warning_params[2]}"
|
132
|
+
else
|
133
|
+
error_text = construct_failure_mes(
|
134
|
+
['Error ClassType', 'Error Message'],
|
135
|
+
[failure_type, line.tr("\n", '').gsub('warning:', '')]
|
136
|
+
)
|
137
|
+
testcase_name = project
|
138
|
+
testcase_name += ':project configuration' if line =~ /BCEROR/
|
139
|
+
file_info = check_for_file_info(warning_params)
|
140
|
+
testcase_name += ":#{file_info[0]}" unless file_info[0].nil?
|
141
|
+
testcase_name += ":#{file_info[1]}" unless file_info[1].nil?
|
132
142
|
end
|
143
|
+
failures = add_failure('', '', error_text)
|
144
|
+
temp_testcase += add_failed_testcase(testcase_name, failures)
|
133
145
|
end
|
134
146
|
temp_testcase
|
135
147
|
else
|
136
|
-
add_success_testcase(project)
|
148
|
+
add_success_testcase(project, '')
|
137
149
|
end
|
138
150
|
end
|
139
|
-
|
151
|
+
|
140
152
|
def self.check_for_file_info(text_list)
|
141
|
-
result =[]
|
153
|
+
result = []
|
142
154
|
text_list.each_with_index do |text, i|
|
143
|
-
file =
|
155
|
+
file = %r{([a-zA-Z0-9\.]*)?(/[a-zA-Z0-9\._-]+)*(\.){1}[a-zA-Z0-9\._-]+}.match(text)
|
144
156
|
file = nil if file.to_s =~ /\.{2,}/
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
end
|
157
|
+
next if file.nil?
|
158
|
+
next_value = text_list.to_a[i.to_i + 1].nil? ? '' : text_list.to_a[i.to_i + 1]
|
159
|
+
line = /[0-9]+/.match(next_value)
|
160
|
+
if line.nil?
|
161
|
+
result = [file]
|
162
|
+
else
|
163
|
+
result = [file, line]
|
164
|
+
end
|
165
|
+
break
|
155
166
|
end
|
156
167
|
result
|
157
168
|
end
|
169
|
+
|
158
170
|
#####################################################
|
159
171
|
# ============== Rubocop-json Parser ================
|
160
172
|
#####################################################
|
@@ -179,7 +191,7 @@ module JunitParser
|
|
179
191
|
error_text = ''
|
180
192
|
errors = inspected_file['offenses']
|
181
193
|
if errors.empty?
|
182
|
-
xml += add_success_testcase((inspected_file['path']).to_s)
|
194
|
+
xml += add_success_testcase((inspected_file['path']).to_s, '')
|
183
195
|
else
|
184
196
|
errors.each do |error|
|
185
197
|
error_text += construct_failure_mes(
|
@@ -208,9 +220,9 @@ module JunitParser
|
|
208
220
|
data_read = File.read(file)
|
209
221
|
data_hash = Crack::XML.parse(data_read)
|
210
222
|
|
211
|
-
if data_hash.empty? or data_hash['pmd_cpd']
|
223
|
+
if data_hash.empty? or data_hash['pmd_cpd'].nil?
|
212
224
|
puts 'empty data_hash'
|
213
|
-
add_success_testcase('casino duplications')
|
225
|
+
add_success_testcase('casino duplications', '')
|
214
226
|
else
|
215
227
|
parse_code_duplications(data_hash)
|
216
228
|
end
|
@@ -245,4 +257,170 @@ module JunitParser
|
|
245
257
|
end
|
246
258
|
file_list_info
|
247
259
|
end
|
260
|
+
|
261
|
+
#####################################################
|
262
|
+
# =================== Clang Parser ==================
|
263
|
+
#####################################################
|
264
|
+
|
265
|
+
def self.parse_clang(clang_changes, inspected_file, file_id, file_lines)
|
266
|
+
count = 0
|
267
|
+
file_replacements = []
|
268
|
+
clang_changes.each_line do |line|
|
269
|
+
next unless line =~ %r{</replacement>}
|
270
|
+
hash = parse_str(line)
|
271
|
+
offset = hash[:offset]
|
272
|
+
offset_long = hash[:length]
|
273
|
+
offset_end = offset.to_i + offset_long.to_i
|
274
|
+
code_lines = get_code_lines(offset, offset_end, file_lines)
|
275
|
+
# read current code fragment that include clang format issue
|
276
|
+
current_code_fragment = read_lines_in_file(inspected_file, code_lines[0], code_lines[1], false)
|
277
|
+
|
278
|
+
# make clang format replacement more understandable
|
279
|
+
replacements = hash[:replacements]
|
280
|
+
fixed_code_fragment = replacements
|
281
|
+
fixed_code_fragment = replacements.sub(" ", '') if replacements =~ /( ){2,}./
|
282
|
+
fixed_code_fragment.gsub!(" ", "\n")
|
283
|
+
replace_in(fixed_code_fragment)
|
284
|
+
fixed_code_fragment = "\n'#{add_code(fixed_code_fragment)}'"
|
285
|
+
|
286
|
+
# if replacements.match(/^([a-zA-Z0-9])[\n ]*(\ )*$/)
|
287
|
+
# if offset_long.to_i == 1
|
288
|
+
# fixed_code_fragment = "change one character (remove/add space/tab/new line). Replacement: #{fixed_code_fragment.sub("\n", '')}"
|
289
|
+
# else
|
290
|
+
# fixed_code_fragment = "generally needed to remove/add space/tab/new line. Replacement: #{fixed_code_fragment.sub("\n", '')}"
|
291
|
+
# end
|
292
|
+
# end
|
293
|
+
|
294
|
+
hash_info = { old_code: current_code_fragment,
|
295
|
+
fix_code: fixed_code_fragment,
|
296
|
+
code_lines: code_lines,
|
297
|
+
file: inspected_file,
|
298
|
+
file_id: file_id }
|
299
|
+
file_replacements.push(hash_info)
|
300
|
+
|
301
|
+
count += 1
|
302
|
+
end
|
303
|
+
[count, file_replacements]
|
304
|
+
end
|
305
|
+
|
306
|
+
def self.create_clang_xml(file_replacements, skipped)
|
307
|
+
xml = ''
|
308
|
+
file_replacements.each do |i_hash|
|
309
|
+
if skipped
|
310
|
+
new_code_fragment = read_lines_in_file(i_hash[:file], i_hash[:code_lines][0], i_hash[:code_lines][1], true)
|
311
|
+
error_text = construct_failure_mes(
|
312
|
+
['Code fragment with formatting issue', "#{xml_level(4)}Clang fix", "#{xml_level(4)}Code fragment after clang fix"],
|
313
|
+
[i_hash[:old_code], i_hash[:fix_code], new_code_fragment]
|
314
|
+
)
|
315
|
+
xml += add_skipped_testcase("F#{i_hash[:file_id]}:#{i_hash[:file]}:#{i_hash[:code_lines][0] + 1}", '', error_text)
|
316
|
+
else
|
317
|
+
error_text = construct_failure_mes(
|
318
|
+
['Code fragment with formatting issue', "#{xml_level(4)}Clang fix"],
|
319
|
+
[i_hash[:old_code], i_hash[:fix_code]]
|
320
|
+
)
|
321
|
+
failure = add_failure('', '', error_text)
|
322
|
+
xml += add_failed_testcase("F#{i_hash[:file_id]}:#{i_hash[:file]}:#{i_hash[:code_lines][0] + 1}", failure)
|
323
|
+
end
|
324
|
+
end
|
325
|
+
xml
|
326
|
+
end
|
327
|
+
|
328
|
+
def self.read_lines_in_file(file, read_start, read_to, is_fixed)
|
329
|
+
lines = ''
|
330
|
+
read_from = (read_start - 5) < 0 ? 0 : (read_start - 5)
|
331
|
+
read_to += 5
|
332
|
+
index = read_from
|
333
|
+
space_count = read_to.to_s.count "0-9"
|
334
|
+
File.readlines(file).values_at(read_from..read_to).each do |spec_line|
|
335
|
+
unless spec_line.nil?
|
336
|
+
spec_line = spec_line.chomp
|
337
|
+
replace_in(spec_line)
|
338
|
+
if index == read_start and !is_fixed
|
339
|
+
lines += format("\n>> %-#{space_count}s %s", (index + 1), add_code(spec_line))
|
340
|
+
else
|
341
|
+
lines += format("\n   %-#{space_count}s %s", (index + 1), add_code(spec_line))
|
342
|
+
end
|
343
|
+
end
|
344
|
+
index += 1
|
345
|
+
end
|
346
|
+
lines
|
347
|
+
end
|
348
|
+
|
349
|
+
def self.parse_str(str)
|
350
|
+
# TODO: check if no any replacements...will it be crashes
|
351
|
+
byte_offset = str.match(/offset='[0-9]+'/).to_s.tr('\'', '').split('=')[1]
|
352
|
+
length_of_changes = str.match(/length='[0-9]+'/).to_s.tr('\'', '').split('=')[1]
|
353
|
+
replaced = str.match(%r{>(.)*</replacement>}).to_s
|
354
|
+
replaced[0] = ''
|
355
|
+
replaced = replaced.sub('</replacement>', '')
|
356
|
+
|
357
|
+
{ offset: byte_offset, length: length_of_changes, replacements: replaced }
|
358
|
+
end
|
359
|
+
|
360
|
+
def self.get_code_lines(value, value_end, file_lines)
|
361
|
+
s_line = file_lines[:start]
|
362
|
+
e_line = file_lines[:finish]
|
363
|
+
value = value.to_i
|
364
|
+
start_flag = false
|
365
|
+
finish_flag = false
|
366
|
+
start = 0
|
367
|
+
finish = 0
|
368
|
+
s_line.each_with_index do |line, index|
|
369
|
+
if value >= line and value < e_line[index] and !start_flag
|
370
|
+
start = file_lines[:line][index]
|
371
|
+
start_flag = true
|
372
|
+
end
|
373
|
+
if value_end > line and value_end <= e_line[index] and !finish_flag
|
374
|
+
finish = file_lines[:line][index]
|
375
|
+
finish_flag = true
|
376
|
+
end
|
377
|
+
break if start_flag and finish_flag
|
378
|
+
end
|
379
|
+
[start, finish]
|
380
|
+
end
|
381
|
+
|
382
|
+
def self.replace_in(text)
|
383
|
+
text.gsub!("<",'<')
|
384
|
+
text.gsub!(">",'>')
|
385
|
+
text.gsub!(""",'"')
|
386
|
+
text.gsub!("&",'&')
|
387
|
+
text.gsub!("'",'\'')
|
388
|
+
end
|
389
|
+
|
390
|
+
#####################################################
|
391
|
+
# ============== Universal Hash Parser ==============
|
392
|
+
#####################################################
|
393
|
+
|
394
|
+
# Method return testsuites xml content
|
395
|
+
# after that call create_junit_xml to get full JUnit file
|
396
|
+
def self.parse_hash(data_hash)
|
397
|
+
testcase_xml = ''
|
398
|
+
testcases = data_hash[:testcase]
|
399
|
+
testcases.each do |error|
|
400
|
+
testcase_name = error[:name]
|
401
|
+
testcase_err_num = error[:assertions]
|
402
|
+
testcase_message = error[:message]
|
403
|
+
case error[:status].downcase
|
404
|
+
when 'success'
|
405
|
+
testcase_xml += add_success_testcase(testcase_name, testcase_err_num, '')
|
406
|
+
when 'fail'
|
407
|
+
if testcase_message.nil?
|
408
|
+
failure = add_failure('', '', '')
|
409
|
+
else
|
410
|
+
failure = add_failure(testcase_message[:additional_info], testcase_message[:type], testcase_message[:message])
|
411
|
+
end
|
412
|
+
testcase_xml += add_failed_testcase(testcase_name, failure)
|
413
|
+
when 'skip'
|
414
|
+
testcase_xml += add_skipped_testcase(testcase_name, testcase_err_num, '')
|
415
|
+
end
|
416
|
+
end
|
417
|
+
properties = ''
|
418
|
+
unless data_hash[:properties].nil?
|
419
|
+
keys = data_hash[:properties].keys
|
420
|
+
values = data_hash[:properties].values
|
421
|
+
properties = add_properties(keys, values)
|
422
|
+
end
|
423
|
+
testsuite = add_testsuite(data_hash[:test_group_name], properties + testcase_xml)
|
424
|
+
testsuite
|
425
|
+
end
|
248
426
|
end
|
@@ -0,0 +1,223 @@
|
|
1
|
+
module Fastlane
|
2
|
+
module Actions
|
3
|
+
module SharedValues
|
4
|
+
CLANG_ANALYZER_STATUS = :CLANG_ANALYZER_STATUS
|
5
|
+
end
|
6
|
+
|
7
|
+
class ClangAnalyzerAction < Action
|
8
|
+
SUPPORTED_STYLE = ['LLVM', 'Google', 'Chromium', 'Mozilla', 'WebKit', 'custom']
|
9
|
+
|
10
|
+
def self.run(params)
|
11
|
+
UI.header 'Clang analyzer' if Actions::CodeStaticAnalyzerAction.run_from_main_action
|
12
|
+
Actions::CodeStaticAnalyzerAction.is_installed('clang-format', 'clang format analyzer') unless Actions::CodeStaticAnalyzerAction.checked_pmd
|
13
|
+
work_dir = Actions::CodeStaticAnalyzerAction.work_dir
|
14
|
+
style = params[:basic_style]
|
15
|
+
autofix = params[:autocorrect]
|
16
|
+
# checking files for analysing
|
17
|
+
files_extentions = params[:files_extention]
|
18
|
+
files_to_exclude = params[:clang_dir_to_exclude] # TODO: add check
|
19
|
+
files_to_inspect = params[:clang_dir_to_inspect] # TODO: add check
|
20
|
+
|
21
|
+
files_extentions = ['m', 'h'] unless files_extentions
|
22
|
+
extention = '{'
|
23
|
+
files_extentions.each do |extent|
|
24
|
+
extention += "#{extent},"
|
25
|
+
end
|
26
|
+
extention += '}'
|
27
|
+
UI.message "[!] CPD analyzer will be run for all files with extentions #{files_extentions}".blue
|
28
|
+
|
29
|
+
# prepare script and metadata for saving results
|
30
|
+
result_dir_path = "#{work_dir}#{params[:result_dir]}"
|
31
|
+
FileUtils.mkdir_p(result_dir_path) unless File.exist?(result_dir_path)
|
32
|
+
result_file = "#{result_dir_path}/codeAnalysResults_clang.xml"
|
33
|
+
run_script_path = File.join CodeStaticAnalyzer::ROOT, "assets/run_script.sh"
|
34
|
+
|
35
|
+
UI.message 'Checking clang configuration file'
|
36
|
+
is_clang_config = Dir.glob("#{work_dir}**/.clang-format").empty?
|
37
|
+
|
38
|
+
if (style == 'custom' and is_clang_config) or style != 'custom'
|
39
|
+
if is_clang_config
|
40
|
+
UI.message 'Your custom clang configuration file (.clang-format) is absent in work directory.'.blue +
|
41
|
+
' Clang will be run with default config file based on LLVM style'.blue
|
42
|
+
end
|
43
|
+
run_script = "clang-format -style=#{style} -dump-config "
|
44
|
+
run_script = "#{run_script_path} \"#{run_script}\" '.clang-format'"
|
45
|
+
FastlaneCore::CommandExecutor.execute(command: run_script.to_s,
|
46
|
+
print_all: true,
|
47
|
+
error: proc do |error_output|
|
48
|
+
# handle error here
|
49
|
+
end)
|
50
|
+
end
|
51
|
+
|
52
|
+
UI.message 'Check files:'
|
53
|
+
status_static_arr = []
|
54
|
+
work_files = file_list_for_clang_formatting(work_dir, files_to_inspect, files_to_exclude, extention)
|
55
|
+
data_hash = {}
|
56
|
+
data_hash["file number"] = 'number of replacements'
|
57
|
+
testcase = ''
|
58
|
+
work_files.each_with_index do |file, index|
|
59
|
+
run_script = "find #{file} | xargs clang-format -i -style=file -fallback-style=none "
|
60
|
+
clang_xml_format = " -output-replacements-xml "
|
61
|
+
clang_changes = FastlaneCore::CommandExecutor.execute(command: "#{run_script}#{clang_xml_format}",
|
62
|
+
print_all: false,
|
63
|
+
print_command: false,
|
64
|
+
error: proc do |error_output|
|
65
|
+
# handle error here
|
66
|
+
end)
|
67
|
+
# if index == 22 #23
|
68
|
+
all_lines = file_to_lines_offset(file)
|
69
|
+
clang_data = JunitParser.parse_clang(clang_changes, file, index, all_lines)
|
70
|
+
data_hash[index.to_s] = clang_data[0]
|
71
|
+
Formatter.clang_format("#{index}:#{file}", clang_data[0])
|
72
|
+
status_static_arr.push(clang_data[0])
|
73
|
+
|
74
|
+
if autofix
|
75
|
+
FastlaneCore::CommandExecutor.execute(command: run_script.to_s,
|
76
|
+
print_all: false,
|
77
|
+
print_command: false,
|
78
|
+
error: proc do |error_output|
|
79
|
+
# handle error here
|
80
|
+
end)
|
81
|
+
end
|
82
|
+
testcase += JunitParser.create_clang_xml(clang_data[1], autofix)
|
83
|
+
# end
|
84
|
+
# JunitParser.create_junit_xml(clang_changes, "#{result_dir_path}/#{index}_clang.xml")
|
85
|
+
end
|
86
|
+
|
87
|
+
# prepare results
|
88
|
+
keys = data_hash.keys
|
89
|
+
values = data_hash.values
|
90
|
+
properties = JunitParser.add_properties(keys, values)
|
91
|
+
junit_xml = JunitParser.add_testsuite('clang', properties + testcase)
|
92
|
+
|
93
|
+
JunitParser.create_junit_xml(junit_xml, result_file)
|
94
|
+
status = if status_static_arr.any? { |x| x > 0 }
|
95
|
+
1
|
96
|
+
else
|
97
|
+
0
|
98
|
+
end
|
99
|
+
Actions.lane_context[SharedValues::CLANG_ANALYZER_STATUS] = status
|
100
|
+
end
|
101
|
+
|
102
|
+
def self.file_list_for_clang_formatting(work_dir, include, exclude, ext)
|
103
|
+
file_list = []
|
104
|
+
if include
|
105
|
+
include.each do |file|
|
106
|
+
file_list += Dir.glob("#{work_dir}#{file}/**/*.#{ext}")
|
107
|
+
end
|
108
|
+
else
|
109
|
+
file_list = Dir.glob("#{work_dir}**/*.#{ext}")
|
110
|
+
end
|
111
|
+
if exclude
|
112
|
+
exclude.each do |file|
|
113
|
+
file_list -= Dir.glob("#{work_dir}#{file}/**/*")
|
114
|
+
end
|
115
|
+
end
|
116
|
+
file_list
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.file_to_lines_offset(file_path_name)
|
120
|
+
line_start = []
|
121
|
+
line_end = []
|
122
|
+
line_num = []
|
123
|
+
file_stream = File.open(file_path_name)
|
124
|
+
File.readlines(file_path_name).each_with_index do |line, index|
|
125
|
+
line_start_pos = file_stream.pos
|
126
|
+
# linelength = line.length - 1
|
127
|
+
line_end_pos = file_stream.pos + line.length
|
128
|
+
file_stream.seek(line_end_pos)
|
129
|
+
line_start.push(line_start_pos)
|
130
|
+
line_end.push(line_end_pos)
|
131
|
+
line_num.push(index) # in File.read index of first line - 0, but in Xcode - 1
|
132
|
+
end
|
133
|
+
file_stream.close
|
134
|
+
{ start: line_start, finish: line_end, line: line_num }
|
135
|
+
end
|
136
|
+
|
137
|
+
#####################################################
|
138
|
+
# @!group Documentation
|
139
|
+
#####################################################
|
140
|
+
|
141
|
+
def self.description
|
142
|
+
"A short description with <= 80 characters of what this action does"
|
143
|
+
end
|
144
|
+
|
145
|
+
def self.details
|
146
|
+
# Optional:
|
147
|
+
# this is your chance to provide a more detailed description of this action
|
148
|
+
"You can use this action to do cool things..."
|
149
|
+
end
|
150
|
+
|
151
|
+
def self.available_options
|
152
|
+
# Define all options your action supports.
|
153
|
+
|
154
|
+
# Below a few examples
|
155
|
+
[
|
156
|
+
FastlaneCore::ConfigItem.new(key: :basic_style,
|
157
|
+
env_name: "FL_CLANG_ANALYZER_BASED_ON_STYLE",
|
158
|
+
description: "Code style.\nSupported styles: #{SUPPORTED_STYLE}",
|
159
|
+
optional: true,
|
160
|
+
type: String,
|
161
|
+
verify_block: proc do |value|
|
162
|
+
UI.user_error!("This style is not supported. Supported languages: #{SUPPORTED_STYLE}") unless SUPPORTED_STYLE.map(&:downcase).include? value.downcase or value.empty? or !value
|
163
|
+
end,
|
164
|
+
default_value: 'custom'),
|
165
|
+
FastlaneCore::ConfigItem.new(key: :clang_dir_to_inspect,
|
166
|
+
env_name: "FL_CLANG_ANALYZER_FILES_TO_INSPECT",
|
167
|
+
description: "List of directories (relative to work directory) to inspect on clang styling",
|
168
|
+
optional: true,
|
169
|
+
type: Array),
|
170
|
+
FastlaneCore::ConfigItem.new(key: :clang_dir_to_exclude,
|
171
|
+
env_name: "FL_CLANG_ANALYZER_FILES_NOT_TO_INSPECT",
|
172
|
+
description: "List of directories (relative to work directory) not to inspect on clang styling",
|
173
|
+
optional: true,
|
174
|
+
type: Array),
|
175
|
+
FastlaneCore::ConfigItem.new(key: :files_extention,
|
176
|
+
env_name: "FL_CLANG_ANALYZER_FILES_EXTENTIONS",
|
177
|
+
description: "List of extentions of inspected files",
|
178
|
+
optional: true,
|
179
|
+
type: Array),
|
180
|
+
FastlaneCore::ConfigItem.new(key: :result_dir,
|
181
|
+
env_name: "FL_CLANG_ANALYZER_RESULT_DIR",
|
182
|
+
description: "Directory's name for storing results",
|
183
|
+
optional: true,
|
184
|
+
type: String,
|
185
|
+
default_value: 'artifacts'),
|
186
|
+
FastlaneCore::ConfigItem.new(key: :autocorrect,
|
187
|
+
env_name: "FL_CLANG_ANALYZER_AUTOCORRECT",
|
188
|
+
optional: true,
|
189
|
+
is_string: false)
|
190
|
+
]
|
191
|
+
end
|
192
|
+
|
193
|
+
def self.output
|
194
|
+
# Define the shared values you are going to provide
|
195
|
+
[
|
196
|
+
['CLANG_ANALYZER_STATUS', 'Clang format analyzer result status (0 - success, any other value - failed)']
|
197
|
+
]
|
198
|
+
end
|
199
|
+
|
200
|
+
def self.return_value
|
201
|
+
# If you method provides a return value, you can describe here what it does
|
202
|
+
end
|
203
|
+
|
204
|
+
def self.authors
|
205
|
+
# So no one will ever forget your contribution to fastlane :) You are awesome btw!
|
206
|
+
["olgakn"]
|
207
|
+
end
|
208
|
+
|
209
|
+
def self.is_supported?(platform)
|
210
|
+
# you can do things like
|
211
|
+
#
|
212
|
+
# true
|
213
|
+
#
|
214
|
+
# platform == :ios
|
215
|
+
#
|
216
|
+
# [:ios, :mac].include?(platform)
|
217
|
+
#
|
218
|
+
|
219
|
+
true
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
@@ -9,13 +9,13 @@ module Fastlane
|
|
9
9
|
end
|
10
10
|
|
11
11
|
class CodeStaticAnalyzerAction < Action
|
12
|
-
SUPPORTED_ANALYZER = ["xcodeWar", "rubocop", "CPD"]
|
12
|
+
SUPPORTED_ANALYZER = ["xcodeWar", "rubocop", "CPD", "clang"]
|
13
13
|
RESULT_FILE = 'codeAnalysResults_analyzers.xml'
|
14
14
|
attr_accessor :checked_pmd, :checked_xcode_param, :xml_content, :run_main
|
15
15
|
|
16
16
|
def self.run(params)
|
17
17
|
@run_main = true
|
18
|
-
Actions::CodeStaticAnalyzerAction.
|
18
|
+
Actions::CodeStaticAnalyzerAction.is_installed('pmd','copy paste analyzer')
|
19
19
|
platform = Actions.lane_context[SharedValues::PLATFORM_NAME].to_s
|
20
20
|
@xml_content = ''
|
21
21
|
root_dir = work_dir
|
@@ -46,6 +46,7 @@ module Fastlane
|
|
46
46
|
|
47
47
|
status_rubocop = 0
|
48
48
|
status_static = 0
|
49
|
+
status_clang = 0
|
49
50
|
clear_all_files = "#{root_dir}#{params[:result_dir]}/*.*"
|
50
51
|
# clear_temp_files = "#{root_dir}#{params[:result_dir]}/*temp*.*"
|
51
52
|
sh "rm -rf #{clear_all_files}"
|
@@ -80,6 +81,16 @@ module Fastlane
|
|
80
81
|
ruby_files: params[:ruby_files],
|
81
82
|
use_junit_format: use_junit
|
82
83
|
)
|
84
|
+
when 'clang'
|
85
|
+
autofix = params[:clang_autocorrect]==nil ? false : params[:clang_autocorrect]
|
86
|
+
status_clang = Actions::ClangAnalyzerAction.run(
|
87
|
+
basic_style: params[:clang_basic_style],
|
88
|
+
files_extention: params[:files_extention],
|
89
|
+
clang_dir_to_inspect: params[:clang_dir_to_inspect],
|
90
|
+
clang_dir_to_exclude: params[:clang_dir_to_exclude],
|
91
|
+
result_dir: params[:result_dir],
|
92
|
+
autocorrect: autofix
|
93
|
+
)
|
83
94
|
end
|
84
95
|
end
|
85
96
|
# sh "rm -rf #{clear_temp_files}"
|
@@ -88,7 +99,8 @@ module Fastlane
|
|
88
99
|
|
89
100
|
if Actions::CodeStaticAnalyzerAction.status_to_boolean(status_cpd) &&
|
90
101
|
Actions::CodeStaticAnalyzerAction.status_to_boolean(status_static) &&
|
91
|
-
Actions::CodeStaticAnalyzerAction.status_to_boolean(status_rubocop)
|
102
|
+
Actions::CodeStaticAnalyzerAction.status_to_boolean(status_rubocop) &&
|
103
|
+
Actions::CodeStaticAnalyzerAction.status_to_boolean(status_clang)
|
92
104
|
Actions.lane_context[SharedValues::ANALYZER_STATUS] = true
|
93
105
|
else
|
94
106
|
Actions.lane_context[SharedValues::ANALYZER_STATUS] = false
|
@@ -151,12 +163,12 @@ module Fastlane
|
|
151
163
|
end
|
152
164
|
end
|
153
165
|
|
154
|
-
def self.
|
166
|
+
def self.is_installed(command_name, for_analyzer)
|
155
167
|
@checked_pmd = false
|
156
168
|
begin
|
157
|
-
Actions.sh(
|
169
|
+
Actions.sh("type #{command_name}") #pmd
|
158
170
|
rescue
|
159
|
-
UI.user_error!
|
171
|
+
UI.user_error! "#{command_name} not installed. Please, install #{command_name} for using #{for_analyzer}."
|
160
172
|
end
|
161
173
|
@checked_pmd = true
|
162
174
|
end
|
@@ -316,7 +328,32 @@ module Fastlane
|
|
316
328
|
FastlaneCore::ConfigItem.new(key: :xcode_targets,
|
317
329
|
description: "List of Xcode targets to inspect",
|
318
330
|
optional: true,
|
319
|
-
type: Array)
|
331
|
+
type: Array),
|
332
|
+
# parameters for Clang analyzer-formatter
|
333
|
+
FastlaneCore::ConfigItem.new(key: :clang_basic_style,
|
334
|
+
description: "Style ... ? ... .\nSupported styles: #{Actions::ClangAnalyzerAction::SUPPORTED_STYLE}",
|
335
|
+
optional: true,
|
336
|
+
type: String,
|
337
|
+
verify_block: proc do |value|
|
338
|
+
UI.user_error!("This style is not supported. Supported languages: #{Actions::ClangAnalyzerAction::SUPPORTED_STYLE}") unless Actions::ClangAnalyzerAction::SUPPORTED_STYLE.map(&:downcase).include? value.downcase or value.empty? or !value
|
339
|
+
end,
|
340
|
+
default_value:'custom'),
|
341
|
+
FastlaneCore::ConfigItem.new(key: :clang_dir_to_inspect,
|
342
|
+
description: "List of directories (relative to work directory) to inspect on clang styling",
|
343
|
+
optional: true,
|
344
|
+
type: Array),
|
345
|
+
FastlaneCore::ConfigItem.new(key: :clang_dir_to_exclude,
|
346
|
+
description: "List of directories (relative to work directory) not to inspect on clang styling",
|
347
|
+
optional: true,
|
348
|
+
type: Array),
|
349
|
+
FastlaneCore::ConfigItem.new(key: :files_extention,
|
350
|
+
description: "List of extentions of inspected files",
|
351
|
+
optional: true,
|
352
|
+
type: Array),
|
353
|
+
FastlaneCore::ConfigItem.new(key: :clang_autocorrect,
|
354
|
+
description: "--?",
|
355
|
+
optional: true,
|
356
|
+
is_string: false)
|
320
357
|
]
|
321
358
|
end
|
322
359
|
|
@@ -9,7 +9,7 @@ module Fastlane
|
|
9
9
|
|
10
10
|
def self.run(params)
|
11
11
|
UI.header 'CPD analyzer' if Actions::CodeStaticAnalyzerAction.run_from_main_action
|
12
|
-
Actions::CodeStaticAnalyzerAction.
|
12
|
+
Actions::CodeStaticAnalyzerAction.is_installed('pmd', 'copy paste analyzer') unless Actions::CodeStaticAnalyzerAction.checked_pmd
|
13
13
|
work_dir = Actions::CodeStaticAnalyzerAction.work_dir
|
14
14
|
# checking files for analysing
|
15
15
|
files_to_exclude = params[:cpd_files_to_exclude]
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fastlane-plugin-code_static_analyzer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Olga Kniazska
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-03-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: crack
|
@@ -135,6 +135,7 @@ files:
|
|
135
135
|
- lib/assets/junit_parser.rb
|
136
136
|
- lib/assets/run_script.sh
|
137
137
|
- lib/fastlane/plugin/code_static_analyzer.rb
|
138
|
+
- lib/fastlane/plugin/code_static_analyzer/actions/clang_analyzer.rb
|
138
139
|
- lib/fastlane/plugin/code_static_analyzer/actions/code_static_analyzer_action.rb
|
139
140
|
- lib/fastlane/plugin/code_static_analyzer/actions/cpd_analyzer.rb
|
140
141
|
- lib/fastlane/plugin/code_static_analyzer/actions/ruby_analyzer.rb
|