Skip to content

Commit 1e513a1

Browse files
committed
fix nesting rules expansion #45
1 parent c4e7d90 commit 1e513a1

File tree

19 files changed

+469
-45
lines changed

19 files changed

+469
-45
lines changed

.github/workflows/jsr.yml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
# publish to jsr
21
name: Publish
3-
42
on:
53
push:
64
branches:
@@ -9,9 +7,13 @@ on:
97
jobs:
108
publish:
119
runs-on: ubuntu-latest
10+
1211
permissions:
1312
contents: read
14-
id-token: write # The OIDC ID token is used for authentication with JSR.
13+
id-token: write
14+
1515
steps:
1616
- uses: actions/checkout@v4
17-
- run: npx jsr publish
17+
18+
- name: Publish package
19+
run: npx jsr publish

CHANGELOG.md

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,30 @@
11
# Changelog
22

3+
# v0.8.0
4+
5+
- [x] fix nesting rules expansion #45
6+
- [ ] at-rules prefix removal
7+
- [ ] at rules validation
8+
- [ ] prelude
9+
- [ ] body
10+
311
#v0.7.0
412

513
- [x] fix merging rules
614
- [ ] experimental CSS prefix removal
715
- [x] declaration name
816
- [ ] declaration value
917
- [ ] exclude -webkit-* gradients
10-
- [x] css selector validation
11-
- [x] pseudo element
12-
- [x] partial pseudo class validation. does not validate parameters
13-
- [x] attribute selector
14-
- [x] combinator
15-
- [x] simple selector
16-
- [x] nested selector
17-
- [x] strict vs permissive validation: allow unknown items such as pseudo classes
18-
- [x] allow unknown pseudo classes
19-
- [x] allow unknown attribute selectors
18+
- [x] css selector validation
19+
- [x] pseudo element
20+
- [x] partial pseudo class validation. does not validate parameters
21+
- [x] attribute selector
22+
- [x] combinator
23+
- [x] simple selector
24+
- [x] nested selector
25+
- [x] strict vs permissive validation: allow unknown items such as pseudo classes
26+
- [x] allow unknown pseudo classes
27+
- [x] allow unknown attribute selectors
2028
- [x] strip universal selector when possible
2129

2230
# v0.6.0
@@ -28,27 +36,27 @@
2836

2937
- [x] incorrectly expand css nesting rules
3038

31-
## V0.5.3
39+
## v0.5.3
3240

3341
- [x] incorrectly expand css nesting rules
3442

35-
## V0.5.1
43+
## v0.5.1
3644

3745
- [x] failed to flatten @import when using url() syntax
3846

39-
## V0.5.0
47+
## v0.5.0
4048

4149
- [x] render node with parents
4250
- [x] fix relative color from xyz
4351
- [x] fix bug when inlineCss is true bug no css variable exists
4452
- [x] compute more shorthands
4553
- [x] (web) fetch imported css files from external domains using cors
4654

47-
## V0.4.1
55+
## v0.4.1
4856

4957
no code change
5058

51-
## V0.4.0
59+
## v0.4.0
5260

5361
Parsing
5462

@@ -66,7 +74,7 @@ CSS color level 4 & 5
6674
- [x] oklab()
6775
- [x] oklch()
6876

69-
## V0.3.0
77+
## v0.3.0
7078

7179
### shorthands
7280

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,15 @@ CSS parser and minifier for node and the browser
66

77
## Installation
88

9+
From npm
910
```shell
1011
$ npm install @tbela99/css-parser
1112
```
13+
from jsr
14+
```shell
15+
$ deno add @tbela99/css-parser
16+
```
17+
1218

1319
## Features
1420

dist/index-umd-web.js

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5760,6 +5760,13 @@
57605760
buffer = '';
57615761
}
57625762
break;
5763+
case '&':
5764+
if (buffer.length > 0) {
5765+
yield pushToken(buffer, parseInfo);
5766+
buffer = '';
5767+
}
5768+
yield pushToken(value, parseInfo);
5769+
break;
57635770
case '<':
57645771
if (buffer.length > 0) {
57655772
yield pushToken(buffer, parseInfo);
@@ -60872,7 +60879,67 @@
6087260879
}
6087360880
}
6087460881
else {
60875-
rule.sel = replaceCompound(rule.sel, ast.sel);
60882+
let childSelectorCompund = [];
60883+
let withCompound = [];
60884+
let withoutCompound = [];
60885+
const rules = splitRule(ast.sel);
60886+
for (const sel of (rule.raw ?? splitRule(rule.sel))) {
60887+
const s = sel.join('');
60888+
if (s.includes('&')) {
60889+
if (s.indexOf('&', 1) == -1) {
60890+
if (s.at(0) == '&') {
60891+
if (s.at(1) == ' ') {
60892+
childSelectorCompund.push(s.slice(2));
60893+
}
60894+
else {
60895+
if (s == '&') {
60896+
withCompound.push(s);
60897+
}
60898+
else {
60899+
withoutCompound.push(s.slice(1));
60900+
}
60901+
}
60902+
}
60903+
}
60904+
else {
60905+
withCompound.push(s);
60906+
}
60907+
}
60908+
else {
60909+
withoutCompound.push(s);
60910+
}
60911+
}
60912+
const selectors = [];
60913+
const selector = rules.length > 1 ? ':is(' + rules.map(a => a.join('')).join(',') + ')' : rules[0].join('');
60914+
if (childSelectorCompund.length > 0) {
60915+
if (childSelectorCompund.length == 1) {
60916+
selectors.push(replaceCompound('& ' + childSelectorCompund[0].trim(), selector));
60917+
}
60918+
else {
60919+
selectors.push(replaceCompound('& :is(' + childSelectorCompund.reduce((acc, curr) => acc + (acc.length > 0 ? ',' : '') + curr.trim(), '') + ')', selector));
60920+
}
60921+
}
60922+
if (withoutCompound.length > 0) {
60923+
if (withoutCompound.length == 1) {
60924+
const useIs = rules.length == 1 && selector.match(/^[a-zA-Z.:]/) != null && selector.includes(' ') && withoutCompound.length == 1 && withoutCompound[0].match(/^[a-zA-Z]+$/) != null;
60925+
const compound = useIs ? ':is(&)' : '&';
60926+
selectors.push(replaceCompound(rules.length == 1 ? (useIs ? withoutCompound[0] + ':is(&)' : (selector.match(/^[.:]/) && withoutCompound[0].match(/^[a-zA-Z]+$/) ? withoutCompound[0] + compound : compound + withoutCompound[0])) : (withoutCompound[0].match(/^[a-zA-Z:]+$/) ? withoutCompound[0].trim() + compound : '&' + (withoutCompound[0].match(/^\S+$/) ? withoutCompound[0].trim() : ':is(' + withoutCompound[0].trim() + ')')), selector));
60927+
}
60928+
else {
60929+
selectors.push(replaceCompound('&:is(' + withoutCompound.reduce((acc, curr) => acc + (acc.length > 0 ? ',' : '') + curr.trim(), '') + ')', selector));
60930+
}
60931+
}
60932+
if (withCompound.length > 0) {
60933+
if (withCompound.length == 1) {
60934+
selectors.push(replaceCompound(withCompound[0], selector));
60935+
}
60936+
else {
60937+
for (const w of withCompound) {
60938+
selectors.push(replaceCompound(w, selector));
60939+
}
60940+
}
60941+
}
60942+
rule.sel = selectors.reduce((acc, curr) => curr.length == 0 ? acc : acc + (acc.length > 0 ? ',' : '') + curr, '');
6087660943
}
6087760944
ast.chi.splice(i--, 1);
6087860945
result.push(...expandRule(rule));
@@ -63355,6 +63422,9 @@
6335563422
return fetch(t, t.origin != self.origin ? { mode: 'cors' } : {}).then(parseResponse);
6335663423
}
6335763424

63425+
/**
63426+
* render ast node
63427+
*/
6335863428
function render(data, options = {}) {
6335963429
return doRender(data, Object.assign(options, {
6336063430
load,
@@ -63363,6 +63433,9 @@
6336363433
cwd: options.cwd ?? self.location.pathname.endsWith('/') ? self.location.pathname : dirname(self.location.pathname)
6336463434
}));
6336563435
}
63436+
/**
63437+
* parse css
63438+
*/
6336663439
async function parse(iterator, opt = {}) {
6336763440
return doParse(iterator, Object.assign(opt, {
6336863441
load,
@@ -63371,6 +63444,9 @@
6337163444
cwd: opt.cwd ?? self.location.pathname.endsWith('/') ? self.location.pathname : dirname(self.location.pathname)
6337263445
}));
6337363446
}
63447+
/**
63448+
* parse and render css
63449+
*/
6337463450
async function transform(css, options = {}) {
6337563451
options = { minify: true, removeEmpty: true, removeCharset: true, ...options };
6337663452
const startTime = performance.now();

dist/index.cjs

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5759,6 +5759,13 @@ function* tokenize(stream) {
57595759
buffer = '';
57605760
}
57615761
break;
5762+
case '&':
5763+
if (buffer.length > 0) {
5764+
yield pushToken(buffer, parseInfo);
5765+
buffer = '';
5766+
}
5767+
yield pushToken(value, parseInfo);
5768+
break;
57625769
case '<':
57635770
if (buffer.length > 0) {
57645771
yield pushToken(buffer, parseInfo);
@@ -60871,7 +60878,67 @@ function expandRule(node) {
6087160878
}
6087260879
}
6087360880
else {
60874-
rule.sel = replaceCompound(rule.sel, ast.sel);
60881+
let childSelectorCompund = [];
60882+
let withCompound = [];
60883+
let withoutCompound = [];
60884+
const rules = splitRule(ast.sel);
60885+
for (const sel of (rule.raw ?? splitRule(rule.sel))) {
60886+
const s = sel.join('');
60887+
if (s.includes('&')) {
60888+
if (s.indexOf('&', 1) == -1) {
60889+
if (s.at(0) == '&') {
60890+
if (s.at(1) == ' ') {
60891+
childSelectorCompund.push(s.slice(2));
60892+
}
60893+
else {
60894+
if (s == '&') {
60895+
withCompound.push(s);
60896+
}
60897+
else {
60898+
withoutCompound.push(s.slice(1));
60899+
}
60900+
}
60901+
}
60902+
}
60903+
else {
60904+
withCompound.push(s);
60905+
}
60906+
}
60907+
else {
60908+
withoutCompound.push(s);
60909+
}
60910+
}
60911+
const selectors = [];
60912+
const selector = rules.length > 1 ? ':is(' + rules.map(a => a.join('')).join(',') + ')' : rules[0].join('');
60913+
if (childSelectorCompund.length > 0) {
60914+
if (childSelectorCompund.length == 1) {
60915+
selectors.push(replaceCompound('& ' + childSelectorCompund[0].trim(), selector));
60916+
}
60917+
else {
60918+
selectors.push(replaceCompound('& :is(' + childSelectorCompund.reduce((acc, curr) => acc + (acc.length > 0 ? ',' : '') + curr.trim(), '') + ')', selector));
60919+
}
60920+
}
60921+
if (withoutCompound.length > 0) {
60922+
if (withoutCompound.length == 1) {
60923+
const useIs = rules.length == 1 && selector.match(/^[a-zA-Z.:]/) != null && selector.includes(' ') && withoutCompound.length == 1 && withoutCompound[0].match(/^[a-zA-Z]+$/) != null;
60924+
const compound = useIs ? ':is(&)' : '&';
60925+
selectors.push(replaceCompound(rules.length == 1 ? (useIs ? withoutCompound[0] + ':is(&)' : (selector.match(/^[.:]/) && withoutCompound[0].match(/^[a-zA-Z]+$/) ? withoutCompound[0] + compound : compound + withoutCompound[0])) : (withoutCompound[0].match(/^[a-zA-Z:]+$/) ? withoutCompound[0].trim() + compound : '&' + (withoutCompound[0].match(/^\S+$/) ? withoutCompound[0].trim() : ':is(' + withoutCompound[0].trim() + ')')), selector));
60926+
}
60927+
else {
60928+
selectors.push(replaceCompound('&:is(' + withoutCompound.reduce((acc, curr) => acc + (acc.length > 0 ? ',' : '') + curr.trim(), '') + ')', selector));
60929+
}
60930+
}
60931+
if (withCompound.length > 0) {
60932+
if (withCompound.length == 1) {
60933+
selectors.push(replaceCompound(withCompound[0], selector));
60934+
}
60935+
else {
60936+
for (const w of withCompound) {
60937+
selectors.push(replaceCompound(w, selector));
60938+
}
60939+
}
60940+
}
60941+
rule.sel = selectors.reduce((acc, curr) => curr.length == 0 ? acc : acc + (acc.length > 0 ? ',' : '') + curr, '');
6087560942
}
6087660943
ast.chi.splice(i--, 1);
6087760944
result.push(...expandRule(rule));
@@ -63343,12 +63410,25 @@ async function load(url, currentFile) {
6334363410
return matchUrl.test(resolved.absolute) ? fetch(resolved.absolute).then(parseResponse) : promises.readFile(resolved.absolute, { encoding: 'utf-8' });
6334463411
}
6334563412

63413+
/**
63414+
* entry point for node and other runtimes
63415+
* @module
63416+
*/
63417+
/**
63418+
* render ast node
63419+
*/
6334663420
function render(data, options = {}) {
6334763421
return doRender(data, Object.assign(options, { load, resolve, dirname, cwd: options.cwd ?? process.cwd() }));
6334863422
}
63423+
/**
63424+
* parse css
63425+
*/
6334963426
async function parse(iterator, opt = {}) {
6335063427
return doParse(iterator, Object.assign(opt, { load, resolve, dirname, cwd: opt.cwd ?? process.cwd() }));
6335163428
}
63429+
/**
63430+
* parse and render css
63431+
*/
6335263432
async function transform(css, options = {}) {
6335363433
options = { minify: true, removeEmpty: true, removeCharset: true, ...options };
6335463434
const startTime = performance.now();

dist/index.d.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1013,8 +1013,22 @@ declare function resolve(url: string, currentDirectory: string, cwd?: string): {
10131013

10141014
declare function load(url: string, currentFile: string): Promise<string>;
10151015

1016+
/**
1017+
* entry point for node and other runtimes
1018+
* @module
1019+
*/
1020+
1021+
/**
1022+
* render ast node
1023+
*/
10161024
declare function render(data: AstNode, options?: RenderOptions): RenderResult;
1025+
/**
1026+
* parse css
1027+
*/
10171028
declare function parse(iterator: string, opt?: ParserOptions): Promise<ParseResult>;
1029+
/**
1030+
* parse and render css
1031+
*/
10181032
declare function transform(css: string, options?: TransformOptions): Promise<TransformResult>;
10191033

10201034
export { EnumToken, dirname, expand, load, minify, parse, parseString, parseTokens, render, renderToken, resolve, transform, walk, walkValues };

0 commit comments

Comments
 (0)