forked from angular/angular-cli
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcss-import-lexer.ts
129 lines (120 loc) · 3.74 KB
/
css-import-lexer.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
/**
* Determines if a unicode code point is a CSS whitespace character.
* @param code The unicode code point to test.
* @returns true, if the code point is CSS whitespace; false, otherwise.
*/
function isWhitespace(code: number): boolean {
// Based on https://www.w3.org/TR/css-syntax-3/#whitespace
switch (code) {
case 0x0009: // tab
case 0x0020: // space
case 0x000a: // line feed
case 0x000c: // form feed
case 0x000d: // carriage return
return true;
default:
return false;
}
}
/**
* Scans a CSS or Sass file and locates all valid import/use directive values as defined by the
* syntax specification.
* @param contents A string containing a CSS or Sass file to scan.
* @returns An iterable that yields each CSS directive value found.
*/
export function* findImports(
contents: string,
sass: boolean,
): Iterable<{ start: number; end: number; specifier: string; fromUse?: boolean }> {
yield* find(contents, '@import ');
if (sass) {
for (const result of find(contents, '@use ')) {
yield { ...result, fromUse: true };
}
}
}
/**
* Scans a CSS or Sass file and locates all valid function/directive values as defined by the
* syntax specification.
* @param contents A string containing a CSS or Sass file to scan.
* @param prefix The prefix to start a valid segment.
* @returns An iterable that yields each CSS url function value found.
*/
function* find(
contents: string,
prefix: string,
): Iterable<{ start: number; end: number; specifier: string }> {
let pos = 0;
let width = 1;
let current = -1;
const next = () => {
pos += width;
current = contents.codePointAt(pos) ?? -1;
width = current > 0xffff ? 2 : 1;
return current;
};
// Based on https://www.w3.org/TR/css-syntax-3/#consume-ident-like-token
while ((pos = contents.indexOf(prefix, pos)) !== -1) {
// Set to position of the last character in prefix
pos += prefix.length - 1;
width = 1;
// Consume all leading whitespace
while (isWhitespace(next())) {
/* empty */
}
// Initialize URL state
const url = { start: pos, end: -1, specifier: '' };
let complete = false;
// If " or ', then consume the value as a string
if (current === 0x0022 || current === 0x0027) {
const ending = current;
// Based on https://www.w3.org/TR/css-syntax-3/#consume-string-token
while (!complete) {
switch (next()) {
case -1: // EOF
return;
case 0x000a: // line feed
case 0x000c: // form feed
case 0x000d: // carriage return
// Invalid
complete = true;
break;
case 0x005c: // \ -- character escape
// If not EOF or newline, add the character after the escape
switch (next()) {
case -1:
return;
case 0x000a: // line feed
case 0x000c: // form feed
case 0x000d: // carriage return
// Skip when inside a string
break;
default:
// TODO: Handle hex escape codes
url.specifier += String.fromCodePoint(current);
break;
}
break;
case ending:
// Full string position should include the quotes for replacement
url.end = pos + 1;
complete = true;
yield url;
break;
default:
url.specifier += String.fromCodePoint(current);
break;
}
}
next();
continue;
}
}
}