Skip to content

Commit

Permalink
update to use Parser.acorn
Browse files Browse the repository at this point in the history
  • Loading branch information
mysticatea authored and marijnh committed Oct 12, 2019
1 parent 1f01445 commit cee1344
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 36 deletions.
111 changes: 75 additions & 36 deletions index.js
Expand Up @@ -5,40 +5,53 @@ const XHTMLEntities = require('./xhtml');
const hexNumber = /^[\da-fA-F]+$/;
const decimalNumber = /^\d+$/;

const acorn = require("acorn");
const tt = acorn.tokTypes;
const TokContext = acorn.TokContext;
const tokContexts = acorn.tokContexts;
const TokenType = acorn.TokenType;
const isNewLine = acorn.isNewLine;
const isIdentifierStart = acorn.isIdentifierStart;
const isIdentifierChar = acorn.isIdentifierChar;

const tc_oTag = new TokContext('<tag', false);
const tc_cTag = new TokContext('</tag', false);
const tc_expr = new TokContext('<tag>...</tag>', true, true);

const tok = {
jsxName: new TokenType('jsxName'),
jsxText: new TokenType('jsxText', {beforeExpr: true}),
jsxTagStart: new TokenType('jsxTagStart'),
jsxTagEnd: new TokenType('jsxTagEnd')
}
// 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'),
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;
}
};

tok.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;
};
tok.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.

Expand All @@ -64,12 +77,38 @@ module.exports = function(options) {
allowNamespaces: options.allowNamespaces !== false,
allowNamespacedObjects: !!options.allowNamespacedObjects
}, Parser);
}
};
};
module.exports.tokTypes = tok;

// 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;
Expand Down Expand Up @@ -419,15 +458,15 @@ function plugin(options, Parser) {
++this.pos;
return this.finishToken(tok.jsxTagStart);
}
return super.readToken(code)
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)
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
Expand Down
1 change: 1 addition & 0 deletions test/run.js
@@ -1,5 +1,6 @@
var driver = require("./driver.js");
require("./tests-jsx.js");
require("./tests-misc.js");

function group(name) {
if (typeof console === "object" && console.group) {
Expand Down
50 changes: 50 additions & 0 deletions test/tests-misc.js
@@ -0,0 +1,50 @@
"use strict";

if (typeof exports !== "undefined") {
var assert = require("assert");
var acorn = require("acorn");
var jsx = require("..");
var testAssert = require("./driver.js").testAssert;
}

testAssert("// the enhanced Parser instance should have a static property 'acornJsx'.", function() {
const JsxParser = acorn.Parser.extend(jsx());
assert(JsxParser.acornJsx);
});

testAssert("// 'acornJsx' should be the same instance for the same acorn.", function() {
const JsxParser1 = acorn.Parser.extend(jsx());
const JsxParser2 = acorn.Parser.extend(jsx());
assert.strictEqual(JsxParser1.acornJsx, JsxParser2.acornJsx);
});

testAssert("// the static property 'acornJsx' should have two properties.", function() {
const JsxParser = acorn.Parser.extend(jsx());
assert(JsxParser.acornJsx.tokTypes, "should have 'tokTypes'.");
assert(JsxParser.acornJsx.tokContexts, "should have 'tokContexts'.");
});

testAssert("// 'acornJsx.tokTypes' should be used.", function() {
const JsxParser = acorn.Parser.extend(jsx());
const code = '<a>{/* foo */}</a>';
const expectedTokTypes = [
JsxParser.acornJsx.tokTypes.jsxTagStart,
JsxParser.acornJsx.tokTypes.jsxName,
JsxParser.acornJsx.tokTypes.jsxTagEnd,
acorn.tokTypes.braceL,
acorn.tokTypes.braceR,
JsxParser.acornJsx.tokTypes.jsxTagStart,
acorn.tokTypes.slash,
JsxParser.acornJsx.tokTypes.jsxName,
JsxParser.acornJsx.tokTypes.jsxTagEnd,
acorn.tokTypes.eof
]
const actualTokens = []

JsxParser.parse(code, {onToken: actualTokens})

assert.strictEqual(actualTokens.length, expectedTokTypes.length);
for (let i = 0; i < actualTokens.length; ++i) {
assert.strictEqual(actualTokens[i].type, expectedTokTypes[i]);
}
});

0 comments on commit cee1344

Please sign in to comment.