lightningcss 1.33.0-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 +4 -4
- data/README.md +153 -5
- data/ext/lightningcss/include/lightningcss.h +2 -0
- data/ext/lightningcss/lightningcss.c +9 -2
- data/lib/lightningcss/3.2/lightningcss.so +0 -0
- data/lib/lightningcss/3.3/lightningcss.so +0 -0
- data/lib/lightningcss/3.4/lightningcss.so +0 -0
- data/lib/lightningcss/4.0/lightningcss.so +0 -0
- data/lib/lightningcss/backend.rb +5 -0
- data/lib/lightningcss/errors.rb +22 -1
- data/lib/lightningcss/location.rb +21 -0
- data/lib/lightningcss/node.rb +220 -0
- data/lib/lightningcss/options.rb +5 -0
- data/lib/lightningcss/parse_result.rb +52 -0
- data/lib/lightningcss/result.rb +4 -4
- data/lib/lightningcss/transformer.rb +5 -0
- data/lib/lightningcss/version.rb +1 -1
- data/lib/lightningcss/visitor.rb +37 -0
- data/lib/lightningcss/warning.rb +60 -0
- data/lib/lightningcss.rb +18 -0
- data/rust/Cargo.lock +32 -0
- data/rust/Cargo.toml +1 -1
- data/rust/src/lib.rs +71 -37
- data/rust/src/options.rs +119 -6
- data/rust/src/scope.rs +2 -1
- data/sig/lightningcss/backend.rbs +3 -0
- data/sig/lightningcss/errors.rbs +9 -0
- data/sig/lightningcss/location.rbs +26 -0
- data/sig/lightningcss/node.rbs +104 -0
- data/sig/lightningcss/options.rbs +2 -0
- data/sig/lightningcss/parse_result.rbs +30 -0
- data/sig/lightningcss/result.rbs +3 -3
- data/sig/lightningcss/transformer.rbs +3 -0
- data/sig/lightningcss/types.rbs +12 -10
- data/sig/lightningcss/visitor.rbs +24 -0
- data/sig/lightningcss/warning.rbs +36 -0
- data/sig/lightningcss.rbs +8 -0
- metadata +12 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5e39ac08bd6aa2cddde39814ee33d33e4f93aed2d42ebba1b26bfbbe762263ad
|
|
4
|
+
data.tar.gz: '090e13ec9176bfb0e3f33b38b329e508258357bffaac40bdd8010766b7f928aa'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
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:
|
|
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
|
|
@@ -141,6 +153,113 @@ LightningCSS.transform_style_attribute("color: #ff0000; border: none", minify: t
|
|
|
141
153
|
|
|
142
154
|
Having no selectors and no names, it takes neither `scope` nor `css_modules`, and says so when given one.
|
|
143
155
|
|
|
156
|
+
#### Parsing
|
|
157
|
+
|
|
158
|
+
`parse` answers what Lightning CSS read a stylesheet as, without printing anything. It takes `filename` and `error_recovery`, the two options that change how a stylesheet is read.
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
parsed = LightningCSS.parse(".card .title:hover { color: #f00 }")
|
|
162
|
+
|
|
163
|
+
parsed.stylesheet #=> the AST exactly as Lightning CSS serializes it
|
|
164
|
+
parsed.warnings #=> []
|
|
165
|
+
parsed.source #=> ".card .title:hover { color: #f00 }"
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The AST is Lightning CSS's own, so its shapes are the ones its [JavaScript visitors](https://lightningcss.dev/transforms.html) see and [`lightningcss/node/ast.d.ts`](https://github.com/parcel-bundler/lightningcss/blob/master/node/ast.d.ts) describes. It is what Lightning CSS understood, not what was written: `#f00` reads as an `rgb` with `r` of `255.0`, and a `0` margin reads as `0px`.
|
|
169
|
+
|
|
170
|
+
#### Walking the AST
|
|
171
|
+
|
|
172
|
+
`root` answers the stylesheet as a `LightningCSS::Node`, which walks, reads its fields by name, and knows what it sits inside.
|
|
173
|
+
|
|
174
|
+
```ruby
|
|
175
|
+
root = LightningCSS.parse(source).root
|
|
176
|
+
|
|
177
|
+
root.type #=> "stylesheet"
|
|
178
|
+
root.keys #=> the fields this node carries
|
|
179
|
+
root.fields #=> those fields and their values, without its type and location
|
|
180
|
+
root.child_nodes #=> the nodes directly under it
|
|
181
|
+
root.every("declaration") #=> every declaration in the stylesheet
|
|
182
|
+
root.each #=> an Enumerator over every node
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Every object in the tree is a node, so reads chain all the way down.
|
|
186
|
+
|
|
187
|
+
```ruby
|
|
188
|
+
rule = root.rules.first
|
|
189
|
+
|
|
190
|
+
rule.type #=> "style"
|
|
191
|
+
rule.value.selectors.first.map(&:type) #=> ["class", "combinator", "class", "pseudo-class"]
|
|
192
|
+
rule.value.declarations.declarations.first.value.r
|
|
193
|
+
#=> 255.0
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Most objects say what they are with their own `type`. The stylesheet, a declaration block, and a declaration do not, so a node names those `"stylesheet"`, `"declaration-block"`, and `"declaration"` from where it sits. Anything else without a `type`, like the `value` a rule wraps, answers `nil`.
|
|
197
|
+
|
|
198
|
+
Inspecting a node shows every field it carries.
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
#<LightningCSS::Node style value=#<LightningCSS::Node>>
|
|
202
|
+
#<LightningCSS::Node location=0:1 selectors=[... 1 item] declarations=#<LightningCSS::Node declaration-block> rules=[]>
|
|
203
|
+
#<LightningCSS::Node declaration property="color" value=#<LightningCSS::Node rgb>>
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Lightning CSS names its fields in camelCase, and a field answers to its snake_case name too.
|
|
207
|
+
|
|
208
|
+
```ruby
|
|
209
|
+
block.important_declarations # the same field as block.importantDeclarations
|
|
210
|
+
query.media_queries # mediaQueries
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Lightning CSS records where each rule starts and nothing else, so `location` is a rule's line, counted from 0, and its column, counted from 1 in UTF-16 code units. There are no end positions, so a node has no `slice` and there is no `at(offset)`.
|
|
214
|
+
|
|
215
|
+
```ruby
|
|
216
|
+
rule.value.location
|
|
217
|
+
#=> {"source_index" => 0, "line" => 0, "column" => 1}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
`ancestors` answers what a node sits inside, and `rule?` says whether it is a rule.
|
|
221
|
+
|
|
222
|
+
```ruby
|
|
223
|
+
declaration = root.every("declaration").first
|
|
224
|
+
declaration.ancestors.find(&:rule?).type
|
|
225
|
+
#=> "style"
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Nodes pattern match, and nest, since a field holding an object comes back as a node. Patterns take snake_case names, and a node's `type` matches even when Lightning CSS gave it none.
|
|
229
|
+
|
|
230
|
+
```ruby
|
|
231
|
+
declaration => { type: "declaration", property:, value: { type: "rgb", r: } }
|
|
232
|
+
property #=> "color"
|
|
233
|
+
r #=> 255.0
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
`to_h` and `to_json` answer the AST the node wraps.
|
|
237
|
+
|
|
238
|
+
```ruby
|
|
239
|
+
declaration.value.to_h
|
|
240
|
+
#=> {"type" => "rgb", "r" => 255.0, "g" => 0.0, "b" => 0.0, "alpha" => 1.0}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
`LightningCSS::Visitor` answers a node with the method named after its type, and walks through anything nothing answers.
|
|
244
|
+
|
|
245
|
+
```ruby
|
|
246
|
+
class Colors < LightningCSS::Visitor
|
|
247
|
+
def visit_style_rule(node)
|
|
248
|
+
puts node.value.location["line"]
|
|
249
|
+
|
|
250
|
+
visit_children(node)
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def visit_declaration(node)
|
|
254
|
+
puts node.property
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
Colors.new.visit(LightningCSS.parse(source))
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
A rule is answered as one, as in `visit_style_rule`, `visit_media_rule`, or `visit_namespace_rule`. Lightning CSS calls the rule `style`, but `namespace` and `nesting` are also parts of a selector, so `visit_namespace` answers `svg|rect` and `visit_namespace_rule` answers `@namespace`. Hyphens become underscores, so a `pseudo-class` is answered by `visit_pseudo_class`.
|
|
262
|
+
|
|
144
263
|
#### Reusing options
|
|
145
264
|
|
|
146
265
|
`LightningCSS::Transformer` holds a set of options to use across many stylesheets. Options given to a call are merged over the ones it was built with.
|
|
@@ -155,6 +274,8 @@ transformer.with(scope: "[s]")
|
|
|
155
274
|
|
|
156
275
|
It answers `call` as well, so it can be handed to anything expecting something callable.
|
|
157
276
|
|
|
277
|
+
`transformer.parse` reads with the `filename` and `error_recovery` it holds and leaves the rest, which only matter to printing.
|
|
278
|
+
|
|
158
279
|
### Options
|
|
159
280
|
|
|
160
281
|
| Option | Type | Description |
|
|
@@ -162,7 +283,7 @@ It answers `call` as well, so it can be handed to anything expecting something c
|
|
|
162
283
|
| `filename` | `String` | The name to use in errors and warnings. |
|
|
163
284
|
| `minify` | `bool` | Whether to print the result as small as it goes. |
|
|
164
285
|
| `error_recovery` | `bool` | Whether to carry on past a rule it cannot read. Off by default, so such a rule raises. |
|
|
165
|
-
| `targets` | `Hash` | The browsers being compiled for, each
|
|
286
|
+
| `targets` | `Hash` | The browsers being compiled for, each with the oldest version supported. |
|
|
166
287
|
| `css_modules` | `bool`, `Hash` | Whether to compile as a CSS module, and how. |
|
|
167
288
|
| `scope` | `String` | A selector fragment to narrow every rule by. |
|
|
168
289
|
|
|
@@ -216,13 +337,40 @@ result = LightningCSS.transform(".a:deep(.b) { color: red }", minify: true)
|
|
|
216
337
|
result.warnings?
|
|
217
338
|
#=> true
|
|
218
339
|
|
|
219
|
-
result.warnings.first
|
|
220
|
-
#=> "'deep' is not recognized as a valid pseudo-class. Did you mean '::deep' (pseudo-element) or is this a typo? at :
|
|
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"
|
|
221
342
|
|
|
222
343
|
result.code
|
|
223
344
|
#=> ".a:deep(.b){color:red}"
|
|
224
345
|
```
|
|
225
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
|
+
|
|
226
374
|
### Development
|
|
227
375
|
|
|
228
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.
|
|
@@ -23,6 +23,8 @@ typedef struct LightningCssResult {
|
|
|
23
23
|
|
|
24
24
|
struct LightningCssResult lightningcss_transform(const char *code, const char *options_json);
|
|
25
25
|
|
|
26
|
+
struct LightningCssResult lightningcss_parse(const char *code, const char *options_json);
|
|
27
|
+
|
|
26
28
|
struct LightningCssResult lightningcss_transform_style_attribute(const char *code,
|
|
27
29
|
const char *options_json);
|
|
28
30
|
|
|
@@ -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
|
|
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
|
-
|
|
51
|
+
rb_exc_raise(rb_funcall(error_class, rb_intern("from_json"), 1, payload));
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
if (!result.value) {
|
|
@@ -90,6 +90,12 @@ static VALUE rb_transform(VALUE self, VALUE code, VALUE options) {
|
|
|
90
90
|
return call(lightningcss_transform, code, options);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
static VALUE rb_parse(VALUE self, VALUE code, VALUE options) {
|
|
94
|
+
(void) self;
|
|
95
|
+
|
|
96
|
+
return call(lightningcss_parse, code, options);
|
|
97
|
+
}
|
|
98
|
+
|
|
93
99
|
static VALUE rb_transform_style_attribute(VALUE self, VALUE code, VALUE options) {
|
|
94
100
|
(void) self;
|
|
95
101
|
|
|
@@ -123,6 +129,7 @@ void Init_lightningcss(void) {
|
|
|
123
129
|
rb_eOptionError = rb_define_class_under(rb_mLightningCSS, "OptionError", rb_eError);
|
|
124
130
|
rb_eBundleError = rb_define_class_under(rb_mLightningCSS, "BundleError", rb_eError);
|
|
125
131
|
|
|
132
|
+
rb_define_singleton_method(rb_mBackend, "parse", rb_parse, 2);
|
|
126
133
|
rb_define_singleton_method(rb_mBackend, "transform", rb_transform, 2);
|
|
127
134
|
rb_define_singleton_method(rb_mBackend, "transform_style_attribute", rb_transform_style_attribute, 2);
|
|
128
135
|
rb_define_singleton_method(rb_mBackend, "bundle", rb_bundle, 2);
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
data/lib/lightningcss/backend.rb
CHANGED
|
@@ -8,6 +8,11 @@ module LightningCSS
|
|
|
8
8
|
unavailable(__method__)
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
+
#: (String, String) -> String
|
|
12
|
+
def parse(_code, _options_json)
|
|
13
|
+
unavailable(__method__)
|
|
14
|
+
end
|
|
15
|
+
|
|
11
16
|
#: (String, String) -> String
|
|
12
17
|
def transform_style_attribute(_code, _options_json)
|
|
13
18
|
unavailable(__method__)
|
data/lib/lightningcss/errors.rb
CHANGED
|
@@ -1,7 +1,28 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module LightningCSS
|
|
4
|
-
class Error < StandardError
|
|
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
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LightningCSS
|
|
4
|
+
# One object in the AST Lightning CSS parsed a stylesheet to.
|
|
5
|
+
#
|
|
6
|
+
# root = LightningCSS.parse(".a { color: red }").root
|
|
7
|
+
#
|
|
8
|
+
# root.type #=> "stylesheet"
|
|
9
|
+
# root.every("declaration") #=> every declaration in the stylesheet
|
|
10
|
+
# root.rules.first.value.location #=> {"source_index" => 0, "line" => 0, "column" => 1}
|
|
11
|
+
#
|
|
12
|
+
# Every object in the tree is a node, so reads chain all the way down. Most say what they are
|
|
13
|
+
# with their own `type`. The stylesheet, a declaration block, and a declaration do not, so a node
|
|
14
|
+
# names those from where it sits.
|
|
15
|
+
#
|
|
16
|
+
class Node
|
|
17
|
+
include Enumerable #[LightningCSS::Node]
|
|
18
|
+
|
|
19
|
+
POSITION = ["type", "loc"].freeze #: Array[String]
|
|
20
|
+
DECLARATIONS = ["declarations", "importantDeclarations"].freeze #: Array[String]
|
|
21
|
+
|
|
22
|
+
attr_reader :parent #: LightningCSS::Node?
|
|
23
|
+
attr_reader :field #: String?
|
|
24
|
+
|
|
25
|
+
#: (Hash[String, untyped], ?LightningCSS::Node?, ?String?) -> void
|
|
26
|
+
def initialize(attributes, parent = nil, field = nil)
|
|
27
|
+
@attributes = attributes
|
|
28
|
+
@parent = parent
|
|
29
|
+
@field = field
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
#: () -> String?
|
|
33
|
+
def type
|
|
34
|
+
attributes.fetch("type") { inferred_type }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
#: () -> String?
|
|
38
|
+
def underscored_type
|
|
39
|
+
return nil unless (name = type)
|
|
40
|
+
|
|
41
|
+
name = name.tr("-", "_").downcase
|
|
42
|
+
|
|
43
|
+
rule? ? "#{name}_rule" : name
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
#: () -> bool
|
|
47
|
+
def rule?
|
|
48
|
+
field == "rules" && attributes.key?("type")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
#: () -> Hash[String, Integer]?
|
|
52
|
+
def location
|
|
53
|
+
attributes["loc"]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
#: (String) -> untyped
|
|
57
|
+
def [](key)
|
|
58
|
+
attributes[key]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
#: () -> Array[String]
|
|
62
|
+
def keys
|
|
63
|
+
attributes.keys
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
#: () -> Hash[String, untyped]
|
|
67
|
+
def to_h
|
|
68
|
+
attributes
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
#: (?untyped) -> String
|
|
72
|
+
def to_json(state = nil)
|
|
73
|
+
state ? attributes.to_json(state) : attributes.to_json
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
#: (Array[Symbol]?) -> Hash[Symbol, untyped]
|
|
77
|
+
def deconstruct_keys(keys)
|
|
78
|
+
found = {} #: Hash[Symbol, untyped]
|
|
79
|
+
|
|
80
|
+
if keys
|
|
81
|
+
keys.each do |name|
|
|
82
|
+
key = field_for(name.to_s)
|
|
83
|
+
|
|
84
|
+
if key
|
|
85
|
+
found[name] = wrap(attributes[key], key)
|
|
86
|
+
elsif name == :type && type
|
|
87
|
+
found[name] = type
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
else
|
|
91
|
+
found[:type] = type if type
|
|
92
|
+
attributes.each_key { |key| found[key.to_sym] = wrap(attributes[key], key) }
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
found
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
#: () -> Array[LightningCSS::Node]
|
|
99
|
+
def child_nodes
|
|
100
|
+
@child_nodes ||= attributes.flat_map { |key, value| key == "loc" ? [] : nodes_in(value, key) }.freeze
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
#: () { (LightningCSS::Node) -> void } -> void
|
|
104
|
+
#: () -> Enumerator[LightningCSS::Node, void]
|
|
105
|
+
def each(&)
|
|
106
|
+
return enum_for(:each) unless block_given?
|
|
107
|
+
|
|
108
|
+
yield self
|
|
109
|
+
|
|
110
|
+
child_nodes.each { |child| child.each(&) }
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
#: () -> Array[LightningCSS::Node]
|
|
114
|
+
def ancestors
|
|
115
|
+
parent ? [parent, *parent.ancestors] : []
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
#: (String) -> Array[LightningCSS::Node]
|
|
119
|
+
def every(type)
|
|
120
|
+
each.select { |node| node.type == type || node.underscored_type == type }
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
#: () -> Hash[String, untyped]
|
|
124
|
+
def fields
|
|
125
|
+
attributes.except(*POSITION)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
#: () -> String
|
|
129
|
+
def inspect
|
|
130
|
+
described = fields.map { |key, value| described_field(key, value) }
|
|
131
|
+
position = location
|
|
132
|
+
described.unshift("location=#{position["line"]}:#{position["column"]}") if position
|
|
133
|
+
|
|
134
|
+
"#<#{self.class.name}#{" #{type}" if type}#{" #{described.join(" ")}" unless described.empty?}>"
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
#: (Symbol, *untyped) -> untyped
|
|
138
|
+
def method_missing(name, *arguments)
|
|
139
|
+
key = field_for(name.to_s)
|
|
140
|
+
|
|
141
|
+
return super unless key
|
|
142
|
+
|
|
143
|
+
wrap(attributes[key], key)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
#: (Symbol, ?bool) -> bool
|
|
147
|
+
def respond_to_missing?(name, include_private = false)
|
|
148
|
+
!field_for(name.to_s).nil? || super
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
protected
|
|
152
|
+
|
|
153
|
+
attr_reader :attributes #: Hash[String, untyped]
|
|
154
|
+
|
|
155
|
+
private
|
|
156
|
+
|
|
157
|
+
#: () -> String?
|
|
158
|
+
def inferred_type
|
|
159
|
+
if parent.nil? && attributes.key?("rules") && attributes.key?("sources")
|
|
160
|
+
"stylesheet"
|
|
161
|
+
elsif attributes.key?("importantDeclarations")
|
|
162
|
+
"declaration-block"
|
|
163
|
+
elsif DECLARATIONS.include?(field) && attributes.key?("property")
|
|
164
|
+
"declaration"
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
#: (untyped, String) -> Array[LightningCSS::Node]
|
|
169
|
+
def nodes_in(value, key)
|
|
170
|
+
case value
|
|
171
|
+
when Hash
|
|
172
|
+
[Node.new(value, self, key)]
|
|
173
|
+
when Array
|
|
174
|
+
value.flat_map { |item| nodes_in(item, key) }
|
|
175
|
+
else
|
|
176
|
+
[]
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
#: (String, untyped) -> String
|
|
181
|
+
def described_field(key, value)
|
|
182
|
+
case value
|
|
183
|
+
when Array
|
|
184
|
+
"#{key}=#{value.empty? ? "[]" : "[... #{counted(value)}]"}"
|
|
185
|
+
when Hash
|
|
186
|
+
type = Node.new(value, self, key).type
|
|
187
|
+
|
|
188
|
+
"#{key}=#<#{self.class.name}#{" #{type}" if type}>"
|
|
189
|
+
else
|
|
190
|
+
"#{key}=#{value.inspect}"
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
#: (Array[untyped]) -> String
|
|
195
|
+
def counted(items)
|
|
196
|
+
"#{items.length} #{items.length == 1 ? "item" : "items"}"
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
#: (String) -> String?
|
|
200
|
+
def field_for(name)
|
|
201
|
+
return name if attributes.key?(name)
|
|
202
|
+
|
|
203
|
+
camelized = name.gsub(/_([a-z\d])/) { Regexp.last_match(1).to_s.upcase }
|
|
204
|
+
|
|
205
|
+
camelized if attributes.key?(camelized)
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
#: (untyped, String) -> untyped
|
|
209
|
+
def wrap(value, key)
|
|
210
|
+
case value
|
|
211
|
+
when Hash
|
|
212
|
+
key == "loc" ? value : Node.new(value, self, key)
|
|
213
|
+
when Array
|
|
214
|
+
value.map { |item| wrap(item, key) }
|
|
215
|
+
else
|
|
216
|
+
value
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
data/lib/lightningcss/options.rb
CHANGED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LightningCSS
|
|
4
|
+
# What a stylesheet parsed to.
|
|
5
|
+
#
|
|
6
|
+
# `stylesheet` is the AST exactly as Lightning CSS serializes it, and `root` is the same tree as a
|
|
7
|
+
# `LightningCSS::Node` to walk. `warnings` are the ones a transform would have reported.
|
|
8
|
+
#
|
|
9
|
+
class ParseResult
|
|
10
|
+
attr_reader :source #: String?
|
|
11
|
+
attr_reader :stylesheet #: Hash[String, untyped]
|
|
12
|
+
attr_reader :warnings #: Array[LightningCSS::Warning]
|
|
13
|
+
|
|
14
|
+
#: (String, ?String?) -> LightningCSS::ParseResult
|
|
15
|
+
def self.from_json(payload, source = nil)
|
|
16
|
+
parsed = JSON.parse(payload)
|
|
17
|
+
|
|
18
|
+
new(
|
|
19
|
+
source: source,
|
|
20
|
+
stylesheet: parsed.fetch("stylesheet"),
|
|
21
|
+
warnings: parsed.fetch("warnings").map { |warning| Warning.from_hash(warning) }
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
#: (stylesheet: Hash[String, untyped], warnings: Array[LightningCSS::Warning], ?source: String?) -> void
|
|
26
|
+
def initialize(stylesheet:, warnings:, source: nil)
|
|
27
|
+
@source = source
|
|
28
|
+
@stylesheet = stylesheet.freeze
|
|
29
|
+
@warnings = warnings.freeze
|
|
30
|
+
|
|
31
|
+
freeze
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
#: () -> LightningCSS::Node
|
|
35
|
+
def root
|
|
36
|
+
Node.new(stylesheet)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
#: () -> bool
|
|
40
|
+
def warnings?
|
|
41
|
+
!warnings.empty?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
#: () -> String
|
|
45
|
+
def inspect
|
|
46
|
+
parts = ["rules=#{stylesheet.fetch("rules").length}"]
|
|
47
|
+
parts << "warnings=#{warnings.map(&:to_s).inspect}" if warnings?
|
|
48
|
+
|
|
49
|
+
"#<#{self.class.name} #{parts.join(" ")}>"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
data/lib/lightningcss/result.rb
CHANGED
|
@@ -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[
|
|
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[
|
|
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
|
|
@@ -28,6 +28,11 @@ module LightningCSS
|
|
|
28
28
|
|
|
29
29
|
alias call transform
|
|
30
30
|
|
|
31
|
+
#: (String, ?filename: String?, ?error_recovery: bool) -> LightningCSS::ParseResult
|
|
32
|
+
def parse(code, **overrides)
|
|
33
|
+
LightningCSS.parse(code, **options.slice(*Options::PARSE), **overrides)
|
|
34
|
+
end
|
|
35
|
+
|
|
31
36
|
#: (String, ?minify: bool, ?error_recovery: bool, ?targets: browsers?, ?css_modules: css_modules?, ?scope: String?) -> LightningCSS::Result
|
|
32
37
|
def bundle(path, **overrides)
|
|
33
38
|
LightningCSS.bundle(path, **options, **overrides)
|
data/lib/lightningcss/version.rb
CHANGED