diff --git a/src/css/Parser.js b/src/css/Parser.js index 990ef9a1..cee20797 100644 --- a/src/css/Parser.js +++ b/src/css/Parser.js @@ -1876,8 +1876,8 @@ Parser.prototype = function() { /* * term * : unary_operator? - * [ NUMBER S* | PERCENTAGE S* | LENGTH S* | ANGLE S* | - * TIME S* | FREQ S* | function | ie_function ] + * [ NUMBER S* | PERCENTAGE S* | DIMENSION S* | LENGTH S* | + * ANGLE S* | TIME S* | FREQ S* | function | ie_function ] * | STRING S* | IDENT S* | URI S* | UNICODERANGE S* | hexcolor * ; */ @@ -1922,9 +1922,9 @@ Parser.prototype = function() { this._readWhitespace(); // see if there's a simple match - } else if (tokenStream.match([Tokens.NUMBER, Tokens.PERCENTAGE, Tokens.LENGTH, - Tokens.ANGLE, Tokens.TIME, - Tokens.FREQ, Tokens.STRING, Tokens.IDENT, Tokens.URI, Tokens.UNICODE_RANGE])) { + } else if (tokenStream.match([Tokens.NUMBER, Tokens.PERCENTAGE, Tokens.DIMENSION, + Tokens.LENGTH, Tokens.ANGLE, Tokens.TIME, Tokens.FREQ, Tokens.STRING, + Tokens.IDENT, Tokens.URI, Tokens.UNICODE_RANGE])) { value = tokenStream.token().value; if (unary === null) { diff --git a/src/css/PropertyValuePart.js b/src/css/PropertyValuePart.js index 4db048db..b4da9e30 100644 --- a/src/css/PropertyValuePart.js +++ b/src/css/PropertyValuePart.js @@ -62,7 +62,7 @@ function PropertyValuePart(text, line, col, optionalHint) { break; case "fr": - this.type = "grid"; + this.type = "flex"; break; case "deg": diff --git a/src/css/TokenStream.js b/src/css/TokenStream.js index 329077c4..224864e2 100755 --- a/src/css/TokenStream.js +++ b/src/css/TokenStream.js @@ -41,10 +41,6 @@ function isNameChar(c) { return c != null && (isNameStart(c) || /[0-9\-\\]/.test(c)); } -function isIdentStart(c) { - return c != null && (isNameStart(c) || /-\\/.test(c)); -} - function mix(receiver, supplier) { for (var prop in supplier) { if (Object.prototype.hasOwnProperty.call(supplier, prop)) { @@ -170,7 +166,7 @@ TokenStream.prototype = mix(new TokenStreamBase(), { */ case ".": if (isDigit(reader.peek())) { - token = this.numberToken(c, startLine, startCol); + token = this.numericToken(c, startLine, startCol); } else { token = this.charToken(c, startLine, startCol); } @@ -187,7 +183,7 @@ TokenStream.prototype = mix(new TokenStreamBase(), { */ case "-": if (wouldStartUnsignedNumber(reader.peekCount(2))) { - token = this.numberToken(c, startLine, startCol); + token = this.numericToken(c, startLine, startCol); break; } else if (reader.peekCount(2) === "->") { token = this.htmlCommentEndToken(c, startLine, startCol); @@ -203,7 +199,7 @@ TokenStream.prototype = mix(new TokenStreamBase(), { */ case "+": if (wouldStartUnsignedNumber(reader.peekCount(2))) { - token = this.numberToken(c, startLine, startCol); + token = this.numericToken(c, startLine, startCol); } else { token = this.charToken(c, startLine, startCol); } @@ -290,16 +286,7 @@ TokenStream.prototype = mix(new TokenStreamBase(), { /** * Produces a token based on the given character and location in the - * stream, when no other case applies. - * Potential tokens: - * - NUMBER - * - DIMENSION - * - LENGTH - * - FREQ - * - TIME - * - EMS - * - EXS - * - ANGLE + * stream, when no other case applies. Many potential tokens. * @param {String} c The character for the token. * @param {int} startLine The beginning line for the character. * @param {int} startCol The beginning column for the character. @@ -311,7 +298,7 @@ TokenStream.prototype = mix(new TokenStreamBase(), { token = null; if (isDigit(c)) { - token = this.numberToken(c, startLine, startCol); + token = this.numericToken(c, startLine, startCol); } else /* @@ -679,10 +666,11 @@ TokenStream.prototype = mix(new TokenStreamBase(), { }, /** - * Produces a number token based on the given character - * and location in the stream. This may return a token of - * NUMBER, EMS, EXS, LENGTH, ANGLE, TIME, FREQ, DIMENSION, - * or PERCENTAGE. + * Consume and return a numeric token, as described in the CSS spec: + * https://drafts.csswg.org/css-syntax-3/#consume-numeric-token. + * This may return a token of NUMBER, PERCENTAGE, or DIMENSION (or for now, + * one of LENGTH, ANGLE, TIME, FREQ, FLEX, or RESOLUTION when appropriate). + * @deprecated Use `numericToken` instead. * @param {String} first The first character for the token. * @param {int} startLine The beginning line for the character. * @param {int} startCol The beginning column for the character. @@ -690,16 +678,32 @@ TokenStream.prototype = mix(new TokenStreamBase(), { * @method numberToken */ numberToken: function(first, startLine, startCol) { + return this.numericToken(first, startLine, startCol); + }, + + /** + * Consume and return a numeric token, as described in the CSS spec: + * https://drafts.csswg.org/css-syntax-3/#consume-numeric-token. + * This may return a token of NUMBER, PERCENTAGE, or DIMENSION (or for now, + * one of LENGTH, ANGLE, TIME, FREQ, FLEX, or RESOLUTION when appropriate). + * @param {String} first The first character for the token. + * @param {int} startLine The beginning line for the character. + * @param {int} startCol The beginning column for the character. + * @return {Object} A token object. + * @method numericToken + */ + numericToken: function(first, startLine, startCol) { var reader = this._reader, value = this.readNumber(first), ident, - tt = Tokens.NUMBER, - c = reader.peek(); + tt = Tokens.NUMBER; - if (isIdentStart(c)) { + if (wouldStartIdent(reader.peekCount(2))) { ident = this.readName(reader.read()); value += ident; + // NOTE: As long as these dimension subtypes exist, these lists must + // be kept in sync with those in PropertyValuePart.js. if (/^em$|^ex$|^px$|^gd$|^rem$|^vw$|^vh$|^vmax$|^vmin$|^ch$|^cm$|^mm$|^in$|^pt$|^pc$/i.test(ident)) { tt = Tokens.LENGTH; } else if (/^deg|^rad$|^grad$|^turn$/i.test(ident)) { @@ -714,7 +718,7 @@ TokenStream.prototype = mix(new TokenStreamBase(), { tt = Tokens.DIMENSION; } - } else if (c === "%") { + } else if (reader.peek() === "%") { value += reader.read(); tt = Tokens.PERCENTAGE; } diff --git a/src/css/Tokens.js b/src/css/Tokens.js index a4eebfee..32fcd287 100644 --- a/src/css/Tokens.js +++ b/src/css/Tokens.js @@ -45,14 +45,10 @@ var Tokens = module.exports = [ // important symbol { name: "IMPORTANT_SYM" }, - // measurements - { name: "LENGTH" }, - { name: "ANGLE" }, - { name: "TIME" }, - { name: "FREQ" }, - { name: "DIMENSION" }, - { name: "PERCENTAGE" }, - { name: "NUMBER" }, + // numeric + { name: "NUMBER" }, // https://www.w3.org/TR/css-syntax-3/#number-token-diagram + { name: "DIMENSION" }, // https://www.w3.org/TR/css-syntax-3/#dimension-token-diagram + { name: "PERCENTAGE" }, // https://www.w3.org/TR/css-syntax-3/#percentage-token-diagram // functions { name: "URI" }, @@ -109,6 +105,13 @@ var Tokens = module.exports = [ * The following token names are not defined in any CSS specification but are used by the lexer. */ + // TODO: Drop these as tokens, so they exist only as PropertyValueParts? + // subtypes of dimension: These are not tokens in CSS3 grammar. + { name: "LENGTH" }, // https://www.w3.org/TR/css3-values/#lengths + { name: "ANGLE" }, // https://www.w3.org/TR/css3-values/#angles + { name: "TIME" }, // https://www.w3.org/TR/css3-values/#time + { name: "FREQ" }, // https://www.w3.org/TR/css3-values/#frequency + // not a real token, but useful for stupid IE filters { name: "IE_FUNCTION" }, diff --git a/tests/css/Parser.js b/tests/css/Parser.js index 3b5ec5e3..43771130 100644 --- a/tests/css/Parser.js +++ b/tests/css/Parser.js @@ -1037,6 +1037,32 @@ var YUITest = require("yuitest"), Assert.areEqual("ch", result.parts[0].units); }, + testDimensionValueFr: function() { + var parser = new Parser(); + var result = parser.parsePropertyValue("2fr"); + + Assert.isInstanceOf(parserlib.css.PropertyValue, result); + Assert.areEqual(1, result.parts.length); + Assert.areEqual("flex", result.parts[0].type); + Assert.areEqual(2, result.parts[0].value); + Assert.areEqual("fr", result.parts[0].units); + }, + + testDimensionValuePxAndFr: function() { + var parser = new Parser(); + var result = parser.parsePropertyValue("100px 1fr"); + + Assert.isInstanceOf(parserlib.css.PropertyValue, result); + Assert.areEqual(2, result.parts.length); + Assert.areEqual("length", result.parts[0].type); + Assert.areEqual(100, result.parts[0].value); + Assert.areEqual("px", result.parts[0].units); + Assert.isInstanceOf(parserlib.css.PropertyValue, result); + Assert.areEqual("flex", result.parts[1].type); + Assert.areEqual(1, result.parts[1].value); + Assert.areEqual("fr", result.parts[1].units); + }, + testViewportRelativeHeightValue: function() { var parser = new Parser(); var result = parser.parsePropertyValue("50vh"); diff --git a/tests/css/TokenStream.js b/tests/css/TokenStream.js index 63914752..3bed8122 100644 --- a/tests/css/TokenStream.js +++ b/tests/css/TokenStream.js @@ -312,7 +312,7 @@ var YUITest = require("yuitest"), })); suite.add(new CSSTokenTestCase({ - name : "Tests for Numbers", + name : "Tests for Numbers, Dimensions, and Percentages", patterns: { @@ -322,7 +322,6 @@ var YUITest = require("yuitest"), "50.0PX" : [CSSTokens.LENGTH], ".6Px" : [CSSTokens.LENGTH], - "7cm" : [CSSTokens.LENGTH], "7CM" : [CSSTokens.LENGTH], "7cM" : [CSSTokens.LENGTH], @@ -363,7 +362,6 @@ var YUITest = require("yuitest"), "50.0CH" : [CSSTokens.LENGTH], ".5cH" : [CSSTokens.LENGTH], - "5deg" : [CSSTokens.ANGLE], "50.0DEG" : [CSSTokens.ANGLE], ".5Deg" : [CSSTokens.ANGLE], @@ -395,6 +393,10 @@ var YUITest = require("yuitest"), "50.0KHZ" : [CSSTokens.FREQ], ".5kHz" : [CSSTokens.FREQ], + "1fr" : [CSSTokens.DIMENSION], + "0fr" : [CSSTokens.DIMENSION], + ".25fr" : [CSSTokens.DIMENSION], + "5ncz" : [CSSTokens.DIMENSION], "50.0NCZ" : [CSSTokens.DIMENSION], ".5nCz" : [CSSTokens.DIMENSION], @@ -506,6 +508,10 @@ var YUITest = require("yuitest"), "background: red;" : [CSSTokens.IDENT, CSSTokens.COLON, CSSTokens.S, CSSTokens.IDENT, CSSTokens.SEMICOLON], "background-color: red;" : [CSSTokens.IDENT, CSSTokens.COLON, CSSTokens.S, CSSTokens.IDENT, CSSTokens.SEMICOLON], + //multiple value parts + "margin: 0 auto 10px;" : [CSSTokens.IDENT, CSSTokens.COLON, CSSTokens.S, CSSTokens.NUMBER, CSSTokens.S, CSSTokens.IDENT, CSSTokens.S, CSSTokens.LENGTH, CSSTokens.SEMICOLON], + "grid-template-columns: 100px 1fr max-content minmax(min-content, 1fr);" : [CSSTokens.IDENT, CSSTokens.COLON, CSSTokens.S, CSSTokens.LENGTH, CSSTokens.S, CSSTokens.DIMENSION, CSSTokens.S, CSSTokens.IDENT, CSSTokens.S, CSSTokens.FUNCTION, CSSTokens.IDENT, CSSTokens.COMMA, CSSTokens.S, CSSTokens.DIMENSION, CSSTokens.RPAREN, CSSTokens.SEMICOLON], + "filter: progid:DXImageTransform.Microsoft.Wave(strength=100);": [CSSTokens.IDENT, CSSTokens.COLON, CSSTokens.S, CSSTokens.IE_FUNCTION, CSSTokens.IDENT, CSSTokens.EQUALS, CSSTokens.NUMBER, CSSTokens.RPAREN, CSSTokens.SEMICOLON] } diff --git a/tests/css/Validation.js b/tests/css/Validation.js index d60054d0..98697898 100644 --- a/tests/css/Validation.js +++ b/tests/css/Validation.js @@ -87,6 +87,7 @@ var YUITest = require("yuitest"), underscoreHack: true }); parser.parse(".foo { " + this.property + ":" + value + "}"); + Assert.pass(); }; @@ -187,11 +188,12 @@ var YUITest = require("yuitest"), invalid: { "1px" : "Expected ([ none | ]#) but found '1px'.", - "--invalid" : "Expected ([ none | ]#) but found '--invalid'." + "--invalid" : "Expected ([ none | ]#) but found '--invalid'.", + "-0num": "Expected ([ none | ]#) but found '-0num'.", }, error: { - "-0num": "Unexpected token '-0num' at line 1, col 23." + "@foo": "Unexpected token '@foo' at line 1, col 23." } })); @@ -981,14 +983,14 @@ var YUITest = require("yuitest"), invalid: { "--Futura, sans-serif" : "Expected ([ | ]#) but found '--Futura , sans-serif'.", + "47Futura, sans-serif" : "Expected ([ | ]#) but found '47Futura , sans-serif'.", + "-7Futura, sans-serif" : "Expected ([ | ]#) but found '-7Futura , sans-serif'.", "Red/Black, sans-serif" : "Expected end of value but found '/'.", "'Lucida' Grande, sans-serif" : "Expected end of value but found 'Grande'.", "Hawaii 5-0, sans-serif" : "Expected end of value but found '5'." }, error: { - "47Futura, sans-serif" : "Unexpected token '47Futura' at line 1, col 20.", - "-7Futura, sans-serif" : "Unexpected token '-7Futura' at line 1, col 20.", "Ahem!, sans-serif" : "Expected RBRACE at line 1, col 24.", "test@foo, sans-serif" : "Expected RBRACE at line 1, col 24.", "#POUND, sans-serif" : "Expected a hex color but found '#POUND' at line 1, col 20." @@ -1061,6 +1063,15 @@ var YUITest = require("yuitest"), ] })); + suite.add(new ValidationTestCase({ + property: "grid-template-columns", + + valid: [ + "100px 1fr", "fit-content(40%)", "repeat(3, 200px)", + "100px 1fr max-content minmax(min-content, 1fr)" + ] + })); + suite.add(new ValidationTestCase({ property: "image-rendering",