diff --git a/README.md b/README.md index 3bd6a0d..72aa91d 100644 --- a/README.md +++ b/README.md @@ -179,14 +179,14 @@ whether the first character in the argument is a quotation mark. ```js { type: 'function', sourceIndex: 0, value: 'url', before: ' ', after: ' ', nodes: [ - type: 'string', sourceIndex: 5, quote: '"', value: '/gfx/img/bg.jpg' }, + { type: 'string', sourceIndex: 5, quote: '"', value: '/gfx/img/bg.jpg' }, ] } ``` ### unicode-range -The unicode-range CSS descriptor sets the specific range of characters to be -used from a font defined by @font-face and made available +The unicode-range CSS descriptor sets the specific range of characters to be +used from a font defined by @font-face and made available for use on the current page (`unicode-range: U+0025-00FF`). Node-specific properties: @@ -195,7 +195,7 @@ Node-specific properties: ## API -``` +```js var valueParser = require('postcss-value-parser'); ``` @@ -258,6 +258,6 @@ Stringifies the node tree. Walks each node inside `parsed.nodes`. See the documentation for `valueParser.walk()` above. -# License +## License MIT © [Bogdan Chadkin](mailto:trysound@yandex.ru) diff --git a/lib/index.d.ts b/lib/index.d.ts new file mode 100644 index 0000000..8759f3f --- /dev/null +++ b/lib/index.d.ts @@ -0,0 +1,177 @@ +declare namespace postcssValueParser { + interface BaseNode { + /** + * The offset, inclusive, inside the CSS value at which the node starts. + */ + sourceIndex: number; + + /** + * The offset, exclusive, inside the CSS value at which the node ends. + */ + sourceEndIndex: number; + + /** + * The node's characteristic value + */ + value: string; + } + + interface ClosableNode { + /** + * Whether the parsed CSS value ended before the node was properly closed + */ + unclosed?: true; + } + + interface AdjacentAwareNode { + /** + * The token at the start of the node + */ + before: string; + + /** + * The token at the end of the node + */ + after: string; + } + + interface CommentNode extends BaseNode, ClosableNode { + type: "comment"; + } + + interface DivNode extends BaseNode, AdjacentAwareNode { + type: "div"; + } + + interface FunctionNode extends BaseNode, ClosableNode, AdjacentAwareNode { + type: "function"; + + /** + * Nodes inside the function + */ + nodes: Node[]; + } + + interface SpaceNode extends BaseNode { + type: "space"; + } + + interface StringNode extends BaseNode, ClosableNode { + type: "string"; + + /** + * The quote type delimiting the string + */ + quote: '"' | "'"; + } + + interface UnicodeRangeNode extends BaseNode { + type: "unicode-range"; + } + + interface WordNode extends BaseNode { + type: "word"; + } + + /** + * Any node parsed from a CSS value + */ + type Node = + | CommentNode + | DivNode + | FunctionNode + | SpaceNode + | StringNode + | UnicodeRangeNode + | WordNode; + + interface CustomStringifierCallback { + /** + * @param node The node to stringify + * @returns The serialized CSS representation of the node + */ + (nodes: Node): string | undefined; + } + + interface WalkCallback { + /** + * @param node The currently visited node + * @param index The index of the node in the series of parsed nodes + * @param nodes The series of parsed nodes + * @returns Returning `false` will prevent traversal of descendant nodes (only applies if `bubble` was set to `true` in the `walk()` call) + */ + (node: Node, index: number, nodes: Node[]): void | boolean; + } + + /** + * A CSS dimension, decomposed into its numeric and unit parts + */ + interface Dimension { + number: string; + unit: string; + } + + /** + * A wrapper around a parsed CSS value that allows for inspecting and walking nodes + */ + interface ParsedValue { + /** + * The series of parsed nodes + */ + nodes: Node[]; + + /** + * Walk all parsed nodes, applying a callback + * + * @param callback A visitor callback that will be executed for each node + * @param bubble When set to `true`, walking will be done inside-out instead of outside-in + */ + walk(callback: WalkCallback, bubble?: boolean): this; + } + + interface ValueParser { + /** + * Decompose a CSS dimension into its numeric and unit part + * + * @param value The dimension to decompose + * @returns An object representing `number` and `unit` part of the dimension or `false` if the decomposing fails + */ + unit(value: string): Dimension | false; + + /** + * Serialize a series of nodes into a CSS value + * + * @param nodes The nodes to stringify + * @param custom A custom stringifier callback + * @returns The generated CSS value + */ + stringify(nodes: Node | Node[], custom?: CustomStringifierCallback): string; + + /** + * Walk a series of nodes, applying a callback + * + * @param nodes The nodes to walk + * @param callback A visitor callback that will be executed for each node + * @param bubble When set to `true`, walking will be done inside-out instead of outside-in + */ + walk(nodes: Node[], callback: WalkCallback, bubble?: boolean): void; + + /** + * Parse a CSS value into a series of nodes to operate on + * + * @param value The value to parse + */ + new (value: string): ParsedValue; + + /** + * Parse a CSS value into a series of nodes to operate on + * + * @param value The value to parse + */ + (value: string): ParsedValue; + } +} + +declare const postcssValueParser: postcssValueParser.ValueParser; + +export = postcssValueParser; diff --git a/lib/parse.js b/lib/parse.js index d9d995f..950631c 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -50,16 +50,21 @@ module.exports = function(input) { after = token; } else if (prev && prev.type === "div") { prev.after = token; + prev.sourceEndIndex += token.length; } else if ( code === comma || code === colon || - (code === slash && value.charCodeAt(next + 1) !== star) + (code === slash && + value.charCodeAt(next + 1) !== star && + (!parent || + (parent && parent.type === "function" && parent.value !== "calc"))) ) { before = token; } else { tokens.push({ type: "space", sourceIndex: pos, + sourceEndIndex: next, value: token }); } @@ -91,22 +96,25 @@ module.exports = function(input) { } } while (escape); token.value = value.slice(pos + 1, next); - + token.sourceEndIndex = token.unclosed ? next : next + 1; tokens.push(token); pos = next + 1; code = value.charCodeAt(pos); // Comments } else if (code === slash && value.charCodeAt(pos + 1) === star) { + next = value.indexOf("*/", pos); + token = { type: "comment", - sourceIndex: pos + sourceIndex: pos, + sourceEndIndex: next + 2 }; - next = value.indexOf("*/", pos); if (next === -1) { token.unclosed = true; next = value.length; + token.sourceEndIndex = next; } token.value = value.slice(pos + 2, next); @@ -126,6 +134,7 @@ module.exports = function(input) { tokens.push({ type: "word", sourceIndex: pos - before.length, + sourceEndIndex: pos + token.length, value: token }); pos += 1; @@ -138,6 +147,7 @@ module.exports = function(input) { tokens.push({ type: "div", sourceIndex: pos - before.length, + sourceEndIndex: pos + token.length, value: token, before: before, after: "" @@ -193,6 +203,7 @@ module.exports = function(input) { { type: "word", sourceIndex: pos, + sourceEndIndex: whitespacePos + 1, value: value.slice(pos, whitespacePos + 1) } ]; @@ -204,21 +215,25 @@ module.exports = function(input) { token.nodes.push({ type: "space", sourceIndex: whitespacePos + 1, + sourceEndIndex: next, value: value.slice(whitespacePos + 1, next) }); } else { token.after = value.slice(whitespacePos + 1, next); + token.sourceEndIndex = next; } } else { token.after = ""; token.nodes = []; } pos = next + 1; + token.sourceEndIndex = token.unclosed ? next : pos; code = value.charCodeAt(pos); tokens.push(token); } else { balanced += 1; token.after = ""; + token.sourceEndIndex = pos + 1; tokens.push(token); stack.push(token); tokens = token.nodes = []; @@ -232,8 +247,10 @@ module.exports = function(input) { code = value.charCodeAt(pos); parent.after = after; + parent.sourceEndIndex += after.length; after = ""; balanced -= 1; + stack[stack.length - 1].sourceEndIndex = pos; stack.pop(); parent = stack[balanced]; tokens = parent.nodes; @@ -279,12 +296,14 @@ module.exports = function(input) { tokens.push({ type: "unicode-range", sourceIndex: pos, + sourceEndIndex: next, value: token }); } else { tokens.push({ type: "word", sourceIndex: pos, + sourceEndIndex: next, value: token }); } @@ -295,6 +314,7 @@ module.exports = function(input) { for (pos = stack.length - 1; pos; pos -= 1) { stack[pos].unclosed = true; + stack[pos].sourceEndIndex = value.length; } return stack[0].nodes; diff --git a/lib/stringify.js b/lib/stringify.js index 5f2c845..6079671 100644 --- a/lib/stringify.js +++ b/lib/stringify.js @@ -16,7 +16,7 @@ function stringifyNode(node, custom) { } else if (type === "div") { return (node.before || "") + value + (node.after || ""); } else if (Array.isArray(node.nodes)) { - buf = stringify(node.nodes); + buf = stringify(node.nodes, custom); if (type !== "function") { return buf; } diff --git a/lib/unit.js b/lib/unit.js index 884d223..c349661 100644 --- a/lib/unit.js +++ b/lib/unit.js @@ -4,46 +4,117 @@ var dot = ".".charCodeAt(0); var exp = "e".charCodeAt(0); var EXP = "E".charCodeAt(0); +// Check if three code points would start a number +// https://www.w3.org/TR/css-syntax-3/#starts-with-a-number +function likeNumber(value) { + var code = value.charCodeAt(0); + var nextCode; + + if (code === plus || code === minus) { + nextCode = value.charCodeAt(1); + + if (nextCode >= 48 && nextCode <= 57) { + return true; + } + + var nextNextCode = value.charCodeAt(2); + + if (nextCode === dot && nextNextCode >= 48 && nextNextCode <= 57) { + return true; + } + + return false; + } + + if (code === dot) { + nextCode = value.charCodeAt(1); + + if (nextCode >= 48 && nextCode <= 57) { + return true; + } + + return false; + } + + if (code >= 48 && code <= 57) { + return true; + } + + return false; +} + +// Consume a number +// https://www.w3.org/TR/css-syntax-3/#consume-number module.exports = function(value) { var pos = 0; var length = value.length; - var dotted = false; - var sciPos = -1; - var containsNumber = false; var code; + var nextCode; + var nextNextCode; + + if (length === 0 || !likeNumber(value)) { + return false; + } + + code = value.charCodeAt(pos); + + if (code === plus || code === minus) { + pos++; + } while (pos < length) { code = value.charCodeAt(pos); - if (code >= 48 && code <= 57) { - containsNumber = true; - } else if (code === exp || code === EXP) { - if (sciPos > -1 || pos === 0) { - break; - } - sciPos = pos; - } else if (code === dot) { - if (dotted) { - break; - } - dotted = true; - } else if (code === plus || code === minus) { - if (pos !== 0) { - break; - } - } else { + if (code < 48 || code > 57) { break; } pos += 1; } - if (sciPos + 1 === pos) pos--; + code = value.charCodeAt(pos); + nextCode = value.charCodeAt(pos + 1); + + if (code === dot && nextCode >= 48 && nextCode <= 57) { + pos += 2; + + while (pos < length) { + code = value.charCodeAt(pos); + + if (code < 48 || code > 57) { + break; + } + + pos += 1; + } + } + + code = value.charCodeAt(pos); + nextCode = value.charCodeAt(pos + 1); + nextNextCode = value.charCodeAt(pos + 2); + + if ( + (code === exp || code === EXP) && + ((nextCode >= 48 && nextCode <= 57) || + ((nextCode === plus || nextCode === minus) && + nextNextCode >= 48 && + nextNextCode <= 57)) + ) { + pos += nextCode === plus || nextCode === minus ? 3 : 2; + + while (pos < length) { + code = value.charCodeAt(pos); - return containsNumber - ? { - number: value.slice(0, pos), - unit: value.slice(pos) + if (code < 48 || code > 57) { + break; } - : false; + + pos += 1; + } + } + + return { + number: value.slice(0, pos), + unit: value.slice(pos) + }; }; diff --git a/package.json b/package.json index 35122da..02d744a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "postcss-value-parser", - "version": "4.0.0", + "version": "4.2.0", "description": "Transforms css values and at-rule params into the tree", "main": "lib/index.js", "files": [ @@ -15,7 +15,7 @@ "tape": "^4.10.2" }, "scripts": { - "lint:prettier": "prettier '**/*.js' --list-different", + "lint:prettier": "prettier \"**/*.js\" \"**/*.ts\" --list-different", "lint:js": "eslint . --cache", "lint": "yarn lint:js && yarn lint:prettier", "pretest": "yarn lint", diff --git a/test/index.js b/test/index.js index 7478f9a..ba356d8 100644 --- a/test/index.js +++ b/test/index.js @@ -38,6 +38,7 @@ test("ValueParser", function(tp) { { type: "function", sourceIndex: 0, + sourceEndIndex: 5, value: "fn", before: " ", after: "", @@ -46,6 +47,7 @@ test("ValueParser", function(tp) { { type: "function", sourceIndex: 6, + sourceEndIndex: 17, value: "fn2", before: " ", after: "", @@ -53,6 +55,7 @@ test("ValueParser", function(tp) { { type: "function", sourceIndex: 11, + sourceEndIndex: 16, value: "fn3", before: "", after: "", @@ -63,6 +66,7 @@ test("ValueParser", function(tp) { { type: "function", sourceIndex: 11, + sourceEndIndex: 16, value: "fn3", before: "", after: "", @@ -90,6 +94,7 @@ test("ValueParser", function(tp) { { type: "function", sourceIndex: 0, + sourceEndIndex: 5, value: "fn", before: " ", after: "", @@ -98,6 +103,7 @@ test("ValueParser", function(tp) { { type: "function", sourceIndex: 6, + sourceEndIndex: 17, value: "fn2", before: " ", after: "", @@ -105,6 +111,7 @@ test("ValueParser", function(tp) { { type: "function", sourceIndex: 11, + sourceEndIndex: 16, value: "fn3", before: "", after: "", @@ -131,15 +138,17 @@ test("ValueParser", function(tp) { { type: "function", sourceIndex: 0, + sourceEndIndex: 5, value: "fn", before: " ", after: "", nodes: [] }, - { type: "space", sourceIndex: 5, value: " " }, + { type: "space", sourceIndex: 5, sourceEndIndex: 6, value: " " }, { type: "word", sourceIndex: 6, + sourceEndIndex: 17, value: "fn2", before: " ", after: "", @@ -147,6 +156,7 @@ test("ValueParser", function(tp) { { type: "function", sourceIndex: 11, + sourceEndIndex: 16, value: "fn3", before: "", after: "", @@ -172,6 +182,7 @@ test("ValueParser", function(tp) { { type: "function", sourceIndex: 5, + sourceEndIndex: 10, value: "fn3", before: "", after: "", @@ -180,6 +191,7 @@ test("ValueParser", function(tp) { { type: "function", sourceIndex: 0, + sourceEndIndex: 11, value: "fn2", before: " ", after: "", @@ -187,6 +199,7 @@ test("ValueParser", function(tp) { { type: "function", sourceIndex: 5, + sourceEndIndex: 10, value: "fn3", before: "", after: "", diff --git a/test/parse.js b/test/parse.js index 5cd41e0..384e783 100644 --- a/test/parse.js +++ b/test/parse.js @@ -10,25 +10,31 @@ var tests = [ { message: "should process escaped parentheses (open)", fixture: "\\(", - expected: [{ type: "word", sourceIndex: 0, value: "\\(" }] + expected: [ + { type: "word", sourceIndex: 0, sourceEndIndex: 2, value: "\\(" } + ] }, { message: "should process escaped parentheses (close)", fixture: "\\)", - expected: [{ type: "word", sourceIndex: 0, value: "\\)" }] + expected: [ + { type: "word", sourceIndex: 0, sourceEndIndex: 2, value: "\\)" } + ] }, { message: "should process escaped parentheses (both)", fixture: "\\(\\)", - expected: [{ type: "word", sourceIndex: 0, value: "\\(\\)" }] + expected: [ + { type: "word", sourceIndex: 0, sourceEndIndex: 4, value: "\\(\\)" } + ] }, { message: "should process escaped parentheses (both)", fixture: "\\( \\)", expected: [ - { type: "word", sourceIndex: 0, value: "\\(" }, - { type: "space", sourceIndex: 2, value: " " }, - { type: "word", sourceIndex: 3, value: "\\)" } + { type: "word", sourceIndex: 0, sourceEndIndex: 2, value: "\\(" }, + { type: "space", sourceIndex: 2, sourceEndIndex: 3, value: " " }, + { type: "word", sourceIndex: 3, sourceEndIndex: 5, value: "\\)" } ] }, { @@ -38,13 +44,14 @@ var tests = [ { type: "function", sourceIndex: 0, + sourceEndIndex: 2, value: "", before: "", after: "", nodes: [] }, - { type: "space", sourceIndex: 2, value: " " }, - { type: "word", sourceIndex: 3, value: ")wo)rd)" } + { type: "space", sourceIndex: 2, sourceEndIndex: 3, value: " " }, + { type: "word", sourceIndex: 3, sourceEndIndex: 10, value: ")wo)rd)" } ] }, { @@ -54,6 +61,7 @@ var tests = [ { type: "function", sourceIndex: 0, + sourceEndIndex: 3, value: "", before: " ", after: "", @@ -68,10 +76,11 @@ var tests = [ { type: "function", sourceIndex: 0, + sourceEndIndex: 5, value: "", before: " ", after: " ", - nodes: [{ type: "word", sourceIndex: 2, value: "|" }] + nodes: [{ type: "word", sourceIndex: 2, sourceEndIndex: 3, value: "|" }] } ] }, @@ -82,6 +91,7 @@ var tests = [ { type: "function", sourceIndex: 0, + sourceEndIndex: 6, value: "name", before: "", after: "", @@ -96,6 +106,7 @@ var tests = [ { type: "function", sourceIndex: 0, + sourceEndIndex: 6, value: "", before: "", after: "", @@ -103,6 +114,7 @@ var tests = [ { type: "function", sourceIndex: 1, + sourceEndIndex: 5, value: "", before: "", after: "", @@ -110,6 +122,7 @@ var tests = [ { type: "function", sourceIndex: 2, + sourceEndIndex: 4, value: "", before: "", after: "", @@ -128,6 +141,7 @@ var tests = [ { type: "function", sourceIndex: 0, + sourceEndIndex: 13, value: "", before: " ", after: "", @@ -135,6 +149,7 @@ var tests = [ { type: "function", sourceIndex: 2, + sourceEndIndex: 12, value: "calc", before: "", after: " ", @@ -142,6 +157,7 @@ var tests = [ { type: "function", sourceIndex: 7, + sourceEndIndex: 10, value: "", before: " ", after: "", @@ -151,35 +167,63 @@ var tests = [ } ] }, - { type: "word", sourceIndex: 13, value: "word" } + { type: "word", sourceIndex: 13, sourceEndIndex: 17, value: "word" } ] }, { message: "should process divider (/)", fixture: "/", expected: [ - { type: "div", sourceIndex: 0, value: "/", before: "", after: "" } + { + type: "div", + sourceIndex: 0, + sourceEndIndex: 1, + value: "/", + before: "", + after: "" + } ] }, { message: "should process divider (:)", fixture: ":", expected: [ - { type: "div", sourceIndex: 0, value: ":", before: "", after: "" } + { + type: "div", + sourceIndex: 0, + sourceEndIndex: 1, + value: ":", + before: "", + after: "" + } ] }, { message: "should process divider (,)", fixture: ",", expected: [ - { type: "div", sourceIndex: 0, value: ",", before: "", after: "" } + { + type: "div", + sourceIndex: 0, + sourceEndIndex: 1, + value: ",", + before: "", + after: "" + } ] }, { message: "should process complex divider", fixture: " , ", expected: [ - { type: "div", sourceIndex: 0, value: ",", before: " ", after: " " } + { + type: "div", + sourceIndex: 0, + sourceEndIndex: 3, + value: ",", + before: " ", + after: " " + } ] }, { @@ -189,6 +233,7 @@ var tests = [ { type: "function", sourceIndex: 0, + sourceEndIndex: 5, value: "", before: " ", after: " ", @@ -196,6 +241,7 @@ var tests = [ { type: "div", sourceIndex: 2, + sourceEndIndex: 3, value: ",", before: "", after: "" @@ -211,49 +257,97 @@ var tests = [ { type: "div", sourceIndex: 0, + sourceEndIndex: 3, value: ",", before: " ", after: " " }, - { type: "div", sourceIndex: 3, value: ":", before: "", after: " " } + { + type: "div", + sourceIndex: 3, + sourceEndIndex: 5, + value: ":", + before: "", + after: " " + } ] }, { message: 'should process empty quoted strings (")', fixture: '""', - expected: [{ type: "string", sourceIndex: 0, value: "", quote: '"' }] + expected: [ + { + type: "string", + sourceIndex: 0, + sourceEndIndex: 2, + value: "", + quote: '"' + } + ] }, { message: "should process empty quoted strings (')", fixture: "''", - expected: [{ type: "string", sourceIndex: 0, value: "", quote: "'" }] + expected: [ + { + type: "string", + sourceIndex: 0, + sourceEndIndex: 2, + value: "", + quote: "'" + } + ] }, { message: "should process escaped quotes (')", fixture: "'word\\'word'", expected: [ - { type: "string", sourceIndex: 0, value: "word\\'word", quote: "'" } + { + type: "string", + sourceIndex: 0, + sourceEndIndex: 12, + value: "word\\'word", + quote: "'" + } ] }, { message: "should process escaped quotes (')", fixture: '"word\\"word"', expected: [ - { type: "string", sourceIndex: 0, value: 'word\\"word', quote: '"' } + { + type: "string", + sourceIndex: 0, + sourceEndIndex: 12, + value: 'word\\"word', + quote: '"' + } ] }, { message: "should process single quotes inside double quotes (')", fixture: '"word\'word"', expected: [ - { type: "string", sourceIndex: 0, value: "word'word", quote: '"' } + { + type: "string", + sourceIndex: 0, + sourceEndIndex: 11, + value: "word'word", + quote: '"' + } ] }, { message: "should process double quotes inside single quotes (')", fixture: "'word\"word'", expected: [ - { type: "string", sourceIndex: 0, value: 'word"word', quote: "'" } + { + type: "string", + sourceIndex: 0, + sourceEndIndex: 11, + value: 'word"word', + quote: "'" + } ] }, { @@ -263,6 +357,7 @@ var tests = [ { type: "string", sourceIndex: 0, + sourceEndIndex: 5, value: "word", quote: '"', unclosed: true @@ -276,6 +371,7 @@ var tests = [ { type: "string", sourceIndex: 0, + sourceEndIndex: 6, value: "word\\", quote: '"', unclosed: true @@ -285,33 +381,58 @@ var tests = [ { message: "should process quoted strings", fixture: '"string"', - expected: [{ type: "string", sourceIndex: 0, value: "string", quote: '"' }] + expected: [ + { + type: "string", + sourceIndex: 0, + sourceEndIndex: 8, + value: "string", + quote: '"' + } + ] }, { message: "should process quoted strings and words", fixture: 'word1"string"word2', expected: [ - { type: "word", sourceIndex: 0, value: "word1" }, - { type: "string", sourceIndex: 5, value: "string", quote: '"' }, - { type: "word", sourceIndex: 13, value: "word2" } + { type: "word", sourceIndex: 0, sourceEndIndex: 5, value: "word1" }, + { + type: "string", + sourceIndex: 5, + sourceEndIndex: 13, + value: "string", + quote: '"' + }, + { type: "word", sourceIndex: 13, sourceEndIndex: 18, value: "word2" } ] }, { message: "should process quoted strings and spaces", fixture: ' "string" ', expected: [ - { type: "space", sourceIndex: 0, value: " " }, - { type: "string", sourceIndex: 1, value: "string", quote: '"' }, - { type: "space", sourceIndex: 9, value: " " } + { type: "space", sourceIndex: 0, sourceEndIndex: 1, value: " " }, + { + type: "string", + sourceIndex: 1, + sourceEndIndex: 9, + value: "string", + quote: '"' + }, + { type: "space", sourceIndex: 9, sourceEndIndex: 10, value: " " } ] }, { message: "should process escaped symbols as words", fixture: " \\\"word\\'\\ \\\t ", expected: [ - { type: "space", sourceIndex: 0, value: " " }, - { type: "word", sourceIndex: 1, value: "\\\"word\\'\\ \\\t" }, - { type: "space", sourceIndex: 13, value: " " } + { type: "space", sourceIndex: 0, sourceEndIndex: 1, value: " " }, + { + type: "word", + sourceIndex: 1, + sourceEndIndex: 13, + value: "\\\"word\\'\\ \\\t" + }, + { type: "space", sourceIndex: 13, sourceEndIndex: 14, value: " " } ] }, { @@ -319,32 +440,41 @@ var tests = [ fixture: "bold italic 12px \t /3 'Open Sans', Arial, \"Helvetica Neue\", sans-serif", expected: [ - { type: "word", sourceIndex: 0, value: "bold" }, - { type: "space", sourceIndex: 4, value: " " }, - { type: "word", sourceIndex: 5, value: "italic" }, - { type: "space", sourceIndex: 11, value: " " }, - { type: "word", sourceIndex: 12, value: "12px" }, + { type: "word", sourceIndex: 0, sourceEndIndex: 4, value: "bold" }, + { type: "space", sourceIndex: 4, sourceEndIndex: 5, value: " " }, + { type: "word", sourceIndex: 5, sourceEndIndex: 11, value: "italic" }, + { type: "space", sourceIndex: 11, sourceEndIndex: 12, value: " " }, + { type: "word", sourceIndex: 12, sourceEndIndex: 16, value: "12px" }, { type: "div", sourceIndex: 16, + sourceEndIndex: 20, value: "/", before: " \t ", after: "" }, - { type: "word", sourceIndex: 20, value: "3" }, - { type: "space", sourceIndex: 21, value: " " }, - { type: "string", sourceIndex: 22, value: "Open Sans", quote: "'" }, + { type: "word", sourceIndex: 20, sourceEndIndex: 21, value: "3" }, + { type: "space", sourceIndex: 21, sourceEndIndex: 22, value: " " }, + { + type: "string", + sourceIndex: 22, + sourceEndIndex: 33, + value: "Open Sans", + quote: "'" + }, { type: "div", sourceIndex: 33, + sourceEndIndex: 35, value: ",", before: "", after: " " }, - { type: "word", sourceIndex: 35, value: "Arial" }, + { type: "word", sourceIndex: 35, sourceEndIndex: 40, value: "Arial" }, { type: "div", sourceIndex: 40, + sourceEndIndex: 42, value: ",", before: "", after: " " @@ -352,17 +482,19 @@ var tests = [ { type: "string", sourceIndex: 42, + sourceEndIndex: 58, value: "Helvetica Neue", quote: '"' }, { type: "div", sourceIndex: 58, + sourceEndIndex: 60, value: ",", before: "", after: " " }, - { type: "word", sourceIndex: 60, value: "sans-serif" } + { type: "word", sourceIndex: 60, sourceEndIndex: 70, value: "sans-serif" } ] }, { @@ -372,27 +504,30 @@ var tests = [ { type: "function", sourceIndex: 0, + sourceEndIndex: 20, value: "rgba", before: " ", after: " ", nodes: [ - { type: "word", sourceIndex: 6, value: "29" }, + { type: "word", sourceIndex: 6, sourceEndIndex: 8, value: "29" }, { type: "div", sourceIndex: 8, + sourceEndIndex: 10, value: ",", before: "", after: " " }, - { type: "word", sourceIndex: 10, value: "439" }, + { type: "word", sourceIndex: 10, sourceEndIndex: 13, value: "439" }, { type: "div", sourceIndex: 13, + sourceEndIndex: 16, value: ",", before: " ", after: " " }, - { type: "word", sourceIndex: 16, value: "29" } + { type: "word", sourceIndex: 16, sourceEndIndex: 18, value: "29" } ] } ] @@ -404,10 +539,18 @@ var tests = [ { type: "function", sourceIndex: 0, + sourceEndIndex: 22, value: "url", before: " ", after: " ", - nodes: [{ type: "word", sourceIndex: 5, value: "/gfx/img/bg.jpg" }] + nodes: [ + { + type: "word", + sourceIndex: 5, + sourceEndIndex: 20, + value: "/gfx/img/bg.jpg" + } + ] } ] }, @@ -418,13 +561,19 @@ var tests = [ { type: "function", sourceIndex: 0, + sourceEndIndex: 21, value: "url", before: " ", after: "", unclosed: true, nodes: [ - { type: "word", sourceIndex: 5, value: "/gfx/img/bg.jpg" }, - { type: "space", sourceIndex: 20, value: " " } + { + type: "word", + sourceIndex: 5, + sourceEndIndex: 20, + value: "/gfx/img/bg.jpg" + }, + { type: "space", sourceIndex: 20, sourceEndIndex: 21, value: " " } ] } ] @@ -436,6 +585,7 @@ var tests = [ { type: "function", sourceIndex: 0, + sourceEndIndex: 30, value: "url", before: " ", after: " ", @@ -443,11 +593,12 @@ var tests = [ { type: "string", sourceIndex: 5, + sourceEndIndex: 22, quote: '"', value: "/gfx/img/bg.jpg" }, - { type: "space", sourceIndex: 22, value: " " }, - { type: "word", sourceIndex: 23, value: "hello" } + { type: "space", sourceIndex: 22, sourceEndIndex: 23, value: " " }, + { type: "word", sourceIndex: 23, sourceEndIndex: 28, value: "hello" } ] } ] @@ -459,6 +610,7 @@ var tests = [ { type: "function", sourceIndex: 0, + sourceEndIndex: 11, value: "calc", before: "", after: "", @@ -466,26 +618,77 @@ var tests = [ { type: "word", sourceIndex: 5, + sourceEndIndex: 6, value: "1" }, { type: "space", sourceIndex: 6, + sourceEndIndex: 7, value: " " }, { type: "word", sourceIndex: 7, + sourceEndIndex: 8, value: "+" }, { type: "space", sourceIndex: 8, + sourceEndIndex: 9, value: " " }, { type: "word", sourceIndex: 9, + sourceEndIndex: 10, + value: "2" + } + ] + } + ] + }, + { + message: "should correctly parse subtraction with spaces", + fixture: "calc(1 - 2)", + expected: [ + { + type: "function", + sourceIndex: 0, + sourceEndIndex: 11, + value: "calc", + before: "", + after: "", + nodes: [ + { + type: "word", + sourceIndex: 5, + sourceEndIndex: 6, + value: "1" + }, + { + type: "space", + sourceIndex: 6, + sourceEndIndex: 7, + value: " " + }, + { + type: "word", + sourceIndex: 7, + sourceEndIndex: 8, + value: "-" + }, + { + type: "space", + sourceIndex: 8, + sourceEndIndex: 9, + value: " " + }, + { + type: "word", + sourceIndex: 9, + sourceEndIndex: 10, value: "2" } ] @@ -499,6 +702,7 @@ var tests = [ { type: "function", sourceIndex: 0, + sourceEndIndex: 11, value: "calc", before: "", after: "", @@ -506,26 +710,77 @@ var tests = [ { type: "word", sourceIndex: 5, + sourceEndIndex: 6, value: "1" }, { type: "space", sourceIndex: 6, + sourceEndIndex: 7, value: " " }, { type: "word", sourceIndex: 7, + sourceEndIndex: 8, value: "*" }, { type: "space", sourceIndex: 8, + sourceEndIndex: 9, + value: " " + }, + { + type: "word", + sourceIndex: 9, + sourceEndIndex: 10, + value: "2" + } + ] + } + ] + }, + { + message: "should correctly parse division with spaces", + fixture: "calc(1 / 2)", + expected: [ + { + type: "function", + sourceIndex: 0, + sourceEndIndex: 11, + value: "calc", + before: "", + after: "", + nodes: [ + { + type: "word", + sourceIndex: 5, + sourceEndIndex: 6, + value: "1" + }, + { + type: "space", + sourceIndex: 6, + sourceEndIndex: 7, + value: " " + }, + { + type: "word", + sourceIndex: 7, + sourceEndIndex: 8, + value: "/" + }, + { + type: "space", + sourceIndex: 8, + sourceEndIndex: 9, value: " " }, { type: "word", sourceIndex: 9, + sourceEndIndex: 10, value: "2" } ] @@ -539,6 +794,7 @@ var tests = [ { type: "function", sourceIndex: 0, + sourceEndIndex: 9, value: "calc", before: "", after: "", @@ -546,16 +802,53 @@ var tests = [ { type: "word", sourceIndex: 5, + sourceEndIndex: 6, value: "1" }, { type: "word", sourceIndex: 6, + sourceEndIndex: 7, value: "*" }, { type: "word", sourceIndex: 7, + sourceEndIndex: 8, + value: "2" + } + ] + } + ] + }, + { + message: "should correctly parse division without spaces", + fixture: "calc(1/2)", + expected: [ + { + type: "function", + sourceIndex: 0, + sourceEndIndex: 9, + value: "calc", + before: "", + after: "", + nodes: [ + { + type: "word", + sourceIndex: 5, + sourceEndIndex: 6, + value: "1" + }, + { + type: "word", + sourceIndex: 6, + sourceEndIndex: 7, + value: "/" + }, + { + type: "word", + sourceIndex: 7, + sourceEndIndex: 8, value: "2" } ] @@ -569,6 +862,7 @@ var tests = [ { type: "function", sourceIndex: 0, + sourceEndIndex: 34, value: "calc", before: "", after: "", @@ -576,6 +870,7 @@ var tests = [ { type: "function", sourceIndex: 5, + sourceEndIndex: 26, value: "", before: "", after: "", @@ -583,6 +878,7 @@ var tests = [ { type: "function", sourceIndex: 6, + sourceEndIndex: 21, value: "", before: "", after: "", @@ -590,26 +886,31 @@ var tests = [ { type: "word", sourceIndex: 7, + sourceEndIndex: 12, value: "768px" }, { type: "space", sourceIndex: 12, + sourceEndIndex: 13, value: " " }, { type: "word", sourceIndex: 13, + sourceEndIndex: 14, value: "-" }, { type: "space", sourceIndex: 14, + sourceEndIndex: 15, value: " " }, { type: "word", sourceIndex: 15, + sourceEndIndex: 20, value: "100vw" } ] @@ -617,17 +918,18 @@ var tests = [ { type: "div", sourceIndex: 21, + sourceEndIndex: 24, value: "/", before: " ", after: " " }, - { type: "word", sourceIndex: 24, value: "2" } + { type: "word", sourceIndex: 24, sourceEndIndex: 25, value: "2" } ] }, - { type: "space", sourceIndex: 26, value: " " }, - { type: "word", sourceIndex: 27, value: "-" }, - { type: "space", sourceIndex: 28, value: " " }, - { type: "word", sourceIndex: 29, value: "15px" } + { type: "space", sourceIndex: 26, sourceEndIndex: 27, value: " " }, + { type: "word", sourceIndex: 27, sourceEndIndex: 28, value: "-" }, + { type: "space", sourceIndex: 28, sourceEndIndex: 29, value: " " }, + { type: "word", sourceIndex: 29, sourceEndIndex: 33, value: "15px" } ] } ] @@ -639,40 +941,59 @@ var tests = [ { type: "function", sourceIndex: 0, + sourceEndIndex: 18, value: "", before: "", after: "", nodes: [ - { type: "word", sourceIndex: 1, value: "min-width" }, + { + type: "word", + sourceIndex: 1, + sourceEndIndex: 10, + value: "min-width" + }, { type: "div", sourceIndex: 10, + sourceEndIndex: 12, value: ":", before: "", after: " " }, - { type: "word", sourceIndex: 12, value: "700px" } + { type: "word", sourceIndex: 12, sourceEndIndex: 17, value: "700px" } ] }, - { type: "space", sourceIndex: 18, value: " " }, - { type: "word", sourceIndex: 19, value: "and" }, - { type: "space", sourceIndex: 22, value: " " }, + { type: "space", sourceIndex: 18, sourceEndIndex: 19, value: " " }, + { type: "word", sourceIndex: 19, sourceEndIndex: 22, value: "and" }, + { type: "space", sourceIndex: 22, sourceEndIndex: 23, value: " " }, { type: "function", sourceIndex: 23, + sourceEndIndex: 49, value: "", before: "", after: "", nodes: [ - { type: "word", sourceIndex: 24, value: "orientation" }, + { + type: "word", + sourceIndex: 24, + sourceEndIndex: 35, + value: "orientation" + }, { type: "div", sourceIndex: 35, + sourceEndIndex: 37, value: ":", before: "", after: " " }, - { type: "word", sourceIndex: 37, value: "\\$landscape" } + { + type: "word", + sourceIndex: 37, + sourceEndIndex: 48, + value: "\\$landscape" + } ] } ] @@ -684,6 +1005,7 @@ var tests = [ { type: "function", sourceIndex: 0, + sourceEndIndex: 39, value: "url", before: " ", after: " ", @@ -691,6 +1013,7 @@ var tests = [ { type: "word", sourceIndex: 5, + sourceEndIndex: 37, value: "http://website.com/assets\\)_test" } ] @@ -704,6 +1027,7 @@ var tests = [ { type: "function", sourceIndex: 0, + sourceEndIndex: 22, value: "fn1", before: "", after: "", @@ -711,14 +1035,18 @@ var tests = [ { type: "function", sourceIndex: 4, + sourceEndIndex: 12, value: "fn2", before: "", after: "", - nodes: [{ type: "word", sourceIndex: 8, value: "255" }] + nodes: [ + { type: "word", sourceIndex: 8, sourceEndIndex: 11, value: "255" } + ] }, { type: "div", sourceIndex: 12, + sourceEndIndex: 14, value: ",", before: "", after: " " @@ -726,16 +1054,20 @@ var tests = [ { type: "function", sourceIndex: 14, + sourceEndIndex: 21, value: "fn3", before: "", after: "", - nodes: [{ type: "word", sourceIndex: 18, value: ".2" }] + nodes: [ + { type: "word", sourceIndex: 18, sourceEndIndex: 20, value: ".2" } + ] } ] }, { type: "div", sourceIndex: 22, + sourceEndIndex: 24, value: ",", before: "", after: " " @@ -743,6 +1075,7 @@ var tests = [ { type: "function", sourceIndex: 24, + sourceEndIndex: 45, value: "fn4", before: "", after: "", @@ -750,29 +1083,37 @@ var tests = [ { type: "function", sourceIndex: 28, + sourceEndIndex: 39, value: "fn5", before: "", after: "", nodes: [ - { type: "word", sourceIndex: 32, value: "255" }, + { + type: "word", + sourceIndex: 32, + sourceEndIndex: 35, + value: "255" + }, { type: "div", sourceIndex: 35, + sourceEndIndex: 36, value: ",", before: "", after: "" }, - { type: "word", sourceIndex: 36, value: ".2" } + { type: "word", sourceIndex: 36, sourceEndIndex: 38, value: ".2" } ] }, { type: "div", sourceIndex: 39, + sourceEndIndex: 41, value: ",", before: "", after: " " }, - { type: "word", sourceIndex: 41, value: "fn6" } + { type: "word", sourceIndex: 41, sourceEndIndex: 44, value: "fn6" } ] } ] @@ -784,17 +1125,18 @@ var tests = [ { type: "function", sourceIndex: 0, + sourceEndIndex: 11, value: "", before: "", after: "", unclosed: true, nodes: [ - { type: "word", sourceIndex: 1, value: "0" }, - { type: "space", sourceIndex: 2, value: " " }, - { type: "word", sourceIndex: 3, value: "32" }, - { type: "space", sourceIndex: 5, value: " " }, - { type: "word", sourceIndex: 6, value: "word" }, - { type: "space", sourceIndex: 10, value: " " } + { type: "word", sourceIndex: 1, sourceEndIndex: 2, value: "0" }, + { type: "space", sourceIndex: 2, sourceEndIndex: 3, value: " " }, + { type: "word", sourceIndex: 3, sourceEndIndex: 5, value: "32" }, + { type: "space", sourceIndex: 5, sourceEndIndex: 6, value: " " }, + { type: "word", sourceIndex: 6, sourceEndIndex: 10, value: "word" }, + { type: "space", sourceIndex: 10, sourceEndIndex: 11, value: " " } ] } ] @@ -806,6 +1148,7 @@ var tests = [ { type: "function", sourceIndex: 0, + sourceEndIndex: 8, value: "", before: " ", after: "", @@ -814,6 +1157,7 @@ var tests = [ { type: "function", sourceIndex: 2, + sourceEndIndex: 8, value: "", before: " ", after: "", @@ -822,12 +1166,13 @@ var tests = [ { type: "function", sourceIndex: 4, + sourceEndIndex: 7, value: "", before: " ", after: "", nodes: [] }, - { type: "space", sourceIndex: 7, value: " " } + { type: "space", sourceIndex: 7, sourceEndIndex: 8, value: " " } ] } ] @@ -838,20 +1183,22 @@ var tests = [ message: "shouldn't throw an error on unopened function", fixture: "0 32 word ) ", expected: [ - { type: "word", sourceIndex: 0, value: "0" }, - { type: "space", sourceIndex: 1, value: " " }, - { type: "word", sourceIndex: 2, value: "32" }, - { type: "space", sourceIndex: 4, value: " " }, - { type: "word", sourceIndex: 5, value: "word" }, - { type: "space", sourceIndex: 9, value: " " }, - { type: "word", sourceIndex: 10, value: ")" }, - { type: "space", sourceIndex: 11, value: " " } + { type: "word", sourceIndex: 0, sourceEndIndex: 1, value: "0" }, + { type: "space", sourceIndex: 1, sourceEndIndex: 2, value: " " }, + { type: "word", sourceIndex: 2, sourceEndIndex: 4, value: "32" }, + { type: "space", sourceIndex: 4, sourceEndIndex: 5, value: " " }, + { type: "word", sourceIndex: 5, sourceEndIndex: 9, value: "word" }, + { type: "space", sourceIndex: 9, sourceEndIndex: 10, value: " " }, + { type: "word", sourceIndex: 10, sourceEndIndex: 11, value: ")" }, + { type: "space", sourceIndex: 11, sourceEndIndex: 12, value: " " } ] }, { message: "should process escaped spaces as word in fonts", fixture: "Bond\\ 007", - expected: [{ type: "word", sourceIndex: 0, value: "Bond\\ 007" }] + expected: [ + { type: "word", sourceIndex: 0, sourceEndIndex: 9, value: "Bond\\ 007" } + ] }, { message: "should parse double url and comma", @@ -860,14 +1207,23 @@ var tests = [ { type: "function", sourceIndex: 0, + sourceEndIndex: 16, value: "url", before: "", after: "", - nodes: [{ type: "word", sourceIndex: 4, value: "foo/bar.jpg" }] + nodes: [ + { + type: "word", + sourceIndex: 4, + sourceEndIndex: 15, + value: "foo/bar.jpg" + } + ] }, { type: "div", sourceIndex: 16, + sourceEndIndex: 18, value: ",", before: "", after: " " @@ -875,6 +1231,7 @@ var tests = [ { type: "function", sourceIndex: 18, + sourceEndIndex: 49, value: "url", before: "", after: "", @@ -882,6 +1239,7 @@ var tests = [ { type: "word", sourceIndex: 22, + sourceEndIndex: 48, value: "http://website.com/img.jpg" } ] @@ -895,6 +1253,7 @@ var tests = [ { type: "function", sourceIndex: 0, + sourceEndIndex: 5, value: "url", before: "", after: "", @@ -909,6 +1268,7 @@ var tests = [ { type: "function", sourceIndex: 0, + sourceEndIndex: 6, value: "url", before: " ", after: "", @@ -923,6 +1283,7 @@ var tests = [ { type: "function", sourceIndex: 0, + sourceEndIndex: 8, value: "url", before: " ", after: "", @@ -937,6 +1298,7 @@ var tests = [ { type: "function", sourceIndex: 0, + sourceEndIndex: 6, value: "url", before: "\n", after: "", @@ -951,6 +1313,7 @@ var tests = [ { type: "function", sourceIndex: 0, + sourceEndIndex: 7, value: "url", before: "\r\n", after: "", @@ -965,6 +1328,7 @@ var tests = [ { type: "function", sourceIndex: 0, + sourceEndIndex: 8, value: "url", before: "\n\n\n", after: "", @@ -979,6 +1343,7 @@ var tests = [ { type: "function", sourceIndex: 0, + sourceEndIndex: 11, value: "url", before: "\r\n\r\n\r\n", after: "", @@ -993,6 +1358,7 @@ var tests = [ { type: "function", sourceIndex: 0, + sourceEndIndex: 16, value: "url", before: " \n \t \r\n ", after: "", @@ -1004,15 +1370,20 @@ var tests = [ message: "should parse comments", fixture: "/*before*/ 1px /*between*/ 1px /*after*/", expected: [ - { type: "comment", sourceIndex: 0, value: "before" }, - { type: "space", sourceIndex: 10, value: " " }, - { type: "word", sourceIndex: 11, value: "1px" }, - { type: "space", sourceIndex: 14, value: " " }, - { type: "comment", sourceIndex: 15, value: "between" }, - { type: "space", sourceIndex: 26, value: " " }, - { type: "word", sourceIndex: 27, value: "1px" }, - { type: "space", sourceIndex: 30, value: " " }, - { type: "comment", sourceIndex: 31, value: "after" } + { type: "comment", sourceIndex: 0, sourceEndIndex: 10, value: "before" }, + { type: "space", sourceIndex: 10, sourceEndIndex: 11, value: " " }, + { type: "word", sourceIndex: 11, sourceEndIndex: 14, value: "1px" }, + { type: "space", sourceIndex: 14, sourceEndIndex: 15, value: " " }, + { + type: "comment", + sourceIndex: 15, + sourceEndIndex: 26, + value: "between" + }, + { type: "space", sourceIndex: 26, sourceEndIndex: 27, value: " " }, + { type: "word", sourceIndex: 27, sourceEndIndex: 30, value: "1px" }, + { type: "space", sourceIndex: 30, sourceEndIndex: 31, value: " " }, + { type: "comment", sourceIndex: 31, sourceEndIndex: 40, value: "after" } ] }, { @@ -1022,36 +1393,40 @@ var tests = [ { type: "function", sourceIndex: 0, + sourceEndIndex: 26, value: "rgba", before: " ", after: " ", nodes: [ - { type: "word", sourceIndex: 6, value: "0" }, + { type: "word", sourceIndex: 6, sourceEndIndex: 7, value: "0" }, { type: "div", sourceIndex: 7, + sourceEndIndex: 9, value: ",", before: "", after: " " }, - { type: "word", sourceIndex: 9, value: "55" }, + { type: "word", sourceIndex: 9, sourceEndIndex: 11, value: "55" }, { type: "div", sourceIndex: 11, + sourceEndIndex: 12, value: "/", before: "", after: "" }, - { type: "word", sourceIndex: 12, value: "55" }, + { type: "word", sourceIndex: 12, sourceEndIndex: 14, value: "55" }, { type: "div", sourceIndex: 14, + sourceEndIndex: 16, value: ",", before: "", after: " " }, - { type: "word", sourceIndex: 16, value: "0" }, - { type: "comment", sourceIndex: 17, value: ",.5" } + { type: "word", sourceIndex: 16, sourceEndIndex: 17, value: "0" }, + { type: "comment", sourceIndex: 17, sourceEndIndex: 24, value: ",.5" } ] } ] @@ -1064,6 +1439,7 @@ var tests = [ { type: "function", sourceIndex: 0, + sourceEndIndex: 33, value: "url", before: " ", after: " ", @@ -1071,11 +1447,17 @@ var tests = [ { type: "string", sourceIndex: 5, + sourceEndIndex: 19, value: "/demo/bg.png", quote: '"' }, - { type: "space", sourceIndex: 19, value: " " }, - { type: "comment", sourceIndex: 20, value: "comment" } + { type: "space", sourceIndex: 19, sourceEndIndex: 20, value: " " }, + { + type: "comment", + sourceIndex: 20, + sourceEndIndex: 31, + value: "comment" + } ] } ] @@ -1088,6 +1470,7 @@ var tests = [ { type: "function", sourceIndex: 0, + sourceEndIndex: 31, value: "url", before: " ", after: " ", @@ -1095,6 +1478,7 @@ var tests = [ { type: "word", sourceIndex: 5, + sourceEndIndex: 29, value: "/*comment*/ /demo/bg.png" } ] @@ -1109,6 +1493,7 @@ var tests = [ { type: "function", sourceIndex: 0, + sourceEndIndex: 31, value: "url", before: " ", after: " ", @@ -1116,6 +1501,7 @@ var tests = [ { type: "word", sourceIndex: 5, + sourceEndIndex: 29, value: "/demo/bg.png /*comment*/" } ] @@ -1126,13 +1512,14 @@ var tests = [ message: "should parse unclosed comments", fixture: "/*comment*/ 1px /* unclosed ", expected: [ - { type: "comment", sourceIndex: 0, value: "comment" }, - { type: "space", sourceIndex: 11, value: " " }, - { type: "word", sourceIndex: 12, value: "1px" }, - { type: "space", sourceIndex: 15, value: " " }, + { type: "comment", sourceIndex: 0, sourceEndIndex: 11, value: "comment" }, + { type: "space", sourceIndex: 11, sourceEndIndex: 12, value: " " }, + { type: "word", sourceIndex: 12, sourceEndIndex: 15, value: "1px" }, + { type: "space", sourceIndex: 15, sourceEndIndex: 16, value: " " }, { type: "comment", sourceIndex: 16, + sourceEndIndex: 28, value: " unclosed ", unclosed: true } @@ -1142,66 +1529,129 @@ var tests = [ message: "should respect escape character", fixture: "Hawaii \\35 -0", expected: [ - { type: "word", sourceIndex: 0, value: "Hawaii" }, - { type: "space", sourceIndex: 6, value: " " }, - { type: "word", sourceIndex: 7, value: "\\35" }, - { type: "space", sourceIndex: 10, value: " " }, - { type: "word", sourceIndex: 11, value: "-0" } + { type: "word", sourceIndex: 0, sourceEndIndex: 6, value: "Hawaii" }, + { type: "space", sourceIndex: 6, sourceEndIndex: 7, value: " " }, + { type: "word", sourceIndex: 7, sourceEndIndex: 10, value: "\\35" }, + { type: "space", sourceIndex: 10, sourceEndIndex: 11, value: " " }, + { type: "word", sourceIndex: 11, sourceEndIndex: 13, value: "-0" } ] }, { message: "should parse unicode-range (single codepoint)", fixture: "U+26", - expected: [{ type: "unicode-range", sourceIndex: 0, value: "U+26" }] + expected: [ + { + type: "unicode-range", + sourceIndex: 0, + sourceEndIndex: 4, + value: "U+26" + } + ] }, { message: "should parse unicode-range (single codepoint) 2", fixture: "U+0-7F", - expected: [{ type: "unicode-range", sourceIndex: 0, value: "U+0-7F" }] + expected: [ + { + type: "unicode-range", + sourceIndex: 0, + sourceEndIndex: 6, + value: "U+0-7F" + } + ] }, { message: "should parse unicode-range (single codepoint) 3", fixture: "U+0-7f", - expected: [{ type: "unicode-range", sourceIndex: 0, value: "U+0-7f" }] + expected: [ + { + type: "unicode-range", + sourceIndex: 0, + sourceEndIndex: 6, + value: "U+0-7f" + } + ] }, { message: "should parse unicode-range (single codepoint) (lowercase)", fixture: "u+26", - expected: [{ type: "unicode-range", sourceIndex: 0, value: "u+26" }] + expected: [ + { + type: "unicode-range", + sourceIndex: 0, + sourceEndIndex: 4, + value: "u+26" + } + ] }, { message: "should parse unicode-range (codepoint range)", fixture: "U+0025-00FF", - expected: [{ type: "unicode-range", sourceIndex: 0, value: "U+0025-00FF" }] + expected: [ + { + type: "unicode-range", + sourceIndex: 0, + sourceEndIndex: 11, + value: "U+0025-00FF" + } + ] }, { message: "should parse unicode-range (wildcard range)", fixture: "U+4??", - expected: [{ type: "unicode-range", sourceIndex: 0, value: "U+4??" }] + expected: [ + { + type: "unicode-range", + sourceIndex: 0, + sourceEndIndex: 5, + value: "U+4??" + } + ] }, { message: "should parse unicode-range (multiple values)", fixture: "U+0025-00FF, U+4??", expected: [ - { type: "unicode-range", sourceIndex: 0, value: "U+0025-00FF" }, - { type: "div", sourceIndex: 11, value: ",", before: "", after: " " }, - { type: "unicode-range", sourceIndex: 13, value: "U+4??" } + { + type: "unicode-range", + sourceIndex: 0, + sourceEndIndex: 11, + value: "U+0025-00FF" + }, + { + type: "div", + sourceIndex: 11, + sourceEndIndex: 13, + value: ",", + before: "", + after: " " + }, + { + type: "unicode-range", + sourceIndex: 13, + sourceEndIndex: 18, + value: "U+4??" + } ] }, { message: "should parse invalid unicode-range as word", fixture: "U+4??Z", - expected: [{ type: "word", sourceIndex: 0, value: "U+4??Z" }] + expected: [ + { type: "word", sourceIndex: 0, sourceEndIndex: 6, value: "U+4??Z" } + ] }, { message: "should parse invalid unicode-range as word 2", fixture: "U+", - expected: [{ type: "word", sourceIndex: 0, value: "U+" }] + expected: [{ type: "word", sourceIndex: 0, sourceEndIndex: 2, value: "U+" }] }, { message: "should parse invalid unicode-range as word 2", fixture: "U+Z", - expected: [{ type: "word", sourceIndex: 0, value: "U+Z" }] + expected: [ + { type: "word", sourceIndex: 0, sourceEndIndex: 3, value: "U+Z" } + ] } ]; diff --git a/test/stringify.js b/test/stringify.js index fb695a5..36d13f2 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -89,7 +89,7 @@ var tests = [ ]; test("Stringify", function(t) { - t.plan(tests.length + 3); + t.plan(tests.length + 4); tests.forEach(function(opts) { t.equal(stringify(parse(opts.fixture)), opts.fixture, opts.message); @@ -131,4 +131,13 @@ test("Stringify", function(t) { tokens[1].type = "word"; t.equal(stringify(tokens), " rgba ", "Shouldn't process nodes of work type"); + + t.equal( + stringify(parse("calc(1px + var(--bar))"), function(node) { + if (node.type === "function" && node.value === "var") { + return "10px"; + } + }), + "calc(1px + 10px)" + ); }); diff --git a/test/unit.js b/test/unit.js index 87279ed..35c94e1 100644 --- a/test/unit.js +++ b/test/unit.js @@ -12,15 +12,15 @@ var tests = [ }, { fixture: "2.", - expected: { number: "2.", unit: "" } + expected: { number: "2", unit: "." } }, { fixture: "+2.", - expected: { number: "+2.", unit: "" } + expected: { number: "+2", unit: "." } }, { fixture: "-2.", - expected: { number: "-2.", unit: "" } + expected: { number: "-2", unit: "." } }, { fixture: "+-2.", @@ -57,6 +57,218 @@ var tests = [ { fixture: "e1", expected: false + }, + { + fixture: "2rem", + expected: { number: "2", unit: "rem" } + }, + { + fixture: "2.000rem", + expected: { number: "2.000", unit: "rem" } + }, + { + fixture: "+2rem", + expected: { number: "+2", unit: "rem" } + }, + { + fixture: "-2rem", + expected: { number: "-2", unit: "rem" } + }, + { + fixture: "1.1rem", + expected: { number: "1.1", unit: "rem" } + }, + { + fixture: "+1.1rem", + expected: { number: "+1.1", unit: "rem" } + }, + { + fixture: "-1.1rem", + expected: { number: "-1.1", unit: "rem" } + }, + { + fixture: "1.1e1rem", + expected: { number: "1.1e1", unit: "rem" } + }, + { + fixture: "+1.1e1rem", + expected: { number: "+1.1e1", unit: "rem" } + }, + { + fixture: "-1.1e1rem", + expected: { number: "-1.1e1", unit: "rem" } + }, + { + fixture: "1.1e+1rem", + expected: { number: "1.1e+1", unit: "rem" } + }, + { + fixture: "1.1e-1rem", + expected: { number: "1.1e-1", unit: "rem" } + }, + { + fixture: "1.1e1e1rem", + expected: { number: "1.1e1", unit: "e1rem" } + }, + { + fixture: "1.1e-1e", + expected: { number: "1.1e-1", unit: "e" } + }, + { + fixture: "1.1e-1rem", + expected: { number: "1.1e-1", unit: "rem" } + }, + { + fixture: "1.1e--++1e", + expected: { number: "1.1", unit: "e--++1e" } + }, + { + fixture: "1.1e--++1rem", + expected: { number: "1.1", unit: "e--++1rem" } + }, + { + fixture: "100+px", + expected: { number: "100", unit: "+px" } + }, + { + fixture: "100.0.0px", + expected: { number: "100.0", unit: ".0px" } + }, + { + fixture: "100e1epx", + expected: { number: "100e1", unit: "epx" } + }, + { + fixture: "100e1e1px", + expected: { number: "100e1", unit: "e1px" } + }, + { + fixture: "+100.1e+1e+1px", + expected: { number: "+100.1e+1", unit: "e+1px" } + }, + { + fixture: "-100.1e-1e-1px", + expected: { number: "-100.1e-1", unit: "e-1px" } + }, + { + fixture: ".5px", + expected: { number: ".5", unit: "px" } + }, + { + fixture: "+.5px", + expected: { number: "+.5", unit: "px" } + }, + { + fixture: "-.5px", + expected: { number: "-.5", unit: "px" } + }, + { + fixture: ".5e1px", + expected: { number: ".5e1", unit: "px" } + }, + { + fixture: "-.5e1px", + expected: { number: "-.5e1", unit: "px" } + }, + { + fixture: "+.5e1px", + expected: { number: "+.5e1", unit: "px" } + }, + { + fixture: ".5e1e1px", + expected: { number: ".5e1", unit: "e1px" } + }, + { + fixture: ".5.5px", + expected: { number: ".5", unit: ".5px" } + }, + { + fixture: "1e", + expected: { number: "1", unit: "e" } + }, + { + fixture: "1e1", + expected: { number: "1e1", unit: "" } + }, + { + fixture: "1ee", + expected: { number: "1", unit: "ee" } + }, + { + fixture: "1e+", + expected: { number: "1", unit: "e+" } + }, + { + fixture: "1e-", + expected: { number: "1", unit: "e-" } + }, + { + fixture: "1e+1", + expected: { number: "1e+1", unit: "" } + }, + { + fixture: "1e++1", + expected: { number: "1", unit: "e++1" } + }, + { + fixture: "1e--1", + expected: { number: "1", unit: "e--1" } + }, + { + fixture: "+10", + expected: { number: "+10", unit: "" } + }, + { + fixture: "-10", + expected: { number: "-10", unit: "" } + }, + { + fixture: ".2px", + expected: { number: ".2", unit: "px" } + }, + { + fixture: "-.2px", + expected: { number: "-.2", unit: "px" } + }, + { + fixture: "+.2px", + expected: { number: "+.2", unit: "px" } + }, + { + fixture: ".a", + expected: false + }, + { + fixture: ".", + expected: false + }, + { + fixture: "+", + expected: false + }, + { + fixture: "-", + expected: false + }, + { + fixture: "-a", + expected: false + }, + { + fixture: "+a", + expected: false + }, + { + fixture: "+.a", + expected: false + }, + { + fixture: "-.a", + expected: false + }, + { + fixture: "", + expected: false } ];