Class: Danger::DangerSwiftlint
- Inherits:
-
Plugin
- Object
- Plugin
- Danger::DangerSwiftlint
- Defined in:
- lib/danger_plugin.rb
Overview
Lint Swift files inside your projects. This is done using the [SwiftLint](github.com/realm/SwiftLint) tool. Results are passed out as a table in markdown.
Instance Attribute Summary collapse
-
#binary_path ⇒ Object
The path to SwiftLint’s execution.
-
#config_file ⇒ Object
The path to SwiftLint’s configuration file.
-
#directory ⇒ Object
Allows you to specify a directory from where swiftlint will be run.
-
#errors ⇒ Object
Errors found.
-
#filter_issues_in_diff ⇒ Object
Whether all issues or ones in PR Diff to be reported.
-
#issues ⇒ Object
All issues found.
-
#lint_all_files ⇒ Object
Whether all files should be linted in one pass.
-
#max_num_violations ⇒ Object
Maximum number of issues to be reported.
-
#strict ⇒ Object
Whether we should fail on warnings.
-
#verbose ⇒ Object
Provides additional logging diagnostic information.
-
#warnings ⇒ Object
Warnings found.
Instance Method Summary collapse
-
#filter_git_diff_issues(issues) ⇒ Array
Filters issues reported against changes in the modified files.
-
#find_swift_files(dir_selected, files = nil, excluded_paths = [], included_paths = []) ⇒ Array
Find swift files from the files glob If files are not provided it will use git modifield and added files.
-
#format_paths(paths, filepath) ⇒ Array
Parses the configuration file and return the specified files in path.
-
#git_modified_files_info ⇒ Array
Finds modified files and added files, creates array of files with modified line numbers.
-
#git_modified_lines(file) ⇒ Array
Gets git patch info and finds modified line numbers, excludes removed lines.
-
#lint_files(files = nil, inline_mode: false, fail_on_error: false, additional_swiftlint_args: '', no_comment: false, &select_block) ⇒ void
Lints Swift files.
-
#load_config(filepath) ⇒ Object
Get the configuration file.
- #log(text) ⇒ Object
-
#markdown_issues(results, heading) ⇒ String
Create a markdown table from swiftlint issues.
- #other_issues_message(issues_count) ⇒ Object
-
#parse_environment_variables(file_contents) ⇒ Object
Find all requested environment variables in the given string and replace them with the correct values.
-
#run_swiftlint(options, additional_swiftlint_args) ⇒ Array
Run swiftlint on all files and returns the issues.
-
#run_swiftlint_for_each(files, options, additional_swiftlint_args) ⇒ Array
Run swiftlint on each file and aggregate collect the issues.
-
#script_input(files) ⇒ Hash
Converts an array of files into ‘SCRIPT_INPUT_FILE_#` format for use with `–use-script-input-files`.
-
#send_inline_comment(results, method) ⇒ void
Send inline comment with danger’s warn or fail method.
-
#swiftlint ⇒ SwiftLint
Make SwiftLint object for binary_path.
Instance Attribute Details
#binary_path ⇒ Object
The path to SwiftLint’s execution
24 25 26 |
# File 'lib/danger_plugin.rb', line 24 def binary_path @binary_path end |
#config_file ⇒ Object
The path to SwiftLint’s configuration file
27 28 29 |
# File 'lib/danger_plugin.rb', line 27 def config_file @config_file end |
#directory ⇒ Object
Allows you to specify a directory from where swiftlint will be run.
30 31 32 |
# File 'lib/danger_plugin.rb', line 30 def directory @directory end |
#errors ⇒ Object
Errors found
48 49 50 |
# File 'lib/danger_plugin.rb', line 48 def errors @errors end |
#filter_issues_in_diff ⇒ Object
Whether all issues or ones in PR Diff to be reported
54 55 56 |
# File 'lib/danger_plugin.rb', line 54 def filter_issues_in_diff @filter_issues_in_diff end |
#issues ⇒ Object
All issues found
51 52 53 |
# File 'lib/danger_plugin.rb', line 51 def issues @issues end |
#lint_all_files ⇒ Object
Whether all files should be linted in one pass
39 40 41 |
# File 'lib/danger_plugin.rb', line 39 def lint_all_files @lint_all_files end |
#max_num_violations ⇒ Object
Maximum number of issues to be reported.
33 34 35 |
# File 'lib/danger_plugin.rb', line 33 def max_num_violations @max_num_violations end |
#strict ⇒ Object
Whether we should fail on warnings
42 43 44 |
# File 'lib/danger_plugin.rb', line 42 def strict @strict end |
#verbose ⇒ Object
Provides additional logging diagnostic information.
36 37 38 |
# File 'lib/danger_plugin.rb', line 36 def verbose @verbose end |
#warnings ⇒ Object
Warnings found
45 46 47 |
# File 'lib/danger_plugin.rb', line 45 def warnings @warnings end |
Instance Method Details
#filter_git_diff_issues(issues) ⇒ Array
Filters issues reported against changes in the modified files
333 334 335 336 337 338 |
# File 'lib/danger_plugin.rb', line 333 def filter_git_diff_issues(issues) modified_files_info = git_modified_files_info() return issues.select { |i| modified_files_info["#{i['file']}"] != nil && modified_files_info["#{i['file']}"].include?(i['line'].to_i) } end |
#find_swift_files(dir_selected, files = nil, excluded_paths = [], included_paths = []) ⇒ Array
Find swift files from the files glob If files are not provided it will use git modifield and added files
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 |
# File 'lib/danger_plugin.rb', line 211 def find_swift_files(dir_selected, files = nil, excluded_paths = [], included_paths = []) # Assign files to lint if files.nil? renamed_files_hash = git.renamed_files.map { |rename| [rename[:before], rename[:after]] }.to_h post_rename_modified_files = git.modified_files.map { |modified_file| renamed_files_hash[modified_file] || modified_file } files = (post_rename_modified_files - git.deleted_files) + git.added_files else files = Dir.glob(files) end # Filter files to lint excluded_paths_list = Find.find(*excluded_paths).to_a included_paths_list = Find.find(*included_paths).to_a files. # Ensure only swift files are selected select { |file| file.end_with?('.swift') }. # Convert to absolute paths map { |file| File.(file) }. # Remove dups uniq. # Ensure only files in the selected directory select { |file| file.start_with?(dir_selected) }. # Reject files excluded on configuration reject { |file| excluded_paths_list.include?(file) }. # Accept files included on configuration select do |file| next true if included_paths.empty? included_paths_list.include?(file) end end |
#format_paths(paths, filepath) ⇒ Array
Parses the configuration file and return the specified files in path
266 267 268 269 270 271 272 |
# File 'lib/danger_plugin.rb', line 266 def format_paths(paths, filepath) # Extract included paths paths .map { |path| File.join(File.dirname(filepath), path) } .map { |path| File.(path) } .select { |path| File.exist?(path) || Dir.exist?(path) } end |
#git_modified_files_info ⇒ Array
Finds modified files and added files, creates array of files with modified line numbers
343 344 345 346 347 348 349 350 351 |
# File 'lib/danger_plugin.rb', line 343 def git_modified_files_info() modified_files_info = Hash.new updated_files = (git.modified_files - git.deleted_files) + git.added_files updated_files.each {|file| modified_lines = git_modified_lines(file) modified_files_info[File.(file)] = modified_lines } modified_files_info end |
#git_modified_lines(file) ⇒ Array
Gets git patch info and finds modified line numbers, excludes removed lines
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 |
# File 'lib/danger_plugin.rb', line 356 def git_modified_lines(file) git_range_info_line_regex = /^@@ .+\+(?<line_number>\d+),/ git_modified_line_regex = /^\+(?!\+|\+)/ git_removed_line_regex = /^\-(?!\-|\-)/ file_info = git.diff_for_file(file) line_number = 0 lines = [] file_info.patch.split("\n").each do |line| starting_line_number = 0 case line when git_range_info_line_regex starting_line_number = Regexp.last_match[:line_number].to_i when git_modified_line_regex lines << line_number end line_number += 1 if line_number > 0 && !git_removed_line_regex.match?(line) line_number = starting_line_number if starting_line_number > 0 end lines end |
#lint_files(files = nil, inline_mode: false, fail_on_error: false, additional_swiftlint_args: '', no_comment: false, &select_block) ⇒ void
This method returns an undefined value.
Lints Swift files. Will fail if ‘swiftlint` cannot be installed correctly. Generates a `markdown` list of warnings for the prose in a corpus of .markdown and .md files.
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
# File 'lib/danger_plugin.rb', line 66 def lint_files(files = nil, inline_mode: false, fail_on_error: false, additional_swiftlint_args: '', no_comment: false, &select_block) # Fails if swiftlint isn't installed raise 'swiftlint is not installed' unless swiftlint.installed? config_file_path = config_file if config_file_path log "Using config file: #{config_file_path}" else log 'Config file was not specified.' end dir_selected = directory ? File.(directory) : Dir.pwd log "Swiftlint will be run from #{dir_selected}" # Get config config = load_config(config_file_path) # Extract excluded paths excluded_paths = format_paths(config['excluded'] || [], config_file_path) log "Swiftlint will exclude the following paths: #{excluded_paths}" # Extract included paths included_paths = format_paths(config['included'] || [], config_file_path) log "Swiftlint includes the following paths: #{included_paths}" # Prepare swiftlint options = { # Make sure we don't fail when config path has spaces config: config_file_path ? Shellwords.escape(config_file_path) : nil, reporter: 'json', quiet: true, pwd: dir_selected, force_exclude: true } log "linting with options: #{}" if lint_all_files issues = run_swiftlint(, additional_swiftlint_args) else # Extract swift files (ignoring excluded ones) files = find_swift_files(dir_selected, files, excluded_paths, included_paths) log "Swiftlint will lint the following files: #{files.join(', ')}" # Lint each file and collect the results issues = run_swiftlint_for_each(files, , additional_swiftlint_args) end if filter_issues_in_diff # Filter issues related to changes in PR Diff issues = filter_git_diff_issues(issues) end @issues = issues other_issues_count = 0 unless @max_num_violations.nil? || no_comment other_issues_count = issues.count - @max_num_violations if issues.count > @max_num_violations issues = issues.take(@max_num_violations) end log "Received from Swiftlint: #{issues}" # filter out any unwanted violations with the passed in select_block if select_block && !no_comment issues = issues.select { |issue| select_block.call(issue) } end # Filter warnings and errors @warnings = issues.select { |issue| issue['severity'] == 'Warning' } @errors = issues.select { |issue| issue['severity'] == 'Error' } # Early exit so we don't comment return if no_comment if inline_mode # Report with inline comment send_inline_comment(warnings, strict ? :fail : :warn) send_inline_comment(errors, (fail_on_error || strict) ? :fail : :warn) warn (other_issues_count) if other_issues_count > 0 elsif warnings.count > 0 || errors.count > 0 # Report if any warning or error = "### SwiftLint found issues\n\n".dup << markdown_issues(warnings, 'Warnings') unless warnings.empty? << markdown_issues(errors, 'Errors') unless errors.empty? << "\n#{(other_issues_count)}" if other_issues_count > 0 markdown # Fail danger on errors should_fail_by_errors = fail_on_error && errors.count > 0 # Fail danger if any warnings or errors and we are strict should_fail_by_strict = strict && (errors.count > 0 || warnings.count > 0) if should_fail_by_errors || should_fail_by_strict fail 'Failed due to SwiftLint errors' end end end |
#load_config(filepath) ⇒ Object
Get the configuration file
242 243 244 245 246 247 248 249 250 251 |
# File 'lib/danger_plugin.rb', line 242 def load_config(filepath) return {} if filepath.nil? || !File.exist?(filepath) config_file = File.open(filepath).read # Replace environment variables config_file = parse_environment_variables(config_file) YAML.safe_load(config_file) end |
#log(text) ⇒ Object
326 327 328 |
# File 'lib/danger_plugin.rb', line 326 def log(text) puts(text) if @verbose end |
#markdown_issues(results, heading) ⇒ String
Create a markdown table from swiftlint issues
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 |
# File 'lib/danger_plugin.rb', line 277 def markdown_issues(results, heading) = "#### #{heading}\n\n".dup << "File | Line | Reason |\n" << "| --- | ----- | ----- |\n" results.each do |r| filename = r['file'].split('/').last line = r['line'] reason = r['reason'] rule = r['rule_id'] # Other available properties can be found int SwiftLint/…/JSONReporter.swift << "#{filename} | #{line} | #{reason} (#{rule})\n" end end |
#other_issues_message(issues_count) ⇒ Object
314 315 316 317 |
# File 'lib/danger_plugin.rb', line 314 def (issues_count) violations = issues_count == 1 ? 'violation' : 'violations' "SwiftLint also found #{issues_count} more #{violations} with this PR." end |
#parse_environment_variables(file_contents) ⇒ Object
Find all requested environment variables in the given string and replace them with the correct values.
254 255 256 257 258 259 260 261 |
# File 'lib/danger_plugin.rb', line 254 def parse_environment_variables(file_contents) # Matches the file contents for environment variables defined like ${VAR_NAME}. # Replaces them with the environment variable value if it exists. file_contents.gsub(/\$\{([^{}]+)\}/) do |env_var| return env_var if ENV[Regexp.last_match[1]].nil? ENV[Regexp.last_match[1]] end end |
#run_swiftlint(options, additional_swiftlint_args) ⇒ Array
Run swiftlint on all files and returns the issues
166 167 168 169 170 171 172 173 |
# File 'lib/danger_plugin.rb', line 166 def run_swiftlint(, additional_swiftlint_args) result = swiftlint.lint(, additional_swiftlint_args) if result == '' {} else JSON.parse(result).flatten end end |
#run_swiftlint_for_each(files, options, additional_swiftlint_args) ⇒ Array
Run swiftlint on each file and aggregate collect the issues
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/danger_plugin.rb', line 178 def run_swiftlint_for_each(files, , additional_swiftlint_args) # Use `--use-script-input-files` flag along with `SCRIPT_INPUT_FILE_#` ENV # variables to pass the list of files we want swiftlint to lint .merge!(use_script_input_files: true) # Set environment variables: # * SCRIPT_INPUT_FILE_COUNT equal to number of files # * a variable in the form of SCRIPT_INPUT_FILE_# for each file env = script_input(files) result = swiftlint.lint(, additional_swiftlint_args, env) if result == '' {} else JSON.parse(result).flatten end end |
#script_input(files) ⇒ Hash
Converts an array of files into ‘SCRIPT_INPUT_FILE_#` format for use with `–use-script-input-files`
200 201 202 203 204 205 |
# File 'lib/danger_plugin.rb', line 200 def script_input(files) files .map.with_index { |file, i| ["SCRIPT_INPUT_FILE_#{i}", file.to_s] } .push(['SCRIPT_INPUT_FILE_COUNT', files.size.to_s]) .to_h end |
#send_inline_comment(results, method) ⇒ void
This method returns an undefined value.
Send inline comment with danger’s warn or fail method
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 |
# File 'lib/danger_plugin.rb', line 298 def send_inline_comment(results, method) dir = "#{Dir.pwd}/" results.each do |r| github_filename = r['file'].gsub(dir, '') = "#{r['reason']}".dup # extended content here filename = r['file'].split('/').last << "\n" << "`#{r['rule_id']}`" # helps writing exceptions // swiftlint:disable:this rule_id << " `#{filename}:#{r['line']}`" # file:line for pasting into Xcode Quick Open send(method, , file: github_filename, line: r['line']) end end |