@borela-tech/eslint-config 1.3.3 → 2.0.1

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 (42) hide show
  1. package/bin/lint +3 -1
  2. package/bin/test +12 -0
  3. package/bin/typecheck +8 -0
  4. package/dist/index.d.ts +2 -2
  5. package/dist/index.js +320 -18
  6. package/dist/index.js.map +1 -1
  7. package/package.json +2 -2
  8. package/src/index.ts +21 -16
  9. package/src/rules/__tests__/dedent/countLeadingSpaces.ts +4 -0
  10. package/src/rules/__tests__/dedent/findMinIndent.ts +7 -0
  11. package/src/rules/__tests__/dedent/index.ts +17 -0
  12. package/src/rules/__tests__/dedent/interpolate.ts +11 -0
  13. package/src/rules/__tests__/dedent/removeEmptyPrefix.ts +6 -0
  14. package/src/rules/__tests__/dedent/removeEmptySuffix.ts +6 -0
  15. package/src/rules/__tests__/dedent/removeIndent.ts +3 -0
  16. package/src/rules/__tests__/individualImports.test.ts +42 -0
  17. package/src/rules/__tests__/sortedImports.test.ts +148 -0
  18. package/src/rules/individualImports.ts +3 -3
  19. package/src/rules/sortedImports/CategorizedImport.ts +8 -0
  20. package/src/rules/sortedImports/ImportError.ts +6 -0
  21. package/src/rules/sortedImports/ImportGroup.ts +1 -0
  22. package/src/rules/sortedImports/areSpecifiersSorted.ts +10 -0
  23. package/src/rules/sortedImports/categorizeImport.ts +15 -0
  24. package/src/rules/sortedImports/categorizeImports.ts +12 -0
  25. package/src/rules/sortedImports/checkAlphabeticalSorting.ts +22 -0
  26. package/src/rules/sortedImports/checkGroupOrdering.ts +22 -0
  27. package/src/rules/sortedImports/checkSpecifiersSorting.ts +21 -0
  28. package/src/rules/sortedImports/createFix/ReplacementRange.ts +4 -0
  29. package/src/rules/sortedImports/createFix/buildSortedCode.ts +22 -0
  30. package/src/rules/sortedImports/createFix/findLastImportIndex.ts +12 -0
  31. package/src/rules/sortedImports/createFix/formatNamedImport.ts +23 -0
  32. package/src/rules/sortedImports/createFix/getReplacementRange.ts +14 -0
  33. package/src/rules/sortedImports/createFix/groupImportsByType.ts +18 -0
  34. package/src/rules/sortedImports/createFix/index.ts +28 -0
  35. package/src/rules/sortedImports/createFix/sortImportGroups.ts +11 -0
  36. package/src/rules/sortedImports/getImportDeclarations.ts +8 -0
  37. package/src/rules/sortedImports/getNamedSpecifiers.ts +8 -0
  38. package/src/rules/sortedImports/getSortKey.ts +20 -0
  39. package/src/rules/sortedImports/getSpecifierName.ts +7 -0
  40. package/src/rules/sortedImports/index.ts +54 -0
  41. package/src/rules/sortedImports/sortSpecifiersText.ts +14 -0
  42. package/src/rules/sortedImports.ts +0 -83
package/bin/lint CHANGED
@@ -3,10 +3,12 @@
3
3
  SCRIPT_DIR=$(dirname -- "${BASH_SOURCE[0]}")
4
4
  pushd "$SCRIPT_DIR/.." > /dev/null
5
5
 
6
+ ./bin/build
6
7
  npx eslint \
7
8
  --ext ts \
8
9
  --max-warnings 0 \
9
10
  --report-unused-disable-directives \
10
- src
11
+ . \
12
+ $@
11
13
 
12
14
  popd > /dev/null
package/bin/test ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env -S bash -e -o pipefail
2
+
3
+ SCRIPT_DIR=$(dirname -- "${BASH_SOURCE[0]}")
4
+ pushd "$SCRIPT_DIR/.." > /dev/null
5
+
6
+ ./bin/build
7
+ node \
8
+ --import jiti/register \
9
+ --test \
10
+ src/rules/__tests__/*.test.ts
11
+
12
+ popd > /dev/null
package/bin/typecheck ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env -S bash -e -o pipefail
2
+
3
+ SCRIPT_DIR=$(dirname -- "${BASH_SOURCE[0]}")
4
+ pushd "$SCRIPT_DIR/.." > /dev/null
5
+
6
+ npx tsc --noEmit
7
+
8
+ popd > /dev/null
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import * as _typescript_eslint_utils_ts_eslint from '@typescript-eslint/utils/ts-eslint';
1
+ import * as eslint_config from 'eslint/config';
2
2
 
3
- declare const CONFIG: _typescript_eslint_utils_ts_eslint.FlatConfig.ConfigArray;
3
+ declare const CONFIG: eslint_config.Config[];
4
4
 
5
5
  export { CONFIG };
package/dist/index.js CHANGED
@@ -4,13 +4,315 @@ import react from "eslint-plugin-react";
4
4
  import reactHooks from "eslint-plugin-react-hooks";
5
5
  import stylistic from "@stylistic/eslint-plugin";
6
6
  import typescript from "typescript-eslint";
7
- var CONFIG = typescript.config(
7
+ import { defineConfig } from "eslint/config";
8
+
9
+ // src/rules/individualImports.ts
10
+ var individualImports = {
11
+ meta: {
12
+ docs: {
13
+ description: "Enforce individual imports instead of grouped imports",
14
+ recommended: true
15
+ },
16
+ fixable: "code",
17
+ messages: {
18
+ individualImports: "Use individual imports instead of grouped imports."
19
+ },
20
+ schema: [],
21
+ type: "suggestion"
22
+ },
23
+ create(context) {
24
+ return {
25
+ ImportDeclaration(node) {
26
+ if (node.specifiers.length <= 1)
27
+ return;
28
+ context.report({
29
+ node,
30
+ messageId: "individualImports",
31
+ fix(fixer) {
32
+ const source = node.source.raw;
33
+ const specifiers = node.specifiers.map((importSpecifier) => {
34
+ if (importSpecifier.type === "ImportSpecifier")
35
+ return `import {${importSpecifier.local.name}} from ${source}`;
36
+ return null;
37
+ }).filter(Boolean);
38
+ if (specifiers.length !== node.specifiers.length)
39
+ return null;
40
+ return fixer.replaceText(
41
+ node,
42
+ specifiers.join("\n")
43
+ );
44
+ }
45
+ });
46
+ }
47
+ };
48
+ }
49
+ };
50
+
51
+ // src/rules/sortedImports/categorizeImport.ts
52
+ function categorizeImport(declaration) {
53
+ if (declaration.importKind === "type")
54
+ return "type";
55
+ if (declaration.specifiers.length === 0)
56
+ return "side-effect";
57
+ if (declaration.specifiers.some((s) => s.type === "ImportDefaultSpecifier"))
58
+ return "default";
59
+ return "named";
60
+ }
61
+
62
+ // src/rules/sortedImports/getSortKey.ts
63
+ function getSortKey(declaration) {
64
+ const group = categorizeImport(declaration);
65
+ if (group === "side-effect")
66
+ return declaration.source.value.toLowerCase();
67
+ if (group === "default") {
68
+ const defaultSpecifier = declaration.specifiers.find(
69
+ (s) => s.type === "ImportDefaultSpecifier"
70
+ );
71
+ return defaultSpecifier?.local.name.toLowerCase() ?? "";
72
+ }
73
+ const specifier = declaration.specifiers[0];
74
+ return specifier.local.name.toLowerCase();
75
+ }
76
+
77
+ // src/rules/sortedImports/categorizeImports.ts
78
+ function categorizeImports(declarations) {
79
+ return declarations.map((declaration) => ({
80
+ declaration,
81
+ group: categorizeImport(declaration),
82
+ sortKey: getSortKey(declaration)
83
+ }));
84
+ }
85
+
86
+ // src/rules/sortedImports/checkAlphabeticalSorting.ts
87
+ function checkAlphabeticalSorting(categorized) {
88
+ const errors = [];
89
+ for (const group of ["side-effect", "default", "named", "type"]) {
90
+ const groupImports = categorized.filter((c) => c.group === group);
91
+ const sorted = [...groupImports].sort((a, b) => a.sortKey.localeCompare(b.sortKey));
92
+ for (let i = 0; i < groupImports.length; i++) {
93
+ if (groupImports[i] !== sorted[i]) {
94
+ errors.push({
95
+ node: groupImports[i].declaration,
96
+ messageId: "sortedImports"
97
+ });
98
+ }
99
+ }
100
+ }
101
+ return errors;
102
+ }
103
+
104
+ // src/rules/sortedImports/checkGroupOrdering.ts
105
+ function checkGroupOrdering(categorized) {
106
+ const groupOrder = ["side-effect", "default", "named", "type"];
107
+ const errors = [];
108
+ let currentGroupIndex = -1;
109
+ for (const { declaration, group } of categorized) {
110
+ const groupIndex = groupOrder.indexOf(group);
111
+ if (groupIndex < currentGroupIndex) {
112
+ errors.push({
113
+ node: declaration,
114
+ messageId: "wrongGroup"
115
+ });
116
+ } else
117
+ currentGroupIndex = groupIndex;
118
+ }
119
+ return errors;
120
+ }
121
+
122
+ // src/rules/sortedImports/getSpecifierName.ts
123
+ function getSpecifierName(specifier) {
124
+ return specifier.imported.type === "Identifier" ? specifier.imported.name : String(specifier.imported.value);
125
+ }
126
+
127
+ // src/rules/sortedImports/areSpecifiersSorted.ts
128
+ function areSpecifiersSorted(specifiers) {
129
+ const names = specifiers.map((s) => getSpecifierName(s));
130
+ const sorted = [...names].sort(
131
+ (a, b) => a.toLowerCase().localeCompare(b.toLowerCase())
132
+ );
133
+ return names.every((name, i) => name === sorted[i]);
134
+ }
135
+
136
+ // src/rules/sortedImports/getNamedSpecifiers.ts
137
+ function getNamedSpecifiers(declaration) {
138
+ return declaration.specifiers.filter(
139
+ (s) => s.type === "ImportSpecifier"
140
+ );
141
+ }
142
+
143
+ // src/rules/sortedImports/checkSpecifiersSorting.ts
144
+ function checkSpecifiersSorting(categorized) {
145
+ const errors = [];
146
+ const namedImports = categorized.filter((c) => c.group === "named");
147
+ for (const { declaration } of namedImports) {
148
+ const specifiers = getNamedSpecifiers(declaration);
149
+ if (specifiers.length > 1 && !areSpecifiersSorted(specifiers)) {
150
+ errors.push({
151
+ node: declaration,
152
+ messageId: "sortedNames"
153
+ });
154
+ }
155
+ }
156
+ return errors;
157
+ }
158
+
159
+ // src/rules/sortedImports/sortSpecifiersText.ts
160
+ function sortSpecifiersText(specifiers, sourceCode) {
161
+ const sorted = [...specifiers].sort((a, b) => {
162
+ const lowerA = getSpecifierName(a).toLowerCase();
163
+ const lowerB = getSpecifierName(b).toLowerCase();
164
+ return lowerA.localeCompare(lowerB);
165
+ });
166
+ return sorted.map((s) => sourceCode.getText(s)).join(", ");
167
+ }
168
+
169
+ // src/rules/sortedImports/createFix/formatNamedImport.ts
170
+ function formatNamedImport(declaration, sourceCode) {
171
+ const specifiers = getNamedSpecifiers(declaration);
172
+ if (specifiers.length > 1 && !areSpecifiersSorted(specifiers)) {
173
+ const importText = sourceCode.getText(declaration);
174
+ const specifiersStart = importText.indexOf("{");
175
+ const specifiersEnd = importText.lastIndexOf("}");
176
+ const before = importText.substring(0, specifiersStart + 1);
177
+ const after = importText.substring(specifiersEnd);
178
+ const sortedSpecifiers = sortSpecifiersText(specifiers, sourceCode);
179
+ return before + " " + sortedSpecifiers + " " + after;
180
+ }
181
+ return sourceCode.getText(declaration);
182
+ }
183
+
184
+ // src/rules/sortedImports/createFix/buildSortedCode.ts
185
+ function buildSortedCode(grouped, sourceCode) {
186
+ const groupOrder = ["side-effect", "default", "named", "type"];
187
+ const sortedCode = [];
188
+ for (const group of groupOrder) {
189
+ for (const { declaration } of grouped[group]) {
190
+ if (group === "named" || group === "type")
191
+ sortedCode.push(formatNamedImport(declaration, sourceCode));
192
+ else
193
+ sortedCode.push(sourceCode.getText(declaration));
194
+ }
195
+ }
196
+ return sortedCode;
197
+ }
198
+
199
+ // src/rules/sortedImports/createFix/findLastImportIndex.ts
200
+ function findLastImportIndex(programBody) {
201
+ let lastIndex = 0;
202
+ for (let i = 0; i < programBody.length; i++) {
203
+ if (programBody[i].type === "ImportDeclaration")
204
+ lastIndex = i;
205
+ else
206
+ break;
207
+ }
208
+ return lastIndex;
209
+ }
210
+
211
+ // src/rules/sortedImports/createFix/getReplacementRange.ts
212
+ function getReplacementRange(programBody) {
213
+ const lastIndex = findLastImportIndex(programBody);
214
+ const firstImport = programBody[0];
215
+ const lastImport = programBody[lastIndex];
216
+ const start = firstImport.range[0];
217
+ const end = lastImport.range[1];
218
+ return { start, end };
219
+ }
220
+
221
+ // src/rules/sortedImports/createFix/groupImportsByType.ts
222
+ function groupImportsByType(categorized) {
223
+ const grouped = {
224
+ "side-effect": [],
225
+ default: [],
226
+ named: [],
227
+ type: []
228
+ };
229
+ for (const item of categorized)
230
+ grouped[item.group].push(item);
231
+ return grouped;
232
+ }
233
+
234
+ // src/rules/sortedImports/createFix/sortImportGroups.ts
235
+ function sortImportGroups(grouped) {
236
+ grouped["side-effect"].sort((a, b) => a.sortKey.localeCompare(b.sortKey));
237
+ grouped["default"].sort((a, b) => a.sortKey.localeCompare(b.sortKey));
238
+ grouped["named"].sort((a, b) => a.sortKey.localeCompare(b.sortKey));
239
+ grouped["type"].sort((a, b) => a.sortKey.localeCompare(b.sortKey));
240
+ }
241
+
242
+ // src/rules/sortedImports/createFix/index.ts
243
+ function createFix(fixer, importDeclarations, sourceCode, programBody) {
244
+ const range = getReplacementRange(programBody);
245
+ const categorized = categorizeImports(importDeclarations);
246
+ const grouped = groupImportsByType(categorized);
247
+ sortImportGroups(grouped);
248
+ const sortedCode = buildSortedCode(grouped, sourceCode).join("\n");
249
+ return fixer.replaceTextRange(
250
+ [range.start, range.end],
251
+ sortedCode
252
+ );
253
+ }
254
+
255
+ // src/rules/sortedImports/getImportDeclarations.ts
256
+ function getImportDeclarations(programBody) {
257
+ return programBody.filter(
258
+ (statement) => statement.type === "ImportDeclaration"
259
+ );
260
+ }
261
+
262
+ // src/rules/sortedImports/index.ts
263
+ var sortedImports = {
264
+ meta: {
265
+ docs: {
266
+ description: "Enforce sorted imports alphabetically",
267
+ recommended: true
268
+ },
269
+ fixable: "code",
270
+ messages: {
271
+ sortedImports: "Imports should be sorted alphabetically",
272
+ sortedNames: "Named imports should be sorted alphabetically",
273
+ wrongGroup: "Import is in wrong group"
274
+ },
275
+ schema: [],
276
+ type: "suggestion"
277
+ },
278
+ create(context) {
279
+ return {
280
+ Program(node) {
281
+ const body = node.body;
282
+ const declarations = getImportDeclarations(body);
283
+ if (declarations.length === 0)
284
+ return;
285
+ const categorized = categorizeImports(declarations);
286
+ const errors = [
287
+ ...checkGroupOrdering(categorized),
288
+ ...checkAlphabeticalSorting(categorized),
289
+ ...checkSpecifiersSorting(categorized)
290
+ ];
291
+ for (const error of errors) {
292
+ context.report({
293
+ node: error.node,
294
+ messageId: error.messageId,
295
+ fix(fixer) {
296
+ const sourceCode = context.sourceCode;
297
+ return createFix(fixer, declarations, sourceCode, body);
298
+ }
299
+ });
300
+ }
301
+ }
302
+ };
303
+ }
304
+ };
305
+
306
+ // src/index.ts
307
+ var CONFIG = defineConfig([
8
308
  {
9
309
  ignores: [
10
310
  "src/graphql/sdk.ts",
11
311
  "**/node_modules/**",
12
312
  "**/dist/**"
13
- ],
313
+ ]
314
+ },
315
+ {
14
316
  settings: {
15
317
  react: {
16
318
  version: "19"
@@ -28,6 +330,20 @@ var CONFIG = typescript.config(
28
330
  },
29
331
  rules: reactHooks.configs.recommended.rules
30
332
  },
333
+ {
334
+ plugins: {
335
+ "@borela-tech": {
336
+ rules: {
337
+ "individual-imports": individualImports,
338
+ "sorted-imports": sortedImports
339
+ }
340
+ }
341
+ },
342
+ rules: {
343
+ "@borela-tech/individual-imports": "error",
344
+ "@borela-tech/sorted-imports": "error"
345
+ }
346
+ },
31
347
  {
32
348
  rules: {
33
349
  "capitalized-comments": [
@@ -102,24 +418,10 @@ var CONFIG = typescript.config(
102
418
  { beforeStatementContinuationChars: "always" }
103
419
  ],
104
420
  "@typescript-eslint/no-empty-function": "off",
105
- "@typescript-eslint/consistent-indexed-object-style": "off",
106
- "sort-imports": [
107
- "error",
108
- {
109
- allowSeparatedGroups: true,
110
- ignoreCase: true,
111
- ignoreMemberSort: false,
112
- memberSyntaxSortOrder: [
113
- "none",
114
- "all",
115
- "single",
116
- "multiple"
117
- ]
118
- }
119
- ]
421
+ "@typescript-eslint/consistent-indexed-object-style": "off"
120
422
  }
121
423
  }
122
- );
424
+ ]);
123
425
  export {
124
426
  CONFIG
125
427
  };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import eslint from '@eslint/js'\nimport react from 'eslint-plugin-react'\nimport reactHooks from 'eslint-plugin-react-hooks'\nimport stylistic from '@stylistic/eslint-plugin'\nimport typescript from 'typescript-eslint'\n\nexport const CONFIG = typescript.config(\n {\n ignores: [\n 'src/graphql/sdk.ts',\n '**/node_modules/**',\n '**/dist/**',\n ],\n settings: {\n react: {\n version: '19',\n },\n },\n },\n eslint.configs.recommended,\n react.configs.flat.recommended,\n stylistic.configs.recommended,\n typescript.configs.recommended,\n typescript.configs.stylistic,\n {\n plugins: {\n 'react-hooks': reactHooks,\n },\n rules: reactHooks.configs.recommended.rules,\n },\n {\n rules: {\n 'capitalized-comments': [\n 'error',\n 'always',\n {ignoreConsecutiveComments: true},\n ],\n 'react/react-in-jsx-scope': 'off',\n '@stylistic/arrow-parens': [\n 'error',\n 'as-needed',\n ],\n '@stylistic/array-bracket-newline': [\n 'error',\n 'consistent',\n ],\n '@stylistic/array-bracket-spacing': [\n 'error',\n 'never',\n ],\n '@stylistic/array-element-newline': [\n 'error',\n 'consistent',\n ],\n '@stylistic/block-spacing': 'off',\n '@stylistic/brace-style': [\n 'error',\n '1tbs',\n {allowSingleLine: true},\n ],\n '@stylistic/indent': [\n 'error',\n 2,\n {ignoredNodes: ['TSMappedType > *']},\n ],\n '@stylistic/jsx-tag-spacing': [\n 'error',\n {\n afterOpening: 'never',\n beforeClosing: 'never',\n beforeSelfClosing: 'never',\n closingSlash: 'never',\n },\n ],\n '@stylistic/jsx-wrap-multilines': 'off',\n '@stylistic/lines-between-class-members': 'off',\n '@stylistic/object-curly-newline': [\n 'error',\n {consistent: true},\n ],\n '@stylistic/object-curly-spacing': [\n 'error',\n 'never',\n ],\n '@stylistic/operator-linebreak': [\n 'error',\n 'before',\n {overrides: {'=': 'after'}},\n ],\n '@stylistic/quotes': [\n 'error',\n 'single',\n {avoidEscape: true},\n ],\n '@stylistic/quote-props': [\n 'error',\n 'as-needed',\n ],\n '@stylistic/semi': [\n 'error',\n 'never',\n {beforeStatementContinuationChars: 'always'},\n ],\n '@typescript-eslint/no-empty-function': 'off',\n '@typescript-eslint/consistent-indexed-object-style': 'off',\n 'sort-imports': [\n 'error',\n {\n allowSeparatedGroups: true,\n ignoreCase: true,\n ignoreMemberSort: false,\n memberSyntaxSortOrder: [\n 'none',\n 'all',\n 'single',\n 'multiple',\n ],\n },\n ],\n },\n },\n)\n"],"mappings":";AAAA,OAAO,YAAY;AACnB,OAAO,WAAW;AAClB,OAAO,gBAAgB;AACvB,OAAO,eAAe;AACtB,OAAO,gBAAgB;AAEhB,IAAM,SAAS,WAAW;AAAA,EAC/B;AAAA,IACE,SAAS;AAAA,MACP;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,UAAU;AAAA,MACR,OAAO;AAAA,QACL,SAAS;AAAA,MACX;AAAA,IACF;AAAA,EACF;AAAA,EACA,OAAO,QAAQ;AAAA,EACf,MAAM,QAAQ,KAAK;AAAA,EACnB,UAAU,QAAQ;AAAA,EAClB,WAAW,QAAQ;AAAA,EACnB,WAAW,QAAQ;AAAA,EACnB;AAAA,IACE,SAAS;AAAA,MACP,eAAe;AAAA,IACjB;AAAA,IACA,OAAO,WAAW,QAAQ,YAAY;AAAA,EACxC;AAAA,EACA;AAAA,IACE,OAAO;AAAA,MACL,wBAAwB;AAAA,QACtB;AAAA,QACA;AAAA,QACA,EAAC,2BAA2B,KAAI;AAAA,MAClC;AAAA,MACA,4BAA4B;AAAA,MAC5B,2BAA2B;AAAA,QACzB;AAAA,QACA;AAAA,MACF;AAAA,MACA,oCAAoC;AAAA,QAClC;AAAA,QACA;AAAA,MACF;AAAA,MACA,oCAAoC;AAAA,QAClC;AAAA,QACA;AAAA,MACF;AAAA,MACA,oCAAoC;AAAA,QAClC;AAAA,QACA;AAAA,MACF;AAAA,MACA,4BAA4B;AAAA,MAC5B,0BAA0B;AAAA,QACxB;AAAA,QACA;AAAA,QACA,EAAC,iBAAiB,KAAI;AAAA,MACxB;AAAA,MACA,qBAAqB;AAAA,QACnB;AAAA,QACA;AAAA,QACA,EAAC,cAAc,CAAC,kBAAkB,EAAC;AAAA,MACrC;AAAA,MACA,8BAA8B;AAAA,QAC5B;AAAA,QACA;AAAA,UACE,cAAc;AAAA,UACd,eAAe;AAAA,UACf,mBAAmB;AAAA,UACnB,cAAc;AAAA,QAChB;AAAA,MACF;AAAA,MACA,kCAAkC;AAAA,MAClC,0CAA0C;AAAA,MAC1C,mCAAmC;AAAA,QACjC;AAAA,QACA,EAAC,YAAY,KAAI;AAAA,MACnB;AAAA,MACA,mCAAmC;AAAA,QACjC;AAAA,QACA;AAAA,MACF;AAAA,MACA,iCAAiC;AAAA,QAC/B;AAAA,QACA;AAAA,QACA,EAAC,WAAW,EAAC,KAAK,QAAO,EAAC;AAAA,MAC5B;AAAA,MACA,qBAAqB;AAAA,QACnB;AAAA,QACA;AAAA,QACA,EAAC,aAAa,KAAI;AAAA,MACpB;AAAA,MACA,0BAA0B;AAAA,QACxB;AAAA,QACA;AAAA,MACF;AAAA,MACA,mBAAmB;AAAA,QACjB;AAAA,QACA;AAAA,QACA,EAAC,kCAAkC,SAAQ;AAAA,MAC7C;AAAA,MACA,wCAAwC;AAAA,MACxC,sDAAsD;AAAA,MACtD,gBAAgB;AAAA,QACd;AAAA,QACA;AAAA,UACE,sBAAsB;AAAA,UACtB,YAAY;AAAA,UACZ,kBAAkB;AAAA,UAClB,uBAAuB;AAAA,YACrB;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/rules/individualImports.ts","../src/rules/sortedImports/categorizeImport.ts","../src/rules/sortedImports/getSortKey.ts","../src/rules/sortedImports/categorizeImports.ts","../src/rules/sortedImports/checkAlphabeticalSorting.ts","../src/rules/sortedImports/checkGroupOrdering.ts","../src/rules/sortedImports/getSpecifierName.ts","../src/rules/sortedImports/areSpecifiersSorted.ts","../src/rules/sortedImports/getNamedSpecifiers.ts","../src/rules/sortedImports/checkSpecifiersSorting.ts","../src/rules/sortedImports/sortSpecifiersText.ts","../src/rules/sortedImports/createFix/formatNamedImport.ts","../src/rules/sortedImports/createFix/buildSortedCode.ts","../src/rules/sortedImports/createFix/findLastImportIndex.ts","../src/rules/sortedImports/createFix/getReplacementRange.ts","../src/rules/sortedImports/createFix/groupImportsByType.ts","../src/rules/sortedImports/createFix/sortImportGroups.ts","../src/rules/sortedImports/createFix/index.ts","../src/rules/sortedImports/getImportDeclarations.ts","../src/rules/sortedImports/index.ts"],"sourcesContent":["import eslint from '@eslint/js'\nimport react from 'eslint-plugin-react'\nimport reactHooks from 'eslint-plugin-react-hooks'\nimport stylistic from '@stylistic/eslint-plugin'\nimport typescript from 'typescript-eslint'\nimport {defineConfig} from 'eslint/config'\nimport {individualImports} from './rules/individualImports'\nimport {sortedImports} from './rules/sortedImports'\n\nexport const CONFIG = defineConfig([\n {\n ignores: [\n 'src/graphql/sdk.ts',\n '**/node_modules/**',\n '**/dist/**',\n ],\n },\n {\n settings: {\n react: {\n version: '19',\n },\n },\n },\n eslint.configs.recommended,\n react.configs.flat.recommended,\n stylistic.configs.recommended,\n typescript.configs.recommended,\n typescript.configs.stylistic,\n {\n plugins: {\n 'react-hooks': reactHooks,\n },\n rules: reactHooks.configs.recommended.rules,\n },\n {\n plugins: {\n '@borela-tech': {\n rules: {\n 'individual-imports': individualImports,\n 'sorted-imports': sortedImports,\n },\n },\n },\n rules: {\n '@borela-tech/individual-imports': 'error',\n '@borela-tech/sorted-imports': 'error',\n },\n },\n {\n rules: {\n 'capitalized-comments': [\n 'error',\n 'always',\n {ignoreConsecutiveComments: true},\n ],\n 'react/react-in-jsx-scope': 'off',\n '@stylistic/arrow-parens': [\n 'error',\n 'as-needed',\n ],\n '@stylistic/array-bracket-newline': [\n 'error',\n 'consistent',\n ],\n '@stylistic/array-bracket-spacing': [\n 'error',\n 'never',\n ],\n '@stylistic/array-element-newline': [\n 'error',\n 'consistent',\n ],\n '@stylistic/block-spacing': 'off',\n '@stylistic/brace-style': [\n 'error',\n '1tbs',\n {allowSingleLine: true},\n ],\n '@stylistic/indent': [\n 'error',\n 2,\n {ignoredNodes: ['TSMappedType > *']},\n ],\n '@stylistic/jsx-tag-spacing': [\n 'error',\n {\n afterOpening: 'never',\n beforeClosing: 'never',\n beforeSelfClosing: 'never',\n closingSlash: 'never',\n },\n ],\n '@stylistic/jsx-wrap-multilines': 'off',\n '@stylistic/lines-between-class-members': 'off',\n '@stylistic/object-curly-newline': [\n 'error',\n {consistent: true},\n ],\n '@stylistic/object-curly-spacing': [\n 'error',\n 'never',\n ],\n '@stylistic/operator-linebreak': [\n 'error',\n 'before',\n {overrides: {'=': 'after'}},\n ],\n '@stylistic/quotes': [\n 'error',\n 'single',\n {avoidEscape: true},\n ],\n '@stylistic/quote-props': [\n 'error',\n 'as-needed',\n ],\n '@stylistic/semi': [\n 'error',\n 'never',\n {beforeStatementContinuationChars: 'always'},\n ],\n '@typescript-eslint/no-empty-function': 'off',\n '@typescript-eslint/consistent-indexed-object-style': 'off',\n },\n },\n])\n","import type {Rule} from 'eslint'\n\nexport const individualImports: Rule.RuleModule = {\n meta: {\n docs: {\n description: 'Enforce individual imports instead of grouped imports',\n recommended: true,\n },\n fixable: 'code',\n messages: {\n individualImports: 'Use individual imports instead of grouped imports.',\n },\n schema: [],\n type: 'suggestion',\n },\n create(context) {\n return {\n ImportDeclaration(node) {\n if (node.specifiers.length <= 1)\n return\n context.report({\n node,\n messageId: 'individualImports',\n fix(fixer) {\n const source = node.source.raw\n const specifiers = node.specifiers\n .map(importSpecifier => {\n if (importSpecifier.type === 'ImportSpecifier')\n return `import {${importSpecifier.local.name}} from ${source}`\n return null\n })\n .filter(Boolean)\n\n if (specifiers.length !== node.specifiers.length)\n return null\n\n return fixer.replaceText(\n node,\n specifiers.join('\\n'),\n )\n },\n })\n },\n }\n },\n}\n","import type {ImportGroup} from './ImportGroup'\nimport type {TSESTree} from '@typescript-eslint/types'\n\nexport function categorizeImport(declaration: TSESTree.ImportDeclaration): ImportGroup {\n if (declaration.importKind === 'type')\n return 'type'\n\n if (declaration.specifiers.length === 0)\n return 'side-effect'\n\n if (declaration.specifiers.some(s => s.type === 'ImportDefaultSpecifier'))\n return 'default'\n\n return 'named'\n}\n","import {categorizeImport} from './categorizeImport'\nimport type {TSESTree} from '@typescript-eslint/types'\n\nexport function getSortKey(declaration: TSESTree.ImportDeclaration): string {\n const group = categorizeImport(declaration)\n\n if (group === 'side-effect')\n return (declaration.source.value as string).toLowerCase()\n\n if (group === 'default') {\n const defaultSpecifier = declaration.specifiers.find(\n s => s.type === 'ImportDefaultSpecifier',\n ) as TSESTree.ImportDefaultSpecifier | undefined\n\n return defaultSpecifier?.local.name.toLowerCase() ?? ''\n }\n\n const specifier = declaration.specifiers[0]\n return specifier.local.name.toLowerCase()\n}\n","import {categorizeImport} from './categorizeImport'\nimport {getSortKey} from './getSortKey'\nimport type {CategorizedImport} from './CategorizedImport'\nimport type {TSESTree} from '@typescript-eslint/types'\n\nexport function categorizeImports(declarations: TSESTree.ImportDeclaration[]): CategorizedImport[] {\n return declarations.map(declaration => ({\n declaration,\n group: categorizeImport(declaration),\n sortKey: getSortKey(declaration),\n }))\n}\n","import type {CategorizedImport} from './CategorizedImport'\nimport type {ImportError} from './ImportError'\nimport type {ImportGroup} from './ImportGroup'\n\nexport function checkAlphabeticalSorting(categorized: CategorizedImport[]): ImportError[] {\n const errors: ImportError[] = []\n\n for (const group of ['side-effect', 'default', 'named', 'type'] as ImportGroup[]) {\n const groupImports = categorized.filter(c => c.group === group)\n const sorted = [...groupImports].sort((a, b) => a.sortKey.localeCompare(b.sortKey))\n for (let i = 0; i < groupImports.length; i++) {\n if (groupImports[i] !== sorted[i]) {\n errors.push({\n node: groupImports[i].declaration,\n messageId: 'sortedImports',\n })\n }\n }\n }\n\n return errors\n}\n","import type {CategorizedImport} from './CategorizedImport'\nimport type {ImportError} from './ImportError'\nimport type {ImportGroup} from './ImportGroup'\n\nexport function checkGroupOrdering(categorized: CategorizedImport[]): ImportError[] {\n const groupOrder: ImportGroup[] = ['side-effect', 'default', 'named', 'type']\n const errors: ImportError[] = []\n\n let currentGroupIndex = -1\n for (const {declaration, group} of categorized) {\n const groupIndex = groupOrder.indexOf(group)\n if (groupIndex < currentGroupIndex) {\n errors.push({\n node: declaration,\n messageId: 'wrongGroup',\n })\n } else\n currentGroupIndex = groupIndex\n }\n\n return errors\n}\n","import type {ImportSpecifier} from 'estree'\n\nexport function getSpecifierName(specifier: ImportSpecifier): string {\n return specifier.imported.type === 'Identifier'\n ? specifier.imported.name\n : String(specifier.imported.value)\n}\n","import {getSpecifierName} from './getSpecifierName'\nimport type {ImportSpecifier} from 'estree'\n\nexport function areSpecifiersSorted(specifiers: ImportSpecifier[]): boolean {\n const names = specifiers.map(s => getSpecifierName(s))\n const sorted = [...names].sort((a, b) =>\n a.toLowerCase().localeCompare(b.toLowerCase()),\n )\n return names.every((name, i) => name === sorted[i])\n}\n","import type {ImportDeclaration} from 'estree'\nimport type {ImportSpecifier} from 'estree'\n\nexport function getNamedSpecifiers(declaration: ImportDeclaration): ImportSpecifier[] {\n return declaration.specifiers.filter(\n (s): s is ImportSpecifier => s.type === 'ImportSpecifier',\n )\n}\n","import {areSpecifiersSorted} from './areSpecifiersSorted'\nimport {getNamedSpecifiers} from './getNamedSpecifiers'\nimport type {CategorizedImport} from './CategorizedImport'\nimport type {ImportError} from './ImportError'\n\nexport function checkSpecifiersSorting(categorized: CategorizedImport[]): ImportError[] {\n const errors: ImportError[] = []\n const namedImports = categorized.filter(c => c.group === 'named')\n\n for (const {declaration} of namedImports) {\n const specifiers = getNamedSpecifiers(declaration)\n if (specifiers.length > 1 && !areSpecifiersSorted(specifiers)) {\n errors.push({\n node: declaration,\n messageId: 'sortedNames',\n })\n }\n }\n\n return errors\n}\n","import {getSpecifierName} from './getSpecifierName'\nimport type {ImportSpecifier} from 'estree'\n\nexport function sortSpecifiersText(\n specifiers: ImportSpecifier[],\n sourceCode: {getText: (node: ImportSpecifier) => string},\n): string {\n const sorted = [...specifiers].sort((a, b) => {\n const lowerA = getSpecifierName(a).toLowerCase()\n const lowerB = getSpecifierName(b).toLowerCase()\n return lowerA.localeCompare(lowerB)\n })\n return sorted.map(s => sourceCode.getText(s)).join(', ')\n}\n","import {areSpecifiersSorted} from '../areSpecifiersSorted'\nimport {getNamedSpecifiers} from '../getNamedSpecifiers'\nimport {sortSpecifiersText} from '../sortSpecifiersText'\nimport type {ImportDeclaration} from 'estree'\n\nexport function formatNamedImport(\n declaration: ImportDeclaration,\n sourceCode: {getText: (node?: unknown) => string},\n): string {\n const specifiers = getNamedSpecifiers(declaration)\n\n if (specifiers.length > 1 && !areSpecifiersSorted(specifiers)) {\n const importText = sourceCode.getText(declaration)\n const specifiersStart = importText.indexOf('{')\n const specifiersEnd = importText.lastIndexOf('}')\n const before = importText.substring(0, specifiersStart + 1)\n const after = importText.substring(specifiersEnd)\n const sortedSpecifiers = sortSpecifiersText(specifiers, sourceCode)\n return before + ' ' + sortedSpecifiers + ' ' + after\n }\n\n return sourceCode.getText(declaration)\n}\n","import {formatNamedImport} from './formatNamedImport'\nimport type {CategorizedImport} from '../CategorizedImport'\nimport type {ImportGroup} from '../ImportGroup'\n\nexport function buildSortedCode(\n grouped: Record<ImportGroup, CategorizedImport[]>,\n sourceCode: {getText: (node?: unknown) => string},\n): string[] {\n const groupOrder: ImportGroup[] = ['side-effect', 'default', 'named', 'type']\n const sortedCode: string[] = []\n\n for (const group of groupOrder) {\n for (const {declaration} of grouped[group]) {\n if (group === 'named' || group === 'type')\n sortedCode.push(formatNamedImport(declaration, sourceCode))\n else\n sortedCode.push(sourceCode.getText(declaration))\n }\n }\n\n return sortedCode\n}\n","import type {TSESTree} from '@typescript-eslint/types'\n\nexport function findLastImportIndex(programBody: TSESTree.ProgramStatement[]): number {\n let lastIndex = 0\n for (let i = 0; i < programBody.length; i++) {\n if (programBody[i].type === 'ImportDeclaration')\n lastIndex = i\n else\n break\n }\n return lastIndex\n}\n","import {findLastImportIndex} from './findLastImportIndex'\nimport type {ReplacementRange} from './ReplacementRange'\nimport type {TSESTree} from '@typescript-eslint/types'\n\nexport function getReplacementRange(\n programBody: TSESTree.ProgramStatement[],\n): ReplacementRange {\n const lastIndex = findLastImportIndex(programBody)\n const firstImport = programBody[0] as TSESTree.ImportDeclaration\n const lastImport = programBody[lastIndex] as TSESTree.ImportDeclaration\n const start = firstImport.range![0]\n const end = lastImport.range![1]\n return {start, end}\n}\n","import type {CategorizedImport} from '../CategorizedImport'\nimport type {ImportGroup} from '../ImportGroup'\n\nexport function groupImportsByType(\n categorized: CategorizedImport[],\n): Record<ImportGroup, CategorizedImport[]> {\n const grouped: Record<ImportGroup, CategorizedImport[]> = {\n 'side-effect': [],\n default: [],\n named: [],\n type: [],\n }\n\n for (const item of categorized)\n grouped[item.group].push(item)\n\n return grouped\n}\n","import type {CategorizedImport} from '../CategorizedImport'\nimport type {ImportGroup} from '../ImportGroup'\n\nexport function sortImportGroups(\n grouped: Record<ImportGroup, CategorizedImport[]>,\n): void {\n grouped['side-effect'].sort((a, b) => a.sortKey.localeCompare(b.sortKey))\n grouped['default'].sort((a, b) => a.sortKey.localeCompare(b.sortKey))\n grouped['named'].sort((a, b) => a.sortKey.localeCompare(b.sortKey))\n grouped['type'].sort((a, b) => a.sortKey.localeCompare(b.sortKey))\n}\n","import {buildSortedCode} from './buildSortedCode'\nimport {categorizeImports} from '../categorizeImports'\nimport {getReplacementRange} from './getReplacementRange'\nimport {groupImportsByType} from './groupImportsByType'\nimport {sortImportGroups} from './sortImportGroups'\nimport type {Rule} from 'eslint'\nimport type {TSESTree} from '@typescript-eslint/types'\n\nexport function createFix(\n fixer: Rule.RuleFixer,\n importDeclarations: TSESTree.ImportDeclaration[],\n sourceCode: {getText: (node?: unknown) => string},\n programBody: TSESTree.ProgramStatement[],\n) {\n const range = getReplacementRange(programBody)\n const categorized = categorizeImports(importDeclarations)\n const grouped = groupImportsByType(categorized)\n\n sortImportGroups(grouped)\n\n const sortedCode = buildSortedCode(grouped, sourceCode)\n .join('\\n')\n\n return fixer.replaceTextRange(\n [range.start, range.end],\n sortedCode,\n )\n}\n","import type {TSESTree} from '@typescript-eslint/types'\n\nexport function getImportDeclarations(programBody: TSESTree.ProgramStatement[]): TSESTree.ImportDeclaration[] {\n return programBody.filter(\n (statement): statement is TSESTree.ImportDeclaration =>\n statement.type === 'ImportDeclaration',\n )\n}\n","import {categorizeImports} from './categorizeImports'\nimport {checkAlphabeticalSorting} from './checkAlphabeticalSorting'\nimport {checkGroupOrdering} from './checkGroupOrdering'\nimport {checkSpecifiersSorting} from './checkSpecifiersSorting'\nimport {createFix} from './createFix'\nimport {getImportDeclarations} from './getImportDeclarations'\nimport type {ImportError} from './ImportError'\nimport type {Rule} from 'eslint'\nimport type {TSESTree} from '@typescript-eslint/types'\n\nexport const sortedImports: Rule.RuleModule = {\n meta: {\n docs: {\n description: 'Enforce sorted imports alphabetically',\n recommended: true,\n },\n fixable: 'code',\n messages: {\n sortedImports: 'Imports should be sorted alphabetically',\n sortedNames: 'Named imports should be sorted alphabetically',\n wrongGroup: 'Import is in wrong group',\n },\n schema: [],\n type: 'suggestion',\n },\n create(context) {\n return {\n Program(node) {\n const body = node.body as TSESTree.ProgramStatement[]\n const declarations = getImportDeclarations(body)\n if (declarations.length === 0)\n return\n\n const categorized = categorizeImports(declarations)\n const errors: ImportError[] = [\n ...checkGroupOrdering(categorized),\n ...checkAlphabeticalSorting(categorized),\n ...checkSpecifiersSorting(categorized),\n ]\n\n for (const error of errors) {\n context.report({\n node: error.node,\n messageId: error.messageId,\n fix(fixer) {\n const sourceCode = context.sourceCode\n return createFix(fixer, declarations, sourceCode, body)\n },\n })\n }\n },\n }\n },\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@borela-tech/eslint-config",
3
- "version": "1.3.3",
3
+ "version": "2.0.1",
4
4
  "description": "ESLint config used in Borela Tech projects.",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
@@ -31,7 +31,7 @@
31
31
  "@stylistic/eslint-plugin": "^4.2.0",
32
32
  "eslint-plugin-react": "^7.37.5",
33
33
  "eslint-plugin-react-hooks": "^5.2.0",
34
- "typescript-eslint": "^8.29.1"
34
+ "typescript-eslint": "8.56.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "eslint": "^9.24.0",
package/src/index.ts CHANGED
@@ -3,14 +3,19 @@ import react from 'eslint-plugin-react'
3
3
  import reactHooks from 'eslint-plugin-react-hooks'
4
4
  import stylistic from '@stylistic/eslint-plugin'
5
5
  import typescript from 'typescript-eslint'
6
+ import {defineConfig} from 'eslint/config'
7
+ import {individualImports} from './rules/individualImports'
8
+ import {sortedImports} from './rules/sortedImports'
6
9
 
7
- export const CONFIG = typescript.config(
10
+ export const CONFIG = defineConfig([
8
11
  {
9
12
  ignores: [
10
13
  'src/graphql/sdk.ts',
11
14
  '**/node_modules/**',
12
15
  '**/dist/**',
13
16
  ],
17
+ },
18
+ {
14
19
  settings: {
15
20
  react: {
16
21
  version: '19',
@@ -28,6 +33,20 @@ export const CONFIG = typescript.config(
28
33
  },
29
34
  rules: reactHooks.configs.recommended.rules,
30
35
  },
36
+ {
37
+ plugins: {
38
+ '@borela-tech': {
39
+ rules: {
40
+ 'individual-imports': individualImports,
41
+ 'sorted-imports': sortedImports,
42
+ },
43
+ },
44
+ },
45
+ rules: {
46
+ '@borela-tech/individual-imports': 'error',
47
+ '@borela-tech/sorted-imports': 'error',
48
+ },
49
+ },
31
50
  {
32
51
  rules: {
33
52
  'capitalized-comments': [
@@ -103,20 +122,6 @@ export const CONFIG = typescript.config(
103
122
  ],
104
123
  '@typescript-eslint/no-empty-function': 'off',
105
124
  '@typescript-eslint/consistent-indexed-object-style': 'off',
106
- 'sort-imports': [
107
- 'error',
108
- {
109
- allowSeparatedGroups: true,
110
- ignoreCase: true,
111
- ignoreMemberSort: false,
112
- memberSyntaxSortOrder: [
113
- 'none',
114
- 'all',
115
- 'single',
116
- 'multiple',
117
- ],
118
- },
119
- ],
120
125
  },
121
126
  },
122
- )
127
+ ])
@@ -0,0 +1,4 @@
1
+ export function countLeadingSpaces(line: string): number {
2
+ const match = line.match(/^(\s*)/)
3
+ return match ? match[1].length : 0
4
+ }
@@ -0,0 +1,7 @@
1
+ import {countLeadingSpaces} from './countLeadingSpaces'
2
+
3
+ export function findMinIndent(lines: string[]): number {
4
+ const nonEmptyLines = lines.filter(line => line.trim().length > 0)
5
+ const indents = nonEmptyLines.map(line => countLeadingSpaces(line))
6
+ return Math.min(...indents)
7
+ }
@@ -0,0 +1,17 @@
1
+ import {findMinIndent} from './findMinIndent'
2
+ import {interpolate} from './interpolate'
3
+ import {removeEmptyPrefix} from './removeEmptyPrefix'
4
+ import {removeEmptySuffix} from './removeEmptySuffix'
5
+ import {removeIndent} from './removeIndent'
6
+
7
+ export function dedent(
8
+ strings: TemplateStringsArray,
9
+ ...values: unknown[]
10
+ ): string {
11
+ const raw = interpolate(strings, values)
12
+ const lines = raw.split('\n')
13
+ const withoutPrefix = removeEmptyPrefix(lines)
14
+ const trimmed = removeEmptySuffix(withoutPrefix)
15
+ const indentSize = findMinIndent(trimmed)
16
+ return removeIndent(trimmed, indentSize)
17
+ }
@@ -0,0 +1,11 @@
1
+ export function interpolate(
2
+ strings: TemplateStringsArray,
3
+ values: unknown[],
4
+ ): string {
5
+ let result = ''
6
+ for (let i = 0; i < strings.length; i++) {
7
+ result += strings[i]
8
+ if (i < values.length) result += String(values[i])
9
+ }
10
+ return result
11
+ }
@@ -0,0 +1,6 @@
1
+ export function removeEmptyPrefix(lines: string[]): string[] {
2
+ const copy = [...lines]
3
+ while (copy.length > 0 && copy[0].trim() === '')
4
+ copy.shift()
5
+ return copy
6
+ }
@@ -0,0 +1,6 @@
1
+ export function removeEmptySuffix(lines: string[]): string[] {
2
+ const copy = [...lines]
3
+ while (copy.length > 0 && copy[copy.length - 1].trim() === '')
4
+ copy.pop()
5
+ return copy
6
+ }
@@ -0,0 +1,3 @@
1
+ export function removeIndent(lines: string[], indentSize: number): string {
2
+ return lines.map(line => line.slice(indentSize)).join('\n')
3
+ }