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

🐛 False positive in rule noUndeclaredVariables #243

Closed
1 task done
AndreiTS opened this issue Sep 11, 2023 · 5 comments · Fixed by #350
Closed
1 task done

🐛 False positive in rule noUndeclaredVariables #243

AndreiTS opened this issue Sep 11, 2023 · 5 comments · Fixed by #350
Assignees
Labels
A-Linter Area: linter L-JavaScript Language: JavaScript and super languages S-Bug-confirmed Status: report has been confirmed as a valid bug

Comments

@AndreiTS
Copy link

Environment information

sorry cant provide it (it's too big)

What happened?

Using the hook specified in https://docs.pmnd.rs/zustand/guides/auto-generating-selectors apparently raises a false positive in the rule noUndeclaredVariables: playground

Expected result

I think in this case there should be no warning

Code of Conduct

  • I agree to follow Biome's Code of Conduct
@AndreiTS AndreiTS changed the title 🐛 False positive on noUndeclaredVariables 🐛 False positive in rule "noUndeclaredVariables" Sep 11, 2023
@denbezrukov denbezrukov added A-Linter Area: linter S-Bug-confirmed Status: report has been confirmed as a valid bug labels Sep 11, 2023
@Conaclos Conaclos added the L-JavaScript Language: JavaScript and super languages label Sep 12, 2023
@denbezrukov
Copy link
Contributor

The issue is the interaction between infer T that creates the binding T and the function signature TsFunctionType that creates its own scope.

| TS_FUNCTION_TYPE
| TS_DECLARE_FUNCTION_DECLARATION => {
self.push_scope(
node.text_range(),
ScopeHoisting::DontHoistDeclarationsToParent,
false,
);
}

Let start with: type A<U> = U extends {a: infer T} ? T : never.

When we don't have a TsFunctionType, the T binding belongs to TsTypeAliasDeclaration.
I assume that if we have infer T inside the TsFunctionType scope and it's inside TsConditionalType, it should propagate to the nearest TsTypeAliasDeclaration/TsConditionalType. TsConditionalType doesn't create a scope now.

Do I understand correctly that infer can be used only inside extends?

@strager
Copy link
Contributor

strager commented Sep 12, 2023

Do I understand correctly that infer can be used only inside extends?

That is the only context in which I've seen infer, yes.


Note that infer can get wacky as it's not lexically scoped. For example, see this quick-lint-js test case:

MyType extends (OtherType extends infer T ? infer U : InnerFalse) ? OuterTrue : OuterFalse
//                         T is usable here ^^^^^^^
//                                                 U is usable here ^^^^^^^^^

@denbezrukov
Copy link
Contributor

@strager Thank you!
Oh, it seems that we handle this case incorrectly.
Playground

Could you please explain how quick-lint-js creates scopes for TS?

@strager
Copy link
Contributor

strager commented Sep 12, 2023

Could you please explain how quick-lint-js creates scopes for TS?

Assuming we are parsing A extends B ? T : F:

Global variable: typescript_infer_declaration_buffer: Vec<Vec<Ident>>

Overall type parsing algorithm (relevant bits only) (this should be obvious):

  1. If we see infer:
    1. Skip infer
    2. Expect an identifier
    3. Stop
  2. Parse type A
  3. If we see extends:
    1. Skip extends
    2. Parse type B
    3. Expect ?
    4. Parse type T
    5. Expect :
    6. Parse type F

When we parse B:

  1. Push a new Vec<Ident> onto typescript_infer_declaration_buffer
  2. Parse type B
  3. Pop from typescript_infer_declaration_buffer into a local variable infers

When we see infer X (in any type, not just inside an extends)

  1. If typescript_infer_declaration_buffer is empty, report Diag_TypeScript_Infer_Outside_Conditional_Type
  2. Otherwise, push X onto the top of typescript_infer_declaration_buffer

When we parse T:

  1. Enter a scope
  2. Declare each variable of infers
  3. Parse type T
  4. Exit a scope

@Conaclos Conaclos changed the title 🐛 False positive in rule "noUndeclaredVariables" 🐛 False positive in rule noUndeclaredVariables Sep 12, 2023
@quantizor
Copy link

Another example of code using infer that triggers a false positive:

type ActionPreferenceNames<Name extends PreferenceName, Actions> = {
    [ActionName in keyof Actions]?: Actions[ActionName] extends (value: infer U) => void
      ? KeysWithValuesOfType<Required<GetPreferenceValue<Name>>, Exclude<U, undefined>>
      : never;
  };

// The U variable is undeclared biome[lint/correctness/noUndeclaredVariables]

@denbezrukov denbezrukov self-assigned this Sep 19, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Linter Area: linter L-JavaScript Language: JavaScript and super languages S-Bug-confirmed Status: report has been confirmed as a valid bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants