@bablr/language-en-json 0.10.0 → 0.12.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/lib/grammar.js +40 -49
- package/lib/grammar.macro.js +47 -60
- package/package.json +9 -9
package/lib/grammar.js
CHANGED
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
/* @macrome
|
|
2
2
|
* @generatedby @bablr/macrome-generator-bablr
|
|
3
|
-
* @generatedfrom ./grammar.macro.js#
|
|
3
|
+
* @generatedfrom ./grammar.macro.js#eb47e9a464d603cac4f3c0b96efeefd4b1e2ab7d
|
|
4
4
|
* This file is autogenerated. Please do not edit it directly.
|
|
5
5
|
* When editing run `npx macrome watch` then change the file this is generated from.
|
|
6
6
|
*/
|
|
7
7
|
import _applyDecs from "@babel/runtime/helpers/applyDecs2305";
|
|
8
8
|
let _initProto, _ExpressionDecs, _ArrayDecs, _ObjectDecs, _StringDecs, _NumberDecs, _InfinityDecs, _BooleanDecs, _NullDecs, _KeywordDecs, _PunctuatorDecs, _ListDecs, _AnyDecs, _AllDecs;
|
|
9
9
|
import { re, spam as m } from '@bablr/boot';
|
|
10
|
-
import {
|
|
10
|
+
import { basicTriviaEnhancer } from '@bablr/helpers/trivia';
|
|
11
11
|
import * as productions from '@bablr/helpers/productions';
|
|
12
|
-
import { o, eat, eatMatch, match, fail } from '@bablr/helpers/grammar';
|
|
13
|
-
import { buildString
|
|
12
|
+
import { o, eat, eatMatch, match, fail, defineAttribute } from '@bablr/helpers/grammar';
|
|
13
|
+
import { buildString } from '@bablr/helpers/builders';
|
|
14
14
|
import { Node, CoveredBy, AllowEmpty, InjectFrom, Literal } from '@bablr/helpers/decorators';
|
|
15
15
|
import * as Space from '@bablr/language-en-blank-space';
|
|
16
16
|
export const dependencies = {
|
|
17
17
|
Space
|
|
18
18
|
};
|
|
19
19
|
export const canonicalURL = 'https://bablr.org/languages/core/en/json';
|
|
20
|
+
export const defaultMatcher = m`<_Expression />`;
|
|
20
21
|
const escapables = new Map(Object.entries({
|
|
21
22
|
b: '\b',
|
|
22
23
|
// these two escapes are antiquated
|
|
@@ -30,32 +31,12 @@ const escapables = new Map(Object.entries({
|
|
|
30
31
|
function first(iter) {
|
|
31
32
|
for (let value of iter) return value;
|
|
32
33
|
}
|
|
33
|
-
export const
|
|
34
|
-
let cooked;
|
|
35
|
-
const codeNode = escapeNode.get('code');
|
|
36
|
-
if (first(codeNode.children)?.value.flags.token) {
|
|
37
|
-
const match_ = ctx.sourceTextFor(codeNode);
|
|
38
|
-
cooked = escapables.get(match_) || match_;
|
|
39
|
-
} else {
|
|
40
|
-
const type = ctx.sourceTextFor(codeNode.get('typeToken'));
|
|
41
|
-
const value = ctx.sourceTextFor(codeNode.get('value'));
|
|
42
|
-
if (!span.startsWith('String')) {
|
|
43
|
-
throw new Error('not implemented');
|
|
44
|
-
}
|
|
45
|
-
if (type === 'u') {
|
|
46
|
-
cooked = String.fromCharCode(parseInt(value, 16));
|
|
47
|
-
} else {
|
|
48
|
-
throw new Error();
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return cooked.toString(10);
|
|
52
|
-
};
|
|
53
|
-
export const grammar = triviaEnhancer({
|
|
34
|
+
export const grammar = basicTriviaEnhancer({
|
|
54
35
|
triviaIsAllowed: s => s.span === 'Bare',
|
|
55
|
-
triviaMatcher: m`#:
|
|
36
|
+
triviaMatcher: m`#: :Space: <*Space /[ \n\r\t]/ />`
|
|
56
37
|
}, class JSONGrammar {
|
|
57
38
|
static {
|
|
58
|
-
[_initProto] = _applyDecs(this, [[_ExpressionDecs, 2, "Expression"], [_ArrayDecs, 2, "Array"], [_ObjectDecs, 2, "Object"], [Node, 2, "Property"], [
|
|
39
|
+
[_initProto] = _applyDecs(this, [[_ExpressionDecs, 2, "Expression"], [_ArrayDecs, 2, "Array"], [_ObjectDecs, 2, "Object"], [Node, 2, "Property"], [_StringDecs, 2, "String"], [[AllowEmpty, Node], 2, "StringContent"], [Node, 2, "EscapeSequence"], [Node, 2, "EscapeCode"], [_NumberDecs, 2, "Number"], [Node, 2, "Integer"], [Node, 2, "UnsignedInteger"], [Node, 2, "UnsignedHexInteger"], [_InfinityDecs, 2, "Infinity"], [_BooleanDecs, 2, "Boolean"], [_NullDecs, 2, "Null"], [_KeywordDecs, 2, "Keyword"], [_PunctuatorDecs, 2, "Punctuator"], [_ListDecs, 2, "List"], [_AnyDecs, 2, "Any"], [_AllDecs, 2, "All"]], []).e;
|
|
59
40
|
}
|
|
60
41
|
constructor() {
|
|
61
42
|
_initProto(this);
|
|
@@ -69,13 +50,13 @@ export const grammar = triviaEnhancer({
|
|
|
69
50
|
yield eat(rootMatcher);
|
|
70
51
|
}
|
|
71
52
|
*Expression() {
|
|
72
|
-
yield eat(m`<
|
|
53
|
+
yield eat(m`<__Any />`, [m`<Array '[' />`, m`<Object '{' />`, m`<String /['"]/ />`, m`<Number /\d|-[\d\g]/ {span: 'Number'} />`, m`<Infinity /-?Infinity/ />`, m`<Null 'null' />`, m`<Boolean /true|false/ />`]);
|
|
73
54
|
}
|
|
74
55
|
*Array() {
|
|
75
56
|
yield eat(m`openToken: <*Punctuator '[' { balanced: ']' } />`);
|
|
76
|
-
yield eat(m`<
|
|
77
|
-
element: m`elements[]+$: <
|
|
78
|
-
separator: m
|
|
57
|
+
yield eat(m`<__List />`, o({
|
|
58
|
+
element: m`elements[]+$: <_Expression />`,
|
|
59
|
+
separator: m`#separatorTokens[]: <*Punctuator ',' />`,
|
|
79
60
|
allowTrailingSeparator: false
|
|
80
61
|
}));
|
|
81
62
|
yield eat(m`closeToken: <*Punctuator ']' { balancer: true } />`);
|
|
@@ -83,34 +64,26 @@ export const grammar = triviaEnhancer({
|
|
|
83
64
|
*Object() {
|
|
84
65
|
yield eat(m`openToken: <*Punctuator '{' { balanced: '}' } />`);
|
|
85
66
|
let sep = true;
|
|
86
|
-
yield eatMatch(m
|
|
67
|
+
yield eatMatch(m`#separatorTokens[]: []`);
|
|
87
68
|
yield eatMatch(m`properties[]$: []`);
|
|
88
69
|
while (sep && (yield match(re`/.|\g/s`))) {
|
|
89
|
-
let suppressGap = !!(yield match(m`<
|
|
70
|
+
let suppressGap = !!(yield match(m`<__All />`, [m`key: <//>`, m`sigilToken: <*Punctuator ':' />`]));
|
|
90
71
|
yield eat(m`properties[]$: <Property />`, null, o({
|
|
91
72
|
suppressGap
|
|
92
73
|
}));
|
|
93
|
-
sep = yield eatMatch(m
|
|
74
|
+
sep = yield eatMatch(m`#separatorTokens[]: <*Punctuator ',' />`);
|
|
94
75
|
}
|
|
95
76
|
yield eat(m`closeToken: <*Punctuator '}' { balancer: true } />`);
|
|
96
77
|
}
|
|
97
78
|
*Property() {
|
|
98
|
-
yield eat(m`key$:
|
|
79
|
+
yield eat(m`key$: <String />`);
|
|
99
80
|
yield eat(m`sigilToken: <*Punctuator ':' />`);
|
|
100
|
-
yield eat(m`value+$: <
|
|
81
|
+
yield eat(m`value+$: <_Expression />`);
|
|
101
82
|
}
|
|
102
|
-
*
|
|
103
|
-
yield eat(
|
|
104
|
-
}
|
|
105
|
-
*String({
|
|
106
|
-
ctx
|
|
107
|
-
}) {
|
|
108
|
-
let q = yield match(re`/['"]/`);
|
|
109
|
-
if (!q) yield fail();
|
|
110
|
-
const q_ = ctx.sourceTextFor(q);
|
|
111
|
-
yield q_ === "'" ? eat(m`openToken: <*Punctuator "'" { balanced: "'", balancedSpan: 'String:Single' } />`) : eat(m`openToken: <*Punctuator '"' { balanced: '"', balancedSpan: 'String:Double' } />`);
|
|
83
|
+
*String() {
|
|
84
|
+
yield eat(m`openToken: <*Punctuator '"' { balanced: '"', balancedSpan: 'String:Double' } />`);
|
|
112
85
|
yield eat(m`content$: <*StringContent />`);
|
|
113
|
-
yield
|
|
86
|
+
yield eat(m`closeToken: <*Punctuator '"' { balancer: true } />`);
|
|
114
87
|
}
|
|
115
88
|
*StringContent({
|
|
116
89
|
state: {
|
|
@@ -134,14 +107,32 @@ export const grammar = triviaEnhancer({
|
|
|
134
107
|
}
|
|
135
108
|
yield eat(m`sigilToken: <*Punctuator '\\' { openSpan: 'Escape' } />`);
|
|
136
109
|
let match_;
|
|
137
|
-
|
|
110
|
+
let cooked;
|
|
111
|
+
if (match_ = span === 'String:Single' ? yield match(re`/[\\/bfnrt0']/`) : yield match(re`/[\\/bfnrt0"]/`)) {
|
|
138
112
|
const matchText = ctx.sourceTextFor(match_);
|
|
139
113
|
yield eat(m`code: <*Keyword ${buildString(matchText)} { closeSpan: 'Escape' } />`);
|
|
114
|
+
cooked = escapables.get(matchText) || matchText;
|
|
140
115
|
} else if (yield match('u')) {
|
|
141
|
-
yield eat(m`code: <EscapeCode { closeSpan: 'Escape' } />`);
|
|
116
|
+
let codeNode = yield eat(m`code: <EscapeCode { closeSpan: 'Escape' } />`);
|
|
117
|
+
const type = ctx.sourceTextFor(codeNode.get('typeToken'));
|
|
118
|
+
if (type) {
|
|
119
|
+
const value = ctx.sourceTextFor(codeNode.get('value'));
|
|
120
|
+
if (!span.startsWith('String')) {
|
|
121
|
+
throw new Error('not implemented');
|
|
122
|
+
}
|
|
123
|
+
if (type === 'u') {
|
|
124
|
+
cooked = String.fromCharCode(parseInt(value, 16));
|
|
125
|
+
} else {
|
|
126
|
+
throw new Error();
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
let value = ctx.sourceTextFor(codeNode);
|
|
130
|
+
cooked = escapables.get(value) || value;
|
|
131
|
+
}
|
|
142
132
|
} else {
|
|
143
133
|
yield fail();
|
|
144
134
|
}
|
|
135
|
+
yield defineAttribute('cooked', cooked);
|
|
145
136
|
}
|
|
146
137
|
*EscapeCode() {
|
|
147
138
|
if (yield eatMatch(m`typeToken: <*Keyword 'u' />`)) {
|
package/lib/grammar.macro.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { re, spam as m } from '@bablr/boot';
|
|
2
|
-
import {
|
|
2
|
+
import { basicTriviaEnhancer } from '@bablr/helpers/trivia';
|
|
3
3
|
import * as productions from '@bablr/helpers/productions';
|
|
4
|
-
import { o, eat, eatMatch, match, fail } from '@bablr/helpers/grammar';
|
|
5
|
-
import { buildString
|
|
4
|
+
import { o, eat, eatMatch, match, fail, defineAttribute } from '@bablr/helpers/grammar';
|
|
5
|
+
import { buildString } from '@bablr/helpers/builders';
|
|
6
6
|
import { Node, CoveredBy, AllowEmpty, InjectFrom, Literal } from '@bablr/helpers/decorators';
|
|
7
7
|
import * as Space from '@bablr/language-en-blank-space';
|
|
8
8
|
|
|
@@ -10,6 +10,8 @@ export const dependencies = { Space };
|
|
|
10
10
|
|
|
11
11
|
export const canonicalURL = 'https://bablr.org/languages/core/en/json';
|
|
12
12
|
|
|
13
|
+
export const defaultMatcher = m`<_Expression />`;
|
|
14
|
+
|
|
13
15
|
const escapables = new Map(
|
|
14
16
|
Object.entries({
|
|
15
17
|
b: '\b', // these two escapes are antiquated
|
|
@@ -25,36 +27,10 @@ function first(iter) {
|
|
|
25
27
|
for (let value of iter) return value;
|
|
26
28
|
}
|
|
27
29
|
|
|
28
|
-
export const
|
|
29
|
-
let cooked;
|
|
30
|
-
const codeNode = escapeNode.get('code');
|
|
31
|
-
|
|
32
|
-
if (first(codeNode.children)?.value.flags.token) {
|
|
33
|
-
const match_ = ctx.sourceTextFor(codeNode);
|
|
34
|
-
|
|
35
|
-
cooked = escapables.get(match_) || match_;
|
|
36
|
-
} else {
|
|
37
|
-
const type = ctx.sourceTextFor(codeNode.get('typeToken'));
|
|
38
|
-
const value = ctx.sourceTextFor(codeNode.get('value'));
|
|
39
|
-
|
|
40
|
-
if (!span.startsWith('String')) {
|
|
41
|
-
throw new Error('not implemented');
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (type === 'u') {
|
|
45
|
-
cooked = String.fromCharCode(parseInt(value, 16));
|
|
46
|
-
} else {
|
|
47
|
-
throw new Error();
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return cooked.toString(10);
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
export const grammar = triviaEnhancer(
|
|
30
|
+
export const grammar = basicTriviaEnhancer(
|
|
55
31
|
{
|
|
56
32
|
triviaIsAllowed: (s) => s.span === 'Bare',
|
|
57
|
-
triviaMatcher: m`#:
|
|
33
|
+
triviaMatcher: m`#: :Space: <*Space /[ \n\r\t]/ />`,
|
|
58
34
|
},
|
|
59
35
|
class JSONGrammar {
|
|
60
36
|
*[Symbol.for('@bablr/fragment')]({ props: { rootMatcher } }) {
|
|
@@ -64,7 +40,7 @@ export const grammar = triviaEnhancer(
|
|
|
64
40
|
|
|
65
41
|
@CoveredBy('Element')
|
|
66
42
|
*Expression() {
|
|
67
|
-
yield eat(m`<
|
|
43
|
+
yield eat(m`<__Any />`, [
|
|
68
44
|
m`<Array '[' />`,
|
|
69
45
|
m`<Object '{' />`,
|
|
70
46
|
m`<String /['"]/ />`,
|
|
@@ -80,10 +56,10 @@ export const grammar = triviaEnhancer(
|
|
|
80
56
|
*Array() {
|
|
81
57
|
yield eat(m`openToken: <*Punctuator '[' { balanced: ']' } />`);
|
|
82
58
|
yield eat(
|
|
83
|
-
m`<
|
|
59
|
+
m`<__List />`,
|
|
84
60
|
o({
|
|
85
|
-
element: m`elements[]+$: <
|
|
86
|
-
separator: m
|
|
61
|
+
element: m`elements[]+$: <_Expression />`,
|
|
62
|
+
separator: m`#separatorTokens[]: <*Punctuator ',' />`,
|
|
87
63
|
allowTrailingSeparator: false,
|
|
88
64
|
}),
|
|
89
65
|
);
|
|
@@ -96,50 +72,35 @@ export const grammar = triviaEnhancer(
|
|
|
96
72
|
yield eat(m`openToken: <*Punctuator '{' { balanced: '}' } />`);
|
|
97
73
|
let sep = true;
|
|
98
74
|
|
|
99
|
-
yield eatMatch(m
|
|
75
|
+
yield eatMatch(m`#separatorTokens[]: []`);
|
|
100
76
|
yield eatMatch(m`properties[]$: []`);
|
|
101
77
|
|
|
102
78
|
while (sep && (yield match(re`/.|\g/s`))) {
|
|
103
|
-
let suppressGap = !!(yield match(m`<
|
|
79
|
+
let suppressGap = !!(yield match(m`<__All />`, [
|
|
104
80
|
m`key: <//>`,
|
|
105
81
|
m`sigilToken: <*Punctuator ':' />`,
|
|
106
82
|
]));
|
|
107
83
|
yield eat(m`properties[]$: <Property />`, null, o({ suppressGap }));
|
|
108
|
-
sep = yield eatMatch(m
|
|
84
|
+
sep = yield eatMatch(m`#separatorTokens[]: <*Punctuator ',' />`);
|
|
109
85
|
}
|
|
110
86
|
yield eat(m`closeToken: <*Punctuator '}' { balancer: true } />`);
|
|
111
87
|
}
|
|
112
88
|
|
|
113
89
|
@Node
|
|
114
90
|
*Property() {
|
|
115
|
-
yield eat(m`key$:
|
|
91
|
+
yield eat(m`key$: <String />`);
|
|
116
92
|
yield eat(m`sigilToken: <*Punctuator ':' />`);
|
|
117
|
-
yield eat(m`value+$: <
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
@Node
|
|
121
|
-
*Identifier() {
|
|
122
|
-
yield eat(re`/[a-zA-Z][a-zA-Z_-]*/`);
|
|
93
|
+
yield eat(m`value+$: <_Expression />`);
|
|
123
94
|
}
|
|
124
95
|
|
|
125
96
|
@CoveredBy('Expression')
|
|
126
97
|
@Node
|
|
127
|
-
*String(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (!q) yield fail();
|
|
131
|
-
|
|
132
|
-
const q_ = ctx.sourceTextFor(q);
|
|
133
|
-
|
|
134
|
-
yield q_ === "'"
|
|
135
|
-
? eat(m`openToken: <*Punctuator "'" { balanced: "'", balancedSpan: 'String:Single' } />`)
|
|
136
|
-
: eat(m`openToken: <*Punctuator '"' { balanced: '"', balancedSpan: 'String:Double' } />`);
|
|
98
|
+
*String() {
|
|
99
|
+
yield eat(m`openToken: <*Punctuator '"' { balanced: '"', balancedSpan: 'String:Double' } />`);
|
|
137
100
|
|
|
138
101
|
yield eat(m`content$: <*StringContent />`);
|
|
139
102
|
|
|
140
|
-
yield
|
|
141
|
-
? eat(m`closeToken: <*Punctuator "'" { balancer: true } />`)
|
|
142
|
-
: eat(m`closeToken: <*Punctuator '"' { balancer: true } />`);
|
|
103
|
+
yield eat(m`closeToken: <*Punctuator '"' { balancer: true } />`);
|
|
143
104
|
}
|
|
144
105
|
|
|
145
106
|
@AllowEmpty
|
|
@@ -164,18 +125,44 @@ export const grammar = triviaEnhancer(
|
|
|
164
125
|
yield eat(m`sigilToken: <*Punctuator '\\' { openSpan: 'Escape' } />`);
|
|
165
126
|
|
|
166
127
|
let match_;
|
|
128
|
+
let cooked;
|
|
167
129
|
|
|
168
130
|
if (
|
|
169
131
|
(match_ =
|
|
170
|
-
span === 'String:Single'
|
|
132
|
+
span === 'String:Single'
|
|
133
|
+
? yield match(re`/[\\/bfnrt0']/`)
|
|
134
|
+
: yield match(re`/[\\/bfnrt0"]/`))
|
|
171
135
|
) {
|
|
172
136
|
const matchText = ctx.sourceTextFor(match_);
|
|
173
137
|
yield eat(m`code: <*Keyword ${buildString(matchText)} { closeSpan: 'Escape' } />`);
|
|
138
|
+
|
|
139
|
+
cooked = escapables.get(matchText) || matchText;
|
|
174
140
|
} else if (yield match('u')) {
|
|
175
|
-
yield eat(m`code: <EscapeCode { closeSpan: 'Escape' } />`);
|
|
141
|
+
let codeNode = yield eat(m`code: <EscapeCode { closeSpan: 'Escape' } />`);
|
|
142
|
+
|
|
143
|
+
const type = ctx.sourceTextFor(codeNode.get('typeToken'));
|
|
144
|
+
|
|
145
|
+
if (type) {
|
|
146
|
+
const value = ctx.sourceTextFor(codeNode.get('value'));
|
|
147
|
+
|
|
148
|
+
if (!span.startsWith('String')) {
|
|
149
|
+
throw new Error('not implemented');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (type === 'u') {
|
|
153
|
+
cooked = String.fromCharCode(parseInt(value, 16));
|
|
154
|
+
} else {
|
|
155
|
+
throw new Error();
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
let value = ctx.sourceTextFor(codeNode);
|
|
159
|
+
cooked = escapables.get(value) || value;
|
|
160
|
+
}
|
|
176
161
|
} else {
|
|
177
162
|
yield fail();
|
|
178
163
|
}
|
|
164
|
+
|
|
165
|
+
yield defineAttribute('cooked', cooked);
|
|
179
166
|
}
|
|
180
167
|
|
|
181
168
|
@Node
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bablr/language-en-json",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "A BABLR language for JSON",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=12.0.0"
|
|
@@ -21,19 +21,19 @@
|
|
|
21
21
|
},
|
|
22
22
|
"sideEffects": false,
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@bablr/agast-helpers": "0.
|
|
25
|
-
"@bablr/agast-vm-helpers": "0.
|
|
26
|
-
"@bablr/boot": "0.
|
|
27
|
-
"@bablr/helpers": "0.
|
|
28
|
-
"@bablr/language-en-blank-space": "0.
|
|
29
|
-
"@babel/runtime": "
|
|
24
|
+
"@bablr/agast-helpers": "0.9.0",
|
|
25
|
+
"@bablr/agast-vm-helpers": "0.9.0",
|
|
26
|
+
"@bablr/boot": "0.10.0",
|
|
27
|
+
"@bablr/helpers": "0.24.0",
|
|
28
|
+
"@bablr/language-en-blank-space": "0.9.0",
|
|
29
|
+
"@babel/runtime": "7.28.2"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@bablr/eslint-config-base": "github:bablr-lang/eslint-config-base#
|
|
32
|
+
"@bablr/eslint-config-base": "github:bablr-lang/eslint-config-base#c97bfa4b3663f8378e9b3e42bb5a41e685406cf9",
|
|
33
33
|
"@bablr/macrome": "^0.1.3",
|
|
34
34
|
"@bablr/macrome-generator-bablr": "^0.3.2",
|
|
35
35
|
"@qnighy/dedent": "0.1.1",
|
|
36
|
-
"bablr": "^0.
|
|
36
|
+
"bablr": "^0.10.0",
|
|
37
37
|
"enhanced-resolve": "^5.12.0",
|
|
38
38
|
"eslint": "^7.32.0",
|
|
39
39
|
"eslint-import-resolver-enhanced-resolve": "^1.0.5",
|