2 * @fileoverview Rule to flag missing semicolons.
3 * @author Nicholas C. Zakas
7 //------------------------------------------------------------------------------
9 //------------------------------------------------------------------------------
11 const FixTracker = require("./utils/fix-tracker");
12 const astUtils = require("./utils/ast-utils");
14 //------------------------------------------------------------------------------
16 //------------------------------------------------------------------------------
23 description: "require or disallow semicolons instead of ASI",
24 category: "Stylistic Issues",
26 url: "https://eslint.org/docs/rules/semi"
42 beforeStatementContinuationChars: {
43 enum: ["always", "any", "never"]
46 additionalProperties: false
61 omitLastInOneLineBlock: { type: "boolean" }
63 additionalProperties: false
75 const OPT_OUT_PATTERN = /^[-[(/+`]/u; // One of [(/+-`
76 const options = context.options[1];
77 const never = context.options[0] === "never";
78 const exceptOneLine = Boolean(options && options.omitLastInOneLineBlock);
79 const beforeStatementContinuationChars = options && options.beforeStatementContinuationChars || "any";
80 const sourceCode = context.getSourceCode();
82 //--------------------------------------------------------------------------
84 //--------------------------------------------------------------------------
87 * Reports a semicolon error with appropriate location and message.
88 * @param {ASTNode} node The node with an extra or missing semicolon.
89 * @param {boolean} missing True if the semicolon is missing.
92 function report(node, missing) {
93 const lastToken = sourceCode.getLastToken(node);
99 message = "Missing semicolon.";
101 start: lastToken.loc.end,
102 end: astUtils.getNextLocation(sourceCode, lastToken.loc.end)
104 fix = function(fixer) {
105 return fixer.insertTextAfter(lastToken, ";");
108 message = "Extra semicolon.";
110 fix = function(fixer) {
113 * Expand the replacement range to include the surrounding
114 * tokens to avoid conflicting with no-extra-semi.
115 * https://github.com/eslint/eslint/issues/7928
117 return new FixTracker(fixer, sourceCode)
118 .retainSurroundingTokens(lastToken)
133 * Check whether a given semicolon token is redandant.
134 * @param {Token} semiToken A semicolon token to check.
135 * @returns {boolean} `true` if the next token is `;` or `}`.
137 function isRedundantSemi(semiToken) {
138 const nextToken = sourceCode.getTokenAfter(semiToken);
142 astUtils.isClosingBraceToken(nextToken) ||
143 astUtils.isSemicolonToken(nextToken)
148 * Check whether a given token is the closing brace of an arrow function.
149 * @param {Token} lastToken A token to check.
150 * @returns {boolean} `true` if the token is the closing brace of an arrow function.
152 function isEndOfArrowBlock(lastToken) {
153 if (!astUtils.isClosingBraceToken(lastToken)) {
156 const node = sourceCode.getNodeByRangeIndex(lastToken.range[0]);
159 node.type === "BlockStatement" &&
160 node.parent.type === "ArrowFunctionExpression"
165 * Check whether a given node is on the same line with the next token.
166 * @param {Node} node A statement node to check.
167 * @returns {boolean} `true` if the node is on the same line with the next token.
169 function isOnSameLineWithNextToken(node) {
170 const prevToken = sourceCode.getLastToken(node, 1);
171 const nextToken = sourceCode.getTokenAfter(node);
173 return !!nextToken && astUtils.isTokenOnSameLine(prevToken, nextToken);
177 * Check whether a given node can connect the next line if the next line is unreliable.
178 * @param {Node} node A statement node to check.
179 * @returns {boolean} `true` if the node can connect the next line.
181 function maybeAsiHazardAfter(node) {
184 if (t === "DoWhileStatement" ||
185 t === "BreakStatement" ||
186 t === "ContinueStatement" ||
187 t === "DebuggerStatement" ||
188 t === "ImportDeclaration" ||
189 t === "ExportAllDeclaration"
193 if (t === "ReturnStatement") {
194 return Boolean(node.argument);
196 if (t === "ExportNamedDeclaration") {
197 return Boolean(node.declaration);
199 if (isEndOfArrowBlock(sourceCode.getLastToken(node, 1))) {
207 * Check whether a given token can connect the previous statement.
208 * @param {Token} token A token to check.
209 * @returns {boolean} `true` if the token is one of `[`, `(`, `/`, `+`, `-`, ```, `++`, and `--`.
211 function maybeAsiHazardBefore(token) {
214 OPT_OUT_PATTERN.test(token.value) &&
215 token.value !== "++" &&
221 * Check if the semicolon of a given node is unnecessary, only true if:
222 * - next token is a valid statement divider (`;` or `}`).
223 * - next token is on a new line and the node is not connectable to the new line.
224 * @param {Node} node A statement node to check.
225 * @returns {boolean} whether the semicolon is unnecessary.
227 function canRemoveSemicolon(node) {
228 if (isRedundantSemi(sourceCode.getLastToken(node))) {
229 return true; // `;;` or `;}`
231 if (isOnSameLineWithNextToken(node)) {
232 return false; // One liner.
234 if (beforeStatementContinuationChars === "never" && !maybeAsiHazardAfter(node)) {
235 return true; // ASI works. This statement doesn't connect to the next.
237 if (!maybeAsiHazardBefore(sourceCode.getTokenAfter(node))) {
238 return true; // ASI works. The next token doesn't connect to this statement.
245 * Checks a node to see if it's in a one-liner block statement.
246 * @param {ASTNode} node The node to check.
247 * @returns {boolean} whether the node is in a one-liner block statement.
249 function isOneLinerBlock(node) {
250 const parent = node.parent;
251 const nextToken = sourceCode.getTokenAfter(node);
253 if (!nextToken || nextToken.value !== "}") {
258 parent.type === "BlockStatement" &&
259 parent.loc.start.line === parent.loc.end.line
264 * Checks a node to see if it's followed by a semicolon.
265 * @param {ASTNode} node The node to check.
268 function checkForSemicolon(node) {
269 const isSemi = astUtils.isSemicolonToken(sourceCode.getLastToken(node));
272 if (isSemi && canRemoveSemicolon(node)) {
274 } else if (!isSemi && beforeStatementContinuationChars === "always" && maybeAsiHazardBefore(sourceCode.getTokenAfter(node))) {
278 const oneLinerBlock = (exceptOneLine && isOneLinerBlock(node));
280 if (isSemi && oneLinerBlock) {
282 } else if (!isSemi && !oneLinerBlock) {
289 * Checks to see if there's a semicolon after a variable declaration.
290 * @param {ASTNode} node The node to check.
293 function checkForSemicolonForVariableDeclaration(node) {
294 const parent = node.parent;
296 if ((parent.type !== "ForStatement" || parent.init !== node) &&
297 (!/^For(?:In|Of)Statement/u.test(parent.type) || parent.left !== node)
299 checkForSemicolon(node);
303 //--------------------------------------------------------------------------
305 //--------------------------------------------------------------------------
308 VariableDeclaration: checkForSemicolonForVariableDeclaration,
309 ExpressionStatement: checkForSemicolon,
310 ReturnStatement: checkForSemicolon,
311 ThrowStatement: checkForSemicolon,
312 DoWhileStatement: checkForSemicolon,
313 DebuggerStatement: checkForSemicolon,
314 BreakStatement: checkForSemicolon,
315 ContinueStatement: checkForSemicolon,
316 ImportDeclaration: checkForSemicolon,
317 ExportAllDeclaration: checkForSemicolon,
318 ExportNamedDeclaration(node) {
319 if (!node.declaration) {
320 checkForSemicolon(node);
323 ExportDefaultDeclaration(node) {
324 if (!/(?:Class|Function)Declaration/u.test(node.declaration.type)) {
325 checkForSemicolon(node);