JS-1381 Fix S6598 false positive for interfaces used as defineEmits type argument in Vue#6525
Conversation
Ruling Report✅ No changes to ruling expected issues in this PR |
|
[4E] Missing upstream FP assertion The new Vue test cases use This acts as a sentinel: if the upstream |
|
[4D]
Minor observation; in-rule placement is acceptable if no broader reuse is anticipated. |
|
Good observation. Since |
|
Good call. Added a sentinel test in |
|
LGTM |
|
Thank you for the review! |
Tests cover the scenario where an interface, inline type literal, or named type alias with a single call signature is used as the type argument of defineEmits<T>() inside a Vue 3 <script setup> block. The tests verify that such patterns are not flagged (valid cases) and that interfaces unrelated to defineEmits inside <script setup> are still flagged (invalid case). Relates to JS-1381
S6598 falsely flagged interfaces with only call signatures when used as the type argument of defineEmits<T>() in Vue 3 <script setup> blocks. This pattern is idiomatic and documented in Vue 3 — each call signature represents a distinct emitted event and cannot be replaced by a function type. The fix extends decorator.ts with two sequential guards in the interceptReport callback: Guard 1 (isInsideVueSetupScript) passes through all reports outside Vue <script setup> unchanged. Guard 2 (isDefineEmitsTypeArg) suppresses reports only when the flagged node is a defineEmits type argument. Three shapes are handled: inline type literal, named type alias, and named interface. Regular S6598 violations inside <script setup> unrelated to defineEmits are still reported. The test for the remaining invalid case now includes the expected output showing the fix applied (interface converted to function type alias). Implementation follows the approved proposal guidelines. Relates to JS-1381.
Refactored decorator.ts to resolve 10 code quality issues reported by SonarQube Next (S3776, S121, S134, S4204 x5, S4325). Replaced estree-based type casts with proper TSESTree types from @typescript-eslint/utils. Extracted getTopLevelCallExprs() helper using Array.flatMap to eliminate 4-level nesting in isReferencedByDefineEmits (fixes S3776 cognitive complexity and S134 nesting depth). All five 'any' type annotations replaced with TSESTree types and 'as unknown as' casts (fixes S4204). The unnecessary 'as any' assertion on greatGrandParent removed as TSESTree types make it redundant (fixes S4325). The if (!name) guard eliminated since name is now typed as string (non-optional) via TSESTree.TSInterfaceDeclaration.id.name (fixes S121). TypeScript compilation verified error-free; tests unchanged.
Comment: **[4E] Missing upstream FP assertion** The new Vue test cases use `rule` (already decorated). A useful addition would be a test that imports `tsEslintRules['prefer-function-type']` directly (before decoration) and asserts that the upstream rule *does* raise an issue on the `defineEmits` FP pattern — for example, an interface with only call signatures used as a `defineEmits<T>()` type argument in a Vue `<script setup>` block. This acts as a sentinel: if the upstream `prefer-function-type` rule is later improved to no longer flag this pattern natively, the test will fail and signal that the decorator suppression logic can be safely removed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The sentinel test for upstream prefer-function-type was missing the required `output` field. ESLint's rule tester compares the actual fixed output against the original input when `output` is omitted, causing an assertion failure because the upstream rule applies an autofix (converting interface to type alias).
75188de to
a4a4e5c
Compare
vdiez
left a comment
There was a problem hiding this comment.
please clarify if the cast you are doing is safe considering all possible reported node types
| context.report({ | ||
| ...reportDescriptor, | ||
| }); | ||
| const node = (reportDescriptor as unknown as { node: TSESTree.TSCallSignatureDeclaration }) |
There was a problem hiding this comment.
from what I see in prefer-function-type, the rule may report in TSESTree.TSThisType, TSCallSignatureDeclaration or TSConstructSignatureDeclaration. not sure this cast is 100% safe
There was a problem hiding this comment.
Good catch! The rule can indeed report on TSConstructSignatureDeclaration and TSThisType (with the unexpectedThisOnFunctionOnlyInterface message) in addition to TSCallSignatureDeclaration. Fixed by casting to TSESTree.Node first and adding a node.type !== 'TSCallSignatureDeclaration' guard that passes through all non-call-signature reports unchanged before narrowing the type.
…corator.ts:103 Comment: from what I see in `prefer-function-type`, the rule may report in `TSESTree.TSThisType`, `TSCallSignatureDeclaration` or `TSConstructSignatureDeclaration`. not sure this cast is 100% safe 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Removed unnecessary type assertion (S4325) in decorator.ts interceptReport callback. After the TSCallSignatureDeclaration type guard, TypeScript narrows the node type automatically, making the intermediate callSigNode variable and its explicit cast redundant. Replaced with direct use of the narrowed node.
|
|
Good point. |




Fix a false positive in S6598 where interfaces with only call signatures were incorrectly flagged when used as the type argument of
defineEmits<T>()in Vue 3<script setup>blocks. This pattern is idiomatic Vue 3 — each call signature represents a distinct emitted event and cannot be replaced by a function type alias.Changes
decorator.tswith two sequential guards in theinterceptReportcallback:isInsideVueSetupScript): passes through all reports outside Vue<script setup>unchangedisDefineEmitsTypeArg): suppresses reports only when the flagged node is adefineEmitstype argument<script setup>unrelated todefineEmitscontinue to be reporteddefineEmitstype argument) and the invalid case (interface unrelated todefineEmitsinside<script setup>)Relates to JS-1381.