|
// 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.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Windows;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Notification;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities;
using Roslyn.Utilities;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.ChangeSignature;
internal class AddParameterDialogViewModel : AbstractNotifyPropertyChanged
{
private readonly INotificationService? _notificationService;
public readonly Document Document;
public readonly int PositionForTypeBinding;
private readonly SemanticModel _semanticModel;
public AddParameterDialogViewModel(Document document, int positionForTypeBinding)
{
_notificationService = document.Project.Solution.Services.GetService<INotificationService>();
_semanticModel = document.GetRequiredSemanticModelAsync(CancellationToken.None).AsTask().WaitAndGetResult_CanCallOnBackground(CancellationToken.None);
TypeIsEmptyImage = Visibility.Visible;
TypeBindsImage = Visibility.Collapsed;
TypeDoesNotParseOrInvalidTypeImage = Visibility.Collapsed;
TypeDoesNotBindImage = Visibility.Collapsed;
TypeBindsDynamicStatus = ServicesVSResources.Please_enter_a_type_name;
Document = document;
PositionForTypeBinding = positionForTypeBinding;
IsRequired = true;
IsCallsiteRegularValue = true;
ParameterName = string.Empty;
CallSiteValue = string.Empty;
DefaultValue = string.Empty;
}
public string ParameterName { get; set; }
public string CallSiteValue { get; set; }
private static readonly SymbolDisplayFormat s_symbolDisplayFormat = new(
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes);
public ITypeSymbol? TypeSymbol { get; set; }
public string? TypeName => TypeSymbol?.ToDisplayString(s_symbolDisplayFormat);
public bool TypeBinds => !TypeSymbol!.IsErrorType();
private bool _isRequired;
public bool IsRequired
{
get => _isRequired;
set
{
if (SetProperty(ref _isRequired, value))
{
NotifyPropertyChanged(nameof(IsOptional));
if (IsCallsiteOmitted)
{
IsCallsiteOmitted = false;
IsCallsiteRegularValue = true;
NotifyPropertyChanged(nameof(IsCallsiteOmitted));
NotifyPropertyChanged(nameof(IsCallsiteRegularValue));
}
}
}
}
public bool IsOptional
{
get => !_isRequired;
set
{
if (_isRequired == value)
{
IsRequired = !value;
}
}
}
public string DefaultValue { get; set; }
public bool IsCallsiteTodo { get; set; }
public bool IsCallsiteOmitted { get; set; }
public bool IsCallsiteInferred { get; set; }
public bool IsCallsiteRegularValue { get; set; } = true;
public bool UseNamedArguments { get; set; }
public string TypeBindsDynamicStatus { get; set; }
public Visibility TypeBindsImage { get; set; }
public Visibility TypeDoesNotBindImage { get; set; }
public Visibility TypeDoesNotParseOrInvalidTypeImage { get; set; }
public Visibility TypeIsEmptyImage { get; set; }
private string _verbatimTypeName = string.Empty;
public string VerbatimTypeName
{
get => _verbatimTypeName;
set
{
if (SetProperty(ref _verbatimTypeName, value))
{
SetCurrentTypeTextAndUpdateBindingStatus(value);
}
}
}
private bool _isVoidParameterType;
internal bool CanSubmit([NotNullWhen(false)] out string? message)
{
if (string.IsNullOrEmpty(VerbatimTypeName) || string.IsNullOrEmpty(ParameterName))
{
message = ServicesVSResources.A_type_and_name_must_be_provided;
return false;
}
if (_isVoidParameterType)
{
message = ServicesVSResources.SystemVoid_is_not_a_valid_type_for_a_parameter;
return false;
}
if (TypeSymbol == null || !IsParameterTypeSyntacticallyValid(VerbatimTypeName))
{
message = ServicesVSResources.Parameter_type_contains_invalid_characters;
return false;
}
if (!IsParameterNameValid(ParameterName))
{
message = ServicesVSResources.Parameter_name_contains_invalid_characters;
return false;
}
if (IsCallsiteRegularValue && string.IsNullOrWhiteSpace(CallSiteValue))
{
message = ServicesVSResources.Enter_a_call_site_value_or_choose_a_different_value_injection_kind;
return false;
}
if (IsOptional && string.IsNullOrWhiteSpace(DefaultValue))
{
message = ServicesVSResources.Optional_parameters_must_provide_a_default_value;
return false;
}
message = null;
return true;
}
internal bool TrySubmit()
{
if (!CanSubmit(out var message))
{
SendFailureNotification(message);
return false;
}
return true;
}
private void SendFailureNotification(string message)
{
_notificationService?.SendNotification(message, severity: NotificationSeverity.Information);
}
private void SetCurrentTypeTextAndUpdateBindingStatus(string typeName)
{
VerbatimTypeName = typeName;
_isVoidParameterType = false;
if (string.IsNullOrWhiteSpace(typeName))
{
TypeIsEmptyImage = Visibility.Visible;
TypeDoesNotParseOrInvalidTypeImage = Visibility.Collapsed;
TypeDoesNotBindImage = Visibility.Collapsed;
TypeBindsImage = Visibility.Collapsed;
TypeBindsDynamicStatus = ServicesVSResources.Please_enter_a_type_name;
TypeSymbol = null;
}
else
{
TypeIsEmptyImage = Visibility.Collapsed;
var languageService = Document.GetRequiredLanguageService<IChangeSignatureViewModelFactoryService>();
TypeSymbol = _semanticModel.GetSpeculativeTypeInfo(PositionForTypeBinding, languageService.GetTypeNode(typeName), SpeculativeBindingOption.BindAsTypeOrNamespace).Type;
if (TypeSymbol is { SpecialType: SpecialType.System_Void })
{
_isVoidParameterType = true;
TypeDoesNotParseOrInvalidTypeImage = Visibility.Visible;
TypeDoesNotBindImage = Visibility.Collapsed;
TypeBindsImage = Visibility.Collapsed;
TypeBindsDynamicStatus = ServicesVSResources.SystemVoid_is_not_a_valid_type_for_a_parameter;
}
else if (!IsParameterTypeSyntacticallyValid(typeName) || TypeSymbol == null)
{
TypeDoesNotParseOrInvalidTypeImage = Visibility.Visible;
TypeDoesNotBindImage = Visibility.Collapsed;
TypeBindsImage = Visibility.Collapsed;
TypeBindsDynamicStatus = ServicesVSResources.Type_name_has_a_syntax_error;
}
else
{
var parameterTypeBinds = DoesTypeFullyBind(TypeSymbol);
TypeDoesNotParseOrInvalidTypeImage = Visibility.Collapsed;
TypeBindsImage = parameterTypeBinds ? Visibility.Visible : Visibility.Collapsed;
TypeDoesNotBindImage = !parameterTypeBinds ? Visibility.Visible : Visibility.Collapsed;
TypeBindsDynamicStatus = parameterTypeBinds
? ServicesVSResources.Type_name_is_recognized
: ServicesVSResources.Type_name_is_not_recognized;
}
}
NotifyPropertyChanged(nameof(TypeBindsDynamicStatus));
NotifyPropertyChanged(nameof(TypeBindsImage));
NotifyPropertyChanged(nameof(TypeDoesNotBindImage));
NotifyPropertyChanged(nameof(TypeDoesNotParseOrInvalidTypeImage));
NotifyPropertyChanged(nameof(TypeIsEmptyImage));
}
private bool IsParameterTypeSyntacticallyValid(string typeName)
{
var languageService = Document.GetRequiredLanguageService<IChangeSignatureViewModelFactoryService>();
return languageService.IsTypeNameValid(typeName);
}
private static bool DoesTypeFullyBind(ITypeSymbol? type)
{
if (type == null || type.IsErrorType())
{
return false;
}
foreach (var typeArgument in type.GetTypeArguments())
{
if (typeArgument is ITypeParameterSymbol)
{
return false;
}
if (!DoesTypeFullyBind(typeArgument))
{
return false;
}
}
return true;
}
private bool IsParameterNameValid(string identifierName)
{
var languageService = Document.GetRequiredLanguageService<ISyntaxFactsService>();
return languageService.IsValidIdentifier(identifierName);
}
}
|