diff --git a/src/validate/html/index.js b/src/validate/html/index.js index f54c4d4fe5..8cad9bd849 100644 --- a/src/validate/html/index.js +++ b/src/validate/html/index.js @@ -1,7 +1,13 @@ import * as namespaces from '../../utils/namespaces.js'; +import flattenReference from '../../utils/flattenReference.js'; const svg = /^(?:altGlyph|altGlyphDef|altGlyphItem|animate|animateColor|animateMotion|animateTransform|circle|clipPath|color-profile|cursor|defs|desc|discard|ellipse|feBlend|feColorMatrix|feComponentTransfer|feComposite|feConvolveMatrix|feDiffuseLighting|feDisplacementMap|feDistantLight|feDropShadow|feFlood|feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feImage|feMerge|feMergeNode|feMorphology|feOffset|fePointLight|feSpecularLighting|feSpotLight|feTile|feTurbulence|filter|font|font-face|font-face-format|font-face-name|font-face-src|font-face-uri|foreignObject|g|glyph|glyphRef|hatch|hatchpath|hkern|image|line|linearGradient|marker|mask|mesh|meshgradient|meshpatch|meshrow|metadata|missing-glyph|mpath|path|pattern|polygon|polyline|radialGradient|rect|set|solidcolor|stop|switch|symbol|text|textPath|title|tref|tspan|unknown|use|view|vkern)$/; +function list ( items, conjunction = 'or' ) { + if ( items.length === 1 ) return items[0]; + return `${items.slice( 0, -1 ).join( ', ' )} ${conjunction} ${items[ items.length - 1 ]}`; +} + export default function validateHtml ( validator, html ) { let elementDepth = 0; @@ -12,6 +18,30 @@ export default function validateHtml ( validator, html ) { } elementDepth += 1; + + node.attributes.forEach( attribute => { + if ( attribute.type === 'EventHandler' ) { + const { callee, start, type } = attribute.expression; + + if ( type !== 'CallExpression' ) { + validator.error( `Expected a call expression`, start ); + } + + const { name } = flattenReference( callee ); + + if ( name === 'this' || name === 'event' ) return; + if ( callee.type === 'Identifier' && callee.name === 'set' || callee.name === 'fire' || callee.name in validator.methods ) return; + + const validCallees = list( [ 'this.*', 'event.*', 'set', 'fire' ].concat( Object.keys( validator.methods ) ) ); + let message = `'${validator.source.slice( callee.start, callee.end )}' is an invalid callee (should be one of ${validCallees})`; + + if ( callee.type === 'Identifier' && callee.name in validator.helpers ) { + message += `. '${callee.name}' exists on 'helpers', did you put it in the wrong place?`; + } + + validator.error( message, start ); + } + }); } if ( node.children ) { diff --git a/src/validate/index.js b/src/validate/index.js index 4efdee8d47..f0a274c9eb 100644 --- a/src/validate/index.js +++ b/src/validate/index.js @@ -36,9 +36,13 @@ export default function validate ( parsed, source, { onerror, onwarn, name, file }); }, + source, + namespace: null, defaultExport: null, - properties: {} + properties: {}, + methods: {}, + helpers: {} }; if ( name && !/^[a-zA-Z_$][a-zA-Z_$0-9]*$/.test( name ) ) { diff --git a/src/validate/js/index.js b/src/validate/js/index.js index d0fc396320..3f7e8c0e42 100644 --- a/src/validate/js/index.js +++ b/src/validate/js/index.js @@ -72,4 +72,12 @@ export default function validateJs ( validator, js ) { }); } }); + + [ 'methods', 'helpers' ].forEach( key => { + if ( validator.properties[ key ] ) { + validator.properties[ key ].value.properties.forEach( prop => { + validator[ key ][ prop.key.name ] = prop.value; + }); + } + }); } diff --git a/test/validator/samples/method-nonexistent-helper/errors.json b/test/validator/samples/method-nonexistent-helper/errors.json new file mode 100644 index 0000000000..80183498ca --- /dev/null +++ b/test/validator/samples/method-nonexistent-helper/errors.json @@ -0,0 +1,8 @@ +[{ + "message": "'foo' is an invalid callee (should be one of this.*, event.*, set, fire or bar). 'foo' exists on 'helpers', did you put it in the wrong place?", + "pos": 18, + "loc": { + "line": 1, + "column": 18 + } +}] diff --git a/test/validator/samples/method-nonexistent-helper/input.html b/test/validator/samples/method-nonexistent-helper/input.html new file mode 100644 index 0000000000..abb756f6c3 --- /dev/null +++ b/test/validator/samples/method-nonexistent-helper/input.html @@ -0,0 +1,17 @@ + + + diff --git a/test/validator/samples/method-nonexistent/errors.json b/test/validator/samples/method-nonexistent/errors.json new file mode 100644 index 0000000000..2eaf6c215d --- /dev/null +++ b/test/validator/samples/method-nonexistent/errors.json @@ -0,0 +1,8 @@ +[{ + "message": "'foo' is an invalid callee (should be one of this.*, event.*, set, fire or bar)", + "pos": 18, + "loc": { + "line": 1, + "column": 18 + } +}] diff --git a/test/validator/samples/method-nonexistent/input.html b/test/validator/samples/method-nonexistent/input.html new file mode 100644 index 0000000000..75f2028b92 --- /dev/null +++ b/test/validator/samples/method-nonexistent/input.html @@ -0,0 +1,11 @@ + + +