Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add JSDoc type annotations #3731

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
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
Expand Up @@ -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
Expand Down
10 changes: 8 additions & 2 deletions lib/rules/button-has-type.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -149,14 +150,19 @@ module.exports = {
}

const props = node.arguments[1].properties;
ljharb marked this conversation as resolved.
Show resolved Hide resolved
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);
},
};
},
Expand Down
3 changes: 2 additions & 1 deletion lib/rules/checked-requires-onchange-or-readonly.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const defaultOptions = {
};

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

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

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
Expand Down Expand Up @@ -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);
}
},
Expand Down
9 changes: 8 additions & 1 deletion lib/rules/forbid-foreign-prop-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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)
Expand All @@ -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', {
Expand Down
19 changes: 13 additions & 6 deletions lib/rules/forbid-prop-types.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const messages = {
forbiddenPropType: 'Prop type "{{target}}" is forbidden',
};

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
) {
Expand All @@ -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);
}
},

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

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

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

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

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

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

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
Expand Down
6 changes: 3 additions & 3 deletions lib/rules/jsx-fragments.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -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);
}
Expand Down
1 change: 1 addition & 0 deletions lib/rules/jsx-indent-props.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
1 change: 1 addition & 0 deletions lib/rules/jsx-indent.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
8 changes: 4 additions & 4 deletions lib/rules/jsx-no-bind.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const messages = {
func: 'JSX props should not use functions',
};

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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]);
}
},

Expand Down
3 changes: 0 additions & 3 deletions lib/rules/jsx-no-leaked-render.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
3 changes: 2 additions & 1 deletion lib/rules/jsx-sort-default-props.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -178,7 +179,7 @@ module.exports = {
return;
}

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

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

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
deprecated: true,
Expand Down Expand Up @@ -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;
Expand Down
Loading