@qq99/eslint-plugin-solid 0.18.0-no-reactive-read-in-effect-callback.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 (90) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +289 -0
  3. package/dist/chunk-4UZNJ5MS.mjs +32 -0
  4. package/dist/chunk-4UZNJ5MS.mjs.map +1 -0
  5. package/dist/chunk-EBU4C5NV.mjs +77 -0
  6. package/dist/chunk-EBU4C5NV.mjs.map +1 -0
  7. package/dist/chunk-LE6SW2M3.mjs +6231 -0
  8. package/dist/chunk-LE6SW2M3.mjs.map +1 -0
  9. package/dist/chunk-MHDQJMLE.mjs +39 -0
  10. package/dist/chunk-MHDQJMLE.mjs.map +1 -0
  11. package/dist/configs/recommended.d.mts +109 -0
  12. package/dist/configs/recommended.d.ts +109 -0
  13. package/dist/configs/recommended.js +5877 -0
  14. package/dist/configs/recommended.js.map +1 -0
  15. package/dist/configs/recommended.mjs +5 -0
  16. package/dist/configs/recommended.mjs.map +1 -0
  17. package/dist/configs/typescript.d.mts +103 -0
  18. package/dist/configs/typescript.d.ts +103 -0
  19. package/dist/configs/typescript.js +6246 -0
  20. package/dist/configs/typescript.js.map +1 -0
  21. package/dist/configs/typescript.mjs +6 -0
  22. package/dist/configs/typescript.mjs.map +1 -0
  23. package/dist/configs/v2-strict.d.mts +130 -0
  24. package/dist/configs/v2-strict.d.ts +130 -0
  25. package/dist/configs/v2-strict.js +6317 -0
  26. package/dist/configs/v2-strict.js.map +1 -0
  27. package/dist/configs/v2-strict.mjs +7 -0
  28. package/dist/configs/v2-strict.mjs.map +1 -0
  29. package/dist/configs/v2.d.mts +127 -0
  30. package/dist/configs/v2.d.ts +127 -0
  31. package/dist/configs/v2.js +6289 -0
  32. package/dist/configs/v2.js.map +1 -0
  33. package/dist/configs/v2.mjs +6 -0
  34. package/dist/configs/v2.mjs.map +1 -0
  35. package/dist/index.d.mts +740 -0
  36. package/dist/index.d.ts +740 -0
  37. package/dist/index.js +6365 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/index.mjs +42 -0
  40. package/dist/index.mjs.map +1 -0
  41. package/package.json +109 -0
  42. package/src/compat.ts +53 -0
  43. package/src/configs/recommended.ts +48 -0
  44. package/src/configs/typescript.ts +21 -0
  45. package/src/configs/v2-strict.ts +28 -0
  46. package/src/configs/v2.ts +68 -0
  47. package/src/deps.d.ts +1 -0
  48. package/src/index.ts +29 -0
  49. package/src/plugin.ts +92 -0
  50. package/src/rules/components-return-once.ts +209 -0
  51. package/src/rules/event-handlers.ts +397 -0
  52. package/src/rules/imports.ts +338 -0
  53. package/src/rules/jsx-no-duplicate-props.ts +110 -0
  54. package/src/rules/jsx-no-script-url.ts +61 -0
  55. package/src/rules/jsx-no-undef.ts +230 -0
  56. package/src/rules/jsx-uses-vars.ts +66 -0
  57. package/src/rules/no-accessor-as-prop.ts +147 -0
  58. package/src/rules/no-array-handlers.ts +57 -0
  59. package/src/rules/no-boolean-enumerated-attribute.ts +209 -0
  60. package/src/rules/no-browser-globals-in-server-function.ts +108 -0
  61. package/src/rules/no-destructure.ts +245 -0
  62. package/src/rules/no-innerhtml.ts +143 -0
  63. package/src/rules/no-invalid-server-capture.ts +104 -0
  64. package/src/rules/no-module-scope-reactive-primitive.ts +69 -0
  65. package/src/rules/no-proxy-apis.ts +96 -0
  66. package/src/rules/no-react-deps.ts +68 -0
  67. package/src/rules/no-react-specific-props.ts +60 -0
  68. package/src/rules/no-reactive-read-in-effect-callback.ts +570 -0
  69. package/src/rules/no-restated-default-options.ts +96 -0
  70. package/src/rules/no-single-arg-create-effect.ts +56 -0
  71. package/src/rules/no-store-mutation-outside-setter.ts +137 -0
  72. package/src/rules/no-unknown-namespaces.ts +140 -0
  73. package/src/rules/no-unused-signal.ts +119 -0
  74. package/src/rules/no-write-in-pure-computation.ts +231 -0
  75. package/src/rules/prefer-classlist.ts +93 -0
  76. package/src/rules/prefer-for.ts +93 -0
  77. package/src/rules/prefer-onSettled-for-side-effects.ts +101 -0
  78. package/src/rules/prefer-show.ts +102 -0
  79. package/src/rules/prefer-structured-class.ts +117 -0
  80. package/src/rules/reactivity.ts +1730 -0
  81. package/src/rules/removed-api.ts +212 -0
  82. package/src/rules/require-async-server-function.ts +122 -0
  83. package/src/rules/self-closing-comp.ts +150 -0
  84. package/src/rules/style-prop.ts +140 -0
  85. package/src/rules/valid-use-server.ts +192 -0
  86. package/src/rules/validate-jsx-nesting.ts +1 -0
  87. package/src/utils/reactive-imports.ts +382 -0
  88. package/src/utils/reactive-types.ts +272 -0
  89. package/src/utils/reactive-values.ts +162 -0
  90. package/src/utils.ts +442 -0
@@ -0,0 +1,272 @@
1
+ import ts from "typescript";
2
+
3
+ export interface TypeModule {
4
+ source: ts.SourceFile;
5
+ checker: ts.TypeChecker;
6
+ }
7
+
8
+ type Fact = "scalar" | "callback" | "nullish" | "void" | null;
9
+ type TypeArgument = { module: TypeModule; node: ts.Node; env: Environment };
10
+ type Environment = Map<ts.Symbol, TypeArgument>;
11
+
12
+ /** Only prove scalar snapshots and conventional void callback signatures.
13
+ * Object types never establish (or disprove) reactivity. No parser services,
14
+ * standard libraries, project type check, or execution of application code.
15
+ */
16
+ export function createReactiveTypeResolver(
17
+ filename: string,
18
+ text: string,
19
+ loadImport: (source: string, from: string) => TypeModule | null,
20
+ solidSource: RegExp
21
+ ) {
22
+ let current: TypeModule | undefined;
23
+ const nodes = new Map<string, ts.Node>();
24
+ const facts = new Map<string, Fact>();
25
+ let steps = 0;
26
+
27
+ const initialize = (): TypeModule => {
28
+ if (current) return current;
29
+ // The active buffer, not its potentially stale on-disk contents. This host
30
+ // is intentionally in-memory so virtual inputs and the browser still work.
31
+ const file = /\.[cm]?[jt]sx?$/.test(filename) ? filename : "input.tsx";
32
+ const source = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true);
33
+ const program = ts.createProgram(
34
+ [file],
35
+ { noLib: true, noResolve: true, types: [] },
36
+ {
37
+ getSourceFile: (name) => (name === file ? source : undefined),
38
+ getDefaultLibFileName: () => "",
39
+ writeFile() {},
40
+ getCurrentDirectory: () => "/",
41
+ getDirectories: () => [],
42
+ getCanonicalFileName: (name) => name,
43
+ useCaseSensitiveFileNames: () => true,
44
+ getNewLine: () => "\n",
45
+ fileExists: (name) => name === file,
46
+ readFile: (name) => (name === file ? text : undefined),
47
+ }
48
+ );
49
+ const visit = (node: ts.Node): void => {
50
+ nodes.set(`${node.getStart(source)}:${node.end}`, node);
51
+ ts.forEachChild(node, visit);
52
+ };
53
+ visit(source);
54
+ return (current = { source, checker: program.getTypeChecker() });
55
+ };
56
+
57
+ const sourceOf = (node: ts.Node): string | null => {
58
+ for (let parent: ts.Node | undefined = node; parent; parent = parent.parent) {
59
+ if (ts.isImportDeclaration(parent) || ts.isExportDeclaration(parent)) {
60
+ return parent.moduleSpecifier && ts.isStringLiteral(parent.moduleSpecifier)
61
+ ? parent.moduleSpecifier.text
62
+ : null;
63
+ }
64
+ }
65
+ return null;
66
+ };
67
+
68
+ const combine = (values: Fact[]): Fact => {
69
+ if (values.some((value) => value === null)) return null;
70
+ const concrete = values.filter((value) => value !== "nullish");
71
+ return concrete.length === 0
72
+ ? "nullish"
73
+ : concrete.every((value) => value === concrete[0])
74
+ ? concrete[0]
75
+ : null;
76
+ };
77
+
78
+ const exported = (
79
+ module: TypeModule,
80
+ name: string,
81
+ path: string[],
82
+ env: Environment,
83
+ seen: Set<ts.Node>,
84
+ args: TypeArgument[] = []
85
+ ): Fact => {
86
+ if (++steps > 1000) return null;
87
+ const symbol = module.checker.getSymbolAtLocation(module.source);
88
+ const target = symbol && module.checker.getExportsOfModule(symbol).find((s) => s.name === name);
89
+ if (target) return declaration(module, target, path, env, seen, args);
90
+ for (const statement of module.source.statements) {
91
+ if (!ts.isExportDeclaration(statement) || statement.exportClause || seen.has(statement))
92
+ continue;
93
+ const source = sourceOf(statement);
94
+ const next = source && loadImport(source, module.source.fileName);
95
+ if (!next) continue;
96
+ const fact = exported(next, name, path, env, new Set(seen).add(statement), args);
97
+ if (fact) return fact;
98
+ }
99
+ return null;
100
+ };
101
+
102
+ const declaration = (
103
+ module: TypeModule,
104
+ symbol: ts.Symbol | undefined,
105
+ path: string[],
106
+ env: Environment,
107
+ seen: Set<ts.Node>,
108
+ args: TypeArgument[] = []
109
+ ): Fact => {
110
+ if (!symbol) return null;
111
+ const substitution = env.get(symbol);
112
+ if (substitution)
113
+ return evaluate(substitution.module, substitution.node, path, substitution.env, seen);
114
+ const declarations = symbol.declarations ?? [];
115
+ // Merged declarations can disagree; never use just one to prove a scalar.
116
+ if (declarations.length !== 1) return null;
117
+ const node = declarations[0];
118
+ if (ts.isTypeAliasDeclaration(node) || ts.isInterfaceDeclaration(node)) {
119
+ const local = new Map(env);
120
+ node.typeParameters?.forEach((param, index) => {
121
+ const symbol = module.checker.getSymbolAtLocation(param.name);
122
+ const value = args[index] ?? (param.default ? { module, node: param.default, env } : null);
123
+ if (symbol && value) local.set(symbol, value);
124
+ });
125
+ return evaluate(module, node, path, local, seen);
126
+ }
127
+ return evaluate(module, node, path, env, seen, args);
128
+ };
129
+
130
+ const evaluate = (
131
+ module: TypeModule,
132
+ node: ts.Node,
133
+ path: string[],
134
+ env: Environment,
135
+ seen: Set<ts.Node>,
136
+ args: TypeArgument[] = []
137
+ ): Fact => {
138
+ if (++steps > 1000 || seen.has(node)) return null;
139
+ const next = new Set(seen).add(node);
140
+ const read = (node: ts.Node, keys = path): Fact => evaluate(module, node, keys, env, next);
141
+ if (ts.isIdentifier(node))
142
+ return declaration(module, module.checker.getSymbolAtLocation(node), path, env, next, args);
143
+ if (ts.isTypeReferenceNode(node) || ts.isExpressionWithTypeArguments(node)) {
144
+ const name = ts.isTypeReferenceNode(node) ? node.typeName : node.expression;
145
+ const typeArgs = node.typeArguments?.map((node) => ({ module, node, env })) ?? [];
146
+ if (ts.isIdentifier(name)) {
147
+ const symbol = module.checker.getSymbolAtLocation(name);
148
+ const imported = symbol?.declarations?.[0];
149
+ // Solid's wrappers preserve the supplied props. Do not need to load
150
+ // the renderer's external declaration graph just to inspect them.
151
+ if (imported && ts.isImportSpecifier(imported)) {
152
+ const source = sourceOf(imported);
153
+ if (
154
+ source &&
155
+ solidSource.test(source) &&
156
+ ["ParentProps", "FlowProps"].includes((imported.propertyName ?? imported.name).text)
157
+ ) {
158
+ return node.typeArguments?.[0] ? read(node.typeArguments[0]) : null;
159
+ }
160
+ }
161
+ if (
162
+ !symbol?.declarations?.length &&
163
+ ["Readonly", "Partial", "Required"].includes(name.text)
164
+ ) {
165
+ return node.typeArguments?.[0] ? read(node.typeArguments[0]) : null;
166
+ }
167
+ return declaration(module, symbol, path, env, next, typeArgs);
168
+ }
169
+ if (ts.isQualifiedName(name) && ts.isIdentifier(name.left)) {
170
+ const imported = module.checker.getSymbolAtLocation(name.left)?.declarations?.[0];
171
+ const source = imported && ts.isNamespaceImport(imported) && sourceOf(imported);
172
+ const target = source && loadImport(source, module.source.fileName);
173
+ return target ? exported(target, name.right.text, path, env, next, typeArgs) : null;
174
+ }
175
+ return null;
176
+ }
177
+ if (ts.isImportSpecifier(node) || ts.isImportClause(node) || ts.isExportSpecifier(node)) {
178
+ const source = sourceOf(node);
179
+ if (!source && ts.isExportSpecifier(node)) {
180
+ return declaration(
181
+ module,
182
+ module.checker.getExportSpecifierLocalTargetSymbol(node),
183
+ path,
184
+ env,
185
+ next,
186
+ args
187
+ );
188
+ }
189
+ const target = source && loadImport(source, module.source.fileName);
190
+ const name = ts.isImportClause(node) ? "default" : (node.propertyName ?? node.name).text;
191
+ return target ? exported(target, name, path, env, next, args) : null;
192
+ }
193
+ if (ts.isPropertyAccessExpression(node))
194
+ return read(node.expression, [node.name.text, ...path]);
195
+ if (ts.isElementAccessExpression(node)) {
196
+ const key = node.argumentExpression;
197
+ return ts.isStringLiteralLike(key) || ts.isNumericLiteral(key)
198
+ ? read(node.expression, [key.text, ...path])
199
+ : null;
200
+ }
201
+ if (ts.isVariableDeclaration(node) || ts.isParameter(node)) {
202
+ return node.type ? read(node.type) : node.initializer ? read(node.initializer) : null;
203
+ }
204
+ if (ts.isTypeAliasDeclaration(node) || ts.isParenthesizedTypeNode(node)) return read(node.type);
205
+ if (ts.isParenthesizedExpression(node) || ts.isNonNullExpression(node))
206
+ return read(node.expression);
207
+ if (ts.isAsExpression(node) || ts.isTypeAssertionExpression(node)) return read(node.type);
208
+ if (ts.isUnionTypeNode(node)) return combine(node.types.map((type) => read(type)));
209
+ if (ts.isIntersectionTypeNode(node)) {
210
+ const known = node.types.map((type) => read(type)).filter((value) => value !== null);
211
+ return known.length ? combine(known) : null;
212
+ }
213
+ if (ts.isTypeLiteralNode(node) || ts.isInterfaceDeclaration(node)) {
214
+ if (!path.length) return null;
215
+ const member = node.members.find(
216
+ (member) =>
217
+ member.name &&
218
+ (ts.isIdentifier(member.name) ||
219
+ ts.isStringLiteralLike(member.name) ||
220
+ ts.isNumericLiteral(member.name)) &&
221
+ member.name.text === path[0]
222
+ );
223
+ if (member && ts.isPropertySignature(member) && member.type)
224
+ return read(member.type, path.slice(1));
225
+ if (member && ts.isMethodSignature(member) && path.length === 1 && member.type) {
226
+ return read(member.type, []) === "void" ? "callback" : null;
227
+ }
228
+ if (ts.isInterfaceDeclaration(node)) {
229
+ const inherited =
230
+ node.heritageClauses
231
+ ?.flatMap((clause) => clause.types.map((type) => read(type)))
232
+ .filter((value) => value !== null) ?? [];
233
+ return inherited.length ? combine(inherited) : null;
234
+ }
235
+ return null;
236
+ }
237
+ if (ts.isArrayTypeNode(node) && path.length && /^\d+$/.test(path[0]))
238
+ return read(node.elementType, path.slice(1));
239
+ if (path.length) return null;
240
+ if (ts.isFunctionTypeNode(node)) return read(node.type, []) === "void" ? "callback" : null;
241
+ if (ts.isLiteralTypeNode(node)) return read(node.literal);
242
+ if (node.kind === ts.SyntaxKind.NullKeyword || node.kind === ts.SyntaxKind.UndefinedKeyword)
243
+ return "nullish";
244
+ if (node.kind === ts.SyntaxKind.VoidKeyword) return "void";
245
+ if (
246
+ [
247
+ ts.SyntaxKind.StringKeyword,
248
+ ts.SyntaxKind.NumberKeyword,
249
+ ts.SyntaxKind.BooleanKeyword,
250
+ ts.SyntaxKind.BigIntKeyword,
251
+ ts.SyntaxKind.SymbolKeyword,
252
+ ts.SyntaxKind.TrueKeyword,
253
+ ts.SyntaxKind.FalseKeyword,
254
+ ].includes(node.kind) ||
255
+ ts.isLiteralExpression(node)
256
+ )
257
+ return "scalar";
258
+ return null;
259
+ };
260
+
261
+ return (range: readonly [number, number]): "scalar" | "callback" | null => {
262
+ const key = `${range[0]}:${range[1]}`;
263
+ if (!facts.has(key)) {
264
+ const module = initialize();
265
+ const node = nodes.get(key);
266
+ steps = 0;
267
+ facts.set(key, node ? evaluate(module, node, [], new Map(), new Set()) : null);
268
+ }
269
+ const fact = facts.get(key);
270
+ return fact === "nullish" ? "scalar" : fact === "scalar" || fact === "callback" ? fact : null;
271
+ };
272
+ }
@@ -0,0 +1,162 @@
1
+ import type { TSESTree as T } from "@typescript-eslint/utils";
2
+
3
+ export type Value =
4
+ | { kind: "object" | "plain"; properties: Map<string, Value>; array?: boolean }
5
+ | { kind: "accessor" | "promise"; result: Value }
6
+ | { kind: "iterator"; async: boolean; yielded: Value; returned: Value }
7
+ | { kind: "union"; values: Value[] }
8
+ | { kind: "setter" }
9
+ | { kind: "scalar" }
10
+ | null;
11
+
12
+ export const object: Value = { kind: "object", properties: new Map() };
13
+ export const setter: Value = { kind: "setter" };
14
+ export const scalar: Value = { kind: "scalar" };
15
+ export const container = (entries: Array<[string, Value]>): Value => ({
16
+ kind: "plain",
17
+ properties: new Map(entries),
18
+ array: true,
19
+ });
20
+
21
+ // Awaiting unwraps only the outer value, never promises stored in its fields.
22
+ export const awaitedValue = (value: Value): Value =>
23
+ value?.kind === "promise"
24
+ ? awaitedValue(value.result)
25
+ : value?.kind === "union"
26
+ ? value.values.map(awaitedValue).reduce(mergeValues, null)
27
+ : value;
28
+
29
+ export const promiseValue = (value: Value): Value => ({
30
+ kind: "promise",
31
+ result: awaitedValue(value),
32
+ });
33
+
34
+ export const iteratorValue = (async: boolean, yielded: Value, returned: Value): Value => ({
35
+ kind: "iterator",
36
+ async,
37
+ // Native async generators await each yield/return, but sync generators do not.
38
+ yielded: async ? awaitedValue(yielded) : yielded,
39
+ returned: async ? awaitedValue(returned) : returned,
40
+ });
41
+
42
+ export const iterationValue = (value: Value): Value => {
43
+ if (value?.kind === "iterator") return value.yielded;
44
+ if (value?.kind === "union") return value.values.map(iterationValue).reduce(mergeValues, null);
45
+ if ((value?.kind === "object" || value?.kind === "plain") && value.array)
46
+ return [...value.properties]
47
+ .filter(([key]) => /^\d+$/.test(key))
48
+ .map(([, item]) => item)
49
+ .reduce(mergeValues, null);
50
+ return null;
51
+ };
52
+
53
+ // The result of yield* is the delegated iterator's final return, not its yields.
54
+ export const iterationReturn = (value: Value): Value =>
55
+ value?.kind === "iterator"
56
+ ? value.returned
57
+ : value?.kind === "union"
58
+ ? value.values.map(iterationReturn).reduce(mergeValues, null)
59
+ : null;
60
+
61
+ // Solid settles a Promise, then consumes one AsyncIterable. It neither drains
62
+ // ordinary sync iterators nor recursively consumes streams yielded as values.
63
+ export const computationValue = (value: Value): Value => {
64
+ value = awaitedValue(value);
65
+ if (value?.kind === "union") return value.values.map(computationValue).reduce(mergeValues, null);
66
+ return value?.kind === "iterator" && value.async ? value.yielded : value;
67
+ };
68
+
69
+ export const hasKind = (value: Value, kind: NonNullable<Value>["kind"]): boolean =>
70
+ value?.kind === kind ||
71
+ (value?.kind === "union" && value.values.some((entry) => hasKind(entry, kind)));
72
+
73
+ export const callResult = (value: Value): Value =>
74
+ value?.kind === "accessor"
75
+ ? value.result
76
+ : value?.kind === "union"
77
+ ? value.values.map(callResult).reduce(mergeValues, null)
78
+ : null;
79
+
80
+ export const propertiesOf = (value: Value): Map<string, Value> => {
81
+ if (value?.kind === "object" || value?.kind === "plain") return value.properties;
82
+ const properties = new Map<string, Value>();
83
+ if (value?.kind === "union") {
84
+ for (const entry of value.values) {
85
+ for (const [key, field] of propertiesOf(entry)) {
86
+ properties.set(key, mergeValues(properties.get(key) ?? null, field));
87
+ }
88
+ }
89
+ }
90
+ return properties;
91
+ };
92
+
93
+ // A store recursively wraps objects, but leaves primitive fields alone.
94
+ export const asStore = (value: Value): Value =>
95
+ value?.kind === "union"
96
+ ? value.values.map(asStore).reduce(mergeValues, null)
97
+ : value?.kind === "plain"
98
+ ? {
99
+ kind: "object",
100
+ properties: new Map([...value.properties].map(([key, value]) => [key, asStore(value)])),
101
+ array: value.array,
102
+ }
103
+ : value ?? object;
104
+
105
+ export const propertyValue = (value: Value, key: string | null): Value => {
106
+ if (value?.kind === "union")
107
+ return value.values.map((entry) => propertyValue(entry, key)).reduce(mergeValues, null);
108
+ if (value?.kind !== "object" && value?.kind !== "plain") return null;
109
+ if (key !== null && value.properties.has(key)) return value.properties.get(key)!;
110
+ return value.kind === "object" ? object : null;
111
+ };
112
+
113
+ export const propertyName = (key: T.Node, computed: boolean): string | null => {
114
+ if (!computed && key.type === "Identifier") return key.name;
115
+ if (key.type === "Literal" && (typeof key.value === "string" || typeof key.value === "number"))
116
+ return String(key.value);
117
+ return null;
118
+ };
119
+
120
+ export const mergeValues = (left: Value, right: Value): Value => {
121
+ if (!left) return right;
122
+ if (!right) return left;
123
+ if (left === right) return left;
124
+ if (left.kind === "promise" && right.kind === "promise")
125
+ return promiseValue(mergeValues(left.result, right.result));
126
+ if (left.kind === "iterator" && right.kind === "iterator" && left.async === right.async)
127
+ return iteratorValue(
128
+ left.async,
129
+ mergeValues(left.yielded, right.yielded),
130
+ mergeValues(left.returned, right.returned)
131
+ );
132
+ // Preserve wrapper/value alternatives until the consuming operation is known.
133
+ // Collapsing them early mistakes Promise/iterator methods for reactive reads.
134
+ if ([left.kind, right.kind].some((kind) => ["promise", "iterator", "union"].includes(kind))) {
135
+ return {
136
+ kind: "union",
137
+ values: [
138
+ ...new Set([
139
+ ...(left.kind === "union" ? left.values : [left]),
140
+ ...(right.kind === "union" ? right.values : [right]),
141
+ ]),
142
+ ],
143
+ };
144
+ }
145
+ if (left.kind === "scalar") return right;
146
+ if (right.kind === "scalar") return left;
147
+ if (
148
+ (left.kind === "object" || left.kind === "plain") &&
149
+ (right.kind === "object" || right.kind === "plain")
150
+ ) {
151
+ const properties = new Map(left.properties);
152
+ for (const [key, value] of right.properties) {
153
+ properties.set(key, mergeValues(properties.get(key) ?? null, value));
154
+ }
155
+ return {
156
+ kind: left.kind === "object" || right.kind === "object" ? "object" : "plain",
157
+ properties,
158
+ array: left.array && right.array,
159
+ };
160
+ }
161
+ return left;
162
+ };