Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[New] add no-namespace rule #2640

Merged
merged 2 commits into from Sep 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Expand Up @@ -5,13 +5,19 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel

## Unreleased

### Added
* add [`no-namespace`] rule ([#2640] @yacinehmito @ljharb)

[#2640]: https://github.com/yannickcr/eslint-plugin-react/pull/2640

## [7.25.3] - 2021.09.19

### Fixed
* [`prop-types`], `propTypes`: bail out unknown generic types inside func params ([#3076] @vedadeepta)

### Changed
* [readme] Update broken link for configuration files ([#3071] @prateek3255)
* [Refactor] create/extract `isCreateElement` and `isDestructuredFromPragmaImport` utils (@ljharb)

[7.25.3]: https://github.com/yannickcr/eslint-plugin-react/compare/v7.25.2...v7.25.3
[#3076]: https://github.com/yannickcr/eslint-plugin-react/pull/3076
Expand Down Expand Up @@ -3436,6 +3442,7 @@ If you're still not using React 15 you can keep the old behavior by setting the
[`no-find-dom-node`]: docs/rules/no-find-dom-node.md
[`no-is-mounted`]: docs/rules/no-is-mounted.md
[`no-multi-comp`]: docs/rules/no-multi-comp.md
[`no-namespace`]: docs/rules/no-namespace.md
[`no-redundant-should-component-update`]: docs/rules/no-redundant-should-component-update.md
[`no-render-return-value`]: docs/rules/no-render-return-value.md
[`no-set-state`]: docs/rules/no-set-state.md
Expand Down
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -145,6 +145,7 @@ Enable the rules that you would like to use.
| ✔ | | [react/no-find-dom-node](docs/rules/no-find-dom-node.md) | Prevent usage of findDOMNode |
| ✔ | | [react/no-is-mounted](docs/rules/no-is-mounted.md) | Prevent usage of isMounted |
| | | [react/no-multi-comp](docs/rules/no-multi-comp.md) | Prevent multiple component definition per file |
| | | [react/no-namespace](docs/rules/no-namespace.md) | Enforce that namespaces are not used in React elements |
| | | [react/no-redundant-should-component-update](docs/rules/no-redundant-should-component-update.md) | Flag shouldComponentUpdate when extending PureComponent |
| ✔ | | [react/no-render-return-value](docs/rules/no-render-return-value.md) | Prevent usage of the return value of React.render |
| | | [react/no-set-state](docs/rules/no-set-state.md) | Prevent usage of setState |
Expand Down
29 changes: 29 additions & 0 deletions docs/rules/no-namespace.md
@@ -0,0 +1,29 @@
# Enforce that namespaces are not used in React elements (react/no-namespace)

Enforces the absence of a namespace in React elements, such as with `svg:circle`, as they are not supported in React.

## Rule Details

The following patterns are considered warnings:

```jsx
<ns:TestComponent />
```

```jsx
<Ns:TestComponent />
```

The following patterns are **not** considered warnings:

```jsx
<TestComponent />
```

```jsx
<testComponent />
```

## When not to use

If you are not using React.
1 change: 1 addition & 0 deletions index.js
Expand Up @@ -67,6 +67,7 @@ const allRules = {
'no-find-dom-node': require('./lib/rules/no-find-dom-node'),
'no-is-mounted': require('./lib/rules/no-is-mounted'),
'no-multi-comp': require('./lib/rules/no-multi-comp'),
'no-namespace': require('./lib/rules/no-namespace'),
'no-set-state': require('./lib/rules/no-set-state'),
'no-string-refs': require('./lib/rules/no-string-refs'),
'no-redundant-should-component-update': require('./lib/rules/no-redundant-should-component-update'),
Expand Down
18 changes: 2 additions & 16 deletions lib/rules/button-has-type.js
Expand Up @@ -8,21 +8,7 @@
const getProp = require('jsx-ast-utils/getProp');
const getLiteralPropValue = require('jsx-ast-utils/getLiteralPropValue');
const docsUrl = require('../util/docsUrl');
const pragmaUtil = require('../util/pragma');

// ------------------------------------------------------------------------------
// Helpers
// ------------------------------------------------------------------------------

function isCreateElement(node, context) {
const pragma = pragmaUtil.getFromContext(context);
return node.callee
&& node.callee.type === 'MemberExpression'
&& node.callee.property.name === 'createElement'
&& node.callee.object
&& node.callee.object.name === pragma
&& node.arguments.length > 0;
}
const isCreateElement = require('../util/isCreateElement');

// ------------------------------------------------------------------------------
// Rule Definition
Expand Down Expand Up @@ -150,7 +136,7 @@ module.exports = {
checkValue(node, propValue);
},
CallExpression(node) {
if (!isCreateElement(node, context)) {
if (!isCreateElement(node, context) || node.arguments.length < 1) {
return;
}

Expand Down
11 changes: 2 additions & 9 deletions lib/rules/forbid-elements.js
Expand Up @@ -7,6 +7,7 @@

const has = require('object.hasown/polyfill')();
const docsUrl = require('../util/docsUrl');
const isCreateElement = require('../util/isCreateElement');

// ------------------------------------------------------------------------------
// Rule Definition
Expand Down Expand Up @@ -65,14 +66,6 @@ module.exports = {
}
});

function isValidCreateElement(node) {
return node.callee
&& node.callee.type === 'MemberExpression'
&& node.callee.object.name === 'React'
&& node.callee.property.name === 'createElement'
&& node.arguments.length > 0;
}

function reportIfForbidden(element, node) {
if (has(indexedForbidConfigs, element)) {
const message = indexedForbidConfigs[element].message;
Expand All @@ -94,7 +87,7 @@ module.exports = {
},

CallExpression(node) {
if (!isValidCreateElement(node)) {
if (!isCreateElement(node, context)) {
return;
}

Expand Down
3 changes: 2 additions & 1 deletion lib/rules/no-adjacent-inline-elements.js
Expand Up @@ -6,6 +6,7 @@
'use strict';

const docsUrl = require('../util/docsUrl');
const isCreateElement = require('../util/isCreateElement');

// ------------------------------------------------------------------------------
// Helpers
Expand Down Expand Up @@ -108,7 +109,7 @@ module.exports = {
validate(node, node.children);
},
CallExpression(node) {
if (!node.callee || node.callee.type !== 'MemberExpression' || node.callee.property.name !== 'createElement') {
if (!isCreateElement(node, context)) {
return;
}
if (node.arguments.length < 2 || !node.arguments[2]) {
Expand Down
10 changes: 5 additions & 5 deletions lib/rules/no-children-prop.js
Expand Up @@ -6,6 +6,7 @@
'use strict';

const docsUrl = require('../util/docsUrl');
const isCreateElement = require('../util/isCreateElement');

// ------------------------------------------------------------------------------
// Helpers
Expand All @@ -14,13 +15,12 @@ const docsUrl = require('../util/docsUrl');
/**
* Checks if the node is a createElement call with a props literal.
* @param {ASTNode} node - The AST node being checked.
* @param {Context} context - The AST node being checked.
* @returns {Boolean} - True if node is a createElement call with a props
* object literal, False if not.
*/
function isCreateElementWithProps(node) {
return node.callee
&& node.callee.type === 'MemberExpression'
&& node.callee.property.name === 'createElement'
function isCreateElementWithProps(node, context) {
return isCreateElement(node, context)
&& node.arguments.length > 1
&& node.arguments[1].type === 'ObjectExpression';
}
Expand Down Expand Up @@ -80,7 +80,7 @@ module.exports = {
});
},
CallExpression(node) {
if (!isCreateElementWithProps(node)) {
if (!isCreateElementWithProps(node, context)) {
return;
}

Expand Down
49 changes: 49 additions & 0 deletions lib/rules/no-namespace.js
@@ -0,0 +1,49 @@
/**
* @fileoverview Enforce that namespaces are not used in React elements
* @author Yacine Hmito
*/

'use strict';

const elementType = require('jsx-ast-utils/elementType');
const docsUrl = require('../util/docsUrl');
const isCreateElement = require('../util/isCreateElement');

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------

module.exports = {
meta: {
docs: {
description: 'Enforce that namespaces are not used in React elements',
category: 'Possible Errors',
recommended: false,
url: docsUrl('no-namespace')
},

schema: [{
type: 'object',
additionalProperties: false
}]
},

create(context) {
return {
CallExpression(node) {
if (isCreateElement(node, context) && node.arguments.length > 0 && node.arguments[0].type === 'Literal') {
const name = node.arguments[0].value;
if (name.indexOf(':') === -1) return undefined;
const message = `React component ${name} must not be in a namespace as React does not support them`;
context.report({node, message});
}
},
JSXOpeningElement(node) {
const name = elementType(node);
if (name.indexOf(':') === -1) return undefined;
const message = `React component ${name} must not be in a namespace as React does not support them`;
context.report({node, message});
}
};
}
};
5 changes: 2 additions & 3 deletions lib/rules/style-prop-object.js
Expand Up @@ -7,6 +7,7 @@

const variableUtil = require('../util/variable');
const docsUrl = require('../util/docsUrl');
const isCreateElement = require('../util/isCreateElement');

// ------------------------------------------------------------------------------
// Rule Definition
Expand Down Expand Up @@ -74,9 +75,7 @@ module.exports = {
return {
CallExpression(node) {
if (
node.callee
&& node.callee.type === 'MemberExpression'
&& node.callee.property.name === 'createElement'
isCreateElement(node, context)
&& node.arguments.length > 1
) {
if (node.arguments[0].name) {
Expand Down
91 changes: 4 additions & 87 deletions lib/util/Components.js
Expand Up @@ -17,6 +17,8 @@ const jsxUtil = require('./jsx');
const usedPropTypesUtil = require('./usedPropTypes');
const defaultPropsUtil = require('./defaultProps');
const isFirstLetterCapitalized = require('./isFirstLetterCapitalized');
const isCreateElement = require('./isCreateElement');
const isDestructuredFromPragmaImport = require('./isDestructuredFromPragmaImport');

function getId(node) {
return node && node.range.join(':');
Expand Down Expand Up @@ -287,70 +289,7 @@ function componentRule(rule, context) {
* @returns {Boolean} True if createElement is destructured from the pragma
*/
isDestructuredFromPragmaImport(variable) {
const variables = variableUtil.variablesInScope(context);
const variableInScope = variableUtil.getVariable(variables, variable);
if (variableInScope) {
const latestDef = variableUtil.getLatestVariableDefinition(variableInScope);
if (latestDef) {
// check if latest definition is a variable declaration: 'variable = value'
if (latestDef.node.type === 'VariableDeclarator' && latestDef.node.init) {
// check for: 'variable = pragma.variable'
if (
latestDef.node.init.type === 'MemberExpression'
&& latestDef.node.init.object.type === 'Identifier'
&& latestDef.node.init.object.name === pragma
) {
return true;
}
// check for: '{variable} = pragma'
if (
latestDef.node.init.type === 'Identifier'
&& latestDef.node.init.name === pragma
) {
return true;
}

// "require('react')"
let requireExpression = null;

// get "require('react')" from: "{variable} = require('react')"
if (latestDef.node.init.type === 'CallExpression') {
requireExpression = latestDef.node.init;
}
// get "require('react')" from: "variable = require('react').variable"
if (
!requireExpression
&& latestDef.node.init.type === 'MemberExpression'
&& latestDef.node.init.object.type === 'CallExpression'
) {
requireExpression = latestDef.node.init.object;
}

// check proper require.
if (
requireExpression
&& requireExpression.callee
&& requireExpression.callee.name === 'require'
&& requireExpression.arguments[0]
&& requireExpression.arguments[0].value === pragma.toLocaleLowerCase()
) {
return true;
}

return false;
}

// latest definition is an import declaration: import {<variable>} from 'react'
if (
latestDef.parent
&& latestDef.parent.type === 'ImportDeclaration'
&& latestDef.parent.source.value === pragma.toLocaleLowerCase()
) {
return true;
}
}
}
return false;
return isDestructuredFromPragmaImport(variable, context);
},

/**
Expand All @@ -360,29 +299,7 @@ function componentRule(rule, context) {
* @returns {Boolean} True if createElement called from pragma
*/
isCreateElement(node) {
// match `React.createElement()`
if (
node
&& node.callee
&& node.callee.object
&& node.callee.object.name === pragma
&& node.callee.property
&& node.callee.property.name === 'createElement'
) {
return true;
}

// match `createElement()`
if (
node
&& node.callee
&& node.callee.name === 'createElement'
&& this.isDestructuredFromPragmaImport('createElement')
) {
return true;
}

return false;
return isCreateElement(node, context);
},

/**
Expand Down