2025-04-17 07:44:37 -04:00

219 lines
5.1 KiB
JavaScript

/**
* @fileoverview Rule to enforce location of semicolons.
* @author Toru Nagashima
* @deprecated in ESLint v8.53.0
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const astUtils = require("./utils/ast-utils");
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
const SELECTOR = [
"BreakStatement",
"ContinueStatement",
"DebuggerStatement",
"DoWhileStatement",
"ExportAllDeclaration",
"ExportDefaultDeclaration",
"ExportNamedDeclaration",
"ExpressionStatement",
"ImportDeclaration",
"ReturnStatement",
"ThrowStatement",
"VariableDeclaration",
"PropertyDefinition",
].join(",");
/**
* Get the child node list of a given node.
* This returns `BlockStatement#body`, `StaticBlock#body`, `Program#body`,
* `ClassBody#body`, or `SwitchCase#consequent`.
* This is used to check whether a node is the first/last child.
* @param {Node} node A node to get child node list.
* @returns {Node[]|null} The child node list.
*/
function getChildren(node) {
const t = node.type;
if (
t === "BlockStatement" ||
t === "StaticBlock" ||
t === "Program" ||
t === "ClassBody"
) {
return node.body;
}
if (t === "SwitchCase") {
return node.consequent;
}
return null;
}
/**
* Check whether a given node is the last statement in the parent block.
* @param {Node} node A node to check.
* @returns {boolean} `true` if the node is the last statement in the parent block.
*/
function isLastChild(node) {
const t = node.parent.type;
if (
t === "IfStatement" &&
node.parent.consequent === node &&
node.parent.alternate
) {
// before `else` keyword.
return true;
}
if (t === "DoWhileStatement") {
// before `while` keyword.
return true;
}
const nodeList = getChildren(node.parent);
return nodeList !== null && nodeList.at(-1) === node; // before `}` or etc.
}
/** @type {import('../types').Rule.RuleModule} */
module.exports = {
meta: {
deprecated: {
message: "Formatting rules are being moved out of ESLint core.",
url: "https://eslint.org/blog/2023/10/deprecating-formatting-rules/",
deprecatedSince: "8.53.0",
availableUntil: "10.0.0",
replacedBy: [
{
message:
"ESLint Stylistic now maintains deprecated stylistic core rules.",
url: "https://eslint.style/guide/migration",
plugin: {
name: "@stylistic/eslint-plugin-js",
url: "https://eslint.style/packages/js",
},
rule: {
name: "semi-style",
url: "https://eslint.style/rules/js/semi-style",
},
},
],
},
type: "layout",
docs: {
description: "Enforce location of semicolons",
recommended: false,
url: "https://eslint.org/docs/latest/rules/semi-style",
},
schema: [{ enum: ["last", "first"] }],
fixable: "whitespace",
messages: {
expectedSemiColon: "Expected this semicolon to be at {{pos}}.",
},
},
create(context) {
const sourceCode = context.sourceCode;
const option = context.options[0] || "last";
/**
* Check the given semicolon token.
* @param {Token} semiToken The semicolon token to check.
* @param {"first"|"last"} expected The expected location to check.
* @returns {void}
*/
function check(semiToken, expected) {
const prevToken = sourceCode.getTokenBefore(semiToken);
const nextToken = sourceCode.getTokenAfter(semiToken);
const prevIsSameLine =
!prevToken || astUtils.isTokenOnSameLine(prevToken, semiToken);
const nextIsSameLine =
!nextToken || astUtils.isTokenOnSameLine(semiToken, nextToken);
if (
(expected === "last" && !prevIsSameLine) ||
(expected === "first" && !nextIsSameLine)
) {
context.report({
loc: semiToken.loc,
messageId: "expectedSemiColon",
data: {
pos:
expected === "last"
? "the end of the previous line"
: "the beginning of the next line",
},
fix(fixer) {
if (
prevToken &&
nextToken &&
sourceCode.commentsExistBetween(
prevToken,
nextToken,
)
) {
return null;
}
const start = prevToken
? prevToken.range[1]
: semiToken.range[0];
const end = nextToken
? nextToken.range[0]
: semiToken.range[1];
const text = expected === "last" ? ";\n" : "\n;";
return fixer.replaceTextRange([start, end], text);
},
});
}
}
return {
[SELECTOR](node) {
if (option === "first" && isLastChild(node)) {
return;
}
const lastToken = sourceCode.getLastToken(node);
if (astUtils.isSemicolonToken(lastToken)) {
check(lastToken, option);
}
},
ForStatement(node) {
const firstSemi =
node.init &&
sourceCode.getTokenAfter(
node.init,
astUtils.isSemicolonToken,
);
const secondSemi =
node.test &&
sourceCode.getTokenAfter(
node.test,
astUtils.isSemicolonToken,
);
if (firstSemi) {
check(firstSemi, "last");
}
if (secondSemi) {
check(secondSemi, "last");
}
},
};
},
};