You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
166 lines
4.5 KiB
166 lines
4.5 KiB
'use strict'; |
|
|
|
const names = require('../../utils/names.cjs'); |
|
const types = require('../../tokenizer/types.cjs'); |
|
|
|
const EXCLAMATIONMARK = 0x0021; // U+0021 EXCLAMATION MARK (!) |
|
const NUMBERSIGN = 0x0023; // U+0023 NUMBER SIGN (#) |
|
const DOLLARSIGN = 0x0024; // U+0024 DOLLAR SIGN ($) |
|
const AMPERSAND = 0x0026; // U+0026 AMPERSAND (&) |
|
const ASTERISK = 0x002A; // U+002A ASTERISK (*) |
|
const PLUSSIGN = 0x002B; // U+002B PLUS SIGN (+) |
|
const SOLIDUS = 0x002F; // U+002F SOLIDUS (/) |
|
|
|
function consumeValueRaw(startToken) { |
|
return this.Raw(startToken, this.consumeUntilExclamationMarkOrSemicolon, true); |
|
} |
|
|
|
function consumeCustomPropertyRaw(startToken) { |
|
return this.Raw(startToken, this.consumeUntilExclamationMarkOrSemicolon, false); |
|
} |
|
|
|
function consumeValue() { |
|
const startValueToken = this.tokenIndex; |
|
const value = this.Value(); |
|
|
|
if (value.type !== 'Raw' && |
|
this.eof === false && |
|
this.tokenType !== types.Semicolon && |
|
this.isDelim(EXCLAMATIONMARK) === false && |
|
this.isBalanceEdge(startValueToken) === false) { |
|
this.error(); |
|
} |
|
|
|
return value; |
|
} |
|
|
|
const name = 'Declaration'; |
|
const walkContext = 'declaration'; |
|
const structure = { |
|
important: [Boolean, String], |
|
property: String, |
|
value: ['Value', 'Raw'] |
|
}; |
|
|
|
function parse() { |
|
const start = this.tokenStart; |
|
const startToken = this.tokenIndex; |
|
const property = readProperty.call(this); |
|
const customProperty = names.isCustomProperty(property); |
|
const parseValue = customProperty ? this.parseCustomProperty : this.parseValue; |
|
const consumeRaw = customProperty ? consumeCustomPropertyRaw : consumeValueRaw; |
|
let important = false; |
|
let value; |
|
|
|
this.skipSC(); |
|
this.eat(types.Colon); |
|
|
|
const valueStart = this.tokenIndex; |
|
|
|
if (!customProperty) { |
|
this.skipSC(); |
|
} |
|
|
|
if (parseValue) { |
|
value = this.parseWithFallback(consumeValue, consumeRaw); |
|
} else { |
|
value = consumeRaw.call(this, this.tokenIndex); |
|
} |
|
|
|
if (customProperty && value.type === 'Value' && value.children.isEmpty) { |
|
for (let offset = valueStart - this.tokenIndex; offset <= 0; offset++) { |
|
if (this.lookupType(offset) === types.WhiteSpace) { |
|
value.children.appendData({ |
|
type: 'WhiteSpace', |
|
loc: null, |
|
value: ' ' |
|
}); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
if (this.isDelim(EXCLAMATIONMARK)) { |
|
important = getImportant.call(this); |
|
this.skipSC(); |
|
} |
|
|
|
// Do not include semicolon to range per spec |
|
// https://drafts.csswg.org/css-syntax/#declaration-diagram |
|
|
|
if (this.eof === false && |
|
this.tokenType !== types.Semicolon && |
|
this.isBalanceEdge(startToken) === false) { |
|
this.error(); |
|
} |
|
|
|
return { |
|
type: 'Declaration', |
|
loc: this.getLocation(start, this.tokenStart), |
|
important, |
|
property, |
|
value |
|
}; |
|
} |
|
|
|
function generate(node) { |
|
this.token(types.Ident, node.property); |
|
this.token(types.Colon, ':'); |
|
this.node(node.value); |
|
|
|
if (node.important) { |
|
this.token(types.Delim, '!'); |
|
this.token(types.Ident, node.important === true ? 'important' : node.important); |
|
} |
|
} |
|
|
|
function readProperty() { |
|
const start = this.tokenStart; |
|
|
|
// hacks |
|
if (this.tokenType === types.Delim) { |
|
switch (this.charCodeAt(this.tokenStart)) { |
|
case ASTERISK: |
|
case DOLLARSIGN: |
|
case PLUSSIGN: |
|
case NUMBERSIGN: |
|
case AMPERSAND: |
|
this.next(); |
|
break; |
|
|
|
// TODO: not sure we should support this hack |
|
case SOLIDUS: |
|
this.next(); |
|
if (this.isDelim(SOLIDUS)) { |
|
this.next(); |
|
} |
|
break; |
|
} |
|
} |
|
|
|
if (this.tokenType === types.Hash) { |
|
this.eat(types.Hash); |
|
} else { |
|
this.eat(types.Ident); |
|
} |
|
|
|
return this.substrToCursor(start); |
|
} |
|
|
|
// ! ws* important |
|
function getImportant() { |
|
this.eat(types.Delim); |
|
this.skipSC(); |
|
|
|
const important = this.consume(types.Ident); |
|
|
|
// store original value in case it differ from `important` |
|
// for better original source restoring and hacks like `!ie` support |
|
return important === 'important' ? true : important; |
|
} |
|
|
|
exports.generate = generate; |
|
exports.name = name; |
|
exports.parse = parse; |
|
exports.structure = structure; |
|
exports.walkContext = walkContext;
|
|
|