Declaration.js 4.9 KB

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