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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f27b577db79a18a272cb128f635addb0ac1d4740
4
- data.tar.gz: 292c5593392ca861eb7915ce1cbdd5bc52f45e8d
3
+ metadata.gz: 6530cd946311c49865759085970367cc7b80d49a
4
+ data.tar.gz: 1f25302307508f4869fd63ddb72ddac10a7c258a
5
5
  SHA512:
6
- metadata.gz: be0c3524e9f856179ac50ef7116d22e1a9ca59e3eb6f7b223b789693d324c45e852d86f49d09c9950fa8c61bcdcc14d875adb668d2b10eb3aa9dcb551e306258
7
- data.tar.gz: 11a075f75f5d5431ef10682cca0bb0f1ba8227f0f9e71a77af1dde540f5ae83bcc33fd8aaba73534a28bac8955d78d497aa456cb178bf274e199fdd200ee9472
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.
@@ -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)
@@ -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
- if line =~ /warning:|error:|BCEROR/
122
+ next unless line =~ /warning:|error:|BCEROR/
110
123
  failure_type = get_failure_type(line)
111
- warning_params = line.split(':')
112
- if warning_params.count == 5
113
- error_text = construct_failure_mes(
114
- ['Error ClassType', 'Error Message'],
115
- [failure_type, warning_params[4].tr("\n", '')]
116
- )
117
- testcase_name = project+':'+warning_params[0].tr('<', '').tr('>', '')+
118
- ":#{warning_params[1]}:#{warning_params[2]}"
119
- else
120
- error_text = construct_failure_mes(
121
- ['Error ClassType', 'Error Message'],
122
- [failure_type, line.tr("\n", '').gsub('warning:','')]
123
- )
124
- testcase_name = project
125
- testcase_name += ':project configuration' if line =~ /BCEROR/
126
- file_info = check_for_file_info(warning_params)
127
- testcase_name += ":#{file_info[0]}" unless file_info[0]==nil
128
- testcase_name += ":#{file_info[1]}" unless file_info[1]==nil
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 = /([a-zA-Z0-9\.]*)?(\/[a-zA-Z0-9\._-]+)*(\.){1}[a-zA-Z0-9\._-]+/.match(text)
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
- unless file==nil
146
- next_value=text_list.to_a[i.to_i+1].nil? ? '' : text_list.to_a[i.to_i+1]
147
- line = /[0-9]+/.match(next_value)
148
- if line ==nil
149
- result=[file]
150
- else
151
- result=[file, line]
152
- end
153
- break
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']==nil
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("&#10;", '') if replacements =~ /(&#10;){2,}./
282
+ fixed_code_fragment.gsub!("&#10;", "\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&#10;]*(\ )*$/)
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&#62;&#62;&#32;%-#{space_count}s&#32;%s", (index + 1), add_code(spec_line))
340
+ else
341
+ lines += format("\n&#32;&#32;&#32;%-#{space_count}s&#32;%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!("&lt;",'<')
384
+ text.gsub!("&gt;",'>')
385
+ text.gsub!("&quot;",'"')
386
+ text.gsub!("&amp;",'&')
387
+ text.gsub!("&apos;",'\'')
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.is_pmd_installed
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.is_pmd_installed
166
+ def self.is_installed(command_name, for_analyzer)
155
167
  @checked_pmd = false
156
168
  begin
157
- Actions.sh('type pmd')
169
+ Actions.sh("type #{command_name}") #pmd
158
170
  rescue
159
- UI.user_error! 'PMD not installed. Please, install PMD for using copy paste analyzer.'
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.is_pmd_installed unless Actions::CodeStaticAnalyzerAction.checked_pmd
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]
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
2
  module CodeStaticAnalyzer
3
- VERSION = "0.1.1"
3
+ VERSION = "0.2.0"
4
4
  end
5
5
  end
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.1.1
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-01-25 00:00:00.000000000 Z
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