cataract 0.2.5 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 898883ee4a30ccd34c034b4799e7a43eccae7f79693a4309d7ac7ef130f21840
4
- data.tar.gz: 511eaf6d4a9fd1f1a601a999f496bc3493900daa2696d20bc1a6b97eaff60eae
3
+ metadata.gz: 6a6424d26e77e0dd4e0167324dc6190f03a9ca041e51fa9e61a78163f6fe312b
4
+ data.tar.gz: 7c538f41ebd8e93a53caed4a5f066eea076277c26174bff7d22837730f03d90d
5
5
  SHA512:
6
- metadata.gz: 59f87ff83a95fecb4e508a71e3055d8ebc53c23ad2d1316358e4a9e53ff8532c66b5686611e1706d6d95f1309428eda2e2df3b1884cbcb8d318dc866a256920b
7
- data.tar.gz: 85eb22a3be35e25d7cfd01fcbb2bd9148411e3cb4ee49ccfd808f57ec17a6a8c95c1dc56b29cb563cc9cc7523fbacfc4083a5953dcf3010c6cb14c872a48872c
6
+ metadata.gz: d2297f4139e8025ac39b9467eaa4bd0541683af7e4870813159c557df813f411d389b59f5b28adb9c46f5390aba73405b937e946d9097b943e0ba70f6cb37963
7
+ data.tar.gz: 0b4d9b6ec564fd674ad8558b7b4f309494c0ca12628947a8501b17ccb3567e1a2667dd0ea89f88ad82f688ac580122ec7e9505b8149433504ef40d923228b75b
@@ -18,7 +18,7 @@ jobs:
18
18
  strategy:
19
19
  fail-fast: false
20
20
  matrix:
21
- ruby: ['3.1', '3.2', '3.3', '3.4']
21
+ ruby: ['3.1', '3.2', '3.3', '3.4', '4.0']
22
22
 
23
23
  steps:
24
24
  - uses: actions/checkout@v4
@@ -37,8 +37,8 @@ jobs:
37
37
  env:
38
38
  COVERAGE: 1
39
39
 
40
- - name: Run debug test (Ruby 3.4 only)
41
- if: matrix.ruby == '3.4'
40
+ - name: Run debug test (Ruby 4.0 only)
41
+ if: matrix.ruby == '4.0'
42
42
  run: |
43
43
  bundle exec rake clean compile
44
44
  bundle exec ruby -Ilib:test test/test_stylesheet.rb
@@ -46,7 +46,7 @@ jobs:
46
46
  CONFIGURE_ARGS: --enable-debug
47
47
 
48
48
  - name: Upload coverage to Codecov
49
- if: runner.os == 'Linux' && matrix.ruby == '3.4'
49
+ if: runner.os == 'Linux' && matrix.ruby == '4.0'
50
50
  uses: codecov/codecov-action@v5
51
51
  with:
52
52
  fail_ci_if_error: false
@@ -54,7 +54,7 @@ jobs:
54
54
  verbose: true
55
55
 
56
56
  - name: Upload coverage report artifact
57
- if: runner.os == 'Linux' && matrix.ruby == '3.4'
57
+ if: runner.os == 'Linux' && matrix.ruby == '4.0'
58
58
  uses: actions/upload-artifact@v4
59
59
  with:
60
60
  name: coverage-report
@@ -69,7 +69,7 @@ jobs:
69
69
  - name: Set up Ruby
70
70
  uses: ruby/setup-ruby@v1
71
71
  with:
72
- ruby-version: '3.4'
72
+ ruby-version: '4.0'
73
73
  bundler-cache: true
74
74
 
75
75
  - name: Run rubocop
data/.gitignore CHANGED
@@ -10,6 +10,8 @@ ext/**/cataract.c
10
10
  # Compiled extensions
11
11
  lib/**/*.so
12
12
  lib/**/*.bundle
13
+ ext/**/*.bundle
14
+ ext/**/*.dSYM/
13
15
 
14
16
  # Makefile generated by extconf.rb
15
17
  ext/**/Makefile
@@ -29,19 +31,31 @@ Thumbs.db
29
31
  vendor/
30
32
  .bundle/
31
33
 
34
+ # Node (this is a Ruby gem; no JS toolchain is tracked, but guard against stray npm installs)
35
+ node_modules/
36
+
32
37
  # Test artifacts
33
38
  coverage/
34
39
  test/.benchmark_results/
35
- test/fuzz_last_input.css
36
- test/fuzz_crash_*.css
37
- test/fuzz_crash_*.log
40
+
41
+ # Fuzzer output (crash inputs, logs, recent-input dumps)
42
+ scripts/fuzzer/output/
38
43
 
39
44
  # Benchmark results
40
45
  benchmarks/.results/
41
46
  benchmarks/.benchmark_results/
42
47
 
48
+ # CSS analyzer example debug output
49
+ examples/css_analyzer/output/
50
+
43
51
  # Documentation (generated by YARD)
44
52
  docs/
45
53
  !docs/files/
46
54
  !docs/files/EXAMPLE.md
47
55
  .yardoc/
56
+
57
+ # Claude Code local project instructions (personal, not shared)
58
+ .claude/CLAUDE.md
59
+
60
+ .cache/
61
+ .cocci/
data/CHANGELOG.md CHANGED
@@ -1,4 +1,22 @@
1
- ## [Unreleased]
1
+ ## [0.3.0] - 2026-07-06
2
+
3
+ - Security/Breaking: `Stylesheet#load_uri` and `#load_file` now reject `file://` paths under `/etc/`, `/proc/`, `/sys/`, and `/dev/` by default (override with `dangerous_path_prefixes: []`). Previously these methods used a separate, unvalidated fetch implementation; they now reuse `ImportResolver` (the same validation and fetching `@import` resolution already uses), which also fixes `load_uri` not following HTTP redirects.
4
+ - Fix: rules nested 3+ levels deep, or nested inside a top-level `@media`/`@supports`/`@container`/`@scope` block, could serialize incorrectly (silently flattened, or printed as an unrelated top-level rule) in the pure Ruby backend.
5
+ - Fix: a bare type selector nested without `&` (invalid CSS nesting) no longer corrupts serialized output with unbalanced braces, in both the C and pure Ruby parsers.
6
+ - Fix: `@keyframes` parsing no longer pollutes the outer rule list with its own inner selectors.
7
+ - Fix: at-rules (`@keyframes`, `@font-face`, etc.) nested inside `@media` blocks now correctly keep their media context across parsing, merging (`add_block`, `concat`/`+`), `@import` resolution, and serialization - previously this could be lost, misattributed, or (combined with CSS nesting) raise `NoMethodError` in the pure Ruby serializer.
8
+ - Fix: `remove_rules!` and the `-` operator no longer desync media query ids from the rules that reference them.
9
+ - Fix: resolving `@import`s no longer leaves duplicate/colliding rule ids.
10
+ - Fix: a plain `@import "file.css";` (no media qualifier) no longer loses that file's own `@media` rules.
11
+ - Fix: `Stylesheet#concat`/`+` now merges the other stylesheet's media queries and selector-list groupings too - previously this silently dropped `h1, h2`-style grouping and corrupted media attribution.
12
+ - Fix: `@import` media queries with a type keyword (e.g. `"screen and (min-width: 500px)"`) no longer keep a stray `"and"` in the parsed conditions.
13
+ - Fix: `to_s`/`to_formatted_s` media filtering is now consistent between the two - both include base (non-media) rules when filtering to a specific media type; previously `to_s` excluded them while `to_formatted_s` included them.
14
+ - Fix: values containing `url(...;...)` no longer lose everything after the first semicolon when the rule also uses CSS nesting.
15
+ - Fix: `!important` now parses correctly with whitespace between `!` and `important`, per the CSS grammar.
16
+ - Fix: expanding `border`, `border-{top,right,bottom,left}`, `font`, `background`, and `list-style` shorthands (used when comparing rules for equality) no longer silently drops `!important`.
17
+ - Fix: `Declarations`/`Stylesheet` `#dup`/`#clone` now produce fully independent copies; previously mutating a copy could mutate the original.
18
+ - Performance: CSS selector specificity calculation is ~30% faster (no longer re-allocates an internal lookup array on every call).
19
+ - Performance: native (C extension) CSS flattening is ~1-5% faster (fewer string allocations when recreating shorthand properties).
2
20
 
3
21
  ## [0.2.5 - 2025-11-25]
4
22
 
data/Gemfile CHANGED
@@ -16,10 +16,10 @@ gem 'css_parser', '~> 1.0' # for benchmarking against
16
16
  gem 'minitest'
17
17
  gem 'minitest-spec'
18
18
  gem 'nokogiri' # for docs
19
- gem 'ruby-prof', require: false # for profiling
19
+ gem 'ruby-prof', require: false, platforms: :ruby # for profiling (MRI only)
20
20
  gem 'simplecov', require: false
21
21
  gem 'simplecov-cobertura', require: false
22
- gem 'stackprof', require: false # for profiling
22
+ gem 'stackprof', require: false, platforms: :ruby # for profiling (MRI only)
23
23
  gem 'webmock', '~> 3.0' # for testing URL loading
24
24
 
25
25
  gem 'overcommit', '~> 0.64', group: :development
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'erb'
4
+ require 'fileutils'
4
5
  require 'uri'
5
6
  require_relative '../../lib/cataract'
6
7
  require_relative 'analyzers/properties'
@@ -71,13 +72,18 @@ module CSSAnalyzer
71
72
  def save_parsed_css
72
73
  # Generate a unique filename based on source
73
74
  source_slug = @source.gsub(%r{[:/]}, '_').gsub(/[^a-zA-Z0-9_.-]/, '')
74
- filename = "parsed-css-#{source_slug}.css"
75
+
76
+ # Write next to this script (not the caller's cwd) so running the
77
+ # analyzer from repo root doesn't scatter debug output there.
78
+ output_dir = File.join(__dir__, 'output')
79
+ FileUtils.mkdir_p(output_dir)
80
+ path = File.join(output_dir, "parsed-css-#{source_slug}.css")
75
81
 
76
82
  # Serialize stylesheet to CSS
77
83
  css_output = @stylesheet.to_s
78
84
 
79
- File.write(filename, css_output)
80
- warn "Parsed CSS saved to #{filename} (#{@stylesheet.size} rules, #{css_output.length} bytes)"
85
+ File.write(path, css_output)
86
+ warn "Parsed CSS saved to #{path} (#{@stylesheet.size} rules, #{css_output.length} bytes)"
81
87
  end
82
88
 
83
89
  private
@@ -26,7 +26,7 @@ module CSSAnalyzer
26
26
  # @param rule [Cataract::Rule] The rule to check
27
27
  # @return [Array<Symbol>] Array of media query symbols this rule appears in
28
28
  def media_queries_for_rule(rule)
29
- stylesheet.instance_variable_get(:@_media_index).select { |_media, ids| ids.include?(rule.id) }.keys
29
+ stylesheet.media_index.select { |_media, ids| ids.include?(rule.id) }.keys
30
30
  end
31
31
  end
32
32
  end