@tuomashatakka/eslint-config 2.6.2 → 3.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.
Files changed (45) hide show
  1. package/.github/workflows/ci.yml +23 -0
  2. package/.github/workflows/publish.yml +45 -0
  3. package/AGENTS.md +29 -0
  4. package/bun.lock +60 -102
  5. package/eslint.config.mjs +1 -0
  6. package/index.mjs +7 -21
  7. package/package.json +11 -19
  8. package/plugins/no-inline-types/index.mjs +11 -0
  9. package/plugins/no-inline-types/rules/no-inline-multiline-types.mjs +181 -0
  10. package/plugins/omit/index.mjs +8 -0
  11. package/plugins/omit/rules/omit-unnecessary-parens-brackets.mjs +329 -0
  12. package/plugins/omit/utils.mjs +91 -0
  13. package/plugins/react-strict/index.mjs +19 -0
  14. package/plugins/react-strict/rules/jsx-prop-layout.mjs +100 -0
  15. package/plugins/react-strict/rules/no-complex-jsx-map.mjs +66 -0
  16. package/plugins/react-strict/rules/no-jsx-value-calculations.mjs +99 -0
  17. package/plugins/react-strict/rules/no-nested-divs.mjs +59 -0
  18. package/plugins/react-strict/rules/no-style-prop.mjs +43 -0
  19. package/plugins/react-strict/rules/prefer-no-use-effect.mjs +26 -0
  20. package/plugins/whitespaced/index.mjs +15 -0
  21. package/plugins/whitespaced/rules/aligned-assignments.mjs +385 -0
  22. package/plugins/whitespaced/rules/block-padding.mjs +289 -0
  23. package/plugins/whitespaced/rules/class-property-grouping.mjs +370 -0
  24. package/plugins/whitespaced/rules/consistent-line-spacing.mjs +266 -0
  25. package/plugins/whitespaced/rules/multiline-format.mjs +533 -0
  26. package/rules.mjs +101 -95
  27. package/test/fixtures/basic-javascript.js +5 -4
  28. package/test/fixtures/complex-patterns.ts +9 -7
  29. package/test/fixtures/edge-cases.js +12 -7
  30. package/test/fixtures/jsx-formatting.jsx +5 -4
  31. package/test/fixtures/omit-parens.invalid.ts +12 -0
  32. package/test/fixtures/omit-parens.valid.ts +13 -0
  33. package/test/fixtures/react-component.tsx +7 -6
  34. package/test/fixtures/react-strict.invalid.tsx +31 -0
  35. package/test/fixtures/react-strict.valid.tsx +76 -0
  36. package/test/fixtures/whitespaced-docstring.invalid.ts +10 -0
  37. package/test/fixtures/whitespaced-docstring.valid.ts +16 -0
  38. package/test/fixtures/whitespaced-members.invalid.ts +22 -0
  39. package/test/fixtures/whitespaced-members.valid.ts +13 -0
  40. package/test/fixtures/whitespaced-multiline.invalid.ts +8 -0
  41. package/test/fixtures/whitespaced-multiline.valid.ts +15 -0
  42. package/test/fixtures/whitespaced-types.valid.ts +5 -0
  43. package/test/fixtures/whitespaced.valid.ts +45 -0
  44. package/test/format-cases.mjs +13 -14
  45. package/test/test-runner.mjs +128 -47
@@ -0,0 +1,370 @@
1
+ /**
2
+ * @fileoverview ESLint rule to enforce grouping of class properties
3
+ * @author tuomashatakka
4
+ */
5
+
6
+ 'use strict'
7
+
8
+
9
+ /** @type {import('eslint').Rule.RuleModule} */
10
+ export default {
11
+ meta: {
12
+ type: "suggestion",
13
+ docs: {
14
+ description: "Enforce grouping of class properties",
15
+ category: "Stylistic Issues",
16
+ recommended: false,
17
+ },
18
+ fixable: "code",
19
+ schema: [
20
+ {
21
+ type: "object",
22
+ properties: {
23
+ groups: {
24
+ type: "array",
25
+ items: {
26
+ type: "object",
27
+ properties: {
28
+ name: { type: "string" },
29
+ types: {
30
+ type: "array",
31
+ items: { type: "string" },
32
+ },
33
+ matches: {
34
+ type: "array",
35
+ items: { type: "string" },
36
+ },
37
+ order: { type: "integer", minimum: 0 },
38
+ },
39
+ required: ["name", "order"],
40
+ additionalProperties: false,
41
+ },
42
+ default: [
43
+ {
44
+ name: "static-properties",
45
+ types: ["ClassProperty"],
46
+ matches: ["static"],
47
+ order: 0,
48
+ },
49
+ {
50
+ name: "static-methods",
51
+ types: ["MethodDefinition"],
52
+ matches: ["static"],
53
+ order: 1,
54
+ },
55
+ {
56
+ name: "instance-properties",
57
+ types: ["ClassProperty"],
58
+ matches: [],
59
+ order: 2,
60
+ },
61
+ {
62
+ name: "constructor",
63
+ types: ["MethodDefinition"],
64
+ matches: ["constructor"],
65
+ order: 3,
66
+ },
67
+ {
68
+ name: "instance-methods",
69
+ types: ["MethodDefinition"],
70
+ matches: [],
71
+ order: 4,
72
+ },
73
+ ],
74
+ },
75
+ paddingBetweenGroups: {
76
+ type: "integer",
77
+ minimum: 0,
78
+ default: 1,
79
+ },
80
+ enforceAlphabeticalSorting: {
81
+ type: "boolean",
82
+ default: false,
83
+ },
84
+ },
85
+ additionalProperties: false,
86
+ },
87
+ ],
88
+ messages: {
89
+ wrongGroupOrder:
90
+ "Class member '{{member}}' should be in group '{{expectedGroup}}' ({{expectedGroupOrder}}) but is in group '{{actualGroup}}' ({{actualGroupOrder}}).",
91
+ wrongAlphabeticalOrder:
92
+ "Class members in the same group should be ordered alphabetically. '{{memberA}}' should come before '{{memberB}}'.",
93
+ incorrectPaddingBetweenGroups:
94
+ "Expected {{expected}} empty {{lineText}} between class member groups, but found {{actual}}."
95
+ },
96
+ },
97
+ create(context) {
98
+ const sourceCode = context.getSourceCode();
99
+ const options = context.options[0] || {};
100
+
101
+ // Get configured options with defaults
102
+ const groups = options.groups || [
103
+ {
104
+ name: "static-properties",
105
+ types: ["ClassProperty"],
106
+ matches: ["static"],
107
+ order: 0,
108
+ },
109
+ {
110
+ name: "static-methods",
111
+ types: ["MethodDefinition"],
112
+ matches: ["static"],
113
+ order: 1,
114
+ },
115
+ {
116
+ name: "instance-properties",
117
+ types: ["ClassProperty"],
118
+ matches: [],
119
+ order: 2,
120
+ },
121
+ {
122
+ name: "constructor",
123
+ types: ["MethodDefinition"],
124
+ matches: ["constructor"],
125
+ order: 3,
126
+ },
127
+ {
128
+ name: "instance-methods",
129
+ types: ["MethodDefinition"],
130
+ matches: [],
131
+ order: 4,
132
+ },
133
+ ];
134
+
135
+ const paddingBetweenGroups = options.paddingBetweenGroups !== undefined ? options.paddingBetweenGroups : 1;
136
+ const enforceAlphabeticalSorting = options.enforceAlphabeticalSorting !== undefined ? options.enforceAlphabeticalSorting : false;
137
+
138
+ /**
139
+ * Determine which group a class member belongs to
140
+ * @param {ASTNode} node The class member node
141
+ * @returns {Object|null} The group object or null if not matched
142
+ */
143
+ function getMemberGroup(node) {
144
+ let nodeType = node.type;
145
+
146
+ // Normalize TypeScript property types to match ESLint's ClassProperty
147
+ if (nodeType === "PropertyDefinition" || nodeType === "TSPropertyDefinition") {
148
+ nodeType = "ClassProperty";
149
+ }
150
+
151
+ // For each group, check if the node matches the group criteria
152
+ for (const group of groups) {
153
+ // Skip if the node type doesn't match any in the group's types
154
+ if (!group.types.includes(nodeType)) {
155
+ continue;
156
+ }
157
+
158
+ // For MethodDefinition nodes
159
+ if (nodeType === "MethodDefinition") {
160
+ const isConstructor = node.kind === "constructor";
161
+ const isStatic = !!node.static;
162
+
163
+ // If this is a constructor and the group is for constructors
164
+ if (isConstructor && group.matches.includes("constructor")) {
165
+ return group;
166
+ }
167
+
168
+ // If this is a static method and the group is for static methods
169
+ if (isStatic && group.matches.includes("static")) {
170
+ return group;
171
+ }
172
+
173
+ // If this is not static and not a constructor and the group is for regular instance methods
174
+ if (!isStatic && !isConstructor && !group.matches.includes("static") && !group.matches.includes("constructor")) {
175
+ return group;
176
+ }
177
+ }
178
+
179
+ // For ClassProperty nodes
180
+ if (nodeType === "ClassProperty") {
181
+ const isStatic = !!node.static;
182
+
183
+ // If this is a static property and the group is for static properties
184
+ if (isStatic && group.matches.includes("static")) {
185
+ return group;
186
+ }
187
+
188
+ // If this is not static and the group is for regular instance properties
189
+ if (!isStatic && !group.matches.includes("static")) {
190
+ return group;
191
+ }
192
+ }
193
+ }
194
+
195
+ return null;
196
+ }
197
+
198
+ /**
199
+ * Get the member name for a node
200
+ * @param {ASTNode} node The class member node
201
+ * @returns {string} The member name
202
+ */
203
+ function getMemberName(node) {
204
+ if (node.type === "MethodDefinition" || node.type === "ClassProperty" ||
205
+ node.type === "PropertyDefinition" || node.type === "TSPropertyDefinition") {
206
+ if (node.key.type === "Identifier") {
207
+ return node.key.name;
208
+ } else if (node.key.type === "Literal") {
209
+ return String(node.key.value);
210
+ }
211
+ }
212
+ return "";
213
+ }
214
+
215
+ /**
216
+ * Check if members are in correct group order
217
+ * @param {Array<Object>} members Array of { node, group } objects
218
+ */
219
+ function checkGroupOrder(members) {
220
+ let lastGroupOrder = -1;
221
+ let lastGroup = null;
222
+
223
+ for (let i = 0; i < members.length; i++) {
224
+ const { node, group } = members[i];
225
+ if (!group) continue;
226
+
227
+ const currentGroupOrder = group.order;
228
+ const memberName = getMemberName(node);
229
+
230
+ // Check if group order is correct
231
+ if (currentGroupOrder < lastGroupOrder) {
232
+ const lastGroupInfo = lastGroup;
233
+
234
+ context.report({
235
+ node,
236
+ messageId: "wrongGroupOrder",
237
+ data: {
238
+ member: memberName,
239
+ expectedGroup: lastGroupInfo.name,
240
+ expectedGroupOrder: lastGroupInfo.order,
241
+ actualGroup: group.name,
242
+ actualGroupOrder: group.order,
243
+ },
244
+ });
245
+ }
246
+
247
+ lastGroupOrder = currentGroupOrder;
248
+ lastGroup = group;
249
+ }
250
+ }
251
+
252
+ /**
253
+ * Check if members within the same group are sorted alphabetically
254
+ * @param {Array<Object>} members Array of { node, group } objects
255
+ */
256
+ function checkAlphabeticalOrder(members) {
257
+ if (!enforceAlphabeticalSorting) return;
258
+
259
+ // Group members by their group
260
+ const groupedMembers = {};
261
+
262
+ for (const member of members) {
263
+ if (!member.group) continue;
264
+
265
+ const groupName = member.group.name;
266
+ if (!groupedMembers[groupName]) {
267
+ groupedMembers[groupName] = [];
268
+ }
269
+
270
+ groupedMembers[groupName].push(member);
271
+ }
272
+
273
+ // Check each group for alphabetical ordering
274
+ for (const groupName in groupedMembers) {
275
+ const groupMembers = groupedMembers[groupName];
276
+
277
+ for (let i = 1; i < groupMembers.length; i++) {
278
+ const prevNode = groupMembers[i - 1].node;
279
+ const currentNode = groupMembers[i].node;
280
+
281
+ const prevName = getMemberName(prevNode);
282
+ const currentName = getMemberName(currentNode);
283
+
284
+ if (prevName && currentName && prevName.localeCompare(currentName) > 0) {
285
+ context.report({
286
+ node: currentNode,
287
+ messageId: "wrongAlphabeticalOrder",
288
+ data: {
289
+ memberA: currentName,
290
+ memberB: prevName,
291
+ },
292
+ });
293
+ }
294
+ }
295
+ }
296
+ }
297
+
298
+ /**
299
+ * Check padding between different groups
300
+ * @param {Array<Object>} members Array of { node, group } objects
301
+ */
302
+ function checkGroupPadding(members) {
303
+ if (paddingBetweenGroups <= 0 || members.length < 2) return;
304
+
305
+ let currentGroup = null;
306
+
307
+ for (let i = 1; i < members.length; i++) {
308
+ const prevMember = members[i - 1];
309
+ const currentMember = members[i];
310
+
311
+ // Skip if either member doesn't have a valid group
312
+ if (!prevMember.group || !currentMember.group) continue;
313
+
314
+ // If the groups are different, check padding
315
+ if (prevMember.group.name !== currentMember.group.name) {
316
+ const prevNode = prevMember.node;
317
+ const currentNode = currentMember.node;
318
+
319
+ const prevNodeEnd = prevNode.loc.end.line;
320
+ const currentNodeStart = currentNode.loc.start.line;
321
+ const blankLines = currentNodeStart - prevNodeEnd - 1;
322
+
323
+ if (blankLines !== paddingBetweenGroups) {
324
+ context.report({
325
+ node: currentNode,
326
+ messageId: "incorrectPaddingBetweenGroups",
327
+ data: {
328
+ expected: paddingBetweenGroups,
329
+ actual: blankLines,
330
+ lineText: paddingBetweenGroups === 1 ? "line" : "lines",
331
+ },
332
+ fix(fixer) {
333
+ const endOfPrevNode = sourceCode.getLastToken(prevNode);
334
+ const startOfCurrentNode = sourceCode.getFirstToken(currentNode);
335
+ const range = [
336
+ endOfPrevNode.range[1],
337
+ startOfCurrentNode.range[0],
338
+ ];
339
+
340
+ // Create the desired padding
341
+ const newLines = "\n".repeat(paddingBetweenGroups + 1); // +1 for end of current line
342
+
343
+ return fixer.replaceTextRange(range, newLines);
344
+ },
345
+ });
346
+ }
347
+ }
348
+ }
349
+ }
350
+
351
+ return {
352
+ ClassBody(node) {
353
+ if (!node.body || node.body.length <= 1) {
354
+ return;
355
+ }
356
+
357
+ // Map each node to its group
358
+ const members = node.body.map(memberNode => ({
359
+ node: memberNode,
360
+ group: getMemberGroup(memberNode),
361
+ }));
362
+
363
+ // Run checks
364
+ checkGroupOrder(members);
365
+ checkAlphabeticalOrder(members);
366
+ checkGroupPadding(members);
367
+ },
368
+ };
369
+ },
370
+ };
@@ -0,0 +1,266 @@
1
+ /**
2
+ * @fileoverview ESLint rule to enforce consistent line spacing before and after statements
3
+ * @author tuomashatakka
4
+ */
5
+
6
+ export default {
7
+ meta: {
8
+ type: 'layout',
9
+ docs: {
10
+ description: 'Enforce consistent line spacing before and after statements',
11
+ category: "Stylistic Issues",
12
+ recommended: false,
13
+ },
14
+ fixable: 'whitespace',
15
+ schema: [{
16
+ type: "object",
17
+ properties: {
18
+ beforeImports: { type: 'integer', minimum: 0, default: 1 },
19
+ afterImports: { type: 'integer', minimum: 0, default: 1 },
20
+ beforeExports: { type: 'integer', minimum: 0, default: 1 },
21
+ afterExports: { type: 'integer', minimum: 0, default: 1 },
22
+ beforeClass: { type: 'integer', minimum: 0, default: 2 },
23
+ afterClass: { type: 'integer', minimum: 0, default: 2 },
24
+ beforeFunction: { type: 'integer', minimum: 0, default: 2 },
25
+ afterFunction: { type: 'integer', minimum: 0, default: 2 },
26
+ beforeComment: { type: 'integer', minimum: 0, default: 1 },
27
+ ignoreTopLevelCode: { type: 'boolean', default: false },
28
+ skipImportGroups: { type: 'boolean', default: true },
29
+ docstringSpacing: { type: 'integer', minimum: 0, default: 0 },
30
+ },
31
+ additionalProperties: false,
32
+ },],
33
+ messages: {
34
+ missingLinesBefore: "Expected {{expected}} empty {{lineText}} before {{nodeType}}, but found {{actual}}.",
35
+ missingLinesAfter: "Expected {{expected}} empty {{lineText}} after {{nodeType}}, but found {{actual}}.",
36
+ incorrectDocstringSpacing: "Expected {{expected}} empty {{lineText}} between docstring and {{nodeType}}, but found {{actual}}.",
37
+ },
38
+ },
39
+ create (context) {
40
+ const sourceCode = context.sourceCode || context.getSourceCode()
41
+ const options = context.options[0] || {}
42
+
43
+ const beforeImports = options.beforeImports !== undefined ? options.beforeImports : 1
44
+ const afterImports = options.afterImports !== undefined ? options.afterImports : 1
45
+ const beforeExports = options.beforeExports !== undefined ? options.beforeExports : 1
46
+ const afterExports = options.afterExports !== undefined ? options.afterExports : 1
47
+ const beforeClass = options.beforeClass !== undefined ? options.beforeClass : 2
48
+ const afterClass = options.afterClass !== undefined ? options.afterClass : 2
49
+ const beforeFunction = options.beforeFunction !== undefined ? options.beforeFunction : 2
50
+ const afterFunction = options.afterFunction !== undefined ? options.afterFunction : 2
51
+ const beforeComment = options.beforeComment !== undefined ? options.beforeComment : 1
52
+ const ignoreTopLevelCode = options.ignoreTopLevelCode !== undefined ? options.ignoreTopLevelCode : false
53
+ const skipImportGroups = options.skipImportGroups !== undefined ? options.skipImportGroups : true
54
+ const docstringSpacing = options.docstringSpacing !== undefined ? options.docstringSpacing : 0
55
+
56
+
57
+ function isFirstInParent (node) {
58
+ const parent = node.parent
59
+ if (!parent || !parent.body || !parent.body.length)
60
+ return true
61
+ return parent.body[0] === node
62
+ }
63
+
64
+
65
+ function isLastInParent (node) {
66
+ const parent = node.parent
67
+ if (!parent || !parent.body || !parent.body.length)
68
+ return true
69
+ return parent.body[parent.body.length - 1] === node
70
+ }
71
+
72
+
73
+ function isTopLevel (node) {
74
+ return node.parent && node.parent.type === 'Program'
75
+ }
76
+
77
+
78
+ function isImport (node) {
79
+ return node.type === 'ImportDeclaration'
80
+ }
81
+
82
+
83
+ /**
84
+ * Returns the first token of the docstring immediately preceding `node`
85
+ * (no blank lines between docstring end and node start), or null.
86
+ *
87
+ * Detects two docstring forms:
88
+ * • Block comment /* ... *\/ on the line immediately before node
89
+ * • One or more consecutive // line comments on the lines immediately before node
90
+ */
91
+ function getDocstringBefore (node) {
92
+ const prev = sourceCode.getTokenBefore(node, { includeComments: true })
93
+ if (!prev)
94
+ return null
95
+
96
+ // Block comment touching the node (0 blank lines between)
97
+ if (prev.type === 'Block' && node.loc.start.line <= prev.loc.end.line + 1)
98
+ return prev
99
+
100
+ // Chain of consecutive line comments touching the node
101
+ if (prev.type === 'Line' && node.loc.start.line <= prev.loc.end.line + 1) {
102
+ let first = prev
103
+ for (;;) {
104
+ const p = sourceCode.getTokenBefore(first, { includeComments: true })
105
+ if (!p || p.type !== 'Line' || first.loc.start.line > p.loc.end.line + 1)
106
+ break
107
+ first = p
108
+ }
109
+ return first
110
+ }
111
+
112
+ return null
113
+ }
114
+
115
+
116
+ /**
117
+ * Checks blank lines before `node` (or before its attached docstring).
118
+ *
119
+ * Key behaviour: if a docstring immediately precedes `node`, we measure
120
+ * `requiredLines` blank lines from the previous non-comment code token to
121
+ * the **start of the docstring**, not to the node keyword itself.
122
+ * This way `beforeFunction: 2` works correctly even when the function has
123
+ * a leading JSDoc / line-comment block.
124
+ */
125
+ function checkLinesBefore (node, requiredLines, nodeType) {
126
+ if (isFirstInParent(node) && ignoreTopLevelCode && isTopLevel(node))
127
+ return
128
+
129
+ const docstring = getDocstringBefore(node)
130
+ const effectiveNode = docstring ?? node // measure TO this node's start
131
+
132
+ // Previous non-comment code token (before the docstring if present)
133
+ const prevCode = sourceCode.getTokenBefore(effectiveNode, {
134
+ includeComments: false,
135
+ })
136
+ if (!prevCode)
137
+ return
138
+
139
+ // Skip consecutive imports when skipImportGroups is on
140
+ if (skipImportGroups && isImport(node)) {
141
+ const prevNode = sourceCode.getNodeByRangeIndex(prevCode.range[0])
142
+ if (prevNode && isImport(prevNode))
143
+ return
144
+ }
145
+
146
+ const blankLines = effectiveNode.loc.start.line - prevCode.loc.end.line - 1
147
+
148
+ if (blankLines !== requiredLines)
149
+ context.report({
150
+ node: effectiveNode,
151
+ messageId: 'missingLinesBefore',
152
+ data: {
153
+ expected: requiredLines,
154
+ actual: blankLines,
155
+ nodeType,
156
+ lineText: requiredLines === 1 ? 'line' : 'lines',
157
+ },
158
+ fix(fixer) {
159
+ return fixer.replaceTextRange(
160
+ [ prevCode.range[1], effectiveNode.range[0] ],
161
+ '\n'.repeat(requiredLines + 1)
162
+ );
163
+ },
164
+ });
165
+
166
+ // Also enforce spacing between docstring end and node start
167
+ if (docstring) {
168
+ const blankBetween = node.loc.start.line - docstring.loc.end.line - 1
169
+ if (blankBetween !== docstringSpacing)
170
+ context.report({
171
+ node,
172
+ messageId: 'incorrectDocstringSpacing',
173
+ data: {
174
+ expected: docstringSpacing,
175
+ actual: blankBetween,
176
+ nodeType,
177
+ lineText: docstringSpacing === 1 ? 'line' : 'lines',
178
+ },
179
+ fix(fixer) {
180
+ return fixer.replaceTextRange(
181
+ [ docstring.range[1], node.range[0] ],
182
+ '\n'.repeat(docstringSpacing + 1)
183
+ );
184
+ },
185
+ });
186
+ }
187
+ }
188
+
189
+
190
+ function checkLinesAfter (node, requiredLines, nodeType) {
191
+ if (isLastInParent(node) && (ignoreTopLevelCode && isTopLevel(node)))
192
+ return
193
+
194
+ const nextToken = sourceCode.getTokenAfter(node, {
195
+ includeComments: false,
196
+ })
197
+ const nextNode = nextToken ? sourceCode.getNodeByRangeIndex(nextToken.range[0]) : null
198
+
199
+ if (skipImportGroups && isImport(node) && nextNode && isImport(nextNode))
200
+ return
201
+
202
+ const nextTokenWithComments = sourceCode.getTokenAfter(node, {
203
+ includeComments: true,
204
+ })
205
+ if (!nextTokenWithComments)
206
+ return
207
+
208
+ const blankLines = nextTokenWithComments.loc.start.line - node.loc.end.line - 1
209
+
210
+ if (blankLines !== requiredLines)
211
+ context.report({
212
+ node,
213
+ messageId: "missingLinesAfter",
214
+ data: {
215
+ expected: requiredLines,
216
+ actual: blankLines,
217
+ nodeType,
218
+ lineText: requiredLines === 1 ? "line" : "lines",
219
+ },
220
+ fix(fixer) {
221
+ const tokenAfter = sourceCode.getTokenAfter(node, { includeComments: true });
222
+ if (!tokenAfter) {
223
+ return null;
224
+ }
225
+
226
+ const range = [node.range[1], tokenAfter.range[0]];
227
+ const newLines = "\n".repeat(requiredLines + 1); // +1 because one newline is the end of the current line
228
+
229
+ return fixer.replaceTextRange(range, newLines);
230
+ },
231
+ });
232
+ }
233
+
234
+
235
+ return {
236
+ ImportDeclaration (node) {
237
+ checkLinesBefore(node, beforeImports, 'import declaration')
238
+ checkLinesAfter(node, afterImports, 'import declaration')
239
+ },
240
+ ExportNamedDeclaration (node) {
241
+ checkLinesBefore(node, beforeExports, 'export declaration')
242
+ checkLinesAfter(node, afterExports, 'export declaration')
243
+ },
244
+ ExportDefaultDeclaration (node) {
245
+ checkLinesBefore(node, beforeExports, 'export declaration')
246
+ checkLinesAfter(node, afterExports, 'export declaration')
247
+ },
248
+ ExportAllDeclaration (node) {
249
+ checkLinesBefore(node, beforeExports, 'export declaration')
250
+ checkLinesAfter(node, afterExports, 'export declaration')
251
+ },
252
+ ClassDeclaration (node) {
253
+ checkLinesBefore(node, beforeClass, 'class declaration')
254
+ checkLinesAfter(node, afterClass, 'class declaration')
255
+ },
256
+ FunctionDeclaration (node) {
257
+ checkLinesBefore(node, beforeFunction, 'function declaration')
258
+ checkLinesAfter(node, afterFunction, 'function declaration')
259
+ },
260
+ BlockComment (node) {
261
+ if (node.loc.start.column === 0)
262
+ checkLinesBefore(node, beforeComment, 'block comment')
263
+ },
264
+ }
265
+ },
266
+ }