Declaration.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. import { isCustomProperty } from '../../utils/names.js';
  2. import {
  3. Ident,
  4. Hash,
  5. Colon,
  6. Semicolon,
  7. Delim,
  8. WhiteSpace
  9. } from '../../tokenizer/index.js';
  10. const EXCLAMATIONMARK = 0x0021; // U+0021 EXCLAMATION MARK (!)
  11. const NUMBERSIGN = 0x0023; // U+0023 NUMBER SIGN (#)
  12. const DOLLARSIGN = 0x0024; // U+0024 DOLLAR SIGN ($)
  13. const AMPERSAND = 0x0026; // U+0026 AMPERSAND (&)
  14. const ASTERISK = 0x002A; // U+002A ASTERISK (*)
  15. const PLUSSIGN = 0x002B; // U+002B PLUS SIGN (+)
  16. const SOLIDUS = 0x002F; // U+002F SOLIDUS (/)
  17. function consumeValueRaw() {
  18. return this.Raw(this.consumeUntilExclamationMarkOrSemicolon, true);
  19. }
  20. function consumeCustomPropertyRaw() {
  21. return this.Raw(this.consumeUntilExclamationMarkOrSemicolon, false);
  22. }
  23. function consumeValue() {
  24. const startValueToken = this.tokenIndex;
  25. const value = this.Value();
  26. if (value.type !== 'Raw' &&
  27. this.eof === false &&
  28. this.tokenType !== Semicolon &&
  29. this.isDelim(EXCLAMATIONMARK) === false &&
  30. this.isBalanceEdge(startValueToken) === false) {
  31. this.error();
  32. }
  33. return value;
  34. }
  35. export const name = 'Declaration';
  36. export const walkContext = 'declaration';
  37. export const structure = {
  38. important: [Boolean, String],
  39. property: String,
  40. value: ['Value', 'Raw']
  41. };
  42. export function parse() {
  43. const start = this.tokenStart;
  44. const startToken = this.tokenIndex;
  45. const property = readProperty.call(this);
  46. const customProperty = isCustomProperty(property);
  47. const parseValue = customProperty ? this.parseCustomProperty : this.parseValue;
  48. const consumeRaw = customProperty ? consumeCustomPropertyRaw : consumeValueRaw;
  49. let important = false;
  50. let value;
  51. this.skipSC();
  52. this.eat(Colon);
  53. const valueStart = this.tokenIndex;
  54. if (!customProperty) {
  55. this.skipSC();
  56. }
  57. if (parseValue) {
  58. value = this.parseWithFallback(consumeValue, consumeRaw);
  59. } else {
  60. value = consumeRaw.call(this, this.tokenIndex);
  61. }
  62. if (customProperty && value.type === 'Value' && value.children.isEmpty) {
  63. for (let offset = valueStart - this.tokenIndex; offset <= 0; offset++) {
  64. if (this.lookupType(offset) === WhiteSpace) {
  65. value.children.appendData({
  66. type: 'WhiteSpace',
  67. loc: null,
  68. value: ' '
  69. });
  70. break;
  71. }
  72. }
  73. }
  74. if (this.isDelim(EXCLAMATIONMARK)) {
  75. important = getImportant.call(this);
  76. this.skipSC();
  77. }
  78. // Do not include semicolon to range per spec
  79. // https://drafts.csswg.org/css-syntax/#declaration-diagram
  80. if (this.eof === false &&
  81. this.tokenType !== Semicolon &&
  82. this.isBalanceEdge(startToken) === false) {
  83. this.error();
  84. }
  85. return {
  86. type: 'Declaration',
  87. loc: this.getLocation(start, this.tokenStart),
  88. important,
  89. property,
  90. value
  91. };
  92. }
  93. export function generate(node) {
  94. this.token(Ident, node.property);
  95. this.token(Colon, ':');
  96. this.node(node.value);
  97. if (node.important) {
  98. this.token(Delim, '!');
  99. this.token(Ident, node.important === true ? 'important' : node.important);
  100. }
  101. }
  102. function readProperty() {
  103. const start = this.tokenStart;
  104. // hacks
  105. if (this.tokenType === Delim) {
  106. switch (this.charCodeAt(this.tokenStart)) {
  107. case ASTERISK:
  108. case DOLLARSIGN:
  109. case PLUSSIGN:
  110. case NUMBERSIGN:
  111. case AMPERSAND:
  112. this.next();
  113. break;
  114. // TODO: not sure we should support this hack
  115. case SOLIDUS:
  116. this.next();
  117. if (this.isDelim(SOLIDUS)) {
  118. this.next();
  119. }
  120. break;
  121. }
  122. }
  123. if (this.tokenType === Hash) {
  124. this.eat(Hash);
  125. } else {
  126. this.eat(Ident);
  127. }
  128. return this.substrToCursor(start);
  129. }
  130. // ! ws* important
  131. function getImportant() {
  132. this.eat(Delim);
  133. this.skipSC();
  134. const important = this.consume(Ident);
  135. // store original value in case it differ from `important`
  136. // for better original source restoring and hacks like `!ie` support
  137. return important === 'important' ? true : important;
  138. }