This commit is contained in:
skirtle 2025-05-05 20:41:54 +00:00 committed by GitHub
commit 148afd352b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 456 additions and 8 deletions

View File

@ -3848,6 +3848,380 @@ exports[`compiler: parse > Errors > UNEXPECTED_SOLIDUS_IN_TAG > <template><div a
}
`;
exports[`compiler: parse > Errors > X_DIRECTIVE_SHORTHAND_NO_ARGUMENT > <div #="obj" /> 1`] = `
{
"cached": 0,
"children": [
{
"children": [],
"codegenNode": undefined,
"isSelfClosing": true,
"loc": {
"end": {
"column": 16,
"line": 1,
"offset": 15,
},
"source": "<div #="obj" />",
"start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"ns": 0,
"props": [
{
"arg": undefined,
"exp": {
"constType": 0,
"content": "obj",
"isStatic": false,
"loc": {
"end": {
"column": 12,
"line": 1,
"offset": 11,
},
"source": "obj",
"start": {
"column": 9,
"line": 1,
"offset": 8,
},
},
"type": 4,
},
"loc": {
"end": {
"column": 13,
"line": 1,
"offset": 12,
},
"source": "#="obj"",
"start": {
"column": 6,
"line": 1,
"offset": 5,
},
},
"modifiers": [],
"name": "slot",
"rawName": "#",
"type": 7,
},
],
"tag": "div",
"tagType": 0,
"type": 1,
},
],
"codegenNode": undefined,
"components": [],
"directives": [],
"helpers": Set {},
"hoists": [],
"imports": [],
"loc": {
"end": {
"column": 16,
"line": 1,
"offset": 15,
},
"source": "<div #="obj" />",
"start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"source": "<div #="obj" />",
"temps": 0,
"type": 0,
}
`;
exports[`compiler: parse > Errors > X_DIRECTIVE_SHORTHAND_NO_ARGUMENT > <div .="obj" /> 1`] = `
{
"cached": 0,
"children": [
{
"children": [],
"codegenNode": undefined,
"isSelfClosing": true,
"loc": {
"end": {
"column": 16,
"line": 1,
"offset": 15,
},
"source": "<div .="obj" />",
"start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"ns": 0,
"props": [
{
"arg": undefined,
"exp": {
"constType": 0,
"content": "obj",
"isStatic": false,
"loc": {
"end": {
"column": 12,
"line": 1,
"offset": 11,
},
"source": "obj",
"start": {
"column": 9,
"line": 1,
"offset": 8,
},
},
"type": 4,
},
"loc": {
"end": {
"column": 13,
"line": 1,
"offset": 12,
},
"source": ".="obj"",
"start": {
"column": 6,
"line": 1,
"offset": 5,
},
},
"modifiers": [
"prop",
],
"name": "bind",
"rawName": ".",
"type": 7,
},
],
"tag": "div",
"tagType": 0,
"type": 1,
},
],
"codegenNode": undefined,
"components": [],
"directives": [],
"helpers": Set {},
"hoists": [],
"imports": [],
"loc": {
"end": {
"column": 16,
"line": 1,
"offset": 15,
},
"source": "<div .="obj" />",
"start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"source": "<div .="obj" />",
"temps": 0,
"type": 0,
}
`;
exports[`compiler: parse > Errors > X_DIRECTIVE_SHORTHAND_NO_ARGUMENT > <div :="obj" /> 1`] = `
{
"cached": 0,
"children": [
{
"children": [],
"codegenNode": undefined,
"isSelfClosing": true,
"loc": {
"end": {
"column": 16,
"line": 1,
"offset": 15,
},
"source": "<div :="obj" />",
"start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"ns": 0,
"props": [
{
"arg": undefined,
"exp": {
"constType": 0,
"content": "obj",
"isStatic": false,
"loc": {
"end": {
"column": 12,
"line": 1,
"offset": 11,
},
"source": "obj",
"start": {
"column": 9,
"line": 1,
"offset": 8,
},
},
"type": 4,
},
"loc": {
"end": {
"column": 13,
"line": 1,
"offset": 12,
},
"source": ":="obj"",
"start": {
"column": 6,
"line": 1,
"offset": 5,
},
},
"modifiers": [],
"name": "bind",
"rawName": ":",
"type": 7,
},
],
"tag": "div",
"tagType": 0,
"type": 1,
},
],
"codegenNode": undefined,
"components": [],
"directives": [],
"helpers": Set {},
"hoists": [],
"imports": [],
"loc": {
"end": {
"column": 16,
"line": 1,
"offset": 15,
},
"source": "<div :="obj" />",
"start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"source": "<div :="obj" />",
"temps": 0,
"type": 0,
}
`;
exports[`compiler: parse > Errors > X_DIRECTIVE_SHORTHAND_NO_ARGUMENT > <div @="obj" /> 1`] = `
{
"cached": 0,
"children": [
{
"children": [],
"codegenNode": undefined,
"isSelfClosing": true,
"loc": {
"end": {
"column": 16,
"line": 1,
"offset": 15,
},
"source": "<div @="obj" />",
"start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"ns": 0,
"props": [
{
"arg": undefined,
"exp": {
"constType": 0,
"content": "obj",
"isStatic": false,
"loc": {
"end": {
"column": 12,
"line": 1,
"offset": 11,
},
"source": "obj",
"start": {
"column": 9,
"line": 1,
"offset": 8,
},
},
"type": 4,
},
"loc": {
"end": {
"column": 13,
"line": 1,
"offset": 12,
},
"source": "@="obj"",
"start": {
"column": 6,
"line": 1,
"offset": 5,
},
},
"modifiers": [],
"name": "on",
"rawName": "@",
"type": 7,
},
],
"tag": "div",
"tagType": 0,
"type": 1,
},
],
"codegenNode": undefined,
"components": [],
"directives": [],
"helpers": Set {},
"hoists": [],
"imports": [],
"loc": {
"end": {
"column": 16,
"line": 1,
"offset": 15,
},
"source": "<div @="obj" />",
"start": {
"column": 1,
"line": 1,
"offset": 0,
},
},
"source": "<div @="obj" />",
"temps": 0,
"type": 0,
}
`;
exports[`compiler: parse > Errors > X_INVALID_END_TAG > <svg><![CDATA[</div>]]></svg> 1`] = `
{
"cached": [],

View File

@ -2254,6 +2254,9 @@ describe('compiler: parse', () => {
exp: undefined,
arg: undefined,
})
expect(
`the directive shorthand '#' cannot be used without an argument`,
).toHaveBeenWarned()
})
// edge case found in vue-macros where the input is TS or JSX
@ -2550,7 +2553,8 @@ describe('compiler: parse', () => {
const patterns: {
[key: string]: Array<{
code: string
errors: Array<{ type: ErrorCodes; loc: Position }>
errors?: Array<{ type: ErrorCodes; loc: Position }>
warnings?: Array<{ type: ErrorCodes; loc: Position }>
options?: Partial<ParserOptions>
}>
} = {
@ -3420,32 +3424,80 @@ describe('compiler: parse', () => {
],
},
],
X_DIRECTIVE_SHORTHAND_NO_ARGUMENT: [
{
code: `<div :="obj" />`,
warnings: [
{
type: ErrorCodes.X_DIRECTIVE_SHORTHAND_NO_ARGUMENT,
loc: { offset: 5, line: 1, column: 6 },
},
],
},
{
code: `<div .="obj" />`,
warnings: [
{
type: ErrorCodes.X_DIRECTIVE_SHORTHAND_NO_ARGUMENT,
loc: { offset: 5, line: 1, column: 6 },
},
],
},
{
code: `<div @="obj" />`,
warnings: [
{
type: ErrorCodes.X_DIRECTIVE_SHORTHAND_NO_ARGUMENT,
loc: { offset: 5, line: 1, column: 6 },
},
],
},
{
code: `<div #="obj" />`,
warnings: [
{
type: ErrorCodes.X_DIRECTIVE_SHORTHAND_NO_ARGUMENT,
loc: { offset: 5, line: 1, column: 6 },
},
],
},
],
}
for (const key of Object.keys(patterns)) {
describe(key, () => {
for (const { code, errors, options } of patterns[key]) {
for (const { code, errors = [], warnings = [], options } of patterns[
key
]) {
test(
code.replace(
/[\r\n]/g,
c => `\\x0${c.codePointAt(0)!.toString(16)};`,
),
() => {
const spy = vi.fn()
const errorSpy = vi.fn()
const warnSpy = vi.fn()
const ast = baseParse(code, {
parseMode: 'html',
getNamespace: tag =>
tag === 'svg' ? Namespaces.SVG : Namespaces.HTML,
...options,
onError: spy,
onError: errorSpy,
onWarn: warnSpy,
})
expect(
spy.mock.calls.map(([err]) => ({
errorSpy.mock.calls.map(([err]) => ({
type: err.code,
loc: err.loc.start,
})),
).toMatchObject(errors)
expect(
warnSpy.mock.calls.map(([err]) => ({
type: err.code,
loc: err.loc.start,
})),
).toMatchObject(warnings)
expect(ast).toMatchSnapshot()
},
)

View File

@ -90,6 +90,7 @@ export enum ErrorCodes {
X_V_MODEL_ON_PROPS,
X_INVALID_EXPRESSION,
X_KEEP_ALIVE_INVALID_CHILDREN,
X_DIRECTIVE_SHORTHAND_NO_ARGUMENT,
// generic errors
X_PREFIX_ID_NOT_SUPPORTED,
@ -179,6 +180,7 @@ export const errorMessages: Record<ErrorCodes, string> = {
[ErrorCodes.X_INVALID_EXPRESSION]: `Error parsing JavaScript expression: `,
[ErrorCodes.X_KEEP_ALIVE_INVALID_CHILDREN]: `<KeepAlive> expects exactly one child component.`,
[ErrorCodes.X_VNODE_HOOKS]: `@vnode-* hooks in templates are no longer supported. Use the vue: prefix instead. For example, @vnode-mounted should be changed to @vue:mounted. @vnode-* hooks support has been removed in 3.4.`,
[ErrorCodes.X_DIRECTIVE_SHORTHAND_NO_ARGUMENT]: `Directive shorthand without an argument: `,
// generic errors
[ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED]: `"prefixIdentifiers" option is not supported in this build of compiler.`,

View File

@ -244,7 +244,21 @@ const tokenizer = new Tokenizer(stack, {
},
ondirarg(start, end) {
if (start === end) return
if (start === end) {
if (__DEV__) {
const currentDir = currentProp as DirectiveNode
if (currentDir.rawName?.length === 1) {
emitWarning(
ErrorCodes.X_DIRECTIVE_SHORTHAND_NO_ARGUMENT,
start - 1,
`the directive shorthand '${currentDir.rawName}' cannot be used without an argument. ` +
`Use v-${currentDir.name} instead or provide an argument.`,
)
}
}
return
}
const arg = getSlice(start, end)
if (inVPre) {
;(currentProp as AttributeNode).name += arg
@ -1025,6 +1039,12 @@ function emitError(code: ErrorCodes, index: number, message?: string) {
)
}
function emitWarning(code: ErrorCodes, index: number, message?: string) {
currentOptions.onWarn(
createCompilerError(code, getLoc(index, index), undefined, message),
)
}
function reset() {
tokenizer.reset()
currentOpenTag = null

View File

@ -21,7 +21,7 @@ export function createDOMCompilerError(
}
export enum DOMErrorCodes {
X_V_HTML_NO_EXPRESSION = 53 /* ErrorCodes.__EXTEND_POINT__ */,
X_V_HTML_NO_EXPRESSION = 54 /* ErrorCodes.__EXTEND_POINT__ */,
X_V_HTML_WITH_CHILDREN,
X_V_TEXT_NO_EXPRESSION,
X_V_TEXT_WITH_CHILDREN,

View File

@ -17,7 +17,7 @@ export function createSSRCompilerError(
}
export enum SSRErrorCodes {
X_SSR_UNSAFE_ATTR_NAME = 65 /* DOMErrorCodes.__EXTEND_POINT__ */,
X_SSR_UNSAFE_ATTR_NAME = 66 /* DOMErrorCodes.__EXTEND_POINT__ */,
X_SSR_NO_TELEPORT_TARGET,
X_SSR_INVALID_AST_NODE,
}