What version of Oxlint are you using?
1.69.0
What command did you run?
oxlint in a repository that invokes oxlint with eslint/no-useless-assignment enabled.
What happened?
eslint/no-useless-assignment reports assignments that are used on later control-flow paths.
Repro 1: for update expression used by the next iteration
const maxRetries = 3;
async function retryUntilSuccess(run: () => Promise<void>): Promise<void> {
for (let attempt = 0; ; attempt++) {
try {
return await run();
} catch (error) {
if (attempt >= maxRetries) {
throw error;
}
}
}
}
Oxlint reports the attempt++ update expression as unused, but the updated value is read by the catch block on the next iteration.
Repro 2: try/finally ownership flag initial value
function makeResource(): { readonly release: () => void } {
return { release() {} };
}
function useResource(unsafe: (resource: { readonly release: () => void }) => void): { readonly release: () => void } {
const resource = makeResource();
let owned = true;
try {
unsafe(resource);
owned = false;
} finally {
if (owned) {
resource.release();
}
}
return resource;
}
Oxlint reports let owned = true as unused. That initial value is observed by the finally block if unsafe(resource) throws before ownership is transferred.
Repro 3: assignments in a loop body used by the next iteration
async function waitWithBackoff(run: () => Promise<void>): Promise<void> {
let backoffMillis = 20;
let releaseRequested = false;
while (Date.now() < Date.now() + 2000) {
try {
return await run();
} catch (error) {
if (!(error instanceof Error) || !error.message.startsWith("LeaseHeldError")) {
throw error;
}
}
if (!releaseRequested) {
releaseRequested = true;
}
const waitUntil = Math.min(Date.now() + backoffMillis, Date.now() + 2000);
backoffMillis *= 2;
await new Promise((resolve) => setTimeout(resolve, waitUntil - Date.now()));
}
}
Oxlint reports both releaseRequested = true and backoffMillis *= 2, but both values are read on subsequent loop iterations.
ESLint documents this conservative behavior as intentional for no-useless-assignment: assignments inside/around try may be observed through exceptional control flow and should not be reported when an exception path can read the value.
What did you expect to happen?
No diagnostics for either snippet. Both assignments affect observable runtime behavior.
Notes
These were found while migrating a codebase from ESLint to oxlint.
What version of Oxlint are you using?
1.69.0
What command did you run?
oxlintin a repository that invokes oxlint witheslint/no-useless-assignmentenabled.What happened?
eslint/no-useless-assignmentreports assignments that are used on later control-flow paths.Repro 1:
forupdate expression used by the next iterationOxlint reports the
attempt++update expression as unused, but the updated value is read by thecatchblock on the next iteration.Repro 2:
try/finallyownership flag initial valueOxlint reports
let owned = trueas unused. That initial value is observed by thefinallyblock ifunsafe(resource)throws before ownership is transferred.Repro 3: assignments in a loop body used by the next iteration
Oxlint reports both
releaseRequested = trueandbackoffMillis *= 2, but both values are read on subsequent loop iterations.ESLint documents this conservative behavior as intentional for
no-useless-assignment: assignments inside/aroundtrymay be observed through exceptional control flow and should not be reported when an exception path can read the value.What did you expect to happen?
No diagnostics for either snippet. Both assignments affect observable runtime behavior.
Notes
These were found while migrating a codebase from ESLint to oxlint.