185 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			185 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
		
			Executable File
		
	
	
	
	
/**
 | 
						|
 * @fileoverview Rule to disallow async functions which have no `await` expression.
 | 
						|
 * @author Toru Nagashima
 | 
						|
 */
 | 
						|
 | 
						|
"use strict";
 | 
						|
 | 
						|
//------------------------------------------------------------------------------
 | 
						|
// Requirements
 | 
						|
//------------------------------------------------------------------------------
 | 
						|
 | 
						|
const astUtils = require("./utils/ast-utils");
 | 
						|
 | 
						|
//------------------------------------------------------------------------------
 | 
						|
// Helpers
 | 
						|
//------------------------------------------------------------------------------
 | 
						|
 | 
						|
/**
 | 
						|
 * Capitalize the 1st letter of the given text.
 | 
						|
 * @param {string} text The text to capitalize.
 | 
						|
 * @returns {string} The text that the 1st letter was capitalized.
 | 
						|
 */
 | 
						|
function capitalizeFirstLetter(text) {
 | 
						|
	return text[0].toUpperCase() + text.slice(1);
 | 
						|
}
 | 
						|
 | 
						|
//------------------------------------------------------------------------------
 | 
						|
// Rule Definition
 | 
						|
//------------------------------------------------------------------------------
 | 
						|
 | 
						|
/** @type {import('../types').Rule.RuleModule} */
 | 
						|
module.exports = {
 | 
						|
	meta: {
 | 
						|
		type: "suggestion",
 | 
						|
 | 
						|
		docs: {
 | 
						|
			description:
 | 
						|
				"Disallow async functions which have no `await` expression",
 | 
						|
			recommended: false,
 | 
						|
			url: "https://eslint.org/docs/latest/rules/require-await",
 | 
						|
		},
 | 
						|
 | 
						|
		schema: [],
 | 
						|
 | 
						|
		messages: {
 | 
						|
			missingAwait: "{{name}} has no 'await' expression.",
 | 
						|
			removeAsync: "Remove 'async'.",
 | 
						|
		},
 | 
						|
 | 
						|
		hasSuggestions: true,
 | 
						|
	},
 | 
						|
 | 
						|
	create(context) {
 | 
						|
		const sourceCode = context.sourceCode;
 | 
						|
		let scopeInfo = null;
 | 
						|
 | 
						|
		/**
 | 
						|
		 * Push the scope info object to the stack.
 | 
						|
		 * @returns {void}
 | 
						|
		 */
 | 
						|
		function enterFunction() {
 | 
						|
			scopeInfo = {
 | 
						|
				upper: scopeInfo,
 | 
						|
				hasAwait: false,
 | 
						|
			};
 | 
						|
		}
 | 
						|
 | 
						|
		/**
 | 
						|
		 * Pop the top scope info object from the stack.
 | 
						|
		 * Also, it reports the function if needed.
 | 
						|
		 * @param {ASTNode} node The node to report.
 | 
						|
		 * @returns {void}
 | 
						|
		 */
 | 
						|
		function exitFunction(node) {
 | 
						|
			if (
 | 
						|
				!node.generator &&
 | 
						|
				node.async &&
 | 
						|
				!scopeInfo.hasAwait &&
 | 
						|
				!astUtils.isEmptyFunction(node)
 | 
						|
			) {
 | 
						|
				/*
 | 
						|
				 * If the function belongs to a method definition or
 | 
						|
				 * property, then the function's range may not include the
 | 
						|
				 * `async` keyword and we should look at the parent instead.
 | 
						|
				 */
 | 
						|
				const nodeWithAsyncKeyword =
 | 
						|
					(node.parent.type === "MethodDefinition" &&
 | 
						|
						node.parent.value === node) ||
 | 
						|
					(node.parent.type === "Property" &&
 | 
						|
						node.parent.method &&
 | 
						|
						node.parent.value === node)
 | 
						|
						? node.parent
 | 
						|
						: node;
 | 
						|
 | 
						|
				const asyncToken = sourceCode.getFirstToken(
 | 
						|
					nodeWithAsyncKeyword,
 | 
						|
					token => token.value === "async",
 | 
						|
				);
 | 
						|
				const asyncRange = [
 | 
						|
					asyncToken.range[0],
 | 
						|
					sourceCode.getTokenAfter(asyncToken, {
 | 
						|
						includeComments: true,
 | 
						|
					}).range[0],
 | 
						|
				];
 | 
						|
 | 
						|
				/*
 | 
						|
				 * Removing the `async` keyword can cause parsing errors if the current
 | 
						|
				 * statement is relying on automatic semicolon insertion. If ASI is currently
 | 
						|
				 * being used, then we should replace the `async` keyword with a semicolon.
 | 
						|
				 */
 | 
						|
				const nextToken = sourceCode.getTokenAfter(asyncToken);
 | 
						|
				const addSemiColon =
 | 
						|
					nextToken.type === "Punctuator" &&
 | 
						|
					(nextToken.value === "[" || nextToken.value === "(") &&
 | 
						|
					(nodeWithAsyncKeyword.type === "MethodDefinition" ||
 | 
						|
						astUtils.isStartOfExpressionStatement(
 | 
						|
							nodeWithAsyncKeyword,
 | 
						|
						)) &&
 | 
						|
					astUtils.needsPrecedingSemicolon(
 | 
						|
						sourceCode,
 | 
						|
						nodeWithAsyncKeyword,
 | 
						|
					);
 | 
						|
 | 
						|
				context.report({
 | 
						|
					node,
 | 
						|
					loc: astUtils.getFunctionHeadLoc(node, sourceCode),
 | 
						|
					messageId: "missingAwait",
 | 
						|
					data: {
 | 
						|
						name: capitalizeFirstLetter(
 | 
						|
							astUtils.getFunctionNameWithKind(node),
 | 
						|
						),
 | 
						|
					},
 | 
						|
					suggest: [
 | 
						|
						{
 | 
						|
							messageId: "removeAsync",
 | 
						|
							fix: fixer =>
 | 
						|
								fixer.replaceTextRange(
 | 
						|
									asyncRange,
 | 
						|
									addSemiColon ? ";" : "",
 | 
						|
								),
 | 
						|
						},
 | 
						|
					],
 | 
						|
				});
 | 
						|
			}
 | 
						|
 | 
						|
			scopeInfo = scopeInfo.upper;
 | 
						|
		}
 | 
						|
 | 
						|
		return {
 | 
						|
			FunctionDeclaration: enterFunction,
 | 
						|
			FunctionExpression: enterFunction,
 | 
						|
			ArrowFunctionExpression: enterFunction,
 | 
						|
			"FunctionDeclaration:exit": exitFunction,
 | 
						|
			"FunctionExpression:exit": exitFunction,
 | 
						|
			"ArrowFunctionExpression:exit": exitFunction,
 | 
						|
 | 
						|
			AwaitExpression() {
 | 
						|
				if (!scopeInfo) {
 | 
						|
					return;
 | 
						|
				}
 | 
						|
 | 
						|
				scopeInfo.hasAwait = true;
 | 
						|
			},
 | 
						|
			ForOfStatement(node) {
 | 
						|
				if (!scopeInfo) {
 | 
						|
					return;
 | 
						|
				}
 | 
						|
 | 
						|
				if (node.await) {
 | 
						|
					scopeInfo.hasAwait = true;
 | 
						|
				}
 | 
						|
			},
 | 
						|
			VariableDeclaration(node) {
 | 
						|
				if (!scopeInfo) {
 | 
						|
					return;
 | 
						|
				}
 | 
						|
 | 
						|
				if (node.kind === "await using") {
 | 
						|
					scopeInfo.hasAwait = true;
 | 
						|
				}
 | 
						|
			},
 | 
						|
		};
 | 
						|
	},
 | 
						|
};
 |