@reasonabletech/eslint-config 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/CHANGELOG.md +9 -0
- package/README.md +89 -0
- package/dist/src/base-configs.d.ts +53 -0
- package/dist/src/base-configs.js +105 -0
- package/dist/src/custom-rules/architecture-patterns.d.ts +193 -0
- package/dist/src/custom-rules/architecture-patterns.js +344 -0
- package/dist/src/custom-rules/code-quality.d.ts +201 -0
- package/dist/src/custom-rules/code-quality.js +388 -0
- package/dist/src/custom-rules/error-handling.d.ts +103 -0
- package/dist/src/custom-rules/error-handling.js +349 -0
- package/dist/src/custom-rules/index.d.ts +79 -0
- package/dist/src/custom-rules/index.js +119 -0
- package/dist/src/custom-rules/null-undefined-checks.d.ts +20 -0
- package/dist/src/custom-rules/null-undefined-checks.js +153 -0
- package/dist/src/custom-rules/platform-conventions.d.ts +115 -0
- package/dist/src/custom-rules/platform-conventions.js +249 -0
- package/dist/src/custom-rules/test-quality.d.ts +38 -0
- package/dist/src/custom-rules/test-quality.js +68 -0
- package/dist/src/custom-rules/type-safety.d.ts +71 -0
- package/dist/src/custom-rules/type-safety.js +121 -0
- package/dist/src/custom-rules/ui-library-imports.d.ts +21 -0
- package/dist/src/custom-rules/ui-library-imports.js +31 -0
- package/dist/src/custom-rules/utils.d.ts +95 -0
- package/dist/src/custom-rules/utils.js +146 -0
- package/dist/src/index.d.ts +73 -0
- package/dist/src/index.js +80 -0
- package/dist/src/next/config.d.ts +26 -0
- package/dist/src/next/config.js +76 -0
- package/dist/src/next/ignores.d.ts +37 -0
- package/dist/src/next/ignores.js +69 -0
- package/dist/src/next/plugins.d.ts +44 -0
- package/dist/src/next/plugins.js +104 -0
- package/dist/src/next/rules.d.ts +39 -0
- package/dist/src/next/rules.js +48 -0
- package/dist/src/next/settings.d.ts +41 -0
- package/dist/src/next/settings.js +45 -0
- package/dist/src/next.d.ts +33 -0
- package/dist/src/next.js +74 -0
- package/dist/src/plugin.d.ts +48 -0
- package/dist/src/plugin.js +30 -0
- package/dist/src/react/config.d.ts +30 -0
- package/dist/src/react/config.js +40 -0
- package/dist/src/react/plugins.d.ts +24 -0
- package/dist/src/react/plugins.js +46 -0
- package/dist/src/react/rules.d.ts +30 -0
- package/dist/src/react/rules.js +35 -0
- package/dist/src/react.d.ts +27 -0
- package/dist/src/react.js +35 -0
- package/dist/src/shared/parser-options.d.ts +3 -0
- package/dist/src/shared/parser-options.js +19 -0
- package/dist/src/shared/plugin-utils.d.ts +8 -0
- package/dist/src/shared/plugin-utils.js +23 -0
- package/dist/src/shared/react-rules.d.ts +97 -0
- package/dist/src/shared/react-rules.js +126 -0
- package/dist/src/shared/strict-rules.d.ts +27 -0
- package/dist/src/shared/strict-rules.js +54 -0
- package/dist/src/shared-ignores.d.ts +15 -0
- package/dist/src/shared-ignores.js +68 -0
- package/dist/src/shared-rules.d.ts +29 -0
- package/dist/src/shared-rules.js +163 -0
- package/package.json +122 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom ESLint rule: no-null-undefined-checks
|
|
3
|
+
*
|
|
4
|
+
* Flags code that checks for both null and undefined on the same value,
|
|
5
|
+
* which indicates a type mismatch. null and undefined are different types
|
|
6
|
+
* in TypeScript, and you should only check for what's in your type union.
|
|
7
|
+
*/
|
|
8
|
+
import { ESLintUtils, AST_NODE_TYPES, } from "@typescript-eslint/utils";
|
|
9
|
+
/**
|
|
10
|
+
* Type guard for BinaryExpression
|
|
11
|
+
* @param node The node to check
|
|
12
|
+
* @returns True if the node is a BinaryExpression
|
|
13
|
+
*/
|
|
14
|
+
function isBinaryExpression(node) {
|
|
15
|
+
return node.type === AST_NODE_TYPES.BinaryExpression;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Type guard for Identifier
|
|
19
|
+
* @param node The node to check
|
|
20
|
+
* @returns True if the node is an Identifier
|
|
21
|
+
*/
|
|
22
|
+
function isIdentifier(node) {
|
|
23
|
+
return node.type === AST_NODE_TYPES.Identifier;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Type guard for Literal
|
|
27
|
+
* @param node The node to check
|
|
28
|
+
* @returns True if the node is a Literal
|
|
29
|
+
*/
|
|
30
|
+
function isLiteral(node) {
|
|
31
|
+
return node.type === AST_NODE_TYPES.Literal;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Checks if a node is a null comparison (=== null, !== null, == null, != null)
|
|
35
|
+
* @param node The node to check
|
|
36
|
+
* @returns The variable being compared to null, or null if not a null comparison
|
|
37
|
+
*/
|
|
38
|
+
function getNullCheckVariable(node) {
|
|
39
|
+
if (!isBinaryExpression(node)) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const { operator, left, right } = node;
|
|
43
|
+
const isComparisonOp = operator === "===" ||
|
|
44
|
+
operator === "!==" ||
|
|
45
|
+
operator === "==" ||
|
|
46
|
+
operator === "!=";
|
|
47
|
+
if (!isComparisonOp) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
// Check if right side is null literal
|
|
51
|
+
if (isLiteral(right) && right.value === null && isIdentifier(left)) {
|
|
52
|
+
return left.name;
|
|
53
|
+
}
|
|
54
|
+
// Check if left side is null literal
|
|
55
|
+
if (isLiteral(left) && left.value === null && isIdentifier(right)) {
|
|
56
|
+
return right.name;
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Checks if a node is an undefined comparison (=== undefined, !== undefined, == undefined, != undefined)
|
|
62
|
+
* @param node The node to check
|
|
63
|
+
* @returns The variable being compared to undefined, or null if not an undefined comparison
|
|
64
|
+
*/
|
|
65
|
+
function getUndefinedCheckVariable(node) {
|
|
66
|
+
if (!isBinaryExpression(node)) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
const { operator, left, right } = node;
|
|
70
|
+
const isComparisonOp = operator === "===" ||
|
|
71
|
+
operator === "!==" ||
|
|
72
|
+
operator === "==" ||
|
|
73
|
+
operator === "!=";
|
|
74
|
+
if (!isComparisonOp) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
// Check if right side is undefined identifier
|
|
78
|
+
if (isIdentifier(right) && right.name === "undefined" && isIdentifier(left)) {
|
|
79
|
+
return left.name;
|
|
80
|
+
}
|
|
81
|
+
// Check if left side is undefined identifier
|
|
82
|
+
if (isIdentifier(left) && left.name === "undefined" && isIdentifier(right)) {
|
|
83
|
+
return right.name;
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Creates the no-null-undefined-checks rule
|
|
89
|
+
*/
|
|
90
|
+
export const noNullUndefinedChecksRule = ESLintUtils.RuleCreator(() => "docs/standards/typescript-design-patterns.md")({
|
|
91
|
+
name: "no-null-undefined-checks",
|
|
92
|
+
meta: {
|
|
93
|
+
type: "problem",
|
|
94
|
+
docs: {
|
|
95
|
+
description: "Flags checking for both null and undefined on the same value, which indicates a type mismatch. null and undefined are different types in TypeScript.",
|
|
96
|
+
},
|
|
97
|
+
messages: {
|
|
98
|
+
checksBoth: "Checking for both null and undefined suggests a type mismatch. Simplify the type: use `T | null` for nullable values, or `T | undefined` for optional values. If both are genuinely in the type, use `x == null` which covers both in a single check.",
|
|
99
|
+
},
|
|
100
|
+
schema: [],
|
|
101
|
+
},
|
|
102
|
+
defaultOptions: [],
|
|
103
|
+
create(context) {
|
|
104
|
+
return {
|
|
105
|
+
LogicalExpression(node) {
|
|
106
|
+
// Only check OR conditions (||)
|
|
107
|
+
if (node.operator !== "||") {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
// Get the variables being checked on each side
|
|
111
|
+
const leftNullVar = getNullCheckVariable(node.left);
|
|
112
|
+
const leftUndefinedVar = getUndefinedCheckVariable(node.left);
|
|
113
|
+
const rightNullVar = getNullCheckVariable(node.right);
|
|
114
|
+
const rightUndefinedVar = getUndefinedCheckVariable(node.right);
|
|
115
|
+
// Look for a variable that appears in both null and undefined checks
|
|
116
|
+
const nullVars = new Set();
|
|
117
|
+
const undefinedVars = new Set();
|
|
118
|
+
if (leftNullVar !== null) {
|
|
119
|
+
nullVars.add(leftNullVar);
|
|
120
|
+
}
|
|
121
|
+
if (rightNullVar !== null) {
|
|
122
|
+
nullVars.add(rightNullVar);
|
|
123
|
+
}
|
|
124
|
+
if (leftUndefinedVar !== null) {
|
|
125
|
+
undefinedVars.add(leftUndefinedVar);
|
|
126
|
+
}
|
|
127
|
+
if (rightUndefinedVar !== null) {
|
|
128
|
+
undefinedVars.add(rightUndefinedVar);
|
|
129
|
+
}
|
|
130
|
+
// Check if any variable is checked against both null and undefined
|
|
131
|
+
for (const variable of nullVars) {
|
|
132
|
+
if (undefinedVars.has(variable)) {
|
|
133
|
+
context.report({
|
|
134
|
+
node,
|
|
135
|
+
messageId: "checksBoth",
|
|
136
|
+
});
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
/**
|
|
145
|
+
* Creates the rule configuration for the null/undefined checks rule
|
|
146
|
+
* @returns ESLint rule configuration
|
|
147
|
+
*/
|
|
148
|
+
export function createNullUndefinedChecksRules() {
|
|
149
|
+
return {
|
|
150
|
+
"@reasonabletech/no-null-undefined-checks": "error",
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=null-undefined-checks.js.map
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform-specific convention rules
|
|
3
|
+
*
|
|
4
|
+
* These rules enforce conventions specific to the platform's
|
|
5
|
+
* standard library and architectural patterns, ensuring consistent use
|
|
6
|
+
* of platform utilities across the codebase.
|
|
7
|
+
*/
|
|
8
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
9
|
+
import type { Linter } from "eslint";
|
|
10
|
+
import { type UILibraryImportRuleOptions } from "./ui-library-imports.js";
|
|
11
|
+
/**
|
|
12
|
+
* Configuration options for platform convention rules
|
|
13
|
+
*/
|
|
14
|
+
export interface PlatformConventionRuleOptions {
|
|
15
|
+
/** Base URL for documentation references */
|
|
16
|
+
docBaseUrl?: string;
|
|
17
|
+
/** Whether to enforce Result helper usage (default: true) */
|
|
18
|
+
enforceResultHelpers?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Legacy alias for discouraging UI library barrel imports.
|
|
21
|
+
* Prefer `discourageUILibraryBarrelImports`.
|
|
22
|
+
*/
|
|
23
|
+
discourageUIBarrelImports?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* UI import boundary options for the shared UI library.
|
|
26
|
+
*/
|
|
27
|
+
uiImportBoundaries?: UILibraryImportRuleOptions;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Custom ESLint rule that enforces using ok() and err() helpers from `@reasonabletech/utils`
|
|
31
|
+
*
|
|
32
|
+
* This rule prevents manual Result object construction, ensuring developers use
|
|
33
|
+
* the platform's standard utility functions for consistency and type safety.
|
|
34
|
+
*
|
|
35
|
+
* **Core Principle**: Always use ok() and err() helpers for Result types instead
|
|
36
|
+
* of manually constructing objects with { success: true/false, ... }
|
|
37
|
+
*
|
|
38
|
+
* ❌ FORBIDDEN (Manual Result construction):
|
|
39
|
+
* ```typescript
|
|
40
|
+
* // Wrong: Manual success result
|
|
41
|
+
* return { success: true, data: user };
|
|
42
|
+
*
|
|
43
|
+
* // Wrong: Manual error result
|
|
44
|
+
* return { success: false, error: "not_found" };
|
|
45
|
+
*
|
|
46
|
+
* // Wrong: Inline object literals
|
|
47
|
+
* const result = { success: true, data: value };
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* ✅ CORRECT (Using platform helpers):
|
|
51
|
+
* ```typescript
|
|
52
|
+
* // Right: Use ok() for success
|
|
53
|
+
* import { ok } from "@reasonabletech/utils";
|
|
54
|
+
* return ok(user);
|
|
55
|
+
*
|
|
56
|
+
* // Right: Use err() for errors
|
|
57
|
+
* import { err } from "@reasonabletech/utils";
|
|
58
|
+
* return err("not_found");
|
|
59
|
+
*
|
|
60
|
+
* // Right: Import and use consistently
|
|
61
|
+
* import { ok, err, type Result } from "@reasonabletech/utils";
|
|
62
|
+
*
|
|
63
|
+
* async function getUser(id: string): Promise<Result<User, "not_found">> {
|
|
64
|
+
* const user = await db.user.findUnique({ where: { id } });
|
|
65
|
+
* if (!user) return err("not_found");
|
|
66
|
+
* return ok(user);
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* **Why this matters**:
|
|
71
|
+
* - Ensures consistent Result construction across the platform
|
|
72
|
+
* - Leverages type inference from helper functions
|
|
73
|
+
* - Makes refactoring easier (change implementation in one place)
|
|
74
|
+
* - Provides better editor autocomplete and type checking
|
|
75
|
+
* - Prevents typos in manual object construction ({ sucess: true })
|
|
76
|
+
*/
|
|
77
|
+
export declare const useResultHelpersRule: ESLintUtils.RuleModule<"useOkHelper" | "useErrHelper", [{
|
|
78
|
+
docBaseUrl: string;
|
|
79
|
+
}], unknown, ESLintUtils.RuleListener> & {
|
|
80
|
+
name: string;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Creates rules that enforce Result helper usage
|
|
84
|
+
*
|
|
85
|
+
* Uses the `@reasonabletech/use-result-helpers` custom rule which detects manual
|
|
86
|
+
* Result construction including non-literal success values, spread elements,
|
|
87
|
+
* and computed property keys.
|
|
88
|
+
* @param options Configuration options for platform convention rules
|
|
89
|
+
* @returns ESLint rules that enforce Result helper usage
|
|
90
|
+
*/
|
|
91
|
+
export declare function createResultHelperRules(options?: PlatformConventionRuleOptions): Linter.RulesRecord;
|
|
92
|
+
/**
|
|
93
|
+
* Creates UI library import-boundary rules.
|
|
94
|
+
*
|
|
95
|
+
* This is a thin adapter that keeps the platform-conventions API stable while
|
|
96
|
+
* delegating UI-library specifics to the dedicated `ui-library-imports` module.
|
|
97
|
+
* @param options Configuration options for platform convention rules
|
|
98
|
+
* @returns ESLint rules that discourage UI library barrel imports
|
|
99
|
+
*/
|
|
100
|
+
export declare function createUIBarrelImportRules(options?: PlatformConventionRuleOptions): Linter.RulesRecord;
|
|
101
|
+
/**
|
|
102
|
+
* Creates a complete set of platform convention rules
|
|
103
|
+
*
|
|
104
|
+
* This is the main function that combines all platform-specific convention
|
|
105
|
+
* rules into a single configuration object.
|
|
106
|
+
* @param options Configuration options for platform convention rules
|
|
107
|
+
* @returns Complete set of platform convention ESLint rules
|
|
108
|
+
*/
|
|
109
|
+
export declare function createPlatformConventionRules(options?: PlatformConventionRuleOptions): Linter.RulesRecord;
|
|
110
|
+
/**
|
|
111
|
+
* Preset for platform convention rules
|
|
112
|
+
* @returns ESLint rules configured for platform conventions
|
|
113
|
+
*/
|
|
114
|
+
export declare function createPlatformConventionPresetRules(): Linter.RulesRecord;
|
|
115
|
+
//# sourceMappingURL=platform-conventions.d.ts.map
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform-specific convention rules
|
|
3
|
+
*
|
|
4
|
+
* These rules enforce conventions specific to the platform's
|
|
5
|
+
* standard library and architectural patterns, ensuring consistent use
|
|
6
|
+
* of platform utilities across the codebase.
|
|
7
|
+
*/
|
|
8
|
+
import { AST_NODE_TYPES, ESLintUtils, } from "@typescript-eslint/utils";
|
|
9
|
+
import { mergeRuleConfigurations } from "./utils.js";
|
|
10
|
+
import { createUILibraryImportRules, } from "./ui-library-imports.js";
|
|
11
|
+
/**
|
|
12
|
+
* Default configuration for platform convention rules
|
|
13
|
+
*/
|
|
14
|
+
const DEFAULT_OPTIONS = {
|
|
15
|
+
docBaseUrl: "",
|
|
16
|
+
enforceResultHelpers: true,
|
|
17
|
+
discourageUIBarrelImports: true,
|
|
18
|
+
uiImportBoundaries: {
|
|
19
|
+
discourageUILibraryBarrelImports: true,
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Custom ESLint rule that enforces using ok() and err() helpers from `@reasonabletech/utils`
|
|
24
|
+
*
|
|
25
|
+
* This rule prevents manual Result object construction, ensuring developers use
|
|
26
|
+
* the platform's standard utility functions for consistency and type safety.
|
|
27
|
+
*
|
|
28
|
+
* **Core Principle**: Always use ok() and err() helpers for Result types instead
|
|
29
|
+
* of manually constructing objects with { success: true/false, ... }
|
|
30
|
+
*
|
|
31
|
+
* ❌ FORBIDDEN (Manual Result construction):
|
|
32
|
+
* ```typescript
|
|
33
|
+
* // Wrong: Manual success result
|
|
34
|
+
* return { success: true, data: user };
|
|
35
|
+
*
|
|
36
|
+
* // Wrong: Manual error result
|
|
37
|
+
* return { success: false, error: "not_found" };
|
|
38
|
+
*
|
|
39
|
+
* // Wrong: Inline object literals
|
|
40
|
+
* const result = { success: true, data: value };
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* ✅ CORRECT (Using platform helpers):
|
|
44
|
+
* ```typescript
|
|
45
|
+
* // Right: Use ok() for success
|
|
46
|
+
* import { ok } from "@reasonabletech/utils";
|
|
47
|
+
* return ok(user);
|
|
48
|
+
*
|
|
49
|
+
* // Right: Use err() for errors
|
|
50
|
+
* import { err } from "@reasonabletech/utils";
|
|
51
|
+
* return err("not_found");
|
|
52
|
+
*
|
|
53
|
+
* // Right: Import and use consistently
|
|
54
|
+
* import { ok, err, type Result } from "@reasonabletech/utils";
|
|
55
|
+
*
|
|
56
|
+
* async function getUser(id: string): Promise<Result<User, "not_found">> {
|
|
57
|
+
* const user = await db.user.findUnique({ where: { id } });
|
|
58
|
+
* if (!user) return err("not_found");
|
|
59
|
+
* return ok(user);
|
|
60
|
+
* }
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
63
|
+
* **Why this matters**:
|
|
64
|
+
* - Ensures consistent Result construction across the platform
|
|
65
|
+
* - Leverages type inference from helper functions
|
|
66
|
+
* - Makes refactoring easier (change implementation in one place)
|
|
67
|
+
* - Provides better editor autocomplete and type checking
|
|
68
|
+
* - Prevents typos in manual object construction ({ sucess: true })
|
|
69
|
+
*/
|
|
70
|
+
export const useResultHelpersRule = ESLintUtils.RuleCreator(() => "")({
|
|
71
|
+
name: "use-result-helpers",
|
|
72
|
+
meta: {
|
|
73
|
+
type: "problem",
|
|
74
|
+
docs: {
|
|
75
|
+
description: "Enforces using ok() and err() helpers from @reasonabletech/utils instead of manual Result construction",
|
|
76
|
+
},
|
|
77
|
+
messages: {
|
|
78
|
+
useOkHelper: "❌ FORBIDDEN: Use ok() from @reasonabletech/utils instead of manually constructing { success: true, data: ... }",
|
|
79
|
+
useErrHelper: "❌ FORBIDDEN: Use err() from @reasonabletech/utils instead of manually constructing { success: false, error: ... }",
|
|
80
|
+
},
|
|
81
|
+
schema: [
|
|
82
|
+
{
|
|
83
|
+
type: "object",
|
|
84
|
+
properties: {
|
|
85
|
+
docBaseUrl: {
|
|
86
|
+
type: "string",
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
additionalProperties: false,
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
defaultOptions: [
|
|
94
|
+
{
|
|
95
|
+
docBaseUrl: "",
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
create(context) {
|
|
99
|
+
/**
|
|
100
|
+
* Checks if an object expression looks like a manual Result construction
|
|
101
|
+
* @param node - AST node representing an object expression
|
|
102
|
+
* @returns True if the object looks like a Result type
|
|
103
|
+
*/
|
|
104
|
+
function isResultLikeObject(node) {
|
|
105
|
+
let hasSuccess = false;
|
|
106
|
+
let successValue = null;
|
|
107
|
+
let hasData = false;
|
|
108
|
+
let hasError = false;
|
|
109
|
+
for (const prop of node.properties) {
|
|
110
|
+
if (prop.type === AST_NODE_TYPES.Property &&
|
|
111
|
+
prop.key.type === AST_NODE_TYPES.Identifier) {
|
|
112
|
+
const keyName = prop.key.name;
|
|
113
|
+
if (keyName === "success") {
|
|
114
|
+
hasSuccess = true;
|
|
115
|
+
// Try to determine if success is true or false
|
|
116
|
+
if (prop.value.type === AST_NODE_TYPES.Literal &&
|
|
117
|
+
typeof prop.value.value === "boolean") {
|
|
118
|
+
successValue = prop.value.value;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else if (keyName === "data") {
|
|
122
|
+
hasData = true;
|
|
123
|
+
}
|
|
124
|
+
else if (keyName === "error") {
|
|
125
|
+
hasError = true;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// Result type has 'success' and either 'data' or 'error'
|
|
130
|
+
const isResult = hasSuccess && (hasData || hasError);
|
|
131
|
+
return { isResult, isSuccess: successValue };
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
ObjectExpression(node) {
|
|
135
|
+
const { isResult, isSuccess } = isResultLikeObject(node);
|
|
136
|
+
if (!isResult) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
// Check if this is a success result (use ok helper)
|
|
140
|
+
if (isSuccess === true) {
|
|
141
|
+
context.report({
|
|
142
|
+
node,
|
|
143
|
+
messageId: "useOkHelper",
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
// Check if this is an error result (use err helper)
|
|
147
|
+
if (isSuccess === false) {
|
|
148
|
+
context.report({
|
|
149
|
+
node,
|
|
150
|
+
messageId: "useErrHelper",
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
// If we can't determine the success value (e.g., variable), still report
|
|
154
|
+
// because manual construction should be avoided
|
|
155
|
+
if (isSuccess === null) {
|
|
156
|
+
// Check which property exists to give better message
|
|
157
|
+
const hasData = node.properties.some((prop) => prop.type === AST_NODE_TYPES.Property &&
|
|
158
|
+
prop.key.type === AST_NODE_TYPES.Identifier &&
|
|
159
|
+
prop.key.name === "data");
|
|
160
|
+
if (hasData) {
|
|
161
|
+
context.report({
|
|
162
|
+
node,
|
|
163
|
+
messageId: "useOkHelper",
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
context.report({
|
|
168
|
+
node,
|
|
169
|
+
messageId: "useErrHelper",
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
/**
|
|
178
|
+
* Creates rules that enforce Result helper usage
|
|
179
|
+
*
|
|
180
|
+
* Uses the `@reasonabletech/use-result-helpers` custom rule which detects manual
|
|
181
|
+
* Result construction including non-literal success values, spread elements,
|
|
182
|
+
* and computed property keys.
|
|
183
|
+
* @param options Configuration options for platform convention rules
|
|
184
|
+
* @returns ESLint rules that enforce Result helper usage
|
|
185
|
+
*/
|
|
186
|
+
export function createResultHelperRules(options = {}) {
|
|
187
|
+
const config = { ...DEFAULT_OPTIONS, ...options };
|
|
188
|
+
if (!config.enforceResultHelpers) {
|
|
189
|
+
return {};
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
"@reasonabletech/use-result-helpers": [
|
|
193
|
+
"error",
|
|
194
|
+
{
|
|
195
|
+
docBaseUrl: config.docBaseUrl,
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Creates UI library import-boundary rules.
|
|
202
|
+
*
|
|
203
|
+
* This is a thin adapter that keeps the platform-conventions API stable while
|
|
204
|
+
* delegating UI-library specifics to the dedicated `ui-library-imports` module.
|
|
205
|
+
* @param options Configuration options for platform convention rules
|
|
206
|
+
* @returns ESLint rules that discourage UI library barrel imports
|
|
207
|
+
*/
|
|
208
|
+
export function createUIBarrelImportRules(options = {}) {
|
|
209
|
+
const config = {
|
|
210
|
+
...DEFAULT_OPTIONS,
|
|
211
|
+
...options,
|
|
212
|
+
uiImportBoundaries: {
|
|
213
|
+
...DEFAULT_OPTIONS.uiImportBoundaries,
|
|
214
|
+
...options.uiImportBoundaries,
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
return createUILibraryImportRules({
|
|
218
|
+
discourageUILibraryBarrelImports: (config.uiImportBoundaries.discourageUILibraryBarrelImports ?? true) &&
|
|
219
|
+
config.discourageUIBarrelImports,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Creates a complete set of platform convention rules
|
|
224
|
+
*
|
|
225
|
+
* This is the main function that combines all platform-specific convention
|
|
226
|
+
* rules into a single configuration object.
|
|
227
|
+
* @param options Configuration options for platform convention rules
|
|
228
|
+
* @returns Complete set of platform convention ESLint rules
|
|
229
|
+
*/
|
|
230
|
+
export function createPlatformConventionRules(options = {}) {
|
|
231
|
+
const resultHelperRules = createResultHelperRules(options);
|
|
232
|
+
const uiBarrelImportRules = createUIBarrelImportRules(options);
|
|
233
|
+
return mergeRuleConfigurations(resultHelperRules, uiBarrelImportRules);
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Preset for platform convention rules
|
|
237
|
+
* @returns ESLint rules configured for platform conventions
|
|
238
|
+
*/
|
|
239
|
+
export function createPlatformConventionPresetRules() {
|
|
240
|
+
return createPlatformConventionRules({
|
|
241
|
+
docBaseUrl: "",
|
|
242
|
+
enforceResultHelpers: true,
|
|
243
|
+
discourageUIBarrelImports: true,
|
|
244
|
+
uiImportBoundaries: {
|
|
245
|
+
discourageUILibraryBarrelImports: true,
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
//# sourceMappingURL=platform-conventions.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test quality rule definitions for ReasonableTech projects
|
|
3
|
+
*
|
|
4
|
+
* These rules prevent low-value test patterns that inflate coverage
|
|
5
|
+
* metrics without verifying real behavior. They are specifically designed
|
|
6
|
+
* to catch AI-generated test anti-patterns.
|
|
7
|
+
*/
|
|
8
|
+
import type { Linter } from "eslint";
|
|
9
|
+
/**
|
|
10
|
+
* Creates ESLint rules that ban `expect(typeof <expr>).<matcher>(...)` patterns.
|
|
11
|
+
*
|
|
12
|
+
* These assertions only verify that JavaScript knows the type of an expression
|
|
13
|
+
* at the time the test runs — information that TypeScript already guarantees at
|
|
14
|
+
* compile time. They are a classic AI-generated coverage-padding anti-pattern.
|
|
15
|
+
*
|
|
16
|
+
* ❌ FORBIDDEN:
|
|
17
|
+
* ```typescript
|
|
18
|
+
* expect(typeof tabula.close).toBe("function");
|
|
19
|
+
* expect(typeof result).toBe("object");
|
|
20
|
+
* expect(typeof user.name).toEqual("string");
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* ✅ CORRECT: test observable behaviour
|
|
24
|
+
* ```typescript
|
|
25
|
+
* // Call the function and assert what it does
|
|
26
|
+
* await tabula.close();
|
|
27
|
+
* expect(tabula.isClosed()).toBe(true);
|
|
28
|
+
*
|
|
29
|
+
* // Assert the shape of a result
|
|
30
|
+
* expect(result).toMatchObject({ id: expect.any(String) });
|
|
31
|
+
*
|
|
32
|
+
* // Assert the value, not the type
|
|
33
|
+
* expect(user.name).toBe("Alice");
|
|
34
|
+
* ```
|
|
35
|
+
* @returns ESLint rules that prevent typeof-in-expect patterns
|
|
36
|
+
*/
|
|
37
|
+
export declare function createNoTypeofInExpectRules(): Linter.RulesRecord;
|
|
38
|
+
//# sourceMappingURL=test-quality.d.ts.map
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test quality rule definitions for ReasonableTech projects
|
|
3
|
+
*
|
|
4
|
+
* These rules prevent low-value test patterns that inflate coverage
|
|
5
|
+
* metrics without verifying real behavior. They are specifically designed
|
|
6
|
+
* to catch AI-generated test anti-patterns.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* AST selector matching `expect(typeof <expr>).<matcher>(...)`.
|
|
10
|
+
*
|
|
11
|
+
* The pattern:
|
|
12
|
+
* - Outer CallExpression → the matcher call, e.g. `.toBe("function")`
|
|
13
|
+
* - callee is a MemberExpression whose object is a CallExpression to `expect`
|
|
14
|
+
* - The argument passed to `expect` is a UnaryExpression with operator `"typeof"`
|
|
15
|
+
*
|
|
16
|
+
* This catches all variants regardless of the matcher used:
|
|
17
|
+
* `expect(typeof x).toBe("function")`
|
|
18
|
+
* `expect(typeof x).toEqual("object")`
|
|
19
|
+
* `expect(typeof x).toStrictEqual("string")`
|
|
20
|
+
*/
|
|
21
|
+
const TYPEOF_IN_EXPECT_SELECTOR = 'CallExpression[callee.type="MemberExpression"]' +
|
|
22
|
+
'[callee.object.type="CallExpression"]' +
|
|
23
|
+
'[callee.object.callee.name="expect"]' +
|
|
24
|
+
'[callee.object.arguments.0.type="UnaryExpression"]' +
|
|
25
|
+
'[callee.object.arguments.0.operator="typeof"]';
|
|
26
|
+
const NO_TYPEOF_IN_EXPECT_MESSAGE = "❌ FORBIDDEN: expect(typeof x).toBe(...) adds zero test value and hides real" +
|
|
27
|
+
" gaps in functionality. Test observable behaviour instead — call the function," +
|
|
28
|
+
" check its return value, assert side-effects.";
|
|
29
|
+
/**
|
|
30
|
+
* Creates ESLint rules that ban `expect(typeof <expr>).<matcher>(...)` patterns.
|
|
31
|
+
*
|
|
32
|
+
* These assertions only verify that JavaScript knows the type of an expression
|
|
33
|
+
* at the time the test runs — information that TypeScript already guarantees at
|
|
34
|
+
* compile time. They are a classic AI-generated coverage-padding anti-pattern.
|
|
35
|
+
*
|
|
36
|
+
* ❌ FORBIDDEN:
|
|
37
|
+
* ```typescript
|
|
38
|
+
* expect(typeof tabula.close).toBe("function");
|
|
39
|
+
* expect(typeof result).toBe("object");
|
|
40
|
+
* expect(typeof user.name).toEqual("string");
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* ✅ CORRECT: test observable behaviour
|
|
44
|
+
* ```typescript
|
|
45
|
+
* // Call the function and assert what it does
|
|
46
|
+
* await tabula.close();
|
|
47
|
+
* expect(tabula.isClosed()).toBe(true);
|
|
48
|
+
*
|
|
49
|
+
* // Assert the shape of a result
|
|
50
|
+
* expect(result).toMatchObject({ id: expect.any(String) });
|
|
51
|
+
*
|
|
52
|
+
* // Assert the value, not the type
|
|
53
|
+
* expect(user.name).toBe("Alice");
|
|
54
|
+
* ```
|
|
55
|
+
* @returns ESLint rules that prevent typeof-in-expect patterns
|
|
56
|
+
*/
|
|
57
|
+
export function createNoTypeofInExpectRules() {
|
|
58
|
+
return {
|
|
59
|
+
"no-restricted-syntax": [
|
|
60
|
+
"error",
|
|
61
|
+
{
|
|
62
|
+
selector: TYPEOF_IN_EXPECT_SELECTOR,
|
|
63
|
+
message: NO_TYPEOF_IN_EXPECT_MESSAGE,
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=test-quality.js.map
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type safety rule definitions for ReasonableTech projects
|
|
3
|
+
*
|
|
4
|
+
* These rules prevent type system bypasses and enforce safe type handling
|
|
5
|
+
* patterns, particularly around Result types and type assertions.
|
|
6
|
+
*/
|
|
7
|
+
import { ESLintUtils } from "@typescript-eslint/utils";
|
|
8
|
+
import type { Linter } from "eslint";
|
|
9
|
+
/**
|
|
10
|
+
* Configuration options for type safety rules
|
|
11
|
+
*/
|
|
12
|
+
export interface TypeSafetyRuleOptions {
|
|
13
|
+
/** Base URL for documentation references */
|
|
14
|
+
docBaseUrl?: string;
|
|
15
|
+
/** Whether to allow type assertions in test files (default: false) */
|
|
16
|
+
allowInTests?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Custom ESLint rule that prevents `as any` and `<any>` type casts
|
|
20
|
+
*
|
|
21
|
+
* This rule detects all forms of casting to `any`, including:
|
|
22
|
+
* - `expr as any`
|
|
23
|
+
* - `<any>expr`
|
|
24
|
+
* - Double casts through any: `(expr as any) as T`
|
|
25
|
+
*
|
|
26
|
+
* Each variant produces a distinct, descriptive error message rather than the
|
|
27
|
+
* generic "no-restricted-syntax" label that the previous AST-selector approach
|
|
28
|
+
* showed in editors.
|
|
29
|
+
*/
|
|
30
|
+
export declare const noAsAnyRule: ESLintUtils.RuleModule<"asAny" | "angleAny" | "doubleCast", [], unknown, ESLintUtils.RuleListener> & {
|
|
31
|
+
name: string;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Creates rules that prevent `as any` type casts
|
|
35
|
+
*
|
|
36
|
+
* These rules block the use of `as any` type assertions, which bypass
|
|
37
|
+
* TypeScript's type checking and create type safety holes.
|
|
38
|
+
*
|
|
39
|
+
* ❌ FORBIDDEN:
|
|
40
|
+
* ```typescript
|
|
41
|
+
* const data = response as any; // Bypasses type checking
|
|
42
|
+
* const x = (response as any) as User; // Double cast through any
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* ✅ CORRECT:
|
|
46
|
+
* ```typescript
|
|
47
|
+
* const data = parseResponse(response); // Proper typed parsing
|
|
48
|
+
* const user = data.result.user; // Type-safe access
|
|
49
|
+
* ```
|
|
50
|
+
* @param _options Configuration options for type safety rules (reserved for future use)
|
|
51
|
+
* @returns ESLint rules that prevent `as any` casts
|
|
52
|
+
*/
|
|
53
|
+
export declare function createNoAnyRules(_options?: TypeSafetyRuleOptions): Linter.RulesRecord;
|
|
54
|
+
/**
|
|
55
|
+
* Creates a complete set of type safety rules
|
|
56
|
+
*
|
|
57
|
+
* This is the main function that combines all type safety rules
|
|
58
|
+
* into a single configuration object.
|
|
59
|
+
* @param options Configuration options for type safety rules
|
|
60
|
+
* @returns Complete set of type safety ESLint rules
|
|
61
|
+
*/
|
|
62
|
+
export declare function createTypeSafetyRules(options?: TypeSafetyRuleOptions): Linter.RulesRecord;
|
|
63
|
+
/**
|
|
64
|
+
* Preset for Platform-specific type safety rules
|
|
65
|
+
*
|
|
66
|
+
* This preset provides all type safety rules configured specifically
|
|
67
|
+
* for platform project conventions and documentation structure.
|
|
68
|
+
* @returns ESLint rules configured for platform projects
|
|
69
|
+
*/
|
|
70
|
+
export declare function createPlatformTypeSafetyRules(): Linter.RulesRecord;
|
|
71
|
+
//# sourceMappingURL=type-safety.d.ts.map
|