Skip to content

fix: stop corrupting violation URLs with position info#226

Merged
YusukeHirao merged 6 commits into
devfrom
fix/225-violation-url-position-leak
Jul 21, 2026
Merged

fix: stop corrupting violation URLs with position info#226
YusukeHirao merged 6 commits into
devfrom
fix/225-violation-url-position-leak

Conversation

@YusukeHirao

Copy link
Copy Markdown
Member

Summary

  • analyze-markuplint and analyze-textlint built Violation.url as
    `${url.href} (${line}:${col})`, corrupting the page URL that
    replaceAnalysisViolations resolves against content_items/url_refs.
    Any archive analyzed with either validator enabled (and at least one
    violation) could make analyze throw, since the corrupted URL can never
    resolve.
  • Both plugins now emit a clean url (matching analyze-axe) and report
    the position through Violation's new optional line/col fields.
  • analysis_violations gains nullable line/col columns. Existing
    archives — including ones whose analysis_violations table predates
    these columns, and legacy analysis/violations.json payloads still
    carrying the corrupted <url> (<line>:<col>) form — are repaired
    automatically the next time replaceAnalysisViolations runs (live
    analyze, or the backfill path), with no operator intervention.

Design note

Column additions to an existing table normally go through a version-gated
migration script. analysis_violations.line/col self-heals instead
(hasColumn + ALTER TABLE ADD COLUMN inside replaceAnalysisViolations
itself), because the columns are nullable, additive, and this function is
the table's only write path. ARCHITECTURE.md's invariants list now states
this as a general, narrow exception (not a one-off hack) so it isn't
mistaken for drift in a future review.

Test plan

  • yarn build
  • yarn lint
  • yarn test
  • New/updated unit tests cover: clean URL + line/col from both plugins,
    legacy corrupted-URL repair, explicit line/col taking priority over URL
    parsing, axe-style violations persisting with line/col as null,
    self-healing an analysis_violations table that predates the columns,
    and retargetLegacyFkTables (used by scripts/migrate-to-0.13.mjs)
    null-filling the same columns for pre-0.13 archives while still
    aborting loudly on any other unexpected missing column.

Closes #225

analyze-markuplint and analyze-textlint validators report a position
alongside each violation, but Violation had no field for it, so both
plugins concatenated it onto url instead (issue #225).
replaceAnalysisViolations resolves each violation's url against
content_items/url_refs and throws if any url can't be resolved. The
position suffix that analyze-markuplint/analyze-textlint used to append
to url never matches a real page url, so any archive analyzed with those
validators enabled could carry violations that can never resolve
(issue #225).

replaceAnalysisViolations now:
- accepts explicit line/col fields and persists them
- when line/col are absent, parses the legacy `<url> (<line>:<col>)`
  suffix back into a clean url plus line/col before resolving, repairing
  both freshly-analyzed and legacy-JSON-backfilled data
- self-heals analysis_violations tables provisioned before line/col
  existed, adding the nullable columns via hasColumn-guarded
  ALTER TABLE before rewriting rows, so no operator intervention is
  needed

create-adjunct-tables.ts gains the line/col columns for newly-created
tables. retarget-legacy-fk-tables.ts's copy-back (used by the pre-0.13
migration script) now null-fills columns on an explicit allowlist when
the staged table predates them, instead of aborting — safe only because
those columns cannot have held a value before they existed; any other
missing column still aborts loudly.
backfillAnalysisViolationsFromJson passes legacy analysis/violations.json
data straight through to replaceAnalysisViolations, whose accepted shape
now includes optional line/col (issue #225). No behavior change here —
the repair logic lives entirely in replaceAnalysisViolations.
eachPage built url as `${url.href} (${line}:${col})`, corrupting the page
URL that replaceAnalysisViolations resolves violations against, so any
markuplint violation could make analyze throw (issue #225).

url is now the plain page URL, matching analyze-axe; line/col are
reported through Violation's new dedicated fields instead.
eachPage built url as `${report.url} (${line}:${column})`, corrupting the
page URL that replaceAnalysisViolations resolves violations against, so
any textlint violation could make analyze throw (issue #225).

url is now the plain page URL, matching analyze-axe; line/col are
reported through Violation's new dedicated fields instead.
@YusukeHirao
YusukeHirao merged commit d594a95 into dev Jul 21, 2026
10 checks passed
@YusukeHirao
YusukeHirao deleted the fix/225-violation-url-position-leak branch July 21, 2026 06:30
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.

analyze-markuplint / analyze-textlint: Violation.url gets position info concatenated onto it, breaking URL-based resolution

1 participant