Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
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 1.0.0-alpha13 to 1.0.0-alpha14

dist/default-syntax.json

34

HISTORY.md

@@ -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

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc