diff --git a/eslint/babel-eslint-parser/acorn-to-esprima.js b/eslint/babel-eslint-parser/acorn-to-esprima.js
index 756b82e974..b562711bcc 100644
--- a/eslint/babel-eslint-parser/acorn-to-esprima.js
+++ b/eslint/babel-eslint-parser/acorn-to-esprima.js
@@ -58,10 +58,82 @@ exports.toAST = function (ast) {
traverse(ast, astTransformVisitor);
};
+exports.toTokens = function (tokens) {
+ // transform tokens to type "Template"
+ convertTemplateType(tokens);
+
+ return tokens.map(exports.toToken);
+};
+
function isCompatTag(tagName) {
return tagName && /^[a-z]|\-/.test(tagName);
}
+function convertTemplateType(tokens) {
+ var startingToken = 0;
+ var currentToken = 0;
+ var numBraces = 0;
+
+ function isTemplateStarter(token) {
+ return tokens[token].type === tt.backQuote ||
+ tokens[token].type === tt.braceR;
+ }
+
+ function isTemplateEnder(token) {
+ return tokens[token].type === tt.dollarBraceL ||
+ tokens[token].type === tt.backQuote;
+ }
+
+ function createTemplateValue(start, end) {
+ var value = "";
+ while (start <= end) {
+ if (tokens[start].value) {
+ value += tokens[start].value;
+ } else if (tokens[start].type !== tt.template) {
+ value += tokens[start].type.label;
+ }
+ start++;
+ }
+ return value;
+ }
+
+ function replaceWithTemplateType(start, end) {
+ var templateToken = {
+ type: 'Template',
+ value: createTemplateValue(start, end),
+ range: [tokens[start].start, tokens[end].end],
+ loc: {
+ start: tokens[start].loc.start,
+ end: tokens[end].loc.end
+ }
+ }
+ tokens.splice(start, end - start + 1, templateToken);
+ }
+
+ function checkNumBraces(token) {
+ if (tokens[token].type === tt.braceL) {
+ numBraces++;
+ } else if (tokens[token].type === tt.braceR) {
+ numBraces--;
+ }
+ }
+
+ while (startingToken < tokens.length) {
+ if (isTemplateStarter(startingToken) && numBraces === 0) {
+ currentToken = startingToken + 1;
+ while (currentToken < tokens.length - 1 && !isTemplateEnder(currentToken)) {
+ checkNumBraces(currentToken);
+ currentToken++;
+ }
+ replaceWithTemplateType(startingToken, currentToken);
+ startingToken++;
+ } else {
+ checkNumBraces(startingToken);
+ startingToken++;
+ }
+ }
+}
+
var astTransformVisitor = {
noScope: true,
exit: function (node, parent) {
@@ -117,5 +189,24 @@ var astTransformVisitor = {
node.delegate = node.all;
delete node.all;
}
+
+ // template strings
+
+ if (t.isTemplateLiteral(node)) {
+ node.quasis.forEach(function (q) {
+ q.range[0] -= 1;
+ if (q.tail) {
+ q.range[1] += 1;
+ } else {
+ q.range[1] += 2;
+ }
+ q.loc.start.column -= 1;
+ if (q.tail) {
+ q.loc.end.column += 1;
+ } else {
+ q.loc.end.column += 2;
+ }
+ });
+ }
}
};
diff --git a/eslint/babel-eslint-parser/index.js b/eslint/babel-eslint-parser/index.js
index c5426b672d..2002b68fd9 100644
--- a/eslint/babel-eslint-parser/index.js
+++ b/eslint/babel-eslint-parser/index.js
@@ -56,7 +56,7 @@ function monkeypatch() {
};
}
-exports.attachComments = function(ast, comments, tokens) {
+exports.attachComments = function (ast, comments, tokens) {
estraverse.attachComments(ast, comments, tokens);
if (comments.length) {
@@ -82,7 +82,7 @@ exports.attachComments = function(ast, comments, tokens) {
node.leadingComments = [];
var firstTokenStart = token.range[0];
var len = comments.length;
- for(var i = 0; i < len && comments[i].start < firstTokenStart; i++ ) {
+ for (var i = 0; i < len && comments[i].start < firstTokenStart; i++) {
node.leadingComments.push(comments[i]);
}
}
@@ -138,7 +138,7 @@ exports.parse = function (code) {
tokens.pop();
// convert tokens
- ast.tokens = tokens.map(acornToEsprima.toToken);
+ ast.tokens = acornToEsprima.toTokens(tokens);
// add comments
ast.comments = comments;
diff --git a/eslint/babel-eslint-parser/test/babel-eslint.js b/eslint/babel-eslint-parser/test/babel-eslint.js
index 7c7c45c5b2..123745f516 100644
--- a/eslint/babel-eslint-parser/test/babel-eslint.js
+++ b/eslint/babel-eslint-parser/test/babel-eslint.js
@@ -20,7 +20,7 @@ function assertImplementsAST(target, source, path) {
error("have different types (" + typeA + " !== " + typeB + ")");
} else if (typeA === "object") {
var keysTarget = Object.keys(target);
- for(var i in keysTarget) {
+ for (var i in keysTarget) {
var key = keysTarget[i];
path.push(key);
assertImplementsAST(target[key], source[key], path);
@@ -34,6 +34,7 @@ function assertImplementsAST(target, source, path) {
function parseAndAssertSame(code) {
var esAST = espree.parse(code, {
ecmaFeatures: {
+ templateStrings: true,
modules: true,
classes: true,
jsx: true
@@ -50,14 +51,60 @@ function parseAndAssertSame(code) {
} catch(err) {
err.message +=
"\nespree:\n" +
- util.inspect(esAST, {depth: err.depth}) +
+ util.inspect(esAST, {depth: err.depth, colors: true}) +
"\nbabel-eslint:\n" +
- util.inspect(acornAST, {depth: err.depth});
+ util.inspect(acornAST, {depth: err.depth, colors: true});
throw err;
}
}
describe("acorn-to-esprima", function () {
+ describe("templates", function () {
+ it("empty template string", function () {
+ parseAndAssertSame("``");
+ });
+
+ it("template string", function () {
+ parseAndAssertSame("`test`");
+ });
+
+ it("template string using $", function () {
+ parseAndAssertSame("`$`");
+ });
+
+ it("template string with expression", function () {
+ parseAndAssertSame("`${a}`");
+ });
+
+ it("template string with multiple expressions", function () {
+ parseAndAssertSame("`${a}${b}${c}`");
+ });
+
+ it("template string with expression and strings", function () {
+ parseAndAssertSame("`a${a}a`");
+ });
+
+ it("template string with binary expression", function () {
+ parseAndAssertSame("`a${a + b}a`");
+ });
+
+ it("tagged template", function () {
+ parseAndAssertSame("jsx``");
+ });
+
+ it("tagged template with expression", function () {
+ parseAndAssertSame("jsx``");
+ });
+
+ it("tagged template with new operator", function () {
+ parseAndAssertSame("new raw`42`");
+ });
+
+ it("template with nested function/object", function () {
+ parseAndAssertSame("`outer${{x: {y: 10}}}bar${`nested${function(){return 1;}}endnest`}end`");
+ });
+ });
+
it("simple expression", function () {
parseAndAssertSame("a = 1");
});
diff --git a/eslint/babel-eslint-parser/test/non-regression.js b/eslint/babel-eslint-parser/test/non-regression.js
index 3e4b3d9bf3..1ce55b0577 100644
--- a/eslint/babel-eslint-parser/test/non-regression.js
+++ b/eslint/babel-eslint-parser/test/non-regression.js
@@ -139,4 +139,14 @@ describe("verify", function () {
[]
);
});
+
+ it("template strings #31", function () {
+ verifyAndAssertMessages(
+ "console.log(`${a}, b`);",
+ { "comma-spacing": 1 },
+ []
+ );
+ });
+
+
});