diff --git a/README.md b/README.md index 89dab74847..d06bbb48ef 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,7 @@ Finally, enable all of the rules that you would like to use. * [jsx-no-duplicate-props](docs/rules/jsx-no-duplicate-props.md): Prevent duplicate props in JSX * [jsx-no-literals](docs/rules/jsx-no-literals.md): Prevent usage of unwrapped JSX strings * [jsx-no-undef](docs/rules/jsx-no-undef.md): Disallow undeclared variables in JSX +* [jsx-pascal-case](docs/rules/jsx-pascal-case.md): Enforce PascalCase for user-defined JSX components * [jsx-quotes](docs/rules/jsx-quotes.md): Enforce quote style for JSX attributes * [jsx-sort-prop-types](docs/rules/jsx-sort-prop-types.md): Enforce propTypes declarations alphabetical sorting * [jsx-sort-props](docs/rules/jsx-sort-props.md): Enforce props alphabetical sorting diff --git a/docs/rules/jsx-pascal-case.md b/docs/rules/jsx-pascal-case.md new file mode 100644 index 0000000000..e6a54c0f05 --- /dev/null +++ b/docs/rules/jsx-pascal-case.md @@ -0,0 +1,37 @@ +# Enforce PasalCase for user-defined JSX components (jsx-pascal-case) + +Enforces coding style that user-defined JSX components are defined and referenced in PascalCase. + +## Rule Details + +The following patterns are considered warnings: + +```js + +``` + +```js + +
+ +``` + +```js + +``` + +The following patterns are not considered warnings: + +```js + +``` + +```js + +
+ +``` + +## When Not To Use It + +If you are not using JSX. \ No newline at end of file diff --git a/index.js b/index.js index fdd87f7ed8..ab51959e1b 100644 --- a/index.js +++ b/index.js @@ -14,6 +14,7 @@ module.exports = { 'no-did-update-set-state': require('./lib/rules/no-did-update-set-state'), 'react-in-jsx-scope': require('./lib/rules/react-in-jsx-scope'), 'jsx-uses-vars': require('./lib/rules/jsx-uses-vars'), + 'jsx-pascal-case': require('./lib/rules/jsx-pascal-case'), 'jsx-no-bind': require('./lib/rules/jsx-no-bind'), 'jsx-no-undef': require('./lib/rules/jsx-no-undef'), 'jsx-quotes': require('./lib/rules/jsx-quotes'), @@ -46,6 +47,7 @@ module.exports = { 'no-did-update-set-state': 0, 'react-in-jsx-scope': 0, 'jsx-uses-vars': 1, + 'jsx-pascal-case': 0, 'jsx-no-bind': 0, 'jsx-no-undef': 0, 'jsx-quotes': 0, diff --git a/lib/rules/jsx-pascal-case.js b/lib/rules/jsx-pascal-case.js new file mode 100644 index 0000000000..c35bf7873c --- /dev/null +++ b/lib/rules/jsx-pascal-case.js @@ -0,0 +1,49 @@ +/** + * @fileoverview Enforce PasalCase for user-defined JSX components + * @author Jake Marsh + */ + +'use strict'; + +var variableUtil = require('../util/variable'); + +// ------------------------------------------------------------------------------ +// Constants +// ------------------------------------------------------------------------------ + +var PASCAL_CASE_REGEX = /^[A-Z][a-z]+(?:[A-Z][a-z]+)*$/; + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = function(context) { + + return { + JSXOpeningElement: function(node) { + var variables = variableUtil.variablesInScope(context); + + switch (node.name.type) { + case 'JSXIdentifier': + node = node.name; + break; + case 'JSXMemberExpression': + node = node.name.object; + break; + case 'JSXNamespacedName': + node = node.name.namespace; + break; + default: + break; + } + + var isImportedVariable = variableUtil.findVariable(variables, node.name); + var isPascalCase = PASCAL_CASE_REGEX.test(node.name); + + if (isImportedVariable && !isPascalCase) { + context.report(node, 'Imported JSX component ' + node.name + ' must be in PascalCase'); + } + } + }; + +}; diff --git a/tests/lib/rules/jsx-pascal-case.js b/tests/lib/rules/jsx-pascal-case.js new file mode 100644 index 0000000000..446e34171c --- /dev/null +++ b/tests/lib/rules/jsx-pascal-case.js @@ -0,0 +1,70 @@ +/** + * @fileoverview Tests for jsx-pascal-case + * @author Jake Marsh + */ +'use strict'; + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +var rule = require('../../../lib/rules/jsx-pascal-case'); +var RuleTester = require('eslint').RuleTester; + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +var ruleTester = new RuleTester(); +ruleTester.run('jsx-pascal-case', rule, { + valid: [{ + code: [ + 'var TestComponent;', + '' + ].join('\n'), + ecmaFeatures: { + jsx: true + } + }, { + code: [ + 'var TestComponent;', + '', + '
', + '' + ].join('\n'), + ecmaFeatures: { + jsx: true + } + }], + + invalid: [{ + code: [ + 'var testComponent;', + '' + ].join('\n'), + ecmaFeatures: { + jsx: true + }, + errors: [{message: 'Imported JSX component testComponent must be in PascalCase'}] + }, { + code: [ + 'var test_component;', + '' + ].join('\n'), + ecmaFeatures: { + jsx: true + }, + errors: [{message: 'Imported JSX component test_component must be in PascalCase'}] + }, { + code: [ + 'var testComponent;', + '', + '
', + '' + ].join('\n'), + ecmaFeatures: { + jsx: true + }, + errors: [{message: 'Imported JSX component testComponent must be in PascalCase'}] + }] +});