rails-css_unused 0.1.0 → 0.2.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0d73f075e43405f546976ed7bac31bda9ccc6ac96bfa16f0dc0fa6dbc78a3242
4
- data.tar.gz: de225a6ff929dfbb1f1075cc142beb93b7e25715c76905efc3daacb00d531662
3
+ metadata.gz: 6be69a5f6aa27dedb1aef9797167bf8b8abed066632fa8fee01fe7fefa54554c
4
+ data.tar.gz: c808dfa0ed3b70ad3a03ec158af1ef5d00a7bcf97ff899b6f4613853955c67fd
5
5
  SHA512:
6
- metadata.gz: 0a0dd43d7354169aa25f7a3274e58ab05d5d4e2a36cb6546994fbf22512404c2d5d8bb6827116358a71fb62911427b9e2692f6a6753fd5040e848043666e30ab
7
- data.tar.gz: af1263e05f8e44c228334495438c493e8c079c2f03e21118435a6d10fe5971cf07d90e7bd610d5654a6731d61b35923c1d512443bc06fc9c608a9feae634424d
6
+ metadata.gz: 62d8dff321fb2dddf72d618747b2e22511a438864b0372067c9c222442f1a39f30513a8922d014a663c4dde0955ca44614d3b9506476df78d41ef7a517ed88cb
7
+ data.tar.gz: 3e7e72d6cb22f7a6a1138162771cb3906e0b9a1399070131ab660b7a43286f4957faa15dd6d51b14a3981f36779264aa6da38a9ab04cbec1b8c889c189f30db8
data/CHANGELOG.md CHANGED
@@ -1,24 +1,30 @@
1
- # Changelog
2
-
3
- All notable changes to this project will be documented in this file.
4
-
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
- and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
-
8
- ## [0.1.0] - 2026-06-01
9
-
10
- ### Added
11
-
12
- - `css_unused:report` and `css_unused:ghosts` Rake tasks (via Railtie)
13
- - View scanner for `app/views` and `app/components` (ERB, HAML, `class:` helpers)
14
- - Stylesheet scanner for CSS/SCSS/Sass under assets and `app/javascript`
15
- - `Rails::CssUnused.report` and `Rails::CssUnused.ghost_classes` APIs
16
- - `Rails::CssUnused.configure` for paths and ignored classes
17
-
18
- ### Known limitations
19
-
20
- - Dynamic classes in ERB may not be detected
21
- - Tailwind / build-time utilities may report false positives
22
- - Classes added only via JavaScript are not detected
23
-
24
- [0.1.0]: https://github.com/sghani001/rails-css_unused/releases/tag/v0.1.0
1
+ # Changelog
2
+
3
+ ## [0.2.0] - 2026-06-02
4
+
5
+ ### 🔧 Fixed
6
+ - **Extension noise eliminated** `.png`, `.css`, `.jpg`, `.js` etc. no longer appear as ghost classes
7
+ - **At-rule noise eliminated** — `@charset`, `@import`, `@keyframes` tokens no longer pollute the class list
8
+ - **`:not()` false positives fixed** — `.foo` inside `:not(.foo)` is no longer treated as a new definition
9
+ - **Double-scanning removed** — `ViewScanner` and `StylesheetScanner` were each instantiated twice in `print_summary`, wasting time and producing wrong counts
10
+
11
+ ### ✨ Added
12
+ - **Source file attribution** — `show_source_files: true` shows which stylesheet each ghost comes from
13
+ - **CI mode** `fail_on_unused: true` + `rake css_unused:ci` exits with code 1 on any ghost
14
+ - **`rake css_unused:report_verbose`** shows ghost classes with source file inline
15
+ - **HAML & Slim support** — proper extraction of `.foo.bar` shorthand selectors
16
+ - **Slim template support** — `div.foo` class shorthand
17
+ - **Ruby component support** — scans ViewComponent `.rb` files for `class:` attributes
18
+ - **Stimulus / JS support** — `classList.add("foo")` calls detected as used classes
19
+ - **ERB dynamic class extraction** — `class="<%= cond ? 'a' : 'b' %>"` — static string parts extracted
20
+ - **`ignore_patterns`** regex-based ignore list (e.g. `/\Ajs-/`, `/\Ais-/`)
21
+ - **`scan_javascript_for_classes`** opt-in JS scanning for dynamically applied classes
22
+ - **`scan_ruby_components`** opt-in scanning of `.rb` component files
23
+ - **Colour terminal output** — red/green/grey ANSI when outputting to a TTY
24
+ - **BEM double-underscore selectors** — `block__element--modifier` handled correctly
25
+
26
+ ### 🗑️ Removed
27
+ - Confusing `stylesheet_scanner.rb` noise filter that used a hardcoded `%w[import media charset...]` list (replaced with proper context-aware skip logic)
28
+
29
+ ## [0.1.0] - 2026-05-01
30
+ - Initial release
data/LICENSE.txt CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 sghani001
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md CHANGED
@@ -1,132 +1,132 @@
1
- # rails-css_unused
2
-
3
- [![Gem Version](https://img.shields.io/gem/v/rails-css_unused)](https://rubygems.org/gems/rails-css_unused)
4
- [![GitHub](https://img.shields.io/github/stars/sghani001/rails-css_unused?style=social)](https://github.com/sghani001/rails-css_unused)
5
-
6
- Find **ghost CSS classes** in your Rails app: selectors that exist in your stylesheets but never show up in views or ViewComponents.
7
-
8
- Pure static analysis — no browser, no headless Chrome. Built for the usual Rails paths (`app/views`, `app/components`, `app/assets/stylesheets`).
9
-
10
- ## Requirements
11
-
12
- - Ruby **>= 3.1**
13
- - Rails **>= 7.0** (via `railties`)
14
-
15
- ## Installation
16
-
17
- ```ruby
18
- # Gemfile
19
- gem "rails-css_unused", group: :development
20
- ```
21
-
22
- ```bash
23
- bundle install
24
- ```
25
-
26
- Or install the gem directly (after it is published on RubyGems):
27
-
28
- ```bash
29
- gem install rails-css_unused
30
- ```
31
-
32
- During development of this gem itself, use a path install:
33
-
34
- ```ruby
35
- gem "rails-css_unused", path: "../rails-css_unused", group: :development
36
- ```
37
-
38
- ## Usage
39
-
40
- ```bash
41
- bin/rails css_unused:report
42
- # or
43
- bin/rails css_unused:ghosts
44
- ```
45
-
46
- Example output:
47
-
48
- ```
49
- rails-css_unused — Ghost Class Report
50
- ========================================
51
- Project root: /path/to/myapp
52
- Classes in stylesheets: 142
53
- Classes referenced in views: 118
54
- Ghost classes (in CSS, not in views): 24
55
-
56
- Ghost classes:
57
- legacy-banner
58
- orphan-widget
59
- ...
60
- ```
61
-
62
- ## What it scans
63
-
64
- | Source | Paths (default) |
65
- |--------|-----------------|
66
- | Views | `app/views` — `.html.erb`, `.html.haml`, `.haml`, `.erb`, `.slim` |
67
- | Components | `app/components` — same extensions |
68
- | Styles | `app/assets/stylesheets`, `app/assets/builds` — `.css`, `.scss`, `.sass` |
69
- | JS CSS | `app/javascript` — same stylesheet extensions |
70
-
71
- ### Class detection in templates
72
-
73
- - `class="foo bar"`
74
- - `class: "foo"`, `class: 'foo'`
75
- - `class: %w[foo bar]`, `class: ["foo", "bar"]`
76
- - `tag.div ..., class: "foo"`
77
- - Basic HAML `.class-name` segments
78
-
79
- ## Configuration
80
-
81
- ```ruby
82
- # config/initializers/rails_css_unused.rb
83
- Rails::CssUnused.configure do |config|
84
- config.ignore_classes += %w[active hidden]
85
- config.stylesheet_paths << "vendor/assets/stylesheets"
86
- config.view_paths << "app/views/admin"
87
- end
88
- ```
89
-
90
- Optional `config/application.rb` hook:
91
-
92
- ```ruby
93
- config.rails_css_unused = ActiveSupport::OrderedOptions.new
94
- config.rails_css_unused.ignore_classes = %w[sr-only]
95
- ```
96
-
97
- ## Limitations
98
-
99
- Static analysis cannot see everything:
100
-
101
- - **Dynamic classes** — `class="<%= dynamic %>"` may be missed or only partially detected.
102
- - **Tailwind / utility frameworks** — utilities are often generated at build time; many “ghost” hits are false positives unless you scan compiled `app/assets/builds` and tune `ignore_classes`.
103
- - **JavaScript-added classes** — Stimulus, React, or `element.classList.add` are not scanned.
104
- - **@extend / mixins** — SCSS may define classes only used inside other rules; review before deleting.
105
-
106
- Treat the report as a **triage list**, not an automatic delete command.
107
-
108
- ## Programmatic API
109
-
110
- ```ruby
111
- Rails::CssUnused.ghost_classes
112
- # => ["orphan-widget", "legacy-banner", ...]
113
-
114
- Rails::CssUnused.report
115
- ```
116
-
117
- ## Development
118
-
119
- ```bash
120
- bundle install
121
- ruby -Ilib -S rspec
122
- ```
123
-
124
- ## Contributing
125
-
126
- Bug reports and pull requests are welcome on [GitHub](https://github.com/sghani001/rails-css_unused).
127
-
128
- Maintainers: see [PUBLISHING.md](PUBLISHING.md) for the release checklist and RubyGems publish steps.
129
-
130
- ## License
131
-
132
- The gem is available as open source under the terms of the [MIT License](LICENSE.txt).
1
+ # rails-css_unused
2
+
3
+ **Find unused CSS classes in your Rails app — zero runtime overhead, zero false positives from file extensions or at-rules.**
4
+
5
+ [![Gem Version](https://badge.fury.io/rb/rails-css_unused.svg)](https://rubygems.org/gems/rails-css_unused)
6
+
7
+ ---
8
+
9
+ ## Why this gem?
10
+
11
+ | Feature | deadweight | rails-css_unused |
12
+ |---------|-----------|-----------------|
13
+ | Maintained | abandoned | ✅ |
14
+ | Rails 7+ | ❌ | ✅ |
15
+ | Static analysis (no server needed) | ❌ needs running server | ✅ |
16
+ | BEM selector support | ❌ | ✅ |
17
+ | HAML / Slim support | ❌ | ✅ |
18
+ | ViewComponent / Phlex support | ❌ | ✅ |
19
+ | Stimulus JS class detection | ❌ | ✅ |
20
+ | ERB dynamic class extraction | ❌ | ✅ |
21
+ | Source file attribution | ❌ | ✅ |
22
+ | CI exit code support | ❌ | ✅ |
23
+ | Regex ignore patterns | ❌ | ✅ |
24
+ | Extension false-positive protection | ❌ | ✅ |
25
+
26
+ ---
27
+
28
+ ## Installation
29
+
30
+ ```ruby
31
+ # Gemfile
32
+ gem "rails-css_unused", group: :development
33
+ ```
34
+
35
+ ```bash
36
+ bundle install
37
+ ```
38
+
39
+ ---
40
+
41
+ ## Usage
42
+
43
+ ```bash
44
+ # Standard report
45
+ bundle exec rake css_unused:report
46
+
47
+ # With source file for each ghost class
48
+ bundle exec rake css_unused:report_verbose
49
+
50
+ # CI — exits with code 1 if any ghost classes found
51
+ bundle exec rake css_unused:ci
52
+ ```
53
+
54
+ ### Programmatic usage
55
+
56
+ ```ruby
57
+ # List ghost class names
58
+ Rails::CssUnused.ghost_classes # => ["old-btn", "legacy-card"]
59
+
60
+ # Full report to custom IO
61
+ Rails::CssUnused.report(output: File.open("report.txt", "w"))
62
+ ```
63
+
64
+ ---
65
+
66
+ ## Configuration
67
+
68
+ Create `config/initializers/css_unused.rb`:
69
+
70
+ ```ruby
71
+ Rails::CssUnused.configure do |config|
72
+ # Paths to scan (relative to Rails.root)
73
+ config.stylesheet_paths = %w[app/assets/stylesheets app/assets/builds]
74
+ config.view_paths = %w[app/views]
75
+ config.component_paths = %w[app/components]
76
+ config.javascript_paths = %w[app/javascript]
77
+
78
+ # Exact class names to never flag as ghost
79
+ config.ignore_classes = %w[
80
+ clearfix sr-only visually-hidden
81
+ active disabled selected
82
+ ]
83
+
84
+ # Regex patterns — any matching class is ignored
85
+ config.ignore_patterns = [
86
+ /\Ajs-/, # JS hook classes (js-submit-btn)
87
+ /\Ais-/, # state classes (is-active, is-open)
88
+ /\Ahas-/, # state classes (has-error)
89
+ ]
90
+
91
+ # Detect classes added via classList.add() in JS files
92
+ config.scan_javascript_for_classes = true
93
+
94
+ # Scan ViewComponent .rb files for class: attributes
95
+ config.scan_ruby_components = true
96
+
97
+ # Show which stylesheet each ghost class came from
98
+ config.show_source_files = false
99
+
100
+ # Exit with code 1 in CI when ghosts are found
101
+ config.fail_on_unused = false
102
+ end
103
+ ```
104
+
105
+ ---
106
+
107
+ ## What is a "ghost class"?
108
+
109
+ A ghost class is a CSS class that is:
110
+ - ✅ **Defined** in a `.css`, `.scss`, or `.sass` file
111
+ - ❌ **Never referenced** in any `.erb`, `.haml`, `.slim`, `.rb`, or `.js` file
112
+
113
+ Ghost classes add dead weight to your CSS bundle and confuse future developers.
114
+
115
+ ---
116
+
117
+ ## Reducing false positives
118
+
119
+ Some classes are legitimately hard to detect statically:
120
+
121
+ | Situation | Solution |
122
+ |-----------|----------|
123
+ | `class="status-#{record.state}"` | Add `ignore_patterns << /\Astatus-/` |
124
+ | JS-only classes (e.g. `js-modal-open`) | Add `ignore_patterns << /\Ajs-/` or enable `scan_javascript_for_classes` |
125
+ | Third-party component classes | Add prefix pattern to `ignore_patterns` |
126
+ | Turbo / Stimulus data-action targets | Add to `ignore_classes` |
127
+
128
+ ---
129
+
130
+ ## License
131
+
132
+ MIT
@@ -1,45 +1,97 @@
1
- # frozen_string_literal: true
2
-
3
- module Rails
4
- module CssUnused
5
- class Configuration
6
- VIEW_EXTENSIONS = %w[.html.erb .html.haml .haml .erb .slim .jbuilder].freeze
7
- COMPONENT_EXTENSIONS = %w[.html.erb .html.haml .haml .erb .slim].freeze
8
- CSS_EXTENSIONS = %w[.css .scss .sass].freeze
9
-
10
- attr_accessor :view_paths,
11
- :component_paths,
12
- :stylesheet_paths,
13
- :javascript_paths,
14
- :ignore_classes,
15
- :ignore_selectors_matching
16
-
17
- def initialize
18
- @view_paths = ["app/views"]
19
- @component_paths = ["app/components"]
20
- @stylesheet_paths = ["app/assets/stylesheets", "app/assets/builds"]
21
- @javascript_paths = ["app/javascript"]
22
- @ignore_classes = %w[
23
- clearfix
24
- sr-only
25
- visually-hidden
26
- ]
27
- @ignore_selectors_matching = []
28
- end
29
- end
30
-
31
- class << self
32
- def configuration
33
- @configuration ||= Configuration.new
34
- end
35
-
36
- def configure
37
- yield(configuration)
38
- end
39
-
40
- def reset_configuration!
41
- @configuration = nil
42
- end
43
- end
44
- end
45
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Rails
4
+ module CssUnused
5
+ class Configuration
6
+ # File extensions treated as view templates
7
+ VIEW_EXTENSIONS = %w[.erb .haml .slim].freeze
8
+ # Compound view extensions (e.g. foo.html.erb)
9
+ COMPOUND_VIEW_ENDINGS = %w[.html.erb .html.haml .html.slim].freeze
10
+ # CSS/preprocessor extensions to scan
11
+ CSS_EXTENSIONS = %w[.css .scss .sass].freeze
12
+ # Ruby component file extensions
13
+ COMPONENT_EXTENSIONS = %w[.rb].freeze
14
+
15
+ # ── Scan paths ────────────────────────────────────────────────────────
16
+ # Directories to search for view templates (relative to project root).
17
+ attr_accessor :view_paths
18
+
19
+ # Directories to search for ViewComponent / Phlex / etc. template files.
20
+ attr_accessor :component_paths
21
+
22
+ # Directories to search for stylesheets.
23
+ attr_accessor :stylesheet_paths
24
+
25
+ # Directories to search for JS files (e.g. Stimulus controllers that
26
+ # add classes dynamically — detected as allowlisted dynamic patterns).
27
+ attr_accessor :javascript_paths
28
+
29
+ # ── Ignore lists ─────────────────────────────────────────────────────
30
+ # Exact class names to always treat as "used" (never reported as ghost).
31
+ # Good for: utility classes, JS-hook classes, server-side rendered classes.
32
+ attr_accessor :ignore_classes
33
+
34
+ # Regex patterns — any CSS class name matching any pattern is ignored.
35
+ # Good for: third-party prefixes, state classes, BEM modifier variants.
36
+ # Example: [/\Ajs-/, /\Ais-/, /\Ahas-/]
37
+ attr_accessor :ignore_patterns
38
+
39
+ # ── Dynamic class detection ───────────────────────────────────────────
40
+ # When true, scan JS/Stimulus files for string literals that look like
41
+ # CSS class names and add them to the used-class set automatically.
42
+ attr_accessor :scan_javascript_for_classes
43
+
44
+ # When true, scan Ruby component files (.rb) for string literals that
45
+ # look like CSS classes passed to html attributes.
46
+ attr_accessor :scan_ruby_components
47
+
48
+ # ── Output ───────────────────────────────────────────────────────────
49
+ # When true, show which file each ghost class was defined in.
50
+ attr_accessor :show_source_files
51
+
52
+ # When true, exit with code 1 if any ghost classes are found.
53
+ # Useful for CI pipelines.
54
+ attr_accessor :fail_on_unused
55
+
56
+ def initialize
57
+ @view_paths = %w[app/views]
58
+ @component_paths = %w[app/components]
59
+ @stylesheet_paths = %w[app/assets/stylesheets app/assets/builds]
60
+ @javascript_paths = %w[app/javascript]
61
+
62
+ @ignore_classes = %w[
63
+ clearfix sr-only visually-hidden
64
+ active disabled selected current open closed
65
+ show hide hidden visible
66
+ fade collapse collapsing
67
+ ]
68
+
69
+ @ignore_patterns = [
70
+ /\Ajs-/, # JS-hook classes
71
+ /\Ais-/, # state classes (is-active, is-open)
72
+ /\Ahas-/, # state classes (has-error)
73
+ /\Adata-/, # sometimes leaked into CSS scanners
74
+ ]
75
+
76
+ @scan_javascript_for_classes = true
77
+ @scan_ruby_components = true
78
+ @show_source_files = false
79
+ @fail_on_unused = false
80
+ end
81
+ end
82
+
83
+ class << self
84
+ def configuration
85
+ @configuration ||= Configuration.new
86
+ end
87
+
88
+ def configure
89
+ yield(configuration)
90
+ end
91
+
92
+ def reset_configuration!
93
+ @configuration = nil
94
+ end
95
+ end
96
+ end
97
+ end
@@ -1,24 +1,25 @@
1
- # frozen_string_literal: true
2
-
3
- module Rails
4
- module CssUnused
5
- class Railtie < Rails::Railtie
6
- rake_tasks do
7
- load File.expand_path("tasks.rake", __dir__)
8
- end
9
-
10
- initializer "rails_css_unused.configuration" do
11
- if Rails.application.config.respond_to?(:rails_css_unused)
12
- cfg = Rails.application.config.rails_css_unused
13
- CssUnused.configure do |c|
14
- c.view_paths = cfg.view_paths if cfg.respond_to?(:view_paths) && cfg.view_paths
15
- c.component_paths = cfg.component_paths if cfg.respond_to?(:component_paths) && cfg.component_paths
16
- c.stylesheet_paths = cfg.stylesheet_paths if cfg.respond_to?(:stylesheet_paths) && cfg.stylesheet_paths
17
- c.javascript_paths = cfg.javascript_paths if cfg.respond_to?(:javascript_paths) && cfg.javascript_paths
18
- c.ignore_classes = cfg.ignore_classes if cfg.respond_to?(:ignore_classes) && cfg.ignore_classes
19
- end
20
- end
21
- end
22
- end
23
- end
24
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Rails
4
+ module CssUnused
5
+ class Railtie < Rails::Railtie
6
+ rake_tasks do
7
+ load File.expand_path("tasks.rake", __dir__)
8
+ end
9
+
10
+ # Support configuration via config/initializers/css_unused.rb:
11
+ #
12
+ # Rails::CssUnused.configure do |c|
13
+ # c.fail_on_unused = true
14
+ # c.show_source_files = true
15
+ # c.ignore_patterns << /\Ashopify-/
16
+ # c.scan_javascript_for_classes = true
17
+ # end
18
+ #
19
+ initializer "rails_css_unused.configuration" do
20
+ # Nothing auto-applied — users configure via Rails::CssUnused.configure.
21
+ # The Railtie merely makes rake tasks available.
22
+ end
23
+ end
24
+ end
25
+ end