rubocop-yast 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +1 -0
- data/README.md +122 -0
- data/Rakefile +24 -0
- data/lib/rubocop/cop/yast/builtins.rb +39 -30
- data/lib/rubocop/cop/yast/ops.rb +3 -157
- data/lib/rubocop/yast/builtins/builtin.rb +38 -0
- data/lib/rubocop/yast/builtins/getenv.rb +19 -0
- data/lib/rubocop/yast/builtins/time.rb +16 -0
- data/lib/rubocop/yast/builtins/y2log.rb +138 -0
- data/lib/rubocop/yast/node_helpers.rb +22 -0
- data/lib/rubocop/yast/reformatter.rb +43 -0
- data/lib/rubocop/yast/track_variable_scope.rb +184 -0
- data/lib/rubocop/yast/variable_scope.rb +2 -0
- data/lib/rubocop/yast/version.rb +1 -1
- data/rubocop-yast.gemspec +1 -0
- data/spec/builtins_spec.md +614 -0
- data/spec/builtins_spec.rb +543 -41
- data/spec/ops_spec.rb +12 -7
- data/spec/rspec_code.rb +47 -0
- data/spec/rspec_renderer.rb +206 -0
- data/spec/spec_helper.rb +21 -13
- metadata +29 -2
@@ -0,0 +1,38 @@
|
|
1
|
+
|
2
|
+
# RuboCop::Cop::CorrectionNotPossible exception
|
3
|
+
require "rubocop/cop/cop"
|
4
|
+
|
5
|
+
module RuboCop
|
6
|
+
module Yast
|
7
|
+
module Builtins
|
8
|
+
# generic class for handling Yast builtins, base class for specific
|
9
|
+
# builtins zombie killers
|
10
|
+
class Builtin
|
11
|
+
# white list of allowed Builtins calls
|
12
|
+
ALLOWED_FUNCTIONS = [
|
13
|
+
# locale dependent sorting in not available in Ruby stdlib
|
14
|
+
:lsort,
|
15
|
+
# gettext helpers
|
16
|
+
:dgettext,
|
17
|
+
:dngettext,
|
18
|
+
:dpgettext,
|
19
|
+
# crypt* helpers
|
20
|
+
:crypt,
|
21
|
+
:cryptmd5,
|
22
|
+
:cryptblowfish,
|
23
|
+
:cryptsha256,
|
24
|
+
:cryptsha512
|
25
|
+
]
|
26
|
+
|
27
|
+
def offense?(node)
|
28
|
+
_receiver, method_name, *_args = *node
|
29
|
+
!ALLOWED_FUNCTIONS.include?(method_name)
|
30
|
+
end
|
31
|
+
|
32
|
+
def correction(_node)
|
33
|
+
raise RuboCop::Cop::CorrectionNotPossible
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
|
2
|
+
require "rubocop/yast/builtins/builtin"
|
3
|
+
|
4
|
+
module RuboCop
|
5
|
+
module Yast
|
6
|
+
module Builtins
|
7
|
+
# getenv() convertor
|
8
|
+
class Getenv < Builtin
|
9
|
+
def correction(node)
|
10
|
+
lambda do |corrector|
|
11
|
+
_builtins, _message, *args = *node
|
12
|
+
new_code = "ENV[#{args.first.loc.expression.source}]"
|
13
|
+
corrector.replace(node.loc.expression, new_code)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "rubocop/yast/builtins/builtin"
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Yast
|
5
|
+
module Builtins
|
6
|
+
# time() convertor
|
7
|
+
class Time < Builtin
|
8
|
+
def correction(node)
|
9
|
+
lambda do |corrector|
|
10
|
+
corrector.replace(node.loc.expression, "::Time.now.to_i")
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require "rubocop/yast/builtins/builtin"
|
2
|
+
require "rubocop/yast/reformatter"
|
3
|
+
require "rubocop/yast/node_helpers"
|
4
|
+
|
5
|
+
module RuboCop
|
6
|
+
module Yast
|
7
|
+
module Builtins
|
8
|
+
# generic class for handling logging builtins
|
9
|
+
class Y2log < Builtin
|
10
|
+
include Reformatter
|
11
|
+
include NodeHelpers
|
12
|
+
|
13
|
+
attr_reader :added_includes
|
14
|
+
|
15
|
+
LOGGING_REPLACEMENENTS = {
|
16
|
+
y2debug: "debug",
|
17
|
+
y2milestone: "info",
|
18
|
+
y2warning: "warn",
|
19
|
+
y2error: "error",
|
20
|
+
y2security: "error",
|
21
|
+
y2internal: "fatal"
|
22
|
+
}
|
23
|
+
|
24
|
+
ASGN_TYPES = [
|
25
|
+
:lvasgn,
|
26
|
+
:op_asgn,
|
27
|
+
:or_asgn,
|
28
|
+
:and_asgn
|
29
|
+
]
|
30
|
+
|
31
|
+
TYPES_WITHOUT_ARG = [
|
32
|
+
:dstr,
|
33
|
+
:send,
|
34
|
+
:lvar
|
35
|
+
]
|
36
|
+
|
37
|
+
def initialize
|
38
|
+
@added_includes = []
|
39
|
+
end
|
40
|
+
|
41
|
+
def correction(node)
|
42
|
+
_receiver, _method_name, format, *params = *node
|
43
|
+
|
44
|
+
# we can replace only standard logging, not backtraces like
|
45
|
+
# Builtins.y2milestone(-1, "foo") or unsafe code like
|
46
|
+
# fmt = "%1: %2"; Builtins.y2milestone(fmt, foo, bar)
|
47
|
+
if !((TYPES_WITHOUT_ARG.include?(format.type) && params.empty?) ||
|
48
|
+
(format.str_type?)) || ignore_node?(node)
|
49
|
+
raise RuboCop::Cop::CorrectionNotPossible
|
50
|
+
end
|
51
|
+
|
52
|
+
correction_lambda(node)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def correction_lambda(node)
|
58
|
+
lambda do |corrector|
|
59
|
+
add_missing_logger(node, corrector)
|
60
|
+
corrector.replace(node.loc.expression, replacement(node))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def replacement(node)
|
65
|
+
_receiver, method_name, format, *params = *node
|
66
|
+
method = LOGGING_REPLACEMENENTS[method_name]
|
67
|
+
src_format = format.loc.expression.source
|
68
|
+
|
69
|
+
if format.str_type?
|
70
|
+
src_params = params.map { |p| p.loc.expression.source }
|
71
|
+
|
72
|
+
"log.#{method} #{interpolate(src_format, src_params)}"
|
73
|
+
else
|
74
|
+
"log.#{method} #{src_format}"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def ignore_node?(node)
|
79
|
+
target_node = parent_node_type(node, [:class, :module])
|
80
|
+
|
81
|
+
log_descendant = target_node.each_descendant.find do |n|
|
82
|
+
n && ASGN_TYPES.include?(n.type) && n.loc.name.source == "log"
|
83
|
+
end
|
84
|
+
|
85
|
+
log_descendant
|
86
|
+
end
|
87
|
+
|
88
|
+
# Add Yast::Logger include somewhere up in the tree
|
89
|
+
def add_missing_logger(node, corrector)
|
90
|
+
target_node = parent_node_type(node, [:class, :module])
|
91
|
+
|
92
|
+
# already added or already present
|
93
|
+
return if added_includes.include?(target_node) ||
|
94
|
+
logger_included?(target_node)
|
95
|
+
|
96
|
+
add_include_to_node(target_node, corrector)
|
97
|
+
|
98
|
+
added_includes << target_node
|
99
|
+
end
|
100
|
+
|
101
|
+
# add the Yast::LOgger statement include to this node
|
102
|
+
def add_include_to_node(node, corrector)
|
103
|
+
if node.class_type? || node.module_type?
|
104
|
+
# indent the include statement
|
105
|
+
corrector.insert_after(class_logger_pos(node),
|
106
|
+
logger_include_code(node.loc.keyword.column))
|
107
|
+
else
|
108
|
+
# otherwise put it at the top
|
109
|
+
corrector.insert_before(node.loc.expression, logger_include_code)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# insert after "class Foo" or "class Foo < Bar" statement
|
114
|
+
def class_logger_pos(node)
|
115
|
+
node.loc.operator ? node.children[1].loc.expression : node.loc.name
|
116
|
+
end
|
117
|
+
|
118
|
+
# simple check for already present include
|
119
|
+
def logger_included?(node)
|
120
|
+
return false if node.nil? || node.loc.nil?
|
121
|
+
|
122
|
+
source = node.loc.expression.source
|
123
|
+
# TODO: better check for the include call, this is a simple "grep"...
|
124
|
+
!source.match(/include\s+(Yast::|)Logger/).nil?
|
125
|
+
end
|
126
|
+
|
127
|
+
# format the include statement
|
128
|
+
def logger_include_code(indent = nil)
|
129
|
+
code = "include Yast::Logger\n"
|
130
|
+
return code unless indent
|
131
|
+
|
132
|
+
indent_str = "\n" + " " * (indent + 2)
|
133
|
+
indent_str + code
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
module RuboCop
|
3
|
+
module Yast
|
4
|
+
# helpers for traversing the AST tree
|
5
|
+
module NodeHelpers
|
6
|
+
# Find the parend node of the requested type
|
7
|
+
# @param [Array<Symbol>] types requested node types
|
8
|
+
# @param node
|
9
|
+
# @return the requested type node or the root node if not found
|
10
|
+
def parent_node_type(node, types)
|
11
|
+
target_node = node
|
12
|
+
|
13
|
+
# find parent "class" node or the root node
|
14
|
+
while !target_node.root? && !types.include?(target_node.type)
|
15
|
+
target_node = target_node.parent
|
16
|
+
end
|
17
|
+
|
18
|
+
target_node
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "yaml"
|
4
|
+
|
5
|
+
module RuboCop
|
6
|
+
module Yast
|
7
|
+
# patch the Rubocop config - include the plugin defaults
|
8
|
+
module Reformatter
|
9
|
+
# converts YCP format string to Ruby interpolation
|
10
|
+
# @param [String] format YCP format string (e.g. "foo: %1")
|
11
|
+
# @param [Array<String>] args argument list
|
12
|
+
# @return [String] String with Ruby interpolation
|
13
|
+
def interpolate(format, args)
|
14
|
+
single_to_double(format).gsub(/%./) do |match|
|
15
|
+
case match
|
16
|
+
when "%%"
|
17
|
+
"%"
|
18
|
+
when /%([1-9])/
|
19
|
+
pos = Regexp.last_match[1].to_i - 1
|
20
|
+
"\#{" + args[pos] + "}" if pos < args.size
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# convert single quoted string to double quoted to allow using string
|
26
|
+
# interpolation
|
27
|
+
def single_to_double(str)
|
28
|
+
ret = str.dup
|
29
|
+
return ret if str.start_with?("\"")
|
30
|
+
|
31
|
+
# esacpe interpolation start (ignred in single quoted string)
|
32
|
+
ret.gsub!("\#{", "\\\#{")
|
33
|
+
# esacpe double quotes (not needed in single quoted string)
|
34
|
+
ret.gsub!("\"", "\\\"")
|
35
|
+
|
36
|
+
# replace the around quotes
|
37
|
+
ret[0] = "\""
|
38
|
+
ret[-1] = "\""
|
39
|
+
ret
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "rubocop/yast/niceness"
|
4
|
+
require "rubocop/yast/variable_scope"
|
5
|
+
|
6
|
+
# We have encountered code that does satisfy our simplifying assumptions,
|
7
|
+
# translating it would not be correct.
|
8
|
+
class TooComplexToTranslateError < Exception
|
9
|
+
end
|
10
|
+
|
11
|
+
module RuboCop
|
12
|
+
module Yast
|
13
|
+
# This module tracks variable usage
|
14
|
+
module TrackVariableScope
|
15
|
+
include Niceness
|
16
|
+
|
17
|
+
def scopes
|
18
|
+
@scopes ||= VariableScopeStack.new
|
19
|
+
end
|
20
|
+
|
21
|
+
# FIXME
|
22
|
+
def process(node)
|
23
|
+
return if node.nil?
|
24
|
+
# if ! @unsafe
|
25
|
+
# oops(node, RuntimeError.new("Unknown node type #{node.type}")) \
|
26
|
+
# unless HANDLED_NODE_TYPES.include? node.type
|
27
|
+
# end
|
28
|
+
end
|
29
|
+
|
30
|
+
# currently visible scope
|
31
|
+
def scope
|
32
|
+
scopes.innermost
|
33
|
+
end
|
34
|
+
|
35
|
+
def with_new_scope_rescuing_oops(node, &block)
|
36
|
+
scopes.with_new do
|
37
|
+
block.call if block_given?
|
38
|
+
end
|
39
|
+
rescue => e
|
40
|
+
oops(node, e)
|
41
|
+
end
|
42
|
+
|
43
|
+
def on_def(node)
|
44
|
+
with_new_scope_rescuing_oops(node)
|
45
|
+
end
|
46
|
+
|
47
|
+
def on_defs(node)
|
48
|
+
with_new_scope_rescuing_oops(node)
|
49
|
+
end
|
50
|
+
|
51
|
+
def on_module(node)
|
52
|
+
with_new_scope_rescuing_oops(node)
|
53
|
+
end
|
54
|
+
|
55
|
+
def on_class(node)
|
56
|
+
with_new_scope_rescuing_oops(node)
|
57
|
+
end
|
58
|
+
|
59
|
+
def on_sclass(node)
|
60
|
+
with_new_scope_rescuing_oops(node)
|
61
|
+
end
|
62
|
+
|
63
|
+
# def on_unless
|
64
|
+
# Does not exist.
|
65
|
+
# `unless` is parsed as an `if` with then_body and else_body swapped.
|
66
|
+
# Compare with `while` and `until` which cannot do that and thus need
|
67
|
+
# distinct node types.
|
68
|
+
# end
|
69
|
+
|
70
|
+
def on_case(node)
|
71
|
+
expr, *cases = *node
|
72
|
+
process(expr)
|
73
|
+
|
74
|
+
cases.each do |case_|
|
75
|
+
scopes.with_copy do
|
76
|
+
process(case_)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# clean slate
|
81
|
+
scope.clear
|
82
|
+
end
|
83
|
+
|
84
|
+
def on_lvasgn(node)
|
85
|
+
name, value = * node
|
86
|
+
return if value.nil? # and-asgn, or-asgn, resbody do this
|
87
|
+
scope[name].nice = nice(value)
|
88
|
+
end
|
89
|
+
|
90
|
+
def on_and_asgn(node)
|
91
|
+
var, value = *node
|
92
|
+
bool_op_asgn(var, value, :and)
|
93
|
+
end
|
94
|
+
|
95
|
+
def on_or_asgn(node)
|
96
|
+
var, value = *node
|
97
|
+
bool_op_asgn(var, value, :or)
|
98
|
+
end
|
99
|
+
|
100
|
+
def on_block(_node)
|
101
|
+
# ignore body, clean slate
|
102
|
+
scope.clear
|
103
|
+
end
|
104
|
+
alias_method :on_for, :on_block
|
105
|
+
|
106
|
+
def on_while(_node)
|
107
|
+
# ignore both condition and body,
|
108
|
+
# with a simplistic scope we cannot handle them
|
109
|
+
|
110
|
+
# clean slate
|
111
|
+
scope.clear
|
112
|
+
end
|
113
|
+
alias_method :on_until, :on_while
|
114
|
+
|
115
|
+
# Exceptions:
|
116
|
+
# `raise` is an ordinary :send for the parser
|
117
|
+
|
118
|
+
def on_rescue(node)
|
119
|
+
# (:rescue, begin-block, resbody..., else-block-or-nil)
|
120
|
+
_begin_body, *_rescue_bodies, _else_body = *node
|
121
|
+
|
122
|
+
# FIXME
|
123
|
+
# @source_rewriter.transaction do
|
124
|
+
# process(begin_body)
|
125
|
+
# process(else_body)
|
126
|
+
# rescue_bodies.each do |r|
|
127
|
+
# process(r)
|
128
|
+
# end
|
129
|
+
# end
|
130
|
+
# rescue TooComplexToTranslateError
|
131
|
+
# warning "begin-rescue is too complex to translate due to a retry"
|
132
|
+
# end
|
133
|
+
end
|
134
|
+
|
135
|
+
def on_resbody(_node)
|
136
|
+
# How it is parsed:
|
137
|
+
# (:resbody, exception-types-or-nil, exception-variable-or-nil, body)
|
138
|
+
# exception-types is an :array
|
139
|
+
# exception-variable is a (:lvasgn, name), without a value
|
140
|
+
|
141
|
+
# A rescue means that *some* previous code was skipped.
|
142
|
+
# We know nothing. We could process the resbodies individually,
|
143
|
+
# and join begin-block with else-block, but it is little worth
|
144
|
+
# because they will contain few zombies.
|
145
|
+
scope.clear
|
146
|
+
end
|
147
|
+
|
148
|
+
def on_ensure(_node)
|
149
|
+
# (:ensure, guarded-code, ensuring-code)
|
150
|
+
# guarded-code may be a :rescue or not
|
151
|
+
|
152
|
+
scope.clear
|
153
|
+
end
|
154
|
+
|
155
|
+
def on_retry(_node)
|
156
|
+
# that makes the :rescue a loop, top-down data-flow fails
|
157
|
+
# FIXME
|
158
|
+
# raise TooComplexToTranslateError
|
159
|
+
end
|
160
|
+
|
161
|
+
private
|
162
|
+
|
163
|
+
def oops(node, exception)
|
164
|
+
puts "Node exception @ #{node.loc.expression}"
|
165
|
+
puts "Offending node: #{node.inspect}"
|
166
|
+
raise exception unless exception.is_a?(TooComplexToTranslateError)
|
167
|
+
end
|
168
|
+
|
169
|
+
def bool_op_asgn(var, value, op)
|
170
|
+
return if var.type != :lvasgn
|
171
|
+
name = var.children[0]
|
172
|
+
|
173
|
+
case op
|
174
|
+
when :and
|
175
|
+
scope[name].nice &&= nice(value)
|
176
|
+
when :or
|
177
|
+
scope[name].nice ||= nice(value)
|
178
|
+
else
|
179
|
+
raise "Unknown operator: #{op}"
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|