rubocop-isucon 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|