Skip to content

Add JSDoc type annotations #3731

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 11, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -18,10 +18,13 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
* [Tests] add @typescript-eslint/parser v6 ([#3629][] @HenryBrown0)
* [Tests] add @typescript-eslint/parser v7 and v8 ([#3629][] @hampustagerud)
* [Docs] [`no-danger`]: update broken link ([#3817][] @lucasrmendonca)
* [types] add jsdoc type annotations ([#3731][] @y-hsgw)
* [Tests] `button-has-type`: add test case with spread ([#3731][] @y-hsgw)

[#3632]: https://github.com/jsx-eslint/eslint-plugin-react/issues/3632

[#3812]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3812
[#3731]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3731
[#3629]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3629
[#3817]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3817
[#3807]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3807
10 changes: 8 additions & 2 deletions lib/rules/button-has-type.js
Original file line number Diff line number Diff line change
@@ -28,6 +28,7 @@ const messages = {
forbiddenValue: '"{{value}}" is an invalid value for button type attribute',
};

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
@@ -149,14 +150,19 @@ module.exports = {
}

const props = node.arguments[1].properties;
const typeProp = props.find((prop) => prop.key && prop.key.name === 'type');
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, typeProp.value);
checkExpression(node, 'value' in typeProp ? typeProp.value : undefined);
},
};
},
3 changes: 2 additions & 1 deletion lib/rules/checked-requires-onchange-or-readonly.js
Original file line number Diff line number Diff line change
@@ -24,7 +24,7 @@ const defaultOptions = {
};

/**
* @param {string[]} properties
* @param {object[]} properties
* @param {string} keyName
* @returns {Set<string>}
*/
@@ -41,6 +41,7 @@ function extractTargetProps(properties, keyName) {
);
}

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
9 changes: 4 additions & 5 deletions lib/rules/forbid-elements.js
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@ const messages = {
forbiddenElement_message: '<{{element}}> is forbidden, {{message}}',
};

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
@@ -105,13 +106,11 @@ module.exports = {
return;
}

const argType = argument.type;

if (argType === 'Identifier' && /^[A-Z_]/.test(argument.name)) {
if (argument.type === 'Identifier' && /^[A-Z_]/.test(argument.name)) {
reportIfForbidden(argument.name, argument);
} else if (argType === 'Literal' && /^[a-z][^.]*$/.test(argument.value)) {
} else if (argument.type === 'Literal' && /^[a-z][^.]*$/.test(String(argument.value))) {
reportIfForbidden(argument.value, argument);
} else if (argType === 'MemberExpression') {
} else if (argument.type === 'MemberExpression') {
reportIfForbidden(getText(context, argument), argument);
}
},
9 changes: 8 additions & 1 deletion lib/rules/forbid-foreign-prop-types.js
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@ 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: {
@@ -108,7 +109,9 @@ module.exports = {
&& !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)
@@ -121,7 +124,11 @@ module.exports = {
},

ObjectPattern(node) {
const propTypesNode = node.properties.find((property) => property.type === 'Property' && property.key.name === 'propTypes');
const propTypesNode = node.properties.find((property) => (
property.type === 'Property'
&& 'name' in property.key
&& property.key.name === 'propTypes'
));

if (propTypesNode) {
report(context, messages.forbiddenPropType, 'forbiddenPropType', {
19 changes: 13 additions & 6 deletions lib/rules/forbid-prop-types.js
Original file line number Diff line number Diff line change
@@ -26,6 +26,7 @@ const messages = {
forbiddenPropType: 'Prop type "{{target}}" is forbidden',
};

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
@@ -192,7 +193,9 @@ module.exports = {
}
if (node.specifiers.length >= 1) {
const propTypesSpecifier = node.specifiers.find((specifier) => (
specifier.imported && specifier.imported.name === 'PropTypes'
'imported' in specifier
&& specifier.imported
&& specifier.imported.name === 'PropTypes'
));
if (propTypesSpecifier) {
propTypesPackageName = propTypesSpecifier.local.name;
@@ -228,12 +231,13 @@ module.exports = {
return;
}

checkNode(node.parent.right);
checkNode('right' in node.parent && node.parent.right);
},

CallExpression(node) {
if (
node.callee.object
node.callee.type === 'MemberExpression'
&& node.callee.object
&& !isPropTypesPackage(node.callee.object)
&& !propsUtil.isPropTypesDeclaration(node.callee)
) {
@@ -242,9 +246,12 @@ module.exports = {

if (
node.arguments.length > 0
&& (node.callee.name === 'shape' || astUtil.getPropertyName(node.callee) === 'shape')
&& (
('name' in node.callee && node.callee.name === 'shape')
|| astUtil.getPropertyName(node.callee) === 'shape'
)
) {
checkProperties(node.arguments[0].properties);
checkProperties('properties' in node.arguments[0] && node.arguments[0].properties);
}
},

@@ -267,7 +274,7 @@ module.exports = {

ObjectExpression(node) {
node.properties.forEach((property) => {
if (!property.key) {
if (!('key' in property) || !property.key) {
return;
}

1 change: 1 addition & 0 deletions lib/rules/hook-use-state.js
Original file line number Diff line number Diff line change
@@ -26,6 +26,7 @@ const messages = {
suggestMemo: 'Replace useState call with useMemo',
};

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
1 change: 1 addition & 0 deletions lib/rules/jsx-closing-bracket-location.js
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@ const messages = {
bracketLocation: 'The closing bracket must be {{location}}{{details}}',
};

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
1 change: 1 addition & 0 deletions lib/rules/jsx-curly-spacing.js
Original file line number Diff line number Diff line change
@@ -35,6 +35,7 @@ const messages = {
spaceNeededBefore: 'A space is required before \'{{token}}\'',
};

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
1 change: 1 addition & 0 deletions lib/rules/jsx-equals-spacing.js
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@ const messages = {
needSpaceAfter: 'A space is required after \'=\'',
};

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
6 changes: 3 additions & 3 deletions lib/rules/jsx-fragments.js
Original file line number Diff line number Diff line change
@@ -22,12 +22,12 @@ function replaceNode(source, node, text) {
}

const messages = {
fragmentsNotSupported: 'Fragments are only supported starting from React v16.2. '
+ 'Please disable the `react/jsx-fragments` rule in `eslint` settings or upgrade your version of React.',
fragmentsNotSupported: 'Fragments are only supported starting from React v16.2. Please disable the `react/jsx-fragments` rule in `eslint` settings or upgrade your version of React.',
preferPragma: 'Prefer {{react}}.{{fragment}} over fragment shorthand',
preferFragment: 'Prefer fragment shorthand over {{react}}.{{fragment}}',
};

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
@@ -170,7 +170,7 @@ module.exports = {
ImportDeclaration(node) {
if (node.source && node.source.value === 'react') {
node.specifiers.forEach((spec) => {
if (spec.imported && spec.imported.name === fragmentPragma) {
if ('imported' in spec && spec.imported && spec.imported.name === fragmentPragma) {
if (spec.local) {
fragmentNames.add(spec.local.name);
}
1 change: 1 addition & 0 deletions lib/rules/jsx-indent-props.js
Original file line number Diff line number Diff line change
@@ -45,6 +45,7 @@ const messages = {
wrongIndent: 'Expected indentation of {{needed}} {{type}} {{characters}} but found {{gotten}}.',
};

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
1 change: 1 addition & 0 deletions lib/rules/jsx-indent.js
Original file line number Diff line number Diff line change
@@ -50,6 +50,7 @@ const messages = {
wrongIndent: 'Expected indentation of {{needed}} {{type}} {{characters}} but found {{gotten}}.',
};

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
8 changes: 4 additions & 4 deletions lib/rules/jsx-no-bind.js
Original file line number Diff line number Diff line change
@@ -25,6 +25,7 @@ const messages = {
func: 'JSX props should not use functions',
};

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
@@ -116,7 +117,7 @@ module.exports = {

/**
* @param {string | number} violationType
* @param {any} variableName
* @param {unknown} variableName
* @param {string | number} blockStart
*/
function addVariableNameToSet(violationType, variableName, blockStart) {
@@ -175,11 +176,10 @@ module.exports = {
if (
blockAncestors.length > 0
&& variableViolationType
&& 'kind' in node.parent
&& node.parent.kind === 'const' // only support const right now
) {
addVariableNameToSet(
variableViolationType, node.id.name, blockAncestors[0].range[0]
);
addVariableNameToSet(variableViolationType, 'name' in node.id ? node.id.name : undefined, blockAncestors[0].range[0]);
}
},

3 changes: 0 additions & 3 deletions lib/rules/jsx-no-leaked-render.js
Original file line number Diff line number Diff line change
@@ -109,9 +109,6 @@ function ruleFixer(context, fixStrategy, fixer, reportedNode, leftNode, rightNod
throw new TypeError('Invalid value for "validStrategies" option');
}

/**
* @type {import('eslint').Rule.RuleModule}
*/
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
3 changes: 2 additions & 1 deletion lib/rules/jsx-sort-default-props.js
Original file line number Diff line number Diff line change
@@ -25,6 +25,7 @@ const messages = {
propsNotSorted: 'Default prop types declarations should be sorted alphabetically',
};

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
deprecated: true,
@@ -178,7 +179,7 @@ module.exports = {
return;
}

checkNode(node.parent.right);
checkNode('right' in node.parent && node.parent.right);
},

Program() {
3 changes: 2 additions & 1 deletion lib/rules/jsx-space-before-closing.js
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@ const messages = {
needSpaceBeforeClose: 'A space is required before closing bracket',
};

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
deprecated: true,
@@ -58,7 +59,7 @@ module.exports = {
const sourceCode = getSourceCode(context);

const leftToken = getTokenBeforeClosingBracket(node);
const closingSlash = sourceCode.getTokenAfter(leftToken);
const closingSlash = /** @type {import("eslint").AST.Token} */ (sourceCode.getTokenAfter(leftToken));

if (leftToken.loc.end.line !== closingSlash.loc.start.line) {
return;
Loading