Example #1
0
        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;
                }
            }
        }
Example #2
0
        public async Task CanParseInterfaceAttributes()
        {
            try
            {
                LazinatorCompilation lazinatorFiles = await GetMiniRoslynFileSet(typeof(Example));

                INamedTypeSymbol exampleInterface         = lazinatorFiles.LookupSymbol("IExample");
                ImmutableArray <AttributeData> attributes = exampleInterface.GetAttributes();
                attributes.Count().Should().BeGreaterOrEqualTo(1);
                Attribute converted = AttributeConverter.ConvertAttribute(attributes[0]);
                LazinatorAnalyzer.AttributeClones.CloneLazinatorAttribute cloneLazinatorAttribute = converted as LazinatorAnalyzer.AttributeClones.CloneLazinatorAttribute;
                cloneLazinatorAttribute.UniqueID.Should().NotBe(0);
            }
            catch (IOException)
            {
                // occurs rarely if other process is looking up same file at same time, so let's ignore it
            }
        }
Example #3
0
        private static async Task CompleteGenerateCode(Type existingType, string project, string mainFolder, string subfolder, AdhocWorkspace ws)
        {
            if (mainFolder == "" && subfolder == "")
            {
                mainFolder = "/";
            }
            if (existingType.IsInterface)
            {
                throw new Exception("Can complete generate code only on class implementing interface, not interface itself.");
            }
            string projectPath = ReadCodeFile.GetCodeBasePath(project);
            string name        = ReadCodeFile.GetNameOfType(existingType);

            ReadCodeFile.GetCodeInFile(projectPath, mainFolder, subfolder, name, ".g.cs", out string codeBehindPath, out string codeBehind);
            LazinatorConfig config = FindConfigFileStartingFromSubfolder(mainFolder, subfolder, projectPath);

            // uncomment to include tracing code
            //if (config == null)
            //    config = new LazinatorConfig();
            //config.IncludeTracingCode = true;

            var compilation = await AdhocWorkspaceManager.GetCompilation(ws);

            LazinatorCompilation lazinatorCompilation = new LazinatorCompilation(compilation, existingType, config);

            var  d      = new ObjectDescription(lazinatorCompilation.ImplementingTypeSymbol, lazinatorCompilation, codeBehindPath, true);
            var  result = d.GetCodeBehind();
            bool match  = codeBehind == result;

            // return; // uncomment this to prevent any changes to classes during testing if automaticallyFix is true

            bool automaticallyFix = true; // Set to true to automatically update all test classes on the local development machine to a new format. This will trivially result in the test passing. Before doing this, submit all changes. After doing this, if code compiles, run all tests. Then set this back to false.

            if (automaticallyFix && !match)
            {
                File.WriteAllText(codeBehindPath, result);
            }
            else
            {
                match.Should().BeTrue();
            }
        }
Example #4
0
        public NonexclusiveInterfaceDescription(LazinatorCompilation fileSet, INamedTypeSymbol t, NullableContext nullableContextSetting, ObjectDescription container)
        {
            string typeName = LazinatorCompilation.TypeSymbolToString(t);

            NullableContextSetting = nullableContextSetting;
            if (!fileSet.NonexclusiveInterfaces.Contains(typeName))
            {
                throw new LazinatorCodeGenException("NonexclusiveLazinator must be applied to a nonexclusive interface.");
            }
            Container = container;
            CloneNonexclusiveLazinatorAttribute nonexclusiveLazinatorAttribute = fileSet.GetFirstAttributeOfType <CloneNonexclusiveLazinatorAttribute>(t);

            if (nonexclusiveLazinatorAttribute == null)
            {
                throw new LazinatorCodeGenException("Expected nonexclusive self-serialize attribute.");
            }
            if (fileSet.PropertiesForType.ContainsKey(typeName))
            {
                Properties = fileSet.PropertiesForType[typeName].Select(x => new PropertyDescription(x.Property, container, NullableContextSetting, null, null, false)).ToList();
            }
        }
Example #5
0
        public async Task CanParseSupportedCollections()
        {
            LazinatorCompilation lazinatorFiles = await GetMiniRoslynFileSet(typeof(DotNetList_Lazinator));

            string interfaceName   = "IDotNetList_Lazinator";
            var    interfaceSymbol = lazinatorFiles.LookupSymbol(interfaceName);
            var    properties      = lazinatorFiles.PropertiesForType[LazinatorCompilation.TypeSymbolToString(interfaceSymbol)];
            var    property        = properties.First().Property;

            property.Type.Name.Should().Be("List");
            (property.Type is INamedTypeSymbol).Should().BeTrue();
            var namedType = property.Type as INamedTypeSymbol;

            namedType.TypeArguments.Count().Should().Be(1);
            var typeArgument = namedType.TypeArguments[0];

            typeArgument.Name.Should().Be("ExampleChild");
            INamedTypeSymbol exampleChildClass = lazinatorFiles.LookupSymbol(typeArgument.Name);

            exampleChildClass.IsReferenceType.Should().BeTrue();
        }
Example #6
0
        // The following are commented out because they are slow.

        //[Fact]
        //public async Task CanParseInterfacePropertiesWithInheritance()
        //{
        //    LazinatorCompilation lazinatorFiles = await GetMiniRoslynFileSet(typeof(ExampleChildInherited));

        //    // load the inherited interface and make sure its properties and base properties can be parsed
        //    string interfaceName = nameof(IExampleChildInherited);
        //    var properties = lazinatorFiles.PropertiesForType[lazinatorFiles.LookupSymbol(interfaceName)];
        //    var propertiesThisLevel = properties.Where(x => x.LevelInfo == PropertyWithDefinitionInfo.Level.IsDefinedThisLevel).Select(x => x.Property).ToList();
        //    var propertiesLowerLevels = properties.Where(x => x.LevelInfo != PropertyWithDefinitionInfo.Level.IsDefinedThisLevel).Select(x => x.Property).ToList();
        //    propertiesThisLevel.Count().Should().Be(1);
        //    propertiesLowerLevels.Count().Should().Be(5);
        //    propertiesThisLevel[0].Name.Should().Be("MyInt");
        //    propertiesThisLevel[0].GetMethod.Name.Should().Be("get_MyInt");
        //    propertiesThisLevel[0].SetMethod.Name.Should().Be("set_MyInt");
        //    propertiesThisLevel[0].Type.Name.Should().Be("Int32");
        //    propertiesLowerLevels[0].Name.Should().Be("ByteSpan");
        //    propertiesLowerLevels[0].GetMethod.Name.Should().Be("get_ByteSpan");
        //    propertiesLowerLevels[0].SetMethod.Name.Should().Be("set_ByteSpan");
        //    propertiesLowerLevels[0].Type.Name.Should().Be("ReadOnlySpan");
        //    propertiesLowerLevels[1].Name.Should().Be("MyExampleGrandchild");
        //    propertiesLowerLevels[1].GetMethod.Name.Should().Be("get_MyExampleGrandchild");
        //    propertiesLowerLevels[1].SetMethod.Name.Should().Be("set_MyExampleGrandchild");
        //    propertiesLowerLevels[1].Type.Name.Should().Be("ExampleGrandchild");
        //    propertiesLowerLevels[2].Name.Should().Be("MyLong");
        //    propertiesLowerLevels[2].GetMethod.Name.Should().Be("get_MyLong");
        //    propertiesLowerLevels[2].SetMethod.Name.Should().Be("set_MyLong");
        //    propertiesLowerLevels[2].Type.Name.Should().Be("Int64");
        //    propertiesLowerLevels[3].Name.Should().Be("MyShort");
        //    propertiesLowerLevels[3].GetMethod.Name.Should().Be("get_MyShort");
        //    propertiesLowerLevels[3].SetMethod.Name.Should().Be("set_MyShort");
        //    propertiesLowerLevels[3].Type.Name.Should().Be("Int16");
        //    propertiesLowerLevels[4].Name.Should().Be("MyWrapperContainer");
        //    propertiesLowerLevels[4].GetMethod.Name.Should().Be("get_MyWrapperContainer");
        //    propertiesLowerLevels[4].SetMethod.Name.Should().Be("set_MyWrapperContainer");
        //    propertiesLowerLevels[4].Type.Name.Should().Be("WrapperContainer");
        //}

        //[Fact]
        //public async Task CanParseIntermediateInterfacePropertiesWithInheritance()
        //{
        //    LazinatorCompilation lazinatorFiles = await GetMiniRoslynFileSet(typeof(ExampleChild));

        //    // make sure we can also parse the intermediate type IExampleChild
        //    string intermediateInterfaceName = nameof(IExampleChild);
        //    var properties = lazinatorFiles.PropertiesForType[lazinatorFiles.LookupSymbol(intermediateInterfaceName)];
        //    var propertiesThisLevel = properties.Where(x => x.LevelInfo == PropertyWithDefinitionInfo.Level.IsDefinedThisLevel).Select(x => x.Property).ToList();
        //    var propertiesLowerLevels = properties.Where(x => x.LevelInfo != PropertyWithDefinitionInfo.Level.IsDefinedThisLevel).Select(x => x.Property).ToList();
        //    propertiesThisLevel[0].Name.Should().Be("ByteSpan");
        //    propertiesThisLevel[0].GetMethod.Name.Should().Be("get_ByteSpan");
        //    propertiesThisLevel[0].SetMethod.Name.Should().Be("set_ByteSpan");
        //    propertiesThisLevel[0].Type.Name.Should().Be("ReadOnlySpan");
        //    propertiesThisLevel[1].Name.Should().Be("MyExampleGrandchild");
        //    propertiesThisLevel[1].GetMethod.Name.Should().Be("get_MyExampleGrandchild");
        //    propertiesThisLevel[1].SetMethod.Name.Should().Be("set_MyExampleGrandchild");
        //    propertiesThisLevel[1].Type.Name.Should().Be("ExampleGrandchild");
        //    propertiesThisLevel[2].Name.Should().Be("MyLong");
        //    propertiesThisLevel[2].GetMethod.Name.Should().Be("get_MyLong");
        //    propertiesThisLevel[2].SetMethod.Name.Should().Be("set_MyLong");
        //    propertiesThisLevel[2].Type.Name.Should().Be("Int64");
        //    propertiesThisLevel[3].Name.Should().Be("MyShort");
        //    propertiesThisLevel[3].GetMethod.Name.Should().Be("get_MyShort");
        //    propertiesThisLevel[3].SetMethod.Name.Should().Be("set_MyShort");
        //    propertiesThisLevel[3].Type.Name.Should().Be("Int16");
        //    propertiesLowerLevels.Count().Should().Be(0);
        //}

        private static async Task <LazinatorCompilation> GetMiniRoslynFileSet(Type implementingType)
        {
            List <(string project, string mainFolder, string subfolder, string filename)> fileinfos = new List <(string project, string mainFolder, string subfolder, string filename)>()
            {
                ("Lazinator", "/Attributes/", "", "LazinatorAttribute"),
                ("LazinatorTests", "/Examples/", "Collections/", "DotNetList_Lazinator"),
                ("LazinatorTests", "/Examples/", "Collections/", "IDotNetList_Lazinator"),
                ("LazinatorTests", "/Examples/", "ExampleHierarchy/", "Example"),
                ("LazinatorTests", "/Examples/", "ExampleHierarchy/", "IExample"),
                ("LazinatorTests", "/Examples/", "ExampleHierarchy/", "ExampleChild"),
                ("LazinatorTests", "/Examples/", "ExampleHierarchy/", "IExampleChild"),
                ("LazinatorTests", "/Examples/", "ExampleHierarchy/", "ExampleChildInherited"),
                ("LazinatorTests", "/Examples/", "ExampleHierarchy/", "IExampleChildInherited"),
            };
            var ws          = AdhocWorkspaceManager.CreateAdHocWorkspaceWithFiles(fileinfos, ".g.cs");
            var compilation = await AdhocWorkspaceManager.GetCompilation(ws);

            var roslynFiles = new LazinatorCompilation(compilation, implementingType, null);

            return(roslynFiles);
        }
Example #7
0
        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);
        }
Example #8
0
        private void SetPropertiesIncludingInherited(INamedTypeSymbol interfaceSymbol)
        {
            List <PropertyWithDefinitionInfo> propertiesWithLevel = Container.Compilation.PropertiesForType[LazinatorCompilation.TypeSymbolToString(interfaceSymbol)];

            foreach (var unofficialProperty in GetUnofficialProperties(interfaceSymbol))
            {
                if (!propertiesWithLevel.Any(x => x.Property.MetadataName == unofficialProperty.PropertySymbol.MetadataName))
                {
                    propertiesWithLevel.Add(new PropertyWithDefinitionInfo(unofficialProperty.PropertySymbol, PropertyWithDefinitionInfo.Level.IsDefinedThisLevel)
                    {
                        PropertyAccessibility = unofficialProperty.PropertyAccessibility
                    });
                }
            }
            foreach (var baseType in Container.GetBaseLazinatorObjects())
            {
                List <IPropertySymbol> additionalProperties;
                bool baseTypeIsIndexed = Container.Compilation.TypeToExclusiveInterface.ContainsKey(LazinatorCompilation.TypeSymbolToString(baseType.ILazinatorTypeSymbol));
                if (baseTypeIsIndexed)
                {
                    var baseExclusiveInterface = Container.Compilation.TypeToExclusiveInterface[LazinatorCompilation.TypeSymbolToString(baseType.ILazinatorTypeSymbol)];
                    additionalProperties = Container.Compilation.PropertiesForType[baseExclusiveInterface].Select(x => x.Property).ToList();
                }
                else
                {
                    additionalProperties = new List <IPropertySymbol>();
                }
                foreach (var baseProperty in additionalProperties)
                {
                    if (!propertiesWithLevel.Any(x => x.Property.MetadataName == baseProperty.MetadataName))
                    {
                        propertiesWithLevel.Add(new PropertyWithDefinitionInfo(baseProperty, PropertyWithDefinitionInfo.Level.IsDefinedLowerLevelButNotInInterface)
                        {
                            DerivationKeyword = "override "
                        });
                    }
                }
                // now, unofficial properties
                var baseUnofficialProperties = GetUnofficialProperties(baseType.InterfaceTypeSymbol);
                foreach (var unofficialProperty in baseUnofficialProperties)
                {
                    if (!propertiesWithLevel.Any(x => x.Property.MetadataName == unofficialProperty.PropertySymbol.MetadataName))
                    {
                        propertiesWithLevel.Add(new PropertyWithDefinitionInfo(unofficialProperty.PropertySymbol, PropertyWithDefinitionInfo.Level.IsDefinedLowerLevelButNotInInterface)
                        {
                            DerivationKeyword = "override ", PropertyAccessibility = unofficialProperty.PropertyAccessibility
                        });
                    }
                }
            }
            var firstProblem = propertiesWithLevel.FirstOrDefault(x => !RoslynHelpers.ParentAndChildShareNullabilityContext(NullableContextSetting, x.Property.GetNullableContextForSymbol(Compilation, true)));

            if (firstProblem != null)
            {
                throw new LazinatorCodeGenException($"Lazinator requires properties of an interface to have the same nullability context as the interface itself. {NamedTypeSymbol} has nullability context {NullableContextSetting} while {firstProblem.Property} has nullability context {firstProblem.Property.GetNullableContextForSymbol(Compilation, true)}");
            }

            var orderedPropertiesWithLevel = propertiesWithLevel.Select(x => new { propertyWithLevel = x, description = new PropertyDescription(x.Property, Container, NullableContextSetting, x.DerivationKeyword, x.PropertyAccessibility, false) })
                                             .OrderByDescending(x => x.description.PropertyType == LazinatorPropertyType.PrimitiveType || x.description.PropertyType == LazinatorPropertyType.PrimitiveTypeNullable) // primitive properties are always first (but will only be redefined if defined abstractly below)
                                             .ThenBy(x => x.propertyWithLevel.LevelInfo == PropertyWithDefinitionInfo.Level.IsDefinedThisLevel)
                                             .ThenBy(x => x.description.RelativeOrder)
                                             .ThenBy(x => x.description.PropertyName).ToList();
            var last = orderedPropertiesWithLevel.LastOrDefault();

            if (last != null)
            {
                last.description.IsLast = true;
            }

            // A property that ends with "_Dirty" is designed to track dirtiness of another property. We will thus treat it specially.
            var dirtyPropertiesWithLevel = propertiesWithLevel.Where(x => x.Property.Name.EndsWith("_Dirty")).ToList();

            if (dirtyPropertiesWithLevel.Any(x => (x.Property.Type as INamedTypeSymbol)?.Name != "Boolean"))
            {
                throw new LazinatorCodeGenException($"Property ending with _Dirty must be of type bool.");
            }
            PropertiesIncludingInherited = new List <PropertyDescription>();
            PropertiesToDefineThisLevel  = new List <PropertyDescription>();
            PropertiesInherited          = new List <PropertyDescription>();

            foreach (var orderedProperty in orderedPropertiesWithLevel)
            {
                if (orderedProperty.propertyWithLevel.LevelInfo ==
                    PropertyWithDefinitionInfo.Level.IsDefinedInLowerLevelInterface)
                {
                    orderedProperty.description.IsDefinedInLowerLevelInterface = true;
                    orderedProperty.description.DerivationKeyword = "override ";
                }

                if (!dirtyPropertiesWithLevel.Any(x => x.Property.Name == orderedProperty.propertyWithLevel.Property.Name))
                { // this is not itself a "_Dirty" property, though it may have a corresponding _Dirty property.
                    PropertiesIncludingInherited.Add(orderedProperty.description);
                    if (orderedProperty.propertyWithLevel.LevelInfo == PropertyWithDefinitionInfo.Level.IsDefinedThisLevel ||
                        (
                            !Container.IsAbstract  // if we have two consecutive abstract classes, we don't want to repeat the abstract properties
                            &&
                            !Container.GetBaseLazinatorObjects().Any(x => !x.IsAbstract && x.PropertiesToDefineThisLevel.Any(y => y.PropertyName == orderedProperty.description.PropertyName)))
                        )
                    {
                        PropertiesToDefineThisLevel.Add(orderedProperty.description);
                    }
                    else
                    {
                        PropertiesInherited.Add(orderedProperty.description);
                    }
                }
            }

            // for each _dirty property, set TrackDirtinessNonSerialized on the corresponding property.
            foreach (var dirtyWithLevel in dirtyPropertiesWithLevel)
            {
                var match = PropertiesIncludingInherited.SingleOrDefault(x => x.PropertyName + "_Dirty" == dirtyWithLevel.Property.Name);
                if (match == null)
                {
                    throw new LazinatorCodeGenException(
                              $"Property ending with _Dirty must have a corresponding property without the ending.");
                }
                if (!match.IsNonLazinatorType)
                {
                    throw new LazinatorCodeGenException(
                              $"Property ending with _Dirty must correspond to a nonserialized property without the ending (not to a Lazinator or primitive type).");
                }
                match.TrackDirtinessNonSerialized = true;
                match = PropertiesToDefineThisLevel.SingleOrDefault(x => x.PropertyName + "_Dirty" == dirtyWithLevel.Property.Name);
                if (match != null)
                {
                    match.TrackDirtinessNonSerialized = true;
                }
            }
        }