Skip to content

fix(copyright): keep a trailing acronym-with-period holder (e.g. CERN.)#1322

Merged
mstykow merged 1 commit into
mainfrom
fix/copyright-trailing-acronym-holder
Jul 23, 2026
Merged

fix(copyright): keep a trailing acronym-with-period holder (e.g. CERN.)#1322
mstykow merged 1 commit into
mainfrom
fix/copyright-trailing-acronym-holder

Conversation

@mstykow

@mstykow mstykow commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Fixes a copyright-detection bug where the holder was dropped from a statement of the shape Copyright © <year> <ACRONYM>. when prose preceded the marker on the same line.
  • Root cause: an all-caps acronym with a sentence-final period (CERN.) lexes as a Pn token, whereas its period-less spelling (CERN) lexes as Caps. A bare Caps reduces to a NameCaps node and is absorbed onto a year-only copyright via the strong_first path in should_start_absorbing; a bare trailing Pn matched no absorption branch and was dropped, yielding a holder-less Copyright (c) <year>.
  • Isolated lines were rescued only by the line-anchored extend_year_only_copyrights_with_trailing_text repair (regex anchored at ^copyright), which cannot fire when prose precedes the copyright — so any real-world file with the copyright mid-line lost the holder.
  • Observed on flask tests/test_cli.py @ 22d924701a6ae2e4cd01e9a15bbaf3946094af65 (... Revised BSD License. Copyright © 2015 CERN.): reported Copyright © 2015 with no holder; now reports Copyright © 2015 CERN. with holder CERN.
  • Fix: in should_start_absorbing, absorb a lone trailing Pn leaf on the same line as a holder-less, year-bearing copyright clause, mirroring the existing Caps/NameCaps treatment so the two source-faithful spellings behave consistently. Scoped to a year-only clause so it cannot swallow ordinary prose.

Scope and exclusions

  • Included: general absorption fix for a trailing Pn acronym holder; detector regression tests.
  • Explicit exclusions: no lexer retagging of CERN. (kept as Pn, which is still correct in name contexts such as Name Pn); no change to the line-anchored repair heuristic.

How to verify

  • Scan a file whose copyright is preceded by prose on the same line, e.g. a line containing its Revised BSD License. Copyright © 2015 CERN. with --copyright; observe the holder CERN is captured and the statement keeps CERN..
  • Edge cases exercised manually: Copyright © 2015 CERN. All rights reserved. (rights-reserved stripped from holder, holder still CERN); Copyright © 2015 U.S. Government (multi-token name unaffected); a bare year-only Copyright © 2015 after prose (correctly yields no holder — no over-capture of surrounding prose).

Expected-output fixture changes

  • Files changed: none. No golden/expected fixtures shifted; the existing copyright golden suite (copyright_golden), scanner_copyright_credits, post_processing_golden, and the copyright:: unit tests all pass unchanged. New coverage is added as unit tests in src/copyright/detector/tests.rs.

A single all-caps acronym carrying a sentence-final period (`CERN.`) lexes
as a `Pn` token, unlike its period-less spelling `CERN`, which lexes as
`Caps`. A bare `Caps` acronym reduces to a `NameCaps` node and is absorbed
onto a year-only copyright via the `strong_first` path, but a bare trailing
`Pn` matched no absorption branch, so the holder was dropped — leaving a
holder-less `Copyright (c) <year>`.

Isolated lines were rescued only by the line-anchored
`extend_year_only_copyrights_with_trailing_text` repair (regex `^copyright`),
which cannot fire when prose precedes the copyright. On flask
`tests/test_cli.py` (`... Revised BSD License. Copyright © 2015 CERN.`) that
produced `Copyright (c) 2015` with no holder.

Absorb a lone trailing `Pn` leaf on the same line as a holder-less,
year-bearing copyright clause, mirroring the existing `Caps`/`NameCaps`
treatment so the two source-faithful spellings behave consistently. Scoped
to a year-only clause so it cannot swallow ordinary prose; a genuine
year-only notice still yields no holder.

Adds detector regression tests covering the flask shape plus `Example Corp`,
a diacritic holder (`Björn`), and a year-only negative guard.

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

greptile-apps Bot commented Jul 23, 2026

Copy link
Copy Markdown

Greptile Summary

Fixes copyright extraction for same-line, trailing acronym holders with sentence-final periods.

  • Absorbs a standalone Pn token following a holder-less, year-bearing copyright clause.
  • Adds regression coverage for CERN., existing holder variants, and genuinely holder-less notices.

Confidence Score: 5/5

The PR appears safe to merge with no actionable defects identified.

The new absorption path is constrained to same-line Pn tokens after holder-less year-bearing clauses, and the added tests verify both the intended acronym recovery and preservation of genuinely holder-less output.

Important Files Changed

Filename Overview
src/copyright/detector/tree_walk/copyright/absorb.rs Narrowly extends trailing-token absorption to same-line Pn leaves after year-only copyright clauses.
src/copyright/detector/tests.rs Adds focused regression and guard tests covering acronym holders and holder-less notices.

Reviews (1): Last reviewed commit: "fix(copyright): keep a trailing acronym-..." | Re-trigger Greptile

@mstykow
mstykow merged commit 279cd86 into main Jul 23, 2026
13 checks passed
@mstykow
mstykow deleted the fix/copyright-trailing-acronym-holder branch July 23, 2026 19:15
mstykow added a commit that referenced this pull request Jul 23, 2026
Rebased onto main (#1321 deps-as-components + #1322 copyright fix) and
regenerated: express 1->45, ripgrep 11->114, flask 4->54 components, 0 dangling
dependsOn refs. `generate-sbom-examples --check` passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Maxim Stykow <maxim.stykow@gmail.com>
mstykow added a commit that referenced this pull request Jul 23, 2026
…kio) (#1318)

* feat(xtask): add generate-sbom-examples and checked-in SBOM examples

Add a durable `generate-sbom-examples` xtask that produces the SBOM
examples under `examples/sbom/`. For each verified target it shallow-
fetches the repo at a pinned commit through the shared repo cache, runs
the current-build Provenant with `scan --license --package --copyright`,
and writes SPDX tag-value plus CycloneDX JSON documents and a short
provenance README. The embedded tool version is pinned to the workspace
package version so committed output is deterministic.

`--check` regenerates in place and diffs against the committed files
after normalizing every per-run volatile field (SPDX `Created`, CycloneDX
`serialNumber`/`metadata.timestamp`, the embedded tool version across
SPDX/CycloneDX/READMEs, and any random package-UID suffix); tool versions
are normalized so a routine release bump does not fail the check before
regeneration.

Wire the `--check` into CI alongside the other generated-artifact checks
and fold regeneration into `release.sh` after the version bump so the
examples refresh on each release. Exclude the generated files from
prettier (matching the OpenAPI-doc precedent).

Targets (ripgrep 15.2.0, express v5.2.1, flask 3.1.3) were verified
against ScanCode with `compare-outputs`: no medium-or-major license,
copyright, or package regressions.

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

* chore(examples): regenerate SBOM examples with dependency components

Rebased onto main (#1321 deps-as-components + #1322 copyright fix) and
regenerated: express 1->45, ripgrep 11->114, flask 4->54 components, 0 dangling
dependsOn refs. `generate-sbom-examples --check` passes.

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

* refactor(xtask): make SBOM examples on-demand; add tokio; stamp 1.0.1

The SBOM examples are illustrative artifacts, not golden fixtures, so
regenerate them on demand instead of drift-checking on every change:

- Drop the generator's --check mode, all field normalization, the per-PR CI
  "Check SBOM examples" step (~14 min/PR, re-cloned + rescanned four repos),
  and the release.sh regen hook.
- Write mode now prunes any examples/sbom/ directory not in TARGETS, so
  regeneration is authoritative (addresses the obsolete-outputs review note).
- Add tokio (tokio-1.53.1) as a fourth verified target: a Cargo workspace that
  showcases monorepo-aware, multi-package inventory. Verified CLEAN vs ScanCode.
- Honor an explicit PROVENANT_BUILD_VERSION over the workspace default and stamp
  the examples 1.0.1 — the release the complete-inventory SBOM behavior ships in.
- READMEs now auto-compute component/dependency counts from the generated
  CycloneDX and list the per-target, source-faithful differences from ScanCode.

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

---------

Signed-off-by: Maxim Stykow <maxim.stykow@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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