docs: function(context: ParserContext): ?Token {
      if (
        !context.reader.match("<<") ||
        !/[\w'"`-]/.test(context.reader.peekWithOffset(2))
      )
        return null;
  
      // cannot be preceded by an a number or a string
      const walker = context.getTokenWalker();
      if (walker.hasPrev()) {
        const prevToken = walker.prev();
        if (util.contains(["number", "string"], prevToken.name)) return null;
      }
  
      // there are still cases where heredocs are falsely detected, because it
      // would require performingstatic analysis
  
      // e.g. foo <<a
      // if foo is an object that has the "<<" method defined, then it will perform a left shift
      // if foo is a function that takes a string argument, it will interpret it as a heredoc
  
      // so, we just force you to have whitespace between << and the rhs operand in these ambiguous cases
  
      // can be between quotes (double, single or back) or not, or preceded by a hyphen
      let value = context.reader.read(2);
      let ident = "";
      let current = context.reader.read();
      if (current === "-") {
        value += current;
        ident += current;
        current = context.reader.read();
      }
  
      let delimiter = "";
      if (util.contains(['"', "'", "`"], current)) delimiter = current;
      else ident += current;
  
      value += current;
  
      while (!context.reader.isEOF()) {
        const peek = context.reader.peek();
        if (peek === "\n" || (delimiter === "" && /\W/.test(peek))) break;
        value += context.reader.read();
        if (peek === delimiter) break;
        ident += peek;
      }
  
      if (Array.isArray(context.items.heredocQueue))
        context.items.heredocQueue.push(ident);
      else
        logger.errorInvalidValue(
          `context.items.heredocQueue is not an array.`,
          context.items.heredocQueue
        );
  
      return context.createToken("heredocDeclaration", value);
    }