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