Skip to content

fix: improve no-unreachable-media-conditions for nested at-rules#98

Merged
bartveneman merged 5 commits intomainfrom
claude/detect-impossible-atrules-mRQOr
Mar 26, 2026
Merged

fix: improve no-unreachable-media-conditions for nested at-rules#98
bartveneman merged 5 commits intomainfrom
claude/detect-impossible-atrules-mRQOr

Conversation

@bartveneman
Copy link
Copy Markdown
Member

@bartveneman bartveneman commented Mar 26, 2026

closes #97

Summary

This PR improves no-unreachable-media-conditions that detects when nested @media and @container queries create contradictory conditions that make the inner rule unreachable.

Key Changes

  • **improve rule: no-unreachable-media-conditions

    • Detects contradictory bounds when nesting @media or @container at-rules
    • Walks up the ancestor chain to collect bounds from parent at-rules of the same type
    • Reports violations on the inner (problematic) rule, not the outer one
    • Handles both min/max syntax (min-width: 100px) and range syntax (width >= 100px)
    • Skips analysis when prelude is too complex (not/or operators)
  • Comprehensive test suite

    • 30+ test cases covering valid and invalid scenarios
    • Tests for @media with width/height features
    • Edge cases: mixed units, equal bounds, multi-level nesting, independent violations
  • Utility function: collect_bounds_from_prelude (src/utils/media-conditions.ts)

    • Parses at-rule preludes and extracts numeric bounds from features
    • Returns null for complex queries that can't be safely analyzed
    • Returns empty array or array of bounds depending on feature presence
    • Supports both @media and @container at-rules

Implementation Details

  • Container name handling: For @container rules, only ANDs bounds when both parent and child use the same non-empty container name. Unnamed containers are treated independently since they apply to different ancestor container elements.
  • Contradiction detection: Reuses existing find_contradictory_feature utility to identify impossible conditions
  • Avoidance of double-reporting: Skips analysis if the current rule or ancestors are already contradictory (handled by no-unreachable-media-conditions)
  • Complexity handling: Safely aborts analysis for comma-separated queries and not/or operators to prevent false positives

https://claude.ai/code/session_019rwTo8FJ5pULsuchZ3f2PN

Detects when nested @media or @container rules create an impossible AND
condition across rule boundaries — e.g. an outer min-width: 1000px combined
with an inner max-width: 500px is equivalent to a single rule with both
conditions, which can never match.

The detection logic is extracted into a new shared utility
collect_bounds_from_prelude() in media-conditions.ts, and the rule reuses
find_contradictory_feature() from the same module to avoid duplicate
detection code.

Edge cases handled:
- Comma-separated (OR) ancestor queries: skipped, too complex to AND
- not/or operators in any query: skipped
- @container unnamed nesting: skipped (each applies to different containers)
- @container named nesting: only flagged when both rules have the same name
- Already-contradictory inner rules: skipped, handled by no-unreachable-media-conditions
- Three-level deep conflicts: detected by accumulating ancestor bounds

https://claude.ai/code/session_019rwTo8FJ5pULsuchZ3f2PN
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Mar 26, 2026

Bundle Report

Changes will increase total bundle size by 3.42kB (5.37%) ⬆️⚠️, exceeding the configured threshold of 5%.

Bundle name Size Change
stylelintPlugin-esm 67.05kB 3.42kB (5.37%) ⬆️⚠️

Affected Assets, Files, and Routes:

view changes for bundle: stylelintPlugin-esm

Assets Changed:

Asset Name Size Change Total Size Change (%)
index.mjs 3.42kB 62.69kB 5.76% ⚠️

Files in index.mjs:

  • ./src/rules/no-unreachable-media-conditions/index.ts → Total Size: 5.13kB

  • ./src/utils/media-conditions.ts → Total Size: 7.64kB

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Mar 26, 2026

Codecov Report

❌ Patch coverage is 93.44262% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.85%. Comparing base (a42d1fb) to head (2c42db3).

Files with missing lines Patch % Lines
src/utils/media-conditions.ts 82.60% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #98      +/-   ##
==========================================
- Coverage   98.15%   97.85%   -0.30%     
==========================================
  Files          34       34              
  Lines         920      981      +61     
  Branches      234      250      +16     
==========================================
+ Hits          903      960      +57     
- Misses         14       18       +4     
  Partials        3        3              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

claude added 4 commits March 26, 2026 12:20
Rather than a separate rule, the detection of impossible conditions across
nested @media rules now lives in no-unreachable-media-conditions alongside
the existing flat-rule detection.

Nested @media rules implicitly AND their conditions with ancestor @media rules,
so this pattern is equivalent to a single rule with both conditions:

  @media (min-width: 1000px) {
    @media (max-width: 500px) { ... }  ← now flagged
  }

A new rejected_nested message distinguishes these from intra-rule contradictions.
The shared collect_bounds_from_prelude() utility (added to media-conditions.ts)
handles prelude parsing for the ancestor chain traversal.

https://claude.ai/code/session_019rwTo8FJ5pULsuchZ3f2PN
Instead of bailing out when an ancestor has comma-separated queries (OR),
take the cartesian product across all nesting levels and flag only when
every possible combination is impossible.

  @media (min-width: 1000px), (min-width: 900px) {
    @media (max-width: 500px) { }  ← flagged: both branches impossible
  }

  @media screen, (min-width: 1000px) {
    @media (max-width: 500px) { }  ← not flagged: screen branch is fine
  }

collect_bounds_from_prelude now returns (Bound[] | null)[] — one entry per
comma-separated branch (null for branches containing `not`/`or`, which
remain too complex to analyse and are conservatively treated as satisfiable).

https://claude.ai/code/session_019rwTo8FJ5pULsuchZ3f2PN
…sible

Previously the nested check only reported when every cartesian-product
combination was contradictory. But a single impossible combination is
already a bug — that branch of the outer @media will never reach the inner
rule, regardless of whether other branches are fine:

  @media screen, (min-width: 1000px) {
    @media (max-width: 500px) {}  ← now flagged
  }
  ↑ (min-width: 1000px) ∧ (max-width: 500px) is impossible even though
    the screen branch is still reachable.

The ancestor-already-contradictory guard is also tightened: instead of
checking each ancestor branch individually, we check the combined ancestor
bounds for the specific combination path — this avoids noisy derivative
reports when a higher-level nesting already created the contradiction.

https://claude.ai/code/session_019rwTo8FJ5pULsuchZ3f2PN
- Cast node.parent as ContainerWithChildren | undefined: the PostCSS type
  includes Document in the union but Document nodes only appear in
  HTML-embedded CSS, never in regular stylesheet traversal.

- Replace map+spread in cartesian() with explicit loops using concat,
  fixing the oxlint no-map-spread performance rule.

https://claude.ai/code/session_019rwTo8FJ5pULsuchZ3f2PN
@bartveneman bartveneman changed the title Add no-unreachable-nested-atrule rule to detect contradictory nested queries fix: improve no-unreachable-media-conditions for nested at-rules Mar 26, 2026
@bartveneman bartveneman merged commit 23f2e62 into main Mar 26, 2026
4 of 5 checks passed
@bartveneman bartveneman deleted the claude/detect-impossible-atrules-mRQOr branch March 26, 2026 13:00
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.

Find impossible media/container conditions for nested atrules

3 participants