source: frontend/node_modules/autoprefixer/lib/processor.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 11 days ago

Fix frontend appearance

  • 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
38function insideGrid(decl) {
39 return decl.parent.nodes.some(node => {
40 if (node.type !== 'decl') return false
41 let displayGrid =
42 node.prop === 'display' && /(inline-)?grid/.test(node.value)
43 let gridTemplate = node.prop.startsWith('grid-template')
44 let gridGap = /^grid-([A-z]+-)?gap/.test(node.prop)
45 return displayGrid || gridTemplate || gridGap
46 })
47}
48
49class Processor {
50 constructor(prefixes) {
51 this.prefixes = prefixes
52 }
53
54 /**
55 * Add necessary prefixes
56 */
57 add(css, result) {
58 // At-rules
59 let resolution = this.prefixes.add['@resolution']
60 let keyframes = this.prefixes.add['@keyframes']
61 let viewport = this.prefixes.add['@viewport']
62 let supports = this.prefixes.add['@supports']
63
64 css.walkAtRules(rule => {
65 if (rule.name === 'keyframes') {
66 if (!this.disabled(rule, result)) {
67 return keyframes && keyframes.process(rule)
68 }
69 } else if (rule.name === 'viewport') {
70 if (!this.disabled(rule, result)) {
71 return viewport && viewport.process(rule)
72 }
73 } else if (rule.name === 'supports') {
74 if (
75 this.prefixes.options.supports !== false &&
76 !this.disabled(rule, result)
77 ) {
78 return supports.process(rule)
79 }
80 } else if (rule.name === 'media' && rule.params.includes('-resolution')) {
81 if (!this.disabled(rule, result)) {
82 return resolution && resolution.process(rule)
83 }
84 }
85
86 return undefined
87 })
88
89 // Selectors
90 css.walkRules(rule => {
91 if (this.disabled(rule, result)) return undefined
92
93 return this.prefixes.add.selectors.map(selector => {
94 return selector.process(rule, result)
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 /** @type {number|false} */
590 let diff = false
591
592 this.prefixes.group(decl).down(other => {
593 parts = other.raw('before').split('\n')
594 let last = parts.length - 1
595
596 if (parts[last].length > prevMin) {
597 if (diff === false) {
598 diff = parts[last].length - prevMin
599 }
600
601 parts[last] = parts[last].slice(0, -diff)
602 other.raws.before = parts.join('\n')
603 }
604 })
605 }
606
607 /**
608 * Remove unnecessary pefixes
609 */
610 remove(css, result) {
611 // At-rules
612 let resolution = this.prefixes.remove['@resolution']
613
614 css.walkAtRules((rule, i) => {
615 if (this.prefixes.remove[`@${rule.name}`]) {
616 if (!this.disabled(rule, result)) {
617 rule.parent.removeChild(i)
618 }
619 } else if (
620 rule.name === 'media' &&
621 rule.params.includes('-resolution') &&
622 resolution
623 ) {
624 resolution.clean(rule)
625 }
626 })
627
628 // Selectors
629 css.walkRules((rule, i) => {
630 if (this.disabled(rule, result)) return
631
632 for (let checker of this.prefixes.remove.selectors) {
633 if (checker.check(rule)) {
634 rule.parent.removeChild(i)
635 return
636 }
637 }
638 })
639
640 return css.walkDecls((decl, i) => {
641 if (this.disabled(decl, result)) return
642
643 let rule = decl.parent
644 let unprefixed = this.prefixes.unprefixed(decl.prop)
645
646 // Transition
647 if (decl.prop === 'transition' || decl.prop === 'transition-property') {
648 this.prefixes.transition.remove(decl)
649 }
650
651 // Properties
652 if (
653 this.prefixes.remove[decl.prop] &&
654 this.prefixes.remove[decl.prop].remove
655 ) {
656 let notHack = this.prefixes.group(decl).down(other => {
657 return this.prefixes.normalize(other.prop) === unprefixed
658 })
659
660 if (unprefixed === 'flex-flow') {
661 notHack = true
662 }
663
664 if (decl.prop === '-webkit-box-orient') {
665 let hacks = { 'flex-direction': true, 'flex-flow': true }
666 if (!decl.parent.some(j => hacks[j.prop])) return
667 }
668
669 if (notHack && !this.withHackValue(decl)) {
670 if (decl.raw('before').includes('\n')) {
671 this.reduceSpaces(decl)
672 }
673 rule.removeChild(i)
674 return
675 }
676 }
677
678 // Values
679 for (let checker of this.prefixes.values('remove', unprefixed)) {
680 if (!checker.check) continue
681 if (!checker.check(decl.value)) continue
682
683 unprefixed = checker.unprefixed
684 let notHack = this.prefixes.group(decl).down(other => {
685 return other.value.includes(unprefixed)
686 })
687
688 if (notHack) {
689 rule.removeChild(i)
690 return
691 }
692 }
693 })
694 }
695
696 /**
697 * Some rare old values, which is not in standard
698 */
699 withHackValue(decl) {
700 return (
701 (decl.prop === '-webkit-background-clip' && decl.value === 'text') ||
702 // Do not remove -webkit-box-orient when -webkit-line-clamp is present.
703 // https://github.com/postcss/autoprefixer/issues/1510
704 (decl.prop === '-webkit-box-orient' &&
705 decl.parent.some(d => d.prop === '-webkit-line-clamp'))
706 )
707 }
708}
709
710module.exports = Processor
Note: See TracBrowser for help on using the repository browser.