rubocop-nueca 1.1.6 → 1.2.3

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: cb582e05cf23ba7da4d65218fa4715cf0086c8ddb3dcfc56f71870e8693fd072
4
- data.tar.gz: 7e4dbe75693071c28501ed3798dbff546d3dafa5b6badea85b9db99c3cc92685
3
+ metadata.gz: a3678eac28f6575b9cee5fb58f88a02e46de0c91bf36437a242fc34208cd4da3
4
+ data.tar.gz: a11bbc18d306047a0b8c1b329430b15d92bff287190adcd99e9994eceb4d33d8
5
5
  SHA512:
6
- metadata.gz: 032bc84ea0bfce6f02a97967a97d59e163a188b6d38cdc257e2160b1bfc77865dfecd82d66d136ec590ebf021375fd4a878ab0e055406178a17da4fd05545995
7
- data.tar.gz: 75fb6fdd200abd96efdd9b75dc40b24a87c53ea3527d67131ba6ab103216db7843aac91ccce2e320e98c1f7b76e66e8ab72b50b85b52b85b98ea6a5c8b0f7131
6
+ metadata.gz: a5ab9039fb41adbeb81026aa2d7bfa8f4f542c6566f01c0c853fce78ee6b987919750c983524eef7cc467902f7b6b1e7921a542ea48c1ff624d8eece8d96e5f1
7
+ data.tar.gz: '078491a8477ad8a25a19c0a6a6e38541f599cc5d44efc35ec24974e86eec267fd1026c5517063367e7a64163f595014f1d912d6cc467f1fd828c7b044b2eb550'
@@ -1,12 +1,16 @@
1
1
  name: Publish Gem
2
2
 
3
3
  on:
4
- push:
4
+ workflow_run:
5
+ workflows: ["RuboCop"]
6
+ types:
7
+ - completed
5
8
  branches: [ master ]
6
9
 
7
10
  jobs:
8
11
  publish:
9
12
  runs-on: ubuntu-latest
13
+ if: ${{ github.event.workflow_run.conclusion == 'success' }}
10
14
 
11
15
  steps:
12
16
  - uses: actions/checkout@v4
@@ -0,0 +1,23 @@
1
+ name: RuboCop
2
+
3
+ on:
4
+ push:
5
+ branches: [ master ]
6
+ pull_request:
7
+ branches: [ master ]
8
+
9
+ jobs:
10
+ rubocop:
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - name: Set up Ruby
17
+ uses: ruby/setup-ruby@v1
18
+ with:
19
+ ruby-version: '3.4'
20
+ bundler-cache: true
21
+
22
+ - name: Run RuboCop
23
+ run: bundle exec rubocop
@@ -1,39 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../shared/route_collector'
3
+ require_relative '../shared/route_helper'
4
4
 
5
5
  module RuboCop
6
6
  module Cop
7
7
  module Rails
8
8
  class RouteConsistentSpacing < RuboCop::Cop::Base
9
+ include RouteHelper
10
+
9
11
  MSG = 'Do not leave blank lines between routes of the same type at the same namespace level.'
10
12
 
11
13
  def on_block(node)
12
- return unless rails_routes_draw_block?(node)
13
-
14
- routes = collect_routes(node)
15
- return if routes.size < 2
14
+ process_route_block(node)
15
+ end
16
16
 
17
- check_consistent_spacing(routes)
17
+ def investigate(processed_source)
18
+ process_route_file(processed_source)
18
19
  end
19
20
 
20
21
  private
21
22
 
22
- def rails_routes_draw_block?(node)
23
- return false unless node.block_type?
24
-
25
- send_node = node.send_node
26
- receiver = send_node.receiver
27
- return false unless receiver
28
-
29
- receiver.source == 'Rails.application.routes' && send_node.method_name == :draw
30
- end
31
-
32
- def collect_routes(routes_block)
33
- collector = RouteCollector.new
34
- body = routes_block.body
35
- collector.collect(body) if body
36
- collector.routes.sort_by { |route| route[:line] }
23
+ def check_routes(routes)
24
+ check_consistent_spacing(routes)
37
25
  end
38
26
 
39
27
  def check_consistent_spacing(routes)
@@ -1,39 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../shared/route_collector'
3
+ require_relative '../shared/route_helper'
4
4
 
5
5
  module RuboCop
6
6
  module Cop
7
7
  module Rails
8
8
  class RouteGrouping < RuboCop::Cop::Base
9
+ include RouteHelper
10
+
9
11
  MSG = 'Group routes by type. Keep simple routes, resources, and namespaces grouped together.'
10
12
 
11
13
  def on_block(node)
12
- return unless rails_routes_draw_block?(node)
13
-
14
- routes = collect_routes(node)
15
- return if routes.size < 2
14
+ process_route_block(node)
15
+ end
16
16
 
17
- check_for_scattered_routes(routes)
17
+ def investigate(processed_source)
18
+ process_route_file(processed_source)
18
19
  end
19
20
 
20
21
  private
21
22
 
22
- def rails_routes_draw_block?(node)
23
- return false unless node.block_type?
24
-
25
- send_node = node.send_node
26
- receiver = send_node.receiver
27
- return false unless receiver
28
-
29
- receiver.source == 'Rails.application.routes' && send_node.method_name == :draw
30
- end
31
-
32
- def collect_routes(routes_block)
33
- collector = RouteCollector.new
34
- body = routes_block.body
35
- collector.collect(body) if body
36
- collector.routes.sort_by { |route| route[:line] }
23
+ def check_routes(routes)
24
+ check_for_scattered_routes(routes)
37
25
  end
38
26
 
39
27
  def check_for_scattered_routes(routes)
@@ -1,39 +1,33 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../shared/route_collector'
3
+ require_relative '../shared/route_helper'
4
4
 
5
5
  module RuboCop
6
6
  module Cop
7
7
  module Rails
8
8
  class RouteRootPosition < RuboCop::Cop::Base
9
+ include RouteHelper
10
+
9
11
  MSG = 'The root route should be positioned at the top of routes within the same namespace level.'
10
12
 
11
13
  def on_block(node)
12
- return unless rails_routes_draw_block?(node)
13
-
14
- routes = collect_routes(node)
15
- return if routes.empty?
14
+ process_route_block(node)
15
+ end
16
16
 
17
- check_root_position(routes)
17
+ def investigate(processed_source)
18
+ process_route_file(processed_source)
18
19
  end
19
20
 
20
21
  private
21
22
 
22
- def rails_routes_draw_block?(node)
23
- return false unless node.block_type?
24
-
25
- send_node = node.send_node
26
- receiver = send_node.receiver
27
- return false unless receiver
28
-
29
- receiver.source == 'Rails.application.routes' && send_node.method_name == :draw
23
+ def minimum_routes_for_check
24
+ 0
30
25
  end
31
26
 
32
- def collect_routes(routes_block)
33
- collector = RouteCollector.new
34
- body = routes_block.body
35
- collector.collect(body) if body
36
- collector.routes.sort_by { |route| route[:line] }
27
+ def check_routes(routes)
28
+ return if routes.empty?
29
+
30
+ check_root_position(routes)
37
31
  end
38
32
 
39
33
  def check_root_position(routes)
@@ -1,39 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../shared/route_collector'
3
+ require_relative '../shared/route_helper'
4
4
 
5
5
  module RuboCop
6
6
  module Cop
7
7
  module Rails
8
8
  class RouteSeparation < RuboCop::Cop::Base
9
+ include RouteHelper
10
+
9
11
  MSG = 'Separate different route types with a blank line.'
10
12
 
11
13
  def on_block(node)
12
- return unless rails_routes_draw_block?(node)
13
-
14
- routes = collect_routes(node)
15
- return if routes.size < 2
14
+ process_route_block(node)
15
+ end
16
16
 
17
- check_route_separation(routes)
17
+ def investigate(processed_source)
18
+ process_route_file(processed_source)
18
19
  end
19
20
 
20
21
  private
21
22
 
22
- def rails_routes_draw_block?(node)
23
- return false unless node.block_type?
24
-
25
- send_node = node.send_node
26
- receiver = send_node.receiver
27
- return false unless receiver
28
-
29
- receiver.source == 'Rails.application.routes' && send_node.method_name == :draw
30
- end
31
-
32
- def collect_routes(routes_block)
33
- collector = RouteCollector.new
34
- body = routes_block.body
35
- collector.collect(body) if body
36
- collector.routes.sort_by { |route| route[:line] }
23
+ def check_routes(routes)
24
+ check_route_separation(routes)
37
25
  end
38
26
 
39
27
  def check_route_separation(routes)
@@ -1,40 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../shared/route_collector'
3
+ require_relative '../shared/route_helper'
4
4
 
5
5
  module RuboCop
6
6
  module Cop
7
7
  module Rails
8
8
  class RouteSorting < RuboCop::Cop::Base
9
+ include RouteHelper
10
+
9
11
  MSG = 'Sort routes of the same type alphabetically within the same namespace level. ' \
10
12
  'Expected order: %<expected>s.'
11
13
 
12
14
  def on_block(node)
13
- return unless rails_routes_draw_block?(node)
14
-
15
- routes = collect_routes(node)
16
- return if routes.size < 2
15
+ process_route_block(node)
16
+ end
17
17
 
18
- check_route_sorting(routes)
18
+ def investigate(processed_source)
19
+ process_route_file(processed_source)
19
20
  end
20
21
 
21
22
  private
22
23
 
23
- def rails_routes_draw_block?(node)
24
- return false unless node.block_type?
25
-
26
- send_node = node.send_node
27
- receiver = send_node.receiver
28
- return false unless receiver
29
-
30
- receiver.source == 'Rails.application.routes' && send_node.method_name == :draw
31
- end
32
-
33
- def collect_routes(routes_block)
34
- collector = RouteCollector.new
35
- body = routes_block.body
36
- collector.collect(body) if body
37
- collector.routes.sort_by { |route| route[:line] }
24
+ def check_routes(routes)
25
+ check_route_sorting(routes)
38
26
  end
39
27
 
40
28
  def check_route_sorting(routes)
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Rails
6
- class CollectionContext
6
+ class CollectionContext # rubocop:disable Metrics/ClassLength
7
7
  ROUTE_CATEGORIES = {
8
8
  simple: [:get, :post, :put, :patch, :delete, :head, :options, :match, :root],
9
9
  resource: [:resource, :resources],
@@ -12,6 +12,7 @@ module RuboCop
12
12
  }.freeze
13
13
 
14
14
  ALL_ROUTE_METHODS = ROUTE_CATEGORIES.values.flatten.freeze
15
+ SCOPE_OPTIONS = [:module, :path].freeze
15
16
 
16
17
  def initialize(collector, namespace_level, namespace_path = [])
17
18
  @collector = collector
@@ -43,19 +44,40 @@ module RuboCop
43
44
  send_node = node.send_node
44
45
  return unless send_node.send_type? && route_method?(send_node)
45
46
 
46
- add_route_if_valid(send_node)
47
+ add_route_block_if_valid(node)
48
+ process_nested_context(node)
49
+ end
50
+
51
+ def add_route_block_if_valid(node)
52
+ send_node = node.send_node
53
+ return unless route_method?(send_node)
54
+
55
+ route_info = build_route_info_from_block(node)
56
+ @collector.add_route(route_info) if route_info
57
+ end
47
58
 
59
+ def process_nested_context(node)
48
60
  body = node.body
49
61
  return unless body
50
62
 
63
+ new_namespace_path = build_namespace_path(node.send_node)
64
+ nested_context = CollectionContext.new(@collector, @namespace_level + 1, new_namespace_path)
65
+ nested_context.process_node(body)
66
+ end
67
+
68
+ def build_namespace_path(send_node)
51
69
  new_namespace_path = @namespace_path.dup
52
- if send_node.method_name == :namespace
70
+
71
+ case send_node.method_name
72
+ when :namespace
53
73
  namespace_name = extract_namespace_name(send_node)
54
74
  new_namespace_path << namespace_name if namespace_name
75
+ when :scope
76
+ scope_name = extract_scope_name(send_node)
77
+ new_namespace_path << scope_name if scope_name
55
78
  end
56
79
 
57
- nested_context = CollectionContext.new(@collector, @namespace_level + 1, new_namespace_path)
58
- nested_context.process_node(body)
80
+ new_namespace_path
59
81
  end
60
82
 
61
83
  def extract_namespace_name(node)
@@ -65,6 +87,46 @@ module RuboCop
65
87
  'unknown'
66
88
  end
67
89
 
90
+ def extract_scope_name(node)
91
+ scope_name = extract_scope_option_value(node)
92
+ return scope_name if scope_name
93
+
94
+ first_arg = node.arguments.first
95
+ return first_arg.value.to_s if first_arg&.sym_type? || first_arg&.str_type?
96
+
97
+ 'scope'
98
+ end
99
+
100
+ def extract_scope_option_value(node)
101
+ node.arguments.each do |arg|
102
+ next unless arg.hash_type?
103
+
104
+ scope_value = find_scope_option_in_hash(arg)
105
+ return scope_value if scope_value
106
+ end
107
+
108
+ nil
109
+ end
110
+
111
+ def find_scope_option_in_hash(hash_arg)
112
+ hash_arg.pairs.each do |pair|
113
+ value = extract_scope_value_from_pair(pair)
114
+ return value if value
115
+ end
116
+
117
+ nil
118
+ end
119
+
120
+ def extract_scope_value_from_pair(pair)
121
+ key = pair.key
122
+ return nil unless key&.sym_type? && SCOPE_OPTIONS.include?(key.value)
123
+
124
+ value = pair.value
125
+ return value.value.to_s if value&.sym_type? || value&.str_type?
126
+
127
+ nil
128
+ end
129
+
68
130
  def route_method?(node)
69
131
  return false unless node.send_type?
70
132
 
@@ -96,6 +158,26 @@ module RuboCop
96
158
  }
97
159
  end
98
160
 
161
+ def build_route_info_from_block(block_node)
162
+ send_node = block_node.send_node
163
+ method_name = send_node.method_name
164
+ route_name = extract_route_name(send_node)
165
+ return nil unless route_name
166
+
167
+ send_range = send_node.source_range
168
+ block_range = block_node.source_range
169
+ {
170
+ node: send_node,
171
+ method: method_name,
172
+ name: route_name,
173
+ line: send_range.line,
174
+ end_line: block_range.last_line,
175
+ namespace_level: @namespace_level,
176
+ namespace_path: @namespace_path.dup,
177
+ type: categorize_route_method(method_name)
178
+ }
179
+ end
180
+
99
181
  def extract_route_name(node)
100
182
  method_name = node.method_name
101
183
  first_arg = node.arguments.first
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'route_collector'
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module Rails
8
+ module RouteHelper
9
+ def route_file?
10
+ processed_source.file_path&.end_with?('routes.rb')
11
+ end
12
+
13
+ def route_block?(node)
14
+ return false unless node.block_type?
15
+
16
+ send_node = node.send_node
17
+ return false unless send_node.send_type?
18
+ return true if send_node.receiver&.source == 'Rails.application.routes' && send_node.method_name == :draw
19
+ return false unless route_file?
20
+
21
+ [:scope, :namespace, :concern].include?(send_node.method_name)
22
+ end
23
+
24
+ def collect_routes(routes_block)
25
+ collector = RouteCollector.new
26
+ body = routes_block.body
27
+ collector.collect(body) if body
28
+ collector.routes.sort_by { |route| route[:line] }
29
+ end
30
+
31
+ def collect_routes_from_file(ast_node)
32
+ collector = RouteCollector.new
33
+ collector.collect(ast_node) if ast_node
34
+ collector.routes.sort_by { |route| route[:line] }
35
+ end
36
+
37
+ def process_route_block(node)
38
+ return unless route_block?(node)
39
+
40
+ routes = collect_routes(node)
41
+ return if routes.size < minimum_routes_for_check
42
+
43
+ check_routes(routes)
44
+ end
45
+
46
+ def process_route_file(processed_source)
47
+ return unless route_file?
48
+ return if processed_source.ast.nil?
49
+
50
+ routes = collect_routes_from_file(processed_source.ast)
51
+ return if routes.size < minimum_routes_for_check
52
+
53
+ check_routes(routes)
54
+ end
55
+
56
+ private
57
+
58
+ def minimum_routes_for_check
59
+ 2
60
+ end
61
+
62
+ def check_routes(routes)
63
+ raise NotImplementedError, 'Subclasses must implement check_routes method'
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module Nueca
5
- VERSION = '1.1.6'
5
+ VERSION = '1.2.3'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-nueca
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.6
4
+ version: 1.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tien
@@ -134,6 +134,7 @@ extensions: []
134
134
  extra_rdoc_files: []
135
135
  files:
136
136
  - ".github/workflows/publish.yml"
137
+ - ".github/workflows/rubocop.yml"
137
138
  - CODE_OF_CONDUCT.md
138
139
  - LICENSE.txt
139
140
  - README.md
@@ -160,6 +161,7 @@ files:
160
161
  - lib/rubocop/cop/rails/time_zone_today.rb
161
162
  - lib/rubocop/cop/shared/collection_context.rb
162
163
  - lib/rubocop/cop/shared/route_collector.rb
164
+ - lib/rubocop/cop/shared/route_helper.rb
163
165
  - lib/rubocop/nueca/plugin.rb
164
166
  - lib/rubocop/nueca/version.rb
165
167
  homepage: https://github.com/tieeeeen1994/rubocop-nueca