UNPKG

7.54 kBJavaScriptView Raw
1/**
2 * @fileoverview Rule to flag missing semicolons.
3 * @author Nicholas C. Zakas
4 */
5"use strict";
6
7//------------------------------------------------------------------------------
8// Rule Definition
9//------------------------------------------------------------------------------
10
11module.exports = {
12 meta: {
13 docs: {
14 description: "require or disallow semicolons instead of ASI",
15 category: "Stylistic Issues",
16 recommended: false
17 },
18
19 fixable: "code",
20
21 schema: {
22 anyOf: [
23 {
24 type: "array",
25 items: [
26 {
27 enum: ["never"]
28 }
29 ],
30 minItems: 0,
31 maxItems: 1
32 },
33 {
34 type: "array",
35 items: [
36 {
37 enum: ["always"]
38 },
39 {
40 type: "object",
41 properties: {
42 omitLastInOneLineBlock: {type: "boolean"}
43 },
44 additionalProperties: false
45 }
46 ],
47 minItems: 0,
48 maxItems: 2
49 }
50 ]
51 }
52 },
53
54 create(context) {
55
56 const OPT_OUT_PATTERN = /[\[\(\/\+\-]/; // One of [(/+-
57 const options = context.options[1];
58 const never = context.options[0] === "never",
59 exceptOneLine = options && options.omitLastInOneLineBlock === true,
60 sourceCode = context.getSourceCode();
61
62 //--------------------------------------------------------------------------
63 // Helpers
64 //--------------------------------------------------------------------------
65
66 /**
67 * Reports a semicolon error with appropriate location and message.
68 * @param {ASTNode} node The node with an extra or missing semicolon.
69 * @param {boolean} missing True if the semicolon is missing.
70 * @returns {void}
71 */
72 function report(node, missing) {
73 const lastToken = sourceCode.getLastToken(node);
74 let message,
75 fix,
76 loc = lastToken.loc;
77
78 if (!missing) {
79 message = "Missing semicolon.";
80 loc = loc.end;
81 fix = function(fixer) {
82 return fixer.insertTextAfter(lastToken, ";");
83 };
84 } else {
85 message = "Extra semicolon.";
86 loc = loc.start;
87 fix = function(fixer) {
88 return fixer.remove(lastToken);
89 };
90 }
91
92 context.report({
93 node,
94 loc,
95 message,
96 fix
97 });
98
99 }
100
101 /**
102 * Checks whether a token is a semicolon punctuator.
103 * @param {Token} token The token.
104 * @returns {boolean} True if token is a semicolon punctuator.
105 */
106 function isSemicolon(token) {
107 return (token.type === "Punctuator" && token.value === ";");
108 }
109
110 /**
111 * Check if a semicolon is unnecessary, only true if:
112 * - next token is on a new line and is not one of the opt-out tokens
113 * - next token is a valid statement divider
114 * @param {Token} lastToken last token of current node.
115 * @returns {boolean} whether the semicolon is unnecessary.
116 */
117 function isUnnecessarySemicolon(lastToken) {
118 if (!isSemicolon(lastToken)) {
119 return false;
120 }
121
122 const nextToken = sourceCode.getTokenAfter(lastToken);
123
124 if (!nextToken) {
125 return true;
126 }
127
128 const lastTokenLine = lastToken.loc.end.line;
129 const nextTokenLine = nextToken.loc.start.line;
130 const isOptOutToken = OPT_OUT_PATTERN.test(nextToken.value);
131 const isDivider = (nextToken.value === "}" || nextToken.value === ";");
132
133 return (lastTokenLine !== nextTokenLine && !isOptOutToken) || isDivider;
134 }
135
136 /**
137 * Checks a node to see if it's in a one-liner block statement.
138 * @param {ASTNode} node The node to check.
139 * @returns {boolean} whether the node is in a one-liner block statement.
140 */
141 function isOneLinerBlock(node) {
142 const nextToken = sourceCode.getTokenAfter(node);
143
144 if (!nextToken || nextToken.value !== "}") {
145 return false;
146 }
147
148 const parent = node.parent;
149
150 return parent && parent.type === "BlockStatement" &&
151 parent.loc.start.line === parent.loc.end.line;
152 }
153
154 /**
155 * Checks a node to see if it's followed by a semicolon.
156 * @param {ASTNode} node The node to check.
157 * @returns {void}
158 */
159 function checkForSemicolon(node) {
160 const lastToken = sourceCode.getLastToken(node);
161
162 if (never) {
163 if (isUnnecessarySemicolon(lastToken)) {
164 report(node, true);
165 }
166 } else {
167 if (!isSemicolon(lastToken)) {
168 if (!exceptOneLine || !isOneLinerBlock(node)) {
169 report(node);
170 }
171 } else {
172 if (exceptOneLine && isOneLinerBlock(node)) {
173 report(node, true);
174 }
175 }
176 }
177 }
178
179 /**
180 * Checks to see if there's a semicolon after a variable declaration.
181 * @param {ASTNode} node The node to check.
182 * @returns {void}
183 */
184 function checkForSemicolonForVariableDeclaration(node) {
185 const ancestors = context.getAncestors(),
186 parentIndex = ancestors.length - 1,
187 parent = ancestors[parentIndex];
188
189 if ((parent.type !== "ForStatement" || parent.init !== node) &&
190 (!/^For(?:In|Of)Statement/.test(parent.type) || parent.left !== node)
191 ) {
192 checkForSemicolon(node);
193 }
194 }
195
196 //--------------------------------------------------------------------------
197 // Public API
198 //--------------------------------------------------------------------------
199
200 return {
201 VariableDeclaration: checkForSemicolonForVariableDeclaration,
202 ExpressionStatement: checkForSemicolon,
203 ReturnStatement: checkForSemicolon,
204 ThrowStatement: checkForSemicolon,
205 DoWhileStatement: checkForSemicolon,
206 DebuggerStatement: checkForSemicolon,
207 BreakStatement: checkForSemicolon,
208 ContinueStatement: checkForSemicolon,
209 ImportDeclaration: checkForSemicolon,
210 ExportAllDeclaration: checkForSemicolon,
211 ExportNamedDeclaration(node) {
212 if (!node.declaration) {
213 checkForSemicolon(node);
214 }
215 },
216 ExportDefaultDeclaration(node) {
217 if (!/(?:Class|Function)Declaration/.test(node.declaration.type)) {
218 checkForSemicolon(node);
219 }
220 }
221 };
222
223 }
224};