@abaplint/core 2.102.48 → 2.102.50
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/build/abaplint.d.ts +6 -0
- package/build/src/abap/2_statements/expressions/index.js +1 -0
- package/build/src/abap/2_statements/expressions/select.js +2 -2
- package/build/src/abap/2_statements/expressions/select_loop.js +3 -2
- package/build/src/abap/2_statements/expressions/sql_into_list.js +14 -0
- package/build/src/abap/2_statements/expressions/sql_into_structure.js +1 -3
- package/build/src/abap/2_statements/statements/fetch_next_cursor.js +1 -1
- package/build/src/abap/5_syntax/expressions/select.js +81 -12
- package/build/src/abap/nodes/expression_node.js +14 -0
- package/build/src/registry.js +1 -1
- package/build/src/rules/many_parentheses.js +1 -1
- package/build/src/rules/obsolete_statement.js +1 -0
- package/build/src/rules/strict_sql.js +1 -0
- package/package.json +4 -4
package/build/abaplint.d.ts
CHANGED
|
@@ -2001,6 +2001,7 @@ declare class ExpressionNode extends AbstractNode<ExpressionNode | TokenNode> {
|
|
|
2001
2001
|
findDirectExpression(type: new () => IStatementRunnable): ExpressionNode | undefined;
|
|
2002
2002
|
findExpressionAfterToken(text: string): ExpressionNode | undefined;
|
|
2003
2003
|
findDirectExpressions(type: new () => IStatementRunnable): readonly ExpressionNode[];
|
|
2004
|
+
findDirectExpressionsMulti(type: (new () => IStatementRunnable)[]): ExpressionNode[];
|
|
2004
2005
|
findDirectTokenByText(text: string): Token | undefined;
|
|
2005
2006
|
findAllExpressionsRecursive(type: new () => IStatementRunnable): readonly ExpressionNode[];
|
|
2006
2007
|
findAllExpressions(type: new () => IStatementRunnable): readonly ExpressionNode[];
|
|
@@ -2186,6 +2187,7 @@ declare namespace Expressions {
|
|
|
2186
2187
|
SQLHaving,
|
|
2187
2188
|
SQLHints,
|
|
2188
2189
|
SQLIn,
|
|
2190
|
+
SQLIntoList,
|
|
2189
2191
|
SQLIntoStructure,
|
|
2190
2192
|
SQLIntoTable,
|
|
2191
2193
|
SQLJoin,
|
|
@@ -5475,6 +5477,10 @@ declare class SQLIn extends Expression {
|
|
|
5475
5477
|
getRunnable(): IStatementRunnable;
|
|
5476
5478
|
}
|
|
5477
5479
|
|
|
5480
|
+
declare class SQLIntoList extends Expression {
|
|
5481
|
+
getRunnable(): IStatementRunnable;
|
|
5482
|
+
}
|
|
5483
|
+
|
|
5478
5484
|
declare class SQLIntoStructure extends Expression {
|
|
5479
5485
|
getRunnable(): IStatementRunnable;
|
|
5480
5486
|
}
|
|
@@ -191,6 +191,7 @@ __exportStar(require("./sql_group_by"), exports);
|
|
|
191
191
|
__exportStar(require("./sql_having"), exports);
|
|
192
192
|
__exportStar(require("./sql_hints"), exports);
|
|
193
193
|
__exportStar(require("./sql_in"), exports);
|
|
194
|
+
__exportStar(require("./sql_into_list"), exports);
|
|
194
195
|
__exportStar(require("./sql_into_structure"), exports);
|
|
195
196
|
__exportStar(require("./sql_into_table"), exports);
|
|
196
197
|
__exportStar(require("./sql_join"), exports);
|
|
@@ -11,13 +11,13 @@ const sql_field_name_1 = require("./sql_field_name");
|
|
|
11
11
|
const sql_up_to_1 = require("./sql_up_to");
|
|
12
12
|
class Select extends combi_1.Expression {
|
|
13
13
|
getRunnable() {
|
|
14
|
-
const into = (0, combi_1.altPrio)(_1.SQLIntoTable, sql_into_structure_1.SQLIntoStructure);
|
|
14
|
+
const into = (0, combi_1.altPrio)(_1.SQLIntoTable, sql_into_structure_1.SQLIntoStructure, _1.SQLIntoList);
|
|
15
15
|
const where = (0, combi_1.seq)("WHERE", _1.SQLCond);
|
|
16
16
|
const offset = (0, combi_1.ver)(version_1.Version.v751, (0, combi_1.seq)("OFFSET", _1.SQLSource));
|
|
17
17
|
const bypass = (0, combi_1.str)("BYPASSING BUFFER");
|
|
18
18
|
const fields = (0, combi_1.ver)(version_1.Version.v750, _1.SQLFields);
|
|
19
19
|
const perm = (0, combi_1.per)(_1.SQLFrom, into, _1.SQLForAllEntries, where, _1.SQLOrderBy, sql_up_to_1.SQLUpTo, offset, _1.SQLClient, _1.SQLHaving, bypass, sql_group_by_1.SQLGroupBy, fields, _1.DatabaseConnection);
|
|
20
|
-
const permSingle = (0, combi_1.per)(_1.SQLFrom, sql_into_structure_1.SQLIntoStructure, where, _1.SQLClient, bypass, fields, _1.DatabaseConnection);
|
|
20
|
+
const permSingle = (0, combi_1.per)(_1.SQLFrom, (0, combi_1.altPrio)(sql_into_structure_1.SQLIntoStructure, _1.SQLIntoList), where, _1.SQLClient, bypass, fields, _1.DatabaseConnection);
|
|
21
21
|
const paren = (0, combi_1.seq)((0, combi_1.tok)(tokens_1.WParenLeftW), sql_field_name_1.SQLFieldName, (0, combi_1.tok)(tokens_1.WParenRightW));
|
|
22
22
|
const fieldList = (0, combi_1.optPrio)((0, combi_1.altPrio)(_1.SQLFieldList, paren));
|
|
23
23
|
const single = (0, combi_1.seq)("SINGLE", (0, combi_1.optPrio)("FOR UPDATE"), fieldList, permSingle);
|
|
@@ -17,8 +17,9 @@ class SelectLoop extends combi_1.Expression {
|
|
|
17
17
|
const pack = (0, combi_1.seq)("PACKAGE SIZE", _1.SQLSource);
|
|
18
18
|
const tab = (0, combi_1.seq)(_1.SQLIntoTable, (0, combi_1.alt)(pack, (0, combi_1.seq)(_1.SQLFrom, pack), (0, combi_1.seq)(pack, _1.SQLFrom)));
|
|
19
19
|
const packTab = (0, combi_1.seq)(pack, _1.SQLIntoTable);
|
|
20
|
-
const
|
|
21
|
-
const
|
|
20
|
+
const into = (0, combi_1.altPrio)(sql_into_structure_1.SQLIntoStructure, _1.SQLIntoList);
|
|
21
|
+
const perm = (0, combi_1.per)(_1.SQLFrom, where, sql_up_to_1.SQLUpTo, sql_order_by_1.SQLOrderBy, sql_having_1.SQLHaving, _1.SQLClient, bypass, _1.SQLGroupBy, _1.SQLForAllEntries, (0, combi_1.alt)(tab, into, packTab));
|
|
22
|
+
const strict = (0, combi_1.seq)(_1.SQLFrom, (0, combi_1.ver)(version_1.Version.v750, _1.SQLFields), where, into, sql_up_to_1.SQLUpTo);
|
|
22
23
|
const ret = (0, combi_1.seq)("SELECT", (0, combi_1.altPrio)((0, combi_1.seq)((0, combi_1.optPrio)("DISTINCT"), sql_field_list_loop_1.SQLFieldListLoop, perm), strict), (0, combi_1.optPrio)(sql_hints_1.SQLHints));
|
|
23
24
|
return ret;
|
|
24
25
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SQLIntoList = void 0;
|
|
4
|
+
const combi_1 = require("../combi");
|
|
5
|
+
const _1 = require(".");
|
|
6
|
+
const paren_left_1 = require("../../1_lexer/tokens/paren_left");
|
|
7
|
+
class SQLIntoList extends combi_1.Expression {
|
|
8
|
+
getRunnable() {
|
|
9
|
+
const intoList = (0, combi_1.seq)((0, combi_1.altPrio)((0, combi_1.tok)(paren_left_1.WParenLeft), (0, combi_1.tok)(paren_left_1.WParenLeftW)), (0, combi_1.starPrio)((0, combi_1.seq)(_1.SQLTarget, ",")), _1.SQLTarget, ")");
|
|
10
|
+
return (0, combi_1.seq)("INTO", intoList);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.SQLIntoList = SQLIntoList;
|
|
14
|
+
//# sourceMappingURL=sql_into_list.js.map
|
|
@@ -3,12 +3,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.SQLIntoStructure = void 0;
|
|
4
4
|
const combi_1 = require("../combi");
|
|
5
5
|
const _1 = require(".");
|
|
6
|
-
const paren_left_1 = require("../../1_lexer/tokens/paren_left");
|
|
7
6
|
class SQLIntoStructure extends combi_1.Expression {
|
|
8
7
|
getRunnable() {
|
|
9
|
-
const intoList = (0, combi_1.seq)((0, combi_1.altPrio)((0, combi_1.tok)(paren_left_1.WParenLeft), (0, combi_1.tok)(paren_left_1.WParenLeftW)), (0, combi_1.starPrio)((0, combi_1.seq)(_1.SQLTarget, ",")), _1.SQLTarget, ")");
|
|
10
8
|
const intoSimple = (0, combi_1.seq)((0, combi_1.optPrio)("CORRESPONDING FIELDS OF"), _1.SQLTarget);
|
|
11
|
-
return (0, combi_1.seq)("INTO",
|
|
9
|
+
return (0, combi_1.seq)("INTO", intoSimple);
|
|
12
10
|
}
|
|
13
11
|
}
|
|
14
12
|
exports.SQLIntoStructure = SQLIntoStructure;
|
|
@@ -8,7 +8,7 @@ const sql_into_structure_1 = require("../expressions/sql_into_structure");
|
|
|
8
8
|
class FetchNextCursor {
|
|
9
9
|
getMatcher() {
|
|
10
10
|
const size = (0, combi_1.seq)("PACKAGE SIZE", expressions_1.SQLSourceSimple);
|
|
11
|
-
const ret = (0, combi_1.seq)("FETCH NEXT CURSOR", expressions_1.SQLSourceSimple, (0, combi_1.alt)(sql_into_structure_1.SQLIntoStructure, expressions_1.SQLIntoTable), (0, combi_1.optPrio)(size));
|
|
11
|
+
const ret = (0, combi_1.seq)("FETCH NEXT CURSOR", expressions_1.SQLSourceSimple, (0, combi_1.alt)(sql_into_structure_1.SQLIntoStructure, expressions_1.SQLIntoTable, expressions_1.SQLIntoList), (0, combi_1.optPrio)(size));
|
|
12
12
|
return (0, combi_1.verNot)(version_1.Version.Cloud, ret);
|
|
13
13
|
}
|
|
14
14
|
}
|
|
@@ -10,6 +10,7 @@ const sql_for_all_entries_1 = require("./sql_for_all_entries");
|
|
|
10
10
|
const _scope_type_1 = require("../_scope_type");
|
|
11
11
|
const sql_source_1 = require("./sql_source");
|
|
12
12
|
const sql_compare_1 = require("./sql_compare");
|
|
13
|
+
const isSimple = /^\w+$/;
|
|
13
14
|
class Select {
|
|
14
15
|
runSyntax(node, scope, filename, skipImplicitInto = false) {
|
|
15
16
|
var _a, _b;
|
|
@@ -22,10 +23,7 @@ class Select {
|
|
|
22
23
|
throw new Error(`fields missing`);
|
|
23
24
|
}
|
|
24
25
|
this.checkFields(fields, dbSources, scope);
|
|
25
|
-
|
|
26
|
-
// todo, for now these are voided
|
|
27
|
-
new inline_data_1.InlineData().runSyntax(inline, scope, filename, this.buildType(fields));
|
|
28
|
-
}
|
|
26
|
+
this.handleInto(node, scope, filename, fields, dbSources);
|
|
29
27
|
const fae = node.findDirectExpression(Expressions.SQLForAllEntries);
|
|
30
28
|
if (fae) {
|
|
31
29
|
scope.push(_scope_type_1.ScopeType.OpenSQL, "SELECT", token.getStart(), filename);
|
|
@@ -37,6 +35,7 @@ class Select {
|
|
|
37
35
|
// check implicit into, the target field is implict equal to the table name
|
|
38
36
|
if (skipImplicitInto === false
|
|
39
37
|
&& node.findDirectExpression(Expressions.SQLIntoTable) === undefined
|
|
38
|
+
&& node.findDirectExpression(Expressions.SQLIntoList) === undefined
|
|
40
39
|
&& node.findDirectExpression(Expressions.SQLIntoStructure) === undefined) {
|
|
41
40
|
const fields = (_a = node.findFirstExpression(Expressions.SQLAggregation)) === null || _a === void 0 ? void 0 : _a.concatTokens();
|
|
42
41
|
const c = new RegExp(/^count\(\s*\*\s*\)$/, "i");
|
|
@@ -68,6 +67,54 @@ class Select {
|
|
|
68
67
|
scope.pop(node.getLastToken().getEnd());
|
|
69
68
|
}
|
|
70
69
|
}
|
|
70
|
+
handleInto(node, scope, filename, fields, dbSources) {
|
|
71
|
+
const intoTable = node.findDirectExpression(Expressions.SQLIntoTable);
|
|
72
|
+
if (intoTable) {
|
|
73
|
+
const inline = intoTable.findFirstExpression(Expressions.InlineData);
|
|
74
|
+
if (inline) {
|
|
75
|
+
new inline_data_1.InlineData().runSyntax(inline, scope, filename, this.buildTableType(fields, dbSources, scope));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const intoStructure = node.findDirectExpression(Expressions.SQLIntoStructure);
|
|
79
|
+
if (intoStructure) {
|
|
80
|
+
for (const inline of intoStructure.findAllExpressions(Expressions.InlineData)) {
|
|
81
|
+
// todo, for now these are voided
|
|
82
|
+
new inline_data_1.InlineData().runSyntax(inline, scope, filename, new basic_1.VoidType("SELECT_todo"));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const intoList = node.findDirectExpression(Expressions.SQLIntoList);
|
|
86
|
+
if (intoList) {
|
|
87
|
+
const isDynamic = fields.length === 1 && fields[0].expression.findDirectExpression(Expressions.Dynamic) !== undefined;
|
|
88
|
+
const targets = intoList.findDirectExpressions(Expressions.SQLTarget);
|
|
89
|
+
if (targets.length !== fields.length && isDynamic !== true) {
|
|
90
|
+
throw new Error(`number of fields selected vs list does not match`);
|
|
91
|
+
}
|
|
92
|
+
for (let i = 0; i < targets.length; i++) {
|
|
93
|
+
const target = targets[i];
|
|
94
|
+
const field = fields[i];
|
|
95
|
+
const inline = target.findFirstExpression(Expressions.InlineData);
|
|
96
|
+
if (inline) {
|
|
97
|
+
if (isDynamic) {
|
|
98
|
+
throw new Error(`dynamic field list, inlining not possible`);
|
|
99
|
+
}
|
|
100
|
+
if (isSimple.test(field.code) && dbSources.length === 1 && dbSources[0] !== undefined) {
|
|
101
|
+
const dbType = dbSources[0].parseType(scope.getRegistry());
|
|
102
|
+
let type = new basic_1.VoidType("SELECT_todo");
|
|
103
|
+
if (dbType instanceof basic_1.StructureType) {
|
|
104
|
+
type = dbType.getComponentByName(field.code);
|
|
105
|
+
if (type === undefined) {
|
|
106
|
+
throw new Error(`handleInto, internal error, should be checked earlier`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
new inline_data_1.InlineData().runSyntax(inline, scope, filename, type);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
new inline_data_1.InlineData().runSyntax(inline, scope, filename, new basic_1.VoidType("SELECT_todo"));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
71
118
|
checkFields(fields, dbSources, scope) {
|
|
72
119
|
if (dbSources.length > 1) {
|
|
73
120
|
return;
|
|
@@ -84,7 +131,6 @@ class Select {
|
|
|
84
131
|
if (!(type instanceof basic_1.StructureType)) {
|
|
85
132
|
throw new Error("checkFields, expected structure, " + type.constructor.name);
|
|
86
133
|
}
|
|
87
|
-
const isSimple = /^\w+$/;
|
|
88
134
|
for (const field of fields) {
|
|
89
135
|
if (field.code === "*") {
|
|
90
136
|
continue;
|
|
@@ -94,21 +140,44 @@ class Select {
|
|
|
94
140
|
}
|
|
95
141
|
}
|
|
96
142
|
}
|
|
97
|
-
|
|
143
|
+
buildTableType(fields, dbSources, scope) {
|
|
144
|
+
if (dbSources.length !== 1) {
|
|
145
|
+
return new basic_1.VoidType("SELECT_todo");
|
|
146
|
+
}
|
|
147
|
+
if (dbSources[0] === undefined) {
|
|
148
|
+
// then its a voided table
|
|
149
|
+
return new basic_1.VoidType("SELECT_todo");
|
|
150
|
+
}
|
|
151
|
+
const dbType = dbSources[0].parseType(scope.getRegistry());
|
|
152
|
+
if (!(dbType instanceof basic_1.StructureType)) {
|
|
153
|
+
return new basic_1.VoidType("SELECT_todo");
|
|
154
|
+
}
|
|
155
|
+
if (fields.length === 1 && fields[0].code === "*") {
|
|
156
|
+
return new basic_1.TableType(dbType, { withHeader: false, keyType: basic_1.TableKeyType.default }, undefined);
|
|
157
|
+
}
|
|
158
|
+
const allFieldsSimple = fields.every(f => isSimple.test(f.code));
|
|
159
|
+
if (allFieldsSimple === true) {
|
|
160
|
+
const components = [];
|
|
161
|
+
for (const field of fields) {
|
|
162
|
+
const type = dbType.getComponentByName(field.code);
|
|
163
|
+
if (type === undefined) {
|
|
164
|
+
return new basic_1.VoidType("SELECT_todo");
|
|
165
|
+
}
|
|
166
|
+
components.push({ name: field.code, type });
|
|
167
|
+
}
|
|
168
|
+
return new basic_1.TableType(new basic_1.StructureType(components), { withHeader: false, keyType: basic_1.TableKeyType.default }, undefined);
|
|
169
|
+
}
|
|
98
170
|
return new basic_1.VoidType("SELECT_todo");
|
|
99
171
|
}
|
|
100
172
|
findFields(node) {
|
|
101
173
|
var _a;
|
|
102
174
|
let expr = undefined;
|
|
103
175
|
const ret = [];
|
|
104
|
-
expr = node.
|
|
105
|
-
if (expr === undefined) {
|
|
106
|
-
expr = node.findDirectExpression(Expressions.SQLFields);
|
|
107
|
-
}
|
|
176
|
+
expr = node.findFirstExpression(Expressions.SQLFieldList);
|
|
108
177
|
if (expr === undefined) {
|
|
109
|
-
node.findDirectExpression(Expressions.
|
|
178
|
+
expr = node.findDirectExpression(Expressions.SQLFieldListLoop);
|
|
110
179
|
}
|
|
111
|
-
for (const field of (expr === null || expr === void 0 ? void 0 : expr.
|
|
180
|
+
for (const field of (expr === null || expr === void 0 ? void 0 : expr.findDirectExpressionsMulti([Expressions.SQLField, Expressions.SQLFieldName])) || []) {
|
|
112
181
|
let code = field.concatTokens().toUpperCase();
|
|
113
182
|
const as = ((_a = field.findDirectExpression(Expressions.SQLAsName)) === null || _a === void 0 ? void 0 : _a.concatTokens()) || "";
|
|
114
183
|
if (as !== "") {
|
|
@@ -153,6 +153,20 @@ class ExpressionNode extends _abstract_node_1.AbstractNode {
|
|
|
153
153
|
}
|
|
154
154
|
return ret;
|
|
155
155
|
}
|
|
156
|
+
findDirectExpressionsMulti(type) {
|
|
157
|
+
const ret = [];
|
|
158
|
+
for (const child of this.getChildren()) {
|
|
159
|
+
if (child instanceof ExpressionNode) {
|
|
160
|
+
for (const t of type) {
|
|
161
|
+
if (child.get() instanceof t) {
|
|
162
|
+
ret.push(child);
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return ret;
|
|
169
|
+
}
|
|
156
170
|
findDirectTokenByText(text) {
|
|
157
171
|
for (const child of this.getChildren()) {
|
|
158
172
|
if (child instanceof token_node_1.TokenNode && child.get().getStr().toUpperCase() === text.toUpperCase()) {
|
package/build/src/registry.js
CHANGED
|
@@ -209,6 +209,7 @@ ENDIF.`,
|
|
|
209
209
|
if (this.conf.selectWithoutInto
|
|
210
210
|
&& (sta instanceof Statements.Select || sta instanceof Statements.SelectLoop)
|
|
211
211
|
&& staNode.findFirstExpression(Expressions.SQLIntoStructure) === undefined
|
|
212
|
+
&& staNode.findFirstExpression(Expressions.SQLIntoList) === undefined
|
|
212
213
|
&& staNode.findFirstExpression(Expressions.SQLIntoTable) === undefined) {
|
|
213
214
|
const concat = (_a = staNode.findFirstExpression(Expressions.SQLFieldList)) === null || _a === void 0 ? void 0 : _a.concatTokens().toUpperCase();
|
|
214
215
|
if (concat !== "COUNT(*)" && concat !== "COUNT( * )") {
|
|
@@ -55,6 +55,7 @@ Activates from v750 and up`,
|
|
|
55
55
|
const where = expr === null || expr === void 0 ? void 0 : expr.findDirectExpression(Expressions.SQLCond);
|
|
56
56
|
const order = expr === null || expr === void 0 ? void 0 : expr.findDirectExpression(Expressions.SQLOrderBy);
|
|
57
57
|
const into = (expr === null || expr === void 0 ? void 0 : expr.findDirectExpression(Expressions.SQLIntoStructure))
|
|
58
|
+
|| (expr === null || expr === void 0 ? void 0 : expr.findDirectExpression(Expressions.SQLIntoList))
|
|
58
59
|
|| (expr === null || expr === void 0 ? void 0 : expr.findDirectExpression(Expressions.SQLIntoTable));
|
|
59
60
|
if (into === undefined || where === undefined) {
|
|
60
61
|
continue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@abaplint/core",
|
|
3
|
-
"version": "2.102.
|
|
3
|
+
"version": "2.102.50",
|
|
4
4
|
"description": "abaplint - Core API",
|
|
5
5
|
"main": "build/src/index.js",
|
|
6
6
|
"typings": "build/abaplint.d.ts",
|
|
@@ -52,9 +52,9 @@
|
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@microsoft/api-extractor": "^7.37.1",
|
|
54
54
|
"@types/chai": "^4.3.6",
|
|
55
|
-
"@types/mocha": "^10.0.
|
|
56
|
-
"@types/node": "^20.7.
|
|
57
|
-
"chai": "^4.3.
|
|
55
|
+
"@types/mocha": "^10.0.2",
|
|
56
|
+
"@types/node": "^20.7.1",
|
|
57
|
+
"chai": "^4.3.9",
|
|
58
58
|
"eslint": "^8.50.0",
|
|
59
59
|
"mocha": "^10.2.0",
|
|
60
60
|
"c8": "^8.0.1",
|