@sveltium/eslint-rules 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js ADDED
@@ -0,0 +1,9 @@
1
+ import blankLineAfterBlockOpenIfLong from "./rules/blank-line-after-block-open-if-long";
2
+ import blankLineAfterObjectDeclaration from "./rules/blank-line-after-object-declaration";
3
+
4
+ export default {
5
+ rules: {
6
+ "blank-line-after-object-declaration": blankLineAfterObjectDeclaration,
7
+ "blank-line-after-block-open-if-long": blankLineAfterBlockOpenIfLong,
8
+ },
9
+ };
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@sveltium/eslint-rules",
3
+ "version": "0.1.0",
4
+ "description": "Custom ESLint rules for Sveltium projects",
5
+ "type": "module",
6
+ "main": "./index.js",
7
+ "exports": "./index.js",
8
+ "files": [
9
+ "index.js",
10
+ "rules"
11
+ ],
12
+ "keywords": [
13
+ "eslint",
14
+ "eslint-plugin",
15
+ "sveltium",
16
+ "code-style"
17
+ ],
18
+ "license": "MIT",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/OosukeRen/Sveltium.git",
22
+ "directory": "packages/eslint-rules"
23
+ },
24
+ "peerDependencies": {
25
+ "eslint": ">=9.0.0"
26
+ }
27
+ }
@@ -0,0 +1,223 @@
1
+ const ZERO = 0;
2
+ const ONE = 1;
3
+ const MAX_LINES_BEFORE_ERROR = 2;
4
+ const NOT_FOUND_INDEX = -1;
5
+ const RANGE_START_INDEX = 0;
6
+ const RANGE_END_INDEX = 1;
7
+ const DEFAULT_MIN_INNER_LINES = 2;
8
+ const DEFAULT_TARGETS = ["function", "if"];
9
+ const FUNCTION_TARGET = "function";
10
+ const IF_TARGET = "if";
11
+ const DOUBLE_NEWLINE = "\n\n";
12
+ const DEFAULT_INDENT = " ";
13
+
14
+ function getInnerLineCount(block) {
15
+
16
+
17
+
18
+ const lineSpan = block.loc.end.line - block.loc.start.line;
19
+ const innerLineCount = Math.max(ZERO, lineSpan - ONE);
20
+
21
+ return innerLineCount;
22
+ }
23
+
24
+ function hasBlankLineAfterOpenBrace(sourceCode, openBrace, firstInsideToken) {
25
+
26
+
27
+
28
+ const betweenText = sourceCode.text.slice(
29
+ openBrace.range[RANGE_END_INDEX],
30
+ firstInsideToken.range[RANGE_START_INDEX]
31
+ );
32
+ const newlineMatches = betweenText.match(/\n/g) || [];
33
+ const newlineCount = newlineMatches.length;
34
+ const hasBlankLine = newlineCount >= MAX_LINES_BEFORE_ERROR;
35
+
36
+ return hasBlankLine;
37
+ }
38
+
39
+ function makeFix(sourceCode, openBrace, firstInsideToken) {
40
+
41
+
42
+
43
+ const betweenText = sourceCode.text.slice(
44
+ openBrace.range[RANGE_END_INDEX],
45
+ firstInsideToken.range[RANGE_START_INDEX]
46
+ );
47
+
48
+ const firstNewlineIndex = betweenText.indexOf("\n");
49
+ let fixFunction = null;
50
+
51
+ if (firstNewlineIndex !== NOT_FOUND_INDEX) {
52
+ const insertPos = openBrace.range[RANGE_END_INDEX] + firstNewlineIndex + ONE;
53
+ fixFunction = (fixer) => fixer.insertTextAfterRange([insertPos, insertPos], "\n");
54
+ }
55
+
56
+ if (fixFunction === null) {
57
+ fixFunction = (fixer) => fixer.insertTextAfter(openBrace, `${DOUBLE_NEWLINE}${DEFAULT_INDENT}`);
58
+ }
59
+
60
+ return fixFunction;
61
+ }
62
+
63
+ const rule = {
64
+ meta: {
65
+ type: "layout",
66
+ docs: {
67
+ description:
68
+ "Require a blank line immediately after '{' when a block is longer than N inner lines.",
69
+ },
70
+ fixable: "whitespace",
71
+ schema: [
72
+ {
73
+ type: "object",
74
+ properties: {
75
+ minInnerLines: { type: "integer", minimum: ZERO },
76
+ targets: {
77
+ type: "array",
78
+ items: { enum: DEFAULT_TARGETS },
79
+ },
80
+ },
81
+ additionalProperties: false,
82
+ },
83
+ ],
84
+ messages: {
85
+ needBlank:
86
+ "Add a blank line after '{' for long blocks (more than {{min}} inner lines).",
87
+ },
88
+ },
89
+
90
+ create(context) {
91
+
92
+ const sourceCode = context.getSourceCode();
93
+ const options = context.options[ZERO] || {};
94
+ const minInnerLines = Number.isInteger(options.minInnerLines)
95
+ ? options.minInnerLines
96
+ : DEFAULT_MIN_INNER_LINES;
97
+ const targets = new Set(options.targets || DEFAULT_TARGETS);
98
+
99
+ function checkBlock(blockNode, reportNode) {
100
+
101
+
102
+
103
+ let shouldCheck = Boolean(blockNode && blockNode.loc);
104
+
105
+ if (shouldCheck) {
106
+ const innerLineCount = getInnerLineCount(blockNode);
107
+ shouldCheck = innerLineCount > minInnerLines;
108
+ }
109
+
110
+ if (shouldCheck) {
111
+
112
+
113
+
114
+ const openBraceToken = sourceCode.getFirstToken(blockNode);
115
+ const tokenAfterOpen = sourceCode.getTokenAfter(openBraceToken, {
116
+ includeComments: true,
117
+ });
118
+ const fallbackToken = sourceCode.getLastToken(blockNode);
119
+ const firstInsideToken = tokenAfterOpen || fallbackToken;
120
+
121
+ shouldCheck = Boolean(openBraceToken && firstInsideToken);
122
+
123
+ if (shouldCheck) {
124
+
125
+
126
+
127
+ const hasBlankLine = hasBlankLineAfterOpenBrace(
128
+ sourceCode,
129
+ openBraceToken,
130
+ firstInsideToken
131
+ );
132
+
133
+ if (!hasBlankLine) {
134
+
135
+
136
+
137
+ context.report({
138
+ node: reportNode,
139
+ messageId: "needBlank",
140
+ data: { min: String(minInnerLines) },
141
+ fix: makeFix(sourceCode, openBraceToken, firstInsideToken),
142
+ });
143
+ }
144
+ }
145
+ }
146
+ }
147
+
148
+ return {
149
+ FunctionDeclaration(node) {
150
+
151
+
152
+
153
+ let shouldCheck = targets.has(FUNCTION_TARGET);
154
+
155
+ if (shouldCheck) {
156
+
157
+
158
+
159
+ const hasBlockBody = Boolean(node.body && node.body.type === "BlockStatement");
160
+
161
+ if (hasBlockBody) {
162
+ checkBlock(node.body, node);
163
+ }
164
+ }
165
+ },
166
+ FunctionExpression(node) {
167
+
168
+
169
+
170
+ let shouldCheck = targets.has(FUNCTION_TARGET);
171
+
172
+ if (shouldCheck) {
173
+
174
+
175
+
176
+ const hasBlockBody = Boolean(node.body && node.body.type === "BlockStatement");
177
+
178
+ if (hasBlockBody) {
179
+ checkBlock(node.body, node);
180
+ }
181
+ }
182
+ },
183
+ ArrowFunctionExpression(node) {
184
+
185
+
186
+
187
+ let shouldCheck = targets.has(FUNCTION_TARGET);
188
+
189
+ if (shouldCheck) {
190
+
191
+
192
+
193
+ const hasBlockBody = Boolean(node.body && node.body.type === "BlockStatement");
194
+
195
+ if (hasBlockBody) {
196
+ checkBlock(node.body, node);
197
+ }
198
+ }
199
+ },
200
+ IfStatement(node) {
201
+
202
+
203
+
204
+ let shouldCheck = targets.has(IF_TARGET);
205
+
206
+ if (shouldCheck) {
207
+
208
+
209
+
210
+ const hasBlockBody = Boolean(
211
+ node.consequent && node.consequent.type === "BlockStatement"
212
+ );
213
+
214
+ if (hasBlockBody) {
215
+ checkBlock(node.consequent, node);
216
+ }
217
+ }
218
+ },
219
+ };
220
+ },
221
+ };
222
+
223
+ export default rule;
@@ -0,0 +1,87 @@
1
+ const RANGE_START_INDEX = 0;
2
+ const RANGE_END_INDEX = 1;
3
+ const MIN_NEWLINES_FOR_BLANK_LINE = 2;
4
+
5
+ function hasBlankLineBetweenTokens(sourceCode, leftToken, rightToken) {
6
+ const betweenText = sourceCode.text.slice(
7
+ leftToken.range[RANGE_END_INDEX],
8
+ rightToken.range[RANGE_START_INDEX]
9
+ );
10
+ const newlineMatches = betweenText.match(/\n/g) || [];
11
+ const newlineCount = newlineMatches.length;
12
+ const hasBlankLine = newlineCount >= MIN_NEWLINES_FOR_BLANK_LINE;
13
+
14
+ return hasBlankLine;
15
+ }
16
+
17
+ function makeBlankLineAfterTokenFix(leftToken) {
18
+ const fixFunction = (fixer) => fixer.insertTextAfter(leftToken, "\n");
19
+
20
+ return fixFunction;
21
+ }
22
+
23
+ function hasObjectInitializer(declarations) {
24
+ let hasMatch = false;
25
+
26
+ for (const declaration of declarations) {
27
+ const initializer = declaration.init;
28
+ const isObjectInit = Boolean(initializer && initializer.type === "ObjectExpression");
29
+
30
+ if (isObjectInit) {
31
+ hasMatch = true;
32
+ }
33
+ }
34
+
35
+ return hasMatch;
36
+ }
37
+
38
+ const rule = {
39
+ meta: {
40
+ type: "layout",
41
+ docs: {
42
+ description: "Require a blank line after variable declarations with object literals.",
43
+ },
44
+ fixable: "whitespace",
45
+ schema: [],
46
+ messages: {
47
+ needBlank: "Add a blank line after object literal declarations.",
48
+ },
49
+ },
50
+
51
+ create(context) {
52
+ const sourceCode = context.getSourceCode();
53
+
54
+ function checkDeclaration(node) {
55
+ const hasObjectInit = hasObjectInitializer(node.declarations);
56
+ let shouldCheck = hasObjectInit;
57
+ let lastToken = null;
58
+ let nextToken = null;
59
+
60
+ if (shouldCheck) {
61
+ lastToken = sourceCode.getLastToken(node);
62
+ nextToken = sourceCode.getTokenAfter(node, { includeComments: true });
63
+ shouldCheck = Boolean(lastToken && nextToken);
64
+ }
65
+
66
+ if (shouldCheck) {
67
+ const hasBlankLine = hasBlankLineBetweenTokens(sourceCode, lastToken, nextToken);
68
+
69
+ if (!hasBlankLine) {
70
+ context.report({
71
+ node,
72
+ messageId: "needBlank",
73
+ fix: makeBlankLineAfterTokenFix(lastToken),
74
+ });
75
+ }
76
+ }
77
+ }
78
+
79
+ return {
80
+ VariableDeclaration(node) {
81
+ checkDeclaration(node);
82
+ },
83
+ };
84
+ },
85
+ };
86
+
87
+ export default rule;