|
1 | 1 | # frozen_string_literal: true |
2 | 2 |
|
3 | 3 | module DuckTyper |
4 | | - # Normalizes method parameters to enable interface comparison. |
5 | | - # Keyword argument order is irrelevant — m(a:, b:) and m(b:, a:) |
6 | | - # are equivalent — so keywords are sorted alphabetically. Positional |
7 | | - # argument names are also replaced with sequential placeholders, |
8 | | - # focusing the comparison on parameter structure rather than naming. |
9 | | - class ParamsNormalizer # :nodoc: |
10 | | - KEYWORD_TYPES = %i[key keyreq].freeze |
11 | | - SEQUENTIAL_TYPES = %i[req opt rest keyrest block].freeze |
12 | | - |
13 | | - class << self |
14 | | - def call(params) |
15 | | - sort_keyword_params(params).then { |sorted| sequentialize_params(sorted) } |
| 4 | + # Factory for parameter normalization. Use ParamsNormalizer.for(strict:) |
| 5 | + # to get the right normalizer for the given comparison mode. |
| 6 | + module ParamsNormalizer # :nodoc: |
| 7 | + def self.for(strict:) |
| 8 | + strict ? StrictParamsNormalizer : DefaultParamsNormalizer |
| 9 | + end |
| 10 | + |
| 11 | + # Normalizes method parameters for default interface comparison. |
| 12 | + # Sorts keywords alphabetically and replaces positional argument |
| 13 | + # names with sequential placeholders. |
| 14 | + module DefaultParamsNormalizer # :nodoc: |
| 15 | + def self.call(params) |
| 16 | + KeywordNormalizer.call(params).then { |p| SequentialNormalizer.call(p) } |
16 | 17 | end |
| 18 | + end |
17 | 19 |
|
18 | | - private |
| 20 | + # Normalizes method parameters for strict interface comparison, |
| 21 | + # where positional argument names are significant. Sorts keywords |
| 22 | + # alphabetically but preserves positional argument names. |
| 23 | + module StrictParamsNormalizer # :nodoc: |
| 24 | + def self.call(params) |
| 25 | + KeywordNormalizer.call(params) |
| 26 | + end |
| 27 | + end |
19 | 28 |
|
20 | | - def sort_keyword_params(params) |
21 | | - keywords, sequentials = params.partition do |type, _| |
22 | | - KEYWORD_TYPES.include?(type) |
23 | | - end |
| 29 | + # Sorts keyword argument parameters alphabetically, making keyword |
| 30 | + # order irrelevant for interface comparison. |
| 31 | + module KeywordNormalizer # :nodoc: |
| 32 | + KEYWORD_TYPES = %i[key keyreq].freeze |
| 33 | + |
| 34 | + def self.call(params) |
| 35 | + keywords, sequentials = params.partition { |type, _| KEYWORD_TYPES.include?(type) } |
24 | 36 |
|
25 | 37 | sequentials + keywords.sort_by { |_, name| name } |
26 | 38 | end |
| 39 | + end |
| 40 | + |
| 41 | + # Replaces positional parameter names with sequential placeholders |
| 42 | + # (a, b, c, ...), focusing comparison on structure rather than naming. |
| 43 | + module SequentialNormalizer # :nodoc: |
| 44 | + SEQUENTIAL_TYPES = %i[req opt rest keyrest block].freeze |
| 45 | + |
| 46 | + class << self |
| 47 | + def call(params) |
| 48 | + sequential_name = ("a".."z").to_enum |
27 | 49 |
|
28 | | - def sequentialize_params(params) |
29 | | - sequential_name = ("a".."z").to_enum |
| 50 | + params.map do |type, name| |
| 51 | + if SEQUENTIAL_TYPES.include?(type) |
| 52 | + name = next_sequential_param(sequential_name) |
| 53 | + end |
30 | 54 |
|
31 | | - params.map do |type, name| |
32 | | - if SEQUENTIAL_TYPES.include?(type) |
33 | | - name = next_sequential_param(sequential_name) |
| 55 | + [type, name] |
34 | 56 | end |
| 57 | + end |
| 58 | + |
| 59 | + private |
35 | 60 |
|
36 | | - [type, name] |
| 61 | + def next_sequential_param(enumerator) |
| 62 | + enumerator.next.to_sym |
| 63 | + rescue StopIteration |
| 64 | + raise TooManyParametersError, "too many positional parameters, maximum supported is 26" |
37 | 65 | end |
38 | 66 | end |
| 67 | + end |
39 | 68 |
|
40 | | - def next_sequential_param(enumerator) |
41 | | - enumerator.next.to_sym |
42 | | - rescue StopIteration |
43 | | - raise TooManyParametersError, "too many positional parameters, maximum supported is 26" |
| 69 | + # A no-op params processor that returns params unchanged. Used when |
| 70 | + # interface comparison should preserve original parameter names rather |
| 71 | + # than normalizing them. |
| 72 | + module NullParamsNormalizer # :nodoc: |
| 73 | + def self.call(params) |
| 74 | + params |
44 | 75 | end |
45 | 76 | end |
46 | 77 | end |
|
0 commit comments