You're Invited:Meet the Socket Team at BlackHat and DEF CON in Las Vegas, Aug 4-6.RSVP
Socket
Book a DemoInstallSign in
Socket

css-tree

Package Overview
Dependencies
Maintainers
1
Versions
58
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

css-tree - npm Package Compare versions

Comparing version

to
1.0.0-alpha14

dist/default-syntax.json

@@ -0,1 +1,35 @@

## 1.0.0-alpha14 (February 3, 2017)
- Implemented `DeclarationList`, `MediaQueryList`, `MediaQuery`, `MediaFeature` and `Ratio` node types
- Implemented `declarationList` context (useful to parse HTML `style` attribute content)
- Implemented custom consumers for `@import`, `@media`, `@page` and `@supports` at-rules
- Implemented `atrule` option for `parse()` config, is used for `atruleExpession` context to specify custom consumer for at-rule if any
- Added `Scanner#skipWS()`, `Scanner#eatNonWS()`, `Scanner#consume()` and `Scanner#consumeNonWS()` helper methods
- Added custom consumers for known functional-pseudos, consume unknown functional-pseudo content as balanced `Raw`
- Allowed any `PseudoElement` to be a functional-pseudo (#33)
- Improved walker implementations to reduce GC thrashing by reusing cursors
- Changed `Atrule.block` to contain a `Block` node type only if any
- Changed `Block.loc` positions to include curly brackets
- Changed `Atrule.expression` to store a `null` if no expression
- Changed parser to use `StyleSheet` node type only for top level node (when context is `stylesheet`, that's by default)
- Changed `Parentheses`, `Brackets` and `Function` consumers to use passed sequence reader instead its own
- Changed `Value` and `AtruleExpression` consumers to use common sequence reader (that reader was used by `Value` consumer only)
- Changed default sequence reader to exclude storage of spaces around `Comma`
- Changed processing of custom properties
- Consume custom property value as balanced `Raw`
- Consume `var()` fallback value as balanced `Raw`
- Validate first argument of `var()` is custom property name (i.e. starts with double dash)
- Custom property's value and fallback includes spaces around its value
- Fixed `Nth` to have a `loc` property
- Fixed `SelectorList.loc` and `Selector.loc` positions to exclude spaces
- Fixed issue Browserify build fail with `default-syntax.json` is not found error (#32, @philschatz)
- Disallowed `Type` selector starting with dash (parser throws an error in this case now)
- Disallowed empty selectors for `Rule` (not sure if it's correct but looks reasonable)
- Removed `>>` combinator support until any browser support (no signals about that yet)
- Removed `PseudoElement.legacy` property
- Removed special case for `:before`, `:after`, `:first-letter` and `:first-line` to represent them as `PseudoElement`, now those pseudos are represented as `PseudoClass` nodes
- Removed deprecated `Syntax#match()` method
- Splitted parser into modules and related changes, one step closer to an extensible parser
- Various fixes and improvements, all changes have negligible impact on performace
## 1.0.0-alpha13 (January 19, 2017)

@@ -2,0 +36,0 @@

2103

lib/parser/index.js
'use strict';
var List = require('../utils/list');
var Scanner = require('../scanner');
var scanner = new Scanner();
var cmpChar = Scanner.cmpChar;
var endsWith = Scanner.endsWith;
var isNumber = Scanner.isNumber;
var isHex = Scanner.isHex;
var needPositions;
var filename;
var TYPE = Scanner.TYPE;
var WHITESPACE = TYPE.Whitespace;
var IDENTIFIER = TYPE.Identifier;
var COMMENT = TYPE.Comment;
var HYPHENMINUS = TYPE.HyphenMinus;
var DESCENDANT_COMBINATOR = {};
var SPACE_NODE = { type: 'Space' };
var NESTED = true;
var ABSOLUTE = false;
var RELATIVE = true;
var sequence = require('./sequence');
var getAnPlusB = require('./type/An+B');
var getAtrule = require('./type/Atrule');
var getAtruleExpression = require('./type/AtruleExpression');
var getAttribute = require('./type/Attribute');
var getBlock = require('./type/Block');
var getBrackets = require('./type/Brackets');
var getClass = require('./type/Class');
var getCombinator = require('./type/Combinator');
var getComment = require('./type/Comment');
var getDeclaration = require('./type/Declaration');
var getDeclarationList = require('./type/DeclarationList');
var getDimension = require('./type/Dimention');
var getFunction = require('./type/Function');
var getHash = require('./type/Hash');
var getId = require('./type/Id');
var getIdentifier = require('./type/Identifier');
var getMediaFeature = require('./type/MediaFeature');
var getMediaQuery = require('./type/MediaQuery');
var getMediaQueryList = require('./type/MediaQueryList');
var getNth = require('./type/Nth');
var getNumber = require('./type/Number');
var getOperator = require('./type/Operator');
var getParentheses = require('./type/Parentheses');
var getPercentage = require('./type/Percentage');
var getProgid = require('./type/Progid');
var getPseudoClass = require('./type/PseudoClass');
var getPseudoElement = require('./type/PseudoElement');
var getRatio = require('./type/Ratio');
var getRaw = require('./type/Raw');
var getRule = require('./type/Rule');
var getSelector = require('./type/Selector');
var getSelectorList = require('./type/SelectorList');
var getString = require('./type/String');
var getStyleSheet = require('./type/StyleSheet');
var getType = require('./type/Type');
var getUnicodeRange = require('./type/UnicodeRange');
var getUniversal = require('./type/Universal');
var getUrl = require('./type/Url');
var getValue = require('./type/Value');
var WHITESPACE = Scanner.TYPE.Whitespace;
var IDENTIFIER = Scanner.TYPE.Identifier;
var NUMBER = Scanner.TYPE.Number;
var STRING = Scanner.TYPE.String;
var COMMENT = Scanner.TYPE.Comment;
var EXCLAMATIONMARK = Scanner.TYPE.ExclamationMark;
var NUMBERSIGN = Scanner.TYPE.NumberSign;
var DOLLARSIGN = Scanner.TYPE.DollarSign;
var PERCENTSIGN = Scanner.TYPE.PercentSign;
var LEFTPARENTHESIS = Scanner.TYPE.LeftParenthesis;
var RIGHTPARENTHESIS = Scanner.TYPE.RightParenthesis;
var ASTERISK = Scanner.TYPE.Asterisk;
var PLUSSIGN = Scanner.TYPE.PlusSign;
var COMMA = Scanner.TYPE.Comma;
var HYPHENMINUS = Scanner.TYPE.HyphenMinus;
var FULLSTOP = Scanner.TYPE.FullStop;
var SOLIDUS = Scanner.TYPE.Solidus;
var COLON = Scanner.TYPE.Colon;
var SEMICOLON = Scanner.TYPE.Semicolon;
var EQUALSSIGN = Scanner.TYPE.EqualsSign;
var GREATERTHANSIGN = Scanner.TYPE.GreaterThanSign;
var QUESTIONMARK = Scanner.TYPE.QuestionMark;
var COMMERCIALAT = Scanner.TYPE.CommercialAt;
var LEFTSQUAREBRACKET = Scanner.TYPE.LeftSquareBracket;
var RIGHTSQUAREBRACKET = Scanner.TYPE.RightSquareBracket;
var CIRCUMFLEXACCENT = Scanner.TYPE.CircumflexAccent;
var LEFTCURLYBRACKET = Scanner.TYPE.LeftCurlyBracket;
var VERTICALLINE = Scanner.TYPE.VerticalLine;
var RIGHTCURLYBRACKET = Scanner.TYPE.RightCurlyBracket;
var TILDE = Scanner.TYPE.Tilde;
var N = 110; // 'n'.charCodeAt(0)
var SCOPE_ATRULE_EXPRESSION = {
url: getUri
};
var SCOPE_SELECTOR = {
url: getUri,
not: getSelectorListFunction,
matches: getSelectorListFunction,
has: getRelativeSelectorListFunction
};
var SCOPE_VALUE = {
url: getUri,
expression: getOldIEExpression,
var: getVarFunction
};
var CONTEXT = {
stylesheet: getStylesheet,
atrule: getAtrule,
atruleExpression: getAtruleExpression,
rule: getRule,
selectorList: getSelectorList,
selector: getSelector,
block: getBlock,
declaration: getDeclaration,
value: getValue
};
function getLocation(start, end) {
if (needPositions) {
return scanner.getLocationRange(
start,
end,
filename
);
}
return null;
}
function getStylesheet(nested) {
var start = scanner.tokenStart;
var children = new List();
var child;
scan:
while (!scanner.eof) {
switch (scanner.tokenType) {
case WHITESPACE:
scanner.next();
continue;
case RIGHTCURLYBRACKET:
if (!nested) {
scanner.error('Unexpected right curly brace');
}
break scan;
case COMMENT:
// ignore comments except exclamation comments (i.e. /*! .. */) on top level
if (nested || scanner.source.charCodeAt(scanner.tokenStart + 2) !== EXCLAMATIONMARK) {
scanner.next();
continue;
}
child = getComment();
break;
case COMMERCIALAT:
child = getAtrule();
break;
default:
child = getRule();
}
children.appendData(child);
}
return {
type: 'StyleSheet',
loc: getLocation(start, scanner.tokenStart),
children: children
};
}
function isBlockAtrule() {
for (var offset = 1, type; type = scanner.lookupType(offset); offset++) {
if (type === RIGHTCURLYBRACKET) {
return true;
}
if (type === LEFTCURLYBRACKET ||
type === COMMERCIALAT) {
return false;
}
}
return true;
}
function getAtruleExpression() {
var start;
var children = new List();
var wasSpace = false;
var lastNonSpace = null;
var child;
readSC();
start = scanner.tokenStart;
scan:
while (!scanner.eof) {
switch (scanner.tokenType) {
case SEMICOLON:
case LEFTCURLYBRACKET:
break scan;
case WHITESPACE:
wasSpace = true;
scanner.next();
continue;
case COMMENT: // ignore comments
scanner.next();
continue;
case COMMA:
child = getOperator();
break;
case COLON:
child = getPseudo();
break;
case LEFTPARENTHESIS:
child = getParentheses(SCOPE_ATRULE_EXPRESSION);
break;
case STRING:
child = getString();
break;
default:
child = getAny(SCOPE_ATRULE_EXPRESSION);
}
if (wasSpace) {
wasSpace = false;
children.appendData(SPACE_NODE);
}
lastNonSpace = scanner.tokenStart;
children.appendData(child);
}
return {
type: 'AtruleExpression',
loc: getLocation(start, lastNonSpace !== null ? lastNonSpace : scanner.tokenStart),
children: children
};
}
function getAtrule() {
var start = scanner.tokenStart;
var name;
var expression;
var block = null;
scanner.eat(COMMERCIALAT);
name = readIdent(false);
expression = getAtruleExpression();
switch (scanner.tokenType) {
case SEMICOLON:
scanner.next(); // ;
break;
case LEFTCURLYBRACKET:
scanner.next(); // {
block = isBlockAtrule()
? getBlock()
: getStylesheet(NESTED);
scanner.eat(RIGHTCURLYBRACKET);
break;
// at-rule expression can ends with semicolon, left curly bracket or eof - no other options
}
return {
type: 'Atrule',
loc: getLocation(start, scanner.tokenStart),
name: name,
expression: expression,
block: block
};
}
function getRule() {
var start = scanner.tokenStart;
var selector = getSelectorList();
var block;
scanner.eat(LEFTCURLYBRACKET);
block = getBlock();
scanner.eat(RIGHTCURLYBRACKET);
return {
type: 'Rule',
loc: getLocation(start, scanner.tokenStart),
selector: selector,
block: block
};
}
function getSelectorList(nested, relative) {
var start = scanner.tokenStart;
var children = new List();
var selector;
var lastComma = -2;
scan:
while (!scanner.eof) {
switch (scanner.tokenType) {
case LEFTCURLYBRACKET:
if (nested) {
scanner.error();
}
break scan;
case RIGHTPARENTHESIS:
if (!nested) {
scanner.error();
}
break scan;
case COMMA:
if (lastComma !== -1) {
scanner.error('Unexpected comma');
}
lastComma = scanner.tokenStart;
scanner.next();
break;
default:
lastComma = -1;
selector = getSelector(nested, relative);
children.appendData(selector);
if (selector.children.isEmpty()) {
scanner.error('Simple selector expected');
}
}
}
if (lastComma !== -1 && lastComma !== -2) { // TODO: fail on empty selector rules?
scanner.error('Unexpected trailing comma', lastComma);
}
return {
type: 'SelectorList',
loc: getLocation(start, scanner.tokenStart),
children: children
};
}
function getSelector(nested, relative) {
var start = scanner.tokenStart;
var children = new List();
var combinator = null;
var combinatorOffset = -1;
var child;
scan:
while (!scanner.eof) {
switch (scanner.tokenType) {
case COMMA:
break scan;
case LEFTCURLYBRACKET:
if (nested) {
scanner.error();
}
break scan;
case RIGHTPARENTHESIS:
if (!nested) {
scanner.error();
}
break scan;
case COMMENT:
scanner.next();
continue;
case WHITESPACE:
if (combinator === null && children.head !== null) {
combinatorOffset = scanner.tokenStart;
combinator = DESCENDANT_COMBINATOR;
} else {
scanner.next();
}
continue;
case PLUSSIGN:
case GREATERTHANSIGN:
case TILDE:
case SOLIDUS:
if ((children.head === null && !relative) || // combinator in the beginning
(combinator !== null && combinator !== DESCENDANT_COMBINATOR)) {
scanner.error('Unexpected combinator');
}
combinatorOffset = scanner.tokenStart;
combinator = getCombinator();
continue;
case FULLSTOP:
child = getClass();
break;
case LEFTSQUAREBRACKET:
child = getAttribute();
break;
case NUMBERSIGN:
child = getId();
break;
case COLON:
child = getPseudo();
break;
case HYPHENMINUS:
case IDENTIFIER:
case ASTERISK:
case VERTICALLINE:
child = getTypeOrUniversal();
break;
case NUMBER:
child = getPercentage();
break;
default:
scanner.error();
}
if (combinator !== null) {
// create descendant combinator on demand to avoid garbage
if (combinator === DESCENDANT_COMBINATOR) {
combinator = {
type: 'Combinator',
loc: getLocation(combinatorOffset, combinatorOffset + 1),
name: ' '
};
}
children.appendData(combinator);
combinator = null;
}
children.appendData(child);
}
if (combinator !== null && combinator !== DESCENDANT_COMBINATOR) {
scanner.error('Unexpected combinator', combinatorOffset);
}
return {
type: 'Selector',
loc: getLocation(start, scanner.tokenStart),
children: children
};
}
function getCompoundSelector(nested) {
var start = scanner.tokenStart;
var children = new List();
var child;
readSC();
scan:
while (!scanner.eof) {
switch (scanner.tokenType) {
case COMMA:
break scan;
case LEFTCURLYBRACKET:
if (nested) {
scanner.error();
}
break scan;
case RIGHTPARENTHESIS:
if (!nested) {
scanner.error();
}
break scan;
case COMMENT:
scanner.next();
continue;
case WHITESPACE:
readSC();
break scan;
case FULLSTOP:
child = getClass();
break;
case LEFTSQUAREBRACKET:
child = getAttribute();
break;
case NUMBERSIGN:
child = getId();
break;
case COLON:
child = getPseudo();
break;
case HYPHENMINUS:
case IDENTIFIER:
case ASTERISK:
case VERTICALLINE:
child = getTypeOrUniversal();
break;
default:
scanner.error();
}
children.appendData(child);
}
if (children.isEmpty()) {
scanner.error('Simple selector expected');
}
return {
type: 'Selector',
loc: getLocation(start, scanner.tokenStart),
children: children
};
}
function getBlock() {
var start = scanner.tokenStart;
var children = new List();
scan:
while (!scanner.eof) {
switch (scanner.tokenType) {
case RIGHTCURLYBRACKET:
break scan;
case WHITESPACE:
case COMMENT:
case SEMICOLON:
scanner.next();
break;
case COMMERCIALAT:
children.appendData(getAtrule());
break;
default:
children.appendData(getDeclaration());
}
}
return {
type: 'Block',
loc: getLocation(start, scanner.tokenStart),
children: children
};
}
function getDeclaration(nested) {
var start = scanner.tokenStart;
var property = readProperty();
var important = false;
var value;
readSC();
scanner.eat(COLON);
value = getValue(nested, property);
if (scanner.tokenType === EXCLAMATIONMARK) {
important = getImportant();
}
// TODO: include or not to include semicolon to range?
// if (scanner.tokenType === SEMICOLON) {
// scanner.next();
// }
return {
type: 'Declaration',
loc: getLocation(start, scanner.tokenStart),
important: important,
property: property,
value: value
};
}
function readProperty() {
var start = scanner.tokenStart;
var type;
for (; type = scanner.tokenType; scanner.next()) {
if (type !== SOLIDUS &&
type !== ASTERISK &&
type !== DOLLARSIGN) {
break;
}
}
scanIdent(true);
return scanner.substrToCursor(start);
}
function getValue(nested, property) {
// special parser for filter property since it can contains non-standart syntax for old IE
if (property !== null && endsWith(property, 'filter') && checkProgid()) {
return getFilterValue();
}
var start = scanner.tokenStart;
var children = new List();
var wasSpace = false;
var child;
readSC();
scan:
while (!scanner.eof) {
switch (scanner.tokenType) {
case RIGHTCURLYBRACKET:
case SEMICOLON:
case EXCLAMATIONMARK:
break scan;
case RIGHTPARENTHESIS:
if (!nested) {
scanner.error();
}
break scan;
case WHITESPACE:
wasSpace = true;
scanner.next();
continue;
case COMMENT: // ignore comments
scanner.next();
continue;
case NUMBERSIGN:
child = getHash();
break;
case SOLIDUS:
case COMMA:
child = getOperator();
break;
case LEFTPARENTHESIS:
child = getParentheses(SCOPE_VALUE);
break;
case LEFTSQUAREBRACKET:
child = getBrackets();
break;
case STRING:
child = getString();
break;
default:
// check for unicode range: U+0F00, U+0F00-0FFF, u+0F00??
if (scanner.tokenType === IDENTIFIER &&
scanner.lookupValue(0, 'u')) {
if (
scanner.lookupType(1) === PLUSSIGN || (
scanner.lookupType(1) === NUMBER &&
cmpChar(scanner.source, scanner.tokenEnd, PLUSSIGN)
)) {
child = getUnicodeRange();
break;
}
}
child = getAny(SCOPE_VALUE);
}
if (wasSpace) {
wasSpace = false;
children.appendData(SPACE_NODE);
}
children.appendData(child);
}
return {
type: 'Value',
loc: getLocation(start, scanner.tokenStart),
children: children
};
}
function getFilterValue() {
var start = scanner.tokenStart;
var children = new List();
var progid;
while (progid = checkProgid()) {
readSC();
children.appendData(getProgid(progid));
}
readSC();
return {
type: 'Value',
loc: getLocation(start, scanner.tokenStart),
children: children
};
}
// any = percentage | dimension | number | operator | ident | function
function getAny(scope) {
switch (scanner.tokenType) {
case IDENTIFIER:
break;
case HYPHENMINUS:
var nextType = scanner.lookupType(1);
if (nextType === IDENTIFIER || nextType === HYPHENMINUS) {
break;
}
return getOperator();
case PLUSSIGN:
return getOperator();
case NUMBER:
switch (scanner.lookupType(1)) {
case PERCENTSIGN:
return getPercentage();
case IDENTIFIER:
return getDimension();
default:
return {
type: 'Number',
loc: getLocation(scanner.tokenStart, scanner.tokenEnd),
value: readNumber()
};
}
default:
scanner.error();
}
var start = scanner.tokenStart;
scanIdent(false);
if (scanner.tokenType === LEFTPARENTHESIS) {
return getFunction(scope, start, scanner.substrToCursor(start));
}
return {
type: 'Identifier',
loc: getLocation(start, scanner.tokenStart),
name: scanner.substrToCursor(start)
};
}
function readAttrselector() {
var start = scanner.tokenStart;
var tokenType = scanner.tokenType;
if (tokenType !== EQUALSSIGN && // =
tokenType !== TILDE && // ~=
tokenType !== CIRCUMFLEXACCENT && // ^=
tokenType !== DOLLARSIGN && // $=
tokenType !== ASTERISK && // *=
tokenType !== VERTICALLINE // |=
) {
scanner.error('Attribute selector (=, ~=, ^=, $=, *=, |=) is expected');
}
if (tokenType === EQUALSSIGN) {
scanner.next();
} else {
scanner.next();
scanner.eat(EQUALSSIGN);
}
return scanner.substrToCursor(start);
}
// '[' S* attrib_name ']'
// '[' S* attrib_name S* attrib_match S* [ IDENT | STRING ] S* attrib_flags? S* ']'
function getAttribute() {
var start = scanner.tokenStart;
var name;
var operator = null;
var value = null;
var flags = null;
scanner.eat(LEFTSQUAREBRACKET);
readSC();
name = getAttributeName();
readSC();
if (scanner.tokenType !== RIGHTSQUAREBRACKET) {
// avoid case `[name i]`
if (scanner.tokenType !== IDENTIFIER) {
operator = readAttrselector();
readSC();
value = scanner.tokenType === STRING
? getString()
: getIdentifier(false);
readSC();
}
// attribute flags
if (scanner.tokenType === IDENTIFIER) {
flags = scanner.getTokenValue();
scanner.next();
readSC();
}
}
scanner.eat(RIGHTSQUAREBRACKET);
return {
type: 'Attribute',
loc: getLocation(start, scanner.tokenStart),
name: name,
operator: operator,
value: value,
flags: flags
};
}
function getParentheses(scope) {
var start = scanner.tokenStart;
var children = new List();
var wasSpace = false;
var child;
// left parenthesis
scanner.eat(LEFTPARENTHESIS);
readSC();
scan:
while (!scanner.eof) {
switch (scanner.tokenType) {
case RIGHTPARENTHESIS:
break scan;
case WHITESPACE:
wasSpace = true;
scanner.next();
continue;
case COMMENT: // ignore comments
scanner.next();
continue;
case LEFTPARENTHESIS:
child = getParentheses(scope);
break;
case SOLIDUS:
case ASTERISK:
case COMMA:
case COLON:
child = getOperator();
break;
case NUMBERSIGN:
child = getHash();
break;
case STRING:
child = getString();
break;
default:
child = getAny(scope);
}
if (wasSpace) {
wasSpace = false;
children.appendData(SPACE_NODE);
}
children.appendData(child);
}
// right parenthesis
scanner.eat(RIGHTPARENTHESIS);
return {
type: 'Parentheses',
loc: getLocation(start, scanner.tokenStart),
children: children
};
}
// currently only Grid Layout uses square brackets
// https://drafts.csswg.org/css-grid/#track-sizing
// [ ident* ]
function getBrackets() {
var start = scanner.tokenStart;
var children = new List();
var wasSpace = false;
var child;
// left bracket
scanner.eat(LEFTSQUAREBRACKET);
readSC();
scan:
while (!scanner.eof) {
switch (scanner.tokenType) {
case RIGHTSQUAREBRACKET:
break scan;
case WHITESPACE:
wasSpace = true;
scanner.next();
continue;
case COMMENT: // ignore comments
scanner.next();
continue;
default:
child = getIdentifier(false);
}
if (wasSpace) {
wasSpace = false;
children.appendData(SPACE_NODE);
}
children.appendData(child);
}
// right bracket
scanner.eat(RIGHTSQUAREBRACKET);
return {
type: 'Brackets',
loc: getLocation(start, scanner.tokenStart),
children: children
};
}
// '.' ident
function getClass() {
var start = scanner.tokenStart;
var name;
scanner.eat(FULLSTOP);
name = readIdent(false);
return {
type: 'Class',
loc: getLocation(start, scanner.tokenStart),
name: name
};
}
// '#' ident
function getId() {
var start = scanner.tokenStart;
var name;
scanner.eat(NUMBERSIGN);
name = readIdent(false);
return {
type: 'Id',
loc: getLocation(start, scanner.tokenStart),
name: name
};
}
// + | > | ~ | /deep/ | >>
function getCombinator() {
var start = scanner.tokenStart;
var combinator;
switch (scanner.tokenType) {
case GREATERTHANSIGN:
scanner.next();
if (scanner.tokenType === GREATERTHANSIGN) {
combinator = '>>';
scanner.next();
} else {
combinator = '>';
}
break;
case PLUSSIGN:
case TILDE:
combinator = scanner.getTokenValue();
scanner.next();
break;
case SOLIDUS:
combinator = '/deep/';
scanner.next();
scanner.expectIdentifier('deep');
scanner.eat(SOLIDUS);
break;
}
return {
type: 'Combinator',
loc: getLocation(start, scanner.tokenStart),
name: combinator
};
}
// '/*' .* '*/'
function getComment() {
var start = scanner.tokenStart;
var end = scanner.tokenEnd;
if ((end - start + 2) >= 2 &&
scanner.source.charCodeAt(end - 2) === ASTERISK &&
scanner.source.charCodeAt(end - 1) === SOLIDUS) {
end -= 2;
}
scanner.next();
return {
type: 'Comment',
loc: getLocation(start, scanner.tokenStart),
value: scanner.source.substring(start + 2, end)
};
}
// special reader for units to avoid adjoined IE hacks (i.e. '1px\9')
function readUnit() {
var unit = scanner.getTokenValue();
var backSlashPos = unit.indexOf('\\');
if (backSlashPos !== -1) {
// patch token offset
scanner.tokenStart += backSlashPos;
// scanner.token.start = scanner.tokenStart;
// return part before backslash
return unit.substring(0, backSlashPos);
}
// no backslash in unit name
scanner.next();
return unit;
}
// number ident
function getDimension() {
var start = scanner.tokenStart;
var value = readNumber();
var unit = readUnit();
return {
type: 'Dimension',
loc: getLocation(start, scanner.tokenStart),
value: value,
unit: unit
};
}
function getPercentage() {
var start = scanner.tokenStart;
var number = readNumber();
scanner.eat(PERCENTSIGN);
return {
type: 'Percentage',
loc: getLocation(start, scanner.tokenStart),
value: number
};
}
// ident '(' functionBody ')'
function getFunction(scope, start, name) {
// parse special functions
var nameLowerCase = name.toLowerCase();
switch (scope) {
case SCOPE_SELECTOR:
if (SCOPE_SELECTOR.hasOwnProperty(nameLowerCase)) {
return SCOPE_SELECTOR[nameLowerCase](scope, start, name);
}
break;
case SCOPE_VALUE:
if (SCOPE_VALUE.hasOwnProperty(nameLowerCase)) {
return SCOPE_VALUE[nameLowerCase](scope, start, name);
}
break;
case SCOPE_ATRULE_EXPRESSION:
if (SCOPE_ATRULE_EXPRESSION.hasOwnProperty(nameLowerCase)) {
return SCOPE_ATRULE_EXPRESSION[nameLowerCase](scope, start, name);
}
break;
}
return getFunctionInternal(getFunctionArguments, scope, start, name);
}
function getFunctionInternal(readSequence, scope, start, name) {
var children;
scanner.eat(LEFTPARENTHESIS);
children = readSequence(scope);
scanner.eat(RIGHTPARENTHESIS);
return {
type: scope === SCOPE_SELECTOR ? 'PseudoClass' : 'Function',
loc: getLocation(start, scanner.tokenStart),
name: name,
children: children
};
}
function getFunctionArguments(scope) {
var children = new List();
var wasSpace = false;
var prevNonSpaceOperator = false;
var nonSpaceOperator = false;
var child;
readSC();
scan:
while (!scanner.eof) {
switch (scanner.tokenType) {
case RIGHTPARENTHESIS:
break scan;
case WHITESPACE:
wasSpace = true;
scanner.next();
continue;
case COMMENT: // ignore comments
scanner.next();
continue;
case NUMBERSIGN: // TODO: not sure it should be here
child = getHash();
break;
case LEFTPARENTHESIS:
child = getParentheses(scope);
break;
case COMMA:
case SOLIDUS:
case ASTERISK:
wasSpace = false;
nonSpaceOperator = true;
child = getOperator();
break;
case STRING:
child = getString();
break;
default:
child = getAny(scope);
}
if (wasSpace) {
wasSpace = false;
// ignore spaces around operator
if (!nonSpaceOperator && !prevNonSpaceOperator) {
children.appendData(SPACE_NODE);
}
}
children.appendData(child);
prevNonSpaceOperator = nonSpaceOperator;
nonSpaceOperator = false;
}
return children;
}
function getSelectorListFunction(scope, start, name) {
return getFunctionInternal(function() {
return new List().appendData(getSelectorList(NESTED, ABSOLUTE));
}, scope, start, name);
}
function getRelativeSelectorListFunction(scope, start, name) {
return getFunctionInternal(function() {
return new List().appendData(getSelectorList(NESTED, RELATIVE));
}, scope, start, name);
}
function getVarFunction(scope, start, name) {
return getFunctionInternal(getVarFunctionArguments, scope, start, name);
}
// var '(' ident (',' <declaration-value>)? ')'
function getVarFunctionArguments() { // TODO: special type Variable?
var children = new List();
readSC();
children.appendData(getIdentifier(true));
readSC();
if (scanner.tokenType === COMMA) {
children.appendData(getOperator());
readSC();
children.appendData(getValue(true, null));
readSC();
}
return children;
}
// url '(' ws* (string | raw) ws* ')'
function getUri(scope, start) {
var value;
scanner.eat(LEFTPARENTHESIS); // (
readSC();
if (scanner.tokenType === STRING) {
value = getString();
} else {
var rawStart = scanner.tokenStart;
// TODO: fix me, looks like incorrect raw scan
for (; !scanner.eof; scanner.next()) {
var type = scanner.tokenType;
if (type === WHITESPACE ||
type === LEFTPARENTHESIS ||
type === RIGHTPARENTHESIS) {
break;
}
}
value = {
type: 'Raw',
loc: getLocation(rawStart, scanner.tokenStart),
value: scanner.substrToCursor(rawStart)
};
}
readSC();
scanner.eat(RIGHTPARENTHESIS); // )
return {
type: 'Url',
loc: getLocation(start, scanner.tokenStart),
value: value
};
}
// expression '(' raw ')'
function getOldIEExpression(scope, start, name) {
var rawStart = scanner.tokenStart + 1; // skip open parenthesis
var rawNode;
scanner.eat(LEFTPARENTHESIS);
for (var balance = 0; !scanner.eof; scanner.next()) {
if (scanner.tokenType === RIGHTPARENTHESIS) {
if (balance === 0) {
break;
}
balance--;
} else if (scanner.tokenType === LEFTPARENTHESIS) {
balance++;
}
}
rawNode = {
type: 'Raw',
loc: getLocation(rawStart, scanner.tokenStart),
value: scanner.substrToCursor(rawStart)
};
scanner.eat(RIGHTPARENTHESIS);
return {
type: 'Function',
loc: getLocation(start, scanner.tokenStart),
name: name,
children: new List().appendData(rawNode)
};
}
function scanUnicodeNumber() {
for (var pos = scanner.tokenStart + 1; pos < scanner.tokenEnd; pos++) {
var code = scanner.source.charCodeAt(pos);
// break on fullstop or hyperminus/plussign after exponent
if (code === FULLSTOP || code === PLUSSIGN) {
// break token, exclude symbol
scanner.tokenStart = pos;
return false;
}
}
return true;
}
// https://drafts.csswg.org/css-syntax-3/#urange
function scanUnicodeRange() {
var hexStart = scanner.tokenStart + 1; // skip +
var hexLength = 0;
scan: {
if (scanner.tokenType === NUMBER) {
if (scanner.source.charCodeAt(scanner.tokenStart) !== FULLSTOP && scanUnicodeNumber()) {
scanner.next();
} else if (scanner.source.charCodeAt(scanner.tokenStart) !== HYPHENMINUS) {
break scan;
}
} else {
scanner.next(); // PLUSSIGN
}
if (scanner.tokenType === HYPHENMINUS) {
scanner.next();
}
if (scanner.tokenType === NUMBER) {
scanner.next();
}
if (scanner.tokenType === IDENTIFIER) {
scanner.next();
}
if (scanner.tokenStart === hexStart) {
scanner.error('Unexpected input', hexStart);
}
}
// validate for U+x{1,6} or U+x{1,6}-x{1,6}
// where x is [0-9a-fA-F]
// TODO: check hex sequence length
for (var i = hexStart, wasHyphenMinus = false; i < scanner.tokenStart; i++) {
var code = scanner.source.charCodeAt(i);
if (isHex(code) === false && (code !== HYPHENMINUS || wasHyphenMinus)) {
scanner.error('Unexpected input', i);
}
if (code === HYPHENMINUS) {
// hex sequence shouldn't be an empty
if (hexLength === 0) {
scanner.error('Unexpected input', i);
}
wasHyphenMinus = true;
hexLength = 0;
} else {
hexLength++;
// to long hex sequence
if (hexLength > 6) {
scanner.error('Unexpected input', i);
}
}
}
// check we have a non-zero sequence
if (hexLength === 0) {
scanner.error('Unexpected input', i - 1);
}
// U+abc???
if (!wasHyphenMinus) {
// consume as many U+003F QUESTION MARK (?) code points as possible
for (; hexLength < 6 && !scanner.eof; scanner.next()) {
if (scanner.tokenType !== QUESTIONMARK) {
break;
}
hexLength++;
}
}
}
function getUnicodeRange() {
var start = scanner.tokenStart;
scanner.next(); // U or u
scanUnicodeRange();
return {
type: 'UnicodeRange',
loc: getLocation(start, scanner.tokenStart),
value: scanner.substrToCursor(start)
};
}
function scanIdent(varAllowed) {
// optional first -
if (scanner.tokenType === HYPHENMINUS) {
scanner.next();
if (this.scanner.tokenType === HYPHENMINUS) {
this.scanner.next();
// variable --
if (varAllowed && scanner.tokenType === HYPHENMINUS) {
scanner.next();
if (varAllowed && this.scanner.tokenType === HYPHENMINUS) {
this.scanner.next();
}
}
scanner.eat(IDENTIFIER);
this.scanner.eat(IDENTIFIER);
}
function readIdent(varAllowed) {
var start = scanner.tokenStart;
var start = this.scanner.tokenStart;
scanIdent(varAllowed);
this.scanIdent(varAllowed);
return scanner.substrToCursor(start);
return this.scanner.substrToCursor(start);
}
function getAttributeName() {
if (scanner.eof) {
scanner.error('Unexpected end of input');
function readSC() {
while (this.scanner.tokenType === WHITESPACE || this.scanner.tokenType === COMMENT) {
this.scanner.next();
}
var start = scanner.tokenStart;
var expectIdentifier = false;
var checkColon = true;
if (scanner.tokenType === ASTERISK) {
expectIdentifier = true;
checkColon = false;
scanner.next();
} else if (scanner.tokenType !== VERTICALLINE) {
scanIdent(false);
}
if (scanner.tokenType === VERTICALLINE) {
if (scanner.lookupType(1) !== EQUALSSIGN) {
scanner.next();
if (scanner.tokenType === HYPHENMINUS || scanner.tokenType === IDENTIFIER) {
scanIdent(false);
} else {
scanner.error('Identifier is expected');
}
} else if (expectIdentifier) {
scanner.error('Identifier is expected', scanner.tokenEnd);
}
} else if (expectIdentifier) {
scanner.error('Vertical line is expected');
}
if (checkColon && scanner.tokenType === COLON) {
scanner.next();
scanIdent(false);
}
return {
type: 'Identifier',
loc: getLocation(start, scanner.tokenStart),
name: scanner.substrToCursor(start)
};
}
function getTypeOrUniversal() {
var start = scanner.tokenStart;
var universal = false;
var Parser = function() {
this.scanner = new Scanner();
this.needPositions = false;
this.filename = '<unknown>';
};
if (scanner.tokenType === ASTERISK) {
universal = true;
scanner.next();
} else if (scanner.tokenType !== VERTICALLINE) {
scanIdent(false);
}
Parser.prototype = {
SPACE_NODE: Object.freeze({ type: 'Space' }),
if (scanner.tokenType === VERTICALLINE) {
universal = false;
scanner.next();
scopeAtruleExpression: {},
scopeValue: {
expression: require('./function/expression'),
var: require('./function/var')
},
atrule: {
'import': require('./atrule/import'),
'media': require('./atrule/media'),
'page': require('./atrule/page'),
'supports': require('./atrule/supports')
},
pseudo: {
'lang': sequence.singleIdentifier,
'dir': sequence.singleIdentifier,
'not': sequence.selectorList,
'matches': sequence.selectorList,
'has': sequence.relativeSelectorList,
'nth-child': sequence.nthWithOfClause,
'nth-last-child': sequence.nthWithOfClause,
'nth-of-type': sequence.nth,
'nth-last-of-type': sequence.nth,
'slotted': sequence.compoundSelectorList
},
context: {
stylesheet: getStyleSheet,
atrule: getAtrule,
atruleExpression: getAtruleExpression,
rule: getRule,
selectorList: getSelectorList,
selector: getSelector,
block: getBlock,
declarationList: getDeclarationList,
declaration: getDeclaration,
value: getValue
},
if (scanner.tokenType === HYPHENMINUS || scanner.tokenType === IDENTIFIER) {
scanIdent(false);
} else if (scanner.tokenType === ASTERISK) {
universal = true;
scanner.next();
} else {
scanner.error('Identifier or asterisk is expected');
}
}
// consumers
AnPlusB: getAnPlusB,
Atrule: getAtrule,
AtruleExpression: getAtruleExpression,
Attribute: getAttribute,
Block: getBlock,
Brackets: getBrackets,
Class: getClass,
Combinator: getCombinator,
Comment: getComment,
Declaration: getDeclaration,
DeclarationList: getDeclarationList,
Dimension: getDimension,
Function: getFunction,
Hash: getHash,
Id: getId,
Identifier: getIdentifier,
MediaFeature: getMediaFeature,
MediaQuery: getMediaQuery,
MediaQueryList: getMediaQueryList,
Nth: getNth,
Number: getNumber,
Operator: getOperator,
Parentheses: getParentheses,
Percentage: getPercentage,
Progid: getProgid,
PseudoClass: getPseudoClass,
PseudoElement: getPseudoElement,
Ratio: getRatio,
Raw: getRaw,
Rule: getRule,
Selector: getSelector,
SelectorList: getSelectorList,
String: getString,
Stylesheet: getStyleSheet,
Type: getType,
UnicodeRange: getUnicodeRange,
Universal: getUniversal,
Url: getUrl,
Value: getValue,
return {
type: universal ? 'Universal' : 'Type',
loc: getLocation(start, scanner.tokenStart),
name: scanner.substrToCursor(start)
};
}
scanIdent: scanIdent,
readIdent: readIdent,
readSC: readSC,
readSequence: sequence.default,
function getIdentifier(varAllowed) {
var start = scanner.tokenStart;
var name = readIdent(varAllowed);
return {
type: 'Identifier',
loc: getLocation(start, scanner.tokenStart),
name: name
};
}
// ! ws* important
function getImportant() {
scanner.eat(EXCLAMATIONMARK);
readSC();
scanner.expectIdentifier('important');
// should return identifier in future for original source restoring as is
// returns true for now since it's fit to optimizer purposes
return true;
}
function checkTokenIsInteger() {
var pos = scanner.tokenStart;
if (scanner.source.charCodeAt(pos) === PLUSSIGN ||
scanner.source.charCodeAt(pos) === HYPHENMINUS) {
pos++;
}
for (; pos < scanner.tokenEnd; pos++) {
if (!isNumber(scanner.source.charCodeAt(pos))) {
scanner.error('Unexpected input', pos);
getLocation: function getLocation(start, end) {
if (this.needPositions) {
return this.scanner.getLocationRange(
start,
end,
this.filename
);
}
}
}
// https://drafts.csswg.org/css-syntax-3/#the-anb-type
function getNthSelector(allowOfClause) {
var start = scanner.tokenStart;
var selector = null;
var query;
var name;
return null;
},
parse: function parse(source, options) {
options = options || {};
scanner.eat(COLON);
name = readIdent(false);
scanner.eat(LEFTPARENTHESIS);
readSC();
var context = options.context || 'stylesheet';
var ast;
var nthStart = scanner.tokenStart;
this.scanner.setSource(source, options.line, options.column);
this.filename = options.filename || '<unknown>';
this.needPositions = Boolean(options.positions);
if (scanner.lookupValue(0, 'odd') || scanner.lookupValue(0, 'even')) {
scanner.next();
query = {
type: 'Identifier',
loc: getLocation(nthStart, scanner.tokenStart),
name: scanner.substrToCursor(nthStart)
};
} else {
// scan An+B microsyntax https://www.w3.org/TR/css-syntax-3/#anb
var prefix = '';
var a = null;
var b = null;
switch (context) {
case 'value':
ast = this.Value(options.property ? String(options.property) : null);
break;
if (scanner.tokenType === HYPHENMINUS ||
scanner.tokenType === PLUSSIGN ||
scanner.tokenType === NUMBER) {
checkTokenIsInteger();
prefix = scanner.getTokenValue();
scanner.next();
}
case 'atruleExpression':
ast = this.Value(options.atrule ? String(options.atrule) : null);
break;
if (scanner.tokenType === IDENTIFIER) {
if (!cmpChar(scanner.source, scanner.tokenStart, N)) {
scanner.error();
}
a = prefix === '' || prefix === '+' ? '1' :
prefix === '-' ? '-1' :
prefix;
var len = scanner.tokenEnd - scanner.tokenStart;
if (len > 1) {
var bStart = scanner.tokenStart;
// ..n-..
if (scanner.source.charCodeAt(bStart + 1) !== HYPHENMINUS) {
scanner.error('Unexpected input', bStart + 1);
default:
if (!this.context.hasOwnProperty(context)) {
throw new Error('Unknown context `' + context + '`');
}
scanner.tokenStart = bStart + 1;
// ..n-{number}..
if (len > 2) {
for (var i = bStart + 2; i < scanner.tokenEnd; i++) {
if (!isNumber(scanner.source.charCodeAt(i))) {
scanner.error('Unexpected input', i);
}
}
scanner.next();
b = '-' + scanner.substrToCursor(bStart + 2);
} else {
scanner.next();
readSC();
if (scanner.tokenType !== NUMBER ||
cmpChar(scanner.source, scanner.tokenStart, PLUSSIGN) ||
cmpChar(scanner.source, scanner.tokenStart, HYPHENMINUS)) {
scanner.error();
}
b = '-' + scanner.getTokenValue();
scanner.next();
}
} else {
prefix = '';
scanner.next();
readSC();
if (scanner.tokenType === HYPHENMINUS ||
scanner.tokenType === PLUSSIGN) {
prefix = scanner.getTokenValue();
scanner.next();
readSC();
}
if (scanner.tokenType === NUMBER) {
checkTokenIsInteger();
if (cmpChar(scanner.source, scanner.tokenStart, PLUSSIGN) ||
cmpChar(scanner.source, scanner.tokenStart, HYPHENMINUS)) {
// prefix or sign should be specified but not both
if (prefix !== '') {
scanner.error();
}
prefix = scanner.source.charAt(scanner.tokenStart);
scanner.tokenStart++;
}
if (prefix === '') {
// should be an operator before number
scanner.error();
} else if (prefix === '+') {
// plus is using by default
prefix = '';
}
b = prefix + scanner.getTokenValue();
scanner.next();
}
}
} else {
if (prefix === '' || prefix === '-' || prefix === '+') { // no number
scanner.error('Number or identifier is expected');
}
b = prefix;
ast = this.context[context].call(this);
}
query = {
type: 'An+B',
loc: getLocation(nthStart, scanner.tokenStart),
a: a,
b: b
};
}
readSC();
if (allowOfClause && scanner.lookupValue(0, 'of')) {
scanner.next();
selector = getSelectorList(NESTED);
}
scanner.eat(RIGHTPARENTHESIS);
return {
type: 'PseudoClass',
loc: getLocation(start, scanner.tokenStart),
name: name,
children: new List().appendData({ // TODO: add loc
type: 'Nth',
nth: query,
selector: selector
})
};
}
function readNumber() {
var number = scanner.getTokenValue();
scanner.eat(NUMBER);
return number;
}
// '/' | '*' | ',' | ':' | '+' | '-'
function getOperator() {
var start = scanner.tokenStart;
scanner.next();
return {
type: 'Operator',
loc: getLocation(start, scanner.tokenStart),
value: scanner.substrToCursor(start)
};
}
// 'progid:' ws* 'DXImageTransform.Microsoft.' ident ws* '(' .* ')'
function checkProgid() {
var startOffset = findNonSCOffset(0);
var offset = startOffset;
if (scanner.lookupValue(offset, 'alpha') ||
scanner.lookupValue(offset, 'dropshadow')) {
offset++;
} else {
if (scanner.lookupValue(offset, 'progid') === false ||
scanner.lookupType(offset + 1) !== COLON) {
return false; // fail
if (!this.scanner.eof) {
this.scanner.error();
}
offset += 2;
offset = findNonSCOffset(offset);
if (scanner.lookupValue(offset + 0, 'dximagetransform') === false ||
scanner.lookupType(offset + 1) !== FULLSTOP ||
scanner.lookupValue(offset + 2, 'microsoft') === false ||
scanner.lookupType(offset + 3) !== FULLSTOP ||
scanner.lookupType(offset + 4) !== IDENTIFIER) {
return false; // fail
}
offset += 5;
offset = findNonSCOffset(offset);
// console.log(JSON.stringify(ast, null, 4));
return ast;
}
};
if (scanner.lookupType(offset) !== LEFTPARENTHESIS) {
return false; // fail
}
var parser = new Parser();
for (var type; type = scanner.lookupType(offset); offset++) {
if (type === RIGHTPARENTHESIS) {
return offset - startOffset + 1;
}
}
return false;
}
function getProgid(progidEnd) {
var start = scanner.tokenStart;
scanner.skip(progidEnd);
return {
type: 'Progid',
loc: getLocation(start, scanner.tokenStart),
value: scanner.substrToCursor(start)
};
}
// <pseudo-element> | <nth-selector> | <pseudo-class>
function getPseudo() {
var nextType = scanner.lookupType(1);
if (nextType === COLON) {
return getPseudoElement();
}
if (nextType === IDENTIFIER) {
// '::' starts a pseudo-element, ':' a pseudo-class
// Exceptions: :first-line, :first-letter, :before and :after
if (scanner.lookupValue(1, 'before') ||
scanner.lookupValue(1, 'after') ||
scanner.lookupValue(1, 'first-letter') ||
scanner.lookupValue(1, 'first-line')) {
return getLegacyPseudoElement();
}
if (scanner.lookupValue(1, 'nth-child') ||
scanner.lookupValue(1, 'nth-last-child')) {
return getNthSelector(true);
}
if (scanner.lookupValue(1, 'nth-of-type') ||
scanner.lookupValue(1, 'nth-last-of-type')) {
return getNthSelector(false);
}
}
return getPseudoClass();
}
// :: ident
function getPseudoElement() {
var start = scanner.tokenStart;
var name;
var children = null;
scanner.eat(COLON);
scanner.eat(COLON);
// https://drafts.csswg.org/css-scoping/#slotted-pseudo
if (scanner.lookupValue(0, 'slotted')) {
name = readIdent(false);
scanner.eat(LEFTPARENTHESIS);
children = new List().appendData(getCompoundSelector(true));
scanner.eat(RIGHTPARENTHESIS);
} else {
name = readIdent(false);
}
return {
type: 'PseudoElement',
loc: getLocation(start, scanner.tokenStart),
name: name,
children: children,
legacy: false
};
}
// : ident
// https://drafts.csswg.org/selectors-4/#grammar
// Some older pseudo-elements (::before, ::after, ::first-line, and ::first-letter)
// can, for legacy reasons, be written using the <pseudo-class-selector> grammar,
// with only a single ":" character at their start.
function getLegacyPseudoElement() {
var start = scanner.tokenStart;
var name;
scanner.eat(COLON);
name = readIdent(false);
return {
type: 'PseudoElement',
loc: getLocation(start, scanner.tokenStart),
name: name,
children: null,
legacy: true
};
}
// : ( ident | function )
function getPseudoClass() {
var start = scanner.tokenStart;
scanner.eat(COLON);
scanIdent(false);
if (scanner.tokenType === LEFTPARENTHESIS) {
return getFunction(SCOPE_SELECTOR, start, scanner.substrToCursor(start + 1));
}
return {
type: 'PseudoClass',
loc: getLocation(start, scanner.tokenStart),
name: scanner.substrToCursor(start + 1),
children: null
};
}
function findNonSCOffset(offset) {
for (var type; type = scanner.lookupType(offset); offset++) {
if (type !== WHITESPACE && type !== COMMENT) {
break;
}
}
return offset;
}
function readSC() {
while (scanner.tokenType === WHITESPACE || scanner.tokenType === COMMENT) {
scanner.next();
}
}
// node: String
function getString() {
var start = scanner.tokenStart;
scanner.next();
return {
type: 'String',
loc: getLocation(start, scanner.tokenStart),
value: scanner.substrToCursor(start)
};
}
// # ident
function getHash() {
var start = scanner.tokenStart;
scanner.eat(NUMBERSIGN);
scan:
switch (scanner.tokenType) {
case NUMBER:
if (!isNumber(scanner.source.charCodeAt(scanner.tokenStart))) {
scanner.error('Unexpected input', scanner.tokenStart);
}
for (var pos = scanner.tokenStart + 1; pos < scanner.tokenEnd; pos++) {
var code = scanner.source.charCodeAt(pos);
// break on fullstop or hyperminus/plussign after exponent
if (code === FULLSTOP || code === HYPHENMINUS || code === PLUSSIGN) {
// break token, exclude symbol
scanner.tokenStart = pos;
break scan;
}
}
// number contains digits only, go to next token
scanner.next();
// if next token is identifier add it to result
if (scanner.tokenType === IDENTIFIER) {
scanner.next();
}
break;
case IDENTIFIER:
scanner.next(); // add token to result
break;
default:
scanner.error('Number or identifier is expected');
}
return {
type: 'Hash',
loc: getLocation(start, scanner.tokenStart),
value: scanner.substrToCursor(start + 1) // skip #
};
}
function parse(source, options) {
var ast;
if (!options || typeof options !== 'object') {
options = {};
}
var context = options.context || 'stylesheet';
needPositions = Boolean(options.positions);
filename = options.filename || '<unknown>';
if (!CONTEXT.hasOwnProperty(context)) {
throw new Error('Unknown context `' + context + '`');
}
scanner.setSource(source, options.line, options.column);
if (context === 'value') {
ast = getValue(false, options.property ? String(options.property) : null);
} else {
ast = CONTEXT[context]();
}
// console.log(JSON.stringify(ast, null, 4));
return ast;
};
// warm up parse to elimitate code branches that never execute
// fix soft deoptimizations (insufficient type feedback)
parse('a.b#c:e:Not(a):Nth-child(2n+1)::g,* b >c+d~e/deep/f,100%{v:1 2em t a(2%, var(--a)) url(..) -foo-bar !important}');
parser.parse('a.b#c:e:Not(a/**/):AFTER:Nth-child(2n+1)::g::slotted(a/**/),* b >c+d~e/deep/f,100%{v:U+123 1 2em t a(2%, var(--a)) -b() url(..) -foo-bar !important}');
module.exports = parse;
module.exports = parser.parse.bind(parser);

@@ -192,2 +192,15 @@ 'use strict';

},
lookupNonWSType: function(offset) {
offset += this.currentToken;
for (var type; offset < this.tokenCount; offset++) {
type = this.offsetAndType[offset] >> TYPE_OFFSET;
if (type !== WHITESPACE) {
return type;
}
}
return NULL;
},
lookupValue: function(offset, referenceStr) {

@@ -215,2 +228,13 @@ offset += this.currentToken;

skipWS: function() {
for (var i = this.currentToken, skipTokenCount = 0; i < this.tokenCount; i++, skipTokenCount++) {
if ((this.offsetAndType[i] >> TYPE_OFFSET) !== WHITESPACE) {
break;
}
}
if (skipTokenCount > 0) {
this.skip(skipTokenCount);
}
},
skip: function(tokenCount) {

@@ -254,2 +278,20 @@ var next = this.currentToken + tokenCount;

},
eatNonWS: function(tokenType) {
this.skipWS();
this.eat(tokenType);
},
consume: function(tokenType) {
var start = this.tokenStart;
this.eat(tokenType);
return this.substrToCursor(start);
},
consumeNonWS: function(tokenType) {
this.skipWS();
return this.consume(tokenType);
},
expectIdentifier: function(name) {

@@ -331,4 +373,4 @@ if (this.tokenType !== IDENTIFIER || cmpStr(this.source, this.tokenStart, this.tokenEnd, name) === false) {

// fix soft deoptimizations (insufficient type feedback)
new Scanner('\n\r\r\n\f//""\'\'/*\r\n\f*/1a;.\\31\t\+2{url(a);+1.2e3 -.4e-5 .6e+7}');
new Scanner('\n\r\r\n\f//""\'\'/*\r\n\f*/1a;.\\31\t\+2{url(a);+1.2e3 -.4e-5 .6e+7}').getLocation();
module.exports = Scanner;

@@ -64,3 +64,3 @@ 'use strict';

array[i] = /[a-zA-Z0-9\-]/.test(String.fromCharCode(i)) ? 1 : 0;
};
}
return array;

@@ -67,0 +67,0 @@ })();

@@ -158,6 +158,2 @@ 'use strict';

lastMatchError: null,
match: function(propertyName, value) {
console.warn('Syntax#match() method is deprecated. Please, use Syntax#matchProperty() or Syntax#matchType() instead');
return this.matchProperty(propertyName, value);
},
matchProperty: function(propertyName, value) {

@@ -164,0 +160,0 @@ var property = names.property(propertyName);

@@ -26,2 +26,3 @@ 'use strict';

var cursors = null;
var List = function() {

@@ -97,9 +98,36 @@ this.cursor = null;

function allocateCursor(node, prev, next) {
var cursor;
if (cursors !== null) {
cursor = cursors;
cursors = cursors.cursor;
cursor.prev = prev;
cursor.next = next;
cursor.cursor = node.cursor;
} else {
cursor = {
prev: prev,
next: next,
cursor: node.cursor
};
}
node.cursor = cursor;
return cursor;
}
function releaseCursor(node) {
var cursor = node.cursor;
node.cursor = cursor.cursor;
cursor.prev = null;
cursor.next = null;
cursor.cursor = cursors;
cursors = cursor;
}
List.prototype.each = function(fn, context) {
var item;
var cursor = {
prev: null,
next: this.head,
cursor: this.cursor
};

@@ -111,3 +139,3 @@ if (context === undefined) {

// push cursor
this.cursor = cursor;
var cursor = allocateCursor(this, null, this.head);

@@ -122,3 +150,3 @@ while (cursor.next !== null) {

// pop cursor
this.cursor = this.cursor.cursor;
releaseCursor(this);
};

@@ -128,7 +156,2 @@

var item;
var cursor = {
prev: this.tail,
next: null,
cursor: this.cursor
};

@@ -140,3 +163,3 @@ if (context === undefined) {

// push cursor
this.cursor = cursor;
var cursor = allocateCursor(this, this.tail, null);

@@ -151,3 +174,3 @@ while (cursor.prev !== null) {

// pop cursor
this.cursor = this.cursor.cursor;
releaseCursor(this);
};

@@ -161,7 +184,2 @@

var item;
var cursor = {
prev: null,
next: start,
cursor: this.cursor
};

@@ -173,3 +191,3 @@ if (context === undefined) {

// push cursor
this.cursor = cursor;
var cursor = allocateCursor(this, null, start);

@@ -186,3 +204,3 @@ while (cursor.next !== null) {

// pop cursor
this.cursor = this.cursor.cursor;
releaseCursor(this);
};

@@ -196,7 +214,2 @@

var item;
var cursor = {
prev: start,
next: null,
cursor: this.cursor
};

@@ -208,3 +221,3 @@ if (context === undefined) {

// push cursor
this.cursor = cursor;
var cursor = allocateCursor(this, start, null);

@@ -221,3 +234,3 @@ while (cursor.prev !== null) {

// pop cursor
this.cursor = this.cursor.cursor;
releaseCursor(this);
};

@@ -224,0 +237,0 @@

@@ -55,3 +55,3 @@ 'use strict';

if (node.block) {
result += '{' + translate(node.block) + '}';
result += translate(node.block);
} else {

@@ -89,2 +89,25 @@ result += ';';

function translateBlock(list) {
var cursor = list.head;
var result = '';
if (cursor === null) {
return result;
}
if (cursor === list.tail) {
return translate(list.head.data);
}
while (cursor !== null) {
result += translate(cursor.data);
if (cursor.next && cursor.data.type === 'Declaration') {
result += ';';
}
cursor = cursor.next;
}
return result;
}
function translate(node) {

@@ -99,3 +122,3 @@ switch (node.type) {

case 'Rule':
return translate(node.selector) + '{' + translate(node.block) + '}';
return translate(node.selector) + translate(node.block);

@@ -109,4 +132,7 @@ case 'SelectorList':

case 'Block':
return eachDelim(node.children, ';');
return '{' + translateBlock(node.children) + '}';
case 'DeclarationList':
return translateBlock(node.children);
case 'Declaration':

@@ -162,2 +188,13 @@ return node.important

case 'MediaQueryList':
return eachDelim(node.children, ',');
case 'MediaQuery':
return eachDelim(node.children, ' ');
case 'MediaFeature':
return node.value !== null
? '(' + node.name + ':' + translate(node.value) + ')'
: '(' + node.name + ')';
case 'Url':

@@ -187,7 +224,5 @@ return 'url(' + translate(node.value) + ')';

case 'PseudoElement':
return node.legacy
? ':' + node.name // :before, :after, :first-letter and :first-line
: node.children !== null
? '::' + node.name + '(' + each(node.children) + ')'
: '::' + node.name;
return node.children !== null
? '::' + node.name + '(' + each(node.children) + ')'
: '::' + node.name;

@@ -242,2 +277,5 @@ case 'Class':

case 'Ratio':
return node.left + '/' + node.right;
case 'Raw':

@@ -244,0 +282,0 @@ return node.value;

@@ -43,4 +43,3 @@ 'use strict';

walk(root, function(chunk, original) {
if (original.line !== null &&
original.column !== null) {
if (original.line !== null && original.column !== null) {
if (lastOriginalLine !== original.line ||

@@ -64,4 +63,4 @@ lastOriginalColumn !== original.column) {

css += chunk;
lastIndexOfNewline = chunk.lastIndexOf('\n');
lastIndexOfNewline = chunk.lastIndexOf('\n');
if (lastIndexOfNewline !== -1) {

@@ -121,2 +120,17 @@ generated.line += chunk.match(/\n/g).length;

function translateBlock(list) {
var cursor = list.head;
var result = [];
while (cursor !== null) {
result.push(translate(cursor.data));
if (cursor.next && cursor.data.type === 'Declaration') {
result.push(';');
}
cursor = cursor.next;
}
return result;
}
function translate(node) {

@@ -135,3 +149,3 @@ switch (node.type) {

if (node.block) {
nodes.push('{', translate(node.block), '}');
nodes.push(translate(node.block));
} else {

@@ -145,3 +159,3 @@ nodes.push(';');

return createAnonymousSourceNode([
translate(node.selector), '{', translate(node.block), '}'
translate(node.selector), translate(node.block)
]);

@@ -165,16 +179,16 @@

case 'Block':
return createAnonymousSourceNode(node.children.map(translate)).join(';');
return createAnonymousSourceNode([
'{', translateBlock(node.children), '}'
]);
case 'DeclarationList':
return translateBlock(node.children);
case 'Declaration':
if (node.important) {
return createSourceNode(
node.loc,
[node.property, ':', translate(node.value), '!important']
);
} else {
return createSourceNode(
node.loc,
[node.property, ':', translate(node.value)]
);
}
return createSourceNode(
node.loc,
node.important
? [node.property, ':', translate(node.value), '!important']
: [node.property, ':', translate(node.value)]
);

@@ -186,5 +200,7 @@ case 'Value':

var nodes = [translate(node.nth)];
if (node.selector !== null) {
nodes.push(' of ', translate(node.selector));
}
return createAnonymousSourceNode(nodes);

@@ -227,2 +243,13 @@

case 'MediaQueryList':
return createAnonymousSourceNode(node.children.map(translate)).join(',');
case 'MediaQuery':
return createAnonymousSourceNode(node.children.map(translate)).join(' ');
case 'MediaFeature':
return node.value !== null
? '(' + node.name + ':' + translate(node.value) + ')'
: '(' + node.name + ')';
case 'Url':

@@ -252,7 +279,5 @@ return 'url(' + translate(node.value) + ')';

case 'PseudoElement':
return node.legacy
? ':' + node.name // :before, :after, :first-letter and :first-line
: node.children !== null
? '::' + node.name + '(' + each(node.children) + ')'
: '::' + node.name;
return node.children !== null
? '::' + node.name + '(' + each(node.children) + ')'
: '::' + node.name;

@@ -307,2 +332,5 @@ case 'Class':

case 'Ratio':
return node.left + '/' + node.right;
case 'Raw':

@@ -309,0 +337,0 @@ return node.value;

@@ -28,6 +28,15 @@ 'use strict';

node.block.children.each(walkRules, this);
walkRules.call(this, node.block);
this.rule = oldRule;
break;
case 'Block':
var oldBlock = this.block;
this.block = node;
node.children.each(walkRules, this);
this.block = oldBlock;
break;
}

@@ -60,3 +69,3 @@

node.block.children.eachRight(walkRulesRight, this);
walkRulesRight.call(this, node.block);

@@ -67,2 +76,11 @@ this.rule = oldRule;

break;
case 'Block':
var oldBlock = this.block;
this.block = node;
node.children.eachRight(walkRulesRight, this);
this.block = oldBlock;
break;
}

@@ -161,2 +179,6 @@ }

case 'Selector':
node.children.each(walk, this);
break;
case 'Nth':

@@ -170,5 +192,14 @@ walk.call(this, node.nth);

case 'Block':
var oldBlock = this.block;
this.block = node;
node.children.each(walk, this);
this.block = oldBlock;
break;
case 'DeclarationList':
node.children.each(walk, this);
break;
case 'Declaration':

@@ -225,5 +256,24 @@ this.declaration = node;

case 'Selector':
case 'MediaQueryList':
node.children.each(walk, this);
break;
case 'MediaQuery':
node.children.each(walk, this);
break;
case 'MediaFeature':
if (node.value !== null) {
walk.call(this, node.value);
}
break;
case 'Value':
node.children.each(walk, this);
break;
case 'Parentheses':
node.children.each(walk, this);
break;
case 'Brackets':

@@ -256,2 +306,3 @@ node.children.each(walk, this);

// case 'Raw':
// case 'Ratio':
}

@@ -268,2 +319,3 @@ }

selector: null,
block: null,
declaration: null,

@@ -270,0 +322,0 @@ function: null

{
"name": "css-tree",
"version": "1.0.0-alpha13",
"version": "1.0.0-alpha14",
"description": "Fast detailed CSS parser",

@@ -37,3 +37,9 @@ "keywords": [

"no-undef": 2,
"no-unused-vars": [2, {"vars": "all", "args": "after-used"}]
"no-unused-vars": [
2,
{
"vars": "all",
"args": "after-used"
}
]
}

@@ -46,7 +52,7 @@ },

"codestyle": "jscs data lib scripts test && eslint data lib scripts test",
"test": "mocha --reporter dot",
"coverage": "istanbul cover _mocha -- -R dot",
"test": "mocha --reporter progress",
"coverage": "istanbul cover _mocha -- -R min",
"prepublish": "npm run build",
"travis": "npm run codestyle-and-test && npm run coveralls",
"coveralls": "istanbul cover _mocha --report lcovonly -- -R dot && cat ./coverage/lcov.info | coveralls",
"coveralls": "istanbul cover _mocha --report lcovonly -- -R min && cat ./coverage/lcov.info | coveralls",
"hydrogen": "node --trace-hydrogen --trace-phase=Z --trace-deopt --code-comments --hydrogen-track-positions --redirect-code-traces --redirect-code-traces-to=code.asm --trace_hydrogen_file=code.cfg --print-opt-code bin/parse --stat -o /dev/null"

@@ -63,2 +69,3 @@ },

"jscs": "~3.0.7",
"json-to-ast": "^1.2.15",
"mocha": "^3.0.2",

@@ -65,0 +72,0 @@ "uglify-js": "^2.6.1"

@@ -13,8 +13,9 @@ <img align="right" width="111" height="111"

Fast detailed CSS parser
[Fast](https://github.com/postcss/benchmark) detailed CSS parser
> Work in progress
> Work in progress. Project in alpha stage since AST format is subject to change.
Docs and tools:
* [AST Explorer](https://astexplorer.net/#/gist/244e2fb4da940df52bf0f4b94277db44/e79aff44611020b22cfd9708f3a99ce09b7d67a8) – explore CSSTree AST format with zero setup
* [CSS syntax reference](https://csstree.github.io/docs/syntax.html)

@@ -69,3 +70,4 @@ * [CSS syntax validator](https://csstree.github.io/docs/validator.html)

- `context` String – parsing context, useful when some part of CSS is parsing (see below)
- `property` String – make sense for `declaration` context to apply some property specific parse rules
- `atrule` String – make sense for `atruleExpression` context to apply some atrule specific parse rules
- `property` String – make sense for `value` context to apply some property specific parse rules
- `positions` Boolean – should AST contains node position or not, store data in `info` property of nodes (`false` by default)

@@ -81,8 +83,9 @@ - `filename` String – filename of source that adds to info when `positions` is true, uses for source map generation (`<unknown>` by default)

- `atruleExpression` – at-rule expression (`screen, print` for example above)
- `ruleset` – rule (e.g. `.foo, .bar:hover { color: red; border: 1px solid black; }`)
- `selectorList` – selector group (`.foo, .bar:hover` for ruleset example)
- `selector` – selector (`.foo` or `.bar:hover` for ruleset example)
- `block` – block content w/o curly braces (`color: red; border: 1px solid black;` for ruleset example)
- `declaration` – declaration (`color: red` or `border: 1px solid black` for ruleset example)
- `value` – declaration value (`red` or `1px solid black` for ruleset example)
- `rule` – rule (e.g. `.foo, .bar:hover { color: red; border: 1px solid black; }`)
- `selectorList` – selector group (`.foo, .bar:hover` for rule example)
- `selector` – selector (`.foo` or `.bar:hover` for rule example)
- `block` – block with curly braces (`{ color: red; border: 1px solid black; }` for rule example)
- `declarationList` – block content w/o curly braces (`color: red; border: 1px solid black;` for rule example), useful to parse HTML `style` attribute value
- `declaration` – declaration (`color: red` or `border: 1px solid black` for rule example)
- `value` – declaration value (`red` or `1px solid black` for rule example)

@@ -153,9 +156,10 @@ ```js

- `root` – refers to `ast` or root node
- `stylesheet` – refers to closest `StyleSheet` node, it may be a top-level or at-rule block stylesheet
- `atruleExpression` – refers to `AtruleExpression` node if current node inside at-rule expression
- `ruleset` – refers to `Rule` node if current node inside a ruleset
- `selector` – refers to `SelectorList` node if current node inside a selector list
- `declaration` – refers to `Declaration` node if current node inside a declaration
- `function` – refers to closest `Function` or `FunctionalPseudo` node if current node inside one of them
- `root` – refers to `ast` root node (actually it's a node passed to walker function)
- `stylesheet` – refers to `StyleSheet` node, usually it's a root node
- `atruleExpression` – refers to `AtruleExpression` node if any
- `rule` – refers to closest `Rule` node if any
- `selector` – refers to `SelectorList` node if any
- `block` - refers to closest `Block` node if any
- `declaration` – refers to `Declaration` node if any
- `function` – refers to closest `Function`, `PseudoClass` or `PseudoElement` node if current node inside one of them

@@ -162,0 +166,0 @@ ```js

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet