diff --git a/docs/rules/no-comment-textnodes.md b/docs/rules/no-comment-textnodes.md new file mode 100644 index 0000000000..22e73b16b3 --- /dev/null +++ b/docs/rules/no-comment-textnodes.md @@ -0,0 +1,68 @@ +# Prevent comments from being inserted as text nodes (no-comment-textnodes) + +This rule prevents comment strings (e.g. beginning with `//` or `/*`) from being accidentally +injected as a text node in JSX statements. + +## Rule Details + +The following patterns are considered warnings: + +```js +var Hello = React.createClass({ + render: function() { + return ( +
// empty div
+ ); + } +}); + +var Hello = React.createClass({ + render: function() { + return ( +
+ /* empty div */ +
+ ); + } +}); +``` + +The following patterns are not considered warnings: + +```js +var Hello = React.createClass({ + displayName: 'Hello', + render: function() { + return
{/* empty div */}
; + } +}); + +var Hello = React.createClass({ + displayName: 'Hello', + render: function() { + return
; + } +}); + +var Hello = React.createClass({ + displayName: 'Hello', + render: function() { + return
; + } +}); +``` + +## Legitimate uses + +It's possible you may want to legitimately output comment start characters (`//` or `/*`) +in a JSX text node. In which case, you can do the following: + +```js +var Hello = React.createClass({ + render: function() { + return ( +
{'/* This will be output as a text node */'}
+ ); + } +}); +``` diff --git a/index.js b/index.js index 1969347591..ffe1304a5b 100644 --- a/index.js +++ b/index.js @@ -8,6 +8,7 @@ module.exports = { 'display-name': require('./lib/rules/display-name'), 'wrap-multilines': require('./lib/rules/wrap-multilines'), 'self-closing-comp': require('./lib/rules/self-closing-comp'), + 'no-comment-textnodes': require('./lib/rules/no-comment-textnodes'), 'no-danger': require('./lib/rules/no-danger'), 'no-set-state': require('./lib/rules/no-set-state'), 'no-is-mounted': require('./lib/rules/no-is-mounted'), diff --git a/lib/rules/no-comment-textnodes.js b/lib/rules/no-comment-textnodes.js new file mode 100644 index 0000000000..7a3bfc5895 --- /dev/null +++ b/lib/rules/no-comment-textnodes.js @@ -0,0 +1,38 @@ +/** + * @fileoverview Comments inside children section of tag should be placed inside braces. + * @author Ben Vinegar + */ +'use strict'; + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = function(context) { + function reportLiteralNode(node) { + context.report(node, 'Comments inside children section of tag should be placed inside braces'); + } + + // -------------------------------------------------------------------------- + // Public + // -------------------------------------------------------------------------- + + return { + Literal: function(node) { + if (/\s*\/(\/|\*)/.test(node.value)) { + // inside component, e.g.
literal
+ if (node.parent.type !== 'JSXAttribute' && + node.parent.type !== 'JSXExpressionContainer' && + node.parent.type.indexOf('JSX') !== -1) { + reportLiteralNode(node); + } + } + } + }; +}; + +module.exports.schema = [{ + type: 'object', + properties: {}, + additionalProperties: false +}]; diff --git a/tests/lib/rules/no-comment-textnodes.js b/tests/lib/rules/no-comment-textnodes.js new file mode 100644 index 0000000000..fcfd78d6a7 --- /dev/null +++ b/tests/lib/rules/no-comment-textnodes.js @@ -0,0 +1,204 @@ +/** + * @fileoverview Tests for no-comment-textnodes + * @author Ben Vinegar + */ +'use strict'; + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +var rule = require('../../../lib/rules/no-comment-textnodes'); +var RuleTester = require('eslint').RuleTester; + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +var ruleTester = new RuleTester(); +ruleTester.run('jsx-needs-i18n', rule, { + + valid: [ + { + code: [ + 'class Comp1 extends Component {', + ' render() {', + ' return (', + '
', + ' {/* valid */}', + '
', + ' );', + ' }', + '}' + ].join('\n'), + args: [1], + parser: 'babel-eslint' + }, { + code: [ + 'class Comp1 extends Component {', + ' render() {', + ' return (
{/* valid */}
);', + ' }', + '}' + ].join('\n'), + args: [1], + parser: 'babel-eslint' + }, { + code: [ + 'class Comp1 extends Component {', + ' render() {', + ' const bar = (
{/* valid */}
);', + ' return bar;', + ' }', + '}' + ].join('\n'), + args: [1], + parser: 'babel-eslint' + }, { + code: [ + 'var Hello = React.createClass({', + ' foo: (
{/* valid */}
),', + ' render() {', + ' return this.foo;', + ' },', + '});' + ].join('\n'), + args: [1], + parser: 'babel-eslint' + }, { + code: [ + 'class Comp1 extends Component {', + ' render() {', + ' return (', + '
', + ' {/* valid */}', + ' {/* valid 2 */}', + ' {/* valid 3 */}', + '
', + ' );', + ' }', + '}' + ].join('\n'), + args: [1], + parser: 'babel-eslint' + }, { + code: [ + 'class Comp1 extends Component {', + ' render() {', + ' return (', + '
', + '
', + ' );', + ' }', + '}' + ].join('\n'), + args: [1], + parser: 'babel-eslint' + }, { + code: [ + 'var foo = require(\'foo\');' + ].join('\n'), + args: [1], + parser: 'babel-eslint' + }, { + code: [ + '', + ' {/* valid */}', + '' + ].join('\n'), + args: [1], + parser: 'babel-eslint' + }, + + // inside element declarations + { + code: [ + '' + ].join('\n'), + args: [1], + parser: 'babel-eslint' + }, + { + code: [ + '' + ].join('\n'), + args: [1], + parser: 'babel-eslint' + } + ], + + invalid: [ + { + code: [ + 'class Comp1 extends Component {', + ' render() {', + ' return (
// invalid
);', + ' }', + '}' + ].join('\n'), + args: [1], + parser: 'babel-eslint', + errors: [{message: 'Comments inside children section of tag should be placed inside braces'}] + }, { + code: [ + 'class Comp1 extends Component {', + ' render() {', + ' return (
/* invalid */
);', + ' }', + '}' + ].join('\n'), + args: [1], + parser: 'babel-eslint', + errors: [{message: 'Comments inside children section of tag should be placed inside braces'}] + }, { + code: [ + 'class Comp1 extends Component {', + ' render() {', + ' return (', + '
', + ' // invalid', + '
', + ' );', + ' }', + '}' + ].join('\n'), + args: [1], + parser: 'babel-eslint', + errors: [{message: 'Comments inside children section of tag should be placed inside braces'}] + }, { + code: [ + 'class Comp1 extends Component {', + ' render() {', + ' return (', + '
', + ' asdjfl', + ' /* invalid */', + ' foo', + '
', + ' );', + ' }', + '}' + ].join('\n'), + args: [1], + parser: 'babel-eslint', + errors: [{message: 'Comments inside children section of tag should be placed inside braces'}] + }, { + code: [ + 'class Comp1 extends Component {', + ' render() {', + ' return (', + '
', + ' {\'asdjfl\'}', + ' // invalid', + ' {\'foo\'}', + '
', + ' );', + ' }', + '}' + ].join('\n'), + args: [1], + parser: 'babel-eslint', + errors: [{message: 'Comments inside children section of tag should be placed inside braces'}] + } + ] +});