Created
January 21, 2024 18:54
-
-
Save atereshkov/e1638a6ddb13a3173f84932f01a5249a to your computer and use it in GitHub Desktop.
Revisions
-
atereshkov renamed this gist
Jan 21, 2024 . 1 changed file with 0 additions and 0 deletions.There are no files selected for viewing
File renamed without changes. -
atereshkov renamed this gist
Jan 21, 2024 . 1 changed file with 0 additions and 0 deletions.There are no files selected for viewing
File renamed without changes. -
atereshkov created this gist
Jan 21, 2024 .There are no files selected for viewing
This file contains hidden or 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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,216 @@ #!/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