rubocop-isucon 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/gh-pages.yml +44 -0
  3. data/.github/workflows/test.yml +91 -0
  4. data/.gitignore +13 -0
  5. data/.rspec +3 -0
  6. data/.rubocop.yml +43 -0
  7. data/.yardopts +7 -0
  8. data/CHANGELOG.md +6 -0
  9. data/Gemfile +8 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +108 -0
  12. data/Rakefile +35 -0
  13. data/benchmark/README.md +69 -0
  14. data/benchmark/memorize.rb +86 -0
  15. data/benchmark/parse_table.rb +103 -0
  16. data/benchmark/shell.rb +26 -0
  17. data/bin/console +15 -0
  18. data/bin/setup +8 -0
  19. data/config/default.yml +83 -0
  20. data/config/enable-only-performance.yml +30 -0
  21. data/gemfiles/activerecord_6_1.gemfile +14 -0
  22. data/gemfiles/activerecord_7_0.gemfile +14 -0
  23. data/lib/rubocop/cop/isucon/correctors/mysql2_n_plus_one_query_corrector/correctable_methods.rb +66 -0
  24. data/lib/rubocop/cop/isucon/correctors/mysql2_n_plus_one_query_corrector/replace_methods.rb +127 -0
  25. data/lib/rubocop/cop/isucon/correctors/mysql2_n_plus_one_query_corrector.rb +112 -0
  26. data/lib/rubocop/cop/isucon/mixin/database_methods.rb +59 -0
  27. data/lib/rubocop/cop/isucon/mixin/mysql2_xquery_methods.rb +176 -0
  28. data/lib/rubocop/cop/isucon/mixin/sinatra_methods.rb +37 -0
  29. data/lib/rubocop/cop/isucon/mysql2/join_without_index.rb +100 -0
  30. data/lib/rubocop/cop/isucon/mysql2/many_join_table.rb +86 -0
  31. data/lib/rubocop/cop/isucon/mysql2/n_plus_one_query.rb +179 -0
  32. data/lib/rubocop/cop/isucon/mysql2/prepare_execute.rb +136 -0
  33. data/lib/rubocop/cop/isucon/mysql2/select_asterisk.rb +171 -0
  34. data/lib/rubocop/cop/isucon/mysql2/where_without_index.rb +105 -0
  35. data/lib/rubocop/cop/isucon/shell/backtick.rb +36 -0
  36. data/lib/rubocop/cop/isucon/shell/system.rb +36 -0
  37. data/lib/rubocop/cop/isucon/sinatra/disable_logging.rb +83 -0
  38. data/lib/rubocop/cop/isucon/sinatra/logger.rb +52 -0
  39. data/lib/rubocop/cop/isucon/sinatra/rack_logger.rb +58 -0
  40. data/lib/rubocop/cop/isucon/sinatra/serve_static_file.rb +73 -0
  41. data/lib/rubocop/cop/isucon_cops.rb +20 -0
  42. data/lib/rubocop/isucon/database_connection.rb +42 -0
  43. data/lib/rubocop/isucon/gda/client.rb +184 -0
  44. data/lib/rubocop/isucon/gda/gda_ext.rb +119 -0
  45. data/lib/rubocop/isucon/gda/join_condition.rb +25 -0
  46. data/lib/rubocop/isucon/gda/join_operand.rb +46 -0
  47. data/lib/rubocop/isucon/gda/node_location.rb +42 -0
  48. data/lib/rubocop/isucon/gda/node_patcher.rb +101 -0
  49. data/lib/rubocop/isucon/gda/where_condition.rb +73 -0
  50. data/lib/rubocop/isucon/gda/where_operand.rb +32 -0
  51. data/lib/rubocop/isucon/gda.rb +28 -0
  52. data/lib/rubocop/isucon/inject.rb +20 -0
  53. data/lib/rubocop/isucon/memorize_methods.rb +38 -0
  54. data/lib/rubocop/isucon/version.rb +7 -0
  55. data/lib/rubocop/isucon.rb +20 -0
  56. data/lib/rubocop-isucon.rb +16 -0
  57. data/rubocop-isucon.gemspec +52 -0
  58. metadata +286 -0
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Isucon
6
+ module Mysql2
7
+ # Use `db.xquery` instead of `db.prepare.execute`
8
+ #
9
+ # @example
10
+ # # bad (auto-correct isn't possible)
11
+ # statement = db.prepare('SELECT * FROM `users` WHERE `id` = ?')
12
+ # statement.execute(
13
+ # session[:user][:id]
14
+ # ).first
15
+ #
16
+ # # bad (auto-correct is possible)
17
+ # db.prepare('SELECT * FROM `users` WHERE `id` = ?').execute(
18
+ # session[:user][:id]
19
+ # ).first
20
+ #
21
+ # # good
22
+ # require 'mysql2-cs-bind'
23
+ #
24
+ # db.xquery('SELECT * FROM `users` WHERE `id` = ?',
25
+ # session[:user][:id]
26
+ # ).first
27
+ #
28
+ class PrepareExecute < Base
29
+ extend AutoCorrector
30
+
31
+ MSG = "Use `db.xquery` instead of `db.prepare.execute`"
32
+
33
+ EXECUTE_LENGTH = "execute".length
34
+
35
+ # @!method find_prepare_execute(node)
36
+ def_node_search :find_prepare_execute, <<~PATTERN
37
+ (send
38
+ (send
39
+ (send nil? _) :prepare $_
40
+ )
41
+ :execute
42
+ ...
43
+ )
44
+ PATTERN
45
+
46
+ # @!method prepare_with_execute?(node)
47
+ # @return [Boolean]
48
+ def_node_matcher :prepare_with_execute?, <<~PATTERN
49
+ (send
50
+ (send
51
+ (send nil? _) :prepare _
52
+ )
53
+ :execute
54
+ ...
55
+ )
56
+ PATTERN
57
+
58
+ # @!method prepare?(node)
59
+ # @return [Boolean]
60
+ def_node_matcher :prepare?, <<~PATTERN
61
+ (send
62
+ (send nil? _) :prepare _
63
+ )
64
+ PATTERN
65
+
66
+ # @param node [RuboCop::AST::Node]
67
+ def on_send(node)
68
+ if prepare_with_execute?(node)
69
+ find_prepare_execute(node) do |prepare_arg_node|
70
+ add_offense(node) do |corrector|
71
+ perform_autocorrect(corrector: corrector, node: node, prepare_arg_node: prepare_arg_node)
72
+ end
73
+ end
74
+ return
75
+ end
76
+
77
+ add_offense(node) if prepare_without_execute?(node)
78
+ end
79
+
80
+ private
81
+
82
+ # @param corrector [RuboCop::Cop::Corrector]
83
+ # @param node [RuboCop::AST::Node]
84
+ # @param prepare_arg_node [RuboCop::AST::Node]
85
+ def perform_autocorrect(corrector:, node:, prepare_arg_node:)
86
+ if node.child_nodes[1]
87
+ perform_autocorrect_for_any_args(corrector: corrector, node: node, prepare_arg_node: prepare_arg_node)
88
+ else
89
+ perform_autocorrect_for_no_args(corrector: corrector, node: node, prepare_arg_node: prepare_arg_node)
90
+ end
91
+ end
92
+
93
+ # @param corrector [RuboCop::Cop::Corrector]
94
+ # @param node [RuboCop::AST::Node]
95
+ # @param prepare_arg_node [RuboCop::AST::Node]
96
+ def perform_autocorrect_for_any_args(corrector:, node:, prepare_arg_node:)
97
+ loc = offence_location(node: node, suffix_length: EXECUTE_LENGTH + 1)
98
+ corrector.replace(loc, "xquery(#{prepare_arg_node.source},")
99
+ end
100
+
101
+ # @param corrector [RuboCop::Cop::Corrector]
102
+ # @param node [RuboCop::AST::Node]
103
+ # @param prepare_arg_node [RuboCop::AST::Node]
104
+ def perform_autocorrect_for_no_args(corrector:, node:, prepare_arg_node:)
105
+ suffix_length =
106
+ if node.source.end_with?("execute()")
107
+ EXECUTE_LENGTH + 2
108
+ else
109
+ EXECUTE_LENGTH
110
+ end
111
+ loc = offence_location(node: node, suffix_length: suffix_length)
112
+ corrector.replace(loc, "xquery(#{prepare_arg_node.source})")
113
+ end
114
+
115
+ # @param node [RuboCop::AST::Node]
116
+ # @param suffix_length [Integer]
117
+ # @return [Parser::Source::Rang]
118
+ def offence_location(node:, suffix_length:)
119
+ prepare_begin_pos = node.child_nodes[0].loc.selector.begin_pos
120
+ execute_begin_pos = node.child_nodes[0].loc.end.end_pos + 1
121
+ execute_end_pos = execute_begin_pos + suffix_length
122
+
123
+ Parser::Source::Range.new(node.loc.expression.source_buffer, prepare_begin_pos, execute_end_pos)
124
+ end
125
+
126
+ # Whether `prepare` isn't followed by `execute`
127
+ # @param node [RuboCop::AST::Node]
128
+ # @return [Boolean]
129
+ def prepare_without_execute?(node)
130
+ prepare?(node) && !prepare_with_execute?(node.parent)
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,171 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Isucon
6
+ module Mysql2
7
+ # Avoid `SELECT *` in `db.xquery`
8
+ #
9
+ # @note If `Database` isn't configured, auto-correct will not be available. (Only offense detection can be used)
10
+ #
11
+ # @note This cop replaces `SELECT *` with a `SELECT` by the columns present in the table (e.g. `SELECT id, name`),
12
+ # but does not check whether the columns are actually used.
13
+ # Please manually delete unused columns after auto corrected
14
+ #
15
+ # @example
16
+ # # bad
17
+ # db.xquery('SELECT * FROM users')
18
+ #
19
+ # # bad
20
+ # db.xquery('SELECT users.* FROM users')
21
+ #
22
+ # # good
23
+ # db.xquery('SELECT id, name FROM users')
24
+ #
25
+ # # good
26
+ # db.xquery('SELECT users.id, users.name FROM users')
27
+ #
28
+ class SelectAsterisk < Base
29
+ include Mixin::DatabaseMethods
30
+ include Mixin::Mysql2XqueryMethods
31
+
32
+ extend AutoCorrector
33
+
34
+ MSG = "Use SELECT with column names. (e.g. `SELECT id, name FROM table_name`)"
35
+
36
+ TODO = "# TODO: Remove needless columns if necessary\n"
37
+
38
+ # @param node [RuboCop::AST::Node]
39
+ def on_send(node)
40
+ with_error_handling(node) do
41
+ with_xquery(node) do |type, root_gda|
42
+ next unless root_gda
43
+
44
+ check_and_register_offence(type: type, root_gda: root_gda, node: node)
45
+ end
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ # @param type [Symbol] Node type. one of `:str`, `:dstr`
52
+ # @param root_gda [RuboCop::Isucon::GDA::Client]
53
+ # @param node [RuboCop::AST::Node]
54
+ def check_and_register_offence(type:, root_gda:, node:)
55
+ root_gda.visit_all do |gda|
56
+ next unless gda.ast.respond_to?(:expr_list)
57
+
58
+ gda.ast.expr_list.each do |select_field_node|
59
+ check_and_register_offence_for_select_field_node(
60
+ type: type, node: node, gda: gda,
61
+ select_field_node: select_field_node
62
+ )
63
+ end
64
+ end
65
+ end
66
+
67
+ # @param type [Symbol] Node type. one of `:str`, `:dstr`
68
+ # @param node [RuboCop::AST::Node]
69
+ # @param gda [RuboCop::Isucon::GDA::Client]
70
+ # @param select_field_node [GDA::Nodes::SelectField]
71
+ def check_and_register_offence_for_select_field_node(type:, node:, gda:, select_field_node:)
72
+ return unless select_field_node.respond_to?(:expr)
73
+
74
+ select_field = parse_select_field_node(select_field_node)
75
+
76
+ return unless select_field[:column_name] == "*"
77
+
78
+ loc = offense_location(type: type, node: node, gda_location: select_field_node.expr.location)
79
+ return unless loc
80
+
81
+ add_offense(loc) do |corrector|
82
+ perform_autocorrect(corrector: corrector, loc: loc, gda: gda, node: node,
83
+ select_table_name: select_field[:table_name])
84
+ end
85
+ end
86
+
87
+ # @param select_field_node [GDA::Nodes::SelectField]
88
+ # @return [Hash<Symbol, String>] table_name, column_name
89
+ def parse_select_field_node(select_field_node)
90
+ column_elements = select_field_node.expr.value.split(".", 2)
91
+
92
+ case column_elements.count
93
+ when 1
94
+ return { column_name: column_elements[0] }
95
+ when 2
96
+ return { table_name: column_elements[0], column_name: column_elements[1] }
97
+ end
98
+
99
+ {}
100
+ end
101
+
102
+ # @param corrector [RuboCop::Cop::Corrector]
103
+ # @param loc [Parser::Source::Range]
104
+ # @param gda [RuboCop::Isucon::GDA::Client]
105
+ # @param node [RuboCop::AST::Node]
106
+ # @param select_table_name [String,nil] table names included in the SELECT clause
107
+ def perform_autocorrect(corrector:, loc:, gda:, node:, select_table_name:)
108
+ return unless enabled_database?
109
+ return if gda.table_names.empty?
110
+
111
+ if select_table_name
112
+ return unless gda.table_names.include?(select_table_name)
113
+
114
+ replace_asterisk(corrector: corrector, loc: loc, table_name: select_table_name, table_prefix: true)
115
+ else
116
+ return unless gda.table_names.length == 1
117
+
118
+ replace_asterisk(corrector: corrector, loc: loc, table_name: gda.table_names[0], table_prefix: false)
119
+ end
120
+
121
+ insert_todo_comment(corrector: corrector, node: node)
122
+ end
123
+
124
+ # @param corrector [RuboCop::Cop::Corrector]
125
+ # @param loc [Parser::Source::Range]
126
+ # @param table_name [String]
127
+ # @param table_prefix [Boolean] Whether add table name to prefix (e.g. `users`.`name`)
128
+ def replace_asterisk(corrector:, loc:, table_name:, table_prefix:)
129
+ select_columns = columns_in_select_clause(table_name: table_name, table_prefix: table_prefix)
130
+ corrector.replace(loc, select_columns)
131
+ end
132
+
133
+ # @param table_name [String]
134
+ # @param table_prefix [Boolean] Whether add table name to prefix (e.g. `users`.`name`)
135
+ # @return [String]
136
+ def columns_in_select_clause(table_name:, table_prefix:)
137
+ column_names = connection.column_names(table_name)
138
+
139
+ column_names.map do |column|
140
+ if table_prefix
141
+ "`#{table_name}`.`#{column}`"
142
+ else
143
+ "`#{column}`"
144
+ end
145
+ end.join(", ")
146
+ end
147
+
148
+ # @param corrector [RuboCop::Cop::Corrector]
149
+ # @param node [RuboCop::AST::Node]
150
+ def insert_todo_comment(corrector:, node:)
151
+ current_line = node.loc.expression.line
152
+ current_line_range = node.loc.expression.source_buffer.line_range(current_line)
153
+
154
+ indent = node_indent_level(node)
155
+ comment_line = (" " * indent) + TODO
156
+ corrector.insert_before(current_line_range, comment_line)
157
+ end
158
+
159
+ # @param node [RuboCop::AST::Node]
160
+ # @return [Integer]
161
+ def node_indent_level(node)
162
+ node.loc.expression.source_line =~ /^(\s+)/
163
+ return 0 unless Regexp.last_match(1)
164
+
165
+ Regexp.last_match(1).length
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Isucon
6
+ module Mysql2
7
+ # Check for `WHERE` without index
8
+ #
9
+ # @note If `Database` isn't configured, this cop's feature (offense detection and auto-correct) will not be available.
10
+ #
11
+ # @example
12
+ # # bad (user_id is not indexed)
13
+ # db.xquery('SELECT id, title FROM articles WHERE used_id = ?', user_id)
14
+ #
15
+ # # good (user_id is indexed)
16
+ # db.xquery('SELECT id, title FROM articles WHERE used_id = ?', user_id)
17
+ #
18
+ # # good (id is primary key)
19
+ # db.xquery('SELECT id, title FROM articles WHERE id = ?', id)
20
+ #
21
+ class WhereWithoutIndex < Base
22
+ include Mixin::DatabaseMethods
23
+ include Mixin::Mysql2XqueryMethods
24
+
25
+ MSG = "This where clause doesn't seem to have an index. " \
26
+ "(e.g. `ALTER TABLE %<table_name>s ADD INDEX index_%<column_name>s (%<column_name>s)`)"
27
+
28
+ # @param node [RuboCop::AST::Node]
29
+ def on_send(node)
30
+ with_error_handling(node) do
31
+ return unless enabled_database?
32
+
33
+ with_xquery(node) do |type, root_gda|
34
+ next unless root_gda
35
+ next if exists_index_in_where_clause_columns?(root_gda)
36
+
37
+ register_offense(type: type, node: node, root_gda: root_gda)
38
+ end
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ # @param type [Symbol] Node type. one of `:str`, `:dstr`
45
+ # @param node [RuboCop::AST::Node]
46
+ # @param root_gda [RuboCop::Isucon::GDA::Client]
47
+ def register_offense(type:, node:, root_gda:)
48
+ root_gda.visit_all do |gda|
49
+ next if gda.where_nodes.empty?
50
+
51
+ loc = offense_location(type: type, node: node, gda_location: gda.where_nodes.first.location)
52
+ next unless loc
53
+
54
+ message = offense_message(gda)
55
+ add_offense(loc, message: message)
56
+ end
57
+ end
58
+
59
+ # @param gda [RuboCop::Isucon::GDA::Client]
60
+ def offense_message(gda)
61
+ column_name = gda.where_conditions[0].column_operand
62
+ table_name = find_table_name_from_column_name(table_names: gda.table_names, column_name: column_name)
63
+ format(MSG, table_name: table_name, column_name: column_name)
64
+ end
65
+
66
+ # @param root_gda [RuboCop::Isucon::GDA::Client]
67
+ # @return [Boolean]
68
+ def exists_index_in_where_clause_columns?(root_gda)
69
+ root_gda.visit_all do |gda|
70
+ gda.table_names.each do |table_name|
71
+ return true if covered_where_column_in_index?(gda: gda, table_name: table_name)
72
+ return true if covered_where_column_in_primary_key?(gda: gda, table_name: table_name)
73
+ end
74
+ end
75
+
76
+ false
77
+ end
78
+
79
+ # @param gda [RuboCop::Isucon::GDA::Client]
80
+ # @param table_name [String]
81
+ # @return [Boolean]
82
+ def covered_where_column_in_index?(gda:, table_name:)
83
+ indexes = connection.indexes(table_name)
84
+ index_first_columns = indexes.map { |index| index.columns[0] }
85
+
86
+ gda.where_conditions.any? do |condition|
87
+ index_first_columns.include?(condition.column_operand)
88
+ end
89
+ end
90
+
91
+ # @param gda [RuboCop::Isucon::GDA::Client]
92
+ # @param table_name [String]
93
+ # @return [Boolean]
94
+ def covered_where_column_in_primary_key?(gda:, table_name:)
95
+ primary_keys = connection.primary_keys(table_name)
96
+ return false if primary_keys.empty?
97
+
98
+ where_columns = gda.where_conditions.map(&:column_operand)
99
+ primary_keys.all? { |primary_key| where_columns.include?(primary_key) }
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Isucon
6
+ module Shell
7
+ # Avoid external command calls with backtick
8
+ #
9
+ # @example
10
+ # # bad
11
+ # def digest(src)
12
+ # `printf "%s" #{Shellwords.shellescape(src)} | openssl dgst -sha512 | sed 's/^.*= //'`.strip
13
+ # end
14
+ #
15
+ # # bad
16
+ # def digest(src)
17
+ # %x(printf "%s" \#{Shellwords.shellescape(src)} | openssl dgst -sha512 | sed 's/^.*= //').strip
18
+ # end
19
+ #
20
+ # # good
21
+ # def digest(src)
22
+ # OpenSSL::Digest::SHA512.hexdigest(src)
23
+ # end
24
+ #
25
+ class Backtick < Base
26
+ MSG = "Use pure-ruby code instead of external command execution if possible"
27
+
28
+ # @param node [RuboCop::AST::Node]
29
+ def on_xstr(node)
30
+ add_offense(node)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Isucon
6
+ module Shell
7
+ # Avoid external command calls with `Kernel#system`
8
+ #
9
+ # @example
10
+ # # bad
11
+ # system("sleep 1")
12
+ #
13
+ # # good
14
+ # sleep 1
15
+ #
16
+ class System < Base
17
+ MSG = "Use pure-ruby code instead of external command execution if possible"
18
+
19
+ # Whether matches `system`
20
+ # @!method system?(node)
21
+ # @return [Boolean]
22
+ def_node_matcher :system?, <<~PATTERN
23
+ (send nil? :system ...)
24
+ PATTERN
25
+
26
+ # @param node [RuboCop::AST::Node]
27
+ def on_send(node)
28
+ return unless system?(node)
29
+
30
+ add_offense(node)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Isucon
6
+ module Sinatra
7
+ # Disable sinatra logging
8
+ #
9
+ # @example
10
+ # # bad
11
+ # class App < Sinatra::Base
12
+ # enable :logging
13
+ # end
14
+ #
15
+ # # bad
16
+ # class App < Sinatra::Base
17
+ # end
18
+ #
19
+ # # good
20
+ # class App < Sinatra::Base
21
+ # disable :logging
22
+ # end
23
+ #
24
+ class DisableLogging < Base
25
+ include Mixin::SinatraMethods
26
+
27
+ extend AutoCorrector
28
+
29
+ MSG = "Disable sinatra logging."
30
+
31
+ # @!method logging_enabled?(node)
32
+ # @param node [RuboCop::AST::Node]
33
+ # @return [Boolean]
34
+ def_node_matcher :logging_enabled?, <<~PATTERN
35
+ (send nil? :enable (sym :logging))
36
+ PATTERN
37
+
38
+ # @param node [RuboCop::AST::Node]
39
+ def on_send(node)
40
+ return unless parent_is_sinatra_app?(node)
41
+ return unless logging_enabled?(node)
42
+
43
+ add_offense(node) do |corrector|
44
+ perform_autocorrect_for_on_send(corrector: corrector, node: node)
45
+ end
46
+ end
47
+
48
+ # @param node [RuboCop::AST::Node]
49
+ def on_class(node)
50
+ return unless subclass_of_sinatra_base?(node)
51
+ return if subclass_of_sinatra_base_contains_logging?(node)
52
+
53
+ add_offense(node) do |corrector|
54
+ perform_autocorrect_for_on_class(corrector: corrector, node: node)
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ # @param corrector [RuboCop::Cop::Corrector]
61
+ # @param node [RuboCop::AST::Node]
62
+ def perform_autocorrect_for_on_send(corrector:, node:)
63
+ corrector.replace(node, "disable :logging")
64
+ end
65
+
66
+ # @param corrector [RuboCop::Cop::Corrector]
67
+ # @param node [RuboCop::AST::Node]
68
+ def perform_autocorrect_for_on_class(corrector:, node:)
69
+ sinatra_base_node = node.child_nodes[1]
70
+
71
+ content = [
72
+ "\n",
73
+ (" " * (node.loc.column + 2)),
74
+ "disable :logging",
75
+ ].join
76
+
77
+ corrector.insert_after(sinatra_base_node.loc.expression, content)
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Isucon
6
+ module Sinatra
7
+ # Disable sinatra logger logging
8
+ #
9
+ # @example
10
+ # # bad
11
+ # logger.error "Search condition not found"
12
+ #
13
+ # # good
14
+ # # no logging
15
+ #
16
+ class Logger < Base
17
+ MSG = "Don't use `logger`"
18
+
19
+ extend AutoCorrector
20
+
21
+ # @!method logger?(node)
22
+ # @param node [RuboCop::AST::Node]
23
+ # @return [Boolean]
24
+ def_node_matcher :logger?, <<~PATTERN
25
+ (send
26
+ (send nil? :logger)
27
+ {:debug | :error | :fatal | :info | :warn}
28
+ ...
29
+ )
30
+ PATTERN
31
+
32
+ # @param node [RuboCop::AST::Node]
33
+ def on_send(node)
34
+ return unless logger?(node)
35
+
36
+ add_offense(node) do |corrector|
37
+ perform_autocorrect(corrector: corrector, node: node)
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ # @param corrector [RuboCop::Cop::Corrector]
44
+ # @param node [RuboCop::AST::Node]
45
+ def perform_autocorrect(corrector:, node:)
46
+ corrector.replace(node, "")
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Isucon
6
+ module Sinatra
7
+ # Disable `request.env['rack.logger']` logging
8
+ #
9
+ # @example
10
+ # # bad
11
+ # request.env['rack.logger'].warn 'drop post isu condition request'
12
+ #
13
+ # # good
14
+ # # no logging
15
+ class RackLogger < Base
16
+ MSG = "Don't use `request.env['rack.logger']`"
17
+
18
+ extend AutoCorrector
19
+
20
+ # @!method rack_logger?(node)
21
+ # @param node [RuboCop::AST::Node]
22
+ # @return [Boolean]
23
+ def_node_matcher :rack_logger?, <<~PATTERN
24
+ (send
25
+ (send
26
+ (send
27
+ (send nil? :request)
28
+ :env
29
+ )
30
+ :[]
31
+ (str "rack.logger")
32
+ )
33
+ {:debug | :error | :fatal | :info | :warn}
34
+ ...
35
+ )
36
+ PATTERN
37
+
38
+ # @param node [RuboCop::AST::Node]
39
+ def on_send(node)
40
+ return unless rack_logger?(node)
41
+
42
+ add_offense(node) do |corrector|
43
+ perform_autocorrect(corrector: corrector, node: node)
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ # @param corrector [RuboCop::Cop::Corrector]
50
+ # @param node [RuboCop::AST::Node]
51
+ def perform_autocorrect(corrector:, node:)
52
+ corrector.replace(node, "")
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end