Skip to content

Commit

Permalink
imp - Used analyzer code to revamp cleaner
Browse files Browse the repository at this point in the history
---

We've used analyzer code from the string analyzer to make a cleaner.

---

Type: imp
Breaking: False
Doc Required: False
Backport Required: False
Part: 1/1
  • Loading branch information
AptiviCEO committed Sep 9, 2024
1 parent 49ecb3b commit 31a484f
Show file tree
Hide file tree
Showing 8 changed files with 581 additions and 187 deletions.
28 changes: 28 additions & 0 deletions private/Nitrocid.LocaleClean/Analyzers/IAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// Nitrocid KS Copyright (C) 2018-2024 Aptivi
//
// This file is part of Nitrocid KS
//
// Nitrocid KS is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Nitrocid KS is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY, without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//

using Microsoft.CodeAnalysis;

namespace Nitrocid.LocaleClean.Analyzers
{
internal interface IAnalyzer
{
bool Analyze(Document document, out string[] localized);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
//
// Nitrocid KS Copyright (C) 2018-2024 Aptivi
//
// This file is part of Nitrocid KS
//
// Nitrocid KS is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Nitrocid KS is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY, without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Nitrocid.LocaleClean;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Terminaux.Colors.Data;
using Terminaux.Writer.ConsoleWriters;
using Terminaux.Writer.MiscWriters;

namespace Nitrocid.LocaleClean.Analyzers
{
internal static class LocalizableResourcesAnalyzer
{
internal static string[] GetLocalizedStrings()
{
List<string> localizedStrings = [];

// Open every resource except the English translations file
var resourceNames = EntryPoint.thisAssembly.GetManifestResourceNames().Except([
"Nitrocid.LocaleClean.eng.json",
]);
foreach (var resourceName in resourceNames)
{
// Open the resource and load it to a JSON token instance
var stream = EntryPoint.thisAssembly.GetManifestResourceStream(resourceName) ??
throw new Exception($"Opening the {resourceName} resource stream has failed.");
var reader = new StreamReader(stream);
var jsonReader = new JsonTextReader(reader);
var document = JToken.Load(jsonReader) ??
throw new Exception($"Unable to parse JSON for {resourceName}.");

// Determine if this is a theme or a settings entries list
var themeMetadata = document.Type == JTokenType.Array ? null : document["Metadata"];
if (themeMetadata is not null)
{
// It's a theme. Get its description and its localizable boolean value
string description = ((string?)themeMetadata["Description"] ?? "").Replace("\\\"", "\"");
bool localizable = (bool?)themeMetadata["Localizable"] ?? false;
if (!string.IsNullOrEmpty(description) && localizable && EntryPoint.localizationList.Contains(description))
localizedStrings.Add(description);
}
else if (document.Type == JTokenType.Array)
{
// It's likely a settings entry list, but verify
foreach (var settingsEntryList in document)
{
// Check the description and the display
string description = ((string?)settingsEntryList["Desc"] ?? "").Replace("\\\"", "\"");
string displayAs = ((string?)settingsEntryList["DisplayAs"] ?? "").Replace("\\\"", "\"");
if (!string.IsNullOrEmpty(description) && EntryPoint.localizationList.Contains(description))
localizedStrings.Add(description);
if (!string.IsNullOrEmpty(displayAs) && EntryPoint.localizationList.Contains(displayAs))
localizedStrings.Add(displayAs);

// Now, check the keys
JArray? keys = (JArray?)settingsEntryList["Keys"];
if (keys is null || keys.Count == 0)
continue;
foreach (var key in keys)
{
string keyName = ((string?)key["Name"] ?? "").Replace("\\\"", "\"");
string keyDesc = ((string?)key["Description"] ?? "").Replace("\\\"", "\"");
if (!string.IsNullOrEmpty(keyName) && EntryPoint.localizationList.Contains(keyName))
localizedStrings.Add(keyName);
if (!string.IsNullOrEmpty(keyDesc) && EntryPoint.localizationList.Contains(keyDesc))
localizedStrings.Add(keyDesc);
}
}
}
}
return [.. localizedStrings];
}
}
}
86 changes: 86 additions & 0 deletions private/Nitrocid.LocaleClean/Analyzers/ReverseNLOC0001.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//
// Nitrocid KS Copyright (C) 2018-2024 Aptivi
//
// This file is part of Nitrocid KS
//
// Nitrocid KS is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Nitrocid KS is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY, without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.Collections.Generic;
using System.Linq;
using Terminaux.Colors.Data;
using Terminaux.Writer.ConsoleWriters;
using Terminaux.Writer.MiscWriters;

namespace Nitrocid.LocaleClean.Analyzers
{
internal class ReverseNLOC0001 : IAnalyzer
{
public bool Analyze(Document document, out string[] localized)
{
localized = [];
var tree = document.GetSyntaxTreeAsync().Result;
if (tree is null)
return false;
var syntaxNodeNodes = tree.GetRoot().DescendantNodesAndSelf().OfType<SyntaxNode>().ToList();
bool found = false;
List<string> localizedStrings = [];
foreach (var syntaxNode in syntaxNodeNodes)
{
// Check for argument
if (syntaxNode is not InvocationExpressionSyntax exp)
continue;
var args = exp.ArgumentList.Arguments;
if (args.Count < 1)
continue;
var localizableStringArgument = args[0] ??
throw new Exception("Can't get localizable string");

// Now, check for the Translate.DoTranslation() call
if (exp.Expression is not MemberAccessExpressionSyntax expMaes)
continue;
if (expMaes.Expression is IdentifierNameSyntax expIdentifier && expMaes.Name is IdentifierNameSyntax identifier)
{
// Verify that we're dealing with Translate.DoTranslation()
var location = syntaxNode.GetLocation();
var idExpression = expIdentifier.Identifier.Text;
var idName = identifier.Identifier.Text;
if (idExpression == "Translate" && idName == "DoTranslation")
{
// Now, get the string representation from the argument count and compare it with the list of translations.
// You'll notice that we sometimes call Translate.DoTranslation() with a variable instead of a string, so
// check that first, because they're usually obtained from a string representation usually prefixed with
// either the /* Localizable */ comment or in individual kernel resources. However, the resources don't
// have a prefix, so the key names alone are enough.
if (localizableStringArgument.Expression is LiteralExpressionSyntax literalText)
{
string text = literalText.ToString();
text = text[1..^1].Replace("\\\"", "\"");
if (EntryPoint.localizationList.Contains(text))
{
found = true;
localizedStrings.Add(text);
}
}
}
}
}
localized = [.. localizedStrings];
return found;
}
}
}
109 changes: 109 additions & 0 deletions private/Nitrocid.LocaleClean/Analyzers/ReverseNLOC0001Implicit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//
// Nitrocid KS Copyright (C) 2018-2024 Aptivi
//
// This file is part of Nitrocid KS
//
// Nitrocid KS is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Nitrocid KS is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY, without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.Collections.Generic;
using System.Linq;
using Terminaux.Colors.Data;
using Terminaux.Writer.ConsoleWriters;
using Terminaux.Writer.MiscWriters;

namespace Nitrocid.LocaleClean.Analyzers
{
internal class ReverseNLOC0001Implicit : IAnalyzer
{
public bool Analyze(Document document, out string[] localized)
{
localized = [];
var tree = document.GetSyntaxTreeAsync().Result;
if (tree is null)
return false;
var syntaxNodeNodes = tree.GetRoot().DescendantNodesAndSelf().OfType<SyntaxNode>().ToList();
bool found = false;
List<string> localizedStrings = [];
foreach (var syntaxNode in syntaxNodeNodes)
{
// Check for argument
if (syntaxNode is not CompilationUnitSyntax exp)
continue;
var triviaList = exp.DescendantTrivia();
var multiLineComments = triviaList.Where((trivia) => trivia.IsKind(SyntaxKind.MultiLineCommentTrivia));
foreach (var multiLineComment in multiLineComments)
{
string comment = multiLineComment.ToString();
if (comment == "/* Localizable */")
{
// We found a localizable string, but we need to find the string itself, so get all the possible
// tokens.
var node = exp.FindNode(multiLineComment.Span);
var tokens = node.DescendantTokens()
.Where(token => token.GetAllTrivia()
.Where((trivia) => trivia.IsKind(SyntaxKind.MultiLineCommentTrivia) && trivia.ToString() == "/* Localizable */").Any());

// Now, enumerate them to find the string
foreach (var token in tokens)
{
void Process(LiteralExpressionSyntax literalText)
{
// Process it.
var location = literalText.GetLocation();
string text = literalText.ToString();
text = text.Substring(1, text.Length - 2).Replace("\\\"", "\"");
if (EntryPoint.localizationList.Contains(text))
{
found = true;
localizedStrings.Add(text);
}
}

// Try to get a child
int start = token.FullSpan.End;
var parent = token.Parent;
if (parent is null)
continue;
if (parent is LiteralExpressionSyntax literalParent)
{
Process(literalParent);
continue;
}
if (parent is NameColonSyntax)
parent = parent.Parent;
if (parent is null)
continue;
var child = (SyntaxNode?)parent.ChildThatContainsPosition(start);
if (child is null)
continue;

// Now, check to see if it's a literal string
if (child is LiteralExpressionSyntax literalText)
Process(literalText);
else if (child is ArgumentSyntax argument && argument.Expression is LiteralExpressionSyntax literalArgText)
Process(literalArgText);
}
}
}
}
localized = [.. localizedStrings];
return found;
}
}
}
54 changes: 54 additions & 0 deletions private/Nitrocid.LocaleClean/AnalyzersList.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// Nitrocid KS Copyright (C) 2018-2024 Aptivi
//
// This file is part of Nitrocid KS
//
// Nitrocid KS is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Nitrocid KS is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY, without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//


//
// Nitrocid KS Copyright (C) 2018-2024 Aptivi
//
// This file is part of Nitrocid KS
//
// Nitrocid KS is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Nitrocid KS is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY, without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
//

using Nitrocid.LocaleClean.Analyzers;

namespace Nitrocid.LocaleClean
{
internal static class AnalyzersList
{
// For contributors: If you're going to add a new analyzer, you need to copy the implementation from Analyzers to here,
// and make a dedicated diagnostic class for the standalone analyzer to recognize your new analyzer.
internal static readonly IAnalyzer[] analyzers =
[
new ReverseNLOC0001(),
new ReverseNLOC0001Implicit(),
];
}
}
Loading

0 comments on commit 31a484f

Please sign in to comment.