forked from PaulTaykalo/swift-scripts
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request PaulTaykalo#2 from PaulTaykalo/unused-variables
Switch to Ruby
- Loading branch information
Showing
3 changed files
with
150 additions
and
68 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,44 +1,23 @@ | ||
# Unused | ||
`unused.sh` Searches for unused swift functions at specified path | ||
`unused.rb` Searches for unused swift functions, and variable at specified path | ||
|
||
## Usage | ||
``` | ||
cd <path-to-the-project> | ||
<path-to-unused.sh>/unused.sh | ||
<path-to-unused.sh>/unused.rb | ||
``` | ||
|
||
## Generated files | ||
- `raw_functions.txt` - a list of all found (declared) functions | ||
- `filtered_functions.txt`- a list of all found functions filtered by some euristics like (override, @IBAction, testXXX) | ||
- `unique_functions.txt`- unique functions names | ||
- `usage.txt` - functions usage (ATM only functions that appeared once are shown) | ||
- `sorted_usage.txt` - functions sorted by the usage (and name) | ||
- `delete_me.txt` - file that contains information about the function and where it is used | ||
|
||
## output | ||
``` | ||
Gathering functions | ||
There are 1898 potential functions found | ||
Gathering usage information | ||
There are 32 potential functions to be deleted found | ||
Gathering usage per each of them | ||
It took 197 seconds. | ||
``` | ||
|
||
## delete_me.txt | ||
This is an example of how outptut can look like | ||
``` | ||
---- selectionColor ---- | ||
ProjectPath/UIColorExtensions.swift- return UIColor(red: 220.0/255, green: 220.0/255, blue: 220.0/255, alpha: 1) | ||
ProjectPath/UIColorExtensions.swift- } | ||
ProjectPath/UIColorExtensions.swift- | ||
ProjectPath/UIColorExtensions.swift: static func selectionColor() -> UIColor { | ||
ProjectPath/UIColorExtensions.swift- return UIColor(red: 240.0/255, green: 240.0/255, blue: 240.0/255, alpha: 1) | ||
ProjectPath/UIColorExtensions.swift- } | ||
ProjectPath/UIColorExtensions.swift- | ||
Item< func loadWebViewTos [private] from:File.swift:23:0> | ||
Total items to be checked 4276 | ||
Total unique items to be checked 1697 | ||
Starting searching globally it can take a while | ||
Item< func applicationHasUnitTestTargetInjected [] from:AnotherFile.swift:31:0> | ||
Item< func getSelectedIds [] from: AnotherFile.swift:82:0> | ||
``` | ||
|
||
## Known issues: | ||
- Fully text search (no fancy stuff) | ||
- A lot of false-positives (protocol functions and overrides) | ||
- A lot of false-negatives (text search, yep) | ||
- A lot of false-positives (protocols, functions, objc interoop, System delegate methods) | ||
- A lot of false-negatives (text search, yep) |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
#!/usr/bin/ruby | ||
|
||
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 serialize | ||
"Item< #{@type.to_s.green} #{@name.to_s.yellow} [#{modifiers.join(" ").cyan}] from: #{@file}:#{@at}:0>" | ||
end | ||
|
||
end | ||
class Unused | ||
def find | ||
items = [] | ||
all_files = Dir.glob("**/*.swift") | ||
|
||
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 | ||
|
||
find_usages_in_files(all_files, items) | ||
|
||
end | ||
|
||
def find_usages_in_files(files, items_in) | ||
items = items_in | ||
usages = items.map { |f| 0 } | ||
files.each { |file| | ||
lines = File.readlines(file).select { |line| !line.match(/^\s*\/\//) } | ||
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) } | ||
} | ||
|
||
|
||
items = items.select { |f| !f.file.start_with?("Pods/") && !f.file.end_with?("Tests.swift") && !f.file.end_with?("Spec.swift") } | ||
if items.length > 0 | ||
puts " #{items.map { |e| e.to_s }.join("\n ")}" | ||
end | ||
end | ||
|
||
def grab_items(file) | ||
result = [] | ||
lines = File.readlines(file).select { |line| !line.match(/^\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 |