diff --git a/lib/rules/keyword-spacing.js b/lib/rules/keyword-spacing.js index d0dd640255b..89b97332587 100644 --- a/lib/rules/keyword-spacing.js +++ b/lib/rules/keyword-spacing.js @@ -436,7 +436,12 @@ module.exports = { * @returns {void} */ function checkSpacingForForOfStatement(node) { - checkSpacingAroundFirstToken(node); + if (node.await) { + checkSpacingBefore(sourceCode.getFirstToken(node, 0)); + checkSpacingAfter(sourceCode.getFirstToken(node, 1)); + } else { + checkSpacingAroundFirstToken(node); + } checkSpacingAround(sourceCode.getTokenBefore(node.right, astUtils.isNotOpeningParenToken)); } diff --git a/lib/rules/no-await-in-loop.js b/lib/rules/no-await-in-loop.js index 7a9427e6abf..d0a2a58935b 100644 --- a/lib/rules/no-await-in-loop.js +++ b/lib/rules/no-await-in-loop.js @@ -4,24 +4,54 @@ */ "use strict"; -// Node types which are considered loops. -const loopTypes = new Set([ - "ForStatement", - "ForOfStatement", - "ForInStatement", - "WhileStatement", - "DoWhileStatement" -]); +/** + * Check whether it should stop traversing ancestors at the given node. + * @param {ASTNode} node A node to check. + * @returns {boolean} `true` if it should stop traversing. + */ +function isBoundary(node) { + const t = node.type; + + return ( + t === "FunctionDeclaration" || + t === "FunctionExpression" || + t === "ArrowFunctionExpression" || -/* - * Node types at which we should stop looking for loops. For example, it is fine to declare an async - * function within a loop, and use await inside of that. + /* + * Don't report the await expressions on for-await-of loop since it's + * asynchronous iteration intentionally. + */ + (t === "ForOfStatement" && node.await === true) + ); +} + +/** + * Check whether the given node is in loop. + * @param {ASTNode} node A node to check. + * @param {ASTNode} parent A parent node to check. + * @returns {boolean} `true` if the node is in loop. */ -const boundaryTypes = new Set([ - "FunctionDeclaration", - "FunctionExpression", - "ArrowFunctionExpression" -]); +function isLooped(node, parent) { + switch (parent.type) { + case "ForStatement": + return ( + node === parent.test || + node === parent.update || + node === parent.body + ); + + case "ForOfStatement": + case "ForInStatement": + return node === parent.body; + + case "WhileStatement": + case "DoWhileStatement": + return node === parent.test || node === parent.body; + + default: + return false; + } +} module.exports = { meta: { @@ -37,51 +67,36 @@ module.exports = { } }, create(context) { - return { - AwaitExpression(node) { - const ancestors = context.getAncestors(); - - // Reverse so that we can traverse from the deepest node upwards. - ancestors.reverse(); - - /* - * Create a set of all the ancestors plus this node so that we can check - * if this use of await appears in the body of the loop as opposed to - * the right-hand side of a for...of, for example. - */ - const ancestorSet = new Set(ancestors).add(node); - - for (let i = 0; i < ancestors.length; i++) { - const ancestor = ancestors[i]; - if (boundaryTypes.has(ancestor.type)) { + /** + * Validate an await expression. + * @param {ASTNode} awaitNode An AwaitExpression or ForOfStatement node to validate. + * @returns {void} + */ + function validate(awaitNode) { + if (awaitNode.type === "ForOfStatement" && !awaitNode.await) { + return; + } - /* - * Short-circuit out if we encounter a boundary type. Loops above - * this do not matter. - */ - return; - } - if (loopTypes.has(ancestor.type)) { + let node = awaitNode; + let parent = node.parent; - /* - * Only report if we are actually in the body or another part that gets executed on - * every iteration. - */ - if ( - ancestorSet.has(ancestor.body) || - ancestorSet.has(ancestor.test) || - ancestorSet.has(ancestor.update) - ) { - context.report({ - node, - messageId: "unexpectedAwait" - }); - return; - } - } + while (parent && !isBoundary(parent)) { + if (isLooped(node, parent)) { + context.report({ + node, + messageId: "unexpectedAwait" + }); + return; } + node = parent; + parent = parent.parent; } + } + + return { + AwaitExpression: validate, + ForOfStatement: validate }; } }; diff --git a/lib/rules/object-shorthand.js b/lib/rules/object-shorthand.js index c6c0b104458..aaaf72d2636 100644 --- a/lib/rules/object-shorthand.js +++ b/lib/rules/object-shorthand.js @@ -233,10 +233,11 @@ module.exports = { const keyText = sourceCode.text.slice(firstKeyToken.range[0], lastKeyToken.range[1]); let keyPrefix = ""; + if (node.value.async) { + keyPrefix += "async "; + } if (node.value.generator) { - keyPrefix = "*"; - } else if (node.value.async) { - keyPrefix = "async "; + keyPrefix += "*"; } if (node.value.type === "FunctionExpression") { @@ -273,10 +274,11 @@ module.exports = { const keyText = sourceCode.text.slice(firstKeyToken.range[0], lastKeyToken.range[1]); let functionHeader = "function"; + if (node.value.async) { + functionHeader = `async ${functionHeader}`; + } if (node.value.generator) { - functionHeader = "function*"; - } else if (node.value.async) { - functionHeader = "async function"; + functionHeader = `${functionHeader}*`; } return fixer.replaceTextRange([node.range[0], lastKeyToken.range[1]], `${keyText}: ${functionHeader}`); diff --git a/lib/rules/require-await.js b/lib/rules/require-await.js index 6adc84ae15c..5517cf8672b 100644 --- a/lib/rules/require-await.js +++ b/lib/rules/require-await.js @@ -90,6 +90,11 @@ module.exports = { AwaitExpression() { scopeInfo.hasAwait = true; + }, + ForOfStatement(node) { + if (node.await) { + scopeInfo.hasAwait = true; + } } }; } diff --git a/tests/lib/rules/generator-star-spacing.js b/tests/lib/rules/generator-star-spacing.js index 5108091ff61..8d378cb5b34 100644 --- a/tests/lib/rules/generator-star-spacing.js +++ b/tests/lib/rules/generator-star-spacing.js @@ -16,7 +16,7 @@ const rule = require("../../../lib/rules/generator-star-spacing"), // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } }); ruleTester.run("generator-star-spacing", rule, { @@ -1336,6 +1336,80 @@ ruleTester.run("generator-star-spacing", rule, { message: "Missing space before *.", type: "Punctuator" }] + }, + + // async generators + { + code: "({ async * foo(){} })", + output: "({ async*foo(){} })", + options: [{ before: false, after: false }], + errors: [{ + message: "Unexpected space before *.", + type: "Punctuator" + }, { + message: "Unexpected space after *.", + type: "Punctuator" + }] + }, + { + code: "({ async*foo(){} })", + output: "({ async * foo(){} })", + options: [{ before: true, after: true }], + errors: [{ + message: "Missing space before *.", + type: "Punctuator" + }, { + message: "Missing space after *.", + type: "Punctuator" + }] + }, + { + code: "class Foo { async * foo(){} }", + output: "class Foo { async*foo(){} }", + options: [{ before: false, after: false }], + errors: [{ + message: "Unexpected space before *.", + type: "Punctuator" + }, { + message: "Unexpected space after *.", + type: "Punctuator" + }] + }, + { + code: "class Foo { async*foo(){} }", + output: "class Foo { async * foo(){} }", + options: [{ before: true, after: true }], + errors: [{ + message: "Missing space before *.", + type: "Punctuator" + }, { + message: "Missing space after *.", + type: "Punctuator" + }] + }, + { + code: "class Foo { static async * foo(){} }", + output: "class Foo { static async*foo(){} }", + options: [{ before: false, after: false }], + errors: [{ + message: "Unexpected space before *.", + type: "Punctuator" + }, { + message: "Unexpected space after *.", + type: "Punctuator" + }] + }, + { + code: "class Foo { static async*foo(){} }", + output: "class Foo { static async * foo(){} }", + options: [{ before: true, after: true }], + errors: [{ + message: "Missing space before *.", + type: "Punctuator" + }, { + message: "Missing space after *.", + type: "Punctuator" + }] } ] diff --git a/tests/lib/rules/keyword-spacing.js b/tests/lib/rules/keyword-spacing.js index d97f819fb66..2ab9b5d3587 100644 --- a/tests/lib/rules/keyword-spacing.js +++ b/tests/lib/rules/keyword-spacing.js @@ -180,6 +180,10 @@ ruleTester.run("keyword-spacing", rule, { { code: "a[ async function foo() {}]", options: [NEITHER], parserOptions: { ecmaVersion: 8 } }, { code: "({[ async function foo() {}]: 0})", options: [NEITHER], parserOptions: { ecmaVersion: 8 } }, + // not conflict with `generator-star-spacing` + { code: "({ async* foo() {} })", parserOptions: { ecmaVersion: 2018 } }, + { code: "({ async *foo() {} })", options: [NEITHER], parserOptions: { ecmaVersion: 2018 } }, + // not conflict with `key-spacing` { code: "({a:async function foo() {} })", parserOptions: { ecmaVersion: 8 } }, { code: "({a: async function foo() {} })", options: [NEITHER], parserOptions: { ecmaVersion: 8 } }, @@ -1530,6 +1534,34 @@ ruleTester.run("keyword-spacing", rule, { errors: unexpectedBefore("await") }, + { + code: "async function wrap() { for await(x of xs); }", + output: "async function wrap() { for await (x of xs); }", + parserOptions: { ecmaVersion: 2018 }, + errors: expectedAfter("await") + }, + { + code: "async function wrap() { for await (x of xs); }", + output: "async function wrap() { for await(x of xs); }", + options: [NEITHER], + parserOptions: { ecmaVersion: 2018 }, + errors: unexpectedAfter("await") + }, + { + code: "async function wrap() { for await(x of xs); }", + output: "async function wrap() { for await (x of xs); }", + options: [override("await", BOTH)], + parserOptions: { ecmaVersion: 2018 }, + errors: expectedAfter("await") + }, + { + code: "async function wrap() { for await (x of xs); }", + output: "async function wrap() { for await(x of xs); }", + options: [override("await", NEITHER)], + parserOptions: { ecmaVersion: 2018 }, + errors: unexpectedAfter("await") + }, + //---------------------------------------------------------------------- // break //---------------------------------------------------------------------- diff --git a/tests/lib/rules/no-await-in-loop.js b/tests/lib/rules/no-await-in-loop.js index bb74db1c039..057223879ab 100644 --- a/tests/lib/rules/no-await-in-loop.js +++ b/tests/lib/rules/no-await-in-loop.js @@ -10,13 +10,14 @@ const rule = require("../../../lib/rules/no-await-in-loop"), const error = { messageId: "unexpectedAwait" }; -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: "2017" } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } }); ruleTester.run("no-await-in-loop", rule, { valid: [ "async function foo() { await bar; }", "async function foo() { for (var bar in await baz) { } }", "async function foo() { for (var bar of await baz) { } }", + "async function foo() { for await (var bar of await baz) { } }", "async function foo() { for (var bar = await baz in qux) {} }", // While loops @@ -35,14 +36,17 @@ ruleTester.run("no-await-in-loop", rule, { "async function foo() { while (true) { var y = async () => { await foo; } } }", // Blocked by a class method - "async function foo() { while (true) { class Foo { async foo() { await bar; } } } }" + "async function foo() { while (true) { class Foo { async foo() { await bar; } } } }", + // Asynchronous iteration intentionally + "async function foo() { for await (var x of xs) { await f(x) } }" ], invalid: [ // While loops { code: "async function foo() { while (baz) { await bar; } }", errors: [error] }, { code: "async function foo() { while (await foo()) { } }", errors: [error] }, + { code: "async function foo() { while (baz) { for await (x of xs); } }", errors: [error] }, // For of loops { code: "async function foo() { for (var bar of baz) { await bar; } }", errors: [error] }, @@ -64,6 +68,9 @@ ruleTester.run("no-await-in-loop", rule, { { code: "async function foo() { while (true) { if (bar) { foo(await bar); } } }", errors: [error] }, // Deep in a loop condition - { code: "async function foo() { while (xyz || 5 > await x) { } }", errors: [error] } + { code: "async function foo() { while (xyz || 5 > await x) { } }", errors: [error] }, + + // In a nested loop of for-await-of + { code: "async function foo() { for await (var x of xs) { while (1) await f(x) } }", errors: [error] } ] }); diff --git a/tests/lib/rules/no-useless-computed-key.js b/tests/lib/rules/no-useless-computed-key.js index c825579fccb..ed9f3daa058 100644 --- a/tests/lib/rules/no-useless-computed-key.js +++ b/tests/lib/rules/no-useless-computed-key.js @@ -16,7 +16,7 @@ const rule = require("../../../lib/rules/no-useless-computed-key"), // Tests //------------------------------------------------------------------------------ -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } }); ruleTester.run("no-useless-computed-key", rule, { valid: [ @@ -162,6 +162,12 @@ ruleTester.run("no-useless-computed-key", rule, { errors: [{ message: "Unnecessarily computed property [2] found.", type: "Property" }] + }, { + code: "({ async*[2]() {} })", + output: "({ async*2() {} })", + errors: [{ + message: "Unnecessarily computed property [2] found.", type: "Property" + }] } ] }); diff --git a/tests/lib/rules/object-shorthand.js b/tests/lib/rules/object-shorthand.js index 0606d0ed812..e153f9fcb4e 100644 --- a/tests/lib/rules/object-shorthand.js +++ b/tests/lib/rules/object-shorthand.js @@ -24,7 +24,7 @@ const LONGFORM_METHOD_STRING_LITERAL_ERROR = { message: "Expected longform metho const ALL_SHORTHAND_ERROR = { message: "Expected shorthand for all properties.", type: "ObjectExpression" }; const MIXED_SHORTHAND_ERROR = { message: "Unexpected mix of shorthand and non-shorthand properties.", type: "ObjectExpression" }; -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } }); +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } }); ruleTester.run("object-shorthand", rule, { valid: [ @@ -979,6 +979,20 @@ ruleTester.run("object-shorthand", rule, { output: "({ a() { return foo; } })", options: ["always", { avoidExplicitReturnArrows: true }], errors: [METHOD_ERROR] + }, + + // async generators + { + code: "({ a: async function*() {} })", + output: "({ async *a() {} })", + options: ["always"], + errors: [METHOD_ERROR] + }, + { + code: "({ async* a() {} })", + output: "({ a: async function*() {} })", + options: ["never"], + errors: [LONGFORM_METHOD_ERROR] } ] }); diff --git a/tests/lib/rules/require-await.js b/tests/lib/rules/require-await.js index 6248ac8a59c..662dc72cbf9 100644 --- a/tests/lib/rules/require-await.js +++ b/tests/lib/rules/require-await.js @@ -18,7 +18,7 @@ const rule = require("../../../lib/rules/require-await"), const ruleTester = new RuleTester({ parserOptions: { - ecmaVersion: 2017 + ecmaVersion: 2018 } }); @@ -38,7 +38,10 @@ ruleTester.run("require-await", rule, { "async () => {}", // normal functions are ok. - "function foo() { doSomething() }" + "function foo() { doSomething() }", + + // for-await-of + "async function foo() { for await (x of xs); }" ], invalid: [ {