Merge pull request babel/babel-eslint#94 from hzoo/support-templates

support template strings - Fixes babel/babel-eslint#31
This commit is contained in:
Sebastian McKenzie 2015-05-14 21:38:56 +01:00
parent 527287aa29
commit d84fc559f2
4 changed files with 154 additions and 6 deletions

View File

@ -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;
}
});
}
}
};

View File

@ -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;

View File

@ -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`<Button>Click</Button>`");
});
it("tagged template with expression", function () {
parseAndAssertSame("jsx`<Button>Hi ${name}</Button>`");
});
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");
});

View File

@ -139,4 +139,14 @@ describe("verify", function () {
[]
);
});
it("template strings #31", function () {
verifyAndAssertMessages(
"console.log(`${a}, b`);",
{ "comma-spacing": 1 },
[]
);
});
});