rubocop-socketry 0.5.0 → 0.6.1

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
  SHA256:
3
- metadata.gz: 535ee9879245dcd6059d6ad744862b405ef49eec9203d70c8b07e03ca77b3f47
4
- data.tar.gz: ccd2df428d0cb2652beb7452f0e80952e3dbe0845adb41bcebea5d8d83c0bff6
3
+ metadata.gz: 199a4f2c374f6e452459742724b7ba8ba9b0e9d9859ddf4ae12172a95e033bd9
4
+ data.tar.gz: ddc2c5a20e4b7181b1cdc0d07c53f85dc7acabb9ea4681616e3000292b45496f
5
5
  SHA512:
6
- metadata.gz: 40daa847ae78f73f5c9d2a2521d457a8de458985dded66fb6d06e027c9545ce5dd65eeb3edd38d202fa4c87941ecdc9eee9cacb12d5a231a222acee64ddad889
7
- data.tar.gz: da03f159be23f86eaea8b43fdfba3f80eca079b8e9c410c8a1f9e66d7e3e09f10e784944f8904fd40cd9d28dd5cd9124d79402633079bc637e3a55652b9c8000
6
+ metadata.gz: f04815a61b6651a7c4325967a9d193ade2ecb85800cb3ef816afed0a0f51d5849ab8a3aeb758474b93a8927b7fd984826f983749a9ff4824a0cf5beba1d72d95
7
+ data.tar.gz: 6b255a3e77c44782ea801bc3de112411896f9395ab0b78191998b8a064528a29cbca77bc50be56223f68c1b2df2e1155332f3985fd6e62bc935f5850a7b91553
checksums.yaml.gz.sig CHANGED
Binary file
@@ -14,26 +14,39 @@ module RuboCop
14
14
  # - `foo {bar}` - space when method has no parentheses and is not chained
15
15
  # - `foo(1, 2) {bar}` - space after closing paren for standalone methods
16
16
  # - `array.each{|x| x*2}.reverse` - no space for method chains (even with parens)
17
+ # - `->(foo){foo}` - no space for lambdas (stabby lambda syntax)
18
+ # - `lambda{foo}` - no space for lambda keyword
19
+ # - `proc{foo}` - no space for proc keyword
20
+ # - `Proc.new{foo}` - no space for Proc.new
17
21
  class BlockDelimiterSpacing < RuboCop::Cop::Base
18
22
  extend Cop::AutoCorrector
19
23
 
20
24
  MSG_ADD_SPACE = "Add a space before the opening brace."
21
25
  MSG_REMOVE_SPACE = "Remove space before the opening brace for method chains."
26
+ MSG_REMOVE_SPACE_LAMBDA = "Remove space before the opening brace for lambdas/procs."
22
27
 
23
28
  def on_block(node)
24
29
  return unless node.braces?
25
30
 
26
31
  send_node = node.send_node
27
32
 
28
- # Priority: Check if it's part of a method chain first
33
+ # Priority 1: Check if it's a lambda or proc
34
+ # Lambdas/procs should never have space before {
35
+ if lambda_or_proc?(send_node)
36
+ # ->(foo){foo} - no space (stabby lambda)
37
+ # lambda{foo} - no space (lambda keyword)
38
+ # proc{foo} - no space (proc keyword)
39
+ # Proc.new{foo} - no space (Proc.new)
40
+ check_no_space_for_lambda(node, send_node)
41
+ # Priority 2: Check if it's part of a method chain
29
42
  # Method chains should never have space, even with parentheses
30
- if part_of_method_chain?(node)
43
+ elsif part_of_method_chain?(node)
31
44
  # array.each{|x| x*2}.reverse - no space
32
45
  # obj.method(1, 2){|x| x}.other - also no space
33
46
  check_no_space_before_brace(node, send_node)
34
47
  elsif has_parentheses?(send_node)
35
48
  # foo(1, 2) {bar} - space after ) for standalone methods
36
- check_space_after_paren(node, send_node)
49
+ check_space_after_parentheses(node, send_node)
37
50
  else
38
51
  # foo {bar} - space for standalone methods without parens
39
52
  check_space_before_brace(node, send_node)
@@ -42,6 +55,56 @@ module RuboCop
42
55
 
43
56
  private
44
57
 
58
+ # Check if the send node is a lambda or proc (any form)
59
+ def lambda_or_proc?(send_node)
60
+ return true if send_node.lambda? # stabby lambda: ->{}
61
+ return true if send_node.method_name == :lambda # lambda keyword: lambda{}
62
+ return true if send_node.method_name == :proc # proc keyword: proc{}
63
+
64
+ # Check for Proc.new{}
65
+ if send_node.method_name == :new && send_node.receiver&.const_type?
66
+ # Check if the receiver is the Proc constant
67
+ receiver = send_node.receiver
68
+ return true if receiver.const_name == :Proc && receiver.children.first.nil?
69
+ end
70
+
71
+ false
72
+ end
73
+
74
+ # Check that there's no space before the opening brace for lambdas
75
+ def check_no_space_for_lambda(block_node, send_node)
76
+ brace_begin = block_node.loc.begin
77
+
78
+ # Find the position just before the brace
79
+ char_before_pos = brace_begin.begin_pos - 1
80
+
81
+ return if char_before_pos < 0
82
+
83
+ char_before = processed_source.buffer.source[char_before_pos]
84
+
85
+ # If there's no space before the brace, we're good
86
+ return unless char_before == " "
87
+
88
+ # Find the extent of whitespace before the brace
89
+ start_pos = char_before_pos
90
+ while start_pos > 0 && processed_source.buffer.source[start_pos - 1] =~ /\s/
91
+ start_pos -= 1
92
+ end
93
+
94
+ space_range = Parser::Source::Range.new(
95
+ processed_source.buffer,
96
+ start_pos,
97
+ brace_begin.begin_pos
98
+ )
99
+
100
+ add_offense(
101
+ space_range,
102
+ message: MSG_REMOVE_SPACE_LAMBDA
103
+ ) do |corrector|
104
+ corrector.remove(space_range)
105
+ end
106
+ end
107
+
45
108
  # Check if the block is part of a method chain (e.g., foo{}.bar or foo.bar{}.baz)
46
109
  def part_of_method_chain?(block_node)
47
110
  send_node = block_node.send_node
@@ -63,7 +126,7 @@ module RuboCop
63
126
  end
64
127
 
65
128
  # Check that there's a space between closing paren and opening brace
66
- def check_space_after_paren(block_node, send_node)
129
+ def check_space_after_parentheses(block_node, send_node)
67
130
  paren_end = send_node.loc.end
68
131
  brace_begin = block_node.loc.begin
69
132
 
@@ -8,7 +8,7 @@ require "rubocop"
8
8
  module RuboCop
9
9
  module Socketry
10
10
  module Style
11
- # A RuboCop cop that warns against using global exception variables.
11
+ # A RuboCop cop that warns against using global exception variables in unsafe contexts.
12
12
  #
13
13
  # This cop discourages the use of:
14
14
  # - `$!` (last exception)
@@ -17,27 +17,52 @@ module RuboCop
17
17
  # - `$ERROR_POSITION` (English name for `$@`)
18
18
  #
19
19
  # These global variables are implicit and can make code harder to understand.
20
- # Instead, use explicit exception handling with rescue blocks and local variables.
20
+ #
21
+ # However, this cop allows their use in safe contexts where the scope is well-defined:
22
+ # - Inside rescue blocks (well-defined scope)
23
+ # - In rescue modifiers (`expression rescue $!`)
24
+ # - In method parameter defaults (`def foo(error = $!)`, `def bar(error: $!)`)
25
+ #
26
+ # This cop specifically flags their use in unsafe contexts:
27
+ # - Inside ensure blocks (extremely unsafe - exception state is unpredictable)
28
+ # - Outside of exception handling contexts
21
29
  #
22
30
  # @example
23
- # # bad
31
+ # # bad - unsafe in ensure block
24
32
  # begin
25
33
  # risky_operation
26
- # rescue
34
+ # ensure
35
+ # log($!.message) if $! # unsafe!
36
+ # end
37
+ #
38
+ # # bad - outside exception handling
39
+ # def process
27
40
  # puts $!.message
28
- # puts $@.first
29
41
  # end
30
42
  #
31
- # # good
43
+ # # good - explicit exception handling
32
44
  # begin
33
45
  # risky_operation
34
46
  # rescue => error
35
47
  # puts error.message
36
- # puts error.backtrace.first
37
48
  # end
49
+ #
50
+ # # allowed - inside rescue block (well-defined scope)
51
+ # begin
52
+ # risky_operation
53
+ # rescue
54
+ # puts $!.message
55
+ # end
56
+ #
57
+ # # allowed - rescue modifier
58
+ # result = risky_operation rescue $!
59
+ #
60
+ # # allowed - parameter defaults
61
+ # def foo(error = $!)
62
+ # def bar(error: $!)
38
63
  class GlobalExceptionVariables < RuboCop::Cop::Base
39
- MSG = "Avoid using global exception variable `%<variable>s`. " \
40
- "Use explicit exception handling with `rescue => error` instead."
64
+ MSG = "Avoid using global exception variable `%<variable>s` in %<context>s. Use explicit exception handling with `rescue => error` instead."
65
+ ENSURE_MSG = "Using global exception variable `%<variable>s` in an ensure block is extremely unsafe."
41
66
 
42
67
  EXCEPTION_VARIABLES = %i[$! $@ $ERROR_INFO $ERROR_POSITION].freeze
43
68
 
@@ -46,11 +71,53 @@ module RuboCop
46
71
 
47
72
  return unless EXCEPTION_VARIABLES.include?(variable_name)
48
73
 
74
+ # Allow in parameter defaults (explicitly opting in)
75
+ return if in_parameter_default?(node)
76
+
77
+ # Allow in rescue modifier (well-defined scope)
78
+ return if in_rescue_modifier?(node)
79
+
80
+ # Allow in rescue block (well-defined scope)
81
+ return if in_rescue_block?(node)
82
+
83
+ # Flag if in ensure block (extremely unsafe)
84
+ if in_ensure_block?(node)
85
+ add_offense(
86
+ node,
87
+ message: format(ENSURE_MSG, variable: variable_name)
88
+ )
89
+ return
90
+ end
91
+
92
+ # Flag in all other contexts
49
93
  add_offense(
50
94
  node,
51
- message: format(MSG, variable: variable_name)
95
+ message: format(MSG, variable: variable_name, context: "this context")
52
96
  )
53
97
  end
98
+
99
+ private
100
+
101
+ def in_parameter_default?(node)
102
+ node.each_ancestor(:args, :optarg, :kwoptarg).any?
103
+ end
104
+
105
+ def in_rescue_modifier?(node)
106
+ node.each_ancestor(:rescue).any? do |ancestor|
107
+ # A rescue modifier has no resbody children
108
+ # e.g., `expression rescue $!` is a rescue node with 2 children: expression and handler
109
+ # A regular rescue has resbody children
110
+ !ancestor.children.any?{|child| child.is_a?(RuboCop::AST::Node) && child.type == :resbody}
111
+ end
112
+ end
113
+
114
+ def in_rescue_block?(node)
115
+ node.each_ancestor(:resbody).any?
116
+ end
117
+
118
+ def in_ensure_block?(node)
119
+ node.each_ancestor(:ensure).any?
120
+ end
54
121
  end
55
122
  end
56
123
  end
@@ -5,6 +5,6 @@
5
5
 
6
6
  module RuboCop
7
7
  module Socketry
8
- VERSION = "0.5.0"
8
+ VERSION = "0.6.1"
9
9
  end
10
10
  end
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-socketry
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
metadata.gz.sig CHANGED
Binary file