js-code-detector 0.0.1

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.
@@ -0,0 +1,277 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+
29
+ // src/util/report_util/code_block_detect.ts
30
+ var code_block_detect_exports = {};
31
+ __export(code_block_detect_exports, {
32
+ default: () => codeBlockDetect
33
+ });
34
+ module.exports = __toCommonJS(code_block_detect_exports);
35
+ var import_path = require("path");
36
+ var import_getAstKitByFilePath = __toESM(require("../ast_util/getAstKitByFilePath"));
37
+ var import_AstUtil = __toESM(require("../ast_util/AstUtil"));
38
+ var createBlockReport = (kind) => ({
39
+ kind,
40
+ diff_txt: [],
41
+ topAdded: [],
42
+ topRemoved: [],
43
+ added: [],
44
+ addedNotUsed: [],
45
+ addedNotFound: [],
46
+ addedEffects: [],
47
+ removed: [],
48
+ removedStillUsing: [],
49
+ removedEffects: []
50
+ });
51
+ var findOrCreateBlockReport = (blockReports, kind, fromHead) => {
52
+ var _a, _b;
53
+ if (fromHead) {
54
+ for (let i = blockReports.length - 1; i >= 0; i--) {
55
+ if (blockReports[i].kind === kind) {
56
+ return blockReports[i];
57
+ }
58
+ }
59
+ }
60
+ if (((_a = blockReports.at(-1)) == null ? void 0 : _a.kind) === kind || kind === "Other" && ((_b = blockReports.at(-1)) == null ? void 0 : _b.kind.startsWith("Other:"))) {
61
+ return blockReports.at(-1);
62
+ }
63
+ return createBlockReport(kind);
64
+ };
65
+ function codeBlockDetect(arg) {
66
+ const { gitDiffItem, absPathPrefix, blockReports } = arg;
67
+ const { filePath, startLineOfNew, items, startLineOfOld } = gitDiffItem;
68
+ const { mapFileLineToNodeSet, mapUuidToNode } = (0, import_getAstKitByFilePath.default)(filePath, absPathPrefix);
69
+ const filePathOfOld = (0, import_path.join)(process.cwd(), "..", "source", filePath);
70
+ const { mapFileLineToNodeSet: mapFileLineToNodeSetOld } = (0, import_getAstKitByFilePath.default)(filePathOfOld, absPathPrefix);
71
+ const programNode = mapUuidToNode.get("Program");
72
+ const lineNumberStartNew = Number(startLineOfNew);
73
+ const lineNumberEndNew = lineNumberStartNew + items.filter((item) => item.startsWith("+")).length - 1;
74
+ const lineNumberStartOld = Number(startLineOfOld);
75
+ const lineNumberEndOld = lineNumberStartOld + items.filter((item) => item.startsWith("-")).length - 1;
76
+ const addNodes = import_AstUtil.default.getTopScopeNodesByLineNumberRange(mapFileLineToNodeSet, lineNumberStartNew, lineNumberEndNew);
77
+ const removeNodes = import_AstUtil.default.getTopScopeNodesByLineNumberRange(mapFileLineToNodeSetOld, lineNumberStartOld, lineNumberEndOld);
78
+ iterateNodes(addNodes, "add", { blockReports, programNode });
79
+ iterateNodes(removeNodes, "remove", { blockReports, programNode });
80
+ const lastReport = blockReports.at(-1);
81
+ if (lastReport) {
82
+ lastReport.diff_txt = items;
83
+ lastReport.topAdded = addNodes;
84
+ lastReport.topRemoved = removeNodes;
85
+ }
86
+ blockReports.push(createBlockReport("Never"));
87
+ }
88
+ function getPathsOfNode(topScopeNodes) {
89
+ const paths = [];
90
+ for (const topScopeNode of topScopeNodes) {
91
+ const nodeCollection = topScopeNode._util.nodeCollection;
92
+ for (const nodeItem of nodeCollection) {
93
+ if (nodeItem.type.endsWith("Identifier") || nodeItem.type.endsWith("Literal")) {
94
+ paths.push(import_AstUtil.default.getNodePath(nodeItem));
95
+ }
96
+ }
97
+ }
98
+ return [...new Set(paths)];
99
+ }
100
+ function mapNodePath(list) {
101
+ return list.map((item) => ({ ...item, effects: item.effects.map((ele) => ({ ele, path: import_AstUtil.default.getNodePath(ele) })) }));
102
+ }
103
+ function filterBySamePathAndLen(list, sameEffectsPaths) {
104
+ return list.map((e) => ({ ...e, effects: e.effects.filter((item) => !sameEffectsPaths.includes(item.path)) })).filter((e) => e.effects.length);
105
+ }
106
+ function extractEffectItem(list) {
107
+ return list.map((e) => ({ ...e, effects: e.effects.map((item) => item.ele) }));
108
+ }
109
+ function pushBlockReport(blockReports, blockReport, programNode) {
110
+ const sameNames = blockReport.added.filter((path) => blockReport.removed.includes(path));
111
+ if (sameNames.length) {
112
+ ["added", "removed"].forEach((key) => {
113
+ blockReport[key] = blockReport[key].filter((path) => !sameNames.includes(path));
114
+ });
115
+ }
116
+ const addedEffectsList = mapNodePath(blockReport.addedEffects);
117
+ const removedEffectsList = mapNodePath(blockReport.removedEffects);
118
+ const addedEffectsPaths = addedEffectsList.map((item) => item.effects.map(({ path }) => path)).flat();
119
+ const removedEffectsPaths = removedEffectsList.map((item) => item.effects.map(({ path }) => path)).flat();
120
+ const sameEffectsPaths = addedEffectsPaths.filter((path) => removedEffectsPaths.includes(path));
121
+ if (sameEffectsPaths.length) {
122
+ const addedEffects = filterBySamePathAndLen(addedEffectsList, sameEffectsPaths);
123
+ const removedEffects = filterBySamePathAndLen(removedEffectsList, sameEffectsPaths);
124
+ blockReport.addedEffects = extractEffectItem(addedEffects);
125
+ blockReport.removedEffects = extractEffectItem(removedEffects);
126
+ }
127
+ if (!blockReports.includes(blockReport) && Object.entries(blockReport).filter((e) => e[0] !== "kind").some((e) => e[1].length)) {
128
+ blockReports.push(blockReport);
129
+ }
130
+ }
131
+ function iterateNodes(topScopeNodes, operation, extra) {
132
+ for (const topScopeNode of topScopeNodes) {
133
+ if (["ImportDeclaration", "ImportSpecifier", "ImportDefaultSpecifier"].includes(topScopeNode.type)) {
134
+ detectImport({ topScopeNode, operation, extra });
135
+ } else if (["VariableDeclaration", "VariableDeclarator"].includes(topScopeNode.type) || import_AstUtil.default.isSubNodeOfVariableDeclarator(topScopeNode)) {
136
+ detectVariableDeclaration({ topScopeNode, operation, extra });
137
+ } else if (["FunctionDeclaration", "ClassDeclaration"].includes(topScopeNode.type)) {
138
+ detectFnClsDeclaration({ topScopeNode, operation, extra });
139
+ } else if (["UnaryExpression", "UpdateExpression"].includes(topScopeNode.type)) {
140
+ detectUpdateEffectExp({ topScopeNode, operation, extra });
141
+ } else if (["CallExpression"].includes(topScopeNode.type)) {
142
+ detectFnCallExp({ topScopeNode, operation, extra });
143
+ } else if (["AssignmentExpression"].includes(topScopeNode.type)) {
144
+ detectAssignmentEffectExp({ topScopeNode, operation, extra });
145
+ } else if (["ExpressionStatement"].includes(topScopeNode.type)) {
146
+ const { expression } = topScopeNode;
147
+ iterateNodes([expression], operation, extra);
148
+ } else {
149
+ detectOther({ topScopeNode, operation, extra });
150
+ }
151
+ }
152
+ }
153
+ function detectOther(arg) {
154
+ const { topScopeNode, operation, extra: { blockReports, programNode } } = arg;
155
+ const blockReport = findOrCreateBlockReport(blockReports, "Other");
156
+ const { added, removed } = blockReport;
157
+ const nodePaths = getPathsOfNode(topScopeNode._util.nodeCollection);
158
+ (operation === "add" ? added : removed).push(...nodePaths);
159
+ blockReport.kind = "Other:" + topScopeNode.type;
160
+ pushBlockReport(blockReports, blockReport, programNode);
161
+ }
162
+ function detectImport(arg) {
163
+ const { topScopeNode, operation, extra: { blockReports, programNode } } = arg;
164
+ const blockReport = findOrCreateBlockReport(blockReports, "Import");
165
+ const { added, removed, addedEffects, removedEffects } = blockReport;
166
+ const specifiers = topScopeNode.type === "ImportDeclaration" ? topScopeNode.specifiers : [topScopeNode];
167
+ if (Array.isArray(specifiers)) {
168
+ specifiers.forEach((s) => {
169
+ const { local } = s;
170
+ (operation === "add" ? added : removed).push(local.name);
171
+ (operation === "add" ? addedEffects : removedEffects).push({ causeBy: local, effects: [...local._util.effectIds] });
172
+ });
173
+ }
174
+ pushBlockReport(blockReports, blockReport, programNode);
175
+ }
176
+ function detectFnClsDeclaration(arg) {
177
+ const { topScopeNode, operation, extra: { blockReports, programNode } } = arg;
178
+ const blockReport = findOrCreateBlockReport(blockReports, "Declaration");
179
+ const { added, removed, addedEffects, removedEffects } = blockReport;
180
+ const { id } = topScopeNode;
181
+ (operation === "add" ? added : removed).push(id.name);
182
+ (operation === "add" ? addedEffects : removedEffects).push({ causeBy: id, effects: [...id._util.effectIds] });
183
+ pushBlockReport(blockReports, blockReport, programNode);
184
+ }
185
+ function insertPrefix(n, prefix, sep = ":") {
186
+ return [prefix, n].join(sep);
187
+ }
188
+ function detectVariableDeclaration(arg) {
189
+ const { topScopeNode, operation, extra: { blockReports, programNode } } = arg;
190
+ const blockReport = findOrCreateBlockReport(blockReports, "Declaration");
191
+ const { added, removed, addedEffects, removedEffects } = blockReport;
192
+ if (["VariableDeclaration", "VariableDeclarator"].includes(topScopeNode.type)) {
193
+ let declarations = [];
194
+ if (topScopeNode.type === "VariableDeclarator") {
195
+ declarations = [topScopeNode];
196
+ } else {
197
+ declarations = topScopeNode.declarations;
198
+ }
199
+ if (Array.isArray(declarations)) {
200
+ for (const declaration of declarations) {
201
+ const { id, init } = declaration;
202
+ if (id) {
203
+ id._util.nodeCollection.forEach((item) => {
204
+ if (item.type === "Identifier") {
205
+ (operation === "add" ? added : removed).push(insertPrefix(item.name, "id"));
206
+ (operation === "add" ? addedEffects : removedEffects).push({ causeBy: item, effects: [...item._util.effectIds] });
207
+ }
208
+ });
209
+ }
210
+ const initIdSet = /* @__PURE__ */ new Set();
211
+ if (init && !["ArrowFunctionExpression", "FunctionExpression"].includes(init.type)) {
212
+ ["Identifier"].includes(init.type) ? initIdSet.add(init) : import_AstUtil.default.deepFindIdOfExpression(init, (id2) => initIdSet.add(id2));
213
+ for (const initId of initIdSet) {
214
+ operation === "add" ? blockReport.added.push(insertPrefix(initId.name, "init")) : blockReport.removed.push(insertPrefix(initId.name, "init"));
215
+ }
216
+ }
217
+ }
218
+ }
219
+ } else {
220
+ topScopeNode._util.nodeCollection.forEach((item) => {
221
+ if (item.type === "Identifier") {
222
+ (operation === "add" ? added : removed).push(insertPrefix(item.name, "id"));
223
+ (operation === "add" ? addedEffects : removedEffects).push({ causeBy: item, effects: [...item._util.effectIds] });
224
+ }
225
+ });
226
+ }
227
+ pushBlockReport(blockReports, blockReport, programNode);
228
+ }
229
+ function detectUpdateEffectExp(arg) {
230
+ const { topScopeNode, operation, extra: { blockReports, programNode } } = arg;
231
+ const blockReport = findOrCreateBlockReport(blockReports, "SelfUpdate");
232
+ const { added, removed, addedEffects, removedEffects } = blockReport;
233
+ const { argument: args } = topScopeNode;
234
+ const createdExpIdSet = /* @__PURE__ */ new Set();
235
+ import_AstUtil.default.deepFindIdOfExpression(args, (id) => createdExpIdSet.add(id));
236
+ for (const createdExpId of createdExpIdSet) {
237
+ (operation === "add" ? added : removed).push(createdExpId.name);
238
+ (operation === "add" ? addedEffects : removedEffects).push({ causeBy: createdExpId, effects: [...createdExpId._util.effectIds] });
239
+ }
240
+ pushBlockReport(blockReports, blockReport, programNode);
241
+ }
242
+ function detectFnCallExp(arg) {
243
+ const { topScopeNode, operation, extra: { blockReports, programNode } } = arg;
244
+ const blockReport = findOrCreateBlockReport(blockReports, "Invoke");
245
+ const { added, removed, addedEffects, removedEffects } = blockReport;
246
+ const { callee, arguments: args } = topScopeNode;
247
+ const argsIds = args.map((arg2) => arg2._util.nodeCollection.filter((n) => n.type === "Identifier")).flat();
248
+ for (const argsId of argsIds) {
249
+ (operation === "add" ? added : removed).push(argsId.name);
250
+ (operation === "add" ? addedEffects : removedEffects).push({ causeBy: argsId, effects: [...argsId._util.effectIds] });
251
+ }
252
+ const createdExpIdSet = /* @__PURE__ */ new Set();
253
+ import_AstUtil.default.deepFindIdOfExpression(callee, (id) => createdExpIdSet.add(id));
254
+ for (const createdExpId of createdExpIdSet) {
255
+ (operation === "add" ? added : removed).push(createdExpId.name);
256
+ (operation === "add" ? addedEffects : removedEffects).push({ causeBy: createdExpId, effects: [...createdExpId._util.effectIds] });
257
+ }
258
+ pushBlockReport(blockReports, blockReport, programNode);
259
+ }
260
+ function detectAssignmentEffectExp(arg) {
261
+ const { topScopeNode, operation, extra: { blockReports, programNode } } = arg;
262
+ const blockReport = findOrCreateBlockReport(blockReports, "Assignment");
263
+ const { added, removed, addedEffects, removedEffects } = blockReport;
264
+ const { left, right } = topScopeNode;
265
+ const idSetLeft = /* @__PURE__ */ new Set();
266
+ ["Identifier"].includes(left.type) ? idSetLeft.add(left) : import_AstUtil.default.deepFindIdOfExpression(left, (id) => idSetLeft.add(id));
267
+ for (const createdExp of idSetLeft) {
268
+ (operation === "add" ? added : removed).push(insertPrefix(createdExp.name, "left"));
269
+ (operation === "add" ? addedEffects : removedEffects).push({ causeBy: createdExp, effects: [...createdExp._util.effectIds] });
270
+ }
271
+ const idSetRight = /* @__PURE__ */ new Set();
272
+ ["Identifier", "ArrowFunctionExpression", "FunctionExpression"].includes(right.type) ? idSetRight.add(right) : import_AstUtil.default.deepFindIdOfExpression(right, (id) => idSetRight.add(id));
273
+ for (const createdExp of idSetRight) {
274
+ (operation === "add" ? added : removed).push(insertPrefix(createdExp.name, "right"));
275
+ }
276
+ pushBlockReport(blockReports, blockReport, programNode);
277
+ }
@@ -0,0 +1,29 @@
1
+ import { GitDiffDetail } from "./format_git_diff_content";
2
+ type Arg = {
3
+ groupGitDiffLines: GitDiffDetail[];
4
+ tree: Record<string, string[]>;
5
+ absPathPrefix: string;
6
+ };
7
+ export declare function createDetectReport(arg: Arg): {
8
+ blockReports: {
9
+ addedEffects: {
10
+ causeBy: string;
11
+ effects: string[];
12
+ }[];
13
+ removedEffects: {
14
+ causeBy: string;
15
+ effects: (string | undefined)[];
16
+ }[];
17
+ diff_txt: string[];
18
+ added: string[];
19
+ addedNotUsed: string[];
20
+ addedNotFound: string[];
21
+ removed: string[];
22
+ removedStillUsing: string[];
23
+ kind: "Import" | "Declaration" | "Assignment" | "SelfUpdate" | "Invoke" | "Other" | "Never";
24
+ }[];
25
+ filePath: string;
26
+ type: "add" | "delete" | "modify";
27
+ filesDependsOnMe: string[];
28
+ }[];
29
+ export {};
@@ -0,0 +1,99 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+
29
+ // src/util/report_util.ts
30
+ var report_util_exports = {};
31
+ __export(report_util_exports, {
32
+ createDetectReport: () => createDetectReport
33
+ });
34
+ module.exports = __toCommonJS(report_util_exports);
35
+ var import_code_block_detect = __toESM(require("./report_util/code_block_detect"));
36
+ var import_getAstKitByFilePath = __toESM(require("./ast_util/getAstKitByFilePath"));
37
+ var import_AstUtil = __toESM(require("./ast_util/AstUtil"));
38
+ function createDetectReport(arg) {
39
+ const { groupGitDiffLines, tree, absPathPrefix } = arg;
40
+ const reports = [];
41
+ const entries = Object.entries(tree);
42
+ groupGitDiffLines.forEach((item) => {
43
+ const { filePath, type } = item;
44
+ const filesDependsOnMe = entries.filter((entry) => entry[1].includes(filePath)).map((e) => e[0]);
45
+ let reportItem = reports.find((e) => e.filePath === filePath);
46
+ if (!reportItem) {
47
+ reportItem = {
48
+ filePath,
49
+ type,
50
+ filesDependsOnMe,
51
+ blockReports: []
52
+ };
53
+ reports.push(reportItem);
54
+ }
55
+ if (type === "modify") {
56
+ (0, import_code_block_detect.default)({ gitDiffItem: item, absPathPrefix, blockReports: reportItem.blockReports });
57
+ }
58
+ });
59
+ return reports.map((report) => {
60
+ const filePath = report.filePath;
61
+ const astKit = (0, import_getAstKitByFilePath.default)(filePath, absPathPrefix);
62
+ const allNodes = new Map([...astKit.mapUuidToNode.values()].map((ele) => [import_AstUtil.default.getNodePath(ele), ele]));
63
+ return {
64
+ ...report,
65
+ blockReports: report.blockReports.filter((e) => e.kind !== "Never").map((blockReport) => {
66
+ const { kind, addedEffects, removedEffects, topAdded, topRemoved, ...rest } = blockReport;
67
+ const removedEffectsInfos = removedEffects.map((item) => {
68
+ const tmpList = item.effects.map((ele) => {
69
+ const nodePath = import_AstUtil.default.getNodePath(ele);
70
+ const item2 = allNodes.get(nodePath);
71
+ return item2 && import_AstUtil.default.createScopeContent(item2);
72
+ }).filter(Boolean);
73
+ const effects = [...new Set(tmpList)];
74
+ return {
75
+ causeBy: item.causeBy.name || item.causeBy.type,
76
+ effects
77
+ };
78
+ }).filter((item) => item.effects.length > 0);
79
+ const addedEffectsInfos = addedEffects.map((item) => {
80
+ const effects = [...new Set(item.effects.map(import_AstUtil.default.createScopeContent))];
81
+ return {
82
+ causeBy: item.causeBy.name || item.causeBy.type,
83
+ effects
84
+ };
85
+ }).filter((item) => item.effects.length > 0);
86
+ return {
87
+ kind,
88
+ ...rest,
89
+ addedEffects: addedEffectsInfos,
90
+ removedEffects: removedEffectsInfos
91
+ };
92
+ })
93
+ };
94
+ });
95
+ }
96
+ // Annotate the CommonJS export names for ESM import in node:
97
+ 0 && (module.exports = {
98
+ createDetectReport
99
+ });
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "js-code-detector",
3
+ "version": "0.0.1",
4
+ "description": "",
5
+ "main": "dist/cjs/index.js",
6
+ "types": "dist/cjs/index.d.ts",
7
+ "scripts": {
8
+ "dev": "father dev",
9
+ "build": "father build",
10
+ "build:deps": "father prebundle",
11
+ "prepublishOnly": "father doctor && npm run build"
12
+ },
13
+ "keywords": [],
14
+ "authors": [
15
+ "John <944268503@qq.com>"
16
+ ],
17
+ "license": "MIT",
18
+ "files": [
19
+ "dist",
20
+ "compiled",
21
+ "bin"
22
+ ],
23
+ "bin": {
24
+ "detect": "bin/detect.js"
25
+ },
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "devDependencies": {
30
+ "@types/madge": "^5.0.3",
31
+ "father": "^4.6.3"
32
+ },
33
+ "dependencies": {
34
+ "@babel/parser": "^7.28.3",
35
+ "@umijs/utils": "^4.4.12",
36
+ "dayjs": "^1.11.13",
37
+ "madge": "^8.0.0",
38
+ "vue-eslint-parser": "^10.2.0"
39
+ }
40
+ }