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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

馃拝 noUnusedImports false negative with TypeScript ambient types #2796

Closed
1 task done
samhh opened this issue May 10, 2024 · 5 comments 路 Fixed by #2812
Closed
1 task done

馃拝 noUnusedImports false negative with TypeScript ambient types #2796

samhh opened this issue May 10, 2024 · 5 comments 路 Fixed by #2812
Assignees
Labels
L-JavaScript Language: JavaScript and super languages S-Enhancement Status: Improve an existing feature

Comments

@samhh
Copy link

samhh commented May 10, 2024

Environment information

CLI:
  Version:                      1.7.3
  Color support:                true

Platform:
  CPU Architecture:             aarch64
  OS:                           macos

Environment:
  BIOME_LOG_DIR:                unset
  NO_COLOR:                     unset
  TERM:                         "screen"
  JS_RUNTIME_VERSION:           "v20.12.2"
  JS_RUNTIME_NAME:              "node"
  NODE_PACKAGE_MANAGER:         "yarn/4.1.1"

Biome Configuration:
  Status:                       Loaded successfully
  Formatter disabled:           false
  Linter disabled:              false
  Organize imports disabled:    false
  VCS disabled:                 false

Linter:
  Recommended:                  true
  All:                          false
  Rules:                        complexity/noUselessRename = "warn"
                                complexity/useArrowFunction = "warn"
                                correctness/noUnusedImports = "warn"
                                correctness/noUnusedVariables = "warn"
                                correctness/useExhaustiveDependencies = {"level":"error","options":{"hooks":[{"name":"useEffect","closureIndex":0,"dependenciesIndex":1,"stableResult":"None"},{"name":"useLayoutEffect","closureIndex":0,"dependenciesIndex":1,"stableResult":"None"},{"name":"useInsertionEffect","closureIndex":0,"dependenciesIndex":1,"stableResult":"None"},{"name":"useCallback","closureIndex":0,"dependenciesIndex":1,"stableResult":"None"},{"name":"useMemo","closureIndex":0,"dependenciesIndex":1,"stableResult":"None"},{"name":"useImperativeHandle","closureIndex":1,"dependenciesIndex":2,"stableResult":"None"},{"name":"useState","closureIndex":null,"dependenciesIndex":null,"stableResult":{"Indices":[1]}},{"name":"useReducer","closureIndex":null,"dependenciesIndex":null,"stableResult":{"Indices":[1]}},{"name":"useTransition","closureIndex":null,"dependenciesIndex":null,"stableResult":{"Indices":[1]}},{"name":"useRef","closureIndex":null,"dependenciesIndex":null,"stableResult":"Identity"},{"name":"useDispatch","closureIndex":null,"dependenciesIndex":null,"stableResult":"Identity"}]}}
                                correctness/useHookAtTopLevel = "error"
                                correctness/useJsxKeyInIterable = "off"
                                style/noUselessElse = "off"
                                style/useConsistentArrayType = {"level":"warn","options":{"syntax":"generic"}}
                                style/useImportType = "off"
                                style/useShorthandArrayType = "off"
                                style/useTemplate = "warn"
                                suspicious/noConsoleLog = "warn"
                                suspicious/noFocusedTests = "warn"
                                suspicious/noRedeclare = "off"
                                suspicious/noShadowRestrictedNames = "off"

Workspace:
  Open Documents:               0

Rule name

noUnusedImports

Playground link

https://biomejs.dev/playground/?code=aQBtAHAAbwByAHQAIAB0AHkAcABlACAAKgAgAGEAcwAgAEEAbQBiAGkAZQBuAHQAIABmAHIAbwBtACAAJwAuAC8AbQBvAGQAdQBsAGUALgBqAHMAJwAKAAoAdAB5AHAAZQAgAEIAYQByACAAPQAgAEEAbQBiAGkAZQBuAHQA

Expected result

The provided snippet cannot possibly typecheck unless Ambient is an ambient type, because the Ambient we import is a namespace. Therefore the lint rule should flag the namespace import as unused. The same applies to import *, where it's semi-correctly flagged as only being used in a type context by useImportType. Arguably it should be fixed in the context of each as it seems like a parser issue.

In practice this is an issue in codebases making use of ambient types and namespace imports, such as some fp-ts/similar codebases. For example:

An ambient somewhere to avoid repetitive non-namespaced type imports:

type Option<A> = import('fp-ts/Option').Option<A>

And a module somewhere:

// This'll initially be flagged by useImportType...
import * as Option from 'fp-ts/Option'
// ...so it becomes this...
import type * as Option from 'fp-ts/Option'

// ...however the import is actually still unused, and noUnusedImports doesn't catch it.
export type Foo = Option<string>

Code of Conduct

  • I agree to follow Biome's Code of Conduct
@Conaclos
Copy link
Member

I don't understand what you mean.

If I understand correctly, the code is invalid because you use the namespace as a type.
This invalid code could be reported by a rule. Why this code should be reported by noUnusedImports?

@Sec-ant
Copy link
Contributor

Sec-ant commented May 10, 2024

This invalid code could be reported by a rule. Why this code should be reported by noUnusedImports?

I believe what OP wants to say is that this doesn't necessarily make the code invalid because we can define an ambient type with the same name in some other .d.ts file, which will make the name valid in a type position. And therefore the unused imported namespace should be reported by noUnusedImports.

@Conaclos Conaclos added L-JavaScript Language: JavaScript and super languages S-Bug-confirmed Status: report has been confirmed as a valid bug labels May 10, 2024
@Conaclos
Copy link
Member

Conaclos commented May 10, 2024

I believe what OP wants to say is that this doesn't necessarily make the code invalid because we can define an ambient type with the same name in some other .d.ts file, which will make the name valid in a type position. And the imported unused namespace should be reported by noUnusedImports.

Thanks for the clarification. It makes sense. This is related to how the semantic model binds references to declarations.

However, taking a look at the TypeScript playground, it seems that TypeScript binds Option<string> to the Option namespace.

I am unsure what we should do in this case because our semantic model matches the TypeScript semantic model in this particular case.

@Conaclos Conaclos removed the S-Bug-confirmed Status: report has been confirmed as a valid bug label May 10, 2024
@Sec-ant
Copy link
Contributor

Sec-ant commented May 10, 2024

Some interesting findings:

In VSCode with the TypeScript language server on, if the ambient type is properly defined and discovered, the language server will resolve the type correctly (to the ambient one) and will mark the imported namespace as unused.

image

But if there isn't an ambient type or it is not discovered, it will fail to resolve the type and issue an error. Meanwhile the imported namespace is still marked as unused.

image

Note

In order for that ambient type to be discovered by the language server, one can either 1) open that ambient type definition file in the editor, 2) use the tripple slash directive /// <reference types="..." />.

However, the TypeScript AST viewer doesn't support a multi-file set-up, so we cannot see for sure whether introducing an ambient type will change the semantic model.

Anyway, it seems to me that project-wise type resolution is involved in the analysis so this might not be a semantic model only issue.


I also came up with another simpler example without the need of ambient types to demonstrate the issue:

import * as A from "./module";

type A = string;

export const a: A = "";

And interestingly, the TypeScript language server will not mark the namespace import as unused and instead it will mark it as the type alias defined below. This seems a bug of TypeScript to me:

image

Biome, however, can correctly mark the namespace import as unused with the above code: https://biomejs.dev/playground/?lintRules=all&code=aQBtAHAAbwByAHQAIAAqACAAYQBzACAAQQAgAGYAcgBvAG0AIAAiAC4ALwBtAG8AZAB1AGwAZQAiADsACgAKAHQAeQBwAGUAIABBACAAPQAgAHMAdAByAGkAbgBnADsACgAKAGUAeABwAG8AcgB0ACAAYwBvAG4AcwB0ACAAYQA6ACAAQQAgAD0AIAAiACIAOwA%3D, but it will trigger a false positive of lint/suspicious/noRedeclare.

@Conaclos Conaclos added the S-Enhancement Status: Improve an existing feature label May 10, 2024
@Conaclos
Copy link
Member

@Sec-ant Thanks for the investigation 鉂わ笍

I think we could improve the semantic model in order to not bind a reference in an ambient context to an import namespace.
This means that in the following code, Ns will not reference the import namespace Ns, i twill be reported as an unresolved reference.

import * as Ns from ""
type X = Ns; // unresolved ref

Note that we should still allow namespace access like:

import * as Ns from ""
type X = Ns.Type; // valid, here `Ns` is bound to the import namesapce `Ns`

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
L-JavaScript Language: JavaScript and super languages S-Enhancement Status: Improve an existing feature
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants