Skip to content

Commit

Permalink
Update: support generators in no constant condition (Fixes #8566)
Browse files Browse the repository at this point in the history
  • Loading branch information
not-an-aardvark authored and VictorHom committed Jul 15, 2017
1 parent 0e90453 commit 7bcd3cc
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 33 deletions.
40 changes: 16 additions & 24 deletions lib/rules/indent.js
Expand Up @@ -895,27 +895,6 @@ module.exports = {
}
}

/**
* Adds indentation for the right-hand side of binary/logical expressions.
* @param {ASTNode} node A BinaryExpression or LogicalExpression node
* @returns {void}
*/
function addBinaryOrLogicalExpressionIndent(node) {
const operator = sourceCode.getFirstTokenBetween(node.left, node.right, token => token.value === node.operator);

/*
* For backwards compatibility, don't check BinaryExpression indents, e.g.
* var foo = bar &&
* baz;
*/

const tokenAfterOperator = sourceCode.getTokenAfter(operator);

offsets.ignoreToken(operator);
offsets.ignoreToken(tokenAfterOperator);
offsets.setDesiredOffsets([operator.range[1], node.range[1]], tokenAfterOperator, 1);
}

/**
* Checks the indentation for nodes that are like function calls (`CallExpression` and `NewExpression`)
* @param {ASTNode} node A CallExpression or NewExpression node
Expand Down Expand Up @@ -1077,7 +1056,22 @@ module.exports = {
offsets.ignoreToken(sourceCode.getTokenAfter(operator));
},

BinaryExpression: addBinaryOrLogicalExpressionIndent,
"BinaryExpression, LogicalExpression"(node) {
const operator = sourceCode.getFirstTokenBetween(node.left, node.right, token => token.value === node.operator);

/*
* For backwards compatibility, don't check BinaryExpression indents, e.g.
* var foo = bar &&
* baz;
*/

const tokenAfterOperator = sourceCode.getTokenAfter(operator);

offsets.ignoreToken(operator);
offsets.ignoreToken(tokenAfterOperator);
offsets.setDesiredOffset(tokenAfterOperator, operator, 0);
offsets.setDesiredOffsets([tokenAfterOperator.range[1], node.range[1]], tokenAfterOperator, 1);
},

BlockStatement: addBlockIndent,

Expand Down Expand Up @@ -1211,8 +1205,6 @@ module.exports = {
}
},

LogicalExpression: addBinaryOrLogicalExpressionIndent,

"MemberExpression, JSXMemberExpression"(node) {
const firstNonObjectToken = sourceCode.getFirstTokenBetween(node.object, node.property, astUtils.isNotClosingParenToken);
const secondNonObjectToken = sourceCode.getTokenAfter(firstNonObjectToken);
Expand Down
68 changes: 62 additions & 6 deletions lib/rules/no-constant-condition.js
Expand Up @@ -33,7 +33,10 @@ module.exports = {

create(context) {
const options = context.options[0] || {},
checkLoops = options.checkLoops !== false;
checkLoops = options.checkLoops !== false,
loopSetStack = [];

let loopsInCurrentScope = new Set();

//--------------------------------------------------------------------------
// Helpers
Expand Down Expand Up @@ -114,18 +117,64 @@ module.exports = {
return false;
}

/**
* Tracks when the given node contains a constant condition.
* @param {ASTNode} node The AST node to check.
* @returns {void}
* @private
*/
function trackConstantConditionLoop(node) {
if (node.test && isConstant(node.test, true)) {
loopsInCurrentScope.add(node);
}
}

/**
* Reports when the set contains the given constant condition node
* @param {ASTNode} node The AST node to check.
* @returns {void}
* @private
*/
function checkConstantConditionLoopInSet(node) {
if (loopsInCurrentScope.has(node)) {
loopsInCurrentScope.delete(node);
context.report({ node, message: "Unexpected constant condition." });
}
}

/**
* Reports when the given node contains a constant condition.
* @param {ASTNode} node The AST node to check.
* @returns {void}
* @private
*/
function checkConstantCondition(node) {
function reportIfConstant(node) {
if (node.test && isConstant(node.test, true)) {
context.report({ node, message: "Unexpected constant condition." });
}
}

/**
* Stores current set of constant loops in loopSetStack temporarily
* and uses a new set to track constant loops
* @returns {void}
* @private
*/
function enterFunction() {
loopSetStack.push(loopsInCurrentScope);
loopsInCurrentScope = new Set();
}

/**
* Reports when the set still contains stored constant conditions
* @param {ASTNode} node The AST node to check.
* @returns {void}
* @private
*/
function exitFunction() {
loopsInCurrentScope = loopSetStack.pop();
}

/**
* Checks node when checkLoops option is enabled
* @param {ASTNode} node The AST node to check.
Expand All @@ -134,7 +183,7 @@ module.exports = {
*/
function checkLoop(node) {
if (checkLoops) {
checkConstantCondition(node);
trackConstantConditionLoop(node);
}
}

Expand All @@ -143,11 +192,18 @@ module.exports = {
//--------------------------------------------------------------------------

return {
ConditionalExpression: checkConstantCondition,
IfStatement: checkConstantCondition,
ConditionalExpression: reportIfConstant,
IfStatement: reportIfConstant,
WhileStatement: checkLoop,
"WhileStatement:exit": checkConstantConditionLoopInSet,
DoWhileStatement: checkLoop,
ForStatement: checkLoop
"DoWhileStatement:exit": checkConstantConditionLoopInSet,
ForStatement: checkLoop,
"ForStatement > .init": node => checkLoop(node.parent),
"ForStatement:exit": checkConstantConditionLoopInSet,
FunctionDeclaration: enterFunction,
"FunctionDeclaration:exit": exitFunction,
YieldExpression: () => loopsInCurrentScope.clear()
};

}
Expand Down
31 changes: 31 additions & 0 deletions tests/lib/rules/indent.js
Expand Up @@ -2751,6 +2751,22 @@ ruleTester.run("indent", rule, {
)
`
},
{
code: unIndent`
foo
|| (
bar
)
`
},
{
code: unIndent`
foo
|| (
bar
)
`
},
{
code: unIndent`
var foo =
Expand Down Expand Up @@ -7424,6 +7440,21 @@ ruleTester.run("indent", rule, {
`,
errors: expectedErrors([3, 0, 4, "Punctuator"])
},
{
code: unIndent`
foo
|| (
bar
)
`,
output: unIndent`
foo
|| (
bar
)
`,
errors: expectedErrors([[3, 12, 16, "Identifier"], [4, 8, 12, "Punctuator"]])
},
{
code: unIndent`
1
Expand Down
48 changes: 45 additions & 3 deletions tests/lib/rules/no-constant-condition.js
Expand Up @@ -16,7 +16,7 @@ const rule = require("../../../lib/rules/no-constant-condition"),
// Tests
//------------------------------------------------------------------------------

const ruleTester = new RuleTester();
const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });

ruleTester.run("no-constant-condition", rule, {
valid: [
Expand Down Expand Up @@ -58,7 +58,15 @@ ruleTester.run("no-constant-condition", rule, {
// { checkLoops: false }
{ code: "while(true);", options: [{ checkLoops: false }] },
{ code: "for(;true;);", options: [{ checkLoops: false }] },
{ code: "do{}while(true)", options: [{ checkLoops: false }] }
{ code: "do{}while(true)", options: [{ checkLoops: false }] },

{ code: "function* foo(){while(true){yield 'foo';}}", parserOptions: { ecmaVersion: 6 } },
{ code: "function* foo(){for(;true;){yield 'foo';}}", parserOptions: { ecmaVersion: 6 } },
{ code: "function* foo(){do{yield 'foo';}while(true)}", parserOptions: { ecmaVersion: 6 } },
{ code: "function* foo(){while (true) { while(true) {yield;}}}", parserOptions: { ecmaVersion: 6 } },
{ code: "function* foo() {for (; yield; ) {}}", parserOptions: { ecmaVersion: 6 } },
{ code: "function* foo() {for (; ; yield) {}}", parserOptions: { ecmaVersion: 6 } },
{ code: "function* foo() {while (true) {function* foo() {yield;}yield;}}", parserOptions: { ecmaVersion: 6 } }
],
invalid: [
{ code: "for(;true;);", errors: [{ message: "Unexpected constant condition.", type: "ForStatement" }] },
Expand All @@ -78,6 +86,7 @@ ruleTester.run("no-constant-condition", rule, {
{ code: "while(~!0);", errors: [{ message: "Unexpected constant condition.", type: "WhileStatement" }] },
{ code: "while(x = 1);", errors: [{ message: "Unexpected constant condition.", type: "WhileStatement" }] },
{ code: "while(function(){});", errors: [{ message: "Unexpected constant condition.", type: "WhileStatement" }] },
{ code: "while(true);", errors: [{ message: "Unexpected constant condition.", type: "WhileStatement" }] },
{ code: "while(() => {});", parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Unexpected constant condition.", type: "WhileStatement" }] },

// #5228 , typeof conditions
Expand All @@ -103,6 +112,39 @@ ruleTester.run("no-constant-condition", rule, {
{ code: "if(abc==='str' || true){}", errors: [{ message: "Unexpected constant condition.", type: "IfStatement" }] },
{ code: "if(abc==='str' || true || def ==='str'){}", errors: [{ message: "Unexpected constant condition.", type: "IfStatement" }] },
{ code: "if(false || true){}", errors: [{ message: "Unexpected constant condition.", type: "IfStatement" }] },
{ code: "if(typeof abc==='str' || true){}", errors: [{ message: "Unexpected constant condition.", type: "IfStatement" }] }
{ code: "if(typeof abc==='str' || true){}", errors: [{ message: "Unexpected constant condition.", type: "IfStatement" }] },

{
code: "function* foo(){while(true){} yield 'foo';}",
errors: [{ message: "Unexpected constant condition.", type: "WhileStatement" }]
},
{
code: "function* foo(){while(true){if (true) {yield 'foo';}}}",
errors: [{ message: "Unexpected constant condition.", type: "IfStatement" }]
},
{
code: "function* foo(){while(true){yield 'foo';} while(true) {}}",
errors: [{ message: "Unexpected constant condition.", type: "WhileStatement" }]
},
{
code: "var a = function* foo(){while(true){} yield 'foo';}",
errors: [{ message: "Unexpected constant condition.", type: "WhileStatement" }]
},
{
code: "while (true) { function* foo() {yield;}}",
errors: [{ message: "Unexpected constant condition.", type: "WhileStatement" }]
},
{
code: "function* foo(){if (true) {yield 'foo';}}",
errors: [{ message: "Unexpected constant condition.", type: "IfStatement" }]
},
{
code: "function* foo() {for (let foo = yield; ;) {}}",
errors: [{ message: "Unexpected constant condition.", type: "ForStatement" }]
},
{
code: "function foo() {while (true) {function* bar() {while (true) {yield;}}}}",
errors: [{ message: "Unexpected constant condition.", type: "WhileStatement" }]
}
]
});

0 comments on commit 7bcd3cc

Please sign in to comment.