Skip to content

Commit

Permalink
Merge pull request #494 from sveltejs/validate-bindings
Browse files Browse the repository at this point in the history
validate bindings
  • Loading branch information
Rich-Harris authored Apr 19, 2017
2 parents 3d57658 + e306366 commit 771dacc
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 32 deletions.
38 changes: 10 additions & 28 deletions src/validate/html/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import * as namespaces from '../../utils/namespaces.js';
import flattenReference from '../../utils/flattenReference.js';
import validateElement from './validateElement.js';
import validateWindow from './validateWindow.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 ]}`;
}
const meta = new Map([
[ ':Window', validateWindow ]
]);

export default function validateHtml ( validator, html ) {
let elementDepth = 0;
Expand All @@ -17,31 +17,13 @@ export default function validateHtml ( validator, html ) {
validator.warn( `<${node.name}> is an SVG element – did you forget to add { namespace: 'svg' } ?`, node.start );
}

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 ( meta.has( node.name ) ) {
return meta.get( node.name )( validator, node );
}

if ( callee.type === 'Identifier' && callee.name in validator.helpers ) {
message += `. '${callee.name}' exists on 'helpers', did you put it in the wrong place?`;
}
elementDepth += 1;

validator.error( message, start );
}
});
validateElement( validator, node );
}

if ( node.children ) {
Expand Down
85 changes: 85 additions & 0 deletions src/validate/html/validateElement.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import flattenReference from '../../utils/flattenReference.js';

export default function validateElement ( validator, node ) {
const isComponent = node.name === ':Self' || validator.components.has( node.name );

node.attributes.forEach( attribute => {
if ( !isComponent && attribute.type === 'Binding' ) {
const { name } = attribute;

if ( name === 'value' ) {
if ( node.name !== 'input' && node.name !== 'textarea' && node.name !== 'select' ) {
validator.error( `'value' is not a valid binding on <${node.name}> elements`, attribute.start );
}
}

else if ( name === 'checked' ) {
if ( node.name !== 'input' ) {
validator.error( `'checked' is not a valid binding on <${node.name}> elements`, attribute.start );
}

if ( getType( validator, node ) !== 'checkbox' ) {
validator.error( `'checked' binding can only be used with <input type="checkbox">` );
}
}

else if ( name === 'group' ) {
if ( node.name !== 'input' ) {
validator.error( `'group' is not a valid binding on <${node.name}> elements`, attribute.start );
}

const type = getType( validator, node );

if ( type !== 'checkbox' && type !== 'radio' ) {
validator.error( `'checked' binding can only be used with <input type="checkbox"> or <input type="radio">` );
}
}

else {
validator.error( `'${attribute.name}' is not a valid binding`, attribute.start );
}
}

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' || validator.methods.has( callee.name ) ) return;

const validCallees = list( [ 'this.*', 'event.*', 'set', 'fire' ].concat( Array.from( validator.methods.keys() ) ) );
let message = `'${validator.source.slice( callee.start, callee.end )}' is an invalid callee (should be one of ${validCallees})`;

if ( callee.type === 'Identifier' && validator.helpers.has( callee.name ) ) {
message += `. '${callee.name}' exists on 'helpers', did you put it in the wrong place?`;
}

validator.error( message, start );
}
});
}

function getType ( validator, node ) {
const attribute = node.attributes.find( attribute => attribute.name === 'type' );
if ( !attribute ) return null;

if ( attribute.value === true ) {
validator.error( `'type' attribute must be specified`, attribute.start );
}

if ( attribute.value.length > 1 || attribute.value[0].type !== 'Text' ) {
validator.error( `'type attribute cannot be dynamic`, attribute.start );
}

return attribute.value[0].data;
}

function list ( items, conjunction = 'or' ) {
if ( items.length === 1 ) return items[0];
return `${items.slice( 0, -1 ).join( ', ' )} ${conjunction} ${items[ items.length - 1 ]}`;
}
3 changes: 3 additions & 0 deletions src/validate/html/validateWindow.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function validateWindow () {
// TODO
}
5 changes: 3 additions & 2 deletions src/validate/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ export default function validate ( parsed, source, { onerror, onwarn, name, file
namespace: null,
defaultExport: null,
properties: {},
methods: {},
helpers: {}
components: new Map(),
methods: new Map(),
helpers: new Map()
};

try {
Expand Down
4 changes: 2 additions & 2 deletions src/validate/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@ export default function validateJs ( validator, js ) {
}
});

[ 'methods', 'helpers' ].forEach( key => {
[ 'components', 'methods', 'helpers' ].forEach( key => {
if ( validator.properties[ key ] ) {
validator.properties[ key ].value.properties.forEach( prop => {
validator[ key ][ prop.key.name ] = prop.value;
validator[ key ].set( prop.key.name, prop.value );
});
}
});
Expand Down
8 changes: 8 additions & 0 deletions test/validator/samples/binding-invalid-on-element/errors.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[{
"message": "'value' is not a valid binding on <div> elements",
"pos": 5,
"loc": {
"line": 1,
"column": 5
}
}]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div bind:value></div>
8 changes: 8 additions & 0 deletions test/validator/samples/binding-invalid/errors.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[{
"message": "'whatever' is not a valid binding",
"pos": 5,
"loc": {
"line": 1,
"column": 5
}
}]
1 change: 1 addition & 0 deletions test/validator/samples/binding-invalid/input.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div bind:whatever></div>

0 comments on commit 771dacc

Please sign in to comment.