rubocop-isucon 0.1.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.
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