rubocop-nueca 1.0.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.
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ class ModelAssociationSeparation < RuboCop::Cop::Base
7
+ MSG = 'Separate different association types with a blank line.'
8
+ ASSOCIATION_METHODS = [
9
+ :belongs_to,
10
+ :has_one,
11
+ :has_many,
12
+ :has_and_belongs_to_many
13
+ ].freeze
14
+
15
+ def on_class(node)
16
+ return unless model_class?(node)
17
+
18
+ associations = find_associations(node)
19
+ return if associations.size < 2
20
+
21
+ check_association_separation(associations)
22
+ end
23
+
24
+ private
25
+
26
+ def model_class?(node)
27
+ parent_class = node.parent_class
28
+ return false unless parent_class
29
+
30
+ parent_name = parent_class.const_name
31
+ ['ApplicationRecord', 'ActiveRecord::Base'].include?(parent_name)
32
+ end
33
+
34
+ def find_associations(class_node)
35
+ associations = []
36
+
37
+ class_node.body&.each_child_node do |child|
38
+ next unless child.type == :send
39
+ next unless association_method?(child)
40
+
41
+ source_range = child.source_range
42
+ associations << {
43
+ node: child,
44
+ method: child.method_name,
45
+ start_line: source_range.line,
46
+ end_line: source_range.last_line
47
+ }
48
+ end
49
+
50
+ associations.sort_by { |assoc| assoc[:start_line] }
51
+ end
52
+
53
+ def association_method?(node)
54
+ return false unless node.receiver.nil?
55
+
56
+ ASSOCIATION_METHODS.include?(node.method_name)
57
+ end
58
+
59
+ def check_association_separation(associations)
60
+ associations.each_with_index do |current, index|
61
+ next_assoc = associations[index + 1]
62
+ break unless next_assoc
63
+ next if same_association_type?(current, next_assoc)
64
+ next if properly_separated?(current, next_assoc)
65
+
66
+ add_offense(next_assoc[:node], message: MSG)
67
+ end
68
+ end
69
+
70
+ def same_association_type?(current, next_assoc)
71
+ current[:method] == next_assoc[:method]
72
+ end
73
+
74
+ def properly_separated?(current, next_assoc)
75
+ current_end_line = current[:end_line]
76
+ next_start_line = next_assoc[:start_line]
77
+ lines_between = next_start_line - current_end_line - 1
78
+
79
+ return true if lines_between >= 2
80
+
81
+ if lines_between == 1
82
+ between_line = current_end_line
83
+ line_content = processed_source.lines[between_line].strip
84
+ return line_content.empty?
85
+ end
86
+
87
+ false
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ class ModelAssociationSorting < RuboCop::Cop::Base
7
+ MSG = 'Sort associations of the same type alphabetically. Expected order: %<expected>s.'
8
+ ASSOCIATION_METHODS = [
9
+ :belongs_to,
10
+ :has_one,
11
+ :has_many,
12
+ :has_and_belongs_to_many
13
+ ].freeze
14
+
15
+ def on_class(node)
16
+ return unless model_class?(node)
17
+
18
+ associations = find_associations(node)
19
+ return if associations.size < 2
20
+
21
+ check_association_sorting(associations)
22
+ end
23
+
24
+ private
25
+
26
+ def model_class?(node)
27
+ parent_class = node.parent_class
28
+ return false unless parent_class
29
+
30
+ parent_name = parent_class.const_name
31
+ ['ApplicationRecord', 'ActiveRecord::Base'].include?(parent_name)
32
+ end
33
+
34
+ def find_associations(class_node)
35
+ associations = []
36
+
37
+ class_node.body&.each_child_node do |child|
38
+ next unless child.type == :send
39
+ next unless association_method?(child)
40
+
41
+ source_range = child.source_range
42
+ associations << {
43
+ node: child,
44
+ method: child.method_name,
45
+ name: association_name(child),
46
+ start_line: source_range.line,
47
+ end_line: source_range.last_line
48
+ }
49
+ end
50
+
51
+ associations.sort_by { |assoc| assoc[:start_line] }
52
+ end
53
+
54
+ def association_method?(node)
55
+ return false unless node.receiver.nil?
56
+
57
+ ASSOCIATION_METHODS.include?(node.method_name)
58
+ end
59
+
60
+ def association_name(node)
61
+ first_arg = node.arguments.first
62
+ return nil unless first_arg&.sym_type?
63
+
64
+ first_arg.value.to_s
65
+ end
66
+
67
+ def check_association_sorting(associations)
68
+ grouped = associations.group_by { |assoc| assoc[:method] }
69
+
70
+ grouped.each_value do |group_associations|
71
+ next if group_associations.size < 2
72
+
73
+ check_group_sorting(group_associations)
74
+ end
75
+ end
76
+
77
+ def check_group_sorting(group_associations)
78
+ names = group_associations.map { |assoc| assoc[:name] }
79
+ sorted_names = names.sort
80
+
81
+ return if names == sorted_names
82
+
83
+ expected_order = sorted_names.join(', ')
84
+ message = format(MSG, expected: expected_order)
85
+
86
+ add_offense(group_associations.first[:node], message: message)
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ class PostgresTimestamp < RuboCop::Cop::Base
7
+ MSG = 'Use t.timestamptz for better timezone support.'
8
+ RESTRICT_ON_SEND = [:datetime, :timestamps].freeze
9
+
10
+ def_node_matcher :table_datetime_usage, <<~PATTERN
11
+ (send lvar :datetime ...)
12
+ PATTERN
13
+
14
+ def_node_matcher :table_timestamps_usage, <<~PATTERN
15
+ (send lvar :timestamps ...)
16
+ PATTERN
17
+
18
+ def on_send(node)
19
+ return unless in_migration_file?
20
+ return unless node.receiver&.name == :t
21
+
22
+ add_offense(node, message: MSG) if table_datetime_usage(node) || table_timestamps_usage(node)
23
+ end
24
+
25
+ private
26
+
27
+ def in_migration_file?
28
+ processed_source.file_path&.include?('db/migrate/') || false
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../shared/route_collector'
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module Rails
8
+ class RouteConsistentSpacing < RuboCop::Cop::Base
9
+ MSG = 'Do not leave blank lines between routes of the same type at the same namespace level.'
10
+
11
+ def on_block(node)
12
+ return unless rails_routes_draw_block?(node)
13
+
14
+ routes = collect_routes(node)
15
+ return if routes.size < 2
16
+
17
+ check_consistent_spacing(routes)
18
+ end
19
+
20
+ private
21
+
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] }
37
+ end
38
+
39
+ def check_consistent_spacing(routes)
40
+ routes.each_with_index do |current_route, index|
41
+ next_route = routes[index + 1]
42
+ next unless next_route
43
+ next unless same_type_and_level?(current_route, next_route)
44
+
45
+ add_offense(next_route[:node], message: MSG) if blank_line_between?(current_route, next_route)
46
+ end
47
+ end
48
+
49
+ def same_type_and_level?(current_route, next_route)
50
+ current_route[:type] == next_route[:type] &&
51
+ current_route[:namespace_level] == next_route[:namespace_level]
52
+ end
53
+
54
+ def blank_line_between?(current_route, next_route)
55
+ current_end_line = current_route[:end_line]
56
+ next_start_line = next_route[:line]
57
+ lines_between = next_start_line - current_end_line - 1
58
+
59
+ lines_between >= 1
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../shared/route_collector'
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module Rails
8
+ class RouteGrouping < RuboCop::Cop::Base
9
+ MSG = 'Group routes by type. Keep simple routes, resources, and namespaces grouped together.'
10
+
11
+ def on_block(node)
12
+ return unless rails_routes_draw_block?(node)
13
+
14
+ routes = collect_routes(node)
15
+ return if routes.size < 2
16
+
17
+ check_for_scattered_routes(routes)
18
+ end
19
+
20
+ private
21
+
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] }
37
+ end
38
+
39
+ def check_for_scattered_routes(routes)
40
+ routes_by_type_and_context = routes.group_by do |route|
41
+ [route[:type], route[:namespace_level], route[:namespace_path]]
42
+ end
43
+
44
+ routes_by_type_and_context.each_value do |type_routes|
45
+ next if type_routes.size < 2
46
+
47
+ find_scattered_routes(type_routes, routes).each do |route|
48
+ add_offense(route[:node], message: MSG)
49
+ end
50
+ end
51
+ end
52
+
53
+ def find_scattered_routes(type_routes, all_routes)
54
+ scattered = []
55
+
56
+ type_routes.each_with_index do |current_route, index|
57
+ next if index.zero?
58
+
59
+ if scattered_from_previous?(current_route, type_routes[0...index],
60
+ all_routes) && !scattered.include?(current_route) # rubocop:disable Rails/NegateInclude
61
+ scattered << current_route
62
+ end
63
+ end
64
+
65
+ scattered
66
+ end
67
+
68
+ def scattered_from_previous?(current_route, previous_routes, all_routes)
69
+ current_line = current_route[:line]
70
+ route_type = current_route[:type]
71
+ namespace_level = current_route[:namespace_level]
72
+
73
+ previous_routes.any? do |prev_route|
74
+ different_types_between?(all_routes, prev_route[:line], current_line, route_type, namespace_level)
75
+ end
76
+ end
77
+
78
+ def different_types_between?(all_routes, start_line, end_line, route_type, namespace_level)
79
+ all_routes.any? do |route|
80
+ line = route[:line]
81
+ line > start_line && line < end_line &&
82
+ route[:namespace_level] == namespace_level && route[:type] != route_type
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../shared/route_collector'
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module Rails
8
+ class RouteRootPosition < RuboCop::Cop::Base
9
+ MSG = 'The root route should be positioned at the top of routes within the same namespace level.'
10
+
11
+ def on_block(node)
12
+ return unless rails_routes_draw_block?(node)
13
+
14
+ routes = collect_routes(node)
15
+ return if routes.empty?
16
+
17
+ check_root_position(routes)
18
+ end
19
+
20
+ private
21
+
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] }
37
+ end
38
+
39
+ def check_root_position(routes)
40
+ routes_by_context = routes.group_by do |route|
41
+ [route[:namespace_level], route[:namespace_path]]
42
+ end
43
+
44
+ routes_by_context.each_value do |context_routes|
45
+ check_root_in_context(context_routes)
46
+ end
47
+ end
48
+
49
+ def check_root_in_context(context_routes)
50
+ return if context_routes.size < 2
51
+
52
+ root_routes, non_root_routes = partition_routes_by_root(context_routes)
53
+ return if root_routes.empty?
54
+
55
+ non_root_simple_routes = filter_simple_routes(non_root_routes)
56
+ return if non_root_simple_routes.empty?
57
+
58
+ detect_mispositioned_roots(root_routes, non_root_simple_routes)
59
+ end
60
+
61
+ def filter_simple_routes(routes)
62
+ routes.select { |route| simple_route?(route) }
63
+ end
64
+
65
+ def detect_mispositioned_roots(root_routes, simple_routes)
66
+ root_routes.each do |root_route|
67
+ if any_simple_route_before?(root_route, simple_routes)
68
+ add_offense(root_route[:node], message: MSG)
69
+ break
70
+ end
71
+ end
72
+ end
73
+
74
+ def any_simple_route_before?(root_route, simple_routes)
75
+ simple_routes.any? { |simple_route| root_route[:line] > simple_route[:line] }
76
+ end
77
+
78
+ def partition_routes_by_root(routes)
79
+ root_routes = []
80
+ non_root_routes = []
81
+
82
+ routes.each do |route|
83
+ if root_route?(route)
84
+ root_routes << route
85
+ else
86
+ non_root_routes << route
87
+ end
88
+ end
89
+
90
+ [root_routes, non_root_routes]
91
+ end
92
+
93
+ def root_route?(route)
94
+ route[:name] == 'root'
95
+ end
96
+
97
+ def simple_route?(route)
98
+ route[:type] == :simple
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../shared/route_collector'
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module Rails
8
+ class RouteSeparation < RuboCop::Cop::Base
9
+ MSG = 'Separate different route types with a blank line.'
10
+
11
+ def on_block(node)
12
+ return unless rails_routes_draw_block?(node)
13
+
14
+ routes = collect_routes(node)
15
+ return if routes.size < 2
16
+
17
+ check_route_separation(routes)
18
+ end
19
+
20
+ private
21
+
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] }
37
+ end
38
+
39
+ def check_route_separation(routes)
40
+ routes_by_context = routes.group_by do |route|
41
+ [route[:namespace_level], route[:namespace_path]]
42
+ end
43
+
44
+ routes_by_context.each_value do |context_routes|
45
+ next if context_routes.size < 2
46
+
47
+ check_separation_within_context(context_routes)
48
+ end
49
+ end
50
+
51
+ def check_separation_within_context(routes)
52
+ routes.each_with_index do |current_route, index|
53
+ next_route = routes[index + 1]
54
+ break unless next_route
55
+ next if same_route_type?(current_route, next_route)
56
+ next if properly_separated?(current_route, next_route)
57
+
58
+ add_offense(next_route[:node], message: MSG)
59
+ end
60
+ end
61
+
62
+ def same_route_type?(current_route, next_route)
63
+ current_route[:type] == next_route[:type]
64
+ end
65
+
66
+ def properly_separated?(current_route, next_route)
67
+ current_end_line = current_route[:end_line]
68
+ next_start_line = next_route[:line]
69
+ lines_between = next_start_line - current_end_line - 1
70
+
71
+ return true if lines_between >= 2
72
+
73
+ if lines_between == 1
74
+ between_line = current_end_line
75
+ line_content = processed_source.lines[between_line].strip
76
+ return line_content.empty?
77
+ end
78
+
79
+ false
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../shared/route_collector'
4
+
5
+ module RuboCop
6
+ module Cop
7
+ module Rails
8
+ class RouteSorting < RuboCop::Cop::Base
9
+ MSG = 'Sort routes of the same type alphabetically within the same namespace level. ' \
10
+ 'Expected order: %<expected>s.'
11
+
12
+ def on_block(node)
13
+ return unless rails_routes_draw_block?(node)
14
+
15
+ routes = collect_routes(node)
16
+ return if routes.size < 2
17
+
18
+ check_route_sorting(routes)
19
+ end
20
+
21
+ private
22
+
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] }
38
+ end
39
+
40
+ def check_route_sorting(routes)
41
+ grouped_routes = routes.group_by do |route|
42
+ [route[:type], route[:namespace_level], route[:namespace_path]]
43
+ end
44
+
45
+ grouped_routes.each_value do |group_routes|
46
+ next if group_routes.size < 2
47
+
48
+ check_group_sorting(group_routes)
49
+ end
50
+ end
51
+
52
+ def check_group_sorting(group_routes)
53
+ non_root_routes = group_routes.reject { |route| root_route?(route) }
54
+ return if non_root_routes.size < 2
55
+
56
+ route_names = non_root_routes.map { |route| route[:name] }
57
+ sorted_names = route_names.sort
58
+
59
+ return if route_names == sorted_names
60
+
61
+ expected_order = sorted_names.uniq.join(', ')
62
+ message = format(MSG, expected: expected_order)
63
+
64
+ add_offense(non_root_routes.first[:node], message: message)
65
+ end
66
+
67
+ def root_route?(route)
68
+ route[:name] == 'root'
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Rails
6
+ class TimeZoneToday < RuboCop::Cop::Base
7
+ MSG = 'Do not use Time.zone.today.'
8
+ RESTRICT_ON_SEND = [:today].freeze
9
+
10
+ def_node_matcher :time_zone_today_usage, <<~PATTERN
11
+ (send
12
+ (send
13
+ (const nil? :Time) :zone) :today)
14
+ PATTERN
15
+
16
+ def on_send(node)
17
+ return unless time_zone_today_usage(node)
18
+
19
+ add_offense(node)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end