sugar-high 2.0.0 → 2.0.2
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.
- package/lib/presets/lang/css.js +123 -0
- package/package.json +1 -1
package/lib/presets/lang/css.js
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
// @ts-check
|
|
2
|
+
import {
|
|
3
|
+
T_BREAK, T_CLASS, T_COMMENT, T_IDENTIFIER, T_PROPERTY, T_SIGN, T_SPACE,
|
|
4
|
+
} from '../../shared.js'
|
|
5
|
+
import { tokenize as tokenizePlain } from '../../core.js'
|
|
6
|
+
|
|
2
7
|
export const keywords = new Set([
|
|
3
8
|
// css keywords like @media, @import, @keyframes, etc.
|
|
4
9
|
'@media', '@import', '@keyframes', '@font-face', '@supports', '@page', '@counter-style',
|
|
@@ -17,3 +22,121 @@ export const onLiteral = (curr, index, code) => {
|
|
|
17
22
|
if (curr !== '#') return 0
|
|
18
23
|
return code.slice(index).match(/^#(?:[\da-f]{8}|[\da-f]{6}|[\da-f]{4}|[\da-f]{3})(?![\w-])/i)?.[0].length || 0
|
|
19
24
|
}
|
|
25
|
+
|
|
26
|
+
const isIgnored = (type) => type === T_SPACE || type === T_BREAK || type === T_COMMENT
|
|
27
|
+
const isPropertyPart = ([type, value]) =>
|
|
28
|
+
type === T_IDENTIFIER || type === T_CLASS || (type === T_SIGN && value === '-')
|
|
29
|
+
const isNamePart = ([type]) =>
|
|
30
|
+
type === T_IDENTIFIER || type === T_CLASS || type === T_PROPERTY
|
|
31
|
+
const isNameStart = (token) => isNamePart(token) && !/^\d/.test(token[1])
|
|
32
|
+
const isHyphen = ([type, value]) => type === T_SIGN && value === '-'
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Rejoin CSS dashed identifiers split by the shared punctuation lexer.
|
|
36
|
+
* @param {Array<[number, string]>} tokens
|
|
37
|
+
*/
|
|
38
|
+
const mergeDashedNames = (tokens) => {
|
|
39
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
40
|
+
let firstWord = index
|
|
41
|
+
let end = index
|
|
42
|
+
|
|
43
|
+
if (isHyphen(tokens[end])) {
|
|
44
|
+
while (isHyphen(tokens[end])) end++
|
|
45
|
+
if (!tokens[end] || !isNameStart(tokens[end])) continue
|
|
46
|
+
firstWord = end++
|
|
47
|
+
} else if (isNameStart(tokens[end])) {
|
|
48
|
+
end++
|
|
49
|
+
} else {
|
|
50
|
+
continue
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let dashed = firstWord > index
|
|
54
|
+
while (tokens[end] && isHyphen(tokens[end])) {
|
|
55
|
+
const hyphenStart = end
|
|
56
|
+
while (tokens[end] && isHyphen(tokens[end])) end++
|
|
57
|
+
if (!tokens[end] || !isNamePart(tokens[end])) {
|
|
58
|
+
end = hyphenStart
|
|
59
|
+
break
|
|
60
|
+
}
|
|
61
|
+
dashed = true
|
|
62
|
+
end++
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!dashed) continue
|
|
66
|
+
const name = tokens.slice(index, end).map(([, value]) => value).join('')
|
|
67
|
+
tokens.splice(index, end - index, [tokens[firstWord][0], name])
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Return true when a colon belongs to a nested selector instead of a declaration. */
|
|
72
|
+
const opensBlock = (tokens, start) => {
|
|
73
|
+
let parentheses = 0
|
|
74
|
+
let brackets = 0
|
|
75
|
+
for (let index = start; index < tokens.length; index++) {
|
|
76
|
+
const [type, value] = tokens[index]
|
|
77
|
+
if (type !== T_SIGN) continue
|
|
78
|
+
if (value === '(') parentheses++
|
|
79
|
+
else if (value === ')') parentheses--
|
|
80
|
+
else if (value === '[') brackets++
|
|
81
|
+
else if (value === ']') brackets--
|
|
82
|
+
else if (!parentheses && !brackets && value === '{') return true
|
|
83
|
+
else if (!parentheses && !brackets && (value === ';' || value === '}')) return false
|
|
84
|
+
}
|
|
85
|
+
return false
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Add CSS declaration context after the shared plain lexer runs.
|
|
90
|
+
* @param {string} code
|
|
91
|
+
* @param {import('../../core.js').ParseOptions} options
|
|
92
|
+
*/
|
|
93
|
+
export const tokenize = (code, options) => {
|
|
94
|
+
const tokens = tokenizePlain(code, { ...options, tokenize: undefined })
|
|
95
|
+
mergeDashedNames(tokens)
|
|
96
|
+
let blockDepth = 0
|
|
97
|
+
let declarationStart = false
|
|
98
|
+
|
|
99
|
+
for (let index = 0; index < tokens.length; index++) {
|
|
100
|
+
const [type, value] = tokens[index]
|
|
101
|
+
|
|
102
|
+
if (type === T_SIGN && value === '{') {
|
|
103
|
+
blockDepth++
|
|
104
|
+
declarationStart = true
|
|
105
|
+
continue
|
|
106
|
+
}
|
|
107
|
+
if (type === T_SIGN && value === '}') {
|
|
108
|
+
blockDepth--
|
|
109
|
+
declarationStart = false
|
|
110
|
+
continue
|
|
111
|
+
}
|
|
112
|
+
if (type === T_SIGN && value === ';') {
|
|
113
|
+
declarationStart = blockDepth > 0
|
|
114
|
+
continue
|
|
115
|
+
}
|
|
116
|
+
if (!declarationStart || isIgnored(type)) continue
|
|
117
|
+
|
|
118
|
+
const propertyStart = index
|
|
119
|
+
let propertyEnd = index
|
|
120
|
+
while (propertyEnd < tokens.length && isPropertyPart(tokens[propertyEnd])) {
|
|
121
|
+
propertyEnd++
|
|
122
|
+
}
|
|
123
|
+
let colon = propertyEnd
|
|
124
|
+
while (colon < tokens.length && isIgnored(tokens[colon][0])) colon++
|
|
125
|
+
|
|
126
|
+
if (
|
|
127
|
+
propertyEnd > propertyStart &&
|
|
128
|
+
tokens[colon]?.[0] === T_SIGN &&
|
|
129
|
+
tokens[colon][1] === ':' &&
|
|
130
|
+
!opensBlock(tokens, colon + 1)
|
|
131
|
+
) {
|
|
132
|
+
const property = tokens
|
|
133
|
+
.slice(propertyStart, propertyEnd)
|
|
134
|
+
.map(([, part]) => part)
|
|
135
|
+
.join('')
|
|
136
|
+
tokens.splice(propertyStart, propertyEnd - propertyStart, [T_PROPERTY, property])
|
|
137
|
+
}
|
|
138
|
+
declarationStart = false
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return tokens
|
|
142
|
+
}
|