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.
@@ -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