lightningcss 1.33.0.1-arm-linux-gnu → 1.33.0.2-arm-linux-gnu

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: f996660960f0f35d06985936300415a8ce668ab5e8bc127662b0ea4d23196ea3
4
- data.tar.gz: e0b2dc6dfa403e093bbb3da2308cdc422032e72e620a70c59cc625fa32e9afdc
3
+ metadata.gz: 5e39ac08bd6aa2cddde39814ee33d33e4f93aed2d42ebba1b26bfbbe762263ad
4
+ data.tar.gz: '090e13ec9176bfb0e3f33b38b329e508258357bffaac40bdd8010766b7f928aa'
5
5
  SHA512:
6
- metadata.gz: c45fd06eff33a4ab77982392a9a1399f7103018a3039cea43306ba3876820b9bf7282d1d9988dbdf7ed809f43eef95d2b3f008e44b1ae33dfdb739a4a1a8a5bf
7
- data.tar.gz: 6a5dbf61055622e5f5864905bfc36911a9db0f54a1e2e90cd94b3174a8ababc828965d409c1ff8b067067267b6d28eb8460a79238bc425bb3a2338ff36039e7b
6
+ metadata.gz: 1259d0850b3ba1263611a438ead3d90ac26ba35dc3a009c8187d16f44f9df3cc8865a2d053da01500148d821dce361d7e7395ddae8ae860c972338b6e2785290
7
+ data.tar.gz: 9fea6113eaab9550d06211fa958d0053a67b2510318944c4f515ca2c4efe84725183dbfe6f88ee7da51688b4e3ee12a4229f5ff3bee36734c60a5560c9d2ec4a
data/README.md CHANGED
@@ -57,13 +57,25 @@ LightningCSS.minify(".a { color: #ff0000 }")
57
57
 
58
58
  #### Targeting browsers
59
59
 
60
- Give the browsers you support, each as its major version. Anything they cannot read is lowered, with the original kept after it so that a browser which understands it still wins.
60
+ Give the browsers you support, each with the oldest version you support. Anything they cannot read is lowered, with the original kept after it so that a browser which understands it still wins.
61
61
 
62
62
  ```ruby
63
63
  LightningCSS.transform(".a { color: lab(50% 40 59) }", targets: { chrome: 80 }, minify: true).code
64
64
  #=> ".a{color:#bf5702;color:lab(50% 40 59)}"
65
65
  ```
66
66
 
67
+ A version is an `Integer` for a major version, or a `Float` or `String` for a minor or patch release. Several features first shipped in a minor Safari release, so the minor version can change the output.
68
+
69
+ ```ruby
70
+ LightningCSS.transform(".a { color: oklch(60% .2 30) }", targets: { safari: 15 }, minify: true).code
71
+ #=> ".a{color:lab(51.6908% 61.9679 48.0969)}"
72
+
73
+ LightningCSS.transform(".a { color: oklch(60% .2 30) }", targets: { safari: "15.4" }, minify: true).code
74
+ #=> ".a{color:oklch(60% .2 30)}"
75
+ ```
76
+
77
+ Prefer a `String` for a minor version above 9, since `16.10` as a `Float` is `16.1`.
78
+
67
79
  #### Bundling
68
80
 
69
81
  `bundle` resolves the `@import` statements a stylesheet was written with by reading the files they name, so it takes a path on disk to the entry stylesheet.
@@ -83,7 +95,7 @@ result.exports
83
95
  #=> {"application" => "_8Z4fiW_application"}
84
96
 
85
97
  result.warnings.first
86
- #=> "'deep' is not recognized as a valid pseudo-class. ... at app/assets/stylesheets/layout.css:0:9"
98
+ #=> "'deep' is not recognized as a valid pseudo-class. ... at app/assets/stylesheets/layout.css:1:9"
87
99
  ```
88
100
 
89
101
  #### CSS modules
@@ -271,7 +283,7 @@ It answers `call` as well, so it can be handed to anything expecting something c
271
283
  | `filename` | `String` | The name to use in errors and warnings. |
272
284
  | `minify` | `bool` | Whether to print the result as small as it goes. |
273
285
  | `error_recovery` | `bool` | Whether to carry on past a rule it cannot read. Off by default, so such a rule raises. |
274
- | `targets` | `Hash` | The browsers being compiled for, each as its major version. |
286
+ | `targets` | `Hash` | The browsers being compiled for, each with the oldest version supported. |
275
287
  | `css_modules` | `bool`, `Hash` | Whether to compile as a CSS module, and how. |
276
288
  | `scope` | `String` | A selector fragment to narrow every rule by. |
277
289
 
@@ -325,13 +337,40 @@ result = LightningCSS.transform(".a:deep(.b) { color: red }", minify: true)
325
337
  result.warnings?
326
338
  #=> true
327
339
 
328
- result.warnings.first
329
- #=> "'deep' is not recognized as a valid pseudo-class. Did you mean '::deep' (pseudo-element) or is this a typo? at :0:9"
340
+ result.warnings.first.to_s
341
+ #=> "'deep' is not recognized as a valid pseudo-class. Did you mean '::deep' (pseudo-element) or is this a typo? at :1:9"
330
342
 
331
343
  result.code
332
344
  #=> ".a:deep(.b){color:red}"
333
345
  ```
334
346
 
347
+ ### Locations
348
+
349
+ A warning is a `LightningCSS::Warning` with a `message`, a `type`, and a `location`, the same as a diagnostic in Prism or Herb. A `LightningCSS::Error` carries the same `type` and `location`, and keeps the location in its message.
350
+
351
+ A `LightningCSS::Location` counts lines from 1 and columns from 0, the same as Prism, Herb, and Yerba, and prints the way an editor reads it:
352
+
353
+ ```ruby
354
+ css = ".a { color: red; }\n.b, { color: blue; }\n"
355
+
356
+ begin
357
+ LightningCSS.transform(css, filename: "main.css")
358
+ rescue LightningCSS::ParseError => error
359
+ error.message #=> "Invalid empty selector at main.css:2:5"
360
+ error.type #=> "SelectorError"
361
+ error.location #=> #<data LightningCSS::Location filename="main.css", line=2, column=4>
362
+ end
363
+
364
+ warning = LightningCSS.transform(css, filename: "main.css", error_recovery: true).warnings.first
365
+
366
+ warning.message #=> "Invalid empty selector"
367
+ warning.type #=> "SelectorError"
368
+ warning.location.line #=> 2
369
+ warning.to_s #=> "Invalid empty selector at main.css:2:5"
370
+ ```
371
+
372
+ A warning converts to the string it always was wherever a `String` is expected and is equal to it, so `warnings.include?("...")` still works. An error without a location, such as an `OptionError`, has `nil` for `type` and `location`.
373
+
335
374
  ### Development
336
375
 
337
376
  The gem is a C extension over a Rust crate. `rust/` builds a static library and generates the C header with [cbindgen](https://github.com/mozilla/cbindgen), `ext/lightningcss/` wraps it, and `lib/` is the Ruby API over that.
@@ -43,12 +43,12 @@ static VALUE error_class_for(enum LightningCssErrorCode code) {
43
43
 
44
44
  static VALUE unwrap(struct LightningCssResult result) {
45
45
  if (result.error) {
46
- VALUE message = make_utf8_string(result.error);
46
+ VALUE payload = make_utf8_string(result.error);
47
47
  VALUE error_class = error_class_for(result.code);
48
48
 
49
49
  lightningcss_result_free(result);
50
50
 
51
- rb_raise(error_class, "%s", StringValueCStr(message));
51
+ rb_exc_raise(rb_funcall(error_class, rb_intern("from_json"), 1, payload));
52
52
  }
53
53
 
54
54
  if (!result.value) {
Binary file
Binary file
Binary file
Binary file
@@ -1,7 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LightningCSS
4
- class Error < StandardError; end
4
+ class Error < StandardError
5
+ attr_reader :type #: String?
6
+ attr_reader :location #: LightningCSS::Location?
7
+
8
+ #: (String) -> instance
9
+ def self.from_json(payload)
10
+ parsed = JSON.parse(payload)
11
+ location = parsed["location"] && Location.from_hash(parsed["location"])
12
+ message = parsed.fetch("message")
13
+
14
+ new(location ? "#{message} at #{location}" : message, type: parsed["type"], location: location)
15
+ end
16
+
17
+ #: (?String?, ?type: String?, ?location: LightningCSS::Location?) -> void
18
+ def initialize(message = nil, type: nil, location: nil)
19
+ super(message)
20
+
21
+ @type = type
22
+ @location = location
23
+ end
24
+ end
25
+
5
26
  class ParseError < Error; end
6
27
  class OptionError < Error; end
7
28
  class BundleError < Error; end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LightningCSS
4
+ Location = Data.define(
5
+ :filename, #: String?
6
+ :line, #: Integer
7
+ :column #: Integer
8
+ )
9
+
10
+ class Location
11
+ #: (Hash[String, untyped]) -> LightningCSS::Location
12
+ def self.from_hash(hash)
13
+ new(filename: hash["filename"], line: hash.fetch("line"), column: hash.fetch("column"))
14
+ end
15
+
16
+ #: () -> String
17
+ def to_s
18
+ "#{filename}:#{line}:#{column + 1}"
19
+ end
20
+ end
21
+ end
@@ -9,7 +9,7 @@ module LightningCSS
9
9
  class ParseResult
10
10
  attr_reader :source #: String?
11
11
  attr_reader :stylesheet #: Hash[String, untyped]
12
- attr_reader :warnings #: Array[String]
12
+ attr_reader :warnings #: Array[LightningCSS::Warning]
13
13
 
14
14
  #: (String, ?String?) -> LightningCSS::ParseResult
15
15
  def self.from_json(payload, source = nil)
@@ -18,11 +18,11 @@ module LightningCSS
18
18
  new(
19
19
  source: source,
20
20
  stylesheet: parsed.fetch("stylesheet"),
21
- warnings: parsed.fetch("warnings")
21
+ warnings: parsed.fetch("warnings").map { |warning| Warning.from_hash(warning) }
22
22
  )
23
23
  end
24
24
 
25
- #: (stylesheet: Hash[String, untyped], warnings: Array[String], ?source: String?) -> void
25
+ #: (stylesheet: Hash[String, untyped], warnings: Array[LightningCSS::Warning], ?source: String?) -> void
26
26
  def initialize(stylesheet:, warnings:, source: nil)
27
27
  @source = source
28
28
  @stylesheet = stylesheet.freeze
@@ -44,7 +44,7 @@ module LightningCSS
44
44
  #: () -> String
45
45
  def inspect
46
46
  parts = ["rules=#{stylesheet.fetch("rules").length}"]
47
- parts << "warnings=#{warnings.inspect}" if warnings?
47
+ parts << "warnings=#{warnings.map(&:to_s).inspect}" if warnings?
48
48
 
49
49
  "#<#{self.class.name} #{parts.join(" ")}>"
50
50
  end
@@ -13,7 +13,7 @@ module LightningCSS
13
13
  class Result
14
14
  attr_reader :code #: String
15
15
  attr_reader :exports #: Hash[String, String]?
16
- attr_reader :warnings #: Array[String]
16
+ attr_reader :warnings #: Array[LightningCSS::Warning]
17
17
 
18
18
  #: (String) -> LightningCSS::Result
19
19
  def self.from_json(payload)
@@ -22,11 +22,11 @@ module LightningCSS
22
22
  new(
23
23
  code: parsed.fetch("code"),
24
24
  exports: parsed["exports"],
25
- warnings: parsed.fetch("warnings")
25
+ warnings: parsed.fetch("warnings").map { |warning| Warning.from_hash(warning) }
26
26
  )
27
27
  end
28
28
 
29
- #: (code: String, warnings: Array[String], ?exports: Hash[String, String]?) -> void
29
+ #: (code: String, warnings: Array[LightningCSS::Warning], ?exports: Hash[String, String]?) -> void
30
30
  def initialize(code:, warnings:, exports: nil)
31
31
  @code = code
32
32
  @exports = exports
@@ -49,7 +49,7 @@ module LightningCSS
49
49
  def inspect
50
50
  parts = ["code=#{code.inspect}"]
51
51
  parts << "exports=#{exports.inspect}" if exports
52
- parts << "warnings=#{warnings.inspect}" if warnings?
52
+ parts << "warnings=#{warnings.map(&:to_s).inspect}" if warnings?
53
53
 
54
54
  "#<#{self.class.name} #{parts.join(" ")}>"
55
55
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LightningCSS
4
- VERSION = "1.33.0.1"
4
+ VERSION = "1.33.0.2"
5
5
  end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LightningCSS
4
+ class Warning
5
+ attr_reader :message #: String
6
+ attr_reader :type #: String?
7
+ attr_reader :location #: LightningCSS::Location?
8
+
9
+ #: (Hash[String, untyped]) -> LightningCSS::Warning
10
+ def self.from_hash(hash)
11
+ new(
12
+ message: hash.fetch("message"),
13
+ type: hash["type"],
14
+ location: hash["location"] && Location.from_hash(hash["location"])
15
+ )
16
+ end
17
+
18
+ #: (message: String, ?type: String?, ?location: LightningCSS::Location?) -> void
19
+ def initialize(message:, type: nil, location: nil)
20
+ @message = message
21
+ @type = type
22
+ @location = location
23
+
24
+ freeze
25
+ end
26
+
27
+ #: () -> Hash[Symbol, untyped]
28
+ def to_h
29
+ { message: message, type: type, location: location&.to_h }
30
+ end
31
+
32
+ #: () -> String
33
+ def to_s
34
+ location ? "#{message} at #{location}" : message
35
+ end
36
+
37
+ alias to_str to_s
38
+
39
+ #: (untyped) -> bool
40
+ def ==(other)
41
+ case other
42
+ when Warning then to_h == other.to_h
43
+ when String then to_s == other
44
+ else false
45
+ end
46
+ end
47
+
48
+ alias eql? ==
49
+
50
+ #: () -> Integer
51
+ def hash
52
+ to_h.hash
53
+ end
54
+
55
+ #: () -> String
56
+ def inspect
57
+ "#<#{self.class.name} #{to_s.inspect}>"
58
+ end
59
+ end
60
+ end
data/lib/lightningcss.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require "json"
4
4
 
5
5
  require_relative "lightningcss/version"
6
+ require_relative "lightningcss/location"
6
7
  require_relative "lightningcss/errors"
7
8
  require_relative "lightningcss/backend"
8
9
 
@@ -14,6 +15,7 @@ rescue LoadError
14
15
  end
15
16
 
16
17
  require_relative "lightningcss/options"
18
+ require_relative "lightningcss/warning"
17
19
  require_relative "lightningcss/result"
18
20
  require_relative "lightningcss/node"
19
21
  require_relative "lightningcss/parse_result"
data/rust/src/lib.rs CHANGED
@@ -21,7 +21,7 @@ use lightningcss::bundler::{BundleErrorKind, Bundler, FileProvider};
21
21
  use lightningcss::stylesheet::{MinifyOptions, ParserOptions, PrinterOptions, StyleAttribute, StyleSheet};
22
22
  use lightningcss::visitor::Visit;
23
23
 
24
- use crate::options::{ParseResult, TransformOptions, TransformResult};
24
+ use crate::options::{Diagnostic, ParseResult, TransformOptions, TransformResult};
25
25
  use crate::scope::Scoper;
26
26
 
27
27
  pub const VERSION: &str = env!("GEM_VERSION");
@@ -56,7 +56,7 @@ impl LightningCssResult {
56
56
  fn err(failure: Failure) -> Self {
57
57
  Self {
58
58
  value: ptr::null_mut(),
59
- error: into_c_string(failure.message),
59
+ error: into_c_string(serde_json::to_string(&failure.diagnostic).unwrap_or(failure.diagnostic.message)),
60
60
  code: failure.code,
61
61
  }
62
62
  }
@@ -64,35 +64,35 @@ impl LightningCssResult {
64
64
 
65
65
  pub struct Failure {
66
66
  code: LightningCssErrorCode,
67
- message: String,
67
+ diagnostic: Diagnostic,
68
68
  }
69
69
 
70
70
  impl Failure {
71
- fn parse(message: impl Into<String>) -> Self {
71
+ fn parse(diagnostic: impl Into<Diagnostic>) -> Self {
72
72
  Self {
73
73
  code: LightningCssErrorCode::Parse,
74
- message: message.into(),
74
+ diagnostic: diagnostic.into(),
75
75
  }
76
76
  }
77
77
 
78
- fn option(message: impl Into<String>) -> Self {
78
+ fn option(diagnostic: impl Into<Diagnostic>) -> Self {
79
79
  Self {
80
80
  code: LightningCssErrorCode::Option,
81
- message: message.into(),
81
+ diagnostic: diagnostic.into(),
82
82
  }
83
83
  }
84
84
 
85
- fn bundle(message: impl Into<String>) -> Self {
85
+ fn bundle(diagnostic: impl Into<Diagnostic>) -> Self {
86
86
  Self {
87
87
  code: LightningCssErrorCode::Bundle,
88
- message: message.into(),
88
+ diagnostic: diagnostic.into(),
89
89
  }
90
90
  }
91
91
 
92
- fn internal(message: impl Into<String>) -> Self {
92
+ fn internal(diagnostic: impl Into<Diagnostic>) -> Self {
93
93
  Self {
94
94
  code: LightningCssErrorCode::Internal,
95
- message: message.into(),
95
+ diagnostic: diagnostic.into(),
96
96
  }
97
97
  }
98
98
  }
@@ -135,11 +135,12 @@ fn parse_source<'i>(code: &'i str, options: &TransformOptions) -> Result<ParseRe
135
135
  ..ParserOptions::default()
136
136
  };
137
137
 
138
- let stylesheet = StyleSheet::parse(code, parser_options).map_err(|error| Failure::parse(error.to_string()))?;
138
+ let stylesheet =
139
+ StyleSheet::parse(code, parser_options).map_err(|error| Failure::parse(Diagnostic::from_error(&error)))?;
139
140
 
140
141
  let warnings = collected
141
142
  .read()
142
- .map(|warnings| warnings.iter().map(|warning| warning.to_string()).collect())
143
+ .map(|warnings| warnings.iter().map(Diagnostic::from_error).collect())
143
144
  .unwrap_or_default();
144
145
 
145
146
  Ok(ParseResult { stylesheet, warnings })
@@ -162,7 +163,8 @@ fn transform_source(code: &str, options: &TransformOptions) -> Result<TransformR
162
163
  ..ParserOptions::default()
163
164
  };
164
165
 
165
- let mut stylesheet = StyleSheet::parse(code, parser_options).map_err(|error| Failure::parse(error.to_string()))?;
166
+ let mut stylesheet =
167
+ StyleSheet::parse(code, parser_options).map_err(|error| Failure::parse(Diagnostic::from_error(&error)))?;
166
168
 
167
169
  if let Some(fragment) = &options.scope {
168
170
  let mut scoper = Scoper::parse(fragment).map_err(Failure::option)?;
@@ -172,16 +174,14 @@ fn transform_source(code: &str, options: &TransformOptions) -> Result<TransformR
172
174
  .map_err(|error| Failure::internal(format!("Failed to scope stylesheet: {error:?}")))?;
173
175
  }
174
176
 
175
- let targets = options.to_targets();
177
+ let targets = options.to_targets().map_err(Failure::option)?;
176
178
 
177
- if options.minify {
178
- stylesheet
179
- .minify(MinifyOptions {
180
- targets,
181
- ..MinifyOptions::default()
182
- })
183
- .map_err(|error| Failure::internal(format!("Failed to minify: {error}")))?;
184
- }
179
+ stylesheet
180
+ .minify(MinifyOptions {
181
+ targets,
182
+ ..MinifyOptions::default()
183
+ })
184
+ .map_err(|error| Failure::internal(format!("Failed to minify: {error}")))?;
185
185
 
186
186
  let printed = stylesheet
187
187
  .to_css(PrinterOptions {
@@ -203,7 +203,7 @@ fn transform_source(code: &str, options: &TransformOptions) -> Result<TransformR
203
203
 
204
204
  let warnings = collected
205
205
  .read()
206
- .map(|warnings| warnings.iter().map(|warning| warning.to_string()).collect())
206
+ .map(|warnings| warnings.iter().map(Diagnostic::from_error).collect())
207
207
  .unwrap_or_default();
208
208
 
209
209
  Ok(TransformResult {
@@ -234,8 +234,8 @@ fn bundle_source(path: &str, options: &TransformOptions) -> Result<TransformResu
234
234
  let mut stylesheet = bundler
235
235
  .bundle(std::path::Path::new(path))
236
236
  .map_err(|error| match &error.kind {
237
- BundleErrorKind::ParserError(_) => Failure::parse(error.to_string()),
238
- _ => Failure::bundle(error.to_string()),
237
+ BundleErrorKind::ParserError(_) => Failure::parse(Diagnostic::from_error(&error)),
238
+ _ => Failure::bundle(Diagnostic::from_error(&error)),
239
239
  })?;
240
240
 
241
241
  if let Some(fragment) = &options.scope {
@@ -246,16 +246,14 @@ fn bundle_source(path: &str, options: &TransformOptions) -> Result<TransformResu
246
246
  .map_err(|error| Failure::internal(format!("Failed to scope stylesheet: {error:?}")))?;
247
247
  }
248
248
 
249
- let targets = options.to_targets();
249
+ let targets = options.to_targets().map_err(Failure::option)?;
250
250
 
251
- if options.minify {
252
- stylesheet
253
- .minify(MinifyOptions {
254
- targets,
255
- ..MinifyOptions::default()
256
- })
257
- .map_err(|error| Failure::internal(format!("Failed to minify: {error}")))?;
258
- }
251
+ stylesheet
252
+ .minify(MinifyOptions {
253
+ targets,
254
+ ..MinifyOptions::default()
255
+ })
256
+ .map_err(|error| Failure::internal(format!("Failed to minify: {error}")))?;
259
257
 
260
258
  let printed = stylesheet
261
259
  .to_css(PrinterOptions {
@@ -274,7 +272,7 @@ fn bundle_source(path: &str, options: &TransformOptions) -> Result<TransformResu
274
272
 
275
273
  let warnings = collected
276
274
  .read()
277
- .map(|warnings| warnings.iter().map(|warning| warning.to_string()).collect())
275
+ .map(|warnings| warnings.iter().map(Diagnostic::from_error).collect())
278
276
  .unwrap_or_default();
279
277
 
280
278
  Ok(TransformResult {
@@ -291,9 +289,10 @@ fn transform_attribute(code: &str, options: &TransformOptions) -> Result<Transfo
291
289
  ..ParserOptions::default()
292
290
  };
293
291
 
294
- let mut attribute = StyleAttribute::parse(code, parser_options).map_err(|error| Failure::parse(error.to_string()))?;
292
+ let mut attribute =
293
+ StyleAttribute::parse(code, parser_options).map_err(|error| Failure::parse(Diagnostic::from_error(&error)))?;
295
294
 
296
- let targets = options.to_targets();
295
+ let targets = options.to_targets().map_err(Failure::option)?;
297
296
 
298
297
  attribute.minify(MinifyOptions {
299
298
  targets,
data/rust/src/options.rs CHANGED
@@ -1,6 +1,7 @@
1
1
  use std::collections::HashMap;
2
2
 
3
3
  use lightningcss::css_modules::{Config as CssModulesConfig, Pattern};
4
+ use lightningcss::error::Error;
4
5
  use lightningcss::targets::{Browsers, Targets};
5
6
  use serde::{Deserialize, Serialize};
6
7
 
@@ -10,7 +11,7 @@ pub struct TransformOptions {
10
11
  pub filename: Option<String>,
11
12
  pub minify: bool,
12
13
  pub error_recovery: bool,
13
- pub targets: Option<HashMap<String, u32>>,
14
+ pub targets: Option<HashMap<String, Version>>,
14
15
  pub css_modules: Option<CssModulesOptions>,
15
16
  pub scope: Option<String>,
16
17
  }
@@ -60,16 +61,61 @@ impl CssModulesOptions {
60
61
  }
61
62
  }
62
63
 
64
+ #[derive(Debug, Deserialize)]
65
+ #[serde(untagged)]
66
+ pub enum Version {
67
+ Major(u32),
68
+ Float(f64),
69
+ String(String),
70
+ }
71
+
72
+ impl Version {
73
+ /// Encodes a version the way Lightning CSS reads it, as `major << 16 | minor << 8 | patch`.
74
+ fn encode(&self) -> Option<u32> {
75
+ match self {
76
+ Version::Major(major) => (*major <= 0xffff).then(|| major << 16),
77
+ Version::Float(float) => Self::parse(&float.to_string()),
78
+ Version::String(string) => Self::parse(string),
79
+ }
80
+ }
81
+
82
+ fn parse(version: &str) -> Option<u32> {
83
+ let mut parts = version.trim().split('.');
84
+
85
+ let major: u16 = parts.next()?.parse().ok()?;
86
+ let minor: u8 = parts.next().map_or(Some(0), |part| part.parse().ok())?;
87
+ let patch: u8 = parts.next().map_or(Some(0), |part| part.parse().ok())?;
88
+
89
+ if parts.next().is_some() {
90
+ return None;
91
+ }
92
+
93
+ Some(u32::from(major) << 16 | u32::from(minor) << 8 | u32::from(patch))
94
+ }
95
+
96
+ fn describe(&self) -> String {
97
+ match self {
98
+ Version::Major(major) => major.to_string(),
99
+ Version::Float(float) => float.to_string(),
100
+ Version::String(string) => format!("{string:?}"),
101
+ }
102
+ }
103
+ }
104
+
63
105
  impl TransformOptions {
64
- pub fn to_targets(&self) -> Targets {
106
+ pub fn to_targets(&self) -> Result<Targets, String> {
65
107
  let Some(versions) = &self.targets else {
66
- return Targets::default();
108
+ return Ok(Targets::default());
67
109
  };
68
110
 
69
111
  let mut browsers = Browsers::default();
70
112
 
71
113
  for (name, version) in versions {
72
- let encoded = Some(version << 16);
114
+ let encoded = Some(
115
+ version
116
+ .encode()
117
+ .ok_or_else(|| format!("Invalid version {} for {name} in targets", version.describe()))?,
118
+ );
73
119
 
74
120
  match name.as_str() {
75
121
  "android" => browsers.android = encoded,
@@ -85,14 +131,14 @@ impl TransformOptions {
85
131
  }
86
132
  }
87
133
 
88
- Targets::from(browsers)
134
+ Ok(Targets::from(browsers))
89
135
  }
90
136
  }
91
137
 
92
138
  #[derive(Debug, Serialize)]
93
139
  pub struct ParseResult<T> {
94
140
  pub stylesheet: T,
95
- pub warnings: Vec<String>,
141
+ pub warnings: Vec<Diagnostic>,
96
142
  }
97
143
 
98
144
  #[derive(Debug, Serialize)]
@@ -100,5 +146,66 @@ pub struct TransformResult {
100
146
  pub code: String,
101
147
  #[serde(skip_serializing_if = "Option::is_none")]
102
148
  pub exports: Option<HashMap<String, String>>,
103
- pub warnings: Vec<String>,
149
+ pub warnings: Vec<Diagnostic>,
150
+ }
151
+
152
+ #[derive(Debug, Serialize)]
153
+ pub struct Diagnostic {
154
+ pub message: String,
155
+ #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
156
+ pub kind: Option<String>,
157
+ #[serde(skip_serializing_if = "Option::is_none")]
158
+ pub location: Option<Location>,
159
+ }
160
+
161
+ #[derive(Debug, Serialize)]
162
+ pub struct Location {
163
+ pub filename: Option<String>,
164
+ pub line: u32,
165
+ pub column: u32,
166
+ }
167
+
168
+ impl Diagnostic {
169
+ pub fn from_error<T: std::fmt::Display + Serialize>(error: &Error<T>) -> Self {
170
+ Self {
171
+ message: error.kind.to_string(),
172
+ kind: serde_json::to_value(&error.kind).ok().as_ref().and_then(type_of),
173
+ location: error.loc.as_ref().map(|location| Location {
174
+ filename: (!location.filename.is_empty()).then(|| location.filename.clone()),
175
+ line: location.line + 1,
176
+ column: location.column.saturating_sub(1),
177
+ }),
178
+ }
179
+ }
180
+ }
181
+
182
+ impl From<String> for Diagnostic {
183
+ fn from(message: String) -> Self {
184
+ Self {
185
+ message,
186
+ kind: None,
187
+ location: None,
188
+ }
189
+ }
190
+ }
191
+
192
+ impl From<&str> for Diagnostic {
193
+ fn from(message: &str) -> Self {
194
+ Self::from(message.to_string())
195
+ }
196
+ }
197
+
198
+ fn type_of(value: &serde_json::Value) -> Option<String> {
199
+ match value {
200
+ serde_json::Value::String(name) => Some(name.clone()),
201
+ serde_json::Value::Object(fields) => match fields.get("type") {
202
+ Some(serde_json::Value::String(name)) => Some(name.clone()),
203
+ _ if fields.len() == 1 => fields.iter().next().map(|(name, inner)| match inner.get("type") {
204
+ Some(serde_json::Value::String(inner_name)) => inner_name.clone(),
205
+ _ => name.clone(),
206
+ }),
207
+ _ => None,
208
+ },
209
+ _ => None,
210
+ }
104
211
  }
data/rust/src/scope.rs CHANGED
@@ -29,7 +29,8 @@ impl<'i> Scoper<'i> {
29
29
  let list = SelectorList::parse_string_with_options(fragment, ParserOptions::default()).map_err(|error| {
30
30
  format!(
31
31
  "Invalid scope selector {fragment:?} at :{}:{}",
32
- error.location.line, error.location.column
32
+ error.location.line + 1,
33
+ error.location.column
33
34
  )
34
35
  })?;
35
36
 
@@ -2,6 +2,15 @@
2
2
 
3
3
  module LightningCSS
4
4
  class Error < StandardError
5
+ attr_reader type: String?
6
+
7
+ attr_reader location: LightningCSS::Location?
8
+
9
+ # : (String) -> instance
10
+ def self.from_json: (String) -> instance
11
+
12
+ # : (?String?, ?type: String?, ?location: LightningCSS::Location?) -> void
13
+ def initialize: (?String?, ?type: String?, ?location: LightningCSS::Location?) -> void
5
14
  end
6
15
 
7
16
  class ParseError < Error
@@ -0,0 +1,26 @@
1
+ # Generated from lib/lightningcss/location.rb with RBS::Inline
2
+
3
+ module LightningCSS
4
+ class Location < Data
5
+ attr_reader filename(): String?
6
+
7
+ attr_reader line(): Integer
8
+
9
+ attr_reader column(): Integer
10
+
11
+ def self.new: (String? filename, Integer line, Integer column) -> instance
12
+ | (filename: String?, line: Integer, column: Integer) -> instance
13
+
14
+ def self.members: () -> [ :filename, :line, :column ]
15
+
16
+ def members: () -> [ :filename, :line, :column ]
17
+ end
18
+
19
+ class Location
20
+ # : (Hash[String, untyped]) -> LightningCSS::Location
21
+ def self.from_hash: (Hash[String, untyped]) -> LightningCSS::Location
22
+
23
+ # : () -> String
24
+ def to_s: () -> String
25
+ end
26
+ end
@@ -10,13 +10,13 @@ module LightningCSS
10
10
 
11
11
  attr_reader stylesheet: Hash[String, untyped]
12
12
 
13
- attr_reader warnings: Array[String]
13
+ attr_reader warnings: Array[LightningCSS::Warning]
14
14
 
15
15
  # : (String, ?String?) -> LightningCSS::ParseResult
16
16
  def self.from_json: (String, ?String?) -> LightningCSS::ParseResult
17
17
 
18
- # : (stylesheet: Hash[String, untyped], warnings: Array[String], ?source: String?) -> void
19
- def initialize: (stylesheet: Hash[String, untyped], warnings: Array[String], ?source: String?) -> void
18
+ # : (stylesheet: Hash[String, untyped], warnings: Array[LightningCSS::Warning], ?source: String?) -> void
19
+ def initialize: (stylesheet: Hash[String, untyped], warnings: Array[LightningCSS::Warning], ?source: String?) -> void
20
20
 
21
21
  # : () -> LightningCSS::Node
22
22
  def root: () -> LightningCSS::Node
@@ -14,13 +14,13 @@ module LightningCSS
14
14
 
15
15
  attr_reader exports: Hash[String, String]?
16
16
 
17
- attr_reader warnings: Array[String]
17
+ attr_reader warnings: Array[LightningCSS::Warning]
18
18
 
19
19
  # : (String) -> LightningCSS::Result
20
20
  def self.from_json: (String) -> LightningCSS::Result
21
21
 
22
- # : (code: String, warnings: Array[String], ?exports: Hash[String, String]?) -> void
23
- def initialize: (code: String, warnings: Array[String], ?exports: Hash[String, String]?) -> void
22
+ # : (code: String, warnings: Array[LightningCSS::Warning], ?exports: Hash[String, String]?) -> void
23
+ def initialize: (code: String, warnings: Array[LightningCSS::Warning], ?exports: Hash[String, String]?) -> void
24
24
 
25
25
  # : () -> bool
26
26
  def warnings?: () -> bool
@@ -1,17 +1,19 @@
1
1
  # Note: This file is hand written, `rbs-inline` does not generate this file.
2
2
 
3
3
  module LightningCSS
4
+ type version = Integer | Float | String
5
+
4
6
  type browsers = {
5
- ?android: Integer,
6
- ?chrome: Integer,
7
- ?edge: Integer,
8
- ?firefox: Integer,
9
- ?ie: Integer,
10
- ?ios: Integer,
11
- ?ios_saf: Integer,
12
- ?opera: Integer,
13
- ?safari: Integer,
14
- ?samsung: Integer
7
+ ?android: version,
8
+ ?chrome: version,
9
+ ?edge: version,
10
+ ?firefox: version,
11
+ ?ie: version,
12
+ ?ios: version,
13
+ ?ios_saf: version,
14
+ ?opera: version,
15
+ ?safari: version,
16
+ ?samsung: version
15
17
  }
16
18
 
17
19
  type css_modules = bool | {
@@ -0,0 +1,36 @@
1
+ # Generated from lib/lightningcss/warning.rb with RBS::Inline
2
+
3
+ module LightningCSS
4
+ class Warning
5
+ attr_reader message: String
6
+
7
+ attr_reader type: String?
8
+
9
+ attr_reader location: LightningCSS::Location?
10
+
11
+ # : (Hash[String, untyped]) -> LightningCSS::Warning
12
+ def self.from_hash: (Hash[String, untyped]) -> LightningCSS::Warning
13
+
14
+ # : (message: String, ?type: String?, ?location: LightningCSS::Location?) -> void
15
+ def initialize: (message: String, ?type: String?, ?location: LightningCSS::Location?) -> void
16
+
17
+ # : () -> Hash[Symbol, untyped]
18
+ def to_h: () -> Hash[Symbol, untyped]
19
+
20
+ # : () -> String
21
+ def to_s: () -> String
22
+
23
+ alias to_str to_s
24
+
25
+ # : (untyped) -> bool
26
+ def ==: (untyped) -> bool
27
+
28
+ alias eql? ==
29
+
30
+ # : () -> Integer
31
+ def hash: () -> Integer
32
+
33
+ # : () -> String
34
+ def inspect: () -> String
35
+ end
36
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lightningcss
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.33.0.1
4
+ version: 1.33.0.2
5
5
  platform: arm-linux-gnu
6
6
  authors:
7
7
  - Marco Roth
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-10-13 00:00:00.000000000 Z
11
+ date: 2026-10-14 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Ruby bindings for Lightning CSS, an extremely fast CSS parser, transformer,
14
14
  bundler, and minifier.
@@ -30,6 +30,7 @@ files:
30
30
  - lib/lightningcss/4.0/lightningcss.so
31
31
  - lib/lightningcss/backend.rb
32
32
  - lib/lightningcss/errors.rb
33
+ - lib/lightningcss/location.rb
33
34
  - lib/lightningcss/node.rb
34
35
  - lib/lightningcss/options.rb
35
36
  - lib/lightningcss/parse_result.rb
@@ -37,6 +38,7 @@ files:
37
38
  - lib/lightningcss/transformer.rb
38
39
  - lib/lightningcss/version.rb
39
40
  - lib/lightningcss/visitor.rb
41
+ - lib/lightningcss/warning.rb
40
42
  - licenses/README.md
41
43
  - licenses/lightningcss-MPL-2.0.txt
42
44
  - lightningcss.gemspec
@@ -51,6 +53,7 @@ files:
51
53
  - sig/lightningcss.rbs
52
54
  - sig/lightningcss/backend.rbs
53
55
  - sig/lightningcss/errors.rbs
56
+ - sig/lightningcss/location.rbs
54
57
  - sig/lightningcss/node.rbs
55
58
  - sig/lightningcss/options.rbs
56
59
  - sig/lightningcss/parse_result.rbs
@@ -59,6 +62,7 @@ files:
59
62
  - sig/lightningcss/types.rbs
60
63
  - sig/lightningcss/version.rbs
61
64
  - sig/lightningcss/visitor.rbs
65
+ - sig/lightningcss/warning.rbs
62
66
  homepage: https://github.com/marcoroth/lightningcss-ruby
63
67
  licenses:
64
68
  - MIT