Skip to content

Commit cccee00

Browse files
Andyhzoo
authored andcommitted
Type-check JSX plugin (babel#496)
* Type-check JSX plugin * Improve test coverage
1 parent 8288f7d commit cccee00

2 files changed

Lines changed: 39 additions & 26 deletions

File tree

src/plugins/jsx/index.js

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1+
// @flow
2+
13
import XHTMLEntities from "./xhtml";
4+
import type Parser from "../../parser";
25
import { TokenType, types as tt } from "../../tokenizer/types";
36
import { TokContext, types as tc } from "../../tokenizer/context";
7+
import * as N from "../../types";
48
import { isIdentifierChar, isIdentifierStart } from "../../util/identifier";
9+
import type { Pos, Position } from "../../util/location";
510
import { isNewLine } from "../../util/whitespace";
611

712
const HEX_NUMBER = /^[\da-fA-F]+$/;
@@ -34,7 +39,7 @@ tt.jsxTagEnd.updateContext = function(prevType) {
3439

3540
// Transforms JSX element name to string.
3641

37-
function getQualifiedJSXName(object) {
42+
function getQualifiedJSXName(object: N.JSXIdentifier | N.JSXNamespacedName | N.JSXMemberExpression): string {
3843
if (object.type === "JSXIdentifier") {
3944
return object.name;
4045
}
@@ -46,12 +51,15 @@ function getQualifiedJSXName(object) {
4651
if (object.type === "JSXMemberExpression") {
4752
return getQualifiedJSXName(object.object) + "." + getQualifiedJSXName(object.property);
4853
}
54+
55+
// istanbul ignore next
56+
throw new Error("Node had unexpected type: " + object.type);
4957
}
5058

51-
export default (superClass) => class extends superClass {
59+
export default (superClass: Class<Parser>): Class<Parser> => class extends superClass {
5260
// Reads inline JSX contents token.
5361

54-
jsxReadToken() {
62+
jsxReadToken(): void {
5563
let out = "";
5664
let chunkStart = this.state.pos;
5765
for (;;) {
@@ -92,7 +100,7 @@ export default (superClass) => class extends superClass {
92100
}
93101
}
94102

95-
jsxReadNewLine(normalizeCRLF) {
103+
jsxReadNewLine(normalizeCRLF: boolean): string {
96104
const ch = this.input.charCodeAt(this.state.pos);
97105
let out;
98106
++this.state.pos;
@@ -108,7 +116,7 @@ export default (superClass) => class extends superClass {
108116
return out;
109117
}
110118

111-
jsxReadString(quote) {
119+
jsxReadString(quote: number): void {
112120
let out = "";
113121
let chunkStart = ++this.state.pos;
114122
for (;;) {
@@ -134,7 +142,7 @@ export default (superClass) => class extends superClass {
134142
return this.finishToken(tt.string, out);
135143
}
136144

137-
jsxReadEntity() {
145+
jsxReadEntity(): string {
138146
let str = "";
139147
let count = 0;
140148
let entity;
@@ -176,7 +184,7 @@ export default (superClass) => class extends superClass {
176184
// Also assumes that first character was already checked
177185
// by isIdentifierStart in readToken.
178186

179-
jsxReadWord() {
187+
jsxReadWord(): void {
180188
let ch;
181189
const start = this.state.pos;
182190
do {
@@ -187,7 +195,7 @@ export default (superClass) => class extends superClass {
187195

188196
// Parse next token as JSX identifier
189197

190-
jsxParseIdentifier() {
198+
jsxParseIdentifier(): N.JSXIdentifier {
191199
const node = this.startNode();
192200
if (this.match(tt.jsxName)) {
193201
node.name = this.state.value;
@@ -202,7 +210,7 @@ export default (superClass) => class extends superClass {
202210

203211
// Parse namespaced identifier.
204212

205-
jsxParseNamespacedName() {
213+
jsxParseNamespacedName(): N.JSXNamespacedName {
206214
const startPos = this.state.start;
207215
const startLoc = this.state.startLoc;
208216
const name = this.jsxParseIdentifier();
@@ -217,7 +225,7 @@ export default (superClass) => class extends superClass {
217225
// Parses element name in any form - namespaced, member
218226
// or single identifier.
219227

220-
jsxParseElementName() {
228+
jsxParseElementName(): N.JSXNamespacedName | N.JSXMemberExpression {
221229
const startPos = this.state.start;
222230
const startLoc = this.state.startLoc;
223231
let node = this.jsxParseNamespacedName();
@@ -232,13 +240,13 @@ export default (superClass) => class extends superClass {
232240

233241
// Parses any type of JSX attribute value.
234242

235-
jsxParseAttributeValue() {
243+
jsxParseAttributeValue(): N.Expression {
236244
let node;
237245
switch (this.state.type) {
238246
case tt.braceL:
239247
node = this.jsxParseExpressionContainer();
240248
if (node.expression.type === "JSXEmptyExpression") {
241-
this.raise(node.start, "JSX attributes must only be assigned a non-empty expression");
249+
throw this.raise(node.start, "JSX attributes must only be assigned a non-empty expression");
242250
} else {
243251
return node;
244252
}
@@ -248,22 +256,22 @@ export default (superClass) => class extends superClass {
248256
return this.parseExprAtom();
249257

250258
default:
251-
this.raise(this.state.start, "JSX value should be either an expression or a quoted JSX text");
259+
throw this.raise(this.state.start, "JSX value should be either an expression or a quoted JSX text");
252260
}
253261
}
254262

255263
// JSXEmptyExpression is unique type since it doesn't actually parse anything,
256264
// and so it should start at the end of last read token (left brace) and finish
257265
// at the beginning of the next one (right brace).
258266

259-
jsxParseEmptyExpression() {
267+
jsxParseEmptyExpression(): N.JSXEmptyExpression {
260268
const node = this.startNodeAt(this.state.lastTokEnd, this.state.lastTokEndLoc);
261269
return this.finishNodeAt(node, "JSXEmptyExpression", this.state.start, this.state.startLoc);
262270
}
263271

264272
// Parse JSX spread child
265273

266-
jsxParseSpreadChild() {
274+
jsxParseSpreadChild(): N.JSXSpreadChild {
267275
const node = this.startNode();
268276
this.expect(tt.braceL);
269277
this.expect(tt.ellipsis);
@@ -276,7 +284,7 @@ export default (superClass) => class extends superClass {
276284
// Parses JSX expression enclosed into curly brackets.
277285

278286

279-
jsxParseExpressionContainer() {
287+
jsxParseExpressionContainer(): N.JSXExpressionContainer {
280288
const node = this.startNode();
281289
this.next();
282290
if (this.match(tt.braceR)) {
@@ -290,7 +298,7 @@ export default (superClass) => class extends superClass {
290298

291299
// Parses following JSX attribute name-value pair.
292300

293-
jsxParseAttribute() {
301+
jsxParseAttribute(): N.JSXAttribute {
294302
const node = this.startNode();
295303
if (this.eat(tt.braceL)) {
296304
this.expect(tt.ellipsis);
@@ -305,7 +313,7 @@ export default (superClass) => class extends superClass {
305313

306314
// Parses JSX opening tag starting after "<".
307315

308-
jsxParseOpeningElementAt(startPos, startLoc) {
316+
jsxParseOpeningElementAt(startPos: number, startLoc: Position): N.JSXOpeningElement {
309317
const node = this.startNodeAt(startPos, startLoc);
310318
node.attributes = [];
311319
node.name = this.jsxParseElementName();
@@ -319,7 +327,7 @@ export default (superClass) => class extends superClass {
319327

320328
// Parses JSX closing tag starting after "</".
321329

322-
jsxParseClosingElementAt(startPos, startLoc) {
330+
jsxParseClosingElementAt(startPos: number, startLoc: Position): N.JSXClosingElement {
323331
const node = this.startNodeAt(startPos, startLoc);
324332
node.name = this.jsxParseElementName();
325333
this.expect(tt.jsxTagEnd);
@@ -329,7 +337,7 @@ export default (superClass) => class extends superClass {
329337
// Parses entire JSX element, including it"s opening tag
330338
// (starting after "<"), attributes, contents and closing tag.
331339

332-
jsxParseElementAt(startPos, startLoc) {
340+
jsxParseElementAt(startPos: number, startLoc: Position): N.JSXElement {
333341
const node = this.startNodeAt(startPos, startLoc);
334342
const children = [];
335343
const openingElement = this.jsxParseOpeningElementAt(startPos, startLoc);
@@ -363,12 +371,14 @@ export default (superClass) => class extends superClass {
363371

364372
// istanbul ignore next - should never happen
365373
default:
366-
this.unexpected();
374+
throw this.unexpected();
367375
}
368376
}
369377

378+
// $FlowIgnore
370379
if (getQualifiedJSXName(closingElement.name) !== getQualifiedJSXName(openingElement.name)) {
371380
this.raise(
381+
// $FlowIgnore
372382
closingElement.start,
373383
"Expected corresponding JSX closing tag for <" + getQualifiedJSXName(openingElement.name) + ">"
374384
);
@@ -386,7 +396,7 @@ export default (superClass) => class extends superClass {
386396

387397
// Parses entire JSX element from current position.
388398

389-
jsxParseElement() {
399+
jsxParseElement(): N.JSXElement {
390400
const startPos = this.state.start;
391401
const startLoc = this.state.startLoc;
392402
this.next();
@@ -397,7 +407,7 @@ export default (superClass) => class extends superClass {
397407
// Overrides
398408
// ==================================
399409

400-
parseExprAtom(refShortHandDefaultPos) {
410+
parseExprAtom(refShortHandDefaultPos: ?Pos): N.Expression {
401411
if (this.match(tt.jsxText)) {
402412
return this.parseLiteral(this.state.value, "JSXText");
403413
} else if (this.match(tt.jsxTagStart)) {
@@ -407,7 +417,7 @@ export default (superClass) => class extends superClass {
407417
}
408418
}
409419

410-
readToken(code) {
420+
readToken(code: number): void {
411421
if (this.state.inPropertyName) return super.readToken(code);
412422

413423
const context = this.curContext();
@@ -439,7 +449,7 @@ export default (superClass) => class extends superClass {
439449
return super.readToken(code);
440450
}
441451

442-
updateContext(prevType) {
452+
updateContext(prevType: TokenType): void {
443453
if (this.match(tt.braceL)) {
444454
const curContext = this.curContext();
445455
if (curContext === tc.j_oTag) {

src/plugins/jsx/xhtml.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
export default {
1+
// @flow
2+
3+
const entities: { [name: string]: string } = {
24
quot: "\u0022",
35
amp: "&",
46
apos: "\u0027",
@@ -253,3 +255,4 @@ export default {
253255
hearts: "\u2665",
254256
diams: "\u2666"
255257
};
258+
export default entities;

0 commit comments

Comments
 (0)