Skip to content

xaml: XAML105 cross-file Source= merged-dictionary key shadowing#135

Merged
PhysShell merged 3 commits into
mainfrom
claude/oun-net-html-analyzer-drff2t
Jun 27, 2026
Merged

xaml: XAML105 cross-file Source= merged-dictionary key shadowing#135
PhysShell merged 3 commits into
mainfrom
claude/oun-net-html-analyzer-drff2t

Conversation

@PhysShell

@PhysShell PhysShell commented Jun 26, 2026

Copy link
Copy Markdown
Owner

What

Closes the documented deferred tail of XAML105 (merged from #130). The in-file rule only sees inline merged dictionaries; this adds a project-level pass that resolves <MergedDictionaries> Source= includes to real files and flags an x:Key that collides across scopes when at least one is an external dictionary.

The classic case it catches: a theme dictionary silently shadowing — or being shadowed by — an app dictionary's key. It's order-dependent (last merged wins, primary beats merged) and completely invisible to a single-file pass.

How

  • _resolve_source handles the real WPF Source= forms: relative (Themes/Colors.xaml), app/site-root (/Colors.xaml), and pack/component (pack://application:,,,/Asm;component/Themes/Colors.xaml or the bare /Asm;component/...). Anything that escapes the scanned target, carries an http(s)/siteoforigin scheme, or doesn't land on an existing scanned file returns Nonean unresolved include can never manufacture a false collision (the conservative half).
  • analyze_cross_file runs outside the per-file RULES loop because it needs every parsed tree + their paths at once. It is disjoint from the in-file rule by construction: it emits only when an external file participates AND the host's own primary/inline scopes don't already collide (< 2 distinct in-file scopes for that key) — so a host the in-file rule already flags is never double-reported. Findings anchor at the host include site, at the same path the host's other findings use.
  • One level deep only (nested merges inside a referenced file aren't followed) — the FP-safe bound.

Tests

xaml_check --selftest (now 61 checks) covers: primary+external collision (with message + anchor assertions), two-external collision, pack-form resolution, and the negatives — distinct keys, a Source resolving to no file, an http(s) Source, and the in-file-disjoint guard. End-to-end verified through run_xaml_check (SARIF emitted at the host include site naming the resolved external dictionary).

Taxonomy needs no change (XAML105 already mapped → cat 9 merged-dictionary-shadowing). Design note + audit README updated to mark cross-file done.

🤖 Generated with Claude Code


Generated by Claude Code

Summary by CodeRabbit

  • New Features

    • Added cross-file XAML resource analysis to detect merged-dictionary key collisions across Source="..." inclusions, including common Source reference forms when they resolve to scanned markup files.
  • Tests

    • Added a dedicated cross-file XAML105 test suite covering resolution success, skip conditions for unresolved/unsupported sources, line-anchored reporting, and prevention of duplicate findings.
  • Documentation

    • Updated the analyzer design notes and the audit README status text to reflect that XAML105 now includes both in-file and cross-file key shadowing behavior.

Closes the documented deferred tail of XAML105. The in-file rule only sees
inline merged dictionaries; this adds a project-level pass that resolves
<MergedDictionaries> Source= includes to real files and flags an x:Key that
collides across scopes when at least one is an external dictionary — the
classic case of a theme dictionary silently shadowing (or being shadowed by)
an app dictionary's key, order-dependent and invisible to a single-file pass.

- _resolve_source handles relative, app-root (/Colors.xaml) and pack
  (;component/) Source forms; anything that escapes the target, carries an
  http(s)/siteoforigin scheme, or doesn't land on a scanned file is skipped —
  an unresolved include can never manufacture a false collision.
- analyze_cross_file runs outside the per-file RULES loop (it needs every
  parsed tree + paths at once) and is disjoint from the in-file rule by
  construction: it emits only when an external file participates AND the host's
  own primary/inline scopes don't already collide, so a host the in-file rule
  flags is never double-reported. Findings anchor at the host include site.
- Selftests: primary+external, two externals, pack-form resolution, and the
  negatives (distinct keys, missing file, http Source, in-file-disjoint).

Docs (design note + audit README) updated to mark cross-file done.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015zoG4YNCUf7RA5r8GAXMmb
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: be3d8ef0-8d27-44e0-b6be-d3a7ee798e50

📥 Commits

Reviewing files that changed from the base of the PR and between fb0025f and f958ff1.

📒 Files selected for processing (1)
  • audit/static/tools/xaml_check.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • audit/static/tools/xaml_check.py

📝 Walkthrough

Walkthrough

The XAML analyzer now runs a cross-file XAML105 pass that resolves ResourceDictionary Source= entries to scanned XAML/AXAML files, compares keyed resources across host and external dictionaries, and updates the runner, tests, and design/status notes.

Changes

XAML105 cross-file shadowing

Layer / File(s) Summary
Source resolution and collision analysis
audit/static/tools/xaml_check.py
_resolve_source, _external_keys, and analyze_cross_file map merged-dictionary Source= values to scanned files, collect keyed resources, and emit XAML105 for cross-file key collisions.
Runner wiring for the cross-file pass
audit/static/tools/xaml_check.py
run_xaml_check stores parsed trees for later reuse and calls analyze_cross_file after the per-file rules finish.
Self-tests and notes
audit/static/tools/xaml_check.py, docs/notes/xaml-analyzer-design.md, audit/README.md
The self-test suite adds cross-file XAML105 cases, and the design/status docs mark XAML105 cross-file Source= shadowing as implemented.

Sequence Diagram(s)

sequenceDiagram
  participant run_xaml_check
  participant analyze_cross_file
  participant _resolve_source
  participant _external_keys
  participant parsed trees

  run_xaml_check->>analyze_cross_file: parsed trees and root path
  analyze_cross_file->>parsed trees: index host and external XAML files
  analyze_cross_file->>_resolve_source: resolve Source="..." include
  analyze_cross_file->>_external_keys: read keyed resources from external dictionary
  analyze_cross_file->>run_xaml_check: emit XAML105 findings
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • PhysShell/Own.NET#114: Introduces the build-free Phase 1 XAML analyzer that this PR extends with cross-file XAML105 handling.
  • PhysShell/Own.NET#115: Adds the parse-once per-file tree collection that run_xaml_check now reuses for the later cross-file pass.
  • PhysShell/Own.NET#130: Implements the in-file XAML105 merged-dictionary shadowing logic that the new cross-file pass avoids double-reporting against.

Poem

A rabbit hopped by moonlit streams,
to chase Source= paths and threaded dreams.
Two keys could shadow, near or far,
now scanned by hop and file-born star.
🐇✨

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: XAML105 cross-file Source= merged-dictionary key shadowing.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/oun-net-html-analyzer-drff2t

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f01419c363

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread audit/static/tools/xaml_check.py Outdated
Codex review: stripping the 'Lib;component/' prefix and probing
target_root/Themes/Colors.xaml could match an unrelated same-named file in a
multi-project target — the app's own Themes/Colors.xaml instead of Lib's —
inventing a false XAML105 collision and breaking the "skip rather than guess"
contract.

A pack '/Asm;component/<path>' Source now resolves <path> against the named
assembly's project directory, located by a unique <Asm>.csproj (indexed once
per run). When the assembly maps to no project, or to more than one (ambiguous),
the include is skipped rather than guessed. Relative and app-root forms are
unchanged. Added selftests: pack-via-csproj resolution, a multi-project case
that must resolve to the named assembly's dictionary, and the FP guard where the
named assembly lacks the file (must not fall back to another project's copy).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015zoG4YNCUf7RA5r8GAXMmb
@PhysShell

Copy link
Copy Markdown
Owner Author

@CodeRabbit

@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown

@PhysShell On it — I'll review the changes in #135.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@audit/static/tools/xaml_check.py`:
- Around line 590-607: The cross-file XAML105 finding is still being anchored to
the earliest resource origin instead of the include site when a host key
collides with an external dictionary. In the XAML105 aggregation logic in
xaml_check.py, update the anchor selection in the block that builds the
XamlFinding so it prefers the include-site line from the external entry set (the
child.line captured in occ) rather than min(by_origin.values()). Also adjust the
selftest to assert the exact include line for the primary+external collision
case, so the SARIF location matches the contract.
- Around line 499-503: The Source URI resolution in xaml_check.py is too
permissive for plain relative paths, because the fallback to target-root can
incorrectly treat a missing include as a match and trigger false XAML105
collisions. Update the URI handling logic in the relative-path branch so that
plain relative Source values are resolved only against the containing XAML/base
context (the host_dir-based lookup) and do not fall back to troot; keep the
app-root behavior in the same resolution flow but remove the repo-root fallback
for non-absolute relative URIs. Verify the change in the Source
parsing/resolution path where rel and bases are computed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: bbbe5ee2-c24d-43c4-97e0-b9c59ff310b5

📥 Commits

Reviewing files that changed from the base of the PR and between ce2fee3 and fb0025f.

📒 Files selected for processing (3)
  • audit/README.md
  • audit/static/tools/xaml_check.py
  • docs/notes/xaml-analyzer-design.md

Comment thread audit/static/tools/xaml_check.py Outdated
Comment thread audit/static/tools/xaml_check.py
…ive resolve

Two CodeRabbit review findings on the cross-file pass:

- Anchor: min(by_origin.values()) picked the primary resource line whenever the
  host key sat above the Source= include, contradicting the rule's own "anchor
  at the host include site" contract and pointing SARIF at the wrong line for
  primary+external collisions. Now anchors at the earliest external include line
  (the any-external guard makes that set non-empty). Selftest tightened to assert
  the exact include line (4) rather than just >= 1.
- Relative Source resolution: WPF resolves a plain relative Source against the
  containing file's base URI only, with no application-root fallback. The extra
  target-root probe could map a missing include onto an unrelated same-named file
  and invent a collision. Relative now resolves against host_dir only; app-root
  ("/X") and pack ";component/" forms are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015zoG4YNCUf7RA5r8GAXMmb
@PhysShell
PhysShell merged commit c624441 into main Jun 27, 2026
26 checks passed
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.

2 participants