scancscode 1.0.33 → 1.0.35

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,11 @@
1
+ # 匿名函数字符串提取 - Verification Checklist
2
+
3
+ - [x] 所有匿名函数相关测试用例(test/CSharpStringExtractor.test.ts:2333-2478)通过
4
+ - [x] 所有现有的测试用例继续通过
5
+ - [x] 代码严格遵循 TypeScript 语法
6
+ - [x] 不使用 HTML 实体编码字符
7
+ - [x] 不使用 arguments 等保留字段作为局部变量名
8
+ - [x] extractObjectInitializerStrings 和 processObjectInitializerStringsOnly 方法行为一致
9
+ - [x] 添加了 alreadyExists 检查以避免重复添加相同片段
10
+ - [x] 匿名函数内的普通字符串被正确提取
11
+ - [x] 匿名函数内的内插字符串被正确提取和转换
@@ -0,0 +1,137 @@
1
+ # 匿名函数字符串提取 - Product Requirement Document
2
+
3
+ ## Overview
4
+
5
+ * **Summary**: 修改 CSharpStringExtractor 类,使其能够正确识别和提取 C# 代码中匿名函数内的字符串表达式。
6
+
7
+ * **Purpose**: 解决当前代码无法处理匿名函数中字符串提取的问题,确保匿名函数中的字符串也能被正确国际化。
8
+
9
+ * **Target Users**: Unity 项目开发者,使用此工具进行 C# 代码国际化。
10
+
11
+ ## Goals
12
+
13
+ * 正确识别 C# 代码中的匿名函数
14
+
15
+ * 提取匿名函数内的字符串表达式
16
+
17
+ * 支持多种形式的匿名函数定义(带修饰词、带参数、不带参数、有大括号、无大括号)
18
+
19
+ * 确保匿名函数内的字符串与其他字符串一起按正确顺序返回
20
+
21
+ * 保持现有功能不变
22
+
23
+ ## Non-Goals (Out of Scope)
24
+
25
+ * 不修改字符串处理的核心逻辑
26
+
27
+ * 不改变已有的测试用例
28
+
29
+ * 不添加新的功能,除了匿名函数字符串提取
30
+
31
+ ## Background & Context
32
+
33
+ * 当前代码可以处理对象初始化器和类成员初始化中的字符串,但无法处理赋值给成员的匿名函数内的字符串
34
+
35
+ * 匿名函数常见形式:`() => { ... }`、`async () => { ... }`、`(param) => expression`
36
+
37
+ * 测试用例位于 test/CSharpStringExtractor.test.ts:2333-2478
38
+
39
+ ## Functional Requirements
40
+
41
+ * **FR-1**: 支持提取对象初始化器中赋值给成员的匿名函数内的字符串
42
+
43
+ * **FR-2**: 支持带 async 修饰词的匿名函数
44
+
45
+ * **FR-3**: 支持带参数和不带参数的匿名函数
46
+
47
+ * **FR-4**: 支持有大括号和无大括号的匿名函数表达式
48
+
49
+ * **FR-5**: 支持匿名函数内的普通字符串和内插字符串
50
+
51
+ # Non-Functional Requirements
52
+
53
+ # **NFR-1**: 所有现有测试用例必须继续通过
54
+
55
+ * **NFR-2**: 性能不应受到明显影响
56
+ * **NFR-3**: 代码必须严格遵循 TypeScript 语法
57
+
58
+ ## Constraints
59
+
60
+ * **Technical**: 使用 TypeScript,不使用 HTML 实体编码,不使用 arguments 等保留字段名
61
+
62
+ * **Business**: 必须通过指定的测试用例
63
+
64
+ * **Dependencies**: 依赖 Jest 测试框架
65
+
66
+ ## Assumptions
67
+
68
+ * 匿名函数总是出现在对象初始化器或类成员赋值的场景中
69
+
70
+ * 匿名函数的边界可以通过大括号匹配或箭头符号确定
71
+
72
+ ## Acceptance Criteria
73
+
74
+ ### AC-1: 处理对象初始化器中匿名函数内的字符串
75
+
76
+ * **Given**: 有一个对象初始化器,其中某个成员被赋值为一个包含字符串的匿名函数
77
+
78
+ * **When**: extractStrings 方法被调用处理此代码
79
+
80
+ * **Then**: 匿名函数内的字符串被正确提取,并且按原始顺序返回
81
+
82
+ * **Verification**: `programmatic`
83
+
84
+ ### AC-2: 处理带 async 修饰词的匿名函数
85
+
86
+ * **Given**: 有一个带 async 修饰词的匿名函数,其中包含字符串
87
+
88
+ * **When**: extractStrings 方法被调用处理此代码
89
+
90
+ * **Then**: 匿名函数内的字符串被正确提取
91
+
92
+ * **Verification**: `programmatic`
93
+
94
+ ### AC-3: 处理带参数的匿名函数
95
+
96
+ * **Given**: 有一个带参数的匿名函数,其中包含字符串
97
+
98
+ * **When**: extractStrings 方法被调用处理此代码
99
+
100
+ * **Then**: 匿名函数内的字符串被正确提取
101
+
102
+ * **Verification**: `programmatic`
103
+
104
+ ### AC-4: 处理无大括号的匿名函数表达式
105
+
106
+ * **Given**: 有一个无大括号的匿名函数表达式,其中包含字符串
107
+
108
+ * **When**: extractStrings 方法被调用处理此代码
109
+
110
+ * **Then**: 匿名函数内的字符串被正确提取
111
+
112
+ * **Verification**: `programmatic`
113
+
114
+ ### AC-5: 处理匿名函数内的内插字符串
115
+
116
+ * **Given**: 有一个匿名函数,其中包含内插字符串
117
+
118
+ * **When**: extractStrings 方法被调用处理此代码
119
+
120
+ * **Then**: 内插字符串被正确提取和转换
121
+
122
+ * **Verification**: `programmatic`
123
+
124
+ ### AC-6: 保持现有功能不变
125
+
126
+ * **Given**: 所有现有的测试用例
127
+
128
+ * **When**: 运行测试
129
+
130
+ * **Then**: 所有现有的测试用例必须通过
131
+
132
+ * **Verification**: `programmatic`
133
+
134
+ ## Open Questions
135
+
136
+ * 无
137
+
@@ -0,0 +1,65 @@
1
+ # 匿名函数字符串提取 - The Implementation Plan (Decomposed and Prioritized Task List)
2
+
3
+ ## [x] Task 1: 分析当前实现和测试失败原因
4
+ - **Priority**: P0
5
+ - **Depends On**: None
6
+ - **Description**:
7
+ - 分析 extractObjectInitializerStrings 和 processObjectInitializerStringsOnly 方法的当前实现
8
+ - 了解为什么它们无法处理匿名函数中的字符串
9
+ - 研究测试用例的预期行为
10
+ - **Acceptance Criteria Addressed**: [AC-1, AC-6]
11
+ - **Test Requirements**:
12
+ - `programmatic` TR-1.1: 运行测试用例,确认失败状态
13
+ - `programmatic` TR-1.2: 分析代码流程,确定问题所在
14
+ - **Notes**: 无
15
+
16
+ ## [x] Task 2: 设计解决方案
17
+ - **Priority**: P0
18
+ - **Depends On**: Task 1
19
+ - **Description**:
20
+ - 设计如何在对象初始化器中识别匿名函数
21
+ - 设计如何在匿名函数内部继续提取字符串
22
+ - 设计如何处理大括号匹配和箭头符号
23
+ - **Acceptance Criteria Addressed**: [AC-1, AC-2, AC-3, AC-4]
24
+ - **Test Requirements**:
25
+ - `human-judgement` TR-2.1: 设计文档清晰说明如何识别和处理匿名函数
26
+ - **Notes**: 确保不破坏现有功能
27
+
28
+ ## [x] Task 3: 实现 extractObjectInitializerStrings 方法的修复
29
+ - **Priority**: P0
30
+ - **Depends On**: Task 2
31
+ - **Description**:
32
+ - 修改 extractObjectInitializerStrings 方法
33
+ - 添加匿名函数识别逻辑
34
+ - 确保匿名函数内的字符串被正确提取
35
+ - 添加 alreadyExists 检查以避免重复
36
+ - **Acceptance Criteria Addressed**: [AC-1, AC-2, AC-3, AC-4, AC-5]
37
+ - **Test Requirements**:
38
+ - `programmatic` TR-3.1: 匿名函数相关测试用例通过
39
+ - `programmatic` TR-3.2: 所有现有测试用例继续通过
40
+ - **Notes**: 严格遵循 TypeScript 语法
41
+
42
+ ## [x] Task 4: 实现 processObjectInitializerStringsOnly 方法的修复
43
+ - **Priority**: P0
44
+ - **Depends On**: Task 3
45
+ - **Description**:
46
+ - 修改 processObjectInitializerStringsOnly 方法
47
+ - 保持与 extractObjectInitializerStrings 方法一致的行为
48
+ - **Acceptance Criteria Addressed**: [AC-1, AC-6]
49
+ - **Test Requirements**:
50
+ - `programmatic` TR-4.1: 匿名函数相关测试用例通过
51
+ - `programmatic` TR-4.2: 所有现有测试用例继续通过
52
+ - **Notes**: 确保两个方法的行为一致
53
+
54
+ ## [x] Task 5: 全面验证和测试
55
+ - **Priority**: P0
56
+ - **Depends On**: Task 4
57
+ - **Description**:
58
+ - 运行所有匿名函数相关测试用例
59
+ - 运行所有现有测试用例
60
+ - 确保没有回归
61
+ - **Acceptance Criteria Addressed**: [AC-1, AC-2, AC-3, AC-4, AC-5, AC-6]
62
+ - **Test Requirements**:
63
+ - `programmatic` TR-5.1: 所有匿名函数测试用例通过
64
+ - `programmatic` TR-5.2: 所有现有测试用例通过
65
+ - **Notes**: 无
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ const { CSharpStringExtractor } = require('./dist/CSharpStringExtractor');
3
+ const extractor = new CSharpStringExtractor();
4
+ const code = `
5
+ private void OnBtnIaa(EventContext context)
6
+ {
7
+ var iaaConfig = IAAConfigTable.GetConfigById((int)IAAId.境界失败_加速冷却);
8
+ if (iaaConfig == null)
9
+ {
10
+ return;
11
+ }
12
+
13
+ string des = $"观看视频后\\n等待时间减少{iaaConfig.EffectValue / 60}分钟";
14
+ this.GetModel<IAAModel>().ShowAdWithDialog(IAAId.境界失败_加速冷却, "加速时间", des, null);
15
+ }
16
+ `;
17
+ console.log('code:', JSON.stringify(code));
18
+ console.log('code.indexOf("加速时间"):', code.indexOf('"加速时间"'));
19
+ console.log('code.indexOf("观看视频后"):', code.indexOf('"观看视频后"'));
20
+ console.log('\n--- Full code ---');
21
+ console.log(code);
22
+ console.log('\n--- Indices ---');
23
+ for (let i = 0; i & lt; code.length)
24
+ ;
25
+ i++;
26
+ {
27
+ if (code[i] === '"') {
28
+ console.log(i, ':', code.substring(i, i + 10));
29
+ }
30
+ }
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const CSharpStringExtractor_1 = require("./src/CSharpStringExtractor");
4
+ const code = `
5
+ private void OnBtnIaa(EventContext context)
6
+ {
7
+ var iaaConfig = IAAConfigTable.GetConfigById((int)IAAId.境界失败_加速冷却);
8
+ if (iaaConfig == null)
9
+ {
10
+ return;
11
+ }
12
+
13
+ string des = $"观看视频后\\n等待时间减少{iaaConfig.EffectValue / 60}分钟";
14
+ this.GetModel<IAAModel>().ShowAdWithDialog(IAAId.境界失败_加速冷却, "加速时间", des, null);
15
+ }
16
+ `;
17
+ const extractor = new CSharpStringExtractor_1.CSharpStringExtractor();
18
+ const snippets = extractor.extractStrings(code);
19
+ console.log('Total snippets:', snippets.length);
20
+ console.log('---');
21
+ for (let i = 0; i < snippets.length; i++) {
22
+ console.log(`Snippet ${i}:`);
23
+ console.log(' originalIndex:', snippets[i].originalIndex);
24
+ console.log(' originalCode:', JSON.stringify(snippets[i].originalCode));
25
+ console.log(' literals:', snippets[i].literals);
26
+ console.log('---');
27
+ }
28
+ // Find the snippet with "加速时间"
29
+ const targetSnippet = snippets.find(s => s.originalCode.includes('加速时间'));
30
+ if (targetSnippet) {
31
+ console.log('Target snippet found at index:', snippets.indexOf(targetSnippet));
32
+ console.log(' originalIndex:', targetSnippet.originalIndex);
33
+ console.log(' Expected index:', code.indexOf('"加速时间"'));
34
+ }
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const CSharpStringExtractor_1 = require("./src/CSharpStringExtractor");
4
+ const extractor = new CSharpStringExtractor_1.CSharpStringExtractor();
5
+ const code = `
6
+ //上锁
7
+ case (int)State.Lock:
8
+ Toast.Info($"灵田{_model.GetFarmLandUnlockLevel(_landId)}级解锁");
9
+ break;
10
+ `;
11
+ console.log('=== 开始调试 ===');
12
+ console.log('完整代码:', JSON.stringify(code));
13
+ console.log('\n内插字符串位置:', code.indexOf(`$"灵田{_model.GetFarmLandUnlockLevel(_landId)}级解锁"`));
14
+ const snippets = extractor.extractStrings(code);
15
+ console.log('\n=== 提取到的 snippet ===');
16
+ console.log(`数量: ${snippets.length}`);
17
+ snippets.forEach((snippet, index) => {
18
+ console.log(`\nSnippet ${index}:`);
19
+ console.log(` originalIndex: ${snippet.originalIndex}`);
20
+ console.log(` originalCode: ${JSON.stringify(snippet.originalCode)}`);
21
+ console.log(` convertedCode: ${JSON.stringify(snippet.convertedCode)}`);
22
+ console.log(` literals:`, snippet.literals);
23
+ console.log(` isChanged: ${snippet.isChanged}`);
24
+ console.log(` 对应代码片段:`, JSON.stringify(code.substring(snippet.originalIndex, snippet.originalIndex + snippet.originalCode.length)));
25
+ });
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const CSharpStringExtractor_1 = require("./src/CSharpStringExtractor");
4
+ const extractor = new CSharpStringExtractor_1.CSharpStringExtractor();
5
+ // Test code 1
6
+ const code = `
7
+ protected override void OnShow(object param)
8
+ {
9
+ // Log.Info("AAA:ChooseSutraLayer显示1");
10
+ base.OnShow(param);
11
+ }`;
12
+ console.log('=== code.indexOf("AAA:Choose..."):', code.indexOf(`"AAA:ChooseSutraLayer显示1"`));
13
+ console.log('\n=== Full code (indices): ===');
14
+ for (let i = 0; i < code.length; i++) {
15
+ console.log(i, ':', JSON.stringify(code[i]));
16
+ }
17
+ console.log('\n=== Extracting snippets ===');
18
+ const snippets = extractor.extractStrings(code);
19
+ console.log(`\n=== Total snippets: ${snippets.length} ===`);
20
+ for (let i = 0; i < snippets.length; i++) {
21
+ console.log(`\n--- Snippet ${i} ---`);
22
+ console.log('Original Index:', snippets[i].originalIndex);
23
+ console.log('Original Code:', JSON.stringify(snippets[i].originalCode));
24
+ }
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const CSharpStringExtractor_1 = require("./src/CSharpStringExtractor");
4
+ const extractor = new CSharpStringExtractor_1.CSharpStringExtractor();
5
+ const code = `infoStr += $"是否暴击: [b]{(_damageInfo.IsCritical ? "[color=#00B000]是[/color]" : "否")}[/b]\n";`;
6
+ console.log('Original code:', code);
7
+ console.log('\n---\n');
8
+ const snippet = new CSharpStringExtractor_1.CodeSnippet();
9
+ snippet.originalCode = code;
10
+ snippet.originalIndex = 0;
11
+ const result = extractor.processStringTemplates(code, snippet, 'Tr', 'Format');
12
+ console.log('Result:', result);
13
+ console.log('\n---\n');
14
+ console.log('Snippet converted code:', snippet.convertedCode);
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ const testCode = `infoStr += $"是否暴击: [b]{(_damageInfo.IsCritical ? "[color=#00B000]是[/color]" : "否")}[/b]\n";`;
3
+ const templateRegex = /(\$@?|@\$)"((?:[^"\\]|\\.)*)"/gs;
4
+ const match = templateRegex.exec(testCode);
5
+ if (match) {
6
+ console.log('Full match:', JSON.stringify(match[0]));
7
+ console.log('Content:', JSON.stringify(match[2]));
8
+ const content = match[2];
9
+ let templateIndex = 0;
10
+ console.log('\n=== Original code without string handling ===');
11
+ while (templateIndex < content.length) {
12
+ if (content[templateIndex] === '{') {
13
+ templateIndex++;
14
+ let exprStart = templateIndex;
15
+ let braceDepth = 1;
16
+ while (templateIndex < content.length && braceDepth > 0) {
17
+ if (content[templateIndex] === '{') {
18
+ braceDepth++;
19
+ }
20
+ else if (content[templateIndex] === '}') {
21
+ braceDepth--;
22
+ }
23
+ console.log(`Index ${templateIndex}: char '${content[templateIndex]}', depth: ${braceDepth}`);
24
+ templateIndex++;
25
+ }
26
+ const varExpr = content.substring(exprStart, templateIndex - 1);
27
+ console.log('Extracted variable expression:', JSON.stringify(varExpr));
28
+ }
29
+ else {
30
+ templateIndex++;
31
+ }
32
+ }
33
+ }
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const CSharpStringExtractor_1 = require("./src/CSharpStringExtractor");
4
+ const extractor = new CSharpStringExtractor_1.CSharpStringExtractor();
5
+ const code = `
6
+ //上锁
7
+ case (int)State.Lock:
8
+ Toast.Info($"灵田{_model.GetFarmLandUnlockLevel(_landId)}级解锁");
9
+ break;
10
+ `;
11
+ console.log('=== code ===');
12
+ console.log(code);
13
+ console.log('\n=== Extracting snippets ===');
14
+ const snippets = extractor.extractStrings(code);
15
+ console.log(`\n=== snippets.length: ${snippets.length}`);
16
+ for (let i = 0; i < snippets.length; i++) {
17
+ console.log(`\n--- snippet ${i} ---`);
18
+ console.log('originalIndex:', snippets[i].originalIndex);
19
+ console.log('originalCode:', JSON.stringify(snippets[i].originalCode));
20
+ console.log('convertedCode:', JSON.stringify(snippets[i].convertedCode));
21
+ console.log('literals:', snippets[i].literals);
22
+ console.log('isChanged:', snippets[i].isChanged);
23
+ }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ const testCode = `
3
+ private void OnBtnIaa(EventContext context)
4
+ {
5
+ var iaaConfig = IAAConfigTable.GetConfigById((int)IAAId.境界失败_加速冷却);
6
+ if (iaaConfig == null)
7
+ {
8
+ return;
9
+ }
10
+
11
+ string des = $"观看视频后\\n等待时间减少{iaaConfig.EffectValue / 60}分钟";
12
+ this.GetModel&lt;IAAModel&gt;().ShowAdWithDialog(IAAId.境界失败_加速冷却, "加速时间", des, null);
13
+ }
14
+ `;
15
+ console.log('Full test code:', testCode);
16
+ console.log('Index of "加速时间":', testCode.indexOf('"加速时间"'));
17
+ console.log('Index of "观看视频后":', testCode.indexOf('"观看视频后'));
18
+ console.log('\n--- Code around index 358 ---');
19
+ console.log(testCode.substring(350, 380));
20
+ console.log('\n--- Code around index 375 ---');
21
+ console.log(testCode.substring(360, 390));
package/dist/debug.js ADDED
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const CSharpStringExtractor_1 = require("./src/CSharpStringExtractor");
4
+ const extractor = new CSharpStringExtractor_1.CSharpStringExtractor();
5
+ const code = `infoStr += $"是否暴击: [b]{(_damageInfo.IsCritical ? "[color=#00B000]是[/color]" : "否")}[/b]\n";`;
6
+ console.log('Testing code:', code);
7
+ console.log('-------------------');
8
+ const snippets = extractor.extractStrings(code);
9
+ console.log('Number of snippets:', snippets.length);
10
+ if (snippets.length > 0) {
11
+ const snippet = snippets[0];
12
+ console.log('Original code:', snippet.originalCode);
13
+ console.log('Converted code:', snippet.convertedCode);
14
+ console.log('Literals:', snippet.literals);
15
+ }
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const CSharpStringExtractor_1 = require("./src/CSharpStringExtractor");
4
+ const extractor = new CSharpStringExtractor_1.CSharpStringExtractor();
5
+ const code = `
6
+ protected override void OnShow(object param)
7
+ {
8
+ // Log.Info("AAA:ChooseSutraLayer显示1");
9
+ base.OnShow(param);
10
+ }`;
11
+ console.log('=== code.indexOf("AAA..."):', code.indexOf(`"AAA:ChooseSutraLayer显示1"`));
12
+ console.log('\n=== Code with indices: ===');
13
+ let idx = 0;
14
+ while (idx < code.length) {
15
+ console.log(idx, code.charCodeAt(idx), JSON.stringify(code[idx]));
16
+ idx++;
17
+ }
18
+ console.log('\n=== Extracting snippets ===');
19
+ const snippets = extractor.extractStrings(code);
20
+ console.log('\n=== snippets.length:', snippets.length);
21
+ let snippetIdx = 0;
22
+ while (snippetIdx < snippets.length) {
23
+ console.log('\n--- snippet', snippetIdx, '---');
24
+ console.log('originalIndex:', snippets[snippetIdx].originalIndex);
25
+ console.log('originalCode:', JSON.stringify(snippets[snippetIdx].originalCode));
26
+ snippetIdx++;
27
+ }
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ const testCode = `infoStr += $"是否暴击: [b]{(_damageInfo.IsCritical ? "[color=#00B000]是[/color]" : "否")}[/b]\n";`;
3
+ console.log('Original code:', testCode);
4
+ console.log('\nLooking for interpolated string...');
5
+ let i = 0;
6
+ while (i < testCode.length - 1) {
7
+ let startIndex = -1;
8
+ let contentStart = -1;
9
+ if (i + 1 < testCode.length) {
10
+ console.log(`i=${i}, char1=${testCode[i]}, char2=${testCode[i + 1]}`);
11
+ if (testCode[i] === '$' && testCode[i + 1] === '"') {
12
+ console.log('→ Found $" at', i);
13
+ startIndex = i;
14
+ contentStart = i + 2;
15
+ }
16
+ }
17
+ if (startIndex !== -1 && contentStart !== -1) {
18
+ console.log('startIndex:', startIndex, 'contentStart:', contentStart);
19
+ let braceDepth = 0;
20
+ let inString = false;
21
+ let escapeNext = false;
22
+ let stringDelimiter = '';
23
+ let j = contentStart;
24
+ let endFound = false;
25
+ while (j < testCode.length) {
26
+ const char = testCode[j];
27
+ console.log(` j=${j}, char=${JSON.stringify(char)}, inString=${inString}, stringDelimiter=${JSON.stringify(stringDelimiter)}, braceDepth=${braceDepth}`);
28
+ if (escapeNext) {
29
+ escapeNext = false;
30
+ }
31
+ else if (char === '\\') {
32
+ escapeNext = true;
33
+ }
34
+ else if (inString) {
35
+ if (char === stringDelimiter) {
36
+ inString = false;
37
+ stringDelimiter = '';
38
+ }
39
+ }
40
+ else if (char === '"' || char === "'") {
41
+ inString = true;
42
+ stringDelimiter = char;
43
+ }
44
+ else if (!inString && char === '{') {
45
+ braceDepth++;
46
+ }
47
+ else if (!inString && char === '}') {
48
+ braceDepth--;
49
+ }
50
+ else if (!inString && char === '"' && braceDepth === 0) {
51
+ console.log('→ Found closing " at', j);
52
+ endFound = true;
53
+ break;
54
+ }
55
+ j++;
56
+ }
57
+ console.log('End loop');
58
+ break;
59
+ }
60
+ i++;
61
+ }