Skip to main content
Go to Latest
File
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721
import { parser } from "./deps.js";import Value from "./value.js";import { insertAreas } from "./hacks/grid-utils.js";
const OLD_LINEAR = /(^|[^-])linear-gradient\(\s*(top|left|right|bottom)/i;const OLD_RADIAL = /(^|[^-])radial-gradient\(\s*\d+(\w*|%)\s+\d+(\w*|%)\s*,/i;const IGNORE_NEXT = /(!\s*)?autoprefixer:\s*ignore\s+next/i;const GRID_REGEX = /(!\s*)?autoprefixer\s*grid:\s*(on|off|(no-)?autoplace)/i;
const SIZES = [ "width", "height", "min-width", "max-width", "min-height", "max-height", "inline-size", "min-inline-size", "max-inline-size", "block-size", "min-block-size", "max-block-size",];
function hasGridTemplate(decl) { return decl.parent.some( (i) => i.prop === "grid-template" || i.prop === "grid-template-areas", );}
function hasRowsAndColumns(decl) { let hasRows = decl.parent.some((i) => i.prop === "grid-template-rows"); let hasColumns = decl.parent.some((i) => i.prop === "grid-template-columns"); return hasRows && hasColumns;}
class Processor { constructor(prefixes) { this.prefixes = prefixes; }
/** * Add necessary prefixes */ add(css, result) { // At-rules let resolution = this.prefixes.add["@resolution"]; let keyframes = this.prefixes.add["@keyframes"]; let viewport = this.prefixes.add["@viewport"]; let supports = this.prefixes.add["@supports"];
css.walkAtRules((rule) => { if (rule.name === "keyframes") { if (!this.disabled(rule, result)) { return keyframes && keyframes.process(rule); } } else if (rule.name === "viewport") { if (!this.disabled(rule, result)) { return viewport && viewport.process(rule); } } else if (rule.name === "supports") { if ( this.prefixes.options.supports !== false && !this.disabled(rule, result) ) { return supports.process(rule); } } else if (rule.name === "media" && rule.params.includes("-resolution")) { if (!this.disabled(rule, result)) { return resolution && resolution.process(rule); } }
return undefined; });
// Selectors css.walkRules((rule) => { if (this.disabled(rule, result)) return undefined;
return this.prefixes.add.selectors.map((selector) => { return selector.process(rule, result); }); });
function insideGrid(decl) { return decl.parent.nodes.some((node) => { if (node.type !== "decl") return false; let displayGrid = node.prop === "display" && /(inline-)?grid/.test(node.value); let gridTemplate = node.prop.startsWith("grid-template"); let gridGap = /^grid-([A-z]+-)?gap/.test(node.prop); return displayGrid || gridTemplate || gridGap; }); } function insideFlex(decl) { return decl.parent.some((node) => { return node.prop === "display" && /(inline-)?flex/.test(node.value); }); }
let gridPrefixes = this.gridStatus(css, result) && this.prefixes.add["grid-area"] && this.prefixes.add["grid-area"].prefixes;
css.walkDecls((decl) => { if (this.disabledDecl(decl, result)) return undefined;
let parent = decl.parent; let prop = decl.prop; let value = decl.value;
if (prop === "color-adjust") { result.warn( "Replace color-adjust to print-color-adjust. " + "The color-adjust shorthand is currently deprecated.", { node: decl }, ); } else if (prop === "grid-row-span") { result.warn( "grid-row-span is not part of final Grid Layout. Use grid-row.", { node: decl }, ); return undefined; } else if (prop === "grid-column-span") { result.warn( "grid-column-span is not part of final Grid Layout. Use grid-column.", { node: decl }, ); return undefined; } else if (prop === "display" && value === "box") { result.warn( "You should write display: flex by final spec " + "instead of display: box", { node: decl }, ); return undefined; } else if (prop === "text-emphasis-position") { if (value === "under" || value === "over") { result.warn( "You should use 2 values for text-emphasis-position " + "For example, `under left` instead of just `under`.", { node: decl }, ); } } else if ( /^(align|justify|place)-(items|content)$/.test(prop) && insideFlex(decl) ) { if (value === "start" || value === "end") { result.warn( `${value} value has mixed support, consider using ` + `flex-${value} instead`, { node: decl }, ); } } else if (prop === "text-decoration-skip" && value === "ink") { result.warn( "Replace text-decoration-skip: ink to " + "text-decoration-skip-ink: auto, because spec had been changed", { node: decl }, ); } else { if (gridPrefixes && this.gridStatus(decl, result)) { if (decl.value === "subgrid") { result.warn("IE does not support subgrid", { node: decl }); } if (/^(align|justify|place)-items$/.test(prop) && insideGrid(decl)) { let fixed = prop.replace("-items", "-self"); result.warn( `IE does not support ${prop} on grid containers. ` + `Try using ${fixed} on child elements instead: ` + `${decl.parent.selector} > * { ${fixed}: ${decl.value} }`, { node: decl }, ); } else if ( /^(align|justify|place)-content$/.test(prop) && insideGrid(decl) ) { result.warn(`IE does not support ${decl.prop} on grid containers`, { node: decl, }); } else if (prop === "display" && decl.value === "contents") { result.warn( "Please do not use display: contents; " + "if you have grid setting enabled", { node: decl }, ); return undefined; } else if (decl.prop === "grid-gap") { let status = this.gridStatus(decl, result); if ( status === "autoplace" && !hasRowsAndColumns(decl) && !hasGridTemplate(decl) ) { result.warn( "grid-gap only works if grid-template(-areas) is being " + "used or both rows and columns have been declared " + "and cells have not been manually " + "placed inside the explicit grid", { node: decl }, ); } else if ( (status === true || status === "no-autoplace") && !hasGridTemplate(decl) ) { result.warn( "grid-gap only works if grid-template(-areas) is being used", { node: decl }, ); } } else if (prop === "grid-auto-columns") { result.warn("grid-auto-columns is not supported by IE", { node: decl, }); return undefined; } else if (prop === "grid-auto-rows") { result.warn("grid-auto-rows is not supported by IE", { node: decl, }); return undefined; } else if (prop === "grid-auto-flow") { let hasRows = parent.some((i) => i.prop === "grid-template-rows"); let hasCols = parent.some((i) => i.prop === "grid-template-columns" );
if (hasGridTemplate(decl)) { result.warn("grid-auto-flow is not supported by IE", { node: decl, }); } else if (value.includes("dense")) { result.warn("grid-auto-flow: dense is not supported by IE", { node: decl, }); } else if (!hasRows && !hasCols) { result.warn( "grid-auto-flow works only if grid-template-rows and " + "grid-template-columns are present in the same rule", { node: decl }, ); } return undefined; } else if (value.includes("auto-fit")) { result.warn("auto-fit value is not supported by IE", { node: decl, word: "auto-fit", }); return undefined; } else if (value.includes("auto-fill")) { result.warn("auto-fill value is not supported by IE", { node: decl, word: "auto-fill", }); return undefined; } else if (prop.startsWith("grid-template") && value.includes("[")) { result.warn( "Autoprefixer currently does not support line names. " + "Try using grid-template-areas instead.", { node: decl, word: "[" }, ); } } if (value.includes("radial-gradient")) { if (OLD_RADIAL.test(decl.value)) { result.warn( "Gradient has outdated direction syntax. " + "New syntax is like `closest-side at 0 0` " + "instead of `0 0, closest-side`.", { node: decl }, ); } else { let ast = parser(value);
for (let i of ast.nodes) { if (i.type === "function" && i.value === "radial-gradient") { for (let word of i.nodes) { if (word.type === "word") { if (word.value === "cover") { result.warn( "Gradient has outdated direction syntax. " + "Replace `cover` to `farthest-corner`.", { node: decl }, ); } else if (word.value === "contain") { result.warn( "Gradient has outdated direction syntax. " + "Replace `contain` to `closest-side`.", { node: decl }, ); } } } } } } } if (value.includes("linear-gradient")) { if (OLD_LINEAR.test(value)) { result.warn( "Gradient has outdated direction syntax. " + "New syntax is like `to left` instead of `right`.", { node: decl }, ); } } }
if (SIZES.includes(decl.prop)) { if (!decl.value.includes("-fill-available")) { if (decl.value.includes("fill-available")) { result.warn( "Replace fill-available to stretch, " + "because spec had been changed", { node: decl }, ); } else if (decl.value.includes("fill")) { let ast = parser(value); if ( ast.nodes.some((i) => i.type === "word" && i.value === "fill") ) { result.warn( "Replace fill to stretch, because spec had been changed", { node: decl }, ); } } } }
let prefixer;
if (decl.prop === "transition" || decl.prop === "transition-property") { // Transition return this.prefixes.transition.add(decl, result); } else if (decl.prop === "align-self") { // align-self flexbox or grid let display = this.displayType(decl); if (display !== "grid" && this.prefixes.options.flexbox !== false) { prefixer = this.prefixes.add["align-self"]; if (prefixer && prefixer.prefixes) { prefixer.process(decl); } } if (this.gridStatus(decl, result) !== false) { prefixer = this.prefixes.add["grid-row-align"]; if (prefixer && prefixer.prefixes) { return prefixer.process(decl, result); } } } else if (decl.prop === "justify-self") { // justify-self flexbox or grid if (this.gridStatus(decl, result) !== false) { prefixer = this.prefixes.add["grid-column-align"]; if (prefixer && prefixer.prefixes) { return prefixer.process(decl, result); } } } else if (decl.prop === "place-self") { prefixer = this.prefixes.add["place-self"]; if ( prefixer && prefixer.prefixes && this.gridStatus(decl, result) !== false ) { return prefixer.process(decl, result); } } else { // Properties prefixer = this.prefixes.add[decl.prop]; if (prefixer && prefixer.prefixes) { return prefixer.process(decl, result); } }
return undefined; });
// Insert grid-area prefixes. We need to be able to store the different // rules as a data and hack API is not enough for this if (this.gridStatus(css, result)) { insertAreas(css, this.disabled); }
// Values return css.walkDecls((decl) => { if (this.disabledValue(decl, result)) return;
let unprefixed = this.prefixes.unprefixed(decl.prop); let list = this.prefixes.values("add", unprefixed); if (Array.isArray(list)) { for (let value of list) { if (value.process) value.process(decl, result); } } Value.save(this.prefixes, decl); }); }
/** * Remove unnecessary pefixes */ remove(css, result) { // At-rules let resolution = this.prefixes.remove["@resolution"];
css.walkAtRules((rule, i) => { if (this.prefixes.remove[`@${rule.name}`]) { if (!this.disabled(rule, result)) { rule.parent.removeChild(i); } } else if ( rule.name === "media" && rule.params.includes("-resolution") && resolution ) { resolution.clean(rule); } });
// Selectors for (let checker of this.prefixes.remove.selectors) { css.walkRules((rule, i) => { if (checker.check(rule)) { if (!this.disabled(rule, result)) { rule.parent.removeChild(i); } } }); }
return css.walkDecls((decl, i) => { if (this.disabled(decl, result)) return;
let rule = decl.parent; let unprefixed = this.prefixes.unprefixed(decl.prop);
// Transition if (decl.prop === "transition" || decl.prop === "transition-property") { this.prefixes.transition.remove(decl); }
// Properties if ( this.prefixes.remove[decl.prop] && this.prefixes.remove[decl.prop].remove ) { let notHack = this.prefixes.group(decl).down((other) => { return this.prefixes.normalize(other.prop) === unprefixed; });
if (unprefixed === "flex-flow") { notHack = true; }
if (decl.prop === "-webkit-box-orient") { let hacks = { "flex-direction": true, "flex-flow": true }; if (!decl.parent.some((j) => hacks[j.prop])) return; }
if (notHack && !this.withHackValue(decl)) { if (decl.raw("before").includes("\n")) { this.reduceSpaces(decl); } rule.removeChild(i); return; } }
// Values for (let checker of this.prefixes.values("remove", unprefixed)) { if (!checker.check) continue; if (!checker.check(decl.value)) continue;
unprefixed = checker.unprefixed; let notHack = this.prefixes.group(decl).down((other) => { return other.value.includes(unprefixed); });
if (notHack) { rule.removeChild(i); return; } } }); }
/** * Some rare old values, which is not in standard */ withHackValue(decl) { return decl.prop === "-webkit-background-clip" && decl.value === "text"; }
/** * Check for grid/flexbox options. */ disabledValue(node, result) { if (this.gridStatus(node, result) === false && node.type === "decl") { if (node.prop === "display" && node.value.includes("grid")) { return true; } } if (this.prefixes.options.flexbox === false && node.type === "decl") { if (node.prop === "display" && node.value.includes("flex")) { return true; } } if (node.type === "decl" && node.prop === "content") { return true; }
return this.disabled(node, result); }
/** * Check for grid/flexbox options. */ disabledDecl(node, result) { if (this.gridStatus(node, result) === false && node.type === "decl") { if (node.prop.includes("grid") || node.prop === "justify-items") { return true; } } if (this.prefixes.options.flexbox === false && node.type === "decl") { let other = ["order", "justify-content", "align-items", "align-content"]; if (node.prop.includes("flex") || other.includes(node.prop)) { return true; } }
return this.disabled(node, result); }
/** * Check for control comment and global options */ disabled(node, result) { if (!node) return false;
if (node._autoprefixerDisabled !== undefined) { return node._autoprefixerDisabled; }
if (node.parent) { let p = node.prev(); if (p && p.type === "comment" && IGNORE_NEXT.test(p.text)) { node._autoprefixerDisabled = true; node._autoprefixerSelfDisabled = true; return true; } }
let value = null; if (node.nodes) { let status; node.each((i) => { if (i.type !== "comment") return; if (/(!\s*)?autoprefixer:\s*(off|on)/i.test(i.text)) { if (typeof status !== "undefined") { result.warn( "Second Autoprefixer control comment " + "was ignored. Autoprefixer applies control " + "comment to whole block, not to next rules.", { node: i }, ); } else { status = /on/i.test(i.text); } } });
if (status !== undefined) { value = !status; } } if (!node.nodes || value === null) { if (node.parent) { let isParentDisabled = this.disabled(node.parent, result); if (node.parent._autoprefixerSelfDisabled === true) { value = false; } else { value = isParentDisabled; } } else { value = false; } } node._autoprefixerDisabled = value; return value; }
/** * Normalize spaces in cascade declaration group */ reduceSpaces(decl) { let stop = false; this.prefixes.group(decl).up(() => { stop = true; return true; }); if (stop) { return; }
let parts = decl.raw("before").split("\n"); let prevMin = parts[parts.length - 1].length; let diff = false;
this.prefixes.group(decl).down((other) => { parts = other.raw("before").split("\n"); let last = parts.length - 1;
if (parts[last].length > prevMin) { if (diff === false) { diff = parts[last].length - prevMin; }
parts[last] = parts[last].slice(0, -diff); other.raws.before = parts.join("\n"); } }); }
/** * Is it flebox or grid rule */ displayType(decl) { for (let i of decl.parent.nodes) { if (i.prop !== "display") { continue; }
if (i.value.includes("flex")) { return "flex"; }
if (i.value.includes("grid")) { return "grid"; } }
return false; }
/** * Set grid option via control comment */ gridStatus(node, result) { if (!node) return false;
if (node._autoprefixerGridStatus !== undefined) { return node._autoprefixerGridStatus; }
let value = null; if (node.nodes) { let status; node.each((i) => { if (i.type !== "comment") return; if (GRID_REGEX.test(i.text)) { let hasAutoplace = /:\s*autoplace/i.test(i.text); let noAutoplace = /no-autoplace/i.test(i.text); if (typeof status !== "undefined") { result.warn( "Second Autoprefixer grid control comment was " + "ignored. Autoprefixer applies control comments to the whole " + "block, not to the next rules.", { node: i }, ); } else if (hasAutoplace) { status = "autoplace"; } else if (noAutoplace) { status = true; } else { status = /on/i.test(i.text); } } });
if (status !== undefined) { value = status; } }
if (node.type === "atrule" && node.name === "supports") { let params = node.params; if (params.includes("grid") && params.includes("auto")) { value = false; } }
if (!node.nodes || value === null) { if (node.parent) { let isParentGrid = this.gridStatus(node.parent, result); if (node.parent._autoprefixerSelfDisabled === true) { value = false; } else { value = isParentGrid; } } else if (typeof this.prefixes.options.grid !== "undefined") { value = this.prefixes.options.grid; } else if (typeof Deno.env.get("AUTOPREFIXER_GRID") !== "undefined") { if (Deno.env.get("AUTOPREFIXER_GRID") === "autoplace") { value = "autoplace"; } else { value = true; } } else { value = false; } }
node._autoprefixerGridStatus = value; return value; }}
export default Processor;