Giant blob of minor changes
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / acorn-jsx / index.js
1 'use strict';
2
3 const XHTMLEntities = require('./xhtml');
4
5 const hexNumber = /^[\da-fA-F]+$/;
6 const decimalNumber = /^\d+$/;
7
8 // The map to `acorn-jsx` tokens from `acorn` namespace objects.
9 const acornJsxMap = new WeakMap();
10
11 // Get the original tokens for the given `acorn` namespace object.
12 function getJsxTokens(acorn) {
13   acorn = acorn.Parser.acorn || acorn;
14   let acornJsx = acornJsxMap.get(acorn);
15   if (!acornJsx) {
16     const tt = acorn.tokTypes;
17     const TokContext = acorn.TokContext;
18     const TokenType = acorn.TokenType;
19     const tc_oTag = new TokContext('<tag', false);
20     const tc_cTag = new TokContext('</tag', false);
21     const tc_expr = new TokContext('<tag>...</tag>', true, true);
22     const tokContexts = {
23       tc_oTag: tc_oTag,
24       tc_cTag: tc_cTag,
25       tc_expr: tc_expr
26     };
27     const tokTypes = {
28       jsxName: new TokenType('jsxName'),
29       jsxText: new TokenType('jsxText', {beforeExpr: true}),
30       jsxTagStart: new TokenType('jsxTagStart', {startsExpr: true}),
31       jsxTagEnd: new TokenType('jsxTagEnd')
32     };
33
34     tokTypes.jsxTagStart.updateContext = function() {
35       this.context.push(tc_expr); // treat as beginning of JSX expression
36       this.context.push(tc_oTag); // start opening tag context
37       this.exprAllowed = false;
38     };
39     tokTypes.jsxTagEnd.updateContext = function(prevType) {
40       let out = this.context.pop();
41       if (out === tc_oTag && prevType === tt.slash || out === tc_cTag) {
42         this.context.pop();
43         this.exprAllowed = this.curContext() === tc_expr;
44       } else {
45         this.exprAllowed = true;
46       }
47     };
48
49     acornJsx = { tokContexts: tokContexts, tokTypes: tokTypes };
50     acornJsxMap.set(acorn, acornJsx);
51   }
52
53   return acornJsx;
54 }
55
56 // Transforms JSX element name to string.
57
58 function getQualifiedJSXName(object) {
59   if (!object)
60     return object;
61
62   if (object.type === 'JSXIdentifier')
63     return object.name;
64
65   if (object.type === 'JSXNamespacedName')
66     return object.namespace.name + ':' + object.name.name;
67
68   if (object.type === 'JSXMemberExpression')
69     return getQualifiedJSXName(object.object) + '.' +
70     getQualifiedJSXName(object.property);
71 }
72
73 module.exports = function(options) {
74   options = options || {};
75   return function(Parser) {
76     return plugin({
77       allowNamespaces: options.allowNamespaces !== false,
78       allowNamespacedObjects: !!options.allowNamespacedObjects
79     }, Parser);
80   };
81 };
82
83 // This is `tokTypes` of the peer dep.
84 // This can be different instances from the actual `tokTypes` this plugin uses.
85 Object.defineProperty(module.exports, "tokTypes", {
86   get: function get_tokTypes() {
87     return getJsxTokens(require("acorn")).tokTypes;
88   },
89   configurable: true,
90   enumerable: true
91 });
92
93 function plugin(options, Parser) {
94   const acorn = Parser.acorn || require("acorn");
95   const acornJsx = getJsxTokens(acorn);
96   const tt = acorn.tokTypes;
97   const tok = acornJsx.tokTypes;
98   const tokContexts = acorn.tokContexts;
99   const tc_oTag = acornJsx.tokContexts.tc_oTag;
100   const tc_cTag = acornJsx.tokContexts.tc_cTag;
101   const tc_expr = acornJsx.tokContexts.tc_expr;
102   const isNewLine = acorn.isNewLine;
103   const isIdentifierStart = acorn.isIdentifierStart;
104   const isIdentifierChar = acorn.isIdentifierChar;
105
106   return class extends Parser {
107     // Expose actual `tokTypes` and `tokContexts` to other plugins.
108     static get acornJsx() {
109       return acornJsx;
110     }
111
112     // Reads inline JSX contents token.
113     jsx_readToken() {
114       let out = '', chunkStart = this.pos;
115       for (;;) {
116         if (this.pos >= this.input.length)
117           this.raise(this.start, 'Unterminated JSX contents');
118         let ch = this.input.charCodeAt(this.pos);
119
120         switch (ch) {
121         case 60: // '<'
122         case 123: // '{'
123           if (this.pos === this.start) {
124             if (ch === 60 && this.exprAllowed) {
125               ++this.pos;
126               return this.finishToken(tok.jsxTagStart);
127             }
128             return this.getTokenFromCode(ch);
129           }
130           out += this.input.slice(chunkStart, this.pos);
131           return this.finishToken(tok.jsxText, out);
132
133         case 38: // '&'
134           out += this.input.slice(chunkStart, this.pos);
135           out += this.jsx_readEntity();
136           chunkStart = this.pos;
137           break;
138
139         case 62: // '>'
140         case 125: // '}'
141           this.raise(
142             this.pos,
143             "Unexpected token `" + this.input[this.pos] + "`. Did you mean `" +
144               (ch === 62 ? "&gt;" : "&rbrace;") + "` or " + "`{\"" + this.input[this.pos] + "\"}" + "`?"
145           );
146
147         default:
148           if (isNewLine(ch)) {
149             out += this.input.slice(chunkStart, this.pos);
150             out += this.jsx_readNewLine(true);
151             chunkStart = this.pos;
152           } else {
153             ++this.pos;
154           }
155         }
156       }
157     }
158
159     jsx_readNewLine(normalizeCRLF) {
160       let ch = this.input.charCodeAt(this.pos);
161       let out;
162       ++this.pos;
163       if (ch === 13 && this.input.charCodeAt(this.pos) === 10) {
164         ++this.pos;
165         out = normalizeCRLF ? '\n' : '\r\n';
166       } else {
167         out = String.fromCharCode(ch);
168       }
169       if (this.options.locations) {
170         ++this.curLine;
171         this.lineStart = this.pos;
172       }
173
174       return out;
175     }
176
177     jsx_readString(quote) {
178       let out = '', chunkStart = ++this.pos;
179       for (;;) {
180         if (this.pos >= this.input.length)
181           this.raise(this.start, 'Unterminated string constant');
182         let ch = this.input.charCodeAt(this.pos);
183         if (ch === quote) break;
184         if (ch === 38) { // '&'
185           out += this.input.slice(chunkStart, this.pos);
186           out += this.jsx_readEntity();
187           chunkStart = this.pos;
188         } else if (isNewLine(ch)) {
189           out += this.input.slice(chunkStart, this.pos);
190           out += this.jsx_readNewLine(false);
191           chunkStart = this.pos;
192         } else {
193           ++this.pos;
194         }
195       }
196       out += this.input.slice(chunkStart, this.pos++);
197       return this.finishToken(tt.string, out);
198     }
199
200     jsx_readEntity() {
201       let str = '', count = 0, entity;
202       let ch = this.input[this.pos];
203       if (ch !== '&')
204         this.raise(this.pos, 'Entity must start with an ampersand');
205       let startPos = ++this.pos;
206       while (this.pos < this.input.length && count++ < 10) {
207         ch = this.input[this.pos++];
208         if (ch === ';') {
209           if (str[0] === '#') {
210             if (str[1] === 'x') {
211               str = str.substr(2);
212               if (hexNumber.test(str))
213                 entity = String.fromCharCode(parseInt(str, 16));
214             } else {
215               str = str.substr(1);
216               if (decimalNumber.test(str))
217                 entity = String.fromCharCode(parseInt(str, 10));
218             }
219           } else {
220             entity = XHTMLEntities[str];
221           }
222           break;
223         }
224         str += ch;
225       }
226       if (!entity) {
227         this.pos = startPos;
228         return '&';
229       }
230       return entity;
231     }
232
233     // Read a JSX identifier (valid tag or attribute name).
234     //
235     // Optimized version since JSX identifiers can't contain
236     // escape characters and so can be read as single slice.
237     // Also assumes that first character was already checked
238     // by isIdentifierStart in readToken.
239
240     jsx_readWord() {
241       let ch, start = this.pos;
242       do {
243         ch = this.input.charCodeAt(++this.pos);
244       } while (isIdentifierChar(ch) || ch === 45); // '-'
245       return this.finishToken(tok.jsxName, this.input.slice(start, this.pos));
246     }
247
248     // Parse next token as JSX identifier
249
250     jsx_parseIdentifier() {
251       let node = this.startNode();
252       if (this.type === tok.jsxName)
253         node.name = this.value;
254       else if (this.type.keyword)
255         node.name = this.type.keyword;
256       else
257         this.unexpected();
258       this.next();
259       return this.finishNode(node, 'JSXIdentifier');
260     }
261
262     // Parse namespaced identifier.
263
264     jsx_parseNamespacedName() {
265       let startPos = this.start, startLoc = this.startLoc;
266       let name = this.jsx_parseIdentifier();
267       if (!options.allowNamespaces || !this.eat(tt.colon)) return name;
268       var node = this.startNodeAt(startPos, startLoc);
269       node.namespace = name;
270       node.name = this.jsx_parseIdentifier();
271       return this.finishNode(node, 'JSXNamespacedName');
272     }
273
274     // Parses element name in any form - namespaced, member
275     // or single identifier.
276
277     jsx_parseElementName() {
278       if (this.type === tok.jsxTagEnd) return '';
279       let startPos = this.start, startLoc = this.startLoc;
280       let node = this.jsx_parseNamespacedName();
281       if (this.type === tt.dot && node.type === 'JSXNamespacedName' && !options.allowNamespacedObjects) {
282         this.unexpected();
283       }
284       while (this.eat(tt.dot)) {
285         let newNode = this.startNodeAt(startPos, startLoc);
286         newNode.object = node;
287         newNode.property = this.jsx_parseIdentifier();
288         node = this.finishNode(newNode, 'JSXMemberExpression');
289       }
290       return node;
291     }
292
293     // Parses any type of JSX attribute value.
294
295     jsx_parseAttributeValue() {
296       switch (this.type) {
297       case tt.braceL:
298         let node = this.jsx_parseExpressionContainer();
299         if (node.expression.type === 'JSXEmptyExpression')
300           this.raise(node.start, 'JSX attributes must only be assigned a non-empty expression');
301         return node;
302
303       case tok.jsxTagStart:
304       case tt.string:
305         return this.parseExprAtom();
306
307       default:
308         this.raise(this.start, 'JSX value should be either an expression or a quoted JSX text');
309       }
310     }
311
312     // JSXEmptyExpression is unique type since it doesn't actually parse anything,
313     // and so it should start at the end of last read token (left brace) and finish
314     // at the beginning of the next one (right brace).
315
316     jsx_parseEmptyExpression() {
317       let node = this.startNodeAt(this.lastTokEnd, this.lastTokEndLoc);
318       return this.finishNodeAt(node, 'JSXEmptyExpression', this.start, this.startLoc);
319     }
320
321     // Parses JSX expression enclosed into curly brackets.
322
323     jsx_parseExpressionContainer() {
324       let node = this.startNode();
325       this.next();
326       node.expression = this.type === tt.braceR
327         ? this.jsx_parseEmptyExpression()
328         : this.parseExpression();
329       this.expect(tt.braceR);
330       return this.finishNode(node, 'JSXExpressionContainer');
331     }
332
333     // Parses following JSX attribute name-value pair.
334
335     jsx_parseAttribute() {
336       let node = this.startNode();
337       if (this.eat(tt.braceL)) {
338         this.expect(tt.ellipsis);
339         node.argument = this.parseMaybeAssign();
340         this.expect(tt.braceR);
341         return this.finishNode(node, 'JSXSpreadAttribute');
342       }
343       node.name = this.jsx_parseNamespacedName();
344       node.value = this.eat(tt.eq) ? this.jsx_parseAttributeValue() : null;
345       return this.finishNode(node, 'JSXAttribute');
346     }
347
348     // Parses JSX opening tag starting after '<'.
349
350     jsx_parseOpeningElementAt(startPos, startLoc) {
351       let node = this.startNodeAt(startPos, startLoc);
352       node.attributes = [];
353       let nodeName = this.jsx_parseElementName();
354       if (nodeName) node.name = nodeName;
355       while (this.type !== tt.slash && this.type !== tok.jsxTagEnd)
356         node.attributes.push(this.jsx_parseAttribute());
357       node.selfClosing = this.eat(tt.slash);
358       this.expect(tok.jsxTagEnd);
359       return this.finishNode(node, nodeName ? 'JSXOpeningElement' : 'JSXOpeningFragment');
360     }
361
362     // Parses JSX closing tag starting after '</'.
363
364     jsx_parseClosingElementAt(startPos, startLoc) {
365       let node = this.startNodeAt(startPos, startLoc);
366       let nodeName = this.jsx_parseElementName();
367       if (nodeName) node.name = nodeName;
368       this.expect(tok.jsxTagEnd);
369       return this.finishNode(node, nodeName ? 'JSXClosingElement' : 'JSXClosingFragment');
370     }
371
372     // Parses entire JSX element, including it's opening tag
373     // (starting after '<'), attributes, contents and closing tag.
374
375     jsx_parseElementAt(startPos, startLoc) {
376       let node = this.startNodeAt(startPos, startLoc);
377       let children = [];
378       let openingElement = this.jsx_parseOpeningElementAt(startPos, startLoc);
379       let closingElement = null;
380
381       if (!openingElement.selfClosing) {
382         contents: for (;;) {
383           switch (this.type) {
384           case tok.jsxTagStart:
385             startPos = this.start; startLoc = this.startLoc;
386             this.next();
387             if (this.eat(tt.slash)) {
388               closingElement = this.jsx_parseClosingElementAt(startPos, startLoc);
389               break contents;
390             }
391             children.push(this.jsx_parseElementAt(startPos, startLoc));
392             break;
393
394           case tok.jsxText:
395             children.push(this.parseExprAtom());
396             break;
397
398           case tt.braceL:
399             children.push(this.jsx_parseExpressionContainer());
400             break;
401
402           default:
403             this.unexpected();
404           }
405         }
406         if (getQualifiedJSXName(closingElement.name) !== getQualifiedJSXName(openingElement.name)) {
407           this.raise(
408             closingElement.start,
409             'Expected corresponding JSX closing tag for <' + getQualifiedJSXName(openingElement.name) + '>');
410         }
411       }
412       let fragmentOrElement = openingElement.name ? 'Element' : 'Fragment';
413
414       node['opening' + fragmentOrElement] = openingElement;
415       node['closing' + fragmentOrElement] = closingElement;
416       node.children = children;
417       if (this.type === tt.relational && this.value === "<") {
418         this.raise(this.start, "Adjacent JSX elements must be wrapped in an enclosing tag");
419       }
420       return this.finishNode(node, 'JSX' + fragmentOrElement);
421     }
422
423     // Parse JSX text
424
425     jsx_parseText() {
426       let node = this.parseLiteral(this.value);
427       node.type = "JSXText";
428       return node;
429     }
430
431     // Parses entire JSX element from current position.
432
433     jsx_parseElement() {
434       let startPos = this.start, startLoc = this.startLoc;
435       this.next();
436       return this.jsx_parseElementAt(startPos, startLoc);
437     }
438
439     parseExprAtom(refShortHandDefaultPos) {
440       if (this.type === tok.jsxText)
441         return this.jsx_parseText();
442       else if (this.type === tok.jsxTagStart)
443         return this.jsx_parseElement();
444       else
445         return super.parseExprAtom(refShortHandDefaultPos);
446     }
447
448     readToken(code) {
449       let context = this.curContext();
450
451       if (context === tc_expr) return this.jsx_readToken();
452
453       if (context === tc_oTag || context === tc_cTag) {
454         if (isIdentifierStart(code)) return this.jsx_readWord();
455
456         if (code == 62) {
457           ++this.pos;
458           return this.finishToken(tok.jsxTagEnd);
459         }
460
461         if ((code === 34 || code === 39) && context == tc_oTag)
462           return this.jsx_readString(code);
463       }
464
465       if (code === 60 && this.exprAllowed && this.input.charCodeAt(this.pos + 1) !== 33) {
466         ++this.pos;
467         return this.finishToken(tok.jsxTagStart);
468       }
469       return super.readToken(code);
470     }
471
472     updateContext(prevType) {
473       if (this.type == tt.braceL) {
474         var curContext = this.curContext();
475         if (curContext == tc_oTag) this.context.push(tokContexts.b_expr);
476         else if (curContext == tc_expr) this.context.push(tokContexts.b_tmpl);
477         else super.updateContext(prevType);
478         this.exprAllowed = true;
479       } else if (this.type === tt.slash && prevType === tok.jsxTagStart) {
480         this.context.length -= 2; // do not consider JSX expr -> JSX open tag -> ... anymore
481         this.context.push(tc_cTag); // reconsider as closing tag context
482         this.exprAllowed = false;
483       } else {
484         return super.updateContext(prevType);
485       }
486     }
487   };
488 }