-
Notifications
You must be signed in to change notification settings - Fork 204
Enable the @typescript-eslint/restrict-template-expressions rule #4111
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
base: main
Are you sure you want to change the base?
Enable the @typescript-eslint/restrict-template-expressions rule #4111
Conversation
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.
Pull Request Overview
This PR enables the @typescript-eslint/restrict-template-expressions
rule to prevent the stringification of objects that don't provide meaningful string representations (like [Object object]
). The changes systematically address violations by explicitly converting values to strings using methods like toString()
, getErrorMessage()
, and JSON.stringify()
.
- Replaces implicit string coercion with explicit string conversion methods
- Uses
getErrorMessage()
helper for error objects to ensure consistent error handling - Adds explicit
.toString()
calls for URI objects, version objects, and other complex types
Reviewed Changes
Copilot reviewed 25 out of 25 changed files in this pull request and generated 5 comments.
File | Description |
---|---|
eslint.config.mjs | Enables the @typescript-eslint/restrict-template-expressions rule |
Multiple source files | Adds explicit string conversion for error objects, URIs, versions, and other complex types in template expressions |
Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.
You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.
treeItem.tooltip = | ||
typeof treeItem.label === "string" | ||
? treeItem.label | ||
: (treeItem.label?.label ?? ""); |
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.
The original line 47 uses element.label || ""
but the tooltip logic assumes treeItem.label
could be non-string. This creates an inconsistency - if element.label
is falsy, treeItem.label
becomes an empty string, making the type check unnecessary. Either use element.label
directly in the tooltip or handle the falsy case consistently.
: (treeItem.label?.label ?? ""); | |
treeItem.tooltip = element.label || ""; |
Copilot uses AI. Check for mistakes.
@@ -80,11 +80,11 @@ class AstViewerDataProvider | |||
const treeItem = new TreeItem(item.label || "", state); | |||
treeItem.description = line ? `Line ${line}` : ""; | |||
treeItem.id = String(item.id); | |||
treeItem.tooltip = `${treeItem.description} ${treeItem.label}`; | |||
treeItem.tooltip = `${treeItem.description} ${typeof treeItem.label === "string" ? treeItem.label : (treeItem.label?.label ?? "")}`; |
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.
[nitpick] This line is extremely long and hard to read. Consider extracting the label logic into a variable: const labelText = typeof treeItem.label === "string" ? treeItem.label : (treeItem.label?.label ?? ""); treeItem.tooltip = \
${treeItem.description} ${labelText}`;`
treeItem.tooltip = `${treeItem.description} ${typeof treeItem.label === "string" ? treeItem.label : (treeItem.label?.label ?? "")}`; | |
const labelText = typeof treeItem.label === "string" ? treeItem.label : (treeItem.label?.label ?? ""); | |
treeItem.tooltip = `${treeItem.description} ${labelText}`; |
Copilot uses AI. Check for mistakes.
@@ -99,7 +99,7 @@ async function previewQueryHelp( | |||
telemetryListener, | |||
errorMessage, | |||
{ | |||
fullMessage: `${errorMessage}\n${getErrorMessage(e)}`, | |||
fullMessage: `${errorMessage.fullMessage}\n${getErrorMessage(e)}`, |
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.
The variable errorMessage
is being treated as an object with a fullMessage
property, but the original code suggests it should be a string. This change appears to introduce a bug where errorMessage.fullMessage
is accessed instead of using errorMessage
directly.
fullMessage: `${errorMessage.fullMessage}\n${getErrorMessage(e)}`, | |
fullMessage: `${errorMessage}\n${getErrorMessage(e)}`, |
Copilot uses AI. Check for mistakes.
void extLogger.log( | ||
`Waiting for event ${event} timed out after ${timeoutMs}ms`, | ||
); | ||
void extLogger.log(`Waiting for event timed out after ${timeoutMs}ms`); |
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.
The log message removes the ${event}
parameter that was in the original message, making the log less informative. The event parameter should be converted to string and included: void extLogger.log(\
Waiting for event ${String(event)} timed out after ${timeoutMs}ms`);`
void extLogger.log(`Waiting for event timed out after ${timeoutMs}ms`); | |
void extLogger.log(`Waiting for event ${String(event)} timed out after ${timeoutMs}ms`); |
Copilot uses AI. Check for mistakes.
@@ -748,7 +748,7 @@ export class CodeQLCliServer implements Disposable { | |||
} else { | |||
reject( | |||
new Error( | |||
`${command} ${commandArgs.join(" ")} failed with code ${code}`, | |||
`${command.join(" ")} ${commandArgs.join(" ")} failed with code ${code}`, |
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.
The command
variable appears to be a string in the original code, not an array. Calling .join(" ")
on a string will cause a runtime error. This should likely be just ${command}
or the variable type needs to be verified.
`${command.join(" ")} ${commandArgs.join(" ")} failed with code ${code}`, | |
`${command} ${commandArgs.join(" ")} failed with code ${code}`, |
Copilot uses AI. Check for mistakes.
@@ -89,7 +89,7 @@ export async function askForLanguage( | |||
if (!isQueryLanguage(language)) { | |||
void showAndLogErrorMessage( | |||
extLogger, | |||
`Language '${language}' is not supported. Only languages ${Object.values( | |||
`Language '${language as QueryLanguage}' is not supported. Only languages ${Object.values( |
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.
This is an interesting case because the language
variable actually has type never
here. An alternative would be to cast it to string
perhaps, or maybe any
and then call .toString()
manually, but I think we need to cast it to something.
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 casting it to string
would make sense here. We know it's not a QueryLanguage
, so I don't think casting it to QueryLanguage
makes sense here.
@@ -80,11 +80,11 @@ class AstViewerDataProvider | |||
const treeItem = new TreeItem(item.label || "", state); | |||
treeItem.description = line ? `Line ${line}` : ""; | |||
treeItem.id = String(item.id); | |||
treeItem.tooltip = `${treeItem.description} ${treeItem.label}`; | |||
treeItem.tooltip = `${treeItem.description} ${typeof treeItem.label === "string" ? treeItem.label : (treeItem.label?.label ?? "")}`; |
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.
This is a bit ugly but the type of treeItem.label
is string | TreeItemLabel | undefined
and it doesn't seem that TreeItemLabel
implements toString()
.
void extLogger.log( | ||
`Waiting for event ${event} timed out after ${timeoutMs}ms`, | ||
); | ||
void extLogger.log(`Waiting for event timed out after ${timeoutMs}ms`); |
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.
The vscode.Event<T>
type is just an interface with a "listen" method. So I don't know if there is any good way we could stringify the event to find out what it was. 🫤
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.
Could we just pass a string
to this function that gives a "friendly name" for the event? It seems like there's only 1 caller anyway.
@@ -89,7 +89,7 @@ export async function askForLanguage( | |||
if (!isQueryLanguage(language)) { | |||
void showAndLogErrorMessage( | |||
extLogger, | |||
`Language '${language}' is not supported. Only languages ${Object.values( | |||
`Language '${language as QueryLanguage}' is not supported. Only languages ${Object.values( |
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 casting it to string
would make sense here. We know it's not a QueryLanguage
, so I don't think casting it to QueryLanguage
makes sense here.
void extLogger.log( | ||
`Waiting for event ${event} timed out after ${timeoutMs}ms`, | ||
); | ||
void extLogger.log(`Waiting for event timed out after ${timeoutMs}ms`); |
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.
Could we just pass a string
to this function that gives a "friendly name" for the event? It seems like there's only 1 caller anyway.
treeItem.command = { | ||
command: "codeQLAstViewer.gotoCode", | ||
title: "Go To Code", | ||
tooltip: `Go To ${item.location}`, | ||
tooltip: `Go To ${item.location?.toString() ?? "Code"}`, |
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.
What will item.location.toString()
return? It looks like item.location
is an object, so would that just give [Object object]
?
Enable the
@typescript-eslint/restrict-template-expressions
rule. This was "disabled" in the eslint 9 migration, though actually I don't think this rule was enabled before so really this is a new rule.This PR will make behavioural changes to the extension, but I think all the changes are for the better 🤞🏼
The purpose of this rule is to protect us from trying to stringify something that won't look nice. It should stop the classic
[Object object]
problem by forcing us to manually convert everything to a string first so we're being deliberate about how that transformation happens. This can be a little bit tedious but enabling it for our repo didn't turn out to be too hard.To be clear, when you try to include a value in a string it'll call
.toString()
for you which is defined on all objects. Often this is fine, and many values (e.g. the URI class) have implemented a goodtoString
method, but generic objects and arrays have no and they do not stringify well.The most common cases we have are errors and URIs and both of those are easy to fix and shouldn't change the behaviour, even if calling
.toString()
on all URIs is a little annoying. But this rule did end up surfacing quite a few cases where I think we did have a bug and if that code had been hit (often it's an error message) then it wouldn't have looked pretty and wouldn't have been useful to us.I haven't split this up into separate commits, but each code change is very localised and you can consider each of them separately when reviewing.