atom.io 0.44.9 → 0.44.11

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.
@@ -0,0 +1,256 @@
1
+ import type { TSESTree } from "@typescript-eslint/utils"
2
+ import { AST_NODE_TYPES, ESLintUtils } from "@typescript-eslint/utils"
3
+ import type {
4
+ InterfaceType,
5
+ Symbol as TsSymbol,
6
+ Type,
7
+ TypeNode,
8
+ } from "typescript"
9
+
10
+ const createRule = ESLintUtils.RuleCreator(
11
+ (name) => `https://atom.io.fyi/docs/eslint-plugin#${name}`,
12
+ )
13
+
14
+ const STATE_FUNCTIONS_WITH_CATCH = [
15
+ `atom`,
16
+ `atomFamily`,
17
+ `selector`,
18
+ `selectorFamily`,
19
+ ]
20
+ const FAMILY_FUNCTIONS = [`atomFamily`, `selectorFamily`]
21
+
22
+ export const exactCatchTypes: ESLintUtils.RuleModule<
23
+ `extraneousErrorTypes` | `invalidCatchProperty` | `missingCatchProperty`,
24
+ [],
25
+ unknown,
26
+ ESLintUtils.RuleListener
27
+ > = createRule({
28
+ name: `catch-constructor-type`,
29
+ meta: {
30
+ type: `problem`,
31
+ docs: {
32
+ description: `Ensures that when an error type (E) is provided to an atom, the 'catch' property is set and all constructors in it are assignable to E.`,
33
+ },
34
+ messages: {
35
+ missingCatchProperty:
36
+ `This {{functionName}} was provided the error type \`{{errorTypeName}}\` ` +
37
+ `but the required 'catch' property is missing from its options. ` +
38
+ `Either remove \`{{errorTypeName}}\`, or add \`catch: [{{errorTypeName}}]\` to the options object.`,
39
+ invalidCatchProperty:
40
+ `This {{functionName}} was provided a catch array containing the class \`{{constructorName}}\`. ` +
41
+ `However, that class is not represented in the {{functionName}}'s error type, \`{{errorTypeName}}\`. ` +
42
+ `As a result, it might catch errors that the {{functionName}} is not designed to handle. ` +
43
+ `Either include \`{{constructorName}}\` in the {{functionName}}'s error type, or remove it from the 'catch' array.`,
44
+ extraneousErrorTypes:
45
+ `This {{functionName}} was provided an error type including the class \`{{errorTypeName}}\`, ` +
46
+ `but its 'catch' property doesn't include a constructor for that class. ` +
47
+ `Either include a constructor for \`{{errorTypeName}}\` in the 'catch' array, or remove \`{{errorTypeName}}\` as a possible error type.`,
48
+ },
49
+ schema: [],
50
+ },
51
+ defaultOptions: [],
52
+ create(context) {
53
+ const parserServices = ESLintUtils.getParserServices(context)
54
+ const checker = parserServices.program.getTypeChecker()
55
+
56
+ return {
57
+ CallExpression(node) {
58
+ const {
59
+ callee,
60
+ typeArguments: directTypeArguments,
61
+ arguments: callArguments,
62
+ } = node
63
+
64
+ let errorParamIndex = 1
65
+
66
+ // Check if the function call is one of the targeted state functions
67
+ let functionName: string | null = null
68
+ if (callee.type === AST_NODE_TYPES.Identifier) {
69
+ if (STATE_FUNCTIONS_WITH_CATCH.includes(callee.name)) {
70
+ functionName = callee.name
71
+ }
72
+ } else if (callee.type === AST_NODE_TYPES.MemberExpression) {
73
+ if (
74
+ callee.property.type === AST_NODE_TYPES.Identifier &&
75
+ STATE_FUNCTIONS_WITH_CATCH.includes(callee.property.name)
76
+ ) {
77
+ functionName = callee.property.name
78
+ }
79
+ }
80
+
81
+ if (!functionName) return
82
+
83
+ // Where do the type arguments come from?
84
+ let typeArguments: TSESTree.TSTypeParameterInstantiation | undefined
85
+ if (directTypeArguments) {
86
+ typeArguments = directTypeArguments
87
+ } else {
88
+ const parent = node.parent
89
+ if (
90
+ parent?.type === AST_NODE_TYPES.VariableDeclarator &&
91
+ parent.init === node
92
+ ) {
93
+ // Check if the VariableDeclarator has an id with a TypeAnnotation
94
+ const declaratorId = parent.id
95
+ if (declaratorId.type === AST_NODE_TYPES.Identifier) {
96
+ // Check for 'const myAtom: AtomToken<string> = ...'
97
+ const typeAnnotation = declaratorId.typeAnnotation?.typeAnnotation
98
+ if (
99
+ typeAnnotation &&
100
+ `typeArguments` in typeAnnotation &&
101
+ typeAnnotation.typeArguments
102
+ ) {
103
+ typeArguments = typeAnnotation.typeArguments
104
+ errorParamIndex = 2 // AtomToken<T, K, E>
105
+ }
106
+ }
107
+ }
108
+ }
109
+
110
+ const optionsObject = callArguments[0]
111
+
112
+ if (optionsObject?.type !== AST_NODE_TYPES.ObjectExpression) return
113
+
114
+ const isFamilyDeclaration = FAMILY_FUNCTIONS.includes(functionName)
115
+ if (isFamilyDeclaration) {
116
+ errorParamIndex = 2 // atomFamily<T, K, E>
117
+ }
118
+
119
+ const errorTypeNode = typeArguments
120
+ ? typeArguments.params[errorParamIndex]
121
+ : undefined
122
+ if (!errorTypeNode) {
123
+ return
124
+ }
125
+
126
+ let catchProperty: TSESTree.Property | undefined
127
+ optionsObject.properties.forEach((property) => {
128
+ if (property.type === AST_NODE_TYPES.Property) {
129
+ if (
130
+ (property.key.type === AST_NODE_TYPES.Identifier &&
131
+ property.key.name === `catch`) ||
132
+ (property.key.type === AST_NODE_TYPES.Literal &&
133
+ property.key.value === `catch`)
134
+ ) {
135
+ catchProperty = property
136
+ }
137
+ }
138
+ })
139
+
140
+ const typeNode = parserServices.esTreeNodeToTSNodeMap.get(
141
+ errorTypeNode,
142
+ ) as TypeNode
143
+ // Get the TypeScript Type object for E
144
+ const errorTypeTs = checker.getTypeFromTypeNode(typeNode)
145
+ const errorTypeName = checker.typeToString(errorTypeTs)
146
+
147
+ if (!catchProperty) {
148
+ context.report({
149
+ node: optionsObject,
150
+ messageId: `missingCatchProperty`,
151
+ data: { functionName, errorTypeName },
152
+ })
153
+ return
154
+ }
155
+
156
+ // --- New Validation: Check Constructor Types ---
157
+ const catchArray = catchProperty.value
158
+ if (catchArray.type !== AST_NODE_TYPES.ArrayExpression) {
159
+ // We only check array literals (e.g., [Ctor1, Ctor2])
160
+ return
161
+ }
162
+
163
+ // 3. Collect all acceptable nominal symbols from E
164
+ const acceptableErrorSymbols: TsSymbol[] = []
165
+
166
+ // Check if E is a Union Type
167
+ if (errorTypeTs.isUnion()) {
168
+ // Add the symbol of every member of the union (e.g., Symbol(SpecialError), Symbol(FancyError))
169
+ for (const memberType of errorTypeTs.types) {
170
+ const symbol = memberType.getSymbol()
171
+ if (symbol) {
172
+ acceptableErrorSymbols.push(symbol)
173
+ }
174
+ }
175
+ } else {
176
+ // E is a single type, add its symbol
177
+ const symbol = errorTypeTs.getSymbol()
178
+ if (symbol) {
179
+ acceptableErrorSymbols.push(symbol)
180
+ }
181
+ }
182
+
183
+ if (catchArray.elements.length === 0) {
184
+ context.report({
185
+ node: catchProperty,
186
+ messageId: `missingCatchProperty`,
187
+ data: { functionName, errorTypeName },
188
+ })
189
+ return
190
+ }
191
+ const errorSymbolsToRepresent = new Set(acceptableErrorSymbols)
192
+
193
+ // Iterate over each constructor reference in the 'catch' array
194
+ for (const element of catchArray.elements) {
195
+ if (!element || element.type !== AST_NODE_TYPES.Identifier) {
196
+ // Only check simple identifier references (e.g., [ClientError])
197
+ continue
198
+ }
199
+
200
+ // Get the type of the constructor identifier (e.g., the Type of 'Error')
201
+ const constructorTsNode =
202
+ parserServices.esTreeNodeToTSNodeMap.get(element)
203
+ const constructorType = checker.getTypeAtLocation(constructorTsNode)
204
+ const constructorName = element.name
205
+
206
+ // console.log(`constructorName`, constructorName)
207
+
208
+ // Extract the instance type from the constructor type.
209
+ // e.g., turn 'typeof ClientError' into 'ClientError'
210
+ let instanceType: Type | undefined
211
+ if (
212
+ (constructorType as InterfaceType).getConstructSignatures().length >
213
+ 0
214
+ ) {
215
+ // Get the return type of the constructor signature
216
+ const signature = (
217
+ constructorType as InterfaceType
218
+ ).getConstructSignatures()[0]
219
+ instanceType = signature.getReturnType()
220
+ }
221
+
222
+ // If we couldn't get the instance type, skip the check
223
+
224
+ const constructorInstanceSymbol = instanceType?.getSymbol()
225
+ if (!constructorInstanceSymbol) continue
226
+
227
+ // Check for symbol identity
228
+ if (acceptableErrorSymbols.includes(constructorInstanceSymbol)) {
229
+ errorSymbolsToRepresent.delete(constructorInstanceSymbol)
230
+ } else {
231
+ context.report({
232
+ node: element,
233
+ messageId: `invalidCatchProperty`,
234
+ data: {
235
+ functionName,
236
+ constructorName: constructorName,
237
+ errorTypeName: errorTypeName,
238
+ },
239
+ })
240
+ }
241
+ }
242
+
243
+ for (const errorSymbol of errorSymbolsToRepresent) {
244
+ context.report({
245
+ node: catchProperty,
246
+ messageId: `extraneousErrorTypes`,
247
+ data: {
248
+ errorTypeName: checker.symbolToString(errorSymbol),
249
+ functionName,
250
+ },
251
+ })
252
+ }
253
+ },
254
+ }
255
+ },
256
+ })
@@ -1,5 +1,4 @@
1
1
  /* eslint-disable @typescript-eslint/switch-exhaustiveness-check */
2
- import type { TSESTree } from "@typescript-eslint/utils"
3
2
  import { AST_NODE_TYPES, ESLintUtils } from "@typescript-eslint/utils"
4
3
 
5
4
  const createRule = ESLintUtils.RuleCreator(
@@ -1 +1,2 @@
1
+ export * from "./exact-catch-types"
1
2
  export * from "./explicit-state-types"
@@ -1,5 +1,5 @@
1
1
  import { useI } from "atom.io/react"
2
- import type { RoomSocketInterface } from "atom.io/realtime/shared-room-store"
2
+ import type { RoomSocketInterface } from "atom.io/realtime"
3
3
  import * as RTC from "atom.io/realtime-client"
4
4
  import * as React from "react"
5
5
  import type { Socket } from "socket.io-client"
@@ -1,7 +1,6 @@
1
+ import type { Flat } from "atom.io/internal"
1
2
  import type { primitive } from "atom.io/json"
2
3
 
3
- import type { Flat } from "../internal/utility-types"
4
-
5
4
  export type IndexOf<
6
5
  T extends readonly unknown[],
7
6
  E,
@@ -1,28 +0,0 @@
1
- import { JoinToken, MutableAtomToken, ReadonlyPureSelectorFamilyToken } from "atom.io";
2
- import { UList } from "atom.io/transceivers/u-list";
3
-
4
- //#region src/realtime/realtime-key-types.d.ts
5
- type SocketKey = `socket::${string}`;
6
- declare const isSocketKey: (key: string) => key is SocketKey;
7
- type UserKey = `user::${string}`;
8
- declare const isUserKey: (key: string) => key is UserKey;
9
- type RoomKey = `room::${string}`;
10
- declare const isRoomKey: (key: string) => key is RoomKey;
11
- //#endregion
12
- //#region src/realtime/shared-room-store.d.ts
13
- type RoomSocketInterface<RoomNames extends string> = {
14
- createRoom: (roomName: RoomNames) => void;
15
- joinRoom: (roomKey: string) => void;
16
- [leaveRoom: `leaveRoom:${string}`]: () => void;
17
- [deleteRoom: `deleteRoom:${string}`]: () => void;
18
- };
19
- declare const roomKeysAtom: MutableAtomToken<UList<string>>;
20
- type UserInRoomMeta = {
21
- enteredAtEpoch: number;
22
- };
23
- declare const DEFAULT_USER_IN_ROOM_META: UserInRoomMeta;
24
- declare const usersInRooms: JoinToken<`room`, RoomKey, `user`, UserKey, `1:n`>;
25
- declare const usersInMyRoomView: ReadonlyPureSelectorFamilyToken<MutableAtomToken<UList<RoomKey>>[], UserKey>;
26
- //#endregion
27
- export { usersInMyRoomView as a, SocketKey as c, isSocketKey as d, isUserKey as f, roomKeysAtom as i, UserKey as l, RoomSocketInterface as n, usersInRooms as o, UserInRoomMeta as r, RoomKey as s, DEFAULT_USER_IN_ROOM_META as t, isRoomKey as u };
28
- //# sourceMappingURL=shared-room-store-ZPGD_PIR.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"shared-room-store-ZPGD_PIR.d.ts","names":["roomKeysAtom: MutableAtomToken<UList<string>>","DEFAULT_USER_IN_ROOM_META: UserInRoomMeta","usersInRooms: JoinToken<`room`, RoomKey, `user`, UserKey, `1:n`>","usersInMyRoomView: ReadonlyPureSelectorFamilyToken<\n\tMutableAtomToken<UList<RoomKey>>[],\n\tUserKey\n>"],"sources":["../src/realtime/realtime-key-types.ts","../src/realtime/shared-room-store.ts"],"sourcesContent":[],"mappings":";;;;KAAY,SAAA;cACC,qCAAoC;KAGrC,OAAA;cACC,mCAAkC;AALnC,KAQA,OAAA,GARA,SAAA,MAAA,EAAA;AACC,cAQA,SARoC,EAAA,CAAA,GAAA,EAAA,MAAA,EAAA,GAAA,GAAA,IAQF,OARE;;;KCcrC;EDfZ,UAAY,EAAA,CAAA,QAAA,ECgBY,SDhBZ,EAAA,GAAA,IAAA;EACZ,QAAa,EAAA,CAAA,OAAoC,EAAA,MAAA,EAAA,GAAA,IAAA;EAGjD,CAAA,SAAY,EAAA,aAAA,MAAA,EAAA,CAAA,EAAA,GAAA,GAAA,IAAA;EACZ,CAAA,UAAa,EAAA,cAAkC,MAAA,EAAA,CAAA,EAAA,GAAA,GAAA,IAAA;AAG/C,CAAA;AACa,cCaAA,YDbkC,ECapB,gBDboB,CCaH,KDbG,CAAA,MAAA,CAAA,CAAA;KCkBnC,cAAA;;;AAZA,cAeCC,yBAfD,EAe4B,cAdhB;AAMXD,cAWAE,YAX+B,EAWjB,SAXA,CAAA,MAAA,EAWkB,OAXlB,EAAA,MAAA,EAWmC,OAXnC,EAAA,KAAA,CAAA;AAKf,cAeCC,iBAfD,EAeoB,+BAfpB,CAgBX,gBAhBW,CAgBM,KAhBN,CAgBY,OAhBZ,CAAA,CAAA,EAAA,EAiBX,OAjBW,CAAA"}
@@ -1,10 +0,0 @@
1
- //#region src/internal/utility-types.d.ts
2
- type Fn = (...parameters: any[]) => any;
3
- type Ctor<T> = new (...args: any[]) => T;
4
- type Flat<R extends { [K in PropertyKey]: any }> = { [K in keyof R]: R[K] };
5
- type Count<N extends number, A extends any[] = []> = [...A, any][`length`] extends N ? A[`length`] : A[`length`] | Count<N, [...A, any]>;
6
- type Each<E extends any[]> = { [P in Count<E[`length`]>]: E[P] };
7
- type Refinement<A, B extends A> = (a: A) => a is B;
8
- //#endregion
9
- export { Fn as a, Flat as i, Ctor as n, Refinement as o, Each as r, Count as t };
10
- //# sourceMappingURL=utility-types-BRPuC_bI.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"utility-types-BRPuC_bI.d.ts","names":[],"sources":["../src/internal/utility-types.ts"],"sourcesContent":[],"mappings":";KAAY,EAAA;AAAA,KAEA,IAFA,CAAA,CAAA,CAAA,GAAA,KAAA,GAAA,IAAA,EAAA,GAAA,EAAA,EAAA,GAEkC,CAFlC;AAEA,KAEA,IAFA,CAAA,UAAkC,QAEX,WAFW,GAAA,GAAA,EAAA,CAAA,GAAA,QAAA,MAGjC,CAHiC,GAG7B,CAH6B,CAG3B,CAH2B,CAAA,EAAA;AAElC,KAIA,KAJA,CAAuB,UAAA,MAAA,EACtB,UAAA,GAAA,EAAA,GAAA,EAAA,CAAI,GAAA,CAAA,GAIb,CAJa,EAAA,GAAA,CAAA,CAAA,QAAA,CAAA,SAMG,CANH,GAOd,CAPc,CAAA,QAAA,CAAA,GAQd,CARc,CAAA,QAAA,CAAA,GAQA,KARA,CAQM,CARN,EAAA,CAAA,GAQa,CARb,EAAA,GAAA,CAAA,CAAA;AAAE,KAUP,IAVO,CAAA,UAAA,GAAA,EAAA,CAAA,GAAA,QAWZ,KAXY,CAWN,CAXM,CAAA,QAAA,CAAA,CAAA,GAWS,CAXT,CAWW,CAXX,CAAA,EAAA;AAAA,KAcP,UAdO,CAGnB,CAAA,EACI,UAUgC,CAVhC,CAEgB,GAAA,CAAA,CAAA,EAQyB,CARzB,EAAA,GAAA,CAAA,IAQoC,CARpC"}