File Coverage

File:blib/lib/App/Test/Generator/Analyzer/Return.pm
Coverage:92.3%

linestmtbrancondsubtimecode
1package App::Test::Generator::Analyzer::Return;
2
3
31
31
31
69487
26
400
use strict;
4
31
31
31
50
22
551
use warnings;
5
31
31
31
58
29
525
use Carp         qw(croak);
6
31
31
31
56
25
490
use Readonly;
7
31
31
31
53
20
6818
use Scalar::Util qw(blessed);
8
9# --------------------------------------------------
10# Evidence weights for each detected return pattern.
11# Higher weights indicate stronger signals that the
12# detected pattern is the primary return behaviour.
13# --------------------------------------------------
14Readonly my $WEIGHT_RETURNS_PROPERTY => 20;
15Readonly my $WEIGHT_RETURNS_SELF     => 15;
16Readonly my $WEIGHT_RETURNS_CONSTANT => 10;
17
18our $VERSION = '0.41';
19
20 - 59
=head1 VERSION

Version 0.41

=head1 DESCRIPTION

Analyses the source code of a method and adds evidence to a
L<App::Test::Generator::Model::Method> object describing what kind of
value the method returns. Evidence is used downstream by
L<App::Test::Generator::Model::Method/resolve_return_type> to determine
the most likely return type.

=head2 new

Construct a new Return analyser.

    my $analyser = App::Test::Generator::Analyzer::Return->new;

=head3 Arguments

None.

=head3 Returns

A blessed hashref.

=head3 API specification

=head4 input

    {}

=head4 output

    {
        type => OBJECT,
        isa  => 'App::Test::Generator::Analyzer::Return',
    }

=cut
60
61sub new {
62
316
111772
        my $class = $_[0];
63
316
313
        return bless {}, $class;
64}
65
66 - 118
=head2 analyze

Scan the source code of a method for return patterns and add weighted
evidence to the method object. Detects three patterns: returning a
property from C<$self>, returning C<$self> itself, and returning a
constant literal value.

    my $analyser = App::Test::Generator::Analyzer::Return->new;
    $analyser->analyze($method);

    my $type = $method->resolve_return_type;

=head3 Arguments

=over 4

=item * C<$method>

Normally an L<App::Test::Generator::Model::Method> object; evidence is
added to it in place via C<add_evidence>. A plain hashref with a
C<source> or C<body> key is also accepted defensively (as SchemaExtractor
callers may pass one), but in that case no evidence is recorded — only
a Model::Method object exposing both C<source()> and C<add_evidence()>
actually accumulates evidence.

=back

=head3 Returns

Nothing (undef). All results are communicated via side effects on the
C<$method> object.

=head3 Notes

The interface of this analyser differs from
L<App::Test::Generator::Analyzer::ReturnMeta>, which operates on a raw
schema hashref. This analyser operates on a C<Model::Method> object
directly.

=head3 API specification

=head4 input

    {
        self   => { type => OBJECT, isa => 'App::Test::Generator::Analyzer::Return' },
        method => { type => OBJECT, isa => 'App::Test::Generator::Model::Method' },
    }

=head4 output

    { type => UNDEF }

=cut
119
120sub analyze {
121
316
2063
        my ($self, $method) = @_;
122
123        # Accept either a Model::Method object or a raw hashref,
124        # since callers in SchemaExtractor pass raw hashrefs
125        my $source = blessed($method) && $method->can('source')
126                ? $method->source()
127
316
1185
                : ($method->{source} // $method->{body} // '');
128
129        my $add = blessed($method) && $method->can('add_evidence')
130
219
237
                ? sub { $method->add_evidence(@_) }
131
316
1086
                : sub {};
132
133        # --------------------------------------------------
134        # Detect: return $self->{property}
135        # Negative lookahead ensures this does not also match
136        # plain return $self (handled separately below).
137        # Matched with /g so a method with several differing
138        # property-returning branches contributes evidence for
139        # each one found, not just the first in the source.
140        # --------------------------------------------------
141
316
519
        while($source =~ /return\s+\$self->\{(\w+)\}/g) {
142
39
67
                $add->(
143                        category => 'return',
144                        signal   => 'returns_property',
145                        value    => $1,
146                        weight   => $WEIGHT_RETURNS_PROPERTY,
147                );
148        }
149
150        # --------------------------------------------------
151        # Detect: return $self
152        # \b after \$self prevents matching variable names that
153        # merely start with "self" (e.g. $self_backup, $selfish);
154        # the negative lookahead then excludes return $self->{...}
155        # which is a property return handled above. Matched with
156        # /g for the same multi-occurrence reason as above.
157        # --------------------------------------------------
158
316
559
        while($source =~ /return\s+\$self\b(?!->)/g) {
159
13
20
                $add->(
160                        category => 'return',
161                        signal   => 'returns_self',
162                        weight   => $WEIGHT_RETURNS_SELF,
163                );
164        }
165
166        # --------------------------------------------------
167        # Detect: return of a constant literal — quoted string,
168        # numeric literal, or undef. All indicate the method
169        # returns a fixed value rather than a computed state.
170        # Matched with /g for the same multi-occurrence reason
171        # as the patterns above.
172        # --------------------------------------------------
173
316
610
        while($source =~ /return\s+(?:['"\d]|undef\b)/g) {
174
170
174
                $add->(
175                        category => 'return',
176                        signal   => 'returns_constant',
177                        weight   => $WEIGHT_RETURNS_CONSTANT,
178                );
179        }
180
181
316
633
        return;
182}
183
184 - 192
=head1 LICENCE AND COPYRIGHT

Copyright 2025-2026 Nigel Horne.

Usage is subject to GPL2 licence terms.
If you use it,
please let me know.

=cut
193
1941;