do_not_use 0.0.0 → 0.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 14e53a6088ff672a43401f6f4e666aebd6d0c833a23f77e3424174f4c24793dc
4
- data.tar.gz: 6a72c3d43e71eb4057c9fb550414b5ccd874b861efaf62804e2e9246672c29e9
3
+ metadata.gz: 6896cabb0a8a7e577ab250ce73f0de5c141c3e5132ca2b3f3c8f90b0f5db2ea1
4
+ data.tar.gz: 54faf975c3653b9ff23821f42128b5433935f7ade489f5654f68d2741d628a09
5
5
  SHA512:
6
- metadata.gz: 42ba9e3bdd224b6390f225dbb017941f98622c605e1aa201cd452b8268eddab4c795ce841e0326a9f547abb9a4ec70d7de701db15c39b6894e3e60ccd7f57b20
7
- data.tar.gz: 1e07bca4b9e532ff1b948b6edf18be6604b4ba63c82b4a476464b796e0c2acc7852737ef93dbdd6e2fe8ea9410d04b50cda56f7409af37164ba0d3f8b8516b6f
6
+ metadata.gz: dac8bb46aa616d09192d07e2c26cb573e8278cd45ce3f22e94cb4de8fef301986db16c44de6ddc4789edea378bdb5f48ef2fbe7dd9e354569a6bf64e0785ab49
7
+ data.tar.gz: c8c79e221519af21ff806786da4ecd432eaedb75dda4920cac68692430ed559df37ed0c4f87a89e4b7f3f43c1f65cfc06c6367b4034d5575d2820123d45c171f
data/README.md CHANGED
@@ -1,28 +1,122 @@
1
1
  # DoNotUse
2
2
 
3
- TODO: Delete this and the text below, and describe your gem
4
-
5
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/do_not_use`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ `DoNotUse` gives teams a hard-stop enforcement layer for Ruby APIs that must no longer be used. It provides a DSL to deprecate methods and raise a descriptive error when a deprecated method is used. It also provides configuration options to lets you manage temporary exceptions in one place.
6
4
 
7
5
  ## Installation
8
6
 
9
- TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
7
+ Add the gem to your application:
8
+
9
+ ```ruby
10
+ gem "do_not_use"
11
+ ```
10
12
 
11
- Install the gem and add to the application's Gemfile by executing:
13
+ Then install it via:
12
14
 
13
- ```bash
14
- bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
15
+ ```sh
16
+ bundle install
15
17
  ```
16
18
 
17
- If bundler is not being used to manage dependencies, install the gem by executing:
19
+ You can also install it directly via RubyGems:
18
20
 
19
- ```bash
20
- gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
21
+ ```sh
22
+ gem install do_not_use
21
23
  ```
22
24
 
23
25
  ## Usage
24
26
 
25
- TODO: Write usage instructions here
27
+ Imagine you have a Ruby class called `User` with a method `raw_data` that stores part of the user’s data in JSON format. You’ve decided to deprecate this method across the codebase, but you don’t have time to remove all its usages immediately. At the same time, you need to proceed with schema and data migrations before fully eliminating it.
28
+
29
+ In this case, you can mark the method as deprecated and continue with the migrations. Then, using the `DoNotUse` gem, you can gradually identify and remove its usages across the codebase in an iterative manner while preventing the further logic built on top of it.
30
+
31
+ ```ruby
32
+ class User < ApplicationRecord
33
+ do_not_use :raw_data
34
+
35
+ def raw_data
36
+ JSON.parse(super)
37
+ end
38
+ end
39
+
40
+ class MyService
41
+ def do_something
42
+ user.raw_data # => raises DoNotUse::Errors::DeprecatedMethodUseError
43
+ end
44
+ end
45
+ ```
46
+
47
+ With the following configuration, you can temporarily allow `MyService` to access `User#raw_data` method;
48
+
49
+ ```ruby
50
+ DoNotUse.configure do |config|
51
+ config.exceptions_yaml_path = Rails.root.join("config", "do_not_use.yaml")
52
+ end
53
+ ```
54
+
55
+ ```yaml
56
+ # do_not_use.yaml
57
+ User:
58
+ instance:
59
+ raw_data:
60
+ allowed:
61
+ - ["MyService#do_something"]
62
+
63
+ ```
64
+
65
+ If someone introduces a new usage of the deprecated method, a `DeprecatedMethodUseError` will be raised. With `DoNotUse`, you can track not only direct usages of deprecated methods, but also any new execution paths that invoke them.
66
+
67
+
68
+ For example, imagine someone creates a new service that calls MyService#do_something;
69
+
70
+ ```ruby
71
+ class AnotherService
72
+ def self.execute
73
+ my_service_object.do_something
74
+ end
75
+
76
+ private
77
+
78
+ def my_service_object
79
+ MyService.new
80
+ end
81
+ end
82
+
83
+ AnotherService.execute # => raises DoNotUse::Errors::DeprecatedMethodUseError
84
+ ```
85
+
86
+ If you want to allow this new usage, you can register its execution path signature;
87
+
88
+ ```yaml
89
+ # do_not_use.yaml
90
+ User:
91
+ instance:
92
+ raw_data:
93
+ allowed:
94
+ - ["MyService#do_something"]
95
+ singleton:
96
+ raw_data:
97
+ allowed:
98
+ - ["AnotherService.execute", "MyService#do_something"]
99
+
100
+ ```
101
+
102
+ You don’t need to manually generate these signatures, they are included in the exception message, so you can copy and register them as allowed usages.
103
+
104
+ ## Configuration
105
+
106
+ Call `DoNotUse.configure` with a block to configure the gem;
107
+
108
+ ```ruby
109
+ DoNotUse.configure do |config|
110
+ config.enabled = !Rails.env.production? # A boolean value is required. It's recommended to disable the gem in production environments.
111
+ config.tracked_paths << Rails.root # The paths where your application logic lives in.
112
+ config.ignored_paths << Rails.root.join("spec") # The paths which you don't want to track.
113
+ config.exceptions_yaml_path = Rails.root.join("exceptions.yaml") # The YAML file path where you specify existing usages of the deprecated APIs.
114
+ config.signature_generators = {
115
+ Rails.root.join("spec", "models") => Proc.new { |instruction| ... }
116
+ } # Helps you configure how the instruction signatures are generated for the ignored paths.
117
+ end
118
+ ```
119
+
26
120
 
27
121
  ## Development
28
122
 
@@ -32,7 +126,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
32
126
 
33
127
  ## Contributing
34
128
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/do_not_use. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/do_not_use/blob/master/CODE_OF_CONDUCT.md).
129
+ Bug reports and pull requests are welcome on gitlab at https://gitlab.com/minac/do_not_use. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://gitlab.com/minac/do_not_use/blob/master/CODE_OF_CONDUCT.md).
36
130
 
37
131
  ## License
38
132
 
@@ -40,4 +134,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
40
134
 
41
135
  ## Code of Conduct
42
136
 
43
- Everyone interacting in the DoNotUse project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/do_not_use/blob/master/CODE_OF_CONDUCT.md).
137
+ Everyone interacting in the DoNotUse project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://gitlab.com/minac/do_not_use/blob/master/CODE_OF_CONDUCT.md).
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+
5
+ module DoNotUse
6
+ class Configuration
7
+ attr_accessor :exceptions_yaml_path, :tracked_paths, :ignored_paths, :enabled, :signature_generators
8
+
9
+ def initialize
10
+ @tracked_paths = []
11
+ @ignored_paths = []
12
+ @enabled = true
13
+ @signature_generators = {}
14
+ end
15
+
16
+ def exceptions
17
+ @exceptions ||= YAML.load_file(exceptions_yaml_path)
18
+ end
19
+
20
+ def exceptions_yaml_path
21
+ @exceptions_yaml_path.to_s unless @exceptions_yaml_path.nil?
22
+ end
23
+
24
+ def exceptions_for(module_name, is_singleton, method_name)
25
+ config_namespace = config_namespace_for(is_singleton)
26
+
27
+ exceptions.dig(module_name, config_namespace, method_name, "allowed").to_a
28
+ end
29
+
30
+ private
31
+
32
+ def config_namespace_for(is_singleton)
33
+ is_singleton ? "singleton" : "instance"
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "binding_of_caller"
4
+
5
+ module DoNotUse
6
+ class Deprecator
7
+ IGNORED_INSTRUCTIONS_COUNT = 3
8
+
9
+ def initialize(module_name, method_name, is_singleton)
10
+ @module_name = module_name
11
+ @method_name = method_name.to_s
12
+ @is_singleton = is_singleton
13
+ end
14
+
15
+ def deprecation_warning(...)
16
+ signature_builder = SignatureBuilder.new(callers)
17
+
18
+ return if allowed_usage?(signature_builder.signature)
19
+
20
+ raise Errors::DeprecatedMethodUseError.new(deprecated_method_signature, signature_builder.signature)
21
+ end
22
+
23
+ private
24
+
25
+ attr_reader :module_name, :method_name, :is_singleton
26
+
27
+ delegate :configuration, to: DoNotUse, private: true
28
+ delegate :exceptions, to: :configuration, private: true
29
+
30
+ def allowed_usage?(signature)
31
+ allowed_usages.include?(signature)
32
+ end
33
+
34
+ def allowed_usages
35
+ @allowed_usages ||= configuration.exceptions_for(module_name, is_singleton, method_name)
36
+ end
37
+
38
+ def deprecated_method_signature
39
+ @deprecated_method_signature ||= "#{module_name}#{method_type_identifier}#{method_name}"
40
+ end
41
+
42
+ def method_type_identifier
43
+ is_singleton ? '.' : '#'
44
+ end
45
+
46
+ def callers
47
+ binding.callers[IGNORED_INSTRUCTIONS_COUNT..]
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DoNotUse
4
+ module Errors
5
+ class DeprecatedMethodUseError < RuntimeError
6
+ def initialize(method_signature, stacktrace)
7
+ super <<~ERR
8
+ `#{method_signature}` method is deprecated and will be removed soon.
9
+
10
+ Using this method is strongly discouraged, as it introduces additional technical
11
+ debt that will need to be addressed later.
12
+
13
+ If you believe this usage is essential and provides sufficient value to justify
14
+ the added technical debt, please update exceptions YAML file with the following
15
+ stack trace signature. Be aware that this is only a short-term allowance; the
16
+ technical debt introduced here must be addressed and removed promptly.
17
+
18
+ #{stacktrace.inspect}
19
+ ERR
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DoNotUse
4
+ class Instruction
5
+ def initialize(binding)
6
+ @binding = binding
7
+ end
8
+
9
+ def receiver
10
+ singleton? ? binding.receiver : binding.receiver.class
11
+ end
12
+
13
+ def singleton?
14
+ binding.receiver.is_a?(Module)
15
+ end
16
+
17
+ def method_name
18
+ binding.eval('__method__')
19
+ end
20
+
21
+ def method_owner
22
+ return unless frame_type == :method
23
+
24
+ meth = singleton? ? receiver.method(method_name) : receiver.instance_method(method_name)
25
+
26
+ meth.owner
27
+ end
28
+
29
+ def track?
30
+ location.track? || (inherited_method? && receiver_location&.track?)
31
+ end
32
+
33
+ def inherited_method_from_ignored_paths?
34
+ !location.track? && inherited_method?
35
+ end
36
+
37
+ delegate :frame_type, to: :binding
38
+ delegate :signature_generator, to: :location
39
+
40
+ private
41
+
42
+ attr_reader :binding
43
+
44
+ # The receiver location may differ from the original definition
45
+ # if the method is inherited.
46
+ #
47
+ # It can also be nil when the receiver location cannot be determined,
48
+ # such as for constants defined internally by the VM in C.
49
+ def receiver_location
50
+ return unless receiver_source_path
51
+
52
+ Location.new(receiver_source_path.first)
53
+ end
54
+
55
+ def location
56
+ @location ||= Location.new(source_path)
57
+ end
58
+
59
+ def inherited_method?
60
+ receiver != method_owner
61
+ end
62
+
63
+ def source_path
64
+ @source_path ||= binding.source_location.first
65
+ end
66
+
67
+ def receiver_source_path
68
+ @receiver_source_path ||= Object.const_source_location(receiver.name)
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DoNotUse
4
+ class Location
5
+ def initialize(source_path)
6
+ @source_path ||= source_path.to_s
7
+ end
8
+
9
+ def track?
10
+ located_in_tracked_path? && !located_in_ignored_path?
11
+ end
12
+
13
+ # Custom signature generators registered via gem configuration.
14
+ # These generators are only used for instructions located in ignored paths.
15
+ #
16
+ # This allows users to define signatures for those instructions as they see fit.
17
+ def signature_generator
18
+ signature_generators.find { |path, _| source_path.starts_with?(path) }&.second
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :source_path
24
+
25
+ delegate :configuration, to: DoNotUse, private: true
26
+ delegate :tracked_paths, :ignored_paths, :signature_generators, to: :configuration, private: true
27
+
28
+ def located_in_tracked_path?
29
+ tracked_paths.any? { |path| source_path.starts_with?(path.to_s) }
30
+ end
31
+
32
+ def located_in_ignored_path?
33
+ ignored_paths.any? { |path| source_path.starts_with?(path.to_s) }
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/module/deprecation"
4
+
5
+ module DoNotUse
6
+ module Patches
7
+ module ModulePatch
8
+ def do_not_use(*method_names)
9
+ return unless DoNotUse.enabled?
10
+
11
+ method_names.each do |method_name|
12
+ deprecator = Deprecator.new(do_not_use_name, method_name, singleton_class?)
13
+
14
+ deprecate(method_name, deprecator: deprecator)
15
+ end
16
+ end
17
+
18
+ def do_not_use_name
19
+ singleton_class? ? attached_object.name : name
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ Module.prepend(DoNotUse::Patches::ModulePatch)
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DoNotUse
4
+ class SignatureBuilder
5
+ def initialize(bindings)
6
+ @bindings = bindings
7
+ end
8
+
9
+ def signature
10
+ @signature ||= signatures.uniq.reverse
11
+ end
12
+
13
+ private
14
+
15
+ attr_reader :bindings
16
+
17
+ def signatures
18
+ instructions.each_with_object([]) do |instruction, memo|
19
+ if instruction.track?
20
+ signature = Signatures::TrackedCode.new(instruction).signature
21
+
22
+ memo << signature if signature
23
+ else
24
+ memo << Signatures::IgnoredCode.new(instruction).signature if memo.empty?
25
+
26
+ break memo
27
+ end
28
+ end
29
+ end
30
+
31
+ def instructions
32
+ @instructions ||= bindings.map { |binding| Instruction.new(binding) }
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DoNotUse
4
+ module Signatures
5
+ class AbstractCode
6
+ def initialize(instruction)
7
+ @instruction = instruction
8
+ end
9
+
10
+ private
11
+
12
+ attr_reader :instruction
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DoNotUse
4
+ module Signatures
5
+ class IgnoredCode < AbstractCode
6
+ def signature
7
+ custom_signature || parent_module.name
8
+ end
9
+
10
+ private
11
+
12
+ delegate :receiver, :signature_generator, to: :instruction, private: true
13
+
14
+ def parent_module
15
+ receiver.module_parents[-2] || receiver
16
+ end
17
+
18
+ def custom_signature
19
+ signature_generator&.call(instruction)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DoNotUse
4
+ module Signatures
5
+ class TrackedCode < AbstractCode
6
+ def signature
7
+ return unless klass_name && method_or_block?
8
+ return inherited_code_signature if inherited_method_from_ignored_paths?
9
+
10
+ self_method_signature
11
+ end
12
+
13
+ private
14
+
15
+ delegate :frame_type, :singleton?, :inherited_method_from_ignored_paths?, :receiver, :method_owner,
16
+ to: :instruction, private: true
17
+
18
+ # Objects like ActiveRecord models inherit many methods from the library.
19
+ # As a result, the stack trace of a simple method call may include numerous
20
+ # private API calls that can change without notice, potentially triggering
21
+ # new deprecation warnings.
22
+ #
23
+ # Additionally, the specific inherited library methods involved are usually
24
+ # not relevant when registering exceptions.
25
+ #
26
+ # For these reasons, we generate a different signature here.
27
+ def inherited_code_signature
28
+ "#{receiver} < #{method_owner}"
29
+ end
30
+
31
+ def self_method_signature
32
+ "#{klass_name}#{separator}#{instruction_name}"
33
+ end
34
+
35
+ def klass_name
36
+ @klass_name ||= instruction.receiver.name
37
+ end
38
+
39
+ def instruction_name
40
+ block? ? 'block' : instruction.method_name
41
+ end
42
+
43
+ def separator
44
+ if block?
45
+ '&'
46
+ elsif singleton?
47
+ '.'
48
+ else
49
+ '#'
50
+ end
51
+ end
52
+
53
+ def method_or_block?
54
+ frame_type == :method || frame_type == :block
55
+ end
56
+
57
+ def block?
58
+ frame_type == :block
59
+ end
60
+ end
61
+ end
62
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DoNotUse
4
- VERSION = "0.0.0"
4
+ VERSION = "0.1.0"
5
5
  end
data/lib/do_not_use.rb CHANGED
@@ -1,8 +1,33 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/all"
4
+
3
5
  require_relative "do_not_use/version"
6
+ require_relative "do_not_use/configuration"
7
+ require_relative "do_not_use/deprecator"
8
+ require_relative "do_not_use/instruction"
9
+ require_relative "do_not_use/location"
10
+ require_relative "do_not_use/signature_builder"
11
+ require_relative "do_not_use/signatures/abstract_code"
12
+ require_relative "do_not_use/signatures/ignored_code"
13
+ require_relative "do_not_use/signatures/tracked_code"
14
+ require_relative "do_not_use/errors/deprecated_method_use_error"
15
+ require_relative "do_not_use/patches/module_patch"
4
16
 
5
17
  module DoNotUse
6
- class Error < StandardError; end
7
- # Your code goes here...
18
+ def self.enabled?
19
+ configuration.enabled
20
+ end
21
+
22
+ def self.configure(&block)
23
+ block.call(configuration)
24
+ end
25
+
26
+ def self.configuration
27
+ @configuration ||= Configuration.new
28
+ end
29
+
30
+ def self.reset_configuration!
31
+ @configuration = Configuration.new
32
+ end
8
33
  end
metadata CHANGED
@@ -1,14 +1,48 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: do_not_use
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mehmet Emin INAC
8
8
  bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
- dependencies: []
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activesupport
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.0'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '9.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '7.0'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '9.0'
32
+ - !ruby/object:Gem::Dependency
33
+ name: binding_of_caller
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - "~>"
37
+ - !ruby/object:Gem::Version
38
+ version: 1.0.0
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - "~>"
44
+ - !ruby/object:Gem::Version
45
+ version: 1.0.0
12
46
  description: Helps capture deprecated method usages.
13
47
  email:
14
48
  - mehmetemininac@gmail.com
@@ -21,6 +55,16 @@ files:
21
55
  - README.md
22
56
  - Rakefile
23
57
  - lib/do_not_use.rb
58
+ - lib/do_not_use/configuration.rb
59
+ - lib/do_not_use/deprecator.rb
60
+ - lib/do_not_use/errors/deprecated_method_use_error.rb
61
+ - lib/do_not_use/instruction.rb
62
+ - lib/do_not_use/location.rb
63
+ - lib/do_not_use/patches/module_patch.rb
64
+ - lib/do_not_use/signature_builder.rb
65
+ - lib/do_not_use/signatures/abstract_code.rb
66
+ - lib/do_not_use/signatures/ignored_code.rb
67
+ - lib/do_not_use/signatures/tracked_code.rb
24
68
  - lib/do_not_use/version.rb
25
69
  homepage: https://gitlab.com/minac/do-not-use
26
70
  licenses:
@@ -42,7 +86,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
42
86
  - !ruby/object:Gem::Version
43
87
  version: '0'
44
88
  requirements: []
45
- rubygems_version: 4.0.8
89
+ rubygems_version: 4.0.9
46
90
  specification_version: 4
47
91
  summary: Marks methods/attributes as deprecated and captures their usages.
48
92
  test_files: []