Skip to content

Instantly share code, notes, and snippets.

@jawwad
Created June 18, 2015 23:06
Show Gist options
  • Save jawwad/60a3f86a0fd78ebffa5e to your computer and use it in GitHub Desktop.
Save jawwad/60a3f86a0fd78ebffa5e to your computer and use it in GitHub Desktop.
#!/usr/bin/env ruby
require 'colorize'
PROJECT_NAME = 'provide'
PROJECT_BUILD_COMMAND = "xcodebuild -workspace #{PROJECT_NAME}.xcworkspace -scheme #{PROJECT_NAME} > /dev/null"
ALL_SWIFT_FILES = Dir["#{PROJECT_NAME}/**/*.swift"]
def main
# checks_to_perform = [
# # :unnecessary_self,
# :private_methods,
# :private_unused_methods,
# :public_unused_methods,
# :class_unused_methods,
# :unused_functions,
# :private_vars,
# :private_set_to_fully_private,
# :private_init_methods,
# :private_subscript_methods,
# :private_unused_vars,
# :unused_lets,
# :unused_weak_vars,
# :unused_vars,
# :var_instead_of_let,
# ]
# This must be empty to perform all checks
checks_to_perform = [
:var_instead_of_let,
# :all_unused_methods,
# :unused_lets,
# :unused_vars,
]
checks_to_skip = [
# :unnecessary_self,
# :unused_lets,
# :unused_vars,
]
# Remove checks to skip
checks_to_delete = []
checks_to_skip.each do |check_name|
$checks_array.each do |check_obj|
if check_name == check_obj[:name]
checks_to_delete << check_obj
puts "Skipping Check: #{check_name}".yellow
end
end
end
checks_to_delete.each { |check_obj| $checks_array.delete(check_obj)}
checks_to_delete = []
unless checks_to_perform.empty?
puts "Only Performing Checks: #{checks_to_perform}".yellow
$checks_array.each do |check_obj|
if !checks_to_perform.include?(check_obj[:name])
checks_to_delete << check_obj
puts "Skipping Check: #{check_obj[:name]}".yellow
end
end
end
checks_to_delete.each { |check_obj| $checks_array.delete(check_obj)}
# Get counts (to estimate time)
$checks_array.each do |check_obj|
before = check_obj[:before]
after = check_obj[:after]
match_count = 0
ALL_SWIFT_FILES.each do |swift_file|
lines = File.readlines(swift_file)
lines.each do |line|
next if should_ignore line
if line =~ /#{before}/
match_count = match_count + 1
end
end
end
max_check_name_length = $checks_array.map { |check_obj| check_obj[:name].length }.max
printf "Check: %-#{max_check_name_length}s - Match Count: #{match_count}\n", check_obj[:name]
check_obj[:match_count] = match_count
end
# sort by lowest matches first
# checks_array.sort! { |h1, h2| h1[:match_count] <=> h2[:match_count] }
match_counts = $checks_array.map { |check_obj| check_obj[:match_count] }
total_match_count = match_counts.inject(:+)
puts "Total match count: #{total_match_count}".cyan
# create branch xcodebuild_audit
system('git checkout -b xcodebuild_audit')
system('git checkout .')
completed_count = 0
$checks_array.each do |check_obj|
check_name = check_obj[:name]
puts "Performing Check: #{check_name}".cyan
perform_check check_obj, completed_count, total_match_count
completed_count += check_obj[:match_count]
end
end
def perform_check check_obj, completed_count, total_match_count
# check_to_perform
before = check_obj[:before]
after = check_obj[:after]
# count the total number of matches
match_count = 0
ALL_SWIFT_FILES.each do |swift_file|
lines = File.readlines(swift_file)
lines.each do |line|
next if should_ignore line
if line =~ /#{before}/
match_count = match_count + 1
end
end
end
# print the count
puts "Match Count: #{match_count}".cyan
# start_time
start_time = Time.now
match_index = 0
# Make sure the project builds
xcodebuild_success = system(PROJECT_BUILD_COMMAND)
# Exit if the project doesn't build
unless xcodebuild_success
puts "Compile Failed!".red
exit
end
ALL_SWIFT_FILES.each_with_index do |swift_file, index|
puts "File: #{index + 1}/#{ALL_SWIFT_FILES.count}: #{swift_file}".green
lines = File.readlines(swift_file)
lines.each do |line|
next if should_ignore line
original_line = line.dup # to use for undo
if line.sub!(/#{before}/, after) # try substitution
match_index += 1
File.open(swift_file, 'w') { |file| file.puts lines }
puts "\n#{match_index + completed_count}/#{total_match_count}"
puts "\n#{match_index}/#{match_count} - Before and After: ".cyan
puts "#{original_line}#{line}"
xcodebuild_success = system(PROJECT_BUILD_COMMAND)
if xcodebuild_success
system("git add #{swift_file}")
puts "Passed".green
else
line.replace original_line
puts "Failed".red
end
# Print Time Stats
time_diff_this_run = Time.now - start_time
average_this_run = time_diff_this_run / match_index
total_expected_this_run = average_this_run * match_count
remaining_time_expected_this_run = total_expected_this_run - time_diff_this_run
grand_total_expected_time = average_this_run * total_match_count
grand_total_completed_count = completed_count + match_index
grand_total_remaining_count = total_match_count - grand_total_completed_count
grand_total_remaining_time = grand_total_remaining_count * average_this_run
puts "Elapsed: #{time_diff_this_run.duration}"
puts "Average: #{average_this_run}"
puts "Total Expected: #{total_expected_this_run.duration}"
puts "Remaining Expected: #{remaining_time_expected_this_run.duration}"
puts "Grand Total Expected: #{grand_total_expected_time.duration}"
puts "Grand Remaining Expected: #{grand_total_remaining_time.duration}"
end
end
File.open(swift_file, 'w') { |file| file.puts lines }
end
# commit changes for this check
system("git commit -m #{check_obj[:name]}")
end
def should_ignore line
$methods_to_ignore.each do |method|
if line =~ /#{method}\(/
# puts "Skipping: #{line}".yellow
return true
end
end
$lines_to_ignore.each do |match|
if line =~ /.*#{match}.*/
# puts "Skipping: #{line}".yellow
return true
end
end
return false
end
class Numeric
def duration
secs = self.to_int
mins = secs / 60
hours = mins / 60
if hours > 0
"#{hours} hr #{mins % 60} min"
elsif mins > 0
"#{mins} min #{secs % 60} sec"
elsif secs >= 0
"#{secs} sec"
end
end
end
# All Checks
$checks_array = [
{
:name => :unnecessary_self,
:before => '\bself\.',
:after => '',
},
{
:name => :all_unused_methods,
:before => '\bfunc\s+',
:after => 'func unused_'
},
{
:name => :unused_lets,
:before => '\blet ',
:after => 'let unused_',
},
{
:name => :unused_vars,
:before => '\bvar ',
:after => 'var unused_',
},
{
:name => :var_instead_of_let,
:before => '\bvar ',
:after => 'let ',
},
{
:name => :private_methods,
:before => '^ func',
:after => ' private func'
},
{
:name => :private_unused_methods,
:before => '^ private func ',
:after => ' private func unused_',
},
{
:name => :public_unused_methods,
:before => '^ func ',
:after => ' func unused_',
},
{
:name => :class_unused_methods,
:before => '^ class func ',
:after => ' class func unused_',
},
{
:name => :unused_functions,
:before => '^func ',
:after => 'func unused_',
},
{
:name => :private_vars,
:before => '^ var',
:after => ' private var',
},
{
:name => :private_set_to_fully_private,
:before => 'private(set)',
:after => 'private',
},
{
:name => :private_init_methods,
:before => ' init\b',
:after => ' private init',
},
{
:name => :private_subscript_methods,
:before => ' subscript\b',
:after => ' private subscript',
},
{
:name => :private_unused_vars,
:before => '^ private var ',
:after => ' private var unused_',
},
{
:name => :unused_weak_vars,
:before => 'weak var ',
:after => 'weak var unused_',
},
]
# Methods To Ignore
$methods_to_ignore = %w[
application
applicationDidBecomeActive
applicationDidEnterBackground
applicationWillEnterForeground
applicationWillResignActive
applicationWillTerminate
clearSDImageCache
collectionView
connection
connectionDidFinishLoading
gestureRecognizer
pageViewController
performSegueOnce
pickerView
scrollViewDidEndDecelerating
scrollViewDidEndScrolling
tableView
textField
textFieldDidBeginEditing
textFieldDidEndEditing
textFieldShouldEndEditing
textFieldShouldReturn
textView
textViewDidBeginEditing
textViewDidChange
textViewDidEndEditing
webViewDidFailLoadWithError
webViewDidFinishLoad
].map { |method_name| "func #{method_name}"}
$lines_to_ignore = [
'(let _)',
'var window: UIWindow',
'class var KIF_SCREENSHOTS',
'var enableInputClicksWhenVisible: Bool', # UIInputViewAudioFeedback protocol
'let CurrentBuildConfig',
'let CurrentEnvironment',
'because the purpose var gets changed',
'read-only var in Swift casts',
'var keyboardType: UIKeyboardType', # UITextInputTraits protocol
]
# Run Main
main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment