@@ -14,11 +14,11 @@ Always use inline type imports for type-only imports:
1414
1515```ts
1616// ✅ Good
17- import { type RuleModule, ESLintUtils } from "@typescript-eslint/utils "
17+ import { type User, fetchUser } from "./api "
1818
1919// ❌ Bad - separate import statement
20- import type { RuleModule } from "@typescript-eslint/utils "
21- import { ESLintUtils } from "@typescript-eslint/utils "
20+ import type { User } from "./api "
21+ import { fetchUser } from "./api "
2222```
2323
2424### Import Order
@@ -30,9 +30,9 @@ import { ESLintUtils } from "@typescript-eslint/utils"
3030```ts
3131import path from "node:path"
3232
33- import { ESLintUtils } from "@typescript-eslint/utils "
33+ import { z } from "zod "
3434
35- import { createRule } from "../utils/create-rule .js"
35+ import { validateInput } from "../utils/validation .js"
3636```
3737
3838## Type Definitions
@@ -43,15 +43,17 @@ Prefer `interface` for object shapes:
4343
4444```ts
4545// ✅ Good
46- interface RuleOptions {
47- allowedCallees: string[]
48- maxDepth: number
46+ interface UserProfile {
47+ name: string
48+ email: string
49+ age: number
4950}
5051
5152// ❌ Bad
52- type RuleOptions = {
53- allowedCallees: string[]
54- maxDepth: number
53+ type UserProfile = {
54+ name: string
55+ email: string
56+ age: number
5557}
5658```
5759
@@ -61,28 +63,32 @@ Prefer simple union string types over enums or const object patterns:
6163
6264```ts
6365// ✅ Good - simple union type
64- type MessageId = "complexExpression " | "nestedMacro "
66+ type Status = "idle " | "loading" | "success" | "error "
6567
6668// ❌ Bad - const object (fake enum pattern)
67- const MessageId = {
68- ComplexExpression: "complexExpression",
69- NestedMacro: "nestedMacro",
69+ const Status = {
70+ Idle: "idle",
71+ Loading: "loading",
72+ Success: "success",
73+ Error: "error",
7074} as const
71- type MessageId = (typeof MessageId )[keyof typeof MessageId ]
75+ type Status = (typeof Status )[keyof typeof Status ]
7276
7377// ❌ Bad - enum
74- enum MessageId {
75- ComplexExpression = "complexExpression",
76- NestedMacro = "nestedMacro",
78+ enum Status {
79+ Idle = "idle",
80+ Loading = "loading",
81+ Success = "success",
82+ Error = "error",
7783}
7884```
7985
8086When you need both runtime values and types, extract the union from an array:
8187
8288```ts
8389// ✅ Good - array as source of truth
84- const LINGUI_MACROS = ["t ", "Trans ", "msg", "defineMessage "] as const
85- type LinguiMacro = (typeof LINGUI_MACROS )[number]
90+ const SUPPORTED_FORMATS = ["json ", "yaml ", "toml "] as const
91+ type SupportedFormat = (typeof SUPPORTED_FORMATS )[number]
8692```
8793
8894## Functions
@@ -93,22 +99,22 @@ Use function declarations for pure functions, utility functions, and React compo
9399
94100```ts
95101// ✅ Good - pure function
96- function isLinguiMacro(node: TSESTree.Node ): boolean {
97- return node.type === "TaggedTemplateExpression"
102+ function formatCurrency(amount: number, currency: string ): string {
103+ return new Intl.NumberFormat("en-US", { style: "currency", currency }).format(amount)
98104}
99105
100106// ✅ Good - React component
101- function UserProfile ({ user }: UserProfileProps ): React.ReactElement {
107+ function UserCard ({ user }: UserCardProps ): React.ReactElement {
102108 return <div>{user.name}</div>
103109}
104110
105111// ❌ Bad - arrow function for pure function
106- const isLinguiMacro = (node: TSESTree.Node ): boolean => {
107- return node.type === "TaggedTemplateExpression"
112+ const formatCurrency = (amount: number, currency: string ): string => {
113+ return new Intl.NumberFormat("en-US", { style: "currency", currency }).format(amount)
108114}
109115
110116// ❌ Bad - arrow function for component
111- const UserProfile = ({ user }: UserProfileProps ) => {
117+ const UserCard = ({ user }: UserCardProps ) => {
112118 return <div>{user.name}</div>
113119}
114120```
@@ -118,14 +124,14 @@ const UserProfile = ({ user }: UserProfileProps) => {
118124Use arrow functions for inline callbacks and closures:
119125
120126```ts
121- // ✅ Good
122- const listeners = nodes .filter((node ) => isRelevant(node) )
127+ // ✅ Good - inline callback
128+ const activeUsers = users .filter((user ) => user.isActive )
123129
124130// ✅ Good - closure capturing context
125- function createVisitor(context: RuleContext ) {
131+ function createHandler(config: Config ) {
126132 return {
127- CallExpression : (node ) => {
128- // Uses context from closure
133+ onSubmit : (data ) => {
134+ // Uses config from closure
129135 },
130136 }
131137}
@@ -137,14 +143,12 @@ All exported functions must have explicit return types:
137143
138144```ts
139145// ✅ Good
140- export function createRule<TOptions extends unknown[]>(
141- options: RuleCreatorOptions<TOptions>
142- ): RuleModule<string, TOptions> {
146+ export function parseConfig(input: string): Config {
143147 // ...
144148}
145149
146150// ❌ Bad - missing return type
147- export function createRule(options ) {
151+ export function parseConfig(input ) {
148152 // ...
149153}
150154```
@@ -154,14 +158,14 @@ export function createRule(options) {
154158Use type guards for narrowing:
155159
156160```ts
157- function isCallExpression(node: TSESTree.Node ): node is TSESTree.CallExpression {
158- return node.type === "CallExpression"
161+ function isError(value: unknown ): value is Error {
162+ return value instanceof Error
159163}
160164
161165// Usage
162- if (isCallExpression(node )) {
163- // node is typed as CallExpression here
164- console.log(node.callee )
166+ if (isError(result )) {
167+ // result is typed as Error here
168+ console.log(result.message )
165169}
166170```
167171
@@ -170,22 +174,24 @@ if (isCallExpression(node)) {
170174### Variables
171175
172176- **Boolean**: Use auxiliary verbs: `isLoading`, `hasError`, `shouldUpdate`
173- - **Collections**: Use plural: `items`, `rules `, `visitors `
174- - **Functions**: Use verbs: `createRule `, `isValid`, `getType `
177+ - **Collections**: Use plural: `items`, `users `, `options `
178+ - **Functions**: Use verbs: `createUser `, `isValid`, `getConfig `
175179
176180### Files
177181
178- - **Rules**: `rule-name.ts` (kebab-case)
179- - **Utils**: `createRule.ts` (camelCase)
180- - **Types**: `types.ts` or inline
182+ - **Components**: `UserCard.tsx` (PascalCase)
183+ - **Utilities**: `formatCurrency.ts` (camelCase)
184+ - **Constants/Config**: `constants.ts`, `config.ts` (camelCase)
185+ - **Types**: `types.ts` or co-located
181186
182187### Constants
183188
184189Use SCREAMING_SNAKE_CASE for true constants:
185190
186191```ts
187- const LINGUI_MACROS = ["t", "Trans", "msg", "defineMessage"] as const
188- const DEFAULT_MAX_DEPTH = 1
192+ const SUPPORTED_FORMATS = ["json", "yaml", "toml"] as const
193+ const DEFAULT_TIMEOUT_MS = 5000
194+ const API_BASE_URL = "https://api.example.com"
189195```
190196
191197## Nullish Handling
@@ -204,10 +210,10 @@ const value = input || defaultValue
204210
205211```ts
206212// ✅ Good
207- const name = user?.profile?.name
213+ const city = user?.address?.city
208214
209215// ❌ Bad
210- const name = user && user.profile && user.profile.name
216+ const city = user && user.address && user.address.city
211217```
212218
213219## Comments
@@ -219,31 +225,33 @@ Use JSDoc without type annotations - let TypeScript handle the types:
219225```ts
220226// ✅ Good - TypeScript style (no type annotations in JSDoc)
221227/**
222- * Creates an ESLint rule for Lingui macro validation .
228+ * Formats a date relative to now (e.g., "2 hours ago") .
223229 *
224- * @param options - Rule configuration options
225- * @returns Configured ESLint rule module
230+ * @param date - The date to format
231+ * @param locale - Optional locale for formatting
232+ * @returns Human-readable relative time string
226233 */
227- export function createLinguiRule(options: RuleOptions ): RuleModule {
234+ export function formatRelativeTime(date: Date, locale?: string ): string {
228235 // ...
229236}
230237
231238// ❌ Bad - JavaScript style (redundant type annotations)
232239/**
233- * Creates an ESLint rule for Lingui macro validation .
240+ * Formats a date relative to now .
234241 *
235- * @param {RuleOptions} options - Rule configuration options
236- * @returns {RuleModule} Configured ESLint rule module
242+ * @param {Date} date - The date to format
243+ * @param {string} [locale] - Optional locale
244+ * @returns {string} Relative time string
237245 */
238- export function createLinguiRule(options: RuleOptions ): RuleModule {
246+ export function formatRelativeTime(date: Date, locale?: string ): string {
239247 // ...
240248}
241249```
242250
243251### Inline Comments for Non-Obvious Logic
244252
245253```ts
246- // TypeScript's getTypeAtLocation returns the *widened* type for literals ,
247- // so we need to check the contextual type instead
248- const contextualType = typeChecker.getContextualType(node )
254+ // Intl.RelativeTimeFormat doesn't auto-select units ,
255+ // so we need to determine the appropriate unit ourselves
256+ const units = selectTimeUnit(diffMs )
249257```
0 commit comments