Skip to content

Commit 0d7b069

Browse files
committed
Add no-unknown-property rule (fixes #28)
1 parent 9b74c46 commit 0d7b069

File tree

5 files changed

+161
-2
lines changed

5 files changed

+161
-2
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ Finally, enable all of the rules that you would like to use.
5050
"react/no-did-mount-set-state": 1,
5151
"react/no-did-update-set-state": 1,
5252
"react/no-multi-comp": 1,
53+
"react/no-unknown-property": 1,
5354
"react/prop-types": 1,
5455
"react/react-in-jsx-scope": 1,
5556
"react/self-closing-comp": 1,
@@ -68,6 +69,7 @@ Finally, enable all of the rules that you would like to use.
6869
* [no-did-mount-set-state](docs/rules/no-did-mount-set-state.md): Prevent usage of setState in componentDidMount
6970
* [no-did-update-set-state](docs/rules/no-did-update-set-state.md): Prevent usage of setState in componentDidUpdate
7071
* [no-multi-comp](docs/rules/no-multi-comp.md): Prevent multiple component definition per file
72+
* [no-unknown-property](docs/rules/no-unknown-property.md): Prevent usage of unknown DOM property
7173
* [prop-types](docs/rules/prop-types.md): Prevent missing props validation in a React component definition
7274
* [react-in-jsx-scope](docs/rules/react-in-jsx-scope.md): Prevent missing React when using JSX
7375
* [self-closing-comp](docs/rules/self-closing-comp.md): Prevent extra closing tags for components without children

docs/rules/no-unknown-property.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Prevent usage of unknown DOM property (no-unknown-property)
2+
3+
In JSX all DOM properties and attributes should be camelCased to be consistent with standard JavaScript style. This can be a possible source of error if you are used to write plain HTML.
4+
5+
## Rule Details
6+
7+
The following patterns are considered warnings:
8+
9+
```js
10+
var React = require('react');
11+
12+
var Hello = <div class="hello">Hello World</div>;
13+
```
14+
15+
The following patterns are not considered warnings:
16+
17+
```js
18+
var React = require('react');
19+
20+
var Hello = <div className="hello">Hello World</div>;
21+
```
22+
23+
## When Not To Use It
24+
25+
If you are not using JSX you can disable this rule.

index.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ module.exports = {
1313
'react-in-jsx-scope': require('./lib/rules/react-in-jsx-scope'),
1414
'jsx-uses-vars': require('./lib/rules/jsx-uses-vars'),
1515
'jsx-no-undef': require('./lib/rules/jsx-no-undef'),
16-
'jsx-quotes': require('./lib/rules/jsx-quotes')
16+
'jsx-quotes': require('./lib/rules/jsx-quotes'),
17+
'no-unknown-property': require('./lib/rules/no-unknown-property')
1718
},
1819
rulesConfig: {
1920
'jsx-uses-react': 0,
@@ -27,6 +28,7 @@ module.exports = {
2728
'react-in-jsx-scope': 0,
2829
'jsx-uses-vars': 0,
2930
'jsx-no-undef': 0,
30-
'jsx-quotes': 0
31+
'jsx-quotes': 0,
32+
'no-unknown-property': 0
3133
}
3234
};

lib/rules/no-unknown-property.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* @fileoverview Prevent usage of unknown DOM property
3+
* @author Yannick Croissant
4+
*/
5+
'use strict';
6+
7+
// ------------------------------------------------------------------------------
8+
// Constants
9+
// ------------------------------------------------------------------------------
10+
11+
var UNKNOWN_MESSAGE = 'Unknown property \'{{name}}\' found, use \'{{standardName}}\' instead';
12+
13+
var DOM_ATTRIBUTE_NAMES = {
14+
'accept-charset': 'acceptCharset',
15+
class: 'className',
16+
for: 'htmlFor',
17+
'http-equiv': 'httpEquiv'
18+
};
19+
20+
var DOM_PROPERTY_NAMES = [
21+
'acceptCharset', 'accessKey', 'allowFullScreen', 'allowTransparency', 'autoComplete', 'autoFocus', 'autoPlay',
22+
'cellPadding', 'cellSpacing', 'charSet', 'classID', 'className', 'colSpan', 'contentEditable', 'contextMenu',
23+
'crossOrigin', 'dateTime', 'encType', 'formAction', 'formEncType', 'formMethod', 'formNoValidate', 'formTarget',
24+
'frameBorder', 'hrefLang', 'htmlFor', 'httpEquiv', 'marginHeight', 'marginWidth', 'maxLength', 'mediaGroup',
25+
'noValidate', 'radioGroup', 'readOnly', 'rowSpan', 'spellCheck', 'srcDoc', 'srcSet', 'tabIndex', 'useMap',
26+
'itemProp', 'itemScope', 'itemType', 'itemRef', 'itemId'
27+
];
28+
29+
// ------------------------------------------------------------------------------
30+
// Helpers
31+
// ------------------------------------------------------------------------------
32+
33+
/**
34+
* Checks if a node name match the JSX tag convention.
35+
* @param {String} name - Name of the node to check.
36+
* @returns {boolean} Whether or not the node name match the JSX tag convention.
37+
*/
38+
var tagConvention = /^[a-z]|\-/;
39+
function isTagName(name) {
40+
return tagConvention.test(name);
41+
}
42+
43+
/**
44+
* Get the standard name of the attribute.
45+
* @param {String} name - Name of the attribute.
46+
* @returns {String} The standard name of the attribute.
47+
*/
48+
function getStandardName(name) {
49+
if (DOM_ATTRIBUTE_NAMES[name]) {
50+
return DOM_ATTRIBUTE_NAMES[name];
51+
}
52+
var i;
53+
var found = DOM_PROPERTY_NAMES.some(function(element, index) {
54+
i = index;
55+
return element.toLowerCase() === name;
56+
});
57+
return found ? DOM_PROPERTY_NAMES[i] : null;
58+
}
59+
60+
// ------------------------------------------------------------------------------
61+
// Rule Definition
62+
// ------------------------------------------------------------------------------
63+
64+
module.exports = function(context) {
65+
66+
return {
67+
68+
JSXAttribute: function(node) {
69+
var standardName = getStandardName(node.name.name);
70+
if (!isTagName(node.parent.name.name) || !standardName) {
71+
return;
72+
}
73+
context.report(node, UNKNOWN_MESSAGE, {
74+
name: node.name.name,
75+
standardName: standardName
76+
});
77+
}
78+
};
79+
80+
};
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* @fileoverview Tests for no-unknown-property
3+
* @author Yannick Croissant
4+
*/
5+
6+
'use strict';
7+
8+
// -----------------------------------------------------------------------------
9+
// Requirements
10+
// -----------------------------------------------------------------------------
11+
12+
var eslint = require('eslint').linter;
13+
var ESLintTester = require('eslint-tester');
14+
15+
// -----------------------------------------------------------------------------
16+
// Tests
17+
// -----------------------------------------------------------------------------
18+
19+
var eslintTester = new ESLintTester(eslint);
20+
eslintTester.addRuleTest('lib/rules/no-unknown-property', {
21+
valid: [
22+
{code: '<App class="bar" />;', ecmaFeatures: {jsx: true}},
23+
{code: '<App for="bar" />;', ecmaFeatures: {jsx: true}},
24+
{code: '<App accept-charset="bar" />;', ecmaFeatures: {jsx: true}},
25+
{code: '<App http-equiv="bar" />;', ecmaFeatures: {jsx: true}},
26+
{code: '<div className="bar"></div>;', ecmaFeatures: {jsx: true}},
27+
{code: '<div data-foo="bar"></div>;', ecmaFeatures: {jsx: true}}
28+
],
29+
invalid: [{
30+
code: '<div class="bar"></div>;',
31+
errors: [{message: 'Unknown property \'class\' found, use \'className\' instead'}],
32+
ecmaFeatures: {jsx: true}
33+
}, {
34+
code: '<div for="bar"></div>;',
35+
errors: [{message: 'Unknown property \'for\' found, use \'htmlFor\' instead'}],
36+
ecmaFeatures: {jsx: true}
37+
}, {
38+
code: '<div accept-charset="bar"></div>;',
39+
errors: [{message: 'Unknown property \'accept-charset\' found, use \'acceptCharset\' instead'}],
40+
ecmaFeatures: {jsx: true}
41+
}, {
42+
code: '<div http-equiv="bar"></div>;',
43+
errors: [{message: 'Unknown property \'http-equiv\' found, use \'httpEquiv\' instead'}],
44+
ecmaFeatures: {jsx: true}
45+
}, {
46+
code: '<div accesskey="bar"></div>;',
47+
errors: [{message: 'Unknown property \'accesskey\' found, use \'accessKey\' instead'}],
48+
ecmaFeatures: {jsx: true}}
49+
]
50+
});

0 commit comments

Comments
 (0)