From 9acfab0a866a39a322a4b23db1ec3414fafa0253 Mon Sep 17 00:00:00 2001 From: Zyy Date: Sat, 15 Aug 2026 12:31:10 +0800 Subject: [PATCH] chore(frontend): enforce template interpolation layout Co-authored-by: Codex --- frontend/eslint.config.ts | 62 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/frontend/eslint.config.ts b/frontend/eslint.config.ts index b35d3fbbe..4fb7e291b 100644 --- a/frontend/eslint.config.ts +++ b/frontend/eslint.config.ts @@ -1,5 +1,48 @@ import antfu from '@antfu/eslint-config' +const templateStylePlugin = { + rules: { + 'single-line-simple-interpolation': { + meta: { + type: 'layout', + fixable: 'whitespace', + schema: [], + messages: { + expectedSingleLine: 'Expected a simple mustache interpolation to stay on one line.', + }, + }, + create(context) { + const { parserServices } = context.sourceCode + if (!parserServices.defineTemplateBodyVisitor) + return {} + + return parserServices.defineTemplateBodyVisitor({ + 'VExpressionContainer[expression!=null]': function (node) { + if (node.parent.type !== 'VElement') + return + + const expression = node.expression + if ( + expression.loc.start.line !== expression.loc.end.line + || node.loc.start.line === node.loc.end.line + ) { + return + } + + context.report({ + node, + messageId: 'expectedSingleLine', + fix(fixer) { + return fixer.replaceText(node, `{{ ${context.sourceCode.getText(expression)} }}`) + }, + }) + }, + }) + }, + }, + }, +} + export default antfu( { type: 'app', @@ -18,4 +61,23 @@ export default antfu( a11y: true, }, }, + { + files: ['**/*.vue'], + plugins: { + 'template-style': templateStylePlugin, + }, + rules: { + 'template-style/single-line-simple-interpolation': 'error', + 'vue/html-closing-bracket-newline': ['error', { + singleline: 'never', + multiline: 'always', + }], + 'vue/multiline-html-element-content-newline': ['error', { + ignores: ['pre', 'textarea'], + ignoreWhenEmpty: true, + allowEmptyLines: false, + }], + 'vue/mustache-interpolation-spacing': ['error', 'always'], + }, + }, )