ivar 0.2.0 → 0.4.0
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 +4 -4
- data/.augment-guidelines +5 -3
- data/.devcontainer/devcontainer.json +28 -20
- data/.devcontainer/post-create.sh +18 -0
- data/.editorconfig +35 -0
- data/.rubocop.yml +6 -0
- data/.standard.yml +1 -1
- data/.vscode/extensions.json +3 -1
- data/.vscode/launch.json +25 -0
- data/.vscode/settings.json +38 -2
- data/CHANGELOG.md +83 -1
- data/README.md +272 -207
- data/Rakefile +1 -1
- data/VERSION.md +44 -0
- data/examples/check_all_block_example.rb +84 -0
- data/examples/check_all_example.rb +42 -0
- data/examples/inheritance_with_kwarg_init.rb +156 -0
- data/examples/inheritance_with_positional_init.rb +142 -0
- data/examples/mixed_positional_and_kwarg_init.rb +125 -0
- data/examples/require_check_all_example.rb +23 -0
- data/examples/sandwich_inheritance.rb +1 -1
- data/examples/sandwich_with_accessors.rb +78 -0
- data/examples/sandwich_with_block_values.rb +54 -0
- data/examples/sandwich_with_checked.rb +0 -1
- data/examples/sandwich_with_checked_once.rb +0 -1
- data/examples/sandwich_with_initial_values.rb +52 -0
- data/examples/sandwich_with_ivar_block.rb +6 -9
- data/examples/sandwich_with_ivar_macro.rb +4 -4
- data/examples/sandwich_with_kwarg_init.rb +78 -0
- data/examples/sandwich_with_positional_init.rb +50 -0
- data/examples/sandwich_with_shared_values.rb +54 -0
- data/hooks/README.md +42 -0
- data/hooks/install.sh +12 -0
- data/hooks/pre-commit +54 -0
- data/ivar.gemspec +5 -4
- data/lib/ivar/check_all.rb +7 -0
- data/lib/ivar/check_all_manager.rb +72 -0
- data/lib/ivar/check_policy.rb +29 -0
- data/lib/ivar/checked/class_methods.rb +19 -0
- data/lib/ivar/checked/instance_methods.rb +35 -0
- data/lib/ivar/checked.rb +17 -24
- data/lib/ivar/declaration.rb +30 -0
- data/lib/ivar/explicit_declaration.rb +56 -0
- data/lib/ivar/explicit_keyword_declaration.rb +24 -0
- data/lib/ivar/explicit_positional_declaration.rb +19 -0
- data/lib/ivar/macros.rb +48 -111
- data/lib/ivar/manifest.rb +124 -0
- data/lib/ivar/policies.rb +13 -1
- data/lib/ivar/project_root.rb +59 -0
- data/lib/ivar/targeted_prism_analysis.rb +144 -0
- data/lib/ivar/validation.rb +6 -29
- data/lib/ivar/version.rb +1 -1
- data/lib/ivar.rb +141 -9
- data/script/console +11 -0
- data/script/de-lint +2 -0
- data/script/de-lint-unsafe +2 -0
- data/script/lint +2 -0
- data/script/release +134 -0
- data/script/setup +8 -0
- data/script/test +2 -0
- metadata +41 -5
- data/examples/sandwich_with_kwarg.rb +0 -45
- data/lib/ivar/auto_check.rb +0 -77
- data/lib/ivar/prism_analysis.rb +0 -102
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ivar"
|
4
|
+
|
5
|
+
class SandwichWithBlockValues
|
6
|
+
include Ivar::Checked
|
7
|
+
|
8
|
+
# Declare condiments with a block that generates default values based on the variable name
|
9
|
+
ivar(:@mayo, :@mustard, :@ketchup) { |varname| !varname.include?("mayo") }
|
10
|
+
|
11
|
+
# Declare bread and cheese with individual values
|
12
|
+
ivar "@bread": "wheat", "@cheese": "cheddar"
|
13
|
+
|
14
|
+
# Declare a variable without an initial value
|
15
|
+
ivar :@side
|
16
|
+
|
17
|
+
def initialize(options = {})
|
18
|
+
# Override any condiments based on options
|
19
|
+
@mayo = true if options[:add_mayo]
|
20
|
+
@mustard = false if options[:no_mustard]
|
21
|
+
@ketchup = false if options[:no_ketchup]
|
22
|
+
|
23
|
+
# Set the side if provided
|
24
|
+
@side = options[:side] if options[:side]
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_s
|
28
|
+
result = "A #{@bread} sandwich with #{@cheese}"
|
29
|
+
|
30
|
+
condiments = []
|
31
|
+
condiments << "mayo" if @mayo
|
32
|
+
condiments << "mustard" if @mustard
|
33
|
+
condiments << "ketchup" if @ketchup
|
34
|
+
|
35
|
+
result += " with #{condiments.join(", ")}" unless condiments.empty?
|
36
|
+
result += " and a side of #{@side}" if defined?(@side) && @side
|
37
|
+
result
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Create a sandwich with default values (no mayo, but has mustard and ketchup)
|
42
|
+
sandwich = SandwichWithBlockValues.new
|
43
|
+
puts sandwich
|
44
|
+
# => "A wheat sandwich with cheddar with mustard, ketchup"
|
45
|
+
|
46
|
+
# Create a sandwich with mayo added
|
47
|
+
sandwich_with_mayo = SandwichWithBlockValues.new(add_mayo: true)
|
48
|
+
puts sandwich_with_mayo
|
49
|
+
# => "A wheat sandwich with cheddar with mayo, mustard, ketchup"
|
50
|
+
|
51
|
+
# Create a sandwich with a side
|
52
|
+
sandwich_with_side = SandwichWithBlockValues.new(side: "chips")
|
53
|
+
puts sandwich_with_side
|
54
|
+
# => "A wheat sandwich with cheddar with mustard, ketchup and a side of chips"
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ivar"
|
4
|
+
|
5
|
+
class SandwichWithInitialValues
|
6
|
+
include Ivar::Checked
|
7
|
+
|
8
|
+
# Declare instance variables with initial values
|
9
|
+
ivar "@bread": "wheat",
|
10
|
+
"@cheese": "muenster",
|
11
|
+
"@condiments": ["mayo", "mustard"],
|
12
|
+
"@pickles": true
|
13
|
+
|
14
|
+
# Declare a variable without an initial value
|
15
|
+
ivar :@side
|
16
|
+
|
17
|
+
def initialize(extra_condiments = [])
|
18
|
+
# The declared variables are already initialized with their values
|
19
|
+
# We can modify them here
|
20
|
+
@condiments += extra_condiments unless extra_condiments.empty?
|
21
|
+
|
22
|
+
# We can also check if pickles were requested and adjust condiments
|
23
|
+
@condiments.delete("mayo") if @pickles
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
result = "A #{@bread} sandwich with #{@cheese}"
|
28
|
+
result += " and #{@condiments.join(", ")}" unless @condiments.empty?
|
29
|
+
result += " with pickles" if @pickles
|
30
|
+
result += " and a side of #{@side}" if defined?(@side) && @side
|
31
|
+
result
|
32
|
+
end
|
33
|
+
|
34
|
+
def add_side(side)
|
35
|
+
@side = side
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Create a sandwich with default values
|
40
|
+
sandwich = SandwichWithInitialValues.new
|
41
|
+
puts sandwich
|
42
|
+
# => "A wheat sandwich with muenster and mustard with pickles"
|
43
|
+
|
44
|
+
# Create a sandwich with extra condiments
|
45
|
+
sandwich_with_extras = SandwichWithInitialValues.new(["ketchup", "relish"])
|
46
|
+
puts sandwich_with_extras
|
47
|
+
# => "A wheat sandwich with muenster and mustard, ketchup, relish with pickles"
|
48
|
+
|
49
|
+
# Add a side
|
50
|
+
sandwich.add_side("chips")
|
51
|
+
puts sandwich
|
52
|
+
# => "A wheat sandwich with muenster and mustard with pickles and a side of chips"
|
@@ -5,27 +5,24 @@ require "ivar"
|
|
5
5
|
class SandwichWithIvarBlock
|
6
6
|
include Ivar::Checked
|
7
7
|
|
8
|
-
#
|
9
|
-
ivar :@side
|
10
|
-
@pickles = true
|
11
|
-
@condiments = []
|
12
|
-
end
|
8
|
+
# Declare instance variables
|
9
|
+
ivar :@side
|
13
10
|
|
14
11
|
def initialize
|
15
12
|
@bread = "wheat"
|
16
13
|
@cheese = "muenster"
|
17
|
-
|
18
|
-
|
14
|
+
@pickles = true
|
15
|
+
@condiments = []
|
19
16
|
@condiments << "mayo" if !@pickles
|
20
17
|
@condiments << "mustard"
|
21
|
-
#
|
18
|
+
# @side is declared but intentionally not initialized here
|
22
19
|
end
|
23
20
|
|
24
21
|
def to_s
|
25
22
|
result = "A #{@bread} sandwich with #{@cheese}"
|
26
23
|
result += " and #{@condiments.join(", ")}" unless @condiments.empty?
|
27
24
|
result += " with pickles" if @pickles
|
28
|
-
result += " and a side of #{@side}" if @side
|
25
|
+
result += " and a side of #{@side}" if defined?(@side) && @side
|
29
26
|
result
|
30
27
|
end
|
31
28
|
|
@@ -5,7 +5,7 @@ require "ivar"
|
|
5
5
|
class SandwichWithIvarMacro
|
6
6
|
include Ivar::Checked
|
7
7
|
|
8
|
-
#
|
8
|
+
# Declare instance variables that might be referenced before being set
|
9
9
|
# You don't need to include variables that are always set in initialize
|
10
10
|
ivar :@side
|
11
11
|
|
@@ -13,13 +13,13 @@ class SandwichWithIvarMacro
|
|
13
13
|
@bread = "wheat"
|
14
14
|
@cheese = "muenster"
|
15
15
|
@condiments = %w[mayo mustard]
|
16
|
-
#
|
16
|
+
# @side is declared but intentionally not initialized here
|
17
17
|
end
|
18
18
|
|
19
19
|
def to_s
|
20
20
|
result = "A #{@bread} sandwich with #{@cheese} and #{@condiments.join(", ")}"
|
21
|
-
#
|
22
|
-
result += " and a side of #{@side}" if @side
|
21
|
+
# Using defined? to safely check for optional @side
|
22
|
+
result += " and a side of #{@side}" if defined?(@side) && @side
|
23
23
|
result
|
24
24
|
end
|
25
25
|
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ivar"
|
4
|
+
|
5
|
+
class SandwichWithKwargInit
|
6
|
+
include Ivar::Checked
|
7
|
+
|
8
|
+
# Declare instance variables with keyword argument initialization
|
9
|
+
# and default values in case they're not provided
|
10
|
+
ivar :@bread, init: :kwarg, value: "wheat"
|
11
|
+
ivar :@cheese, init: :kwarg, value: "cheddar"
|
12
|
+
|
13
|
+
# Declare condiments with a default value
|
14
|
+
ivar :@condiments, value: []
|
15
|
+
|
16
|
+
# Declare pickles with both a default value and kwarg initialization
|
17
|
+
ivar :@pickles, value: false, init: :kwarg
|
18
|
+
|
19
|
+
def initialize(extra_condiments: [])
|
20
|
+
# The declared variables are already initialized with their values
|
21
|
+
# from keyword arguments or defaults
|
22
|
+
# Note: bread, cheese, and pickles keywords are "peeled off" and won't be passed to this method
|
23
|
+
# But extra_condiments will be passed through
|
24
|
+
|
25
|
+
# Add default condiments (clear first to avoid duplicates)
|
26
|
+
@condiments = []
|
27
|
+
@condiments << "mayo" unless @pickles
|
28
|
+
@condiments << "mustard"
|
29
|
+
|
30
|
+
# Add any extra condiments
|
31
|
+
@condiments.concat(extra_condiments)
|
32
|
+
|
33
|
+
# For demonstration, we'll print what keywords were actually received
|
34
|
+
received_vars = []
|
35
|
+
local_variables.each do |v|
|
36
|
+
next if v == :_ || binding.local_variable_get(v).nil?
|
37
|
+
received_vars << "#{v}: #{binding.local_variable_get(v).inspect}"
|
38
|
+
end
|
39
|
+
puts " Initialize received: #{received_vars.join(", ")}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_s
|
43
|
+
result = "A #{@bread} sandwich with #{@cheese}"
|
44
|
+
result += " and #{@condiments.join(", ")}" unless @condiments.empty?
|
45
|
+
result += " with pickles" if @pickles
|
46
|
+
result
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Create a sandwich with default values
|
51
|
+
puts "Default sandwich:"
|
52
|
+
sandwich = SandwichWithKwargInit.new
|
53
|
+
puts sandwich
|
54
|
+
# => "A wheat sandwich with cheddar and mayo, mustard"
|
55
|
+
|
56
|
+
# Create a sandwich with custom bread and cheese
|
57
|
+
puts "\nCustom bread and cheese:"
|
58
|
+
custom_sandwich = SandwichWithKwargInit.new(bread: "rye", cheese: "swiss")
|
59
|
+
puts custom_sandwich
|
60
|
+
# => "A rye sandwich with swiss and mayo, mustard"
|
61
|
+
|
62
|
+
# Create a sandwich with pickles
|
63
|
+
puts "\nSandwich with pickles:"
|
64
|
+
pickle_sandwich = SandwichWithKwargInit.new(pickles: true)
|
65
|
+
puts pickle_sandwich
|
66
|
+
# => "A wheat sandwich with cheddar and mustard with pickles"
|
67
|
+
|
68
|
+
# Create a sandwich with extra condiments (not peeled off)
|
69
|
+
puts "\nSandwich with extra condiments:"
|
70
|
+
extra_sandwich = SandwichWithKwargInit.new(extra_condiments: ["ketchup", "relish"])
|
71
|
+
puts extra_sandwich
|
72
|
+
# => "A wheat sandwich with cheddar and mayo, mustard, ketchup, relish"
|
73
|
+
|
74
|
+
# Create a sandwich with both peeled off and passed through kwargs
|
75
|
+
puts "\nSandwich with both types of kwargs:"
|
76
|
+
combo_sandwich = SandwichWithKwargInit.new(bread: "sourdough", pickles: true, extra_condiments: ["hot sauce"])
|
77
|
+
puts combo_sandwich
|
78
|
+
# => "A sourdough sandwich with cheddar and mustard, hot sauce with pickles"
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ivar"
|
4
|
+
|
5
|
+
class SandwichWithPositionalInit
|
6
|
+
include Ivar::Checked
|
7
|
+
|
8
|
+
# Declare instance variables with positional argument initialization
|
9
|
+
# and default values in case they're not provided
|
10
|
+
ivar :@bread, init: :positional, value: "wheat"
|
11
|
+
ivar :@cheese, init: :positional, value: "cheddar"
|
12
|
+
|
13
|
+
# Declare condiments with a default value
|
14
|
+
ivar :@condiments, value: []
|
15
|
+
|
16
|
+
# Declare pickles with both a default value and positional initialization
|
17
|
+
ivar :@pickles, value: false, init: :positional
|
18
|
+
|
19
|
+
# Note: Don't define parameters for the peeled-off positional arguments
|
20
|
+
def initialize(extra_condiments = [])
|
21
|
+
# The declared variables are already initialized with their values
|
22
|
+
# from positional arguments or defaults
|
23
|
+
@condiments += extra_condiments unless extra_condiments.empty?
|
24
|
+
|
25
|
+
# We can also check if pickles were requested and adjust condiments
|
26
|
+
@condiments.delete("mayo") if @pickles
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_s
|
30
|
+
result = "#{@bread} sandwich with #{@cheese} cheese"
|
31
|
+
result += " and pickles" if @pickles
|
32
|
+
result += ", condiments: #{@condiments.join(", ")}" unless @condiments.empty?
|
33
|
+
result
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Create a sandwich with all positional arguments
|
38
|
+
sandwich1 = SandwichWithPositionalInit.new("rye", "swiss", true, ["mustard"])
|
39
|
+
puts "Sandwich 1: #{sandwich1}"
|
40
|
+
# => "Sandwich 1: rye sandwich with swiss cheese and pickles, condiments: mustard"
|
41
|
+
|
42
|
+
# Create a sandwich with some positional arguments
|
43
|
+
sandwich2 = SandwichWithPositionalInit.new("sourdough", "provolone", ["mayo", "mustard"])
|
44
|
+
puts "Sandwich 2: #{sandwich2}"
|
45
|
+
# => "Sandwich 2: sourdough sandwich with provolone cheese, condiments: mayo, mustard"
|
46
|
+
|
47
|
+
# Create a sandwich with default values
|
48
|
+
sandwich3 = SandwichWithPositionalInit.new
|
49
|
+
puts "Sandwich 3: #{sandwich3}"
|
50
|
+
# => "Sandwich 3: wheat sandwich with cheddar cheese"
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ivar"
|
4
|
+
|
5
|
+
class SandwichWithSharedValues
|
6
|
+
include Ivar::Checked
|
7
|
+
|
8
|
+
# Declare multiple condiments with the same initial value (true)
|
9
|
+
ivar :@mayo, :@mustard, :@ketchup, value: true
|
10
|
+
|
11
|
+
# Declare bread and cheese with individual values
|
12
|
+
ivar "@bread": "wheat", "@cheese": "cheddar"
|
13
|
+
|
14
|
+
# Declare a variable without an initial value
|
15
|
+
ivar :@side
|
16
|
+
|
17
|
+
def initialize(options = {})
|
18
|
+
# Override any condiments based on options
|
19
|
+
@mayo = false if options[:no_mayo]
|
20
|
+
@mustard = false if options[:no_mustard]
|
21
|
+
@ketchup = false if options[:no_ketchup]
|
22
|
+
|
23
|
+
# Set the side if provided
|
24
|
+
@side = options[:side] if options[:side]
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_s
|
28
|
+
result = "A #{@bread} sandwich with #{@cheese}"
|
29
|
+
|
30
|
+
condiments = []
|
31
|
+
condiments << "mayo" if @mayo
|
32
|
+
condiments << "mustard" if @mustard
|
33
|
+
condiments << "ketchup" if @ketchup
|
34
|
+
|
35
|
+
result += " with #{condiments.join(", ")}" unless condiments.empty?
|
36
|
+
result += " and a side of #{@side}" if defined?(@side) && @side
|
37
|
+
result
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Create a sandwich with default values (all condiments)
|
42
|
+
sandwich = SandwichWithSharedValues.new
|
43
|
+
puts sandwich
|
44
|
+
# => "A wheat sandwich with cheddar with mayo, mustard, ketchup"
|
45
|
+
|
46
|
+
# Create a sandwich with no mayo
|
47
|
+
sandwich_no_mayo = SandwichWithSharedValues.new(no_mayo: true)
|
48
|
+
puts sandwich_no_mayo
|
49
|
+
# => "A wheat sandwich with cheddar with mustard, ketchup"
|
50
|
+
|
51
|
+
# Create a sandwich with a side
|
52
|
+
sandwich_with_side = SandwichWithSharedValues.new(side: "chips")
|
53
|
+
puts sandwich_with_side
|
54
|
+
# => "A wheat sandwich with cheddar with mayo, mustard, ketchup and a side of chips"
|
data/hooks/README.md
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# Git Hooks
|
2
|
+
|
3
|
+
This directory contains Git hooks for the ivar project.
|
4
|
+
|
5
|
+
## Available Hooks
|
6
|
+
|
7
|
+
- **pre-commit**: Automatically checks and fixes linting issues before committing.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
To install the hooks, run:
|
12
|
+
|
13
|
+
```bash
|
14
|
+
./hooks/install.sh
|
15
|
+
```
|
16
|
+
|
17
|
+
This will copy the hooks to your local `.git/hooks` directory and make them executable.
|
18
|
+
|
19
|
+
## Automatic Installation
|
20
|
+
|
21
|
+
The hooks are automatically installed when you open the project in a devcontainer.
|
22
|
+
|
23
|
+
## Manual Installation
|
24
|
+
|
25
|
+
If you prefer to install the hooks manually, you can copy them to your `.git/hooks` directory:
|
26
|
+
|
27
|
+
```bash
|
28
|
+
cp hooks/pre-commit .git/hooks/pre-commit
|
29
|
+
chmod +x .git/hooks/pre-commit
|
30
|
+
```
|
31
|
+
|
32
|
+
## How the Pre-commit Hook Works
|
33
|
+
|
34
|
+
The pre-commit hook:
|
35
|
+
|
36
|
+
1. Identifies staged Ruby files
|
37
|
+
2. Checks them for linting issues using standardrb
|
38
|
+
3. If issues are found, attempts to automatically fix them
|
39
|
+
4. Adds the fixed files back to the staging area
|
40
|
+
5. Performs a final check to ensure all issues are fixed
|
41
|
+
|
42
|
+
If any issues cannot be automatically fixed, the commit will be aborted with an error message.
|
data/hooks/install.sh
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
|
3
|
+
# Create hooks directory if it doesn't exist
|
4
|
+
mkdir -p .git/hooks
|
5
|
+
|
6
|
+
# Copy pre-commit hook
|
7
|
+
cp hooks/pre-commit .git/hooks/pre-commit
|
8
|
+
|
9
|
+
# Make pre-commit hook executable
|
10
|
+
chmod +x .git/hooks/pre-commit
|
11
|
+
|
12
|
+
echo "Git hooks installed successfully!"
|
data/hooks/pre-commit
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
#!/bin/sh
|
2
|
+
|
3
|
+
# Get list of staged Ruby files
|
4
|
+
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep "\.rb$")
|
5
|
+
|
6
|
+
# Exit if no Ruby files are staged
|
7
|
+
if [ -z "$STAGED_FILES" ]; then
|
8
|
+
echo "No Ruby files staged for commit. Skipping linting."
|
9
|
+
exit 0
|
10
|
+
fi
|
11
|
+
|
12
|
+
# Check if there are any linting issues
|
13
|
+
echo "Checking for linting issues..."
|
14
|
+
./script/lint $STAGED_FILES
|
15
|
+
LINT_RESULT=$?
|
16
|
+
|
17
|
+
# If there are linting issues, try to fix them automatically
|
18
|
+
if [ $LINT_RESULT -ne 0 ]; then
|
19
|
+
echo "Linting issues found. Attempting to fix automatically..."
|
20
|
+
|
21
|
+
# Stash unstaged changes
|
22
|
+
git stash -q --keep-index
|
23
|
+
|
24
|
+
# Run de-lint to auto-fix
|
25
|
+
./script/de-lint $STAGED_FILES
|
26
|
+
FIX_RESULT=$?
|
27
|
+
|
28
|
+
# If auto-fix was successful, add the fixed files back to staging
|
29
|
+
if [ $FIX_RESULT -eq 0 ]; then
|
30
|
+
echo "Auto-fix successful. Adding fixed files to staging area..."
|
31
|
+
git add $STAGED_FILES
|
32
|
+
else
|
33
|
+
echo "Auto-fix failed. Please fix the issues manually."
|
34
|
+
# Restore unstaged changes
|
35
|
+
git stash pop -q
|
36
|
+
exit 1
|
37
|
+
fi
|
38
|
+
|
39
|
+
# Restore unstaged changes
|
40
|
+
git stash pop -q 2>/dev/null || true
|
41
|
+
fi
|
42
|
+
|
43
|
+
# Run a final check to make sure everything is fixed
|
44
|
+
echo "Running final linting check..."
|
45
|
+
./script/lint $STAGED_FILES
|
46
|
+
FINAL_RESULT=$?
|
47
|
+
|
48
|
+
if [ $FINAL_RESULT -ne 0 ]; then
|
49
|
+
echo "Linting issues still exist after auto-fix. Please fix them manually."
|
50
|
+
exit 1
|
51
|
+
fi
|
52
|
+
|
53
|
+
echo "Linting passed. Proceeding with commit."
|
54
|
+
exit 0
|
data/ivar.gemspec
CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.email = ["avdi@avdi.codes"]
|
10
10
|
|
11
11
|
spec.summary = "Automatically check instance variables for typos."
|
12
|
-
spec.description = <<~
|
12
|
+
spec.description = <<~DESCRIPTION
|
13
13
|
Ruby instance variables are so convenient - you don't even need to declare them!
|
14
14
|
But... they are also dangerous, because a mispelled variable name results in `nil`
|
15
15
|
instead of an error.
|
@@ -22,11 +22,11 @@ Gem::Specification.new do |spec|
|
|
22
22
|
dynamic, a little bit static. It doesn't encumber your instance variable reads and
|
23
23
|
writes with any extra checking. And with the `:warn_once` policy, it won't overwhelm
|
24
24
|
you with output.
|
25
|
-
|
25
|
+
DESCRIPTION
|
26
26
|
|
27
27
|
spec.homepage = "https://github.com/avdi/ivar"
|
28
28
|
spec.license = "MIT"
|
29
|
-
spec.required_ruby_version = ">= 3.
|
29
|
+
spec.required_ruby_version = ">= 3.3.0"
|
30
30
|
|
31
31
|
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
32
32
|
|
@@ -39,7 +39,8 @@ Gem::Specification.new do |spec|
|
|
39
39
|
spec.files = Dir.chdir(__dir__) do
|
40
40
|
`git ls-files -z`.split("\x0").reject do |f|
|
41
41
|
(File.expand_path(f) == __FILE__) ||
|
42
|
-
f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor Gemfile])
|
42
|
+
f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor Gemfile]) ||
|
43
|
+
f.end_with?(".gem")
|
43
44
|
end
|
44
45
|
end
|
45
46
|
spec.require_paths = ["lib"]
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pathname"
|
4
|
+
|
5
|
+
module Ivar
|
6
|
+
# Manages automatic inclusion of Ivar::Checked in classes and modules
|
7
|
+
class CheckAllManager
|
8
|
+
def initialize
|
9
|
+
@trace_point = nil
|
10
|
+
@mutex = Mutex.new
|
11
|
+
end
|
12
|
+
|
13
|
+
# Enables automatic inclusion of Ivar::Checked in all classes and modules
|
14
|
+
# defined within the project root.
|
15
|
+
#
|
16
|
+
# @param project_root [String] The project root directory path
|
17
|
+
# @param block [Proc] Optional block. If provided, auto-checking is only active
|
18
|
+
# for the duration of the block. Otherwise, it remains active indefinitely.
|
19
|
+
# @return [void]
|
20
|
+
def enable(project_root, &block)
|
21
|
+
disable if @trace_point
|
22
|
+
root_pathname = Pathname.new(project_root)
|
23
|
+
@mutex.synchronize do
|
24
|
+
# :end means "end of module or class definition" in TracePoint
|
25
|
+
@trace_point = TracePoint.new(:end) do |tp|
|
26
|
+
next unless tp.path
|
27
|
+
file_path = Pathname.new(File.expand_path(tp.path))
|
28
|
+
if file_path.to_s.start_with?(root_pathname.to_s)
|
29
|
+
klass = tp.self
|
30
|
+
next if klass.included_modules.include?(Ivar::Checked)
|
31
|
+
klass.include(Ivar::Checked)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
@trace_point.enable
|
36
|
+
end
|
37
|
+
|
38
|
+
if block
|
39
|
+
begin
|
40
|
+
yield
|
41
|
+
ensure
|
42
|
+
disable
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
49
|
+
# Disables automatic inclusion of Ivar::Checked in classes and modules.
|
50
|
+
# @return [void]
|
51
|
+
def disable
|
52
|
+
@mutex.synchronize do
|
53
|
+
if @trace_point
|
54
|
+
@trace_point.disable
|
55
|
+
@trace_point = nil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns whether check_all is currently enabled
|
61
|
+
# @return [Boolean] true if check_all is enabled, false otherwise
|
62
|
+
def enabled?
|
63
|
+
@mutex.synchronize { !@trace_point.nil? && @trace_point.enabled? }
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns the current trace point (mainly for testing)
|
67
|
+
# @return [TracePoint, nil] The current trace point or nil if not enabled
|
68
|
+
def trace_point
|
69
|
+
@mutex.synchronize { @trace_point }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ivar
|
4
|
+
# Module for adding instance variable check policy configuration to classes.
|
5
|
+
# This module provides a way to set and inherit check policies for instance variables.
|
6
|
+
# When extended in a class, it allows setting a class-specific policy that overrides
|
7
|
+
# the global Ivar policy.
|
8
|
+
module CheckPolicy
|
9
|
+
# Set or get the check policy for this class
|
10
|
+
# @param policy [Symbol, Policy] The check policy to set
|
11
|
+
# @param options [Hash] Additional options for the policy
|
12
|
+
# @return [Symbol, Policy] The current check policy
|
13
|
+
def ivar_check_policy(policy = nil, **options)
|
14
|
+
if policy.nil?
|
15
|
+
@__ivar_check_policy || Ivar.check_policy
|
16
|
+
else
|
17
|
+
@__ivar_check_policy = options.empty? ? policy : [policy, options]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Ensure subclasses inherit the check policy from their parent
|
22
|
+
# This method is called automatically when a class is inherited
|
23
|
+
# @param subclass [Class] The subclass that is inheriting from this class
|
24
|
+
def inherited(subclass)
|
25
|
+
super
|
26
|
+
subclass.instance_variable_set(:@__ivar_check_policy, @__ivar_check_policy)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "instance_methods"
|
4
|
+
|
5
|
+
module Ivar
|
6
|
+
module Checked
|
7
|
+
# Class methods added to the including class.
|
8
|
+
# These methods ensure proper inheritance of Checked functionality.
|
9
|
+
module ClassMethods
|
10
|
+
# Ensure subclasses inherit the Checked functionality
|
11
|
+
# This method is called automatically when a class is inherited
|
12
|
+
# @param subclass [Class] The subclass that is inheriting from this class
|
13
|
+
def inherited(subclass)
|
14
|
+
super
|
15
|
+
subclass.prepend(Ivar::Checked::InstanceMethods)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ivar
|
4
|
+
module Checked
|
5
|
+
# Instance methods that will be prepended to the including class.
|
6
|
+
# These methods provide the core functionality for automatic instance variable validation.
|
7
|
+
module InstanceMethods
|
8
|
+
# The semantics of prepend are such that the super method becomes wholly inaccessible. So if we override a method
|
9
|
+
# (like, say, initialize), we have to stash the original method implementation if we ever want to find out its
|
10
|
+
# file and line number.
|
11
|
+
def self.prepend_features(othermod)
|
12
|
+
(instance_methods(false) | private_instance_methods(false)).each do |method_name|
|
13
|
+
Ivar.stash_method(othermod, method_name)
|
14
|
+
end
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
# Wrap the initialize method to automatically call check_ivars
|
19
|
+
# This method handles the initialization process, including:
|
20
|
+
# 1. Processing manifest declarations before calling super
|
21
|
+
# 3. Checking instance variables for validity
|
22
|
+
def initialize(*args, **kwargs, &block)
|
23
|
+
if @__ivar_skip_init
|
24
|
+
super
|
25
|
+
else
|
26
|
+
@__ivar_skip_init = true
|
27
|
+
manifest = Ivar.get_or_create_manifest(self.class)
|
28
|
+
manifest.process_before_init(self, args, kwargs)
|
29
|
+
super
|
30
|
+
check_ivars
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|