Skip to content
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

How to lint legacy usage of new Disposable type? #187

Open
rixtox opened this issue Jul 10, 2023 · 7 comments
Open

How to lint legacy usage of new Disposable type? #187

rixtox opened this issue Jul 10, 2023 · 7 comments
Labels
discussion Discussion topic, no immediate action required. external Indicates a dependency on an external task, process, or group question Further information is requested

Comments

@rixtox
Copy link

rixtox commented Jul 10, 2023

One reason of having the Disposable type is to support better static analysis on resource management. However, after we upgraded existing functions to return a Disposable type, we now have two valid mechanisms to dispose the resource:

  1. Legacy explicit disposal mechanism.
  2. Symbol.dispose or Symbol.asyncDispose.

It raises question on how can we apply generic linter rules to effectively detect resource management mistakes when having two mechanisms both being valid.

Take the following examples using the new node:fs Disposable APIs:

import { open } from 'node:fs/promises';

async function legacyUsage() {
    let filehandle;
    try {
        filehandle = await open('thefile.txt', 'r');
    } finally {
        await filehandle?.close();
    } 
}

async function newUsage() {
    await using filehandle = await open('thefile.txt', 'r');
}

async function leak() {
    const filehandle = await open('thefile.txt', 'r');
}

async function excessDispose() {
    await using filehandle = await open('thefile.txt', 'r');
    await filehandle.close();
}

The first two cases are valid, while the others are misuses. How can we design a generic linter to effectively detect the misuses without false positive on the legacy usage?

@ljharb
Copy link
Member

ljharb commented Jul 10, 2023

You can't design such a thing in JS without type information - but for TS codebases, i assume eslint-plugin-typescript would be a natural place to suggest such a feature.

@bakkot
Copy link

bakkot commented Jul 10, 2023

The excess dispose is not obviously a problem. Most disposables, including node file handles, can safely be closed multiple times (with later calls being a no-op). using guarantees it will be closed at the end of the block, but it's legitimate to close it earlier.

Also, there's no reason to use an explicit try/finally to clean up a Disposable, so getting a lint warning in that case is fine.

As such, a lint warning for any use of a Disposable which is not either using or being passed into a DisposableStack would be sufficient. There are rare cases when users will want to suppress such a lint rule, but that's fine.

@rixtox
Copy link
Author

rixtox commented Jul 10, 2023

@bakkot
The excess dispose is not obviously a problem. Most disposables, including node file handles, can safely be closed multiple times (with later calls being a no-op). using guarantees it will be closed at the end of the block, but it's legitimate to close it earlier.

Although it's valid in this specific case, it's generally not a good idea to dispose something twice. This is also a challenge for linters to tell if it's safe to disposed a resource more than once. Or should we recommend all Disposable to safely handle multiple dispose?

Also, there's no reason to use an explicit try/finally to clean up a Disposable, so getting a lint warning in that case is fine.

As such, a lint warning for any use of a Disposable which is not either using or being passed into a DisposableStack would be sufficient. There are rare cases when users will want to suppress such a lint rule, but that's fine.

Then all legacy usage would get that warning. A bit annoying but manageable I guess.

@bakkot
Copy link

bakkot commented Jul 10, 2023

This is also a challenge for linters to tell if it's safe to disposed a resource more than once. Or should we recommend all Disposable to safely handle multiple dispose?

I don't think linters should try to warn you against disposing something multiple times, and I do think Disposables should safely handle multiple disposals (with the second being a no-op).

Yes, sometimes there will be Disposables which are in fact not safe to dispose multiple times, but linters don't need to catch every possible error.

Then all legacy usage would get that warning. A bit annoying but manageable I guess.

Personally, once I start using the new syntax and enabling lint rules and so on for it, I will want get get warnings for the legacy pattern - it's more verbose and error-prone. So I don't really see this as a problem.

@rbuckton
Copy link
Collaborator

In lieu of type information, a linter could at the very least have heuristics to recognize well-known host APIs that will produce disposables, such as fs.promises.open().

@rixtox
Copy link
Author

rixtox commented Jul 11, 2023

@rbuckton
In lieu of type information, a linter could at the very least have heuristics to recognize well-known host APIs that will produce disposables, such as fs.promises.open().

I'm extending your idea of type annotating used and unused Disposable types in this comment of yours (under "Linters and type checkers can help as well" section).

What you were describing sounded like a generic type annotation that can help Disposable type authors to provide type hinting for type checkers and linters to understand if a value has been used or not. It appears to me you were suggesting a generic linter for Disposable is possible with the annotated type info.

I'm expanding that thread of discussion to explore more on that idea.

@rbuckton rbuckton added question Further information is requested discussion Discussion topic, no immediate action required. external Indicates a dependency on an external task, process, or group labels Jul 19, 2023
@phryneas
Copy link
Member

phryneas commented Sep 8, 2023

I've created a very basic lint rule (just warns on use of Disposable/AsyncDisposable without using/await using) as a part of apollographql/apollo-client#11177 - maybe that's of use for someone here 🙂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discussion Discussion topic, no immediate action required. external Indicates a dependency on an external task, process, or group question Further information is requested
Projects
None yet
Development

No branches or pull requests

5 participants