Created
January 21, 2024 18:54
-
-
Save atereshkov/e1638a6ddb13a3173f84932f01a5249a to your computer and use it in GitHub Desktop.
Detect unused swift code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/ruby | |
#encoding: utf-8 | |
Encoding.default_external = Encoding::UTF_8 | |
Encoding.default_internal = Encoding::UTF_8 | |
class Item | |
def initialize(file, line, at) | |
@file = file | |
@line = line | |
@at = at + 1 | |
if match = line.match(/(func|let|var|class|enum|struct|protocol)\s+(\w+)/) | |
@type = match.captures[0] | |
@name = match.captures[1] | |
end | |
end | |
def modifiers | |
return @modifiers if @modifiers | |
@modifiers = [] | |
if match = @line.match(/(.*?)#{@type}/) | |
@modifiers = match.captures[0].split(" ") | |
end | |
return @modifiers | |
end | |
def name | |
@name | |
end | |
def file | |
@file | |
end | |
def to_s | |
serialize | |
end | |
def to_str | |
serialize | |
end | |
def full_file_path | |
Dir.pwd + '/' + @file | |
end | |
def serialize | |
"Item< #{@type.to_s.green} #{@name.to_s.yellow} [#{modifiers.join(" ").cyan}] from: #{@file}:#{@at}:0>" | |
end | |
def to_xcode | |
"#{full_file_path}:#{@at}:0: warning: #{@type.to_s} #{@name.to_s} is unused" | |
end | |
end | |
class Unused | |
def find | |
items = [] | |
all_files = Dir.glob("**/*.swift").reject do |path| | |
File.directory?(path) | |
end | |
all_files.each { |my_text_file| | |
file_items = grab_items(my_text_file) | |
file_items = filter_items(file_items) | |
non_private_items, private_items = file_items.partition { |f| !f.modifiers.include?("private") && !f.modifiers.include?("fileprivate") } | |
items += non_private_items | |
# Usage within the file | |
if private_items.length > 0 | |
find_usages_in_files([my_text_file], [], private_items) | |
end | |
} | |
puts "Total items to be checked #{items.length}" | |
items = items.uniq { |f| f.name } | |
puts "Total unique items to be checked #{items.length}" | |
puts "Starting searching globally it can take a while".green | |
xibs = Dir.glob("**/*.xib") | |
storyboards = Dir.glob("**/*.storyboard") | |
find_usages_in_files(all_files, xibs + storyboards, items) | |
end | |
def ignore_files_with_regexps(files, regexps) | |
files.select { |f| regexps.all? { |r| Regexp.new(r).match(f.file).nil? } } | |
end | |
def ignoring_regexps_from_command_line_args | |
regexps = [] | |
should_skip_predefined_ignores = false | |
arguments = ARGV.clone | |
until arguments.empty? | |
item = arguments.shift | |
if item == "--ignore" | |
regex = arguments.shift | |
regexps += [regex] | |
end | |
if item == "--skip-predefined-ignores" | |
should_skip_predefined_ignores = true | |
end | |
end | |
if not should_skip_predefined_ignores | |
regexps += [ | |
"^Pods/", | |
"fastlane/", | |
"Tests.swift$", | |
"Spec.swift$", | |
"Tests/" | |
] | |
end | |
regexps | |
end | |
def find_usages_in_files(files, xibs, items_in) | |
items = items_in | |
usages = items.map { |f| 0 } | |
files.each { |file| | |
lines = File.readlines(file).map {|line| line.gsub(/^[^\/]*\/\/.*/, "") } | |
words = lines.join("\n").split(/\W+/) | |
words_arrray = words.group_by { |w| w }.map { |w, ws| [w, ws.length] }.flatten | |
wf = Hash[*words_arrray] | |
items.each_with_index { |f, i| | |
usages[i] += (wf[f.name] || 0) | |
} | |
# Remove all items which has usage 2+ | |
indexes = usages.each_with_index.select { |u, i| u >= 2 }.map { |f, i| i } | |
# reduce usage array if we found some functions already | |
indexes.reverse.each { |i| usages.delete_at(i) && items.delete_at(i) } | |
} | |
xibs.each { |xib| | |
lines = File.readlines(xib).map {|line| line.gsub(/^\s*\/\/.*/, "") } | |
full_xml = lines.join(" ") | |
classes = full_xml.scan(/(class|customClass)="([^"]+)"/).map { |cd| cd[1] } | |
classes_array = classes.group_by { |w| w }.map { |w, ws| [w, ws.length] }.flatten | |
wf = Hash[*classes_array] | |
items.each_with_index { |f, i| | |
usages[i] += (wf[f.name] || 0) | |
} | |
# Remove all items which has usage 2+ | |
indexes = usages.each_with_index.select { |u, i| u >= 2 }.map { |f, i| i } | |
# reduce usage array if we found some functions already | |
indexes.reverse.each { |i| usages.delete_at(i) && items.delete_at(i) } | |
} | |
regexps = ignoring_regexps_from_command_line_args() | |
items = ignore_files_with_regexps(items, regexps) | |
if items.length > 0 | |
if ARGV[0] == "xcode" | |
$stderr.puts "#{items.map { |e| e.to_xcode }.join("\n")}" | |
else | |
puts "#{items.map { |e| e.to_s }.join("\n ")}" | |
end | |
end | |
end | |
def grab_items(file) | |
lines = File.readlines(file).map {|line| line.gsub(/^\s*\/\/.*/, "") } | |
items = lines.each_with_index.select { |line, i| line[/(func|let|var|class|enum|struct|protocol)\s+\w+/] }.map { |line, i| Item.new(file, line, i)} | |
end | |
def filter_items(items) | |
items.select { |f| | |
!f.name.start_with?("test") && !f.modifiers.include?("@IBAction") && !f.modifiers.include?("override") && !f.modifiers.include?("@objc") && !f.modifiers.include?("@IBInspectable") | |
} | |
end | |
end | |
class String | |
def black; "\e[30m#{self}\e[0m" end | |
def red; "\e[31m#{self}\e[0m" end | |
def green; "\e[32m#{self}\e[0m" end | |
def yellow; "\e[33m#{self}\e[0m" end | |
def blue; "\e[34m#{self}\e[0m" end | |
def magenta; "\e[35m#{self}\e[0m" end | |
def cyan; "\e[36m#{self}\e[0m" end | |
def gray; "\e[37m#{self}\e[0m" end | |
def bg_black; "\e[40m#{self}\e[0m" end | |
def bg_red; "\e[41m#{self}\e[0m" end | |
def bg_green; "\e[42m#{self}\e[0m" end | |
def bg_brown; "\e[43m#{self}\e[0m" end | |
def bg_blue; "\e[44m#{self}\e[0m" end | |
def bg_magenta; "\e[45m#{self}\e[0m" end | |
def bg_cyan; "\e[46m#{self}\e[0m" end | |
def bg_gray; "\e[47m#{self}\e[0m" end | |
def bold; "\e[1m#{self}\e[22m" end | |
def italic; "\e[3m#{self}\e[23m" end | |
def underline; "\e[4m#{self}\e[24m" end | |
def blink; "\e[5m#{self}\e[25m" end | |
def reverse_color; "\e[7m#{self}\e[27m" end | |
end | |
Unused.new.find |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment