test: 优化 CodeStyle 测试流程

This commit is contained in:
Fu Diwei
2021-11-10 12:59:08 +08:00
parent 11ce0773a1
commit c8ee389674
20 changed files with 334 additions and 218 deletions

View File

@@ -11,7 +11,10 @@ namespace SKIT.FlurlHttpClient.Wechat
{
public static class CodeStyleUtil
{
private static bool TryJsonize(string json, Type type, out Exception exception)
private static readonly Regex CODESTYLE_RULE_REGEX_NO_JSONABLE_PROPERTY_IN_REQUEST = new Regex(@"\/\* \@codestyle-disable[a-zA-Z0-9,\-\s]*no-jsonable-property-in-request-get[a-zA-Z0-9,\-\s]*\*\/", RegexOptions.Compiled);
private static readonly Regex CODESTYLE_RULE_REGEX_NO_INSTANTIATED_PROPERTY_IN_RESPONSE = new Regex(@"\/\* \@codestyle-disable[a-zA-Z0-9,\-\s]*no-instantiated-property-in-response[a-zA-Z0-9,\-\s]*\*\/", RegexOptions.Compiled);
private static bool TryJsonize(Type type, string json, out Exception exception)
{
exception = null;
@@ -29,11 +32,13 @@ namespace SKIT.FlurlHttpClient.Wechat
catch (Exception ex)
{
if (ex is Newtonsoft.Json.JsonException)
exception = new Exception($"通过 Newtonsoft.Json 反序列化 `{type.Name}` 失败。", ex);
exception = new Exception($"[模型] 通过 Newtonsoft.Json 反序列化 `{type.Name}` 失败。", ex);
else if (ex is System.Text.Json.JsonException)
exception = new Exception($"通过 System.Text.Json 反序列化 `{type.Name}` 失败。", ex);
exception = new Exception($"[模型] 通过 System.Text.Json 反序列化 `{type.Name}` 失败。", ex);
else
exception = new Exception($"JSON 反序列化 `{type.Name}` 遇到问题。", ex);
exception = new Exception($"[模型] JSON 反序列化 `{type.Name}` 遇到问题。", ex);
return false;
}
try
@@ -47,20 +52,97 @@ namespace SKIT.FlurlHttpClient.Wechat
catch (Exception ex)
{
if (ex is Newtonsoft.Json.JsonException)
exception = new Exception($"通过 Newtonsoft.Json 序列化 `{type.Name}` 失败。", ex);
exception = new Exception($"[模型] 通过 Newtonsoft.Json 序列化 `{type.Name}` 失败。", ex);
else if (ex is System.Text.Json.JsonException)
exception = new Exception($"通过 System.Text.Json 序列化 `{type.Name}` 失败。", ex);
exception = new Exception($"[模型] 通过 System.Text.Json 序列化 `{type.Name}` 失败。", ex);
else
exception = new Exception($"JSON 序列化 `{type.Name}` 遇到问题。", ex);
exception = new Exception($"[模型] JSON 序列化 `{type.Name}` 遇到问题。", ex);
return false;
}
PropertyInfo[] lstPropInfo = TestReflectionUtil.GetAllProperties(type);
foreach (PropertyInfo propInfo in lstPropInfo)
try
{
var newtonsoftJsonAttribute = propInfo.GetCustomAttribute<Newtonsoft.Json.JsonPropertyAttribute>();
var systemTextJsonAttribute = propInfo.GetCustomAttribute<System.Text.Json.Serialization.JsonPropertyNameAttribute>();
if (newtonsoftJsonAttribute?.PropertyName != systemTextJsonAttribute?.Name)
exception = new Exception($"类型 `{type.Name}` 的可 JSON 序列化字段声明不一致:`{newtonsoftJsonAttribute.PropertyName}` & `{systemTextJsonAttribute.Name}`。");
PropertyInfo[] lstPropInfo = TestReflectionUtil.GetAllProperties(type);
foreach (PropertyInfo propInfo in lstPropInfo)
{
var newtonsoftJsonAttribute = propInfo.GetCustomAttribute<Newtonsoft.Json.JsonPropertyAttribute>(inherit: true);
var systemTextJsonAttribute = propInfo.GetCustomAttribute<System.Text.Json.Serialization.JsonPropertyNameAttribute>(inherit: true);
if (newtonsoftJsonAttribute?.PropertyName != systemTextJsonAttribute?.Name)
{
exception = new Exception($"[模型] 类型 `{type.Name}` 的可 JSON 序列化字段 `{propInfo.Name}` 声明不一致:`{newtonsoftJsonAttribute.PropertyName}` & `{systemTextJsonAttribute.Name}`。");
return false;
}
}
}
catch (Exception ex)
{
exception = new Exception($"[模型] JSON 分析 `{type.Name}` 遇到问题。", ex);
return false;
}
return exception == null;
}
private static bool TryXmlize(Type type, string xml, out Exception exception)
{
exception = null;
try
{
using var reader = new StringReader(xml);
var xmlSerializer = new XmlSerializer(type, new XmlRootAttribute("xml"));
xmlSerializer.Deserialize(reader);
}
catch (Exception ex)
{
exception = new Exception($"[模型] XML 反序列化 `{type.Name}` 遇到问题。", ex);
return false;
}
try
{
var xmlDoc = new System.Xml.XmlDocument();
xmlDoc.LoadXml(xml);
Func<System.Xml.XmlNode, Exception> fn = default;
fn = new Func<System.Xml.XmlNode, Exception>((xmlNode) =>
{
if (xmlNode.NodeType == System.Xml.XmlNodeType.Element)
{
PropertyInfo[] lstPropInfo = TestReflectionUtil.GetAllProperties(type);
var xmlElementAttributes = lstPropInfo.SelectMany(e => e.GetCustomAttributes<XmlElementAttribute>(inherit: true)).ToArray();
var xmlArrayAttributes = lstPropInfo.SelectMany(e => e.GetCustomAttributes<XmlArrayAttribute>(inherit: true)).ToArray();
var xmlArrayItemAttributes = lstPropInfo.SelectMany(e => e.GetCustomAttributes<XmlArrayItemAttribute>(inherit: true)).ToArray();
if (!xmlElementAttributes.Any(e => e.ElementName == xmlNode.Name) &&
!xmlArrayAttributes.Any(e => e.ElementName == xmlNode.Name) &&
!xmlArrayItemAttributes.Any(e => e.ElementName == xmlNode.Name))
{
return new Exception($"[模型] 类型 `{type.Name}` 的可 XML 序列化字段缺失项:`{xmlNode.Name}`。");
}
}
for (int i = 0; i < xmlNode.ChildNodes.Count; i++)
{
Exception ex = fn.Invoke(xmlNode.ChildNodes[i]);
if (ex != null)
return ex;
}
return null;
});
for (int i = 0; i < xmlDoc.DocumentElement.ChildNodes.Count; i++)
{
exception = fn.Invoke(xmlDoc.DocumentElement.ChildNodes[i]);
if (exception != null)
return false;
}
}
catch (Exception ex)
{
exception = new Exception($"[模型] XML 分析 `{type.Name}` 遇到问题。", ex);
return false;
}
return exception == null;
@@ -79,7 +161,7 @@ namespace SKIT.FlurlHttpClient.Wechat
if (!name.EndsWith("Request") && !name.EndsWith("Response"))
{
lstError.Add(new Exception($"`{name}` 类名结尾应为 \"Request\" 或 \"Response\"。"));
lstError.Add(new Exception($"[模型] `{name}` 类名结尾应为 \"Request\" 或 \"Response\"。"));
continue;
}
@@ -87,13 +169,13 @@ namespace SKIT.FlurlHttpClient.Wechat
{
if (!typeof(ICommonRequest).IsAssignableFrom(modelType))
{
lstError.Add(new Exception($"`{name}` 类需实现自 `ICommonRequest`。"));
lstError.Add(new Exception($"[模型] `{name}` 是请求模型,需实现自 `{nameof(ICommonRequest)}`。"));
continue;
}
if (!lstModelType.Any(e => e.Name == $"{name.Substring(0, name.Length - "Request".Length)}Response"))
{
lstError.Add(new Exception($"`{name}` 是请求模型,但不存在对应的响应模型。"));
lstError.Add(new Exception($"[模型] `{name}` 是请求模型,但不存在对应的响应模型。"));
continue;
}
}
@@ -102,13 +184,13 @@ namespace SKIT.FlurlHttpClient.Wechat
{
if (!typeof(ICommonResponse).IsAssignableFrom(modelType))
{
lstError.Add(new Exception($"`{name}` 类需实现自 `ICommonResponse`。"));
lstError.Add(new Exception($"[模型] `{name}` 是响应模型,需实现自 `{nameof(ICommonResponse)}`。"));
continue;
}
if (!lstModelType.Any(e => e.Name == $"{name.Substring(0, name.Length - "Response".Length)}Request"))
{
lstError.Add(new Exception($"`{name}` 是响应模型,但不存在对应的请求模型。"));
lstError.Add(new Exception($"[模型] `{name}` 是响应模型,但不存在对应的请求模型。"));
continue;
}
}
@@ -132,29 +214,32 @@ namespace SKIT.FlurlHttpClient.Wechat
var lstModelType = TestReflectionUtil.GetAllApiModelsTypes(assembly);
var lstError = new List<Exception>();
var lstFile = TestIOUtil.GetAllFiles(workdir)
var lstFilePath = TestIOUtil.GetAllFiles(workdir)
.Where(e => string.Equals(Path.GetExtension(e), ".json", StringComparison.InvariantCultureIgnoreCase))
.ToList();
if (!lstFile.Any())
if (!lstFilePath.Any())
{
lstError.Add(new Exception($"路径 \"{workdir}\" 下不存在 JSON 格式的模型示例文件,请检查路径是否正确。"));
lstError.Add(new Exception($"[模型] \"{workdir}\" 下不存在示例文件,请检查配置的扫描路径是否正确。"));
}
foreach (string file in lstFile)
foreach (string filePath in lstFilePath)
{
string json = File.ReadAllText(file);
string name = Path.GetFileNameWithoutExtension(file).Split('.')[0];
string name = Path.GetFileNameWithoutExtension(filePath).Split('.')[0];
Type type = assembly.GetTypes().SingleOrDefault(e => e.FullName.StartsWith($"{assembly.GetName().Name}.Models.") && e.Name == name);
Type type = assembly.GetTypes().FirstOrDefault(e => e.FullName.StartsWith($"{assembly.GetName().Name}.Models.") && e.Name == name);
if (type == null)
{
lstError.Add(new Exception($"类型 `{name}`不存在。"));
lstError.Add(new Exception($"[模型] 扫描到示例文件 \"{filePath}\",但类型 `{name}` 不存在。"));
continue;
}
if (!TryJsonize(json, type, out Exception ex))
if (string.Equals(Path.GetExtension(filePath), ".json", StringComparison.InvariantCultureIgnoreCase))
{
lstError.Add(ex);
string json = File.ReadAllText(filePath);
if (!TryJsonize(type, json, out Exception ex))
{
lstError.Add(ex);
}
}
}
@@ -176,56 +261,45 @@ namespace SKIT.FlurlHttpClient.Wechat
var lstModelType = TestReflectionUtil.GetAllApiModelsTypes(assembly);
var lstError = new List<Exception>();
var lstJsonFile = TestIOUtil.GetAllFiles(workdir)
.Where(e => string.Equals(Path.GetExtension(e), ".json", StringComparison.InvariantCultureIgnoreCase))
var lstFilePath = TestIOUtil.GetAllFiles(workdir)
.Where(e =>
string.Equals(Path.GetExtension(e), ".json", StringComparison.InvariantCultureIgnoreCase) ||
string.Equals(Path.GetExtension(e), ".xml", StringComparison.InvariantCultureIgnoreCase)
)
.ToArray();
var lstXmlFile = TestIOUtil.GetAllFiles(workdir)
.Where(e => string.Equals(Path.GetExtension(e), ".xml", StringComparison.InvariantCultureIgnoreCase))
.ToArray();
if (!lstJsonFile.Any() && !lstXmlFile.Any())
if (!lstFilePath.Any())
{
lstError.Add(new Exception($"路径 \"{workdir}\" 下不存在 JSON 或 XML 格式的事件示例文件,请检查路径是否正确。"));
lstError.Add(new Exception($"[模型] \"{workdir}\" 下不存在示例文件,请检查配置的扫描路径是否正确。"));
}
foreach (string file in lstJsonFile)
foreach (string filePath in lstFilePath)
{
string json = File.ReadAllText(file);
string name = Path.GetFileNameWithoutExtension(file).Split('.')[0];
string name = Path.GetFileNameWithoutExtension(filePath).Split('.')[0];
Type type = assembly.GetTypes().SingleOrDefault(e => e.FullName.StartsWith($"{assembly.GetName().Name}.Events.") && e.Name == name);
if (type == null)
{
lstError.Add(new Exception($"类型 `{name}`不存在。"));
lstError.Add(new Exception($"[模型] 扫描到示例文件 \"{filePath}\",但类型 `{name}` 不存在。"));
continue;
}
if (!TryJsonize(json, type, out Exception ex))
if (string.Equals(Path.GetExtension(filePath), ".json", StringComparison.InvariantCultureIgnoreCase))
{
lstError.Add(ex);
}
}
string json = File.ReadAllText(filePath);
foreach (string file in lstXmlFile)
{
string xml = File.ReadAllText(file);
string name = Path.GetFileNameWithoutExtension(file).Split('.')[0];
Type type = assembly.GetTypes().SingleOrDefault(e => e.FullName.StartsWith($"{assembly.GetName().Name}.Events.") && e.Name == name);
if (type == null)
{
lstError.Add(new Exception($"类型 `{name}`不存在。"));
continue;
if (!TryJsonize(type, json, out Exception ex))
{
lstError.Add(ex);
}
}
else if (string.Equals(Path.GetExtension(filePath), ".xml", StringComparison.InvariantCultureIgnoreCase))
{
string xml = File.ReadAllText(filePath);
try
{
using StringReader reader = new StringReader(xml);
XmlSerializer xmlSerializer = new XmlSerializer(type, new XmlRootAttribute("xml"));
xmlSerializer.Deserialize(reader);
}
catch (Exception ex)
{
lstError.Add(new Exception($"XML 反序列化 `{type.Name}` 遇到问题。", ex));
if (!TryXmlize(type, xml, out Exception ex))
{
lstError.Add(ex);
}
}
}
@@ -246,12 +320,11 @@ namespace SKIT.FlurlHttpClient.Wechat
var lstExtType = TestReflectionUtil.GetAllApiExtensionsTypes(assembly);
var lstError = new List<Exception>();
foreach (Type extType in lstExtType)
foreach (Type extensionsType in lstExtType)
{
MethodInfo[] lstMethod = extType.GetMethods()
MethodInfo[] lstMethod = extensionsType.GetMethods()
.Where(e =>
e.IsPublic &&
e.IsStatic &&
e.IsPublic && e.IsStatic &&
typeof(ICommonClient).IsAssignableFrom(e.GetParameters().FirstOrDefault().ParameterType)
)
.ToArray();
@@ -260,17 +333,15 @@ namespace SKIT.FlurlHttpClient.Wechat
{
ParameterInfo[] lstParamInfo = methodInfo.GetParameters();
// 参数签名必为 this client + request + cancelToken
if (lstParamInfo.Length != 3)
{
lstError.Add(new Exception($"`{extType.Name}.{methodInfo.Name}` 方法需有且仅有 3 个入参。"));
lstError.Add(new Exception($"[接口] `{extensionsType.Name}.{methodInfo.Name}` 方法需有且仅有 3 个入参。"));
continue;
}
// 第二个参数必为 ICommonRequest 子类
if (!typeof(ICommonRequest).IsAssignableFrom(lstParamInfo[1].ParameterType))
{
lstError.Add(new Exception($"`{extType.Name}.{methodInfo.Name}` 方法第 1 个入参实现 `ICommonRequest`。"));
lstError.Add(new Exception($"[接口] `{extensionsType.Name}.{methodInfo.Name}` 方法第 1 个入参实现 `{nameof(ICommonRequest)}`。"));
continue;
}
@@ -280,25 +351,25 @@ namespace SKIT.FlurlHttpClient.Wechat
string retv = methodInfo.ReturnType.GenericTypeArguments.FirstOrDefault()?.Name;
if (para == null || !para.EndsWith("Request"))
{
lstError.Add(new Exception($"`{extType.Name}.{methodInfo.Name}` 方法第 1 个入参类名以 `Request` 结尾。"));
lstError.Add(new Exception($"[接口] `{extensionsType.Name}.{methodInfo.Name}` 方法第 1 个入参类名以 `Request` 结尾。"));
continue;
}
else if (retv == null || !retv.EndsWith("Response"))
{
if (!methodInfo.ReturnType.GenericTypeArguments.First().IsGenericType)
{
lstError.Add(new Exception($"`{extType.Name}.{methodInfo.Name}` 方法返回值类名以 `Response` 结尾。"));
lstError.Add(new Exception($"[接口] `{extensionsType.Name}.{methodInfo.Name}` 方法返回值类名以 `Response` 结尾。"));
}
continue;
}
else if (!string.Equals(func, $"Execute{para.Substring(0, para.Length - "Request".Length)}Async"))
{
lstError.Add(new Exception($"`{extType.Name}.{methodInfo.Name}` 方法与请求模型同名。"));
lstError.Add(new Exception($"[接口] `{extensionsType.Name}.{methodInfo.Name}` 方法与请求模型同名。"));
continue;
}
else if (!string.Equals(func, $"Execute{retv.Substring(0, retv.Length - "Response".Length)}Async"))
{
lstError.Add(new Exception($"`{extType.Name}.{methodInfo.Name}` 方法与响应模型同名。"));
lstError.Add(new Exception($"[接口] `{extensionsType.Name}.{methodInfo.Name}` 方法与响应模型同名。"));
continue;
}
}
@@ -323,7 +394,6 @@ namespace SKIT.FlurlHttpClient.Wechat
var lstExtensionsCodeFile = TestIOUtil.GetAllFiles(workdir)
.Where(e => string.Equals(Path.GetExtension(e), ".cs", StringComparison.InvariantCultureIgnoreCase))
.Where(e => Path.GetDirectoryName(e).StartsWith(Path.Combine(workdir, "Extensions")))
.Where(e => Path.GetFileNameWithoutExtension(e).StartsWith("Wechat"))
.Where(e => Path.GetFileNameWithoutExtension(e).Contains("ClientExecute"))
.Where(e => Path.GetFileNameWithoutExtension(e).EndsWith("Extensions"))
.ToArray();
@@ -334,18 +404,18 @@ namespace SKIT.FlurlHttpClient.Wechat
.ToArray();
if (!lstExtensionsCodeFile.Any() || !lstModelsCodeFile.Any())
{
lstError.Add(new Exception($"路径 \"{workdir}\" 下不存在 CSharp 格式的源代码文件,请检查路径是否正确。"));
lstError.Add(new Exception($"[风格] \"{workdir}\" 下不存在源代码文件,请检查配置的扫描路径是否正确。"));
}
foreach (string extCodeFilePath in lstExtensionsCodeFile)
{
string extCodeFileName = Path.GetFileName(extCodeFilePath);
string[] array = File.ReadAllText(extCodeFilePath)
string[] segments = File.ReadAllText(extCodeFilePath)
.Split("<summary>", StringSplitOptions.RemoveEmptyEntries)
.Where(e => e.Contains("Async") && !e.Contains("public static class"))
.ToArray();
for (int i = 0; i < array.Length; i++)
for (int i = 0; i < segments.Length; i++)
{
bool TryCheckExtensionSourceCode(string sourceCode, out (string Name, string Method, string Url)? request)
{
@@ -355,7 +425,7 @@ namespace SKIT.FlurlHttpClient.Wechat
var regexPara = new Regex("<para(([\\s\\S])*?)</para>").Match(sourceCode);
if (!regexPara.Success)
{
lstError.Add(new Exception($"源代码 \"{extCodeFileName}\" 下第 {i + 1} 段文档注释不齐全,未能匹配到 \"<para> ... </para>\"。"));
lstError.Add(new Exception($"[风格] 源代码 \"{extCodeFileName}\" 下第 {i + 1} 段文档注释不齐全,未能匹配到 \"<para> ... </para>\"。"));
return false;
}
@@ -363,7 +433,7 @@ namespace SKIT.FlurlHttpClient.Wechat
var regexApi = new Regex("\\[(\\S*)\\]\\s*(\\S*)").Match(regexPara.Groups[1].Value);
if (!regexApi.Success)
{
lstError.Add(new Exception($"源代码 \"{extCodeFileName}\" 下第 {i + 1} 段文档注释不齐全,未能匹配到 \"异步调用 ... 接口\"。"));
lstError.Add(new Exception($"[风格] 源代码 \"{extCodeFileName}\" 下第 {i + 1} 段文档注释不齐全,未能匹配到 \"异步调用 ... 接口\"。"));
return false;
}
@@ -377,7 +447,7 @@ namespace SKIT.FlurlHttpClient.Wechat
string.Empty;
if (!string.Equals(expectedMethod, actualMethod, StringComparison.OrdinalIgnoreCase))
{
lstError.Add(new Exception($"源代码 \"{extCodeFileName}\" 下第 {i + 1} 段文档注释有误,`[{expectedMethod}] {expectedUrl}` 与实际接口谓词不一致。"));
lstError.Add(new Exception($"[风格] 源代码 \"{extCodeFileName}\" 下第 {i + 1} 段文档注释有误,`[{expectedMethod}] {expectedUrl}` 与实际接口谓词不一致。"));
return false;
}
@@ -393,7 +463,7 @@ namespace SKIT.FlurlHttpClient.Wechat
string[] actualUrlSegments = actualUrl.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(e => e.Trim()).ToArray();
if (expectedUrlSegments.Length != actualUrlSegments.Length)
{
lstError.Add(new Exception($"源代码 \"{extCodeFileName}\" 下第 {i + 1} 段文档注释有误,`[{expectedMethod}] {expectedUrl}` 与实际接口路由不一致。"));
lstError.Add(new Exception($"[风格] 源代码 \"{extCodeFileName}\" 下第 {i + 1} 段文档注释有误,`[{expectedMethod}] {expectedUrl}` 与实际接口路由不一致。"));
return false;
}
else
@@ -406,7 +476,7 @@ namespace SKIT.FlurlHttpClient.Wechat
{
if (actualUrlSegment.StartsWith("\""))
{
lstError.Add(new Exception($"源代码 \"{extCodeFileName}\" 下第 {i + 1} 段文档注释有误,`[{expectedMethod}] {expectedUrl}` 与实际接口路由不一致。"));
lstError.Add(new Exception($"[风格] 源代码 \"{extCodeFileName}\" 下第 {i + 1} 段文档注释有误,`[{expectedMethod}] {expectedUrl}` 与实际接口路由不一致。"));
break;
}
}
@@ -415,7 +485,7 @@ namespace SKIT.FlurlHttpClient.Wechat
actualUrlSegment = actualUrlSegment.Replace("\"", string.Empty).Trim('/');
if (!string.Equals(expectedUrlSegment, actualUrlSegment))
{
lstError.Add(new Exception($"源代码 \"{extCodeFileName}\" 下第 {i + 1} 段文档注释有误,`[{expectedMethod}] {expectedUrl}` 与实际接口路由不一致。"));
lstError.Add(new Exception($"[风格] 源代码 \"{extCodeFileName}\" 下第 {i + 1} 段文档注释有误,`[{expectedMethod}] {expectedUrl}` 与实际接口路由不一致。"));
break;
}
}
@@ -427,7 +497,7 @@ namespace SKIT.FlurlHttpClient.Wechat
{
if (sourceCode.Contains("flurlReq, cancellationToken"))
{
lstError.Add(new Exception($"源代码 \"{extCodeFileName}\" 下第 {i + 1} 段代码有误,`[{expectedMethod}] {expectedUrl}` 为非简单请求但不包含请求正文。"));
lstError.Add(new Exception($"[风格] 源代码 \"{extCodeFileName}\" 下第 {i + 1} 段代码有误,`[{expectedMethod}] {expectedUrl}` 为非简单请求但不包含请求正文。"));
return false;
}
}
@@ -438,6 +508,12 @@ namespace SKIT.FlurlHttpClient.Wechat
}
bool TryCheckRequestModelSourceCode(string filePath, string expectedRequestMethod, string expectedRequestUrl)
{
if (!File.Exists(filePath))
{
lstError.Add(new Exception($"[风格] 源代码 \"{filePath}\" 不存在。"));
return false;
}
string reqCodeFileName = Path.GetFileName(filePath);
string reqCodeSourceCode = File.ReadAllText(filePath);
@@ -445,7 +521,7 @@ namespace SKIT.FlurlHttpClient.Wechat
var regexPara = new Regex("<para(([\\s\\S])*?)</para>").Match(reqCodeSourceCode);
if (!regexPara.Success)
{
lstError.Add(new Exception($"源代码 \"{reqCodeFileName}\" 下文档注释不齐全,未能匹配到 \"<para> ... </para>\"。"));
lstError.Add(new Exception($"[风格] 源代码 \"{reqCodeFileName}\" 下文档注释不齐全,未能匹配到 \"<para> ... </para>\"。"));
return false;
}
@@ -453,7 +529,7 @@ namespace SKIT.FlurlHttpClient.Wechat
var regexApi = new Regex("\\[(\\S*)\\]\\s*(\\S*)").Match(regexPara.Groups[1].Value);
if (!regexApi.Success)
{
lstError.Add(new Exception($"源代码 \"{reqCodeFileName}\" 下文档注释不齐全,未能匹配到 \"异步调用 ... 接口\"。"));
lstError.Add(new Exception($"[风格] 源代码 \"{reqCodeFileName}\" 下文档注释不齐全,未能匹配到 \"异步调用 ... 接口\"。"));
return false;
}
@@ -461,7 +537,7 @@ namespace SKIT.FlurlHttpClient.Wechat
string actualMethod = regexApi.Groups[1].Value.Trim();
if (!string.Equals(expectedRequestMethod, actualMethod, StringComparison.OrdinalIgnoreCase))
{
lstError.Add(new Exception($"源代码 \"{reqCodeFileName}\" 下文档注释有误,与实际接口谓词不一致。"));
lstError.Add(new Exception($"[风格] 源代码 \"{reqCodeFileName}\" 下文档注释有误,与实际接口谓词不一致。"));
return false;
}
@@ -469,45 +545,45 @@ namespace SKIT.FlurlHttpClient.Wechat
string actualUrl = regexApi.Groups[2].Value.Split('?')[0].Trim();
if (!string.Equals(expectedRequestUrl, actualUrl, StringComparison.OrdinalIgnoreCase))
{
lstError.Add(new Exception($"源代码 \"{reqCodeFileName}\" 下文档注释有误,与实际接口路由不一致。"));
lstError.Add(new Exception($"[风格] 源代码 \"{reqCodeFileName}\" 下文档注释有误,与实际接口路由不一致。"));
return false;
}
// 检验是否包含 `default!` 的赋值
if (new Regex("=\\s*default!").IsMatch(reqCodeSourceCode))
{
lstError.Add(new Exception($"源代码 \"{reqCodeFileName}\" 下代码有误,请求模型不应包含 `= default!` 赋值。"));
lstError.Add(new Exception($"[风格] 源代码 \"{reqCodeFileName}\" 下代码有误,请求模型不应包含 `= default!` 赋值。"));
return false;
}
// 检验是否包含数组类型字段
if (new Regex("public\\s*[a-zA-Z0-9.]*\\[\\]\\s*[a-zA-Z0-9]*\\s*{\\s*get;\\s*set;\\s*}").IsMatch(reqCodeSourceCode.Replace("byte[]", string.Empty).Replace("Byte[]", string.Empty)))
{
lstError.Add(new Exception($"源代码 \"{reqCodeFileName}\" 下代码有误,请求模型不应包含数组类型字段。"));
lstError.Add(new Exception($"[风格] 源代码 \"{reqCodeFileName}\" 下代码有误,请求模型不应包含数组类型字段。"));
return false;
}
// 校验是否包含可空字段的初始化
if (new Regex("\\? [a-zA-Z0-9]* \\{ get; set; \\} =").IsMatch(reqCodeSourceCode))
{
lstError.Add(new Exception($"源代码 \"{reqCodeFileName}\" 下代码有误,可空字段不应初始化。"));
lstError.Add(new Exception($"[风格] 源代码 \"{reqCodeFileName}\" 下代码有误,可空字段不应初始化。"));
return false;
}
// 如果是 GET 请求,检查是否包含 JSON 序列化字段
if ("GET".Equals(expectedRequestMethod, StringComparison.OrdinalIgnoreCase))
{
if (!(reqCodeSourceCode.Contains("/* @codestyle-disable") && reqCodeSourceCode.Contains("no-jsonable-property-in-request-get")))
if (!(CODESTYLE_RULE_REGEX_NO_JSONABLE_PROPERTY_IN_REQUEST.IsMatch(reqCodeSourceCode)))
{
if (new Regex("\\[Newtonsoft.Json.JsonProperty\\(\"[a-zA-Z0-9_]*\"\\)\\]").IsMatch(reqCodeSourceCode))
{
lstError.Add(new Exception($"源代码 \"{reqCodeFileName}\" 下代码有误,请求模型为简单请求、不应包含可 JSON 序列化字段。"));
lstError.Add(new Exception($"[风格] 源代码 \"{reqCodeFileName}\" 下代码有误,请求模型为简单请求、不应包含可 JSON 序列化字段。"));
return false;
}
if (new Regex("\\[System.Text.Json.Serialization.JsonPropertyName\\(\"[a-zA-Z0-9_]*\"\\)\\]").IsMatch(reqCodeSourceCode))
{
lstError.Add(new Exception($"源代码 \"{reqCodeFileName}\" 下代码有误,请求模型为简单请求、不应包含可 JSON 序列化字段。"));
lstError.Add(new Exception($"[风格] 源代码 \"{reqCodeFileName}\" 下代码有误,请求模型为简单请求、不应包含可 JSON 序列化字段。"));
return false;
}
}
@@ -517,6 +593,12 @@ namespace SKIT.FlurlHttpClient.Wechat
}
bool TryCheckResponseModelSourceCode(string filePath, string expectedRequestMethod, string expectedRequestUrl)
{
if (!File.Exists(filePath))
{
lstError.Add(new Exception($"[风格] 源代码 \"{filePath}\" 不存在。"));
return false;
}
string resCodeFileName = Path.GetFileName(filePath);
string resCodeSourceCode = File.ReadAllText(filePath);
@@ -524,7 +606,7 @@ namespace SKIT.FlurlHttpClient.Wechat
var regexPara = new Regex("<para(([\\s\\S])*?)</para>").Match(resCodeSourceCode);
if (!regexPara.Success)
{
lstError.Add(new Exception($"源代码 \"{resCodeFileName}\" 下文档注释不齐全,未能匹配到 \"<para> ... </para>\"。"));
lstError.Add(new Exception($"[风格] 源代码 \"{resCodeFileName}\" 下文档注释不齐全,未能匹配到 \"<para> ... </para>\"。"));
return false;
}
@@ -532,7 +614,7 @@ namespace SKIT.FlurlHttpClient.Wechat
var regexApi = new Regex("\\[(\\S*)\\]\\s*(\\S*)").Match(regexPara.Groups[1].Value);
if (!regexApi.Success)
{
lstError.Add(new Exception($"源代码 \"{resCodeFileName}\" 下文档注释不齐全,未能匹配到 \"异步调用 ... 接口\"。"));
lstError.Add(new Exception($"[风格] 源代码 \"{resCodeFileName}\" 下文档注释不齐全,未能匹配到 \"异步调用 ... 接口\"。"));
return false;
}
@@ -540,7 +622,7 @@ namespace SKIT.FlurlHttpClient.Wechat
string actualMethod = regexApi.Groups[1].Value.Trim();
if (!string.Equals(expectedRequestMethod, actualMethod, StringComparison.OrdinalIgnoreCase))
{
lstError.Add(new Exception($"源代码 \"{resCodeFileName}\" 下文档注释有误,与实际接口谓词不一致。"));
lstError.Add(new Exception($"[风格] 源代码 \"{resCodeFileName}\" 下文档注释有误,与实际接口谓词不一致。"));
return false;
}
@@ -548,23 +630,23 @@ namespace SKIT.FlurlHttpClient.Wechat
string actualUrl = regexApi.Groups[2].Value.Split('?')[0].Trim();
if (!string.Equals(expectedRequestUrl, actualUrl, StringComparison.OrdinalIgnoreCase))
{
lstError.Add(new Exception($"源代码 \"{resCodeFileName}\" 下文档注释有误,与实际接口路由不一致。"));
lstError.Add(new Exception($"[风格] 源代码 \"{resCodeFileName}\" 下文档注释有误,与实际接口路由不一致。"));
return false;
}
// 检验是否包含 `string.Empty` 的赋值
if (new Regex("=\\s*string.Empty").IsMatch(resCodeSourceCode))
{
lstError.Add(new Exception($"源代码 \"{resCodeFileName}\" 下代码有误,响应模型不应包含 `= string.Empty` 赋值。"));
lstError.Add(new Exception($"[风格] 源代码 \"{resCodeFileName}\" 下代码有误,响应模型不应包含 `= string.Empty` 赋值。"));
return false;
}
// 检验是否包含 `new class()` 的赋值
if (!(resCodeSourceCode.Contains("/* @codestyle-disable") && resCodeSourceCode.Contains("no-instantiated-property-in-response")))
if (new Regex("=\\s*new\\s[a-zA-Z0-9.]*\\(\\)").IsMatch(resCodeSourceCode))
{
if (new Regex("=\\s*new\\s[a-zA-Z0-9.]*\\(\\)").IsMatch(resCodeSourceCode))
if (!(CODESTYLE_RULE_REGEX_NO_INSTANTIATED_PROPERTY_IN_RESPONSE.IsMatch(resCodeSourceCode)))
{
lstError.Add(new Exception($"源代码 \"{resCodeFileName}\" 下代码有误,响应模型不应包含 `= new class()` 赋值。"));
lstError.Add(new Exception($"[风格] 源代码 \"{resCodeFileName}\" 下代码有误,响应模型不应包含 `= new class()` 赋值。"));
return false;
}
}
@@ -572,23 +654,23 @@ namespace SKIT.FlurlHttpClient.Wechat
// 检验是否包含列表类型字段
if (new Regex("public\\s*IList<[a-zA-Z0-9.]*>\\s*[a-zA-Z0-9]*\\s*{\\s*get;\\s*set;\\s*}").IsMatch(resCodeSourceCode))
{
lstError.Add(new Exception($"源代码 \"{resCodeFileName}\" 下代码有误,响应模型不应包含列表类型字段。"));
lstError.Add(new Exception($"[风格] 源代码 \"{resCodeFileName}\" 下代码有误,响应模型不应包含列表类型字段。"));
return false;
}
return true;
}
if (!TryCheckExtensionSourceCode(array[i], out var request))
if (!TryCheckExtensionSourceCode(segments[i], out var request))
{
continue;
}
else
{
string requestModelFileName = lstModelsCodeFile.Single(e => string.Equals(Path.GetFileNameWithoutExtension(e), $"{request.Value.Name}Request"));
string responseModelFileName = lstModelsCodeFile.Single(e => string.Equals(Path.GetFileNameWithoutExtension(e), $"{request.Value.Name}Response"));
bool isValidReq = TryCheckRequestModelSourceCode(requestModelFileName, request.Value.Method, request.Value.Url);
bool isValidRes = TryCheckResponseModelSourceCode(responseModelFileName, request.Value.Method, request.Value.Url);
string reqModelFileName = lstModelsCodeFile.FirstOrDefault(e => string.Equals(Path.GetFileNameWithoutExtension(e), $"{request.Value.Name}Request"));
string resModelFileName = lstModelsCodeFile.FirstOrDefault(e => string.Equals(Path.GetFileNameWithoutExtension(e), $"{request.Value.Name}Response"));
bool isValidReq = TryCheckRequestModelSourceCode(reqModelFileName, request.Value.Method, request.Value.Url);
bool isValidRes = TryCheckResponseModelSourceCode(resModelFileName, request.Value.Method, request.Value.Url);
if (!isValidReq || !isValidRes)
{
continue;