public override void Populate(CSharpGeneratorContext context)
        {
            if (!context.Project.IsUnityProject())
            {
                return;
            }

            if (!(context.ClassDeclaration.DeclaredElement is IClass typeElement))
            {
                return;
            }

            // CompactOneToListMap is optimised for the typical use case of only one item per key
            var existingMethods = new CompactOneToListMap <string, IMethod>();

            foreach (var typeMemberInstance in typeElement.GetAllClassMembers <IMethod>())
            {
                existingMethods.AddValue(typeMemberInstance.Member.ShortName, typeMemberInstance.Member);
            }

            var groupingTypeLookup = new Dictionary <IClrTypeName, ITypeElement>();

            var factory  = CSharpElementFactory.GetInstance(context.ClassDeclaration);
            var elements = new List <GeneratorDeclaredElement>();

            var unityVersion   = myUnityVersion.GetActualVersion(context.Project);
            var eventFunctions = myUnityApi.GetEventFunctions(typeElement, unityVersion);

            foreach (var eventFunction in eventFunctions.OrderBy(e => e.Name, new UnityEventFunctionComparer()))
            {
                // Note that we handle grouping, but it's off by default, and Rider doesn't save and restore the last
                // used grouping value. We can set EnforceGrouping, but that's a bit too much
                // https://youtrack.jetbrains.com/issue/RIDER-25194
                if (!groupingTypeLookup.TryGetValue(eventFunction.TypeName, out var groupingType))
                {
                    groupingType = myKnownTypesCache.GetByClrTypeName(eventFunction.TypeName, context.PsiModule)
                                   .GetTypeElement();
                    groupingTypeLookup.Add(eventFunction.TypeName, groupingType);
                }

                var makeVirtual  = false;
                var accessRights = AccessRights.PRIVATE;

                var exactMatch = existingMethods[eventFunction.Name]
                                 .FirstOrDefault(m => eventFunction.Match(m) == MethodSignatureMatch.ExactMatch);
                if (exactMatch != null)
                {
                    // Exact match. Only offer to implement if it's virtual and in a base class
                    if (!exactMatch.IsVirtual)
                    {
                        continue;
                    }

                    var containingType = exactMatch.GetContainingType();
                    if (Equals(containingType, typeElement))
                    {
                        continue;
                    }

                    makeVirtual  = true;
                    accessRights = exactMatch.GetAccessRights();
                    groupingType = containingType;
                }

                var newMethodDeclaration = eventFunction
                                           .CreateDeclaration(factory, myKnownTypesCache, context.ClassDeclaration, accessRights, makeVirtual);
                if (makeVirtual)
                {
                    // Make the parameter names are the same as the overridden method, or the "redundant override"
                    // inspection doesn't kick in
                    var overrideParameters = exactMatch.Parameters;
                    var newParameters      = newMethodDeclaration.ParameterDeclarations;
                    for (var i = 0; i < overrideParameters.Count; i++)
                    {
                        newParameters[i].SetName(overrideParameters[i].ShortName);
                    }
                }

                var newMethod = newMethodDeclaration.DeclaredElement;
                Assertion.AssertNotNull(newMethod, "newMethod != null");

                elements.Add(new GeneratorDeclaredElement(newMethod, newMethod.IdSubstitution, groupingType));
            }

            context.ProvidedElements.AddRange(elements.Distinct(m => m.TestDescriptor));
        }
예제 #2
0
        protected override void Analyze(IMemberOwnerDeclaration element, ElementProblemAnalyzerData data,
                                        IHighlightingConsumer consumer)
        {
            var typeElement = element.DeclaredElement;

            if (typeElement == null)
            {
                return;
            }

            if (!Api.IsUnityType(typeElement))
            {
                return;
            }

            var project = element.GetProject();

            if (project == null)
            {
                return;
            }

            var unityVersion = Api.GetNormalisedActualVersion(project);

            var map = new CompactOneToListMap <UnityEventFunction, Candidate>(new UnityEventFunctionKeyComparer());

            foreach (var instance in typeElement.GetAllClassMembers <IMethod>())
            {
                var unityEventFunction = Api.GetUnityEventFunction(instance.Member, unityVersion, out var match);
                if (unityEventFunction != null)
                {
                    map.AddValue(unityEventFunction, new Candidate(instance.Member, match));
                }
            }

            foreach (var(function, candidates) in map)
            {
                if (candidates.Count == 1)
                {
                    // Only one function, mark it as a unity function, even if it's not an exact match
                    // We'll let other inspections handle invalid signatures
                    var method = candidates[0].Method;
                    PutEventToCustomData(method, data);
                    AddMethodSignatureInspections(consumer, method, function, candidates[0].Match);
                }
                else
                {
                    var hasExactMatch = false;

                    // All exact matches should be marked as an event function
                    var duplicates = new FrugalLocalList <IMethod>();
                    foreach (var candidate in candidates)
                    {
                        if (candidate.Match == MethodSignatureMatch.ExactMatch)
                        {
                            hasExactMatch = true;
                            if (Equals(candidate.Method.GetContainingType(), typeElement))
                            {
                                PutEventToCustomData(candidate.Method, data);
                                duplicates.Add(candidate.Method);
                            }
                        }
                    }

                    // Multiple exact matches should be marked as duplicate/ambiguous
                    if (duplicates.Count > 1)
                    {
                        foreach (var method in duplicates)
                        {
                            foreach (var declaration in method.GetDeclarations())
                            {
                                consumer.AddHighlighting(
                                    new DuplicateEventFunctionWarning((IMethodDeclaration)declaration));
                            }
                        }
                    }

                    // If there are no exact matches, mark all as unity functions, with inspections
                    // to fix up signature errors
                    if (!hasExactMatch)
                    {
                        foreach (var candidate in candidates)
                        {
                            if (Equals(candidate.Method.GetContainingType(), typeElement))
                            {
                                var method = candidate.Method;
                                PutEventToCustomData(method, data);
                                AddMethodSignatureInspections(consumer, method, function, candidate.Match);
                            }
                        }
                    }
                }
            }
        }