-
Notifications
You must be signed in to change notification settings - Fork 12.9k
Report deprecation for object literal assignments (#39374) #46167
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
Conversation
3f3711f
to
d89d05d
Compare
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.
Functionality looks right to me, it just needs to be called from the right place.
src/compiler/checker.ts
Outdated
@@ -16924,7 +16924,7 @@ namespace ts { | |||
containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, | |||
errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined | |||
): boolean { | |||
if (isTypeRelatedTo(source, target, relation)) return true; | |||
if (isTypeRelatedTo(source, target, relation, /*reportDeprecatedProperties*/ true)) return true; |
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.
Are any other deprecations reported from isTypeRelatedTo
? Based on my experience, it doesn't seem like the logical place for them.
Later: I checked the other callers of addDeprecatedSuggestion
and they're all check*
functions -- the top-level functions whose primary function is to issue errors.
You probably want to call your code starting from checkJSXAttribute or checkPropertyAssignment. Those both call checkExpressionForMutableLocation.
Also, there's already deprecation checking in checkIdentifier
, for uncalled functions I believe. That code might apply after a few tweaks.
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.
Thank you for feedback!
The reason I used checkTypeRelatedToAndOptionallyElaborate
(it calls isTypeRelatedTo
) for the entrypoint is that: we should know actual assignability (isRelated
in my current code) to only report deprecation for finally selected overload signature.
Reporting from e.g. checkObjectLiteral
/ checkPropertyAssignment
and checkJsxAttributes
/ checkJsxAttribute
emits false-positive reports for every overload tried but not chosen by chooseOverload (I tried to implement but got that result 😭).
The another reason of my choice was: addDeprecatedSuggestion
is called from getPropertyTypeForIndexType
, which is not check*
function.
Perhaps we can utilize logic similar to errorOutputContainer
to stick with checkObjectLiteral
and checkJsxAttributes
, but it causes deep bucket brigade from checkExpression to every check*
methods.
I also considered that calling reporting logic directly from checkApplicableSignatureForJsxOpeningLikeElement
itself is cleaner, but it seems to require doubling type traversing logic (isRelatedTo
) only for the deprecation report.
For more context:
In checkApplicableSignatureForJsxOpeningLikeElement
, checkJsxAttributes
is (indirectly) called from checkExpressionWithContextualType
to retrieve type of written attributes (it returns Type
), and that result will be passed to checkTypeRelatedToAndOptionallyElaborate
to check assignability of the attributes vs the signature.
function checkApplicableSignatureForJsxOpeningLikeElement(
node: JsxOpeningLikeElement,
signature: Signature,
relation: ESMap<string, RelationComparisonResult>,
checkMode: CheckMode,
reportErrors: boolean,
containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined,
errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean }
) {
// Stateless function components can have maximum of three arguments: "props", "context", and "updater".
// However "context" and "updater" are implicit and can't be specify by users. Only the first parameter, props,
// can be specified by users through attributes property.
const paramType = getEffectiveFirstArgumentForJsxSignature(signature, node);
const attributesType = checkExpressionWithContextualType(node.attributes, paramType, /*inferenceContext*/ undefined, checkMode);
return checkTagNameDoesNotExpectTooManyArguments() && checkTypeRelatedToAndOptionallyElaborate(
attributesType,
paramType,
...
By doing good old printf debug in createJsxAttributesTypeFromAttributesProperty
(body of checkJsxAttributes
), stack trace is like this:
Error: debug
at createJsxAttributesTypeFromAttributesProperty (src/compiler/checker.ts:27259:31)
at checkJsxAttributes (src/compiler/checker.ts:27381:20)
at checkExpressionWorker (src/compiler/checker.ts:33946:28)
at checkExpression (src/compiler/checker.ts:33794:40)
at checkExpressionWithContextualType (src/compiler/checker.ts:33407:30)
at checkApplicableSignatureForJsxOpeningLikeElement (src/compiler/checker.ts:29296:36)
at getSignatureApplicabilityError (src/compiler/checker.ts:29397:22)
at chooseOverload (src/compiler/checker.ts:29987:25)
at resolveCall (src/compiler/checker.ts:29814:26)
at resolveJsxOpeningLikeElement (src/compiler/checker.ts:30687:20)
at resolveSignature (src/compiler/checker.ts:30714:28)
at getResolvedSignature (src/compiler/checker.ts:30737:28)
at checkJsxOpeningLikeElementOrOpeningFragment (src/compiler/checker.ts:27728:29)
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.
I think you can re-use most of the work with getResolvedSignature
. See my other comment.
d89d05d
to
5a06bd3
Compare
Added test for function with overload. |
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.
Would the code I propose below work?
src/compiler/checker.ts
Outdated
@@ -18441,6 +18456,27 @@ namespace ts { | |||
return prop.valueDeclaration && container.valueDeclaration && prop.valueDeclaration.parent === container.valueDeclaration; | |||
} | |||
|
|||
function checkDeprecatedProperties(source: FreshObjectLiteralType, target: Type) { |
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.
you should be able to call this from createJsxAttributesTypeFromAttributesProperty
, inside the for (const attributeDecl of attributes.properties)
loop.
- Before the loop, save
const sig = resolveSignature(openingLikeElement)
const arg = getTypeOfSymbol(sig.parameters[0])
(you should first check sig.parameters.length)- Inside the loop,
checkDeprecatedProperty(member, getPropertyOfType(arg, member.name))
checkDeprecatedProperty is mostly the body of the for
loop of checkDeprecatedProperties
. I'm not sure where isExcessPropertyCheckTarget
will need to go, probably before the loop in createJsxAttributesTypeFromAttributesProperty
. It should report errors directly instead of storing them in a list.
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.
createJsxAttributesTypeFromAttributesProperty()
is called from resolvedSignature()
itself (see stack trace in this comment #46167 (comment) ), so 1. in your suggestion causes an infinite recursion.
Actual stack trace when placing 1. code
Placing this right before loop in createJsxAttributesTypeFromAttributesProperty()
:
const sig = resolveSignature(openingLikeElement, undefined, checkMode ?? CheckMode.Normal);
caused below stack trace:
1) fourslash tests
tests/cases/fourslash/jsdocDeprecated_suggestion16_jsxArguments.ts
fourslash test jsdocDeprecated_suggestion16_jsxArguments.ts runs correctly:
RangeError: Maximum call stack size exceeded
at identifierIsThisKeyword (src/compiler/utilities.ts:4457:19)
at isThisIdentifier (src/compiler/utilities.ts:4441:65)
at Object.isThisInTypeQuery (src/compiler/utilities.ts:4445:14)
at isMatchingReference (src/compiler/checker.ts:22547:28)
at getTypeAtFlowAssignment (src/compiler/checker.ts:23708:21)
at getTypeAtFlowNode (src/compiler/checker.ts:23626:32)
at getFlowTypeOfReference (src/compiler/checker.ts:23578:53)
at checkIdentifier (src/compiler/checker.ts:25002:30)
at checkExpressionWorker (src/compiler/checker.ts:33838:28)
at checkExpression (src/compiler/checker.ts:33781:40)
at resolveJsxOpeningLikeElement (src/compiler/checker.ts:30657:31)
at resolveSignature (src/compiler/checker.ts:30701:28)
at createJsxAttributesTypeFromAttributesProperty (src/compiler/checker.ts:27225:25)
at checkJsxAttributes (src/compiler/checker.ts:27368:20)
at checkExpressionWorker (src/compiler/checker.ts:33933:28)
at checkExpression (src/compiler/checker.ts:33781:40)
at checkExpressionWithContextualType (src/compiler/checker.ts:33394:30)
at checkApplicableSignatureForJsxOpeningLikeElement (src/compiler/checker.ts:29283:36)
at getSignatureApplicabilityError (src/compiler/checker.ts:29384:22)
at chooseOverload (src/compiler/checker.ts:29933:25)
at resolveCall (src/compiler/checker.ts:29804:26)
at resolveJsxOpeningLikeElement (src/compiler/checker.ts:30674:20)
at resolveSignature (src/compiler/checker.ts:30701:28)
at createJsxAttributesTypeFromAttributesProperty (src/compiler/checker.ts:27225:25)
...
In my understanding, call tree for resolving jsx element signature is roughly in below graph (some nodes are omitted):
checkJsxOpeningLikeElementOrOpeningFragment
└getResolvedSignature
└chooseOverload
└checkApplicableSignatureForJsxOpeningLikeElement
├checkExpressionWithContextualType
| └createJsxAttributesTypeFromAttributesProperty (create Type instance)
└checkTypeRelatedToAndOptionallyElaborate
└isTypeRelatedTo ("type check")
So we do not know final signature in createJsxAttributesTypeFromAttributesProperty.
How do you feel if I add new variable under createTypeChecker to store diagnostics while overload resolution? It may reduce bucket brigade in checkExpression*** call chain.
src/compiler/checker.ts
Outdated
@@ -16924,7 +16924,7 @@ namespace ts { | |||
containingMessageChain: (() => DiagnosticMessageChain | undefined) | undefined, | |||
errorOutputContainer: { errors?: Diagnostic[], skipLogging?: boolean } | undefined | |||
): boolean { | |||
if (isTypeRelatedTo(source, target, relation)) return true; | |||
if (isTypeRelatedTo(source, target, relation, /*reportDeprecatedProperties*/ true)) return true; |
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.
I think you can re-use most of the work with getResolvedSignature
. See my other comment.
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.
I tried moving this myself and ended up with commit a9d808b, which puts the call in checkObjectLiteral and checkJsxOpeningLikeElementOrOpeningFragment.
The downside is that the checkObjectLiteral is called once for each overload, so if any overload has a deprecated property, there will be a deprecated suggestion. As a result, I turned off reporting when the contextual type comes from an overload.
tests/cases/fourslash/jsdocDeprecated_suggestion16_jsxArguments.ts
Outdated
Show resolved
Hide resolved
tests/cases/fourslash/jsdocDeprecated_suggestion16_jsxArguments.ts
Outdated
Show resolved
Hide resolved
tests/cases/fourslash/jsdocDeprecated_suggestion18_functionArgumentsWithOverloads.ts
Outdated
Show resolved
Hide resolved
tests/cases/fourslash/jsdocDeprecated_suggestion16_jsxArguments.ts
Outdated
Show resolved
Hide resolved
//// <Component [|bool|] [|callback|]={() => {}} [|foo|]="foo" bar="bar" baz={{ [|foo|]: true }} />; | ||
|
||
//// // Skip spread in jsx. | ||
//// <Component {...{ foo: "foo", bar: "bar" }} />; |
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.
why skip spread in JSX?
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.
(It was following same flow as excess property check which does not check for spread.)
@ypresto Do you want to keep working on this? Otherwise I'd like to close it to reduce the number of open PRs. |
Okay finally I made some time in next weekend. I'll fix conflicts and review comments. 🙏 |
…OpeningFragment It is now obvious that this only works when there's a contextual type for the object literal.
5a06bd3
to
e507dd3
Compare
I cherry-picked your a9d808b and added global variable of this comment:
|
Looking back at this PR, I feel like it makes too large and risky a change for the benefit of fixing its bug. I think it's best to close this since it is now quite old. |
It's already in version milestone (although rescheduled sometimes), but it seems no one assigned, I tried to implement it.
Context: I want to use this to help upgrading major version of material-ui in my app. :)
We need diagnoses only for contextually typed object literals, and it is very similar to conditions for excess properties check. So I placed the logic to around of it.
This intendedly hides suggestion if any type errors in the assignment. Showing deprecation even if an error is found is perhaps better in some cases. But it requires additional traversing cost and also it can be noisy for majority cases.
Fixes #39374