Revision history for Perl500503Syntax::OrDie

0.04  2026-09-21 JST (Japan Standard Time)

  - The \x{HHHH} and \N{name} escapes are now detected inside
    interpolating string literals, which is where they are actually
    written.  Both were listed in CHECKED CONSTRUCTS and both were, in
    practice, unreachable: the bodies of string literals are masked
    before the blacklist runs, so the entries could fire only on the
    rare source that puts the escape outside a string, and the regex
    stage covered it only inside a regex.  The ordinary spelling

        my $smiley = "\x{263A}";

    passed unreported -- the exact defect this distribution exists to
    catch, since Perl 5.005_03 reads \x{263A} as \x with no hex digits
    followed by the literal text "{263A}" and carries on with the wrong
    string.
    A new stage 4 scans the body of every interpolating string literal:
    "...", qq//, qx//, `...`, an unquoted or double-quoted heredoc, and
    the replacement half of s/// unless it is single-quote delimited.
    '...', q//, qw// and <<'WORD' process no escapes and are not
    scanned.  Escaped backslashes are neutralised first, so "\\x{41}"
    -- a literal backslash followed by text -- is not reported.
  - doc/: all 21 cheatsheets still stated version 0.03 while everything
    else in this release had moved on, and the suite reported success
    throughout because nothing read that number.  They now state the
    current version, and t/9080-cheatsheets.t gained CS12, which
    compares the version each sheet states against $VERSION -- the doc/
    counterpart of the B8 check INA_CPAN_Check makes on =head1 VERSION.
  - t/0002-check-source.t: the xUNI case asserted the old behaviour
    ("inside dquote string - masked (not detected)").  It now asserts
    detection, and six cases were added for the single-quoted,
    escaped-backslash, heredoc and s/// spellings.
  - POD and README: the CHECKED CONSTRUCTS entries for \x{} and \N{}
    now say where the escapes are looked for, and the "RAW checks"
    section, which claimed that no string content is ever inspected,
    has been split so that the rule it states is the one the code
    follows: what a string says is not inspected, how it is spelled is.
  - Three constructs that a port to Perl 5.005_03 meets early were
    checked by nothing, and all three fail on 5.005_03 for reasons that
    have nothing to do with what a string in them says:
      * 'use warnings' and 'no warnings' without the
        $INC{'warnings.pm'} stub.  The pragma is Perl 5.6 and the bare
        statement dies at compile time with "Can't locate warnings.pm".
        The guarded idiom that every cross-version file in this author's
        distributions already carries is a no-op on 5.005_03 and is
        treated as compatible, the same way the empty-import form of
        'use feature' is.  The stub counts only where it is live code,
        so the idiom quoted in a comment or in POD does not excuse an
        unguarded statement.
      * a lexical filehandle or directory handle -- open(my $fh, ...),
        opendir, sysopen, pipe, socket, socketpair, accept.  Perl
        5.005_03 has nothing to autovivify a handle into and fails at
        run time with "Can't use an undefined value as a symbol
        reference", so the defect survives compilation.  Only the 'my'
        spelling is reported: open($fh, ...) against a lexical that
        already holds a glob is valid 5.005_03 and cannot be told apart
        from the invalid case.
      * binmode() with a LAYER argument.  This is a question of arity,
        not of the layer string: 5.005_03 binmode takes the filehandle
        alone and rejects a second argument with "Too many arguments for
        binmode".  The earlier decision to drop the PerlIO-layer entry
        from RAW_BLACKLIST reasoned about the string, which is why the
        call was left unchecked; the commas are counted at the top level
        of the call instead, as the 3-argument open() stage already
        does.
    Verified against the 23 corpus modules and this distribution's own
    sources, which carry guarded 'use warnings' and one-argument
    binmode() throughout: no false positives.  t/0002-check-source.t
    gained 25 cases, and t/0005-regex-checks.t had two that asserted
    the old reasoning ("binmode with layer string - NOT a syntax
    violation"); they now assert detection, with two more for the layer
    held in a variable and for the layer string on its own.  doc/ and
    README list the three constructs.
  - t/corpus-stack: Jacode4e, Jacode4e::RoundTrip and mb shipped as
    complete copies of their distributions -- 3.4 MB of the 5.2 MB this
    distribution weighed, against 64 KB of lib/.  Almost none of it
    reached the scanner.  Measured with the masker itself, 99% of
    Jacode4e.pm and of RoundTrip.pm, and 73% of mb.pm, was POD, data
    tables and text after __END__: regions the masker blanks before any
    stage runs.  The three now ship as excerpts and the distribution is
    1.2 MB.
    Only lines the masker renders as dead were removed.  Lines inside a
    regex literal or an interpolating string literal were kept, because
    stages 3 and 4 read those, and so were POD directives, heredoc
    terminators, __END__ and three lines of context on each side of
    every removed run, so that each file still parses and still reads
    as the source it came from.  No marker was written where lines were
    removed: a marker is a line of text dropped into whatever quoted
    region surrounds it, and one delimiter character in it -- a paren
    inside qw(...) -- would close that region early and change what the
    scanner sees.  Each file carries one header block instead.
    The reduction was verified against the full sources: the masked
    live code, the list of regex bodies and the list of string bodies
    are byte-identical before and after, and all three still report
    zero violations.
  - t/0011-corpus-excerpt.t (new): guards the properties that can be
    checked from inside the distribution -- a 256 KB cap on every
    corpus module, the excerpt header, and the agreement between the
    module named in that header and the package the file declares.
    The cap is the one that matters: a corpus is refreshed by copying a
    newer release over the old file, and the obvious way to do that is
    to copy the whole thing.
  - META.yml now conforms to the Meta 1.4 specification.  The
    meta-spec url was written as an https URL, and CPAN::Meta::Validator
    compares that value literally against the one the specification
    names, so the file was rejected with "Unknown META specification"
    and the CPANTS Kwalitee indicator
    meta_yml_conforms_to_known_spec failed.  The url is now the
    http form the specification itself uses.
  - Rebuilt the distribution metadata with pmake.bat 0.51, which
    writes the corrected url and declares the test-phase
    prerequisites separately from the runtime ones.
  - Updated the bundled t/lib/INA_CPAN_Check.pm to 0.44.  Its ok()
    now carries an ($;$) prototype, which forces a match handed to it
    into scalar context; without it an assertion written as
    ok($out =~ /.../, 'name') silently passed whenever the match
    failed.
  - t/0007-selfcheck.t: run_cmd() built a fully quoted command line and
    opened it as a pipe.  On Windows that line reaches cmd.exe, which
    strips the first and the last quote character of the line it is
    given, so a line beginning with the quoted absolute path in $^X was
    torn in half: the child never ran and run_cmd() returned the empty
    string for every call.  The whole file still reported success there
    only because of the list-context defect above.  It now runs the
    child with system() and a LIST, which uses no shell on either
    platform, and captures the output through redirected handles.
  - t/0007-selfcheck.t: SC05 asserted that the module reports a
    violation when scanned against itself, describing a qr//
    false positive as a documented limitation.  The module is in
    fact clean against itself, and the assertion only appeared to
    pass because of the list-context defect above.  It now asserts
    that the self-check finds no violations.

0.03  2026-05-31 JST (Japan Standard Time)

  - Updated the README DOCUMENTATION section to list the actual 21
    cheatsheet languages shipped in doc/ (it still named the eleven
    removed languages and omitted the eleven new ones).
  - Audited every cheatsheet against the module implementation and
    corrected the abbreviated "CHECKED CONSTRUCTS (static)" block used by
    the template-language cheatsheets, which was both wrong and stale:
      * removed "PerlIO layers" from the checked list -- PerlIO layers
        are runtime string values and are deliberately NOT checked (the
        perl58delta note now says so explicitly);
      * the Perl 5.8 entry now lists the "use constant { HASH }"
        multi-constant form that the module actually detects;
      * added the constructs that were missing entirely: *name{SLOT}
        (5.6), my sub / state sub (5.18), key/value slices (5.20),
        string bitwise operators and foreach reference aliasing (5.22),
        variable-length lookbehind (5.30), ^^ / ^^= and __CLASS__ (5.40),
        and any/all BLOCK LIST, my method, ->&name (5.42).
    The English and Japanese full cheatsheets already matched the
    implementation and were left as-is apart from the version bump.
  - Regenerated the doc/ cheatsheets as the standard 21-language set
    (BM BN EN FR HI ID JA KM KO MN MY NE SI TH TL TR TW UR UZ VI ZH).
    Eleven non-standard languages (AR CS DE ES IT NL PL PT RU SV UK)
    had been introduced by mistake and are removed; MANIFEST now matches.
  - Fixed UTF-8 double-encoding (mojibake) in twelve cheatsheets and a
    literal "EOF_UNDERLINE" placeholder left on the title-underline line
    of fourteen cheatsheets. All cheatsheets are now well-formed UTF-8.
  - Synchronised the version in README and in every cheatsheet to 0.03
    (they previously read 0.01 while the rest of the distribution was
    already 0.03).
  - Fixed eg/check_compatibility.pl: it wrapped check_source() in
    eval{}/if($@), but check_source() returns a violation list rather
    than dying, so every example printed "(not detected)". The examples
    now use the return-value API and report violations correctly.
  - Canonicalised the module header: added the distribution URL and a
    "use 5.00503;" minimum-version declaration.
  - Fixed a false positive in t/9020-perl5compat.t (P5): the
    "use VERSION >= 5.6" pattern stripped the leading zeros of 5.00503
    and flagged the legitimate minimum-version line. P5 now mirrors the
    module's own @BLACKLIST alternation.
  - Closed test blind spots:
      * t/9080-cheatsheets.t lists the standard 21 languages, adds a
        per-language guard against UTF-8 double-encoding, and declares
        its plan via plan_tests() so a failure also exits non-zero.
      * t/9070-examples.t now runs the demonstration script and asserts
        it reports violations (guarding the eval/$@ regression).
      * t/9060-readme.t H9 genuinely gates on the module version. The
        list-context idiom "ok($x =~ /.../, name)" used by the README
        checks let a failed match shift the test name into the truth
        slot and pass vacuously; the predicates are now scalar-forced.
      * t/9030-distribution.t language list updated to the standard set.

  - Fixed t/9050-pod.t failing on every Perl from 5.16 through 5.24.
    The test called Pod::Checker->parse_file(), a method that exists only
    in the Pod::Simple-based Pod::Checker (1.73+, shipped with Perl 5.26
    and later). The Pod::Parser-based versions bundled with those Perls
    (1.51 with 5.16; 1.60 with 5.18-5.24) provide only parse_from_file(),
    so parse_file() threw, the eval trapped it, and subtest G12 (errors)
    failed regardless of the actual POD content. The test now selects the
    available API via ->can('parse_file'); the Pod::Parser path routes
    messages to STDERR so the TAP stream is never corrupted.
  - Fixed three "unescaped <>" POD warnings reported by Pod::Checker 1.60
    (Perl 5.18-5.24), which would have failed subtest G13 once G12 ran:
    escaped the bare '>' in "VERSION >= 5.6" / "VERSION >= v5.6" as
    E<gt>=, and wrapped the match-position variables @-, $-[N] in C<>
    so no bare '>' remains in the CHECKED CONSTRUCTS list.
  - Fixed false positive: an empty pattern // used as the first argument
    of split/grep/map, e.g. "for my $ch (split //, $str)", is no longer
    reported as the Perl 5.10 defined-or operator. The source masker now
    recognizes split/grep/map as regex-introducing list operators, so a
    '/' immediately following one of these barewords is masked as the
    start of a pattern (a division can never follow these operators).
  - Added regression tests for the fix in t/0008-new-features.t.
  - Fixed false positive: a subroutine definition "sub any { ... }" or
    "sub all { ... }" is no longer reported as the Perl 5.42 "any BLOCK
    LIST" / "all BLOCK LIST" keyword operator. Matches preceded by "sub"
    or by an arrow operator are now skipped.
  - Fixed false positive: a 2-argument open() whose second argument
    contains a function call with a comma, e.g.
    open(FH, '>' . File::Spec->catfile($dir, $file)), is no longer
    mistaken for a 3-argument open(). Detection now counts only
    top-level commas inside the open(...) parentheses (paren-aware).
  - Added regression tests for both fixes in t/0008-new-features.t.
  - Fixed false positive: here-document bodies are now masked correctly.
    The source masker previously consumed only the sentinel-length of a
    heredoc body and could not handle two or more heredocs sharing one
    line (e.g. "$_ = <<'A'; $x eq <<'B';"). Heredoc bodies that contained
    post-5.005_03 example text (<<>>, $+[N], tr///r, subroutine
    signatures, //x, ...) leaked into the code scan and were wrongly
    reported. The masker now queues each heredoc sentinel at its <<
    operator and consumes all queued bodies, in order, at the end of the
    line.
  - Fixed false positive: a typeglob ("local (*s, $n) = @_;") or a
    subroutine sigil ("&s") immediately before a quote-like word is no
    longer mistaken for a quote-like operator (s///, m//, tr///, ...).
    Likewise a package-qualified call such as "mb::tr($x, 'A', '1')" is
    no longer read as the tr/// transliteration operator: the masker now
    refuses to start a quote-like operator when the preceding character
    is '*', '&', or ':'.
  - Fixed false positive: the old-style apostrophe package separator
    (e.g. "&jcode'tr(...)", equivalent to "&jcode::tr(...)") is now
    recognized. A sigil-led identifier followed by "'" and a word
    character is treated as a package separator, not the start of a
    single-quoted string, so the remainder of the line no longer desyncs.
  - Fixed false positive: an escaped backslash followed by a regex-escape
    letter inside a regex literal, e.g. the two-character literal "\\h"
    in "(?:(?![$bare_h])$x)" style replacement strings, is no longer
    mistaken for the Perl 5.10 \h horizontal-whitespace escape. Backslash
    pairs are neutralized before the regex-body scan.
  - Fixed false positive: a postfix-increment operator on a
    package-qualified or subscripted lvalue (e.g. "$mb::seq++") is no
    longer reported as a Perl 5.10 possessive quantifier; "++" in masked
    code is always the increment operator (a genuine possessive
    quantifier can occur only inside a regex, which is scanned separately).
  - Added regression tests for the above fixes in t/0008-new-features.t.
  - Fixed false positive: the empty-import form "use feature ()" is no
    longer reported as the Perl 5.10 "use feature" pragma. Used with the
    "BEGIN { $INC{'feature.pm'} = '' if $] < 5.010 }" stub it imports and
    loads nothing on Perl 5.005_03 (a no-op on every Perl version), so it
    is the standard cross-version guard idiom. A non-empty import list
    such as "use feature 'say'" or "use feature qw(...)" is still
    reported. Regression tests added in t/0002-check-source.t.

  - Added t/0009-clean-corpus.t: a clean-corpus regression test that
    scans the shipped library modules (lib/*.pm) of eight known
    5.005_03-compatible ina@CPAN distributions -- CSV-LINQ, LTSV-LINQ,
    JSON-LINQ, mb-JSON, Modern-Open, Perl7-Handy, BATsh and UTF8-R2 --
    and asserts zero violations for each. The sample sources live under
    t/corpus/. This guards against future BLACKLIST/masker changes
    introducing false positives against genuine clean code.

  - Added t/0010-clean-corpus-stack.t: a second clean-corpus regression
    test scanning the shipped library modules (lib/*.pm) of eight more
    5.005_03-compatible ina@CPAN distributions -- the Handy runtime
    stack (PSGI-Handy, HTTP-Handy, DB-Handy, HP-Handy) and the
    character-encoding libraries (Jacode, mb, Jacode4e,
    Jacode4e-RoundTrip) -- and asserting zero violations for each (12
    module files). The sample sources live under t/corpus-stack/, a
    sibling of t/corpus/ so the 0009 walker does not descend into it.
    Three of the sources (mb.pm, Jacode4e.pm, Jacode4e/RoundTrip.pm) are
    multibyte (MBCS); they are read with binmode and exercise the masker
    against large, real-world non-ASCII code.
  - t/9010-encoding.t: the t/corpus and t/corpus-stack trees are now
    exempted from the encoding-hygiene checks, the same kind of exemption
    already granted to doc/. External corpus sources are stored byte-exact
    and are not held to OrDie's own US-ASCII house rule. This also avoids
    a false positive in the C2 (trailing-whitespace) check: a legacy
    multibyte source whose double-byte character ends in 0x20 or 0x09 just
    before a newline would otherwise be flagged by the byte-pattern
    /[ \t]+\n/ even though no real trailing whitespace exists. The corpus
    trees are matched directly from the MANIFEST list with
    !m{^t/corpus(?:-stack)?/}; their 5.005_03 compatibility is validated
    by t/0009-clean-corpus.t and t/0010-clean-corpus-stack.t.
  - t/9020-perl5compat.t: the corpus exemption regex was widened from
    [/\\]corpus[/\\] to [/\\]corpus(?:-stack)?[/\\] so that the sibling
    t/corpus-stack tree is excluded as well. Without this the external
    samples (e.g. HTTP::Handy and mb, which use a scalar filehandle in
    open()) were wrongly held to this distribution's bareword-filehandle
    (P14) house rule.

0.02  2026-05-29 JST (Japan Standard Time)

  - Fixed malformed POD interior sequences (C<<<>>>, C<<<~>) that Pod::Checker 1.51-1.73 flagged as errors on perl 5.16-5.24

0.01  2026-05-24 JST (Japan Standard Time)

  - original version
