1/** 2 * @fileoverview Rule to flag use of unnecessary semicolons 3 * @author Nicholas C. Zakas 4 */ 5 6"use strict"; 7 8//------------------------------------------------------------------------------ 9// Requirements 10//------------------------------------------------------------------------------ 11 12const FixTracker = require("./utils/fix-tracker"); 13const astUtils = require("./utils/ast-utils"); 14 15//------------------------------------------------------------------------------ 16// Rule Definition 17//------------------------------------------------------------------------------ 18 19module.exports = { 20 meta: { 21 type: "suggestion", 22 23 docs: { 24 description: "disallow unnecessary semicolons", 25 category: "Possible Errors", 26 recommended: true, 27 url: "https://eslint.org/docs/rules/no-extra-semi" 28 }, 29 30 fixable: "code", 31 schema: [], 32 33 messages: { 34 unexpected: "Unnecessary semicolon." 35 } 36 }, 37 38 create(context) { 39 const sourceCode = context.getSourceCode(); 40 41 /** 42 * Reports an unnecessary semicolon error. 43 * @param {Node|Token} nodeOrToken A node or a token to be reported. 44 * @returns {void} 45 */ 46 function report(nodeOrToken) { 47 context.report({ 48 node: nodeOrToken, 49 messageId: "unexpected", 50 fix(fixer) { 51 52 /* 53 * Expand the replacement range to include the surrounding 54 * tokens to avoid conflicting with semi. 55 * https://github.com/eslint/eslint/issues/7928 56 */ 57 return new FixTracker(fixer, context.getSourceCode()) 58 .retainSurroundingTokens(nodeOrToken) 59 .remove(nodeOrToken); 60 } 61 }); 62 } 63 64 /** 65 * Checks for a part of a class body. 66 * This checks tokens from a specified token to a next MethodDefinition or the end of class body. 67 * @param {Token} firstToken The first token to check. 68 * @returns {void} 69 */ 70 function checkForPartOfClassBody(firstToken) { 71 for (let token = firstToken; 72 token.type === "Punctuator" && !astUtils.isClosingBraceToken(token); 73 token = sourceCode.getTokenAfter(token) 74 ) { 75 if (astUtils.isSemicolonToken(token)) { 76 report(token); 77 } 78 } 79 } 80 81 return { 82 83 /** 84 * Reports this empty statement, except if the parent node is a loop. 85 * @param {Node} node A EmptyStatement node to be reported. 86 * @returns {void} 87 */ 88 EmptyStatement(node) { 89 const parent = node.parent, 90 allowedParentTypes = [ 91 "ForStatement", 92 "ForInStatement", 93 "ForOfStatement", 94 "WhileStatement", 95 "DoWhileStatement", 96 "IfStatement", 97 "LabeledStatement", 98 "WithStatement" 99 ]; 100 101 if (allowedParentTypes.indexOf(parent.type) === -1) { 102 report(node); 103 } 104 }, 105 106 /** 107 * Checks tokens from the head of this class body to the first MethodDefinition or the end of this class body. 108 * @param {Node} node A ClassBody node to check. 109 * @returns {void} 110 */ 111 ClassBody(node) { 112 checkForPartOfClassBody(sourceCode.getFirstToken(node, 1)); // 0 is `{`. 113 }, 114 115 /** 116 * Checks tokens from this MethodDefinition to the next MethodDefinition or the end of this class body. 117 * @param {Node} node A MethodDefinition node of the start point. 118 * @returns {void} 119 */ 120 MethodDefinition(node) { 121 checkForPartOfClassBody(sourceCode.getTokenAfter(node)); 122 } 123 }; 124 125 } 126}; 127