fastlane-plugin-code_static_analyzer 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|