ivar 0.2.0 → 0.4.6

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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.augment-guidelines +5 -3
  3. data/.devcontainer/devcontainer.json +28 -20
  4. data/.devcontainer/post-create.sh +18 -0
  5. data/.editorconfig +35 -0
  6. data/.rubocop.yml +6 -0
  7. data/.standard.yml +1 -1
  8. data/.vscode/extensions.json +3 -1
  9. data/.vscode/launch.json +25 -0
  10. data/.vscode/settings.json +38 -2
  11. data/CHANGELOG.md +99 -1
  12. data/README.md +272 -207
  13. data/Rakefile +1 -1
  14. data/VERSION.md +46 -0
  15. data/examples/check_all_block_example.rb +84 -0
  16. data/examples/check_all_example.rb +42 -0
  17. data/examples/inheritance_with_kwarg_init.rb +156 -0
  18. data/examples/inheritance_with_positional_init.rb +142 -0
  19. data/examples/mixed_positional_and_kwarg_init.rb +125 -0
  20. data/examples/require_check_all_example.rb +23 -0
  21. data/examples/sandwich_inheritance.rb +1 -1
  22. data/examples/sandwich_with_accessors.rb +78 -0
  23. data/examples/sandwich_with_block_values.rb +54 -0
  24. data/examples/sandwich_with_checked.rb +0 -1
  25. data/examples/sandwich_with_checked_once.rb +0 -1
  26. data/examples/sandwich_with_initial_values.rb +52 -0
  27. data/examples/sandwich_with_ivar_block.rb +6 -9
  28. data/examples/sandwich_with_ivar_macro.rb +4 -4
  29. data/examples/sandwich_with_kwarg_init.rb +78 -0
  30. data/examples/sandwich_with_positional_init.rb +50 -0
  31. data/examples/sandwich_with_shared_values.rb +54 -0
  32. data/hooks/README.md +42 -0
  33. data/hooks/install.sh +12 -0
  34. data/hooks/pre-commit +54 -0
  35. data/lib/ivar/check_all.rb +7 -0
  36. data/lib/ivar/check_all_manager.rb +72 -0
  37. data/lib/ivar/check_policy.rb +29 -0
  38. data/lib/ivar/checked/class_methods.rb +19 -0
  39. data/lib/ivar/checked/instance_methods.rb +35 -0
  40. data/lib/ivar/checked.rb +17 -24
  41. data/lib/ivar/declaration.rb +30 -0
  42. data/lib/ivar/explicit_declaration.rb +56 -0
  43. data/lib/ivar/explicit_keyword_declaration.rb +24 -0
  44. data/lib/ivar/explicit_positional_declaration.rb +19 -0
  45. data/lib/ivar/macros.rb +48 -111
  46. data/lib/ivar/manifest.rb +124 -0
  47. data/lib/ivar/policies.rb +13 -1
  48. data/lib/ivar/project_root.rb +59 -0
  49. data/lib/ivar/targeted_prism_analysis.rb +144 -0
  50. data/lib/ivar/validation.rb +6 -29
  51. data/lib/ivar/version.rb +1 -1
  52. data/lib/ivar.rb +141 -9
  53. data/script/console +11 -0
  54. data/script/de-lint +2 -0
  55. data/script/de-lint-unsafe +2 -0
  56. data/script/lint +2 -0
  57. data/script/release +213 -0
  58. data/script/setup +8 -0
  59. data/script/test +2 -0
  60. metadata +46 -8
  61. data/examples/sandwich_with_kwarg.rb +0 -45
  62. data/ivar.gemspec +0 -49
  63. data/lib/ivar/auto_check.rb +0 -77
  64. data/lib/ivar/prism_analysis.rb +0 -102
metadata CHANGED
@@ -1,13 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ivar
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Avdi Grimm
8
+ autorequire:
8
9
  bindir: bin
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2025-05-07 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: prism
@@ -45,29 +46,64 @@ files:
45
46
  - ".augment-guidelines"
46
47
  - ".devcontainer/Dockerfile"
47
48
  - ".devcontainer/devcontainer.json"
49
+ - ".devcontainer/post-create.sh"
50
+ - ".editorconfig"
51
+ - ".rubocop.yml"
48
52
  - ".standard.yml"
49
53
  - ".vscode/extensions.json"
54
+ - ".vscode/launch.json"
50
55
  - ".vscode/settings.json"
51
56
  - CHANGELOG.md
52
57
  - CODE_OF_CONDUCT.md
53
58
  - LICENSE.txt
54
59
  - README.md
55
60
  - Rakefile
61
+ - VERSION.md
62
+ - examples/check_all_block_example.rb
63
+ - examples/check_all_example.rb
64
+ - examples/inheritance_with_kwarg_init.rb
65
+ - examples/inheritance_with_positional_init.rb
66
+ - examples/mixed_positional_and_kwarg_init.rb
67
+ - examples/require_check_all_example.rb
56
68
  - examples/sandwich_inheritance.rb
69
+ - examples/sandwich_with_accessors.rb
70
+ - examples/sandwich_with_block_values.rb
57
71
  - examples/sandwich_with_checked.rb
58
72
  - examples/sandwich_with_checked_once.rb
73
+ - examples/sandwich_with_initial_values.rb
59
74
  - examples/sandwich_with_ivar_block.rb
60
75
  - examples/sandwich_with_ivar_macro.rb
61
- - examples/sandwich_with_kwarg.rb
62
- - ivar.gemspec
76
+ - examples/sandwich_with_kwarg_init.rb
77
+ - examples/sandwich_with_positional_init.rb
78
+ - examples/sandwich_with_shared_values.rb
79
+ - hooks/README.md
80
+ - hooks/install.sh
81
+ - hooks/pre-commit
63
82
  - lib/ivar.rb
64
- - lib/ivar/auto_check.rb
83
+ - lib/ivar/check_all.rb
84
+ - lib/ivar/check_all_manager.rb
85
+ - lib/ivar/check_policy.rb
65
86
  - lib/ivar/checked.rb
87
+ - lib/ivar/checked/class_methods.rb
88
+ - lib/ivar/checked/instance_methods.rb
89
+ - lib/ivar/declaration.rb
90
+ - lib/ivar/explicit_declaration.rb
91
+ - lib/ivar/explicit_keyword_declaration.rb
92
+ - lib/ivar/explicit_positional_declaration.rb
66
93
  - lib/ivar/macros.rb
94
+ - lib/ivar/manifest.rb
67
95
  - lib/ivar/policies.rb
68
- - lib/ivar/prism_analysis.rb
96
+ - lib/ivar/project_root.rb
97
+ - lib/ivar/targeted_prism_analysis.rb
69
98
  - lib/ivar/validation.rb
70
99
  - lib/ivar/version.rb
100
+ - script/console
101
+ - script/de-lint
102
+ - script/de-lint-unsafe
103
+ - script/lint
104
+ - script/release
105
+ - script/setup
106
+ - script/test
71
107
  - sig/ivar.rbs
72
108
  - test_file.rb
73
109
  homepage: https://github.com/avdi/ivar
@@ -78,6 +114,7 @@ metadata:
78
114
  homepage_uri: https://github.com/avdi/ivar
79
115
  source_code_uri: https://github.com/avdi/ivar
80
116
  changelog_uri: https://github.com/avdi/ivar/blob/main/CHANGELOG.md
117
+ post_install_message:
81
118
  rdoc_options: []
82
119
  require_paths:
83
120
  - lib
@@ -85,14 +122,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
85
122
  requirements:
86
123
  - - ">="
87
124
  - !ruby/object:Gem::Version
88
- version: 3.4.0
125
+ version: 3.3.0
89
126
  required_rubygems_version: !ruby/object:Gem::Requirement
90
127
  requirements:
91
128
  - - ">="
92
129
  - !ruby/object:Gem::Version
93
130
  version: '0'
94
131
  requirements: []
95
- rubygems_version: 3.6.7
132
+ rubygems_version: 3.5.3
133
+ signing_key:
96
134
  specification_version: 4
97
135
  summary: Automatically check instance variables for typos.
98
136
  test_files: []
@@ -1,45 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "ivar"
4
-
5
- class SandwichWithKwarg
6
- include Ivar::Checked
7
-
8
- # Pre-declare instance variables to be initialized from keyword arguments
9
- ivar kwarg: [:@bread, :@cheese, :@condiments]
10
-
11
- def initialize(pickles: false, side: nil)
12
- # Note: @bread, @cheese, and @condiments are already set from keyword arguments
13
- # We only need to handle the remaining keyword arguments
14
- @pickles = pickles
15
- @side = side
16
- end
17
-
18
- def to_s
19
- result = "A #{@bread} sandwich with #{@cheese}"
20
- result += " and #{@condiments.join(", ")}" unless @condiments.empty?
21
- result += " with pickles" if @pickles
22
- result += " and a side of #{@side}" if @side
23
- result
24
- end
25
- end
26
-
27
- # Create a sandwich with keyword arguments
28
- sandwich = SandwichWithKwarg.new(
29
- bread: "wheat",
30
- cheese: "muenster",
31
- condiments: ["mayo", "mustard"],
32
- side: "chips"
33
- )
34
-
35
- puts sandwich # Outputs: A wheat sandwich with muenster and mayo, mustard and a side of chips
36
-
37
- # Create another sandwich with different keyword arguments
38
- sandwich2 = SandwichWithKwarg.new(
39
- bread: "rye",
40
- cheese: "swiss",
41
- condiments: ["mustard"],
42
- pickles: true
43
- )
44
-
45
- puts sandwich2 # Outputs: A rye sandwich with swiss and mustard with pickles
data/ivar.gemspec DELETED
@@ -1,49 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "lib/ivar/version"
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = "ivar"
7
- spec.version = Ivar::VERSION
8
- spec.authors = ["Avdi Grimm"]
9
- spec.email = ["avdi@avdi.codes"]
10
-
11
- spec.summary = "Automatically check instance variables for typos."
12
- spec.description = <<~EOF
13
- Ruby instance variables are so convenient - you don't even need to declare them!
14
- But... they are also dangerous, because a mispelled variable name results in `nil`
15
- instead of an error.
16
-
17
- Why not have the best of both worlds? Ivar lets you use plain-old instance variables,
18
- and automatically checks for typos.
19
-
20
- Ivar waits until an instance is created to do the checking, then uses Prism to look
21
- for variables that don't match what was set in initialization. So it's a little bit
22
- dynamic, a little bit static. It doesn't encumber your instance variable reads and
23
- writes with any extra checking. And with the `:warn_once` policy, it won't overwhelm
24
- you with output.
25
- EOF
26
-
27
- spec.homepage = "https://github.com/avdi/ivar"
28
- spec.license = "MIT"
29
- spec.required_ruby_version = ">= 3.4.0"
30
-
31
- spec.metadata["allowed_push_host"] = "https://rubygems.org"
32
-
33
- spec.metadata["homepage_uri"] = spec.homepage
34
- spec.metadata["source_code_uri"] = "https://github.com/avdi/ivar"
35
- spec.metadata["changelog_uri"] = "https://github.com/avdi/ivar/blob/main/CHANGELOG.md"
36
-
37
- # Specify which files should be added to the gem when it is released.
38
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
39
- spec.files = Dir.chdir(__dir__) do
40
- `git ls-files -z`.split("\x0").reject do |f|
41
- (File.expand_path(f) == __FILE__) ||
42
- f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor Gemfile])
43
- end
44
- end
45
- spec.require_paths = ["lib"]
46
-
47
- # Dependencies
48
- spec.add_dependency "prism", "~> 1.2"
49
- end
@@ -1,77 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "validation"
4
- require_relative "macros"
5
-
6
- module Ivar
7
- # Module for adding ivar_check_policy to classes
8
- module CheckPolicy
9
- # Set the check policy for this class
10
- # @param policy [Symbol, Policy] The check policy
11
- # @return [Symbol, Policy] The check policy
12
- def ivar_check_policy(policy = nil, **options)
13
- if policy.nil?
14
- # Getter - return the current policy
15
- @__ivar_check_policy || Ivar.check_policy
16
- else
17
- # Setter - set the policy
18
- @__ivar_check_policy = options.empty? ? policy : [policy, options]
19
- end
20
- end
21
-
22
- # Hook method called when the module is included
23
- def inherited(subclass)
24
- super
25
- # Copy the check policy to the subclass
26
- subclass.instance_variable_set(:@__ivar_check_policy, @__ivar_check_policy)
27
- end
28
- end
29
-
30
- # Provides automatic validation for instance variables
31
- # When included, automatically calls check_ivars after initialization
32
- module Checked
33
- # When this module is included in a class, it extends the class
34
- # with ClassMethods and includes the Validation module
35
- def self.included(base)
36
- base.include(Validation)
37
- base.include(PreInitializeIvars)
38
- base.extend(ClassMethods)
39
- base.extend(CheckPolicy)
40
- base.extend(Macros)
41
- base.prepend(InstanceMethods)
42
-
43
- # Set default policy for Checked to :warn
44
- base.ivar_check_policy(:warn)
45
- end
46
-
47
- # Class methods added to the including class
48
- module ClassMethods
49
- # Hook method called when the module is included
50
- def inherited(subclass)
51
- super
52
- # Ensure subclasses also get the initialize wrapper
53
- subclass.prepend(Ivar::Checked::InstanceMethods)
54
- end
55
- end
56
-
57
- # Instance methods that will be prepended to the including class
58
- module InstanceMethods
59
- # Wrap the initialize method to automatically call check_ivars
60
- def initialize(*args, **kwargs, &block)
61
- # Initialize pre-declared instance variables
62
- initialize_pre_declared_ivars
63
- # Execute the initialization block if provided
64
- execute_ivar_init_block
65
-
66
- # Process keyword arguments
67
- remaining_kwargs = initialize_from_kwargs(kwargs)
68
-
69
- # Call the original initialize method with remaining arguments
70
- super(*args, **remaining_kwargs, &block)
71
-
72
- # Automatically check instance variables
73
- check_ivars
74
- end
75
- end
76
- end
77
- end
@@ -1,102 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "prism"
4
-
5
- module Ivar
6
- # Analyzes a class to find all instance variables using Prism
7
- class PrismAnalysis
8
- attr_reader :ivars
9
-
10
- def initialize(klass)
11
- @klass = klass
12
- @references = nil
13
- collect_references
14
- @ivars = unique_ivar_names
15
- end
16
-
17
- # Returns a list of hashes each representing a code reference to an ivar
18
- # Each hash includes var name, path, line number, and column number
19
- def ivar_references
20
- @references
21
- end
22
-
23
- private
24
-
25
- def collect_references
26
- source_files = collect_source_files
27
- @references = []
28
-
29
- source_files.each do |file_path|
30
- code = File.read(file_path)
31
- result = Prism.parse(code)
32
- visitor = IvarReferenceVisitor.new(file_path)
33
- result.value.accept(visitor)
34
- @references.concat(visitor.references)
35
- end
36
- end
37
-
38
- def unique_ivar_names
39
- @references.map { |ref| ref[:name] }.uniq.sort
40
- end
41
-
42
- def collect_source_files
43
- # Get all instance methods
44
- instance_methods = @klass.instance_methods(false) | @klass.private_instance_methods(false)
45
-
46
- # Collect source files for all methods
47
- source_files = Set.new
48
- instance_methods.each do |method_name|
49
- next unless @klass.instance_method(method_name).source_location
50
-
51
- source_files << @klass.instance_method(method_name).source_location.first
52
- end
53
-
54
- source_files
55
- end
56
-
57
- # Visitor that collects instance variable references with location information
58
- class IvarReferenceVisitor < Prism::Visitor
59
- attr_reader :references
60
-
61
- def initialize(file_path)
62
- super()
63
- @file_path = file_path
64
- @references = []
65
- end
66
-
67
- def visit_instance_variable_read_node(node)
68
- add_reference(node)
69
- true
70
- end
71
-
72
- def visit_instance_variable_write_node(node)
73
- add_reference(node)
74
- true
75
- end
76
-
77
- def visit_instance_variable_operator_write_node(node)
78
- add_reference(node)
79
- true
80
- end
81
-
82
- def visit_instance_variable_target_node(node)
83
- add_reference(node)
84
- true
85
- end
86
-
87
- private
88
-
89
- def add_reference(node)
90
- location = node.location
91
- reference = {
92
- name: node.name.to_sym,
93
- path: @file_path,
94
- line: location.start_line,
95
- column: location.start_column
96
- }
97
-
98
- @references << reference
99
- end
100
- end
101
- end
102
- end