UNPKG

4.33 kBJavaScriptView Raw
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("../ast-utils");
13
14//------------------------------------------------------------------------------
15// Rule Definition
16//------------------------------------------------------------------------------
17
18const SELECTOR = `:matches(${
19 [
20 "BreakStatement", "ContinueStatement", "DebuggerStatement",
21 "DoWhileStatement", "EmptyStatement", "ExportAllDeclaration",
22 "ExportDefaultDeclaration", "ExportNamedDeclaration",
23 "ExpressionStatement", "ImportDeclaration", "ReturnStatement",
24 "ThrowStatement", "VariableDeclaration"
25 ].join(",")
26})`;
27
28module.exports = {
29 meta: {
30 docs: {
31 description: "enforce location of semicolons",
32 category: "Stylistic Issues",
33 recommended: false
34 },
35 schema: [{ enum: ["last", "first"] }],
36 fixable: "whitespace"
37 },
38
39 create(context) {
40 const sourceCode = context.getSourceCode();
41 const option = context.options[0] || "last";
42
43 /**
44 * Check whether comments exist between the given 2 tokens.
45 * @param {Token} left The left token to check.
46 * @param {Token} right The right token to check.
47 * @returns {boolean} `true` if comments exist between the given 2 tokens.
48 */
49 function commentsExistBetween(left, right) {
50 return sourceCode.getFirstTokenBetween(
51 left,
52 right,
53 {
54 includeComments: true,
55 filter: astUtils.isCommentToken
56 }
57 ) !== null;
58 }
59
60 /**
61 * Check the given semicolon token.
62 * @param {Token} semiToken The semicolon token to check.
63 * @param {"first"|"last"} expected The expected location to check.
64 * @returns {void}
65 */
66 function check(semiToken, expected) {
67 const prevToken = sourceCode.getTokenBefore(semiToken);
68 const nextToken = sourceCode.getTokenAfter(semiToken);
69 const prevIsSameLine = !prevToken || astUtils.isTokenOnSameLine(prevToken, semiToken);
70 const nextIsSameLine = !nextToken || astUtils.isTokenOnSameLine(semiToken, nextToken);
71
72 if ((expected === "last" && !prevIsSameLine) || (expected === "first" && !nextIsSameLine)) {
73 context.report({
74 loc: semiToken.loc,
75 message: "Expected this semicolon to be at {{pos}}.",
76 data: {
77 pos: (expected === "last")
78 ? "the end of the previous line"
79 : "the beginning of the next line"
80 },
81 fix(fixer) {
82 if (prevToken && nextToken && commentsExistBetween(prevToken, nextToken)) {
83 return null;
84 }
85
86 const start = prevToken ? prevToken.range[1] : semiToken.range[0];
87 const end = nextToken ? nextToken.range[0] : semiToken.range[1];
88 const text = (expected === "last") ? ";\n" : "\n;";
89
90 return fixer.replaceTextRange([start, end], text);
91 }
92 });
93 }
94 }
95
96 return {
97 [SELECTOR](node) {
98 const lastToken = sourceCode.getLastToken(node);
99
100 if (astUtils.isSemicolonToken(lastToken)) {
101 check(lastToken, option);
102 }
103 },
104
105 ForStatement(node) {
106 const firstSemi = node.init && sourceCode.getTokenAfter(node.init, astUtils.isSemicolonToken);
107 const secondSemi = node.test && sourceCode.getTokenAfter(node.test, astUtils.isSemicolonToken);
108
109 if (firstSemi) {
110 check(firstSemi, "last");
111 }
112 if (secondSemi) {
113 check(secondSemi, "last");
114 }
115 }
116 };
117 }
118};