Skip to content

fix: reduzir complexidade do gerador e payload#71

Merged
afonsoft merged 7 commits into
feature/devin-20260712-sonar-qualityfrom
feature/devin-20260712-sonar-complexity-generators
Jul 12, 2026
Merged

fix: reduzir complexidade do gerador e payload#71
afonsoft merged 7 commits into
feature/devin-20260712-sonar-qualityfrom
feature/devin-20260712-sonar-complexity-generators

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

All Submissions:

  • Have you followed the guidelines in our Contributing document?
  • Have you checked to ensure there aren't other open Pull Requests for the same update/change?

Changes to Core Features:

  • Have you added an explanation of what your changes do and why you'd like us to include them?
  • Have you written new tests for your core changes, as applicable?
  • Have you successfully ran tests with your changes locally?

Summary

Refatora QRCodeGenerator e PayloadGenerator para reduzir complexidade cognitiva e excesso de parâmetros sem alterar a API pública.

  • QRCodeGenerator:
    • extraiu helpers para padding, blocos de codewords, interleaving e placement
    • preservou a lógica de seleção de versão/máscara e corrigiu o count indicator para Byte em v1-v9
  • PayloadGenerator:
    • separou a montagem de MeCard/vCard em helpers
    • corrigiu a formatação de endereço para vCard usar ; e MeCard usar ,

Verification

  • dotnet build QRCoder.Core.sln -c Release
  • dotnet test QRCoder.Core.sln -c Release

Link to Devin session: https://app.devin.ai/sessions/644073fe782e408da591b82ae2c25071
Requested by: @afonsoft


Open in Devin Review

@afonsoft afonsoft self-assigned this Jul 12, 2026
@afonsoft
afonsoft self-requested a review July 12, 2026 04:07
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 5 potential issues.

Open in Devin Review

Comment on lines 843 to 855
private static int ScoreFinderPatterns(QRCodeData qrCode, int size)
{
messagePol.PolyItems.Add(new PolynomItem(BinToDec(bitString.Substring(0, 8)), i));
bitString = bitString.Remove(0, 8);
var score = 0;
for (var y = 0; y < size; y++)
{
for (var x = 0; x < size - 10; x++)
{
if (MatchesFinderPattern(qrCode, x, y))
score += 40;
}
}
return score;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 New ScoreFinderPatterns helper only checks horizontal patterns, missing vertical

The newly added ScoreFinderPatterns method at QRCoder.Core/Generators/QRCodeGenerator.cs:843-855 only scans rows (horizontal patterns using [y][x+n]), but the original inline Penalty 3 code in the Score method at QRCoder.Core/Generators/QRCodeGenerator.cs:723-780 checks both horizontal AND vertical patterns (the second block uses [x+n][y]). This method is currently dead code — Score at line 672 still has the full inline implementation and is the only method called at line 453. However, if a future refactor replaces the inline code with a call to ScoreFinderPatterns, it would silently halve the Penalty 3 score, producing incorrect QR mask selection.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines 883 to 889
private static int ScoreBalance(QRCodeData qrCode, int size)
{
blocklist.Add(bitString.Substring(i, blockSize));
var darkCount = qrCode.ModuleMatrix.Sum(row => row.Cast<bool>().Count(x => x));
var totalCount = size * size;
var k = Math.Abs(darkCount * 100 / totalCount - 50) / 5;
return k * 10;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 New ScoreBalance helper uses integer division instead of floating-point

The new ScoreBalance method at line 887 computes darkCount * 100 / totalCount - 50 using integer arithmetic (darkCount is int from .Count(), totalCount is int). The original Penalty 4 code at QRCoder.Core/Generators/QRCodeGenerator.cs:783-792 uses double blackModules and floating-point division (blackModules / (count * count)) * 100. Integer division truncates differently than floating-point, so these can produce different scores. Like ScoreFinderPatterns, this is currently dead code, but would cause incorrect behavior if wired up.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines 797 to 889
private static int ScoreLines(QRCodeData qrCode, int size)
{
if (leadTermSource.PolyItems[0].Coefficient == 0)
var score = 0;
for (var y = 0; y < size; y++)
{
leadTermSource.PolyItems.RemoveAt(0);
leadTermSource.PolyItems.Add(new PolynomItem(0, leadTermSource.PolyItems[leadTermSource.PolyItems.Count - 1].Exponent - 1));
score += ScoreLine(qrCode.ModuleMatrix[y].Cast<bool>().ToList());
score += ScoreLine(qrCode.ModuleMatrix.Select(row => row[y]).ToList());
}
else
return score;
}

private static int ScoreLine(IReadOnlyList<bool> line)
{
var score = 0;
var runLength = 1;
for (var i = 1; i < line.Count; i++)
{
var resPoly = MultiplyGeneratorPolynomByLeadterm(generatorPolynom, ConvertToAlphaNotation(leadTermSource).PolyItems[0], i);
resPoly = ConvertToDecNotation(resPoly);
resPoly = XORPolynoms(leadTermSource, resPoly);
leadTermSource = resPoly;
if (line[i] == line[i - 1])
runLength++;
else
runLength = 1;

if (runLength == 5)
score += 3;
else if (runLength > 5)
score++;
}
return score;
}
return leadTermSource.PolyItems.Select(x => DecToBin(x.Coefficient, 8)).ToList();
}

private static Polynom ConvertToAlphaNotation(Polynom poly)
{
var newPoly = new Polynom();
for (var i = 0; i < poly.PolyItems.Count; i++)
newPoly.PolyItems.Add(
new PolynomItem(
(poly.PolyItems[i].Coefficient != 0
? GetAlphaExpFromIntVal(poly.PolyItems[i].Coefficient)
: 0), poly.PolyItems[i].Exponent));
return newPoly;
}

private static Polynom ConvertToDecNotation(Polynom poly)
{
var newPoly = new Polynom();
for (var i = 0; i < poly.PolyItems.Count; i++)
newPoly.PolyItems.Add(new PolynomItem(GetIntValFromAlphaExp(poly.PolyItems[i].Coefficient), poly.PolyItems[i].Exponent));
return newPoly;
}

private static int GetVersion(int length, EncodingMode encMode, ECCLevel eccLevel)
{
var fittingVersions = capacityTable.Where(
x => x.Details.Any(
y => (y.ErrorCorrectionLevel == eccLevel
&& y.CapacityDict[encMode] >= Convert.ToInt32(length)
)
)
).Select(x => new
{
version = x.Version,
capacity = x.Details.Single(y => y.ErrorCorrectionLevel == eccLevel)
.CapacityDict[encMode]
});

if (fittingVersions.Any())
return fittingVersions.Min(x => x.version);

var maxSizeByte = capacityTable.Where(
x => x.Details.Any(
y => (y.ErrorCorrectionLevel == eccLevel))
).Max(x => x.Details.Single(y => y.ErrorCorrectionLevel == eccLevel).CapacityDict[encMode]);
throw new DataTooLongException(eccLevel.ToString(), encMode.ToString(), maxSizeByte);
}

private static EncodingMode GetEncodingFromPlaintext(string plainText, bool forceUtf8)
{
if (forceUtf8) return EncodingMode.Byte;
EncodingMode result = EncodingMode.Numeric; // assume numeric
foreach (char c in plainText)
private static int ScoreBlocks(QRCodeData qrCode, int size)
{
if (IsInRange(c, '0', '9')) continue; // numeric - char.IsDigit() for Latin1
result = EncodingMode.Alphanumeric; // not numeric, assume alphanumeric
if (IsInRange(c, 'A', 'Z') || alphanumEncTable.Contains(c)) continue; // alphanumeric
return EncodingMode.Byte; // not numeric or alphanumeric, assume byte
var score = 0;
for (var y = 0; y < size - 1; y++)
{
for (var x = 0; x < size - 1; x++)
{
if (qrCode.ModuleMatrix[y][x] == qrCode.ModuleMatrix[y][x + 1] &&
qrCode.ModuleMatrix[y][x] == qrCode.ModuleMatrix[y + 1][x] &&
qrCode.ModuleMatrix[y][x] == qrCode.ModuleMatrix[y + 1][x + 1])
score += 3;
}
}
return score;
}
return result; // either numeric or alphanumeric
}

private static bool IsInRange(char c, char min, char max)
{
return (uint)(c - min) <= (uint)(max - min);
}

private static Polynom CalculateMessagePolynom(string bitString)
{
var messagePol = new Polynom();
for (var i = bitString.Length / 8 - 1; i >= 0; i--)
private static int ScoreFinderPatterns(QRCodeData qrCode, int size)
{
messagePol.PolyItems.Add(new PolynomItem(BinToDec(bitString.Substring(0, 8)), i));
bitString = bitString.Remove(0, 8);
var score = 0;
for (var y = 0; y < size; y++)
{
for (var x = 0; x < size - 10; x++)
{
if (MatchesFinderPattern(qrCode, x, y))
score += 40;
}
}
return score;
}
return messagePol;
}

private static Polynom CalculateGeneratorPolynom(int numEccWords)
{
var generatorPolynom = new Polynom();
generatorPolynom.PolyItems.AddRange(new[]{
new PolynomItem(0,1),
new PolynomItem(0,0)
});
for (var i = 1; i <= numEccWords - 1; i++)
private static bool MatchesFinderPattern(QRCodeData qrCode, int x, int y)
{
var multiplierPolynom = new Polynom();
multiplierPolynom.PolyItems.AddRange(new[]{
new PolynomItem(0,1),
new PolynomItem(i,0)
});

generatorPolynom = MultiplyAlphaPolynoms(generatorPolynom, multiplierPolynom);
return (qrCode.ModuleMatrix[y][x] &&
!qrCode.ModuleMatrix[y][x + 1] &&
qrCode.ModuleMatrix[y][x + 2] &&
qrCode.ModuleMatrix[y][x + 3] &&
qrCode.ModuleMatrix[y][x + 4] &&
!qrCode.ModuleMatrix[y][x + 5] &&
qrCode.ModuleMatrix[y][x + 6] &&
!qrCode.ModuleMatrix[y][x + 7] &&
!qrCode.ModuleMatrix[y][x + 8] &&
!qrCode.ModuleMatrix[y][x + 9] &&
!qrCode.ModuleMatrix[y][x + 10]) ||
(!qrCode.ModuleMatrix[y][x] &&
!qrCode.ModuleMatrix[y][x + 1] &&
!qrCode.ModuleMatrix[y][x + 2] &&
!qrCode.ModuleMatrix[y][x + 3] &&
qrCode.ModuleMatrix[y][x + 4] &&
!qrCode.ModuleMatrix[y][x + 5] &&
qrCode.ModuleMatrix[y][x + 6] &&
qrCode.ModuleMatrix[y][x + 7] &&
qrCode.ModuleMatrix[y][x + 8] &&
qrCode.ModuleMatrix[y][x + 9] &&
!qrCode.ModuleMatrix[y][x + 10]);
}

return generatorPolynom;
}

private static List<string> BinaryStringToBitBlockList(string bitString)
{
const int blockSize = 8;
var numberOfBlocks = (int)Math.Ceiling(bitString.Length / (double)blockSize);
var blocklist = new List<string>(numberOfBlocks);

for (int i = 0; i < bitString.Length; i += blockSize)
private static int ScoreBalance(QRCodeData qrCode, int size)
{
blocklist.Add(bitString.Substring(i, blockSize));
var darkCount = qrCode.ModuleMatrix.Sum(row => row.Cast<bool>().Count(x => x));
var totalCount = size * size;
var k = Math.Abs(darkCount * 100 / totalCount - 50) / 5;
return k * 10;
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Four new Score helper methods are dead code — never called

ScoreLines (line 797), ScoreBlocks (line 827), ScoreFinderPatterns (line 843), and ScoreBalance (line 883) are all defined but never invoked. The actual scoring still happens inline in MaskPattern.Score at line 672. These appear to be an incomplete refactoring — the helpers were extracted but the original Score method was never updated to delegate to them. This adds ~90 lines of unreachable code to the engine's core file, which the AGENTS.md soft rule S1 flags as requiring extensive testing when modifying QRCodeGenerator.cs.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +352 to +357
private static string ReverseString(string inp)
{
var sb = new StringBuilder(inp.Length);
for (var i = inp.Length - 1; i >= 0; i--)
sb.Append(inp[i]);
return sb.ToString();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: ReverseString and several other methods have inconsistent indentation after extraction

ReverseString (line 352), PlaceAlignmentPatterns (line 580), and IsBlocked (line 624) are indented at the outer class level rather than the ModulePlacer nested class level, even though they are still syntactically inside ModulePlacer (the closing braces at lines 891-893 confirm this). This inconsistent indentation makes the code harder to read and could mislead future contributors about which class these methods belong to.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +2016 to +2020
AppendParameter(payload, "separeference", Uri.EscapeDataString(sepaReference));
if (authority == AuthorityType.singledirectdebitsepa)
{
bezahlCodePayload += $"account={account}&";
bezahlCodePayload += $"bnc={bnc}&";
if (postingKey > 0)
bezahlCodePayload += $"postingkey={postingKey}&";
AppendParameter(payload, "creditorid", Uri.EscapeDataString(creditorId));
AppendParameter(payload, "mandateid", Uri.EscapeDataString(mandateId));

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: BezahlCode now calls Uri.EscapeDataString unconditionally on sepaReference, creditorId, mandateId

The old code guarded sepaReference, creditorId, and mandateId with !string.IsNullOrEmpty() before calling Uri.EscapeDataString. The new code at lines 2016, 2019-2020 calls Uri.EscapeDataString() unconditionally and relies on AppendParameter's null/empty check on the result. This works correctly because: (1) the constructor defaults these to "", (2) Uri.EscapeDataString("") returns "", and (3) AppendParameter skips empty values. The behavioral equivalence holds, but the approach is more fragile — if a future constructor path sets these to null, Uri.EscapeDataString would throw ArgumentNullException.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

devin-ai-integration Bot and others added 3 commits July 12, 2026 04:22
Co-Authored-By: Afonso Dutra Nogueira Filho <afonsoft@gmail.com>
Co-Authored-By: Afonso Dutra Nogueira Filho <afonsoft@gmail.com>
Co-Authored-By: Afonso Dutra Nogueira Filho <afonsoft@gmail.com>
@devin-ai-integration
devin-ai-integration Bot force-pushed the feature/devin-20260712-sonar-complexity-generators branch from 8486ec2 to e2e9540 Compare July 12, 2026 04:22
@sonarqubecloud

Copy link
Copy Markdown

@afonsoft
afonsoft merged commit 85fc3a6 into feature/devin-20260712-sonar-quality Jul 12, 2026
9 checks passed
@afonsoft
afonsoft deleted the feature/devin-20260712-sonar-complexity-generators branch July 12, 2026 16:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant