| '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); | 
|       } | 
|     } | 
|   }; | 
| } |