Commit 83ce168a68eaf63bb9d65042c88c388c3350d6d6
1 parent
dea5e07e
fix(infra): 代码生成统一 LF 与尾逗号处理,修复 Windows 单测
Showing
4 changed files
with
211 additions
and
17 deletions
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java
| ... | ... | @@ -457,10 +457,8 @@ public class CodegenEngine { |
| 457 | 457 | * @return 格式化后的代码 |
| 458 | 458 | */ |
| 459 | 459 | private String prettyCode(String content, String vmPath) { |
| 460 | - // Vue 界面:去除字段后面多余的 , 逗号,解决前端的 Pretty 代码格式检查的报错(需要排除 vben5、vue3_admin_uniapp) | |
| 461 | - if (!StrUtil.containsAny(vmPath, "vben5", "vue3_admin_uniapp")) { | |
| 462 | - content = content.replaceAll(",\n}", "\n}").replaceAll(",\n }", "\n }"); | |
| 463 | - } | |
| 460 | + // Vue 界面:去除字段后面多余的 , 逗号,解决前端的 Pretty 代码格式检查的报错(内含换行归一化) | |
| 461 | + content = CodegenFormatUtils.applyVueTrailingCommaFix(content, vmPath); | |
| 464 | 462 | // Vue 界面:去除多的 dateFormatter,只有一个的情况下,说明没使用到 |
| 465 | 463 | if (StrUtil.count(content, "dateFormatter") == 1) { |
| 466 | 464 | content = StrUtils.removeLineContains(content, "dateFormatter"); |
| ... | ... | @@ -482,7 +480,7 @@ public class CodegenEngine { |
| 482 | 480 | if (StrUtil.count(content, "DICT_TYPE.") == 0) { |
| 483 | 481 | content = StrUtils.removeLineContains(content, "DICT_TYPE"); |
| 484 | 482 | } |
| 485 | - return content; | |
| 483 | + return CodegenFormatUtils.formatGeneratedContent(content); | |
| 486 | 484 | } |
| 487 | 485 | |
| 488 | 486 | private Map<String, Object> initBindingMap(DbType dbType, CodegenTableDO table, List<CodegenColumnDO> columns, | ... | ... |
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenFormatUtils.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.infra.service.codegen.inner; | |
| 2 | + | |
| 3 | +import cn.hutool.core.util.StrUtil; | |
| 4 | + | |
| 5 | +/** | |
| 6 | + * 代码生成结果格式化工具 | |
| 7 | + */ | |
| 8 | +final class CodegenFormatUtils { | |
| 9 | + | |
| 10 | + /** | |
| 11 | + * 经典 Vue2 模板目录,与 {@link CodegenEngine#vueTemplatePath(String)} 一致 | |
| 12 | + */ | |
| 13 | + private static final String CLASSIC_VUE2_TEMPLATE_PREFIX = "codegen/vue/"; | |
| 14 | + | |
| 15 | + /** | |
| 16 | + * 经典 Vue3 模板目录,与 {@link CodegenEngine#vue3TemplatePath(String)} 一致。 | |
| 17 | + * 使用前缀 {@code codegen/vue3/} 而非子串匹配,避免误伤 {@code vue3_vben5}、{@code vue3_admin_uniapp} 等目录。 | |
| 18 | + */ | |
| 19 | + private static final String CLASSIC_VUE3_TEMPLATE_PREFIX = "codegen/vue3/"; | |
| 20 | + | |
| 21 | + private CodegenFormatUtils() { | |
| 22 | + } | |
| 23 | + | |
| 24 | + /** | |
| 25 | + * 经典 Vue2 / Vue3 模板(Element UI / Element Plus 标准模版) | |
| 26 | + */ | |
| 27 | + static boolean isClassicVueTemplate(String vmPath) { | |
| 28 | + if (StrUtil.isEmpty(vmPath)) { | |
| 29 | + return false; | |
| 30 | + } | |
| 31 | + return StrUtil.startWith(vmPath, CLASSIC_VUE2_TEMPLATE_PREFIX) | |
| 32 | + || StrUtil.startWith(vmPath, CLASSIC_VUE3_TEMPLATE_PREFIX); | |
| 33 | + } | |
| 34 | + | |
| 35 | + /** | |
| 36 | + * 是否处理 {@code ,\n}} / {@code ,\n }} 形式的末尾逗号(经典 Vue + vue3_vben2;排除 vben5、uniapp) | |
| 37 | + */ | |
| 38 | + static boolean needsVueTrailingCommaBeforeBraceFix(String vmPath) { | |
| 39 | + if (StrUtil.containsAny(vmPath, "vben5", "vue3_admin_uniapp")) { | |
| 40 | + return false; | |
| 41 | + } | |
| 42 | + return isClassicVueTemplate(vmPath) || StrUtil.startWith(vmPath, "codegen/vue3_vben/"); | |
| 43 | + } | |
| 44 | + | |
| 45 | + /** | |
| 46 | + * 是否处理 {@code ,\n)} 形式的末尾逗号(仅经典 Vue 的 .vue 视图模板) | |
| 47 | + */ | |
| 48 | + static boolean needsReactiveTrailingCommaFix(String vmPath) { | |
| 49 | + return isClassicVueTemplate(vmPath) && StrUtil.endWith(vmPath, ".vue.vm"); | |
| 50 | + } | |
| 51 | + | |
| 52 | + /** | |
| 53 | + * Vue 模板:统一去除对象/参数列表末尾多余逗号(与 {@link CodegenEngine#prettyCode} 历史行为一致) | |
| 54 | + */ | |
| 55 | + static String applyVueTrailingCommaFix(String content, String vmPath) { | |
| 56 | + if (content == null) { | |
| 57 | + return null; | |
| 58 | + } | |
| 59 | + content = normalizeLineSeparators(content); | |
| 60 | + if (needsVueTrailingCommaBeforeBraceFix(vmPath)) { | |
| 61 | + content = content.replaceAll(",\\r?\\n}", "\n}") | |
| 62 | + .replaceAll(",\\r?\\n }", "\n }"); | |
| 63 | + } | |
| 64 | + if (needsReactiveTrailingCommaFix(vmPath)) { | |
| 65 | + content = removeTrailingCommaBeforeCloseParen(content); | |
| 66 | + } | |
| 67 | + return content; | |
| 68 | + } | |
| 69 | + | |
| 70 | + /** | |
| 71 | + * 去除多行实参中 {@code ,\n)} 形式的末尾逗号(如 {@code foo(bar,\n)};{@code reactive} 的 {@code ,\n})} 由 {@code ,\n}} 规则处理) | |
| 72 | + */ | |
| 73 | + static String removeTrailingCommaBeforeCloseParen(String content) { | |
| 74 | + if (content == null) { | |
| 75 | + return null; | |
| 76 | + } | |
| 77 | + return content.replaceAll(",\\r?\\n\\)", "\n)"); | |
| 78 | + } | |
| 79 | + | |
| 80 | + /** | |
| 81 | + * 统一换行符为 LF | |
| 82 | + */ | |
| 83 | + static String normalizeLineSeparators(String content) { | |
| 84 | + if (content == null) { | |
| 85 | + return null; | |
| 86 | + } | |
| 87 | + return content.replace("\r\n", "\n").replace('\r', '\n'); | |
| 88 | + } | |
| 89 | + | |
| 90 | + /** | |
| 91 | + * 生产生成结果:LF + 保证文件以单个换行结尾(符合 POSIX / IDE 习惯) | |
| 92 | + */ | |
| 93 | + static String formatGeneratedContent(String content) { | |
| 94 | + content = normalizeLineSeparators(content); | |
| 95 | + if (StrUtil.isEmpty(content)) { | |
| 96 | + return content; | |
| 97 | + } | |
| 98 | + while (content.endsWith("\n\n")) { | |
| 99 | + content = content.substring(0, content.length() - 1); | |
| 100 | + } | |
| 101 | + if (!content.endsWith("\n")) { | |
| 102 | + content = content + "\n"; | |
| 103 | + } | |
| 104 | + return content; | |
| 105 | + } | |
| 106 | + | |
| 107 | + /** | |
| 108 | + * 单测断言:LF + 去掉末尾空白(与历史 fixture 无尾换行对齐) | |
| 109 | + */ | |
| 110 | + static String normalizeForAssert(String content) { | |
| 111 | + if (content == null) { | |
| 112 | + return null; | |
| 113 | + } | |
| 114 | + return normalizeLineSeparators(content).stripTrailing(); | |
| 115 | + } | |
| 116 | +} | ... | ... |
yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngineAbstractTest.java
| ... | ... | @@ -18,12 +18,7 @@ import org.mockito.Spy; |
| 18 | 18 | |
| 19 | 19 | import java.io.ByteArrayInputStream; |
| 20 | 20 | import java.io.ByteArrayOutputStream; |
| 21 | -import java.util.ArrayList; | |
| 22 | -import java.util.HashMap; | |
| 23 | -import java.util.LinkedHashSet; | |
| 24 | -import java.util.List; | |
| 25 | -import java.util.Map; | |
| 26 | -import java.util.Set; | |
| 21 | +import java.util.*; | |
| 27 | 22 | |
| 28 | 23 | import static org.junit.jupiter.api.Assertions.assertEquals; |
| 29 | 24 | |
| ... | ... | @@ -104,15 +99,14 @@ public abstract class CodegenEngineAbstractTest extends BaseMockitoUnitTest { |
| 104 | 99 | .map(m -> (String) m.get("filePath")) |
| 105 | 100 | .collect(java.util.stream.Collectors.toCollection(LinkedHashSet::new)); |
| 106 | 101 | assertEquals(expectedFiles, result.keySet(), "生成文件集合不匹配"); |
| 107 | - // 校验每个文件;归一化 \r\n 为 \n,让断言不依赖文件落盘的换行风格 | |
| 102 | + // 校验每个文件;归一化换行符,让断言不依赖文件落盘的换行风格 | |
| 108 | 103 | asserts.forEach(assertMap -> { |
| 109 | 104 | String contentPath = (String) assertMap.get("contentPath"); |
| 110 | 105 | String filePath = (String) assertMap.get("filePath"); |
| 111 | - String expected = ResourceUtil.readUtf8Str("codegen/" + path + "/" + contentPath) | |
| 112 | - .replace("\r\n", "\n"); | |
| 113 | - String actual = result.get(filePath); | |
| 114 | - assertEquals(expected, actual == null ? null : actual.replace("\r\n", "\n"), | |
| 115 | - filePath + ":不匹配"); | |
| 106 | + String expected = CodegenFormatUtils.normalizeForAssert( | |
| 107 | + ResourceUtil.readUtf8Str("codegen/" + path + "/" + contentPath)); | |
| 108 | + String actual = CodegenFormatUtils.normalizeForAssert(result.get(filePath)); | |
| 109 | + assertEquals(expected, actual, filePath + ":不匹配"); | |
| 116 | 110 | }); |
| 117 | 111 | } |
| 118 | 112 | ... | ... |
yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenFormatUtilsTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.infra.service.codegen.inner; | |
| 2 | + | |
| 3 | +import org.junit.jupiter.api.Test; | |
| 4 | + | |
| 5 | +import static org.junit.jupiter.api.Assertions.*; | |
| 6 | + | |
| 7 | +class CodegenFormatUtilsTest { | |
| 8 | + | |
| 9 | + @Test | |
| 10 | + void testNormalizeLineSeparators() { | |
| 11 | + assertEquals("a\nb", CodegenFormatUtils.normalizeLineSeparators("a\r\nb")); | |
| 12 | + assertEquals("a\nb", CodegenFormatUtils.normalizeLineSeparators("a\rb")); | |
| 13 | + } | |
| 14 | + | |
| 15 | + @Test | |
| 16 | + void testFormatGeneratedContent() { | |
| 17 | + assertEquals("class A {\n}\n", CodegenFormatUtils.formatGeneratedContent("class A {\r\n}\r\n")); | |
| 18 | + assertEquals("x\n", CodegenFormatUtils.formatGeneratedContent("x")); | |
| 19 | + assertEquals("x\n", CodegenFormatUtils.formatGeneratedContent("x\n\n\n")); | |
| 20 | + assertEquals("", CodegenFormatUtils.formatGeneratedContent("")); | |
| 21 | + assertEquals(" \n", CodegenFormatUtils.formatGeneratedContent(" ")); | |
| 22 | + } | |
| 23 | + | |
| 24 | + @Test | |
| 25 | + void testNormalizeForAssert() { | |
| 26 | + assertEquals("}", CodegenFormatUtils.normalizeForAssert("}\n")); | |
| 27 | + assertEquals("a\nb", CodegenFormatUtils.normalizeForAssert("a\r\nb\r\n")); | |
| 28 | + } | |
| 29 | + | |
| 30 | + @Test | |
| 31 | + void testNormalizeForAssert_matchesFormatGeneratedContentForAssert() { | |
| 32 | + String raw = "package demo;\r\n\r\npublic class A {\r\n private Long id;\r\n}\r\n"; | |
| 33 | + String generated = CodegenFormatUtils.formatGeneratedContent(raw); | |
| 34 | + assertEquals(CodegenFormatUtils.normalizeForAssert(raw), | |
| 35 | + CodegenFormatUtils.normalizeForAssert(generated)); | |
| 36 | + } | |
| 37 | + | |
| 38 | + @Test | |
| 39 | + void testRemoveTrailingCommaBeforeCloseParen() { | |
| 40 | + assertEquals("foo(bar\n)", CodegenFormatUtils.removeTrailingCommaBeforeCloseParen("foo(bar,\n)")); | |
| 41 | + } | |
| 42 | + | |
| 43 | + @Test | |
| 44 | + void testApplyVueTrailingCommaFix_classicVueReactive() { | |
| 45 | + String input = "const queryParams = reactive({\n createTime: [],\n})\n"; | |
| 46 | + String expected = "const queryParams = reactive({\n createTime: []\n})\n"; | |
| 47 | + assertEquals(expected, CodegenFormatUtils.applyVueTrailingCommaFix( | |
| 48 | + input, "codegen/vue3/views/index.vue.vm")); | |
| 49 | + } | |
| 50 | + | |
| 51 | + @Test | |
| 52 | + void testApplyVueTrailingCommaFix_skipVben5() { | |
| 53 | + String input = "const x = {\n a: 1,\n}\n"; | |
| 54 | + assertEquals(input, CodegenFormatUtils.applyVueTrailingCommaFix( | |
| 55 | + input, "codegen/vue3_vben5_antd/general/views/index.vue.vm")); | |
| 56 | + } | |
| 57 | + | |
| 58 | + @Test | |
| 59 | + void testIsClassicVueTemplate() { | |
| 60 | + assertTrue(CodegenFormatUtils.isClassicVueTemplate("codegen/vue/views/index.vue.vm")); | |
| 61 | + assertTrue(CodegenFormatUtils.isClassicVueTemplate("codegen/vue3/views/index.vue.vm")); | |
| 62 | + assertTrue(CodegenFormatUtils.isClassicVueTemplate("codegen/vue3/api/api.ts.vm")); | |
| 63 | + assertFalse(CodegenFormatUtils.isClassicVueTemplate("codegen/vue3_vben5_antd/schema/views/index.vue.vm")); | |
| 64 | + assertFalse(CodegenFormatUtils.isClassicVueTemplate("codegen/vue3_vben/views/index.vue.vm")); | |
| 65 | + assertFalse(CodegenFormatUtils.isClassicVueTemplate("codegen/vue3_admin_uniapp/views/index.vue.vm")); | |
| 66 | + assertFalse(CodegenFormatUtils.isClassicVueTemplate("codegen/vue30/views/index.vue.vm")); // 非 codegen/vue3/ 前缀 | |
| 67 | + assertFalse(CodegenFormatUtils.isClassicVueTemplate("codegen/java/controller/controller.vm")); | |
| 68 | + assertFalse(CodegenFormatUtils.isClassicVueTemplate(null)); | |
| 69 | + } | |
| 70 | + | |
| 71 | + @Test | |
| 72 | + void testNeedsReactiveTrailingCommaFix() { | |
| 73 | + assertTrue(CodegenFormatUtils.needsReactiveTrailingCommaFix("codegen/vue3/views/index.vue.vm")); | |
| 74 | + assertFalse(CodegenFormatUtils.needsReactiveTrailingCommaFix("codegen/vue3/api/api.ts.vm")); | |
| 75 | + assertFalse(CodegenFormatUtils.needsReactiveTrailingCommaFix("codegen/vue3_vben5_antd/general/views/index.vue.vm")); | |
| 76 | + } | |
| 77 | + | |
| 78 | + @Test | |
| 79 | + void testNeedsVueTrailingCommaBeforeBraceFix() { | |
| 80 | + assertTrue(CodegenFormatUtils.needsVueTrailingCommaBeforeBraceFix("codegen/vue3/views/index.vue.vm")); | |
| 81 | + assertTrue(CodegenFormatUtils.needsVueTrailingCommaBeforeBraceFix("codegen/vue3_vben/views/index.vue.vm")); | |
| 82 | + assertFalse(CodegenFormatUtils.needsVueTrailingCommaBeforeBraceFix("codegen/vue3_vben5_antd/general/views/index.vue.vm")); | |
| 83 | + assertFalse(CodegenFormatUtils.needsVueTrailingCommaBeforeBraceFix("codegen/vue3_admin_uniapp/views/index.vue.vm")); | |
| 84 | + } | |
| 85 | +} | |
| 86 | + | ... | ... |