rubocop-yast 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
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