@valbuild/eslint-plugin 0.47.0 → 0.48.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.
- package/dist/valbuild-eslint-plugin.cjs.dev.js +46 -10
- package/dist/valbuild-eslint-plugin.cjs.prod.js +46 -10
- package/dist/valbuild-eslint-plugin.esm.js +46 -10
- package/package.json +3 -2
- package/src/index.js +2 -0
- package/src/rules/exportContentMustBeValid.js +44 -0
- package/src/rules/noIllegalImports.js +13 -7
- package/src/rules/noIllegalModuleIds.js +1 -2
- package/test/rules/exportContentMustBeValid.test.js +56 -0
- package/test/rules/noIllegalImports.test.js +40 -5
@@ -46,8 +46,7 @@ var noIllegalModuleIds = {
|
|
46
46
|
meta: {
|
47
47
|
type: "problem",
|
48
48
|
docs: {
|
49
|
-
description: "Check that the first argument of export default declaration matches the string from val.config.
|
50
|
-
category: "Best Practices",
|
49
|
+
description: "Check that the first argument of export default declaration matches the string from val.config.{j,t}s file.",
|
51
50
|
recommended: true
|
52
51
|
},
|
53
52
|
fixable: "code",
|
@@ -130,10 +129,8 @@ var noIllegalImports = {
|
|
130
129
|
type: "problem",
|
131
130
|
docs: {
|
132
131
|
description: "Check that val files only has valid imports.",
|
133
|
-
category: "Best Practices",
|
134
132
|
recommended: true
|
135
133
|
},
|
136
|
-
fixable: "code",
|
137
134
|
schema: []
|
138
135
|
},
|
139
136
|
create: function create(context) {
|
@@ -144,11 +141,49 @@ var noIllegalImports = {
|
|
144
141
|
var isValFile = filename.endsWith(".val.ts") || filename.endsWith(".val.js");
|
145
142
|
// only allow: .val files, @valbuild packages, and val config
|
146
143
|
if (isValFile && typeof importSource === "string" && !importSource.match(/\.val(\.ts|\.js|)$/) && !importSource.match(/^@valbuild/) && !importSource.match(/val\.config(\.ts|\.js|)$/)) {
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
message:
|
151
|
-
|
144
|
+
if ("importKind" in node && node["importKind"] !== "type" && !node.specifiers.every(function (s) {
|
145
|
+
return "importKind" in s && s["importKind"] === "type";
|
146
|
+
})) {
|
147
|
+
var message = "Val: can only 'import type' or import from source that is either: a .val.{j,t}s file, a @valbuild package, or val.config.{j,t}s.";
|
148
|
+
context.report({
|
149
|
+
node: node.source,
|
150
|
+
message: message
|
151
|
+
});
|
152
|
+
}
|
153
|
+
}
|
154
|
+
}
|
155
|
+
};
|
156
|
+
}
|
157
|
+
};
|
158
|
+
|
159
|
+
// @ts-check
|
160
|
+
|
161
|
+
/**
|
162
|
+
* @type {import('eslint').Rule.RuleModule}
|
163
|
+
*/
|
164
|
+
var exportContentMustBeValid = {
|
165
|
+
meta: {
|
166
|
+
type: "problem",
|
167
|
+
docs: {
|
168
|
+
description: "Export val.content should only happen in .val files.",
|
169
|
+
recommended: true
|
170
|
+
},
|
171
|
+
messages: {
|
172
|
+
"val/export-content-must-be-valid": "Val: val.content should only be exported from .val files"
|
173
|
+
},
|
174
|
+
schema: []
|
175
|
+
},
|
176
|
+
create: function create(context) {
|
177
|
+
return {
|
178
|
+
ExportDefaultDeclaration: function ExportDefaultDeclaration(node) {
|
179
|
+
if (node.declaration && node.declaration.type === "CallExpression" && node.declaration.callee.type === "MemberExpression" && node.declaration.callee.object.type === "Identifier" && node.declaration.callee.object.name === "val" && node.declaration.callee.property.type === "Identifier" && node.declaration.callee.property.name === "content") {
|
180
|
+
var filename = context.filename || context.getFilename();
|
181
|
+
if (!(filename !== null && filename !== void 0 && filename.endsWith(".val.ts") || filename !== null && filename !== void 0 && filename.endsWith(".val.js"))) {
|
182
|
+
context.report({
|
183
|
+
node: node.declaration.callee,
|
184
|
+
messageId: "val/export-content-must-be-valid"
|
185
|
+
});
|
186
|
+
}
|
152
187
|
}
|
153
188
|
}
|
154
189
|
};
|
@@ -162,7 +197,8 @@ var noIllegalImports = {
|
|
162
197
|
*/
|
163
198
|
var rules = {
|
164
199
|
"no-illegal-module-ids": noIllegalModuleIds,
|
165
|
-
"no-illegal-imports": noIllegalImports
|
200
|
+
"no-illegal-imports": noIllegalImports,
|
201
|
+
"export-content-must-be-valid": exportContentMustBeValid
|
166
202
|
};
|
167
203
|
|
168
204
|
/**
|
@@ -46,8 +46,7 @@ var noIllegalModuleIds = {
|
|
46
46
|
meta: {
|
47
47
|
type: "problem",
|
48
48
|
docs: {
|
49
|
-
description: "Check that the first argument of export default declaration matches the string from val.config.
|
50
|
-
category: "Best Practices",
|
49
|
+
description: "Check that the first argument of export default declaration matches the string from val.config.{j,t}s file.",
|
51
50
|
recommended: true
|
52
51
|
},
|
53
52
|
fixable: "code",
|
@@ -130,10 +129,8 @@ var noIllegalImports = {
|
|
130
129
|
type: "problem",
|
131
130
|
docs: {
|
132
131
|
description: "Check that val files only has valid imports.",
|
133
|
-
category: "Best Practices",
|
134
132
|
recommended: true
|
135
133
|
},
|
136
|
-
fixable: "code",
|
137
134
|
schema: []
|
138
135
|
},
|
139
136
|
create: function create(context) {
|
@@ -144,11 +141,49 @@ var noIllegalImports = {
|
|
144
141
|
var isValFile = filename.endsWith(".val.ts") || filename.endsWith(".val.js");
|
145
142
|
// only allow: .val files, @valbuild packages, and val config
|
146
143
|
if (isValFile && typeof importSource === "string" && !importSource.match(/\.val(\.ts|\.js|)$/) && !importSource.match(/^@valbuild/) && !importSource.match(/val\.config(\.ts|\.js|)$/)) {
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
message:
|
151
|
-
|
144
|
+
if ("importKind" in node && node["importKind"] !== "type" && !node.specifiers.every(function (s) {
|
145
|
+
return "importKind" in s && s["importKind"] === "type";
|
146
|
+
})) {
|
147
|
+
var message = "Val: can only 'import type' or import from source that is either: a .val.{j,t}s file, a @valbuild package, or val.config.{j,t}s.";
|
148
|
+
context.report({
|
149
|
+
node: node.source,
|
150
|
+
message: message
|
151
|
+
});
|
152
|
+
}
|
153
|
+
}
|
154
|
+
}
|
155
|
+
};
|
156
|
+
}
|
157
|
+
};
|
158
|
+
|
159
|
+
// @ts-check
|
160
|
+
|
161
|
+
/**
|
162
|
+
* @type {import('eslint').Rule.RuleModule}
|
163
|
+
*/
|
164
|
+
var exportContentMustBeValid = {
|
165
|
+
meta: {
|
166
|
+
type: "problem",
|
167
|
+
docs: {
|
168
|
+
description: "Export val.content should only happen in .val files.",
|
169
|
+
recommended: true
|
170
|
+
},
|
171
|
+
messages: {
|
172
|
+
"val/export-content-must-be-valid": "Val: val.content should only be exported from .val files"
|
173
|
+
},
|
174
|
+
schema: []
|
175
|
+
},
|
176
|
+
create: function create(context) {
|
177
|
+
return {
|
178
|
+
ExportDefaultDeclaration: function ExportDefaultDeclaration(node) {
|
179
|
+
if (node.declaration && node.declaration.type === "CallExpression" && node.declaration.callee.type === "MemberExpression" && node.declaration.callee.object.type === "Identifier" && node.declaration.callee.object.name === "val" && node.declaration.callee.property.type === "Identifier" && node.declaration.callee.property.name === "content") {
|
180
|
+
var filename = context.filename || context.getFilename();
|
181
|
+
if (!(filename !== null && filename !== void 0 && filename.endsWith(".val.ts") || filename !== null && filename !== void 0 && filename.endsWith(".val.js"))) {
|
182
|
+
context.report({
|
183
|
+
node: node.declaration.callee,
|
184
|
+
messageId: "val/export-content-must-be-valid"
|
185
|
+
});
|
186
|
+
}
|
152
187
|
}
|
153
188
|
}
|
154
189
|
};
|
@@ -162,7 +197,8 @@ var noIllegalImports = {
|
|
162
197
|
*/
|
163
198
|
var rules = {
|
164
199
|
"no-illegal-module-ids": noIllegalModuleIds,
|
165
|
-
"no-illegal-imports": noIllegalImports
|
200
|
+
"no-illegal-imports": noIllegalImports,
|
201
|
+
"export-content-must-be-valid": exportContentMustBeValid
|
166
202
|
};
|
167
203
|
|
168
204
|
/**
|
@@ -38,8 +38,7 @@ var noIllegalModuleIds = {
|
|
38
38
|
meta: {
|
39
39
|
type: "problem",
|
40
40
|
docs: {
|
41
|
-
description: "Check that the first argument of export default declaration matches the string from val.config.
|
42
|
-
category: "Best Practices",
|
41
|
+
description: "Check that the first argument of export default declaration matches the string from val.config.{j,t}s file.",
|
43
42
|
recommended: true
|
44
43
|
},
|
45
44
|
fixable: "code",
|
@@ -122,10 +121,8 @@ var noIllegalImports = {
|
|
122
121
|
type: "problem",
|
123
122
|
docs: {
|
124
123
|
description: "Check that val files only has valid imports.",
|
125
|
-
category: "Best Practices",
|
126
124
|
recommended: true
|
127
125
|
},
|
128
|
-
fixable: "code",
|
129
126
|
schema: []
|
130
127
|
},
|
131
128
|
create: function create(context) {
|
@@ -136,11 +133,49 @@ var noIllegalImports = {
|
|
136
133
|
var isValFile = filename.endsWith(".val.ts") || filename.endsWith(".val.js");
|
137
134
|
// only allow: .val files, @valbuild packages, and val config
|
138
135
|
if (isValFile && typeof importSource === "string" && !importSource.match(/\.val(\.ts|\.js|)$/) && !importSource.match(/^@valbuild/) && !importSource.match(/val\.config(\.ts|\.js|)$/)) {
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
message:
|
143
|
-
|
136
|
+
if ("importKind" in node && node["importKind"] !== "type" && !node.specifiers.every(function (s) {
|
137
|
+
return "importKind" in s && s["importKind"] === "type";
|
138
|
+
})) {
|
139
|
+
var message = "Val: can only 'import type' or import from source that is either: a .val.{j,t}s file, a @valbuild package, or val.config.{j,t}s.";
|
140
|
+
context.report({
|
141
|
+
node: node.source,
|
142
|
+
message: message
|
143
|
+
});
|
144
|
+
}
|
145
|
+
}
|
146
|
+
}
|
147
|
+
};
|
148
|
+
}
|
149
|
+
};
|
150
|
+
|
151
|
+
// @ts-check
|
152
|
+
|
153
|
+
/**
|
154
|
+
* @type {import('eslint').Rule.RuleModule}
|
155
|
+
*/
|
156
|
+
var exportContentMustBeValid = {
|
157
|
+
meta: {
|
158
|
+
type: "problem",
|
159
|
+
docs: {
|
160
|
+
description: "Export val.content should only happen in .val files.",
|
161
|
+
recommended: true
|
162
|
+
},
|
163
|
+
messages: {
|
164
|
+
"val/export-content-must-be-valid": "Val: val.content should only be exported from .val files"
|
165
|
+
},
|
166
|
+
schema: []
|
167
|
+
},
|
168
|
+
create: function create(context) {
|
169
|
+
return {
|
170
|
+
ExportDefaultDeclaration: function ExportDefaultDeclaration(node) {
|
171
|
+
if (node.declaration && node.declaration.type === "CallExpression" && node.declaration.callee.type === "MemberExpression" && node.declaration.callee.object.type === "Identifier" && node.declaration.callee.object.name === "val" && node.declaration.callee.property.type === "Identifier" && node.declaration.callee.property.name === "content") {
|
172
|
+
var filename = context.filename || context.getFilename();
|
173
|
+
if (!(filename !== null && filename !== void 0 && filename.endsWith(".val.ts") || filename !== null && filename !== void 0 && filename.endsWith(".val.js"))) {
|
174
|
+
context.report({
|
175
|
+
node: node.declaration.callee,
|
176
|
+
messageId: "val/export-content-must-be-valid"
|
177
|
+
});
|
178
|
+
}
|
144
179
|
}
|
145
180
|
}
|
146
181
|
};
|
@@ -154,7 +189,8 @@ var noIllegalImports = {
|
|
154
189
|
*/
|
155
190
|
var rules = {
|
156
191
|
"no-illegal-module-ids": noIllegalModuleIds,
|
157
|
-
"no-illegal-imports": noIllegalImports
|
192
|
+
"no-illegal-imports": noIllegalImports,
|
193
|
+
"export-content-must-be-valid": exportContentMustBeValid
|
158
194
|
};
|
159
195
|
|
160
196
|
/**
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@valbuild/eslint-plugin",
|
3
|
-
"version": "0.
|
3
|
+
"version": "0.48.0",
|
4
4
|
"description": "ESLint rules for val",
|
5
5
|
"keywords": [
|
6
6
|
"eslint",
|
@@ -27,7 +27,8 @@
|
|
27
27
|
},
|
28
28
|
"devDependencies": {
|
29
29
|
"@types/jest": "^29.5.11",
|
30
|
-
"eslint": "^
|
30
|
+
"@typescript-eslint/rule-tester": "^6.15.0",
|
31
|
+
"eslint": "^8.56.0",
|
31
32
|
"jest": "^29.6"
|
32
33
|
},
|
33
34
|
"scripts": {
|
package/src/index.js
CHANGED
@@ -8,6 +8,7 @@
|
|
8
8
|
|
9
9
|
import noIllegalModuleIds from "./rules/noIllegalModuleIds";
|
10
10
|
import noIllegalImports from "./rules/noIllegalImports";
|
11
|
+
import exportContentMustBeValid from "./rules/exportContentMustBeValid";
|
11
12
|
|
12
13
|
/**
|
13
14
|
* @type {Plugin["rules"]}
|
@@ -15,6 +16,7 @@ import noIllegalImports from "./rules/noIllegalImports";
|
|
15
16
|
export let rules = {
|
16
17
|
"no-illegal-module-ids": noIllegalModuleIds,
|
17
18
|
"no-illegal-imports": noIllegalImports,
|
19
|
+
"export-content-must-be-valid": exportContentMustBeValid,
|
18
20
|
};
|
19
21
|
|
20
22
|
/**
|
@@ -0,0 +1,44 @@
|
|
1
|
+
// @ts-check
|
2
|
+
|
3
|
+
/**
|
4
|
+
* @type {import('eslint').Rule.RuleModule}
|
5
|
+
*/
|
6
|
+
export default {
|
7
|
+
meta: {
|
8
|
+
type: "problem",
|
9
|
+
docs: {
|
10
|
+
description: "Export val.content should only happen in .val files.",
|
11
|
+
recommended: true,
|
12
|
+
},
|
13
|
+
messages: {
|
14
|
+
"val/export-content-must-be-valid":
|
15
|
+
"Val: val.content should only be exported from .val files",
|
16
|
+
},
|
17
|
+
schema: [],
|
18
|
+
},
|
19
|
+
create: function (context) {
|
20
|
+
return {
|
21
|
+
ExportDefaultDeclaration(node) {
|
22
|
+
if (
|
23
|
+
node.declaration &&
|
24
|
+
node.declaration.type === "CallExpression" &&
|
25
|
+
node.declaration.callee.type === "MemberExpression" &&
|
26
|
+
node.declaration.callee.object.type === "Identifier" &&
|
27
|
+
node.declaration.callee.object.name === "val" &&
|
28
|
+
node.declaration.callee.property.type === "Identifier" &&
|
29
|
+
node.declaration.callee.property.name === "content"
|
30
|
+
) {
|
31
|
+
const filename = context.filename || context.getFilename();
|
32
|
+
if (
|
33
|
+
!(filename?.endsWith(".val.ts") || filename?.endsWith(".val.js"))
|
34
|
+
) {
|
35
|
+
context.report({
|
36
|
+
node: node.declaration.callee,
|
37
|
+
messageId: "val/export-content-must-be-valid",
|
38
|
+
});
|
39
|
+
}
|
40
|
+
}
|
41
|
+
},
|
42
|
+
};
|
43
|
+
},
|
44
|
+
};
|
@@ -8,10 +8,8 @@ export default {
|
|
8
8
|
type: "problem",
|
9
9
|
docs: {
|
10
10
|
description: "Check that val files only has valid imports.",
|
11
|
-
category: "Best Practices",
|
12
11
|
recommended: true,
|
13
12
|
},
|
14
|
-
fixable: "code",
|
15
13
|
schema: [],
|
16
14
|
},
|
17
15
|
create: function (context) {
|
@@ -30,11 +28,19 @@ export default {
|
|
30
28
|
!importSource.match(/^@valbuild/) &&
|
31
29
|
!importSource.match(/val\.config(\.ts|\.js|)$/)
|
32
30
|
) {
|
33
|
-
|
34
|
-
|
35
|
-
node
|
36
|
-
|
37
|
-
|
31
|
+
if (
|
32
|
+
"importKind" in node &&
|
33
|
+
node["importKind"] !== "type" &&
|
34
|
+
!node.specifiers.every(
|
35
|
+
(s) => "importKind" in s && s["importKind"] === "type"
|
36
|
+
)
|
37
|
+
) {
|
38
|
+
const message = `Val: can only 'import type' or import from source that is either: a .val.{j,t}s file, a @valbuild package, or val.config.{j,t}s.`;
|
39
|
+
context.report({
|
40
|
+
node: node.source,
|
41
|
+
message,
|
42
|
+
});
|
43
|
+
}
|
38
44
|
}
|
39
45
|
},
|
40
46
|
};
|
@@ -9,8 +9,7 @@ export default {
|
|
9
9
|
type: "problem",
|
10
10
|
docs: {
|
11
11
|
description:
|
12
|
-
"Check that the first argument of export default declaration matches the string from val.config.
|
13
|
-
category: "Best Practices",
|
12
|
+
"Check that the first argument of export default declaration matches the string from val.config.{j,t}s file.",
|
14
13
|
recommended: true,
|
15
14
|
},
|
16
15
|
fixable: "code",
|
@@ -0,0 +1,56 @@
|
|
1
|
+
import { RuleTester } from "@typescript-eslint/rule-tester";
|
2
|
+
import { rules as valRules } from "@valbuild/eslint-plugin";
|
3
|
+
import path from "path";
|
4
|
+
|
5
|
+
const rule = valRules["export-content-must-be-valid"];
|
6
|
+
|
7
|
+
RuleTester.setDefaultConfig({
|
8
|
+
parserOptions: {
|
9
|
+
ecmaVersion: 2018,
|
10
|
+
sourceType: "module",
|
11
|
+
ecmaFeatures: {},
|
12
|
+
},
|
13
|
+
});
|
14
|
+
|
15
|
+
const ruleTester = new RuleTester();
|
16
|
+
|
17
|
+
ruleTester.run("export-content-must-be-valid", rule, {
|
18
|
+
valid: [
|
19
|
+
{
|
20
|
+
filename: path.join(process.cwd(), "./foo/test.val.ts"),
|
21
|
+
code: `
|
22
|
+
import { val, s } from '../val.config';
|
23
|
+
|
24
|
+
export const schema = s.string();
|
25
|
+
export default val.content('/foo/test', schema, '')`,
|
26
|
+
},
|
27
|
+
],
|
28
|
+
invalid: [
|
29
|
+
{
|
30
|
+
filename: path.join(process.cwd(), "./foo/test.ts"),
|
31
|
+
code: `
|
32
|
+
import { val, s } from '../val.config';
|
33
|
+
|
34
|
+
export const schema = s.string();
|
35
|
+
export default val.content('/foo/test', schema, '')`,
|
36
|
+
errors: [
|
37
|
+
{
|
38
|
+
message: "Val: val.content should only be exported from .val files",
|
39
|
+
},
|
40
|
+
],
|
41
|
+
},
|
42
|
+
{
|
43
|
+
filename: path.join(process.cwd(), "./foo/test.js"),
|
44
|
+
code: `
|
45
|
+
import { val, s } from '../val.config';
|
46
|
+
|
47
|
+
export const schema = s.string();
|
48
|
+
export default val.content('/foo/test', schema, '')`,
|
49
|
+
errors: [
|
50
|
+
{
|
51
|
+
message: "Val: val.content should only be exported from .val files",
|
52
|
+
},
|
53
|
+
],
|
54
|
+
},
|
55
|
+
],
|
56
|
+
});
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { RuleTester } from "eslint";
|
1
|
+
import { RuleTester } from "@typescript-eslint/rule-tester";
|
2
2
|
import { rules as valRules } from "@valbuild/eslint-plugin";
|
3
3
|
import path from "path";
|
4
4
|
|
@@ -8,9 +8,8 @@ RuleTester.setDefaultConfig({
|
|
8
8
|
parserOptions: {
|
9
9
|
ecmaVersion: 2018,
|
10
10
|
sourceType: "module",
|
11
|
-
|
12
|
-
|
13
|
-
},
|
11
|
+
|
12
|
+
ecmaFeatures: {},
|
14
13
|
},
|
15
14
|
});
|
16
15
|
|
@@ -40,6 +39,26 @@ export default val.content('/foo/test', schema, [])`,
|
|
40
39
|
filename: path.join(process.cwd(), "./foo/test.val.ts"),
|
41
40
|
code: `
|
42
41
|
import { val, s } from '../val.config.ts';
|
42
|
+
import type { Event } from './eventSchema';
|
43
|
+
|
44
|
+
export const schema = s.array(s.string());
|
45
|
+
type Test = Event;
|
46
|
+
export default val.content('/foo/test', schema, [])`,
|
47
|
+
},
|
48
|
+
{
|
49
|
+
filename: path.join(process.cwd(), "./foo/test.val.ts"),
|
50
|
+
code: `
|
51
|
+
import { val, s } from '../val.config.ts';
|
52
|
+
import { type Event } from './eventSchema';
|
53
|
+
|
54
|
+
export const schema = s.array(s.string());
|
55
|
+
type Test = Event;
|
56
|
+
export default val.content('/foo/test', schema, [])`,
|
57
|
+
},
|
58
|
+
{
|
59
|
+
filename: path.join(process.cwd(), "./foo/test.val.ts"),
|
60
|
+
code: `
|
61
|
+
import { val, s } from '../val.config.ts';
|
43
62
|
|
44
63
|
export const schema = s.string();
|
45
64
|
export default val.content('/foo/test', schema, 'String')`,
|
@@ -57,7 +76,23 @@ export default val.content('/foo/test', schema, [])`,
|
|
57
76
|
errors: [
|
58
77
|
{
|
59
78
|
message:
|
60
|
-
"Val: import source
|
79
|
+
"Val: can only 'import type' or import from source that is either: a .val.{j,t}s file, a @valbuild package, or val.config.{j,t}s.",
|
80
|
+
},
|
81
|
+
],
|
82
|
+
},
|
83
|
+
{
|
84
|
+
filename: path.join(process.cwd(), "./foo/test.val.ts"),
|
85
|
+
code: `
|
86
|
+
import { val, s } from '../val.config';
|
87
|
+
import { eventSchema, type Unused } from './event';
|
88
|
+
|
89
|
+
export const schema = s.array(eventSchema);
|
90
|
+
type Event = Unused;
|
91
|
+
export default val.content('/foo/test', schema, [])`,
|
92
|
+
errors: [
|
93
|
+
{
|
94
|
+
message:
|
95
|
+
"Val: can only 'import type' or import from source that is either: a .val.{j,t}s file, a @valbuild package, or val.config.{j,t}s.",
|
61
96
|
},
|
62
97
|
],
|
63
98
|
},
|