| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433 | 'use strict';var XHTMLEntities = require('./xhtml');var hexNumber = /^[\da-fA-F]+$/;var decimalNumber = /^\d+$/;module.exports = function(acorn) {  var tt = acorn.tokTypes;  var tc = acorn.tokContexts;  tc.j_oTag = new acorn.TokContext('<tag', false);  tc.j_cTag = new acorn.TokContext('</tag', false);  tc.j_expr = new acorn.TokContext('<tag>...</tag>', true, true);  tt.jsxName = new acorn.TokenType('jsxName');  tt.jsxText = new acorn.TokenType('jsxText', {beforeExpr: true});  tt.jsxTagStart = new acorn.TokenType('jsxTagStart');  tt.jsxTagEnd = new acorn.TokenType('jsxTagEnd');  tt.jsxTagStart.updateContext = function() {    this.context.push(tc.j_expr); // treat as beginning of JSX expression    this.context.push(tc.j_oTag); // start opening tag context    this.exprAllowed = false;  };  tt.jsxTagEnd.updateContext = function(prevType) {    var out = this.context.pop();    if (out === tc.j_oTag && prevType === tt.slash || out === tc.j_cTag) {      this.context.pop();      this.exprAllowed = this.curContext() === tc.j_expr;    } else {      this.exprAllowed = true;    }  };  var pp = acorn.Parser.prototype;  // Reads inline JSX contents token.  pp.jsx_readToken = function() {    var out = '', chunkStart = this.pos;    for (;;) {      if (this.pos >= this.input.length)        this.raise(this.start, 'Unterminated JSX contents');      var ch = this.input.charCodeAt(this.pos);      switch (ch) {      case 60: // '<'      case 123: // '{'        if (this.pos === this.start) {          if (ch === 60 && this.exprAllowed) {            ++this.pos;            return this.finishToken(tt.jsxTagStart);          }          return this.getTokenFromCode(ch);        }        out += this.input.slice(chunkStart, this.pos);        return this.finishToken(tt.jsxText, out);      case 38: // '&'        out += this.input.slice(chunkStart, this.pos);        out += this.jsx_readEntity();        chunkStart = this.pos;        break;      default:        if (acorn.isNewLine(ch)) {          out += this.input.slice(chunkStart, this.pos);          out += this.jsx_readNewLine(true);          chunkStart = this.pos;        } else {          ++this.pos;        }      }    }  };  pp.jsx_readNewLine = function(normalizeCRLF) {    var ch = this.input.charCodeAt(this.pos);    var out;    ++this.pos;    if (ch === 13 && this.input.charCodeAt(this.pos) === 10) {      ++this.pos;      out = normalizeCRLF ? '\n' : '\r\n';    } else {      out = String.fromCharCode(ch);    }    if (this.options.locations) {      ++this.curLine;      this.lineStart = this.pos;    }    return out;  };  pp.jsx_readString = function(quote) {    var out = '', chunkStart = ++this.pos;    for (;;) {      if (this.pos >= this.input.length)        this.raise(this.start, 'Unterminated string constant');      var ch = this.input.charCodeAt(this.pos);      if (ch === quote) break;      if (ch === 38) { // '&'        out += this.input.slice(chunkStart, this.pos);        out += this.jsx_readEntity();        chunkStart = this.pos;      } else if (acorn.isNewLine(ch)) {        out += this.input.slice(chunkStart, this.pos);        out += this.jsx_readNewLine(false);        chunkStart = this.pos;      } else {        ++this.pos;      }    }    out += this.input.slice(chunkStart, this.pos++);    return this.finishToken(tt.string, out);  };  pp.jsx_readEntity = function() {    var str = '', count = 0, entity;    var ch = this.input[this.pos];    if (ch !== '&')      this.raise(this.pos, 'Entity must start with an ampersand');    var startPos = ++this.pos;    while (this.pos < this.input.length && count++ < 10) {      ch = this.input[this.pos++];      if (ch === ';') {        if (str[0] === '#') {          if (str[1] === 'x') {            str = str.substr(2);            if (hexNumber.test(str))              entity = String.fromCharCode(parseInt(str, 16));          } else {            str = str.substr(1);            if (decimalNumber.test(str))              entity = String.fromCharCode(parseInt(str, 10));          }        } else {          entity = XHTMLEntities[str];        }        break;      }      str += ch;    }    if (!entity) {      this.pos = startPos;      return '&';    }    return entity;  };  // Read a JSX identifier (valid tag or attribute name).  //  // Optimized version since JSX identifiers can't contain  // escape characters and so can be read as single slice.  // Also assumes that first character was already checked  // by isIdentifierStart in readToken.  pp.jsx_readWord = function() {    var ch, start = this.pos;    do {      ch = this.input.charCodeAt(++this.pos);    } while (acorn.isIdentifierChar(ch) || ch === 45); // '-'    return this.finishToken(tt.jsxName, this.input.slice(start, this.pos));  };  // Transforms JSX element name to string.  function getQualifiedJSXName(object) {    if (object.type === 'JSXIdentifier')      return object.name;    if (object.type === 'JSXNamespacedName')      return object.namespace.name + ':' + object.name.name;    if (object.type === 'JSXMemberExpression')      return getQualifiedJSXName(object.object) + '.' +      getQualifiedJSXName(object.property);  }  // Parse next token as JSX identifier  pp.jsx_parseIdentifier = function() {    var node = this.startNode();    if (this.type === tt.jsxName)      node.name = this.value;    else if (this.type.keyword)      node.name = this.type.keyword;    else      this.unexpected();    this.next();    return this.finishNode(node, 'JSXIdentifier');  };  // Parse namespaced identifier.  pp.jsx_parseNamespacedName = function() {    var startPos = this.start, startLoc = this.startLoc;    var name = this.jsx_parseIdentifier();    if (!this.options.plugins.jsx.allowNamespaces || !this.eat(tt.colon)) return name;    var node = this.startNodeAt(startPos, startLoc);    node.namespace = name;    node.name = this.jsx_parseIdentifier();    return this.finishNode(node, 'JSXNamespacedName');  };  // Parses element name in any form - namespaced, member  // or single identifier.  pp.jsx_parseElementName = function() {    var startPos = this.start, startLoc = this.startLoc;    var node = this.jsx_parseNamespacedName();    if (this.type === tt.dot && node.type === 'JSXNamespacedName' && !this.options.plugins.jsx.allowNamespacedObjects) {      this.unexpected();    }    while (this.eat(tt.dot)) {      var newNode = this.startNodeAt(startPos, startLoc);      newNode.object = node;      newNode.property = this.jsx_parseIdentifier();      node = this.finishNode(newNode, 'JSXMemberExpression');    }    return node;  };  // Parses any type of JSX attribute value.  pp.jsx_parseAttributeValue = function() {    switch (this.type) {    case tt.braceL:      var node = this.jsx_parseExpressionContainer();      if (node.expression.type === 'JSXEmptyExpression')        this.raise(node.start, 'JSX attributes must only be assigned a non-empty expression');      return node;    case tt.jsxTagStart:    case tt.string:      return this.parseExprAtom();    default:      this.raise(this.start, 'JSX value should be either an expression or a quoted JSX text');    }  };  // JSXEmptyExpression is unique type since it doesn't actually parse anything,  // and so it should start at the end of last read token (left brace) and finish  // at the beginning of the next one (right brace).  pp.jsx_parseEmptyExpression = function() {    var node = this.startNodeAt(this.lastTokEnd, this.lastTokEndLoc);    return this.finishNodeAt(node, 'JSXEmptyExpression', this.start, this.startLoc);  };  // Parses JSX expression enclosed into curly brackets.  pp.jsx_parseExpressionContainer = function() {    var node = this.startNode();    this.next();    node.expression = this.type === tt.braceR      ? this.jsx_parseEmptyExpression()      : this.parseExpression();    this.expect(tt.braceR);    return this.finishNode(node, 'JSXExpressionContainer');  };  // Parses following JSX attribute name-value pair.  pp.jsx_parseAttribute = function() {    var node = this.startNode();    if (this.eat(tt.braceL)) {      this.expect(tt.ellipsis);      node.argument = this.parseMaybeAssign();      this.expect(tt.braceR);      return this.finishNode(node, 'JSXSpreadAttribute');    }    node.name = this.jsx_parseNamespacedName();    node.value = this.eat(tt.eq) ? this.jsx_parseAttributeValue() : null;    return this.finishNode(node, 'JSXAttribute');  };  // Parses JSX opening tag starting after '<'.  pp.jsx_parseOpeningElementAt = function(startPos, startLoc) {    var node = this.startNodeAt(startPos, startLoc);    node.attributes = [];    node.name = this.jsx_parseElementName();    while (this.type !== tt.slash && this.type !== tt.jsxTagEnd)      node.attributes.push(this.jsx_parseAttribute());    node.selfClosing = this.eat(tt.slash);    this.expect(tt.jsxTagEnd);    return this.finishNode(node, 'JSXOpeningElement');  };  // Parses JSX closing tag starting after '</'.  pp.jsx_parseClosingElementAt = function(startPos, startLoc) {    var node = this.startNodeAt(startPos, startLoc);    node.name = this.jsx_parseElementName();    this.expect(tt.jsxTagEnd);    return this.finishNode(node, 'JSXClosingElement');  };  // Parses entire JSX element, including it's opening tag  // (starting after '<'), attributes, contents and closing tag.  pp.jsx_parseElementAt = function(startPos, startLoc) {    var node = this.startNodeAt(startPos, startLoc);    var children = [];    var openingElement = this.jsx_parseOpeningElementAt(startPos, startLoc);    var closingElement = null;    if (!openingElement.selfClosing) {      contents: for (;;) {        switch (this.type) {        case tt.jsxTagStart:          startPos = this.start; startLoc = this.startLoc;          this.next();          if (this.eat(tt.slash)) {            closingElement = this.jsx_parseClosingElementAt(startPos, startLoc);            break contents;          }          children.push(this.jsx_parseElementAt(startPos, startLoc));          break;        case tt.jsxText:          children.push(this.parseExprAtom());          break;        case tt.braceL:          children.push(this.jsx_parseExpressionContainer());          break;        default:          this.unexpected();        }      }      if (getQualifiedJSXName(closingElement.name) !== getQualifiedJSXName(openingElement.name)) {        this.raise(          closingElement.start,          'Expected corresponding JSX closing tag for <' + getQualifiedJSXName(openingElement.name) + '>');      }    }    node.openingElement = openingElement;    node.closingElement = closingElement;    node.children = children;    if (this.type === tt.relational && this.value === "<") {      this.raise(this.start, "Adjacent JSX elements must be wrapped in an enclosing tag");    }    return this.finishNode(node, 'JSXElement');  };  // Parses entire JSX element from current position.  pp.jsx_parseElement = function() {    var startPos = this.start, startLoc = this.startLoc;    this.next();    return this.jsx_parseElementAt(startPos, startLoc);  };  acorn.plugins.jsx = function(instance, opts) {    if (!opts) {      return;    }    if (typeof opts !== 'object') {      opts = {};    }    instance.options.plugins.jsx = {      allowNamespaces: opts.allowNamespaces !== false,      allowNamespacedObjects: !!opts.allowNamespacedObjects    };    instance.extend('parseExprAtom', function(inner) {      return function(refShortHandDefaultPos) {        if (this.type === tt.jsxText)          return this.parseLiteral(this.value);        else if (this.type === tt.jsxTagStart)          return this.jsx_parseElement();        else          return inner.call(this, refShortHandDefaultPos);      };    });    instance.extend('readToken', function(inner) {      return function(code) {        var context = this.curContext();        if (context === tc.j_expr) return this.jsx_readToken();        if (context === tc.j_oTag || context === tc.j_cTag) {          if (acorn.isIdentifierStart(code)) return this.jsx_readWord();          if (code == 62) {            ++this.pos;            return this.finishToken(tt.jsxTagEnd);          }          if ((code === 34 || code === 39) && context == tc.j_oTag)            return this.jsx_readString(code);        }        if (code === 60 && this.exprAllowed) {          ++this.pos;          return this.finishToken(tt.jsxTagStart);        }        return inner.call(this, code);      };    });    instance.extend('updateContext', function(inner) {      return function(prevType) {        if (this.type == tt.braceL) {          var curContext = this.curContext();          if (curContext == tc.j_oTag) this.context.push(tc.b_expr);          else if (curContext == tc.j_expr) this.context.push(tc.b_tmpl);          else inner.call(this, prevType);          this.exprAllowed = true;        } else if (this.type === tt.slash && prevType === tt.jsxTagStart) {          this.context.length -= 2; // do not consider JSX expr -> JSX open tag -> ... anymore          this.context.push(tc.j_cTag); // reconsider as closing tag context          this.exprAllowed = false;        } else {          return inner.call(this, prevType);        }      };    });  };  return acorn;};
 |