Comparing version
@@ -0,1 +1,37 @@ | ||
## 1.0.0-alpha15 (February 8, 2017) | ||
- Fixed broken `atruleExpression` context | ||
- Fixed vendor prefix detection in `keyword()` and `property()` | ||
- Fixed `property()` to not lowercase custom property names | ||
- Added `variable` boolean flag in `property()` result | ||
- Renamed `scanner` into `tokenizer` | ||
- Ranamed `syntax` into `lexer` | ||
- Moved `docs/*.html` files to [csstree/docs](https://github.com/csstree/docs) repo | ||
- Added `element()` function for `Value` context (`-moz-element()` supported as well) | ||
- Merged `Universal` node type into `Type` | ||
- Renamed node types: | ||
- `Id` -> `IdSelector` | ||
- `Class` -> `ClassSelector` | ||
- `Type` -> `TypeSelector` | ||
- `Attribute` -> `AttributeSelector` | ||
- `PseudoClass` -> `PseudoClassSelector` | ||
- `PseudoElement` -> `PseudoElementSelector` | ||
- `Hash` -> `HexColor` | ||
- `Space` -> `WhiteSpace` | ||
- `An+B` -> `AnPlusB` | ||
- Removed `Progid` node type | ||
- Relaxed `MediaQuery` consumer to not validate syntax on parse and to include whitespaces in children sequence as is | ||
- Added `WhiteSpace.value` property to store whitespace sequence | ||
- Implemented parser options to specify what should be parsed in details (when option is `false` some part of CSS represents as balanced `Raw`): | ||
- `parseAtruleExpression` – to parse at-rule expressions (`true` by default) | ||
- `parseSelector` – to parse rule's selector (`true` by default) | ||
- `parseValue` - to parse declaration's value (`true` by default) | ||
- `parseCustomProperty` – to parse value and fallback of custom property (`false` by default) | ||
- Changed tokenization to stick leading hyphen minus to identifier token | ||
- Changed selector parsing: | ||
- Don't convert spaces into descendant combinator | ||
- Don't validate selector structure on parsing (selectors may be checked by lexer later) | ||
- Initial refactoring of [docs](https://github.com/csstree/csstree/blob/master/docs) | ||
- Various improvements and fixes | ||
## 1.0.0-alpha14 (February 3, 2017) | ||
@@ -15,10 +51,10 @@ | ||
- 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 `Parentheses`, `Brackets` and `Function` consumers to use passed sequence reader instead of its own | ||
- Changed `Value` and `AtruleExpression` consumers to use common sequence reader (that reader was used by `Value` consumer before) | ||
- Changed default sequence reader to exclude storage of spaces around `Comma` | ||
- Changed processing of custom properties | ||
- Consume custom property value as balanced `Raw` | ||
- Changed processing of custom properties: | ||
- Consume declaration 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 | ||
- Validate first argument of `var()` starts with double dash | ||
- Custom property's value and fallback includes spaces around | ||
- Fixed `Nth` to have a `loc` property | ||
@@ -33,4 +69,4 @@ - Fixed `SelectorList.loc` and `Selector.loc` positions to exclude spaces | ||
- 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 | ||
- Parser was splitted into modules and related changes, one step closer to an extensible parser | ||
- Various fixes and improvements, all changes have negligible impact on performance | ||
@@ -37,0 +73,0 @@ ## 1.0.0-alpha13 (January 19, 2017) |
@@ -8,5 +8,7 @@ 'use strict'; | ||
List: require('./utils/list'), | ||
Scanner: require('./scanner'), | ||
Tokenizer: require('./tokenizer'), | ||
Lexer: require('./lexer/Lexer'), | ||
syntax: require('./syntax'), | ||
syntax: require('./lexer'), | ||
property: names.property, | ||
@@ -13,0 +15,0 @@ keyword: names.keyword, |
var List = require('../../utils/list'); | ||
var TYPE = require('../../scanner').TYPE; | ||
var TYPE = require('../../tokenizer').TYPE; | ||
@@ -27,7 +27,5 @@ var STRING = TYPE.String; | ||
this.readSC(); | ||
if (this.scanner.tokenType === IDENTIFIER || | ||
this.scanner.tokenType === LEFTPARENTHESIS) { | ||
children.appendData(this.SPACE_NODE); | ||
if (this.scanner.lookupNonWSType(0) === IDENTIFIER || | ||
this.scanner.lookupNonWSType(0) === LEFTPARENTHESIS) { | ||
children.appendData(this.WhiteSpace()); | ||
children.appendData(this.MediaQueryList()); | ||
@@ -34,0 +32,0 @@ } |
@@ -1,2 +0,2 @@ | ||
var TYPE = require('../../scanner').TYPE; | ||
var TYPE = require('../../tokenizer').TYPE; | ||
var LEFTCURLYBRACKET = TYPE.LeftCurlyBracket; | ||
@@ -3,0 +3,0 @@ |
var List = require('../../utils/list'); | ||
var TYPE = require('../../scanner').TYPE; | ||
var TYPE = require('../../tokenizer').TYPE; | ||
@@ -10,3 +10,2 @@ var WHITESPACE = TYPE.Whitespace; | ||
var COLON = TYPE.Colon; | ||
var DISALLOW_VAR = false; | ||
var BALANCED = true; | ||
@@ -28,9 +27,5 @@ | ||
index = 1; | ||
} else if (this.scanner.tokenType === HYPHENMINUS) { | ||
if (this.scanner.lookupType(1) === IDENTIFIER) { | ||
index = 2; | ||
} else if (this.scanner.lookupType(1) === HYPHENMINUS && | ||
this.scanner.lookupType(2) === IDENTIFIER) { | ||
index = 3; | ||
} | ||
} else if (this.scanner.tokenType === HYPHENMINUS && | ||
this.scanner.lookupType(1) === IDENTIFIER) { | ||
index = 2; | ||
} | ||
@@ -49,3 +44,3 @@ | ||
var children = new List(); | ||
var wasSpace = false; | ||
var space = null; | ||
var child; | ||
@@ -59,4 +54,3 @@ | ||
case WHITESPACE: | ||
this.scanner.next(); | ||
wasSpace = true; | ||
space = this.WhiteSpace(); | ||
continue; | ||
@@ -72,3 +66,3 @@ | ||
} else { | ||
child = this.Identifier(DISALLOW_VAR); | ||
child = this.Identifier(); | ||
} | ||
@@ -86,5 +80,5 @@ | ||
if (wasSpace) { | ||
wasSpace = false; | ||
children.appendData(this.SPACE_NODE); | ||
if (space !== null) { | ||
children.appendData(space); | ||
space = null; | ||
} | ||
@@ -91,0 +85,0 @@ |
var List = require('../../utils/list'); | ||
var TYPE = require('../../scanner').TYPE; | ||
var TYPE = require('../../tokenizer').TYPE; | ||
var IDENTIFIER = TYPE.Identifier; | ||
var COMMA = TYPE.Comma; | ||
var HYPHENMINUS = TYPE.HyphenMinus; | ||
var EXCLAMATIONMARK = TYPE.ExclamationMark; | ||
var ALLOWED_VAR = true; | ||
var BALANCED = true; | ||
@@ -16,10 +16,15 @@ | ||
if (this.scanner.tokenType !== HYPHENMINUS) { | ||
this.scanner.error('Hyphen minus is expected'); | ||
var identStart = this.scanner.tokenStart; | ||
this.scanner.eat(HYPHENMINUS); | ||
if (this.scanner.source.charCodeAt(this.scanner.tokenStart) !== HYPHENMINUS) { | ||
this.scanner.error('HyphenMinus is expected'); | ||
} | ||
if (this.scanner.lookupType(1) !== HYPHENMINUS) { | ||
this.scanner.error('Hyphen minus is expected', this.scanner.tokenStart + 1); | ||
} | ||
this.scanner.eat(IDENTIFIER); | ||
children.appendData(this.Identifier(ALLOWED_VAR)); | ||
children.appendData({ | ||
type: 'Identifier', | ||
loc: this.getLocation(identStart, this.scanner.tokenStart), | ||
name: this.scanner.substrToCursor(identStart) | ||
}); | ||
@@ -30,3 +35,6 @@ this.readSC(); | ||
children.appendData(this.Operator()); | ||
children.appendData(this.Raw(BALANCED, HYPHENMINUS, EXCLAMATIONMARK)); | ||
children.appendData(this.parseCustomProperty | ||
? this.Value(null) | ||
: this.Raw(BALANCED, HYPHENMINUS, EXCLAMATIONMARK) | ||
); | ||
} | ||
@@ -33,0 +41,0 @@ |
@@ -1,224 +0,4 @@ | ||
'use strict'; | ||
var Scanner = require('../scanner'); | ||
var TYPE = Scanner.TYPE; | ||
var WHITESPACE = TYPE.Whitespace; | ||
var IDENTIFIER = TYPE.Identifier; | ||
var COMMENT = TYPE.Comment; | ||
var HYPHENMINUS = TYPE.HyphenMinus; | ||
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'); | ||
function scanIdent(varAllowed) { | ||
// optional first - | ||
if (this.scanner.tokenType === HYPHENMINUS) { | ||
this.scanner.next(); | ||
// variable -- | ||
if (varAllowed && this.scanner.tokenType === HYPHENMINUS) { | ||
this.scanner.next(); | ||
} | ||
} | ||
this.scanner.eat(IDENTIFIER); | ||
} | ||
function readIdent(varAllowed) { | ||
var start = this.scanner.tokenStart; | ||
this.scanIdent(varAllowed); | ||
return this.scanner.substrToCursor(start); | ||
} | ||
function readSC() { | ||
while (this.scanner.tokenType === WHITESPACE || this.scanner.tokenType === COMMENT) { | ||
this.scanner.next(); | ||
} | ||
} | ||
var Parser = function() { | ||
this.scanner = new Scanner(); | ||
this.needPositions = false; | ||
this.filename = '<unknown>'; | ||
}; | ||
Parser.prototype = { | ||
SPACE_NODE: Object.freeze({ type: 'Space' }), | ||
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 | ||
}, | ||
// 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, | ||
scanIdent: scanIdent, | ||
readIdent: readIdent, | ||
readSC: readSC, | ||
readSequence: sequence.default, | ||
getLocation: function getLocation(start, end) { | ||
if (this.needPositions) { | ||
return this.scanner.getLocationRange( | ||
start, | ||
end, | ||
this.filename | ||
); | ||
} | ||
return null; | ||
}, | ||
parse: function parse(source, options) { | ||
options = options || {}; | ||
var context = options.context || 'stylesheet'; | ||
var ast; | ||
this.scanner.setSource(source, options.line, options.column); | ||
this.filename = options.filename || '<unknown>'; | ||
this.needPositions = Boolean(options.positions); | ||
switch (context) { | ||
case 'value': | ||
ast = this.Value(options.property ? String(options.property) : null); | ||
break; | ||
case 'atruleExpression': | ||
ast = this.Value(options.atrule ? String(options.atrule) : null); | ||
break; | ||
default: | ||
if (!this.context.hasOwnProperty(context)) { | ||
throw new Error('Unknown context `' + context + '`'); | ||
} | ||
ast = this.context[context].call(this); | ||
} | ||
if (!this.scanner.eof) { | ||
this.scanner.error(); | ||
} | ||
// console.log(JSON.stringify(ast, null, 4)); | ||
return ast; | ||
} | ||
}; | ||
var Parser = require('./Parser'); | ||
var parser = new Parser(); | ||
// warm up parse to elimitate code branches that never execute | ||
// fix soft deoptimizations (insufficient type feedback) | ||
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 = parser.parse.bind(parser); |
var List = require('../utils/list'); | ||
var cmpChar = require('../scanner').cmpChar; | ||
var TYPE = require('../scanner').TYPE; | ||
var cmpChar = require('../tokenizer').cmpChar; | ||
var TYPE = require('../tokenizer').TYPE; | ||
@@ -21,12 +21,8 @@ var WHITESPACE = TYPE.Whitespace; | ||
var ABSOLUTE = false; | ||
var RELATIVE = true; | ||
var ALLOW_OF_CLAUSE = true; | ||
var DISALLOW_OF_CLAUSE = false; | ||
var DISALLOW_VAR = false; | ||
var DISALLOW_COMBINATORS = true; | ||
function singleIdentifier() { | ||
return new List().appendData( | ||
this.Identifier(DISALLOW_VAR) | ||
this.Identifier() | ||
); | ||
@@ -37,3 +33,3 @@ } | ||
return new List().appendData( | ||
this.SelectorList(ABSOLUTE) | ||
this.SelectorList() | ||
); | ||
@@ -44,9 +40,9 @@ } | ||
return new List().appendData( | ||
this.SelectorList(RELATIVE) | ||
this.SelectorList() | ||
); | ||
} | ||
function compoundSelectorList() { | ||
function compoundSelector() { | ||
return new List().appendData( | ||
this.Selector(ABSOLUTE, DISALLOW_COMBINATORS) | ||
this.Selector() | ||
); | ||
@@ -69,5 +65,5 @@ } | ||
var children = new List(); | ||
var wasSpace = false; | ||
var nonSpaceOperator = false; | ||
var prevNonSpaceOperator = false; | ||
var space = null; | ||
var nonWSOperator = false; | ||
var prevNonWSOperator = false; | ||
var child; | ||
@@ -81,4 +77,3 @@ | ||
case WHITESPACE: | ||
wasSpace = true; | ||
this.scanner.next(); | ||
space = this.WhiteSpace(); | ||
continue; | ||
@@ -91,8 +86,8 @@ | ||
case NUMBERSIGN: | ||
child = this.Hash(); | ||
child = this.HexColor(); | ||
break; | ||
case COMMA: | ||
wasSpace = false; | ||
nonSpaceOperator = true; | ||
space = null; | ||
nonWSOperator = true; | ||
child = this.Operator(); | ||
@@ -104,18 +99,6 @@ break; | ||
case PLUSSIGN: | ||
case HYPHENMINUS: | ||
child = this.Operator(); | ||
break; | ||
case HYPHENMINUS: | ||
if (this.scanner.lookupType(1) === IDENTIFIER) { | ||
if (this.scanner.lookupType(2) === LEFTPARENTHESIS) { | ||
child = this.Function(scope, defaultSequence); | ||
} else { | ||
child = this.Identifier(DISALLOW_VAR); | ||
} | ||
} else { | ||
child = this.Operator(); | ||
} | ||
break; | ||
case LEFTPARENTHESIS: | ||
@@ -145,3 +128,2 @@ child = this.Parentheses(defaultSequence); | ||
child = this.Number(); | ||
break; | ||
} | ||
@@ -163,3 +145,3 @@ | ||
} else { | ||
child = this.Identifier(DISALLOW_VAR); | ||
child = this.Identifier(); | ||
} | ||
@@ -173,14 +155,14 @@ | ||
if (wasSpace) { | ||
wasSpace = false; | ||
if (space !== null) { | ||
// ignore spaces around operator | ||
if (!nonSpaceOperator && !prevNonSpaceOperator) { | ||
children.appendData(this.SPACE_NODE); | ||
if (!nonWSOperator && !prevNonWSOperator) { | ||
children.appendData(space); | ||
} | ||
space = null; | ||
} | ||
children.appendData(child); | ||
prevNonSpaceOperator = nonSpaceOperator; | ||
nonSpaceOperator = false; | ||
prevNonWSOperator = nonWSOperator; | ||
nonWSOperator = false; | ||
} | ||
@@ -195,3 +177,3 @@ | ||
relativeSelectorList: relativeSelectorList, | ||
compoundSelectorList: compoundSelectorList, | ||
compoundSelector: compoundSelector, | ||
nth: nth, | ||
@@ -198,0 +180,0 @@ nthWithOfClause: nthWithOfClause, |
@@ -1,3 +0,4 @@ | ||
var TYPE = require('../../scanner').TYPE; | ||
var TYPE = require('../../tokenizer').TYPE; | ||
var IDENTIFIER = TYPE.Identifier; | ||
var SEMICOLON = TYPE.Semicolon; | ||
@@ -7,3 +8,3 @@ var COMMERCIALAT = TYPE.CommercialAt; | ||
var RIGHTCURLYBRACKET = TYPE.RightCurlyBracket; | ||
var DISALLOW_VAR = false; | ||
var BALANCED = true; | ||
@@ -35,8 +36,12 @@ function isBlockAtrule() { | ||
name = this.readIdent(DISALLOW_VAR); | ||
name = this.scanner.consume(IDENTIFIER); | ||
nameLowerCase = name.toLowerCase(); | ||
this.readSC(); | ||
expression = this.AtruleExpression(name); | ||
this.readSC(); | ||
if (this.parseAtruleExpression) { | ||
expression = this.AtruleExpression(name); | ||
this.readSC(); | ||
} else { | ||
expression = this.Raw(BALANCED, SEMICOLON, LEFTCURLYBRACKET); | ||
} | ||
@@ -43,0 +48,0 @@ if (this.atrule.hasOwnProperty(nameLowerCase)) { |
var List = require('../../utils/list'); | ||
module.exports = function AtruleExpression(name) { | ||
var start = this.scanner.tokenStart; | ||
var end = start; | ||
var children = null; | ||
name = name.toLowerCase(); | ||
if (name !== null) { | ||
name = name.toLowerCase(); | ||
} | ||
@@ -14,2 +14,3 @@ // custom consumer | ||
children = this.atrule[name].expression.call(this); | ||
if (children instanceof List === false) { | ||
@@ -29,11 +30,7 @@ return children; | ||
if (this.needPositions) { | ||
end = children.last().loc.end.offset; | ||
} | ||
return { | ||
type: 'AtruleExpression', | ||
loc: this.getLocation(start, end), | ||
loc: this.getLocationFromList(children), | ||
children: children | ||
}; | ||
}; |
var List = require('../../utils/list'); | ||
var TYPE = require('../../scanner').TYPE; | ||
var TYPE = require('../../tokenizer').TYPE; | ||
@@ -4,0 +4,0 @@ var WHITESPACE = TYPE.Whitespace; |
@@ -1,2 +0,2 @@ | ||
var TYPE = require('../../scanner').TYPE; | ||
var TYPE = require('../../tokenizer').TYPE; | ||
var LEFTSQUAREBRACKET = TYPE.LeftSquareBracket; | ||
@@ -10,3 +10,3 @@ var RIGHTSQUAREBRACKET = TYPE.RightSquareBracket; | ||
var start = this.scanner.tokenStart; | ||
var children; | ||
var children = null; | ||
@@ -13,0 +13,0 @@ this.scanner.eat(LEFTSQUAREBRACKET); |
@@ -1,2 +0,2 @@ | ||
var TYPE = require('../../scanner').TYPE; | ||
var TYPE = require('../../tokenizer').TYPE; | ||
@@ -3,0 +3,0 @@ var PLUSSIGN = TYPE.PlusSign; |
@@ -1,2 +0,2 @@ | ||
var TYPE = require('../../scanner').TYPE; | ||
var TYPE = require('../../tokenizer').TYPE; | ||
@@ -3,0 +3,0 @@ var ASTERISK = TYPE.Asterisk; |
@@ -1,3 +0,4 @@ | ||
var TYPE = require('../../scanner').TYPE; | ||
var TYPE = require('../../tokenizer').TYPE; | ||
var IDENTIFIER = TYPE.Identifier; | ||
var COLON = TYPE.Colon; | ||
@@ -16,14 +17,27 @@ var EXCLAMATIONMARK = TYPE.ExclamationMark; | ||
var start = this.scanner.tokenStart; | ||
var type; | ||
var prefix = 0; | ||
for (; type = this.scanner.tokenType; this.scanner.next()) { | ||
if (type !== SOLIDUS && | ||
type !== ASTERISK && | ||
type !== DOLLARSIGN) { | ||
// hacks | ||
switch (this.scanner.tokenType) { | ||
case ASTERISK: | ||
case DOLLARSIGN: | ||
prefix = 1; | ||
break; | ||
} | ||
// TODO: not sure we should support this hack | ||
case SOLIDUS: | ||
prefix = this.scanner.lookupType(1) === SOLIDUS ? 2 : 1; | ||
break; | ||
} | ||
this.scanIdent(true); | ||
if (this.scanner.lookupType(prefix) === HYPHENMINUS) { | ||
prefix++; | ||
} | ||
if (prefix) { | ||
this.scanner.skip(prefix); | ||
} | ||
this.scanner.eat(IDENTIFIER); | ||
return this.scanner.substrToCursor(start); | ||
@@ -44,2 +58,8 @@ } | ||
function isCustomProperty(name) { | ||
return name.length >= 2 && | ||
name.charCodeAt(0) === HYPHENMINUS && | ||
name.charCodeAt(1) === HYPHENMINUS; | ||
} | ||
module.exports = function Declaration() { | ||
@@ -54,8 +74,6 @@ var start = this.scanner.tokenStart; | ||
if (property.length >= 2 && | ||
property.charCodeAt(0) === HYPHENMINUS && | ||
property.charCodeAt(1) === HYPHENMINUS) { | ||
if (isCustomProperty(property) ? this.parseCustomProperty : this.parseValue) { | ||
value = this.Value(property); | ||
} else { | ||
value = this.Raw(BALANCED, SEMICOLON, EXCLAMATIONMARK); | ||
} else { | ||
value = this.Value(property); | ||
} | ||
@@ -75,4 +93,4 @@ | ||
this.scanner.tokenType !== SEMICOLON && | ||
this.scanner.tokenType !== RIGHTCURLYBRACKET && | ||
this.scanner.tokenType !== RIGHTPARENTHESIS) { | ||
this.scanner.tokenType !== RIGHTPARENTHESIS && | ||
this.scanner.tokenType !== RIGHTCURLYBRACKET) { | ||
this.scanner.error(); | ||
@@ -79,0 +97,0 @@ } |
var List = require('../../utils/list'); | ||
var TYPE = require('../../scanner').TYPE; | ||
var TYPE = require('../../tokenizer').TYPE; | ||
@@ -9,3 +9,2 @@ var WHITESPACE = TYPE.Whitespace; | ||
module.exports = function DeclarationList() { | ||
var start = this.scanner.tokenStart; | ||
var children = new List(); | ||
@@ -29,5 +28,5 @@ | ||
type: 'DeclarationList', | ||
loc: this.getLocation(start, this.scanner.tokenStart), | ||
loc: this.getLocationFromList(children), | ||
children: children | ||
}; | ||
}; |
@@ -1,2 +0,2 @@ | ||
var NUMBER = require('../../scanner').TYPE.Number; | ||
var NUMBER = require('../../tokenizer').TYPE.Number; | ||
@@ -3,0 +3,0 @@ // special reader for units to avoid adjoined IE hacks (i.e. '1px\9') |
@@ -1,6 +0,6 @@ | ||
var TYPE = require('../../scanner').TYPE; | ||
var TYPE = require('../../tokenizer').TYPE; | ||
var IDENTIFIER = TYPE.Identifier; | ||
var LEFTPARENTHESIS = TYPE.LeftParenthesis; | ||
var RIGHTPARENTHESIS = TYPE.RightParenthesis; | ||
var DISALLOW_VAR = false; | ||
@@ -10,3 +10,3 @@ // ident '(' <sequence> ')' | ||
var start = this.scanner.tokenStart; | ||
var name = this.readIdent(DISALLOW_VAR); | ||
var name = this.scanner.consume(IDENTIFIER); | ||
var nameLowerCase = name.toLowerCase(); | ||
@@ -13,0 +13,0 @@ var children; |
@@ -1,10 +0,10 @@ | ||
module.exports = function Identifier(varAllowed) { | ||
var start = this.scanner.tokenStart; | ||
var name = this.readIdent(varAllowed); | ||
var TYPE = require('../../tokenizer').TYPE; | ||
var IDENTIFIER = TYPE.Identifier; | ||
module.exports = function Identifier() { | ||
return { | ||
type: 'Identifier', | ||
loc: this.getLocation(start, this.scanner.tokenStart), | ||
name: name | ||
loc: this.getLocation(this.scanner.tokenStart, this.scanner.tokenEnd), | ||
name: this.scanner.consume(IDENTIFIER) | ||
}; | ||
}; |
@@ -1,2 +0,2 @@ | ||
var TYPE = require('../../scanner').TYPE; | ||
var TYPE = require('../../tokenizer').TYPE; | ||
@@ -9,3 +9,2 @@ var IDENTIFIER = TYPE.Identifier; | ||
var SOLIDUS = TYPE.Solidus; | ||
var DISALLOW_VAR = false; | ||
@@ -20,3 +19,3 @@ module.exports = function MediaFeature() { | ||
name = this.readIdent(DISALLOW_VAR); | ||
name = this.scanner.consume(IDENTIFIER); | ||
this.readSC(); | ||
@@ -41,3 +40,3 @@ | ||
case IDENTIFIER: | ||
value = this.Identifier(DISALLOW_VAR); | ||
value = this.Identifier(); | ||
@@ -44,0 +43,0 @@ break; |
var List = require('../../utils/list'); | ||
var TYPE = require('../../scanner').TYPE; | ||
var TYPE = require('../../tokenizer').TYPE; | ||
@@ -8,3 +8,2 @@ var WHITESPACE = TYPE.Whitespace; | ||
var LEFTPARENTHESIS = TYPE.LeftParenthesis; | ||
var DISALLOW_VAR = false; | ||
@@ -14,7 +13,5 @@ module.exports = function MediaQuery() { | ||
var start = this.scanner.tokenStart; | ||
var end = start; | ||
var children = new List(); | ||
var wasSpace = false; | ||
var child = null; | ||
var space = null; | ||
@@ -24,24 +21,15 @@ scan: | ||
switch (this.scanner.tokenType) { | ||
case WHITESPACE: | ||
wasSpace = true; | ||
case COMMENT: | ||
this.scanner.next(); | ||
continue; | ||
case COMMENT: | ||
this.scanner.next(); | ||
case WHITESPACE: | ||
space = this.WhiteSpace(); | ||
continue; | ||
case IDENTIFIER: | ||
if (!children.isEmpty() && !wasSpace) { | ||
this.scanner.error('Space is expected'); | ||
} | ||
child = this.Identifier(DISALLOW_VAR); | ||
child = this.Identifier(); | ||
break; | ||
case LEFTPARENTHESIS: | ||
if (!children.isEmpty() && !wasSpace) { | ||
this.scanner.error('Space is expected'); | ||
} | ||
child = this.MediaFeature(); | ||
@@ -54,2 +42,7 @@ break; | ||
if (space !== null) { | ||
children.appendData(space); | ||
space = null; | ||
} | ||
children.appendData(child); | ||
@@ -62,11 +55,7 @@ } | ||
if (this.needPositions) { | ||
end = child.loc.end.offset; | ||
} | ||
return { | ||
type: 'MediaQuery', | ||
loc: this.getLocation(start, end), | ||
loc: this.getLocationFromList(children), | ||
children: children | ||
}; | ||
}; |
var List = require('../../utils/list'); | ||
var COMMA = require('../../scanner').TYPE.Comma; | ||
var COMMA = require('../../tokenizer').TYPE.Comma; | ||
module.exports = function MediaQueryList(relative) { | ||
var children = new List(); | ||
this.readSC(); | ||
var start = this.scanner.tokenStart; | ||
var end = start; | ||
var children = new List(); | ||
var mediaQuery = null; | ||
while (!this.scanner.eof) { | ||
mediaQuery = this.MediaQuery(relative); | ||
children.appendData(mediaQuery); | ||
children.appendData(this.MediaQuery(relative)); | ||
@@ -23,11 +19,7 @@ if (this.scanner.tokenType !== COMMA) { | ||
if (this.needPositions) { | ||
end = mediaQuery.children.last().loc.end.offset; | ||
} | ||
return { | ||
type: 'MediaQueryList', | ||
loc: this.getLocation(start, end), | ||
loc: this.getLocationFromList(children), | ||
children: children | ||
}; | ||
}; |
@@ -1,3 +0,1 @@ | ||
var DISALLOW_VAR = false; | ||
// https://drafts.csswg.org/css-syntax-3/#the-anb-type | ||
@@ -8,3 +6,3 @@ module.exports = function Nth(allowOfClause) { | ||
var start = this.scanner.tokenStart; | ||
var end; | ||
var end = start; | ||
var selector = null; | ||
@@ -14,4 +12,3 @@ var query; | ||
if (this.scanner.lookupValue(0, 'odd') || this.scanner.lookupValue(0, 'even')) { | ||
query = this.Identifier(DISALLOW_VAR); | ||
end = this.scanner.tokenStart; | ||
query = this.Identifier(); | ||
} else { | ||
@@ -29,3 +26,3 @@ query = this.AnPlusB(); | ||
if (this.needPositions) { | ||
end = selector.children.last().children.last().loc.end.offset; | ||
end = selector.children.last().loc.end.offset; | ||
} | ||
@@ -32,0 +29,0 @@ } else { |
@@ -1,2 +0,2 @@ | ||
var NUMBER = require('../../scanner').TYPE.Number; | ||
var NUMBER = require('../../tokenizer').TYPE.Number; | ||
@@ -3,0 +3,0 @@ module.exports = function Number() { |
@@ -1,2 +0,2 @@ | ||
var TYPE = require('../../scanner').TYPE; | ||
var TYPE = require('../../tokenizer').TYPE; | ||
var LEFTPARENTHESIS = TYPE.LeftParenthesis; | ||
@@ -3,0 +3,0 @@ var RIGHTPARENTHESIS = TYPE.RightParenthesis; |
@@ -1,2 +0,2 @@ | ||
var TYPE = require('../../scanner').TYPE; | ||
var TYPE = require('../../tokenizer').TYPE; | ||
@@ -3,0 +3,0 @@ var NUMBER = TYPE.Number; |
@@ -1,3 +0,3 @@ | ||
var isNumber = require('../../scanner').isNumber; | ||
var TYPE = require('../../scanner').TYPE; | ||
var isNumber = require('../../tokenizer').isNumber; | ||
var TYPE = require('../../tokenizer').TYPE; | ||
var NUMBER = TYPE.Number; | ||
@@ -4,0 +4,0 @@ var SOLIDUS = TYPE.Solidus; |
@@ -1,2 +0,2 @@ | ||
var TYPE = require('../../scanner').TYPE; | ||
var TYPE = require('../../tokenizer').TYPE; | ||
@@ -15,11 +15,16 @@ var WHITESPACE = TYPE.Whitespace; | ||
var popType = 0; | ||
var type = 0; | ||
if (balanced) { | ||
scan: | ||
for (; !this.scanner.eof; this.scanner.next()) { | ||
switch (this.scanner.tokenType) { | ||
for (var i = 0; type = this.scanner.lookupType(i); i++) { | ||
if (popType === 0) { | ||
if (type === endTokenType1 || | ||
type === endTokenType2) { | ||
break scan; | ||
} | ||
} | ||
switch (type) { | ||
case popType: | ||
if (stack.length === 0) { | ||
break scan; | ||
} | ||
popType = stack.pop(); | ||
@@ -31,3 +36,4 @@ break; | ||
case RIGHTSQUAREBRACKET: | ||
if (stack.length !== 0) { | ||
if (popType !== 0) { | ||
this.scanner.skip(i); | ||
this.scanner.error(); | ||
@@ -51,15 +57,6 @@ } | ||
break; | ||
case endTokenType1: | ||
case endTokenType2: | ||
if (stack.length === 0) { | ||
break scan; | ||
} | ||
break; | ||
} | ||
} | ||
} else { | ||
for (; !this.scanner.eof; this.scanner.next()) { | ||
var type = this.scanner.tokenType; | ||
for (var i = 0; type = this.scanner.lookupType(i); i++) { | ||
if (type === WHITESPACE || | ||
@@ -73,2 +70,8 @@ type === endTokenType1 || | ||
this.scanner.skip(i); | ||
if (popType !== 0) { | ||
this.scanner.eat(popType); | ||
} | ||
return { | ||
@@ -75,0 +78,0 @@ type: 'Raw', |
@@ -0,4 +1,8 @@ | ||
var TYPE = require('../../tokenizer').TYPE; | ||
var LEFTCURLYBRACKET = TYPE.LeftCurlyBracket; | ||
var BALANCED = true; | ||
module.exports = function Rule() { | ||
var start = this.scanner.tokenStart; | ||
var selector = this.SelectorList(); | ||
var selector = this.parseSelector ? this.SelectorList() : this.Raw(BALANCED, LEFTCURLYBRACKET, 0); | ||
var block = this.Block(this.Declaration); | ||
@@ -5,0 +9,0 @@ |
var List = require('../../utils/list'); | ||
var TYPE = require('../../scanner').TYPE; | ||
var DESCENDANT_COMBINATOR = {}; | ||
var TYPE = require('../../tokenizer').TYPE; | ||
@@ -20,14 +19,10 @@ var WHITESPACE = TYPE.Whitespace; | ||
module.exports = function Selector(relative, disallowCombinators) { | ||
this.readSC(); | ||
var start = this.scanner.tokenStart; | ||
var end = start; | ||
module.exports = function Selector() { | ||
var children = new List(); | ||
var combinator = null; | ||
var combinatorOffset = -1; | ||
var space = null; | ||
var child = null; | ||
var ignoreWSAfter = false; | ||
var ignoreWS = false; | ||
relative = relative || false; | ||
disallowCombinators = disallowCombinators || false; | ||
this.readSC(); | ||
@@ -38,7 +33,2 @@ scan: | ||
case COMMENT: | ||
if (disallowCombinators) { | ||
this.readSC(); | ||
break scan; | ||
} | ||
this.scanner.next(); | ||
@@ -48,12 +38,6 @@ continue; | ||
case WHITESPACE: | ||
if (disallowCombinators) { | ||
this.readSC(); | ||
break scan; | ||
} | ||
if (combinator === null && children.head !== null) { | ||
combinatorOffset = this.scanner.tokenStart; | ||
combinator = DESCENDANT_COMBINATOR; | ||
if (ignoreWS) { | ||
this.scanner.next(); | ||
} else { | ||
this.scanner.next(); | ||
space = this.WhiteSpace(); | ||
} | ||
@@ -65,23 +49,21 @@ continue; | ||
case TILDE: | ||
case SOLIDUS: | ||
if (disallowCombinators || | ||
(children.head === null && !relative) || // combinator in the beginning | ||
(combinator !== null && combinator !== DESCENDANT_COMBINATOR)) { | ||
this.scanner.error('Unexpected combinator'); | ||
} | ||
space = null; | ||
ignoreWSAfter = true; | ||
child = this.Combinator(); | ||
break; | ||
combinatorOffset = this.scanner.tokenStart; | ||
combinator = this.Combinator(); | ||
continue; | ||
case SOLIDUS: // /deep/ | ||
child = this.Combinator(); | ||
break; | ||
case FULLSTOP: | ||
child = this.Class(); | ||
child = this.ClassSelector(); | ||
break; | ||
case LEFTSQUAREBRACKET: | ||
child = this.Attribute(); | ||
child = this.AttributeSelector(); | ||
break; | ||
case NUMBERSIGN: | ||
child = this.Id(); | ||
child = this.IdSelector(); | ||
break; | ||
@@ -91,5 +73,5 @@ | ||
if (this.scanner.lookupType(1) === COLON) { | ||
child = this.PseudoElement(); | ||
child = this.PseudoElementSelector(); | ||
} else { | ||
child = this.PseudoClass(); | ||
child = this.PseudoClassSelector(); | ||
} | ||
@@ -102,21 +84,3 @@ | ||
case VERTICALLINE: | ||
var idx = | ||
this.scanner.tokenType === VERTICALLINE ? 1 : | ||
this.scanner.lookupType(1) === VERTICALLINE ? 2 : | ||
0; | ||
switch (this.scanner.lookupType(idx)) { | ||
case IDENTIFIER: | ||
child = this.Type(); | ||
break; | ||
case ASTERISK: | ||
child = this.Universal(); | ||
break; | ||
default: | ||
this.scanner.skip(idx); | ||
this.scanner.error('Identifier or asterisk is expected'); | ||
} | ||
child = this.TypeSelector(); | ||
break; | ||
@@ -132,18 +96,13 @@ | ||
if (combinator !== null) { | ||
// create descendant combinator on demand to avoid garbage | ||
if (combinator === DESCENDANT_COMBINATOR) { | ||
combinator = { | ||
type: 'Combinator', | ||
loc: this.getLocation(combinatorOffset, combinatorOffset + 1), | ||
name: ' ' | ||
}; | ||
} | ||
children.appendData(combinator); | ||
combinator = null; | ||
if (space !== null) { | ||
children.appendData(space); | ||
space = null; | ||
} | ||
children.appendData(child); | ||
end = this.scanner.tokenStart; | ||
if (ignoreWSAfter) { | ||
ignoreWSAfter = false; | ||
ignoreWS = true; | ||
} | ||
} | ||
@@ -156,11 +115,7 @@ | ||
if (combinator !== null && combinator !== DESCENDANT_COMBINATOR) { | ||
this.scanner.error('Unexpected combinator', combinatorOffset); | ||
} | ||
return { | ||
type: 'Selector', | ||
loc: this.getLocation(start, end), | ||
loc: this.getLocationFromList(children), | ||
children: children | ||
}; | ||
}; |
var List = require('../../utils/list'); | ||
var COMMA = require('../../scanner').TYPE.Comma; | ||
var TYPE = require('../../tokenizer').TYPE; | ||
module.exports = function SelectorList(relative) { | ||
this.readSC(); | ||
var COMMA = TYPE.Comma; | ||
var LEFTCURLYBRACKET = TYPE.LeftCurlyBracket; | ||
var BALANCED = true; | ||
var start = this.scanner.tokenStart; | ||
var end = start; | ||
module.exports = function SelectorList() { | ||
var children = new List(); | ||
var selector = null; | ||
this.readSC(); | ||
while (!this.scanner.eof) { | ||
selector = this.Selector(relative); | ||
children.appendData(selector); | ||
children.appendData(this.parseSelector | ||
? this.Selector() | ||
: this.Raw(BALANCED, COMMA, LEFTCURLYBRACKET) | ||
); | ||
if (this.needPositions) { | ||
end = selector.children.last().loc.end.offset; | ||
} | ||
if (this.scanner.tokenType === COMMA) { | ||
@@ -30,5 +29,5 @@ this.scanner.next(); | ||
type: 'SelectorList', | ||
loc: this.getLocation(start, end), | ||
loc: this.getLocationFromList(children), | ||
children: children | ||
}; | ||
}; |
@@ -1,2 +0,2 @@ | ||
var STRING = require('../../scanner').TYPE.String; | ||
var STRING = require('../../tokenizer').TYPE.String; | ||
@@ -3,0 +3,0 @@ module.exports = function String() { |
var List = require('../../utils/list'); | ||
var TYPE = require('../../scanner').TYPE; | ||
var TYPE = require('../../tokenizer').TYPE; | ||
@@ -4,0 +4,0 @@ var WHITESPACE = TYPE.Whitespace; |
@@ -1,3 +0,3 @@ | ||
var isHex = require('../../scanner').isHex; | ||
var TYPE = require('../../scanner').TYPE; | ||
var isHex = require('../../tokenizer').isHex; | ||
var TYPE = require('../../tokenizer').TYPE; | ||
@@ -4,0 +4,0 @@ var IDENTIFIER = TYPE.Identifier; |
@@ -1,2 +0,2 @@ | ||
var TYPE = require('../../scanner').TYPE; | ||
var TYPE = require('../../tokenizer').TYPE; | ||
@@ -3,0 +3,0 @@ var STRING = TYPE.String; |
@@ -1,12 +0,11 @@ | ||
var List = require('../../utils/list'); | ||
var endsWith = require('../../scanner').endsWith; | ||
var TYPE = require('../../scanner').TYPE; | ||
var endsWith = require('../../tokenizer').endsWith; | ||
var TYPE = require('../../tokenizer').TYPE; | ||
var WHITESPACE = TYPE.Whitespace; | ||
var IDENTIFIER = TYPE.Identifier; | ||
var COMMENT = TYPE.Comment; | ||
var LEFTPARENTHESIS = TYPE.LeftParenthesis; | ||
var RIGHTPARENTHESIS = TYPE.RightParenthesis; | ||
var FULLSTOP = TYPE.FullStop; | ||
var COLON = TYPE.Colon; | ||
var SEMICOLON = TYPE.Semicolon; | ||
var EXCLAMATIONMARK = TYPE.ExclamationMark; | ||
var BALANCED = true; | ||
@@ -16,3 +15,4 @@ module.exports = function Value(property) { | ||
if (property !== null && endsWith(property, 'filter') && checkProgid.call(this)) { | ||
return FilterValue.call(this); | ||
this.readSC(); | ||
return this.Raw(BALANCED, SEMICOLON, EXCLAMATIONMARK); | ||
} | ||
@@ -30,21 +30,2 @@ | ||
function FilterValue() { | ||
var start = this.scanner.tokenStart; | ||
var children = new List(); | ||
var progid; | ||
while (progid = checkProgid.call(this)) { | ||
this.readSC(); | ||
children.appendData(this.Progid(progid)); | ||
} | ||
this.readSC(); | ||
return { | ||
type: 'Value', | ||
loc: this.getLocation(start, this.scanner.tokenStart), | ||
children: children | ||
}; | ||
} | ||
function findNonSCOffset(offset) { | ||
@@ -67,3 +48,5 @@ for (var type; type = this.scanner.lookupType(offset); offset++) { | ||
this.scanner.lookupValue(offset, 'dropshadow')) { | ||
offset++; | ||
if (this.scanner.lookupType(offset + 1) !== LEFTPARENTHESIS) { | ||
return false; // fail | ||
} | ||
} else { | ||
@@ -74,29 +57,5 @@ if (this.scanner.lookupValue(offset, 'progid') === false || | ||
} | ||
offset += 2; | ||
offset = findNonSCOffset.call(this, offset); | ||
if (this.scanner.lookupValue(offset + 0, 'dximagetransform') === false || | ||
this.scanner.lookupType(offset + 1) !== FULLSTOP || | ||
this.scanner.lookupValue(offset + 2, 'microsoft') === false || | ||
this.scanner.lookupType(offset + 3) !== FULLSTOP || | ||
this.scanner.lookupType(offset + 4) !== IDENTIFIER) { | ||
return false; // fail | ||
} | ||
offset += 5; | ||
offset = findNonSCOffset.call(this, offset); | ||
} | ||
if (this.scanner.lookupType(offset) !== LEFTPARENTHESIS) { | ||
return false; // fail | ||
} | ||
for (var type; type = this.scanner.lookupType(offset); offset++) { | ||
if (type === RIGHTPARENTHESIS) { | ||
return offset - startOffset + 1; | ||
} | ||
} | ||
return false; | ||
return true; | ||
} |
@@ -280,7 +280,7 @@ 'use strict'; | ||
while (cursor !== null) { | ||
if (prevNew === true || cursor.prev === prevOld) { | ||
if (cursor.prev === prevOld) { | ||
cursor.prev = prevNew; | ||
} | ||
if (nextNew === true || cursor.next === nextOld) { | ||
if (cursor.next === nextOld) { | ||
cursor.next = nextNew; | ||
@@ -287,0 +287,0 @@ } |
'use strict'; | ||
var hasOwnProperty = Object.prototype.hasOwnProperty; | ||
var knownKeywords = Object.create(null); | ||
var knownProperties = Object.create(null); | ||
var keywords = Object.create(null); | ||
var properties = Object.create(null); | ||
var HYPHENMINUS = 45; // '-'.charCodeAt() | ||
function getVendorPrefix(string) { | ||
if (string[0] === '-') { | ||
// skip 2 chars to avoid wrong match with variables names | ||
var secondDashIndex = string.indexOf('-', 2); | ||
function isVariable(str, offset) { | ||
return str.charCodeAt(offset) === HYPHENMINUS && | ||
str.charCodeAt(offset + 1) === HYPHENMINUS; | ||
} | ||
function getVendorPrefix(str, offset) { | ||
if (str.charCodeAt(offset) === HYPHENMINUS) { | ||
// vendor should contain at least one letter | ||
var secondDashIndex = str.indexOf('-', offset + 2); | ||
if (secondDashIndex !== -1) { | ||
return string.substr(0, secondDashIndex + 1); | ||
return str.substring(offset, secondDashIndex + 1); | ||
} | ||
@@ -21,18 +27,18 @@ } | ||
function getKeywordInfo(keyword) { | ||
if (hasOwnProperty.call(knownKeywords, keyword)) { | ||
return knownKeywords[keyword]; | ||
if (hasOwnProperty.call(keywords, keyword)) { | ||
return keywords[keyword]; | ||
} | ||
var lowerCaseKeyword = keyword.toLowerCase(); | ||
var vendor = getVendorPrefix(lowerCaseKeyword); | ||
var name = lowerCaseKeyword; | ||
var name = keyword.toLowerCase(); | ||
if (vendor) { | ||
name = name.substr(vendor.length); | ||
if (hasOwnProperty.call(keywords, name)) { | ||
return keywords[keyword] = keywords[name]; | ||
} | ||
return knownKeywords[keyword] = Object.freeze({ | ||
var vendor = !isVariable(name, 0) ? getVendorPrefix(name, 0) : ''; | ||
return keywords[keyword] = Object.freeze({ | ||
vendor: vendor, | ||
prefix: vendor, | ||
name: name | ||
name: name.substr(vendor.length) | ||
}); | ||
@@ -42,30 +48,32 @@ } | ||
function getPropertyInfo(property) { | ||
if (hasOwnProperty.call(knownProperties, property)) { | ||
return knownProperties[property]; | ||
if (hasOwnProperty.call(properties, property)) { | ||
return properties[property]; | ||
} | ||
var lowerCaseProperty = property.toLowerCase(); | ||
var hack = lowerCaseProperty[0]; | ||
var name = property; | ||
var hack = property[0]; | ||
if (hack === '*' || hack === '_' || hack === '$') { | ||
lowerCaseProperty = lowerCaseProperty.substr(1); | ||
} else if (hack === '/' && property[1] === '/') { | ||
if (hack === '/' && property[1] === '/') { | ||
hack = '//'; | ||
lowerCaseProperty = lowerCaseProperty.substr(2); | ||
} else { | ||
} else if (hack !== '*' && hack !== '_' && hack !== '$') { | ||
hack = ''; | ||
} | ||
var vendor = getVendorPrefix(lowerCaseProperty); | ||
var name = lowerCaseProperty; | ||
var variable = isVariable(name, hack.length); | ||
if (vendor) { | ||
name = name.substr(vendor.length); | ||
if (!variable) { | ||
name = name.toLowerCase(); | ||
if (hasOwnProperty.call(properties, name)) { | ||
return properties[property] = properties[name]; | ||
} | ||
} | ||
return knownProperties[property] = Object.freeze({ | ||
var vendor = !variable ? getVendorPrefix(name, hack.length) : ''; | ||
return properties[property] = Object.freeze({ | ||
hack: hack, | ||
vendor: vendor, | ||
prefix: hack + vendor, | ||
name: name | ||
name: name.substr(hack.length + vendor.length), | ||
variable: variable | ||
}); | ||
@@ -72,0 +80,0 @@ } |
@@ -50,3 +50,3 @@ 'use strict'; | ||
if (node.expression && !node.expression.children.isEmpty()) { | ||
if (node.expression !== null) { | ||
result += ' ' + translate(node.expression); | ||
@@ -77,8 +77,3 @@ } | ||
while (cursor !== null) { | ||
if (cursor.data.type === 'Combinator' && cursor.data.name === '/deep/') { | ||
// add extra spaces around /deep/ combinator since comment beginning/ending may to be produced | ||
result += ' ' + translate(cursor.data) + ' '; | ||
} else { | ||
result += translate(cursor.data); | ||
} | ||
result += translate(cursor.data); | ||
cursor = cursor.next; | ||
@@ -151,3 +146,3 @@ } | ||
case 'Attribute': | ||
case 'AttributeSelector': | ||
var result = translate(node.name); | ||
@@ -191,3 +186,3 @@ var flagsPrefix = ' '; | ||
case 'MediaQuery': | ||
return eachDelim(node.children, ' '); | ||
return each(node.children); | ||
@@ -202,18 +197,12 @@ case 'MediaFeature': | ||
case 'Progid': | ||
return node.value; | ||
case 'Combinator': | ||
return node.name; | ||
case 'Type': | ||
case 'TypeSelector': | ||
return node.name; | ||
case 'Universal': | ||
return node.name; | ||
case 'Identifier': | ||
return node.name; | ||
case 'PseudoClass': | ||
case 'PseudoClassSelector': | ||
return node.children !== null | ||
@@ -223,3 +212,3 @@ ? ':' + node.name + '(' + each(node.children) + ')' | ||
case 'PseudoElement': | ||
case 'PseudoElementSelector': | ||
return node.children !== null | ||
@@ -229,6 +218,6 @@ ? '::' + node.name + '(' + each(node.children) + ')' | ||
case 'Class': | ||
case 'ClassSelector': | ||
return '.' + node.name; | ||
case 'Id': | ||
case 'IdSelector': | ||
return '#' + node.name; | ||
@@ -239,3 +228,3 @@ | ||
case 'Hash': | ||
case 'HexColor': | ||
return '#' + node.value; | ||
@@ -246,3 +235,3 @@ | ||
case 'An+B': | ||
case 'AnPlusB': | ||
var result = ''; | ||
@@ -289,4 +278,4 @@ var a = node.a !== null && node.a !== undefined; | ||
case 'Space': | ||
return ' '; | ||
case 'WhiteSpace': | ||
return node.value; | ||
@@ -293,0 +282,0 @@ case 'Comment': |
@@ -15,7 +15,2 @@ 'use strict'; | ||
if (chunk instanceof SourceNode) { | ||
// this is a hack, because source maps doesn't support for 1(generated):N(original) | ||
// if (chunk.merged) { | ||
// fn('', chunk); | ||
// } | ||
walk(chunk, fn); | ||
@@ -89,12 +84,2 @@ } else { | ||
function createSourceNode(loc, children) { | ||
if (loc.primary) { | ||
// special marker node to add several references to original | ||
// var merged = createSourceNode(loc.merged, []); | ||
// merged.merged = true; | ||
// children.unshift(merged); | ||
// use recursion, because primary can also has a primary/merged loc | ||
return createSourceNode(loc.primary, children); | ||
} | ||
return new SourceNode( | ||
@@ -143,3 +128,3 @@ loc.start ? loc.start.line : null, | ||
if (node.expression && !node.expression.children.isEmpty()) { | ||
if (node.expression !== null) { | ||
nodes.push(' ', translate(node.expression)); | ||
@@ -165,13 +150,4 @@ } | ||
case 'Selector': | ||
var nodes = node.children.map(function(node) { | ||
// add extra spaces around /deep/ combinator since comment beginning/ending may to be produced | ||
if (node.type === 'Combinator' && node.name === '/deep/') { | ||
return ' ' + translate(node) + ' '; | ||
} | ||
return createSourceNode(node.loc, node.children.map(translate)); | ||
return translate(node); | ||
}); | ||
return createSourceNode(node.loc, nodes); | ||
case 'Block': | ||
@@ -205,3 +181,3 @@ return createAnonymousSourceNode([ | ||
case 'Attribute': | ||
case 'AttributeSelector': | ||
var result = translate(node.name); | ||
@@ -245,3 +221,3 @@ var flagsPrefix = ' '; | ||
case 'MediaQuery': | ||
return createAnonymousSourceNode(node.children.map(translate)).join(' '); | ||
return each(node.children); | ||
@@ -256,18 +232,12 @@ case 'MediaFeature': | ||
case 'Progid': | ||
return node.value; | ||
case 'Combinator': | ||
return node.name; | ||
case 'Type': | ||
case 'TypeSelector': | ||
return node.name; | ||
case 'Universal': | ||
return node.name; | ||
case 'Identifier': | ||
return node.name; | ||
case 'PseudoClass': | ||
case 'PseudoClassSelector': | ||
return node.children !== null | ||
@@ -277,3 +247,3 @@ ? ':' + node.name + '(' + each(node.children) + ')' | ||
case 'PseudoElement': | ||
case 'PseudoElementSelector': | ||
return node.children !== null | ||
@@ -283,6 +253,6 @@ ? '::' + node.name + '(' + each(node.children) + ')' | ||
case 'Class': | ||
case 'ClassSelector': | ||
return '.' + node.name; | ||
case 'Id': | ||
case 'IdSelector': | ||
return '#' + node.name; | ||
@@ -293,3 +263,3 @@ | ||
case 'Hash': | ||
case 'HexColor': | ||
return '#' + node.value; | ||
@@ -300,3 +270,3 @@ | ||
case 'An+B': | ||
case 'AnPlusB': | ||
var result = ''; | ||
@@ -343,4 +313,4 @@ var a = node.a !== null && node.a !== undefined; | ||
case 'Space': | ||
return ' '; | ||
case 'WhiteSpace': | ||
return node.value; | ||
@@ -347,0 +317,0 @@ case 'Comment': |
@@ -208,3 +208,3 @@ 'use strict'; | ||
case 'Attribute': | ||
case 'AttributeSelector': | ||
walk.call(this, node.name); | ||
@@ -216,3 +216,3 @@ if (node.value !== null) { | ||
case 'PseudoClass': | ||
case 'PseudoClassSelector': | ||
if (node.children !== null) { | ||
@@ -227,3 +227,3 @@ this['function'] = node; | ||
case 'PseudoElement': | ||
case 'PseudoElementSelector': | ||
if (node.children !== null) { | ||
@@ -283,23 +283,2 @@ this['function'] = node; | ||
break; | ||
// nothig to do with | ||
// case 'Progid': | ||
// case 'Property': | ||
// case 'Combinator': | ||
// case 'Dimension': | ||
// case 'Hash': | ||
// case 'Type': | ||
// case 'Universal': | ||
// case 'Identifier': | ||
// case 'UnicodeRange': | ||
// case 'An+B': | ||
// case 'Class': | ||
// case 'Id': | ||
// case 'Percentage': | ||
// case 'Space': | ||
// case 'Number': | ||
// case 'String': | ||
// case 'Operator': | ||
// case 'Raw': | ||
// case 'Ratio': | ||
} | ||
@@ -306,0 +285,0 @@ } |
{ | ||
"name": "css-tree", | ||
"version": "1.0.0-alpha14", | ||
"version": "1.0.0-alpha15", | ||
"description": "Fast detailed CSS parser", | ||
@@ -5,0 +5,0 @@ "keywords": [ |
202
README.md
@@ -17,2 +17,14 @@ <img align="right" width="111" height="111" | ||
- [Parsing CSS into AST](docs/parsing.md) | ||
- [AST format](docs/ast.md) | ||
- [Translate AST to string](docs/translate.md) | ||
- [AST traversal](docs/traversal.md) | ||
- [Utils to work with AST](docs/utils.md) | ||
- [Working with syntax](docs/syntax.md) | ||
- API references: | ||
- [Tokenizer](docs/Tokenizer.md) | ||
- [Parser](docs/Parser.md) | ||
- [Lexer](docs/Lexer.md) | ||
- [List](docs/List.md) | ||
Docs and tools: | ||
@@ -44,194 +56,14 @@ | ||
var csstree = require('css-tree'); | ||
var ast = csstree.parse('.example { world: "!" }'); | ||
csstree.walk(csstree.parse('.a { color: red; }'), function(node) { | ||
console.log(node.type); | ||
}); | ||
// StyleSheet | ||
// Rule | ||
// SelectorList | ||
// Selector | ||
// Class | ||
// Block | ||
// Declaration | ||
// Value | ||
// Identifier | ||
``` | ||
## API | ||
### parse(source[, options]) | ||
Parses CSS to AST. | ||
> NOTE: Currenly parser omits redundant separators, spaces and comments (except exclamation comments, i.e. `/*! comment */`) on AST build. | ||
Options: | ||
- `context` String – parsing context, useful when some part of CSS is parsing (see below) | ||
- `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) | ||
- `filename` String – filename of source that adds to info when `positions` is true, uses for source map generation (`<unknown>` by default) | ||
- `line` Number – initial line number, useful when parse fragment of CSS to compute correct positions | ||
- `column` Number – initial column number, useful when parse fragment of CSS to compute correct positions | ||
Contexts: | ||
- `stylesheet` (default) – regular stylesheet, should be suitable in most cases | ||
- `atrule` – at-rule (e.g. `@media screen, print { ... }`) | ||
- `atruleExpression` – at-rule expression (`screen, print` for example above) | ||
- `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) | ||
```js | ||
// simple parsing with no options | ||
var ast = csstree.parse('.example { color: red }'); | ||
// parse with options | ||
var ast = csstree.parse('.foo.bar', { | ||
context: 'simpleSelector', | ||
positions: true | ||
}); | ||
``` | ||
### clone(ast) | ||
Make an AST node deep copy. | ||
```js | ||
var orig = csstree.parse('.test { color: red }'); | ||
var copy = csstree.clone(orig); | ||
csstree.walk(copy, function(node) { | ||
if (node.type === 'Class') { | ||
node.name = 'replaced'; | ||
csstree.walk(ast, function(node) { | ||
if (node.type === 'Class' && node.name === 'example') { | ||
node.name = 'hello'; | ||
} | ||
}); | ||
console.log(csstree.translate(orig)); | ||
// .test{color:red} | ||
console.log(csstree.translate(copy)); | ||
// .replaced{color:red} | ||
``` | ||
### translate(ast) | ||
Converts AST to string. | ||
```js | ||
var ast = csstree.parse('.test { color: red }'); | ||
console.log(csstree.translate(ast)); | ||
// > .test{color:red} | ||
// .hello{world:"!"} | ||
``` | ||
### translateWithSourceMap(ast) | ||
The same as `translate()` but also generates source map (nodes should contain positions in `info` property). | ||
```js | ||
var ast = csstree.parse('.test { color: red }', { | ||
filename: 'my.css', | ||
positions: true | ||
}); | ||
console.log(csstree.translateWithSourceMap(ast)); | ||
// { css: '.test{color:red}', map: SourceMapGenerator {} } | ||
``` | ||
### walk(ast, handler) | ||
Visits each node of AST in natural way and calls handler for each one. `handler` receives three arguments: | ||
- `node` – current AST node | ||
- `item` – node wrapper when node is a list member; this wrapper contains references to `prev` and `next` nodes in list | ||
- `list` – reference to list when node is a list member; it's useful for operations on list like `remove()` or `insert()` | ||
Context for handler an object, that contains references to some parent nodes: | ||
- `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 | ||
```js | ||
// collect all urls in declarations | ||
var csstree = require('./lib/index.js'); | ||
var urls = []; | ||
var ast = csstree.parse(` | ||
@import url(import.css); | ||
.foo { background: url('foo.jpg'); } | ||
.bar { background-image: url(bar.png); } | ||
`); | ||
csstree.walk(ast, function(node) { | ||
if (this.declaration !== null && node.type === 'Url') { | ||
var value = node.value; | ||
if (value.type === 'Raw') { | ||
urls.push(value.value); | ||
} else { | ||
urls.push(value.value.substr(1, value.value.length - 2)); | ||
} | ||
} | ||
}); | ||
console.log(urls); | ||
// [ 'foo.jpg', 'bar.png' ] | ||
``` | ||
### walkUp(ast, handler) | ||
Same as `walk()` but visits nodes in down-to-top order. Useful to process deepest nodes and then their parents. | ||
```js | ||
var csstree = require('css-tree'); | ||
var ast = csstree.parse('.a { color: red; }'); | ||
csstree.walk(ast, function(node) { | ||
console.log(node.type); | ||
}); | ||
// StyleSheet | ||
// Rule | ||
// SelectorList | ||
// Selector | ||
// Class | ||
// Block | ||
// Declaration | ||
// Value | ||
// Identifier | ||
csstree.walkUp(ast, function(node) { | ||
console.log(node.type); | ||
}); | ||
// Class | ||
// Selector | ||
// SelectorList | ||
// Identifier | ||
// Value | ||
// Declaration | ||
// Block | ||
// Rule | ||
// StyleSheet | ||
``` | ||
### walkRules(ast, handler) | ||
Same as `walk()` but visits `Rule` and `Atrule` nodes only. | ||
### walkRulesRight(ast, handler) | ||
Same as `walkRules()` but visits nodes in reverse order (from last to first). | ||
### walkDeclarations(ast, handler) | ||
Visit all declarations. | ||
## License | ||
@@ -238,0 +70,0 @@ |
Sorry, the diff of this file is too big to display
91
10.98%633432
-0.9%12926
-1.04%72
-70%