Skip to content

fix(copyright): drop Erlang format directives and sigil-keyed bindings - #1386

Merged
mstykow merged 1 commit into
mainfrom
fix/copyright-erlang-format-directives
Aug 21, 2026
Merged

fix(copyright): drop Erlang format directives and sigil-keyed bindings#1386
mstykow merged 1 commit into
mainfrom
fix/copyright-erlang-format-directives

Conversation

@mstykow

@mstykow mstykow commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Verifying erlang/otp at 6146f0df — after the four earlier rounds of copyright fixes — still surfaced five Provenant-only copyright/holder false positives across four Erlang files. ScanCode emits none of them. Both classes are Erlang syntax that the existing guards already had a predicate for, and in both cases the predicate was never reached.

  • A format directive standing in for the end year. lib/stdlib/scripts/update_deprecations:144, lib/stdlib/test/shell_docs_SUITE.erl:223, and erts/emulator/test/send_term_SUITE.erl:334 build a notice with io:format, so the source reads Copyright Ericsson AB 2020-~p. All Rights Reserved. and Copyright 2007, Ericsson AB.~n. fix(copyright): drop notice templates, tooling prose, and format strings #1385 already made ~ts/~n/~p/~w count as template markers, but it put that test inside the has_code_markers set that is_junk_copyright_code_fragment / is_junk_holder_code_fragment gate on && !has_copyright_year(trimmed) — and every one of these values carries a literal year in the template's own prefix (2020, 2021, 2007). The year exemption fired first and the directive test never ran. This is the same ordering hazard Greptile flagged on fix(copyright): drop notice templates, tooling prose, and format strings #1385 for the placeholder rule, in a second place.

    The directive test is now its own predicate, contains_format_control_directive, and it short-circuits ahead of the year exemption in both functions — the way contains_windows_versioninfo_token already does. A year inside a format string is part of the template rather than evidence of a notice, so it cannot excuse one. contains_regex_or_template_marker delegates to the new predicate rather than duplicating the regex, so its other (weaker) markers — \d, {{, a trailing $ — keep the year exemption they had.

  • A sigil-quoted map key rescuing a binding line. .github/scripts/ort-scanner.es:220 and :398 are Erlang map patterns: #{ ~"copyrights" := Copyrights, ~"licenses" := Licenses} = Summary,. fix(copyright): reject authors and holders that source lines disprove #1382's is_pattern_match_binding_line covers := lines, but it exempts any line whose quoted text mentions copyright — the guard that keeps a real notice := "Copyright (c) 2020 Acme Corp.". Erlang 27's ~"..." binary sigil made the map key a quoted string containing copyrights, so every one of these lines looked like an assigned notice.

    A quoted key name is not an assigned notice. has_quoted_copyright now tests quoted segments one at a time and ignores a segment that is a lone identifier-shaped token (copyrights, copyright_notice) — a notice always carries punctuation or a party name alongside its marker. Per-segment testing also closes a latent hole in the old whole-line accumulator, where two neighbouring strings could combine into a marker that neither of them contained ("copy" ++ "right").

Issues

  • Covers: erlang/otp benchmark verification follow-ups (copyright false positives).

Scope and exclusions

  • Included: the format-directive predicate and its two call sites in src/copyright/refiner/junk.rs, and has_quoted_copyright in src/copyright/detector/phases/postprocess.rs.
  • Explicit exclusions: is_notice_template_line and the structured-data/edoc value rules are untouched — a sibling PR owns them. No author-detection, license-index, or BENCHMARKS.md changes.

How to verify

  • Fetch the four affected files plus the two guard files and scan them; every remaining value should be a real notice.

    for p in lib/stdlib/scripts/update_deprecations lib/stdlib/test/shell_docs_SUITE.erl \
             erts/emulator/test/send_term_SUITE.erl .github/scripts/ort-scanner.es \
             lib/stdlib/src/io_lib.erl lib/stdlib/src/shell.erl; do
      mkdir -p "$(dirname "$p")"
      curl -sL "https://raw.githubusercontent.com/erlang/otp/6146f0df6794451472fac4735e694ec1f56b873e/$p" -o "$p"
    done
    provenant scan --json-pp - --copyright lib erts .github

    Before: Copyright Ericsson AB 2020-~p. All Rights Reserved. / holder Ericsson AB 2020-~p (update_deprecations:144), the same shape with 2021-~p (shell_docs_SUITE.erl:223), Copyright 2007, Ericsson AB.~n (send_term_SUITE.erl:334), and the two := lines with holder 'licenses Licenses Summary (ort-scanner.es:220, :398). After: each file reports only its own header notice with the holder Ericsson AB.

  • The guards are what bounds the blast radius, so they are worth poking at directly. A real dated notice must survive the directive rule (Copyright Ericsson AB 1996-2026. All Rights Reserved., Copyright Ericsson AB 2020-2024.), and a tilde that is not a directive must not condemn one (Copyright (c) 2020 ~ Acme Corp. — a ~ followed by a word character is not a control sequence, so ~pete in a URL is out of scope). lib/stdlib/src/io_lib.erl and lib/stdlib/src/shell.erl are the counterpart check: real Erlang sources with 8 and 43 tilde sequences and a genuine header, both of which must still report Copyright Ericsson AB 1996-2026 — the rule is per value, so directives elsewhere in the file are irrelevant. For the key-name rule, a quoted notice being assigned on a := line must survive whether or not it is dated, including on a sigil-keyed line (#{ ~"notice" := "Copyright Acme Corp. All rights reserved." },) and with an escaped quote inside the string.

Intentional differences from Python

  • ScanCode emits none of these five values, so this is a Provenant-only false-positive removal rather than a parity change.

Follow-up work

  • Created or intentionally deferred: two holders in ort-scanner.es survive and are separate mechanisms rather than this change regressing them — both were present before it. Area, Mappings (:399) is a holder whose own start line is the }, Area, Mappings) -> continuation, which carries neither a := nor a marker, so no line-scoped rule can see it; dropping the copyright it accompanied leaves it orphaned, which is a symptom of the raw-line filter keying on start_line alone. licenses deduplicate H T Found) (:476) is comment prose (... in their copyrights and licenses) glued onto the following function head. Both belong to their own changes.

Expected-output fixture changes

  • Files changed: none. The full copyright golden corpus (36 tests / 2452 fixtures in copyright_golden), post_processing_golden (24), and scanner_integration (22) all pass unchanged, as does cargo test --lib copyright (1254 tests). Scoping the directive rule to the format-control regex rather than hoisting all of contains_regex_or_template_marker above the year exemption is what keeps that true: the weaker markers in that predicate ( ?, a trailing $) would have been much likelier to condemn a real dated notice.

🤖 Generated with Claude Code

Two Provenant-only copyright/holder false-positive classes in erlang/otp
at 6146f0df, neither of which ScanCode emits.

An `io:format` control sequence proved a value was a template, but the
check sat inside the code-marker set that `!has_copyright_year` gates, so
a template carrying a literal year in its own prefix — `Copyright Ericsson
AB 2020-~p.` — was exempted as a real dated notice. The directive test is
now its own predicate and short-circuits ahead of the year exemption, the
way the Windows VERSIONINFO markers already do: a year inside a format
string is part of the template, not evidence of a notice.

`is_pattern_match_binding_line` kept any line whose quoted text mentioned
copyright, so Erlang's `~"copyrights" := Copyrights` map key rescued the
binding line it appears on. A quoted key name is not an assigned notice:
segments are now tested one at a time, and a lone identifier-shaped token
does not count.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Maxim Stykow <maxim.stykow@gmail.com>
@greptile-apps

greptile-apps Bot commented Aug 21, 2026

Copy link
Copy Markdown

Greptile Summary

The PR narrows two copyright false-positive paths in Erlang source.

  • Rejects copyright and holder templates containing Erlang io:format directives even when the template includes a literal year.
  • Distinguishes bare quoted map keys from quoted copyright notices on pattern-binding lines.
  • Adds regression coverage for the affected templates, sigil-keyed bindings, and legitimate notices that must survive.

Confidence Score: 5/5

The PR appears safe to merge with no concrete changed-code defect identified.

The new filters are narrowly scoped to Erlang format-control sequences and bare quoted identifiers, while the added regressions preserve dated notices, assigned notice strings, escaped quotes, and non-directive tildes.

Important Files Changed

Filename Overview
src/copyright/detector/phases/postprocess.rs Splits quoted text into individual segments and excludes bare identifier-shaped keys from the quoted-notice exemption.
src/copyright/detector/tests_false_positives.rs Adds end-to-end regressions for Erlang sigil-keyed map bindings and format-string false positives.
src/copyright/refiner/junk.rs Extracts the Erlang format-control predicate and applies it before the literal-year exemption for copyrights and holders.
src/copyright/refiner/tests.rs Verifies directive-bearing templates are rejected while ordinary dated notices and non-directive tildes remain accepted.

Reviews (1): Last reviewed commit: "fix(copyright): drop Erlang format direc..." | Re-trigger Greptile

@mstykow
mstykow merged commit 26de312 into main Aug 21, 2026
13 checks passed
@mstykow
mstykow deleted the fix/copyright-erlang-format-directives branch August 21, 2026 09:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant