blob: 4144cc92d65835ad238251ff805a37d4c6ce3f02 [file] [log] [blame]
Tim van der Lippe16b82282021-11-08 13:50:26 +00001'use strict'
Mathias Bynens79e2cf02020-05-29 16:46:17 +02002
Tim van der Lippe16b82282021-11-08 13:50:26 +00003let Declaration = require('./declaration')
4let tokenizer = require('./tokenize')
5let Comment = require('./comment')
6let AtRule = require('./at-rule')
7let Root = require('./root')
8let Rule = require('./rule')
Mathias Bynens79e2cf02020-05-29 16:46:17 +02009
Tim van der Lippe16b82282021-11-08 13:50:26 +000010class Parser {
11 constructor(input) {
12 this.input = input
Mathias Bynens79e2cf02020-05-29 16:46:17 +020013
Tim van der Lippe16b82282021-11-08 13:50:26 +000014 this.root = new Root()
15 this.current = this.root
16 this.spaces = ''
17 this.semicolon = false
18 this.customProperty = false
Mathias Bynens79e2cf02020-05-29 16:46:17 +020019
Tim van der Lippe16b82282021-11-08 13:50:26 +000020 this.createTokenizer()
21 this.root.source = { input, start: { offset: 0, line: 1, column: 1 } }
Mathias Bynens79e2cf02020-05-29 16:46:17 +020022 }
23
Tim van der Lippe16b82282021-11-08 13:50:26 +000024 createTokenizer() {
25 this.tokenizer = tokenizer(this.input)
26 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +020027
Tim van der Lippe16b82282021-11-08 13:50:26 +000028 parse() {
29 let token
Mathias Bynens79e2cf02020-05-29 16:46:17 +020030 while (!this.tokenizer.endOfFile()) {
Tim van der Lippe16b82282021-11-08 13:50:26 +000031 token = this.tokenizer.nextToken()
Mathias Bynens79e2cf02020-05-29 16:46:17 +020032
33 switch (token[0]) {
34 case 'space':
Tim van der Lippe16b82282021-11-08 13:50:26 +000035 this.spaces += token[1]
36 break
Mathias Bynens79e2cf02020-05-29 16:46:17 +020037
38 case ';':
Tim van der Lippe16b82282021-11-08 13:50:26 +000039 this.freeSemicolon(token)
40 break
Mathias Bynens79e2cf02020-05-29 16:46:17 +020041
42 case '}':
Tim van der Lippe16b82282021-11-08 13:50:26 +000043 this.end(token)
44 break
Mathias Bynens79e2cf02020-05-29 16:46:17 +020045
46 case 'comment':
Tim van der Lippe16b82282021-11-08 13:50:26 +000047 this.comment(token)
48 break
Mathias Bynens79e2cf02020-05-29 16:46:17 +020049
50 case 'at-word':
Tim van der Lippe16b82282021-11-08 13:50:26 +000051 this.atrule(token)
52 break
Mathias Bynens79e2cf02020-05-29 16:46:17 +020053
54 case '{':
Tim van der Lippe16b82282021-11-08 13:50:26 +000055 this.emptyRule(token)
56 break
Mathias Bynens79e2cf02020-05-29 16:46:17 +020057
58 default:
Tim van der Lippe16b82282021-11-08 13:50:26 +000059 this.other(token)
60 break
Mathias Bynens79e2cf02020-05-29 16:46:17 +020061 }
62 }
Tim van der Lippe16b82282021-11-08 13:50:26 +000063 this.endFile()
64 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +020065
Tim van der Lippe16b82282021-11-08 13:50:26 +000066 comment(token) {
67 let node = new Comment()
68 this.init(node, token[2])
69 node.source.end = this.getPosition(token[3] || token[2])
Mathias Bynens79e2cf02020-05-29 16:46:17 +020070
Tim van der Lippe16b82282021-11-08 13:50:26 +000071 let text = token[1].slice(2, -2)
Mathias Bynens79e2cf02020-05-29 16:46:17 +020072 if (/^\s*$/.test(text)) {
Tim van der Lippe16b82282021-11-08 13:50:26 +000073 node.text = ''
74 node.raws.left = text
75 node.raws.right = ''
Mathias Bynens79e2cf02020-05-29 16:46:17 +020076 } else {
Tim van der Lippe16b82282021-11-08 13:50:26 +000077 let match = text.match(/^(\s*)([^]*\S)(\s*)$/)
78 node.text = match[2]
79 node.raws.left = match[1]
80 node.raws.right = match[3]
Mathias Bynens79e2cf02020-05-29 16:46:17 +020081 }
Tim van der Lippe16b82282021-11-08 13:50:26 +000082 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +020083
Tim van der Lippe16b82282021-11-08 13:50:26 +000084 emptyRule(token) {
85 let node = new Rule()
86 this.init(node, token[2])
87 node.selector = ''
88 node.raws.between = ''
89 this.current = node
90 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +020091
Tim van der Lippe16b82282021-11-08 13:50:26 +000092 other(start) {
93 let end = false
94 let type = null
95 let colon = false
96 let bracket = null
97 let brackets = []
98 let customProperty = start[1].startsWith('--')
Mathias Bynens79e2cf02020-05-29 16:46:17 +020099
Tim van der Lippe16b82282021-11-08 13:50:26 +0000100 let tokens = []
101 let token = start
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200102 while (token) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000103 type = token[0]
104 tokens.push(token)
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200105
106 if (type === '(' || type === '[') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000107 if (!bracket) bracket = token
108 brackets.push(type === '(' ? ')' : ']')
109 } else if (customProperty && colon && type === '{') {
110 if (!bracket) bracket = token
111 brackets.push('}')
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200112 } else if (brackets.length === 0) {
113 if (type === ';') {
114 if (colon) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000115 this.decl(tokens, customProperty)
116 return
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200117 } else {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000118 break
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200119 }
120 } else if (type === '{') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000121 this.rule(tokens)
122 return
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200123 } else if (type === '}') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000124 this.tokenizer.back(tokens.pop())
125 end = true
126 break
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200127 } else if (type === ':') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000128 colon = true
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200129 }
130 } else if (type === brackets[brackets.length - 1]) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000131 brackets.pop()
132 if (brackets.length === 0) bracket = null
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200133 }
134
Tim van der Lippe16b82282021-11-08 13:50:26 +0000135 token = this.tokenizer.nextToken()
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200136 }
137
Tim van der Lippe16b82282021-11-08 13:50:26 +0000138 if (this.tokenizer.endOfFile()) end = true
139 if (brackets.length > 0) this.unclosedBracket(bracket)
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200140
141 if (end && colon) {
142 while (tokens.length) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000143 token = tokens[tokens.length - 1][0]
144 if (token !== 'space' && token !== 'comment') break
145 this.tokenizer.back(tokens.pop())
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200146 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000147 this.decl(tokens, customProperty)
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200148 } else {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000149 this.unknownWord(tokens)
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200150 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000151 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200152
Tim van der Lippe16b82282021-11-08 13:50:26 +0000153 rule(tokens) {
154 tokens.pop()
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200155
Tim van der Lippe16b82282021-11-08 13:50:26 +0000156 let node = new Rule()
157 this.init(node, tokens[0][2])
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200158
Tim van der Lippe16b82282021-11-08 13:50:26 +0000159 node.raws.between = this.spacesAndCommentsFromEnd(tokens)
160 this.raw(node, 'selector', tokens)
161 this.current = node
162 }
163
164 decl(tokens, customProperty) {
165 let node = new Declaration()
166 this.init(node, tokens[0][2])
167
168 let last = tokens[tokens.length - 1]
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200169 if (last[0] === ';') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000170 this.semicolon = true
171 tokens.pop()
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200172 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000173 node.source.end = this.getPosition(last[3] || last[2])
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200174
175 while (tokens[0][0] !== 'word') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000176 if (tokens.length === 1) this.unknownWord(tokens)
177 node.raws.before += tokens.shift()[1]
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200178 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000179 node.source.start = this.getPosition(tokens[0][2])
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200180
Tim van der Lippe16b82282021-11-08 13:50:26 +0000181 node.prop = ''
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200182 while (tokens.length) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000183 let type = tokens[0][0]
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200184 if (type === ':' || type === 'space' || type === 'comment') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000185 break
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200186 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000187 node.prop += tokens.shift()[1]
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200188 }
189
Tim van der Lippe16b82282021-11-08 13:50:26 +0000190 node.raws.between = ''
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200191
Tim van der Lippe16b82282021-11-08 13:50:26 +0000192 let token
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200193 while (tokens.length) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000194 token = tokens.shift()
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200195
196 if (token[0] === ':') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000197 node.raws.between += token[1]
198 break
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200199 } else {
200 if (token[0] === 'word' && /\w/.test(token[1])) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000201 this.unknownWord([token])
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200202 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000203 node.raws.between += token[1]
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200204 }
205 }
206
207 if (node.prop[0] === '_' || node.prop[0] === '*') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000208 node.raws.before += node.prop[0]
209 node.prop = node.prop.slice(1)
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200210 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000211 let firstSpaces = this.spacesAndCommentsFromStart(tokens)
212 this.precheckMissedSemicolon(tokens)
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200213
Tim van der Lippe16b82282021-11-08 13:50:26 +0000214 for (let i = tokens.length - 1; i >= 0; i--) {
215 token = tokens[i]
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200216 if (token[1].toLowerCase() === '!important') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000217 node.important = true
218 let string = this.stringFrom(tokens, i)
219 string = this.spacesFromEnd(tokens) + string
220 if (string !== ' !important') node.raws.important = string
221 break
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200222 } else if (token[1].toLowerCase() === 'important') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000223 let cache = tokens.slice(0)
224 let str = ''
225 for (let j = i; j > 0; j--) {
226 let type = cache[j][0]
227 if (str.trim().indexOf('!') === 0 && type !== 'space') {
228 break
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200229 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000230 str = cache.pop()[1] + str
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200231 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200232 if (str.trim().indexOf('!') === 0) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000233 node.important = true
234 node.raws.important = str
235 tokens = cache
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200236 }
237 }
238
239 if (token[0] !== 'space' && token[0] !== 'comment') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000240 break
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200241 }
242 }
243
Tim van der Lippe16b82282021-11-08 13:50:26 +0000244 let hasWord = tokens.some(i => i[0] !== 'space' && i[0] !== 'comment')
245 this.raw(node, 'value', tokens)
246 if (hasWord) {
247 node.raws.between += firstSpaces
248 } else {
249 node.value = firstSpaces + node.value
Tim van der Lippef2ea2c92021-11-08 10:55:56 +0000250 }
Tim van der Lippef2ea2c92021-11-08 10:55:56 +0000251
Tim van der Lippe16b82282021-11-08 13:50:26 +0000252 if (node.value.includes(':') && !customProperty) {
253 this.checkMissedSemicolon(tokens)
254 }
255 }
256
257 atrule(token) {
258 let node = new AtRule()
259 node.name = token[1].slice(1)
260 if (node.name === '') {
261 this.unnamedAtrule(node, token)
262 }
263 this.init(node, token[2])
264
265 let type
266 let prev
267 let shift
268 let last = false
269 let open = false
270 let params = []
271 let brackets = []
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200272
273 while (!this.tokenizer.endOfFile()) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000274 token = this.tokenizer.nextToken()
275 type = token[0]
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200276
Tim van der Lippe16b82282021-11-08 13:50:26 +0000277 if (type === '(' || type === '[') {
278 brackets.push(type === '(' ? ')' : ']')
279 } else if (type === '{' && brackets.length > 0) {
280 brackets.push('}')
281 } else if (type === brackets[brackets.length - 1]) {
282 brackets.pop()
283 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200284
Tim van der Lippe16b82282021-11-08 13:50:26 +0000285 if (brackets.length === 0) {
286 if (type === ';') {
287 node.source.end = this.getPosition(token[2])
288 this.semicolon = true
289 break
290 } else if (type === '{') {
291 open = true
292 break
293 } else if (type === '}') {
294 if (params.length > 0) {
295 shift = params.length - 1
296 prev = params[shift]
297 while (prev && prev[0] === 'space') {
298 prev = params[--shift]
299 }
300 if (prev) {
301 node.source.end = this.getPosition(prev[3] || prev[2])
302 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200303 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000304 this.end(token)
305 break
306 } else {
307 params.push(token)
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200308 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200309 } else {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000310 params.push(token)
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200311 }
312
313 if (this.tokenizer.endOfFile()) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000314 last = true
315 break
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200316 }
317 }
318
Tim van der Lippe16b82282021-11-08 13:50:26 +0000319 node.raws.between = this.spacesAndCommentsFromEnd(params)
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200320 if (params.length) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000321 node.raws.afterName = this.spacesAndCommentsFromStart(params)
322 this.raw(node, 'params', params)
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200323 if (last) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000324 token = params[params.length - 1]
325 node.source.end = this.getPosition(token[3] || token[2])
326 this.spaces = node.raws.between
327 node.raws.between = ''
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200328 }
329 } else {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000330 node.raws.afterName = ''
331 node.params = ''
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200332 }
333
334 if (open) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000335 node.nodes = []
336 this.current = node
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200337 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000338 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200339
Tim van der Lippe16b82282021-11-08 13:50:26 +0000340 end(token) {
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200341 if (this.current.nodes && this.current.nodes.length) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000342 this.current.raws.semicolon = this.semicolon
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200343 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000344 this.semicolon = false
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200345
Tim van der Lippe16b82282021-11-08 13:50:26 +0000346 this.current.raws.after = (this.current.raws.after || '') + this.spaces
347 this.spaces = ''
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200348
349 if (this.current.parent) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000350 this.current.source.end = this.getPosition(token[2])
351 this.current = this.current.parent
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200352 } else {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000353 this.unexpectedClose(token)
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200354 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000355 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200356
Tim van der Lippe16b82282021-11-08 13:50:26 +0000357 endFile() {
358 if (this.current.parent) this.unclosedBlock()
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200359 if (this.current.nodes && this.current.nodes.length) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000360 this.current.raws.semicolon = this.semicolon
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200361 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000362 this.current.raws.after = (this.current.raws.after || '') + this.spaces
363 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200364
Tim van der Lippe16b82282021-11-08 13:50:26 +0000365 freeSemicolon(token) {
366 this.spaces += token[1]
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200367 if (this.current.nodes) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000368 let prev = this.current.nodes[this.current.nodes.length - 1]
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200369 if (prev && prev.type === 'rule' && !prev.raws.ownSemicolon) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000370 prev.raws.ownSemicolon = this.spaces
371 this.spaces = ''
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200372 }
373 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000374 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200375
Tim van der Lippe16b82282021-11-08 13:50:26 +0000376 // Helpers
377
378 getPosition(offset) {
379 let pos = this.input.fromOffset(offset)
380 return {
381 offset,
382 line: pos.line,
383 column: pos.col
384 }
385 }
386
387 init(node, offset) {
388 this.current.push(node)
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200389 node.source = {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000390 start: this.getPosition(offset),
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200391 input: this.input
Tim van der Lippe16b82282021-11-08 13:50:26 +0000392 }
393 node.raws.before = this.spaces
394 this.spaces = ''
395 if (node.type !== 'comment') this.semicolon = false
396 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200397
Tim van der Lippe16b82282021-11-08 13:50:26 +0000398 raw(node, prop, tokens) {
399 let token, type
400 let length = tokens.length
401 let value = ''
402 let clean = true
403 let next, prev
404 let pattern = /^([#.|])?(\w)+/i
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200405
Tim van der Lippe16b82282021-11-08 13:50:26 +0000406 for (let i = 0; i < length; i += 1) {
407 token = tokens[i]
408 type = token[0]
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200409
410 if (type === 'comment' && node.type === 'rule') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000411 prev = tokens[i - 1]
412 next = tokens[i + 1]
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200413
Tim van der Lippe16b82282021-11-08 13:50:26 +0000414 if (
415 prev[0] !== 'space' &&
416 next[0] !== 'space' &&
417 pattern.test(prev[1]) &&
418 pattern.test(next[1])
419 ) {
420 value += token[1]
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200421 } else {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000422 clean = false
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200423 }
424
Tim van der Lippe16b82282021-11-08 13:50:26 +0000425 continue
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200426 }
427
Tim van der Lippe16b82282021-11-08 13:50:26 +0000428 if (type === 'comment' || (type === 'space' && i === length - 1)) {
429 clean = false
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200430 } else {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000431 value += token[1]
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200432 }
433 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200434 if (!clean) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000435 let raw = tokens.reduce((all, i) => all + i[1], '')
436 node.raws[prop] = { value, raw }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200437 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000438 node[prop] = value
439 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200440
Tim van der Lippe16b82282021-11-08 13:50:26 +0000441 spacesAndCommentsFromEnd(tokens) {
442 let lastTokenType
443 let spaces = ''
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200444 while (tokens.length) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000445 lastTokenType = tokens[tokens.length - 1][0]
446 if (lastTokenType !== 'space' && lastTokenType !== 'comment') break
447 spaces = tokens.pop()[1] + spaces
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200448 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000449 return spaces
450 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200451
Tim van der Lippe16b82282021-11-08 13:50:26 +0000452 spacesAndCommentsFromStart(tokens) {
453 let next
454 let spaces = ''
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200455 while (tokens.length) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000456 next = tokens[0][0]
457 if (next !== 'space' && next !== 'comment') break
458 spaces += tokens.shift()[1]
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200459 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000460 return spaces
461 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200462
Tim van der Lippe16b82282021-11-08 13:50:26 +0000463 spacesFromEnd(tokens) {
464 let lastTokenType
465 let spaces = ''
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200466 while (tokens.length) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000467 lastTokenType = tokens[tokens.length - 1][0]
468 if (lastTokenType !== 'space') break
469 spaces = tokens.pop()[1] + spaces
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200470 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000471 return spaces
472 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200473
Tim van der Lippe16b82282021-11-08 13:50:26 +0000474 stringFrom(tokens, from) {
475 let result = ''
476 for (let i = from; i < tokens.length; i++) {
477 result += tokens[i][1]
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200478 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000479 tokens.splice(from, tokens.length - from)
480 return result
481 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200482
Tim van der Lippe16b82282021-11-08 13:50:26 +0000483 colon(tokens) {
484 let brackets = 0
485 let token, type, prev
486 for (let [i, element] of tokens.entries()) {
487 token = element
488 type = token[0]
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200489
490 if (type === '(') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000491 brackets += 1
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200492 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200493 if (type === ')') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000494 brackets -= 1
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200495 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200496 if (brackets === 0 && type === ':') {
497 if (!prev) {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000498 this.doubleColon(token)
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200499 } else if (prev[0] === 'word' && prev[1] === 'progid') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000500 continue
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200501 } else {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000502 return i
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200503 }
504 }
505
Tim van der Lippe16b82282021-11-08 13:50:26 +0000506 prev = token
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200507 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000508 return false
509 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200510
Tim van der Lippe16b82282021-11-08 13:50:26 +0000511 // Errors
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200512
Tim van der Lippe16b82282021-11-08 13:50:26 +0000513 unclosedBracket(bracket) {
514 throw this.input.error('Unclosed bracket', bracket[2])
515 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200516
Tim van der Lippe16b82282021-11-08 13:50:26 +0000517 unknownWord(tokens) {
518 throw this.input.error('Unknown word', tokens[0][2])
519 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200520
Tim van der Lippe16b82282021-11-08 13:50:26 +0000521 unexpectedClose(token) {
522 throw this.input.error('Unexpected }', token[2])
523 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200524
Tim van der Lippe16b82282021-11-08 13:50:26 +0000525 unclosedBlock() {
526 let pos = this.current.source.start
527 throw this.input.error('Unclosed block', pos.line, pos.column)
528 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200529
Tim van der Lippe16b82282021-11-08 13:50:26 +0000530 doubleColon(token) {
531 throw this.input.error('Double colon', token[2])
532 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200533
Tim van der Lippe16b82282021-11-08 13:50:26 +0000534 unnamedAtrule(node, token) {
535 throw this.input.error('At-rule without name', token[2])
536 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200537
Tim van der Lippe16b82282021-11-08 13:50:26 +0000538 precheckMissedSemicolon(/* tokens */) {
539 // Hook for Safe Parser
540 }
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200541
Tim van der Lippe16b82282021-11-08 13:50:26 +0000542 checkMissedSemicolon(tokens) {
543 let colon = this.colon(tokens)
544 if (colon === false) return
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200545
Tim van der Lippe16b82282021-11-08 13:50:26 +0000546 let founded = 0
547 let token
548 for (let j = colon - 1; j >= 0; j--) {
549 token = tokens[j]
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200550 if (token[0] !== 'space') {
Tim van der Lippe16b82282021-11-08 13:50:26 +0000551 founded += 1
552 if (founded === 2) break
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200553 }
554 }
Tim van der Lippe16b82282021-11-08 13:50:26 +0000555 // If the token is a word, e.g. `!important`, `red` or any other valid property's value.
556 // Then we need to return the colon after that word token. [3] is the "end" colon of that word.
557 // And because we need it after that one we do +1 to get the next one.
558 throw this.input.error(
559 'Missed semicolon',
560 token[0] === 'word' ? token[3] + 1 : token[2]
561 )
562 }
563}
Mathias Bynens79e2cf02020-05-29 16:46:17 +0200564
Tim van der Lippe16b82282021-11-08 13:50:26 +0000565module.exports = Parser