diff --git a/src/compiler/parser/html-parser.js b/src/compiler/parser/html-parser.js index 1b304fbdb6a..d237f517775 100644 --- a/src/compiler/parser/html-parser.js +++ b/src/compiler/parser/html-parser.js @@ -15,6 +15,7 @@ import { unicodeLetters } from 'core/util/lang' // Regular Expressions for parsing tags and attributes const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/ +const dynamicArgAttribute = /^\s*((?:v-[\w-]+:|@|:|#)\[[^=]+\][^\s"'<>\/=]*)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/ const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z${unicodeLetters}]*` const qnameCapture = `((?:${ncname}\\:)?${ncname})` const startTagOpen = new RegExp(`^<${qnameCapture}`) @@ -192,7 +193,7 @@ export function parseHTML (html, options) { } advance(start[0].length) let end, attr - while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) { + while (!(end = html.match(startTagClose)) && (attr = html.match(dynamicArgAttribute) || html.match(attribute))) { attr.start = index advance(attr[0].length) attr.end = index diff --git a/src/compiler/parser/index.js b/src/compiler/parser/index.js index e3433928059..145d5638bb8 100644 --- a/src/compiler/parser/index.js +++ b/src/compiler/parser/index.js @@ -38,6 +38,8 @@ const slotRE = /^v-slot(:|$)|^#/ const lineBreakRE = /[\r\n]/ const whitespaceRE = /\s+/g +const invalidAttributeRE = /[\s"'<>\/=]/ + const decodeHTMLCached = cached(he.decode) // configurable state @@ -194,12 +196,26 @@ export function parse ( element.ns = ns } - if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) { - element.start = start - element.rawAttrsMap = element.attrsList.reduce((cumulated, attr) => { - cumulated[attr.name] = attr - return cumulated - }, {}) + if (process.env.NODE_ENV !== 'production') { + if (options.outputSourceRange) { + element.start = start + element.rawAttrsMap = element.attrsList.reduce((cumulated, attr) => { + cumulated[attr.name] = attr + return cumulated + }, {}) + } + attrs.forEach(attr => { + if (invalidAttributeRE.test(attr.name)) { + warn( + `Invalid dynamic argument expression: attribute names cannot contain ` + + `spaces, quotes, <, >, / or =.`, + { + start: attr.start + attr.name.indexOf(`[`), + end: attr.start + attr.name.length + } + ) + } + }) } if (isForbiddenTag(element) && !isServerRendering()) { diff --git a/test/unit/modules/compiler/parser.spec.js b/test/unit/modules/compiler/parser.spec.js index fcfb8be6ae1..08f1745fc67 100644 --- a/test/unit/modules/compiler/parser.spec.js +++ b/test/unit/modules/compiler/parser.spec.js @@ -550,6 +550,23 @@ describe('parser', () => { expect(ast.props).toEqual([{ name: 'id', value: 'foo', dynamic: true }]) }) + // This only works for string templates. + // In-DOM templates will be malformed before Vue can parse it. + describe('parse and warn invalid dynamic arguments', () => { + [ + `
`, + `
`, + `
`, + ``, + `
` + ].forEach(template => { + it(template, () => { + const ast = parse(template, baseOptions) + expect(`Invalid dynamic argument expression`).toHaveBeenWarned() + }) + }) + }) + // #6887 it('special case static attribute that must be props', () => { const ast = parse('', baseOptions)