Repository: postcss/autoprefixer
Pull request #938 (2017-11-08 11:15:20)

Add partial grid-area support, improve grid-row and grid-column
Another take on #883 

While the original issue suggested only translation of span syntax, this is an attempt to translate `grid-area` shorthand to `-ms-` properties as fully as possible. It is also applied to `grid-row` and `grid-column` shorthands.

~~Tests are failing:~~
~~1) 'removes unnecessary prefixes': presumably because it also should remove `-ms-` properties and currently it doesn't.~~
~~2) 'prevents doubling prefixes': it adds properties again.~~

~~Currently I'm trying to figure it out.~~

ai: Tests fall. Seems like you fix can’t clean own prefixes. In this case, you can create separated test case just for `grid-area` and do not use this tests in `removes unnecessary prefixes`.
Alex7Kom: Done! I also have figured out 'doubling prefixes' check.
Now it fails on size limit, not sure what to do about it.
ai: @Alex7Kom just increase limit in `package.json/size-limit` :)
Alex7Kom: Fixed everything noted, also added `grid-row` and `grid-column` translation.
ai: Awesome!

Patch:
diff --git a/data/prefixes.js b/data/prefixes.js
index 96e8ba8..3afd353 100644
--- a/data/prefixes.js
+++ b/data/prefixes.js
@@ -710,7 +710,7 @@ f(grid, browsers => {
         'grid-template-columns', 'grid-template-rows',
         'grid-row-start', 'grid-column-start',
         'grid-row-end', 'grid-column-end',
-        'grid-row', 'grid-column'
+        'grid-row', 'grid-column', 'grid-area'
     ], {
         feature: 'css-grid',
         browsers
diff --git a/lib/hacks/grid-area.js b/lib/hacks/grid-area.js
new file mode 100644
index 0000000..f2ff12e
--- /dev/null
+++ b/lib/hacks/grid-area.js
@@ -0,0 +1,74 @@
+const Declaration = require('../declaration');
+const shorthand = require('./grid-shorthand');
+
+class GridArea extends Declaration {
+
+    static names = [
+        'grid-area'
+    ];
+
+    /**
+     * Check if -ms-grid-row is already present
+     */
+    checkIfAlreadyApplied(decl) {
+        for (const i in decl.parent.nodes) {
+            if (decl.parent.nodes.hasOwnProperty(i)) {
+                const element = decl.parent.nodes[i];
+                if (element.prop === '-ms-grid-row') {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Translate grid-area to separate -ms- prefixed properties
+     */
+    insert(decl, prefix, prefixes) {
+        if (prefix !== '-ms-') {
+            return super.insert(decl, prefix, prefixes);
+        }
+
+        if (!this.checkIfAlreadyApplied(decl)) {
+            return undefined;
+        }
+
+        const values = shorthand.parse(decl);
+
+        const [rowStart, rowSpan] = shorthand.translate(values, 0, 2);
+        const [columnStart, columnSpan] = shorthand.translate(values, 1, 3);
+
+        if (rowStart) {
+            decl.cloneBefore({
+                prop: '-ms-grid-row',
+                value: rowStart.toString()
+            });
+        }
+
+        if (rowSpan) {
+            decl.cloneBefore({
+                prop: '-ms-grid-row-span',
+                value: rowSpan.toString()
+            });
+        }
+
+        if (columnStart) {
+            decl.cloneBefore({
+                prop: '-ms-grid-column',
+                value: columnStart.toString()
+            });
+        }
+
+        if (columnSpan) {
+            decl.cloneBefore({
+                prop: '-ms-grid-column-span',
+                value: columnSpan.toString()
+            });
+        }
+
+        return undefined;
+    }
+}
+
+module.exports = GridArea;
diff --git a/lib/hacks/grid-row-column.js b/lib/hacks/grid-row-column.js
new file mode 100644
index 0000000..4ade5e4
--- /dev/null
+++ b/lib/hacks/grid-row-column.js
@@ -0,0 +1,59 @@
+const Declaration = require('../declaration');
+const shorthand = require('./grid-shorthand');
+
+class GridStart extends Declaration {
+
+    static names = [
+        'grid-row', 'grid-column'
+    ];
+
+    /**
+     * Check if -ms-grid-{row|column} is already present
+     */
+    checkIfAlreadyApplied(decl) {
+        for (const i in decl.parent.nodes) {
+            if (decl.parent.nodes.hasOwnProperty(i)) {
+                const element = decl.parent.nodes[i];
+                if (element.prop === '-ms-' + decl.prop) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Translate grid-row / grid-column to separate -ms- prefixed properties
+     */
+    insert(decl, prefix, prefixes) {
+        if (prefix !== '-ms-') {
+            return super.insert(decl, prefix, prefixes);
+        }
+
+        if (!this.checkIfAlreadyApplied(decl)) {
+            return undefined;
+        }
+
+        const values = shorthand.parse(decl);
+
+        const [start, span] = shorthand.translate(values, 0, 1);
+
+        if (start) {
+            decl.cloneBefore({
+                prop: `-ms-${decl.prop}`,
+                value: start.toString()
+            });
+        }
+
+        if (span) {
+            decl.cloneBefore({
+                prop: `-ms-${decl.prop}-span`,
+                value: span.toString()
+            });
+        }
+
+        return undefined;
+    }
+}
+
+module.exports = GridStart;
diff --git a/lib/hacks/grid-shorthand.js b/lib/hacks/grid-shorthand.js
new file mode 100644
index 0000000..94358e4
--- /dev/null
+++ b/lib/hacks/grid-shorthand.js
@@ -0,0 +1,77 @@
+const parser = require('postcss-value-parser');
+
+function convert(value) {
+    if (value &&
+        value.length === 2 &&
+        value[0] === 'span' &&
+        parseInt(value[1], 10) > 0
+    ) {
+        return [false, parseInt(value[1], 10)];
+    }
+
+    if (value &&
+        value.length === 1 &&
+        parseInt(value[0], 10) > 0
+    ) {
+        return [parseInt(value[0], 10), false];
+    }
+
+    return [false, false];
+}
+
+/**
+ * Translate values to start-span
+ */
+function translate(values, startIndex, endIndex) {
+    const startValue = values[startIndex];
+    const endValue = values[endIndex];
+
+    if (!startValue) {
+        return [false, false];
+    }
+
+    const [start, spanStart] = convert(startValue);
+    const [end, spanEnd] = convert(endValue);
+
+    if (start && !endValue) {
+        return [start, false];
+    }
+
+    if (spanStart && end) {
+        return [end - spanStart, spanStart];
+    }
+
+    if (start && spanEnd) {
+        return [start, spanEnd];
+    }
+
+    if (start && end) {
+        return [start, end - start];
+    }
+
+    return [false, false];
+}
+
+function parse(decl) {
+    const node = parser(decl.value);
+
+    let values = [];
+    let current = 0;
+    values[current] = [];
+
+    for (const i of node.nodes) {
+        if (i.type === 'div') {
+            current += 1;
+            values[current] = [];
+        } else if (i.type === 'word') {
+            values[current].push(i.value);
+        }
+    }
+
+    return values;
+}
+
+module.exports = {
+    translate,
+    parse
+};
diff --git a/lib/hacks/grid-start.js b/lib/hacks/grid-start.js
index 1ddb55b..4e511ff 100644
--- a/lib/hacks/grid-start.js
+++ b/lib/hacks/grid-start.js
@@ -3,7 +3,7 @@ const Declaration = require('../declaration');
 class GridStart extends Declaration {
 
     static names = [
-        'grid-row-start', 'grid-column-start', 'grid-row', 'grid-column'
+        'grid-row-start', 'grid-column-start'
     ];
 
     /**
@@ -31,45 +31,6 @@ class GridStart extends Declaration {
             return super.prefixed(prop, prefix);
         }
     }
-
-    /**
-     * Split one value to two
-     */
-    insert(decl, prefix, prefixes) {
-        const parts = this.splitValue(decl, prefix);
-        if (parts.length === 2) {
-            decl.cloneBefore({
-                prop: `-ms-${decl.prop}-span`,
-                value: parts[1]
-            });
-        }
-        return super.insert(decl, prefix, prefixes);
-    }
-
-    /**
-     * Change value for combine property
-     */
-    set(decl, prefix) {
-        const parts = this.splitValue(decl, prefix);
-        if (parts.length === 2) {
-            decl.value = parts[0];
-        }
-        return super.set(decl, prefix);
-    }
-
-    /**
-     * If property contains start and end
-     */
-    splitValue(decl, prefix) {
-        if (prefix === '-ms-' && decl.prop.indexOf('-start') === -1) {
-            const parts = decl.value.split(/\s*\/\s*span\s+/);
-            if (parts.length === 2) {
-                return parts;
-            }
-        }
-        return false;
-    }
-
 }
 
 module.exports = GridStart;
diff --git a/lib/prefixes.js b/lib/prefixes.js
index f1f8d51..4097bee 100644
--- a/lib/prefixes.js
+++ b/lib/prefixes.js
@@ -46,6 +46,8 @@ Declaration.hack(require('./hacks/justify-content'));
 Declaration.hack(require('./hacks/background-size'));
 Declaration.hack(require('./hacks/grid-column-align'));
 Declaration.hack(require('./hacks/text-emphasis-position'));
+Declaration.hack(require('./hacks/grid-area'));
+Declaration.hack(require('./hacks/grid-row-column'));
 
 Value.hack(require('./hacks/gradient'));
 Value.hack(require('./hacks/intrinsic'));
diff --git a/package.json b/package.json
index 354e9eb..81c5e44 100644
--- a/package.json
+++ b/package.json
@@ -56,7 +56,7 @@
   "size-limit": [
     {
       "path": "build/lib/autoprefixer.js",
-      "limit": "240 KB"
+      "limit": "242 KB"
     }
   ],
   "eslintConfig": {


Test patch:
diff --git a/test/autoprefixer.test.js b/test/autoprefixer.test.js
index 4d95d95..536400a 100644
--- a/test/autoprefixer.test.js
+++ b/test/autoprefixer.test.js
@@ -61,7 +61,7 @@ const resolutioner = autoprefixer({
 });
 
 function prefixer(name) {
-    if (name === 'grid') {
+    if (name === 'grid' || name === 'grid-area') {
         return grider;
     } else if (name === 'keyframes') {
         return keyframer;
@@ -133,7 +133,7 @@ const COMMONS = [
     'supports', 'viewport', 'resolution', 'logical', 'appearance',
     'advanced-filter', 'element', 'image-set', 'image-rendering',
     'mask-border', 'writing-mode', 'cross-fade', 'gradient-fix',
-    'text-emphasis-position', 'grid'
+    'text-emphasis-position', 'grid', 'grid-area'
 ];
 
 it('throws on wrong options', () => {
@@ -233,6 +233,7 @@ it('removes unnecessary prefixes', () => {
         if (type === 'cascade' ) continue;
         if (type === 'mistakes' ) continue;
         if (type === 'flex-rewrite' ) continue;
+        if (type === 'grid-area' ) continue;
         const input  = read(type + '.out');
         const output = read(type);
         expect(processor.process(input).css).toEqual(output);
diff --git a/test/cases/grid-area.css b/test/cases/grid-area.css
new file mode 100644
index 0000000..14b4f2d
--- /dev/null
+++ b/test/cases/grid-area.css
@@ -0,0 +1,23 @@
+.a {
+    grid-area: 5 / 1 / span 1 / span 5;
+}
+
+.b {
+    grid-area: span 1 / span 3 / 4 / 4;
+}
+
+.c {
+    grid-area: 2 / 2;
+}
+
+.d {
+    grid-area: "custom-ident";
+}
+
+.e {
+    grid-area: 2 / 2 / 3 / 8;
+}
+
+.f {
+    grid-area: 2 / 2 / 3;
+}
diff --git a/test/cases/grid-area.out.css b/test/cases/grid-area.out.css
new file mode 100644
index 0000000..990420f
--- /dev/null
+++ b/test/cases/grid-area.out.css
@@ -0,0 +1,40 @@
+.a {
+    -ms-grid-row: 5;
+    -ms-grid-row-span: 1;
+    -ms-grid-column: 1;
+    -ms-grid-column-span: 5;
+    grid-area: 5 / 1 / span 1 / span 5;
+}
+
+.b {
+    -ms-grid-row: 3;
+    -ms-grid-row-span: 1;
+    -ms-grid-column: 1;
+    -ms-grid-column-span: 3;
+    grid-area: span 1 / span 3 / 4 / 4;
+}
+
+.c {
+    -ms-grid-row: 2;
+    -ms-grid-column: 2;
+    grid-area: 2 / 2;
+}
+
+.d {
+    grid-area: "custom-ident";
+}
+
+.e {
+    -ms-grid-row: 2;
+    -ms-grid-row-span: 1;
+    -ms-grid-column: 2;
+    -ms-grid-column-span: 6;
+    grid-area: 2 / 2 / 3 / 8;
+}
+
+.f {
+    -ms-grid-row: 2;
+    -ms-grid-row-span: 1;
+    -ms-grid-column: 2;
+    grid-area: 2 / 2 / 3;
+}
diff --git a/test/cases/grid.css b/test/cases/grid.css
index 639e129..bade428 100644
--- a/test/cases/grid.css
+++ b/test/cases/grid.css
@@ -20,6 +20,11 @@
     grid-column: 1 / span 3;
 }
 
+.d {
+    grid-column: 1 / 3;
+    grid-row: span 2 / 5;
+}
+
 .webkit {
     grid: subgrid;
     grid-template-areas: "head head"
diff --git a/test/cases/grid.disabled.css b/test/cases/grid.disabled.css
index d8359cf..3d1a30c 100644
--- a/test/cases/grid.disabled.css
+++ b/test/cases/grid.disabled.css
@@ -20,6 +20,11 @@
     grid-column: 1 / span 3;
 }
 
+.d {
+    grid-column: 1 / 3;
+    grid-row: span 2 / 5;
+}
+
 .webkit {
     grid: subgrid;
     grid-template-areas: "head head"
diff --git a/test/cases/grid.out.css b/test/cases/grid.out.css
index 2ff71d3..91db210 100644
--- a/test/cases/grid.out.css
+++ b/test/cases/grid.out.css
@@ -29,11 +29,20 @@
 }
 
 .c {
-    -ms-grid-column-span: 3;
     -ms-grid-column: 1;
+    -ms-grid-column-span: 3;
     grid-column: 1 / span 3;
 }
 
+.d {
+    -ms-grid-column: 1;
+    -ms-grid-column-span: 2;
+    grid-column: 1 / 3;
+    -ms-grid-row: 3;
+    -ms-grid-row-span: 2;
+    grid-row: span 2 / 5;
+}
+
 .webkit {
     grid: subgrid;
     grid-template-areas: "head head"