Compare commits

...

3 Commits

4 changed files with 21 additions and 213 deletions

View File

@ -457,8 +457,10 @@ public class CodegenEngine {
* @return 格式化后的代码
*/
private String prettyCode(String content, String vmPath) {
// Vue 界面:去除字段后面多余的 , 逗号,解决前端的 Pretty 代码格式检查的报错(内含换行归一化
content = CodegenFormatUtils.applyVueTrailingCommaFix(content, vmPath);
// Vue 界面:去除字段后面多余的 , 逗号,解决前端的 Pretty 代码格式检查的报错(需要排除 vben5、vue3_admin_uniapp
if (!StrUtil.containsAny(vmPath, "vben5", "vue3_admin_uniapp")) {
content = content.replaceAll(",\\r?\\n}", "\n}").replaceAll(",\\r?\\n }", "\n }");
}
// Vue 界面:去除多的 dateFormatter只有一个的情况下说明没使用到
if (StrUtil.count(content, "dateFormatter") == 1) {
content = StrUtils.removeLineContains(content, "dateFormatter");
@ -480,7 +482,7 @@ public class CodegenEngine {
if (StrUtil.count(content, "DICT_TYPE.") == 0) {
content = StrUtils.removeLineContains(content, "DICT_TYPE");
}
return CodegenFormatUtils.formatGeneratedContent(content);
return content;
}
private Map<String, Object> initBindingMap(DbType dbType, CodegenTableDO table, List<CodegenColumnDO> columns,

View File

@ -1,116 +0,0 @@
package cn.iocoder.yudao.module.infra.service.codegen.inner;
import cn.hutool.core.util.StrUtil;
/**
* 代码生成结果格式化工具
*/
final class CodegenFormatUtils {
/**
* 经典 Vue2 模板目录,与 {@link CodegenEngine#vueTemplatePath(String)} 一致
*/
private static final String CLASSIC_VUE2_TEMPLATE_PREFIX = "codegen/vue/";
/**
* 经典 Vue3 模板目录,与 {@link CodegenEngine#vue3TemplatePath(String)} 一致。
* 使用前缀 {@code codegen/vue3/} 而非子串匹配,避免误伤 {@code vue3_vben5}、{@code vue3_admin_uniapp} 等目录。
*/
private static final String CLASSIC_VUE3_TEMPLATE_PREFIX = "codegen/vue3/";
private CodegenFormatUtils() {
}
/**
* 经典 Vue2 / Vue3 模板Element UI / Element Plus 标准模版)
*/
static boolean isClassicVueTemplate(String vmPath) {
if (StrUtil.isEmpty(vmPath)) {
return false;
}
return StrUtil.startWith(vmPath, CLASSIC_VUE2_TEMPLATE_PREFIX)
|| StrUtil.startWith(vmPath, CLASSIC_VUE3_TEMPLATE_PREFIX);
}
/**
* 是否处理 {@code ,\n}} / {@code ,\n }} 形式的末尾逗号(经典 Vue + vue3_vben2排除 vben5、uniapp
*/
static boolean needsVueTrailingCommaBeforeBraceFix(String vmPath) {
if (StrUtil.containsAny(vmPath, "vben5", "vue3_admin_uniapp")) {
return false;
}
return isClassicVueTemplate(vmPath) || StrUtil.startWith(vmPath, "codegen/vue3_vben/");
}
/**
* 是否处理 {@code ,\n)} 形式的末尾逗号(仅经典 Vue 的 .vue 视图模板)
*/
static boolean needsReactiveTrailingCommaFix(String vmPath) {
return isClassicVueTemplate(vmPath) && StrUtil.endWith(vmPath, ".vue.vm");
}
/**
* Vue 模板:统一去除对象/参数列表末尾多余逗号(与 {@link CodegenEngine#prettyCode} 历史行为一致)
*/
static String applyVueTrailingCommaFix(String content, String vmPath) {
if (content == null) {
return null;
}
content = normalizeLineSeparators(content);
if (needsVueTrailingCommaBeforeBraceFix(vmPath)) {
content = content.replaceAll(",\\r?\\n}", "\n}")
.replaceAll(",\\r?\\n }", "\n }");
}
if (needsReactiveTrailingCommaFix(vmPath)) {
content = removeTrailingCommaBeforeCloseParen(content);
}
return content;
}
/**
* 去除多行实参中 {@code ,\n)} 形式的末尾逗号(如 {@code foo(bar,\n)}{@code reactive} 的 {@code ,\n})} 由 {@code ,\n}} 规则处理)
*/
static String removeTrailingCommaBeforeCloseParen(String content) {
if (content == null) {
return null;
}
return content.replaceAll(",\\r?\\n\\)", "\n)");
}
/**
* 统一换行符为 LF
*/
static String normalizeLineSeparators(String content) {
if (content == null) {
return null;
}
return content.replace("\r\n", "\n").replace('\r', '\n');
}
/**
* 生产生成结果LF + 保证文件以单个换行结尾(符合 POSIX / IDE 习惯)
*/
static String formatGeneratedContent(String content) {
content = normalizeLineSeparators(content);
if (StrUtil.isEmpty(content)) {
return content;
}
while (content.endsWith("\n\n")) {
content = content.substring(0, content.length() - 1);
}
if (!content.endsWith("\n")) {
content = content + "\n";
}
return content;
}
/**
* 单测断言LF + 去掉末尾空白(与历史 fixture 无尾换行对齐)
*/
static String normalizeForAssert(String content) {
if (content == null) {
return null;
}
return normalizeLineSeparators(content).stripTrailing();
}
}

View File

@ -18,7 +18,12 @@ import org.mockito.Spy;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -99,17 +104,20 @@ public abstract class CodegenEngineAbstractTest extends BaseMockitoUnitTest {
.map(m -> (String) m.get("filePath"))
.collect(java.util.stream.Collectors.toCollection(LinkedHashSet::new));
assertEquals(expectedFiles, result.keySet(), "生成文件集合不匹配");
// 校验每个文件;归一化换行符,让断言不依赖文件落盘的换行风格
// 校验每个文件;归一化 \r\n 为 \n,让断言不依赖文件落盘的换行风格
asserts.forEach(assertMap -> {
String contentPath = (String) assertMap.get("contentPath");
String filePath = (String) assertMap.get("filePath");
String expected = CodegenFormatUtils.normalizeForAssert(
ResourceUtil.readUtf8Str("codegen/" + path + "/" + contentPath));
String actual = CodegenFormatUtils.normalizeForAssert(result.get(filePath));
assertEquals(expected, actual, filePath + ":不匹配");
String expected = normalizeLineEndings(ResourceUtil.readUtf8Str("codegen/" + path + "/" + contentPath));
String actual = result.get(filePath);
assertEquals(expected, normalizeLineEndings(actual), filePath + ":不匹配");
});
}
private static String normalizeLineEndings(String content) {
return content == null ? null : content.replace("\r\n", "\n").replace("\r", "\n").stripTrailing();
}
// ==================== 调试专用 ====================
/**
@ -143,10 +151,10 @@ public abstract class CodegenEngineAbstractTest extends BaseMockitoUnitTest {
+ '/' + StrUtil.subBefore(lastFilePath, '.', true);
asserts.add(MapUtil.<String, String>builder().put("filePath", filePath)
.put("contentPath", contentPath).build());
FileUtil.writeUtf8String(fileContent, basePath + "/" + contentPath);
FileUtil.writeUtf8String(normalizeLineEndings(fileContent), basePath + "/" + contentPath);
});
// 写入 assert.json 文件
FileUtil.writeUtf8String(JsonUtils.toJsonPrettyString(asserts), basePath + "/assert.json");
FileUtil.writeUtf8String(normalizeLineEndings(JsonUtils.toJsonPrettyString(asserts)), basePath + "/assert.json");
}
}

View File

@ -1,86 +0,0 @@
package cn.iocoder.yudao.module.infra.service.codegen.inner;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class CodegenFormatUtilsTest {
@Test
void testNormalizeLineSeparators() {
assertEquals("a\nb", CodegenFormatUtils.normalizeLineSeparators("a\r\nb"));
assertEquals("a\nb", CodegenFormatUtils.normalizeLineSeparators("a\rb"));
}
@Test
void testFormatGeneratedContent() {
assertEquals("class A {\n}\n", CodegenFormatUtils.formatGeneratedContent("class A {\r\n}\r\n"));
assertEquals("x\n", CodegenFormatUtils.formatGeneratedContent("x"));
assertEquals("x\n", CodegenFormatUtils.formatGeneratedContent("x\n\n\n"));
assertEquals("", CodegenFormatUtils.formatGeneratedContent(""));
assertEquals(" \n", CodegenFormatUtils.formatGeneratedContent(" "));
}
@Test
void testNormalizeForAssert() {
assertEquals("}", CodegenFormatUtils.normalizeForAssert("}\n"));
assertEquals("a\nb", CodegenFormatUtils.normalizeForAssert("a\r\nb\r\n"));
}
@Test
void testNormalizeForAssert_matchesFormatGeneratedContentForAssert() {
String raw = "package demo;\r\n\r\npublic class A {\r\n private Long id;\r\n}\r\n";
String generated = CodegenFormatUtils.formatGeneratedContent(raw);
assertEquals(CodegenFormatUtils.normalizeForAssert(raw),
CodegenFormatUtils.normalizeForAssert(generated));
}
@Test
void testRemoveTrailingCommaBeforeCloseParen() {
assertEquals("foo(bar\n)", CodegenFormatUtils.removeTrailingCommaBeforeCloseParen("foo(bar,\n)"));
}
@Test
void testApplyVueTrailingCommaFix_classicVueReactive() {
String input = "const queryParams = reactive({\n createTime: [],\n})\n";
String expected = "const queryParams = reactive({\n createTime: []\n})\n";
assertEquals(expected, CodegenFormatUtils.applyVueTrailingCommaFix(
input, "codegen/vue3/views/index.vue.vm"));
}
@Test
void testApplyVueTrailingCommaFix_skipVben5() {
String input = "const x = {\n a: 1,\n}\n";
assertEquals(input, CodegenFormatUtils.applyVueTrailingCommaFix(
input, "codegen/vue3_vben5_antd/general/views/index.vue.vm"));
}
@Test
void testIsClassicVueTemplate() {
assertTrue(CodegenFormatUtils.isClassicVueTemplate("codegen/vue/views/index.vue.vm"));
assertTrue(CodegenFormatUtils.isClassicVueTemplate("codegen/vue3/views/index.vue.vm"));
assertTrue(CodegenFormatUtils.isClassicVueTemplate("codegen/vue3/api/api.ts.vm"));
assertFalse(CodegenFormatUtils.isClassicVueTemplate("codegen/vue3_vben5_antd/schema/views/index.vue.vm"));
assertFalse(CodegenFormatUtils.isClassicVueTemplate("codegen/vue3_vben/views/index.vue.vm"));
assertFalse(CodegenFormatUtils.isClassicVueTemplate("codegen/vue3_admin_uniapp/views/index.vue.vm"));
assertFalse(CodegenFormatUtils.isClassicVueTemplate("codegen/vue30/views/index.vue.vm")); // 非 codegen/vue3/ 前缀
assertFalse(CodegenFormatUtils.isClassicVueTemplate("codegen/java/controller/controller.vm"));
assertFalse(CodegenFormatUtils.isClassicVueTemplate(null));
}
@Test
void testNeedsReactiveTrailingCommaFix() {
assertTrue(CodegenFormatUtils.needsReactiveTrailingCommaFix("codegen/vue3/views/index.vue.vm"));
assertFalse(CodegenFormatUtils.needsReactiveTrailingCommaFix("codegen/vue3/api/api.ts.vm"));
assertFalse(CodegenFormatUtils.needsReactiveTrailingCommaFix("codegen/vue3_vben5_antd/general/views/index.vue.vm"));
}
@Test
void testNeedsVueTrailingCommaBeforeBraceFix() {
assertTrue(CodegenFormatUtils.needsVueTrailingCommaBeforeBraceFix("codegen/vue3/views/index.vue.vm"));
assertTrue(CodegenFormatUtils.needsVueTrailingCommaBeforeBraceFix("codegen/vue3_vben/views/index.vue.vm"));
assertFalse(CodegenFormatUtils.needsVueTrailingCommaBeforeBraceFix("codegen/vue3_vben5_antd/general/views/index.vue.vm"));
assertFalse(CodegenFormatUtils.needsVueTrailingCommaBeforeBraceFix("codegen/vue3_admin_uniapp/views/index.vue.vm"));
}
}