| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488 | 'use strict';const XHTMLEntities = require('./xhtml');const hexNumber = /^[\da-fA-F]+$/;const decimalNumber = /^\d+$/;// The map to `acorn-jsx` tokens from `acorn` namespace objects.const acornJsxMap = new WeakMap();// Get the original tokens for the given `acorn` namespace object.function getJsxTokens(acorn) {  acorn = acorn.Parser.acorn || acorn;  let acornJsx = acornJsxMap.get(acorn);  if (!acornJsx) {    const tt = acorn.tokTypes;    const TokContext = acorn.TokContext;    const TokenType = acorn.TokenType;    const tc_oTag = new TokContext('<tag', false);    const tc_cTag = new TokContext('</tag', false);    const tc_expr = new TokContext('<tag>...</tag>', true, true);    const tokContexts = {      tc_oTag: tc_oTag,      tc_cTag: tc_cTag,      tc_expr: tc_expr    };    const tokTypes = {      jsxName: new TokenType('jsxName'),      jsxText: new TokenType('jsxText', {beforeExpr: true}),      jsxTagStart: new TokenType('jsxTagStart', {startsExpr: true}),      jsxTagEnd: new TokenType('jsxTagEnd')    };    tokTypes.jsxTagStart.updateContext = function() {      this.context.push(tc_expr); // treat as beginning of JSX expression      this.context.push(tc_oTag); // start opening tag context      this.exprAllowed = false;    };    tokTypes.jsxTagEnd.updateContext = function(prevType) {      let out = this.context.pop();      if (out === tc_oTag && prevType === tt.slash || out === tc_cTag) {        this.context.pop();        this.exprAllowed = this.curContext() === tc_expr;      } else {        this.exprAllowed = true;      }    };    acornJsx = { tokContexts: tokContexts, tokTypes: tokTypes };    acornJsxMap.set(acorn, acornJsx);  }  return acornJsx;}// Transforms JSX element name to string.function getQualifiedJSXName(object) {  if (!object)    return 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);}module.exports = function(options) {  options = options || {};  return function(Parser) {    return plugin({      allowNamespaces: options.allowNamespaces !== false,      allowNamespacedObjects: !!options.allowNamespacedObjects    }, Parser);  };};// This is `tokTypes` of the peer dep.// This can be different instances from the actual `tokTypes` this plugin uses.Object.defineProperty(module.exports, "tokTypes", {  get: function get_tokTypes() {    return getJsxTokens(require("acorn")).tokTypes;  },  configurable: true,  enumerable: true});function plugin(options, Parser) {  const acorn = Parser.acorn || require("acorn");  const acornJsx = getJsxTokens(acorn);  const tt = acorn.tokTypes;  const tok = acornJsx.tokTypes;  const tokContexts = acorn.tokContexts;  const tc_oTag = acornJsx.tokContexts.tc_oTag;  const tc_cTag = acornJsx.tokContexts.tc_cTag;  const tc_expr = acornJsx.tokContexts.tc_expr;  const isNewLine = acorn.isNewLine;  const isIdentifierStart = acorn.isIdentifierStart;  const isIdentifierChar = acorn.isIdentifierChar;  return class extends Parser {    // Expose actual `tokTypes` and `tokContexts` to other plugins.    static get acornJsx() {      return acornJsx;    }    // Reads inline JSX contents token.    jsx_readToken() {      let out = '', chunkStart = this.pos;      for (;;) {        if (this.pos >= this.input.length)          this.raise(this.start, 'Unterminated JSX contents');        let 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(tok.jsxTagStart);            }            return this.getTokenFromCode(ch);          }          out += this.input.slice(chunkStart, this.pos);          return this.finishToken(tok.jsxText, out);        case 38: // '&'          out += this.input.slice(chunkStart, this.pos);          out += this.jsx_readEntity();          chunkStart = this.pos;          break;        case 62: // '>'        case 125: // '}'          this.raise(            this.pos,            "Unexpected token `" + this.input[this.pos] + "`. Did you mean `" +              (ch === 62 ? ">" : "}") + "` or " + "`{\"" + this.input[this.pos] + "\"}" + "`?"          );        default:          if (isNewLine(ch)) {            out += this.input.slice(chunkStart, this.pos);            out += this.jsx_readNewLine(true);            chunkStart = this.pos;          } else {            ++this.pos;          }        }      }    }    jsx_readNewLine(normalizeCRLF) {      let ch = this.input.charCodeAt(this.pos);      let 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;    }    jsx_readString(quote) {      let out = '', chunkStart = ++this.pos;      for (;;) {        if (this.pos >= this.input.length)          this.raise(this.start, 'Unterminated string constant');        let 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 (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);    }    jsx_readEntity() {      let str = '', count = 0, entity;      let ch = this.input[this.pos];      if (ch !== '&')        this.raise(this.pos, 'Entity must start with an ampersand');      let 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.    jsx_readWord() {      let ch, start = this.pos;      do {        ch = this.input.charCodeAt(++this.pos);      } while (isIdentifierChar(ch) || ch === 45); // '-'      return this.finishToken(tok.jsxName, this.input.slice(start, this.pos));    }    // Parse next token as JSX identifier    jsx_parseIdentifier() {      let node = this.startNode();      if (this.type === tok.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.    jsx_parseNamespacedName() {      let startPos = this.start, startLoc = this.startLoc;      let name = this.jsx_parseIdentifier();      if (!options.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.    jsx_parseElementName() {      if (this.type === tok.jsxTagEnd) return '';      let startPos = this.start, startLoc = this.startLoc;      let node = this.jsx_parseNamespacedName();      if (this.type === tt.dot && node.type === 'JSXNamespacedName' && !options.allowNamespacedObjects) {        this.unexpected();      }      while (this.eat(tt.dot)) {        let 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.    jsx_parseAttributeValue() {      switch (this.type) {      case tt.braceL:        let 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 tok.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).    jsx_parseEmptyExpression() {      let node = this.startNodeAt(this.lastTokEnd, this.lastTokEndLoc);      return this.finishNodeAt(node, 'JSXEmptyExpression', this.start, this.startLoc);    }    // Parses JSX expression enclosed into curly brackets.    jsx_parseExpressionContainer() {      let 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.    jsx_parseAttribute() {      let 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 '<'.    jsx_parseOpeningElementAt(startPos, startLoc) {      let node = this.startNodeAt(startPos, startLoc);      node.attributes = [];      let nodeName = this.jsx_parseElementName();      if (nodeName) node.name = nodeName;      while (this.type !== tt.slash && this.type !== tok.jsxTagEnd)        node.attributes.push(this.jsx_parseAttribute());      node.selfClosing = this.eat(tt.slash);      this.expect(tok.jsxTagEnd);      return this.finishNode(node, nodeName ? 'JSXOpeningElement' : 'JSXOpeningFragment');    }    // Parses JSX closing tag starting after '</'.    jsx_parseClosingElementAt(startPos, startLoc) {      let node = this.startNodeAt(startPos, startLoc);      let nodeName = this.jsx_parseElementName();      if (nodeName) node.name = nodeName;      this.expect(tok.jsxTagEnd);      return this.finishNode(node, nodeName ? 'JSXClosingElement' : 'JSXClosingFragment');    }    // Parses entire JSX element, including it's opening tag    // (starting after '<'), attributes, contents and closing tag.    jsx_parseElementAt(startPos, startLoc) {      let node = this.startNodeAt(startPos, startLoc);      let children = [];      let openingElement = this.jsx_parseOpeningElementAt(startPos, startLoc);      let closingElement = null;      if (!openingElement.selfClosing) {        contents: for (;;) {          switch (this.type) {          case tok.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 tok.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) + '>');        }      }      let fragmentOrElement = openingElement.name ? 'Element' : 'Fragment';      node['opening' + fragmentOrElement] = openingElement;      node['closing' + fragmentOrElement] = 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, 'JSX' + fragmentOrElement);    }    // Parse JSX text    jsx_parseText() {      let node = this.parseLiteral(this.value);      node.type = "JSXText";      return node;    }    // Parses entire JSX element from current position.    jsx_parseElement() {      let startPos = this.start, startLoc = this.startLoc;      this.next();      return this.jsx_parseElementAt(startPos, startLoc);    }    parseExprAtom(refShortHandDefaultPos) {      if (this.type === tok.jsxText)        return this.jsx_parseText();      else if (this.type === tok.jsxTagStart)        return this.jsx_parseElement();      else        return super.parseExprAtom(refShortHandDefaultPos);    }    readToken(code) {      let context = this.curContext();      if (context === tc_expr) return this.jsx_readToken();      if (context === tc_oTag || context === tc_cTag) {        if (isIdentifierStart(code)) return this.jsx_readWord();        if (code == 62) {          ++this.pos;          return this.finishToken(tok.jsxTagEnd);        }        if ((code === 34 || code === 39) && context == tc_oTag)          return this.jsx_readString(code);      }      if (code === 60 && this.exprAllowed && this.input.charCodeAt(this.pos + 1) !== 33) {        ++this.pos;        return this.finishToken(tok.jsxTagStart);      }      return super.readToken(code);    }    updateContext(prevType) {      if (this.type == tt.braceL) {        var curContext = this.curContext();        if (curContext == tc_oTag) this.context.push(tokContexts.b_expr);        else if (curContext == tc_expr) this.context.push(tokContexts.b_tmpl);        else super.updateContext(prevType);        this.exprAllowed = true;      } else if (this.type === tt.slash && prevType === tok.jsxTagStart) {        this.context.length -= 2; // do not consider JSX expr -> JSX open tag -> ... anymore        this.context.push(tc_cTag); // reconsider as closing tag context        this.exprAllowed = false;      } else {        return super.updateContext(prevType);      }    }  };}
 |