Declaration.cjs 4.4 KB

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