rubocop-yast 0.0.3 → 0.0.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 440ff0688d1a756f31597e9a7bead83c60682fbb
4
- data.tar.gz: 1e7593995a22e3ddd79838378aab1d20e848709c
3
+ metadata.gz: c16c03c56b76dc2b1b8896fda2f2d5765d686c41
4
+ data.tar.gz: 695426ba9cb0fd0ff1d273d337c3947f8a785b80
5
5
  SHA512:
6
- metadata.gz: d5465be57775161e935f2a29adf12a91f24dcb17a8f654000d2e39b157e8e3b6483129a6c597b0de87ee5579c26718d9031c4a39887c8bb27de546cd4c10a186
7
- data.tar.gz: 851a3358159679e9b4e10404b12e31a402a4e83ad94b3a9d3734b3b95fc87cffd522cb33096083bc54da7df78c71d93e2b48e6d8519e3e7b1176f636faf20805
6
+ metadata.gz: 5d69f1ac5711c04993f135091ea1ea1d6ac4159c579a45afe7d3c783f6650416af0aef1b4819ec0a0033dfc140fd1ad54f4e994332ef389905096fad1de8ca94
7
+ data.tar.gz: 5bd8f3cd68cdbd225c148eec3082914ff9c4345926bf14b0819f9d61b39a857277e0be03deebffb2192ee6a3a520d051935a65ea7695c28f13dbd0adc6478d2b
@@ -0,0 +1,8 @@
1
+ Yast/Builtins:
2
+ Description: "Check for obsolete Builtins.* calls"
3
+ Enabled: true
4
+
5
+ Yast/Ops:
6
+ Description: "Check for obsolete Ops.* calls"
7
+ Enabled: true
8
+ SafeMode: true
@@ -2,5 +2,9 @@
2
2
 
3
3
  require "rubocop"
4
4
 
5
+ require_relative "rubocop/yast/config"
6
+ RuboCop::Yast::Config.load_defaults
7
+
5
8
  require_relative "rubocop/yast/version"
6
9
  require_relative "rubocop/cop/yast/builtins"
10
+ require_relative "rubocop/cop/yast/ops"
@@ -16,7 +16,13 @@ module RuboCop
16
16
  # gettext helpers
17
17
  :dgettext,
18
18
  :dngettext,
19
- :dpgettext
19
+ :dpgettext,
20
+ # crypt* helpers
21
+ :crypt,
22
+ :cryptmd5,
23
+ :cryptblowfish,
24
+ :cryptsha256,
25
+ :cryptsha512
20
26
  ]
21
27
 
22
28
  BUILTINS_NODES = [
@@ -36,6 +42,27 @@ module RuboCop
36
42
 
37
43
  add_offense(node, :selector, format(MSG, method_name))
38
44
  end
45
+
46
+ def autocorrect(node)
47
+ @corrections << lambda do |corrector|
48
+ _builtins, message, args = *node
49
+
50
+ new_code = builtins_replacement(message, args)
51
+
52
+ corrector.replace(node.loc.expression, new_code) if new_code
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def builtins_replacement(message, args)
59
+ case message
60
+ when :getenv
61
+ "ENV[#{args.loc.expression.source}]"
62
+ when :time
63
+ "::Time.now.to_i"
64
+ end
65
+ end
39
66
  end
40
67
  end
41
68
  end
@@ -0,0 +1,226 @@
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 Cop
13
+ module Yast
14
+ # This cop checks for Ops.* calls, it can autocorrect safe places or
15
+ # all places in unsafe mode
16
+ class Ops < Cop
17
+ include Niceness
18
+
19
+ # Ops replacement mapping
20
+ REPLACEMENT = {
21
+ add: "+"
22
+ }
23
+
24
+ MSG = "Obsolete Ops.%s call found"
25
+
26
+ def initialize(config = nil, options = nil)
27
+ super(config, options)
28
+
29
+ @scopes = VariableScopeStack.new
30
+ @safe_mode = cop_config["SafeMode"]
31
+ @replaced_nodes = []
32
+ end
33
+
34
+ # FIXME
35
+ def process(node)
36
+ return if node.nil?
37
+ # if ! @unsafe
38
+ # oops(node, RuntimeError.new("Unknown node type #{node.type}")) \
39
+ # unless HANDLED_NODE_TYPES.include? node.type
40
+ # end
41
+ end
42
+
43
+ # currently visible scope
44
+ def scope
45
+ scopes.innermost
46
+ end
47
+
48
+ def with_new_scope_rescuing_oops(node, &block)
49
+ scopes.with_new do
50
+ block.call if block_given?
51
+ end
52
+ rescue => e
53
+ oops(node, e)
54
+ end
55
+
56
+ def on_def(node)
57
+ with_new_scope_rescuing_oops(node)
58
+ end
59
+
60
+ def on_defs(node)
61
+ with_new_scope_rescuing_oops(node)
62
+ end
63
+
64
+ def on_module(node)
65
+ with_new_scope_rescuing_oops(node)
66
+ end
67
+
68
+ def on_class(node)
69
+ with_new_scope_rescuing_oops(node)
70
+ end
71
+
72
+ def on_sclass(node)
73
+ with_new_scope_rescuing_oops(node)
74
+ end
75
+
76
+ # def on_unless
77
+ # Does not exist.
78
+ # `unless` is parsed as an `if` with then_body and else_body swapped.
79
+ # Compare with `while` and `until` which cannot do that and thus need
80
+ # distinct node types.
81
+ # end
82
+
83
+ def on_case(node)
84
+ expr, *cases = *node
85
+ process(expr)
86
+
87
+ cases.each do |case_|
88
+ scopes.with_copy do
89
+ process(case_)
90
+ end
91
+ end
92
+
93
+ # clean slate
94
+ scope.clear
95
+ end
96
+
97
+ def on_lvasgn(node)
98
+ name, value = * node
99
+ return if value.nil? # and-asgn, or-asgn, resbody do this
100
+ scope[name].nice = nice(value)
101
+ end
102
+
103
+ def on_and_asgn(node)
104
+ var, value = * node
105
+ return if var.type != :lvasgn
106
+ name = var.children[0]
107
+
108
+ scope[name].nice &&= nice(value)
109
+ end
110
+
111
+ def on_or_asgn(node)
112
+ var, value = * node
113
+ return if var.type != :lvasgn
114
+ name = var.children[0]
115
+
116
+ scope[name].nice ||= nice(value)
117
+ end
118
+
119
+ def on_send(node)
120
+ return unless call?(node, :Ops, :add)
121
+
122
+ _ops, method, a, b = *node
123
+ return if !(nice(a) && nice(b)) && safe_mode
124
+
125
+ add_offense(node, :selector, format(MSG, method))
126
+ end
127
+
128
+ def on_block(_node)
129
+ # ignore body, clean slate
130
+ scope.clear
131
+ end
132
+ alias_method :on_for, :on_block
133
+
134
+ def on_while(_node)
135
+ # ignore both condition and body,
136
+ # with a simplistic scope we cannot handle them
137
+
138
+ # clean slate
139
+ scope.clear
140
+ end
141
+ alias_method :on_until, :on_while
142
+
143
+ # Exceptions:
144
+ # `raise` is an ordinary :send for the parser
145
+
146
+ def on_rescue(node)
147
+ # (:rescue, begin-block, resbody..., else-block-or-nil)
148
+ _begin_body, *_rescue_bodies, _else_body = *node
149
+
150
+ # FIXME
151
+ # @source_rewriter.transaction do
152
+ # process(begin_body)
153
+ # process(else_body)
154
+ # rescue_bodies.each do |r|
155
+ # process(r)
156
+ # end
157
+ # end
158
+ # rescue TooComplexToTranslateError
159
+ # warning "begin-rescue is too complex to translate due to a retry"
160
+ # end
161
+ end
162
+
163
+ def on_resbody(_node)
164
+ # How it is parsed:
165
+ # (:resbody, exception-types-or-nil, exception-variable-or-nil, body)
166
+ # exception-types is an :array
167
+ # exception-variable is a (:lvasgn, name), without a value
168
+
169
+ # A rescue means that *some* previous code was skipped.
170
+ # We know nothing. We could process the resbodies individually,
171
+ # and join begin-block with else-block, but it is little worth
172
+ # because they will contain few zombies.
173
+ scope.clear
174
+ end
175
+
176
+ def on_ensure(_node)
177
+ # (:ensure, guarded-code, ensuring-code)
178
+ # guarded-code may be a :rescue or not
179
+
180
+ scope.clear
181
+ end
182
+
183
+ def on_retry(_node)
184
+ # that makes the :rescue a loop, top-down data-flow fails
185
+ # FIXME
186
+ # raise TooComplexToTranslateError
187
+ end
188
+
189
+ private
190
+
191
+ def oops(node, exception)
192
+ puts "Node exception @ #{node.loc.expression}"
193
+ puts "Offending node: #{node.inspect}"
194
+ raise exception unless exception.is_a?(TooComplexToTranslateError)
195
+ end
196
+
197
+ def call?(node, namespace, message)
198
+ n_receiver, n_message = *node
199
+ n_receiver && n_receiver.type == :const &&
200
+ n_receiver.children[0].nil? &&
201
+ n_receiver.children[1] == namespace &&
202
+ n_message == message
203
+ end
204
+
205
+ def autocorrect(node)
206
+ @corrections << lambda do |corrector|
207
+ _ops, message, arg1, arg2 = *node
208
+
209
+ new_ops = REPLACEMENT[message]
210
+ return unless new_ops
211
+
212
+ corrector.replace(node.loc.expression,
213
+ ops_replacement(new_ops, arg1, arg2))
214
+ end
215
+ end
216
+
217
+ def ops_replacement(new_ops, arg1, arg2)
218
+ "#{arg1.loc.expression.source} #{new_ops} " \
219
+ "#{arg2.loc.expression.source}"
220
+ end
221
+
222
+ attr_reader :scopes, :safe_mode
223
+ end
224
+ end
225
+ end
226
+ end
@@ -0,0 +1,19 @@
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 Config
9
+ DEFAULT = File.expand_path("../../../../config/default.yml", __FILE__)
10
+
11
+ def self.load_defaults
12
+ plugin_config = YAML.load_file(DEFAULT)
13
+ config = ConfigLoader.merge_with_default(plugin_config, DEFAULT)
14
+
15
+ ConfigLoader.instance_variable_set(:@default_configuration, config)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,60 @@
1
+ require "set"
2
+
3
+ # Niceness of a node means that it cannot be nil.
4
+ #
5
+ # Note that the module depends on the includer
6
+ # to provide #scope (for #nice_variable)
7
+ module Niceness
8
+ # Literals are nice, except the nil literal.
9
+ NICE_LITERAL_NODE_TYPES = [
10
+ :self,
11
+ :false, :true,
12
+ :int, :float,
13
+ :str, :sym, :regexp,
14
+ :array, :hash, :pair, :irange, # may contain nils but they are not nil
15
+ :dstr, # "String #{interpolation}" mixes :str, :begin
16
+ :dsym # :"#{foo}"
17
+ ].to_set
18
+
19
+ def nice(node)
20
+ nice_literal(node) || nice_variable(node) || nice_send(node) ||
21
+ nice_begin(node)
22
+ end
23
+
24
+ def nice_literal(node)
25
+ NICE_LITERAL_NODE_TYPES.include? node.type
26
+ end
27
+
28
+ def nice_variable(node)
29
+ node.type == :lvar && scope[node.children.first].nice
30
+ end
31
+
32
+ # Methods that preserve niceness if all their arguments are nice
33
+ # These are global, called with a nil receiver
34
+ NICE_GLOBAL_METHODS = {
35
+ # message, number of arguments
36
+ _: 1
37
+ }.freeze
38
+
39
+ NICE_OPERATORS = {
40
+ # message, number of arguments (other than receiver)
41
+ :+ => 1
42
+ }.freeze
43
+
44
+ def nice_send(node)
45
+ return false unless node.type == :send
46
+ receiver, message, *args = *node
47
+
48
+ if receiver.nil?
49
+ arity = NICE_GLOBAL_METHODS.fetch(message, -1)
50
+ else
51
+ return false unless nice(receiver)
52
+ arity = NICE_OPERATORS.fetch(message, -1)
53
+ end
54
+ args.size == arity && args.all? { |a| nice(a) }
55
+ end
56
+
57
+ def nice_begin(node)
58
+ node.type == :begin && nice(node.children.last)
59
+ end
60
+ end
@@ -0,0 +1,62 @@
1
+ # Tracks state for a variable
2
+ class VariableState
3
+ attr_accessor :nice
4
+ end
5
+
6
+ # Tracks state for local variables visible at certain point.
7
+ # Keys are symbols, values are VariableState
8
+ class VariableScope < Hash
9
+ def initialize
10
+ super do |hash, key|
11
+ hash[key] = VariableState.new
12
+ end
13
+ end
14
+
15
+ # Deep copy the VariableState values
16
+ def dup
17
+ copy = self.class.new
18
+ each do |k, v|
19
+ copy[k] = v.dup
20
+ end
21
+ copy
22
+ end
23
+
24
+ # @return [VariableState] state
25
+ def [](varname)
26
+ super
27
+ end
28
+
29
+ # Set state for a variable
30
+ def []=(varname, state)
31
+ super
32
+ end
33
+ end
34
+
35
+ # A stack of VariableScope
36
+ class VariableScopeStack
37
+ def initialize
38
+ outer_scope = VariableScope.new
39
+ @stack = [outer_scope]
40
+ end
41
+
42
+ # The innermost, or current VariableScope
43
+ def innermost
44
+ @stack.last
45
+ end
46
+
47
+ # Run *block* using a new clean scope
48
+ # @return the scope as the block left it, popped from the stack
49
+ def with_new(&block)
50
+ @stack.push VariableScope.new
51
+ block.call
52
+ @stack.pop
53
+ end
54
+
55
+ # Run *block* using a copy of the innermost scope
56
+ # @return the scope as the block left it, popped from the stack
57
+ def with_copy(&block)
58
+ @stack.push innermost.dup
59
+ block.call
60
+ @stack.pop
61
+ end
62
+ end
@@ -3,6 +3,6 @@
3
3
  module RuboCop
4
4
  # Yast plugin settings
5
5
  module Yast
6
- VERSION = "0.0.3"
6
+ VERSION = "0.0.4"
7
7
  end
8
8
  end
@@ -19,7 +19,7 @@ Gem::Specification.new do |spec|
19
19
 
20
20
  spec.require_paths = ["lib"]
21
21
  spec.files = Dir[
22
- "{lib,spec}/**/*",
22
+ "{config,lib,spec}/**/*",
23
23
  "*.md",
24
24
  "*.gemspec",
25
25
  "Gemfile",
@@ -28,7 +28,8 @@ Gem::Specification.new do |spec|
28
28
  spec.test_files = spec.files.grep(/^spec\//)
29
29
  spec.extra_rdoc_files = ["LICENSE", "README.md"]
30
30
 
31
- spec.add_development_dependency("rubocop", "~> 0.27")
31
+ spec.add_runtime_dependency("rubocop", "~> 0.27")
32
+
32
33
  spec.add_development_dependency("rake")
33
34
  spec.add_development_dependency("rspec", "~> 3.1.0")
34
35
  spec.add_development_dependency("simplecov")
@@ -6,7 +6,7 @@ def expect_y2milestone_offense(cop)
6
6
  expect(cop.offenses.size).to eq(1)
7
7
  expect(cop.offenses.first.line).to eq(1)
8
8
  expect(cop.messages).to eq(["Builtin call `y2milestone` is obsolete, " \
9
- "use native Ruby function instead."])
9
+ "use native Ruby function instead."])
10
10
  end
11
11
 
12
12
  describe RuboCop::Cop::Yast::Builtins do
@@ -48,4 +48,18 @@ describe RuboCop::Cop::Yast::Builtins do
48
48
  expect(cop.offenses).to be_empty
49
49
  end
50
50
 
51
+ it "auto-corrects Builtins.time with ::Time.now.to_i" do
52
+ new_source = autocorrect_source(cop, "Builtins.time")
53
+ expect(new_source).to eq("::Time.now.to_i")
54
+ end
55
+
56
+ it 'auto-corrects Builtins.getenv("foo") with ENV["foo"]' do
57
+ new_source = autocorrect_source(cop, 'Builtins.getenv("foo")')
58
+ expect(new_source).to eq('ENV["foo"]')
59
+ end
60
+
61
+ it "auto-corrects Builtins.getenv(foo) with ENV[foo]" do
62
+ new_source = autocorrect_source(cop, "Builtins.getenv(foo)")
63
+ expect(new_source).to eq("ENV[foo]")
64
+ end
51
65
  end
@@ -0,0 +1,116 @@
1
+ # encoding: utf-8
2
+
3
+ require "spec_helper"
4
+
5
+ def config(safe_mode: true)
6
+ conf = { "Yast/Ops" => { "SafeMode" => safe_mode } }
7
+ RuboCop::Config.new(conf)
8
+ end
9
+
10
+ describe RuboCop::Cop::Yast::Ops do
11
+ context("In safe mode") do
12
+ subject(:cop) { described_class.new(config(safe_mode: true)) }
13
+
14
+ it "finds trivial Ops.add call" do
15
+ inspect_source(cop, ["Ops.add(2, 4)"])
16
+
17
+ expect(cop.offenses.size).to eq(1)
18
+ end
19
+
20
+ it "finds Ops.add call with variable" do
21
+ inspect_source(cop, ["foo = 2\n Ops.add(foo, 4)"])
22
+
23
+ expect(cop.offenses.size).to eq(1)
24
+ end
25
+
26
+ it "finds Ops.add call with variable inside condition" do
27
+ inspect_source(cop, ["foo = 1\nif true\nOps.add(foo, 4)\nend"])
28
+
29
+ expect(cop.offenses.size).to eq(1)
30
+ end
31
+
32
+ it "ignores unsafe calls" do
33
+ inspect_source(cop, ["if true\nOps.add(foo, 4)\nend"])
34
+
35
+ expect(cop.offenses).to be_empty
36
+ end
37
+
38
+ # check that all node types are handled properly
39
+ it "parses complex code" do
40
+ src = <<-EOF
41
+ module Foo
42
+ class Bar
43
+ def baz(arg)
44
+ case arg
45
+ when :foo
46
+ a &&= true
47
+ b ||= true
48
+ end
49
+ rescue e
50
+ while false
51
+ find.foo do
52
+ end
53
+ retry
54
+ end
55
+ ensure
56
+ sure
57
+ end
58
+ class << foo
59
+ end
60
+ def self.foo
61
+ end
62
+ end
63
+ end
64
+ EOF
65
+
66
+ inspect_source(cop, src)
67
+
68
+ expect(cop.offenses).to be_empty
69
+ end
70
+
71
+ it "auto-corrects Ops.add(2, 4) with 2 + 4" do
72
+ new_source = autocorrect_source(cop, "Ops.add(2, 4)")
73
+ expect(new_source).to eq("2 + 4")
74
+ end
75
+
76
+ it "auto-corrects Ops.add(a, b) with a + b" do
77
+ new_source = autocorrect_source(cop, "a = 1; b = 2; Ops.add(a, b)")
78
+ expect(new_source).to eq("a = 1; b = 2; a + b")
79
+ end
80
+
81
+ it 'auto-corrects Ops.add("foo", "bar") with "foo" + "bar"' do
82
+ new_source = autocorrect_source(cop, 'Ops.add("foo", "bar")')
83
+ expect(new_source).to eq('"foo" + "bar"')
84
+ end
85
+
86
+ # FIXME: auto-correct does not work work recursively
87
+ xit "auto-corrects nested Ops.add calls" do
88
+ new_source = autocorrect_source(cop,
89
+ 'Ops.add("foo", Ops.add("bar", "baz"))')
90
+ expect(new_source).to eq('"foo" + "bar + baz"')
91
+ end
92
+
93
+ it "keeps unsafe call Ops.add(foo, bar)" do
94
+ source = "foo = 1; Ops.add(foo, bar)"
95
+ new_source = autocorrect_source(cop, source)
96
+ expect(new_source).to eq(source)
97
+ end
98
+
99
+ end
100
+
101
+ context("In unsafe mode") do
102
+ subject(:cop) { described_class.new(config(safe_mode: false)) }
103
+
104
+ it "finds unsafe Ops.add calls" do
105
+ inspect_source(cop, ["if true\nOps.add(foo, 4)\nend"])
106
+
107
+ expect(cop.offenses.size).to eq(1)
108
+ end
109
+
110
+ it "auto-corrects unsafe call Ops.add(foo, bar) with foo + bar" do
111
+ new_source = autocorrect_source(cop, "Ops.add(foo, bar)")
112
+ expect(new_source).to eq("foo + bar")
113
+ end
114
+ end
115
+
116
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-yast
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ladislav Slezák
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-12-02 00:00:00.000000000 Z
11
+ date: 2014-12-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubocop
@@ -17,7 +17,7 @@ dependencies:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '0.27'
20
- type: :development
20
+ type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
@@ -81,11 +81,17 @@ files:
81
81
  - LICENSE
82
82
  - README.md
83
83
  - Rakefile
84
+ - config/default.yml
84
85
  - lib/rubocop-yast.rb
85
86
  - lib/rubocop/cop/yast/builtins.rb
87
+ - lib/rubocop/cop/yast/ops.rb
88
+ - lib/rubocop/yast/config.rb
89
+ - lib/rubocop/yast/niceness.rb
90
+ - lib/rubocop/yast/variable_scope.rb
86
91
  - lib/rubocop/yast/version.rb
87
92
  - rubocop-yast.gemspec
88
93
  - spec/builtins_spec.rb
94
+ - spec/ops_spec.rb
89
95
  - spec/spec_helper.rb
90
96
  homepage: http://github.com/yast/rubocop-yast
91
97
  licenses:
@@ -113,4 +119,5 @@ specification_version: 4
113
119
  summary: Specific YaST Rubocop checks
114
120
  test_files:
115
121
  - spec/builtins_spec.rb
122
+ - spec/ops_spec.rb
116
123
  - spec/spec_helper.rb