@emmetio/css-abbreviation
Advanced tools
Comparing version 0.2.2 to 0.3.0
@@ -7,2 +7,3 @@ 'use strict'; | ||
var StreamReader = _interopDefault(require('@emmetio/stream-reader')); | ||
var _emmetio_streamReaderUtils = require('@emmetio/stream-reader-utils'); | ||
@@ -13,96 +14,24 @@ /** | ||
class CSSValue { | ||
constructor() { | ||
this.type = 'css-value'; | ||
this.value = []; | ||
} | ||
constructor() { | ||
this.type = 'css-value'; | ||
this.value = []; | ||
} | ||
get size() { | ||
return this.value.length; | ||
} | ||
get size() { | ||
return this.value.length; | ||
} | ||
add(value) { | ||
this.value.push(value); | ||
} | ||
add(value) { | ||
this.value.push(value); | ||
} | ||
has(value) { | ||
return this.value.indexOf(value) !== -1; | ||
} | ||
has(value) { | ||
return this.value.indexOf(value) !== -1; | ||
} | ||
toString() { | ||
return this.value.join(' '); | ||
} | ||
toString() { | ||
return this.value.join(' '); | ||
} | ||
} | ||
/** | ||
* Consumes characters in given string while they pass `test` code test | ||
* @param {StreamReader} stream | ||
* @param {Function} test | ||
* @return {Boolean} Returns `true` stream was consumed at least once | ||
*/ | ||
function eatWhile(stream, test) { | ||
const start = stream.pos; | ||
while (!stream.eol() && test(stream.peekCode())) { | ||
stream.pos++; | ||
} | ||
return start < stream.pos; | ||
} | ||
/** | ||
* Eats alpha word in given stream | ||
* @param {StreamReader} stream | ||
* @return {Boolean} Returns `true` if word was consumed | ||
*/ | ||
function eatAlphaWord(stream) { | ||
return eatWhile(stream, isAlphaWord); | ||
} | ||
/** | ||
* Eats alpha word in given stream | ||
* @param {StreamReader} stream | ||
* @return {Boolean} Returns `true` if word was consumed | ||
*/ | ||
function eatAlphaNumericWord(stream) { | ||
return eatWhile(stream, isAlphaNumericWord); | ||
} | ||
/** | ||
* @param {Number} code | ||
* @return {Boolean} | ||
*/ | ||
function isAlphaNumericWord(code) { | ||
return isNumber(code) || isAlphaWord(code); | ||
} | ||
/** | ||
* @param {Number} code | ||
* @return {Boolean} | ||
*/ | ||
function isAlphaWord(code) { | ||
return code === 95 /* _ */ || isAlpha(code); | ||
} | ||
/** | ||
* Check if given code is a number | ||
* @param {Number} code | ||
* @return {Boolean} | ||
*/ | ||
function isNumber(code) { | ||
return code > 47 && code < 58; | ||
} | ||
/** | ||
* Check if given character code is alpha code (letter though A to Z) | ||
* @param {Number} code | ||
* @param {Number} [from] | ||
* @param {Number} [to] | ||
* @return {Boolean} | ||
*/ | ||
function isAlpha(code, from, to) { | ||
from = from || 65; // A | ||
to = to || 90; // Z | ||
code &= ~32; // quick hack to convert any char code to uppercase char code | ||
return code >= from && code <= to; // A-F | ||
} | ||
const HASH = 35; // # | ||
@@ -117,96 +46,98 @@ const DOT = 46; // . | ||
var consumeColor = function(stream) { | ||
// supported color variations: | ||
// #abc → #aabbccc | ||
// #0 → #000000 | ||
// #fff.5 → rgba(255, 255, 255, 0.5) | ||
// #t → transparent | ||
if (stream.peekCode() === HASH) { | ||
stream.start = stream.pos++; | ||
stream.eat(116) /* t */ || eatWhile(stream, isHex); | ||
const base = stream.current(); | ||
// supported color variations: | ||
// #abc → #aabbccc | ||
// #0 → #000000 | ||
// #fff.5 → rgba(255, 255, 255, 0.5) | ||
// #t → transparent | ||
if (stream.peek() === HASH) { | ||
stream.start = stream.pos; | ||
stream.next(); | ||
// a hex color can be followed by `.num` alpha value | ||
stream.start = stream.pos; | ||
if (stream.eat(DOT) && !eatWhile(stream, isNumber)) { | ||
throw stream.error('Unexpected character for alpha value of color'); | ||
} | ||
stream.eat(116) /* t */ || stream.eatWhile(isHex); | ||
const base = stream.current(); | ||
return new Color(base, stream.current()); | ||
} | ||
// a hex color can be followed by `.num` alpha value | ||
stream.start = stream.pos; | ||
if (stream.eat(DOT) && !stream.eatWhile(_emmetio_streamReaderUtils.isNumber)) { | ||
throw stream.error('Unexpected character for alpha value of color'); | ||
} | ||
return new Color(base, stream.current()); | ||
} | ||
}; | ||
class Color { | ||
constructor(value, alpha) { | ||
this.type = 'color'; | ||
this.raw = value; | ||
this.alpha = Number(alpha != null && alpha !== '' ? alpha : 1); | ||
value = value.slice(1); // remove # | ||
constructor(value, alpha) { | ||
this.type = 'color'; | ||
this.raw = value; | ||
this.alpha = Number(alpha != null && alpha !== '' ? alpha : 1); | ||
value = value.slice(1); // remove # | ||
let r = 0, g = 0, b = 0; | ||
let r = 0, g = 0, b = 0; | ||
if (value === 't') { | ||
this.alpha = 0; | ||
} else { | ||
switch (value.length) { | ||
case 0: | ||
break; | ||
if (value === 't') { | ||
this.alpha = 0; | ||
} else { | ||
switch (value.length) { | ||
case 0: | ||
break; | ||
case 1: | ||
r = g = b = value + value; | ||
break; | ||
case 1: | ||
r = g = b = value + value; | ||
break; | ||
case 2: | ||
r = g = b = value; | ||
break; | ||
case 2: | ||
r = g = b = value; | ||
break; | ||
case 3: | ||
r = value[0] + value[0]; | ||
g = value[1] + value[1]; | ||
b = value[2] + value[2]; | ||
break; | ||
case 3: | ||
r = value[0] + value[0]; | ||
g = value[1] + value[1]; | ||
b = value[2] + value[2]; | ||
break; | ||
default: | ||
value += value; | ||
r = value.slice(0, 2); | ||
g = value.slice(2, 4); | ||
b = value.slice(4, 6); | ||
} | ||
} | ||
default: | ||
value += value; | ||
r = value.slice(0, 2); | ||
g = value.slice(2, 4); | ||
b = value.slice(4, 6); | ||
} | ||
} | ||
this.r = parseInt(r, 16); | ||
this.g = parseInt(g, 16); | ||
this.b = parseInt(b, 16); | ||
} | ||
this.r = parseInt(r, 16); | ||
this.g = parseInt(g, 16); | ||
this.b = parseInt(b, 16); | ||
} | ||
/** | ||
* Output current color as hex value | ||
* @param {Boolean} shor Produce short value (e.g. #fff instead of #ffffff), if possible | ||
* @return {String} | ||
*/ | ||
toHex(short) { | ||
const fn = (short && isShortHex(this.r) && isShortHex(this.g) && isShortHex(this.b)) | ||
? toShortHex : toHex; | ||
/** | ||
* Output current color as hex value | ||
* @param {Boolean} shor Produce short value (e.g. #fff instead of #ffffff), if possible | ||
* @return {String} | ||
*/ | ||
toHex(short) { | ||
const fn = (short && isShortHex(this.r) && isShortHex(this.g) && isShortHex(this.b)) | ||
? toShortHex : toHex; | ||
return '#' + fn(this.r) + fn(this.g) + fn(this.b); | ||
} | ||
return '#' + fn(this.r) + fn(this.g) + fn(this.b); | ||
} | ||
/** | ||
* Output current color as `rgba?(...)` CSS color | ||
* @return {String} | ||
*/ | ||
toRGB() { | ||
const values = [this.r, this.g, this.b]; | ||
if (this.alpha !== 1) { | ||
values.push(this.alpha.toFixed(8).replace(/\.?0+$/, '')); | ||
} | ||
/** | ||
* Output current color as `rgba?(...)` CSS color | ||
* @return {String} | ||
*/ | ||
toRGB() { | ||
const values = [this.r, this.g, this.b]; | ||
if (this.alpha !== 1) { | ||
values.push(this.alpha.toFixed(8).replace(/\.?0+$/, '')); | ||
} | ||
return `${values.length === 3 ? 'rgb' : 'rgba'}(${values.join(', ')})`; | ||
} | ||
return `${values.length === 3 ? 'rgb' : 'rgba'}(${values.join(', ')})`; | ||
} | ||
toString(short) { | ||
if (!this.r && !this.g && !this.b && !this.alpha) { | ||
return 'transparent'; | ||
} | ||
return this.alpha === 1 ? this.toHex(short) : this.toRGB(); | ||
} | ||
toString(short) { | ||
if (!this.r && !this.g && !this.b && !this.alpha) { | ||
return 'transparent'; | ||
} | ||
return this.alpha === 1 ? this.toHex(short) : this.toRGB(); | ||
} | ||
} | ||
@@ -220,24 +151,40 @@ | ||
function isHex(code) { | ||
return isNumber(code) || isAlpha(code, 65, 70); // A-F | ||
return _emmetio_streamReaderUtils.isNumber(code) || _emmetio_streamReaderUtils.isAlpha(code, 65, 70); // A-F | ||
} | ||
function isShortHex(hex) { | ||
return !(hex % 17); | ||
return !(hex % 17); | ||
} | ||
function toShortHex(num) { | ||
return (num >> 4).toString(16); | ||
return (num >> 4).toString(16); | ||
} | ||
function toHex(num) { | ||
return pad(num.toString(16), 2); | ||
return pad(num.toString(16), 2); | ||
} | ||
function pad(value, len) { | ||
while (value.length < len) { | ||
value = '0' + value; | ||
} | ||
return value; | ||
while (value.length < len) { | ||
value = '0' + value; | ||
} | ||
return value; | ||
} | ||
/** | ||
* @param {Number} code | ||
* @return {Boolean} | ||
*/ | ||
function isAlphaNumericWord(code) { | ||
return _emmetio_streamReaderUtils.isNumber(code) || isAlphaWord(code); | ||
} | ||
/** | ||
* @param {Number} code | ||
* @return {Boolean} | ||
*/ | ||
function isAlphaWord(code) { | ||
return code === 95 /* _ */ || _emmetio_streamReaderUtils.isAlpha(code); | ||
} | ||
const PERCENT = 37; // % | ||
@@ -254,11 +201,11 @@ const DOT$1 = 46; // . | ||
var consumeNumericValue = function(stream) { | ||
stream.start = stream.pos; | ||
if (eatNumber(stream)) { | ||
const num = stream.current(); | ||
stream.start = stream.pos; | ||
stream.start = stream.pos; | ||
if (eatNumber(stream)) { | ||
const num = stream.current(); | ||
stream.start = stream.pos; | ||
// eat unit, which can be a % or alpha word | ||
stream.eat(PERCENT) || eatAlphaWord(stream); | ||
return new NumericValue(num, stream.current()); | ||
} | ||
// eat unit, which can be a % or alpha word | ||
stream.eat(PERCENT) || stream.eatWhile(isAlphaWord); | ||
return new NumericValue(num, stream.current()); | ||
} | ||
}; | ||
@@ -270,11 +217,11 @@ | ||
class NumericValue { | ||
constructor(value, unit) { | ||
this.type = 'numeric'; | ||
this.value = Number(value); | ||
this.unit = unit || ''; | ||
} | ||
constructor(value, unit) { | ||
this.type = 'numeric'; | ||
this.value = Number(value); | ||
this.unit = unit || ''; | ||
} | ||
toString() { | ||
return `${this.value}${this.unit}`; | ||
} | ||
toString() { | ||
return `${this.value}${this.unit}`; | ||
} | ||
} | ||
@@ -289,26 +236,28 @@ | ||
const start = stream.pos; | ||
const negative = stream.eat(DASH$1); | ||
let hadDot = false, code; | ||
const negative = stream.eat(DASH$1); | ||
let hadDot = false, consumed = false, code; | ||
while (!stream.eol()) { | ||
code = stream.peekCode(); | ||
while (!stream.eof()) { | ||
code = stream.peek(); | ||
// either a second dot or not a number: stop parsing | ||
if (code === DOT$1 ? hadDot : !isNumber(code)) { | ||
// either a second dot or not a number: stop parsing | ||
if (code === DOT$1 ? hadDot : !_emmetio_streamReaderUtils.isNumber(code)) { | ||
break; | ||
} | ||
if (code === DOT$1) { | ||
hadDot = true; | ||
} | ||
consumed = true; | ||
stream.pos++; | ||
if (code === DOT$1) { | ||
hadDot = true; | ||
} | ||
stream.next(); | ||
} | ||
if (negative && stream.pos - start === 1) { | ||
// edge case: consumed dash only, bail out | ||
stream.pos = start; | ||
} | ||
if (negative && !consumed) { | ||
// edge case: consumed dash only, bail out | ||
stream.pos = start; | ||
} | ||
return start < stream.pos; | ||
return start !== stream.pos; | ||
} | ||
@@ -331,14 +280,14 @@ | ||
var consumeKeyword = function(stream, short) { | ||
stream.start = stream.pos; | ||
stream.start = stream.pos; | ||
if (stream.eat(DOLLAR$1) || stream.eat(AT$1)) { | ||
// SCSS or LESS variable | ||
eatAlphaNumericWord(stream); | ||
} else if (short) { | ||
eatAlphaWord(stream); | ||
} else { | ||
eatKeyword(stream); | ||
if (stream.eat(DOLLAR$1) || stream.eat(AT$1)) { | ||
// SCSS or LESS variable | ||
stream.eatWhile(isAlphaNumericWord); | ||
} else if (short) { | ||
stream.eatWhile(isAlphaWord); | ||
} else { | ||
stream.eatWhile(isKeyword); | ||
} | ||
return stream.start !== stream.pos ? new Keyword(stream.current()) : null; | ||
return stream.start !== stream.pos ? new Keyword(stream.current()) : null; | ||
}; | ||
@@ -357,6 +306,2 @@ | ||
function eatKeyword(stream) { | ||
return eatWhile(stream, isKeyword); | ||
} | ||
function isKeyword(code) { | ||
@@ -366,4 +311,3 @@ return isAlphaNumericWord(code) || code === DASH$2; | ||
const SINGLE_QUOTE = 39; // ' | ||
const DOUBLE_QUOTE = 34; // " | ||
const opt = { throws: true }; | ||
@@ -376,7 +320,3 @@ /** | ||
var consumeQuoted = function(stream) { | ||
const code = stream.peekCode(); | ||
if (code === SINGLE_QUOTE || code === DOUBLE_QUOTE) { | ||
stream.start = stream.pos++; | ||
stream.eatQuoted(code); | ||
if (_emmetio_streamReaderUtils.eatQuoted(stream, opt)) { | ||
return new QuotedString(stream.current()); | ||
@@ -397,4 +337,2 @@ } | ||
const TAB = 9; // \t | ||
const SPACE = 32; // ( | ||
const LBRACE = 40; // ( | ||
@@ -412,11 +350,11 @@ const RBRACE = 41; // ) | ||
function consumeArgumentList(stream) { | ||
if (!stream.eat(LBRACE)) { | ||
// not an argument list | ||
return null; | ||
} | ||
if (!stream.eat(LBRACE)) { | ||
// not an argument list | ||
return null; | ||
} | ||
let level = 1, code, arg; | ||
const argsList = []; | ||
let level = 1, code, arg; | ||
const argsList = []; | ||
while (!stream.eol()) { | ||
while (!stream.eof()) { | ||
if (arg = consumeArgument(stream)) { | ||
@@ -426,3 +364,3 @@ argsList.push(arg); | ||
// didn’t consumed argument, expect argument separator or end-of-arguments | ||
eatSpace(stream); | ||
stream.eatWhile(_emmetio_streamReaderUtils.isWhiteSpace); | ||
@@ -438,5 +376,5 @@ if (stream.eat(RBRACE)) { | ||
} | ||
} | ||
} | ||
return argsList; | ||
return argsList; | ||
} | ||
@@ -454,4 +392,4 @@ | ||
while (!stream.eol()) { | ||
eatSpace(stream); | ||
while (!stream.eof()) { | ||
stream.eatWhile(_emmetio_streamReaderUtils.isWhiteSpace); | ||
value = consumeNumericValue(stream) || consumeColor(stream) | ||
@@ -499,19 +437,2 @@ || consumeQuoted(stream) || consumeKeywordOrFunction(stream); | ||
/** | ||
* Check if given character code is a white-space code | ||
* @param {Number} code | ||
* @return {Boolean} | ||
*/ | ||
function isWhite(code) { | ||
return code === SPACE || code === TAB; | ||
} | ||
/** | ||
* @param {StreamReader} stream | ||
* @return {Boolean} Returns `true` if space was consumed | ||
*/ | ||
function eatSpace(stream) { | ||
return eatWhile(stream, isWhite); | ||
} | ||
const EXCL = 33; // ! | ||
@@ -529,38 +450,38 @@ const DOLLAR = 36; // $ | ||
var index = function(abbr) { | ||
const root = new Node(); | ||
const stream = new StreamReader(abbr); | ||
let node; | ||
const root = new Node(); | ||
const stream = new StreamReader(abbr); | ||
let node; | ||
while (!stream.eol()) { | ||
let node = new Node(consumeIdent(stream)); | ||
node.value = consumeValue(stream); | ||
while (!stream.eof()) { | ||
let node = new Node(consumeIdent(stream)); | ||
node.value = consumeValue(stream); | ||
const args = consumeArgumentList(stream); | ||
if (args) { | ||
// technically, arguments in CSS are anonymous Emmet Node attributes, | ||
// but since Emmet can support only one anonymous, `null`-name | ||
// attribute (for good reasons), we’ll use argument index as name | ||
for (let i = 0; i < args.length; i++) { | ||
node.setAttribute(String(i), args[i]); | ||
} | ||
} | ||
const args = consumeArgumentList(stream); | ||
if (args) { | ||
// technically, arguments in CSS are anonymous Emmet Node attributes, | ||
// but since Emmet can support only one anonymous, `null`-name | ||
// attribute (for good reasons), we’ll use argument index as name | ||
for (let i = 0; i < args.length; i++) { | ||
node.setAttribute(String(i), args[i]); | ||
} | ||
} | ||
// Consume `!important` modifier at the end of expression | ||
if (stream.eat(EXCL)) { | ||
node.value.add('!'); | ||
} | ||
// Consume `!important` modifier at the end of expression | ||
if (stream.eat(EXCL)) { | ||
node.value.add('!'); | ||
} | ||
root.appendChild(node); | ||
root.appendChild(node); | ||
// CSS abbreviations cannot be nested, only listed | ||
if (!stream.eat(PLUS)) { | ||
break; | ||
} | ||
} | ||
// CSS abbreviations cannot be nested, only listed | ||
if (!stream.eat(PLUS)) { | ||
break; | ||
} | ||
} | ||
if (!stream.eol()) { | ||
throw stream.error('Unexpected character'); | ||
} | ||
if (!stream.eof()) { | ||
throw stream.error('Unexpected character'); | ||
} | ||
return root; | ||
return root; | ||
}; | ||
@@ -574,6 +495,6 @@ | ||
function consumeIdent(stream) { | ||
stream.start = stream.pos; | ||
eatWhile(stream, isIdentPrefix); | ||
eatWhile(stream, isIdent); | ||
return stream.start !== stream.pos ? stream.current() : null; | ||
stream.start = stream.pos; | ||
stream.eatWhile(isIdentPrefix); | ||
stream.eatWhile(isIdent); | ||
return stream.start !== stream.pos ? stream.current() : null; | ||
} | ||
@@ -587,25 +508,25 @@ | ||
function consumeValue(stream) { | ||
const values = new CSSValue(); | ||
let value; | ||
const values = new CSSValue(); | ||
let value; | ||
while (!stream.eol()) { | ||
if (value = consumeNumericValue(stream) || consumeColor(stream)) { | ||
// edge case: a dash after unit-less numeric value or color should | ||
// be treated as value separator, not negative sign | ||
if (!value.unit) { | ||
stream.eat(DASH); | ||
} | ||
} else { | ||
stream.eat(DASH); | ||
value = consumeKeyword(stream, true); | ||
} | ||
while (!stream.eof()) { | ||
if (value = consumeNumericValue(stream) || consumeColor(stream)) { | ||
// edge case: a dash after unit-less numeric value or color should | ||
// be treated as value separator, not negative sign | ||
if (!value.unit) { | ||
stream.eat(DASH); | ||
} | ||
} else { | ||
stream.eat(DASH); | ||
value = consumeKeyword(stream, true); | ||
} | ||
if (!value) { | ||
if (!value) { | ||
break; | ||
} | ||
} | ||
values.add(value); | ||
} | ||
} | ||
return values; | ||
return values; | ||
} | ||
@@ -618,3 +539,3 @@ | ||
function isIdent(code) { | ||
return isAlphaWord(code); | ||
return isAlphaWord(code); | ||
} | ||
@@ -621,0 +542,0 @@ |
import Node from '@emmetio/node'; | ||
import StreamReader from '@emmetio/stream-reader'; | ||
import { eatQuoted, isAlpha, isNumber, isWhiteSpace } from '@emmetio/stream-reader-utils'; | ||
@@ -8,96 +9,24 @@ /** | ||
class CSSValue { | ||
constructor() { | ||
this.type = 'css-value'; | ||
this.value = []; | ||
} | ||
constructor() { | ||
this.type = 'css-value'; | ||
this.value = []; | ||
} | ||
get size() { | ||
return this.value.length; | ||
} | ||
get size() { | ||
return this.value.length; | ||
} | ||
add(value) { | ||
this.value.push(value); | ||
} | ||
add(value) { | ||
this.value.push(value); | ||
} | ||
has(value) { | ||
return this.value.indexOf(value) !== -1; | ||
} | ||
has(value) { | ||
return this.value.indexOf(value) !== -1; | ||
} | ||
toString() { | ||
return this.value.join(' '); | ||
} | ||
toString() { | ||
return this.value.join(' '); | ||
} | ||
} | ||
/** | ||
* Consumes characters in given string while they pass `test` code test | ||
* @param {StreamReader} stream | ||
* @param {Function} test | ||
* @return {Boolean} Returns `true` stream was consumed at least once | ||
*/ | ||
function eatWhile(stream, test) { | ||
const start = stream.pos; | ||
while (!stream.eol() && test(stream.peekCode())) { | ||
stream.pos++; | ||
} | ||
return start < stream.pos; | ||
} | ||
/** | ||
* Eats alpha word in given stream | ||
* @param {StreamReader} stream | ||
* @return {Boolean} Returns `true` if word was consumed | ||
*/ | ||
function eatAlphaWord(stream) { | ||
return eatWhile(stream, isAlphaWord); | ||
} | ||
/** | ||
* Eats alpha word in given stream | ||
* @param {StreamReader} stream | ||
* @return {Boolean} Returns `true` if word was consumed | ||
*/ | ||
function eatAlphaNumericWord(stream) { | ||
return eatWhile(stream, isAlphaNumericWord); | ||
} | ||
/** | ||
* @param {Number} code | ||
* @return {Boolean} | ||
*/ | ||
function isAlphaNumericWord(code) { | ||
return isNumber(code) || isAlphaWord(code); | ||
} | ||
/** | ||
* @param {Number} code | ||
* @return {Boolean} | ||
*/ | ||
function isAlphaWord(code) { | ||
return code === 95 /* _ */ || isAlpha(code); | ||
} | ||
/** | ||
* Check if given code is a number | ||
* @param {Number} code | ||
* @return {Boolean} | ||
*/ | ||
function isNumber(code) { | ||
return code > 47 && code < 58; | ||
} | ||
/** | ||
* Check if given character code is alpha code (letter though A to Z) | ||
* @param {Number} code | ||
* @param {Number} [from] | ||
* @param {Number} [to] | ||
* @return {Boolean} | ||
*/ | ||
function isAlpha(code, from, to) { | ||
from = from || 65; // A | ||
to = to || 90; // Z | ||
code &= ~32; // quick hack to convert any char code to uppercase char code | ||
return code >= from && code <= to; // A-F | ||
} | ||
const HASH = 35; // # | ||
@@ -112,96 +41,98 @@ const DOT = 46; // . | ||
var consumeColor = function(stream) { | ||
// supported color variations: | ||
// #abc → #aabbccc | ||
// #0 → #000000 | ||
// #fff.5 → rgba(255, 255, 255, 0.5) | ||
// #t → transparent | ||
if (stream.peekCode() === HASH) { | ||
stream.start = stream.pos++; | ||
stream.eat(116) /* t */ || eatWhile(stream, isHex); | ||
const base = stream.current(); | ||
// supported color variations: | ||
// #abc → #aabbccc | ||
// #0 → #000000 | ||
// #fff.5 → rgba(255, 255, 255, 0.5) | ||
// #t → transparent | ||
if (stream.peek() === HASH) { | ||
stream.start = stream.pos; | ||
stream.next(); | ||
// a hex color can be followed by `.num` alpha value | ||
stream.start = stream.pos; | ||
if (stream.eat(DOT) && !eatWhile(stream, isNumber)) { | ||
throw stream.error('Unexpected character for alpha value of color'); | ||
} | ||
stream.eat(116) /* t */ || stream.eatWhile(isHex); | ||
const base = stream.current(); | ||
return new Color(base, stream.current()); | ||
} | ||
// a hex color can be followed by `.num` alpha value | ||
stream.start = stream.pos; | ||
if (stream.eat(DOT) && !stream.eatWhile(isNumber)) { | ||
throw stream.error('Unexpected character for alpha value of color'); | ||
} | ||
return new Color(base, stream.current()); | ||
} | ||
}; | ||
class Color { | ||
constructor(value, alpha) { | ||
this.type = 'color'; | ||
this.raw = value; | ||
this.alpha = Number(alpha != null && alpha !== '' ? alpha : 1); | ||
value = value.slice(1); // remove # | ||
constructor(value, alpha) { | ||
this.type = 'color'; | ||
this.raw = value; | ||
this.alpha = Number(alpha != null && alpha !== '' ? alpha : 1); | ||
value = value.slice(1); // remove # | ||
let r = 0, g = 0, b = 0; | ||
let r = 0, g = 0, b = 0; | ||
if (value === 't') { | ||
this.alpha = 0; | ||
} else { | ||
switch (value.length) { | ||
case 0: | ||
break; | ||
if (value === 't') { | ||
this.alpha = 0; | ||
} else { | ||
switch (value.length) { | ||
case 0: | ||
break; | ||
case 1: | ||
r = g = b = value + value; | ||
break; | ||
case 1: | ||
r = g = b = value + value; | ||
break; | ||
case 2: | ||
r = g = b = value; | ||
break; | ||
case 2: | ||
r = g = b = value; | ||
break; | ||
case 3: | ||
r = value[0] + value[0]; | ||
g = value[1] + value[1]; | ||
b = value[2] + value[2]; | ||
break; | ||
case 3: | ||
r = value[0] + value[0]; | ||
g = value[1] + value[1]; | ||
b = value[2] + value[2]; | ||
break; | ||
default: | ||
value += value; | ||
r = value.slice(0, 2); | ||
g = value.slice(2, 4); | ||
b = value.slice(4, 6); | ||
} | ||
} | ||
default: | ||
value += value; | ||
r = value.slice(0, 2); | ||
g = value.slice(2, 4); | ||
b = value.slice(4, 6); | ||
} | ||
} | ||
this.r = parseInt(r, 16); | ||
this.g = parseInt(g, 16); | ||
this.b = parseInt(b, 16); | ||
} | ||
this.r = parseInt(r, 16); | ||
this.g = parseInt(g, 16); | ||
this.b = parseInt(b, 16); | ||
} | ||
/** | ||
* Output current color as hex value | ||
* @param {Boolean} shor Produce short value (e.g. #fff instead of #ffffff), if possible | ||
* @return {String} | ||
*/ | ||
toHex(short) { | ||
const fn = (short && isShortHex(this.r) && isShortHex(this.g) && isShortHex(this.b)) | ||
? toShortHex : toHex; | ||
/** | ||
* Output current color as hex value | ||
* @param {Boolean} shor Produce short value (e.g. #fff instead of #ffffff), if possible | ||
* @return {String} | ||
*/ | ||
toHex(short) { | ||
const fn = (short && isShortHex(this.r) && isShortHex(this.g) && isShortHex(this.b)) | ||
? toShortHex : toHex; | ||
return '#' + fn(this.r) + fn(this.g) + fn(this.b); | ||
} | ||
return '#' + fn(this.r) + fn(this.g) + fn(this.b); | ||
} | ||
/** | ||
* Output current color as `rgba?(...)` CSS color | ||
* @return {String} | ||
*/ | ||
toRGB() { | ||
const values = [this.r, this.g, this.b]; | ||
if (this.alpha !== 1) { | ||
values.push(this.alpha.toFixed(8).replace(/\.?0+$/, '')); | ||
} | ||
/** | ||
* Output current color as `rgba?(...)` CSS color | ||
* @return {String} | ||
*/ | ||
toRGB() { | ||
const values = [this.r, this.g, this.b]; | ||
if (this.alpha !== 1) { | ||
values.push(this.alpha.toFixed(8).replace(/\.?0+$/, '')); | ||
} | ||
return `${values.length === 3 ? 'rgb' : 'rgba'}(${values.join(', ')})`; | ||
} | ||
return `${values.length === 3 ? 'rgb' : 'rgba'}(${values.join(', ')})`; | ||
} | ||
toString(short) { | ||
if (!this.r && !this.g && !this.b && !this.alpha) { | ||
return 'transparent'; | ||
} | ||
return this.alpha === 1 ? this.toHex(short) : this.toRGB(); | ||
} | ||
toString(short) { | ||
if (!this.r && !this.g && !this.b && !this.alpha) { | ||
return 'transparent'; | ||
} | ||
return this.alpha === 1 ? this.toHex(short) : this.toRGB(); | ||
} | ||
} | ||
@@ -215,24 +146,40 @@ | ||
function isHex(code) { | ||
return isNumber(code) || isAlpha(code, 65, 70); // A-F | ||
return isNumber(code) || isAlpha(code, 65, 70); // A-F | ||
} | ||
function isShortHex(hex) { | ||
return !(hex % 17); | ||
return !(hex % 17); | ||
} | ||
function toShortHex(num) { | ||
return (num >> 4).toString(16); | ||
return (num >> 4).toString(16); | ||
} | ||
function toHex(num) { | ||
return pad(num.toString(16), 2); | ||
return pad(num.toString(16), 2); | ||
} | ||
function pad(value, len) { | ||
while (value.length < len) { | ||
value = '0' + value; | ||
} | ||
return value; | ||
while (value.length < len) { | ||
value = '0' + value; | ||
} | ||
return value; | ||
} | ||
/** | ||
* @param {Number} code | ||
* @return {Boolean} | ||
*/ | ||
function isAlphaNumericWord(code) { | ||
return isNumber(code) || isAlphaWord(code); | ||
} | ||
/** | ||
* @param {Number} code | ||
* @return {Boolean} | ||
*/ | ||
function isAlphaWord(code) { | ||
return code === 95 /* _ */ || isAlpha(code); | ||
} | ||
const PERCENT = 37; // % | ||
@@ -249,11 +196,11 @@ const DOT$1 = 46; // . | ||
var consumeNumericValue = function(stream) { | ||
stream.start = stream.pos; | ||
if (eatNumber(stream)) { | ||
const num = stream.current(); | ||
stream.start = stream.pos; | ||
stream.start = stream.pos; | ||
if (eatNumber(stream)) { | ||
const num = stream.current(); | ||
stream.start = stream.pos; | ||
// eat unit, which can be a % or alpha word | ||
stream.eat(PERCENT) || eatAlphaWord(stream); | ||
return new NumericValue(num, stream.current()); | ||
} | ||
// eat unit, which can be a % or alpha word | ||
stream.eat(PERCENT) || stream.eatWhile(isAlphaWord); | ||
return new NumericValue(num, stream.current()); | ||
} | ||
}; | ||
@@ -265,11 +212,11 @@ | ||
class NumericValue { | ||
constructor(value, unit) { | ||
this.type = 'numeric'; | ||
this.value = Number(value); | ||
this.unit = unit || ''; | ||
} | ||
constructor(value, unit) { | ||
this.type = 'numeric'; | ||
this.value = Number(value); | ||
this.unit = unit || ''; | ||
} | ||
toString() { | ||
return `${this.value}${this.unit}`; | ||
} | ||
toString() { | ||
return `${this.value}${this.unit}`; | ||
} | ||
} | ||
@@ -284,9 +231,9 @@ | ||
const start = stream.pos; | ||
const negative = stream.eat(DASH$1); | ||
let hadDot = false, code; | ||
const negative = stream.eat(DASH$1); | ||
let hadDot = false, consumed = false, code; | ||
while (!stream.eol()) { | ||
code = stream.peekCode(); | ||
while (!stream.eof()) { | ||
code = stream.peek(); | ||
// either a second dot or not a number: stop parsing | ||
// either a second dot or not a number: stop parsing | ||
if (code === DOT$1 ? hadDot : !isNumber(code)) { | ||
@@ -296,15 +243,17 @@ break; | ||
if (code === DOT$1) { | ||
hadDot = true; | ||
} | ||
consumed = true; | ||
stream.pos++; | ||
if (code === DOT$1) { | ||
hadDot = true; | ||
} | ||
stream.next(); | ||
} | ||
if (negative && stream.pos - start === 1) { | ||
// edge case: consumed dash only, bail out | ||
stream.pos = start; | ||
} | ||
if (negative && !consumed) { | ||
// edge case: consumed dash only, bail out | ||
stream.pos = start; | ||
} | ||
return start < stream.pos; | ||
return start !== stream.pos; | ||
} | ||
@@ -327,14 +276,14 @@ | ||
var consumeKeyword = function(stream, short) { | ||
stream.start = stream.pos; | ||
stream.start = stream.pos; | ||
if (stream.eat(DOLLAR$1) || stream.eat(AT$1)) { | ||
// SCSS or LESS variable | ||
eatAlphaNumericWord(stream); | ||
} else if (short) { | ||
eatAlphaWord(stream); | ||
} else { | ||
eatKeyword(stream); | ||
if (stream.eat(DOLLAR$1) || stream.eat(AT$1)) { | ||
// SCSS or LESS variable | ||
stream.eatWhile(isAlphaNumericWord); | ||
} else if (short) { | ||
stream.eatWhile(isAlphaWord); | ||
} else { | ||
stream.eatWhile(isKeyword); | ||
} | ||
return stream.start !== stream.pos ? new Keyword(stream.current()) : null; | ||
return stream.start !== stream.pos ? new Keyword(stream.current()) : null; | ||
}; | ||
@@ -353,6 +302,2 @@ | ||
function eatKeyword(stream) { | ||
return eatWhile(stream, isKeyword); | ||
} | ||
function isKeyword(code) { | ||
@@ -362,4 +307,3 @@ return isAlphaNumericWord(code) || code === DASH$2; | ||
const SINGLE_QUOTE = 39; // ' | ||
const DOUBLE_QUOTE = 34; // " | ||
const opt = { throws: true }; | ||
@@ -372,7 +316,3 @@ /** | ||
var consumeQuoted = function(stream) { | ||
const code = stream.peekCode(); | ||
if (code === SINGLE_QUOTE || code === DOUBLE_QUOTE) { | ||
stream.start = stream.pos++; | ||
stream.eatQuoted(code); | ||
if (eatQuoted(stream, opt)) { | ||
return new QuotedString(stream.current()); | ||
@@ -393,4 +333,2 @@ } | ||
const TAB = 9; // \t | ||
const SPACE = 32; // ( | ||
const LBRACE = 40; // ( | ||
@@ -408,11 +346,11 @@ const RBRACE = 41; // ) | ||
function consumeArgumentList(stream) { | ||
if (!stream.eat(LBRACE)) { | ||
// not an argument list | ||
return null; | ||
} | ||
if (!stream.eat(LBRACE)) { | ||
// not an argument list | ||
return null; | ||
} | ||
let level = 1, code, arg; | ||
const argsList = []; | ||
let level = 1, code, arg; | ||
const argsList = []; | ||
while (!stream.eol()) { | ||
while (!stream.eof()) { | ||
if (arg = consumeArgument(stream)) { | ||
@@ -422,3 +360,3 @@ argsList.push(arg); | ||
// didn’t consumed argument, expect argument separator or end-of-arguments | ||
eatSpace(stream); | ||
stream.eatWhile(isWhiteSpace); | ||
@@ -434,5 +372,5 @@ if (stream.eat(RBRACE)) { | ||
} | ||
} | ||
} | ||
return argsList; | ||
return argsList; | ||
} | ||
@@ -450,4 +388,4 @@ | ||
while (!stream.eol()) { | ||
eatSpace(stream); | ||
while (!stream.eof()) { | ||
stream.eatWhile(isWhiteSpace); | ||
value = consumeNumericValue(stream) || consumeColor(stream) | ||
@@ -495,19 +433,2 @@ || consumeQuoted(stream) || consumeKeywordOrFunction(stream); | ||
/** | ||
* Check if given character code is a white-space code | ||
* @param {Number} code | ||
* @return {Boolean} | ||
*/ | ||
function isWhite(code) { | ||
return code === SPACE || code === TAB; | ||
} | ||
/** | ||
* @param {StreamReader} stream | ||
* @return {Boolean} Returns `true` if space was consumed | ||
*/ | ||
function eatSpace(stream) { | ||
return eatWhile(stream, isWhite); | ||
} | ||
const EXCL = 33; // ! | ||
@@ -525,38 +446,38 @@ const DOLLAR = 36; // $ | ||
var index = function(abbr) { | ||
const root = new Node(); | ||
const stream = new StreamReader(abbr); | ||
let node; | ||
const root = new Node(); | ||
const stream = new StreamReader(abbr); | ||
let node; | ||
while (!stream.eol()) { | ||
let node = new Node(consumeIdent(stream)); | ||
node.value = consumeValue(stream); | ||
while (!stream.eof()) { | ||
let node = new Node(consumeIdent(stream)); | ||
node.value = consumeValue(stream); | ||
const args = consumeArgumentList(stream); | ||
if (args) { | ||
// technically, arguments in CSS are anonymous Emmet Node attributes, | ||
// but since Emmet can support only one anonymous, `null`-name | ||
// attribute (for good reasons), we’ll use argument index as name | ||
for (let i = 0; i < args.length; i++) { | ||
node.setAttribute(String(i), args[i]); | ||
} | ||
} | ||
const args = consumeArgumentList(stream); | ||
if (args) { | ||
// technically, arguments in CSS are anonymous Emmet Node attributes, | ||
// but since Emmet can support only one anonymous, `null`-name | ||
// attribute (for good reasons), we’ll use argument index as name | ||
for (let i = 0; i < args.length; i++) { | ||
node.setAttribute(String(i), args[i]); | ||
} | ||
} | ||
// Consume `!important` modifier at the end of expression | ||
if (stream.eat(EXCL)) { | ||
node.value.add('!'); | ||
} | ||
// Consume `!important` modifier at the end of expression | ||
if (stream.eat(EXCL)) { | ||
node.value.add('!'); | ||
} | ||
root.appendChild(node); | ||
root.appendChild(node); | ||
// CSS abbreviations cannot be nested, only listed | ||
if (!stream.eat(PLUS)) { | ||
break; | ||
} | ||
} | ||
// CSS abbreviations cannot be nested, only listed | ||
if (!stream.eat(PLUS)) { | ||
break; | ||
} | ||
} | ||
if (!stream.eol()) { | ||
throw stream.error('Unexpected character'); | ||
} | ||
if (!stream.eof()) { | ||
throw stream.error('Unexpected character'); | ||
} | ||
return root; | ||
return root; | ||
}; | ||
@@ -570,6 +491,6 @@ | ||
function consumeIdent(stream) { | ||
stream.start = stream.pos; | ||
eatWhile(stream, isIdentPrefix); | ||
eatWhile(stream, isIdent); | ||
return stream.start !== stream.pos ? stream.current() : null; | ||
stream.start = stream.pos; | ||
stream.eatWhile(isIdentPrefix); | ||
stream.eatWhile(isIdent); | ||
return stream.start !== stream.pos ? stream.current() : null; | ||
} | ||
@@ -583,25 +504,25 @@ | ||
function consumeValue(stream) { | ||
const values = new CSSValue(); | ||
let value; | ||
const values = new CSSValue(); | ||
let value; | ||
while (!stream.eol()) { | ||
if (value = consumeNumericValue(stream) || consumeColor(stream)) { | ||
// edge case: a dash after unit-less numeric value or color should | ||
// be treated as value separator, not negative sign | ||
if (!value.unit) { | ||
stream.eat(DASH); | ||
} | ||
} else { | ||
stream.eat(DASH); | ||
value = consumeKeyword(stream, true); | ||
} | ||
while (!stream.eof()) { | ||
if (value = consumeNumericValue(stream) || consumeColor(stream)) { | ||
// edge case: a dash after unit-less numeric value or color should | ||
// be treated as value separator, not negative sign | ||
if (!value.unit) { | ||
stream.eat(DASH); | ||
} | ||
} else { | ||
stream.eat(DASH); | ||
value = consumeKeyword(stream, true); | ||
} | ||
if (!value) { | ||
if (!value) { | ||
break; | ||
} | ||
} | ||
values.add(value); | ||
} | ||
} | ||
return values; | ||
return values; | ||
} | ||
@@ -614,3 +535,3 @@ | ||
function isIdent(code) { | ||
return isAlphaWord(code); | ||
return isAlphaWord(code); | ||
} | ||
@@ -617,0 +538,0 @@ |
{ | ||
"name": "@emmetio/css-abbreviation", | ||
"version": "0.2.2", | ||
"version": "0.3.0", | ||
"description": "Parses Emmet CSS abbreviatoin into AST tree", | ||
@@ -25,3 +25,4 @@ "main": "dist/css-abbreviation.cjs.js", | ||
"@emmetio/node": "^0.1.0", | ||
"@emmetio/stream-reader": "^1.0.0" | ||
"@emmetio/stream-reader": "^2.0.0", | ||
"@emmetio/stream-reader-utils": "^0.1.0" | ||
}, | ||
@@ -28,0 +29,0 @@ "devDependencies": { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
24836
3
880
+ Added@emmetio/stream-reader@2.2.0(transitive)
+ Added@emmetio/stream-reader-utils@0.1.0(transitive)
- Removed@emmetio/stream-reader@1.0.0(transitive)