Ajout de promotion et de commande

This commit is contained in:
Aubert Marvin
2026-04-25 15:28:39 +02:00
parent eddb103755
commit faa3d7718c
8428 changed files with 1126442 additions and 6 deletions
+3
View File
@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=boolean-prop-naming.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"boolean-prop-naming.d.ts","sourceRoot":"","sources":["boolean-prop-naming.js"],"names":[],"mappings":"wBAyCW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
+426
View File
@@ -0,0 +1,426 @@
/**
* @fileoverview Enforces consistent naming for boolean props
* @author Ev Haus
*/
'use strict';
const flatMap = require('array.prototype.flatmap');
const values = require('object.values');
const Components = require('../util/Components');
const propsUtil = require('../util/props');
const astUtil = require('../util/ast');
const docsUrl = require('../util/docsUrl');
const propWrapperUtil = require('../util/propWrapper');
const report = require('../util/report');
const eslintUtil = require('../util/eslint');
const getSourceCode = eslintUtil.getSourceCode;
const getText = eslintUtil.getText;
/**
* Checks if prop is nested
* @param {Object} prop Property object, single prop type declaration
* @returns {boolean}
*/
function nestedPropTypes(prop) {
return (
prop.type === 'Property'
&& astUtil.isCallExpression(prop.value)
);
}
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
const messages = {
patternMismatch: 'Prop name `{{propName}}` doesnt match rule `{{pattern}}`',
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
category: 'Stylistic Issues',
description: 'Enforces consistent naming for boolean props',
recommended: false,
url: docsUrl('boolean-prop-naming'),
},
messages,
schema: [{
additionalProperties: false,
properties: {
propTypeNames: {
items: {
type: 'string',
},
minItems: 1,
type: 'array',
uniqueItems: true,
},
rule: {
default: '^(is|has)[A-Z]([A-Za-z0-9]?)+',
minLength: 1,
type: 'string',
},
message: {
minLength: 1,
type: 'string',
},
validateNested: {
default: false,
type: 'boolean',
},
},
type: 'object',
}],
},
create: Components.detect((context, components, utils) => {
const config = context.options[0] || {};
const rule = config.rule ? new RegExp(config.rule) : null;
const propTypeNames = config.propTypeNames || ['bool'];
// Remembers all Flowtype object definitions
const objectTypeAnnotations = new Map();
/**
* Returns the prop key to ensure we handle the following cases:
* propTypes: {
* full: React.PropTypes.bool,
* short: PropTypes.bool,
* direct: bool,
* required: PropTypes.bool.isRequired
* }
* @param {Object} node The node we're getting the name of
* @returns {string | null}
*/
function getPropKey(node) {
// Check for `ExperimentalSpreadProperty` (eslint 3/4) and `SpreadElement` (eslint 5)
// so we can skip validation of those fields.
// Otherwise it will look for `node.value.property` which doesn't exist and breaks eslint.
if (node.type === 'ExperimentalSpreadProperty' || node.type === 'SpreadElement') {
return null;
}
if (node.value && node.value.property) {
const name = node.value.property.name;
if (name === 'isRequired') {
if (node.value.object && node.value.object.property) {
return node.value.object.property.name;
}
return null;
}
return name;
}
if (node.value && node.value.type === 'Identifier') {
return node.value.name;
}
return null;
}
/**
* Returns the name of the given node (prop)
* @param {Object} node The node we're getting the name of
* @returns {string}
*/
function getPropName(node) {
// Due to this bug https://github.com/babel/babel-eslint/issues/307
// we can't get the name of the Flow object key name. So we have
// to hack around it for now.
if (node.type === 'ObjectTypeProperty') {
return getSourceCode(context).getFirstToken(node).value;
}
return node.key.name;
}
/**
* Checks if prop is declared in flow way
* @param {Object} prop Property object, single prop type declaration
* @returns {boolean}
*/
function flowCheck(prop) {
return (
prop.type === 'ObjectTypeProperty'
&& prop.value.type === 'BooleanTypeAnnotation'
&& rule.test(getPropName(prop)) === false
);
}
/**
* Checks if prop is declared in regular way
* @param {Object} prop Property object, single prop type declaration
* @returns {boolean}
*/
function regularCheck(prop) {
const propKey = getPropKey(prop);
return (
propKey
&& propTypeNames.indexOf(propKey) >= 0
&& rule.test(getPropName(prop)) === false
);
}
function tsCheck(prop) {
if (prop.type !== 'TSPropertySignature') return false;
const typeAnnotation = (prop.typeAnnotation || {}).typeAnnotation;
return (
typeAnnotation
&& typeAnnotation.type === 'TSBooleanKeyword'
&& rule.test(getPropName(prop)) === false
);
}
/**
* Runs recursive check on all proptypes
* @param {Array} proptypes A list of Property object (for each proptype defined)
* @param {Function} addInvalidProp callback to run for each error
*/
function runCheck(proptypes, addInvalidProp) {
if (proptypes) {
proptypes.forEach((prop) => {
if (config.validateNested && nestedPropTypes(prop)) {
runCheck(prop.value.arguments[0].properties, addInvalidProp);
return;
}
if (flowCheck(prop) || regularCheck(prop) || tsCheck(prop)) {
addInvalidProp(prop);
}
});
}
}
/**
* Checks and mark props with invalid naming
* @param {Object} node The component node we're testing
* @param {Array} proptypes A list of Property object (for each proptype defined)
*/
function validatePropNaming(node, proptypes) {
const component = components.get(node) || node;
const invalidProps = component.invalidProps || [];
runCheck(proptypes, (prop) => {
invalidProps.push(prop);
});
components.set(node, {
invalidProps,
});
}
/**
* Reports invalid prop naming
* @param {Object} component The component to process
*/
function reportInvalidNaming(component) {
component.invalidProps.forEach((propNode) => {
const propName = getPropName(propNode);
report(context, config.message || messages.patternMismatch, !config.message && 'patternMismatch', {
node: propNode,
data: {
component: propName,
propName,
pattern: config.rule,
},
});
});
}
function checkPropWrapperArguments(node, args) {
if (!node || !Array.isArray(args)) {
return;
}
args.filter((arg) => arg.type === 'ObjectExpression').forEach((object) => validatePropNaming(node, object.properties));
}
function getComponentTypeAnnotation(component) {
// If this is a functional component that uses a global type, check it
if (
(component.node.type === 'FunctionDeclaration' || component.node.type === 'ArrowFunctionExpression')
&& component.node.params
&& component.node.params.length > 0
&& component.node.params[0].typeAnnotation
) {
return component.node.params[0].typeAnnotation.typeAnnotation;
}
if (
!component.node.parent
|| component.node.parent.type !== 'VariableDeclarator'
|| !component.node.parent.id
|| component.node.parent.id.type !== 'Identifier'
|| !component.node.parent.id.typeAnnotation
|| !component.node.parent.id.typeAnnotation.typeAnnotation
) {
return;
}
const annotationTypeArguments = propsUtil.getTypeArguments(
component.node.parent.id.typeAnnotation.typeAnnotation
);
if (
annotationTypeArguments && (
annotationTypeArguments.type === 'TSTypeParameterInstantiation'
|| annotationTypeArguments.type === 'TypeParameterInstantiation'
)
) {
return annotationTypeArguments.params.find(
(param) => param.type === 'TSTypeReference' || param.type === 'GenericTypeAnnotation'
);
}
}
function findAllTypeAnnotations(identifier, node) {
if (node.type === 'TSTypeLiteral' || node.type === 'ObjectTypeAnnotation' || node.type === 'TSInterfaceBody') {
const currentNode = [].concat(
objectTypeAnnotations.get(identifier.name) || [],
node
);
objectTypeAnnotations.set(identifier.name, currentNode);
} else if (
node.type === 'TSParenthesizedType'
&& (
node.typeAnnotation.type === 'TSIntersectionType'
|| node.typeAnnotation.type === 'TSUnionType'
)
) {
node.typeAnnotation.types.forEach((type) => {
findAllTypeAnnotations(identifier, type);
});
} else if (
node.type === 'TSIntersectionType'
|| node.type === 'TSUnionType'
|| node.type === 'IntersectionTypeAnnotation'
|| node.type === 'UnionTypeAnnotation'
) {
node.types.forEach((type) => {
findAllTypeAnnotations(identifier, type);
});
}
}
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
return {
'ClassProperty, PropertyDefinition'(node) {
if (!rule || !propsUtil.isPropTypesDeclaration(node)) {
return;
}
if (
node.value
&& astUtil.isCallExpression(node.value)
&& propWrapperUtil.isPropWrapperFunction(
context,
getText(context, node.value.callee)
)
) {
checkPropWrapperArguments(node, node.value.arguments);
}
if (node.value && node.value.properties) {
validatePropNaming(node, node.value.properties);
}
if (node.typeAnnotation && node.typeAnnotation.typeAnnotation) {
validatePropNaming(node, node.typeAnnotation.typeAnnotation.properties);
}
},
MemberExpression(node) {
if (!rule || !propsUtil.isPropTypesDeclaration(node)) {
return;
}
const component = utils.getRelatedComponent(node);
if (!component || !node.parent.right) {
return;
}
const right = node.parent.right;
if (
astUtil.isCallExpression(right)
&& propWrapperUtil.isPropWrapperFunction(
context,
getText(context, right.callee)
)
) {
checkPropWrapperArguments(component.node, right.arguments);
return;
}
validatePropNaming(component.node, node.parent.right.properties);
},
ObjectExpression(node) {
if (!rule) {
return;
}
// Search for the proptypes declaration
node.properties.forEach((property) => {
if (!propsUtil.isPropTypesDeclaration(property)) {
return;
}
validatePropNaming(node, property.value.properties);
});
},
TypeAlias(node) {
findAllTypeAnnotations(node.id, node.right);
},
TSTypeAliasDeclaration(node) {
findAllTypeAnnotations(node.id, node.typeAnnotation);
},
TSInterfaceDeclaration(node) {
findAllTypeAnnotations(node.id, node.body);
},
// eslint-disable-next-line object-shorthand
'Program:exit'() {
if (!rule) {
return;
}
values(components.list()).forEach((component) => {
const annotation = getComponentTypeAnnotation(component);
if (annotation) {
let propType;
if (annotation.type === 'GenericTypeAnnotation') {
propType = objectTypeAnnotations.get(annotation.id.name);
} else if (annotation.type === 'ObjectTypeAnnotation' || annotation.type === 'TSTypeLiteral') {
propType = annotation;
} else if (annotation.type === 'TSTypeReference') {
propType = objectTypeAnnotations.get(annotation.typeName.name);
} else if (annotation.type === 'TSIntersectionType') {
propType = flatMap(annotation.types, (type) => (
type.type === 'TSTypeReference'
? objectTypeAnnotations.get(type.typeName.name)
: type
));
}
if (propType) {
[].concat(propType).filter(Boolean).forEach((prop) => {
validatePropNaming(
component.node,
prop.properties || prop.members || prop.body
);
});
}
}
if (component.invalidProps && component.invalidProps.length > 0) {
reportInvalidNaming(component);
}
});
// Reset cache
objectTypeAnnotations.clear();
},
};
}),
};
+3
View File
@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=button-has-type.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"button-has-type.d.ts","sourceRoot":"","sources":["button-has-type.js"],"names":[],"mappings":"wBA8BW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
+169
View File
@@ -0,0 +1,169 @@
/**
* @fileoverview Forbid "button" element without an explicit "type" attribute
* @author Filipp Riabchun
*/
'use strict';
const getProp = require('jsx-ast-utils/getProp');
const getLiteralPropValue = require('jsx-ast-utils/getLiteralPropValue');
const docsUrl = require('../util/docsUrl');
const isCreateElement = require('../util/isCreateElement');
const report = require('../util/report');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
const optionDefaults = {
button: true,
submit: true,
reset: true,
};
const messages = {
missingType: 'Missing an explicit type attribute for button',
complexType: 'The button type attribute must be specified by a static string or a trivial ternary expression',
invalidValue: '"{{value}}" is an invalid value for button type attribute',
forbiddenValue: '"{{value}}" is an invalid value for button type attribute',
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
description: 'Disallow usage of `button` elements without an explicit `type` attribute',
category: 'Possible Errors',
recommended: false,
url: docsUrl('button-has-type'),
},
messages,
schema: [{
type: 'object',
properties: {
button: {
default: optionDefaults.button,
type: 'boolean',
},
submit: {
default: optionDefaults.submit,
type: 'boolean',
},
reset: {
default: optionDefaults.reset,
type: 'boolean',
},
},
additionalProperties: false,
}],
},
create(context) {
const configuration = Object.assign({}, optionDefaults, context.options[0]);
function reportMissing(node) {
report(context, messages.missingType, 'missingType', {
node,
});
}
function reportComplex(node) {
report(context, messages.complexType, 'complexType', {
node,
});
}
function checkValue(node, value) {
if (!(value in configuration)) {
report(context, messages.invalidValue, 'invalidValue', {
node,
data: {
value,
},
});
} else if (!configuration[value]) {
report(context, messages.forbiddenValue, 'forbiddenValue', {
node,
data: {
value,
},
});
}
}
function checkExpression(node, expression) {
switch (expression.type) {
case 'Literal':
checkValue(node, expression.value);
return;
case 'TemplateLiteral':
if (expression.expressions.length === 0) {
checkValue(node, expression.quasis[0].value.raw);
} else {
reportComplex(expression);
}
return;
case 'ConditionalExpression':
checkExpression(node, expression.consequent);
checkExpression(node, expression.alternate);
return;
default:
reportComplex(expression);
}
}
return {
JSXElement(node) {
if (node.openingElement.name.name !== 'button') {
return;
}
const typeProp = getProp(node.openingElement.attributes, 'type');
if (!typeProp) {
reportMissing(node);
return;
}
if (typeProp.value && typeProp.value.type === 'JSXExpressionContainer') {
checkExpression(node, typeProp.value.expression);
return;
}
const propValue = getLiteralPropValue(typeProp);
checkValue(node, propValue);
},
CallExpression(node) {
if (!isCreateElement(context, node) || node.arguments.length < 1) {
return;
}
if (node.arguments[0].type !== 'Literal' || node.arguments[0].value !== 'button') {
return;
}
if (!node.arguments[1] || node.arguments[1].type !== 'ObjectExpression') {
reportMissing(node);
return;
}
const props = node.arguments[1].properties;
const typeProp = props.find((prop) => (
'key' in prop
&& prop.key
&& 'name' in prop.key
&& prop.key.name === 'type'
));
if (!typeProp) {
reportMissing(node);
return;
}
checkExpression(node, 'value' in typeProp ? typeProp.value : undefined);
},
};
},
};
@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=checked-requires-onchange-or-readonly.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"checked-requires-onchange-or-readonly.d.ts","sourceRoot":"","sources":["checked-requires-onchange-or-readonly.js"],"names":[],"mappings":"wBA2CW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
@@ -0,0 +1,142 @@
/**
* @fileoverview Enforce the use of the 'onChange' or 'readonly' attribute when 'checked' is used'
* @author Jaesoekjjang
*/
'use strict';
const ASTUtils = require('jsx-ast-utils');
const flatMap = require('array.prototype.flatmap');
const isCreateElement = require('../util/isCreateElement');
const report = require('../util/report');
const docsUrl = require('../util/docsUrl');
const messages = {
missingProperty: '`checked` should be used with either `onChange` or `readOnly`.',
exclusiveCheckedAttribute: 'Use either `checked` or `defaultChecked`, but not both.',
};
const targetPropSet = new Set(['checked', 'onChange', 'readOnly', 'defaultChecked']);
const defaultOptions = {
ignoreMissingProperties: false,
ignoreExclusiveCheckedAttribute: false,
};
/**
* @param {object[]} properties
* @param {string} keyName
* @returns {Set<string>}
*/
function extractTargetProps(properties, keyName) {
return new Set(
flatMap(
properties,
(prop) => (
prop[keyName] && targetPropSet.has(prop[keyName].name)
? [prop[keyName].name]
: []
)
)
);
}
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
description: 'Enforce using `onChange` or `readonly` attribute when `checked` is used',
category: 'Best Practices',
recommended: false,
url: docsUrl('checked-requires-onchange-or-readonly'),
},
messages,
schema: [{
additionalProperties: false,
properties: {
ignoreMissingProperties: {
type: 'boolean',
},
ignoreExclusiveCheckedAttribute: {
type: 'boolean',
},
},
}],
},
create(context) {
const options = Object.assign({}, defaultOptions, context.options[0]);
function reportMissingProperty(node) {
report(
context,
messages.missingProperty,
'missingProperty',
{ node }
);
}
function reportExclusiveCheckedAttribute(node) {
report(
context,
messages.exclusiveCheckedAttribute,
'exclusiveCheckedAttribute',
{ node }
);
}
/**
* @param {ASTNode} node
* @param {Set<string>} propSet
* @returns {void}
*/
const checkAttributesAndReport = (node, propSet) => {
if (!propSet.has('checked')) {
return;
}
if (!options.ignoreExclusiveCheckedAttribute && propSet.has('defaultChecked')) {
reportExclusiveCheckedAttribute(node);
}
if (
!options.ignoreMissingProperties
&& !(propSet.has('onChange') || propSet.has('readOnly'))
) {
reportMissingProperty(node);
}
};
return {
JSXOpeningElement(node) {
if (ASTUtils.elementType(node) !== 'input') {
return;
}
const propSet = extractTargetProps(node.attributes, 'name');
checkAttributesAndReport(node, propSet);
},
CallExpression(node) {
if (!isCreateElement(context, node)) {
return;
}
const firstArg = node.arguments[0];
const secondArg = node.arguments[1];
if (
!firstArg
|| firstArg.type !== 'Literal'
|| firstArg.value !== 'input'
) {
return;
}
if (!secondArg || secondArg.type !== 'ObjectExpression') {
return;
}
const propSet = extractTargetProps(secondArg.properties, 'key');
checkAttributesAndReport(node, propSet);
},
};
},
};
@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=default-props-match-prop-types.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"default-props-match-prop-types.d.ts","sourceRoot":"","sources":["default-props-match-prop-types.js"],"names":[],"mappings":"wBAuBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
@@ -0,0 +1,109 @@
/**
* @fileOverview Enforce all defaultProps are defined in propTypes
* @author Vitor Balocco
* @author Roy Sutton
*/
'use strict';
const values = require('object.values');
const Components = require('../util/Components');
const docsUrl = require('../util/docsUrl');
const report = require('../util/report');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
const messages = {
requiredHasDefault: 'defaultProp "{{name}}" defined for isRequired propType.',
defaultHasNoType: 'defaultProp "{{name}}" has no corresponding propTypes declaration.',
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
description: 'Enforce all defaultProps have a corresponding non-required PropType',
category: 'Best Practices',
url: docsUrl('default-props-match-prop-types'),
},
messages,
schema: [{
type: 'object',
properties: {
allowRequiredDefaults: {
default: false,
type: 'boolean',
},
},
additionalProperties: false,
}],
},
create: Components.detect((context, components) => {
const configuration = context.options[0] || {};
const allowRequiredDefaults = configuration.allowRequiredDefaults || false;
/**
* Reports all defaultProps passed in that don't have an appropriate propTypes counterpart.
* @param {Object[]} propTypes Array of propTypes to check.
* @param {Object} defaultProps Object of defaultProps to check. Keys are the props names.
* @return {void}
*/
function reportInvalidDefaultProps(propTypes, defaultProps) {
// If this defaultProps is "unresolved" or the propTypes is undefined, then we should ignore
// this component and not report any errors for it, to avoid false-positives with e.g.
// external defaultProps/propTypes declarations or spread operators.
if (defaultProps === 'unresolved' || !propTypes || Object.keys(propTypes).length === 0) {
return;
}
Object.keys(defaultProps).forEach((defaultPropName) => {
const defaultProp = defaultProps[defaultPropName];
const prop = propTypes[defaultPropName];
if (prop && (allowRequiredDefaults || !prop.isRequired)) {
return;
}
if (prop) {
report(context, messages.requiredHasDefault, 'requiredHasDefault', {
node: defaultProp.node,
data: {
name: defaultPropName,
},
});
} else {
report(context, messages.defaultHasNoType, 'defaultHasNoType', {
node: defaultProp.node,
data: {
name: defaultPropName,
},
});
}
});
}
// --------------------------------------------------------------------------
// Public API
// --------------------------------------------------------------------------
return {
'Program:exit'() {
// If no defaultProps could be found, we don't report anything.
values(components.list())
.filter((component) => component.defaultProps)
.forEach((component) => {
reportInvalidDefaultProps(
component.declaredPropTypes,
component.defaultProps || {}
);
});
},
};
}),
};
@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=destructuring-assignment.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"destructuring-assignment.d.ts","sourceRoot":"","sources":["destructuring-assignment.js"],"names":[],"mappings":"wBA2DW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
+319
View File
@@ -0,0 +1,319 @@
/**
* @fileoverview Enforce consistent usage of destructuring assignment of props, state, and context.
*/
'use strict';
const Components = require('../util/Components');
const docsUrl = require('../util/docsUrl');
const eslintUtil = require('../util/eslint');
const isAssignmentLHS = require('../util/ast').isAssignmentLHS;
const report = require('../util/report');
const getScope = eslintUtil.getScope;
const getText = eslintUtil.getText;
const DEFAULT_OPTION = 'always';
function createSFCParams() {
const queue = [];
return {
push(params) {
queue.unshift(params);
},
pop() {
queue.shift();
},
propsName() {
const found = queue.find((params) => {
const props = params[0];
return props && !props.destructuring && props.name;
});
return found && found[0] && found[0].name;
},
contextName() {
const found = queue.find((params) => {
const context = params[1];
return context && !context.destructuring && context.name;
});
return found && found[1] && found[1].name;
},
};
}
function evalParams(params) {
return params.map((param) => ({
destructuring: param.type === 'ObjectPattern',
name: param.type === 'Identifier' && param.name,
}));
}
const messages = {
noDestructPropsInSFCArg: 'Must never use destructuring props assignment in SFC argument',
noDestructContextInSFCArg: 'Must never use destructuring context assignment in SFC argument',
noDestructAssignment: 'Must never use destructuring {{type}} assignment',
useDestructAssignment: 'Must use destructuring {{type}} assignment',
destructureInSignature: 'Must destructure props in the function signature.',
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
description: 'Enforce consistent usage of destructuring assignment of props, state, and context',
category: 'Stylistic Issues',
recommended: false,
url: docsUrl('destructuring-assignment'),
},
fixable: 'code',
messages,
schema: [{
type: 'string',
enum: [
'always',
'never',
],
}, {
type: 'object',
properties: {
ignoreClassFields: {
type: 'boolean',
},
destructureInSignature: {
type: 'string',
enum: [
'always',
'ignore',
],
},
},
additionalProperties: false,
}],
},
create: Components.detect((context, components, utils) => {
const configuration = context.options[0] || DEFAULT_OPTION;
const ignoreClassFields = (context.options[1] && (context.options[1].ignoreClassFields === true)) || false;
const destructureInSignature = (context.options[1] && context.options[1].destructureInSignature) || 'ignore';
const sfcParams = createSFCParams();
/**
* @param {ASTNode} node We expect either an ArrowFunctionExpression,
* FunctionDeclaration, or FunctionExpression
*/
function handleStatelessComponent(node) {
const params = evalParams(node.params);
const SFCComponent = components.get(getScope(context, node).block);
if (!SFCComponent) {
return;
}
sfcParams.push(params);
if (params[0] && params[0].destructuring && components.get(node) && configuration === 'never') {
report(context, messages.noDestructPropsInSFCArg, 'noDestructPropsInSFCArg', {
node,
});
} else if (params[1] && params[1].destructuring && components.get(node) && configuration === 'never') {
report(context, messages.noDestructContextInSFCArg, 'noDestructContextInSFCArg', {
node,
});
}
}
function handleStatelessComponentExit(node) {
const SFCComponent = components.get(getScope(context, node).block);
if (SFCComponent) {
sfcParams.pop();
}
}
function handleSFCUsage(node) {
const propsName = sfcParams.propsName();
const contextName = sfcParams.contextName();
// props.aProp || context.aProp
const isPropUsed = (
(propsName && node.object.name === propsName)
|| (contextName && node.object.name === contextName)
)
&& !isAssignmentLHS(node);
if (isPropUsed && configuration === 'always' && !node.optional) {
report(context, messages.useDestructAssignment, 'useDestructAssignment', {
node,
data: {
type: node.object.name,
},
});
}
}
function isInClassProperty(node) {
let curNode = node.parent;
while (curNode) {
if (curNode.type === 'ClassProperty' || curNode.type === 'PropertyDefinition') {
return true;
}
curNode = curNode.parent;
}
return false;
}
function handleClassUsage(node) {
// this.props.Aprop || this.context.aProp || this.state.aState
const isPropUsed = (
node.object.type === 'MemberExpression' && node.object.object.type === 'ThisExpression'
&& (node.object.property.name === 'props' || node.object.property.name === 'context' || node.object.property.name === 'state')
&& !isAssignmentLHS(node)
);
if (
isPropUsed && configuration === 'always'
&& !(ignoreClassFields && isInClassProperty(node))
) {
report(context, messages.useDestructAssignment, 'useDestructAssignment', {
node,
data: {
type: node.object.property.name,
},
});
}
}
// valid-jsdoc cannot read function types
// eslint-disable-next-line valid-jsdoc
/**
* Find a parent that satisfy the given predicate
* @param {ASTNode} node
* @param {(node: ASTNode) => boolean} predicate
* @returns {ASTNode | undefined}
*/
function findParent(node, predicate) {
let n = node;
while (n) {
if (predicate(n)) {
return n;
}
n = n.parent;
}
return undefined;
}
return {
FunctionDeclaration: handleStatelessComponent,
ArrowFunctionExpression: handleStatelessComponent,
FunctionExpression: handleStatelessComponent,
'FunctionDeclaration:exit': handleStatelessComponentExit,
'ArrowFunctionExpression:exit': handleStatelessComponentExit,
'FunctionExpression:exit': handleStatelessComponentExit,
MemberExpression(node) {
const SFCComponent = utils.getParentStatelessComponent(node);
if (SFCComponent) {
handleSFCUsage(node);
}
const classComponent = utils.getParentComponent(node);
if (classComponent) {
handleClassUsage(node);
}
},
TSQualifiedName(node) {
if (configuration !== 'always') {
return;
}
// handle `typeof props.a.b`
if (node.left.type === 'Identifier'
&& node.left.name === sfcParams.propsName()
&& findParent(node, (n) => n.type === 'TSTypeQuery')
&& utils.getParentStatelessComponent(node)
) {
report(context, messages.useDestructAssignment, 'useDestructAssignment', {
node,
data: {
type: 'props',
},
});
}
},
VariableDeclarator(node) {
const classComponent = utils.getParentComponent(node);
const SFCComponent = components.get(getScope(context, node).block);
const destructuring = (node.init && node.id && node.id.type === 'ObjectPattern');
// let {foo} = props;
const destructuringSFC = destructuring && (node.init.name === 'props' || node.init.name === 'context');
// let {foo} = this.props;
const destructuringClass = destructuring && node.init.object && node.init.object.type === 'ThisExpression' && (
node.init.property.name === 'props' || node.init.property.name === 'context' || node.init.property.name === 'state'
);
if (SFCComponent && destructuringSFC && configuration === 'never') {
report(context, messages.noDestructAssignment, 'noDestructAssignment', {
node,
data: {
type: node.init.name,
},
});
}
if (
classComponent && destructuringClass && configuration === 'never'
&& !(ignoreClassFields && (node.parent.type === 'ClassProperty' || node.parent.type === 'PropertyDefinition'))
) {
report(context, messages.noDestructAssignment, 'noDestructAssignment', {
node,
data: {
type: node.init.property.name,
},
});
}
if (
SFCComponent
&& destructuringSFC
&& configuration === 'always'
&& destructureInSignature === 'always'
&& node.init.name === 'props'
) {
const scopeSetProps = getScope(context, node).set.get('props');
const propsRefs = scopeSetProps && scopeSetProps.references;
if (!propsRefs) {
return;
}
// Skip if props is used elsewhere
if (propsRefs.length > 1) {
return;
}
report(context, messages.destructureInSignature, 'destructureInSignature', {
node,
fix(fixer) {
const param = SFCComponent.node.params[0];
if (!param) {
return;
}
const replaceRange = [
param.range[0],
param.typeAnnotation ? param.typeAnnotation.range[0] : param.range[1],
];
return [
fixer.replaceTextRange(replaceRange, getText(context, node.id)),
fixer.remove(node.parent),
];
},
});
}
},
};
}),
};
+3
View File
@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=display-name.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"display-name.d.ts","sourceRoot":"","sources":["display-name.js"],"names":[],"mappings":"wBA6BW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
+285
View File
@@ -0,0 +1,285 @@
/**
* @fileoverview Prevent missing displayName in a React component definition
* @author Yannick Croissant
*/
'use strict';
const values = require('object.values');
const filter = require('es-iterator-helpers/Iterator.prototype.filter');
const forEach = require('es-iterator-helpers/Iterator.prototype.forEach');
const Components = require('../util/Components');
const isCreateContext = require('../util/isCreateContext');
const astUtil = require('../util/ast');
const componentUtil = require('../util/componentUtil');
const docsUrl = require('../util/docsUrl');
const testReactVersion = require('../util/version').testReactVersion;
const propsUtil = require('../util/props');
const report = require('../util/report');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
const messages = {
noDisplayName: 'Component definition is missing display name',
noContextDisplayName: 'Context definition is missing display name',
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
description: 'Disallow missing displayName in a React component definition',
category: 'Best Practices',
recommended: true,
url: docsUrl('display-name'),
},
messages,
schema: [{
type: 'object',
properties: {
ignoreTranspilerName: {
type: 'boolean',
},
checkContextObjects: {
type: 'boolean',
},
},
additionalProperties: false,
}],
},
create: Components.detect((context, components, utils) => {
const config = context.options[0] || {};
const ignoreTranspilerName = config.ignoreTranspilerName || false;
const checkContextObjects = (config.checkContextObjects || false) && testReactVersion(context, '>= 16.3.0');
const contextObjects = new Map();
/**
* Mark a prop type as declared
* @param {ASTNode} node The AST node being checked.
*/
function markDisplayNameAsDeclared(node) {
components.set(node, {
hasDisplayName: true,
});
}
/**
* Checks if React.forwardRef is nested inside React.memo
* @param {ASTNode} node The AST node being checked.
* @returns {boolean} True if React.forwardRef is nested inside React.memo, false if not.
*/
function isNestedMemo(node) {
return astUtil.isCallExpression(node)
&& node.arguments
&& astUtil.isCallExpression(node.arguments[0])
&& utils.isPragmaComponentWrapper(node);
}
/**
* Reports missing display name for a given component
* @param {Object} component The component to process
*/
function reportMissingDisplayName(component) {
if (
testReactVersion(context, '^0.14.10 || ^15.7.0 || >= 16.12.0')
&& isNestedMemo(component.node)
) {
return;
}
report(context, messages.noDisplayName, 'noDisplayName', {
node: component.node,
});
}
/**
* Reports missing display name for a given context object
* @param {Object} contextObj The context object to process
*/
function reportMissingContextDisplayName(contextObj) {
report(context, messages.noContextDisplayName, 'noContextDisplayName', {
node: contextObj.node,
});
}
/**
* Checks if the component have a name set by the transpiler
* @param {ASTNode} node The AST node being checked.
* @returns {boolean} True if component has a name, false if not.
*/
function hasTranspilerName(node) {
const namedObjectAssignment = (
node.type === 'ObjectExpression'
&& node.parent
&& node.parent.parent
&& node.parent.parent.type === 'AssignmentExpression'
&& (
!node.parent.parent.left.object
|| node.parent.parent.left.object.name !== 'module'
|| node.parent.parent.left.property.name !== 'exports'
)
);
const namedObjectDeclaration = (
node.type === 'ObjectExpression'
&& node.parent
&& node.parent.parent
&& node.parent.parent.type === 'VariableDeclarator'
);
const namedClass = (
(node.type === 'ClassDeclaration' || node.type === 'ClassExpression')
&& node.id
&& !!node.id.name
);
const namedFunctionDeclaration = (
(node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression')
&& node.id
&& !!node.id.name
);
const namedFunctionExpression = (
astUtil.isFunctionLikeExpression(node)
&& node.parent
&& (node.parent.type === 'VariableDeclarator' || node.parent.type === 'Property' || node.parent.method === true)
&& (!node.parent.parent || !componentUtil.isES5Component(node.parent.parent, context))
);
if (
namedObjectAssignment || namedObjectDeclaration
|| namedClass
|| namedFunctionDeclaration || namedFunctionExpression
) {
return true;
}
return false;
}
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
return {
ExpressionStatement(node) {
if (checkContextObjects && isCreateContext(node)) {
contextObjects.set(node.expression.left.name, { node, hasDisplayName: false });
}
},
VariableDeclarator(node) {
if (checkContextObjects && isCreateContext(node)) {
contextObjects.set(node.id.name, { node, hasDisplayName: false });
}
},
'ClassProperty, PropertyDefinition'(node) {
if (!propsUtil.isDisplayNameDeclaration(node)) {
return;
}
markDisplayNameAsDeclared(node);
},
MemberExpression(node) {
if (!propsUtil.isDisplayNameDeclaration(node.property)) {
return;
}
if (
checkContextObjects
&& node.object
&& node.object.name
&& contextObjects.has(node.object.name)
) {
contextObjects.get(node.object.name).hasDisplayName = true;
}
const component = utils.getRelatedComponent(node);
if (!component) {
return;
}
markDisplayNameAsDeclared(astUtil.unwrapTSAsExpression(component.node));
},
'FunctionExpression, FunctionDeclaration, ArrowFunctionExpression'(node) {
if (ignoreTranspilerName || !hasTranspilerName(node)) {
return;
}
if (components.get(node)) {
markDisplayNameAsDeclared(node);
}
},
MethodDefinition(node) {
if (!propsUtil.isDisplayNameDeclaration(node.key)) {
return;
}
markDisplayNameAsDeclared(node);
},
'ClassExpression, ClassDeclaration'(node) {
if (ignoreTranspilerName || !hasTranspilerName(node)) {
return;
}
markDisplayNameAsDeclared(node);
},
ObjectExpression(node) {
if (!componentUtil.isES5Component(node, context)) {
return;
}
if (ignoreTranspilerName || !hasTranspilerName(node)) {
// Search for the displayName declaration
node.properties.forEach((property) => {
if (!property.key || !propsUtil.isDisplayNameDeclaration(property.key)) {
return;
}
markDisplayNameAsDeclared(node);
});
return;
}
markDisplayNameAsDeclared(node);
},
CallExpression(node) {
if (!utils.isPragmaComponentWrapper(node)) {
return;
}
if (node.arguments.length > 0 && astUtil.isFunctionLikeExpression(node.arguments[0])) {
// Skip over React.forwardRef declarations that are embedded within
// a React.memo i.e. React.memo(React.forwardRef(/* ... */))
// This means that we raise a single error for the call to React.memo
// instead of one for React.memo and one for React.forwardRef
const isWrappedInAnotherPragma = utils.getPragmaComponentWrapper(node);
if (
!isWrappedInAnotherPragma
&& (ignoreTranspilerName || !hasTranspilerName(node.arguments[0]))
) {
return;
}
if (components.get(node)) {
markDisplayNameAsDeclared(node);
}
}
},
'Program:exit'() {
const list = components.list();
// Report missing display name for all components
values(list).filter((component) => !component.hasDisplayName).forEach((component) => {
reportMissingDisplayName(component);
});
if (checkContextObjects) {
// Report missing display name for all context objects
forEach(
filter(contextObjects.values(), (v) => !v.hasDisplayName),
(contextObj) => reportMissingContextDisplayName(contextObj)
);
}
},
};
}),
};
@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=forbid-component-props.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"forbid-component-props.d.ts","sourceRoot":"","sources":["forbid-component-props.js"],"names":[],"mappings":"wBAyBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
+239
View File
@@ -0,0 +1,239 @@
/**
* @fileoverview Forbid certain props on components
* @author Joe Lencioni
*/
'use strict';
const minimatch = require('minimatch');
const docsUrl = require('../util/docsUrl');
const report = require('../util/report');
// ------------------------------------------------------------------------------
// Constants
// ------------------------------------------------------------------------------
const DEFAULTS = ['className', 'style'];
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
const messages = {
propIsForbidden: 'Prop "{{prop}}" is forbidden on Components',
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
description: 'Disallow certain props on components',
category: 'Best Practices',
recommended: false,
url: docsUrl('forbid-component-props'),
},
messages,
schema: [{
type: 'object',
properties: {
forbid: {
type: 'array',
items: {
anyOf: [
{ type: 'string' },
{
type: 'object',
properties: {
propName: { type: 'string' },
allowedFor: {
type: 'array',
uniqueItems: true,
items: { type: 'string' },
},
allowedForPatterns: {
type: 'array',
uniqueItems: true,
items: { type: 'string' },
},
message: { type: 'string' },
},
additionalProperties: false,
},
{
type: 'object',
properties: {
propName: { type: 'string' },
disallowedFor: {
type: 'array',
uniqueItems: true,
minItems: 1,
items: { type: 'string' },
},
disallowedForPatterns: {
type: 'array',
uniqueItems: true,
minItems: 1,
items: { type: 'string' },
},
message: { type: 'string' },
},
anyOf: [
{ required: ['disallowedFor'] },
{ required: ['disallowedForPatterns'] },
],
additionalProperties: false,
},
{
type: 'object',
properties: {
propNamePattern: { type: 'string' },
allowedFor: {
type: 'array',
uniqueItems: true,
items: { type: 'string' },
},
allowedForPatterns: {
type: 'array',
uniqueItems: true,
items: { type: 'string' },
},
message: { type: 'string' },
},
additionalProperties: false,
},
{
type: 'object',
properties: {
propNamePattern: { type: 'string' },
disallowedFor: {
type: 'array',
uniqueItems: true,
minItems: 1,
items: { type: 'string' },
},
disallowedForPatterns: {
type: 'array',
uniqueItems: true,
minItems: 1,
items: { type: 'string' },
},
message: { type: 'string' },
},
anyOf: [
{ required: ['disallowedFor'] },
{ required: ['disallowedForPatterns'] },
],
additionalProperties: false,
},
],
},
},
},
}],
},
create(context) {
const configuration = context.options[0] || {};
const forbid = new Map((configuration.forbid || DEFAULTS).map((value) => {
const propName = typeof value === 'string' ? value : value.propName;
const propPattern = value.propNamePattern;
const prop = propName || propPattern;
const options = {
allowList: [].concat(value.allowedFor || []),
allowPatternList: [].concat(value.allowedForPatterns || []),
disallowList: [].concat(value.disallowedFor || []),
disallowPatternList: [].concat(value.disallowedForPatterns || []),
message: typeof value === 'string' ? null : value.message,
isPattern: !!value.propNamePattern,
};
return [prop, options];
}));
function getPropOptions(prop) {
// Get config options having pattern
const propNamePatternArray = Array.from(forbid.entries()).filter((propEntry) => propEntry[1].isPattern);
// Match current prop with pattern options, return if matched
const propNamePattern = propNamePatternArray.find((propPatternVal) => minimatch(prop, propPatternVal[0]));
// Get options for matched propNamePattern
const propNamePatternOptions = propNamePattern && propNamePattern[1];
const options = forbid.get(prop) || propNamePatternOptions;
return options;
}
function isForbidden(prop, tagName) {
const options = getPropOptions(prop);
if (!options) {
return false;
}
function checkIsTagForbiddenByAllowOptions() {
if (options.allowList.indexOf(tagName) !== -1) {
return false;
}
if (options.allowPatternList.length === 0) {
return true;
}
return options.allowPatternList.every(
(pattern) => !minimatch(tagName, pattern)
);
}
function checkIsTagForbiddenByDisallowOptions() {
if (options.disallowList.indexOf(tagName) !== -1) {
return true;
}
if (options.disallowPatternList.length === 0) {
return false;
}
return options.disallowPatternList.some(
(pattern) => minimatch(tagName, pattern)
);
}
const hasDisallowOptions = options.disallowList.length > 0 || options.disallowPatternList.length > 0;
// disallowList should have a least one item (schema configuration)
const isTagForbidden = hasDisallowOptions
? checkIsTagForbiddenByDisallowOptions()
: checkIsTagForbiddenByAllowOptions();
// if the tagName is undefined (`<this.something>`), we assume it's a forbidden element
return typeof tagName === 'undefined' || isTagForbidden;
}
return {
JSXAttribute(node) {
const parentName = node.parent.name;
// Extract a component name when using a "namespace", e.g. `<AntdLayout.Content />`.
const tag = parentName.name || `${parentName.object.name}.${parentName.property.name}`;
const componentName = parentName.name || parentName.property.name;
if (componentName && typeof componentName[0] === 'string' && componentName[0] !== componentName[0].toUpperCase()) {
// This is a DOM node, not a Component, so exit.
return;
}
const prop = node.name.name;
if (!isForbidden(prop, tag)) {
return;
}
const customMessage = getPropOptions(prop).message;
report(context, customMessage || messages.propIsForbidden, !customMessage && 'propIsForbidden', {
node,
data: {
prop,
},
});
},
};
},
};
+3
View File
@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=forbid-dom-props.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"forbid-dom-props.d.ts","sourceRoot":"","sources":["forbid-dom-props.js"],"names":[],"mappings":"wBAuCW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
+122
View File
@@ -0,0 +1,122 @@
/**
* @fileoverview Forbid certain props on DOM Nodes
* @author David Vázquez
*/
'use strict';
const docsUrl = require('../util/docsUrl');
const report = require('../util/report');
// ------------------------------------------------------------------------------
// Constants
// ------------------------------------------------------------------------------
const DEFAULTS = [];
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
/**
* @param {Map<string, object>} forbidMap // { disallowList: null | string[], message: null | string }
* @param {string} prop
* @param {string} tagName
* @returns {boolean}
*/
function isForbidden(forbidMap, prop, tagName) {
const options = forbidMap.get(prop);
return options && (
typeof tagName === 'undefined'
|| !options.disallowList
|| options.disallowList.indexOf(tagName) !== -1
);
}
const messages = {
propIsForbidden: 'Prop "{{prop}}" is forbidden on DOM Nodes',
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
description: 'Disallow certain props on DOM Nodes',
category: 'Best Practices',
recommended: false,
url: docsUrl('forbid-dom-props'),
},
messages,
schema: [{
type: 'object',
properties: {
forbid: {
type: 'array',
items: {
anyOf: [{
type: 'string',
}, {
type: 'object',
properties: {
propName: {
type: 'string',
},
disallowedFor: {
type: 'array',
uniqueItems: true,
items: {
type: 'string',
},
},
message: {
type: 'string',
},
},
}],
minLength: 1,
},
uniqueItems: true,
},
},
additionalProperties: false,
}],
},
create(context) {
const configuration = context.options[0] || {};
const forbid = new Map((configuration.forbid || DEFAULTS).map((value) => {
const propName = typeof value === 'string' ? value : value.propName;
return [propName, {
disallowList: typeof value === 'string' ? null : (value.disallowedFor || null),
message: typeof value === 'string' ? null : value.message,
}];
}));
return {
JSXAttribute(node) {
const tag = node.parent.name.name;
if (!(tag && typeof tag === 'string' && tag[0] !== tag[0].toUpperCase())) {
// This is a Component, not a DOM node, so exit.
return;
}
const prop = node.name.name;
if (!isForbidden(forbid, prop, tag)) {
return;
}
const customMessage = forbid.get(prop).message;
report(context, customMessage || messages.propIsForbidden, !customMessage && 'propIsForbidden', {
node,
data: {
prop,
},
});
},
};
},
};
+3
View File
@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=forbid-elements.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"forbid-elements.d.ts","sourceRoot":"","sources":["forbid-elements.js"],"names":[],"mappings":"wBAsBW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
+119
View File
@@ -0,0 +1,119 @@
/**
* @fileoverview Forbid certain elements
* @author Kenneth Chung
*/
'use strict';
const has = require('hasown');
const docsUrl = require('../util/docsUrl');
const getText = require('../util/eslint').getText;
const isCreateElement = require('../util/isCreateElement');
const report = require('../util/report');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
const messages = {
forbiddenElement: '<{{element}}> is forbidden',
forbiddenElement_message: '<{{element}}> is forbidden, {{message}}',
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
description: 'Disallow certain elements',
category: 'Best Practices',
recommended: false,
url: docsUrl('forbid-elements'),
},
messages,
schema: [{
type: 'object',
properties: {
forbid: {
type: 'array',
items: {
anyOf: [
{ type: 'string' },
{
type: 'object',
properties: {
element: { type: 'string' },
message: { type: 'string' },
},
required: ['element'],
additionalProperties: false,
},
],
},
},
},
additionalProperties: false,
}],
},
create(context) {
const configuration = context.options[0] || {};
const forbidConfiguration = configuration.forbid || [];
/** @type {Record<string, { element: string, message?: string }>} */
const indexedForbidConfigs = {};
forbidConfiguration.forEach((item) => {
if (typeof item === 'string') {
indexedForbidConfigs[item] = { element: item };
} else {
indexedForbidConfigs[item.element] = item;
}
});
function reportIfForbidden(element, node) {
if (has(indexedForbidConfigs, element)) {
const message = indexedForbidConfigs[element].message;
report(
context,
message ? messages.forbiddenElement_message : messages.forbiddenElement,
message ? 'forbiddenElement_message' : 'forbiddenElement',
{
node,
data: {
element,
message,
},
}
);
}
}
return {
JSXOpeningElement(node) {
reportIfForbidden(getText(context, node.name), node.name);
},
CallExpression(node) {
if (!isCreateElement(context, node)) {
return;
}
const argument = node.arguments[0];
if (!argument) {
return;
}
if (argument.type === 'Identifier' && /^[A-Z_]/.test(argument.name)) {
reportIfForbidden(argument.name, argument);
} else if (argument.type === 'Literal' && /^[a-z][^.]*$/.test(String(argument.value))) {
reportIfForbidden(argument.value, argument);
} else if (argument.type === 'MemberExpression') {
reportIfForbidden(getText(context, argument), argument);
}
},
};
},
};
@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=forbid-foreign-prop-types.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"forbid-foreign-prop-types.d.ts","sourceRoot":"","sources":["forbid-foreign-prop-types.js"],"names":[],"mappings":"wBAeW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
+141
View File
@@ -0,0 +1,141 @@
/**
* @fileoverview Forbid using another component's propTypes
* @author Ian Christian Myers
*/
'use strict';
const docsUrl = require('../util/docsUrl');
const ast = require('../util/ast');
const report = require('../util/report');
const messages = {
forbiddenPropType: 'Using propTypes from another component is not safe because they may be removed in production builds',
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
description: 'Disallow using another component\'s propTypes',
category: 'Best Practices',
recommended: false,
url: docsUrl('forbid-foreign-prop-types'),
},
messages,
schema: [
{
type: 'object',
properties: {
allowInPropTypes: {
type: 'boolean',
},
},
additionalProperties: false,
},
],
},
create(context) {
const config = context.options[0] || {};
const allowInPropTypes = config.allowInPropTypes || false;
// --------------------------------------------------------------------------
// Helpers
// --------------------------------------------------------------------------
function findParentAssignmentExpression(node) {
let parent = node.parent;
while (parent && parent.type !== 'Program') {
if (parent.type === 'AssignmentExpression') {
return parent;
}
parent = parent.parent;
}
return null;
}
function findParentClassProperty(node) {
let parent = node.parent;
while (parent && parent.type !== 'Program') {
if (parent.type === 'ClassProperty' || parent.type === 'PropertyDefinition') {
return parent;
}
parent = parent.parent;
}
return null;
}
function isAllowedAssignment(node) {
if (!allowInPropTypes) {
return false;
}
const assignmentExpression = findParentAssignmentExpression(node);
if (
assignmentExpression
&& assignmentExpression.left
&& assignmentExpression.left.property
&& assignmentExpression.left.property.name === 'propTypes'
) {
return true;
}
const classProperty = findParentClassProperty(node);
if (
classProperty
&& classProperty.key
&& classProperty.key.name === 'propTypes'
) {
return true;
}
return false;
}
return {
MemberExpression(node) {
if (
(node.property
&& (
!node.computed
&& node.property.type === 'Identifier'
&& node.property.name === 'propTypes'
&& !ast.isAssignmentLHS(node)
&& !isAllowedAssignment(node)
)) || (
// @ts-expect-error: The JSXText type is not present in the estree type definitions
(node.property.type === 'Literal' || node.property.type === 'JSXText')
&& 'value' in node.property
&& node.property.value === 'propTypes'
&& !ast.isAssignmentLHS(node)
&& !isAllowedAssignment(node)
)
) {
report(context, messages.forbiddenPropType, 'forbiddenPropType', {
node: node.property,
});
}
},
ObjectPattern(node) {
const propTypesNode = node.properties.find((property) => (
property.type === 'Property'
&& 'name' in property.key
&& property.key.name === 'propTypes'
));
if (propTypesNode) {
report(context, messages.forbiddenPropType, 'forbiddenPropType', {
node: propTypesNode,
});
}
},
};
},
};
+3
View File
@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=forbid-prop-types.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"forbid-prop-types.d.ts","sourceRoot":"","sources":["forbid-prop-types.js"],"names":[],"mappings":"wBA4BW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
+298
View File
@@ -0,0 +1,298 @@
/**
* @fileoverview Forbid certain propTypes
*/
'use strict';
const variableUtil = require('../util/variable');
const propsUtil = require('../util/props');
const astUtil = require('../util/ast');
const docsUrl = require('../util/docsUrl');
const propWrapperUtil = require('../util/propWrapper');
const report = require('../util/report');
const getText = require('../util/eslint').getText;
// ------------------------------------------------------------------------------
// Constants
// ------------------------------------------------------------------------------
const DEFAULTS = ['any', 'array', 'object'];
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
const messages = {
forbiddenPropType: 'Prop type "{{target}}" is forbidden',
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
description: 'Disallow certain propTypes',
category: 'Best Practices',
recommended: false,
url: docsUrl('forbid-prop-types'),
},
messages,
schema: [{
type: 'object',
properties: {
forbid: {
type: 'array',
items: {
type: 'string',
},
},
checkContextTypes: {
type: 'boolean',
},
checkChildContextTypes: {
type: 'boolean',
},
},
additionalProperties: true,
}],
},
create(context) {
const configuration = context.options[0] || {};
const checkContextTypes = configuration.checkContextTypes || false;
const checkChildContextTypes = configuration.checkChildContextTypes || false;
let propTypesPackageName = null;
let reactPackageName = null;
let isForeignPropTypesPackage = false;
function isPropTypesPackage(node) {
return (
node.type === 'Identifier'
&& (
node.name === null
|| node.name === propTypesPackageName
|| !isForeignPropTypesPackage
)
) || (
node.type === 'MemberExpression'
&& (
node.object.name === null
|| node.object.name === reactPackageName
|| !isForeignPropTypesPackage
)
);
}
function isForbidden(type) {
const forbid = configuration.forbid || DEFAULTS;
return forbid.indexOf(type) >= 0;
}
function reportIfForbidden(type, declaration, target) {
if (isForbidden(type)) {
report(context, messages.forbiddenPropType, 'forbiddenPropType', {
node: declaration,
data: {
target,
},
});
}
}
function shouldCheckContextTypes(node) {
if (checkContextTypes && propsUtil.isContextTypesDeclaration(node)) {
return true;
}
return false;
}
function shouldCheckChildContextTypes(node) {
if (checkChildContextTypes && propsUtil.isChildContextTypesDeclaration(node)) {
return true;
}
return false;
}
/**
* Checks if propTypes declarations are forbidden
* @param {Array} declarations The array of AST nodes being checked.
* @returns {void}
*/
function checkProperties(declarations) {
if (declarations) {
declarations.forEach((declaration) => {
if (declaration.type !== 'Property') {
return;
}
let target;
let value = declaration.value;
if (
value.type === 'MemberExpression'
&& value.property
&& value.property.name
&& value.property.name === 'isRequired'
) {
value = value.object;
}
if (astUtil.isCallExpression(value)) {
if (!isPropTypesPackage(value.callee)) {
return;
}
value.arguments.forEach((arg) => {
const name = arg.type === 'MemberExpression' ? arg.property.name : arg.name;
reportIfForbidden(name, declaration, name);
});
value = value.callee;
}
if (!isPropTypesPackage(value)) {
return;
}
if (value.property) {
target = value.property.name;
} else if (value.type === 'Identifier') {
target = value.name;
}
reportIfForbidden(target, declaration, target);
});
}
}
function checkNode(node) {
if (!node) {
return;
}
if (node.type === 'ObjectExpression') {
checkProperties(node.properties);
} else if (node.type === 'Identifier') {
const propTypesObject = variableUtil.findVariableByName(context, node, node.name);
if (propTypesObject && propTypesObject.properties) {
checkProperties(propTypesObject.properties);
}
} else if (astUtil.isCallExpression(node)) {
const innerNode = node.arguments && node.arguments[0];
if (
propWrapperUtil.isPropWrapperFunction(context, getText(context, node.callee))
&& innerNode
) {
checkNode(innerNode);
}
}
}
return {
ImportDeclaration(node) {
if (node.source && node.source.value === 'prop-types') { // import PropType from "prop-types"
if (node.specifiers.length > 0) {
propTypesPackageName = node.specifiers[0].local.name;
}
} else if (node.source && node.source.value === 'react') { // import { PropTypes } from "react"
if (node.specifiers.length > 0) {
reactPackageName = node.specifiers[0].local.name; // guard against accidental anonymous `import "react"`
}
if (node.specifiers.length >= 1) {
const propTypesSpecifier = node.specifiers.find((specifier) => (
'imported' in specifier
&& specifier.imported
&& 'name' in specifier.imported
&& specifier.imported.name === 'PropTypes'
));
if (propTypesSpecifier) {
propTypesPackageName = propTypesSpecifier.local.name;
}
}
} else { // package is not imported from "react" or "prop-types"
// eslint-disable-next-line no-lonely-if
if (node.specifiers.some((x) => x.local.name === 'PropTypes')) { // assert: node.specifiers.length > 1
isForeignPropTypesPackage = true;
}
}
},
'ClassProperty, PropertyDefinition'(node) {
if (
!propsUtil.isPropTypesDeclaration(node)
&& !isPropTypesPackage(node)
&& !shouldCheckContextTypes(node)
&& !shouldCheckChildContextTypes(node)
) {
return;
}
checkNode(node.value);
},
MemberExpression(node) {
if (
!propsUtil.isPropTypesDeclaration(node)
&& !isPropTypesPackage(node)
&& !shouldCheckContextTypes(node)
&& !shouldCheckChildContextTypes(node)
) {
return;
}
checkNode('right' in node.parent && node.parent.right);
},
CallExpression(node) {
if (
node.callee.type === 'MemberExpression'
&& node.callee.object
&& !isPropTypesPackage(node.callee.object)
&& !propsUtil.isPropTypesDeclaration(node.callee)
) {
return;
}
if (
node.arguments.length > 0
&& (
('name' in node.callee && node.callee.name === 'shape')
|| astUtil.getPropertyName(node.callee) === 'shape'
)
) {
checkProperties('properties' in node.arguments[0] && node.arguments[0].properties);
}
},
MethodDefinition(node) {
if (
!propsUtil.isPropTypesDeclaration(node)
&& !isPropTypesPackage(node)
&& !shouldCheckContextTypes(node)
&& !shouldCheckChildContextTypes(node)
) {
return;
}
const returnStatement = astUtil.findReturnStatement(node);
if (returnStatement && returnStatement.argument) {
checkNode(returnStatement.argument);
}
},
ObjectExpression(node) {
node.properties.forEach((property) => {
if (!('key' in property) || !property.key) {
return;
}
if (
!propsUtil.isPropTypesDeclaration(property)
&& !isPropTypesPackage(property)
&& !shouldCheckContextTypes(property)
&& !shouldCheckChildContextTypes(property)
) {
return;
}
if (property.value.type === 'ObjectExpression') {
checkProperties(property.value.properties);
}
});
},
};
},
};
+3
View File
@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=forward-ref-uses-ref.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"forward-ref-uses-ref.d.ts","sourceRoot":"","sources":["forward-ref-uses-ref.js"],"names":[],"mappings":"wBA2CW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
+100
View File
@@ -0,0 +1,100 @@
/**
* @fileoverview Require all forwardRef components include a ref parameter
*/
'use strict';
const isParenthesized = require('../util/ast').isParenthesized;
const docsUrl = require('../util/docsUrl');
const report = require('../util/report');
const getMessageData = require('../util/message');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
/**
* @param {ASTNode} node
* @returns {boolean} If the node represents the identifier `forwardRef`.
*/
function isForwardRefIdentifier(node) {
return node.type === 'Identifier' && node.name === 'forwardRef';
}
/**
* @param {ASTNode} node
* @returns {boolean} If the node represents a function call `forwardRef()` or `React.forwardRef()`.
*/
function isForwardRefCall(node) {
return (
node.type === 'CallExpression'
&& (
isForwardRefIdentifier(node.callee)
|| (node.callee.type === 'MemberExpression' && isForwardRefIdentifier(node.callee.property))
)
);
}
const messages = {
missingRefParameter: 'forwardRef is used with this component but no ref parameter is set',
addRefParameter: 'Add a ref parameter',
removeForwardRef: 'Remove forwardRef wrapper',
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
description: 'Require all forwardRef components include a ref parameter',
category: 'Possible Errors',
recommended: false,
url: docsUrl('forward-ref-uses-ref'),
},
messages,
schema: [],
type: 'suggestion',
hasSuggestions: true,
},
create(context) {
const sourceCode = context.getSourceCode();
return {
'FunctionExpression, ArrowFunctionExpression'(node) {
if (!isForwardRefCall(node.parent)) {
return;
}
if (node.params.length === 1) {
report(context, messages.missingRefParameter, 'missingRefParameter', {
node,
suggest: [
Object.assign(
getMessageData('addRefParameter', messages.addRefParameter),
{
fix(fixer) {
const param = node.params[0];
// If using shorthand arrow function syntax, add parentheses around the new parameter pair
const shouldAddParentheses = node.type === 'ArrowFunctionExpression' && !isParenthesized(context, param);
return [].concat(
shouldAddParentheses ? fixer.insertTextBefore(param, '(') : [],
fixer.insertTextAfter(param, `, ref${shouldAddParentheses ? ')' : ''}`)
);
},
}
),
Object.assign(
getMessageData('removeForwardRef', messages.removeForwardRef),
{
fix(fixer) {
return fixer.replaceText(node.parent, sourceCode.getText(node));
},
}
),
],
});
}
},
};
},
};
@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=function-component-definition.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"function-component-definition.d.ts","sourceRoot":"","sources":["function-component-definition.js"],"names":[],"mappings":"wBAqHW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
@@ -0,0 +1,285 @@
/**
* @fileoverview Standardize the way function component get defined
* @author Stefan Wullems
*/
'use strict';
const arrayIncludes = require('array-includes');
const Components = require('../util/Components');
const docsUrl = require('../util/docsUrl');
const reportC = require('../util/report');
const getText = require('../util/eslint').getText;
const propsUtil = require('../util/props');
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
function buildFunction(template, parts) {
return Object.keys(parts).reduce(
(acc, key) => acc.replace(`{${key}}`, () => parts[key] || ''),
template
);
}
const NAMED_FUNCTION_TEMPLATES = {
'function-declaration': 'function {name}{typeParams}({params}){returnType} {body}',
'arrow-function': '{varType} {name}{typeAnnotation} = {typeParams}({params}){returnType} => {body}',
'function-expression': '{varType} {name}{typeAnnotation} = function{typeParams}({params}){returnType} {body}',
};
const UNNAMED_FUNCTION_TEMPLATES = {
'function-expression': 'function{typeParams}({params}){returnType} {body}',
'arrow-function': '{typeParams}({params}){returnType} => {body}',
};
function hasOneUnconstrainedTypeParam(node) {
const nodeTypeArguments = propsUtil.getTypeArguments(node);
return nodeTypeArguments
&& nodeTypeArguments.params
&& nodeTypeArguments.params.length === 1
&& !nodeTypeArguments.params[0].constraint;
}
function hasName(node) {
return (
node.type === 'FunctionDeclaration'
|| node.parent.type === 'VariableDeclarator'
);
}
function getNodeText(prop, source) {
if (!prop) return null;
return source.slice(prop.range[0], prop.range[1]);
}
function getName(node) {
if (node.type === 'FunctionDeclaration') {
return node.id.name;
}
if (
node.type === 'ArrowFunctionExpression'
|| node.type === 'FunctionExpression'
) {
return hasName(node) && node.parent.id.name;
}
}
function getParams(node, source) {
if (node.params.length === 0) return null;
return source.slice(
node.params[0].range[0],
node.params[node.params.length - 1].range[1]
);
}
function getBody(node, source) {
const range = node.body.range;
if (node.body.type !== 'BlockStatement') {
return ['{', ` return ${source.slice(range[0], range[1])}`, '}'].join('\n');
}
return source.slice(range[0], range[1]);
}
function getTypeAnnotation(node, source) {
if (!hasName(node) || node.type === 'FunctionDeclaration') return;
if (
node.type === 'ArrowFunctionExpression'
|| node.type === 'FunctionExpression'
) {
return getNodeText(node.parent.id.typeAnnotation, source);
}
}
function isUnfixableBecauseOfExport(node) {
return (
node.type === 'FunctionDeclaration'
&& node.parent
&& node.parent.type === 'ExportDefaultDeclaration'
);
}
function isFunctionExpressionWithName(node) {
return node.type === 'FunctionExpression' && node.id && node.id.name;
}
const messages = {
'function-declaration': 'Function component is not a function declaration',
'function-expression': 'Function component is not a function expression',
'arrow-function': 'Function component is not an arrow function',
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
description: 'Enforce a specific function type for function components',
category: 'Stylistic Issues',
recommended: false,
url: docsUrl('function-component-definition'),
},
fixable: 'code',
messages,
schema: [
{
type: 'object',
properties: {
namedComponents: {
anyOf: [
{
enum: [
'function-declaration',
'arrow-function',
'function-expression',
],
},
{
type: 'array',
items: {
type: 'string',
enum: [
'function-declaration',
'arrow-function',
'function-expression',
],
},
},
],
},
unnamedComponents: {
anyOf: [
{ enum: ['arrow-function', 'function-expression'] },
{
type: 'array',
items: {
type: 'string',
enum: ['arrow-function', 'function-expression'],
},
},
],
},
},
},
],
},
create: Components.detect((context, components) => {
const configuration = context.options[0] || {};
let fileVarType = 'var';
const namedConfig = [].concat(
configuration.namedComponents || 'function-declaration'
);
const unnamedConfig = [].concat(
configuration.unnamedComponents || 'function-expression'
);
function getFixer(node, options) {
const source = getText(context);
const typeAnnotation = getTypeAnnotation(node, source);
if (options.type === 'function-declaration' && typeAnnotation) {
return;
}
if (options.type === 'arrow-function' && hasOneUnconstrainedTypeParam(node)) {
return;
}
if (isUnfixableBecauseOfExport(node)) return;
if (isFunctionExpressionWithName(node)) return;
let varType = fileVarType;
if (
(node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression')
&& node.parent.type === 'VariableDeclarator'
) {
varType = node.parent.parent.kind;
}
const nodeTypeArguments = propsUtil.getTypeArguments(node);
return (fixer) => fixer.replaceTextRange(
options.range,
buildFunction(options.template, {
typeAnnotation,
typeParams: getNodeText(nodeTypeArguments, source),
params: getParams(node, source),
returnType: getNodeText(node.returnType, source),
body: getBody(node, source),
name: getName(node),
varType,
})
);
}
function report(node, options) {
reportC(context, messages[options.messageId], options.messageId, {
node,
fix: getFixer(node, options.fixerOptions),
});
}
function validate(node, functionType) {
if (!components.get(node)) return;
if (node.parent && node.parent.type === 'Property') return;
if (hasName(node) && !arrayIncludes(namedConfig, functionType)) {
report(node, {
messageId: namedConfig[0],
fixerOptions: {
type: namedConfig[0],
template: NAMED_FUNCTION_TEMPLATES[namedConfig[0]],
range:
node.type === 'FunctionDeclaration'
? node.range
: node.parent.parent.range,
},
});
}
if (!hasName(node) && !arrayIncludes(unnamedConfig, functionType)) {
report(node, {
messageId: unnamedConfig[0],
fixerOptions: {
type: unnamedConfig[0],
template: UNNAMED_FUNCTION_TEMPLATES[unnamedConfig[0]],
range: node.range,
},
});
}
}
// --------------------------------------------------------------------------
// Public
// --------------------------------------------------------------------------
const validatePairs = [];
let hasES6OrJsx = false;
return {
FunctionDeclaration(node) {
validatePairs.push([node, 'function-declaration']);
},
ArrowFunctionExpression(node) {
validatePairs.push([node, 'arrow-function']);
},
FunctionExpression(node) {
validatePairs.push([node, 'function-expression']);
},
VariableDeclaration(node) {
hasES6OrJsx = hasES6OrJsx || node.kind === 'const' || node.kind === 'let';
},
'Program:exit'() {
if (hasES6OrJsx) fileVarType = 'const';
validatePairs.forEach((pair) => validate(pair[0], pair[1]));
},
'ImportDeclaration, ExportNamedDeclaration, ExportDefaultDeclaration, ExportAllDeclaration, ExportSpecifier, ExportDefaultSpecifier, JSXElement, TSExportAssignment, TSImportEqualsDeclaration'() {
hasES6OrJsx = true;
},
};
}),
};
+3
View File
@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=hook-use-state.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"hook-use-state.d.ts","sourceRoot":"","sources":["hook-use-state.js"],"names":[],"mappings":"wBA4BW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
+205
View File
@@ -0,0 +1,205 @@
/**
* @fileoverview Ensure symmetric naming of useState hook value and setter variables
* @author Duncan Beevers
*/
'use strict';
const Components = require('../util/Components');
const docsUrl = require('../util/docsUrl');
const report = require('../util/report');
const getMessageData = require('../util/message');
const getText = require('../util/eslint').getText;
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
function isNodeDestructuring(node) {
return node && (node.type === 'ArrayPattern' || node.type === 'ObjectPattern');
}
const messages = {
useStateErrorMessage: 'useState call is not destructured into value + setter pair',
useStateErrorMessageOrAddOption: 'useState call is not destructured into value + setter pair (you can allow destructuring by enabling "allowDestructuredState" option)',
suggestPair: 'Destructure useState call into value + setter pair',
suggestMemo: 'Replace useState call with useMemo',
};
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
description: 'Ensure destructuring and symmetric naming of useState hook value and setter variables',
category: 'Best Practices',
recommended: false,
url: docsUrl('hook-use-state'),
},
messages,
schema: [{
type: 'object',
properties: {
allowDestructuredState: {
default: false,
type: 'boolean',
},
},
additionalProperties: false,
}],
type: 'suggestion',
hasSuggestions: true,
},
create: Components.detect((context, components, util) => {
const configuration = context.options[0] || {};
const allowDestructuredState = configuration.allowDestructuredState || false;
return {
CallExpression(node) {
const isImmediateReturn = node.parent
&& node.parent.type === 'ReturnStatement';
if (isImmediateReturn || !util.isReactHookCall(node, ['useState'])) {
return;
}
const isDestructuringDeclarator = node.parent
&& node.parent.type === 'VariableDeclarator'
&& node.parent.id.type === 'ArrayPattern';
if (!isDestructuringDeclarator) {
report(
context,
messages.useStateErrorMessage,
'useStateErrorMessage',
{
node,
suggest: false,
}
);
return;
}
const variableNodes = node.parent.id.elements;
const valueVariable = variableNodes[0];
const setterVariable = variableNodes[1];
const isOnlyValueDestructuring = isNodeDestructuring(valueVariable) && !isNodeDestructuring(setterVariable);
if (allowDestructuredState && isOnlyValueDestructuring) {
return;
}
const valueVariableName = valueVariable
? valueVariable.name
: undefined;
const setterVariableName = setterVariable
? setterVariable.name
: undefined;
const caseCandidateMatch = valueVariableName ? valueVariableName.match(/(^[a-z]+)(.*)/) : undefined;
const upperCaseCandidatePrefix = caseCandidateMatch ? caseCandidateMatch[1] : undefined;
const caseCandidateSuffix = caseCandidateMatch ? caseCandidateMatch[2] : undefined;
const expectedSetterVariableNames = upperCaseCandidatePrefix ? [
`set${upperCaseCandidatePrefix.charAt(0).toUpperCase()}${upperCaseCandidatePrefix.slice(1)}${caseCandidateSuffix}`,
`set${upperCaseCandidatePrefix.toUpperCase()}${caseCandidateSuffix}`,
] : [];
const isSymmetricGetterSetterPair = valueVariable
&& setterVariable
&& expectedSetterVariableNames.indexOf(setterVariableName) !== -1
&& variableNodes.length === 2;
if (!isSymmetricGetterSetterPair) {
const suggestions = [
Object.assign(
getMessageData('suggestPair', messages.suggestPair),
{
fix(fixer) {
if (expectedSetterVariableNames.length > 0) {
return fixer.replaceTextRange(
node.parent.id.range,
`[${valueVariableName}, ${expectedSetterVariableNames[0]}]`
);
}
},
}
),
];
const defaultReactImports = components.getDefaultReactImports();
const defaultReactImportSpecifier = defaultReactImports
? defaultReactImports[0]
: undefined;
const defaultReactImportName = defaultReactImportSpecifier
? defaultReactImportSpecifier.local.name
: undefined;
const namedReactImports = components.getNamedReactImports();
const useStateReactImportSpecifier = namedReactImports
? namedReactImports.find((specifier) => specifier.imported.name === 'useState')
: undefined;
const isSingleGetter = valueVariable && variableNodes.length === 1;
const isUseStateCalledWithSingleArgument = node.arguments.length === 1;
if (isSingleGetter && isUseStateCalledWithSingleArgument) {
const useMemoReactImportSpecifier = namedReactImports
&& namedReactImports.find((specifier) => specifier.imported.name === 'useMemo');
let useMemoCode;
if (useMemoReactImportSpecifier) {
useMemoCode = useMemoReactImportSpecifier.local.name;
} else if (defaultReactImportName) {
useMemoCode = `${defaultReactImportName}.useMemo`;
} else {
useMemoCode = 'useMemo';
}
suggestions.unshift(Object.assign(
getMessageData('suggestMemo', messages.suggestMemo),
{
fix: (fixer) => [
// Add useMemo import, if necessary
useStateReactImportSpecifier
&& (!useMemoReactImportSpecifier || defaultReactImportName)
&& fixer.insertTextAfter(useStateReactImportSpecifier, ', useMemo'),
// Convert single-value destructure to simple assignment
fixer.replaceTextRange(node.parent.id.range, valueVariableName),
// Convert useState call to useMemo + arrow function + dependency array
fixer.replaceTextRange(
node.range,
`${useMemoCode}(() => ${getText(context, node.arguments[0])}, [])`
),
].filter(Boolean),
}
));
}
if (isOnlyValueDestructuring) {
report(
context,
messages.useStateErrorMessageOrAddOption,
'useStateErrorMessageOrAddOption',
{
node: node.parent.id,
suggest: false,
}
);
return;
}
report(
context,
messages.useStateErrorMessage,
'useStateErrorMessage',
{
node: node.parent.id,
suggest: suggestions,
}
);
}
},
};
}),
};
@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=iframe-missing-sandbox.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"iframe-missing-sandbox.d.ts","sourceRoot":"","sources":["iframe-missing-sandbox.js"],"names":[],"mappings":"wBA+GW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}
+143
View File
@@ -0,0 +1,143 @@
/**
* @fileoverview TBD
*/
'use strict';
const docsUrl = require('../util/docsUrl');
const isCreateElement = require('../util/isCreateElement');
const report = require('../util/report');
const messages = {
attributeMissing: 'An iframe element is missing a sandbox attribute',
invalidValue: 'An iframe element defines a sandbox attribute with invalid value "{{ value }}"',
invalidCombination: 'An iframe element defines a sandbox attribute with both allow-scripts and allow-same-origin which is invalid',
};
const ALLOWED_VALUES = [
// From https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-sandbox
'',
'allow-downloads-without-user-activation',
'allow-downloads',
'allow-forms',
'allow-modals',
'allow-orientation-lock',
'allow-pointer-lock',
'allow-popups',
'allow-popups-to-escape-sandbox',
'allow-presentation',
'allow-same-origin',
'allow-scripts',
'allow-storage-access-by-user-activation',
'allow-top-navigation',
'allow-top-navigation-by-user-activation',
];
function validateSandboxAttribute(context, node, attribute) {
if (typeof attribute !== 'string') {
// Only string literals are supported for now
return;
}
const values = attribute.split(' ');
let allowScripts = false;
let allowSameOrigin = false;
values.forEach((attributeValue) => {
const trimmedAttributeValue = attributeValue.trim();
if (ALLOWED_VALUES.indexOf(trimmedAttributeValue) === -1) {
report(context, messages.invalidValue, 'invalidValue', {
node,
data: {
value: trimmedAttributeValue,
},
});
}
if (trimmedAttributeValue === 'allow-scripts') {
allowScripts = true;
}
if (trimmedAttributeValue === 'allow-same-origin') {
allowSameOrigin = true;
}
});
if (allowScripts && allowSameOrigin) {
report(context, messages.invalidCombination, 'invalidCombination', {
node,
});
}
}
function checkAttributes(context, node) {
let sandboxAttributeFound = false;
node.attributes.forEach((attribute) => {
if (attribute.type === 'JSXAttribute'
&& attribute.name
&& attribute.name.type === 'JSXIdentifier'
&& attribute.name.name === 'sandbox'
) {
sandboxAttributeFound = true;
if (
attribute.value
&& attribute.value.type === 'Literal'
&& attribute.value.value
) {
validateSandboxAttribute(context, node, attribute.value.value);
}
}
});
if (!sandboxAttributeFound) {
report(context, messages.attributeMissing, 'attributeMissing', {
node,
});
}
}
function checkProps(context, node) {
let sandboxAttributeFound = false;
if (node.arguments.length > 1) {
const props = node.arguments[1];
const sandboxProp = props.properties && props.properties.find((x) => x.type === 'Property' && x.key.name === 'sandbox');
if (sandboxProp) {
sandboxAttributeFound = true;
if (sandboxProp.value && sandboxProp.value.type === 'Literal' && sandboxProp.value.value) {
validateSandboxAttribute(context, node, sandboxProp.value.value);
}
}
}
if (!sandboxAttributeFound) {
report(context, messages.attributeMissing, 'attributeMissing', {
node,
});
}
}
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
description: 'Enforce sandbox attribute on iframe elements',
category: 'Best Practices',
recommended: false,
url: docsUrl('iframe-missing-sandbox'),
},
schema: [],
messages,
},
create(context) {
return {
'JSXOpeningElement[name.name="iframe"]'(node) {
checkAttributes(context, node);
},
CallExpression(node) {
if (isCreateElement(context, node) && node.arguments && node.arguments.length > 0) {
const tag = node.arguments[0];
if (tag.type === 'Literal' && tag.value === 'iframe') {
checkProps(context, node);
}
}
},
};
},
};
+108
View File
@@ -0,0 +1,108 @@
export = rules;
/** @satisfies {Record<string, import('eslint').Rule.RuleModule>} */
declare const rules: {
'boolean-prop-naming': import("eslint").Rule.RuleModule;
'button-has-type': import("eslint").Rule.RuleModule;
'checked-requires-onchange-or-readonly': import("eslint").Rule.RuleModule;
'default-props-match-prop-types': import("eslint").Rule.RuleModule;
'destructuring-assignment': import("eslint").Rule.RuleModule;
'display-name': import("eslint").Rule.RuleModule;
'forbid-component-props': import("eslint").Rule.RuleModule;
'forbid-dom-props': import("eslint").Rule.RuleModule;
'forbid-elements': import("eslint").Rule.RuleModule;
'forbid-foreign-prop-types': import("eslint").Rule.RuleModule;
'forbid-prop-types': import("eslint").Rule.RuleModule;
'forward-ref-uses-ref': import("eslint").Rule.RuleModule;
'function-component-definition': import("eslint").Rule.RuleModule;
'hook-use-state': import("eslint").Rule.RuleModule;
'iframe-missing-sandbox': import("eslint").Rule.RuleModule;
'jsx-boolean-value': import("eslint").Rule.RuleModule;
'jsx-child-element-spacing': import("eslint").Rule.RuleModule;
'jsx-closing-bracket-location': import("eslint").Rule.RuleModule;
'jsx-closing-tag-location': import("eslint").Rule.RuleModule;
'jsx-curly-spacing': import("eslint").Rule.RuleModule;
'jsx-curly-newline': import("eslint").Rule.RuleModule;
'jsx-equals-spacing': import("eslint").Rule.RuleModule;
'jsx-filename-extension': import("eslint").Rule.RuleModule;
'jsx-first-prop-new-line': import("eslint").Rule.RuleModule;
'jsx-handler-names': import("eslint").Rule.RuleModule;
'jsx-indent': import("eslint").Rule.RuleModule;
'jsx-indent-props': import("eslint").Rule.RuleModule;
'jsx-key': import("eslint").Rule.RuleModule;
'jsx-max-depth': import("eslint").Rule.RuleModule;
'jsx-max-props-per-line': import("eslint").Rule.RuleModule;
'jsx-newline': import("eslint").Rule.RuleModule;
'jsx-no-bind': import("eslint").Rule.RuleModule;
'jsx-no-comment-textnodes': import("eslint").Rule.RuleModule;
'jsx-no-constructed-context-values': import("eslint").Rule.RuleModule;
'jsx-no-duplicate-props': import("eslint").Rule.RuleModule;
'jsx-no-leaked-render': import("eslint").Rule.RuleModule;
'jsx-no-literals': import("eslint").Rule.RuleModule;
'jsx-no-script-url': import("eslint").Rule.RuleModule;
'jsx-no-target-blank': import("eslint").Rule.RuleModule;
'jsx-no-useless-fragment': import("eslint").Rule.RuleModule;
'jsx-one-expression-per-line': import("eslint").Rule.RuleModule;
'jsx-no-undef': import("eslint").Rule.RuleModule;
'jsx-curly-brace-presence': import("eslint").Rule.RuleModule;
'jsx-pascal-case': import("eslint").Rule.RuleModule;
'jsx-fragments': import("eslint").Rule.RuleModule;
'jsx-props-no-multi-spaces': import("eslint").Rule.RuleModule;
'jsx-props-no-spreading': import("eslint").Rule.RuleModule;
'jsx-props-no-spread-multi': import("eslint").Rule.RuleModule;
'jsx-sort-default-props': import("eslint").Rule.RuleModule;
'jsx-sort-props': import("eslint").Rule.RuleModule;
'jsx-space-before-closing': import("eslint").Rule.RuleModule;
'jsx-tag-spacing': import("eslint").Rule.RuleModule;
'jsx-uses-react': import("eslint").Rule.RuleModule;
'jsx-uses-vars': import("eslint").Rule.RuleModule;
'jsx-wrap-multilines': import("eslint").Rule.RuleModule;
'no-invalid-html-attribute': import("eslint").Rule.RuleModule;
'no-access-state-in-setstate': import("eslint").Rule.RuleModule;
'no-adjacent-inline-elements': import("eslint").Rule.RuleModule;
'no-array-index-key': import("eslint").Rule.RuleModule;
'no-arrow-function-lifecycle': import("eslint").Rule.RuleModule;
'no-children-prop': import("eslint").Rule.RuleModule;
'no-danger': import("eslint").Rule.RuleModule;
'no-danger-with-children': import("eslint").Rule.RuleModule;
'no-deprecated': import("eslint").Rule.RuleModule;
'no-did-mount-set-state': import("eslint").Rule.RuleModule;
'no-did-update-set-state': import("eslint").Rule.RuleModule;
'no-direct-mutation-state': import("eslint").Rule.RuleModule;
'no-find-dom-node': import("eslint").Rule.RuleModule;
'no-is-mounted': import("eslint").Rule.RuleModule;
'no-multi-comp': import("eslint").Rule.RuleModule;
'no-namespace': import("eslint").Rule.RuleModule;
'no-set-state': import("eslint").Rule.RuleModule;
'no-string-refs': import("eslint").Rule.RuleModule;
'no-redundant-should-component-update': import("eslint").Rule.RuleModule;
'no-render-return-value': import("eslint").Rule.RuleModule;
'no-this-in-sfc': import("eslint").Rule.RuleModule;
'no-typos': import("eslint").Rule.RuleModule;
'no-unescaped-entities': import("eslint").Rule.RuleModule;
'no-unknown-property': import("eslint").Rule.RuleModule;
'no-unsafe': import("eslint").Rule.RuleModule;
'no-unstable-nested-components': import("eslint").Rule.RuleModule;
'no-unused-class-component-methods': import("eslint").Rule.RuleModule;
'no-unused-prop-types': import("eslint").Rule.RuleModule;
'no-unused-state': import("eslint").Rule.RuleModule;
'no-object-type-as-default-prop': import("eslint").Rule.RuleModule;
'no-will-update-set-state': import("eslint").Rule.RuleModule;
'prefer-es6-class': import("eslint").Rule.RuleModule;
'prefer-exact-props': import("eslint").Rule.RuleModule;
'prefer-read-only-props': import("eslint").Rule.RuleModule;
'prefer-stateless-function': import("eslint").Rule.RuleModule;
'prop-types': import("eslint").Rule.RuleModule;
'react-in-jsx-scope': import("eslint").Rule.RuleModule;
'require-default-props': import("eslint").Rule.RuleModule;
'require-optimization': import("eslint").Rule.RuleModule;
'require-render-return': import("eslint").Rule.RuleModule;
'self-closing-comp': import("eslint").Rule.RuleModule;
'sort-comp': import("eslint").Rule.RuleModule;
'sort-default-props': import("eslint").Rule.RuleModule;
'sort-prop-types': import("eslint").Rule.RuleModule;
'state-in-constructor': import("eslint").Rule.RuleModule;
'static-property-placement': import("eslint").Rule.RuleModule;
'style-prop-object': import("eslint").Rule.RuleModule;
'void-dom-elements-no-children': import("eslint").Rule.RuleModule;
};
//# sourceMappingURL=index.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.js"],"names":[],"mappings":";AAIA,oEAAoE;AACpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAwGE"}
+112
View File
@@ -0,0 +1,112 @@
'use strict';
/* eslint global-require: 0 */
/** @satisfies {Record<string, import('eslint').Rule.RuleModule>} */
const rules = {
'boolean-prop-naming': require('./boolean-prop-naming'),
'button-has-type': require('./button-has-type'),
'checked-requires-onchange-or-readonly': require('./checked-requires-onchange-or-readonly'),
'default-props-match-prop-types': require('./default-props-match-prop-types'),
'destructuring-assignment': require('./destructuring-assignment'),
'display-name': require('./display-name'),
'forbid-component-props': require('./forbid-component-props'),
'forbid-dom-props': require('./forbid-dom-props'),
'forbid-elements': require('./forbid-elements'),
'forbid-foreign-prop-types': require('./forbid-foreign-prop-types'),
'forbid-prop-types': require('./forbid-prop-types'),
'forward-ref-uses-ref': require('./forward-ref-uses-ref'),
'function-component-definition': require('./function-component-definition'),
'hook-use-state': require('./hook-use-state'),
'iframe-missing-sandbox': require('./iframe-missing-sandbox'),
'jsx-boolean-value': require('./jsx-boolean-value'),
'jsx-child-element-spacing': require('./jsx-child-element-spacing'),
'jsx-closing-bracket-location': require('./jsx-closing-bracket-location'),
'jsx-closing-tag-location': require('./jsx-closing-tag-location'),
'jsx-curly-spacing': require('./jsx-curly-spacing'),
'jsx-curly-newline': require('./jsx-curly-newline'),
'jsx-equals-spacing': require('./jsx-equals-spacing'),
'jsx-filename-extension': require('./jsx-filename-extension'),
'jsx-first-prop-new-line': require('./jsx-first-prop-new-line'),
'jsx-handler-names': require('./jsx-handler-names'),
'jsx-indent': require('./jsx-indent'),
'jsx-indent-props': require('./jsx-indent-props'),
'jsx-key': require('./jsx-key'),
'jsx-max-depth': require('./jsx-max-depth'),
'jsx-max-props-per-line': require('./jsx-max-props-per-line'),
'jsx-newline': require('./jsx-newline'),
'jsx-no-bind': require('./jsx-no-bind'),
'jsx-no-comment-textnodes': require('./jsx-no-comment-textnodes'),
'jsx-no-constructed-context-values': require('./jsx-no-constructed-context-values'),
'jsx-no-duplicate-props': require('./jsx-no-duplicate-props'),
'jsx-no-leaked-render': require('./jsx-no-leaked-render'),
'jsx-no-literals': require('./jsx-no-literals'),
'jsx-no-script-url': require('./jsx-no-script-url'),
'jsx-no-target-blank': require('./jsx-no-target-blank'),
'jsx-no-useless-fragment': require('./jsx-no-useless-fragment'),
'jsx-one-expression-per-line': require('./jsx-one-expression-per-line'),
'jsx-no-undef': require('./jsx-no-undef'),
'jsx-curly-brace-presence': require('./jsx-curly-brace-presence'),
'jsx-pascal-case': require('./jsx-pascal-case'),
'jsx-fragments': require('./jsx-fragments'),
'jsx-props-no-multi-spaces': require('./jsx-props-no-multi-spaces'),
'jsx-props-no-spreading': require('./jsx-props-no-spreading'),
'jsx-props-no-spread-multi': require('./jsx-props-no-spread-multi'),
'jsx-sort-default-props': require('./jsx-sort-default-props'),
'jsx-sort-props': require('./jsx-sort-props'),
'jsx-space-before-closing': require('./jsx-space-before-closing'),
'jsx-tag-spacing': require('./jsx-tag-spacing'),
'jsx-uses-react': require('./jsx-uses-react'),
'jsx-uses-vars': require('./jsx-uses-vars'),
'jsx-wrap-multilines': require('./jsx-wrap-multilines'),
'no-invalid-html-attribute': require('./no-invalid-html-attribute'),
'no-access-state-in-setstate': require('./no-access-state-in-setstate'),
'no-adjacent-inline-elements': require('./no-adjacent-inline-elements'),
'no-array-index-key': require('./no-array-index-key'),
'no-arrow-function-lifecycle': require('./no-arrow-function-lifecycle'),
'no-children-prop': require('./no-children-prop'),
'no-danger': require('./no-danger'),
'no-danger-with-children': require('./no-danger-with-children'),
'no-deprecated': require('./no-deprecated'),
'no-did-mount-set-state': require('./no-did-mount-set-state'),
'no-did-update-set-state': require('./no-did-update-set-state'),
'no-direct-mutation-state': require('./no-direct-mutation-state'),
'no-find-dom-node': require('./no-find-dom-node'),
'no-is-mounted': require('./no-is-mounted'),
'no-multi-comp': require('./no-multi-comp'),
'no-namespace': require('./no-namespace'),
'no-set-state': require('./no-set-state'),
'no-string-refs': require('./no-string-refs'),
'no-redundant-should-component-update': require('./no-redundant-should-component-update'),
'no-render-return-value': require('./no-render-return-value'),
'no-this-in-sfc': require('./no-this-in-sfc'),
'no-typos': require('./no-typos'),
'no-unescaped-entities': require('./no-unescaped-entities'),
'no-unknown-property': require('./no-unknown-property'),
'no-unsafe': require('./no-unsafe'),
'no-unstable-nested-components': require('./no-unstable-nested-components'),
'no-unused-class-component-methods': require('./no-unused-class-component-methods'),
'no-unused-prop-types': require('./no-unused-prop-types'),
'no-unused-state': require('./no-unused-state'),
'no-object-type-as-default-prop': require('./no-object-type-as-default-prop'),
'no-will-update-set-state': require('./no-will-update-set-state'),
'prefer-es6-class': require('./prefer-es6-class'),
'prefer-exact-props': require('./prefer-exact-props'),
'prefer-read-only-props': require('./prefer-read-only-props'),
'prefer-stateless-function': require('./prefer-stateless-function'),
'prop-types': require('./prop-types'),
'react-in-jsx-scope': require('./react-in-jsx-scope'),
'require-default-props': require('./require-default-props'),
'require-optimization': require('./require-optimization'),
'require-render-return': require('./require-render-return'),
'self-closing-comp': require('./self-closing-comp'),
'sort-comp': require('./sort-comp'),
'sort-default-props': require('./sort-default-props'),
'sort-prop-types': require('./sort-prop-types'),
'state-in-constructor': require('./state-in-constructor'),
'static-property-placement': require('./static-property-placement'),
'style-prop-object': require('./style-prop-object'),
'void-dom-elements-no-children': require('./void-dom-elements-no-children'),
};
module.exports = rules;
+3
View File
@@ -0,0 +1,3 @@
declare const _exports: import('eslint').Rule.RuleModule;
export = _exports;
//# sourceMappingURL=jsx-boolean-value.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"jsx-boolean-value.d.ts","sourceRoot":"","sources":["jsx-boolean-value.js"],"names":[],"mappings":"wBAwDW,OAAO,QAAQ,EAAE,IAAI,CAAC,UAAU"}

Some files were not shown because too many files have changed in this diff Show More