Skip to content

Commit 6681513

Browse files
Update css.js
1 parent 9d7b7e2 commit 6681513

File tree

1 file changed

+185
-100
lines changed

1 file changed

+185
-100
lines changed

lib/ast/css.js

Lines changed: 185 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ exports.JSON = async function(cssText) {
7979
}
8080

8181
function peek() {
82-
return cleanedCSS[index];
82+
return index < length ? cleanedCSS[index] : null;
8383
}
8484

8585
function consume() {
@@ -98,14 +98,15 @@ exports.JSON = async function(cssText) {
9898

9999
if (char === '}') {
100100
index++;
101-
if (stack.length > 0) stack.pop();
101+
if (stack.length > 0) stack.pop();
102102
continue;
103103
}
104104

105105
if (char === '@') {
106106
rules.push(parseAtRuleV2());
107107
} else {
108-
rules.push(parseSelectorOrNested());
108+
const rule = parseSelectorOrNested();
109+
if (rule) rules.push(rule);
109110
}
110111
}
111112

@@ -116,7 +117,7 @@ exports.JSON = async function(cssText) {
116117
}
117118
const atRuleHeader = cleanedCSS.slice(start, index).trim();
118119

119-
if (cleanedCSS[index] === '{') {
120+
if (index < length && cleanedCSS[index] === '{') {
120121
index++;
121122
const nestedRules = [];
122123
stack.push({ type: 'at-rule', name: atRuleHeader, rules: nestedRules, id: ruleid++ });
@@ -127,118 +128,201 @@ exports.JSON = async function(cssText) {
127128
index++;
128129
break;
129130
}
130-
nestedRules.push(parseSelectorOrNested());
131+
if (peek() === '@') {
132+
nestedRules.push(parseAtRuleV2());
133+
} else {
134+
const rule = parseSelectorOrNested();
135+
if (rule) nestedRules.push(rule);
136+
}
131137
}
132138

133139
return { type: 'at-rule', name: atRuleHeader, rules: nestedRules, id: ruleid++ };
134140

135-
} else if (cleanedCSS[index] === ';') {
141+
} else if (index < length && cleanedCSS[index] === ';') {
136142
index++;
137143
return { type: 'at-rule', name: atRuleHeader, rules: [], id: ruleid++ };
138144
}
139145

140146
return { type: 'at-rule', name: atRuleHeader, rules: [], id: ruleid++ };
141147
}
148+
142149
function parseAtRuleV2() {
143150
let text = '';
144-
function insert() {
145-
let reading = true;
146-
let readingValue = false;
147-
while (reading) {
148-
if (!readingValue) {skipWhitespace()};
149-
if (peek()===':') {readingValue=true}
150-
else if(peek()===';'){readingValue=false}
151-
else if(peek()==='}'){reading=false};
152-
text += cleanedCSS[index++];
151+
const startIndex = index;
152+
153+
let braceCount = 0;
154+
let inString = false;
155+
let stringChar = null;
156+
let escaped = false;
157+
158+
while (index < length) {
159+
const char = cleanedCSS[index];
160+
161+
if (escaped) {
162+
escaped = false;
163+
text += char;
164+
index++;
165+
continue;
153166
}
154-
return { type: 'insert', text }
155-
}
156-
function insert2() {
157-
let reading = true;
158-
let readingString = false;
159-
let quoteChar = null;
160167

161-
while (reading && index < length) {
162-
const currentChar = peek();
163-
164-
if ((currentChar === '"' || currentChar === "'") && cleanedCSS[index - 1] !== '\\') {
165-
if (!readingString) {
166-
readingString = true;
167-
quoteChar = currentChar;
168-
} else if (currentChar === quoteChar) {
169-
readingString = false;
170-
}
171-
}
172-
173-
if (!readingString && (currentChar === ';' || currentChar === '}')) {
174-
reading = false;
175-
if (currentChar === ';') {
176-
text += cleanedCSS[index++];
168+
if (char === '\\') {
169+
escaped = true;
170+
text += char;
171+
index++;
172+
continue;
173+
}
174+
175+
if ((char === '"' || char === "'") && !inString) {
176+
inString = true;
177+
stringChar = char;
178+
text += char;
179+
index++;
180+
continue;
181+
}
182+
183+
if (inString && char === stringChar) {
184+
inString = false;
185+
text += char;
186+
index++;
187+
continue;
188+
}
189+
190+
if (!inString) {
191+
if (char === '{') {
192+
braceCount++;
193+
} else if (char === '}') {
194+
braceCount--;
195+
if (braceCount === 0) {
196+
text += char;
197+
index++;
198+
break;
177199
}
200+
} else if (char === ';' && braceCount === 0) {
201+
text += char;
202+
index++;
178203
break;
179204
}
180-
181-
text += cleanedCSS[index++];
182205
}
183-
return { type: 'insert', text };
206+
207+
text += char;
208+
index++;
184209
}
185-
const nextChars = cleanedCSS.slice(index + 1, index + 10);
186-
187-
if (nextChars.startsWith('property')) {
188-
index += 9;
189-
text = '@property ';
190-
return insert();
191-
} else if (nextChars.startsWith('font-face')) {
192-
index += 10;
193-
text = '@font-face ';
194-
return insert();
195-
} else if (nextChars.startsWith('import')) {
196-
index += 7;
197-
skipWhitespace();
210+
211+
return { type: 'insert', text: text.trim(), id: ruleid++ };
212+
}
213+
214+
function parsePropertyValue() {
215+
let value = '';
216+
let inString = false;
217+
let stringChar = null;
218+
let escaped = false;
219+
let parenCount = 0;
220+
let bracketCount = 0;
221+
222+
while (index < length) {
223+
const char = cleanedCSS[index];
198224

199-
if (cleanedCSS.slice(index, index + 3) === 'url') {
200-
index += 3;
201-
text = '@import url';
202-
return insert2();
203-
} else {
204-
text = '@import ';
205-
let reading = true;
206-
while (reading && index < length) {
207-
const currentChar = peek();
208-
if (currentChar === ';' || currentChar === '}') {
209-
reading = false;
210-
if (currentChar === ';') {
211-
text += cleanedCSS[index++];
212-
}
213-
break;
214-
}
215-
text += cleanedCSS[index++];
225+
if (escaped) {
226+
escaped = false;
227+
value += char;
228+
index++;
229+
continue;
230+
}
231+
232+
if (char === '\\') {
233+
escaped = true;
234+
value += char;
235+
index++;
236+
continue;
237+
}
238+
239+
if ((char === '"' || char === "'") && !inString) {
240+
inString = true;
241+
stringChar = char;
242+
value += char;
243+
index++;
244+
continue;
245+
}
246+
247+
if (inString && char === stringChar) {
248+
inString = false;
249+
value += char;
250+
index++;
251+
continue;
252+
}
253+
254+
if (!inString) {
255+
if (char === '(') {
256+
parenCount++;
257+
} else if (char === ')') {
258+
parenCount--;
259+
} else if (char === '[') {
260+
bracketCount++;
261+
} else if (char === ']') {
262+
bracketCount--;
263+
} else if (char === ';' && parenCount === 0 && bracketCount === 0) {
264+
break;
265+
} else if (char === '}' && parenCount === 0 && bracketCount === 0) {
266+
break;
216267
}
217-
return { type: 'insert', text };
218268
}
219-
} else {
220-
return parseAtRule();
269+
270+
value += char;
271+
index++;
221272
}
273+
274+
return value.trim();
222275
}
223276

224277
function parseSelectorOrNested() {
225278
let start = index;
226-
while (
227-
index < length &&
228-
cleanedCSS[index] !== '{' &&
229-
cleanedCSS[index] !== '}'
230-
) {
279+
let inString = false;
280+
let stringChar = null;
281+
let escaped = false;
282+
283+
while (index < length) {
284+
const char = cleanedCSS[index];
285+
286+
if (escaped) {
287+
escaped = false;
288+
index++;
289+
continue;
290+
}
291+
292+
if (char === '\\') {
293+
escaped = true;
294+
index++;
295+
continue;
296+
}
297+
298+
if ((char === '"' || char === "'") && !inString) {
299+
inString = true;
300+
stringChar = char;
301+
index++;
302+
continue;
303+
}
304+
305+
if (inString && char === stringChar) {
306+
inString = false;
307+
index++;
308+
continue;
309+
}
310+
311+
if (!inString && (char === '{' || char === '}')) {
312+
break;
313+
}
314+
231315
index++;
232316
}
233317

234318
const selectorText = cleanedCSS.slice(start, index).trim();
235319

236-
if (cleanedCSS[index] === '{') {
320+
if (index < length && cleanedCSS[index] === '{') {
237321
index++;
238322

239323
skipWhitespace();
240324

241-
if (/^[.#a-zA-Z0-9\-\s,:()%*\+<>_\[\]="]+$/.test(selectorText)) {
325+
if (selectorText) {
242326
const selectors = selectorText.split(',').map(s => s.trim());
243327
let properties = [];
244328
const nestedRules = [];
@@ -256,38 +340,39 @@ exports.JSON = async function(cssText) {
256340
}
257341

258342
let propStart = index;
259-
while (
260-
index < length &&
261-
cleanedCSS[index] !== ';' &&
262-
cleanedCSS[index] !== '}'
263-
) {
343+
while (index < length && cleanedCSS[index] !== ':' && cleanedCSS[index] !== ';' && cleanedCSS[index] !== '}') {
264344
index++;
265345
}
266346

267-
const line = cleanedCSS.slice(propStart, index).trim();
268-
269-
if (!line) break;
270-
271-
if (line.includes(':')) {
272-
const [key, value] = line.split(':').map(s => s.trim());
273-
properties.push([key, value]);
274-
if (cleanedCSS[index] === ';') index++;
347+
if (index >= length) break;
348+
349+
const key = cleanedCSS.slice(propStart, index).trim();
350+
351+
if (cleanedCSS[index] === ':') {
352+
index++;
353+
const value = parsePropertyValue();
354+
355+
if (key && value !== '') {
356+
properties.push([key, value]);
357+
}
358+
359+
if (index < length && cleanedCSS[index] === ';') {
360+
index++;
361+
}
362+
} else if (cleanedCSS[index] === ';') {
363+
index++;
275364
} else if (cleanedCSS[index] === '}') {
276365
index++;
277366
break;
278367
}
279368
}
280369

281370
return { type: 'rule', selectors, properties, nestedRules, id: ruleid++ };
282-
283-
} else {
284-
return null;
285371
}
286-
287-
} else {
288-
return null;
289372
}
373+
374+
return null;
290375
}
291376

292-
return rules.filter(Boolean).sort((a, b) => a.id - b.id);;
377+
return rules.filter(Boolean).sort((a, b) => a.id - b.id);
293378
}

0 commit comments

Comments
 (0)