• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

luttje / Key2Joy / 6749638574

03 Nov 2023 08:00PM UTC coverage: 44.916% (+1.1%) from 43.795%
6749638574

push

github

luttje
fix JS failing on callback/function parameter + add tests

802 of 2447 branches covered (0.0%)

Branch coverage included in aggregate %.

75 of 76 new or added lines in 4 files covered. (98.68%)

147 existing lines in 11 files now uncovered.

4048 of 8351 relevant lines covered (48.47%)

17051.46 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.38
/Core/Key2Joy.Core/Mapping/Actions/Scripting/ExposedMethod.cs
1
using System;
2
using System.Collections.Generic;
3
using System.Linq;
4
using System.Reflection;
5
using Key2Joy.Contracts.Util;
6
using Key2Joy.Plugins;
7

8
namespace Key2Joy.Mapping.Actions.Scripting;
9

10
public delegate object ParameterTransformerDelegate(object parameter, Type methodParameterType);
11

12
public delegate object ParameterTransformerDelegate<T>(T parameter, Type methodParameterType);
13

14
public abstract class ExposedMethod
15
{
16
    public string FunctionName { get; protected set; }
612✔
17
    public string MethodName { get; protected set; }
743✔
18
    public bool IsPrepared => this.Instance != null;
389✔
19

20
    protected object Instance { get; private set; }
639✔
21

22
    private readonly Dictionary<Type, ParameterTransformerDelegate> parameterTransformers = new();
392✔
23
    protected IList<Type> ParameterTypes { get; private set; } = new List<Type>();
705✔
24
    protected IList<object> ParameterDefaultValues { get; private set; } = new List<object>();
625✔
25
    protected bool IsLastParameterParams { get; private set; } = false;
249✔
26

27
    public ExposedMethod(string functionName, string methodName)
392✔
28
    {
29
        this.FunctionName = functionName;
392✔
30
        this.MethodName = methodName;
392✔
31
    }
392✔
32

33
    public void Prepare(object instance)
34
    {
35
        this.Instance = instance;
230✔
36
        this.ParameterTypes = this.GetParameterTypes(out var parameterDefaultValues, out var isLastParameterParams);
230✔
37
        this.ParameterDefaultValues = parameterDefaultValues;
230✔
38
        this.IsLastParameterParams = isLastParameterParams;
230✔
39
    }
230✔
40

41
    public abstract IList<Type> GetParameterTypes(out IList<object> parameterDefaultValues, out bool isLastParameterParams);
42

43
    public abstract object InvokeMethod(object[] transformedParameters);
44

45
    /// <summary>
46
    /// Register a transformer for certain types coming from scripts.
47
    /// The transformer will get the parameter value and the type of the method parameter.
48
    /// The transformer must return an object that will be passed to the method.
49
    /// </summary>
50
    /// <typeparam name="T"></typeparam>
51
    /// <param name="transformer"></param>
52
    public void RegisterParameterTransformer<T>(ParameterTransformerDelegate<T> transformer)
53
    {
54
        if (!this.IsPrepared)
369✔
55
        {
56
            throw new InvalidOperationException("Cannot register parameter transformer before preparing the method. Call .Prepare on the exposed method first.");
1✔
57
        }
58

59
        var key = typeof(T);
368✔
60

61
        if (this.parameterTransformers.ContainsKey(key))
368✔
62
        {
63
            this.parameterTransformers.Remove(key);
1✔
64
        }
65

66
        this.parameterTransformers.Add(key, (p, t) => transformer((T)p, t));
373✔
67
    }
368✔
68

69
    /// <summary>
70
    /// Will try to transform the parameter to the type of the method parameter.
71
    /// </summary>
72
    /// <param name="parameters"></param>
73
    /// <returns></returns>
74
    /// <exception cref="NotImplementedException"></exception>
75
    public object TransformAndRedirect(params object[] parameters)
76
    {
77
        if (!this.IsPrepared)
20✔
78
        {
79
            throw new InvalidOperationException("Cannot TransformAndRedirect before preparing the method. Call .Prepare on the exposed method first.");
2✔
80
        }
81

82
        // When we pass TransformAndRedirect any array as the only parameter, C# treats that
83
        // array as the parameters array. Whilst we likely want to send through the array as is.
84
        // To workaround this we'll check that if we get more parameters than we expect, we'll
85
        // wrap it in an object[]
86
        if (this.ParameterTypes.Count == 1
18✔
87
            && parameters.Length > this.ParameterTypes.Count)
18✔
88
        {
89
            parameters = new object[] { parameters };
1✔
90
        }
91

92
        // If there's more arguments than parameters, and the last parameter is params,
93
        // we'll wrap the rest of the arguments in an object[] and pass that through.
94
        if (this.IsLastParameterParams)
18✔
95
        {
96
            var surplusParameters = parameters.Skip(this.ParameterTypes.Count - 1).ToArray();
5✔
97
            var parametersToPass = parameters.Take(this.ParameterTypes.Count - 1).ToList();
5✔
98
            parametersToPass.Add(surplusParameters);
5✔
99
            parameters = parametersToPass.ToArray();
5✔
100
        }
101

102
        var transformedParameters = parameters
18✔
103
            .Select((parameter, parameterIndex) =>
18✔
104
            {
18✔
105
                var parameterType = this.ParameterTypes[parameterIndex];
28✔
106

18✔
107
                if (this.parameterTransformers.TryGetValue(parameter.GetType(), out var transformer))
28✔
108
                {
18✔
109
                    parameter = transformer(parameter, parameterType);
5✔
110
                }
18✔
111

18✔
112
                return TypeConverter.ConvertToType(parameter, parameterType);
28✔
113
            })
18✔
114
            .ToList();
18✔
115

116
        // Ensure the transformedParameters list has the same number of items as this.ParameterTypes
117
        while (transformedParameters.Count < this.ParameterTypes.Count)
19✔
118
        {
119
            transformedParameters.Add(this.GetDefaultParameterValue(transformedParameters.Count));
1✔
120
        }
121

122
        return this.InvokeMethod(transformedParameters.ToArray());
18✔
123
    }
124

125
    /// <summary>
126
    /// Method to get default parameter value or create an empty array for params
127
    /// </summary>
128
    /// <param name="parameterIndex"></param>
129
    /// <returns></returns>
130
    /// <exception cref="ArgumentException"></exception>
131
    private object GetDefaultParameterValue(int parameterIndex)
132
    {
133
        if (this.IsLastParameterParams
1!
134
            && parameterIndex == this.ParameterTypes.Count - 1)
1✔
135
        {
UNCOV
136
            var lastParameterType = this.ParameterTypes.Last();
×
UNCOV
137
            var elementType = lastParameterType.GetElementType();
×
UNCOV
138
            return Array.CreateInstance(elementType ?? typeof(object), 0);
×
139
        }
140
        else if (parameterIndex < this.ParameterDefaultValues.Count
1!
141
            && this.ParameterDefaultValues[parameterIndex] != DBNull.Value)
1✔
142
        {
143
            return this.ParameterDefaultValues[parameterIndex];
1✔
144
        }
145

UNCOV
146
        throw new ArgumentException("No default value available for parameter at index " + parameterIndex);
×
147
    }
148

149
    /// <summary>
150
    /// MethodInfo that can be bound to scripts
151
    /// </summary>
152
    /// <returns></returns>
153
    public virtual MethodInfo GetExecutorMethodInfo() => typeof(ExposedMethod).GetMethod(nameof(TransformAndRedirect));
214✔
154
}
155

156
public class TypeExposedMethod : ExposedMethod
157
{
158
    public Type Type { get; protected set; }
680✔
159

160
    private readonly MethodInfo cachedMethodInfo;
161

162
    public TypeExposedMethod(string functionName, string methodName, Type type)
163
        : base(functionName, methodName)
340✔
164
    {
165
        this.Type = type;
340✔
166
        this.cachedMethodInfo = this.Type.GetMethod(this.MethodName);
340✔
167
    }
340✔
168

169
    public override IList<Type> GetParameterTypes(out IList<object> parameterDefaultValues, out bool isLastParameterParams)
170
    {
171
        var parameterInfos = this.cachedMethodInfo.GetParameters();
224✔
172

173
        isLastParameterParams = parameterInfos.Length > 0
224✔
174
            && parameterInfos.Last().IsDefined(typeof(ParamArrayAttribute), false);
224✔
175

176
        parameterDefaultValues = parameterInfos.Select(p => p.DefaultValue).ToList();
615✔
177

178
        return parameterInfos.Select(p => p.ParameterType).ToList();
615✔
179
    }
180

181
    public override object InvokeMethod(object[] transformedParameters)
182
        => this.cachedMethodInfo.Invoke(this.Instance, transformedParameters);
8✔
183
}
184

185
public class PluginExposedMethod : ExposedMethod
186
{
187
    public string TypeName { get; protected set; }
45✔
188

189
    public PluginExposedMethod(string typeName, string functionName, string methodName)
190
        : base(functionName, methodName)
45✔
191
        => this.TypeName = typeName;
45✔
192

193
    public override IList<Type> GetParameterTypes(out IList<object> parameterDefaultValues, out bool isLastParameterParams)
194
    {
195
        var instance = (PluginActionProxy)this.Instance;
5✔
196
        return instance.GetMethodParameterTypes(this.MethodName, out parameterDefaultValues, out isLastParameterParams);
5✔
197
    }
198

199
    public override object InvokeMethod(object[] transformedParameters)
200
    {
201
        var instance = (PluginActionProxy)this.Instance;
6✔
202
        return instance.InvokeScriptMethod(this.MethodName, transformedParameters);
6✔
203
    }
204
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc