public override async Task RegisterCodeFixesAsync(CodeFixContext context) { var document = context.Document; var cancellationToken = context.CancellationToken; var syntaxRoot = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var compilationUnit = (CompilationUnitSyntax)syntaxRoot; #if CODE_STYLE var options = document.Project.AnalyzerOptions.GetAnalyzerOptionSet(syntaxRoot.SyntaxTree, cancellationToken); var simplifierOptions = CSharpSimplifierOptions.Create(options, fallbackOptions: null); #else var options = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); var simplifierOptions = await SimplifierOptions.FromDocumentAsync(document, fallbackOptions: context.Options(document.Project.LanguageServices).SimplifierOptions, cancellationToken).ConfigureAwait(false); #endif var codeStyleOption = options.GetOption(CSharpCodeStyleOptions.PreferredUsingDirectivePlacement); // Read the preferred placement option and verify if it can be applied to this code file. // There are cases where we will not be able to fix the diagnostic and the user will need to resolve // it manually. var (placement, preferPreservation) = DeterminePlacement(compilationUnit, codeStyleOption); if (preferPreservation) return; foreach (var diagnostic in context.Diagnostics) { context.RegisterCodeFix( CodeAction.Create( CSharpAnalyzersResources.Move_misplaced_using_directives, token => GetTransformedDocumentAsync(document, compilationUnit, GetAllUsingDirectives(compilationUnit), placement, simplifierOptions, token), nameof(CSharpAnalyzersResources.Move_misplaced_using_directives)), diagnostic); } }
internal static async Task<Document> TransformDocumentIfRequiredAsync( Document document, SimplifierOptions simplifierOptions, CodeStyleOption2<AddImportPlacement> importPlacementStyleOption, CancellationToken cancellationToken) { var syntaxRoot = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var compilationUnit = (CompilationUnitSyntax)syntaxRoot; var (placement, preferPreservation) = DeterminePlacement(compilationUnit, importPlacementStyleOption); if (preferPreservation) { return document; } // We are called from a diagnostic, but also for all new documents, so check if there are any usings at all // otherwise there is nothing to do. var allUsingDirectives = GetAllUsingDirectives(compilationUnit); if (allUsingDirectives.Count == 0) { return document; } return await GetTransformedDocumentAsync(document, compilationUnit, allUsingDirectives, placement, simplifierOptions, cancellationToken).ConfigureAwait(false); }
/// <summary> /// Checks a member access expression <c>expr.Name</c> and, if it is of the form <c>this.Name</c> or /// <c>Me.Name</c> determines if it is safe to replace with just <c>Name</c> alone. /// </summary> public bool ShouldSimplifyThisMemberAccessExpression( TMemberAccessExpressionSyntax?memberAccessExpression, SemanticModel semanticModel, SimplifierOptions simplifierOptions, [NotNullWhen(true)] out TThisExpressionSyntax?thisExpression, out ReportDiagnostic severity, CancellationToken cancellationToken) { severity = default; thisExpression = null; if (memberAccessExpression is null) { return(false); } var syntaxFacts = this.SyntaxFacts; if (!syntaxFacts.IsSimpleMemberAccessExpression(memberAccessExpression)) { return(false); } thisExpression = syntaxFacts.GetExpressionOfMemberAccessExpression(memberAccessExpression) as TThisExpressionSyntax; if (!syntaxFacts.IsThisExpression(thisExpression)) { return(false); } var symbolInfo = semanticModel.GetSymbolInfo(memberAccessExpression, cancellationToken); if (symbolInfo.Symbol == null) { return(false); } if (!simplifierOptions.TryGetQualifyMemberAccessOption(symbolInfo.Symbol.Kind, out var optionValue)) { return(false); } // We always simplify a static accesses off of this/me. Otherwise, we fall back to whatever the user's option is. if (!symbolInfo.Symbol.IsStatic && optionValue.Value) { return(false); } var speculationAnalyzer = GetSpeculationAnalyzer(semanticModel, memberAccessExpression, cancellationToken); var newSymbolInfo = speculationAnalyzer.SpeculativeSemanticModel.GetSymbolInfo(speculationAnalyzer.ReplacedExpression, cancellationToken); if (!symbolInfo.Symbol.Equals(newSymbolInfo.Symbol, SymbolEqualityComparer.IncludeNullability)) { return(false); } severity = optionValue.Notification.Severity; return(!semanticModel.SyntaxTree.OverlapsHiddenPosition(memberAccessExpression.Span, cancellationToken) && !MayCauseParseDifference(memberAccessExpression)); }
private async Task <string?> GetSimplifiedEnumNameAsync( Document document, string fullyQualifiedTypeName, string firstEnumMemberName, TextSpan caseGenerationLocation, SimplifierOptions simplifierOptions, CancellationToken cancellationToken) { // Insert switch with enum case into the document. var(documentWithFullyQualified, fullyQualifiedTypeLocation) = await GetDocumentWithEnumCaseAsync(document, fullyQualifiedTypeName, firstEnumMemberName, caseGenerationLocation, cancellationToken).ConfigureAwait(false); // Simplify enum case. var simplifiedEnum = await GetSimplifiedTypeNameAtSpanAsync(documentWithFullyQualified, fullyQualifiedTypeLocation, simplifierOptions, cancellationToken).ConfigureAwait(false); return(simplifiedEnum); }
public void OptionsAreMessagePackSerializable(string language) { var messagePackOptions = MessagePackSerializerOptions.Standard.WithResolver(MessagePackFormatters.DefaultResolver); using var workspace = new AdhocWorkspace(); var languageServices = workspace.Services.GetLanguageServices(language); var options = new object[] { SimplifierOptions.GetDefault(languageServices), SyntaxFormattingOptions.GetDefault(languageServices), CodeCleanupOptions.GetDefault(languageServices), CodeGenerationOptions.GetDefault(languageServices), IdeCodeStyleOptions.GetDefault(languageServices), CodeActionOptions.GetDefault(languageServices), IndentationOptions.GetDefault(languageServices), ExtractMethodGenerationOptions.GetDefault(languageServices), // some non-default values: new VisualBasicIdeCodeStyleOptions( new IdeCodeStyleOptions.CommonOptions() { AllowStatementImmediatelyAfterBlock = new CodeStyleOption2 <bool>(false, NotificationOption2.Error) }, PreferredModifierOrder: new CodeStyleOption2 <string>("Public Private", NotificationOption2.Error)) }; foreach (var original in options) { using var stream = new MemoryStream(); MessagePackSerializer.Serialize(stream, original, messagePackOptions); stream.Position = 0; var deserialized = MessagePackSerializer.Deserialize(original.GetType(), stream, messagePackOptions); Assert.Equal(original, deserialized); } }
private static async Task <Document> GetTransformedDocumentAsync( Document document, CompilationUnitSyntax compilationUnit, IEnumerable <UsingDirectiveSyntax> allUsingDirectives, AddImportPlacement placement, SimplifierOptions simplifierOptions, CancellationToken cancellationToken) { var bannerService = document.GetRequiredLanguageService <IFileBannerFactsService>(); // Expand usings so that they can be properly simplified after they are relocated. var compilationUnitWithExpandedUsings = await ExpandUsingDirectivesAsync(document, compilationUnit, allUsingDirectives, cancellationToken).ConfigureAwait(false); // Remove the file header from the compilation unit so that we do not lose it when making changes to usings. var(compilationUnitWithoutHeader, fileHeader) = RemoveFileHeader(compilationUnitWithExpandedUsings, bannerService); // A blanket warning that this codefix may change code so that it does not compile. var warningAnnotation = WarningAnnotation.Create(CSharpAnalyzersResources.Warning_colon_Moving_using_directives_may_change_code_meaning); var newCompilationUnit = placement == AddImportPlacement.InsideNamespace ? MoveUsingsInsideNamespace(compilationUnitWithoutHeader, warningAnnotation) : MoveUsingsOutsideNamespaces(compilationUnitWithoutHeader, warningAnnotation); // Re-attach the header now that using have been moved and LeadingTrivia is no longer being altered. var newCompilationUnitWithHeader = AddFileHeader(newCompilationUnit, fileHeader); var newDocument = document.WithSyntaxRoot(newCompilationUnitWithHeader); // Simplify usings now that they have been moved and are in the proper context. #if CODE_STYLE #pragma warning disable RS0030 // Do not used banned APIs (ReduceAsync with SimplifierOptions isn't public) return(await Simplifier.ReduceAsync(newDocument, Simplifier.Annotation, optionSet : null, cancellationToken).ConfigureAwait(false)); #pragma warning restore #else return(await Simplifier.ReduceAsync(newDocument, Simplifier.Annotation, simplifierOptions, cancellationToken).ConfigureAwait(false)); #endif }
public bool OpenFileOnly(SimplifierOptions options) { return(true); }
ValueTask <SimplifierOptions> OptionsProvider <SimplifierOptions> .GetOptionsAsync(HostLanguageServices languageServices, CancellationToken cancellationToken) => ValueTaskFactory.FromResult(GetOptions(languageServices).CleanupOptions?.SimplifierOptions ?? SimplifierOptions.GetDefault(languageServices));
private static async Task <string?> GetSimplifiedTypeNameAtSpanAsync(Document documentWithFullyQualifiedTypeName, TextSpan fullyQualifiedTypeSpan, SimplifierOptions simplifierOptions, CancellationToken cancellationToken) { // Simplify var typeAnnotation = new SyntaxAnnotation(); var syntaxRoot = await documentWithFullyQualifiedTypeName.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var nodeToReplace = syntaxRoot.DescendantNodes().FirstOrDefault(n => n.Span == fullyQualifiedTypeSpan); if (nodeToReplace == null) { return(null); } var updatedRoot = syntaxRoot.ReplaceNode(nodeToReplace, nodeToReplace.WithAdditionalAnnotations(typeAnnotation, Simplifier.Annotation)); var documentWithAnnotations = documentWithFullyQualifiedTypeName.WithSyntaxRoot(updatedRoot); var simplifiedDocument = await Simplifier.ReduceAsync(documentWithAnnotations, simplifierOptions, cancellationToken).ConfigureAwait(false); var simplifiedRoot = await simplifiedDocument.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var simplifiedTypeName = simplifiedRoot.GetAnnotatedNodesAndTokens(typeAnnotation).Single().ToString(); return(simplifiedTypeName); }
public bool OpenFileOnly(SimplifierOptions options) => false;
public sealed override bool IsApplicable(SimplifierOptions options) => IsApplicable((CSharpSimplifierOptions)options);
private static SyntaxNode SimplifyAnonymousTypeMemberName(AnonymousObjectMemberDeclaratorSyntax node, SemanticModel semanticModel, SimplifierOptions options, CancellationToken canellationToken) { if (CanSimplifyAnonymousTypeMemberName(node)) { return(node.WithNameEquals(null).WithTriviaFrom(node)); } return(node); }
private ArgumentSyntax SimplifyTupleName(ArgumentSyntax node, SemanticModel semanticModel, SimplifierOptions options, CancellationToken cancellationToken) { if (CanSimplifyTupleElementName(node, this.ParseOptions)) { return(node.WithNameColon(null).WithTriviaFrom(node)); } return(node); }
/// <summary> /// For a document with the default switch snippet inserted, generate the expanded set of cases based on the value /// of the field currently inserted into the switch statement. /// </summary> public async Task <string?> GetSwitchExpansionAsync(Document document, TextSpan caseGenerationLocation, TextSpan switchExpressionLocation, SimplifierOptions simplifierOptions, CancellationToken cancellationToken) { var typeSymbol = await GetEnumSymbolAsync(document, switchExpressionLocation, cancellationToken).ConfigureAwait(false); if (typeSymbol?.TypeKind != TypeKind.Enum) { return(null); } var enumFields = typeSymbol.GetMembers().Where(m => m.Kind == SymbolKind.Field && m.IsStatic); if (!enumFields.Any()) { return(null); } // Find and use the most simplified legal version of the enum type name in this context var fullyQualifiedEnumName = typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); var simplifiedTypeName = await GetSimplifiedEnumNameAsync(document, fullyQualifiedEnumName, enumFields.First().Name, caseGenerationLocation, simplifierOptions, cancellationToken).ConfigureAwait(false); if (simplifiedTypeName == null) { return(null); } using var _ = PooledStringBuilder.GetInstance(out var casesBuilder); foreach (var member in enumFields) { casesBuilder.AppendFormat(SwitchCaseFormat, simplifiedTypeName, member.Name); } casesBuilder.Append(SwitchDefaultCaseForm); return(casesBuilder.ToString()); }
/// <summary> /// For a specified snippet field, replace it with the fully qualified name then simplify in the context of the document /// in order to retrieve the simplified type name. /// </summary> public static async Task <string?> GetSimplifiedTypeNameAsync(Document document, TextSpan fieldSpan, string fullyQualifiedTypeName, SimplifierOptions simplifierOptions, CancellationToken cancellationToken) { // Insert the function parameter (fully qualified type name) into the document. var updatedTextSpan = new TextSpan(fieldSpan.Start, fullyQualifiedTypeName.Length); var textChange = new TextChange(fieldSpan, fullyQualifiedTypeName); var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); var documentWithFullyQualifiedTypeName = document.WithText(text.WithChanges(textChange)); // Simplify var simplifiedTypeName = await GetSimplifiedTypeNameAtSpanAsync(documentWithFullyQualifiedTypeName, updatedTextSpan, simplifierOptions, cancellationToken).ConfigureAwait(false); return(simplifiedTypeName); }
/// <summary> /// Formats the snippet by applying the snippet to the document with the default values / function results for snippet declarations. /// Then converts back into an LSP snippet by replacing the declarations with the appropriate LSP tab stops. /// /// Note that the operations in this method are sensitive to the context in the document and so must be calculated on each request. /// </summary> private static async Task <string> GetFormattedLspSnippetAsync( ParsedXmlSnippet parsedSnippet, TextSpan snippetShortcut, Document originalDocument, SourceText originalSourceText, SyntaxFormattingOptions formattingOptions, SimplifierOptions simplifierOptions, CancellationToken cancellationToken) { // Calculate the snippet text with defaults + snippet function results. var(snippetFullText, fields, caretSpan) = await GetReplacedSnippetTextAsync( originalDocument, originalSourceText, snippetShortcut, parsedSnippet, simplifierOptions, cancellationToken).ConfigureAwait(false); // Create a document with the default snippet text that we can use to format the snippet. var textChange = new TextChange(snippetShortcut, snippetFullText); var snippetEndPosition = textChange.Span.Start + textChange.NewText !.Length; var documentWithSnippetText = originalSourceText.WithChanges(textChange); var root = await originalDocument.WithText(documentWithSnippetText).GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var spanToFormat = TextSpan.FromBounds(textChange.Span.Start, snippetEndPosition); var formattingChanges = Formatter.GetFormattedTextChanges(root, spanToFormat, originalDocument.Project.Solution.Services, formattingOptions, cancellationToken: cancellationToken) ?.ToImmutableArray() ?? ImmutableArray <TextChange> .Empty; var formattedText = documentWithSnippetText.WithChanges(formattingChanges); // We now have a formatted snippet with default values. We need to // replace the fields and caret with the proper LSP tab stop notation. // Since formatting changes are entirely whitespace, we can calculate the new locations by // adjusting the old spans based on the formatting changes that occured before them. // Get the adjusted snippet bounds. snippetEndPosition = GetAdjustedSpan(formattingChanges, new TextSpan(snippetEndPosition, 0)).Start; var spanContainingFormattedSnippet = TextSpan.FromBounds(snippetShortcut.Start, snippetEndPosition); // Get the adjusted fields and determine the text edits to make LSP formatted tab stops. using var _1 = ArrayBuilder <TextChange> .GetInstance(out var lspTextChanges); foreach (var(field, spans) in fields) { var lspTextForField = string.IsNullOrEmpty(field.DefaultText) ? $"${{{field.EditIndex}}}" : $"${{{field.EditIndex}:{field.DefaultText}}}"; foreach (var span in spans) { // Adjust the span based on the formatting changes and build the snippet text change. var fieldInFormattedText = GetAdjustedSpan(formattingChanges, span); var fieldInSnippetContext = GetTextSpanInContextOfSnippet(fieldInFormattedText.Start, spanContainingFormattedSnippet.Start, fieldInFormattedText.Length); lspTextChanges.Add(new TextChange(fieldInSnippetContext, lspTextForField)); } } // Get the adjusted caret location and replace the placeholder comment with the LSP formatted tab stop. if (caretSpan != null) { var caretInFormattedText = GetAdjustedSpan(formattingChanges, caretSpan.Value); var caretInSnippetContext = GetTextSpanInContextOfSnippet(caretInFormattedText.Start, spanContainingFormattedSnippet.Start, caretInFormattedText.Length); lspTextChanges.Add(new TextChange(caretInSnippetContext, "$0")); } // Apply all the text changes to get the text formatted as the LSP snippet syntax. var formattedLspSnippetText = formattedText.GetSubText(spanContainingFormattedSnippet).WithChanges(lspTextChanges); return(formattedLspSnippetText.ToString());
public async Task <SnippetFunctionPart> WithSnippetFunctionResultAsync(Document documentWithSnippet, TextSpan fieldSpan, SimplifierOptions simplifierOptions, CancellationToken cancellationToken) { var snippetFunctionService = documentWithSnippet.Project.GetRequiredLanguageService <SnippetFunctionService>(); switch (FunctionName) { case "SimpleTypeName": if (FunctionParam == null) { return(this); } var simplifiedTypeName = await SnippetFunctionService.GetSimplifiedTypeNameAsync(documentWithSnippet, fieldSpan, FunctionParam, simplifierOptions, cancellationToken).ConfigureAwait(false); if (simplifiedTypeName == null) { return(this); } return(this with { DefaultText = simplifiedTypeName });
public static async ValueTask <SimplifierOptions> GetSimplifierOptionsAsync(this Document document, SimplifierOptions?fallbackOptions, CancellationToken cancellationToken) { var documentOptions = await document.GetOptionsAsync(cancellationToken).ConfigureAwait(false); return(SimplifierOptions.Create(documentOptions, document.Project.Solution.Workspace.Services, fallbackOptions, document.Project.Language)); }