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