babel / babel
Showing 10 of 22 files from the diff.

@@ -4,6 +4,7 @@
Loading
4 4
import {
5 5
  tokenIsIdentifier,
6 6
  tokenIsLoop,
7 +
  tokenIsTemplate,
7 8
  tt,
8 9
  type TokenType,
9 10
  getExportedToken,
@@ -39,7 +40,7 @@
Loading
39 40
} from "../util/expression-scope";
40 41
import type { SourceType } from "../options";
41 42
import { Token } from "../tokenizer";
42 -
import { Position } from "../util/location";
43 +
import { createPositionWithColumnOffset } from "../util/location";
43 44
import { cloneStringLiteral, cloneIdentifier } from "./node";
44 45
45 46
const loopLabel = { kind: "loop" },
@@ -55,7 +56,10 @@
Loading
55 56
const keywordRelationalOperator = /in(?:stanceof)?/y;
56 57
57 58
/**
58 -
 * Convert tt.privateName to tt.hash + tt.name for backward Babel 7 compat.
59 +
 * Convert tokens for backward Babel 7 compat.
60 +
 * tt.privateName => tt.hash + tt.name
61 +
 * tt.templateTail => tt.backquote/tt.braceR + tt.template + tt.backquote
62 +
 * tt.templateNonTail => tt.backquote/tt.braceR + tt.template + tt.dollarBraceL
59 63
 * For performance reasons this routine mutates `tokens`, it is okay
60 64
 * here since we execute `parseTopLevel` once for every file.
61 65
 * @param {*} tokens
@@ -65,38 +69,116 @@
Loading
65 69
  for (let i = 0; i < tokens.length; i++) {
66 70
    const token = tokens[i];
67 71
    const { type } = token;
68 -
    if (type === tt.privateName) {
72 +
    if (typeof type === "number") {
69 73
      if (!process.env.BABEL_8_BREAKING) {
70 -
        const { loc, start, value, end } = token;
71 -
        const hashEndPos = start + 1;
72 -
        const hashEndLoc = new Position(loc.start.line, loc.start.column + 1);
73 -
        tokens.splice(
74 -
          i,
75 -
          1,
76 -
          // $FlowIgnore: hacky way to create token
77 -
          new Token({
78 -
            type: getExportedToken(tt.hash),
79 -
            value: "#",
80 -
            start: start,
81 -
            end: hashEndPos,
82 -
            startLoc: loc.start,
83 -
            endLoc: hashEndLoc,
84 -
          }),
85 -
          // $FlowIgnore: hacky way to create token
86 -
          new Token({
87 -
            type: getExportedToken(tt.name),
88 -
            value: value,
89 -
            start: hashEndPos,
90 -
            end: end,
91 -
            startLoc: hashEndLoc,
92 -
            endLoc: loc.end,
93 -
          }),
94 -
        );
95 -
        i++;
96 -
        continue;
74 +
        if (type === tt.privateName) {
75 +
          const { loc, start, value, end } = token;
76 +
          const hashEndPos = start + 1;
77 +
          const hashEndLoc = createPositionWithColumnOffset(loc.start, 1);
78 +
          tokens.splice(
79 +
            i,
80 +
            1,
81 +
            // $FlowIgnore: hacky way to create token
82 +
            new Token({
83 +
              type: getExportedToken(tt.hash),
84 +
              value: "#",
85 +
              start: start,
86 +
              end: hashEndPos,
87 +
              startLoc: loc.start,
88 +
              endLoc: hashEndLoc,
89 +
            }),
90 +
            // $FlowIgnore: hacky way to create token
91 +
            new Token({
92 +
              type: getExportedToken(tt.name),
93 +
              value: value,
94 +
              start: hashEndPos,
95 +
              end: end,
96 +
              startLoc: hashEndLoc,
97 +
              endLoc: loc.end,
98 +
            }),
99 +
          );
100 +
          i++;
101 +
          continue;
102 +
        }
103 +
104 +
        if (tokenIsTemplate(type)) {
105 +
          const { loc, start, value, end } = token;
106 +
          const backquoteEnd = start + 1;
107 +
          const backquoteEndLoc = createPositionWithColumnOffset(loc.start, 1);
108 +
          let startToken;
109 +
          if (value.charCodeAt(0) === charCodes.graveAccent) {
110 +
            // $FlowIgnore: hacky way to create token
111 +
            startToken = new Token({
112 +
              type: getExportedToken(tt.backQuote),
113 +
              value: "`",
114 +
              start: start,
115 +
              end: backquoteEnd,
116 +
              startLoc: loc.start,
117 +
              endLoc: backquoteEndLoc,
118 +
            });
119 +
          } else {
120 +
            // $FlowIgnore: hacky way to create token
121 +
            startToken = new Token({
122 +
              type: getExportedToken(tt.braceR),
123 +
              value: "}",
124 +
              start: start,
125 +
              end: backquoteEnd,
126 +
              startLoc: loc.start,
127 +
              endLoc: backquoteEndLoc,
128 +
            });
129 +
          }
130 +
          let templateValue,
131 +
            templateElementEnd,
132 +
            templateElementEndLoc,
133 +
            endToken;
134 +
          if (type === tt.templateTail) {
135 +
            // ends with '`'
136 +
            templateElementEnd = end - 1;
137 +
            templateElementEndLoc = createPositionWithColumnOffset(loc.end, -1);
138 +
            templateValue = value.slice(1, -1);
139 +
            // $FlowIgnore: hacky way to create token
140 +
            endToken = new Token({
141 +
              type: getExportedToken(tt.backQuote),
142 +
              value: "`",
143 +
              start: templateElementEnd,
144 +
              end: end,
145 +
              startLoc: templateElementEndLoc,
146 +
              endLoc: loc.end,
147 +
            });
148 +
          } else {
149 +
            // ends with `${`
150 +
            templateElementEnd = end - 2;
151 +
            templateElementEndLoc = createPositionWithColumnOffset(loc.end, -2);
152 +
            templateValue = value.slice(1, -2);
153 +
            // $FlowIgnore: hacky way to create token
154 +
            endToken = new Token({
155 +
              type: getExportedToken(tt.dollarBraceL),
156 +
              value: "${",
157 +
              start: templateElementEnd,
158 +
              end: end,
159 +
              startLoc: templateElementEndLoc,
160 +
              endLoc: loc.end,
161 +
            });
162 +
          }
163 +
          tokens.splice(
164 +
            i,
165 +
            1,
166 +
            startToken,
167 +
            // $FlowIgnore: hacky way to create token
168 +
            new Token({
169 +
              type: getExportedToken(tt.template),
170 +
              value: templateValue,
171 +
              start: backquoteEnd,
172 +
              end: templateElementEnd,
173 +
              startLoc: backquoteEndLoc,
174 +
              endLoc: templateElementEndLoc,
175 +
            }),
176 +
            endToken,
177 +
          );
178 +
          i += 2;
179 +
          continue;
180 +
        }
97 181
      }
98 -
    }
99 -
    if (typeof type === "number") {
100 182
      // $FlowIgnore: we manipulate `token` for performance reasons
101 183
      token.type = getExportedToken(type);
102 184
    }

@@ -50,3 +50,22 @@
Loading
50 50
51 51
  return new Position(line, offset - lineStart);
52 52
}
53 +
54 +
/**
55 +
 * creates a new position with a non-zero column offset from the given position.
56 +
 * This function should be only be used when we create AST node out of the token
57 +
 * boundaries, such as TemplateElement ends before tt.templateNonTail. This
58 +
 * function does not skip whitespaces.
59 +
 *
60 +
 * @export
61 +
 * @param {Position} position
62 +
 * @param {number} columnOffset
63 +
 * @returns {Position}
64 +
 */
65 +
export function createPositionWithColumnOffset(
66 +
  position: Position,
67 +
  columnOffset: number,
68 +
) {
69 +
  const { line, column } = position;
70 +
  return new Position(line, column + columnOffset);
71 +
}

@@ -27,6 +27,7 @@
Loading
27 27
  tokenIsPostfix,
28 28
  tokenIsPrefix,
29 29
  tokenIsRightAssociative,
30 +
  tokenIsTemplate,
30 31
  tokenKeywordOrIdentifierIsKeyword,
31 32
  tokenLabelName,
32 33
  tokenOperatorPrecedence,
@@ -43,7 +44,7 @@
Loading
43 44
  isIdentifierStart,
44 45
  canBeReservedWord,
45 46
} from "../util/identifier";
46 -
import { Position } from "../util/location";
47 +
import { Position, createPositionWithColumnOffset } from "../util/location";
47 48
import * as charCodes from "charcodes";
48 49
import {
49 50
  BIND_OUTSIDE,
@@ -706,9 +707,10 @@
Loading
706 707
    noCalls: ?boolean,
707 708
    state: N.ParseSubscriptState,
708 709
  ): N.Expression {
709 -
    if (!noCalls && this.eat(tt.doubleColon)) {
710 +
    const { type } = this.state;
711 +
    if (!noCalls && type === tt.doubleColon) {
710 712
      return this.parseBind(base, startPos, startLoc, noCalls, state);
711 -
    } else if (this.match(tt.backQuote)) {
713 +
    } else if (tokenIsTemplate(type)) {
712 714
      return this.parseTaggedTemplateExpression(
713 715
        base,
714 716
        startPos,
@@ -719,7 +721,7 @@
Loading
719 721
720 722
    let optional = false;
721 723
722 -
    if (this.match(tt.questionDot)) {
724 +
    if (type === tt.questionDot) {
723 725
      if (noCalls && this.lookaheadCharCode() === charCodes.leftParenthesis) {
724 726
        // stop at `?.` when parsing `new a?.()`
725 727
        state.stop = true;
@@ -801,6 +803,7 @@
Loading
801 803
  ): N.Expression {
802 804
    const node = this.startNodeAt(startPos, startLoc);
803 805
    node.object = base;
806 +
    this.next(); // eat '::'
804 807
    node.callee = this.parseNoCallExpr();
805 808
    state.stop = true;
806 809
    return this.parseSubscripts(
@@ -1153,7 +1156,8 @@
Loading
1153 1156
      case tt._new:
1154 1157
        return this.parseNewOrNewTarget();
1155 1158
1156 -
      case tt.backQuote:
1159 +
      case tt.templateNonTail:
1160 +
      case tt.templateTail:
1157 1161
        return this.parseTemplate(false);
1158 1162
1159 1163
      // BindExpression[Yield]
@@ -1832,37 +1836,47 @@
Loading
1832 1836
  // Parse template expression.
1833 1837
1834 1838
  parseTemplateElement(isTagged: boolean): N.TemplateElement {
1835 -
    const elem = this.startNode();
1836 -
    if (this.state.value === null) {
1839 +
    const { start, end, value } = this.state;
1840 +
    const elemStart = start + 1;
1841 +
    const elem = this.startNodeAt(
1842 +
      elemStart,
1843 +
      createPositionWithColumnOffset(this.state.startLoc, 1),
1844 +
    );
1845 +
    if (value === null) {
1837 1846
      if (!isTagged) {
1838 -
        this.raise(this.state.start + 1, Errors.InvalidEscapeSequenceTemplate);
1847 +
        this.raise(start + 2, Errors.InvalidEscapeSequenceTemplate);
1839 1848
      }
1840 1849
    }
1850 +
1851 +
    const isTail = this.match(tt.templateTail);
1852 +
    const endOffset = isTail ? -1 : -2;
1853 +
    const elemEnd = end + endOffset;
1841 1854
    elem.value = {
1842 -
      raw: this.input
1843 -
        .slice(this.state.start, this.state.end)
1844 -
        .replace(/\r\n?/g, "\n"),
1845 -
      cooked: this.state.value,
1855 +
      raw: this.input.slice(elemStart, elemEnd).replace(/\r\n?/g, "\n"),
1856 +
      cooked: value === null ? null : value.slice(1, endOffset),
1846 1857
    };
1858 +
    elem.tail = isTail;
1847 1859
    this.next();
1848 -
    elem.tail = this.match(tt.backQuote);
1849 -
    return this.finishNode(elem, "TemplateElement");
1860 +
    this.finishNode(elem, "TemplateElement");
1861 +
    this.resetEndLocation(
1862 +
      elem,
1863 +
      elemEnd,
1864 +
      createPositionWithColumnOffset(this.state.lastTokEndLoc, endOffset),
1865 +
    );
1866 +
    return elem;
1850 1867
  }
1851 1868
1852 1869
  // https://tc39.es/ecma262/#prod-TemplateLiteral
1853 1870
  parseTemplate(isTagged: boolean): N.TemplateLiteral {
1854 1871
    const node = this.startNode();
1855 -
    this.next();
1856 1872
    node.expressions = [];
1857 1873
    let curElt = this.parseTemplateElement(isTagged);
1858 1874
    node.quasis = [curElt];
1859 1875
    while (!curElt.tail) {
1860 -
      this.expect(tt.dollarBraceL);
1861 1876
      node.expressions.push(this.parseTemplateSubstitution());
1862 -
      this.expect(tt.braceR);
1877 +
      this.readTemplateContinuation();
1863 1878
      node.quasis.push((curElt = this.parseTemplateElement(isTagged)));
1864 1879
    }
1865 -
    this.next();
1866 1880
    return this.finishNode(node, "TemplateLiteral");
1867 1881
  }
1868 1882
@@ -2681,21 +2695,22 @@
Loading
2681 2695
  }
2682 2696
2683 2697
  isAmbiguousAwait(): boolean {
2698 +
    if (this.hasPrecedingLineBreak()) return true;
2699 +
    const { type } = this.state;
2684 2700
    return (
2685 -
      this.hasPrecedingLineBreak() ||
2686 2701
      // All the following expressions are ambiguous:
2687 2702
      //   await + 0, await - 0, await ( 0 ), await [ 0 ], await / 0 /u, await ``
2688 -
      this.match(tt.plusMin) ||
2689 -
      this.match(tt.parenL) ||
2690 -
      this.match(tt.bracketL) ||
2691 -
      this.match(tt.backQuote) ||
2703 +
      type === tt.plusMin ||
2704 +
      type === tt.parenL ||
2705 +
      type === tt.bracketL ||
2706 +
      tokenIsTemplate(type) ||
2692 2707
      // Sometimes the tokenizer generates tt.slash for regexps, and this is
2693 2708
      // handler by parseExprAtom
2694 -
      this.match(tt.regexp) ||
2695 -
      this.match(tt.slash) ||
2709 +
      type === tt.regexp ||
2710 +
      type === tt.slash ||
2696 2711
      // This code could be parsed both as a modulo operator or as an intrinsic:
2697 2712
      //   await %x(0)
2698 -
      (this.hasPlugin("v8intrinsic") && this.match(tt.modulo))
2713 +
      (this.hasPlugin("v8intrinsic") && type === tt.modulo)
2699 2714
    );
2700 2715
  }
2701 2716

@@ -2807,11 +2807,9 @@
Loading
2807 2807
        // by parsing `jsxTagStart` to stop the JSX plugin from
2808 2808
        // messing with the tokens
2809 2809
        const { context } = this.state;
2810 -
        const curContext = context[context.length - 1];
2811 -
        if (curContext === tc.j_oTag) {
2812 -
          context.length -= 2;
2813 -
        } else if (curContext === tc.j_expr) {
2814 -
          context.length -= 1;
2810 +
        const currentContext = context[context.length - 1];
2811 +
        if (currentContext === tc.j_oTag || currentContext === tc.j_expr) {
2812 +
          context.pop();
2815 2813
        }
2816 2814
      }
2817 2815

@@ -14,8 +14,9 @@
Loading
14 14
  tokenIsKeywordOrIdentifier,
15 15
  tt,
16 16
  type TokenType,
17 +
  tokenIsTemplate,
17 18
} from "../../tokenizer/types";
18 -
import { types as ct } from "../../tokenizer/context";
19 +
import { types as tc } from "../../tokenizer/context";
19 20
import * as N from "../../types";
20 21
import type { Position } from "../../util/location";
21 22
import type Parser from "../../parser";
@@ -1071,7 +1072,8 @@
Loading
1071 1072
          }
1072 1073
1073 1074
          return this.tsParseParenthesizedType();
1074 -
        case tt.backQuote:
1075 +
        case tt.templateNonTail:
1076 +
        case tt.templateTail:
1075 1077
          return this.tsParseTemplateLiteralType();
1076 1078
        default: {
1077 1079
          const { type } = this.state;
@@ -2196,7 +2198,7 @@
Loading
2196 2198
              }
2197 2199
2198 2200
              return this.finishCallExpression(node, state.optionalChainMember);
2199 -
            } else if (this.match(tt.backQuote)) {
2201 +
            } else if (tokenIsTemplate(this.state.type)) {
2200 2202
              const result = this.parseTaggedTemplateExpression(
2201 2203
                base,
2202 2204
                startPos,
@@ -2872,14 +2874,13 @@
Loading
2872 2874
        /*:: invariant(jsx.node != null) */
2873 2875
        if (!jsx.error) return jsx.node;
2874 2876
2875 -
        // Remove `tc.j_expr` and `tc.j_oTag` from context added
2877 +
        // Remove `tc.j_expr` or `tc.j_oTag` from context added
2876 2878
        // by parsing `jsxTagStart` to stop the JSX plugin from
2877 2879
        // messing with the tokens
2878 2880
        const { context } = this.state;
2879 -
        if (context[context.length - 1] === ct.j_oTag) {
2880 -
          context.length -= 2;
2881 -
        } else if (context[context.length - 1] === ct.j_expr) {
2882 -
          context.length -= 1;
2881 +
        const currentContext = context[context.length - 1];
2882 +
        if (currentContext === tc.j_oTag || currentContext === tc.j_expr) {
2883 +
          context.pop();
2883 2884
        }
2884 2885
      }
2885 2886

@@ -1,7 +1,7 @@
Loading
1 1
// @flow
2 2
3 -
// The token context is used to track whether the apostrophe "`"
4 -
// starts or ends a string template
3 +
// The token context is used in JSX plugin to track
4 +
// jsx tag / jsx text / normal JavaScript expression
5 5
6 6
export class TokContext {
7 7
  constructor(token: string, preserveSpace?: boolean) {
@@ -13,9 +13,17 @@
Loading
13 13
  preserveSpace: boolean;
14 14
}
15 15
16 -
export const types: {
16 +
const types: {
17 17
  [key: string]: TokContext,
18 18
} = {
19 -
  brace: new TokContext("{"),
20 -
  template: new TokContext("`", true),
19 +
  brace: new TokContext("{"), // normal JavaScript expression
20 +
  j_oTag: new TokContext("<tag"), // JSX openning tag
21 +
  j_cTag: new TokContext("</tag"), // JSX closing tag
22 +
  j_expr: new TokContext("<tag>...</tag>", true), // JSX expressions
21 23
};
24 +
25 +
if (!process.env.BABEL_8_BREAKING) {
26 +
  types.template = new TokContext("`", true);
27 +
}
28 +
29 +
export { types };

@@ -158,6 +158,10 @@
Loading
158 158
  ellipsis: createToken("...", { beforeExpr }),
159 159
  backQuote: createToken("`", { startsExpr }),
160 160
  dollarBraceL: createToken("${", { beforeExpr, startsExpr }),
161 +
  // start: isTemplate
162 +
  templateTail: createToken("...`", { startsExpr }),
163 +
  templateNonTail: createToken("...${", { beforeExpr, startsExpr }),
164 +
  // end: isTemplate
161 165
  at: createToken("@"),
162 166
  hash: createToken("#", { startsExpr }),
163 167
@@ -402,6 +406,10 @@
Loading
402 406
  return token === tt.exponent;
403 407
}
404 408
409 +
export function tokenIsTemplate(token: TokenType): boolean {
410 +
  return token >= tt.templateTail && token <= tt.templateNonTail;
411 +
}
412 +
405 413
export function getExportedToken(token: TokenType): ExportedTokenType {
406 414
  return tokenTypes[token];
407 415
}

@@ -71,13 +71,6 @@
Loading
71 71
        templateTokens.push(token);
72 72
        break;
73 73
74 -
      case tl.eof:
75 -
        if (curlyBrace) {
76 -
          result.push(curlyBrace);
77 -
        }
78 -
79 -
        break;
80 -
81 74
      default:
82 75
        if (curlyBrace) {
83 76
          result.push(curlyBrace);
@@ -186,6 +179,8 @@
Loading
186 179
    token.value = `${token.value}n`;
187 180
  } else if (label === tl.privateName) {
188 181
    token.type = "PrivateIdentifier";
182 +
  } else if (label === tl.templateNonTail || label === tl.templateTail) {
183 +
    token.type = "Template";
189 184
  }
190 185
191 186
  if (typeof token.type !== "string") {
@@ -196,12 +191,16 @@
Loading
196 191
197 192
module.exports = function convertTokens(tokens, code, tl) {
198 193
  const result = [];
199 -
200 -
  const withoutComments = convertTemplateType(tokens, tl).filter(
201 -
    t => t.type !== "CommentLine" && t.type !== "CommentBlock",
202 -
  );
203 -
  for (let i = 0, { length } = withoutComments; i < length; i++) {
204 -
    const token = withoutComments[i];
194 +
  const templateTypeMergedTokens = process.env.BABEL_8_BREAKING
195 +
    ? tokens
196 +
    : convertTemplateType(tokens, tl);
197 +
  // The last token is always tt.eof and should be skipped
198 +
  for (let i = 0, { length } = templateTypeMergedTokens; i < length - 1; i++) {
199 +
    const token = templateTypeMergedTokens[i];
200 +
    const tokenType = token.type;
201 +
    if (tokenType === "CommentLine" || tokenType === "CommentBlock") {
202 +
      continue;
203 +
    }
205 204
206 205
    if (!process.env.BABEL_8_BREAKING) {
207 206
      // Babel 8 already produces a single token
@@ -209,9 +208,9 @@
Loading
209 208
      if (
210 209
        ESLINT_VERSION >= 8 &&
211 210
        i + 1 < length &&
212 -
        token.type.label === tl.hash
211 +
        tokenType.label === tl.hash
213 212
      ) {
214 -
        const nextToken = withoutComments[i + 1];
213 +
        const nextToken = templateTypeMergedTokens[i + 1];
215 214
216 215
        // We must disambiguate private identifier from the hack pipes topic token
217 216
        if (nextToken.type.label === tl.name && token.end === nextToken.start) {

@@ -13,7 +13,7 @@
Loading
13 13
  keywords as keywordTypes,
14 14
  type TokenType,
15 15
} from "./types";
16 -
import { type TokContext, types as ct } from "./context";
16 +
import { type TokContext } from "./context";
17 17
import ParserErrors, { Errors, type ErrorTemplate } from "../parser/error";
18 18
import { SourceLocation } from "../util/location";
19 19
import {
@@ -296,8 +296,7 @@
Loading
296 296
  // properties.
297 297
298 298
  nextToken(): void {
299 -
    const curContext = this.curContext();
300 -
    if (!curContext.preserveSpace) this.skipSpace();
299 +
    this.skipSpace();
301 300
    this.state.start = this.state.pos;
302 301
    if (!this.isLookahead) this.state.startLoc = this.state.curPosition();
303 302
    if (this.state.pos >= this.length) {
@@ -305,11 +304,7 @@
Loading
305 304
      return;
306 305
    }
307 306
308 -
    if (curContext === ct.template) {
309 -
      this.readTmplToken();
310 -
    } else {
311 -
      this.getTokenFromCode(this.codePointAtPos(this.state.pos));
312 -
    }
307 +
    this.getTokenFromCode(this.codePointAtPos(this.state.pos));
313 308
  }
314 309
315 310
  skipBlockComment(): N.CommentBlock | void {
@@ -921,8 +916,7 @@
Loading
921 916
        return;
922 917
923 918
      case charCodes.graveAccent:
924 -
        ++this.state.pos;
925 -
        this.finishToken(tt.backQuote);
919 +
        this.readTemplateToken();
926 920
        return;
927 921
928 922
      case charCodes.digit0: {
@@ -1375,36 +1369,40 @@
Loading
1375 1369
    this.finishToken(tt.string, out);
1376 1370
  }
1377 1371
1378 -
  // Reads template string tokens.
1372 +
  // Reads tempalte continuation `}...`
1373 +
  readTemplateContinuation(): void {
1374 +
    if (!this.match(tt.braceR)) {
1375 +
      this.unexpected(this.state.start, tt.braceR);
1376 +
    }
1377 +
    // rewind pos to `}`
1378 +
    this.state.pos--;
1379 +
    this.readTemplateToken();
1380 +
  }
1379 1381
1380 -
  readTmplToken(): void {
1382 +
  // Reads template string tokens.
1383 +
  readTemplateToken(): void {
1381 1384
    let out = "",
1382 -
      chunkStart = this.state.pos,
1385 +
      chunkStart = this.state.pos, // eat '`' or `}`
1383 1386
      containsInvalid = false;
1387 +
    ++this.state.pos; // eat '`' or `}`
1384 1388
    for (;;) {
1385 1389
      if (this.state.pos >= this.length) {
1386 -
        throw this.raise(this.state.start, Errors.UnterminatedTemplate);
1390 +
        throw this.raise(this.state.start + 1, Errors.UnterminatedTemplate);
1387 1391
      }
1388 1392
      const ch = this.input.charCodeAt(this.state.pos);
1393 +
      if (ch === charCodes.graveAccent) {
1394 +
        ++this.state.pos; // eat '`'
1395 +
        out += this.input.slice(chunkStart, this.state.pos);
1396 +
        this.finishToken(tt.templateTail, containsInvalid ? null : out);
1397 +
        return;
1398 +
      }
1389 1399
      if (
1390 -
        ch === charCodes.graveAccent ||
1391 -
        (ch === charCodes.dollarSign &&
1392 -
          this.input.charCodeAt(this.state.pos + 1) ===
1393 -
            charCodes.leftCurlyBrace)
1400 +
        ch === charCodes.dollarSign &&
1401 +
        this.input.charCodeAt(this.state.pos + 1) === charCodes.leftCurlyBrace
1394 1402
      ) {
1395 -
        if (this.state.pos === this.state.start && this.match(tt.template)) {
1396 -
          if (ch === charCodes.dollarSign) {
1397 -
            this.state.pos += 2;
1398 -
            this.finishToken(tt.dollarBraceL);
1399 -
            return;
1400 -
          } else {
1401 -
            ++this.state.pos;
1402 -
            this.finishToken(tt.backQuote);
1403 -
            return;
1404 -
          }
1405 -
        }
1403 +
        this.state.pos += 2; // eat '${'
1406 1404
        out += this.input.slice(chunkStart, this.state.pos);
1407 -
        this.finishToken(tt.template, containsInvalid ? null : out);
1405 +
        this.finishToken(tt.templateNonTail, containsInvalid ? null : out);
1408 1406
        return;
1409 1407
      }
1410 1408
      if (ch === charCodes.backslash) {
@@ -1633,44 +1631,7 @@
Loading
1633 1631
    }
1634 1632
  }
1635 1633
1636 -
  // the prevType is required by the jsx plugin
1634 +
  // updateContext is used by the jsx plugin
1637 1635
  // eslint-disable-next-line no-unused-vars
1638 -
  updateContext(prevType: TokenType): void {
1639 -
    // Token-specific context update code
1640 -
    // Note that we should avoid accessing `this.prodParam` in context update,
1641 -
    // because it is executed immediately when last token is consumed, which may be
1642 -
    // before `this.prodParam` is updated. e.g.
1643 -
    // ```
1644 -
    // function *g() { () => yield / 2 }
1645 -
    // ```
1646 -
    // When `=>` is eaten, the context update of `yield` is executed, however,
1647 -
    // `this.prodParam` still has `[Yield]` production because it is not yet updated
1648 -
    const { context, type } = this.state;
1649 -
    switch (type) {
1650 -
      case tt.braceR:
1651 -
        context.pop();
1652 -
        break;
1653 -
      // we don't need to update context for tt.braceBarL because we do not pop context for tt.braceBarR
1654 -
      // ideally only dollarBraceL "${" needs a non-template context
1655 -
      // in order to indicate that the last "`" in `${`" starts a new string template
1656 -
      // inside a template element within outer string template.
1657 -
      // but when we popped such context in `}`, we lost track of whether this
1658 -
      // `}` matches a `${` or other tokens matching `}`, so we have to push
1659 -
      // such context in every token that `}` will match.
1660 -
      case tt.braceL:
1661 -
      case tt.braceHashL:
1662 -
      case tt.dollarBraceL:
1663 -
        context.push(ct.brace);
1664 -
        break;
1665 -
      case tt.backQuote:
1666 -
        if (context[context.length - 1] === ct.template) {
1667 -
          context.pop();
1668 -
        } else {
1669 -
          context.push(ct.template);
1670 -
        }
1671 -
        break;
1672 -
      default:
1673 -
        break;
1674 -
    }
1675 -
  }
1636 +
  updateContext(prevType: TokenType): void {}
1676 1637
}

@@ -46,12 +46,6 @@
Loading
46 46
);
47 47
/* eslint-disable sort-keys */
48 48
49 -
// Be aware that this file is always executed and not only when the plugin is enabled.
50 -
// Therefore the contexts do always exist.
51 -
tc.j_oTag = new TokContext("<tag");
52 -
tc.j_cTag = new TokContext("</tag");
53 -
tc.j_expr = new TokContext("<tag>...</tag>", true);
54 -
55 49
function isFragment(object: ?N.JSXElement): boolean {
56 50
  return object
57 51
    ? object.type === "JSXOpeningFragment" ||
@@ -301,8 +295,9 @@
Loading
301 295
      switch (this.state.type) {
302 296
        case tt.braceL:
303 297
          node = this.startNode();
298 +
          this.setContext(tc.brace);
304 299
          this.next();
305 -
          node = this.jsxParseExpressionContainer(node);
300 +
          node = this.jsxParseExpressionContainer(node, tc.j_oTag);
306 301
          if (node.expression.type === "JSXEmptyExpression") {
307 302
            this.raise(node.start, JsxErrors.AttributeIsEmpty);
308 303
          }
@@ -339,6 +334,7 @@
Loading
339 334
    jsxParseSpreadChild(node: N.JSXSpreadChild): N.JSXSpreadChild {
340 335
      this.next(); // ellipsis
341 336
      node.expression = this.parseExpression();
337 +
      this.setContext(tc.j_oTag);
342 338
      this.expect(tt.braceR);
343 339
344 340
      return this.finishNode(node, "JSXSpreadChild");
@@ -348,6 +344,7 @@
Loading
348 344
349 345
    jsxParseExpressionContainer(
350 346
      node: N.JSXExpressionContainer,
347 +
      previousContext: TokContext,
351 348
    ): N.JSXExpressionContainer {
352 349
      if (this.match(tt.braceR)) {
353 350
        node.expression = this.jsxParseEmptyExpression();
@@ -368,6 +365,7 @@
Loading
368 365
369 366
        node.expression = expression;
370 367
      }
368 +
      this.setContext(previousContext);
371 369
      this.expect(tt.braceR);
372 370
373 371
      return this.finishNode(node, "JSXExpressionContainer");
@@ -377,9 +375,12 @@
Loading
377 375
378 376
    jsxParseAttribute(): N.JSXAttribute {
379 377
      const node = this.startNode();
380 -
      if (this.eat(tt.braceL)) {
378 +
      if (this.match(tt.braceL)) {
379 +
        this.setContext(tc.brace);
380 +
        this.next();
381 381
        this.expect(tt.ellipsis);
382 382
        node.argument = this.parseMaybeAssignAllowIn();
383 +
        this.setContext(tc.j_oTag);
383 384
        this.expect(tt.braceR);
384 385
        return this.finishNode(node, "JSXSpreadAttribute");
385 386
      }
@@ -464,11 +465,14 @@
Loading
464 465
465 466
            case tt.braceL: {
466 467
              const node = this.startNode();
468 +
              this.setContext(tc.brace);
467 469
              this.next();
468 470
              if (this.match(tt.ellipsis)) {
469 471
                children.push(this.jsxParseSpreadChild(node));
470 472
              } else {
471 -
                children.push(this.jsxParseExpressionContainer(node));
473 +
                children.push(
474 +
                  this.jsxParseExpressionContainer(node, tc.j_expr),
475 +
                );
472 476
              }
473 477
474 478
              break;
@@ -537,6 +541,11 @@
Loading
537 541
      return this.jsxParseElementAt(startPos, startLoc);
538 542
    }
539 543
544 +
    setContext(newContext: TokContext) {
545 +
      const { context } = this.state;
546 +
      context[context.length - 1] = newContext;
547 +
    }
548 +
540 549
    // ==================================
541 550
    // Overrides
542 551
    // ==================================
@@ -559,6 +568,11 @@
Loading
559 568
      }
560 569
    }
561 570
571 +
    skipSpace() {
572 +
      const curContext = this.curContext();
573 +
      if (!curContext.preserveSpace) super.skipSpace();
574 +
    }
575 +
562 576
    getTokenFromCode(code: number): void {
563 577
      const context = this.curContext();
564 578
@@ -597,7 +611,6 @@
Loading
597 611
    }
598 612
599 613
    updateContext(prevType: TokenType): void {
600 -
      super.updateContext(prevType);
601 614
      const { context, type } = this.state;
602 615
      if (type === tt.slash && prevType === tt.jsxTagStart) {
603 616
        // do not consider JSX expr -> JSX open tag -> ... anymore
@@ -605,17 +618,16 @@
Loading
605 618
        context.splice(-2, 2, tc.j_cTag);
606 619
        this.state.canStartJSXElement = false;
607 620
      } else if (type === tt.jsxTagStart) {
608 -
        context.push(
609 -
          tc.j_expr, // treat as beginning of JSX expression
610 -
          tc.j_oTag, // start opening tag context
611 -
        );
621 +
        // start opening tag context
622 +
        context.push(tc.j_oTag);
612 623
      } else if (type === tt.jsxTagEnd) {
613 -
        const out = context.pop();
624 +
        const out = context[context.length - 1];
614 625
        if ((out === tc.j_oTag && prevType === tt.slash) || out === tc.j_cTag) {
615 626
          context.pop();
616 627
          this.state.canStartJSXElement =
617 628
            context[context.length - 1] === tc.j_expr;
618 629
        } else {
630 +
          this.setContext(tc.j_expr);
619 631
          this.state.canStartJSXElement = true;
620 632
        }
621 633
      } else {
Files Coverage
codemods 100.00%
eslint 94.76%
packages 90.89%
Project Totals (462 files) 91.03%
1
coverage:
2
  parsers:
3
    javascript:
4
      enable_partials: yes
5
  status:
6
    project:
7
      default:
8
        target: "90%"
9
    patch:
10
      enabled: false
11
ignore:
12
  - packages/babel-types/src/*/generated/*
Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading