biomejs / biome

A toolchain for web projects, aimed to provide functionalities to maintain them. Biome offers formatter and linter, usable via CLI and LSP.
https://biomejs.dev
Apache License 2.0
12.51k stars 393 forks source link

`noUndeclaredVariables` erroring on self-referential TypeScript enums #2974

Closed KurtPreston closed 1 day ago

KurtPreston commented 1 month ago

Environment information

CLI:
  Version:                      1.7.3
  Color support:                true

Platform:
  CPU Architecture:             x86_64
  OS:                           linux

Environment:
  BIOME_LOG_DIR:                unset
  NO_COLOR:                     unset
  TERM:                         "xterm-256color"
  JS_RUNTIME_VERSION:           "v20.12.2"
  JS_RUNTIME_NAME:              "node"
  NODE_PACKAGE_MANAGER:         "npm/10.5.0"

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

Linter:
  Recommended:                  false
  All:                          false
  Rules:                        a11y/noBlankTarget = "off"
                                complexity/noExtraBooleanCast = "off"
                                complexity/noForEach = "off"
                                complexity/noMultipleSpacesInRegularExpressionLiterals = "error"
                                complexity/noStaticOnlyClass = "off"
                                complexity/noUselessCatch = "error"
                                complexity/noUselessConstructor = "error"
                                complexity/noUselessLoneBlockStatements = "error"
                                complexity/noUselessSwitchCase = "off"
                                complexity/noWith = "error"
                                complexity/useFlatMap = "error"
                                correctness/noChildrenProp = "off"
                                correctness/noConstAssign = "error"
                                correctness/noConstantCondition = "off"
                                correctness/noEmptyCharacterClassInRegex = "error"
                                correctness/noEmptyPattern = "off"
                                correctness/noGlobalObjectCalls = "error"
                                correctness/noInnerDeclarations = "off"
                                correctness/noInvalidConstructorSuper = "error"
                                correctness/noNewSymbol = "error"
                                correctness/noNonoctalDecimalEscape = "error"
                                correctness/noPrecisionLoss = "error"
                                correctness/noSelfAssign = "error"
                                correctness/noSetterReturn = "error"
                                correctness/noSwitchDeclarations = "off"
                                correctness/noUndeclaredVariables = "error"
                                correctness/noUnreachable = "error"
                                correctness/noUnreachableSuper = "error"
                                correctness/noUnsafeFinally = "error"
                                correctness/noUnsafeOptionalChaining = "error"
                                correctness/noUnusedLabels = "error"
                                correctness/noUnusedVariables = "error"
                                correctness/useExhaustiveDependencies = "warn"
                                correctness/useHookAtTopLevel = "error"
                                correctness/useIsNan = "error"
                                correctness/useJsxKeyInIterable = "error"
                                correctness/useValidForDirection = "error"
                                correctness/useYield = "error"
                                security/noDangerouslySetInnerHtml = "off"
                                security/noGlobalEval = "error"
                                style/noCommaOperator = "error"
                                style/noImplicitBoolean = "off"
                                style/noNegationElse = "off"
                                style/noRestrictedGlobals = {"level":"error","options":{"deniedGlobals":["bbenchmark","alert","event","fdescribe","fit","isFinite","length","name","open","performance","setInterval","ssuite","status"]}}
                                style/noVar = "error"
                                style/useBlockStatements = "error"
                                style/useConst = "error"
                                style/useForOf = "off"
                                style/useNodejsImportProtocol = "off"
                                style/useNumberNamespace = "off"
                                suspicious/noAsyncPromiseExecutor = "off"
                                suspicious/noCatchAssign = "off"
                                suspicious/noClassAssign = "error"
                                suspicious/noCommentText = "error"
                                suspicious/noCompareNegZero = "error"
                                suspicious/noControlCharactersInRegex = "error"
                                suspicious/noDebugger = "error"
                                suspicious/noDoubleEquals = "error"
                                suspicious/noDuplicateCase = "error"
                                suspicious/noDuplicateClassMembers = "error"
                                suspicious/noDuplicateJsxProps = "error"
                                suspicious/noDuplicateObjectKeys = "error"
                                suspicious/noDuplicateParameters = "error"
                                suspicious/noEmptyBlockStatements = "off"
                                suspicious/noFallthroughSwitchClause = "error"
                                suspicious/noFunctionAssign = "error"
                                suspicious/noGlobalAssign = "error"
                                suspicious/noImportAssign = "error"
                                suspicious/noMisleadingCharacterClass = "error"
                                suspicious/noPrototypeBuiltins = "off"
                                suspicious/noRedeclare = "error"
                                suspicious/noShadowRestrictedNames = "off"
                                suspicious/noThenProperty = "off"
                                suspicious/noUnsafeNegation = "error"
                                suspicious/useGetterReturn = "off"
                                suspicious/useIsArray = "error"
                                suspicious/useValidTypeof = "error"

Workspace:
  Open Documents:               0

Rule name

lint/correctness/noUndeclaredVariables

Playground link

https://biomejs.dev/playground/?lintRules=all&code=ZQB4AHAAbwByAHQAIABlAG4AdQBtACAAVQBzAGUAcgBBAGMAdABpAG8AbgBUAHkAcABlACAAewAKACAAIABOAG8AbgBlACAAPQAgADAALAAKACAAIABMAGUAZgB0AEMAbABpAGMAawAgAD0AIAAxACwACgAgACAATQBpAGQAZABsAGUAQwBsAGkAYwBrACAAPQAgADIALAAKACAAIABSAGkAZwBoAHQAQwBsAGkAYwBrACAAPQAgADQALAAKACAAIABMAGUAZgB0AEQAcgBhAGcAIAA9ACAAOAAsAAoAIAAgAE0AaQBkAGQAbABlAEQAcgBhAGcAIAA9ACAAMQA2ACwACgAgACAAUgBpAGcAaAB0AEQAcgBhAGcAIAA9ACAAMwAyACwACgAgACAAQQBsAGwAQwBsAGkAYwBrAHMAIAA9ACAATABlAGYAdABDAGwAaQBjAGsAIAB8ACAATQBpAGQAZABsAGUAQwBsAGkAYwBrACAAfAAgAFIAaQBnAGgAdABDAGwAaQBjAGsALAAKACAAIABBAGwAbABEAHIAYQBnAHMAIAA9ACAATABlAGYAdABEAHIAYQBnACAAfAAgAE0AaQBkAGQAbABlAEQAcgBhAGcAIAB8ACAAUgBpAGcAaAB0AEQAcgBhAGcALAAKACAAIABBAGwAbAAgAD0AIABMAGUAZgB0AEMAbABpAGMAawAgAHwAIABNAGkAZABkAGwAZQBDAGwAaQBjAGsAIAB8ACAAUgBpAGcAaAB0AEMAbABpAGMAawAgAHwAIABMAGUAZgB0AEQAcgBhAGcAIAB8ACAATQBpAGQAZABsAGUARAByAGEAZwAgAHwAIABSAGkAZwBoAHQARAByAGEAZwAKAH0A

Expected result

I am declaring a self-referential TypeScript enum like this:

export enum ClickType {
  None = 0,
  LeftClick = 1,
  MiddleClick = 2,
  RightClick = 4,
  AllClicks = LeftClick | MiddleClick | RightClick
}

This is considered valid TypeScript syntax, however, Biome reports the errors:

The LeftClick variable is undeclared
The MiddleClick variable is undeclared
The RightClick variable is undeclared

Code of Conduct

Conaclos commented 1 month ago

I am not sure how we should address this issue. I first thought we should add an exception in noUndeclaredVariables. However, very complex cases could be difficult or impossible to handle. Just to give an example:

enum Enum {
    X = 0,
    Y = ((): X => X)()
}

It seems that it would be better to solve the problem by modifying the semantic model. However, this will mean adding a binding for every member name, including classes and objects. This could be useful if we decide to handle private class members in the semantic model. However, this will create many declarations (class methods, object keys, ...) that we won't need because they will never be bound to references. Alternatively we could create a new node for literal enum member names JsLiteralEnumMemberName.

Any opinion?