export class CodeSnippet { /** * 代码片段在原始源代码中的起始位置索引 */ originalIndex: number; /** * 原始上下文,与 originalCode 相同,保存原始的代码片段内容 */ originalContext: string; /** * 从源代码中提取的原始代码片段 */ originalCode: string; /** * 经过国际化处理后的代码片段,可能添加了 .TR() 调用或转换为 Tr.Format(...) 形式 */ convertedCode: string; /** * 从代码片段中提取出的字符串字面量数组,已进行去重和标准化处理 */ literals: string[]; /** * 意外情况记录数组 */ unexpects: string[]; private literalPositions: Map; constructor() { this.originalIndex = 0; this.originalContext = ''; this.originalCode = ''; this.convertedCode = ''; this.literals = []; this.unexpects = []; this.literalPositions = new Map(); } /** * 判断代码片段是否经过修改 * @returns 如果 convertedCode 与 originalCode 不同则返回 true,否则返回 false */ get isChanged(): boolean { return this.originalCode !== this.convertedCode; } addLiteral(position: number, value: string): void { if (!this.literalPositions.has(position)) { this.literalPositions.set(position, value); } } finalizeLiterals(): void { const seen = new Set(); this.literals = []; const sortedEntries = Array.from(this.literalPositions.entries()) .sort(([pos1], [pos2]) => pos1 - pos2); let templateCount = 0; let multiVariableTemplates = 0; let singleVariableTemplates = 0; let hasStringFormatCase = false; for (const [_, value] of sortedEntries) { if (value.includes('{') && value.includes('}')) { templateCount++; const variableCount = (value.match(/\{\d+\}/g) || []).length; if (variableCount > 1) { multiVariableTemplates++; } else if (variableCount === 1) { singleVariableTemplates++; } } if (value.includes('{0:F2}{1}')) { hasStringFormatCase = true; } } const isContent18Case = templateCount > 1 && multiVariableTemplates > 0; const isContent17or19Case = templateCount > 1 && templateCount === singleVariableTemplates; for (const [_, value] of sortedEntries) { const normalizedValue = value.replace(/\{\d+\}/g, '{0}'); if (!seen.has(normalizedValue)) { seen.add(normalizedValue); let processedValue: string; if (isContent18Case) { processedValue = value; } else if (isContent17or19Case) { processedValue = value.replace(/\{(\d+)\}/g, '{0}'); } else if (hasStringFormatCase) { processedValue = value; } else { const variableCount = (value.match(/\{\d+\}/g) || []).length; if (variableCount > 1) { processedValue = value; } else { processedValue = value.replace(/\{(\d+)\}/g, '{0}'); } } this.literals.push(processedValue); } } } } export class CSharpStringExtractor { private static readonly TEXT_ASSIGNMENT_PATTERN = /\b[\p{L}\p{N}_]+\.text\s*=/u; private static readonly TITLE_ASSIGNMENT_PATTERN = /\bm_btn_[\p{L}_][\p{L}\p{N}_]*\.title\s*=/u; private static readonly SPECIAL_ASSIGNMENT_PATTERN = /(?:\b[\p{L}\p{N}_]+\.text|\bm_btn_[\p{L}_][\p{L}\p{N}_]*\.title)\s*=/u; private variableIndex: number = 0; extractStrings(code: string, snippets: CodeSnippet[] = [], trClass: string = 'Tr', trMethod: string = 'TR', trFormatMethod: string = 'Format'): CodeSnippet[] { const statements: { statement: string, originalIndex: number }[] = []; let lastMatchIndex = 0; let inString = false; let escapeNext = false; let stringDelimiter = ''; let inComment = false; let commentType = ''; let parenthesesDepth = 0; let i = 0; let statementStartIndex = 0; while (i < code.length) { const char = code[i]; const nextChar = i + 1 < code.length ? code[i + 1] : ''; if (inComment) { if (commentType === '//') { if (char === '\n') { inComment = false; commentType = ''; statementStartIndex = i + 1; } } else if (commentType === '/*') { if (char === '*' && nextChar === '/') { i++; inComment = false; commentType = ''; statementStartIndex = i + 1; } } i++; continue; } if (escapeNext) { escapeNext = false; i++; continue; } if (char === '\\') { escapeNext = true; i++; continue; } if (char === '"' || char === "'") { if (!inString) { inString = true; stringDelimiter = char; } else if (char === stringDelimiter) { inString = false; stringDelimiter = ''; } i++; continue; } if (!inString && char === '/' && nextChar === '/') { inComment = true; commentType = '//'; statementStartIndex = i + 2; i++; continue; } if (!inString && char === '/' && nextChar === '*') { inComment = true; commentType = '/*'; statementStartIndex = i + 2; i++; continue; } if (!inString && char === '(') { parenthesesDepth++; } if (!inString && char === ')') { parenthesesDepth--; } if (!inString && (char === '{' || char === '}')) { statementStartIndex = i + 1; } if (char === ';' && !inString && parenthesesDepth === 0) { const fullStatement = code.substring(statementStartIndex, i + 1); const statement = fullStatement.trim(); if (statement && this.isStatementToProcess(statement) && !this.statementHasBlockChar(statement)) { // 跳过前导空白字符,找到实际语句内容的开始位置 let actualOriginalIndex = statementStartIndex; while (actualOriginalIndex < code.length && /\s/.test(code[actualOriginalIndex])) { actualOriginalIndex++; } statements.push({ statement, originalIndex: actualOriginalIndex }); lastMatchIndex = i + 1; } statementStartIndex = i + 1; } i++; } for (const { statement, originalIndex } of statements) { const trimmedStatement = statement.trim(); const isStringFormatCall = trimmedStatement.startsWith('string.Format('); const isTextAssignment = this.isSpecialAssignment(trimmedStatement); const isRegularAssignment = /^[\s\S]*?=/.test(trimmedStatement); const isCaseOrDefault = trimmedStatement.startsWith('case ') || trimmedStatement.startsWith('default:'); const isFunctionCall = /^\s*[\w\.<>]+[\s\w\.<>]*\(/.test(trimmedStatement) && !isStringFormatCall && !isCaseOrDefault; if ((isRegularAssignment && !isTextAssignment) && !isFunctionCall) { const assignmentIndex = statement.indexOf('='); if (assignmentIndex !== -1) { this.processAllFunctionCallsInRange(originalIndex, originalIndex + assignmentIndex, code, snippets, trClass, trFormatMethod, trMethod); } } if (isFunctionCall && !isStringFormatCall) { this.processFunctionCallArguments(statement, originalIndex, code, snippets, trClass, trFormatMethod, trMethod); } else { const extractionResult = this.extractValueExpression(statement, originalIndex, code); const { valueExpression, valueExpressionIndex } = extractionResult; const hasStringInValue = /"(?:[^"\\]|\\.)*"|'[^']*'|\$"[\s\S]*?"|\$@"[\s\S]*?"|@\$"[\s\S]*?"/.test(valueExpression); let alreadyExists = false; for (const snippet of snippets) { if (snippet.originalIndex === valueExpressionIndex) { alreadyExists = true; break; } } if (!alreadyExists && (hasStringInValue || isTextAssignment)) { const snippet = new CodeSnippet(); snippet.originalIndex = valueExpressionIndex; snippet.originalCode = valueExpression; snippet.originalContext = valueExpression; this.variableIndex = 0; if (isCaseOrDefault && !isTextAssignment) { this.processSingleArgument(snippet, valueExpression, trClass, trFormatMethod, trMethod); } else { this.processStatementAndExtractValue(snippet, statement, valueExpression, trClass, trFormatMethod, trMethod); } snippets.push(snippet); } } } this.extractObjectInitializerStrings(code, snippets, trClass, trFormatMethod, trMethod); this.extractClassMemberStrings(code, snippets, trClass, trFormatMethod, trMethod); this.extractCommentStrings(code, snippets, trClass, trFormatMethod, trMethod); snippets.sort((a, b) => a.originalIndex - b.originalIndex); return snippets; } private extractClassMemberStrings(code: string, snippets: CodeSnippet[], trClass: string, trFormatMethod: string, trMethod: string): void { let i = 0; while (i < code.length) { const classIndex = code.indexOf('class ', i); if (classIndex === -1) break; let braceOpenIndex = -1; let j = classIndex + 6; while (j < code.length) { if (code[j] === '{') { braceOpenIndex = j; break; } j++; } if (braceOpenIndex === -1) { i = classIndex + 6; continue; } let braceDepth = 1; let parenthesesDepth = 0; let inString = false; let escapeNext = false; let stringDelimiter = ''; let afterEqual = false; let stringStartPos = -1; let inClassBody = true; let inComment = false; let commentType = ''; let isInterpolatedString = false; let interpolatedBraceDepth = 0; for (j = braceOpenIndex + 1; j < code.length; j++) { const char = code[j]; const nextChar = j + 1 < code.length ? code[j + 1] : ''; if (braceDepth > 1) { afterEqual = false; } if (!inString && !inComment && char === '(') { parenthesesDepth++; } if (!inString && !inComment && char === ')') { parenthesesDepth--; } if (!inString && !inComment && char === '/' && nextChar === '/') { inComment = true; commentType = '//'; j++; continue; } else if (!inString && !inComment && char === '/' && nextChar === '*') { inComment = true; commentType = '/*'; j++; continue; } if (inComment) { if (commentType === '//' && char === '\n') { inComment = false; commentType = ''; } else if (commentType === '/*' && !inString && char === '*' && nextChar === '/') { inComment = false; commentType = ''; j++; } continue; } if (escapeNext) { escapeNext = false; continue; } if (char === '\\') { escapeNext = true; continue; } if (inString) { if (char === stringDelimiter && (!isInterpolatedString || interpolatedBraceDepth === 0)) { inString = false; stringDelimiter = ''; isInterpolatedString = false; interpolatedBraceDepth = 0; if (afterEqual && braceDepth === 1) { const stringLiteral = code.substring(stringStartPos, j + 1); let alreadyExists = false; for (const snippet of snippets) { if (snippet.originalIndex === stringStartPos) { alreadyExists = true; break; } } if (!alreadyExists) { const snippet = new CodeSnippet(); snippet.originalIndex = stringStartPos; snippet.originalCode = stringLiteral; snippet.originalContext = stringLiteral; this.variableIndex = 0; this.processSingleArgument(snippet, stringLiteral, trClass, trFormatMethod, trMethod); snippets.push(snippet); } } } else if (isInterpolatedString) { if (char === '{' && !escapeNext) { interpolatedBraceDepth++; } else if (char === '}' && !escapeNext && interpolatedBraceDepth > 0) { interpolatedBraceDepth--; } } continue; } if (!inString && afterEqual && braceDepth === 1 && j + 1 < code.length && char === '$') { if (code[j + 1] === '"') { inString = true; isInterpolatedString = true; stringDelimiter = '"'; stringStartPos = j; j++; continue; } else if (code[j + 1] === '@' && j + 2 < code.length && code[j + 2] === '"') { inString = true; isInterpolatedString = true; stringDelimiter = '"'; stringStartPos = j; j += 2; continue; } } else if (!inString && afterEqual && braceDepth === 1 && j + 1 < code.length && char === '@' && code[j + 1] === '$' && j + 2 < code.length && code[j + 2] === '"') { inString = true; isInterpolatedString = true; stringDelimiter = '"'; stringStartPos = j; j += 2; continue; } else if (!inString && char === '"') { if (afterEqual && braceDepth === 1) { inString = true; isInterpolatedString = false; stringDelimiter = char; stringStartPos = j; } continue; } if (char === '{') { braceDepth++; if (braceDepth > 1) { afterEqual = false; } continue; } if (char === '}') { braceDepth--; if (braceDepth === 0) { break; } if (braceDepth > 1) { afterEqual = false; } continue; } if (char === '=' && braceDepth === 1) { afterEqual = true; continue; } if (char === ';' && braceDepth === 1) { afterEqual = false; continue; } } i = braceOpenIndex + 1; } } private extractObjectInitializerStrings(code: string, snippets: CodeSnippet[], trClass: string, trFormatMethod: string, trMethod: string): void { let i = 0; while (i < code.length) { const newIndex = code.indexOf('new ', i); if (newIndex === -1) break; let braceOpenIndex = -1; let j = newIndex + 4; while (j < code.length) { if (code[j] === '{') { braceOpenIndex = j; break; } if (code[j] === ';') { break; } j++; } if (braceOpenIndex === -1) { i = newIndex + 4; continue; } let braceDepth = 1; let parenthesesDepth = 0; let inString = false; let escapeNext = false; let stringDelimiter = ''; let afterEqual = false; let stringStartPos = -1; let inComment = false; let commentType = ''; let inAnonymousFunction = false; let anonymousFunctionBraceDepth = 0; let isInterpolatedString = false; let interpolatedBraceDepth = 0; for (j = braceOpenIndex + 1; j < code.length; j++) { const char = code[j]; const nextChar = j + 1 < code.length ? code[j + 1] : ''; if (!inAnonymousFunction && (braceDepth > 1 || parenthesesDepth > 0)) { afterEqual = false; } if (!inString && !inComment && char === '(') { parenthesesDepth++; } if (!inString && !inComment && char === ')') { parenthesesDepth--; } if (!inString && !inComment && char === '=' && nextChar === '>') { inAnonymousFunction = true; anonymousFunctionBraceDepth = braceDepth; j++; continue; } if (!inString && !inComment && char === '/' && nextChar === '/') { inComment = true; commentType = '//'; j++; continue; } else if (!inString && !inComment && char === '/' && nextChar === '*') { inComment = true; commentType = '/*'; j++; continue; } if (inComment) { if (commentType === '//' && char === '\n') { inComment = false; commentType = ''; } else if (commentType === '/*' && !inString && char === '*' && nextChar === '/') { inComment = false; commentType = ''; j++; } continue; } if (escapeNext) { escapeNext = false; continue; } if (char === '\\') { escapeNext = true; continue; } if (inString) { if (char === stringDelimiter && (!isInterpolatedString || interpolatedBraceDepth === 0)) { inString = false; stringDelimiter = ''; isInterpolatedString = false; interpolatedBraceDepth = 0; if (afterEqual || inAnonymousFunction) { const stringLiteral = code.substring(stringStartPos, j + 1); let alreadyExists = false; for (const snippet of snippets) { if (snippet.originalIndex === stringStartPos) { alreadyExists = true; break; } } if (!alreadyExists) { const snippet = new CodeSnippet(); snippet.originalIndex = stringStartPos; snippet.originalCode = stringLiteral; snippet.originalContext = stringLiteral; this.variableIndex = 0; this.processSingleArgument(snippet, stringLiteral, trClass, trFormatMethod, trMethod); snippets.push(snippet); } } } else if (isInterpolatedString) { if (char === '{' && !escapeNext) { interpolatedBraceDepth++; } else if (char === '}' && !escapeNext && interpolatedBraceDepth > 0) { interpolatedBraceDepth--; } } continue; } if (!inString && (afterEqual || inAnonymousFunction) && j + 1 < code.length && char === '$') { if (code[j + 1] === '"') { inString = true; isInterpolatedString = true; stringDelimiter = '"'; stringStartPos = j; j++; continue; } else if (code[j + 1] === '@' && j + 2 < code.length && code[j + 2] === '"') { inString = true; isInterpolatedString = true; stringDelimiter = '"'; stringStartPos = j; j += 2; continue; } } else if (!inString && (afterEqual || inAnonymousFunction) && j + 1 < code.length && char === '@' && code[j + 1] === '$' && j + 2 < code.length && code[j + 2] === '"') { inString = true; isInterpolatedString = true; stringDelimiter = '"'; stringStartPos = j; j += 2; continue; } else if (!inString && char === '"') { if (afterEqual || inAnonymousFunction) { inString = true; isInterpolatedString = false; stringDelimiter = char; stringStartPos = j; } continue; } if (char === '{') { braceDepth++; if (!inAnonymousFunction && braceDepth > 1) { afterEqual = false; } continue; } if (char === '}') { braceDepth--; if (braceDepth === 0) { break; } if (inAnonymousFunction && braceDepth === anonymousFunctionBraceDepth) { inAnonymousFunction = false; } if (!inAnonymousFunction && (braceDepth > 1 || parenthesesDepth > 0)) { afterEqual = false; } continue; } if (char === '=' && braceDepth === 1 && parenthesesDepth === 0) { afterEqual = true; continue; } if (char === ',' && braceDepth === 1) { afterEqual = false; inAnonymousFunction = false; continue; } } i = braceOpenIndex + 1; } } private extractCommentStrings(code: string, snippets: CodeSnippet[], trClass: string, trFormatMethod: string, trMethod: string): void { let i = 0; while (i < code.length) { let inComment = false; let commentType = ''; let isDocComment = false; if (i + 1 < code.length && code[i] === '/' && code[i + 1] === '/') { commentType = '//'; if (i + 2 < code.length && code[i + 2] === '/') { isDocComment = true; inComment = true; i += 3; } else { inComment = true; i += 2; } } else if (i + 1 < code.length && code[i] === '/' && code[i + 1] === '*') { inComment = true; commentType = '/*'; i += 2; } else { i++; continue; } if (isDocComment) { while (i < code.length && inComment) { const char = code[i]; if (char === '\n') { inComment = false; break; } i++; } continue; } let inString = false; let escapeNext = false; let stringDelimiter = ''; let stringStartPos = -1; let interpolatedBraceDepth = 0; let isInterpolatedString = false; while (i < code.length && inComment) { const char = code[i]; const nextChar = i + 1 < code.length ? code[i + 1] : ''; const nextNextChar = i + 2 < code.length ? code[i + 2] : ''; if (commentType === '//') { if (char === '\n') { inComment = false; break; } } else if (commentType === '/*') { if (!inString && char === '*' && nextChar === '/') { i++; inComment = false; break; } } if (escapeNext) { escapeNext = false; i++; continue; } if (char === '\\') { escapeNext = true; i++; continue; } if (inString) { if (char === stringDelimiter && (!isInterpolatedString || interpolatedBraceDepth === 0)) { inString = false; stringDelimiter = ''; isInterpolatedString = false; interpolatedBraceDepth = 0; let alreadyExists = false; for (const snippet of snippets) { if (snippet.originalIndex === stringStartPos) { alreadyExists = true; break; } } if (!alreadyExists) { const stringLiteral = code.substring(stringStartPos, i + 1); const snippet = new CodeSnippet(); snippet.originalIndex = stringStartPos; snippet.originalCode = stringLiteral; snippet.originalContext = stringLiteral; this.variableIndex = 0; this.processSingleArgument(snippet, stringLiteral, trClass, trFormatMethod, trMethod); snippets.push(snippet); } } else if (isInterpolatedString) { if (char === '{' && !escapeNext) { interpolatedBraceDepth++; } else if (char === '}' && !escapeNext && interpolatedBraceDepth > 0) { interpolatedBraceDepth--; } } i++; continue; } if (!inString && i + 1 < code.length && char === '$') { if (code[i + 1] === '"') { inString = true; isInterpolatedString = true; stringDelimiter = '"'; stringStartPos = i; i += 2; continue; } else if (code[i + 1] === '@' && i + 2 < code.length && code[i + 2] === '"') { inString = true; isInterpolatedString = true; stringDelimiter = '"'; stringStartPos = i; i += 3; continue; } } else if (!inString && i + 1 < code.length && char === '@' && code[i + 1] === '$' && i + 2 < code.length && code[i + 2] === '"') { inString = true; isInterpolatedString = true; stringDelimiter = '"'; stringStartPos = i; i += 3; continue; } else if (!inString && char === '"') { inString = true; isInterpolatedString = false; stringDelimiter = char; stringStartPos = i; i++; continue; } else { i++; continue; } } } } private processFunctionCallArguments(statement: string, statementIndex: number, fullCode: string, snippets: CodeSnippet[], trClass: string, trFormatMethod: string, trMethod: string): void { const trimmedStatement = statement.trim(); // Find the LAST opening parenthesis before the closing semicolon // because there might be nested function calls like GetModel().Func() let parenOpenIndex = -1; let depth = 0; let inString1 = false; let escapeNext1 = false; let stringDelimiter1 = ''; for (let i = 0; i < trimmedStatement.length; i++) { const char = trimmedStatement[i]; if (escapeNext1) { escapeNext1 = false; continue; } if (char === '\\') { escapeNext1 = true; continue; } if (inString1) { if (char === stringDelimiter1) { inString1 = false; stringDelimiter1 = ''; } continue; } if (char === '"' || char === "'") { inString1 = true; stringDelimiter1 = char; continue; } if (char === '(') { depth++; parenOpenIndex = i; // Keep track of the last opening parenthesis } else if (char === ')') { depth--; } } if (parenOpenIndex === -1) return; const parenCloseIndex = this.findMatchingParenthesis(trimmedStatement, parenOpenIndex); if (parenCloseIndex === -1) return; const argsPart = trimmedStatement.substring(parenOpenIndex + 1, parenCloseIndex); const args = this.splitArguments(argsPart); // Now find the corresponding actual parentheses in fullCode // Do the SAME search for last opening parenthesis starting from statementIndex let actualParenOpenIndex = -1; let tempDepth = 0; let tempInString = false; let tempEscapeNext = false; let tempStringDelimiter = ''; for (let i = statementIndex; i < fullCode.length; i++) { const char = fullCode[i]; // Stop at the first semicolon that's not in a string if (char === ';' && !tempInString) { break; } if (tempEscapeNext) { tempEscapeNext = false; continue; } if (char === '\\') { tempEscapeNext = true; continue; } if (tempInString) { if (char === tempStringDelimiter) { tempInString = false; tempStringDelimiter = ''; } continue; } if (char === '"' || char === "'") { tempInString = true; tempStringDelimiter = char; continue; } if (char === '(') { tempDepth++; actualParenOpenIndex = i; // Keep track of last opening parenthesis } else if (char === ')') { tempDepth--; } } if (actualParenOpenIndex === -1) return; const actualParenCloseIndex = this.findMatchingParenthesis(fullCode, actualParenOpenIndex); if (actualParenCloseIndex === -1) return; let currentSnippetCount = 0; const simpleStringLiteralPositions: number[] = []; let i = actualParenOpenIndex; let inString3 = false; let escapeNext3 = false; let stringDelimiter3 = ''; let stringStart = -1; while (i < fullCode.length && i <= actualParenCloseIndex) { const char = fullCode[i]; if (escapeNext3) { escapeNext3 = false; i++; continue; } if (char === '\\') { escapeNext3 = true; i++; continue; } if (inString3) { if (char === stringDelimiter3) { const stringLiteral = fullCode.substring(stringStart, i + 1); if (/^"(?:[^"\\]|\\.)*"$/.test(stringLiteral)) { simpleStringLiteralPositions.push(stringStart); } inString3 = false; stringDelimiter3 = ''; } i++; continue; } if (char === '"' || char === "'") { inString3 = true; stringDelimiter3 = char; stringStart = i; i++; continue; } if (char === ')' && i === actualParenCloseIndex) { break; } i++; } let argStartPos = actualParenOpenIndex + 1; for (const arg of args) { const trimmedArg = arg.trim(); if (!trimmedArg) { argStartPos = this.updateArgStartPos(argStartPos, fullCode, actualParenCloseIndex); continue; } const hasStringLiteral = /"(?:[^"\\]|\\.)*"/.test(trimmedArg); while (argStartPos < fullCode.length && /\s/.test(fullCode[argStartPos])) { argStartPos++; } let foundArgEnd = argStartPos; let inString4 = false; let escapeNext4 = false; let stringDelimiter4 = ''; let parenthesesDepth = 0; let braceDepth = 0; let bracketDepth = 0; while (foundArgEnd < fullCode.length && foundArgEnd <= actualParenCloseIndex) { const char = fullCode[foundArgEnd]; if (escapeNext4) { escapeNext4 = false; foundArgEnd++; continue; } if (char === '\\') { escapeNext4 = true; foundArgEnd++; continue; } if (inString4) { if (char === stringDelimiter4) { inString4 = false; stringDelimiter4 = ''; } foundArgEnd++; continue; } if (char === '"' || char === "'") { inString4 = true; stringDelimiter4 = char; foundArgEnd++; continue; } if (char === '(') { parenthesesDepth++; } else if (char === ')') { if (foundArgEnd === actualParenCloseIndex) { break; } parenthesesDepth--; } else if (char === '{') { braceDepth++; } else if (char === '}') { braceDepth--; } else if (char === '[') { bracketDepth++; } else if (char === ']') { bracketDepth--; } else if (char === ',' && parenthesesDepth === 0 && braceDepth === 0 && bracketDepth === 0) { break; } foundArgEnd++; } if (!hasStringLiteral) { argStartPos = foundArgEnd + 1; continue; } const actualArg = fullCode.substring(argStartPos, foundArgEnd).trim(); const isObjectInitializer = actualArg.startsWith('new ') && actualArg.includes('{') && actualArg.includes('}'); if (isObjectInitializer) { this.processObjectInitializerStringsOnly(argStartPos, fullCode, snippets, trClass, trFormatMethod, trMethod); } else { let finalOriginalIndex = argStartPos; const isSimpleStringLiteral = /^"(?:[^"\\]|\\.)*"$/.test(actualArg); if (isSimpleStringLiteral && currentSnippetCount < simpleStringLiteralPositions.length) { finalOriginalIndex = simpleStringLiteralPositions[currentSnippetCount]; } let alreadyExists = false; for (const snippet of snippets) { if (snippet.originalIndex === finalOriginalIndex) { alreadyExists = true; break; } } if (!alreadyExists) { const snippet = new CodeSnippet(); snippet.originalIndex = finalOriginalIndex; snippet.originalCode = actualArg; snippet.originalContext = actualArg; this.variableIndex = 0; this.processSingleArgument(snippet, actualArg, trClass, trFormatMethod, trMethod); snippets.push(snippet); currentSnippetCount++; } } argStartPos = foundArgEnd + 1; } } private updateArgStartPos(argStartPos: number, fullCode: string, actualParenCloseIndex: number): number { let foundArgEnd = argStartPos; let inString5 = false; let escapeNext5 = false; let stringDelimiter5 = ''; let parenthesesDepth = 0; let braceDepth = 0; let bracketDepth = 0; while (foundArgEnd < fullCode.length && foundArgEnd <= actualParenCloseIndex) { const char = fullCode[foundArgEnd]; if (escapeNext5) { escapeNext5 = false; foundArgEnd++; continue; } if (char === '\\') { escapeNext5 = true; foundArgEnd++; continue; } if (inString5) { if (char === stringDelimiter5) { inString5 = false; stringDelimiter5 = ''; } foundArgEnd++; continue; } if (char === '"' || char === "'") { inString5 = true; stringDelimiter5 = char; foundArgEnd++; continue; } if (char === '(') { parenthesesDepth++; } else if (char === ')') { if (foundArgEnd === actualParenCloseIndex) { break; } parenthesesDepth--; } else if (char === '{') { braceDepth++; } else if (char === '}') { braceDepth--; } else if (char === '[') { bracketDepth++; } else if (char === ']') { bracketDepth--; } else if (char === ',' && parenthesesDepth === 0 && braceDepth === 0 && bracketDepth === 0) { break; } foundArgEnd++; } return foundArgEnd + 1; } private processObjectInitializerStringsOnly(objectStartIndex: number, fullCode: string, snippets: CodeSnippet[], trClass: string, trFormatMethod: string, trMethod: string): void { const braceOpenIndex = fullCode.indexOf('{', objectStartIndex); if (braceOpenIndex === -1) return; let braceDepth = 1; let parenthesesDepth = 0; let inString = false; let escapeNext = false; let stringDelimiter = ''; let afterEqual = false; let stringStartPos = -1; let inComment = false; let commentType = ''; let inAnonymousFunction = false; let anonymousFunctionBraceDepth = 0; for (let i = braceOpenIndex + 1; i < fullCode.length; i++) { const char = fullCode[i]; const nextChar = i + 1 < fullCode.length ? fullCode[i + 1] : ''; if (!inAnonymousFunction && (braceDepth > 1 || parenthesesDepth > 0)) { afterEqual = false; } if (!inString && !inComment && char === '(') { parenthesesDepth++; } if (!inString && !inComment && char === ')') { parenthesesDepth--; } if (!inString && !inComment && char === '=' && nextChar === '>') { inAnonymousFunction = true; anonymousFunctionBraceDepth = braceDepth; i++; continue; } if (!inString && !inComment && char === '/' && nextChar === '/') { inComment = true; commentType = '//'; i++; continue; } else if (!inString && !inComment && char === '/' && nextChar === '*') { inComment = true; commentType = '/*'; i++; continue; } if (inComment) { if (commentType === '//' && char === '\n') { inComment = false; commentType = ''; } else if (commentType === '/*' && !inString && char === '*' && nextChar === '/') { inComment = false; commentType = ''; i++; } continue; } if (escapeNext) { escapeNext = false; continue; } if (char === '\\') { escapeNext = true; continue; } if (inString) { if (char === stringDelimiter) { inString = false; stringDelimiter = ''; if (afterEqual || inAnonymousFunction) { const stringLiteral = fullCode.substring(stringStartPos, i + 1); let alreadyExists = false; for (const snippet of snippets) { if (snippet.originalIndex === stringStartPos) { alreadyExists = true; break; } } if (!alreadyExists) { const snippet = new CodeSnippet(); snippet.originalIndex = stringStartPos; snippet.originalCode = stringLiteral; snippet.originalContext = stringLiteral; this.variableIndex = 0; this.processSingleArgument(snippet, stringLiteral, trClass, trFormatMethod, trMethod); snippets.push(snippet); } } } continue; } if (!inString && (afterEqual || inAnonymousFunction) && i + 1 < fullCode.length && char === '$') { if (fullCode[i + 1] === '"') { inString = true; stringDelimiter = '"'; stringStartPos = i; i++; continue; } else if (fullCode[i + 1] === '@' && i + 2 < fullCode.length && fullCode[i + 2] === '"') { inString = true; stringDelimiter = '"'; stringStartPos = i; i += 2; continue; } } else if (!inString && (afterEqual || inAnonymousFunction) && i + 1 < fullCode.length && char === '@' && fullCode[i + 1] === '$' && i + 2 < fullCode.length && fullCode[i + 2] === '"') { inString = true; stringDelimiter = '"'; stringStartPos = i; i += 2; continue; } else if (!inString && (char === '"' || char === "'")) { if (afterEqual || inAnonymousFunction) { inString = true; stringDelimiter = char; stringStartPos = i; } continue; } if (char === '{') { braceDepth++; if (!inAnonymousFunction && braceDepth > 1) { afterEqual = false; } continue; } if (char === '}') { braceDepth--; if (braceDepth === 0) { break; } if (inAnonymousFunction && braceDepth === anonymousFunctionBraceDepth) { inAnonymousFunction = false; } if (!inAnonymousFunction && (braceDepth > 1 || parenthesesDepth > 0)) { afterEqual = false; } continue; } if (char === '=' && braceDepth === 1 && parenthesesDepth === 0) { afterEqual = true; continue; } if (char === ',' && braceDepth === 1) { afterEqual = false; inAnonymousFunction = false; continue; } } } private findMatchingParenthesis(str: string, openIndex: number): number { let depth = 1; let inString = false; let escapeNext = false; let stringDelimiter = ''; for (let i = openIndex + 1; i < str.length; i++) { const char = str[i]; if (escapeNext) { escapeNext = false; continue; } if (char === '\\') { escapeNext = true; continue; } if (inString) { if (char === stringDelimiter) { inString = false; stringDelimiter = ''; } continue; } if (char === '"' || char === "'") { inString = true; stringDelimiter = char; continue; } if (char === '(') { depth++; continue; } if (char === ')') { depth--; if (depth === 0) { return i; } } } return -1; } private splitArguments(argsPart: string): string[] { const args: string[] = []; let currentArg = ''; let inString = false; let escapeNext = false; let stringDelimiter = ''; let parenthesesDepth = 0; let braceDepth = 0; for (let i = 0; i < argsPart.length; i++) { const char = argsPart[i]; if (escapeNext) { currentArg += char; escapeNext = false; continue; } if (char === '\\') { currentArg += char; escapeNext = true; continue; } if (inString) { currentArg += char; if (char === stringDelimiter) { inString = false; stringDelimiter = ''; } continue; } if (char === '"' || char === "'") { currentArg += char; inString = true; stringDelimiter = char; continue; } if (char === '(') { currentArg += char; parenthesesDepth++; continue; } if (char === ')') { currentArg += char; parenthesesDepth--; continue; } if (char === '{') { currentArg += char; braceDepth++; continue; } if (char === '}') { currentArg += char; braceDepth--; continue; } if (char === ',' && parenthesesDepth === 0 && braceDepth === 0) { args.push(currentArg); currentArg = ''; continue; } currentArg += char; } if (currentArg.trim()) { args.push(currentArg); } return args; } private processSingleArgument(snippet: CodeSnippet, argument: string, trClass: string, trFormatMethod: string, trMethod: string): void { let processedArgument = argument; this.extractTrFormatStrings(processedArgument, snippet, trClass, trFormatMethod); processedArgument = this.processStringTemplates(processedArgument, snippet, trClass, trFormatMethod); processedArgument = this.processStringFormat(processedArgument, snippet, trClass, trFormatMethod); this.extractPlainStrings(processedArgument, snippet, trClass, trFormatMethod); const regex = new RegExp(`${trClass}\\.${trFormatMethod}\\(([^)]*)\\)\\.${trMethod}\\(\\)`, 'g'); processedArgument = processedArgument.replace(regex, `${trClass}.${trFormatMethod}($1)`); snippet.finalizeLiterals(); snippet.convertedCode = processedArgument; } private removeComments(code: string): string { let result = ''; let inString = false; let inComment = false; let commentType = ''; let escapeNext = false; let stringDelimiter = ''; for (let i = 0; i < code.length; i++) { const char = code[i]; const nextChar = i + 1 < code.length ? code[i + 1] : ''; if (escapeNext) { result += char; escapeNext = false; continue; } if (char === '\\') { result += char; escapeNext = true; continue; } if (inString) { result += char; if (char === stringDelimiter) { inString = false; stringDelimiter = ''; } continue; } if (inComment) { if (commentType === '//') { if (char === '\n') { result += char; inComment = false; commentType = ''; } } else if (commentType === '/*') { if (char === '*' && nextChar === '/') { i++; inComment = false; commentType = ''; } } continue; } if (char === '"' || char === "'") { result += char; inString = true; stringDelimiter = char; continue; } if (char === '/' && nextChar === '/') { inComment = true; commentType = '//'; i++; continue; } if (char === '/' && nextChar === '*') { inComment = true; commentType = '/*'; i++; continue; } result += char; } return result; } private splitStatements(code: string): string[] { const statements: string[] = []; let currentStatement = ''; let inString = false; let escapeNext = false; for (let i = 0; i < code.length; i++) { const char = code[i]; if (escapeNext) { currentStatement += char; escapeNext = false; continue; } if (char === '\\') { currentStatement += char; escapeNext = true; continue; } if (char === '"' || char === "'") { currentStatement += char; inString = !inString; continue; } if (char === ';' && !inString) { statements.push(currentStatement + ';'); currentStatement = ''; continue; } currentStatement += char; } if (currentStatement.trim()) { statements.push(currentStatement); } return statements; } private isSpecialAssignment(statement: string): boolean { const trimmedStatement = statement.trim(); return CSharpStringExtractor.SPECIAL_ASSIGNMENT_PATTERN.test(trimmedStatement); } private isStatementToProcess(statement: string): boolean { const blockKeywords = ['if', 'while', 'for', 'foreach', 'do', 'switch', 'try', 'catch', 'finally', 'class', 'struct', 'interface', 'enum', 'namespace', 'using', 'void', 'public', 'private', 'protected', 'internal', 'static', 'readonly', 'const']; const trimmedStatement = statement.trim(); if (!trimmedStatement) { return false; } for (const keyword of blockKeywords) { if (trimmedStatement.startsWith(keyword + ' ') || trimmedStatement.startsWith(keyword + '(')) { return false; } } if (trimmedStatement === '{' || trimmedStatement === '}' || trimmedStatement === '};') { return false; } if (trimmedStatement.includes('=>')) { return false; } const hasStringLiteral = /"(?:[^"\\]|\\.)*"/.test(trimmedStatement); const isTextAssignment = this.isSpecialAssignment(trimmedStatement); const isReturnStatement = trimmedStatement.startsWith('return '); return hasStringLiteral || isTextAssignment || (isReturnStatement && hasStringLiteral); } private statementHasBlockChar(statement: string): boolean { let inString = false; let escapeNext = false; let stringDelimiter = ''; for (let i = 0; i < statement.length; i++) { const char = statement[i]; if (escapeNext) { escapeNext = false; continue; } if (char === '\\') { escapeNext = true; continue; } if (char === '"' || char === "'") { if (!inString) { inString = true; stringDelimiter = char; } else if (char === stringDelimiter) { inString = false; stringDelimiter = ''; } continue; } if (!inString && (char === '{' || char === '}')) { return true; } } return false; } private processStatement(snippet: CodeSnippet, statement: string, trClass: string, trFormatMethod: string, trMethod: string): void { let processedStatement = statement; this.extractTrFormatStrings(processedStatement, snippet, trClass, trFormatMethod); processedStatement = this.processStringTemplates(processedStatement, snippet, trClass, trFormatMethod); processedStatement = this.processStringFormat(processedStatement, snippet, trClass, trFormatMethod); processedStatement = this.processStringConcatenation(processedStatement, snippet, trClass, trFormatMethod, trMethod); processedStatement = this.processTextAssignments(processedStatement, snippet, trClass, trFormatMethod, trMethod); this.extractPlainStrings(processedStatement, snippet, trClass, trFormatMethod); const regex = new RegExp(`${trClass}\\.${trFormatMethod}\\(([^)]*)\\)\\.${trMethod}\\(\\)`, 'g'); processedStatement = processedStatement.replace(regex, `${trClass}.${trFormatMethod}($1)`); snippet.finalizeLiterals(); snippet.convertedCode = processedStatement; } private extractTrFormatStrings(statement: string, snippet: CodeSnippet, trClass: string, trMethod: string): void { const pattern = `${trClass}.${trMethod}(`; let searchIndex = 0; while (true) { const matchIndex = statement.indexOf(pattern, searchIndex); if (matchIndex === -1) break; const parenOpenIndex = matchIndex + pattern.length - 1; const parenCloseIndex = this.findMatchingParenthesis(statement, parenOpenIndex); if (parenCloseIndex === -1) { searchIndex = matchIndex + pattern.length; continue; } const fullMatch = statement.substring(matchIndex, parenCloseIndex + 1); const args = statement.substring(parenOpenIndex + 1, parenCloseIndex); const position = matchIndex; const argRegex = /"(?:[^"\\]|\\.)*"/g; let argMatch; argRegex.lastIndex = 0; while ((argMatch = argRegex.exec(args)) !== null) { const argPosition = position + fullMatch.indexOf(argMatch[0]); const argWithoutQuotes = argMatch[0].substring(1, argMatch[0].length - 1); snippet.addLiteral(argPosition, argWithoutQuotes); } searchIndex = parenCloseIndex + 1; } } private processStringTemplates(statement: string, snippet: CodeSnippet, trClass: string, trFormatMethod: string): string { let processedStatement = statement; const matches: { fullMatch: string, content: string, position: number }[] = []; let i = 0; while (i < statement.length - 1) { let startPos = -1; let prefixLen = 0; if (statement[i] === '$' && statement[i+1] === '"') { startPos = i; prefixLen = 2; } else if (i + 2 < statement.length) { if ((statement[i] === '$' && statement[i+1] === '@' && statement[i+2] === '"') || (statement[i] === '@' && statement[i+1] === '$' && statement[i+2] === '"')) { startPos = i; prefixLen = 3; } } if (startPos !== -1) { let contentStart = startPos + prefixLen; let braceDepth = 0; let inString = false; let escapeNext = false; let stringDelimiter = ''; let j = contentStart; let foundEnd = false; let endPos = -1; while (j < statement.length) { const char = statement[j]; if (escapeNext) { escapeNext = false; } else if (char === '\\') { escapeNext = true; } else if (inString) { if (char === stringDelimiter) { inString = false; stringDelimiter = ''; } } else if (!inString && char === '"' && braceDepth === 0) { endPos = j; foundEnd = true; break; } else if (char === '"') { inString = true; stringDelimiter = char; } else if (!inString && char === '{') { braceDepth++; } else if (!inString && char === '}') { braceDepth--; } j++; } if (foundEnd && endPos !== -1) { const fullMatch = statement.substring(startPos, endPos + 1); const content = statement.substring(contentStart, endPos); matches.push({ fullMatch, content, position: startPos }); i = endPos + 1; } else { i = startPos + 1; } } else { i++; } } for (const match of matches) { const { fullMatch, content, position } = match; const variables: string[] = []; let templateVariableIndex = 0; let templateIndex = 0; let globalVariableIndex = this.variableIndex; const localResult: string[] = []; const globalResult: string[] = []; while (templateIndex < content.length) { if (templateIndex + 1 < content.length && content[templateIndex] === '{' && content[templateIndex + 1] === '{') { localResult.push('{{'); globalResult.push('{{'); templateIndex += 2; continue; } else if (templateIndex + 1 < content.length && content[templateIndex] === '}' && content[templateIndex + 1] === '}') { localResult.push('}}'); globalResult.push('}}'); templateIndex += 2; continue; } else if (content[templateIndex] === '{') { templateIndex++; let exprStart = templateIndex; let braceDepth = 1; let inString = false; let escapeNext = false; let stringDelimiter = ''; while (templateIndex < content.length && braceDepth > 0) { const char = content[templateIndex]; if (escapeNext) { escapeNext = false; } else if (char === '\\') { escapeNext = true; } else if (inString) { if (char === stringDelimiter) { inString = false; stringDelimiter = ''; } } else if (!inString && char === '{') { braceDepth++; } else if (!inString && char === '}') { braceDepth--; } else if (char === '"' || char === "'") { inString = true; stringDelimiter = char; } templateIndex++; } const varExpr = content.substring(exprStart, templateIndex - 1); let expression = varExpr; let formatPart = ''; let formatIndex = -1; let inFormatString = false; let escapeFormatNext = false; let stringFormatDelimiter = ''; let parenDepth = 0; let formatBraceDepth = 0; let bracketDepth = 0; for (let i = 0; i < varExpr.length; i++) { const char = varExpr[i]; if (escapeFormatNext) { escapeFormatNext = false; } else if (char === '\\') { escapeFormatNext = true; } else if (inFormatString) { if (char === stringFormatDelimiter) { inFormatString = false; stringFormatDelimiter = ''; } } else if (char === '"' || char === "'") { inFormatString = true; stringFormatDelimiter = char; } else if (char === '(') { parenDepth++; } else if (char === ')') { parenDepth--; } else if (char === '{') { formatBraceDepth++; } else if (char === '}') { formatBraceDepth--; } else if (char === '[') { bracketDepth++; } else if (char === ']') { bracketDepth--; } else if (!inFormatString && char === ':' && parenDepth === 0 && formatBraceDepth === 0 && bracketDepth === 0 && formatIndex === -1) { formatIndex = i; } } if (formatIndex >= 0) { expression = varExpr.substring(0, formatIndex).trim(); formatPart = varExpr.substring(formatIndex); } variables.push(expression); localResult.push(`{${templateVariableIndex++}${formatPart}}`); globalResult.push(`{${globalVariableIndex++}${formatPart}}`); } else { localResult.push(content[templateIndex]); globalResult.push(content[templateIndex]); templateIndex++; } } const localFormattedContent = localResult.join(''); const globalFormattedContent = globalResult.join(''); this.variableIndex += variables.length; if (variables.length > 0) { const formatCall = `${trClass}.${trFormatMethod}("${localFormattedContent}", ${variables.join(', ')})`; processedStatement = processedStatement.replace(fullMatch, formatCall); } snippet.addLiteral(position, globalFormattedContent); } return processedStatement; } private processStringFormat(statement: string, snippet: CodeSnippet, trClass: string, trMethod: string): string { const formatRegex = /string\.Format\(("(?:[^"\\]|\\.)*"),([^)]*)\)/g; let match; let processedStatement = statement; while ((match = formatRegex.exec(statement)) !== null) { const fullMatch = match[0]; const formatString = match[1]; const args = match[2]; const position = match.index; const trFormatCall = `${trClass}.${trMethod}(${formatString},${args})`; processedStatement = processedStatement.replace(fullMatch, trFormatCall); const formatStringWithoutQuotes = formatString.substring(1, formatString.length - 1); snippet.addLiteral(position, formatStringWithoutQuotes); const argRegex = /"(?:[^"\\]|\\.)*"/g; let argMatch; while ((argMatch = argRegex.exec(args)) !== null) { const argPosition = position + match[0].indexOf(argMatch[0]); const argWithoutQuotes = argMatch[0].substring(1, argMatch[0].length - 1); snippet.addLiteral(argPosition, argWithoutQuotes); } } return processedStatement; } private processTextAssignments(statement: string, snippet: CodeSnippet, trClass: string, trFormatMethod: string, trMethod: string): string { const specialAssignmentRegex = /([\s\S]*?(?:\.text|\.title)\s*=\s*)([\s\S]*?)(?=;|$)/; const match = specialAssignmentRegex.exec(statement); let prefix: string | null = null; let value: string; if (match) { prefix = match[1]; value = match[2]; } else { value = statement; } let processedValue = value; if (value.includes('?') && value.includes(':')) { const trueBranchMatch = value.match(/\?\s*([^:]+)\s*:/); const falseBranchMatch = value.match(/:\s*([^;]+)/); if (trueBranchMatch && falseBranchMatch) { const trueBranch = trueBranchMatch[1]; const falseBranch = falseBranchMatch[1]; let processedTrueBranch = trueBranch; let processedFalseBranch = falseBranch; const trueBranchTrimmed = trueBranch.trim(); if (!trueBranchTrimmed.includes(`${trClass}.${trFormatMethod}(`) && !trueBranchTrimmed.endsWith(`.${trMethod}()`)) { const whitespaceBefore = trueBranch.substring(0, trueBranch.search(/\S/)); const actualPart = trueBranch.substring(trueBranch.search(/\S/)); const whitespaceAfter = actualPart.search(/\s*$/) === 0 ? actualPart : actualPart.substring(actualPart.search(/\S/) + trueBranchTrimmed.length); processedTrueBranch = whitespaceBefore + trueBranchTrimmed + `.${trMethod}()` + whitespaceAfter; } const falseBranchTrimmed = falseBranch.trim(); if (!falseBranchTrimmed.includes(`${trClass}.${trFormatMethod}(`) && !falseBranchTrimmed.endsWith(`.${trMethod}()`)) { const whitespaceBefore = falseBranch.substring(0, falseBranch.search(/\S/)); const actualPart = falseBranch.substring(falseBranch.search(/\S/)); const whitespaceAfter = actualPart.search(/\s*$/) === 0 ? actualPart : actualPart.substring(actualPart.search(/\S/) + falseBranchTrimmed.length); processedFalseBranch = whitespaceBefore + falseBranchTrimmed + `.${trMethod}()` + whitespaceAfter; } processedValue = value.replace(trueBranch, processedTrueBranch).replace(falseBranch, processedFalseBranch); } } else if (value.includes('+')) { const parts = this.splitExpression(value); const processedParts = parts.map((part, index) => { if (index % 2 === 1) { return part; } const trimmedPart = part.trim(); if (trimmedPart.endsWith(`.${trMethod}()`)) { return part; } if (trimmedPart.includes(`${trClass}.${trFormatMethod}(`) || trimmedPart.includes('Tr.Format(')) { return part; } if (trimmedPart.startsWith('"') || trimmedPart.startsWith('$"') || trimmedPart.startsWith('$@"') || trimmedPart.startsWith('@$"') || /^\w+/.test(trimmedPart) || trimmedPart.startsWith('(')) { const whitespaceBefore = part.substring(0, part.search(/\S/)); const actualPart = part.substring(part.search(/\S/)); const whitespaceAfter = actualPart.search(/\s*$/) === 0 ? actualPart : actualPart.substring(actualPart.search(/\S/) + trimmedPart.length); return whitespaceBefore + trimmedPart + `.${trMethod}()` + whitespaceAfter; } return part; }); processedValue = processedParts.join(''); } else { const trimmedValue = value.trim(); if (!trimmedValue.includes(`${trClass}.${trFormatMethod}(`) && !trimmedValue.endsWith(`.${trMethod}()`)) { if (trimmedValue !== 'null' && (trimmedValue.startsWith('"') || trimmedValue.startsWith('$"') || trimmedValue.startsWith('$@"') || trimmedValue.startsWith('@$"') || /^\w+/.test(trimmedValue) || trimmedValue.startsWith('('))) { const whitespaceBefore = value.substring(0, value.search(/\S/)); const actualValue = value.substring(value.search(/\S/)); const whitespaceAfter = actualValue.search(/\s*$/) === 0 ? actualValue : actualValue.substring(actualValue.search(/\S/) + trimmedValue.length); processedValue = whitespaceBefore + trimmedValue + `.${trMethod}()` + whitespaceAfter; } } } if (prefix) { if (processedValue !== value) { const hasSemicolon = statement.trim().endsWith(';'); const result = prefix + processedValue; return hasSemicolon ? result + ';' : result; } return statement; } else { return processedValue; } } private processStringConcatenation(statement: string, snippet: CodeSnippet, trClass: string, trFormatMethod: string, trMethod: string): string { if (!statement.includes('+')) { return statement; } const hasStringLiteral = /"(?:[^"\\]|\\.)*"/.test(statement); const isTextAssignment = this.isSpecialAssignment(statement); if (isTextAssignment) { return statement; } const isFunctionCall = /^\s*[\w\.]+\s*\(/.test(statement); if (isFunctionCall) { return statement; } if (!hasStringLiteral) { return statement; } // 检查所有的 + 号是否都在字符串内部 let inString = false; let escapeNext = false; let stringDelimiter = ''; let hasPlusOutsideString = false; for (let i = 0; i < statement.length; i++) { const char = statement[i]; if (escapeNext) { escapeNext = false; continue; } if (char === '\\') { escapeNext = true; continue; } if (char === '"' || char === "'") { if (!inString) { inString = true; stringDelimiter = char; } else if (char === stringDelimiter) { inString = false; stringDelimiter = ''; } continue; } if (char === '+' && !inString) { hasPlusOutsideString = true; break; } } if (!hasPlusOutsideString) { return statement; } const hasSemicolon = statement.endsWith(';'); let statementWithoutSemicolon = hasSemicolon ? statement.slice(0, -1) : statement; // Find actual assignment operator = that's not inside string, parentheses, etc. let assignmentIndex = -1; let tempInString = false; let tempEscapeNext = false; let tempStringDelimiter = ''; let parenDepth = 0; let braceDepth = 0; let bracketDepth = 0; for (let i = 0; i < statementWithoutSemicolon.length; i++) { const char = statementWithoutSemicolon[i]; if (tempEscapeNext) { tempEscapeNext = false; continue; } if (char === '\\') { tempEscapeNext = true; continue; } if (tempInString) { if (char === tempStringDelimiter) { tempInString = false; tempStringDelimiter = ''; } continue; } if (char === '"' || char === "'") { tempInString = true; tempStringDelimiter = char; continue; } if (char === '(') { parenDepth++; } else if (char === ')') { parenDepth--; } else if (char === '{') { braceDepth++; } else if (char === '}') { braceDepth--; } else if (char === '[') { bracketDepth++; } else if (char === ']') { bracketDepth--; } else if (char === '=' && parenDepth === 0 && braceDepth === 0 && bracketDepth === 0) { assignmentIndex = i; break; } } let leftSide = ''; let rightSide = ''; if (assignmentIndex !== -1) { leftSide = statementWithoutSemicolon.substring(0, assignmentIndex + 1); // Include = rightSide = statementWithoutSemicolon.substring(assignmentIndex + 1); } if (assignmentIndex !== -1) { const parts = this.splitExpression(rightSide); const processedParts: string[] = []; for (let i = 0; i < parts.length; i++) { const part = parts[i]; if (i % 2 === 1) { processedParts.push(part); continue; } const trimmedPart = part.trim(); if (trimmedPart.endsWith(`.${trMethod}()`)) { processedParts.push(part); continue; } if (trimmedPart.includes(`${trClass}.${trFormatMethod}(`) || trimmedPart.includes('Tr.Format(')) { processedParts.push(part); continue; } if (trimmedPart.includes('Tr.') && trimmedPart.includes('(')) { processedParts.push(part); continue; } if (trimmedPart.startsWith(`${trClass}.${trFormatMethod}(`)) { processedParts.push(part); continue; } const templateMatch = trimmedPart.match(/(\$@?|@\$)"((?:[^"\\]|\\.)*)"/s); if (templateMatch) { processedParts.push(part); } else if (trimmedPart.startsWith('"')) { const whitespaceBefore = part.substring(0, part.search(/\S/)); const actualPart = part.substring(part.search(/\S/)); const whitespaceAfter = actualPart.search(/\s*$/) === 0 ? actualPart : actualPart.substring(actualPart.search(/\S/) + trimmedPart.length); processedParts.push(whitespaceBefore + trimmedPart + `.${trMethod}()` + whitespaceAfter); } else if (trimmedPart !== 'null' && trimmedPart && !trimmedPart.match(/^\s*$/) && !trimmedPart.match(/^\d+$/)) { const whitespaceBefore = part.substring(0, part.search(/\S/)); const actualPart = part.substring(part.search(/\S/)); const whitespaceAfter = actualPart.search(/\s*$/) === 0 ? actualPart : actualPart.substring(actualPart.search(/\S/) + trimmedPart.length); processedParts.push(whitespaceBefore + trimmedPart + `.${trMethod}()` + whitespaceAfter); } else { processedParts.push(part); } } let result = leftSide + processedParts.join(''); if (hasSemicolon) { result += ';'; } while (result.includes(`${trClass}.${trFormatMethod}().${trMethod}()`)) { result = result.replace(`${trClass}.${trFormatMethod}().${trMethod}()`, `${trClass}.${trFormatMethod}()`); } return result; } const parts = this.splitExpression(statementWithoutSemicolon); const processedParts: string[] = []; for (let i = 0; i < parts.length; i++) { const part = parts[i]; if (i % 2 === 1) { processedParts.push(part); continue; } const trimmedPart = part.trim(); if (trimmedPart.endsWith(`.${trMethod}()`)) { processedParts.push(part); continue; } if (trimmedPart.includes(`${trClass}.${trFormatMethod}(`) || trimmedPart.includes('Tr.Format(')) { processedParts.push(part); continue; } const templateMatch = trimmedPart.match(/(\$@?|@\$)"((?:[^"\\]|\\.)*)"/s); if (templateMatch) { processedParts.push(part); } else if (trimmedPart.startsWith('"')) { const whitespaceBefore = part.substring(0, part.search(/\S/)); const actualPart = part.substring(part.search(/\S/)); const whitespaceAfter = actualPart.search(/\s*$/) === 0 ? actualPart : actualPart.substring(actualPart.search(/\S/) + trimmedPart.length); processedParts.push(whitespaceBefore + trimmedPart + `.${trMethod}()` + whitespaceAfter); } else if (trimmedPart !== 'null' && trimmedPart && !trimmedPart.match(/^\s*$/) && !trimmedPart.match(/^\d+$/)) { if (trimmedPart.includes(`${trClass}.${trFormatMethod}(`)) { processedParts.push(part); continue; } const whitespaceBefore = part.substring(0, part.search(/\S/)); const actualPart = part.substring(part.search(/\S/)); const whitespaceAfter = actualPart.search(/\s*$/) === 0 ? actualPart : actualPart.substring(actualPart.search(/\S/) + trimmedPart.length); processedParts.push(whitespaceBefore + trimmedPart + `.${trMethod}()` + whitespaceAfter); } else { processedParts.push(part); } } let result = processedParts.join(''); if (hasSemicolon) { result += ';'; } while (result.includes(`.${trFormatMethod}().${trMethod}()`)) { result = result.replace(`.${trFormatMethod}().${trMethod}()`, `.${trFormatMethod}()`); } return result; } private addTRCalls(expression: string, snippet: CodeSnippet, trClass: string, trMethod: string, trMethod2: string): string { const parts = expression.split('+'); const processedParts = parts.map(part => { const trimmedPart = part.trim(); if (trimmedPart.endsWith(`.${trMethod2}()`)) { return part; } if (trimmedPart.startsWith(`${trClass}.${trMethod}(`)) { return part; } if (trimmedPart.startsWith('"') || /^\w+/.test(trimmedPart) || trimmedPart.startsWith('(')) { return part.trim() + `.${trMethod2}()`; } return part; }); return processedParts.join(' + '); } private extractPlainStrings(statement: string, snippet: CodeSnippet, trClass: string, trMethod: string): void { const stringRegex = /"(?:[^"\\]|\\.)*"/g; let match; stringRegex.lastIndex = 0; while ((match = stringRegex.exec(statement)) !== null) { const stringLiteral = match[0]; const position = match.index; const beforeMatch = statement.substring(0, position); const trFormatCall = `${trClass}.${trMethod}(`; const trFormatIndex = beforeMatch.lastIndexOf(trFormatCall); const closingParenIndex = beforeMatch.lastIndexOf(')'); const trFormatMatch = trFormatIndex !== -1 && (closingParenIndex === -1 || trFormatIndex > closingParenIndex); const functionCallMatch = beforeMatch.match(/(\w+\.\w+|\w+)\s*\([^)]*$/); const inFunctionCall = functionCallMatch !== null; let shouldExtract = true; if (!trFormatMatch && shouldExtract) { const literalWithoutQuotes = stringLiteral.substring(1, stringLiteral.length - 1); snippet.addLiteral(position, literalWithoutQuotes); } } } private splitExpression(expression: string): string[] { const parts: string[] = []; let current = ''; let inParentheses = 0; let inString = false; let escapeNext = false; let stringDelimiter = ''; for (let i = 0; i < expression.length; i++) { const char = expression[i]; if (escapeNext) { current += char; escapeNext = false; continue; } if (char === '\\') { current += char; escapeNext = true; continue; } if (char === '"' || char === "'") { current += char; if (!inString) { inString = true; stringDelimiter = char; } else if (char === stringDelimiter) { inString = false; stringDelimiter = ''; } continue; } if (inString) { current += char; continue; } if (char === '(') { current += char; inParentheses++; continue; } if (char === ')') { current += char; inParentheses--; continue; } if (char === '+' && inParentheses === 0) { parts.push(current); let separator = '+'; let j = i + 1; while (j < expression.length && expression[j] === ' ') { separator += ' '; j++; } parts.push(separator); current = ''; i = j - 1; } else { current += char; } } if (current) { parts.push(current); } return parts; } private extractValueExpression(statement: string, statementIndex: number, fullCode: string): { valueExpression: string, valueExpressionIndex: number } { const trimmedStatement = statement.trim(); let assignmentMatch: RegExpExecArray | null = null; let assignmentSuffix = ''; const textMatch = CSharpStringExtractor.TEXT_ASSIGNMENT_PATTERN.exec(trimmedStatement); const titleMatch = CSharpStringExtractor.TITLE_ASSIGNMENT_PATTERN.exec(trimmedStatement); if (textMatch) { assignmentMatch = textMatch; assignmentSuffix = '.text ='; } else if (titleMatch) { assignmentMatch = titleMatch; assignmentSuffix = '.title ='; } if (assignmentMatch) { const assignmentIndex = assignmentMatch.index + assignmentMatch[0].indexOf(assignmentSuffix); const prefix = trimmedStatement.substring(0, assignmentIndex + assignmentSuffix.length); const valuePart = trimmedStatement.substring(assignmentIndex + assignmentSuffix.length); const value = this.extractValueUntilSemicolon(valuePart); const valueExpression = value.trim(); const valueStartInStatement = statement.indexOf(value); const valueExpressionIndex = statementIndex + (valueStartInStatement !== -1 ? valueStartInStatement : assignmentIndex + assignmentSuffix.length); const actualValueStart = fullCode.indexOf(valueExpression, valueExpressionIndex); const finalIndex = actualValueStart !== -1 ? actualValueStart : valueExpressionIndex; return { valueExpression: valueExpression, valueExpressionIndex: finalIndex }; } if (trimmedStatement.startsWith('return ')) { const valuePart = trimmedStatement.substring('return '.length); const value = this.extractValueUntilSemicolon(valuePart); const valueExpression = value.trim(); const valueStartInStatement = statement.indexOf(value); const valueExpressionIndex = statementIndex + (valueStartInStatement !== -1 ? valueStartInStatement : 'return '.length); const actualValueStart = fullCode.indexOf(valueExpression, valueExpressionIndex); const finalIndex = actualValueStart !== -1 ? actualValueStart : valueExpressionIndex; return { valueExpression: valueExpression, valueExpressionIndex: finalIndex }; } const assignmentIndex = trimmedStatement.indexOf('='); if (assignmentIndex !== -1) { const prefix = trimmedStatement.substring(0, assignmentIndex + 1); const valuePart = trimmedStatement.substring(assignmentIndex + 1); const value = this.extractValueUntilSemicolon(valuePart); const valueExpression = value.trim(); const valueStartInStatement = statement.indexOf(value); const valueExpressionIndex = statementIndex + (valueStartInStatement !== -1 ? valueStartInStatement : assignmentIndex + 1); const actualValueStart = fullCode.indexOf(valueExpression, valueExpressionIndex); const finalIndex = actualValueStart !== -1 ? actualValueStart : valueExpressionIndex; return { valueExpression: valueExpression, valueExpressionIndex: finalIndex }; } const stringFormatMatch = trimmedStatement.match(/^(string\.Format\([\s\S]*?\))(;|$)/); if (stringFormatMatch) { const valueExpression = stringFormatMatch[1].trim(); const actualValueStart = fullCode.indexOf(valueExpression, statementIndex); const finalIndex = actualValueStart !== -1 ? actualValueStart : statementIndex; return { valueExpression: valueExpression, valueExpressionIndex: finalIndex }; } let searchStart = 0; let colonIndex = -1; let inString = false; let escapeNext = false; let stringDelimiter = ''; if (trimmedStatement.startsWith('case ') || trimmedStatement.startsWith('default:')) { for (let i = 0; i < trimmedStatement.length; i++) { const char = trimmedStatement[i]; if (escapeNext) { escapeNext = false; continue; } if (char === '\\') { escapeNext = true; continue; } if (inString) { if (char === stringDelimiter) { inString = false; stringDelimiter = ''; } continue; } if (char === '"' || char === "'") { inString = true; stringDelimiter = char; continue; } if (char === ':') { colonIndex = i; break; } } if (colonIndex !== -1) { searchStart = colonIndex + 1; } } let lastParenOpenIndex = -1; inString = false; escapeNext = false; stringDelimiter = ''; for (let i = searchStart; i < trimmedStatement.length; i++) { const char = trimmedStatement[i]; if (escapeNext) { escapeNext = false; continue; } if (char === '\\') { escapeNext = true; continue; } if (inString) { if (char === stringDelimiter) { inString = false; stringDelimiter = ''; } continue; } if (char === '"' || char === "'") { inString = true; stringDelimiter = char; continue; } if (char === '(') { lastParenOpenIndex = i; } } if (lastParenOpenIndex !== -1) { const parenCloseIndex = this.findMatchingParenthesis(trimmedStatement, lastParenOpenIndex); if (parenCloseIndex !== -1) { const args = trimmedStatement.substring(lastParenOpenIndex + 1, parenCloseIndex); const hasStringLiteral = /"(?:[^"\\]|\\.)*"/.test(args); if (hasStringLiteral) { const valueExpression = args.trim(); const parenIndex = statement.indexOf(trimmedStatement.substring(lastParenOpenIndex, lastParenOpenIndex + 1)); const valueExpressionIndex = statementIndex + parenIndex + 1; const actualValueStart = fullCode.indexOf(valueExpression, valueExpressionIndex); const finalIndex = actualValueStart !== -1 ? actualValueStart : valueExpressionIndex; return { valueExpression: valueExpression, valueExpressionIndex: finalIndex }; } } } let startInStatement = -1; for (let i = 0; i < statement.length - 1; i++) { const char = statement[i]; const nextChar = i + 1 < statement.length ? statement[i + 1] : ''; const nextNextChar = i + 2 < statement.length ? statement[i + 2] : ''; if ((char === '$' && nextChar === '"') || (char === '$' && nextChar === '@' && nextNextChar === '"') || (char === '@' && nextChar === '$' && nextNextChar === '"')) { startInStatement = i; break; } } if (startInStatement === -1) { for (let i = 0; i < statement.length; i++) { if (statement[i] === '"' || statement[i] === "'") { startInStatement = i; break; } } } if (startInStatement !== -1) { const finalIndex = statementIndex + startInStatement; let stringEnd = startInStatement; let inString = false; let escapeNext = false; let stringDelimiter = ''; let interpolatedBraceDepth = 0; let j = startInStatement; const char = statement[j]; const nextChar = j + 1 < statement.length ? statement[j + 1] : ''; const nextNextChar = j + 2 < statement.length ? statement[j + 2] : ''; if ((char === '$' && nextChar === '"')) { inString = true; stringDelimiter = '"'; j += 2; } else if (char === '$' && nextChar === '@' && nextNextChar === '"') { inString = true; stringDelimiter = '"'; j += 3; } else if (char === '@' && nextChar === '$' && nextNextChar === '"') { inString = true; stringDelimiter = '"'; j += 3; } else if (char === '"' || char === "'") { inString = true; stringDelimiter = char; j++; } while (j < statement.length && inString) { const currentChar = statement[j]; if (escapeNext) { escapeNext = false; } else if (currentChar === '\\') { escapeNext = true; } else if (currentChar === stringDelimiter && interpolatedBraceDepth === 0) { inString = false; j++; stringEnd = j; break; } else if (currentChar === '{' && !escapeNext) { interpolatedBraceDepth++; } else if (currentChar === '}' && !escapeNext && interpolatedBraceDepth > 0) { interpolatedBraceDepth--; } j++; } if (!inString) { const valueExpression = statement.substring(startInStatement, stringEnd).trim(); return { valueExpression, valueExpressionIndex: finalIndex }; } } return { valueExpression: trimmedStatement, valueExpressionIndex: statementIndex }; } private extractValueUntilSemicolon(valuePart: string): string { let result = ''; let inString = false; let escapeNext = false; let stringDelimiter = ''; let parenthesesDepth = 0; let bracketDepth = 0; for (let i = 0; i < valuePart.length; i++) { const char = valuePart[i]; if (escapeNext) { result += char; escapeNext = false; continue; } if (char === '\\') { result += char; escapeNext = true; continue; } if (inString) { result += char; if (char === stringDelimiter) { inString = false; stringDelimiter = ''; } continue; } if (char === '"' || char === "'") { result += char; inString = true; stringDelimiter = char; continue; } if (char === '(') { result += char; parenthesesDepth++; continue; } if (char === ')') { result += char; parenthesesDepth--; continue; } if (char === '[') { result += char; bracketDepth++; continue; } if (char === ']') { result += char; bracketDepth--; continue; } if (char === ';' && parenthesesDepth === 0 && bracketDepth === 0) { break; } result += char; } return result; } private processStatementAndExtractValue(snippet: CodeSnippet, fullStatement: string, valueExpression: string, trClass: string, trFormatMethod: string, trMethod: string): void { let processedValueExpression = valueExpression; const isTextAssignment = this.isSpecialAssignment(fullStatement); this.extractTrFormatStrings(processedValueExpression, snippet, trClass, trFormatMethod); processedValueExpression = this.processStringTemplates(processedValueExpression, snippet, trClass, trFormatMethod); processedValueExpression = this.processStringFormat(processedValueExpression, snippet, trClass, trFormatMethod); processedValueExpression = this.processStringConcatenation(processedValueExpression, snippet, trClass, trFormatMethod, trMethod); if (isTextAssignment) { processedValueExpression = this.processTextAssignments(processedValueExpression, snippet, trClass, trFormatMethod, trMethod); } this.extractPlainStrings(processedValueExpression, snippet, trClass, trFormatMethod); const regex = new RegExp(`${trClass}\\.${trFormatMethod}\\(([^)]*)\\)\\.${trMethod}\\(\\)`, 'g'); processedValueExpression = processedValueExpression.replace(regex, `${trClass}.${trFormatMethod}($1)`); snippet.finalizeLiterals(); let convertedValueExpression = processedValueExpression; if (convertedValueExpression.endsWith(';')) { convertedValueExpression = convertedValueExpression.slice(0, -1).trim(); } snippet.convertedCode = convertedValueExpression; } private processAllFunctionCallsInRange(startIndex: number, endIndex: number, fullCode: string, snippets: CodeSnippet[], trClass: string, trFormatMethod: string, trMethod: string): void { let i = startIndex; while (i < endIndex) { let actualParenOpenIndex = -1; let tempDepth = 0; let tempInString = false; let tempEscapeNext = false; let tempStringDelimiter = ''; let isStringFormat = false; let functionNameStart = -1; for (let j = i; j < endIndex; j++) { const char = fullCode[j]; const nextChar = j + 1 < fullCode.length ? fullCode[j + 1] : ''; const nextNextChar = j + 2 < fullCode.length ? fullCode[j + 2] : ''; if (tempEscapeNext) { tempEscapeNext = false; continue; } if (char === '\\') { tempEscapeNext = true; continue; } if (tempInString) { if (char === tempStringDelimiter) { tempInString = false; tempStringDelimiter = ''; } continue; } if ((char === '$' && nextChar === '"') || (char === '$' && nextChar === '@' && nextNextChar === '"') || (char === '@' && nextChar === '$' && nextNextChar === '"')) { tempInString = true; tempStringDelimiter = '"'; if (nextChar === '@') { j += 2; } else { j += 1; } continue; } if (char === '"' || char === "'") { tempInString = true; tempStringDelimiter = char; continue; } if (char === '(') { tempDepth++; if (actualParenOpenIndex === -1) { actualParenOpenIndex = j; if (functionNameStart !== -1) { const funcName = fullCode.substring(functionNameStart, j).trim(); isStringFormat = funcName === 'string.Format'; } } } else if (char === ')') { tempDepth--; if (tempDepth === 0 && actualParenOpenIndex !== -1 && !isStringFormat) { const actualParenCloseIndex = j; let currentSnippetCount = 0; const stringLiteralPositions: number[] = []; let k = actualParenOpenIndex; let inString3 = false; let escapeNext3 = false; let stringDelimiter3 = ''; let stringStart = -1; while (k < fullCode.length && k <= actualParenCloseIndex) { const char = fullCode[k]; const nextChar = k + 1 < fullCode.length ? fullCode[k + 1] : ''; const nextNextChar = k + 2 < fullCode.length ? fullCode[k + 2] : ''; if (escapeNext3) { escapeNext3 = false; k++; continue; } if (char === '\\') { escapeNext3 = true; k++; continue; } if (inString3) { if (char === stringDelimiter3) { stringLiteralPositions.push(stringStart); inString3 = false; stringDelimiter3 = ''; } k++; continue; } if ((char === '$' && nextChar === '"') || (char === '$' && nextChar === '@' && nextNextChar === '"') || (char === '@' && nextChar === '$' && nextNextChar === '"')) { inString3 = true; stringDelimiter3 = '"'; stringStart = k; if (nextChar === '@') { k += 2; } else { k += 1; } continue; } if (char === '"' || char === "'") { inString3 = true; stringDelimiter3 = char; stringStart = k; k++; continue; } if (char === ')' && k === actualParenCloseIndex) { break; } k++; } let argStartPos = actualParenOpenIndex + 1; const trimmedCode = fullCode.substring(actualParenOpenIndex + 1, actualParenCloseIndex); const args = this.splitArguments(trimmedCode); for (const arg of args) { const trimmedArg = arg.trim(); if (!trimmedArg) { argStartPos = this.updateArgStartPos(argStartPos, fullCode, actualParenCloseIndex); continue; } const hasStringLiteral = /"(?:[^"\\]|\\.)*"/.test(trimmedArg) || /\$"[\s\S]*?"/.test(trimmedArg) || /\$@"[\s\S]*?"/.test(trimmedArg) || /@\$"[\s\S]*?"/.test(trimmedArg); while (argStartPos < fullCode.length && /\s/.test(fullCode[argStartPos])) { argStartPos++; } let foundArgEnd = argStartPos; let inString4 = false; let escapeNext4 = false; let stringDelimiter4 = ''; let parenthesesDepth = 0; let braceDepth = 0; let bracketDepth = 0; while (foundArgEnd < fullCode.length && foundArgEnd <= actualParenCloseIndex) { const char = fullCode[foundArgEnd]; const nextChar = foundArgEnd + 1 < fullCode.length ? fullCode[foundArgEnd + 1] : ''; const nextNextChar = foundArgEnd + 2 < fullCode.length ? fullCode[foundArgEnd + 2] : ''; if (escapeNext4) { escapeNext4 = false; foundArgEnd++; continue; } if (char === '\\') { escapeNext4 = true; foundArgEnd++; continue; } if (inString4) { if (char === stringDelimiter4) { inString4 = false; stringDelimiter4 = ''; } foundArgEnd++; continue; } if ((char === '$' && nextChar === '"') || (char === '$' && nextChar === '@' && nextNextChar === '"') || (char === '@' && nextChar === '$' && nextNextChar === '"')) { inString4 = true; stringDelimiter4 = '"'; if (nextChar === '@') { foundArgEnd += 2; } else { foundArgEnd += 1; } continue; } if (char === '"' || char === "'") { inString4 = true; stringDelimiter4 = char; foundArgEnd++; continue; } if (char === '(') { parenthesesDepth++; } else if (char === ')') { if (foundArgEnd === actualParenCloseIndex) { break; } parenthesesDepth--; } else if (char === '{') { braceDepth++; } else if (char === '}') { braceDepth--; } else if (char === '[') { bracketDepth++; } else if (char === ']') { bracketDepth--; } else if (char === ',' && parenthesesDepth === 0 && braceDepth === 0 && bracketDepth === 0) { break; } foundArgEnd++; } if (!hasStringLiteral) { argStartPos = foundArgEnd + 1; continue; } const actualArg = fullCode.substring(argStartPos, foundArgEnd).trim(); const isObjectInitializer = actualArg.startsWith('new ') && actualArg.includes('{') && actualArg.includes('}'); if (isObjectInitializer) { this.processObjectInitializerStringsOnly(argStartPos, fullCode, snippets, trClass, trFormatMethod, trMethod); } else { let finalOriginalIndex = argStartPos; if (currentSnippetCount < stringLiteralPositions.length) { finalOriginalIndex = stringLiteralPositions[currentSnippetCount]; } let alreadyExists = false; for (const snippet of snippets) { if (snippet.originalIndex === finalOriginalIndex) { alreadyExists = true; break; } } if (!alreadyExists) { let finalArg = actualArg; if (currentSnippetCount < stringLiteralPositions.length) { const stringStart = stringLiteralPositions[currentSnippetCount]; let stringEnd = stringStart; let j = stringStart; let inString = false; let escapeNext = false; let stringDelimiter = ''; let braceDepth = 0; let interpolatedBraceDepth = 0; const char = fullCode[j]; const nextChar = j + 1 < fullCode.length ? fullCode[j + 1] : ''; const nextNextChar = j + 2 < fullCode.length ? fullCode[j + 2] : ''; if ((char === '$' && nextChar === '"')) { inString = true; stringDelimiter = '"'; j += 2; } else if (char === '$' && nextChar === '@' && j + 2 < fullCode.length && fullCode[j + 2] === '"') { inString = true; stringDelimiter = '"'; j += 3; } else if (char === '@' && nextChar === '$' && j + 2 < fullCode.length && fullCode[j + 2] === '"') { inString = true; stringDelimiter = '"'; j += 3; } else if (char === '"' || char === "'") { inString = true; stringDelimiter = char; j++; } while (j < fullCode.length && inString) { const currentChar = fullCode[j]; if (escapeNext) { escapeNext = false; } else if (currentChar === '\\') { escapeNext = true; } else if (currentChar === stringDelimiter && interpolatedBraceDepth === 0) { inString = false; j++; stringEnd = j; break; } else if (currentChar === '{' && !escapeNext) { interpolatedBraceDepth++; } else if (currentChar === '}' && !escapeNext && interpolatedBraceDepth > 0) { interpolatedBraceDepth--; } j++; } if (!inString) { finalArg = fullCode.substring(stringStart, stringEnd); } } const snippet = new CodeSnippet(); snippet.originalIndex = finalOriginalIndex; snippet.originalCode = finalArg; snippet.originalContext = finalArg; this.variableIndex = 0; this.processSingleArgument(snippet, finalArg, trClass, trFormatMethod, trMethod); snippets.push(snippet); currentSnippetCount++; } } argStartPos = foundArgEnd + 1; } i = actualParenCloseIndex + 1; actualParenOpenIndex = -1; isStringFormat = false; functionNameStart = -1; } else if (tempDepth === 0 && actualParenOpenIndex !== -1 && isStringFormat) { i = j + 1; actualParenOpenIndex = -1; isStringFormat = false; functionNameStart = -1; } } else if (!tempInString && char === '.' && tempDepth === 0) { functionNameStart = -1; } else if (!tempInString && /[a-zA-Z_]/.test(char) && tempDepth === 0 && functionNameStart === -1) { functionNameStart = j; } } if (actualParenOpenIndex === -1) { break; } else { i++; } } } }