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

Draft
wants to merge 22 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/rules/boolean-prop-naming.js
Expand Up @@ -22,6 +22,8 @@ const messages = {
patternMismatch: 'Prop name `{{propName}}` doesn’t match rule `{{pattern}}`',
};


/** @type { import('eslint').Rule.RuleModule } */
module.exports = {
meta: {
docs: {
Expand Down
7 changes: 5 additions & 2 deletions lib/rules/button-has-type.js
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,9 +150,11 @@ module.exports = {
}

const props = node.arguments[1].properties;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the type of node properly narrowed to a CallExpression node here? if not, let's do that as an annotation on the visitor function rather than needing to add all these runtime changes.

Copy link
Contributor Author

@y-hsgw y-hsgw Apr 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I attempted to make corrections in 040d77c, but is my understanding correct?
Sorry. I didn't fully understand it.

is the type of node properly narrowed to a CallExpression node here?

The type of node is not properly narrowed down.

It's (parameter) node: CallExpression & Rule.NodeParentExtension.

const typeProp = props.find((prop) => prop.key && prop.key.name === 'type');
const typeProp = props.find(
(prop) => prop.type === "Property" && prop.key && prop.key.type === "PrivateIdentifier" && prop.key.name === 'type'
);

if (!typeProp) {
if (typeProp.type !== "Property") {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these changes aren't simple annotation changes. can they be extracted at least to a different commit, if not to a different PR?

Copy link
Contributor Author

@y-hsgw y-hsgw Apr 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. My apologies 🙇‍♂️
Would it be acceptable to create a PR that adds annotations only to rules where type changes are unnecessary?
Or would it be better to add annotations to all rules, and if changes are needed, ignore the entire file (@ ts-nocheck)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that would be fine.

any non-type changes that are needed must be accompanied by tests - if the ostensible type error can't be reproduced, then the type system is wrong.

reportMissing(node);
return;
}
Expand Down
3 changes: 2 additions & 1 deletion lib/rules/checked-requires-onchange-or-readonly.js
Expand Up @@ -24,7 +24,7 @@ const defaultOptions = {
};

/**
* @param {string[]} properties
* @param {Array} properties
y-hsgw marked this conversation as resolved.
Show resolved Hide resolved
* @param {string} keyName
* @returns {Set<string>}
*/
Expand All @@ -41,6 +41,7 @@ function extractTargetProps(properties, keyName) {
);
}

/** @type { import('eslint').Rule.RuleModule } */
ljharb marked this conversation as resolved.
Show resolved Hide resolved
module.exports = {
meta: {
docs: {
Expand Down
1 change: 1 addition & 0 deletions lib/rules/default-props-match-prop-types.js
Expand Up @@ -21,6 +21,7 @@ const messages = {
defaultHasNoType: 'defaultProp "{{name}}" has no corresponding propTypes declaration.',
};

/** @type { import('eslint').Rule.RuleModule } */
module.exports = {
meta: {
docs: {
Expand Down
1 change: 1 addition & 0 deletions lib/rules/destructuring-assignment.js
Expand Up @@ -53,6 +53,7 @@ const messages = {
destructureInSignature: 'Must destructure props in the function signature.',
};

/** @type { import('eslint').Rule.RuleModule } */
module.exports = {
meta: {
docs: {
Expand Down
1 change: 1 addition & 0 deletions lib/rules/display-name.js
Expand Up @@ -27,6 +27,7 @@ const messages = {
noContextDisplayName: 'Context definition is missing display name',
};

/** @type { import('eslint').Rule.RuleModule } */
module.exports = {
meta: {
docs: {
Expand Down
1 change: 1 addition & 0 deletions lib/rules/forbid-component-props.js
Expand Up @@ -22,6 +22,7 @@ const messages = {
propIsForbidden: 'Prop "{{prop}}" is forbidden on Components',
};

/** @type { import('eslint').Rule.RuleModule } */
module.exports = {
meta: {
docs: {
Expand Down
1 change: 1 addition & 0 deletions lib/rules/forbid-dom-props.js
Expand Up @@ -37,6 +37,7 @@ const messages = {
propIsForbidden: 'Prop "{{prop}}" is forbidden on DOM Nodes',
};

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

/** @type { import('eslint').Rule.RuleModule } */
module.exports = {
meta: {
docs: {
Expand Down Expand Up @@ -103,13 +104,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(argument.value.toString())) {
reportIfForbidden(argument.value, argument);
} else if (argType === 'MemberExpression') {
} else if (argument.type === 'MemberExpression') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the original only has one observable lookup; this has 3. why is this an improvement?

also, String(x), never x.toString()

Copy link
Contributor Author

@y-hsgw y-hsgw Apr 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the original only has one observable lookup; this has 3. why is this an improvement?

The reason is because the following error message occurs during the execution of the type-check script.

Property 'name' does not exist on type 'ClassExpression | ArrayExpression | ArrowFunctionExpression | AssignmentExpression | ... 23 more ... | SpreadElement'.  Property 'name' does not exist on type 'ClassExpression'.
if (argType === 'Identifier' && /^[A-Z_]/.test(argument.name)) {

also, String(x), never x.toString()

thanks! Fixed in 4ae24a2 .

reportIfForbidden(context.getSourceCode().getText(argument), argument);
}
},
Expand Down
7 changes: 5 additions & 2 deletions lib/rules/forbid-foreign-prop-types.js
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,7 @@ module.exports = {
&& !ast.isAssignmentLHS(node)
&& !isAllowedAssignment(node)
)) || (
(node.property.type === 'Literal' || node.property.type === 'JSXText')
node.property.type === 'Literal'
&& node.property.value === 'propTypes'
ljharb marked this conversation as resolved.
Show resolved Hide resolved
&& !ast.isAssignmentLHS(node)
&& !isAllowedAssignment(node)
Expand All @@ -121,7 +122,9 @@ 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' && property.key.type === "PrivateIdentifier" && property.key.name === 'propTypes'
);

if (propTypesNode) {
report(context, messages.forbiddenPropType, 'forbiddenPropType', {
Expand Down
2 changes: 2 additions & 0 deletions lib/rules/forbid-prop-types.js
@@ -1,3 +1,4 @@
// @ts-nocheck
ljharb marked this conversation as resolved.
Show resolved Hide resolved
/**
* @fileoverview Forbid certain propTypes
*/
Expand Down Expand Up @@ -25,6 +26,7 @@ const messages = {
forbiddenPropType: 'Prop type "{{target}}" is forbidden',
};

/** @type { import('eslint').Rule.RuleModule } */
module.exports = {
meta: {
docs: {
Expand Down
1 change: 1 addition & 0 deletions lib/rules/function-component-definition.js
Expand Up @@ -113,6 +113,7 @@ const messages = {
'arrow-function': 'Function component is not an arrow function',
};

/** @type { import('eslint').Rule.RuleModule } */
module.exports = {
meta: {
docs: {
Expand Down
1 change: 1 addition & 0 deletions lib/rules/hook-use-state.js
Expand Up @@ -25,6 +25,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/iframe-missing-sandbox.js
Expand Up @@ -109,6 +109,7 @@ function checkProps(context, node) {
}
}

/** @type { import('eslint').Rule.RuleModule } */
module.exports = {
meta: {
docs: {
Expand Down
1 change: 1 addition & 0 deletions lib/rules/jsx-boolean-value.js
Expand Up @@ -54,6 +54,7 @@ const messages = {
omitPropAndBoolean: 'Value must be omitted for `false` attribute: `{{propName}}`',
};

/** @type { import('eslint').Rule.RuleModule } */
module.exports = {
meta: {
docs: {
Expand Down
1 change: 1 addition & 0 deletions lib/rules/jsx-child-element-spacing.js
Expand Up @@ -44,6 +44,7 @@ const messages = {
spacingBeforeNext: 'Ambiguous spacing before next element {{element}}',
};

/** @type { import('eslint').Rule.RuleModule } */
module.exports = {
meta: {
docs: {
Expand Down
3 changes: 3 additions & 0 deletions lib/rules/jsx-closing-bracket-location.js
Expand Up @@ -17,6 +17,7 @@ const messages = {
bracketLocation: 'The closing bracket must be {{location}}{{details}}',
};

/** @type { import('eslint').Rule.RuleModule } */
module.exports = {
meta: {
docs: {
Expand Down Expand Up @@ -195,7 +196,9 @@ module.exports = {
*/
function getTokensLocations(node) {
const sourceCode = context.getSourceCode();
// @ts-ignore
const opening = sourceCode.getFirstToken(node).loc.start;
// @ts-ignore
ljharb marked this conversation as resolved.
Show resolved Hide resolved
const closing = sourceCode.getLastTokens(node, node.selfClosing ? 2 : 1)[0].loc.start;
const tag = sourceCode.getFirstToken(node.name).loc.start;
let lastProp;
Expand Down
1 change: 1 addition & 0 deletions lib/rules/jsx-closing-tag-location.js
Expand Up @@ -18,6 +18,7 @@ const messages = {
matchIndent: 'Expected closing tag to match indentation of opening.',
};

/** @type { import('eslint').Rule.RuleModule } */
module.exports = {
meta: {
docs: {
Expand Down
1 change: 1 addition & 0 deletions lib/rules/jsx-curly-brace-presence.js
Expand Up @@ -36,6 +36,7 @@ const messages = {
missingCurly: 'Need to wrap this literal in a JSX expression.',
};

/** @type { import('eslint').Rule.RuleModule } */
module.exports = {
meta: {
docs: {
Expand Down
1 change: 1 addition & 0 deletions lib/rules/jsx-curly-newline.js
Expand Up @@ -41,6 +41,7 @@ const messages = {
unexpectedAfter: 'Unexpected newline after \'{\'.',
};

/** @type { import('eslint').Rule.RuleModule } */
module.exports = {
meta: {
type: 'layout',
Expand Down
2 changes: 2 additions & 0 deletions lib/rules/jsx-curly-spacing.js
@@ -1,3 +1,4 @@
// @ts-nocheck
ljharb marked this conversation as resolved.
Show resolved Hide resolved
/**
* @fileoverview Enforce or disallow spaces inside of curly braces in JSX attributes.
* @author Jamund Ferguson
Expand Down Expand Up @@ -34,6 +35,7 @@ const messages = {
spaceNeededBefore: 'A space is required before \'{{token}}\'',
};

/** @type { import('eslint').Rule.RuleModule } */
module.exports = {
meta: {
docs: {
Expand Down
3 changes: 3 additions & 0 deletions lib/rules/jsx-equals-spacing.js
Expand Up @@ -19,6 +19,7 @@ const messages = {
needSpaceAfter: 'A space is required after \'=\'',
};

/** @type { import('eslint').Rule.RuleModule } */
module.exports = {
meta: {
docs: {
Expand Down Expand Up @@ -61,7 +62,9 @@ module.exports = {

const sourceCode = context.getSourceCode();
const equalToken = sourceCode.getTokenAfter(attrNode.name);
// @ts-ignore
const spacedBefore = sourceCode.isSpaceBetweenTokens(attrNode.name, equalToken);
// @ts-ignore
ljharb marked this conversation as resolved.
Show resolved Hide resolved
const spacedAfter = sourceCode.isSpaceBetweenTokens(equalToken, attrNode.value);

if (config === 'never') {
Expand Down
1 change: 1 addition & 0 deletions lib/rules/jsx-filename-extension.js
Expand Up @@ -28,6 +28,7 @@ const messages = {
extensionOnlyForJSX: 'Only files containing JSX may use the extension \'{{ext}}\'',
};

/** @type { import('eslint').Rule.RuleModule } */
module.exports = {
meta: {
docs: {
Expand Down
1 change: 1 addition & 0 deletions lib/rules/jsx-first-prop-new-line.js
Expand Up @@ -17,6 +17,7 @@ const messages = {
propOnSameLine: 'Property should be placed on the same line as the component declaration',
};

/** @type { import('eslint').Rule.RuleModule } */
module.exports = {
meta: {
docs: {
Expand Down
3 changes: 2 additions & 1 deletion lib/rules/jsx-fragments.js
Expand Up @@ -27,6 +27,7 @@ const messages = {
preferFragment: 'Prefer fragment shorthand over {{react}}.{{fragment}}',
};

/** @type { import('eslint').Rule.RuleModule } */
module.exports = {
meta: {
docs: {
Expand Down Expand Up @@ -171,7 +172,7 @@ module.exports = {
ImportDeclaration(node) {
if (node.source && node.source.value === 'react') {
node.specifiers.forEach((spec) => {
if (spec.imported && spec.imported.name === fragmentPragma) {
if (spec.type === "ImportSpecifier" && 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-handler-names.js
Expand Up @@ -17,6 +17,7 @@ const messages = {
badPropKey: 'Prop key for {{propValue}} must begin with \'{{handlerPropPrefix}}\'',
};

/** @type { import('eslint').Rule.RuleModule } */
module.exports = {
meta: {
docs: {
Expand Down
2 changes: 2 additions & 0 deletions lib/rules/jsx-indent-props.js
Expand Up @@ -42,6 +42,7 @@ const messages = {
wrongIndent: 'Expected indentation of {{needed}} {{type}} {{characters}} but found {{gotten}}.',
};

/** @type { import('eslint').Rule.RuleModule } */
module.exports = {
meta: {
docs: {
Expand Down Expand Up @@ -140,6 +141,7 @@ module.exports = {
* @return {Number} Indent
*/
function getNodeIndent(node) {
// @ts-ignore
ljharb marked this conversation as resolved.
Show resolved Hide resolved
let src = context.getSourceCode().getText(node, node.loc.start.column + extraColumnStart);
const lines = src.split('\n');
src = lines[0];
Expand Down
2 changes: 2 additions & 0 deletions lib/rules/jsx-indent.js
@@ -1,3 +1,4 @@
// @ts-nocheck
ljharb marked this conversation as resolved.
Show resolved Hide resolved
/**
* @fileoverview Validate JSX indentation
* @author Yannick Croissant
Expand Down Expand Up @@ -45,6 +46,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-key.js
Expand Up @@ -32,6 +32,7 @@ const messages = {
nonUniqueKeys: '`key` prop must be unique',
};

/** @type { import('eslint').Rule.RuleModule } */
module.exports = {
meta: {
docs: {
Expand Down
1 change: 1 addition & 0 deletions lib/rules/jsx-max-depth.js
Expand Up @@ -20,6 +20,7 @@ const messages = {
wrongDepth: 'Expected the depth of nested jsx elements to be <= {{needed}}, but found {{found}}.',
};

/** @type { import('eslint').Rule.RuleModule } */
module.exports = {
meta: {
docs: {
Expand Down
1 change: 1 addition & 0 deletions lib/rules/jsx-max-props-per-line.js
Expand Up @@ -23,6 +23,7 @@ const messages = {
newLine: 'Prop `{{prop}}` must be placed on a new line',
};

/** @type { import('eslint').Rule.RuleModule } */
module.exports = {
meta: {
docs: {
Expand Down
1 change: 1 addition & 0 deletions lib/rules/jsx-newline.js
Expand Up @@ -23,6 +23,7 @@ function isMultilined(node) {
return node && node.loc.start.line !== node.loc.end.line;
}

/** @type { import('eslint').Rule.RuleModule } */
module.exports = {
meta: {
docs: {
Expand Down
5 changes: 4 additions & 1 deletion lib/rules/jsx-no-bind.js
Expand Up @@ -23,6 +23,7 @@ const messages = {
func: 'JSX props should not use functions',
};

/** @type { import('eslint').Rule.RuleModule } */
module.exports = {
meta: {
docs: {
Expand Down Expand Up @@ -123,7 +124,7 @@ module.exports = {
}

function getBlockStatementAncestors(node) {
return context.getAncestors(node).filter(
return context.getAncestors().filter(
(ancestor) => ancestor.type === 'BlockStatement'
).reverse();
}
Expand Down Expand Up @@ -174,7 +175,9 @@ module.exports = {
if (
blockAncestors.length > 0
&& variableViolationType
&& node.parent.type === "VariableDeclaration"
&& node.parent.kind === 'const' // only support const right now
&& node.id.type === 'Identifier'
) {
addVariableNameToSet(
variableViolationType, node.id.name, blockAncestors[0].range[0]
Expand Down