solargraph-arc 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0f641277bcc7f3c74cb9f54ce2b9b4fbed6659bbb18d7220f557133874dcbf01
4
+ data.tar.gz: 3ad1bafb9a1976a233d8ce47bc9d551cd68b08c1aed272e95e9512aadd471227
5
+ SHA512:
6
+ metadata.gz: a3ceb156986b4249722188c9403c27d3dd48dd4912cf228827128a33d6cb6a58ae9326f2f482c32603cf7a191478a083eeb959849a1e1d7891bf0f5b29365620
7
+ data.tar.gz: 806f4b1d9dc59fcdf148f3ee89c52215481505c2469a8e91c2a2e36d6de8daf6743382eed58ebff0c98114cc8826056440f501bb8c5dda9e700a4bc840feff23
@@ -0,0 +1,33 @@
1
+ ---
2
+ name: Bug report
3
+ about: Create a report to help us improve
4
+ title: ''
5
+ labels: ''
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ **Describe the bug**
11
+ A clear and concise description of what the bug is.
12
+
13
+ **To Reproduce**
14
+ Steps to reproduce the behavior:
15
+ 1. Go to '...'
16
+ 2. Click on '....'
17
+ 3. Scroll down to '....'
18
+ 4. See error
19
+
20
+ **Expected behavior**
21
+ A clear and concise description of what you expected to happen.
22
+
23
+ **Screenshots**
24
+ If applicable, add screenshots to help explain your problem.
25
+
26
+ **Debug log**
27
+
28
+ Run the following command in the project you are having problems:
29
+ ```
30
+ ruby -r'solargraph-arc' -e 'Solargraph::Arc::Debug.run()'
31
+ ```
32
+
33
+ and paste the output here
@@ -0,0 +1,41 @@
1
+ # This workflow uses actions that are not certified by GitHub.
2
+ # They are provided by a third-party and are governed by
3
+ # separate terms of service, privacy policy, and support
4
+ # documentation.
5
+ # This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
6
+ # For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
7
+
8
+ name: Ruby
9
+
10
+ on:
11
+ push:
12
+ branches: [ master ]
13
+ pull_request:
14
+ branches: [ master ]
15
+
16
+ jobs:
17
+ test:
18
+ runs-on: ubuntu-latest
19
+ strategy:
20
+ matrix:
21
+ ruby-version:
22
+ - '2.5'
23
+ # - '2.6'
24
+ # - '2.7'
25
+
26
+ steps:
27
+ - uses: actions/checkout@v2
28
+ - name: Set up Ruby
29
+ # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
30
+ # change this to (see https://github.com/ruby/setup-ruby#versioning):
31
+ # uses: ruby/setup-ruby@v1
32
+ uses: ruby/setup-ruby@473e4d8fe5dd94ee328fdfca9f8c9c7afc9dae5e
33
+ with:
34
+ ruby-version: ${{ matrix.ruby-version }}
35
+ bundler-cache: true # runs 'bundle install' and caches installed gems automatically
36
+ - name: Install Rails 5 deps
37
+ run: (cd spec/rails5; bundle install; yard gems)
38
+ - name: Install Rails 6 deps
39
+ run: (cd spec/rails6; bundle install; yard gems)
40
+ - name: Run tests
41
+ run: bundle exec rspec
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ tmp/examples.txt
2
+ .DS_Store
3
+ .gem
4
+ spec/rails5/log/
5
+ spec/rails5/tmp/
6
+ spec/rails6/log/
7
+ spec/rails6/tmp/
data/.projections.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "*": {
3
+ "make": "bundle exec rspec {file}"
4
+ }
5
+ }
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/.solargraph.yml ADDED
@@ -0,0 +1,19 @@
1
+ ---
2
+ include:
3
+ - "**/*.rb"
4
+ exclude:
5
+ - test/**/*
6
+ - vendor/**/*
7
+ - ".bundle/**/*"
8
+ require: []
9
+ domains: []
10
+ reporters: []
11
+ formatter:
12
+ rubocop:
13
+ cops: safe
14
+ except: []
15
+ only: []
16
+ extra_args: []
17
+ require_paths: []
18
+ plugins: []
19
+ max_files: 5000
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # CHANGELOG
2
+
3
+ # 0.1.0
4
+
5
+ Initial alpha
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+ # gem 'solargraph', path: "../solargraph"
5
+ # gem 'rspec'
6
+ # gem 'activesupport'
7
+ # gem 'pry'
data/Gemfile.lock ADDED
@@ -0,0 +1,107 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ solargraph-arc (0.2.0)
5
+ activesupport (~> 6.1.4.1)
6
+ solargraph (~> 0.44.2)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ activesupport (6.1.4.1)
12
+ concurrent-ruby (~> 1.0, >= 1.0.2)
13
+ i18n (>= 1.6, < 2)
14
+ minitest (>= 5.1)
15
+ tzinfo (~> 2.0)
16
+ zeitwerk (~> 2.3)
17
+ ast (2.4.2)
18
+ backport (1.2.0)
19
+ benchmark (0.2.0)
20
+ coderay (1.1.3)
21
+ concurrent-ruby (1.1.9)
22
+ diff-lcs (1.4.4)
23
+ e2mmap (0.1.0)
24
+ i18n (1.8.11)
25
+ concurrent-ruby (~> 1.0)
26
+ jaro_winkler (1.5.4)
27
+ kramdown (2.3.1)
28
+ rexml
29
+ kramdown-parser-gfm (1.1.0)
30
+ kramdown (~> 2.0)
31
+ method_source (1.0.0)
32
+ minitest (5.14.4)
33
+ nokogiri (1.12.5-arm64-darwin)
34
+ racc (~> 1.4)
35
+ parallel (1.21.0)
36
+ parser (3.0.3.1)
37
+ ast (~> 2.4.1)
38
+ pry (0.14.1)
39
+ coderay (~> 1.1)
40
+ method_source (~> 1.0)
41
+ racc (1.6.0)
42
+ rainbow (3.0.0)
43
+ rake (10.5.0)
44
+ regexp_parser (2.1.1)
45
+ reverse_markdown (2.1.1)
46
+ nokogiri
47
+ rexml (3.2.5)
48
+ rspec (3.9.0)
49
+ rspec-core (~> 3.9.0)
50
+ rspec-expectations (~> 3.9.0)
51
+ rspec-mocks (~> 3.9.0)
52
+ rspec-core (3.9.3)
53
+ rspec-support (~> 3.9.3)
54
+ rspec-expectations (3.9.4)
55
+ diff-lcs (>= 1.2.0, < 2.0)
56
+ rspec-support (~> 3.9.0)
57
+ rspec-mocks (3.9.1)
58
+ diff-lcs (>= 1.2.0, < 2.0)
59
+ rspec-support (~> 3.9.0)
60
+ rspec-support (3.9.4)
61
+ rubocop (1.23.0)
62
+ parallel (~> 1.10)
63
+ parser (>= 3.0.0.0)
64
+ rainbow (>= 2.2.2, < 4.0)
65
+ regexp_parser (>= 1.8, < 3.0)
66
+ rexml
67
+ rubocop-ast (>= 1.12.0, < 2.0)
68
+ ruby-progressbar (~> 1.7)
69
+ unicode-display_width (>= 1.4.0, < 3.0)
70
+ rubocop-ast (1.13.0)
71
+ parser (>= 3.0.1.1)
72
+ ruby-progressbar (1.11.0)
73
+ solargraph (0.44.2)
74
+ backport (~> 1.2)
75
+ benchmark
76
+ bundler (>= 1.17.2)
77
+ diff-lcs (~> 1.4)
78
+ e2mmap
79
+ jaro_winkler (~> 1.5)
80
+ kramdown (~> 2.3)
81
+ kramdown-parser-gfm (~> 1.1)
82
+ parser (~> 3.0)
83
+ reverse_markdown (>= 1.0.5, < 3)
84
+ rubocop (>= 0.52)
85
+ thor (~> 1.0)
86
+ tilt (~> 2.0)
87
+ yard (~> 0.9, >= 0.9.24)
88
+ thor (1.1.0)
89
+ tilt (2.0.10)
90
+ tzinfo (2.0.4)
91
+ concurrent-ruby (~> 1.0)
92
+ unicode-display_width (2.1.0)
93
+ yard (0.9.26)
94
+ zeitwerk (2.5.1)
95
+
96
+ PLATFORMS
97
+ ruby
98
+
99
+ DEPENDENCIES
100
+ bundler (~> 1.17)
101
+ pry (~> 0.14.1)
102
+ rake (~> 10.0)
103
+ rspec (~> 3.9.0)
104
+ solargraph-arc!
105
+
106
+ BUNDLED WITH
107
+ 1.17.3
data/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # Solargraph ARC
2
+
3
+ [Solargraph](https://solargraph.org/) **A**wesome **R**ails **C**completions
4
+
5
+ ## Features
6
+
7
+ - fixes autocompletion for multi-level classes defined in 1 line `class Foo::Bar::Baz`. See https://github.com/castwide/solargraph/issues/506
8
+ - autocomplete database columns by parsing db/schema.rb
9
+ - autocomplete of model relations
10
+ - parsing of `delegate` calls
11
+ - completions for methods generated by Devise (`current_user`, `sign_in_and_redirect`, etc)
12
+ - better support for running solargraph outside bundle
13
+ - better completion inside controllers. `request`, `response`, `params`, etc.
14
+ - autocomplete inside routes.rb
15
+ - autocomplete inside migrations
16
+ - completions for methods generated by ActiveStorage
17
+ - better ActiveRecord completions
18
+
19
+ Still, due to dynamic nature of some Rails code, not all completion candidates are returned. Things get worse when you chain methods.
20
+ Without type information, Solargraph cannot deduce the object you are calling methods on.
21
+
22
+ For an up to date coverage report, please see https://github.com/alisnic/solargraph-arc/tree/master/coverage.
23
+
24
+ ## Supported Ruby/Rails versions
25
+
26
+ This gem currently supports:
27
+ - Ruby 2.5, 2.6, and 2.7. Ruby 3.0 [almost](https://github.com/alisnic/solargraph-arc/issues/16) works
28
+ - Rails 5 and Rails 6
29
+
30
+ ## Usage
31
+
32
+ 1. Install gem and [make sure your editor is integrated with it](https://solargraph.org/)
33
+
34
+ ```
35
+ gem install solargraph-arc
36
+ ```
37
+
38
+ 2. If you project does not have a solagraph config, generate one using `solargraph config`
39
+ 2. update your project `.solargraph.yml`:
40
+
41
+ ```yml
42
+ # ...
43
+ plugins:
44
+ - solargraph-arc
45
+ ```
46
+ 2. if you use solargraph from bundle, don't forget to include `solagraph-arc` in your Gemfile
47
+ 2. **IMPORTANT**: Make sure yard documentation is built for your project (solargraph uses it for completions)
48
+ Execute this in your project root:
49
+
50
+ ```
51
+ $ yard gems
52
+ ```
53
+
54
+ otherwise a lot of completions won't work
55
+
56
+ ## Contributing
57
+
58
+ 1. create fork and clone the repo
59
+ 2. install gem deps `bundle install`
60
+ 3. install dummy rails5 app deps and build its yard cache
61
+
62
+ ```
63
+ $ cd spec/rails5
64
+ $ bundle install && yard gems
65
+ $ cd ../../
66
+ ```
67
+
68
+ 3. install dummy rails6 app deps and build its yard cache
69
+
70
+ ```
71
+ $ cd spec/rails6
72
+ $ bundle install && yard gems
73
+ $ cd ../../
74
+ ```
75
+ 4. now tests should pass locally and you can try different changes
76
+ 5. sumbit PR
@@ -0,0 +1,114 @@
1
+ [
2
+ {
3
+ "class_name": "ActionController::Base",
4
+ "total": 434,
5
+ "covered": 149,
6
+ "typed": 13,
7
+ "percent_covered": 34.3,
8
+ "percent_typed": 3.0
9
+ },
10
+ {
11
+ "class_name": "ActionDispatch::Routing::Mapper",
12
+ "total": 142,
13
+ "covered": 126,
14
+ "typed": 8,
15
+ "percent_covered": 88.7,
16
+ "percent_typed": 5.6
17
+ },
18
+ {
19
+ "class_name": "ActiveJob::Base",
20
+ "total": 172,
21
+ "covered": 88,
22
+ "typed": 8,
23
+ "percent_covered": 51.2,
24
+ "percent_typed": 4.7
25
+ },
26
+ {
27
+ "class_name": "ActiveRecord::Base",
28
+ "total": 688,
29
+ "covered": 299,
30
+ "typed": 13,
31
+ "percent_covered": 43.5,
32
+ "percent_typed": 1.9
33
+ },
34
+ {
35
+ "class_name": "Array",
36
+ "total": 99,
37
+ "covered": 95,
38
+ "typed": 7,
39
+ "percent_covered": 96.0,
40
+ "percent_typed": 7.1
41
+ },
42
+ {
43
+ "class_name": "Class",
44
+ "total": 112,
45
+ "covered": 104,
46
+ "typed": 8,
47
+ "percent_covered": 92.9,
48
+ "percent_typed": 7.1
49
+ },
50
+ {
51
+ "class_name": "Date",
52
+ "total": 166,
53
+ "covered": 162,
54
+ "typed": 8,
55
+ "percent_covered": 97.6,
56
+ "percent_typed": 4.8
57
+ },
58
+ {
59
+ "class_name": "DateTime",
60
+ "total": 193,
61
+ "covered": 123,
62
+ "typed": 8,
63
+ "percent_covered": 63.7,
64
+ "percent_typed": 4.1
65
+ },
66
+ {
67
+ "class_name": "File",
68
+ "total": 83,
69
+ "covered": 79,
70
+ "typed": 8,
71
+ "percent_covered": 95.2,
72
+ "percent_typed": 9.6
73
+ },
74
+ {
75
+ "class_name": "Hash",
76
+ "total": 110,
77
+ "covered": 106,
78
+ "typed": 7,
79
+ "percent_covered": 96.4,
80
+ "percent_typed": 6.4
81
+ },
82
+ {
83
+ "class_name": "Integer",
84
+ "total": 110,
85
+ "covered": 106,
86
+ "typed": 8,
87
+ "percent_covered": 96.4,
88
+ "percent_typed": 7.3
89
+ },
90
+ {
91
+ "class_name": "Kernel",
92
+ "total": 56,
93
+ "covered": 54,
94
+ "typed": 4,
95
+ "percent_covered": 96.4,
96
+ "percent_typed": 7.1
97
+ },
98
+ {
99
+ "class_name": "String",
100
+ "total": 117,
101
+ "covered": 113,
102
+ "typed": 8,
103
+ "percent_covered": 96.6,
104
+ "percent_typed": 6.8
105
+ },
106
+ {
107
+ "class_name": "Time",
108
+ "total": 192,
109
+ "covered": 187,
110
+ "typed": 8,
111
+ "percent_covered": 97.4,
112
+ "percent_typed": 4.2
113
+ }
114
+ ]
@@ -0,0 +1,114 @@
1
+ [
2
+ {
3
+ "class_name": "ActionController::Base",
4
+ "total": 435,
5
+ "covered": 147,
6
+ "typed": 13,
7
+ "percent_covered": 33.8,
8
+ "percent_typed": 3.0
9
+ },
10
+ {
11
+ "class_name": "ActionDispatch::Routing::Mapper",
12
+ "total": 139,
13
+ "covered": 126,
14
+ "typed": 8,
15
+ "percent_covered": 90.6,
16
+ "percent_typed": 5.8
17
+ },
18
+ {
19
+ "class_name": "ActiveJob::Base",
20
+ "total": 187,
21
+ "covered": 107,
22
+ "typed": 8,
23
+ "percent_covered": 57.2,
24
+ "percent_typed": 4.3
25
+ },
26
+ {
27
+ "class_name": "ActiveRecord::Base",
28
+ "total": 800,
29
+ "covered": 321,
30
+ "typed": 10,
31
+ "percent_covered": 40.1,
32
+ "percent_typed": 1.3
33
+ },
34
+ {
35
+ "class_name": "Array",
36
+ "total": 103,
37
+ "covered": 101,
38
+ "typed": 7,
39
+ "percent_covered": 98.1,
40
+ "percent_typed": 6.8
41
+ },
42
+ {
43
+ "class_name": "Class",
44
+ "total": 108,
45
+ "covered": 104,
46
+ "typed": 8,
47
+ "percent_covered": 96.3,
48
+ "percent_typed": 7.4
49
+ },
50
+ {
51
+ "class_name": "Date",
52
+ "total": 169,
53
+ "covered": 167,
54
+ "typed": 10,
55
+ "percent_covered": 98.8,
56
+ "percent_typed": 5.9
57
+ },
58
+ {
59
+ "class_name": "DateTime",
60
+ "total": 196,
61
+ "covered": 121,
62
+ "typed": 8,
63
+ "percent_covered": 61.7,
64
+ "percent_typed": 4.1
65
+ },
66
+ {
67
+ "class_name": "File",
68
+ "total": 85,
69
+ "covered": 83,
70
+ "typed": 8,
71
+ "percent_covered": 97.6,
72
+ "percent_typed": 9.4
73
+ },
74
+ {
75
+ "class_name": "Hash",
76
+ "total": 115,
77
+ "covered": 113,
78
+ "typed": 7,
79
+ "percent_covered": 98.3,
80
+ "percent_typed": 6.1
81
+ },
82
+ {
83
+ "class_name": "Integer",
84
+ "total": 107,
85
+ "covered": 105,
86
+ "typed": 8,
87
+ "percent_covered": 98.1,
88
+ "percent_typed": 7.5
89
+ },
90
+ {
91
+ "class_name": "Kernel",
92
+ "total": 55,
93
+ "covered": 54,
94
+ "typed": 4,
95
+ "percent_covered": 98.2,
96
+ "percent_typed": 7.3
97
+ },
98
+ {
99
+ "class_name": "String",
100
+ "total": 115,
101
+ "covered": 113,
102
+ "typed": 8,
103
+ "percent_covered": 98.3,
104
+ "percent_typed": 7.0
105
+ },
106
+ {
107
+ "class_name": "Time",
108
+ "total": 198,
109
+ "covered": 194,
110
+ "typed": 10,
111
+ "percent_covered": 98.0,
112
+ "percent_typed": 5.1
113
+ }
114
+ ]
@@ -0,0 +1,60 @@
1
+ # The following comments fill some of the gaps in Solargraph's understanding of
2
+ # Rails apps. Since they're all in YARD, they get mapped in Solargraph but
3
+ # ignored at runtime.
4
+ #
5
+ # You can put this file anywhere in the project, as long as it gets included in
6
+ # the workspace maps. It's recommended that you keep it in a standalone file
7
+ # instead of pasting it into an existing one.
8
+ #
9
+ # @!parse
10
+ # class ActionController::Base
11
+ # include ActionController::MimeResponds
12
+ # include ActionController::Redirecting
13
+ # include ActionController::Cookies
14
+ # include AbstractController::Rendering
15
+ # extend ActiveSupport::Callbacks::ClassMethods
16
+ # extend ActiveSupport::Rescuable::ClassMethods
17
+ # extend AbstractController::Callbacks::ClassMethods
18
+ # extend ActionController::RequestForgeryProtection::ClassMethods
19
+ # end
20
+ # class ActionDispatch::Routing::Mapper
21
+ # include ActionDispatch::Routing::Mapper::Base
22
+ # include ActionDispatch::Routing::Mapper::HttpHelpers
23
+ # include ActionDispatch::Routing::Mapper::Redirection
24
+ # include ActionDispatch::Routing::Mapper::Scoping
25
+ # include ActionDispatch::Routing::Mapper::Concerns
26
+ # include ActionDispatch::Routing::Mapper::Resources
27
+ # include ActionDispatch::Routing::Mapper::CustomUrls
28
+ # end
29
+ # class Rails
30
+ # # @return [Rails::Application]
31
+ # def self.application; end
32
+ # end
33
+ # class Rails::Application
34
+ # # @return [ActionDispatch::Routing::RouteSet]
35
+ # def routes; end
36
+ # end
37
+ # class ActionDispatch::Routing::RouteSet
38
+ # # @yieldself [ActionDispatch::Routing::Mapper]
39
+ # def draw; end
40
+ # end
41
+ # class ActiveRecord::Base
42
+ # extend ActiveRecord::QueryMethods
43
+ # extend ActiveRecord::FinderMethods
44
+ # extend ActiveRecord::Associations::ClassMethods
45
+ # extend ActiveRecord::Inheritance::ClassMethods
46
+ # extend ActiveRecord::ModelSchema::ClassMethods
47
+ # extend ActiveRecord::Transactions::ClassMethods
48
+ # extend ActiveRecord::Scoping::Named::ClassMethods
49
+ # include ActiveRecord::Persistence
50
+ # end
51
+ # @!override ActiveRecord::FinderMethods#find
52
+ # @overload find(id)
53
+ # @param id [Integer]
54
+ # @return [self]
55
+ # @overload find(list)
56
+ # @param list [Array]
57
+ # @return [Array<self>]
58
+ # @overload find(*args)
59
+ # @return [Array<self>]
60
+ # @return [self, Array<self>]
@@ -0,0 +1,48 @@
1
+ module Solargraph
2
+ module Arc
3
+ class Autoload
4
+ def self.instance
5
+ @instance ||= self.new
6
+ end
7
+
8
+ def process(source_map, ns, ds)
9
+ return [] unless ds.size == 1 && ns.path.include?("::")
10
+ Solargraph.logger.debug("[Arc][Autoload] seeding class tree for #{ns.path}")
11
+
12
+ root_ns = source_map.pins.find {|p| p.path == "" }
13
+ namespace_stubs(root_ns, ns)
14
+ end
15
+
16
+ def namespace_stubs(root_ns, ns)
17
+ parts = ns.path.split("::")
18
+
19
+ candidates = parts.each_with_index.reduce([]) do |acc, (_, i)|
20
+ acc + [parts[0..i].join("::")]
21
+ end.reject {|el| el == ns.path }
22
+
23
+ previous_ns = root_ns
24
+ pins = []
25
+
26
+ parts[0..-2].each_with_index do |name, i|
27
+ gates = candidates[0..i].reverse + [""]
28
+ path = gates.first
29
+ next if path == ns.path
30
+
31
+ previous_ns = Solargraph::Pin::Namespace.new(
32
+ type: :class,
33
+ location: ns.location,
34
+ closure: previous_ns,
35
+ name: name,
36
+ comments: ns.comments,
37
+ visibility: :public,
38
+ gates: gates[1..-1]
39
+ )
40
+
41
+ pins << previous_ns
42
+ end
43
+
44
+ pins
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,30 @@
1
+ module Solargraph
2
+ module Arc
3
+ class Debug
4
+ def self.run(query=nil)
5
+ self.new.run(query)
6
+ end
7
+
8
+ def run(query)
9
+ Solargraph.logger.level = Logger::DEBUG
10
+
11
+ api_map = Solargraph::ApiMap.load('./')
12
+
13
+ puts "Ruby version: #{RUBY_VERSION}"
14
+ puts "Solargraph version: #{Solargraph::VERSION}"
15
+ puts "Solargraph ARC version: #{Solargraph::Arc::VERSION}"
16
+
17
+ return unless query
18
+
19
+ puts "Known methods for #{query}"
20
+
21
+ pin = api_map.pins.find {|p| p.path == query }
22
+ return unless pin
23
+
24
+ api_map.get_complex_type_methods(pin.return_type).each do |pin|
25
+ puts "- #{pin.path}"
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end