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.
- checksums.yaml +7 -0
- data/.github/workflows/gh-pages.yml +44 -0
- data/.github/workflows/test.yml +91 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.rubocop.yml +43 -0
- data/.yardopts +7 -0
- data/CHANGELOG.md +6 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +108 -0
- data/Rakefile +35 -0
- data/benchmark/README.md +69 -0
- data/benchmark/memorize.rb +86 -0
- data/benchmark/parse_table.rb +103 -0
- data/benchmark/shell.rb +26 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/config/default.yml +83 -0
- data/config/enable-only-performance.yml +30 -0
- data/gemfiles/activerecord_6_1.gemfile +14 -0
- data/gemfiles/activerecord_7_0.gemfile +14 -0
- data/lib/rubocop/cop/isucon/correctors/mysql2_n_plus_one_query_corrector/correctable_methods.rb +66 -0
- data/lib/rubocop/cop/isucon/correctors/mysql2_n_plus_one_query_corrector/replace_methods.rb +127 -0
- data/lib/rubocop/cop/isucon/correctors/mysql2_n_plus_one_query_corrector.rb +112 -0
- data/lib/rubocop/cop/isucon/mixin/database_methods.rb +59 -0
- data/lib/rubocop/cop/isucon/mixin/mysql2_xquery_methods.rb +176 -0
- data/lib/rubocop/cop/isucon/mixin/sinatra_methods.rb +37 -0
- data/lib/rubocop/cop/isucon/mysql2/join_without_index.rb +100 -0
- data/lib/rubocop/cop/isucon/mysql2/many_join_table.rb +86 -0
- data/lib/rubocop/cop/isucon/mysql2/n_plus_one_query.rb +179 -0
- data/lib/rubocop/cop/isucon/mysql2/prepare_execute.rb +136 -0
- data/lib/rubocop/cop/isucon/mysql2/select_asterisk.rb +171 -0
- data/lib/rubocop/cop/isucon/mysql2/where_without_index.rb +105 -0
- data/lib/rubocop/cop/isucon/shell/backtick.rb +36 -0
- data/lib/rubocop/cop/isucon/shell/system.rb +36 -0
- data/lib/rubocop/cop/isucon/sinatra/disable_logging.rb +83 -0
- data/lib/rubocop/cop/isucon/sinatra/logger.rb +52 -0
- data/lib/rubocop/cop/isucon/sinatra/rack_logger.rb +58 -0
- data/lib/rubocop/cop/isucon/sinatra/serve_static_file.rb +73 -0
- data/lib/rubocop/cop/isucon_cops.rb +20 -0
- data/lib/rubocop/isucon/database_connection.rb +42 -0
- data/lib/rubocop/isucon/gda/client.rb +184 -0
- data/lib/rubocop/isucon/gda/gda_ext.rb +119 -0
- data/lib/rubocop/isucon/gda/join_condition.rb +25 -0
- data/lib/rubocop/isucon/gda/join_operand.rb +46 -0
- data/lib/rubocop/isucon/gda/node_location.rb +42 -0
- data/lib/rubocop/isucon/gda/node_patcher.rb +101 -0
- data/lib/rubocop/isucon/gda/where_condition.rb +73 -0
- data/lib/rubocop/isucon/gda/where_operand.rb +32 -0
- data/lib/rubocop/isucon/gda.rb +28 -0
- data/lib/rubocop/isucon/inject.rb +20 -0
- data/lib/rubocop/isucon/memorize_methods.rb +38 -0
- data/lib/rubocop/isucon/version.rb +7 -0
- data/lib/rubocop/isucon.rb +20 -0
- data/lib/rubocop-isucon.rb +16 -0
- data/rubocop-isucon.gemspec +52 -0
- 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
|