rails-css_unused 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +53 -24
- data/LICENSE.txt +21 -21
- data/README.md +132 -132
- data/lib/rails/css_unused/configuration.rb +97 -45
- data/lib/rails/css_unused/railtie.rb +25 -24
- data/lib/rails/css_unused/report.rb +111 -50
- data/lib/rails/css_unused/spinner.rb +80 -0
- data/lib/rails/css_unused/stylesheet_scanner.rb +149 -82
- data/lib/rails/css_unused/tasks.rake +32 -12
- data/lib/rails/css_unused/version.rb +7 -7
- data/lib/rails/css_unused/view_scanner.rb +324 -117
- data/lib/rails/css_unused.rb +40 -36
- data/lib/rails-css_unused.rb +6 -3
- metadata +10 -7
|
@@ -1,50 +1,111 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "spinner"
|
|
4
|
+
|
|
5
|
+
module Rails
|
|
6
|
+
module CssUnused
|
|
7
|
+
# Computes and renders the unused CSS class report.
|
|
8
|
+
class Report
|
|
9
|
+
Ghost = Struct.new(:class_name, :source_file, keyword_init: true)
|
|
10
|
+
|
|
11
|
+
RESET = "\e[0m"
|
|
12
|
+
BOLD = "\e[1m"
|
|
13
|
+
RED = "\e[31m"
|
|
14
|
+
GREEN = "\e[32m"
|
|
15
|
+
YELLOW = "\e[33m"
|
|
16
|
+
CYAN = "\e[36m"
|
|
17
|
+
GRAY = "\e[90m"
|
|
18
|
+
|
|
19
|
+
def initialize(root:, output: $stdout, config: CssUnused.configuration)
|
|
20
|
+
@root = Pathname(root)
|
|
21
|
+
@output = output
|
|
22
|
+
@config = config
|
|
23
|
+
@tty = output.respond_to?(:isatty) && output.isatty
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Returns Array<Ghost> of unused classes.
|
|
27
|
+
def ghost_classes
|
|
28
|
+
used = Spinner.run("Scanning views & components") { ViewScanner.new(root: @root, config: @config).used_classes }
|
|
29
|
+
defined = Spinner.run("Scanning stylesheets") { StylesheetScanner.new(root: @root, config: @config).defined_classes_with_sources }
|
|
30
|
+
|
|
31
|
+
unused_names = defined.map(&:name).to_set - used
|
|
32
|
+
defined
|
|
33
|
+
.select { |dc| unused_names.include?(dc.name) }
|
|
34
|
+
.sort_by(&:name)
|
|
35
|
+
.map { |dc| Ghost.new(class_name: dc.name, source_file: dc.source_file) }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Prints the full report to @output.
|
|
39
|
+
# Returns the exit code (0 = clean, 1 = ghosts found and fail_on_unused set).
|
|
40
|
+
def print_summary
|
|
41
|
+
puts_color "", nil
|
|
42
|
+
puts_color "rails-css_unused v#{Rails::CssUnused::VERSION}", BOLD
|
|
43
|
+
|
|
44
|
+
# ── Scan with spinners ────────────────────────────────────────────
|
|
45
|
+
used = Spinner.run("Scanning views & components") do
|
|
46
|
+
ViewScanner.new(root: @root, config: @config).used_classes
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
defined = Spinner.run("Scanning stylesheets") do
|
|
50
|
+
StylesheetScanner.new(root: @root, config: @config).defined_classes_with_sources
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
ghosts = Spinner.run("Comparing & computing ghost classes") do
|
|
54
|
+
unused_names = defined.map(&:name).to_set - used
|
|
55
|
+
defined
|
|
56
|
+
.select { |dc| unused_names.include?(dc.name) }
|
|
57
|
+
.sort_by(&:name)
|
|
58
|
+
.map { |dc| Ghost.new(class_name: dc.name, source_file: dc.source_file) }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# ── Report ────────────────────────────────────────────────────────
|
|
62
|
+
puts_color "", nil
|
|
63
|
+
puts_color "Ghost Class Report", BOLD
|
|
64
|
+
puts_color "=" * 44, GRAY
|
|
65
|
+
puts_color "Project root : #{@root}", nil
|
|
66
|
+
puts_color "Stylesheet classes found : #{defined.size}", CYAN
|
|
67
|
+
puts_color "View classes referenced : #{used.size}", CYAN
|
|
68
|
+
puts_color "Ghost classes (unused) : #{ghosts.size}", ghosts.empty? ? GREEN : RED
|
|
69
|
+
puts_color "", nil
|
|
70
|
+
|
|
71
|
+
if ghosts.empty?
|
|
72
|
+
puts_color "✓ No ghost classes found — stylesheet is clean!", GREEN
|
|
73
|
+
else
|
|
74
|
+
puts_color "Ghost classes:", BOLD
|
|
75
|
+
puts_color "", nil
|
|
76
|
+
|
|
77
|
+
ghosts.each do |g|
|
|
78
|
+
if @config.show_source_files && g.source_file
|
|
79
|
+
relative = g.source_file.relative_path_from(@root) rescue g.source_file
|
|
80
|
+
puts_color " #{g.class_name}", RED
|
|
81
|
+
puts_color " → #{relative}", GRAY
|
|
82
|
+
else
|
|
83
|
+
puts_color " • #{g.class_name}", RED
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
puts_color "", nil
|
|
88
|
+
puts_color "Tip: Add classes to ignore_classes or ignore_patterns in your initializer", YELLOW
|
|
89
|
+
puts_color " if they are used dynamically (JS, server-side conditions, third-party).", YELLOW
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
puts_color "", nil
|
|
93
|
+
puts_color "Note: Dynamic class names (JS conditions, runtime interpolation) may still", GRAY
|
|
94
|
+
puts_color "produce false positives. Enable scan_javascript_for_classes to reduce them.", GRAY
|
|
95
|
+
puts_color "", nil
|
|
96
|
+
|
|
97
|
+
(ghosts.any? && @config.fail_on_unused) ? 1 : 0
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
def puts_color(msg, color)
|
|
103
|
+
if @tty && color
|
|
104
|
+
@output.puts "#{color}#{msg}#{RESET}"
|
|
105
|
+
else
|
|
106
|
+
@output.puts msg
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "io/console"
|
|
4
|
+
|
|
5
|
+
module Rails
|
|
6
|
+
module CssUnused
|
|
7
|
+
# A lightweight TTY spinner shown while scanning is in progress.
|
|
8
|
+
# Automatically disabled when output is not a TTY (CI, pipes, file output).
|
|
9
|
+
class Spinner
|
|
10
|
+
FRAMES = %w[⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏].freeze
|
|
11
|
+
BOLD = "\e[1m"
|
|
12
|
+
CYAN = "\e[36m"
|
|
13
|
+
GREEN = "\e[32m"
|
|
14
|
+
YELLOW = "\e[33m"
|
|
15
|
+
RESET = "\e[0m"
|
|
16
|
+
CLEAR = "\r\e[K" # move to column 0, erase line
|
|
17
|
+
|
|
18
|
+
INTERVAL = 0.08 # seconds between frames
|
|
19
|
+
|
|
20
|
+
def initialize(output: $stderr)
|
|
21
|
+
@output = output
|
|
22
|
+
@tty = output.respond_to?(:isatty) && output.isatty
|
|
23
|
+
@thread = nil
|
|
24
|
+
@frame = 0
|
|
25
|
+
@label = ""
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Run a block with a spinner, returning the block's return value.
|
|
29
|
+
# The spinner is suppressed when not on a TTY.
|
|
30
|
+
#
|
|
31
|
+
# result = Spinner.run("Scanning stylesheets") { expensive_work }
|
|
32
|
+
#
|
|
33
|
+
def self.run(label, output: $stderr, &block)
|
|
34
|
+
new(output: output).run(label, &block)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def run(label, &block)
|
|
38
|
+
@label = label
|
|
39
|
+
start!
|
|
40
|
+
result = block.call
|
|
41
|
+
stop!(success: true)
|
|
42
|
+
result
|
|
43
|
+
rescue => e
|
|
44
|
+
stop!(success: false)
|
|
45
|
+
raise e
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def start!
|
|
51
|
+
return unless @tty
|
|
52
|
+
|
|
53
|
+
@running = true
|
|
54
|
+
@thread = Thread.new do
|
|
55
|
+
while @running
|
|
56
|
+
render_frame
|
|
57
|
+
sleep INTERVAL
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def stop!(success:)
|
|
63
|
+
return unless @tty
|
|
64
|
+
|
|
65
|
+
@running = false
|
|
66
|
+
@thread&.join
|
|
67
|
+
@thread = nil
|
|
68
|
+
|
|
69
|
+
icon = success ? "#{GREEN}✔#{RESET}" : "#{YELLOW}✘#{RESET}"
|
|
70
|
+
@output.print "#{CLEAR}#{icon} #{BOLD}#{@label}#{RESET}\n"
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def render_frame
|
|
74
|
+
frame = FRAMES[@frame % FRAMES.size]
|
|
75
|
+
@frame += 1
|
|
76
|
+
@output.print "#{CLEAR}#{CYAN}#{frame}#{RESET} #{BOLD}#{@label}…#{RESET}"
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -1,82 +1,149 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Rails
|
|
4
|
-
module CssUnused
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rails
|
|
4
|
+
module CssUnused
|
|
5
|
+
# Scans CSS / SCSS / Sass stylesheets and extracts every defined class selector.
|
|
6
|
+
#
|
|
7
|
+
# Key improvements over v0.1:
|
|
8
|
+
# - Tracks which FILE each class was defined in (for --show-source-files).
|
|
9
|
+
# - Properly strips @charset, @import, @media, @keyframes, @font-face noise.
|
|
10
|
+
# - Ignores pseudo-class arguments like :not(.foo) — .foo is counted as used.
|
|
11
|
+
# - Handles BEM double-dash and double-underscore selectors.
|
|
12
|
+
# - Skips file-extension false positives (.png, .jpg, .css, .js etc.)
|
|
13
|
+
# - Skips @-rule word tokens that look like class selectors (.keyframe-name).
|
|
14
|
+
# - Applies ignore_classes AND ignore_patterns from configuration.
|
|
15
|
+
class StylesheetScanner
|
|
16
|
+
# Match .classname anywhere a class selector can appear.
|
|
17
|
+
# Deliberately broad — noise is filtered by the skip logic below.
|
|
18
|
+
CLASS_SELECTOR_PATTERN = /(?<![:\w])\.(-?[a-zA-Z_][a-zA-Z0-9_-]*)/
|
|
19
|
+
|
|
20
|
+
BLOCK_COMMENT = %r{/\*.*?\*/}m
|
|
21
|
+
LINE_COMMENT = %r{//[^\n]*}
|
|
22
|
+
STRING_LITERAL = /(['"])(?:(?!\1).)*\1/ # strip quoted strings first
|
|
23
|
+
|
|
24
|
+
# At-rule keywords whose following token looks like a class selector
|
|
25
|
+
# but is actually metadata: @charset, @import, @namespace, @keyframes, etc.
|
|
26
|
+
AT_RULE_NOISE = %w[
|
|
27
|
+
charset import namespace supports keyframes
|
|
28
|
+
font-face font-feature-values counter-style layer
|
|
29
|
+
page media document
|
|
30
|
+
].freeze
|
|
31
|
+
|
|
32
|
+
# File extensions that produce false positives when found after a dot.
|
|
33
|
+
EXTENSION_NOISE = %w[
|
|
34
|
+
png jpg jpeg gif svg webp ico bmp tiff
|
|
35
|
+
css scss sass less
|
|
36
|
+
js ts jsx tsx mjs cjs
|
|
37
|
+
rb erb haml slim html htm xml json yaml yml
|
|
38
|
+
pdf zip gz tar woff woff2 ttf eot
|
|
39
|
+
map min
|
|
40
|
+
].freeze
|
|
41
|
+
|
|
42
|
+
# Result struct — class name plus the file it was found in.
|
|
43
|
+
DefinedClass = Struct.new(:name, :source_file, keyword_init: true)
|
|
44
|
+
|
|
45
|
+
def initialize(root:, config: CssUnused.configuration)
|
|
46
|
+
@root = Pathname(root)
|
|
47
|
+
@config = config
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Returns a Set of plain class name strings (for diff calculations).
|
|
51
|
+
def defined_classes
|
|
52
|
+
scan_all.map(&:name).to_set
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Returns Array<DefinedClass> with source file info.
|
|
56
|
+
def defined_classes_with_sources
|
|
57
|
+
scan_all
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def scan_all
|
|
63
|
+
results = []
|
|
64
|
+
ignore_set = @config.ignore_classes.map(&:to_s).to_set
|
|
65
|
+
ignore_patterns = Array(@config.ignore_patterns)
|
|
66
|
+
|
|
67
|
+
each_stylesheet_file do |path, content|
|
|
68
|
+
extract_from(clean(content), path).each do |name|
|
|
69
|
+
next if ignore_set.include?(name)
|
|
70
|
+
next if ignore_patterns.any? { |pat| name.match?(pat) }
|
|
71
|
+
results << DefinedClass.new(name: name, source_file: path)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Deduplicate by name, keeping first occurrence.
|
|
76
|
+
seen = Set.new
|
|
77
|
+
results.select { |dc| seen.add?(dc.name) }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def each_stylesheet_file
|
|
81
|
+
dirs = (@config.stylesheet_paths + @config.javascript_paths)
|
|
82
|
+
.map { |p| @root.join(p) }
|
|
83
|
+
.select(&:directory?)
|
|
84
|
+
|
|
85
|
+
dirs.each do |dir|
|
|
86
|
+
Dir.glob(dir.join("**", "*")).each do |file|
|
|
87
|
+
path = Pathname(file)
|
|
88
|
+
next unless path.file?
|
|
89
|
+
next unless Configuration::CSS_EXTENSIONS.include?(path.extname)
|
|
90
|
+
|
|
91
|
+
content = safe_read(path)
|
|
92
|
+
yield path, content if content
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Remove comments and string literals so we don't extract class names
|
|
98
|
+
# from inside @import "file.css" or url("image.png").
|
|
99
|
+
def clean(css)
|
|
100
|
+
css
|
|
101
|
+
.gsub(BLOCK_COMMENT, " ")
|
|
102
|
+
.gsub(LINE_COMMENT, " ")
|
|
103
|
+
.gsub(STRING_LITERAL, " ")
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def extract_from(css, _path)
|
|
107
|
+
found = Set.new
|
|
108
|
+
|
|
109
|
+
css.scan(CLASS_SELECTOR_PATTERN) do |match|
|
|
110
|
+
name = match[0]
|
|
111
|
+
next if skip?(name, Regexp.last_match.pre_match)
|
|
112
|
+
found << name
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
found
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def skip?(name, pre_match)
|
|
119
|
+
# File extension false positives: .png .jpg .css etc.
|
|
120
|
+
return true if EXTENSION_NOISE.include?(name.downcase)
|
|
121
|
+
|
|
122
|
+
# Pure numbers or starting with a digit
|
|
123
|
+
return true if name.match?(/\A\d/)
|
|
124
|
+
|
|
125
|
+
# Single-character tokens are almost always noise
|
|
126
|
+
return true if name.length == 1
|
|
127
|
+
|
|
128
|
+
# At-rule keyword false positives: @keyframes slide-in → .slide-in is OK,
|
|
129
|
+
# but @charset, @import etc. followed by a dot-prefixed string.
|
|
130
|
+
preceding_word = pre_match.strip.split(/\s+/).last.to_s.gsub(/\A@/, "")
|
|
131
|
+
return true if AT_RULE_NOISE.include?(preceding_word.downcase)
|
|
132
|
+
|
|
133
|
+
# :not(.foo), :is(.foo), :where(.foo) — .foo is a USED class, not defined here.
|
|
134
|
+
# We skip it from the defined set; it will be found in views anyway.
|
|
135
|
+
return true if pre_match.match?(/:(not|is|where|has)\s*\(\s*\z/)
|
|
136
|
+
|
|
137
|
+
false
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def safe_read(path)
|
|
141
|
+
path.read(encoding: "UTF-8")
|
|
142
|
+
rescue ArgumentError, Encoding::UndefinedConversionError
|
|
143
|
+
path.read(encoding: "BINARY").encode("UTF-8", invalid: :replace, undef: :replace)
|
|
144
|
+
rescue Errno::ENOENT, Errno::EACCES
|
|
145
|
+
nil
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
@@ -1,12 +1,32 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
namespace :css_unused do
|
|
4
|
-
desc "List CSS classes defined in stylesheets but
|
|
5
|
-
task report: :environment do
|
|
6
|
-
require "rails/css_unused"
|
|
7
|
-
Rails::CssUnused.report
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :css_unused do
|
|
4
|
+
desc "List CSS classes defined in stylesheets but never referenced in views/components"
|
|
5
|
+
task report: :environment do
|
|
6
|
+
require "rails/css_unused"
|
|
7
|
+
exit_code = Rails::CssUnused.report
|
|
8
|
+
exit(exit_code) if exit_code != 0
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
desc "Alias for css_unused:report"
|
|
12
|
+
task ghosts: :report
|
|
13
|
+
|
|
14
|
+
desc "Exit with code 1 if any ghost classes exist (CI-friendly)"
|
|
15
|
+
task ci: :environment do
|
|
16
|
+
require "rails/css_unused"
|
|
17
|
+
original = Rails::CssUnused.configuration.fail_on_unused
|
|
18
|
+
Rails::CssUnused.configuration.fail_on_unused = true
|
|
19
|
+
exit_code = Rails::CssUnused.report
|
|
20
|
+
Rails::CssUnused.configuration.fail_on_unused = original
|
|
21
|
+
exit(exit_code) if exit_code != 0
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
desc "Show ghost classes with their source stylesheet file"
|
|
25
|
+
task report_verbose: :environment do
|
|
26
|
+
require "rails/css_unused"
|
|
27
|
+
original = Rails::CssUnused.configuration.show_source_files
|
|
28
|
+
Rails::CssUnused.configuration.show_source_files = true
|
|
29
|
+
Rails::CssUnused.report
|
|
30
|
+
Rails::CssUnused.configuration.show_source_files = original
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Rails
|
|
4
|
-
module CssUnused
|
|
5
|
-
VERSION = "0.1
|
|
6
|
-
end
|
|
7
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Rails
|
|
4
|
+
module CssUnused
|
|
5
|
+
VERSION = "0.2.1"
|
|
6
|
+
end
|
|
7
|
+
end
|