1 | /**
|
---|
2 | * @fileoverview Rule to enforce location of semicolons.
|
---|
3 | * @author Toru Nagashima
|
---|
4 | * @deprecated in ESLint v8.53.0
|
---|
5 | */
|
---|
6 |
|
---|
7 | "use strict";
|
---|
8 |
|
---|
9 | //------------------------------------------------------------------------------
|
---|
10 | // Requirements
|
---|
11 | //------------------------------------------------------------------------------
|
---|
12 |
|
---|
13 | const astUtils = require("./utils/ast-utils");
|
---|
14 |
|
---|
15 | //------------------------------------------------------------------------------
|
---|
16 | // Rule Definition
|
---|
17 | //------------------------------------------------------------------------------
|
---|
18 |
|
---|
19 | const SELECTOR = [
|
---|
20 | "BreakStatement", "ContinueStatement", "DebuggerStatement",
|
---|
21 | "DoWhileStatement", "ExportAllDeclaration",
|
---|
22 | "ExportDefaultDeclaration", "ExportNamedDeclaration",
|
---|
23 | "ExpressionStatement", "ImportDeclaration", "ReturnStatement",
|
---|
24 | "ThrowStatement", "VariableDeclaration", "PropertyDefinition"
|
---|
25 | ].join(",");
|
---|
26 |
|
---|
27 | /**
|
---|
28 | * Get the child node list of a given node.
|
---|
29 | * This returns `BlockStatement#body`, `StaticBlock#body`, `Program#body`,
|
---|
30 | * `ClassBody#body`, or `SwitchCase#consequent`.
|
---|
31 | * This is used to check whether a node is the first/last child.
|
---|
32 | * @param {Node} node A node to get child node list.
|
---|
33 | * @returns {Node[]|null} The child node list.
|
---|
34 | */
|
---|
35 | function getChildren(node) {
|
---|
36 | const t = node.type;
|
---|
37 |
|
---|
38 | if (
|
---|
39 | t === "BlockStatement" ||
|
---|
40 | t === "StaticBlock" ||
|
---|
41 | t === "Program" ||
|
---|
42 | t === "ClassBody"
|
---|
43 | ) {
|
---|
44 | return node.body;
|
---|
45 | }
|
---|
46 | if (t === "SwitchCase") {
|
---|
47 | return node.consequent;
|
---|
48 | }
|
---|
49 | return null;
|
---|
50 | }
|
---|
51 |
|
---|
52 | /**
|
---|
53 | * Check whether a given node is the last statement in the parent block.
|
---|
54 | * @param {Node} node A node to check.
|
---|
55 | * @returns {boolean} `true` if the node is the last statement in the parent block.
|
---|
56 | */
|
---|
57 | function isLastChild(node) {
|
---|
58 | const t = node.parent.type;
|
---|
59 |
|
---|
60 | if (t === "IfStatement" && node.parent.consequent === node && node.parent.alternate) { // before `else` keyword.
|
---|
61 | return true;
|
---|
62 | }
|
---|
63 | if (t === "DoWhileStatement") { // before `while` keyword.
|
---|
64 | return true;
|
---|
65 | }
|
---|
66 | const nodeList = getChildren(node.parent);
|
---|
67 |
|
---|
68 | return nodeList !== null && nodeList[nodeList.length - 1] === node; // before `}` or etc.
|
---|
69 | }
|
---|
70 |
|
---|
71 | /** @type {import('../shared/types').Rule} */
|
---|
72 | module.exports = {
|
---|
73 | meta: {
|
---|
74 | deprecated: true,
|
---|
75 | replacedBy: [],
|
---|
76 | type: "layout",
|
---|
77 |
|
---|
78 | docs: {
|
---|
79 | description: "Enforce location of semicolons",
|
---|
80 | recommended: false,
|
---|
81 | url: "https://eslint.org/docs/latest/rules/semi-style"
|
---|
82 | },
|
---|
83 |
|
---|
84 | schema: [{ enum: ["last", "first"] }],
|
---|
85 | fixable: "whitespace",
|
---|
86 |
|
---|
87 | messages: {
|
---|
88 | expectedSemiColon: "Expected this semicolon to be at {{pos}}."
|
---|
89 | }
|
---|
90 | },
|
---|
91 |
|
---|
92 | create(context) {
|
---|
93 | const sourceCode = context.sourceCode;
|
---|
94 | const option = context.options[0] || "last";
|
---|
95 |
|
---|
96 | /**
|
---|
97 | * Check the given semicolon token.
|
---|
98 | * @param {Token} semiToken The semicolon token to check.
|
---|
99 | * @param {"first"|"last"} expected The expected location to check.
|
---|
100 | * @returns {void}
|
---|
101 | */
|
---|
102 | function check(semiToken, expected) {
|
---|
103 | const prevToken = sourceCode.getTokenBefore(semiToken);
|
---|
104 | const nextToken = sourceCode.getTokenAfter(semiToken);
|
---|
105 | const prevIsSameLine = !prevToken || astUtils.isTokenOnSameLine(prevToken, semiToken);
|
---|
106 | const nextIsSameLine = !nextToken || astUtils.isTokenOnSameLine(semiToken, nextToken);
|
---|
107 |
|
---|
108 | if ((expected === "last" && !prevIsSameLine) || (expected === "first" && !nextIsSameLine)) {
|
---|
109 | context.report({
|
---|
110 | loc: semiToken.loc,
|
---|
111 | messageId: "expectedSemiColon",
|
---|
112 | data: {
|
---|
113 | pos: (expected === "last")
|
---|
114 | ? "the end of the previous line"
|
---|
115 | : "the beginning of the next line"
|
---|
116 | },
|
---|
117 | fix(fixer) {
|
---|
118 | if (prevToken && nextToken && sourceCode.commentsExistBetween(prevToken, nextToken)) {
|
---|
119 | return null;
|
---|
120 | }
|
---|
121 |
|
---|
122 | const start = prevToken ? prevToken.range[1] : semiToken.range[0];
|
---|
123 | const end = nextToken ? nextToken.range[0] : semiToken.range[1];
|
---|
124 | const text = (expected === "last") ? ";\n" : "\n;";
|
---|
125 |
|
---|
126 | return fixer.replaceTextRange([start, end], text);
|
---|
127 | }
|
---|
128 | });
|
---|
129 | }
|
---|
130 | }
|
---|
131 |
|
---|
132 | return {
|
---|
133 | [SELECTOR](node) {
|
---|
134 | if (option === "first" && isLastChild(node)) {
|
---|
135 | return;
|
---|
136 | }
|
---|
137 |
|
---|
138 | const lastToken = sourceCode.getLastToken(node);
|
---|
139 |
|
---|
140 | if (astUtils.isSemicolonToken(lastToken)) {
|
---|
141 | check(lastToken, option);
|
---|
142 | }
|
---|
143 | },
|
---|
144 |
|
---|
145 | ForStatement(node) {
|
---|
146 | const firstSemi = node.init && sourceCode.getTokenAfter(node.init, astUtils.isSemicolonToken);
|
---|
147 | const secondSemi = node.test && sourceCode.getTokenAfter(node.test, astUtils.isSemicolonToken);
|
---|
148 |
|
---|
149 | if (firstSemi) {
|
---|
150 | check(firstSemi, "last");
|
---|
151 | }
|
---|
152 | if (secondSemi) {
|
---|
153 | check(secondSemi, "last");
|
---|
154 | }
|
---|
155 | }
|
---|
156 | };
|
---|
157 | }
|
---|
158 | };
|
---|