diff --git a/src/Middleware.js b/src/Middleware.js index faf234f..1d7df2c 100644 --- a/src/Middleware.js +++ b/src/Middleware.js @@ -1,6 +1,6 @@ import {MS, MOZ, WEBKIT, RULESET, KEYFRAMES, DECLARATION} from './Enum.js' -import {match, charat, substr, strlen, sizeof, replace, combine} from './Utility.js' -import {copy, tokenize} from './Tokenizer.js' +import {match, charat, substr, strlen, sizeof, replace, combine, filter, assign} from './Utility.js' +import {copy, lift, tokenize} from './Tokenizer.js' import {serialize} from './Serializer.js' import {prefix} from './Prefixer.js' @@ -49,18 +49,22 @@ export function prefixer (element, index, children, callback) { return serialize([copy(element, {value: replace(element.value, '@', '@' + WEBKIT)})], callback) case RULESET: if (element.length) - return combine(element.props, function (value) { - switch (match(value, /(::plac\w+|:read-\w+)/)) { + return combine(children = element.props, function (value) { + switch (match(value, callback = /(::plac\w+|:read-\w+)/)) { // :read-(only|write) case ':read-only': case ':read-write': - return serialize([copy(element, {props: [replace(value, /:(read-\w+)/, ':' + MOZ + '$1')]})], callback) + lift(copy(element, {props: [replace(value, /:(read-\w+)/, ':' + MOZ + '$1')]})) + lift(copy(element, {props: [value]})) + assign(element, {props: filter(children, callback)}) + break // :placeholder case '::placeholder': - return serialize([ - copy(element, {props: [replace(value, /:(plac\w+)/, ':' + WEBKIT + 'input-$1')]}), - copy(element, {props: [replace(value, /:(plac\w+)/, ':' + MOZ + '$1')]}), - copy(element, {props: [replace(value, /:(plac\w+)/, MS + 'input-$1')]}) - ], callback) + lift(copy(element, {props: [replace(value, /:(plac\w+)/, ':' + WEBKIT + 'input-$1')]})) + lift(copy(element, {props: [replace(value, /:(plac\w+)/, ':' + MOZ + '$1')]})) + lift(copy(element, {props: [replace(value, /:(plac\w+)/, MS + 'input-$1')]})) + lift(copy(element, {props: [value]})) + assign(element, {props: filter(children, callback)}) + break } return '' diff --git a/src/Parser.js b/src/Parser.js index 68c5c02..e7472be 100644 --- a/src/Parser.js +++ b/src/Parser.js @@ -64,7 +64,7 @@ export function parse (value, root, parent, rule, rules, rulesets, pseudo, point case 47: switch (peek()) { case 42: case 47: - append(comment(commenter(next(), caret()), root, parent), declarations) + append(comment(commenter(next(), caret()), root, parent, declarations), declarations) break default: characters += '/' @@ -81,13 +81,13 @@ export function parse (value, root, parent, rule, rules, rulesets, pseudo, point // ; case 59 + offset: if (ampersand == -1) characters = replace(characters, /\f/g, '') if (property > 0 && (strlen(characters) - length)) - append(property > 32 ? declaration(characters + ';', rule, parent, length - 1) : declaration(replace(characters, ' ', '') + ';', rule, parent, length - 2), declarations) + append(property > 32 ? declaration(characters + ';', rule, parent, length - 1, declarations) : declaration(replace(characters, ' ', '') + ';', rule, parent, length - 2, declarations), declarations) break // @ ; case 59: characters += ';' // { rule/at-rule default: - append(reference = ruleset(characters, root, parent, index, offset, rules, points, type, props = [], children = [], length), rulesets) + append(reference = ruleset(characters, root, parent, index, offset, rules, points, type, props = [], children = [], length, rulesets), rulesets) if (character === 123) if (offset === 0) @@ -96,7 +96,7 @@ export function parse (value, root, parent, rule, rules, rulesets, pseudo, point switch (atrule === 99 && charat(characters, 3) === 110 ? 100 : atrule) { // d l m s case 100: case 108: case 109: case 115: - parse(value, reference, reference, rule && append(ruleset(value, reference, reference, 0, 0, rules, points, type, rules, props = [], length), children), rules, children, length, points, rule ? props : children) + parse(value, reference, reference, rule && append(ruleset(value, reference, reference, 0, 0, rules, points, type, rules, props = [], length, children), children), rules, children, length, points, rule ? props : children) break default: parse(characters, reference, reference, reference, [''], children, 0, points, children) @@ -154,9 +154,10 @@ export function parse (value, root, parent, rule, rules, rulesets, pseudo, point * @param {string[]} props * @param {string[]} children * @param {number} length + * @param {object[]} siblings * @return {object} */ -export function ruleset (value, root, parent, index, offset, rules, points, type, props, children, length) { +export function ruleset (value, root, parent, index, offset, rules, points, type, props, children, length, siblings) { var post = offset - 1 var rule = offset === 0 ? rules : [''] var size = sizeof(rule) @@ -166,17 +167,18 @@ export function ruleset (value, root, parent, index, offset, rules, points, type if (z = trim(j > 0 ? rule[x] + ' ' + y : replace(y, /&\f/g, rule[x]))) props[k++] = z - return node(value, root, parent, offset === 0 ? RULESET : type, props, children, length) + return node(value, root, parent, offset === 0 ? RULESET : type, props, children, length, siblings) } /** * @param {number} value * @param {object} root * @param {object?} parent + * @param {object[]} siblings * @return {object} */ -export function comment (value, root, parent) { - return node(value, root, parent, COMMENT, from(char()), substr(value, 2, -2), 0) +export function comment (value, root, parent, siblings) { + return node(value, root, parent, COMMENT, from(char()), substr(value, 2, -2), 0, siblings) } /** @@ -184,8 +186,9 @@ export function comment (value, root, parent) { * @param {object} root * @param {object?} parent * @param {number} length + * @param {object[]} siblings * @return {object} */ -export function declaration (value, root, parent, length) { - return node(value, root, parent, DECLARATION, substr(value, 0, length), substr(value, length + 1, -1), length) +export function declaration (value, root, parent, length, siblings) { + return node(value, root, parent, DECLARATION, substr(value, 0, length), substr(value, length + 1, -1), length, siblings) } diff --git a/src/Serializer.js b/src/Serializer.js index 0c1ffa3..cad20d4 100644 --- a/src/Serializer.js +++ b/src/Serializer.js @@ -1,5 +1,5 @@ import {IMPORT, LAYER, COMMENT, RULESET, DECLARATION, KEYFRAMES} from './Enum.js' -import {strlen, sizeof} from './Utility.js' +import {strlen} from './Utility.js' /** * @param {object[]} children @@ -8,9 +8,8 @@ import {strlen, sizeof} from './Utility.js' */ export function serialize (children, callback) { var output = '' - var length = sizeof(children) - for (var i = 0; i < length; i++) + for (var i = 0; i < children.length; i++) output += callback(children[i], i, children, callback) || '' return output @@ -29,7 +28,7 @@ export function stringify (element, index, children, callback) { case IMPORT: case DECLARATION: return element.return = element.return || element.value case COMMENT: return '' case KEYFRAMES: return element.return = element.value + '{' + serialize(element.children, callback) + '}' - case RULESET: element.value = element.props.join(',') + case RULESET: if (!strlen(element.value = element.props.join(','))) return '' } return strlen(children = serialize(element.children, callback)) ? element.return = element.value + '{' + children + '}' : '' diff --git a/src/Tokenizer.js b/src/Tokenizer.js index dd482ce..201fb51 100644 --- a/src/Tokenizer.js +++ b/src/Tokenizer.js @@ -14,10 +14,11 @@ export var characters = '' * @param {string} type * @param {string[] | string} props * @param {object[] | string} children + * @param {object[]} siblings * @param {number} length */ -export function node (value, root, parent, type, props, children, length) { - return {value: value, root: root, parent: parent, type: type, props: props, children: children, line: line, column: column, length: length, return: ''} +export function node (value, root, parent, type, props, children, length, siblings) { + return {value: value, root: root, parent: parent, type: type, props: props, children: children, line: line, column: column, length: length, return: '', siblings: siblings} } /** @@ -26,7 +27,17 @@ export function node (value, root, parent, type, props, children, length) { * @return {object} */ export function copy (root, props) { - return assign(node('', null, null, '', null, null, 0), root, {length: -root.length}, props) + return assign(node('', null, null, '', null, null, 0, root.siblings), root, {length: -root.length}, props) +} + +/** + * @param {object} root + */ +export function lift (root) { + while (root.root) + root = copy(root.root, {children: [root]}) + + append(root, root.siblings) } /** diff --git a/src/Utility.js b/src/Utility.js index 702fe6e..8357dbc 100644 --- a/src/Utility.js +++ b/src/Utility.js @@ -113,3 +113,12 @@ export function append (value, array) { export function combine (array, callback) { return array.map(callback).join('') } + +/** + * @param {string[]} array + * @param {RegExp} pattern + * @return {string[]} + */ +export function filter (array, pattern) { + return array.filter(function (value) { return !match(value, pattern) }) +} diff --git a/test/Middleware.js b/test/Middleware.js index 596c8e9..3167d0b 100644 --- a/test/Middleware.js +++ b/test/Middleware.js @@ -3,9 +3,23 @@ import {compile, serialize, stringify, middleware, rulesheet, prefixer, namespac const stack = [] describe('Middleware', () => { - test('rulesheet', () => { - serialize(compile(`@import url('something.com/file.css');.user{ h1 {width:0;} @media{width:1;}@keyframes name{from{width:0;}to{width:1;}}}@keyframes empty{}`), middleware([prefixer, stringify, rulesheet(value => stack.push(value))])) - expect(stack).to.deep.equal([ + test('rulesheet', () => { + serialize(compile(` + @import url('something.com/file.css'); + .user{ h1 {width:0;} + @media{width:1;} + @keyframes name{from{width:0;}to{width:1;}}} + @keyframes empty{} + @supports (display: grid) { + @media (min-width: 500px){ + .a::placeholder{color: red;} + } + } + @media (min-width: 500px) { + a:read-only{color:red;} + } + `), middleware([prefixer, stringify, rulesheet(value => stack.push(value))])) + expect(stack).to.deep.equal([ `@import url('something.com/file.css');`, `.user h1{width:0;}`, `@media{.user{width:1;}}`, @@ -13,11 +27,17 @@ describe('Middleware', () => { `@keyframes name{from{width:0;}to{width:1;}}`, '@-webkit-keyframes empty{}', '@keyframes empty{}', + '@supports (display: grid){@media (min-width: 500px){.a::-webkit-input-placeholder{color:red;}}}', + '@supports (display: grid){@media (min-width: 500px){.a::-moz-placeholder{color:red;}}}', + '@supports (display: grid){@media (min-width: 500px){.a:-ms-input-placeholder{color:red;}}}', + '@supports (display: grid){@media (min-width: 500px){.a::placeholder{color:red;}}}', + '@media (min-width: 500px){a:-moz-read-only{color:red;}}', + '@media (min-width: 500px){a:read-only{color:red;}}', ]) }) test('namespace', () => { - expect(serialize(compile(`.user{width:0; :global(p,a){width:1;} h1 {width:1; h2:last-child {width:2} h2 h3 {width:3}}}`), middleware([namespace, stringify]))).to.equal([ + expect(serialize(compile(`.user{width:0; :global(p,a){width:1;} h1 {width:1; h2:last-child {width:2} h2 h3 {width:3}}}`), middleware([namespace, stringify]))).to.equal([ `.user{width:0;}`, `p,a{width:1;}`, `h1.user.user{width:1;}`, `h1.user h2:last-child.user{width:2;}`, `h1.user h2 h3.user{width:3;}` ].join('')) @@ -59,34 +79,42 @@ describe('Middleware', () => { ].join('')) expect(serialize(compile(`textarea::placeholder{font-size:14px;@media{font-size:16px;}}`), middleware([prefixer, stringify]))).to.equal([ - `textarea::-webkit-input-placeholder{font-size:14px;}textarea::-moz-placeholder{font-size:14px;}textarea:-ms-input-placeholder{font-size:14px;}textarea::placeholder{font-size:14px;}`, - `@media{textarea::-webkit-input-placeholder{font-size:16px;}textarea::-moz-placeholder{font-size:16px;}textarea:-ms-input-placeholder{font-size:16px;}textarea::placeholder{font-size:16px;}}` + `textarea::-webkit-input-placeholder{font-size:14px;}`, + `textarea::-moz-placeholder{font-size:14px;}`, + `textarea:-ms-input-placeholder{font-size:14px;}`, + `textarea::placeholder{font-size:14px;}`, + `@media{textarea::-webkit-input-placeholder{font-size:16px;}}`, + `@media{textarea::-moz-placeholder{font-size:16px;}}`, + `@media{textarea:-ms-input-placeholder{font-size:16px;}}`, + `@media{textarea::placeholder{font-size:16px;}}`, ].join('')) expect(serialize(compile(`div:read-write{background-color:red;span{background-color:green;}}`), middleware([prefixer, stringify]))).to.equal([ `div:-moz-read-write{background-color:red;}`, `div:read-write{background-color:red;}`, `div:-moz-read-write span{background-color:green;}`, - `div:read-write span{background-color:green;}` + `div:read-write span{background-color:green;}`, ].join('')) expect(serialize(compile(`div:read-write span{background-color:hotpink;}`), middleware([prefixer, stringify]))).to.equal([ `div:-moz-read-write span{background-color:hotpink;}`, - `div:read-write span{background-color:hotpink;}` + `div:read-write span{background-color:hotpink;}`, ].join('')) expect(serialize(compile(`.read-write:read-write,.read-only:read-only,.placeholder::placeholder{background-color:hotpink;}`), middleware([prefixer, stringify]))).to.equal([ `.read-write:-moz-read-write{background-color:hotpink;}`, + `.read-write:read-write{background-color:hotpink;}`, `.read-only:-moz-read-only{background-color:hotpink;}`, + `.read-only:read-only{background-color:hotpink;}`, `.placeholder::-webkit-input-placeholder{background-color:hotpink;}`, `.placeholder::-moz-placeholder{background-color:hotpink;}`, `.placeholder:-ms-input-placeholder{background-color:hotpink;}`, - `.read-write:read-write,.read-only:read-only,.placeholder::placeholder{background-color:hotpink;}`, + `.placeholder::placeholder{background-color:hotpink;}`, ].join('')) expect(serialize(compile(`:read-write{background-color:hotpink;}`), middleware([prefixer, stringify]))).to.equal([ `:-moz-read-write{background-color:hotpink;}`, - `:read-write{background-color:hotpink;}` + `:read-write{background-color:hotpink;}`, ].join('')) }) }) diff --git a/test/Tokenizer.js b/test/Tokenizer.js index fedf45b..42695a8 100644 --- a/test/Tokenizer.js +++ b/test/Tokenizer.js @@ -2,9 +2,9 @@ import {tokenize} from "../index.js" describe('Tokenizer', () => { test('tokenize', () => { - expect(tokenize(`h1 h2 (h1 h2) 1 / 3 * 2 + 1 [1 2] "1 2" a`)).to.deep.equal([ - 'h1', ' ', 'h2', ' ', '(h1 h2)', ' ', '1', ' ', '/', ' ', '3', ' ', '*', ' ', '2', ' ', '+', ' ', '1', ' ', '[1 2]', ' ', '"1 2"', ' ', 'a' - ]) + expect(tokenize(`h1 h2 (h1 h2) 1 / 3 * 2 + 1 [1 2] "1 2" a`)).to.deep.equal([ + 'h1', ' ', 'h2', ' ', '(h1 h2)', ' ', '1', ' ', '/', ' ', '3', ' ', '*', ' ', '2', ' ', '+', ' ', '1', ' ', '[1 2]', ' ', '"1 2"', ' ', 'a' + ]) expect(tokenize(`:global([data-popper-placement^='top'])`)).to.deep.equal([ `:`, `global`, `([data-popper-placement^='top'])`