-
Notifications
You must be signed in to change notification settings - Fork 472
Fix TypeScript never-narrowing in require-async-entrypoint-catch for setup/js lint path
#43440
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -411,4 +411,29 @@ main().then(() => process.exit(0)).catch(err => { console.error(err); process.ex | |
| ], | ||
| }); | ||
| }); | ||
|
|
||
| it("invalid: bare call to async function in multi-declarator module-scope const is flagged", () => { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. New test missing valid-case for non-async sibling declarator: the test only asserts 💡 Add a valid case to fully exercise the per-declarator discriminationWithout a valid entry for valid: [
{
code: `const helper = () => 1, main = async () => { return 42; };
helper();`,
},
],This ensures the rule doesn't accidentally flag all declarators in a multi-declarator |
||
| cjsRuleTester.run("require-async-entrypoint-catch", requireAsyncEntrypointCatchRule, { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing ESM regression coverage: the multi-declarator fix is only tested in CJS mode, leaving the 💡 Add an ESM test variant
return node.parent.type === AST_NODE_TYPES.Program ||
(node.parent.type === AST_NODE_TYPES.ExportNamedDeclaration &&
node.parent.parent.type === AST_NODE_TYPES.Program);The new test only exercises the first ( it("invalid: bare call in multi-declarator exported const is flagged (ESM)", () => {
esmRuleTester.run("require-async-entrypoint-catch", requireAsyncEntrypointCatchRule, {
valid: [],
invalid: [
{
code: `export const helper = () => 1, main = async () => { return 42; };\nmain();`,
errors: [{ messageId: "requireCatch", data: { name: "main" } }],
},
],
});
}); |
||
| valid: [], | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/tdd] The new test block only covers the invalid case — 💡 Suggested additionvalid: [
{
code: `const helper = () => 1, main = async () => { return 42; };
main().catch(err => { console.error(err); process.exitCode = 1; });`,
},
],Without this, the test only checks that the rule fires, not that it correctly accepts the handled form after the @copilot please address this. |
||
| invalid: [ | ||
| { | ||
| code: `const helper = () => 1, main = async () => { return 42; }; | ||
| main();`, | ||
| errors: [ | ||
| { | ||
| messageId: "requireCatch", | ||
| data: { name: "main" }, | ||
| suggestions: [ | ||
| { | ||
| messageId: "addCatch", | ||
| output: `const helper = () => 1, main = async () => { return 42; }; | ||
| main().catch(err => { console.error(err); process.exitCode = 1; });`, | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| }, | ||
| ], | ||
| }); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,14 +4,8 @@ const createRule = ESLintUtils.RuleCreator(name => `https://github.com/github/gh | |
|
|
||
| type AsyncFuncNode = TSESTree.FunctionDeclaration | TSESTree.FunctionExpression | TSESTree.ArrowFunctionExpression; | ||
| type SourceCodeScope = TSESLint.Scope.Scope; | ||
| type FunctionDeclarationDefinition = TSESLint.Scope.Definition & { | ||
| type: "FunctionName"; | ||
| node: TSESTree.FunctionDeclaration; | ||
| }; | ||
| type VariableDefinition = TSESLint.Scope.Definition & { | ||
| type: "Variable"; | ||
| node: TSESTree.VariableDeclarator; | ||
| }; | ||
| type FunctionDeclarationDefinition = TSESLint.Scope.Definitions.FunctionNameDefinition & { node: TSESTree.FunctionDeclaration }; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/codebase-design] 💡 Investigate and simplifyCheck whether type FunctionDeclarationDefinition = TSESLint.Scope.Definitions.FunctionNameDefinition;This mirrors how @copilot please address this.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good call using
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant 💡 Simplify the type alias
type FunctionDeclarationDefinition = TSESLint.Scope.Definitions.FunctionNameDefinition;If the purpose of keeping the intersection is to be explicit or guard against future upstream type drift, add a comment explaining the intent. Otherwise the redundancy is misleading — a reader will wonder what additional narrowing is happening here. |
||
| type VariableDefinition = TSESLint.Scope.Definitions.VariableDefinition; | ||
|
|
||
| function isAsyncFuncNode(node: TSESTree.Node): node is AsyncFuncNode { | ||
| return node.type === AST_NODE_TYPES.FunctionDeclaration || node.type === AST_NODE_TYPES.FunctionExpression || node.type === AST_NODE_TYPES.ArrowFunctionExpression; | ||
|
|
@@ -49,20 +43,20 @@ function getRootCallIdentifier(node: TSESTree.CallExpression): TSESTree.Identifi | |
| } | ||
|
|
||
| function isFunctionDeclarationDefinition(definition: TSESLint.Scope.Definition): definition is FunctionDeclarationDefinition { | ||
| return definition.type === "FunctionName" && definition.node.type === AST_NODE_TYPES.FunctionDeclaration; | ||
| return definition.type === TSESLint.Scope.DefinitionType.FunctionName && definition.node.type === AST_NODE_TYPES.FunctionDeclaration; | ||
| } | ||
|
|
||
| function isVariableDefinition(definition: TSESLint.Scope.Definition): definition is VariableDefinition { | ||
| return definition.type === "Variable" && definition.node.type === AST_NODE_TYPES.VariableDeclarator; | ||
| return definition.type === TSESLint.Scope.DefinitionType.Variable && definition.node.type === AST_NODE_TYPES.VariableDeclarator; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dead runtime check in 💡 Remove or explain the redundant checkBefore the type alias change, the explicit runtime check provided a meaningful runtime narrowing guard. Now that function isVariableDefinition(definition: TSESLint.Scope.Definition): definition is VariableDefinition {
return definition.type === TSESLint.Scope.DefinitionType.Variable;
}The type-guard cast to |
||
| } | ||
|
|
||
| function isModuleScopeVariableDeclaration(node: TSESTree.VariableDeclaration): boolean { | ||
| return node.parent.type === AST_NODE_TYPES.Program || (node.parent.type === AST_NODE_TYPES.ExportNamedDeclaration && node.parent.parent.type === AST_NODE_TYPES.Program); | ||
| } | ||
|
|
||
| function isAsyncVariableEntrypoint(definition: VariableDefinition): boolean { | ||
| const declaration = definition.node.parent; | ||
| if (!declaration || declaration.type !== AST_NODE_TYPES.VariableDeclaration || !isModuleScopeVariableDeclaration(declaration)) return false; | ||
| const declaration = definition.parent; | ||
| if (!declaration || !isModuleScopeVariableDeclaration(declaration)) return false; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Silent implicit type change in 💡 Why this mattersThe old code had an explicit guard: if (!declaration || declaration.type !== AST_NODE_TYPES.VariableDeclaration || ...)The new code drops the const declaration = definition.parent;
if (!declaration || !isModuleScopeVariableDeclaration(declaration)) return false;
function isModuleScopeVariableDeclaration(node: TSESTree.VariableDeclaration): boolean {
return node.parent.type === AST_NODE_TYPES.Program || ...
}If |
||
| const init = definition.node.init; | ||
| return (init?.type === AST_NODE_TYPES.FunctionExpression || init?.type === AST_NODE_TYPES.ArrowFunctionExpression) && init.async; | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[/tdd] The test name describes the fix trigger (multi-declarator const) but not the expected outcome. Renaming it to follow the Arrange/Act/Assert convention makes it self-documenting and easier to diagnose in CI output.
💡 Suggested rename
The pattern
[valid|invalid]: <condition> [reports|does not report] <messageId>matches the spec-style naming used throughout the rest of the file.@copilot please address this.