diff --git a/lib/rules/no-unknown-property.js b/lib/rules/no-unknown-property.js index e431587254..70b5772dc4 100644 --- a/lib/rules/no-unknown-property.js +++ b/lib/rules/no-unknown-property.js @@ -15,12 +15,18 @@ const DEFAULTS = { }; const UNKNOWN_MESSAGE = 'Unknown property \'{{name}}\' found, use \'{{standardName}}\' instead'; +const WRONG_TAG_MESSAGE = 'Invalid property \'{{name}}\' found on tag \'{{tagName}}\', but it is only allowed on: {{allowedTags}}'; const DOM_ATTRIBUTE_NAMES = { 'accept-charset': 'acceptCharset', class: 'className', for: 'htmlFor', - 'http-equiv': 'httpEquiv' + 'http-equiv': 'httpEquiv', + crossOrigin: 'crossorigin' +}; + +const ATTRIBUTE_TAGS_MAP = { + crossorigin: ['script', 'img', 'video'] }; const SVGDOM_ATTRIBUTE_NAMES = { @@ -112,7 +118,7 @@ const DOM_PROPERTY_NAMES = [ // Standard 'acceptCharset', 'accessKey', 'allowFullScreen', 'allowTransparency', 'autoComplete', 'autoFocus', 'autoPlay', 'cellPadding', 'cellSpacing', 'charSet', 'classID', 'className', 'colSpan', 'contentEditable', 'contextMenu', - 'crossOrigin', 'dateTime', 'encType', 'formAction', 'formEncType', 'formMethod', 'formNoValidate', 'formTarget', + 'dateTime', 'encType', 'formAction', 'formEncType', 'formMethod', 'formNoValidate', 'formTarget', 'frameBorder', 'hrefLang', 'htmlFor', 'httpEquiv', 'inputMode', 'keyParams', 'keyType', 'marginHeight', 'marginWidth', 'maxLength', 'mediaGroup', 'minLength', 'noValidate', 'onAnimationEnd', 'onAnimationIteration', 'onAnimationStart', 'onBlur', 'onChange', 'onClick', 'onContextMenu', 'onCopy', 'onCompositionEnd', 'onCompositionStart', @@ -149,6 +155,18 @@ function isTagName(node) { return false; } +/** + * Extracts the tag name for the JSXAttribute + * @param {Object} node - JSXAttribute being tested. + * @returns {String} tag name + */ +function getTagName(node) { + if (node && node.parent && node.parent.name && node.parent.name) { + return node.parent.name.name; + } + return null; +} + /** * Get the standard name of the attribute. * @param {String} name - Name of the attribute. @@ -209,8 +227,26 @@ module.exports = { JSXAttribute: function(node) { const ignoreNames = getIgnoreConfig(); const name = sourceCode.getText(node.name); + if (ignoreNames.indexOf(name) >= 0) { + return; + } + + const tagName = getTagName(node); + const allowedTags = ATTRIBUTE_TAGS_MAP[name]; + if (tagName && allowedTags && /[^A-Z]/.test(tagName.charAt(0)) && allowedTags.indexOf(tagName) === -1) { + context.report({ + node: node, + message: WRONG_TAG_MESSAGE, + data: { + name: name, + tagName: tagName, + allowedTags: allowedTags.join(', ') + } + }); + } + const standardName = getStandardName(name); - if (!isTagName(node) || !standardName || ignoreNames.indexOf(name) >= 0) { + if (!isTagName(node) || !standardName) { return; } context.report({ diff --git a/tests/lib/rules/no-unknown-property.js b/tests/lib/rules/no-unknown-property.js index eb079fd614..5a730b9c29 100644 --- a/tests/lib/rules/no-unknown-property.js +++ b/tests/lib/rules/no-unknown-property.js @@ -41,7 +41,8 @@ ruleTester.run('no-unknown-property', rule, { {code: ';'}, { code: '
;', options: [{ignore: ['class']}] - } + }, + {code: '