@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,338 @@
1
+ import { TSESTree as T, TSESLint, ESLintUtils } from "@typescript-eslint/utils";
2
+ import { appendImports, insertImports, isSolidV2, removeSpecifier } from "../utils";
3
+ import { getSourceCode } from "../compat";
4
+
5
+ const createRule = ESLintUtils.RuleCreator.withoutDocs;
6
+
7
+ // Below: create maps of imports and types to designated import source.
8
+ // We could mess with `Object.keys(require("solid-js"))` to generate this, but requiring it from
9
+ // node activates the "node" export condition, which doesn't necessarily match what users will
10
+ // receive i.e. through bundlers. Instead, we're manually listing all of the public exports that
11
+ // should be imported from "solid-js", etc.
12
+ // ==============
13
+
14
+ type Source = "solid-js" | "solid-js/web" | "solid-js/store";
15
+
16
+ // Set up map of imports to module
17
+ const primitiveMap = new Map<string, Source>();
18
+ for (const primitive of [
19
+ "createSignal",
20
+ "createEffect",
21
+ "createMemo",
22
+ "createResource",
23
+ "onMount",
24
+ "onCleanup",
25
+ "onError",
26
+ "untrack",
27
+ "batch",
28
+ "on",
29
+ "createRoot",
30
+ "getOwner",
31
+ "runWithOwner",
32
+ "mergeProps",
33
+ "splitProps",
34
+ "useTransition",
35
+ "observable",
36
+ "from",
37
+ "mapArray",
38
+ "indexArray",
39
+ "createContext",
40
+ "useContext",
41
+ "children",
42
+ "lazy",
43
+ "createUniqueId",
44
+ "createDeferred",
45
+ "createRenderEffect",
46
+ "createComputed",
47
+ "createReaction",
48
+ "createSelector",
49
+ "DEV",
50
+ "For",
51
+ "Show",
52
+ "Switch",
53
+ "Match",
54
+ "Index",
55
+ "ErrorBoundary",
56
+ "Suspense",
57
+ "SuspenseList",
58
+ ]) {
59
+ primitiveMap.set(primitive, "solid-js");
60
+ }
61
+ for (const primitive of [
62
+ "Portal",
63
+ "render",
64
+ "hydrate",
65
+ "renderToString",
66
+ "renderToStream",
67
+ "isServer",
68
+ "renderToStringAsync",
69
+ "generateHydrationScript",
70
+ "HydrationScript",
71
+ "Dynamic",
72
+ ]) {
73
+ primitiveMap.set(primitive, "solid-js/web");
74
+ }
75
+ for (const primitive of [
76
+ "createStore",
77
+ "produce",
78
+ "reconcile",
79
+ "unwrap",
80
+ "createMutable",
81
+ "modifyMutable",
82
+ ]) {
83
+ primitiveMap.set(primitive, "solid-js/store");
84
+ }
85
+
86
+ // Set up map of type imports to module
87
+ const typeMap = new Map<string, Source>();
88
+ for (const type of [
89
+ "Signal",
90
+ "Accessor",
91
+ "Setter",
92
+ "Resource",
93
+ "ResourceActions",
94
+ "ResourceOptions",
95
+ "ResourceReturn",
96
+ "ResourceFetcher",
97
+ "InitializedResourceReturn",
98
+ "Component",
99
+ "VoidProps",
100
+ "VoidComponent",
101
+ "ParentProps",
102
+ "ParentComponent",
103
+ "FlowProps",
104
+ "FlowComponent",
105
+ "ValidComponent",
106
+ "ComponentProps",
107
+ "Ref",
108
+ "MergeProps",
109
+ "SplitPrips",
110
+ "Context",
111
+ "JSX",
112
+ "ResolvedChildren",
113
+ "MatchProps",
114
+ ]) {
115
+ typeMap.set(type, "solid-js");
116
+ }
117
+ for (const type of [/* "JSX", */ "MountableElement"]) {
118
+ typeMap.set(type, "solid-js/web");
119
+ }
120
+ for (const type of ["StoreNode", "Store", "SetStoreFunction"]) {
121
+ typeMap.set(type, "solid-js/store");
122
+ }
123
+
124
+ // Solid 2.0 moved these exports into core "solid-js" (and removed the old locations in
125
+ // some cases). Don't flag them when imported from the source listed here, so the rule
126
+ // gives correct guidance for both Solid 1.x and 2.0 projects.
127
+ const alternateSourceMap = new Map<string, Source>([
128
+ ["createStore", "solid-js"],
129
+ ["reconcile", "solid-js"],
130
+ ["Store", "solid-js"],
131
+ ["StoreNode", "solid-js"],
132
+ ["SetStoreFunction", "solid-js"],
133
+ ]);
134
+
135
+ // ==============
136
+ // Solid 2.0: "solid-js/store" no longer exists (store exports live in core),
137
+ // and "solid-js/web" became the "@solidjs/web" package. When `settings.solid.
138
+ // version` is 2, the rule *requires* the 2.0 locations. Lists verified against
139
+ // the RC source (packages/solid/src/index.ts, packages/solid-web/src/index.ts).
140
+ type SourceV2 = "solid-js" | "@solidjs/web";
141
+
142
+ const primitiveMapV2 = new Map<string, SourceV2>();
143
+ for (const primitive of [
144
+ // reactive primitives
145
+ "createSignal",
146
+ "createMemo",
147
+ "createStore",
148
+ "createEffect",
149
+ "createRenderEffect",
150
+ "createProjection",
151
+ "createOptimistic",
152
+ "createOptimisticStore",
153
+ "createReaction",
154
+ "createRoot",
155
+ "createOwner",
156
+ "createContext",
157
+ "useContext",
158
+ "children",
159
+ "lazy",
160
+ "createUniqueId",
161
+ "createErrorBoundary",
162
+ "createLoadingBoundary",
163
+ "createRevealOrder",
164
+ // lifecycle and utilities
165
+ "onCleanup",
166
+ "onSettled",
167
+ "untrack",
168
+ "flush",
169
+ "merge",
170
+ "omit",
171
+ "mapArray",
172
+ "repeat",
173
+ "getOwner",
174
+ "getObserver",
175
+ "runWithOwner",
176
+ "isEqual",
177
+ "isPending",
178
+ "latest",
179
+ "action",
180
+ "reconcile",
181
+ "snapshot",
182
+ "resolve",
183
+ "refresh",
184
+ "flatten",
185
+ "deep",
186
+ "affects",
187
+ "storePath",
188
+ "DEV",
189
+ // control flow components
190
+ "For",
191
+ "Repeat",
192
+ "Show",
193
+ "Switch",
194
+ "Match",
195
+ "Errored",
196
+ "Loading",
197
+ "Reveal",
198
+ ]) {
199
+ primitiveMapV2.set(primitive, "solid-js");
200
+ }
201
+ for (const primitive of [
202
+ "Portal",
203
+ "Dynamic",
204
+ "render",
205
+ "hydrate",
206
+ "isServer",
207
+ "renderToString",
208
+ "renderToStream",
209
+ // renderToStringAsync no longer exists in 2.0 (removed-api reports it)
210
+ "generateHydrationScript",
211
+ "HydrationScript",
212
+ ]) {
213
+ primitiveMapV2.set(primitive, "@solidjs/web");
214
+ }
215
+
216
+ const typeMapV2 = new Map<string, SourceV2>();
217
+ for (const type of [
218
+ "Signal",
219
+ "Accessor",
220
+ "Setter",
221
+ "Component",
222
+ "Context",
223
+ "ResolvedChildren",
224
+ "Store",
225
+ "StoreNode",
226
+ "StoreSetter",
227
+ "StoreOptions",
228
+ "SignalOptions",
229
+ "MemoOptions",
230
+ "EffectOptions",
231
+ "Owner",
232
+ ]) {
233
+ typeMapV2.set(type, "solid-js");
234
+ }
235
+ typeMapV2.set("JSX", "@solidjs/web");
236
+
237
+ const sourceRegex = /^solid-js(?:\/web|\/store)?$/;
238
+ const isSource = (source: string): source is Source => sourceRegex.test(source);
239
+
240
+ // The legacy "solid-js/store" and "solid-js/web" sources are deliberately
241
+ // excluded here: in v2 mode those modules no longer exist and solid/removed-api
242
+ // owns reporting/rewriting them, so this rule only polices specifier placement
243
+ // between the surviving modules.
244
+ const sourceRegexV2 = /^(?:solid-js|@solidjs\/web)$/;
245
+ const isSourceV2 = (source: string): source is Source | "@solidjs/web" =>
246
+ sourceRegexV2.test(source);
247
+
248
+ export default createRule({
249
+ meta: {
250
+ type: "suggestion",
251
+ docs: {
252
+ description:
253
+ 'Enforce consistent imports from "solid-js", "solid-js/web", and "solid-js/store".',
254
+ url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/packages/eslint-plugin-solid/docs/imports.md",
255
+ },
256
+ fixable: "code",
257
+ schema: [],
258
+ messages: {
259
+ "prefer-source": 'Prefer importing {{name}} from "{{source}}".',
260
+ },
261
+ },
262
+ defaultOptions: [],
263
+ create(context) {
264
+ const v2 = isSolidV2(context);
265
+ return {
266
+ ImportDeclaration(node) {
267
+ const source = node.source.value;
268
+ if (v2 ? !isSourceV2(source) : !isSource(source)) return;
269
+
270
+ for (const specifier of node.specifiers) {
271
+ if (specifier.type === "ImportSpecifier") {
272
+ const isType = specifier.importKind === "type" || node.importKind === "type";
273
+ const map = v2
274
+ ? isType
275
+ ? typeMapV2
276
+ : primitiveMapV2
277
+ : isType
278
+ ? typeMap
279
+ : primitiveMap;
280
+ const importedName =
281
+ specifier.imported.type === "Identifier"
282
+ ? specifier.imported.name
283
+ : specifier.imported.value;
284
+ const correctSource = map.get(importedName);
285
+ if (
286
+ correctSource != null &&
287
+ correctSource !== source &&
288
+ (v2 || alternateSourceMap.get(importedName) !== source)
289
+ ) {
290
+ context.report({
291
+ node: specifier,
292
+ messageId: "prefer-source",
293
+ data: {
294
+ name: importedName,
295
+ source: correctSource,
296
+ },
297
+ fix(fixer) {
298
+ const sourceCode = getSourceCode(context);
299
+ const program: T.Program = sourceCode.ast;
300
+ const correctDeclaration = program.body.find(
301
+ (node) =>
302
+ node.type === "ImportDeclaration" && node.source.value === correctSource
303
+ ) as T.ImportDeclaration | undefined;
304
+
305
+ if (correctDeclaration) {
306
+ return [
307
+ removeSpecifier(fixer, sourceCode, specifier),
308
+ appendImports(fixer, sourceCode, correctDeclaration, [
309
+ sourceCode.getText(specifier),
310
+ ]),
311
+ ].filter(Boolean) as Array<TSESLint.RuleFix>;
312
+ }
313
+
314
+ const firstSolidDeclaration = program.body.find(
315
+ (node) =>
316
+ node.type === "ImportDeclaration" &&
317
+ (v2 ? isSourceV2(node.source.value) : isSource(node.source.value))
318
+ ) as T.ImportDeclaration | undefined;
319
+ return [
320
+ removeSpecifier(fixer, sourceCode, specifier),
321
+ insertImports(
322
+ fixer,
323
+ sourceCode,
324
+ correctSource,
325
+ [sourceCode.getText(specifier)],
326
+ firstSolidDeclaration,
327
+ node.importKind === "type"
328
+ ),
329
+ ];
330
+ },
331
+ });
332
+ }
333
+ }
334
+ }
335
+ },
336
+ };
337
+ },
338
+ });
@@ -0,0 +1,110 @@
1
+ /**
2
+ * FIXME: remove this comments and import when below issue is fixed.
3
+ * This import is necessary for type generation due to a bug in the TypeScript compiler.
4
+ * See: https://github.com/microsoft/TypeScript/issues/42873
5
+ */
6
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
7
+ import type { TSESLint } from "@typescript-eslint/utils";
8
+
9
+ import { TSESTree as T, ESLintUtils } from "@typescript-eslint/utils";
10
+ import { isSolidV2, jsxGetAllProps } from "../utils";
11
+
12
+ const createRule = ESLintUtils.RuleCreator.withoutDocs;
13
+
14
+ /*
15
+ * This rule is adapted from eslint-plugin-react's jsx-no-duplicate-props rule under
16
+ * the MIT license, with some enhancements. Thank you for your work!
17
+ */
18
+
19
+ type MessageIds =
20
+ | "noDuplicateProps"
21
+ | "noDuplicateClass"
22
+ | "noDuplicateClassV2"
23
+ | "noDuplicateChildren";
24
+ type Options = [{ ignoreCase?: boolean }?];
25
+
26
+ export default createRule<Options, MessageIds>({
27
+ meta: {
28
+ type: "problem",
29
+ docs: {
30
+ description: "Disallow passing the same prop twice in JSX.",
31
+ url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/packages/eslint-plugin-solid/docs/jsx-no-duplicate-props.md",
32
+ },
33
+ schema: [
34
+ {
35
+ type: "object",
36
+ properties: {
37
+ ignoreCase: {
38
+ type: "boolean",
39
+ description: "Consider two prop names differing only by case to be the same.",
40
+ default: false,
41
+ },
42
+ },
43
+ additionalProperties: false,
44
+ },
45
+ ],
46
+ messages: {
47
+ noDuplicateProps: "Duplicate props are not allowed.",
48
+ noDuplicateClass:
49
+ "Duplicate `class` props are not allowed; while it might seem to work, it can break unexpectedly. Use `classList` instead.",
50
+ noDuplicateClassV2:
51
+ "Duplicate `class` props are not allowed; while it might seem to work, it can break unexpectedly. Merge them into one `class` — it accepts arrays and objects: `class={[base, { active: active() }]}`.",
52
+ noDuplicateChildren: "Using {{used}} at the same time is not allowed.",
53
+ },
54
+ },
55
+ defaultOptions: [],
56
+ create(context) {
57
+ const v2 = isSolidV2(context);
58
+ return {
59
+ JSXOpeningElement(node) {
60
+ const ignoreCase = context.options[0]?.ignoreCase ?? false;
61
+ const props = new Set();
62
+ const checkPropName = (name: string, node: T.Node) => {
63
+ if (ignoreCase || name.startsWith("on")) {
64
+ name = name
65
+ .toLowerCase()
66
+ .replace(/^on(?:capture)?:/, "on")
67
+ .replace(/^(?:attr|prop):/, "");
68
+ }
69
+ if (props.has(name)) {
70
+ context.report({
71
+ node,
72
+ messageId:
73
+ // classList was removed in 2.0; `class` takes arrays/objects itself
74
+ name === "class"
75
+ ? v2
76
+ ? "noDuplicateClassV2"
77
+ : "noDuplicateClass"
78
+ : "noDuplicateProps",
79
+ });
80
+ }
81
+ props.add(name);
82
+ };
83
+
84
+ for (const [name, propNode] of jsxGetAllProps(node.attributes)) {
85
+ checkPropName(name, propNode);
86
+ }
87
+
88
+ const hasChildrenProp = props.has("children");
89
+ const hasChildren = (node.parent as T.JSXElement | T.JSXFragment).children.length > 0;
90
+ const hasInnerHTML = props.has("innerHTML") || props.has("innerhtml");
91
+ const hasTextContent = props.has("textContent") || props.has("textcontent");
92
+ const used = [
93
+ hasChildrenProp && "`props.children`",
94
+ hasChildren && "JSX children",
95
+ hasInnerHTML && "`props.innerHTML`",
96
+ hasTextContent && "`props.textContent`",
97
+ ].filter(Boolean);
98
+ if (used.length > 1) {
99
+ context.report({
100
+ node,
101
+ messageId: "noDuplicateChildren",
102
+ data: {
103
+ used: used.join(", "),
104
+ },
105
+ });
106
+ }
107
+ },
108
+ };
109
+ },
110
+ });
@@ -0,0 +1,61 @@
1
+ /**
2
+ * FIXME: remove this comments and import when below issue is fixed.
3
+ * This import is necessary for type generation due to a bug in the TypeScript compiler.
4
+ * See: https://github.com/microsoft/TypeScript/issues/42873
5
+ */
6
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
7
+ import type { TSESLint } from "@typescript-eslint/utils";
8
+
9
+ import { ESLintUtils, ASTUtils } from "@typescript-eslint/utils";
10
+ import { getScope } from "../compat";
11
+
12
+ const createRule = ESLintUtils.RuleCreator.withoutDocs;
13
+ const { getStaticValue }: { getStaticValue: any } = ASTUtils;
14
+
15
+ // A javascript: URL can contain leading C0 control or \u0020 SPACE,
16
+ // and any newline or tab are filtered out as if they're not part of the URL.
17
+ // https://url.spec.whatwg.org/#url-parsing
18
+ // Tab or newline are defined as \r\n\t:
19
+ // https://infra.spec.whatwg.org/#ascii-tab-or-newline
20
+ // A C0 control is a code point in the range \u0000 NULL to \u001F
21
+ // INFORMATION SEPARATOR ONE, inclusive:
22
+ // https://infra.spec.whatwg.org/#c0-control-or-space
23
+ const isJavaScriptProtocol =
24
+ /^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*:/i; // eslint-disable-line no-control-regex
25
+
26
+ /**
27
+ * This rule is adapted from eslint-plugin-react's jsx-no-script-url rule under the MIT license.
28
+ * Thank you for your work!
29
+ */
30
+ export default createRule({
31
+ meta: {
32
+ type: "problem",
33
+ docs: {
34
+ description: "Disallow javascript: URLs.",
35
+ url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/packages/eslint-plugin-solid/docs/jsx-no-script-url.md",
36
+ },
37
+ schema: [],
38
+ messages: {
39
+ noJSURL: "For security, don't use javascript: URLs. Use event handlers instead if you can.",
40
+ },
41
+ },
42
+ defaultOptions: [],
43
+ create(context) {
44
+ return {
45
+ JSXAttribute(node) {
46
+ if (node.name.type === "JSXIdentifier" && node.value) {
47
+ const link: { value: unknown } | null = getStaticValue(
48
+ node.value.type === "JSXExpressionContainer" ? node.value.expression : node.value,
49
+ getScope(context, node)
50
+ );
51
+ if (link && typeof link.value === "string" && isJavaScriptProtocol.test(link.value)) {
52
+ context.report({
53
+ node: node.value,
54
+ messageId: "noJSURL",
55
+ });
56
+ }
57
+ }
58
+ },
59
+ };
60
+ },
61
+ });