@saasmakers/eslint 0.1.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.
package/dist/index.cjs ADDED
@@ -0,0 +1,138 @@
1
+ 'use strict';
2
+
3
+ const rule = {
4
+ meta: {
5
+ docs: {
6
+ category: "Best Practices",
7
+ description: "Enforce sorted test functions grouped by method with sorted exceptions and middlewares",
8
+ recommended: true
9
+ },
10
+ fixable: "code",
11
+ messages: { sortError: "Test functions should be grouped by method with sorted exceptions and middlewares." },
12
+ schema: [],
13
+ type: "suggestion"
14
+ },
15
+ create(context) {
16
+ if (!context.filename.endsWith(".spec.ts")) {
17
+ return {};
18
+ }
19
+ function getTestName(node) {
20
+ if (node.arguments && node.arguments[0] && node.arguments[0].type === "Literal") {
21
+ return node.arguments[0].value;
22
+ }
23
+ return "";
24
+ }
25
+ function getTestPriority(testName) {
26
+ if (testName.includes(".middlewares.")) {
27
+ return 3;
28
+ }
29
+ if (testName.includes(".exceptions.")) {
30
+ return 2;
31
+ }
32
+ return 1;
33
+ }
34
+ function getFunctionName(testName) {
35
+ const parts = testName.split(".");
36
+ return parts.slice(0, 2).join(".");
37
+ }
38
+ function getSpecificName(testName) {
39
+ const parts = testName.split(".");
40
+ const specialIndex = parts.findIndex(
41
+ (part) => part === "exceptions" || part === "middlewares"
42
+ );
43
+ if (specialIndex === -1) {
44
+ return parts.slice(2).join(".");
45
+ }
46
+ return parts.slice(specialIndex + 1).join(".");
47
+ }
48
+ function compareTests(testA, testB) {
49
+ const functionA = getFunctionName(testA);
50
+ const functionB = getFunctionName(testB);
51
+ if (functionA !== functionB) {
52
+ return functionA.localeCompare(functionB);
53
+ }
54
+ const priorityA = getTestPriority(testA);
55
+ const priorityB = getTestPriority(testB);
56
+ if (priorityA !== priorityB) {
57
+ return priorityA - priorityB;
58
+ }
59
+ const specificNameA = getSpecificName(testA);
60
+ const specificNameB = getSpecificName(testB);
61
+ return specificNameA.localeCompare(specificNameB);
62
+ }
63
+ function handleTestGroup(node) {
64
+ const testGroup = node.arguments[0];
65
+ if (!testGroup || !node.arguments[1]) {
66
+ return;
67
+ }
68
+ const groupCallback = node.arguments[1];
69
+ if (groupCallback.type !== "FunctionExpression" && groupCallback.type !== "ArrowFunctionExpression") {
70
+ return;
71
+ }
72
+ const callbackBody = groupCallback.body;
73
+ if (callbackBody.type !== "BlockStatement" || !callbackBody.body) {
74
+ return;
75
+ }
76
+ const tests = callbackBody.body.filter((statement) => {
77
+ return statement.type === "ExpressionStatement" && statement.expression.type === "CallExpression" && statement.expression.callee.type === "Identifier" && (statement.expression.callee.name === "test" || statement.expression.callee.name === "it");
78
+ });
79
+ const testNames = tests.map((test) => getTestName(test.expression));
80
+ const sortedTestNames = [...testNames].sort(compareTests);
81
+ const isSorted = testNames.every(
82
+ (name, index) => name === sortedTestNames[index]
83
+ );
84
+ if (!isSorted) {
85
+ context.report({
86
+ fix(fixer) {
87
+ const testMap = new Map(
88
+ tests.map((test) => [getTestName(test.expression), test])
89
+ );
90
+ const sortedTests = sortedTestNames.map((name) => testMap.get(name));
91
+ const fixes = [];
92
+ for (let index = 0; index < tests.length; index++) {
93
+ const originalTest = tests[index];
94
+ const sortedTest = sortedTests[index];
95
+ if (originalTest !== sortedTest) {
96
+ fixes.push(fixer.replaceText(
97
+ originalTest,
98
+ context.sourceCode.getText(sortedTest)
99
+ ));
100
+ }
101
+ }
102
+ return fixes;
103
+ },
104
+ loc: node.loc ?? void 0,
105
+ messageId: "sortError",
106
+ node
107
+ });
108
+ }
109
+ }
110
+ return {
111
+ 'CallExpression[callee.name="describe"]': handleTestGroup,
112
+ 'CallExpression[callee.object.name="describe"][callee.property.name="concurrent"]': handleTestGroup,
113
+ 'CallExpression[callee.object.name="test"][callee.property.name="group"]': handleTestGroup
114
+ };
115
+ }
116
+ };
117
+
118
+ const index = {
119
+ rules: {
120
+ // 'ts-multiline-ternary': tsMultilineTernary,
121
+ // 'ts-multiline-union': tsMultilineUnion,
122
+ "ts-sort-tests": rule
123
+ // 'vue-i18n-consistent-locales': vueI18nConsistentLocales,
124
+ // 'vue-i18n-consistent-t': vueI18nConsistentT,
125
+ // 'vue-i18n-sort-keys': vueI18nSortKeys,
126
+ // 'vue-i18n-unused-strings': vueI18nUnusedStrings,
127
+ // 'vue-script-format-computed': vueScriptFormatComputed,
128
+ // 'vue-script-format-emits': vueScriptFormatEmits,
129
+ // 'vue-script-format-props': vueScriptFormatProps,
130
+ // 'vue-script-order': vueScriptOrder,
131
+ // 'vue-template-format-classes': vueTemplateFormatClasses,
132
+ // 'vue-template-format-props': vueTemplateFormatProps,
133
+ // 'vue-template-remove-comments': vueTemplateRemoveComments,
134
+ // 'vue-template-remove-true-attributes': vueTemplateRemoveTrueAttributes,
135
+ }
136
+ };
137
+
138
+ module.exports = index;
@@ -0,0 +1,9 @@
1
+ import * as eslint from 'eslint';
2
+
3
+ declare const _default: {
4
+ rules: {
5
+ 'ts-sort-tests': eslint.Rule.RuleModule;
6
+ };
7
+ };
8
+
9
+ export = _default;
@@ -0,0 +1,9 @@
1
+ import * as eslint from 'eslint';
2
+
3
+ declare const _default: {
4
+ rules: {
5
+ 'ts-sort-tests': eslint.Rule.RuleModule;
6
+ };
7
+ };
8
+
9
+ export { _default as default };
@@ -0,0 +1,9 @@
1
+ import * as eslint from 'eslint';
2
+
3
+ declare const _default: {
4
+ rules: {
5
+ 'ts-sort-tests': eslint.Rule.RuleModule;
6
+ };
7
+ };
8
+
9
+ export = _default;
package/dist/index.mjs ADDED
@@ -0,0 +1,136 @@
1
+ const rule = {
2
+ meta: {
3
+ docs: {
4
+ category: "Best Practices",
5
+ description: "Enforce sorted test functions grouped by method with sorted exceptions and middlewares",
6
+ recommended: true
7
+ },
8
+ fixable: "code",
9
+ messages: { sortError: "Test functions should be grouped by method with sorted exceptions and middlewares." },
10
+ schema: [],
11
+ type: "suggestion"
12
+ },
13
+ create(context) {
14
+ if (!context.filename.endsWith(".spec.ts")) {
15
+ return {};
16
+ }
17
+ function getTestName(node) {
18
+ if (node.arguments && node.arguments[0] && node.arguments[0].type === "Literal") {
19
+ return node.arguments[0].value;
20
+ }
21
+ return "";
22
+ }
23
+ function getTestPriority(testName) {
24
+ if (testName.includes(".middlewares.")) {
25
+ return 3;
26
+ }
27
+ if (testName.includes(".exceptions.")) {
28
+ return 2;
29
+ }
30
+ return 1;
31
+ }
32
+ function getFunctionName(testName) {
33
+ const parts = testName.split(".");
34
+ return parts.slice(0, 2).join(".");
35
+ }
36
+ function getSpecificName(testName) {
37
+ const parts = testName.split(".");
38
+ const specialIndex = parts.findIndex(
39
+ (part) => part === "exceptions" || part === "middlewares"
40
+ );
41
+ if (specialIndex === -1) {
42
+ return parts.slice(2).join(".");
43
+ }
44
+ return parts.slice(specialIndex + 1).join(".");
45
+ }
46
+ function compareTests(testA, testB) {
47
+ const functionA = getFunctionName(testA);
48
+ const functionB = getFunctionName(testB);
49
+ if (functionA !== functionB) {
50
+ return functionA.localeCompare(functionB);
51
+ }
52
+ const priorityA = getTestPriority(testA);
53
+ const priorityB = getTestPriority(testB);
54
+ if (priorityA !== priorityB) {
55
+ return priorityA - priorityB;
56
+ }
57
+ const specificNameA = getSpecificName(testA);
58
+ const specificNameB = getSpecificName(testB);
59
+ return specificNameA.localeCompare(specificNameB);
60
+ }
61
+ function handleTestGroup(node) {
62
+ const testGroup = node.arguments[0];
63
+ if (!testGroup || !node.arguments[1]) {
64
+ return;
65
+ }
66
+ const groupCallback = node.arguments[1];
67
+ if (groupCallback.type !== "FunctionExpression" && groupCallback.type !== "ArrowFunctionExpression") {
68
+ return;
69
+ }
70
+ const callbackBody = groupCallback.body;
71
+ if (callbackBody.type !== "BlockStatement" || !callbackBody.body) {
72
+ return;
73
+ }
74
+ const tests = callbackBody.body.filter((statement) => {
75
+ return statement.type === "ExpressionStatement" && statement.expression.type === "CallExpression" && statement.expression.callee.type === "Identifier" && (statement.expression.callee.name === "test" || statement.expression.callee.name === "it");
76
+ });
77
+ const testNames = tests.map((test) => getTestName(test.expression));
78
+ const sortedTestNames = [...testNames].sort(compareTests);
79
+ const isSorted = testNames.every(
80
+ (name, index) => name === sortedTestNames[index]
81
+ );
82
+ if (!isSorted) {
83
+ context.report({
84
+ fix(fixer) {
85
+ const testMap = new Map(
86
+ tests.map((test) => [getTestName(test.expression), test])
87
+ );
88
+ const sortedTests = sortedTestNames.map((name) => testMap.get(name));
89
+ const fixes = [];
90
+ for (let index = 0; index < tests.length; index++) {
91
+ const originalTest = tests[index];
92
+ const sortedTest = sortedTests[index];
93
+ if (originalTest !== sortedTest) {
94
+ fixes.push(fixer.replaceText(
95
+ originalTest,
96
+ context.sourceCode.getText(sortedTest)
97
+ ));
98
+ }
99
+ }
100
+ return fixes;
101
+ },
102
+ loc: node.loc ?? void 0,
103
+ messageId: "sortError",
104
+ node
105
+ });
106
+ }
107
+ }
108
+ return {
109
+ 'CallExpression[callee.name="describe"]': handleTestGroup,
110
+ 'CallExpression[callee.object.name="describe"][callee.property.name="concurrent"]': handleTestGroup,
111
+ 'CallExpression[callee.object.name="test"][callee.property.name="group"]': handleTestGroup
112
+ };
113
+ }
114
+ };
115
+
116
+ const index = {
117
+ rules: {
118
+ // 'ts-multiline-ternary': tsMultilineTernary,
119
+ // 'ts-multiline-union': tsMultilineUnion,
120
+ "ts-sort-tests": rule
121
+ // 'vue-i18n-consistent-locales': vueI18nConsistentLocales,
122
+ // 'vue-i18n-consistent-t': vueI18nConsistentT,
123
+ // 'vue-i18n-sort-keys': vueI18nSortKeys,
124
+ // 'vue-i18n-unused-strings': vueI18nUnusedStrings,
125
+ // 'vue-script-format-computed': vueScriptFormatComputed,
126
+ // 'vue-script-format-emits': vueScriptFormatEmits,
127
+ // 'vue-script-format-props': vueScriptFormatProps,
128
+ // 'vue-script-order': vueScriptOrder,
129
+ // 'vue-template-format-classes': vueTemplateFormatClasses,
130
+ // 'vue-template-format-props': vueTemplateFormatProps,
131
+ // 'vue-template-remove-comments': vueTemplateRemoveComments,
132
+ // 'vue-template-remove-true-attributes': vueTemplateRemoveTrueAttributes,
133
+ }
134
+ };
135
+
136
+ export { index as default };
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@saasmakers/eslint",
3
+ "type": "module",
4
+ "version": "0.1.1",
5
+ "private": false,
6
+ "description": "Shared ESLint config for SaaS Makers projects",
7
+ "license": "MIT",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/saasmakers/saasmakers-turborepo.git",
11
+ "directory": "packages/eslint"
12
+ },
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.mjs",
17
+ "require": "./dist/index.cjs"
18
+ }
19
+ },
20
+ "main": "dist/index.cjs",
21
+ "types": "dist/index.d.ts",
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "files": [
26
+ "dist/*"
27
+ ],
28
+ "peerDependencies": {
29
+ "eslint": "^9.0.0"
30
+ },
31
+ "devDependencies": {
32
+ "@types/estree": "1.0.8",
33
+ "typescript": "5.9.3",
34
+ "@saasmakers/config": "0.1.19"
35
+ },
36
+ "scripts": {
37
+ "build": "unbuild",
38
+ "lint": "eslint . --max-warnings=0",
39
+ "typecheck": "tsc --noEmit"
40
+ }
41
+ }