Skip to content

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

Closed
Closed
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
60 changes: 51 additions & 9 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1022,6 +1022,7 @@ namespace ts {

const diagnostics = createDiagnosticCollection();
const suggestionDiagnostics = createDiagnosticCollection();
let deprecationsInChooseOverload: Diagnostic[] | undefined;

const typeofTypesByName: ReadonlyESMap<string, Type> = new Map(getEntries({
string: stringType,
Expand Down Expand Up @@ -1220,7 +1221,12 @@ namespace ts {
);
}
// We call `addRelatedInfo()` before adding the diagnostic to prevent duplicates.
suggestionDiagnostics.add(diagnostic);
if (deprecationsInChooseOverload) {
deprecationsInChooseOverload.push(diagnostic);
}
else {
suggestionDiagnostics.add(diagnostic);
}
return diagnostic;
}

Expand Down Expand Up @@ -18142,7 +18148,8 @@ namespace ts {
}
}
if (source.flags & TypeFlags.StructuredOrInstantiable || target.flags & TypeFlags.StructuredOrInstantiable) {
return checkTypeRelatedTo(source, target, relation, /*errorNode*/ undefined);
return checkTypeRelatedTo(source, target, relation,
/*errorNode*/ undefined, /*headMessage*/ undefined, /*containingMessageChain*/ undefined, /*errorOutputContainer*/ undefined);
}
return false;
}
Expand Down Expand Up @@ -18252,7 +18259,6 @@ namespace ts {
Debug.assert(!!errorOutputContainer.errors, "missed opportunity to interact with error.");
}


return result !== Ternary.False;

function resetErrorInfo(saved: ReturnType<typeof captureErrorCalculationState>) {
Expand Down Expand Up @@ -27614,6 +27620,10 @@ namespace ts {
symbolToString(member), typeToString(contextualType));
}
}
if (contextualType) {
const contextualProperty = getPropertyOfType(contextualType, member.escapedName);
checkDeprecatedProperty(member, contextualProperty);
}

prop.declarations = member.declarations;
prop.parent = member.parent;
Expand Down Expand Up @@ -28319,10 +28329,19 @@ namespace ts {
}

if (isNodeOpeningLikeElement) {
const jsxOpeningLikeNode = node ;
const sig = getResolvedSignature(jsxOpeningLikeNode);
const sig = getResolvedSignature(node);
checkDeprecatedSignature(sig, node);
checkJsxReturnAssignableToAppropriateBound(getJsxReferenceKind(jsxOpeningLikeNode), getReturnTypeOfSignature(sig), jsxOpeningLikeNode);
const param = sig.parameters[0];
if (param) {
for (const source of node.attributes.properties) {
const member = source.symbol;
const attributesTarget = getTypeOfSymbol(param);
if (member && attributesTarget) {
checkDeprecatedProperty(member, getPropertyOfType(attributesTarget, member.escapedName));
}
}
}
checkJsxReturnAssignableToAppropriateBound(getJsxReferenceKind(node), getReturnTypeOfSignature(sig), node);
}
}

Expand Down Expand Up @@ -30405,6 +30424,8 @@ namespace ts {
const signatureHelpTrailingComma =
!!(checkMode & CheckMode.IsForSignatureHelp) && node.kind === SyntaxKind.CallExpression && node.arguments.hasTrailingComma;

const deprecationsOutArray: Diagnostic[] = [];

// Section 4.12.1:
// if the candidate list contains one or more signatures for which the type of each argument
// expression is a subtype of each corresponding parameter type, the return type of the first
Expand All @@ -30416,12 +30437,15 @@ namespace ts {
// is just important for choosing the best signature. So in the case where there is only one
// signature, the subtype pass is useless. So skipping it is an optimization.
if (candidates.length > 1) {
result = chooseOverload(candidates, subtypeRelation, isSingleNonGenericCandidate, signatureHelpTrailingComma);
result = chooseOverload(candidates, subtypeRelation, isSingleNonGenericCandidate, signatureHelpTrailingComma, deprecationsOutArray);
}
if (!result) {
result = chooseOverload(candidates, assignableRelation, isSingleNonGenericCandidate, signatureHelpTrailingComma);
result = chooseOverload(candidates, assignableRelation, isSingleNonGenericCandidate, signatureHelpTrailingComma, deprecationsOutArray);
}
if (result) {
for (const diagnostic of deprecationsOutArray ?? []) {
suggestionDiagnostics.add(diagnostic);
}
return result;
}

Expand Down Expand Up @@ -30538,7 +30562,7 @@ namespace ts {
candidateForTypeArgumentError = oldCandidateForTypeArgumentError;
}

function chooseOverload(candidates: Signature[], relation: ESMap<string, RelationComparisonResult>, isSingleNonGenericCandidate: boolean, signatureHelpTrailingComma = false) {
function chooseOverload(candidates: Signature[], relation: ESMap<string, RelationComparisonResult>, isSingleNonGenericCandidate: boolean, signatureHelpTrailingComma = false, deprecationsOutArray?: Diagnostic[]) {
candidatesForArgumentError = undefined;
candidateForArgumentArityError = undefined;
candidateForTypeArgumentError = undefined;
Expand All @@ -30555,7 +30579,13 @@ namespace ts {
return candidate;
}

// Suppress suggestions for overload which is not chosen yet.
const oldDeprecationsInChooseOverload = deprecationsInChooseOverload;
deprecationsInChooseOverload = deprecationsOutArray ?? [];

for (let candidateIndex = 0; candidateIndex < candidates.length; candidateIndex++) {
deprecationsInChooseOverload.length = 0;

const candidate = candidates[candidateIndex];
if (!hasCorrectTypeArgumentArity(candidate, typeArguments) || !hasCorrectArity(node, args, candidate, signatureHelpTrailingComma)) {
continue;
Expand Down Expand Up @@ -30616,9 +30646,11 @@ namespace ts {
}
}
candidates[candidateIndex] = checkCandidate;
deprecationsInChooseOverload = oldDeprecationsInChooseOverload;
return checkCandidate;
}

deprecationsInChooseOverload = oldDeprecationsInChooseOverload;
return undefined;
}
}
Expand Down Expand Up @@ -31551,6 +31583,16 @@ namespace ts {
}
}

function checkDeprecatedProperty(source: Symbol, target: Symbol | undefined) {
if (target?.declarations?.some(decl => decl.flags & NodeFlags.Deprecated)) {
const node = source.valueDeclaration!;
const nameNode = isPropertyAssignment(node) || isShorthandPropertyAssignment(node) || isObjectLiteralMethod(node) || isJsxAttribute(node)
? node.name
: source.valueDeclaration!;
addDeprecatedSuggestion(nameNode, target.declarations, source.escapedName as string);
}
}

function getDeprecatedSuggestionNode(node: Node): Node {
node = skipParentheses(node);
switch (node.kind) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//// interface Props {
//// /** @deprecated */
//// foo?: string;
//// bar: string;
//// baz?: {
//// /** @deprecated */
//// foo?: boolean;
//// bar?: boolean;
//// };
//// /** @deprecated */
//// callback?: () => void
//// }
//// const Component = (props: Props) => props && <div />;
//// const foo = "foo";
//// const bar = "bar";
//// const callback = () => {};

//// let props: Props = { [|callback|]: () => {}, [|foo|]: "foo", bar: "bar", baz: { [|foo|]: true } };
//// props = { [|"foo"|]: "foo", "bar": "bar" };
//// props = { [|["foo"]|]: "foo", ["bar"]: "bar" };
//// props = { [|foo|], bar, [|callback|] };
//// props = { bar, [|callback|]() {} };

//// // Do not skip if there is a type incompatibility error.
//// const props5: Props = { [|foo|]: "foo", boo: "boo" };

//// // Skip for union types.
//// const props6: { foo: { /** @deprecated */ bar: string } | { bar: string, baz: string } } = { foo: { bar: "bar" } };

const ranges = test.ranges();

verify.getSuggestionDiagnostics([
{
message: "'callback' is deprecated.",
code: 6385,
range: ranges[0],
reportsDeprecated: true,
},
{
message: "'foo' is deprecated.",
code: 6385,
range: ranges[1],
reportsDeprecated: true as const,
},
{
message: "'foo' is deprecated.",
code: 6385,
range: ranges[2],
reportsDeprecated: true as const,
},
{
message: "'foo' is deprecated.",
code: 6385,
range: ranges[3],
reportsDeprecated: true as const,
},
{
message: "'foo' is deprecated.",
code: 6385,
range: ranges[4],
reportsDeprecated: true as const,
},
{
message: "'foo' is deprecated.",
code: 6385,
range: ranges[5],
reportsDeprecated: true as const,
},
{
message: "'callback' is deprecated.",
code: 6385,
range: ranges[6],
reportsDeprecated: true,
},
{
message: "'callback' is deprecated.",
code: 6385,
range: ranges[7],
reportsDeprecated: true,
},
{
message: "'foo' is deprecated.",
code: 6385,
range: ranges[8],
reportsDeprecated: true as const,
},
])
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
//// interface Props {
//// /** @deprecated */
//// foo?: string;
//// bar: string;
//// baz?: {
//// /** @deprecated */
//// foo?: boolean;
//// bar?: boolean;
//// };
//// /** @deprecated */
//// callback?: () => void
//// }
//// const func = (_props: Props) => {};
//// const foo = "foo";
//// const bar = "bar";
//// const callback = () => {};

//// func({ [|callback|]: () => {}, [|foo|]: "foo", bar: "bar", baz: { [|foo|]: true } });
//// func({ [|"foo"|]: "foo", "bar": "bar" });
//// func({ [|["foo"]|]: "foo", ["bar"]: "bar" });
//// func({ [|foo|], bar, [|callback|] });
//// func({ bar, [|callback|]() {} });

//// // Do not skip if there is a type incompatibility error.
//// func({ [|foo|]: "foo", boo: "boo" });

//// // Skip for union types.
//// function test(_args: { foo: { /** @deprecated */ bar: string } | { bar: string, baz: string } }) {}
//// test({ foo: { bar: "bar" } })

const ranges = test.ranges();

verify.getSuggestionDiagnostics([
{
message: "'callback' is deprecated.",
code: 6385,
range: ranges[0],
reportsDeprecated: true,
},
{
message: "'foo' is deprecated.",
code: 6385,
range: ranges[1],
reportsDeprecated: true as const,
},
{
message: "'foo' is deprecated.",
code: 6385,
range: ranges[2],
reportsDeprecated: true as const,
},
{
message: "'foo' is deprecated.",
code: 6385,
range: ranges[3],
reportsDeprecated: true as const,
},
{
message: "'foo' is deprecated.",
code: 6385,
range: ranges[4],
reportsDeprecated: true as const,
},
{
message: "'foo' is deprecated.",
code: 6385,
range: ranges[5],
reportsDeprecated: true as const,
},
{
message: "'callback' is deprecated.",
code: 6385,
range: ranges[6],
reportsDeprecated: true,
},
{
message: "'callback' is deprecated.",
code: 6385,
range: ranges[7],
reportsDeprecated: true,
},
{
message: "'foo' is deprecated.",
code: 6385,
range: ranges[8],
reportsDeprecated: true,
},
])
Loading