diff --git a/packages/compiler-core/src/index.ts b/packages/compiler-core/src/index.ts index 4898a181d..8aaa0d77e 100644 --- a/packages/compiler-core/src/index.ts +++ b/packages/compiler-core/src/index.ts @@ -25,6 +25,8 @@ export { generate, type CodegenContext, type CodegenResult } from './codegen' export { ErrorCodes, createCompilerError, + defaultOnError, + defaultOnWarn, type CoreCompilerError, type CompilerError } from './errors' diff --git a/packages/compiler-vapor/__tests__/compile.test.ts b/packages/compiler-vapor/__tests__/compile.test.ts index 979bb094f..a70442eed 100644 --- a/packages/compiler-vapor/__tests__/compile.test.ts +++ b/packages/compiler-vapor/__tests__/compile.test.ts @@ -2,6 +2,7 @@ import { BindingTypes, CompilerOptions, RootNode } from '@vue/compiler-dom' // TODO remove it import { format } from 'prettier' import { compile as _compile } from '../src' +import { ErrorCodes } from '../src/errors' async function compile( template: string | RootNode, @@ -64,6 +65,25 @@ describe('comile', () => { }) expect(code).matchSnapshot() }) + + test('should error if no expression', async () => { + const onError = vi.fn() + await compile(`
`, { onError }) + + expect(onError.mock.calls[0][0]).toMatchObject({ + code: ErrorCodes.VAPOR_BIND_NO_EXPRESSION, + loc: { + start: { + line: 1, + column: 6, + }, + end: { + line: 1, + column: 16, + }, + }, + }) + }) }) describe('v-on', () => { @@ -75,6 +95,24 @@ describe('comile', () => { }) expect(code).matchSnapshot() }) + + test('should error if no expression AND no modifier', async () => { + const onError = vi.fn() + await compile(`
`, { onError }) + expect(onError.mock.calls[0][0]).toMatchObject({ + code: ErrorCodes.VAPOR_ON_NO_EXPRESSION, + loc: { + start: { + line: 1, + column: 6, + }, + end: { + line: 1, + column: 16, + }, + }, + }) + }) }) describe('v-html', () => { diff --git a/packages/compiler-vapor/src/errors.ts b/packages/compiler-vapor/src/errors.ts new file mode 100644 index 000000000..0f343e703 --- /dev/null +++ b/packages/compiler-vapor/src/errors.ts @@ -0,0 +1,19 @@ +export { + createCompilerError, + defaultOnError, + defaultOnWarn, + type CoreCompilerError, + type CompilerError, +} from '@vue/compiler-dom' + +export const enum ErrorCodes { + // transform errors + VAPOR_BIND_NO_EXPRESSION, + VAPOR_ON_NO_EXPRESSION, +} + +export const errorMessages: Record = { + // transform errors + [ErrorCodes.VAPOR_BIND_NO_EXPRESSION]: `v-bind is missing expression.`, + [ErrorCodes.VAPOR_ON_NO_EXPRESSION]: `v-on is missing expression.`, +} diff --git a/packages/compiler-vapor/src/transform.ts b/packages/compiler-vapor/src/transform.ts index a948284fa..510e7a1f3 100644 --- a/packages/compiler-vapor/src/transform.ts +++ b/packages/compiler-vapor/src/transform.ts @@ -1,14 +1,14 @@ -import type { +import { NodeTypes, - RootNode, - Node, - TemplateChildNode, - ElementNode, - AttributeNode, - InterpolationNode, - TransformOptions, - DirectiveNode, - ExpressionNode, + type RootNode, + type Node, + type TemplateChildNode, + type ElementNode, + type AttributeNode, + type InterpolationNode, + type TransformOptions, + type DirectiveNode, + type ExpressionNode, } from '@vue/compiler-dom' import { type OperationNode, @@ -17,6 +17,12 @@ import { DynamicInfo, } from './ir' import { isVoidTag } from '@vue/shared' +import { + ErrorCodes, + createCompilerError, + defaultOnError, + defaultOnWarn, +} from './errors' export interface TransformContext { node: T @@ -129,6 +135,9 @@ export function transform( root: RootNode, options: TransformOptions = {}, ): RootIRNode { + options.onError ||= defaultOnError + options.onWarn ||= defaultOnWarn + const ir: RootIRNode = { type: IRNodeTypes.ROOT, loc: root.loc, @@ -145,6 +154,7 @@ export function transform( helpers: new Set([]), vaporHelpers: new Set([]), } + const ctx = createRootContext(ir, root, options) // TODO: transform presets, see packages/compiler-core/src/transforms @@ -343,9 +353,21 @@ function transformProp( return } - const expr = processExpression(ctx, node.exp) + const { exp, loc, modifiers } = node + + const expr = processExpression(ctx, exp) switch (name) { case 'bind': { + if ( + !exp || + (exp.type === NodeTypes.SIMPLE_EXPRESSION! && !exp.content.trim()) + ) { + ctx.options.onError!( + createCompilerError(ErrorCodes.VAPOR_BIND_NO_EXPRESSION, loc), + ) + return + } + if (expr === null) { // TODO: Vue 3.4 supported shorthand syntax // https://github.com/vuejs/core/pull/9451 @@ -370,6 +392,13 @@ function transformProp( break } case 'on': { + if (!exp && !modifiers.length) { + ctx.options.onError!( + createCompilerError(ErrorCodes.VAPOR_ON_NO_EXPRESSION, loc), + ) + return + } + if (!node.arg) { // TODO support v-on="{}" return