|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Data;
using Microsoft.CodeAnalysis.EditorConfig.Parsing.NamingStyles;
using Microsoft.CodeAnalysis.NamingStyles;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Text;
using static Microsoft.CodeAnalysis.EditorConfig.Parsing.NamingStyles.EditorConfigNamingStylesParser;
using RoslynEnumerableExtensions = Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Extensions.EnumerableExtensions;
namespace Microsoft.CodeAnalysis.Editor.EditorConfigSettings.Updater;
internal partial class NamingStyleSettingsUpdater(Workspace workspace, IGlobalOptionService globalOptions, string editorconfigPath) : SettingsUpdaterBase<(Action<(object, object?)> onSettingChange, NamingStyleSetting option), object>(workspace, editorconfigPath)
{
public readonly IGlobalOptionService GlobalOptions = globalOptions;
protected override SourceText? GetNewText(
SourceText analyzerConfigDocument,
IReadOnlyList<((Action<(object, object?)> onSettingChange, NamingStyleSetting option) option, object value)> settingsToUpdate,
CancellationToken token)
{
var result = Parse(analyzerConfigDocument, EditorconfigPath);
if (!result.Rules.Any() && settingsToUpdate.Any())
{
// handle no naming style rules in the editorconfig file.
// The implementation does not allow naming style rules to layer meaning all rules are either
// defined in Visual Studios settings or in an editorconfig file.
analyzerConfigDocument = analyzerConfigDocument.WithNamingStyles(GlobalOptions);
result = Parse(analyzerConfigDocument, EditorconfigPath);
}
foreach (var ((onSettingChange, option), value) in settingsToUpdate)
{
if (result.TryGetParseResultForRule(option, out var parseResult))
{
var endOfSection = new TextSpan(parseResult.Section.Span.End, 0);
if (value is ReportDiagnostic enforcement)
{
var newLine = $"dotnet_naming_rule.{parseResult.RuleName.Value}.severity = {enforcement.ToEditorConfigString()}";
analyzerConfigDocument = UpdateDocument(analyzerConfigDocument, newLine, parseResult.Severity.Span, endOfSection);
result = Parse(analyzerConfigDocument, EditorconfigPath);
onSettingChange((enforcement, null));
}
if (value is NamingStyle prevStyle)
{
var allCurrentStyles = result.Rules.Select(x => x.NamingScheme).Distinct().Select(x => (x, style: x.AsNamingStyle()));
var styleParseResult = TryGetStyleParseResult(prevStyle, allCurrentStyles);
var allDistinctStyles = RoslynEnumerableExtensions.DistinctBy(allCurrentStyles.Select(x => x.style), x => x.Name).ToArray();
if (styleParseResult is (NamingScheme namingScheme, NamingStyle style))
{
var newLine = $"dotnet_naming_rule.{parseResult.RuleName.Value}.style = {namingScheme.OptionName.Value}";
analyzerConfigDocument = UpdateDocument(analyzerConfigDocument, newLine, parseResult.NamingScheme.OptionName.Span, endOfSection);
result = Parse(analyzerConfigDocument, EditorconfigPath);
onSettingChange((style, allDistinctStyles));
}
continue;
}
}
}
return analyzerConfigDocument;
static (NamingScheme? scheme, NamingStyle style) TryGetStyleParseResult(
NamingStyle prevStyle,
IEnumerable<(NamingScheme scheme, NamingStyle style)> allCurrentStyles)
{
foreach (var (scheme, currentStyle) in allCurrentStyles)
{
if (prevStyle.Prefix == currentStyle.Prefix &&
prevStyle.Suffix == currentStyle.Suffix &&
prevStyle.WordSeparator == currentStyle.WordSeparator &&
prevStyle.CapitalizationScheme == currentStyle.CapitalizationScheme)
{
return (scheme, currentStyle);
}
}
return (null, default);
}
static SourceText UpdateDocument(SourceText sourceText, string newLine, TextSpan? potentialSpan, TextSpan backupSpan)
{
if (potentialSpan is null)
{
// there is no place to update in the current document instead
// we are appending to the end of a section so we need to add a newline
newLine = "\r\n" + newLine;
}
var span = potentialSpan ?? backupSpan;
var textChange = new TextChange(span, newLine);
return sourceText.WithChanges(textChange);
}
}
}
|