source: node_modules/autoprefixer/lib/processor.js@ 2058e5c

Last change on this file since 2058e5c was 2058e5c, checked in by istevanoska <ilinastevanoska@…>, 6 months ago

Working / before login

  • Property mode set to 100644
File size: 21.0 KB
Line 
1let parser = require('postcss-value-parser')
2
3let Value = require('./value')
4let insertAreas = require('./hacks/grid-utils').insertAreas
5
6const OLD_LINEAR = /(^|[^-])linear-gradient\(\s*(top|left|right|bottom)/i
7const OLD_RADIAL = /(^|[^-])radial-gradient\(\s*\d+(\w*|%)\s+\d+(\w*|%)\s*,/i
8const IGNORE_NEXT = /(!\s*)?autoprefixer:\s*ignore\s+next/i
9const GRID_REGEX = /(!\s*)?autoprefixer\s*grid:\s*(on|off|(no-)?autoplace)/i
10
11const SIZES = [
12 'width',
13 'height',
14 'min-width',
15 'max-width',
16 'min-height',
17 'max-height',
18 'inline-size',
19 'min-inline-size',
20 'max-inline-size',
21 'block-size',
22 'min-block-size',
23 'max-block-size'
24]
25
26function hasGridTemplate(decl) {
27 return decl.parent.some(
28 i => i.prop === 'grid-template' || i.prop === 'grid-template-areas'
29 )
30}
31
32function hasRowsAndColumns(decl) {
33 let hasRows = decl.parent.some(i => i.prop === 'grid-template-rows')
34 let hasColumns = decl.parent.some(i => i.prop === 'grid-template-columns')
35 return hasRows && hasColumns
36}
37
38class Processor {
39 constructor(prefixes) {
40 this.prefixes = prefixes
41 }
42
43 /**
44 * Add necessary prefixes
45 */
46 add(css, result) {
47 // At-rules
48 let resolution = this.prefixes.add['@resolution']
49 let keyframes = this.prefixes.add['@keyframes']
50 let viewport = this.prefixes.add['@viewport']
51 let supports = this.prefixes.add['@supports']
52
53 css.walkAtRules(rule => {
54 if (rule.name === 'keyframes') {
55 if (!this.disabled(rule, result)) {
56 return keyframes && keyframes.process(rule)
57 }
58 } else if (rule.name === 'viewport') {
59 if (!this.disabled(rule, result)) {
60 return viewport && viewport.process(rule)
61 }
62 } else if (rule.name === 'supports') {
63 if (
64 this.prefixes.options.supports !== false &&
65 !this.disabled(rule, result)
66 ) {
67 return supports.process(rule)
68 }
69 } else if (rule.name === 'media' && rule.params.includes('-resolution')) {
70 if (!this.disabled(rule, result)) {
71 return resolution && resolution.process(rule)
72 }
73 }
74
75 return undefined
76 })
77
78 // Selectors
79 css.walkRules(rule => {
80 if (this.disabled(rule, result)) return undefined
81
82 return this.prefixes.add.selectors.map(selector => {
83 return selector.process(rule, result)
84 })
85 })
86
87 function insideGrid(decl) {
88 return decl.parent.nodes.some(node => {
89 if (node.type !== 'decl') return false
90 let displayGrid =
91 node.prop === 'display' && /(inline-)?grid/.test(node.value)
92 let gridTemplate = node.prop.startsWith('grid-template')
93 let gridGap = /^grid-([A-z]+-)?gap/.test(node.prop)
94 return displayGrid || gridTemplate || gridGap
95 })
96 }
97
98 let gridPrefixes =
99 this.gridStatus(css, result) &&
100 this.prefixes.add['grid-area'] &&
101 this.prefixes.add['grid-area'].prefixes
102
103 css.walkDecls(decl => {
104 if (this.disabledDecl(decl, result)) return undefined
105
106 let parent = decl.parent
107 let prop = decl.prop
108 let value = decl.value
109
110 if (prop === 'color-adjust') {
111 if (parent.every(i => i.prop !== 'print-color-adjust')) {
112 result.warn(
113 'Replace color-adjust to print-color-adjust. ' +
114 'The color-adjust shorthand is currently deprecated.',
115 { node: decl }
116 )
117 }
118 } else if (prop === 'grid-row-span') {
119 result.warn(
120 'grid-row-span is not part of final Grid Layout. Use grid-row.',
121 { node: decl }
122 )
123 return undefined
124 } else if (prop === 'grid-column-span') {
125 result.warn(
126 'grid-column-span is not part of final Grid Layout. Use grid-column.',
127 { node: decl }
128 )
129 return undefined
130 } else if (prop === 'display' && value === 'box') {
131 result.warn(
132 'You should write display: flex by final spec ' +
133 'instead of display: box',
134 { node: decl }
135 )
136 return undefined
137 } else if (prop === 'text-emphasis-position') {
138 if (value === 'under' || value === 'over') {
139 result.warn(
140 'You should use 2 values for text-emphasis-position ' +
141 'For example, `under left` instead of just `under`.',
142 { node: decl }
143 )
144 }
145 } else if (prop === 'text-decoration-skip' && value === 'ink') {
146 result.warn(
147 'Replace text-decoration-skip: ink to ' +
148 'text-decoration-skip-ink: auto, because spec had been changed',
149 { node: decl }
150 )
151 } else {
152 if (gridPrefixes && this.gridStatus(decl, result)) {
153 if (decl.value === 'subgrid') {
154 result.warn('IE does not support subgrid', { node: decl })
155 }
156 if (/^(align|justify|place)-items$/.test(prop) && insideGrid(decl)) {
157 let fixed = prop.replace('-items', '-self')
158 result.warn(
159 `IE does not support ${prop} on grid containers. ` +
160 `Try using ${fixed} on child elements instead: ` +
161 `${decl.parent.selector} > * { ${fixed}: ${decl.value} }`,
162 { node: decl }
163 )
164 } else if (
165 /^(align|justify|place)-content$/.test(prop) &&
166 insideGrid(decl)
167 ) {
168 result.warn(`IE does not support ${decl.prop} on grid containers`, {
169 node: decl
170 })
171 } else if (prop === 'display' && decl.value === 'contents') {
172 result.warn(
173 'Please do not use display: contents; ' +
174 'if you have grid setting enabled',
175 { node: decl }
176 )
177 return undefined
178 } else if (decl.prop === 'grid-gap') {
179 let status = this.gridStatus(decl, result)
180 if (
181 status === 'autoplace' &&
182 !hasRowsAndColumns(decl) &&
183 !hasGridTemplate(decl)
184 ) {
185 result.warn(
186 'grid-gap only works if grid-template(-areas) is being ' +
187 'used or both rows and columns have been declared ' +
188 'and cells have not been manually ' +
189 'placed inside the explicit grid',
190 { node: decl }
191 )
192 } else if (
193 (status === true || status === 'no-autoplace') &&
194 !hasGridTemplate(decl)
195 ) {
196 result.warn(
197 'grid-gap only works if grid-template(-areas) is being used',
198 { node: decl }
199 )
200 }
201 } else if (prop === 'grid-auto-columns') {
202 result.warn('grid-auto-columns is not supported by IE', {
203 node: decl
204 })
205 return undefined
206 } else if (prop === 'grid-auto-rows') {
207 result.warn('grid-auto-rows is not supported by IE', { node: decl })
208 return undefined
209 } else if (prop === 'grid-auto-flow') {
210 let hasRows = parent.some(i => i.prop === 'grid-template-rows')
211 let hasCols = parent.some(i => i.prop === 'grid-template-columns')
212
213 if (hasGridTemplate(decl)) {
214 result.warn('grid-auto-flow is not supported by IE', {
215 node: decl
216 })
217 } else if (value.includes('dense')) {
218 result.warn('grid-auto-flow: dense is not supported by IE', {
219 node: decl
220 })
221 } else if (!hasRows && !hasCols) {
222 result.warn(
223 'grid-auto-flow works only if grid-template-rows and ' +
224 'grid-template-columns are present in the same rule',
225 { node: decl }
226 )
227 }
228 return undefined
229 } else if (value.includes('auto-fit')) {
230 result.warn('auto-fit value is not supported by IE', {
231 node: decl,
232 word: 'auto-fit'
233 })
234 return undefined
235 } else if (value.includes('auto-fill')) {
236 result.warn('auto-fill value is not supported by IE', {
237 node: decl,
238 word: 'auto-fill'
239 })
240 return undefined
241 } else if (prop.startsWith('grid-template') && value.includes('[')) {
242 result.warn(
243 'Autoprefixer currently does not support line names. ' +
244 'Try using grid-template-areas instead.',
245 { node: decl, word: '[' }
246 )
247 }
248 }
249 if (value.includes('radial-gradient')) {
250 if (OLD_RADIAL.test(decl.value)) {
251 result.warn(
252 'Gradient has outdated direction syntax. ' +
253 'New syntax is like `closest-side at 0 0` ' +
254 'instead of `0 0, closest-side`.',
255 { node: decl }
256 )
257 } else {
258 let ast = parser(value)
259
260 for (let i of ast.nodes) {
261 if (i.type === 'function' && i.value === 'radial-gradient') {
262 for (let word of i.nodes) {
263 if (word.type === 'word') {
264 if (word.value === 'cover') {
265 result.warn(
266 'Gradient has outdated direction syntax. ' +
267 'Replace `cover` to `farthest-corner`.',
268 { node: decl }
269 )
270 } else if (word.value === 'contain') {
271 result.warn(
272 'Gradient has outdated direction syntax. ' +
273 'Replace `contain` to `closest-side`.',
274 { node: decl }
275 )
276 }
277 }
278 }
279 }
280 }
281 }
282 }
283 if (value.includes('linear-gradient')) {
284 if (OLD_LINEAR.test(value)) {
285 result.warn(
286 'Gradient has outdated direction syntax. ' +
287 'New syntax is like `to left` instead of `right`.',
288 { node: decl }
289 )
290 }
291 }
292 }
293
294 if (SIZES.includes(decl.prop)) {
295 if (!decl.value.includes('-fill-available')) {
296 if (decl.value.includes('fill-available')) {
297 result.warn(
298 'Replace fill-available to stretch, ' +
299 'because spec had been changed',
300 { node: decl }
301 )
302 } else if (decl.value.includes('fill')) {
303 let ast = parser(value)
304 if (ast.nodes.some(i => i.type === 'word' && i.value === 'fill')) {
305 result.warn(
306 'Replace fill to stretch, because spec had been changed',
307 { node: decl }
308 )
309 }
310 }
311 }
312 }
313
314 let prefixer
315
316 if (decl.prop === 'transition' || decl.prop === 'transition-property') {
317 // Transition
318 return this.prefixes.transition.add(decl, result)
319 } else if (decl.prop === 'align-self') {
320 // align-self flexbox or grid
321 let display = this.displayType(decl)
322 if (display !== 'grid' && this.prefixes.options.flexbox !== false) {
323 prefixer = this.prefixes.add['align-self']
324 if (prefixer && prefixer.prefixes) {
325 prefixer.process(decl)
326 }
327 }
328 if (this.gridStatus(decl, result) !== false) {
329 prefixer = this.prefixes.add['grid-row-align']
330 if (prefixer && prefixer.prefixes) {
331 return prefixer.process(decl, result)
332 }
333 }
334 } else if (decl.prop === 'justify-self') {
335 // justify-self flexbox or grid
336 if (this.gridStatus(decl, result) !== false) {
337 prefixer = this.prefixes.add['grid-column-align']
338 if (prefixer && prefixer.prefixes) {
339 return prefixer.process(decl, result)
340 }
341 }
342 } else if (decl.prop === 'place-self') {
343 prefixer = this.prefixes.add['place-self']
344 if (
345 prefixer &&
346 prefixer.prefixes &&
347 this.gridStatus(decl, result) !== false
348 ) {
349 return prefixer.process(decl, result)
350 }
351 } else {
352 // Properties
353 prefixer = this.prefixes.add[decl.prop]
354 if (prefixer && prefixer.prefixes) {
355 return prefixer.process(decl, result)
356 }
357 }
358
359 return undefined
360 })
361
362 // Insert grid-area prefixes. We need to be able to store the different
363 // rules as a data and hack API is not enough for this
364 if (this.gridStatus(css, result)) {
365 insertAreas(css, this.disabled)
366 }
367
368 // Values
369 return css.walkDecls(decl => {
370 if (this.disabledValue(decl, result)) return
371
372 let unprefixed = this.prefixes.unprefixed(decl.prop)
373 let list = this.prefixes.values('add', unprefixed)
374 if (Array.isArray(list)) {
375 for (let value of list) {
376 if (value.process) value.process(decl, result)
377 }
378 }
379 Value.save(this.prefixes, decl)
380 })
381 }
382
383 /**
384 * Check for control comment and global options
385 */
386 disabled(node, result) {
387 if (!node) return false
388
389 if (node._autoprefixerDisabled !== undefined) {
390 return node._autoprefixerDisabled
391 }
392
393 if (node.parent) {
394 let p = node.prev()
395 if (p && p.type === 'comment' && IGNORE_NEXT.test(p.text)) {
396 node._autoprefixerDisabled = true
397 node._autoprefixerSelfDisabled = true
398 return true
399 }
400 }
401
402 let value = null
403 if (node.nodes) {
404 let status
405 node.each(i => {
406 if (i.type !== 'comment') return
407 if (/(!\s*)?autoprefixer:\s*(off|on)/i.test(i.text)) {
408 if (typeof status !== 'undefined') {
409 result.warn(
410 'Second Autoprefixer control comment ' +
411 'was ignored. Autoprefixer applies control ' +
412 'comment to whole block, not to next rules.',
413 { node: i }
414 )
415 } else {
416 status = /on/i.test(i.text)
417 }
418 }
419 })
420
421 if (status !== undefined) {
422 value = !status
423 }
424 }
425 if (!node.nodes || value === null) {
426 if (node.parent) {
427 let isParentDisabled = this.disabled(node.parent, result)
428 if (node.parent._autoprefixerSelfDisabled === true) {
429 value = false
430 } else {
431 value = isParentDisabled
432 }
433 } else {
434 value = false
435 }
436 }
437 node._autoprefixerDisabled = value
438 return value
439 }
440
441 /**
442 * Check for grid/flexbox options.
443 */
444 disabledDecl(node, result) {
445 if (node.type === 'decl' && this.gridStatus(node, result) === false) {
446 if (node.prop.includes('grid') || node.prop === 'justify-items') {
447 return true
448 }
449 }
450 if (node.type === 'decl' && this.prefixes.options.flexbox === false) {
451 let other = ['order', 'justify-content', 'align-items', 'align-content']
452 if (node.prop.includes('flex') || other.includes(node.prop)) {
453 return true
454 }
455 }
456
457 return this.disabled(node, result)
458 }
459
460 /**
461 * Check for grid/flexbox options.
462 */
463 disabledValue(node, result) {
464 if (this.gridStatus(node, result) === false && node.type === 'decl') {
465 if (node.prop === 'display' && node.value.includes('grid')) {
466 return true
467 }
468 }
469 if (this.prefixes.options.flexbox === false && node.type === 'decl') {
470 if (node.prop === 'display' && node.value.includes('flex')) {
471 return true
472 }
473 }
474 if (node.type === 'decl' && node.prop === 'content') {
475 return true
476 }
477
478 return this.disabled(node, result)
479 }
480
481 /**
482 * Is it flebox or grid rule
483 */
484 displayType(decl) {
485 for (let i of decl.parent.nodes) {
486 if (i.prop !== 'display') {
487 continue
488 }
489
490 if (i.value.includes('flex')) {
491 return 'flex'
492 }
493
494 if (i.value.includes('grid')) {
495 return 'grid'
496 }
497 }
498
499 return false
500 }
501
502 /**
503 * Set grid option via control comment
504 */
505 gridStatus(node, result) {
506 if (!node) return false
507
508 if (node._autoprefixerGridStatus !== undefined) {
509 return node._autoprefixerGridStatus
510 }
511
512 let value = null
513 if (node.nodes) {
514 let status
515 node.each(i => {
516 if (i.type !== 'comment') return
517 if (GRID_REGEX.test(i.text)) {
518 let hasAutoplace = /:\s*autoplace/i.test(i.text)
519 let noAutoplace = /no-autoplace/i.test(i.text)
520 if (typeof status !== 'undefined') {
521 result.warn(
522 'Second Autoprefixer grid control comment was ' +
523 'ignored. Autoprefixer applies control comments to the whole ' +
524 'block, not to the next rules.',
525 { node: i }
526 )
527 } else if (hasAutoplace) {
528 status = 'autoplace'
529 } else if (noAutoplace) {
530 status = true
531 } else {
532 status = /on/i.test(i.text)
533 }
534 }
535 })
536
537 if (status !== undefined) {
538 value = status
539 }
540 }
541
542 if (node.type === 'atrule' && node.name === 'supports') {
543 let params = node.params
544 if (params.includes('grid') && params.includes('auto')) {
545 value = false
546 }
547 }
548
549 if (!node.nodes || value === null) {
550 if (node.parent) {
551 let isParentGrid = this.gridStatus(node.parent, result)
552 if (node.parent._autoprefixerSelfDisabled === true) {
553 value = false
554 } else {
555 value = isParentGrid
556 }
557 } else if (typeof this.prefixes.options.grid !== 'undefined') {
558 value = this.prefixes.options.grid
559 } else if (typeof process.env.AUTOPREFIXER_GRID !== 'undefined') {
560 if (process.env.AUTOPREFIXER_GRID === 'autoplace') {
561 value = 'autoplace'
562 } else {
563 value = true
564 }
565 } else {
566 value = false
567 }
568 }
569
570 node._autoprefixerGridStatus = value
571 return value
572 }
573
574 /**
575 * Normalize spaces in cascade declaration group
576 */
577 reduceSpaces(decl) {
578 let stop = false
579 this.prefixes.group(decl).up(() => {
580 stop = true
581 return true
582 })
583 if (stop) {
584 return
585 }
586
587 let parts = decl.raw('before').split('\n')
588 let prevMin = parts[parts.length - 1].length
589 let diff = false
590
591 this.prefixes.group(decl).down(other => {
592 parts = other.raw('before').split('\n')
593 let last = parts.length - 1
594
595 if (parts[last].length > prevMin) {
596 if (diff === false) {
597 diff = parts[last].length - prevMin
598 }
599
600 parts[last] = parts[last].slice(0, -diff)
601 other.raws.before = parts.join('\n')
602 }
603 })
604 }
605
606 /**
607 * Remove unnecessary pefixes
608 */
609 remove(css, result) {
610 // At-rules
611 let resolution = this.prefixes.remove['@resolution']
612
613 css.walkAtRules((rule, i) => {
614 if (this.prefixes.remove[`@${rule.name}`]) {
615 if (!this.disabled(rule, result)) {
616 rule.parent.removeChild(i)
617 }
618 } else if (
619 rule.name === 'media' &&
620 rule.params.includes('-resolution') &&
621 resolution
622 ) {
623 resolution.clean(rule)
624 }
625 })
626
627 // Selectors
628 css.walkRules((rule, i) => {
629 if (this.disabled(rule, result)) return
630
631 for (let checker of this.prefixes.remove.selectors) {
632 if (checker.check(rule)) {
633 rule.parent.removeChild(i)
634 return
635 }
636 }
637 })
638
639 return css.walkDecls((decl, i) => {
640 if (this.disabled(decl, result)) return
641
642 let rule = decl.parent
643 let unprefixed = this.prefixes.unprefixed(decl.prop)
644
645 // Transition
646 if (decl.prop === 'transition' || decl.prop === 'transition-property') {
647 this.prefixes.transition.remove(decl)
648 }
649
650 // Properties
651 if (
652 this.prefixes.remove[decl.prop] &&
653 this.prefixes.remove[decl.prop].remove
654 ) {
655 let notHack = this.prefixes.group(decl).down(other => {
656 return this.prefixes.normalize(other.prop) === unprefixed
657 })
658
659 if (unprefixed === 'flex-flow') {
660 notHack = true
661 }
662
663 if (decl.prop === '-webkit-box-orient') {
664 let hacks = { 'flex-direction': true, 'flex-flow': true }
665 if (!decl.parent.some(j => hacks[j.prop])) return
666 }
667
668 if (notHack && !this.withHackValue(decl)) {
669 if (decl.raw('before').includes('\n')) {
670 this.reduceSpaces(decl)
671 }
672 rule.removeChild(i)
673 return
674 }
675 }
676
677 // Values
678 for (let checker of this.prefixes.values('remove', unprefixed)) {
679 if (!checker.check) continue
680 if (!checker.check(decl.value)) continue
681
682 unprefixed = checker.unprefixed
683 let notHack = this.prefixes.group(decl).down(other => {
684 return other.value.includes(unprefixed)
685 })
686
687 if (notHack) {
688 rule.removeChild(i)
689 return
690 }
691 }
692 })
693 }
694
695 /**
696 * Some rare old values, which is not in standard
697 */
698 withHackValue(decl) {
699 return (
700 (decl.prop === '-webkit-background-clip' && decl.value === 'text') ||
701 // Do not remove -webkit-box-orient when -webkit-line-clamp is present.
702 // https://github.com/postcss/autoprefixer/issues/1510
703 (decl.prop === '-webkit-box-orient' &&
704 decl.parent.some(d => d.prop === '-webkit-line-clamp'))
705 )
706 }
707}
708
709module.exports = Processor
Note: See TracBrowser for help on using the repository browser.