UNPKG

6.3 kBJavaScriptView Raw
1/**
2 * @fileoverview Require or disallow newlines around directives.
3 * @author Kai Cataldo
4 */
5
6"use strict";
7
8const astUtils = require("../ast-utils");
9
10//------------------------------------------------------------------------------
11// Rule Definition
12//------------------------------------------------------------------------------
13
14module.exports = {
15 meta: {
16 docs: {
17 description: "require or disallow newlines around directives",
18 category: "Stylistic Issues",
19 recommended: false
20 },
21 schema: [{
22 oneOf: [
23 {
24 enum: ["always", "never"]
25 },
26 {
27 type: "object",
28 properties: {
29 before: {
30 enum: ["always", "never"]
31 },
32 after: {
33 enum: ["always", "never"]
34 },
35 },
36 additionalProperties: false,
37 minProperties: 2
38 }
39 ]
40 }]
41 },
42
43 create(context) {
44 const sourceCode = context.getSourceCode();
45 const config = context.options[0] || "always";
46 const expectLineBefore = typeof config === "string" ? config : config.before;
47 const expectLineAfter = typeof config === "string" ? config : config.after;
48
49 //--------------------------------------------------------------------------
50 // Helpers
51 //--------------------------------------------------------------------------
52
53 /**
54 * Check if node is preceded by a blank newline.
55 * @param {ASTNode} node Node to check.
56 * @returns {boolean} Whether or not the passed in node is preceded by a blank newline.
57 */
58 function hasNewlineBefore(node) {
59 const tokenBefore = sourceCode.getTokenOrCommentBefore(node);
60 const tokenLineBefore = tokenBefore ? tokenBefore.loc.end.line : 0;
61
62 return node.loc.start.line - tokenLineBefore >= 2;
63 }
64
65 /**
66 * Check if node is followed by a blank newline.
67 * @param {ASTNode} node Node to check.
68 * @returns {boolean} Whether or not the passed in node is followed by a blank newline.
69 */
70 function hasNewlineAfter(node) {
71 const tokenAfter = sourceCode.getTokenOrCommentAfter(node);
72
73 return tokenAfter.loc.start.line - node.loc.end.line >= 2;
74 }
75
76 /**
77 * Report errors for newlines around directives.
78 * @param {ASTNode} node Node to check.
79 * @param {string} location Whether the error was found before or after the directive.
80 * @param {boolean} expected Whether or not a newline was expected or unexpected.
81 * @returns {void}
82 */
83 function reportError(node, location, expected) {
84 context.report({
85 node,
86 message: "{{expected}} newline {{location}} \"{{value}}\" directive.",
87 data: {
88 expected: expected ? "Expected" : "Unexpected",
89 value: node.expression.value,
90 location
91 }
92 });
93 }
94
95 /**
96 * Check lines around directives in node
97 * @param {ASTNode} node - node to check
98 * @returns {void}
99 */
100 function checkDirectives(node) {
101 const directives = astUtils.getDirectivePrologue(node);
102
103 if (!directives.length) {
104 return;
105 }
106
107 const firstDirective = directives[0];
108 const hasTokenOrCommentBefore = !!sourceCode.getTokenOrCommentBefore(firstDirective);
109
110 // Only check before the first directive if it is preceded by a comment or if it is at the top of
111 // the file and expectLineBefore is set to "never". This is to not force a newline at the top of
112 // the file if there are no comments as well as for compatibility with padded-blocks.
113 if (
114 firstDirective.leadingComments && firstDirective.leadingComments.length ||
115
116 // Shebangs are not added to leading comments but are accounted for by the following.
117 node.type === "Program" && hasTokenOrCommentBefore
118 ) {
119 if (expectLineBefore === "always" && !hasNewlineBefore(firstDirective)) {
120 reportError(firstDirective, "before", true);
121 }
122
123 if (expectLineBefore === "never" && hasNewlineBefore(firstDirective)) {
124 reportError(firstDirective, "before", false);
125 }
126 } else if (
127 node.type === "Program" &&
128 expectLineBefore === "never" &&
129 !hasTokenOrCommentBefore &&
130 hasNewlineBefore(firstDirective)
131 ) {
132 reportError(firstDirective, "before", false);
133 }
134
135 const lastDirective = directives[directives.length - 1];
136 const statements = node.type === "Program" ? node.body : node.body.body;
137
138 // Do not check after the last directive if the body only
139 // contains a directive prologue and isn't followed by a comment to ensure
140 // this rule behaves well with padded-blocks.
141 if (lastDirective === statements[statements.length - 1] && !lastDirective.trailingComments) {
142 return;
143 }
144
145 if (expectLineAfter === "always" && !hasNewlineAfter(lastDirective)) {
146 reportError(lastDirective, "after", true);
147 }
148
149 if (expectLineAfter === "never" && hasNewlineAfter(lastDirective)) {
150 reportError(lastDirective, "after", false);
151 }
152 }
153
154 //--------------------------------------------------------------------------
155 // Public
156 //--------------------------------------------------------------------------
157
158 return {
159 Program: checkDirectives,
160 FunctionDeclaration: checkDirectives,
161 FunctionExpression: checkDirectives,
162 ArrowFunctionExpression: checkDirectives
163 };
164 }
165};