rubocop-yast 0.0.4 → 0.0.5
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 +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
|