text: var isCustomProperty = require('../../utils/names').isCustomProperty;
  var TYPE = require('../../tokenizer').TYPE;
  var CHARCODE = require('../../tokenizer').CHARCODE;
  var rawMode = require('./Raw').mode;
  
  var IDENT = TYPE.Ident;
  var HASH = TYPE.Hash;
  var COLON = TYPE.Colon;
  var SEMICOLON = TYPE.Semicolon;
  var DELIM = TYPE.Delim;
  var EXCLAMATIONMARK = CHARCODE.ExclamationMark;
  var SOLIDUS = CHARCODE.Solidus;
  var ASTERISK = CHARCODE.Asterisk;
  var AMPERSAND = CHARCODE.Ampersand;
  var DOLLARSIGN = CHARCODE.DollarSign;
  var PLUSSIGN = CHARCODE.PlusSign;
  var NUMBERSIGN = CHARCODE.NumberSign;
  
  function consumeValueRaw(startToken) {
      return this.Raw(startToken, rawMode.exclamationMarkOrSemicolon, true);
  }
  
  function consumeCustomPropertyRaw(startToken) {
      return this.Raw(startToken, rawMode.exclamationMarkOrSemicolon, false);
  }
  
  function consumeValue() {
      var startValueToken = this.scanner.tokenIndex;
      var value = this.Value();
  
      if (value.type !== 'Raw' &&
          this.scanner.eof === false &&
          this.scanner.tokenType !== SEMICOLON &&
          this.scanner.isDelim(EXCLAMATIONMARK) === false &&
          this.scanner.isBalanceEdge(startValueToken) === false) {
          this.error();
      }
  
      return value;
  }
  
  module.exports = {
      name: 'Declaration',
      structure: {
          important: [Boolean, String],
          property: String,
          value: ['Value', 'Raw']
      },
      parse: function() {
          var start = this.scanner.tokenStart;
          var startToken = this.scanner.tokenIndex;
          var property = readProperty.call(this);
          var customProperty = isCustomProperty(property);
          var parseValue = customProperty ? this.parseCustomProperty : this.parseValue;
          var consumeRaw = customProperty ? consumeCustomPropertyRaw : consumeValueRaw;
          var important = false;
          var value;
  
          this.scanner.skipSC();
          this.eat(COLON);
  
          if (!customProperty) {
              this.scanner.skipSC();
          }
  
          if (parseValue) {
              value = this.parseWithFallback(consumeValue, consumeRaw);
          } else {
              value = consumeRaw.call(this, this.scanner.tokenIndex);
          }
  
          if (this.scanner.isDelim(EXCLAMATIONMARK)) {
              important = getImportant.call(this);
              this.scanner.skipSC();
          }
  
          // Do not include semicolon to range per spec
          // https://drafts.csswg.org/css-syntax/#declaration-diagram
  
          if (this.scanner.eof === false &&
              this.scanner.tokenType !== SEMICOLON &&
              this.scanner.isBalanceEdge(startToken) === false) {
              this.error();
          }
  
          return {
              type: 'Declaration',
              loc: this.getLocation(start, this.scanner.tokenStart),
              important: important,
              property: property,
              value: value
          };
      },
      generate: function(node) {
          this.chunk(node.property);
          this.chunk(':');
          this.node(node.value);
  
          if (node.important) {
              this.chunk(node.important === true ? '!important' : '!' + node.important);
          }
      },
      walkContext: 'declaration'
  };
  
  function readProperty() {
      var start = this.scanner.tokenStart;
      var prefix = 0;
  
      // hacks
      if (this.scanner.tokenType === DELIM) {
          switch (this.scanner.source.charCodeAt(this.scanner.tokenStart)) {
              case ASTERISK:
              case DOLLARSIGN:
              case PLUSSIGN:
              case NUMBERSIGN:
              case AMPERSAND:
                  this.scanner.next();
                  break;
  
              // TODO: not sure we should support this hack
              case SOLIDUS:
                  this.scanner.next();
                  if (this.scanner.isDelim(SOLIDUS)) {
                      this.scanner.next();
                  }
                  break;
          }
      }
  
      if (prefix) {
          this.scanner.skip(prefix);
      }
  
      if (this.scanner.tokenType === HASH) {
          this.eat(HASH);
      } else {
          this.eat(IDENT);
      }
  
      return this.scanner.substrToCursor(start);
  }
  
  // ! ws* important
  function getImportant() {
      this.eat(DELIM);
      this.scanner.skipSC();
  
      var important = this.consume(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;
  }
  