in resharper/resharper-unity/src/Unity/Core/Feature/Internal/DumpSpellCheckWordListsAction.cs [36:181]
public void Execute(IDataContext context, DelegateExecute nextExecute)
{
var solution = context.GetData(ProjectModelDataConstants.SOLUTION);
if (solution == null)
return;
var wordsPerAssembly = new OneToSetMap<string, string>(valueComparer: StringComparer.InvariantCultureIgnoreCase);
var abbreviationsWithOriginalWord = new OneToSetMap<string, string>(valueComparer: StringComparer.InvariantCultureIgnoreCase);
// TODO: This should be in a background task
var psiModules = solution.GetComponent<IPsiModules>();
var symbolCache = solution.GetComponent<ISymbolCache>();
foreach (var assemblyPsiModule in psiModules.GetAssemblyModules().OrderBy(m => m.DisplayName))
{
if (!ShouldProcessAssembly(assemblyPsiModule))
continue;
var symbolScope = symbolCache.GetSymbolScope(assemblyPsiModule, false, true);
var typeElements = symbolScope.GetAllTypeElementsGroupedByName();
foreach (var typeElement in typeElements)
{
if (!typeElement.CanBeVisibleToSolution())
continue;
AddWords(wordsPerAssembly, abbreviationsWithOriginalWord, assemblyPsiModule,
typeElement is IInterface ? typeElement.ShortName.RemoveStart("I") : typeElement.ShortName);
foreach (var namespaceName in typeElement.GetClrName().NamespaceNames)
AddWords(wordsPerAssembly, abbreviationsWithOriginalWord, assemblyPsiModule, namespaceName);
// TODO: Should we skip enum values?
// It's unlikely that a user would name a method or variable after a value such as AdvertisingNetwork.Aarki,
// but on the other hand, if it's used in a comment, it'll show up as a typo.
foreach (var typeMember in typeElement.GetMembers())
{
if (!ShouldProcessTypeMember(typeMember))
continue;
// Don't use any enum values as abbreviations. Avoids false positives with ALL_UPPER_CASE style
AddWords(wordsPerAssembly,
typeElement is IEnum ? new OneToSetMap<string, string>() : abbreviationsWithOriginalWord,
assemblyPsiModule, typeMember.ShortName);
// TODO: Include parameter names?
// Respeller will not check parameter names of overriding or implementing functions, so this is
// probably unnecessary
}
}
}
// Case insensitive. There will not be duplicates
var allWords = (from pair in wordsPerAssembly
from word in pair.Value
select word).ToJetHashSet(StringComparer.InvariantCultureIgnoreCase);
var spellService = solution.GetComponent<ISpellServiceBackendProvider>().SpellService;
if (spellService == null) return;
var wordsCheckResult = spellService.CheckWords(
Lifetime.Eternal,
allWords.Concat(abbreviationsWithOriginalWord.Keys).AsList()
);
if (wordsCheckResult == null)
{
var logger = Logger.GetLogger<DumpSpellCheckWordListsAction>();
logger.Error("Failed to check words");
return;
}
var unknownWords = (from word in allWords
where !wordsCheckResult.TryGetValue(word)
select word).ToJetHashSet(StringComparer.InvariantCultureIgnoreCase);
// Add abbreviations separately. If added to the dictionary, we don't get typo warnings, but we can get
// naming standard inspection warnings. E.g. BlahBlahAABB is converted to BlahBlahAabb. Don't add anything
// that's already known, but the spell checker will only check for words of 4 characters or more.
// TODO: Ideally, we should disable AbbreviationsSettingsProvider, or we'll ignore our own abbreviations
// Merge files by hand
var unknownAbbreviations = (from word in abbreviationsWithOriginalWord.Keys
where (word.Length > 1 && word.Length < 4) || !wordsCheckResult.TryGetValue(word)
select word).ToJetHashSet();
Dumper.DumpToNotepad(w =>
{
w.WriteLine("Abbreviations:");
w.WriteLine();
foreach (var (abbr, originalWords) in abbreviationsWithOriginalWord.OrderBy(kv => kv.Key))
{
w.Write(abbr);
w.Write(": ");
foreach (var word in originalWords)
{
w.Write(word);
w.Write(", ");
}
w.WriteLine();
}
});
// Remove non-abbreviations, known typos or exclusions. Yes, this is all done by hand
RemoveNonAbbreviations(unknownAbbreviations);
RemoveTyposAndExclusions(unknownWords);
// Dump all words for diagnostics
Dumper.DumpToNotepad(w =>
{
w.WriteLine("All words");
w.WriteLine();
foreach (var (assembly, words) in wordsPerAssembly)
{
w.WriteLine("Words for assembly: " + assembly);
w.WriteLine();
foreach (var word in words.OrderBy(IdentityFunc<string>.Instance))
w.WriteLine(word.ToLowerInvariant());
w.WriteLine();
}
});
// Dump all abbreviations, so we can avoid naming standards inspections when an abbreviation is used.
// E.g. BlahBlahAABB is accepted instead of converted to BlahBlahAabb
Dumper.DumpToNotepad(w =>
{
w.WriteLine("Abbreviations");
w.WriteLine();
foreach (var abbreviation in unknownAbbreviations.OrderBy(IdentityFunc<string>.Instance))
w.WriteLine(abbreviation.ToUpperInvariant());
});
// Dump all unknown words, minus abbreviations, for use as a dictionary
Dumper.DumpToNotepad(w =>
{
var dictionary = new JetHashSet<string>(unknownWords, StringComparer.InvariantCultureIgnoreCase);
dictionary.ExceptWith(abbreviationsWithOriginalWord.Keys);
w.WriteLine("Dictionary (unknown words minus abbreviations)");
w.WriteLine();
w.WriteLine(dictionary.Count); // Hunspell dictionaries start with the number of words
foreach (var word in dictionary.OrderBy(IdentityFunc<string>.Instance))
w.WriteLine(word.ToLowerInvariant());
});
}