@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.
Files changed (61) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/README.md +89 -0
  3. package/dist/src/base-configs.d.ts +53 -0
  4. package/dist/src/base-configs.js +105 -0
  5. package/dist/src/custom-rules/architecture-patterns.d.ts +193 -0
  6. package/dist/src/custom-rules/architecture-patterns.js +344 -0
  7. package/dist/src/custom-rules/code-quality.d.ts +201 -0
  8. package/dist/src/custom-rules/code-quality.js +388 -0
  9. package/dist/src/custom-rules/error-handling.d.ts +103 -0
  10. package/dist/src/custom-rules/error-handling.js +349 -0
  11. package/dist/src/custom-rules/index.d.ts +79 -0
  12. package/dist/src/custom-rules/index.js +119 -0
  13. package/dist/src/custom-rules/null-undefined-checks.d.ts +20 -0
  14. package/dist/src/custom-rules/null-undefined-checks.js +153 -0
  15. package/dist/src/custom-rules/platform-conventions.d.ts +115 -0
  16. package/dist/src/custom-rules/platform-conventions.js +249 -0
  17. package/dist/src/custom-rules/test-quality.d.ts +38 -0
  18. package/dist/src/custom-rules/test-quality.js +68 -0
  19. package/dist/src/custom-rules/type-safety.d.ts +71 -0
  20. package/dist/src/custom-rules/type-safety.js +121 -0
  21. package/dist/src/custom-rules/ui-library-imports.d.ts +21 -0
  22. package/dist/src/custom-rules/ui-library-imports.js +31 -0
  23. package/dist/src/custom-rules/utils.d.ts +95 -0
  24. package/dist/src/custom-rules/utils.js +146 -0
  25. package/dist/src/index.d.ts +73 -0
  26. package/dist/src/index.js +80 -0
  27. package/dist/src/next/config.d.ts +26 -0
  28. package/dist/src/next/config.js +76 -0
  29. package/dist/src/next/ignores.d.ts +37 -0
  30. package/dist/src/next/ignores.js +69 -0
  31. package/dist/src/next/plugins.d.ts +44 -0
  32. package/dist/src/next/plugins.js +104 -0
  33. package/dist/src/next/rules.d.ts +39 -0
  34. package/dist/src/next/rules.js +48 -0
  35. package/dist/src/next/settings.d.ts +41 -0
  36. package/dist/src/next/settings.js +45 -0
  37. package/dist/src/next.d.ts +33 -0
  38. package/dist/src/next.js +74 -0
  39. package/dist/src/plugin.d.ts +48 -0
  40. package/dist/src/plugin.js +30 -0
  41. package/dist/src/react/config.d.ts +30 -0
  42. package/dist/src/react/config.js +40 -0
  43. package/dist/src/react/plugins.d.ts +24 -0
  44. package/dist/src/react/plugins.js +46 -0
  45. package/dist/src/react/rules.d.ts +30 -0
  46. package/dist/src/react/rules.js +35 -0
  47. package/dist/src/react.d.ts +27 -0
  48. package/dist/src/react.js +35 -0
  49. package/dist/src/shared/parser-options.d.ts +3 -0
  50. package/dist/src/shared/parser-options.js +19 -0
  51. package/dist/src/shared/plugin-utils.d.ts +8 -0
  52. package/dist/src/shared/plugin-utils.js +23 -0
  53. package/dist/src/shared/react-rules.d.ts +97 -0
  54. package/dist/src/shared/react-rules.js +126 -0
  55. package/dist/src/shared/strict-rules.d.ts +27 -0
  56. package/dist/src/shared/strict-rules.js +54 -0
  57. package/dist/src/shared-ignores.d.ts +15 -0
  58. package/dist/src/shared-ignores.js +68 -0
  59. package/dist/src/shared-rules.d.ts +29 -0
  60. package/dist/src/shared-rules.js +163 -0
  61. package/package.json +122 -0
@@ -0,0 +1,388 @@
1
+ /**
2
+ * Code quality rule definitions for ReasonableTech projects
3
+ *
4
+ * These rules prevent technical debt accumulation by detecting patterns
5
+ * that bypass code quality tools or create maintainability issues.
6
+ */
7
+ import { ESLintUtils, } from "@typescript-eslint/utils";
8
+ import { mergeRuleConfigurations } from "./utils.js";
9
+ /**
10
+ * Default options for no-linter-disabling rule
11
+ */
12
+ const DEFAULT_LINTER_DISABLING_OPTIONS = {
13
+ allowInTests: true,
14
+ requireJustification: true,
15
+ allowedRules: [],
16
+ allowedPatterns: ["**/*.test.ts", "**/*.test.tsx", "**/tests/**"],
17
+ };
18
+ /**
19
+ * Custom ESLint rule that prevents disabling linter rules without justification
20
+ *
21
+ * This rule detects and blocks the use of:
22
+ * - `eslint disable` comments
23
+ * - TypeScript ignore directives (for example, `ts-ignore`)
24
+ * - TypeScript no-check directives (for example, `ts-nocheck`)
25
+ *
26
+ * Unless:
27
+ * - The file matches allowed patterns (e.g., test files)
28
+ * - A justification comment is provided (// Reason: ...)
29
+ * - The specific rule is in the allowed list
30
+ *
31
+ * ❌ FORBIDDEN:
32
+ * ```typescript
33
+ * /* eslint disable *\/ // No justification
34
+ * // `@ts ignore` // No justification
35
+ * ```
36
+ *
37
+ * ✅ CORRECT:
38
+ * ```typescript
39
+ * // Reason: Testing error message parsing - legitimate use case
40
+ * /* eslint disable no-restricted-syntax *\/
41
+ * const hasMatch = items.some(x => x.message?.includes("pattern"));
42
+ * /* eslint enable no-restricted-syntax *\/
43
+ * ```
44
+ */
45
+ export const noLinterDisablingRule = ESLintUtils.RuleCreator(() => "docs/standards/typescript-standards.md")({
46
+ name: "no-linter-disabling",
47
+ meta: {
48
+ type: "problem",
49
+ deprecated: true,
50
+ docs: {
51
+ description: "Prevents disabling linter rules without proper justification. Deprecated: use ESLint's built-in reportUnusedDisableDirectives and the native -- reason syntax instead.",
52
+ },
53
+ messages: {
54
+ noDisable: "❌ FORBIDDEN: Disabling linter rules is not allowed. Fix the underlying issue instead.",
55
+ noJustification: "❌ REQUIRED: Linter rule disabling requires a justification comment (// Reason: <explanation>).",
56
+ specificRule: "❌ FORBIDDEN: Disabling '{{rule}}' is not allowed. Fix the issue instead.",
57
+ },
58
+ schema: [
59
+ {
60
+ type: "object",
61
+ properties: {
62
+ allowInTests: { type: "boolean" },
63
+ requireJustification: { type: "boolean" },
64
+ allowedRules: { type: "array", items: { type: "string" } },
65
+ allowedPatterns: { type: "array", items: { type: "string" } },
66
+ },
67
+ additionalProperties: false,
68
+ },
69
+ ],
70
+ },
71
+ defaultOptions: [DEFAULT_LINTER_DISABLING_OPTIONS],
72
+ create(context) {
73
+ const options = {
74
+ ...DEFAULT_LINTER_DISABLING_OPTIONS,
75
+ ...context.options[0],
76
+ };
77
+ const filename = typeof context.getFilename ===
78
+ "function"
79
+ ? context.getFilename()
80
+ : (context.filename ?? "<input>");
81
+ const sourceCode = typeof context
82
+ .getSourceCode === "function"
83
+ ? context.getSourceCode()
84
+ : (context.sourceCode ??
85
+ null);
86
+ if (sourceCode == null) {
87
+ return {};
88
+ }
89
+ // Check if file matches allowed patterns (e.g., test files)
90
+ if (options.allowInTests === true &&
91
+ isTestFile(filename, options.allowedPatterns ?? [])) {
92
+ return {};
93
+ }
94
+ return {
95
+ Program() {
96
+ const comments = sourceCode.getAllComments();
97
+ for (const comment of comments) {
98
+ const text = comment.value.trim();
99
+ // Detect linter-disable directives
100
+ if (text.includes("eslint-disable") ||
101
+ text.includes("@ts-ignore") ||
102
+ text.includes("@ts-nocheck")) {
103
+ const disabledRule = extractDisabledRule(text);
104
+ // Check if this specific rule is allowed
105
+ if (disabledRule !== null &&
106
+ disabledRule !== "" &&
107
+ options.allowedRules?.includes(disabledRule) === true) {
108
+ continue;
109
+ }
110
+ // Check for justification if required
111
+ if (options.requireJustification === true) {
112
+ const hasJustification = checkJustification(comments, comment);
113
+ if (!hasJustification) {
114
+ context.report({
115
+ loc: comment.loc,
116
+ messageId: "noJustification",
117
+ });
118
+ continue;
119
+ }
120
+ if (disabledRule !== null && disabledRule !== "") {
121
+ context.report({
122
+ loc: comment.loc,
123
+ messageId: "specificRule",
124
+ data: { rule: disabledRule },
125
+ });
126
+ }
127
+ else {
128
+ context.report({
129
+ loc: comment.loc,
130
+ messageId: "noDisable",
131
+ });
132
+ }
133
+ continue;
134
+ }
135
+ // Report the violation (only reached when requireJustification is false)
136
+ if (disabledRule !== null && disabledRule !== "") {
137
+ context.report({
138
+ loc: comment.loc,
139
+ messageId: "specificRule",
140
+ data: { rule: disabledRule },
141
+ });
142
+ }
143
+ else {
144
+ context.report({
145
+ loc: comment.loc,
146
+ messageId: "noDisable",
147
+ });
148
+ }
149
+ }
150
+ }
151
+ },
152
+ };
153
+ },
154
+ });
155
+ /**
156
+ * Checks if a filename matches test file patterns
157
+ * @param filename File path to check
158
+ * @param _patterns Optional glob patterns to match against (reserved for future use)
159
+ * @returns True if the file is a test file
160
+ */
161
+ function isTestFile(filename, _patterns = []) {
162
+ return (filename.includes(".test.") ||
163
+ filename.includes(".spec.") ||
164
+ filename.includes("/tests/") ||
165
+ filename.includes("/__tests__/"));
166
+ }
167
+ /**
168
+ * Extracts the specific rule name from an eslint disable comment
169
+ * @param comment Comment text to parse
170
+ * @returns Rule name if found, null otherwise
171
+ */
172
+ function extractDisabledRule(comment) {
173
+ const match = comment.match(/eslint-disable(?:-next-line)?\s+([a-z-/@]+)/i);
174
+ return match !== null ? match[1] : null;
175
+ }
176
+ /**
177
+ * Checks if a justification comment exists before a disable comment
178
+ * @param allComments All comments in the file
179
+ * @param disableComment The disable comment to check
180
+ * @returns True if a justification comment is found
181
+ */
182
+ function checkJustification(allComments, disableComment) {
183
+ // Look for comments within 3 lines before the disable comment
184
+ const precedingComments = allComments.filter((c) => c.loc.end.line >= disableComment.loc.start.line - 3 &&
185
+ c.loc.end.line < disableComment.loc.start.line);
186
+ return precedingComments.some((c) => {
187
+ const trimmedValue = c.value.trim().toLowerCase();
188
+ return trimmedValue.startsWith("reason:");
189
+ });
190
+ }
191
+ /**
192
+ * Custom ESLint rule that prevents barrel exports (`export *`)
193
+ *
194
+ * Barrel exports create bloated namespaces and make it harder to track
195
+ * what's exported from a module. This rule flags all `export * from "..."`
196
+ * declarations, enforcing explicit named exports instead.
197
+ *
198
+ * ❌ `export * from "./user-service";`
199
+ * ✅ `export { UserService, type CreateUserError } from "./user-service";`
200
+ */
201
+ export const noBarrelExportsRule = ESLintUtils.RuleCreator(() => "docs/standards/typescript-standards.md")({
202
+ name: "no-barrel-exports",
203
+ meta: {
204
+ type: "suggestion",
205
+ docs: {
206
+ description: "Prevents 'export *' barrel exports that create bloated namespaces",
207
+ },
208
+ messages: {
209
+ barrelExport: "Never use 'export *' barrel exports. Use explicit named exports or import from specific modules.",
210
+ },
211
+ schema: [],
212
+ },
213
+ defaultOptions: [],
214
+ create(context) {
215
+ return {
216
+ ExportAllDeclaration(node) {
217
+ context.report({
218
+ node,
219
+ messageId: "barrelExport",
220
+ });
221
+ },
222
+ };
223
+ },
224
+ });
225
+ /**
226
+ * Creates rules for detecting barrel exports (export *)
227
+ *
228
+ * These rules prevent the use of `export *` patterns, which create
229
+ * bloated namespaces and make it harder to track what's exported.
230
+ *
231
+ * ❌ FORBIDDEN:
232
+ * ```typescript
233
+ * export * from "./user-service"; // Exports all 30 items
234
+ * export * from "./workspace-service"; // Exports all 25 items
235
+ * ```
236
+ *
237
+ * ✅ CORRECT:
238
+ * ```typescript
239
+ * // Import directly from implementation
240
+ * import { UserService } from "@reasonabletech/platform/user-service";
241
+ *
242
+ * // Or use explicit named exports
243
+ * export { UserService, type CreateUserError } from "./user-service";
244
+ * ```
245
+ * @returns ESLint rules that prevent barrel exports
246
+ */
247
+ export function createBarrelExportRules() {
248
+ return {
249
+ "@reasonabletech/no-barrel-exports": "error",
250
+ };
251
+ }
252
+ /**
253
+ * Creates rules preventing mixed async/await and Promise patterns
254
+ *
255
+ * These rules prevent mixing `.then()` chains with `async/await` in
256
+ * the same function, enforcing consistent async patterns.
257
+ *
258
+ * ❌ FORBIDDEN:
259
+ * ```typescript
260
+ * async function fetchAndProcess() {
261
+ * const user = await getUser(); // async/await
262
+ * return saveUser(user)
263
+ * .then(result => result); // .then() chain
264
+ * }
265
+ * ```
266
+ *
267
+ * ✅ CORRECT:
268
+ * ```typescript
269
+ * async function fetchAndProcess() {
270
+ * const user = await getUser();
271
+ * const result = await saveUser(user);
272
+ * return result;
273
+ * }
274
+ * ```
275
+ * @param _options Configuration options (reserved for future use)
276
+ * @returns ESLint rules preventing mixed async patterns
277
+ */
278
+ export function createAsyncPatternRules(_options = {}) {
279
+ return {
280
+ "@typescript-eslint/no-misused-promises": "error",
281
+ "@typescript-eslint/no-floating-promises": "error",
282
+ "@typescript-eslint/await-thenable": "error",
283
+ "@typescript-eslint/promise-function-async": "error",
284
+ };
285
+ }
286
+ /**
287
+ * Creates rules for enforcing code quality standards
288
+ *
289
+ * This function combines multiple code quality rules including:
290
+ * - no-barrel-exports (prevents bloated namespaces)
291
+ * - async pattern consistency (prevents mixing await and .then())
292
+ * @returns Complete set of code quality ESLint rules
293
+ */
294
+ export function createCodeQualityRules() {
295
+ const barrelExportRules = createBarrelExportRules();
296
+ const asyncPatternRules = createAsyncPatternRules();
297
+ return mergeRuleConfigurations(barrelExportRules, asyncPatternRules);
298
+ }
299
+ /**
300
+ * Creates rules for enforcing platform-specific terminology
301
+ *
302
+ * Enforces consistent terminology across the codebase to prevent confusion
303
+ * and maintain clear communication patterns.
304
+ *
305
+ * ❌ FORBIDDEN:
306
+ * ```typescript
307
+ * interface ActionSchema {
308
+ * toolCall: string; // Wrong terminology
309
+ * }
310
+ * ```
311
+ *
312
+ * ✅ CORRECT:
313
+ * ```typescript
314
+ * interface ActionSchema {
315
+ * action: string; // Correct platform terminology
316
+ * }
317
+ * ```
318
+ * @param options Configuration options for terminology rules
319
+ * @param options.forbiddenTerms Map of forbidden terms to preferred alternatives
320
+ * @returns ESLint rules enforcing terminology standards
321
+ */
322
+ export function createTerminologyRules(options = {}) {
323
+ const forbiddenTerms = options.forbiddenTerms ?? {};
324
+ if (Object.keys(forbiddenTerms).length === 0) {
325
+ return {};
326
+ }
327
+ const patterns = Object.entries(forbiddenTerms).map(([forbidden, required]) => ({
328
+ selector: `Identifier[name='${forbidden}']`,
329
+ message: `❌ Use '${required}' instead of '${forbidden}' for platform consistency.`,
330
+ }));
331
+ return {
332
+ "no-restricted-syntax": ["warn", ...patterns],
333
+ };
334
+ }
335
+ /**
336
+ * Creates rules for detecting magic numbers and strings
337
+ *
338
+ * Prevents hardcoded values that should be extracted to named constants,
339
+ * improving maintainability and reducing duplication.
340
+ *
341
+ * ❌ FORBIDDEN:
342
+ * ```typescript
343
+ * const client = http.createClient("https://api.example.com/v1"); // Hardcoded URL
344
+ * setTimeout(() => retry(), 5000); // Magic timeout
345
+ * if (retryCount > 3) { ... } // Magic retry limit
346
+ * ```
347
+ *
348
+ * ✅ CORRECT:
349
+ * ```typescript
350
+ * const API_ENDPOINT = "https://api.example.com/v1";
351
+ * const RETRY_DELAY_MS = 5000;
352
+ * const MAX_RETRIES = 3;
353
+ *
354
+ * const client = http.createClient(API_ENDPOINT);
355
+ * setTimeout(() => retry(), RETRY_DELAY_MS);
356
+ * if (retryCount > MAX_RETRIES) { ... }
357
+ * ```
358
+ * @param options Configuration options for magic numbers detection
359
+ * @param options.allowedNumbers Numbers that don't require constants
360
+ * @param options.allowedPatterns File patterns where magic numbers are allowed
361
+ * @returns ESLint rules detecting magic numbers
362
+ */
363
+ export function createMagicNumbersRules(options = {}) {
364
+ const allowed = options.allowedNumbers ?? [-1, 0, 1, 2];
365
+ return {
366
+ "no-magic-numbers": [
367
+ "warn",
368
+ {
369
+ ignore: allowed,
370
+ ignoreArrayIndexes: true,
371
+ ignoreDefaultValues: true,
372
+ enforceConst: true,
373
+ detectObjects: false, // Don't flag object property values
374
+ },
375
+ ],
376
+ };
377
+ }
378
+ /**
379
+ * Preset for platform code quality rules
380
+ *
381
+ * This preset provides all code quality rules configured specifically
382
+ * for platform project conventions.
383
+ * @returns ESLint rules configured for platform projects
384
+ */
385
+ export function createPlatformCodeQualityRules() {
386
+ return createCodeQualityRules();
387
+ }
388
+ //# sourceMappingURL=code-quality.js.map
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Error handling rule definitions for ReasonableTech projects
3
+ *
4
+ * These rules enforce safe error handling patterns and prevent dangerous
5
+ * error message parsing. They are designed to be reusable across different
6
+ * projects with configurable documentation references.
7
+ */
8
+ import { ESLintUtils } from "@typescript-eslint/utils";
9
+ import type { Linter } from "eslint";
10
+ /**
11
+ * Configuration options for error handling rules
12
+ */
13
+ export interface ErrorHandlingRuleOptions {
14
+ /** Base URL for documentation references */
15
+ docBaseUrl?: string;
16
+ /** Pattern for error type names (default: ".*Error$") */
17
+ errorTypePattern?: string;
18
+ /** Name of Result type to check for inline unions (default: "Result") */
19
+ resultTypeName?: string;
20
+ /** Whether to require JSDoc on error types (default: true) */
21
+ requireErrorTypeJSDoc?: boolean;
22
+ }
23
+ /**
24
+ * Custom ESLint rule that prevents parsing error messages with string methods
25
+ *
26
+ * Detects the following patterns on any `.message` property:
27
+ * - `error.message.includes(...)` / `.startsWith(...)` / `.endsWith(...)` / `.match(...)`
28
+ * - `error.message === "..."` / `error.message == "..."`
29
+ * - `/.../\.test(error.message)`
30
+ *
31
+ * All of these are fragile because error messages are not part of a stable API
32
+ * and may change without notice. Use `error.code`, `error.status`, or
33
+ * `instanceof` checks instead.
34
+ */
35
+ export declare const noErrorMessageParsingRule: ESLintUtils.RuleModule<"stringMethod" | "directComparison" | "regexTest", [], unknown, ESLintUtils.RuleListener> & {
36
+ name: string;
37
+ };
38
+ /**
39
+ * Creates rules that prevent dangerous error message parsing patterns
40
+ *
41
+ * These rules block the use of string methods on error.message properties,
42
+ * enforcing the use of structured error detection instead.
43
+ * @param _options Configuration options for error handling rules (reserved for future use)
44
+ * @returns ESLint rules that prevent error message parsing
45
+ */
46
+ export declare function createErrorMessageParsingRules(_options?: ErrorHandlingRuleOptions): Linter.RulesRecord;
47
+ /**
48
+ * Creates rules that enforce JSDoc documentation on error types
49
+ *
50
+ * Requires comprehensive documentation on all exported error type aliases
51
+ * to ensure error codes are properly documented and understood.
52
+ * @param options Configuration options for error handling rules
53
+ * @returns ESLint rules that enforce JSDoc documentation on error types
54
+ */
55
+ export declare function createErrorTypeDocumentationRules(options?: ErrorHandlingRuleOptions): Linter.RulesRecord;
56
+ /**
57
+ * Creates rules that enforce consistent error type naming conventions
58
+ *
59
+ * Enforces PascalCase with "Error" suffix for error types and
60
+ * lowercase_with_underscores for error code literals.
61
+ * @param options Configuration options for error handling rules
62
+ * @returns ESLint rules that enforce consistent error type naming
63
+ */
64
+ export declare function createErrorTypeNamingRules(options?: ErrorHandlingRuleOptions): Linter.RulesRecord;
65
+ /**
66
+ * Custom ESLint rule that prevents inline error union types in Result types
67
+ *
68
+ * Detects inline union types (containing literal members) used as type
69
+ * arguments in `Result<T, E>` and `Promise<Result<T, E>>` type references.
70
+ * These should be extracted to documented named types for maintainability.
71
+ *
72
+ * ❌ `Result<User, "not_found" | "forbidden">`
73
+ * ✅ `Result<User, GetUserError>` where `GetUserError` is a named type
74
+ */
75
+ export declare const noInlineErrorUnionsRule: ESLintUtils.RuleModule<"inlineUnion", [{
76
+ resultTypeName: string;
77
+ }], unknown, ESLintUtils.RuleListener> & {
78
+ name: string;
79
+ };
80
+ /**
81
+ * Creates rules that detect inline error unions in Result types
82
+ *
83
+ * Prevents the use of inline union types in Result<T, E> signatures,
84
+ * enforcing extraction to documented named types.
85
+ * @param options Configuration options for error handling rules
86
+ * @returns ESLint rules that detect inline error unions in Result types
87
+ */
88
+ export declare function createInlineErrorUnionRules(options?: ErrorHandlingRuleOptions): Linter.RulesRecord;
89
+ /**
90
+ * Creates a complete set of error handling rules with all components
91
+ *
92
+ * This is the main function that combines all error handling rules
93
+ * into a single configuration object.
94
+ * @param options Configuration options for error handling rules
95
+ * @returns Complete set of error handling ESLint rules
96
+ */
97
+ export declare function createErrorHandlingRules(options?: ErrorHandlingRuleOptions): Linter.RulesRecord;
98
+ /**
99
+ * Preset for Platform-specific error handling rules
100
+ * @returns ESLint rules configured for platform projects
101
+ */
102
+ export declare function createPlatformErrorHandlingRules(): Linter.RulesRecord;
103
+ //# sourceMappingURL=error-handling.d.ts.map