TER1 (Statement): 100.00%
TER2 (Branch): 91.67%
TER3 (LCSAJ): 100.0% (4/4)
Approximate LCSAJ segments: 13
● Covered — this LCSAJ path was executed during testing.
● Not covered — this LCSAJ path was never executed. These are the paths to focus on.
Multiple dots on a line indicate that multiple control-flow paths begin at that line. Hovering over any dot shows:
start → end → jump
Uncovered paths show [NOT COVERED] in the tooltip.
1: package App::Project::Doctor::Check::Meta; 2: 3: use strict; 4: use warnings; 5: use autodie qw(:all); 6: 7: use parent -norequire, 'App::Project::Doctor::Check::Base'; 8: 9: use Carp qw(croak carp); 10: use Readonly; 11: 12: our $VERSION = '0.02'; 13: 14: Readonly::Array my @REQUIRED_FIELDS => qw(name version author abstract license); 15: Readonly::Array my @META_FILES => qw(META.json META.yml MYMETA.json MYMETA.yml); 16: 17: sub name { 'META' } 18: sub description { 'META.yml or META.json is present, parseable, and complete.' } 19: sub can_fix { 0 } 20: sub order { 30 } 21: 22: sub check { ●23 → 30 → 46 23: my ($self, $ctx) = @_; 24: croak 'check requires an App::Project::Doctor::Context' unless ref $ctx; 25: 26: my @findings; 27: 28: my ($meta_file) = grep { $ctx->has_file($_) } @META_FILES; 29: 30: unless ($meta_file) {Mutants (Total: 1, Killed: 1, Survived: 0)
31: push @findings, _f( 32: severity => 'warning', 33: message => 'No META.{yml,json} -- run your builder to generate one.', 34: detail => 'CPAN indexers require META to discover name and version.', 35: ); 36: # Fall back: at least confirm a builder file exists. 37: unless ($ctx->builder_file) {
Mutants (Total: 1, Killed: 1, Survived: 0)
38: push @findings, _f( 39: severity => 'error', 40: message => 'No Makefile.PL, Build.PL, or dist.ini found.', 41: ); 42: } 43: return @findings;
Mutants (Total: 2, Killed: 2, Survived: 0)
44: } 45: ●46 → 48 → 56 46: require CPAN::Meta; 47: my $meta = eval { CPAN::Meta->load_file($ctx->abs_path($meta_file)) }; 48: if ($@) {
Mutants (Total: 1, Killed: 1, Survived: 0)
49: return _f(
Mutants (Total: 2, Killed: 2, Survived: 0)
50: severity => 'error', 51: message => "Failed to parse $meta_file -- file may be malformed.", 52: file => $meta_file, 53: ); 54: } 55: ●56 → 57 → 66 56: my %data = %{ $meta->as_struct }; 57: for my $field (@REQUIRED_FIELDS) { 58: next if defined $data{$field} && length $data{$field}; 59: push @findings, _f( 60: severity => 'error', 61: message => "META field '$field' is missing or empty in $meta_file.", 62: file => $meta_file, 63: ); 64: } 65: ●66 → 66 → 73 66: unless (@findings) {
Mutants (Total: 1, Killed: 1, Survived: 0)
67: push @findings, _f( 68: severity => 'pass', 69: message => "$meta_file is valid and all required fields are present.", 70: ); 71: } 72: 73: return @findings;
Mutants (Total: 2, Killed: 2, Survived: 0)
74: } 75: 76: sub _f { 77: require App::Project::Doctor::Finding; 78: return App::Project::Doctor::Finding->new(check_name => 'META', @_);
Mutants (Total: 2, Killed: 2, Survived: 0)
79: } 80: 81: 1; 82: 83: __END__ 84: 85: =head1 NAME 86: 87: App::Project::Doctor::Check::Meta - Check META file presence and validity 88: 89: =head1 DESCRIPTION 90: 91: Uses L<CPAN::Meta> to parse the distribution META file and verifies that all 92: required fields (name, version, author, abstract, license) are present. 93: 94: =head1 METHODS 95: 96: =head2 check( $context ) 97: 98: Locates the first available META file, parses it, and validates required fields. 99: 100: =head3 API SPECIFICATION 101: 102: =head4 Input 103: 104: $context : App::Project::Doctor::Context 105: 106: =head4 Output 107: 108: List of App::Project::Doctor::Finding -- 109: warning when no META.* file is found (builder file present), 110: warning + error when no META.* and no builder file found, 111: error when the META file cannot be parsed, 112: one error per missing required field (name/version/author/abstract/license), 113: pass when all required fields are present. 114: 115: =head3 MESSAGES 116: 117: Code | Trigger | Resolution 118: -----|-----------------------------|----------------------------------------- 119: M001 | No META.* file found | Run builder to generate (make or dzil) 120: M002 | META parse failure | Correct malformed YAML/JSON by hand 121: M003 | Required META field missing | Add field to Makefile.PL / dist.ini 122: 123: =head3 FORMAL SPECIFICATION 124: 125: check : Context -> [Finding] 126: check ctx == 127: let f = first meta_file existing in ctx 128: in if f = undef then [warning] ++ (if no builder then [error] else []) 129: else let m = parse f 130: in if parse_fails then [error] 131: else [error per missing field] ++ (if all ok then [pass] else []) 132: 133: =head1 AUTHOR 134: 135: Nigel Horne C<< <njh@nigelhorne.com> >> 136: 137: =head1 LICENSE 138: 139: Copyright (C) 2026 Nigel Horne. 140: This library is free software; you can redistribute it and/or modify 141: it under the same terms as Perl itself. 142: 143: =cut