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: '