Skip to content

Commit 0c2ae3d

Browse files
authored
Merge pull request #3 from code0-tech/2-extract-rubocops-from-sagittarius
Extract rubocops from sagittarius
2 parents 32e63cc + d0151e0 commit 0c2ae3d

File tree

15 files changed

+784
-0
lines changed

15 files changed

+784
-0
lines changed

.rubocop.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ Style/ArgumentsForwarding:
6262
Style/Documentation:
6363
Enabled: false
6464

65+
Style/FormatStringToken:
66+
Exclude:
67+
- spec/rubocop/**/*
68+
6569
Style/HashSyntax:
6670
EnforcedShorthandSyntax: never
6771

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Code0
5+
module ZeroTrack
6+
module FileHelpers
7+
def dirname(node)
8+
File.dirname(filepath(node))
9+
end
10+
11+
def basename(node)
12+
File.basename(filepath(node))
13+
end
14+
15+
def filepath(node)
16+
node.location.expression.source_buffer.name
17+
end
18+
19+
def in_migration?(node)
20+
dirname(node).end_with?('db/migrate')
21+
end
22+
end
23+
end
24+
end
25+
end
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
module Code0
6+
module ZeroTrack
7+
module Logs
8+
# Cop that checks if 'timestamps' method is called with timezone information.
9+
class RailsLogger < RuboCop::Cop::Base
10+
MSG = 'Do not use `Rails.logger` directly, include `Code0::ZeroTrack::Loggable` instead'
11+
LOG_METHODS = %i[debug error fatal info warn].freeze
12+
LOG_METHODS_PATTERN = LOG_METHODS.map(&:inspect).join(' ').freeze
13+
14+
def_node_matcher :rails_logger_log?, <<~PATTERN
15+
(send
16+
(send (const nil? :Rails) :logger)
17+
{#{LOG_METHODS_PATTERN}} ...
18+
)
19+
PATTERN
20+
21+
def on_send(node)
22+
return unless rails_logger_log?(node)
23+
24+
add_offense(node)
25+
end
26+
end
27+
end
28+
end
29+
end
30+
end
31+
end
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# frozen_string_literal: true
2+
3+
require_relative '../../../../code0/zero_track/file_helpers'
4+
5+
module RuboCop
6+
module Cop
7+
module Code0
8+
module ZeroTrack
9+
module Migration
10+
class CreateTableWithTimestamps < RuboCop::Cop::Base
11+
include RuboCop::Code0::ZeroTrack::FileHelpers
12+
13+
MSG = 'Add timestamps when creating a new table.'
14+
RESTRICT_ON_SEND = %i[create_table].freeze
15+
16+
def_node_matcher :create_table_with_timestamps_proc?, <<~PATTERN
17+
(send nil? :create_table (sym _) ... (block-pass (sym :timestamps_with_timezone)))
18+
PATTERN
19+
20+
def_node_search :timestamps_included?, <<~PATTERN
21+
(send _var :timestamps_with_timezone ...)
22+
PATTERN
23+
24+
def_node_search :created_at_included?, <<~PATTERN
25+
(send _var :datetime_with_timezone
26+
{(sym :created_at)(str "created_at")}
27+
...)
28+
PATTERN
29+
30+
def_node_search :updated_at_included?, <<~PATTERN
31+
(send _var :datetime_with_timezone
32+
{(sym :updated_at)(str "updated_at")}
33+
...)
34+
PATTERN
35+
36+
def_node_matcher :create_table_with_block?, <<~PATTERN
37+
(block
38+
(send nil? :create_table ...)
39+
(args (arg _var)+)
40+
_)
41+
PATTERN
42+
43+
def on_send(node)
44+
return unless in_migration?(node)
45+
return unless node.command?(:create_table)
46+
47+
parent = node.parent
48+
49+
if create_table_with_block?(parent)
50+
add_offense(parent) if parent.body.nil? || !time_columns_included?(parent.body)
51+
elsif create_table_with_timestamps_proc?(node)
52+
# nothing to do
53+
else
54+
add_offense(node)
55+
end
56+
end
57+
58+
private
59+
60+
def time_columns_included?(node)
61+
timestamps_included?(node) || created_at_and_updated_at_included?(node)
62+
end
63+
64+
def created_at_and_updated_at_included?(node)
65+
created_at_included?(node) && updated_at_included?(node)
66+
end
67+
end
68+
end
69+
end
70+
end
71+
end
72+
end
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# frozen_string_literal: true
2+
3+
require_relative '../../../../code0/zero_track/file_helpers'
4+
5+
module RuboCop
6+
module Cop
7+
module Code0
8+
module ZeroTrack
9+
module Migration
10+
# Cop that checks if datetime data type is added with timezone information.
11+
class Datetime < RuboCop::Cop::Base
12+
include RuboCop::Code0::ZeroTrack::FileHelpers
13+
extend AutoCorrector
14+
15+
MSG = 'Do not use the `%s` data type, use `datetime_with_timezone` instead'
16+
17+
# Check methods in table creation.
18+
def on_def(node)
19+
return unless in_migration?(node)
20+
21+
node.each_descendant(:send) do |send_node|
22+
method_name = send_node.children[1]
23+
24+
next unless %i[datetime timestamp].include?(method_name)
25+
26+
add_offense(send_node.loc.selector, message: format(MSG, method_name)) do |corrector|
27+
corrector.replace(send_node.loc.selector, 'datetime_with_timezone')
28+
end
29+
end
30+
end
31+
32+
# Check methods.
33+
def on_send(node)
34+
return unless in_migration?(node)
35+
36+
node.each_descendant do |descendant|
37+
next unless descendant.type == :sym
38+
39+
last_argument = descendant.children.last
40+
41+
next unless %i[datetime timestamp].include?(last_argument)
42+
43+
add_offense(descendant, message: format(MSG, last_argument)) do |corrector|
44+
corrector.replace(descendant, ':datetime_with_timezone')
45+
end
46+
end
47+
end
48+
end
49+
end
50+
end
51+
end
52+
end
53+
end
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# frozen_string_literal: true
2+
3+
require_relative '../../../../code0/zero_track/file_helpers'
4+
5+
module RuboCop
6+
module Cop
7+
module Code0
8+
module ZeroTrack
9+
module Migration
10+
# Cop that checks if 'timestamps' method is called with timezone information.
11+
class Timestamps < RuboCop::Cop::Base
12+
include RuboCop::Code0::ZeroTrack::FileHelpers
13+
extend AutoCorrector
14+
15+
MSG = 'Do not use `timestamps`, use `timestamps_with_timezone` instead'
16+
17+
# Check methods in table creation.
18+
def on_def(node)
19+
return unless in_migration?(node)
20+
21+
node.each_descendant(:send) do |send_node|
22+
next unless method_name(send_node) == :timestamps
23+
24+
add_offense(send_node.loc.selector) do |corrector|
25+
corrector.replace(send_node.loc.selector, 'timestamps_with_timezone')
26+
end
27+
end
28+
end
29+
30+
def method_name(node)
31+
node.children[1]
32+
end
33+
end
34+
end
35+
end
36+
end
37+
end
38+
end
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# frozen_string_literal: true
2+
3+
require_relative '../../../../code0/zero_track/file_helpers'
4+
5+
module RuboCop
6+
module Cop
7+
module Code0
8+
module ZeroTrack
9+
module Migration
10+
class VersionedClass < RuboCop::Cop::Base
11+
include RuboCop::Code0::ZeroTrack::FileHelpers
12+
extend AutoCorrector
13+
14+
MIGRATION_CLASS = 'Code0::ZeroTrack::Database::Migration'
15+
16+
# rubocop:disable Layout/LineLength
17+
MSG_WRONG_BASE_CLASS = "Don't use `%<base_class>s`. Use `#{MIGRATION_CLASS}` instead.".freeze
18+
MSG_WRONG_VERSION = "Don't use version `%<current_version>s` of `#{MIGRATION_CLASS}`. Use version `%<allowed_version>s` instead.".freeze
19+
# rubocop:enable Layout/LineLength
20+
21+
def on_class(node)
22+
return unless in_migration?(node)
23+
24+
return on_zerotrack_migration(node) if zerotrack_migration?(node)
25+
26+
add_offense(
27+
node.parent_class,
28+
message: format(MSG_WRONG_BASE_CLASS, base_class: superclass(node))
29+
) do |corrector|
30+
corrector.replace(node.parent_class, "#{MIGRATION_CLASS}[#{find_allowed_versions(node).last}]")
31+
end
32+
end
33+
34+
private
35+
36+
def on_zerotrack_migration(node)
37+
return if cop_config['AllowedVersions'].nil? # allow all versions if nothing configured
38+
return if correct_migration_version?(node)
39+
40+
current_version = get_migration_version(node)
41+
allowed_version = find_allowed_versions(node).last
42+
43+
version_node = get_migration_version_node(node)
44+
45+
add_offense(
46+
version_node,
47+
message: format(MSG_WRONG_VERSION, current_version: current_version, allowed_version: allowed_version)
48+
) do |corrector|
49+
corrector.replace(version_node, find_allowed_versions(node).last.to_s)
50+
end
51+
end
52+
53+
def zerotrack_migration?(node)
54+
superclass(node) == MIGRATION_CLASS
55+
end
56+
57+
def superclass(class_node)
58+
_, *others = class_node.descendants
59+
60+
others.find { |node| node.const_type? && node.const_name != 'Types' }&.const_name
61+
end
62+
63+
def correct_migration_version?(node)
64+
find_allowed_versions(node).include?(get_migration_version(node))
65+
end
66+
67+
def get_migration_version_node(node)
68+
node.parent_class.arguments[0]
69+
end
70+
71+
def get_migration_version(node)
72+
get_migration_version_node(node).value
73+
end
74+
75+
def find_allowed_versions(node)
76+
migration_version = basename(node).split('_').first.to_i
77+
allowed_versions.select do |range, _|
78+
range.include?(migration_version)
79+
end.values
80+
end
81+
82+
def allowed_versions
83+
cop_config['AllowedVersions'].transform_keys do |range|
84+
range_ints = range.split('..').map(&:to_i)
85+
range_ints[0]..range_ints[1]
86+
end
87+
end
88+
end
89+
end
90+
end
91+
end
92+
end
93+
end

lib/rubocop/zero_track.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# frozen_string_literal: true
2+
3+
# rubocop:disable Lint/RedundantDirGlobSort
4+
Dir[File.join(__dir__, 'cop', '**', '*.rb')].sort.each { |file| require file }
5+
# rubocop:enable Lint/RedundantDirGlobSort

rubocop-zero_track.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
require:
2+
- ./lib/rubocop/zero_track
3+
4+
Code0/ZeroTrack/Logs/RailsLogger:
5+
Enabled: false
6+
7+
Code0/ZeroTrack/Migration/CreateTableWithTimestamps:
8+
Enabled: false
9+
10+
Code0/ZeroTrack/Migration/Datetime:
11+
Enabled: false
12+
13+
Code0/ZeroTrack/Migration/Timestamps:
14+
Enabled: false
15+
16+
Code0/ZeroTrack/Migration/VersionedClass:
17+
Enabled: false
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
5+
RSpec.describe RuboCop::Cop::Code0::ZeroTrack::Logs::RailsLogger, type: :rubocop do
6+
described_class::LOG_METHODS.each do |method|
7+
it "flags the use of Rails.logger.#{method} with a constant receiver" do
8+
node = "Rails.logger.#{method}('some error')"
9+
msg = 'Do not use `Rails.logger` directly, include `Code0::ZeroTrack::Loggable` instead'
10+
11+
expect_offense(<<~CODE, node: node, msg: msg)
12+
%{node}
13+
^{node} %{msg}
14+
CODE
15+
end
16+
end
17+
18+
it 'does not flag the use of Rails.logger with a constant that is not Rails' do
19+
expect_no_offenses("AppLogger.error('some error')")
20+
end
21+
22+
it 'does not flag the use of logger with a send receiver' do
23+
expect_no_offenses("file_logger.info('important info')")
24+
end
25+
26+
it 'does not flag the use of Rails.logger.level' do
27+
expect_no_offenses('Rails.logger.level')
28+
end
29+
end

0 commit comments

Comments
 (0)