private static bool UnaccountedForFieldExists(LazinatorPairInformation lazinatorPairInformation, out List <FieldDeclarationSyntax> problemFields) { var fields = lazinatorPairInformation.LazinatorObjectLocationsExcludingCodeBehind.Select(x => { bool success = x.SourceTree.TryGetRoot(out SyntaxNode root); if (success) { return(root); } return(null); } ) .Where(x => x != null) .SelectMany(x => x.DescendantNodes().OfType <FieldDeclarationSyntax>()); var fieldsWithoutNonserializedAttribute = fields .Where(x => !x.Modifiers.Any(y => y.Kind() == SyntaxKind.StaticKeyword || y.Kind() == SyntaxKind.ConstKeyword)) .Where(x => !x.AttributeLists.Any( y => y.Attributes.Any( z => (z.Name as IdentifierNameSyntax)?.Identifier.Text == "NonSerialized" ) ) ) .ToList(); if (fieldsWithoutNonserializedAttribute.Any()) { problemFields = fieldsWithoutNonserializedAttribute; return(true); } problemFields = null; return(false); }
private void RememberLazinatorPair(LazinatorPairInformation lazinatorPairInfo) { if (lazinatorPairInfo != null) { Location key = lazinatorPairInfo.PrimaryLocation; CompilationInformation.AddOrUpdate(key, lazinatorPairInfo, (k, v) => lazinatorPairInfo); } }
private List <Diagnostic> GetExtraFileDiagnosticsToReport(LazinatorPairInformation lazinatorPairInfo) { List <Diagnostic> diagnosticsToReturn = new List <Diagnostic>(); List <Location> additionalLocations = GetAdditionalLocations(lazinatorPairInfo); additionalLocations.AddRange(lazinatorPairInfo.IncorrectCodeBehindLocations); diagnosticsToReturn.Add(Diagnostic.Create(LazinatorCodeAnalyzer.ExtraFileRule, lazinatorPairInfo.PrimaryLocation, additionalLocations, lazinatorPairInfo.GetSourceFileDictionary(_configPath, _configString))); return(diagnosticsToReturn); }
private static List <Location> GetAdditionalLocations(LazinatorPairInformation lazinatorPairInfo) { var additionalLocations = new List <Location>(); if (lazinatorPairInfo.CodeBehindLocation != null) { additionalLocations.Add(lazinatorPairInfo.CodeBehindLocation); } additionalLocations.AddRange(lazinatorPairInfo.LazinatorObjectLocationsExcludingCodeBehind); return(additionalLocations); }
private Diagnostic GetDiagnosticForGeneratable(LazinatorPairInformation lazinatorPairInfo, bool needsGeneration, Location locationOfImplementingType, Microsoft.CodeAnalysis.Text.TextSpan?textSpan, bool suppressRegenerate) { Location interfaceSpecificationLocation = Location.Create( locationOfImplementingType.SourceTree, textSpan.Value); List <Location> additionalLocations = GetAdditionalLocations(lazinatorPairInfo); if (suppressRegenerate && !needsGeneration) { return(null); } var diagnostic = Diagnostic.Create(needsGeneration ? LazinatorCodeAnalyzer.OutOfDateRule : LazinatorCodeAnalyzer.OptionalRegenerationRule, interfaceSpecificationLocation, additionalLocations, lazinatorPairInfo.GetSourceFileDictionary(_configPath, _configString)); return(diagnostic); }
private static void AssessGenerationFeasibility(LazinatorPairInformation lazinatorPairInfo, out bool couldBeGenerated, out bool needsGeneration) { couldBeGenerated = true; needsGeneration = lazinatorPairInfo.CodeBehindLocation == null; if (!needsGeneration) { var success = lazinatorPairInfo.CodeBehindLocation.SourceTree.TryGetRoot(out SyntaxNode root); if (success) { SyntaxTrivia possibleComment = root.DescendantTrivia().FirstOrDefault(); if (possibleComment.IsKind(SyntaxKind.SingleLineCommentTrivia)) { string commentContent = possibleComment.ToString().Substring(2); bool parse = Guid.TryParse(commentContent, out Guid codeBehindGuid); if (parse) { var interfaceLocations = lazinatorPairInfo.LazinatorInterface.Locations; if (interfaceLocations.Count() != 1) { couldBeGenerated = false; } else { var hash = LazinatorCompilation.GetHashForInterface(lazinatorPairInfo.LazinatorInterface, lazinatorPairInfo.LazinatorObject); if (hash != codeBehindGuid) { needsGeneration = true; } } } else { needsGeneration = true; } } else { needsGeneration = true; } } else { needsGeneration = true; } } }
public List <Diagnostic> GetDiagnosticsToReport(LazinatorPairInformation lazinatorPairInfo, bool suppressRegenerate) { bool couldBeGenerated, needsGeneration; AssessGenerationFeasibility(lazinatorPairInfo, out couldBeGenerated, out needsGeneration); if (needsGeneration || couldBeGenerated) { var locationOfImplementingType = lazinatorPairInfo.PrimaryLocation; var implementingTypeRoot = locationOfImplementingType.SourceTree.GetRoot(); TypeDeclarationSyntax implementingTypeSyntaxNode = (TypeDeclarationSyntax)implementingTypeRoot.FindNode(locationOfImplementingType.SourceSpan); Microsoft.CodeAnalysis.Text.TextSpan?textSpan = implementingTypeSyntaxNode.BaseList.Types .FirstOrDefault(x => (x.Type as IdentifierNameSyntax)?.Identifier.Text.Contains(lazinatorPairInfo.LazinatorInterface.Name) ?? (x.Type as GenericNameSyntax)?.Identifier.Text.Contains(lazinatorPairInfo.LazinatorInterface.Name) ?? false )?.Span; if (textSpan == null) { return(null); } else { List <Diagnostic> diagnosticsToReturn; if (UnaccountedForFieldExists(lazinatorPairInfo, out var problemFields)) { diagnosticsToReturn = problemFields.Select(x => Diagnostic.Create(LazinatorCodeAnalyzer.UnaccountedForFieldRule, x.GetLocation())).ToList(); } else { diagnosticsToReturn = new List <Diagnostic>(); } Diagnostic diagnosticForGeneratable = GetDiagnosticForGeneratable(lazinatorPairInfo, needsGeneration, locationOfImplementingType, textSpan, suppressRegenerate); if (diagnosticForGeneratable != null) { diagnosticsToReturn.Add(diagnosticForGeneratable); } return(diagnosticsToReturn); } } else { return(null); } }
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) { var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); var diagnostic = context.Diagnostics .Where(x => FixableDiagnosticIds.Contains(x.Id)) .FirstOrDefault(); if (diagnostic == null) { return; } LazinatorPairInformation lazinatorPairInfo = new LazinatorPairInformation( await context.Document.GetSemanticModelAsync(), diagnostic.Properties, diagnostic.AdditionalLocations); var diagnosticSpan = diagnostic.Location.SourceSpan; // Find the type declaration identified by the diagnostic. if (diagnostic.Id == LazinatorCodeAnalyzer.Lazin001 || diagnostic.Id == LazinatorCodeAnalyzer.Lazin002) { var syntaxToken = root.FindToken(diagnosticSpan.Start); var declaration = syntaxToken.Parent.AncestorsAndSelf().OfType <TypeDeclarationSyntax>().FirstOrDefault(); // Register a code action that will invoke the fix. string title = diagnostic.Id == LazinatorCodeAnalyzer.Lazin001 ? Lazin001Title : Lazin002Title; if (declaration != null) { context.RegisterCodeFix( CodeAction.Create( title: title, createChangedSolution: c => FixGenerateLazinatorCodeBehind(context.Document, declaration, lazinatorPairInfo, c), equivalenceKey: title), diagnostic); } } if (diagnostic.Id == LazinatorCodeAnalyzer.Lazin004) { string title = Lazin004Title; context.RegisterCodeFix( CodeAction.Create( title: title, createChangedSolution: c => DeleteExtraFiles(context.Document.Project.Solution, lazinatorPairInfo.IncorrectCodeBehindLocations, c), equivalenceKey: title), diagnostic); } }
private LazinatorPairInformation GetLazinatorPairInfo(Compilation compilation, INamedTypeSymbol lazinatorObjectType, INamedTypeSymbol namedInterfaceType) { var locationsExcludingCodeBehind = lazinatorObjectType.Locations .Where(x => !x.SourceTree.FilePath.EndsWith(GetGeneratedCodeFileExtension())) .ToList(); var codeBehindLocations = lazinatorObjectType.Locations .Where(x => x.SourceTree.FilePath.EndsWith(GetGeneratedCodeFileExtension())) .ToList(); // Complication: we should exclude code behind locations that are just for partial classes. // Similarly, if this is a partial class, we should exclude code behind locations for the main class. // We can figure this out by looking at the last partial class declared in the syntax tree and seeing if it matches. HashSet <Location> excludedCodeBehindLocations = null; foreach (Location location in codeBehindLocations) { var lastClassNode = location.SourceTree.GetRoot().DescendantNodes().Where(x => x is ClassDeclarationSyntax or StructDeclarationSyntax or RecordDeclarationSyntax).Select(x => (TypeDeclarationSyntax)x).LastOrDefault(); SemanticModel semanticModel = compilation.GetSemanticModel(location.SourceTree); INamedTypeSymbol mainClass = lastClassNode == null ? null : semanticModel.GetDeclaredSymbol(lastClassNode); if (!SymbolEqualityComparer.Default.Equals(mainClass, lazinatorObjectType)) { if (excludedCodeBehindLocations == null) { excludedCodeBehindLocations = new HashSet <Location>(); } excludedCodeBehindLocations.Add(location); } } // Now, make a list of all non-excluded locations List <Location> allLocations = lazinatorObjectType.Locations.Where(x => excludedCodeBehindLocations == null || !excludedCodeBehindLocations.Contains(x)).ToList(); var primaryLocation = locationsExcludingCodeBehind .FirstOrDefault(); if (primaryLocation != null) { LazinatorPairInformation lazinatorPairInfo = new LazinatorPairInformation(); lazinatorPairInfo.LazinatorObject = lazinatorObjectType; lazinatorPairInfo.LazinatorInterface = namedInterfaceType; (lazinatorPairInfo.CodeBehindLocation, lazinatorPairInfo.IncorrectCodeBehindLocations, lazinatorPairInfo.LazinatorObjectLocationsExcludingCodeBehind) = LazinatorPairInformation.CategorizeLocations(Config, lazinatorObjectType, allLocations); return(lazinatorPairInfo); } return(null); }
public static async Task <Solution> AttemptFixGenerateLazinatorCodeBehind(Document originalDocument, LazinatorPairInformation lazinatorPairInformation, CancellationToken cancellationToken) { var originalSolution = originalDocument.Project.Solution; LazinatorConfig config = lazinatorPairInformation.LoadLazinatorConfig(); var semanticModel = await originalDocument.GetSemanticModelAsync(cancellationToken); LazinatorCompilation generator = null; if (!RecycleLazinatorCompilation || _LastLazinatorCompilation == null) { generator = new LazinatorCompilation(semanticModel.Compilation, lazinatorPairInformation.LazinatorObject.Name, lazinatorPairInformation.LazinatorObject.GetFullMetadataName(), config); if (RecycleLazinatorCompilation) { _LastLazinatorCompilation = generator; } } else { generator = _LastLazinatorCompilation; generator.Initialize(semanticModel.Compilation, lazinatorPairInformation.LazinatorObject.Name, lazinatorPairInformation.LazinatorObject.GetFullMetadataName()); } var d = new ObjectDescription(generator.ImplementingTypeSymbol, generator, originalDocument.FilePath); var codeBehind = d.GetCodeBehind(); string fileExtension = config?.GeneratedCodeFileExtension ?? ".laz.cs"; var project = originalDocument.Project; string codeBehindFilePath = null; string codeBehindName = null; string[] codeBehindFolders = null; bool useFullyQualifiedNames = (config?.UseFullyQualifiedNames ?? false) || generator.ImplementingTypeSymbol.ContainingType != null || generator.ImplementingTypeSymbol.IsGenericType; codeBehindName = RoslynHelpers.GetEncodableVersionOfIdentifier(generator.ImplementingTypeSymbol, useFullyQualifiedNames) + fileExtension; string[] GetFolders(string fileName) { return(fileName.Split('\\', '/').Where(x => !x.Contains(".cs") && !x.Contains(".csproj")).ToArray()); } if (config?.GeneratedCodePath == null) { // use short form of name in same location as original code codeBehindFilePath = originalDocument.FilePath; codeBehindFolders = GetFolders(codeBehindFilePath).Skip(GetFolders(originalDocument.Project.FilePath).Count()).ToArray(); } else { // we have a config file specifying a common directory codeBehindFilePath = config.GeneratedCodePath; if (!codeBehindFilePath.EndsWith("\\")) { codeBehindFilePath += "\\"; } codeBehindFolders = config.RelativeGeneratedCodePath.Split('\\', '/'); } codeBehindFilePath = System.IO.Path.GetDirectoryName(codeBehindFilePath); while (codeBehindFilePath.EndsWith(".cs")) { var lastSlash = codeBehindFilePath.IndexOfAny(new char[] { '\\', '/' }); if (lastSlash >= 0) { codeBehindFilePath = codeBehindFilePath.Substring(0, lastSlash); } } while (codeBehindFilePath.EndsWith("\\")) { codeBehindFilePath = codeBehindFilePath.Substring(0, codeBehindFilePath.Length - 1); } codeBehindFilePath += "\\" + codeBehindName; Solution revisedSolution; if (lazinatorPairInformation.CodeBehindLocation == null) { // The file does not already exist // codeBehindFilePath = System.IO.Path.GetDirectoryName(codeBehindFilePath); // omit file name Document documentToAdd = project.AddDocument(codeBehindName, codeBehind, codeBehindFolders, codeBehindFilePath); //if (config.GeneratedCodePath != null) // documentToAdd = documentToAdd.WithFolders(codeBehindFolders); revisedSolution = documentToAdd.Project.Solution; } else { // the file does already exist var currentDocumentWithCode = originalSolution.GetDocument(lazinatorPairInformation.CodeBehindLocation.SourceTree); var replacementDocument = currentDocumentWithCode.WithText(SourceText.From(codeBehind)); revisedSolution = originalSolution.WithDocumentText(currentDocumentWithCode.Id, SourceText.From(codeBehind)); } revisedSolution = await AddAnnotationToIndicateSuccess(revisedSolution, true); return(revisedSolution); }
public static async Task <Solution> FixGenerateLazinatorCodeBehind(Document originalDocument, TypeDeclarationSyntax enclosingType, LazinatorPairInformation lazinatorPairInformation, CancellationToken cancellationToken) { if (!(enclosingType is ClassDeclarationSyntax or StructDeclarationSyntax or RecordDeclarationSyntax)) { throw new LazinatorCodeGenException("Could not fix. Attribute is not in a class or struct."); } try { if (originalDocument == null) { return(null); } Solution revisedSolution = await AttemptFixGenerateLazinatorCodeBehind(originalDocument, lazinatorPairInformation, cancellationToken); return(revisedSolution); } catch (LazinatorCodeGenException e) { Solution revisedSolution = await InsertLazinatorCodeGenerationError(originalDocument, enclosingType, e, cancellationToken); return(revisedSolution); } }