Skip to content

Instantly share code, notes, and snippets.

@atereshkov
Created January 21, 2024 18:54
Show Gist options
  • Save atereshkov/e1638a6ddb13a3173f84932f01a5249a to your computer and use it in GitHub Desktop.
Save atereshkov/e1638a6ddb13a3173f84932f01a5249a to your computer and use it in GitHub Desktop.

Revisions

  1. atereshkov renamed this gist Jan 21, 2024. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  2. atereshkov renamed this gist Jan 21, 2024. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  3. atereshkov created this gist Jan 21, 2024.
    216 changes: 216 additions & 0 deletions gistfile1.txt
    Original 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