Skip to content

Commit

Permalink
[[FIX]] Handle multi-line tokens after return or yield
Browse files Browse the repository at this point in the history
This is a slight alteration of #2142 to cleanly rebase atop the previous diffs adding context, allowing it to work with nested templates.

Closes #1814
Closes #2142
  • Loading branch information
leebyron authored and caitp committed Feb 6, 2015
1 parent 20ff670 commit 5c9c7fd
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 25 deletions.
30 changes: 17 additions & 13 deletions src/jshint.js
Original file line number Diff line number Diff line change
Expand Up @@ -826,7 +826,7 @@ var JSHINT = (function() {
return true;
}
if (isInfix(next) === isInfix(curr) || (curr.id === "yield" && state.option.inMoz(true))) {
return curr.line !== next.line;
return curr.line !== startLine(next);
}
return false;
}
Expand Down Expand Up @@ -877,7 +877,7 @@ var JSHINT = (function() {

var isDangerous =
state.option.asi &&
state.tokens.prev.line < state.tokens.curr.line &&
state.tokens.prev.line !== startLine(state.tokens.curr) &&
_.contains(["]", ")"], state.tokens.prev.id) &&
_.contains(["[", "("], state.tokens.curr.id);

Expand Down Expand Up @@ -951,23 +951,27 @@ var JSHINT = (function() {

// Functions for conformance of style.

function startLine(token) {
return token.startLine || token.line;
}

function nobreaknonadjacent(left, right) {
left = left || state.tokens.curr;
right = right || state.tokens.next;
if (!state.option.laxbreak && left.line !== right.line) {
if (!state.option.laxbreak && left.line !== startLine(right)) {
warning("W014", right, right.value);
}
}

function nolinebreak(t) {
t = t || state.tokens.curr;
if (t.line !== state.tokens.next.line) {
if (t.line !== startLine(state.tokens.next)) {
warning("E022", t, t.value);
}
}

function nobreakcomma(left, right) {
if (left.line !== right.line) {
if (left.line !== startLine(right)) {
if (!state.option.laxcomma) {
if (comma.first) {
warning("I001");
Expand Down Expand Up @@ -1488,7 +1492,7 @@ var JSHINT = (function() {
// the same line *and* option lastsemic is on, ignore the warning.
// Otherwise, complain about missing semicolon.
if (!state.option.lastsemic || state.tokens.next.id !== "}" ||
state.tokens.next.line !== state.tokens.curr.line) {
startLine(state.tokens.next) !== state.tokens.curr.line) {
warningAt("W033", state.tokens.curr.line, state.tokens.curr.character);
}
}
Expand Down Expand Up @@ -1981,7 +1985,7 @@ var JSHINT = (function() {
type: "(template)",
lbp: 0,
identifier: false,
fud: doTemplateLiteral
nud: doTemplateLiteral
};

type("(template middle)", function() {
Expand Down Expand Up @@ -2632,7 +2636,7 @@ var JSHINT = (function() {
} else if (blocktype.isDestAssign && !state.option.inESNext()) {
warning("W104", state.tokens.curr, "destructuring assignment");
}
var b = state.tokens.curr.line !== state.tokens.next.line;
var b = state.tokens.curr.line !== startLine(state.tokens.next);
this.first = [];
if (b) {
indent += state.option.indent;
Expand Down Expand Up @@ -3042,7 +3046,7 @@ var JSHINT = (function() {
var b, f, i, p, t, g, nextVal;
var props = {}; // All properties, including accessors

b = state.tokens.curr.line !== state.tokens.next.line;
b = state.tokens.curr.line !== startLine(state.tokens.next);
if (b) {
indent += state.option.indent;
if (state.tokens.next.from === indent + state.option.indent) {
Expand Down Expand Up @@ -4102,7 +4106,7 @@ var JSHINT = (function() {
nolinebreak(this);

if (state.tokens.next.id !== ";" && !state.tokens.next.reach) {
if (state.tokens.curr.line === state.tokens.next.line) {
if (state.tokens.curr.line === startLine(state.tokens.next)) {
if (funct[v] !== "label") {
warning("W090", state.tokens.next, v);
} else if (scope[v] !== funct) {
Expand All @@ -4129,7 +4133,7 @@ var JSHINT = (function() {
nolinebreak(this);

if (state.tokens.next.id !== ";" && !state.tokens.next.reach) {
if (state.tokens.curr.line === state.tokens.next.line) {
if (state.tokens.curr.line === startLine(state.tokens.next)) {
if (funct[v] !== "label") {
warning("W090", state.tokens.next, v);
} else if (scope[v] !== funct) {
Expand All @@ -4149,7 +4153,7 @@ var JSHINT = (function() {


stmt("return", function() {
if (this.line === state.tokens.next.line) {
if (this.line === startLine(state.tokens.next)) {
if (state.tokens.next.id !== ";" && !state.tokens.next.reach) {
this.first = expression(0);

Expand Down Expand Up @@ -4192,7 +4196,7 @@ var JSHINT = (function() {
advance("*");
}

if (this.line === state.tokens.next.line || !state.option.inMoz(true)) {
if (this.line === startLine(state.tokens.next) || !state.option.inMoz(true)) {
if (delegatingYield ||
(state.tokens.next.id !== ";" && !state.tokens.next.reach && state.tokens.next.nud)) {

Expand Down
49 changes: 37 additions & 12 deletions src/lex.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,7 @@ function Lexer(source) {
this.input = "";
this.inComment = false;
this.context = [];
this.templateLine = null;
this.templateChar = null;
this.templateStarts = [];

for (var i = 0; i < state.option.indent; i += 1) {
state.tab += " ";
Expand Down Expand Up @@ -1004,15 +1003,16 @@ Lexer.prototype = {
var tokenType;
var value = "";
var ch;
var startLine = this.line;
var startChar = this.char;

if (!state.option.esnext) {
// Only lex template strings in ESNext mode.
return null;
} else if (this.peek() === "`") {
// Template must start with a backtick.
tokenType = Token.TemplateHead;
this.templateLine = this.line;
this.templateChar = this.char;
this.templateStarts.push({ line: this.line, char: this.char });
this.skip(1);
this.context.push(Context.Template);
} else if (this.inContext(Context.Template) && this.peek() === "}") {
Expand All @@ -1027,16 +1027,19 @@ Lexer.prototype = {
while ((ch = this.peek()) === "") {
value += "\n";
if (!this.nextLine()) {
// Unclosed template literal --- point to the starting line, or the EOF?
// Unclosed template literal --- point to the starting "`"
this.context.pop();
var startPos = this.templateStarts.pop();
this.trigger("error", {
code: "E052",
line: this.templateLine,
character: this.templateChar
line: startPos.line,
character: startPos.char
});
return {
type: tokenType,
value: value,
startLine: startLine,
startChar: startChar,
isUnclosed: true
};
}
Expand All @@ -1048,6 +1051,8 @@ Lexer.prototype = {
return {
type: tokenType,
value: value,
startLine: startLine,
startChar: startChar,
isUnclosed: false
};
} else if (ch === '\\') {
Expand All @@ -1065,10 +1070,13 @@ Lexer.prototype = {
tokenType = tokenType === Token.TemplateHead ? Token.NoSubstTemplate : Token.TemplateTail;
this.skip(1);
this.context.pop();
this.templateStarts.pop();

return {
type: tokenType,
value: value,
startLine: startLine,
startChar: startChar,
isUnclosed: false,
quote: "`"
};
Expand Down Expand Up @@ -1156,6 +1164,8 @@ Lexer.prototype = {
return {
type: Token.StringLiteral,
value: value,
startLine: startLine,
startChar: startChar,
isUnclosed: true,
quote: quote
};
Expand Down Expand Up @@ -1193,6 +1203,8 @@ Lexer.prototype = {
return {
type: Token.StringLiteral,
value: value,
startLine: startLine,
startChar: startChar,
isUnclosed: false,
quote: quote
};
Expand Down Expand Up @@ -1597,6 +1609,9 @@ Lexer.prototype = {
obj.character = this.char;
obj.from = this.from;
if (obj.identifier && token) obj.raw_text = token.text || token.value;
if (token && token.startLine && token.startLine !== this.line) {
obj.startLine = token.startLine;
}

if (isProperty && obj.identifier) {
obj.isProperty = isProperty;
Expand Down Expand Up @@ -1636,47 +1651,57 @@ Lexer.prototype = {
line: this.line,
char: this.char,
from: this.from,
startLine: token.startLine,
startChar: token.startChar,
value: token.value,
quote: token.quote
}, checks, function() { return true; });

return create("(string)", token.value);
return create("(string)", token.value, null, token);

case Token.TemplateHead:
this.trigger("TemplateHead", {
line: this.line,
char: this.char,
from: this.from,
startLine: token.startLine,
startChar: token.startChar,
value: token.value
});
return create("(template)", token.value);
return create("(template)", token.value, null, token);

case Token.TemplateMiddle:
this.trigger("TemplateMiddle", {
line: this.line,
char: this.char,
from: this.from,
startLine: token.startLine,
startChar: token.startChar,
value: token.value
});
return create("(template middle)", token.value);
return create("(template middle)", token.value, null, token);

case Token.TemplateTail:
this.trigger("TemplateTail", {
line: this.line,
char: this.char,
from: this.from,
startLine: token.startLine,
startChar: token.startChar,
value: token.value
});
return create("(template tail)", token.value);
return create("(template tail)", token.value, null, token);

case Token.NoSubstTemplate:
this.trigger("NoSubstTemplate", {
line: this.line,
char: this.char,
from: this.from,
startLine: token.startLine,
startChar: token.startChar,
value: token.value
});
return create("(no subst template)", token.value);
return create("(no subst template)", token.value, null, token);

case Token.Identifier:
this.trigger("Identifier", {
Expand Down
70 changes: 70 additions & 0 deletions tests/unit/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -915,6 +915,76 @@ exports.testES6TemplateLiteralsAreNotDirectives = function (test) {
test.done();
};

exports.testES6TemplateLiteralReturnValue = function (test) {
var src = [
'function sayHello(to) {',
' return `Hello, ${to}!`;',
'}',
'print(sayHello("George"));'
];

TestRun(test).test(src, { esnext: true });

var src = [
'function* sayHello(to) {',
' yield `Hello, ${to}!`;',
'}',
'print(sayHello("George"));'
];

TestRun(test).test(src, { esnext: true });

test.done();
};

exports.testES6TemplateLiteralMultilineReturnValue = function (test) {
var src = [
'function sayHello(to) {',
' return `Hello, ',
' ${to}!`;',
'}',
'print(sayHello("George"));'
];

TestRun(test).test(src, { esnext: true });

var src = [
'function* sayHello(to) {',
' yield `Hello, ',
' ${to}!`;',
'}',
'print(sayHello("George"));'
];

TestRun(test).test(src, { esnext: true });

test.done();
};

exports.tesMultilineReturnValueStringLiteral = function (test) {
var src = [
'function sayHello(to) {',
' return "Hello, \\',
' " + to;',
'}',
'print(sayHello("George"));'
];

TestRun(test).test(src, { multistr: true });

var src = [
'function* sayHello(to) {',
' yield "Hello, \\',
' " + to;',
'}',
'print(sayHello("George"));'
];

TestRun(test).test(src, { esnext: true, multistr: true });

test.done();
};

exports.testES6ExportStarFrom = function (test) {
var src = fs.readFileSync(__dirname + "/fixtures/es6-export-star-from.js", "utf8");
TestRun(test)
Expand Down

0 comments on commit 5c9c7fd

Please sign in to comment.