validate <:Window>

pull/565/head
Rich-Harris 7 years ago
parent a85b6002a7
commit 37749bd3e4

@ -80,7 +80,8 @@
}, },
"nyc": { "nyc": {
"include": [ "include": [
"src/**/*.js" "src/**/*.js",
"shared.js"
], ],
"exclude": [ "exclude": [
"src/**/__test__.js", "src/**/__test__.js",

@ -60,11 +60,6 @@ export default function visitWindow ( generator, block, node ) {
} }
if ( attribute.type === 'Binding' ) { if ( attribute.type === 'Binding' ) {
if ( attribute.value.type !== 'Identifier' ) {
const { parts, keypath } = flattenReference( attribute.value );
throw new Error( `Bindings on <:Window/> must be to top-level properties, e.g. '${parts.pop()}' rather than '${keypath}'` );
}
// in dev mode, throw if read-only values are written to // in dev mode, throw if read-only values are written to
if ( readonly.has( attribute.name ) ) { if ( readonly.has( attribute.name ) ) {
generator.readonly.add( attribute.value.name ); generator.readonly.add( attribute.value.name );

@ -1,10 +1,4 @@
import flattenReference from '../../utils/flattenReference.js'; import validateEventHandler from './validateEventHandler.js';
const validBuiltins = new Set([
'set',
'fire',
'destroy'
]);
export default function validateElement ( validator, node ) { export default function validateElement ( validator, node ) {
const isComponent = node.name === ':Self' || validator.components.has( node.name ); const isComponent = node.name === ':Self' || validator.components.has( node.name );
@ -53,30 +47,7 @@ export default function validateElement ( validator, node ) {
} }
if ( attribute.type === 'EventHandler' ) { if ( attribute.type === 'EventHandler' ) {
const { callee, start, type } = attribute.expression; validateEventHandler( validator, attribute );
if ( type !== 'CallExpression' ) {
validator.error( `Expected a call expression`, start );
}
const { name } = flattenReference( callee );
if ( name === 'this' || name === 'event' ) return;
if ( callee.type === 'Identifier' && validBuiltins.has( callee.name ) || validator.methods.has( callee.name ) ) return;
const validCallees = [ 'this.*', 'event.*' ]
.concat(
Array.from( validBuiltins ),
Array.from( validator.methods.keys() )
);
let message = `'${validator.source.slice( callee.start, callee.end )}' is an invalid callee (should be one of ${list( 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 );
} }
}); });
} }
@ -95,8 +66,3 @@ function getType ( validator, node ) {
return attribute.value[0].data; 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 ]}`;
}

@ -0,0 +1,35 @@
import flattenReference from '../../utils/flattenReference.js';
import list from '../utils/list.js';
const validBuiltins = new Set([
'set',
'fire',
'destroy'
]);
export default function validateEventHandlerCallee ( validator, attribute ) {
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' && validBuiltins.has( callee.name ) || validator.methods.has( callee.name ) ) return;
const validCallees = [ 'this.*', 'event.*' ]
.concat(
Array.from( validBuiltins ),
Array.from( validator.methods.keys() )
);
let message = `'${validator.source.slice( callee.start, callee.end )}' is an invalid callee (should be one of ${list( 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 );
}

@ -1,3 +1,48 @@
export default function validateWindow () { import flattenReference from '../../utils/flattenReference.js';
// TODO import fuzzymatch from '../utils/fuzzymatch.js';
import list from '../utils/list.js';
import validateEventHandler from './validateEventHandler.js';
const validBindings = [
'innerWidth',
'innerHeight',
'outerWidth',
'outerHeight',
'scrollX',
'scrollY'
];
export default function validateWindow ( validator, node ) {
node.attributes.forEach( attribute => {
if ( attribute.type === 'Binding' ) {
if ( attribute.value.type !== 'Identifier' ) {
const { parts } = flattenReference( attribute.value );
validator.error(
`Bindings on <:Window/> must be to top-level properties, e.g. '${parts[ parts.length - 1 ]}' rather than '${parts.join( '.' )}'`,
attribute.value.start
);
}
if ( !~validBindings.indexOf( attribute.name ) ) {
const match = (
attribute.name === 'width' ? 'innerWidth' :
attribute.name === 'height' ? 'innerHeight' :
fuzzymatch( attribute.name, validBindings )
);
const message = `'${attribute.name}' is not a valid binding on <:Window>`;
if ( match ) {
validator.error( `${message} (did you mean '${match}'?)`, attribute.start );
} else {
validator.error( `${message} — valid bindings are ${list( validBindings )}`, attribute.start );
}
}
}
else if ( attribute.type === 'EventHandler' ) {
validateEventHandler( validator, attribute );
}
});
} }

@ -1,13 +1,11 @@
import propValidators from './propValidators/index.js'; import propValidators from './propValidators/index.js';
import FuzzySet from './utils/FuzzySet.js'; import fuzzymatch from '../utils/fuzzymatch.js';
import checkForDupes from './utils/checkForDupes.js'; import checkForDupes from './utils/checkForDupes.js';
import checkForComputedKeys from './utils/checkForComputedKeys.js'; import checkForComputedKeys from './utils/checkForComputedKeys.js';
import namespaces from '../../utils/namespaces.js'; import namespaces from '../../utils/namespaces.js';
const validPropList = Object.keys( propValidators ); const validPropList = Object.keys( propValidators );
const fuzzySet = new FuzzySet( validPropList );
export default function validateJs ( validator, js ) { export default function validateJs ( validator, js ) {
js.content.body.forEach( node => { js.content.body.forEach( node => {
// check there are no named exports // check there are no named exports
@ -45,9 +43,9 @@ export default function validateJs ( validator, js ) {
if ( propValidator ) { if ( propValidator ) {
propValidator( validator, prop ); propValidator( validator, prop );
} else { } else {
const matches = fuzzySet.get( prop.key.name ); const match = fuzzymatch( prop.key.name, validPropList );
if ( matches && matches[0] && matches[0][0] > 0.7 ) { if ( match ) {
validator.error( `Unexpected property '${prop.key.name}' (did you mean '${matches[0][1]}'?)`, prop.start ); validator.error( `Unexpected property '${prop.key.name}' (did you mean '${match}'?)`, prop.start );
} else if ( /FunctionExpression/.test( prop.value.type ) ) { } else if ( /FunctionExpression/.test( prop.value.type ) ) {
validator.error( `Unexpected property '${prop.key.name}' (did you mean to include it in 'methods'?)`, prop.start ); validator.error( `Unexpected property '${prop.key.name}' (did you mean to include it in 'methods'?)`, prop.start );
} else { } else {

@ -1,7 +1,6 @@
import * as namespaces from '../../../utils/namespaces.js'; import * as namespaces from '../../../utils/namespaces.js';
import FuzzySet from '../utils/FuzzySet.js'; import fuzzymatch from '../../utils/fuzzymatch.js';
const fuzzySet = new FuzzySet( namespaces.validNamespaces );
const valid = new Set( namespaces.validNamespaces ); const valid = new Set( namespaces.validNamespaces );
export default function namespace ( validator, prop ) { export default function namespace ( validator, prop ) {
@ -12,9 +11,9 @@ export default function namespace ( validator, prop ) {
} }
if ( !valid.has( ns ) ) { if ( !valid.has( ns ) ) {
const matches = fuzzySet.get( ns ); const match = fuzzymatch( ns, namespaces.validNamespaces );
if ( matches && matches[0] && matches[0][0] > 0.7 ) { if ( match ) {
validator.error( `Invalid namespace '${ns}' (did you mean '${matches[0][1]}'?)`, prop.start ); validator.error( `Invalid namespace '${ns}' (did you mean '${match}'?)`, prop.start );
} else { } else {
validator.error( `Invalid namespace '${ns}'`, prop.start ); validator.error( `Invalid namespace '${ns}'`, prop.start );
} }

@ -0,0 +1,10 @@
import FuzzySet from './FuzzySet.js';
export default function fuzzymatch ( name, names ) {
const set = new FuzzySet( names );
const matches = set.get( name );
return matches && matches[0] && matches[0][0] > 0.7 ?
matches[0][1] :
null;
}

@ -0,0 +1,4 @@
export default function list ( items, conjunction = 'or' ) {
if ( items.length === 1 ) return items[0];
return `${items.slice( 0, -1 ).join( ', ' )} ${conjunction} ${items[ items.length - 1 ]}`;
}

@ -0,0 +1,8 @@
[{
"message": "'innerwidth' is not a valid binding on <:Window> (did you mean 'innerWidth'?)",
"loc": {
"line": 1,
"column": 9
},
"pos": 9
}]

@ -0,0 +1,8 @@
[{
"message": "Bindings on <:Window/> must be to top-level properties, e.g. 'baz' rather than 'foo.bar.baz'",
"loc": {
"line": 1,
"column": 26
},
"pos": 26
}]

@ -0,0 +1 @@
<:Window bind:innerWidth='foo.bar.baz'/>

@ -0,0 +1,8 @@
[{
"message": "'width' is not a valid binding on <:Window> (did you mean 'innerWidth'?)",
"loc": {
"line": 1,
"column": 9
},
"pos": 9
}]

@ -0,0 +1,8 @@
[{
"message": "'potato' is not a valid binding on <:Window> — valid bindings are innerWidth, innerHeight, outerWidth, outerHeight, scrollX or scrollY",
"loc": {
"line": 1,
"column": 9
},
"pos": 9
}]

@ -0,0 +1,8 @@
[{
"message": "'resize' is an invalid callee (should be one of this.*, event.*, set, fire or destroy)",
"loc": {
"line": 1,
"column": 20
},
"pos": 20
}]
Loading…
Cancel
Save