Skip to content

Fix value property to return undefined for nodes without value concept#260

Merged
bartveneman merged 2 commits into
mainfrom
claude/laughing-hawking-opc13h
Jun 24, 2026
Merged

Fix value property to return undefined for nodes without value concept#260
bartveneman merged 2 commits into
mainfrom
claude/laughing-hawking-opc13h

Conversation

@bartveneman

Copy link
Copy Markdown
Member

Summary

Fixed a regression where certain CSS node types were incorrectly returning null instead of undefined for their value property. This distinction is semantically important: null means "has a value, but it is absent", while undefined means "value is not a property of this node type".

Key Changes

  • Updated CSSNode.value getter in src/css-node.ts to explicitly return undefined for node types that don't have a value concept, rather than attempting to read from the arena value fields
  • Added comprehensive regression tests in src/api.test.ts covering 20+ node types that should return undefined for their value property, including:
    • Structural nodes: StyleSheet, Rule, Block, SelectorList, Selector
    • Selector types: TypeSelector, ClassSelector, IdSelector, PseudoClassSelector, PseudoElementSelector, UniversalSelector, Combinator, Nth
    • Value nodes: Value, Identifier, Hash, String
    • At-rule nodes: Atrule, MediaQuery
    • Other nodes: Raw

Implementation Details

The fix adds an explicit type check that returns undefined early for all node types except the five that actually store values in the arena:

  • DECLARATION
  • FUNCTION
  • ATTRIBUTE_SELECTOR
  • SUPPORTS_QUERY
  • PRELUDE_SELECTORLIST

This ensures the correct semantic distinction between nodes that have no value concept (undefined) and nodes that have a value but it's empty (null).

https://claude.ai/code/session_01KETKttcckhFQtg8JzjxvuU

…lue concept

Node types like SelectorList, TypeSelector, Selector, Block, etc. have no
value property in their type definitions. The value getter was falling through
to the arena lookup and returning null (length === 0) for these nodes when it
should return undefined, since null signals "has a value field but it is absent"
while undefined signals "value is not a property of this node type at all".

Added a guard in the value getter so only DECLARATION, FUNCTION,
ATTRIBUTE_SELECTOR, SUPPORTS_QUERY, and PRELUDE_SELECTORLIST reach the arena
value_start/value_length lookup. All other types now correctly return undefined.

Added 20 regression tests covering the reported node types and representatives
from every category (structural, selector, value, at-rule).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KETKttcckhFQtg8JzjxvuU
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Duplicate Dependencies (found: 35, threshold: 1)

📦 Package 📋 Versions
tinybench
2 versions
  • root@
    • @vitest/coverage-v8@4.1.9
      • vitest@4.1.9
        • tinybench@2.9.0

  • root@
    • tinybench@6.0.2

@babel/helper-validator-identifier
2 versions
  • root@
    • @vitest/coverage-v8@4.1.9
      • magicast@0.5.2
        • @babel/parser@7.29.3
          • @babel/types@7.29.0
            • @babel/helper-validator-identifier@7.28.5

  • root@
    • tsdown@0.22.3
      • ...
        • @babel/parser@8.0.0
          • @babel/types@8.0.0
            • @babel/helper-validator-identifier@8.0.2

js-tokens
2 versions
  • root@
    • @vitest/coverage-v8@4.1.9
      • ast-v8-to-istanbul@1.0.0
        • js-tokens@10.0.0

  • root@
    • tailwindcss@2.2.19
      • cosmiconfig@7.1.0
        • parse-json@5.2.0
          • @babel/code-frame@7.27.1
            • js-tokens@4.0.0

@babel/parser
2 versions
  • root@
    • @vitest/coverage-v8@4.1.9
      • magicast@0.5.2
        • @babel/parser@7.29.3

  • root@
    • tsdown@0.22.3
      • rolldown-plugin-dts@0.26.0
        • @babel/generator@8.0.0
          • @babel/parser@8.0.0

@babel/types
2 versions
  • root@
    • @vitest/coverage-v8@4.1.9
      • magicast@0.5.2
        • @babel/parser@7.29.3
          • @babel/types@7.29.0

  • root@
    • tsdown@0.22.3
      • rolldown-plugin-dts@0.26.0
        • @babel/generator@8.0.0
          • @babel/parser@8.0.0
            • @babel/types@8.0.0

@babel/helper-string-parser
2 versions
  • root@
    • @vitest/coverage-v8@4.1.9
      • magicast@0.5.2
        • @babel/parser@7.29.3
          • @babel/types@7.29.0
            • @babel/helper-string-parser@7.27.1

  • root@
    • tsdown@0.22.3
      • ...
        • @babel/parser@8.0.0
          • @babel/types@8.0.0
            • @babel/helper-string-parser@8.0.0

@emnapi/core
3 versions
  • root@
    • @vitest/coverage-v8@4.1.9
      • ...
        • rolldown@1.0.3
          • @rolldown/binding-wasm32-wasi@1.0.3
            • @emnapi/core@1.10.0

  • root@
    • knip@6.18.0
      • oxc-resolver@11.21.3
        • @oxc-resolver/binding-wasm32-wasi@11.21.3
          • @emnapi/core@1.11.0

  • root@
    • knip@6.18.0
      • oxc-parser@0.137.0
        • @oxc-parser/binding-wasm32-wasi@0.137.0
          • @emnapi/core@1.11.1

@emnapi/wasi-threads
2 versions
  • root@
    • @vitest/coverage-v8@4.1.9
      • ...
        • @rolldown/binding-wasm32-wasi@1.0.3
          • @emnapi/core@1.10.0
            • @emnapi/wasi-threads@1.2.1

  • root@
    • knip@6.18.0
      • oxc-parser@0.137.0
        • @oxc-parser/binding-wasm32-wasi@0.137.0
          • @emnapi/core@1.11.1
            • @emnapi/wasi-threads@1.2.2

@emnapi/runtime
3 versions
  • root@
    • @vitest/coverage-v8@4.1.9
      • ...
        • rolldown@1.0.3
          • @rolldown/binding-wasm32-wasi@1.0.3
            • @emnapi/runtime@1.10.0

  • root@
    • knip@6.18.0
      • oxc-resolver@11.21.3
        • @oxc-resolver/binding-wasm32-wasi@11.21.3
          • @emnapi/runtime@1.11.0

  • root@
    • knip@6.18.0
      • oxc-parser@0.137.0
        • @oxc-parser/binding-wasm32-wasi@0.137.0
          • @emnapi/runtime@1.11.1

@rolldown/binding-wasm32-wasi
2 versions
  • root@
    • @vitest/coverage-v8@4.1.9
      • ...
        • vite@8.0.16
          • rolldown@1.0.3
            • @rolldown/binding-wasm32-wasi@1.0.3

  • root@
    • tsdown@0.22.3
      • rolldown@1.1.2
        • @rolldown/binding-wasm32-wasi@1.1.2

obug
2 versions
  • root@
    • @vitest/coverage-v8@4.1.9
      • obug@2.1.1

  • root@
    • tsdown@0.22.3
      • obug@2.1.3

picomatch
2 versions
  • root@
    • tailwindcss@2.2.19
      • chokidar@3.6.0
        • anymatch@3.1.3
          • picomatch@2.3.2

  • root@
    • @vitest/coverage-v8@4.1.9
      • vitest@4.1.9
        • @vitest/mocker@4.1.9
          • vite@8.0.16
            • picomatch@4.0.4

postcss-value-parser
2 versions
  • root@
    • tailwindcss@2.2.19
      • reduce-css-calc@2.1.8
        • postcss-value-parser@3.3.1

  • root@
    • tailwindcss@2.2.19
      • autoprefixer@10.4.23
        • postcss-value-parser@4.2.0

glob-parent
2 versions
  • root@
    • tailwindcss@2.2.19
      • chokidar@3.6.0
        • glob-parent@5.1.2

  • root@
    • tailwindcss@2.2.19
      • glob-parent@6.0.2

yaml
2 versions
  • root@
    • tailwindcss@2.2.19
      • cosmiconfig@7.1.0
        • yaml@1.10.3

  • root@
    • @vitest/coverage-v8@4.1.9
      • vitest@4.1.9
        • @vitest/mocker@4.1.9
          • vite@8.0.16
            • yaml@2.9.0

is-arrayish
2 versions
  • root@
    • tailwindcss@2.2.19
      • cosmiconfig@7.1.0
        • parse-json@5.2.0
          • error-ex@1.3.4
            • is-arrayish@0.2.1

  • root@
    • tailwindcss@2.2.19
      • color@4.2.3
        • color-string@1.9.1
          • simple-swizzle@0.2.4
            • is-arrayish@0.3.4

get-tsconfig
2 versions
  • root@
    • knip@6.18.0
      • get-tsconfig@4.14.0

  • root@
    • tsdown@0.22.3
      • rolldown-plugin-dts@0.26.0
        • get-tsconfig@5.0.0-beta.5

semver
2 versions
  • root@
    • @vitest/coverage-v8@4.1.9
      • istanbul-lib-report@3.0.1
        • make-dir@4.0.0
          • semver@7.8.0

  • root@
    • tsdown@0.22.3
      • semver@7.8.5

@oxc-project/types
2 versions
  • root@
    • @vitest/coverage-v8@4.1.9
      • ...
        • vite@8.0.16
          • rolldown@1.0.3
            • @oxc-project/types@0.133.0

  • root@
    • knip@6.18.0
      • oxc-parser@0.137.0
        • @oxc-project/types@0.137.0

rolldown
2 versions
  • root@
    • @vitest/coverage-v8@4.1.9
      • vitest@4.1.9
        • @vitest/mocker@4.1.9
          • vite@8.0.16
            • rolldown@1.0.3

  • root@
    • tsdown@0.22.3
      • rolldown@1.1.2

@rolldown/binding-android-arm64
2 versions
  • root@
    • @vitest/coverage-v8@4.1.9
      • ...
        • vite@8.0.16
          • rolldown@1.0.3
            • @rolldown/binding-android-arm64@1.0.3

  • root@
    • tsdown@0.22.3
      • rolldown@1.1.2
        • @rolldown/binding-android-arm64@1.1.2

@rolldown/binding-darwin-arm64
2 versions
  • root@
    • @vitest/coverage-v8@4.1.9
      • ...
        • vite@8.0.16
          • rolldown@1.0.3
            • @rolldown/binding-darwin-arm64@1.0.3

  • root@
    • tsdown@0.22.3
      • rolldown@1.1.2
        • @rolldown/binding-darwin-arm64@1.1.2

@rolldown/binding-darwin-x64
2 versions
  • root@
    • @vitest/coverage-v8@4.1.9
      • ...
        • vite@8.0.16
          • rolldown@1.0.3
            • @rolldown/binding-darwin-x64@1.0.3

  • root@
    • tsdown@0.22.3
      • rolldown@1.1.2
        • @rolldown/binding-darwin-x64@1.1.2

@rolldown/binding-freebsd-x64
2 versions
  • root@
    • @vitest/coverage-v8@4.1.9
      • ...
        • vite@8.0.16
          • rolldown@1.0.3
            • @rolldown/binding-freebsd-x64@1.0.3

  • root@
    • tsdown@0.22.3
      • rolldown@1.1.2
        • @rolldown/binding-freebsd-x64@1.1.2

@rolldown/binding-linux-arm-gnueabihf
2 versions
  • root@
    • @vitest/coverage-v8@4.1.9
      • ...
        • vite@8.0.16
          • rolldown@1.0.3
            • @rolldown/binding-linux-arm-gnueabihf@1.0.3

  • root@
    • tsdown@0.22.3
      • rolldown@1.1.2
        • @rolldown/binding-linux-arm-gnueabihf@1.1.2

@rolldown/binding-linux-arm64-gnu
2 versions
  • root@
    • @vitest/coverage-v8@4.1.9
      • ...
        • vite@8.0.16
          • rolldown@1.0.3
            • @rolldown/binding-linux-arm64-gnu@1.0.3

  • root@
    • tsdown@0.22.3
      • rolldown@1.1.2
        • @rolldown/binding-linux-arm64-gnu@1.1.2

@rolldown/binding-linux-arm64-musl
2 versions
  • root@
    • @vitest/coverage-v8@4.1.9
      • ...
        • vite@8.0.16
          • rolldown@1.0.3
            • @rolldown/binding-linux-arm64-musl@1.0.3

  • root@
    • tsdown@0.22.3
      • rolldown@1.1.2
        • @rolldown/binding-linux-arm64-musl@1.1.2

@rolldown/binding-linux-ppc64-gnu
2 versions
  • root@
    • @vitest/coverage-v8@4.1.9
      • ...
        • vite@8.0.16
          • rolldown@1.0.3
            • @rolldown/binding-linux-ppc64-gnu@1.0.3

  • root@
    • tsdown@0.22.3
      • rolldown@1.1.2
        • @rolldown/binding-linux-ppc64-gnu@1.1.2

@rolldown/binding-linux-s390x-gnu
2 versions
  • root@
    • @vitest/coverage-v8@4.1.9
      • ...
        • vite@8.0.16
          • rolldown@1.0.3
            • @rolldown/binding-linux-s390x-gnu@1.0.3

  • root@
    • tsdown@0.22.3
      • rolldown@1.1.2
        • @rolldown/binding-linux-s390x-gnu@1.1.2

@rolldown/binding-linux-x64-gnu
2 versions
  • root@
    • @vitest/coverage-v8@4.1.9
      • ...
        • vite@8.0.16
          • rolldown@1.0.3
            • @rolldown/binding-linux-x64-gnu@1.0.3

  • root@
    • tsdown@0.22.3
      • rolldown@1.1.2
        • @rolldown/binding-linux-x64-gnu@1.1.2

@rolldown/binding-linux-x64-musl
2 versions
  • root@
    • @vitest/coverage-v8@4.1.9
      • ...
        • vite@8.0.16
          • rolldown@1.0.3
            • @rolldown/binding-linux-x64-musl@1.0.3

  • root@
    • tsdown@0.22.3
      • rolldown@1.1.2
        • @rolldown/binding-linux-x64-musl@1.1.2

@rolldown/binding-openharmony-arm64
2 versions
  • root@
    • @vitest/coverage-v8@4.1.9
      • ...
        • vite@8.0.16
          • rolldown@1.0.3
            • @rolldown/binding-openharmony-arm64@1.0.3

  • root@
    • tsdown@0.22.3
      • rolldown@1.1.2
        • @rolldown/binding-openharmony-arm64@1.1.2

@rolldown/binding-win32-arm64-msvc
2 versions
  • root@
    • @vitest/coverage-v8@4.1.9
      • ...
        • vite@8.0.16
          • rolldown@1.0.3
            • @rolldown/binding-win32-arm64-msvc@1.0.3

  • root@
    • tsdown@0.22.3
      • rolldown@1.1.2
        • @rolldown/binding-win32-arm64-msvc@1.1.2

@rolldown/binding-win32-x64-msvc
2 versions
  • root@
    • @vitest/coverage-v8@4.1.9
      • ...
        • vite@8.0.16
          • rolldown@1.0.3
            • @rolldown/binding-win32-x64-msvc@1.0.3

  • root@
    • tsdown@0.22.3
      • rolldown@1.1.2
        • @rolldown/binding-win32-x64-msvc@1.1.2

tinyexec
2 versions
  • root@
    • @vitest/coverage-v8@4.1.9
      • vitest@4.1.9
        • tinyexec@1.1.2

  • root@
    • tsdown@0.22.3
      • tinyexec@1.2.4

💡 To find out what depends on a specific package, run: pnpm -r why example-package

⚠️ Package Size Increase

📦 Package 📏 Base Size 📏 Source Size 📈 Size Change
@projectwallace/css-parser 41 kB 41 kB +12 B

TypeScript correctly rejects .value access on types that omit the property
(StyleSheet, Rule, SelectorList, etc.) — that's the whole point. Tests that
verify the runtime getter returns undefined must cast through the base CSSNode
class, which has the generic value getter. Use a local get_value() helper to
avoid repeating the cast on every assertion.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KETKttcckhFQtg8JzjxvuU
@bartveneman bartveneman merged commit 355d077 into main Jun 24, 2026
8 checks passed
@bartveneman bartveneman deleted the claude/laughing-hawking-opc13h branch June 24, 2026 19:26
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