Skip to content

Commit 619d849

Browse files
committed
chore(config): streamline conventions and configuration
- Prefer union string types over const objects/enums - Move ESLint-specific error handling to eslint-plugin-patterns - Add JSDoc TypeScript style (no type annotations) - Remove redundant tsconfig options covered by strict - Add options reference table with links - Update prettier: no semi, no trailing commas, 120 width
1 parent cb853e7 commit 619d849

File tree

5 files changed

+205
-130
lines changed

5 files changed

+205
-130
lines changed

.cursor/rules/eslint-plugin-patterns.mdc

Lines changed: 97 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ alwaysApply: false
1111
### Using @typescript-eslint/utils
1212

1313
```ts
14-
import { ESLintUtils, type TSESTree } from "@typescript-eslint/utils";
14+
import { ESLintUtils, type TSESTree } from "@typescript-eslint/utils"
1515

1616
const createRule = ESLintUtils.RuleCreator(
1717
(name) => `https://github.com/your-org/eslint-plugin-lingui/docs/rules/${name}.md`
18-
);
18+
)
1919

2020
export const noComplexExpressions = createRule({
2121
name: "no-complex-expressions-in-message",
@@ -46,27 +46,80 @@ export const noComplexExpressions = createRule({
4646
TaggedTemplateExpression(node) {
4747
// Rule implementation
4848
},
49-
};
49+
}
5050
},
51-
});
51+
})
52+
```
53+
54+
## Error Handling in Rules
55+
56+
### No Throwing in Rule Logic
57+
58+
ESLint rules should report errors, not throw. This ensures rules degrade gracefully when type information is unavailable:
59+
60+
```ts
61+
// ✅ Good - graceful degradation
62+
if (!parserServices.program) {
63+
// Skip type-aware checks, continue with syntax-only checks
64+
return
65+
}
66+
67+
// ✅ Good - report instead of throw
68+
if (isInvalidPattern(node)) {
69+
context.report({
70+
node,
71+
messageId: "invalidPattern",
72+
})
73+
}
74+
75+
// ❌ Bad - throws and crashes the linter
76+
if (!parserServices.program) {
77+
throw new Error("TypeScript program required")
78+
}
79+
```
80+
81+
### Safe Type Checker Access
82+
83+
Always check for type information availability before using it:
84+
85+
```ts
86+
create(context) {
87+
const parserServices = ESLintUtils.getParserServices(context, true)
88+
const hasTypeInfo = parserServices.program !== undefined
89+
90+
return {
91+
Literal(node) {
92+
// Syntax-only checks always run
93+
if (!isStringLiteral(node)) {
94+
return
95+
}
96+
97+
// Type-aware checks only when available
98+
if (hasTypeInfo) {
99+
const typeChecker = parserServices.program.getTypeChecker()
100+
// ... type-aware logic
101+
}
102+
},
103+
}
104+
}
52105
```
53106

54107
## Type-Aware Rules
55108

56109
### Accessing TypeScript Services
57110

58111
```ts
59-
import { ESLintUtils } from "@typescript-eslint/utils";
112+
import { ESLintUtils } from "@typescript-eslint/utils"
60113

61114
create(context) {
62115
// Get parser services (may or may not have type info)
63-
const parserServices = ESLintUtils.getParserServices(context, true);
116+
const parserServices = ESLintUtils.getParserServices(context, true)
64117

65118
// Check if type info is available
66-
const hasTypeInfo = parserServices.program !== undefined;
119+
const hasTypeInfo = parserServices.program !== undefined
67120

68121
if (hasTypeInfo) {
69-
const typeChecker = parserServices.program.getTypeChecker();
122+
const typeChecker = parserServices.program.getTypeChecker()
70123
// Use type-aware logic
71124
} else {
72125
// Fall back to syntax-only checks
@@ -81,9 +134,9 @@ function getTypeOfNode(
81134
node: TSESTree.Node,
82135
parserServices: ParserServicesWithTypeInformation
83136
): ts.Type {
84-
const typeChecker = parserServices.program.getTypeChecker();
85-
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
86-
return typeChecker.getTypeAtLocation(tsNode);
137+
const typeChecker = parserServices.program.getTypeChecker()
138+
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node)
139+
return typeChecker.getTypeAtLocation(tsNode)
87140
}
88141
```
89142

@@ -93,15 +146,15 @@ function getTypeOfNode(
93146
function isStringLiteralType(type: ts.Type): boolean {
94147
// Check for single string literal
95148
if (type.isStringLiteral()) {
96-
return true;
149+
return true
97150
}
98151

99152
// Check for union of string literals
100153
if (type.isUnion()) {
101-
return type.types.every((t) => t.isStringLiteral());
154+
return type.types.every((t) => t.isStringLiteral())
102155
}
103156

104-
return false;
157+
return false
105158
}
106159
```
107160

@@ -110,22 +163,22 @@ function isStringLiteralType(type: ts.Type): boolean {
110163
### Detecting Lingui Macros
111164

112165
```ts
113-
const LINGUI_MACROS = ["t", "Trans", "msg", "defineMessage"] as const;
166+
const LINGUI_MACROS = ["t", "Trans", "msg", "defineMessage"] as const
114167

115168
function isLinguiTaggedTemplate(node: TSESTree.TaggedTemplateExpression): boolean {
116-
return node.tag.type === "Identifier" && node.tag.name === "t";
169+
return node.tag.type === "Identifier" && node.tag.name === "t"
117170
}
118171

119172
function isLinguiJSXComponent(node: TSESTree.JSXElement): boolean {
120-
const name = node.openingElement.name;
121-
return name.type === "JSXIdentifier" && name.name === "Trans";
173+
const name = node.openingElement.name
174+
return name.type === "JSXIdentifier" && name.name === "Trans"
122175
}
123176

124177
function isLinguiCallExpression(node: TSESTree.CallExpression): boolean {
125178
if (node.callee.type === "Identifier") {
126-
return ["msg", "defineMessage"].includes(node.callee.name);
179+
return ["msg", "defineMessage"].includes(node.callee.name)
127180
}
128-
return false;
181+
return false
129182
}
130183
```
131184

@@ -135,7 +188,7 @@ function isLinguiCallExpression(node: TSESTree.CallExpression): boolean {
135188
function getTemplateExpressions(
136189
node: TSESTree.TaggedTemplateExpression
137190
): TSESTree.Expression[] {
138-
return node.quasi.expressions;
191+
return node.quasi.expressions
139192
}
140193

141194
function getJSXExpressions(
@@ -144,33 +197,33 @@ function getJSXExpressions(
144197
return node.children.filter(
145198
(child): child is TSESTree.JSXExpressionContainer =>
146199
child.type === "JSXExpressionContainer"
147-
);
200+
)
148201
}
149202
```
150203

151204
### Checking Expression Complexity
152205

153206
```ts
154207
function getMemberExpressionDepth(node: TSESTree.MemberExpression): number {
155-
let depth = 1;
156-
let current: TSESTree.Expression = node.object;
208+
let depth = 1
209+
let current: TSESTree.Expression = node.object
157210

158211
while (current.type === "MemberExpression") {
159-
depth++;
160-
current = current.object;
212+
depth++
213+
current = current.object
161214
}
162215

163-
return depth;
216+
return depth
164217
}
165218

166219
function isSimpleExpression(node: TSESTree.Expression): boolean {
167220
switch (node.type) {
168221
case "Identifier":
169-
return true;
222+
return true
170223
case "MemberExpression":
171-
return getMemberExpressionDepth(node) <= 1;
224+
return getMemberExpressionDepth(node) <= 1
172225
default:
173-
return false;
226+
return false
174227
}
175228
}
176229
```
@@ -180,14 +233,14 @@ function isSimpleExpression(node: TSESTree.Expression): boolean {
180233
### Using RuleTester
181234

182235
```ts
183-
import { RuleTester } from "@typescript-eslint/rule-tester";
184-
import { afterAll, describe, it } from "vitest";
185-
import { noComplexExpressions } from "./no-complex-expressions-in-message.js";
236+
import { RuleTester } from "@typescript-eslint/rule-tester"
237+
import { afterAll, describe, it } from "vitest"
238+
import { noComplexExpressions } from "./no-complex-expressions-in-message.js"
186239

187240
// Configure RuleTester to use Vitest
188-
RuleTester.afterAll = afterAll;
189-
RuleTester.describe = describe;
190-
RuleTester.it = it;
241+
RuleTester.afterAll = afterAll
242+
RuleTester.describe = describe
243+
RuleTester.it = it
191244

192245
const ruleTester = new RuleTester({
193246
languageOptions: {
@@ -200,7 +253,7 @@ const ruleTester = new RuleTester({
200253
},
201254
},
202255
},
203-
});
256+
})
204257

205258
ruleTester.run("no-complex-expressions-in-message", noComplexExpressions, {
206259
valid: [
@@ -213,7 +266,7 @@ ruleTester.run("no-complex-expressions-in-message", noComplexExpressions, {
213266
errors: [{ messageId: "complexExpression" }],
214267
},
215268
],
216-
});
269+
})
217270
```
218271

219272
### Type-Aware Test Cases
@@ -229,7 +282,7 @@ const ruleTesterWithTypes = new RuleTester({
229282
tsconfigRootDir: __dirname,
230283
},
231284
},
232-
});
285+
})
233286
```
234287

235288
## Error Messages
@@ -250,7 +303,7 @@ context.report({
250303
data: {
251304
expression: context.sourceCode.getText(node),
252305
},
253-
});
306+
})
254307
```
255308

256309
## Plugin Export
@@ -259,8 +312,8 @@ context.report({
259312

260313
```ts
261314
// src/index.ts
262-
import { noComplexExpressions } from "./rules/no-complex-expressions-in-message.js";
263-
import { noNestedMacros } from "./rules/no-nested-macros.js";
315+
import { noComplexExpressions } from "./rules/no-complex-expressions-in-message.js"
316+
import { noNestedMacros } from "./rules/no-nested-macros.js"
264317
// ... other rules
265318

266319
const plugin = {
@@ -285,7 +338,7 @@ const plugin = {
285338
},
286339
},
287340
},
288-
};
341+
}
289342

290-
export default plugin;
343+
export default plugin
291344
```

0 commit comments

Comments
 (0)