@@ -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
1616const createRule = ESLintUtils.RuleCreator(
1717 (name) => `https://github.com/your-org/eslint-plugin-lingui/docs/rules/${name}.md`
18- );
18+ )
1919
2020export 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
61114create(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(
93146function 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
115168function 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
119172function 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
124177function 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 {
135188function getTemplateExpressions(
136189 node: TSESTree.TaggedTemplateExpression
137190): TSESTree.Expression[] {
138- return node.quasi.expressions;
191+ return node.quasi.expressions
139192}
140193
141194function 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
154207function 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
166219function 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
192245const ruleTester = new RuleTester({
193246 languageOptions: {
@@ -200,7 +253,7 @@ const ruleTester = new RuleTester({
200253 },
201254 },
202255 },
203- });
256+ })
204257
205258ruleTester.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
266319const 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