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
14.95k stars 464 forks source link

🐛 useSortedClasses lint error with Template literals #3394

Closed hangaoke1 closed 2 months ago

hangaoke1 commented 3 months ago

Environment information

✖ These CSS classes should be sorted.

    3 │ const App = () => {
    4 │   const listType = 'picture-card';
  > 5 │   return <div className={`m-websession-uploadlist fishd-upload-list-${listType}`}>app</div>;
      │                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    6 │ };
    7 │ 

  â„č Unsafe fix: Sort the classes.

    3 3 │   const App = () => {
    4 4 │     const listType = 'picture-card';
    5   │ - ··return·<div·className={`m-websession-uploadlist·fishd-upload-list-${listType}`}>app</div>;
      5 │ + ··return·<div·className={`fishd-upload-list-·m-websession-uploadlist${listType}`}>app</div>;
    6 6 │   };
    7 7 │   

### Configuration

```JSON
{
  "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
  "vcs": {
    "enabled": true,
    "clientKind": "git",
    "useIgnoreFile": true
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": false,
      "a11y": {
        "noBlankTarget": "error"
      },
      "complexity": {
        "noBannedTypes": "error",
        "noEmptyTypeParameters": "error",
        "noExcessiveNestedTestSuites": "error",
        "noExtraBooleanCast": "error",
        "noMultipleSpacesInRegularExpressionLiterals": "error",
        "noUselessCatch": "warn",
        "noUselessConstructor": "warn",
        "noUselessEmptyExport": "error",
        "noUselessLabel": "error",
        "noUselessLoneBlockStatements": "error",
        "noUselessRename": "error",
        "noUselessSwitchCase": "error",
        "noUselessTernary": "warn",
        "noUselessTypeConstraint": "error",
        "noVoid": "warn",
        "noWith": "error",
        "useLiteralKeys": "warn"
      },
      "correctness": {
        "noChildrenProp": "warn",
        "noConstAssign": "error",
        "noConstantCondition": "warn",
        "noConstructorReturn": "error",
        "noEmptyCharacterClassInRegex": "error",
        "noEmptyPattern": "error",
        "noGlobalObjectCalls": "error",
        "noInnerDeclarations": "warn",
        "noInvalidConstructorSuper": "error",
        "noInvalidNewBuiltin": "error",
        "noInvalidUseBeforeDeclaration": "warn",
        "noNewSymbol": "error",
        "noNonoctalDecimalEscape": "error",
        "noPrecisionLoss": "error",
        "noRenderReturnValue": "warn",
        "noSelfAssign": "error",
        "noSetterReturn": "error",
        "noStringCaseMismatch": "error",
        "noSwitchDeclarations": "warn",
        "noUndeclaredVariables": "off",
        "noUnnecessaryContinue": "error",
        "noUnreachable": "error",
        "noUnreachableSuper": "error",
        "noUnsafeFinally": "error",
        "noUnsafeOptionalChaining": "error",
        "noUnusedVariables": "warn",
        "noVoidElementsWithChildren": "error",
        "noVoidTypeReturn": "error",
        "useExhaustiveDependencies": "warn",
        "useHookAtTopLevel": "error",
        "useIsNan": "error",
        "useJsxKeyInIterable": "error"
      },
      "performance": {
        "noAccumulatingSpread": "error"
      },
      "security": {
        "noDangerouslySetInnerHtml": "warn",
        "noGlobalEval": "error"
      },
      "style": {
        "noArguments": "warn",
        "noCommaOperator": "error",
        "noInferrableTypes": "error",
        "noNamespace": "error",
        "noNonNullAssertion": "warn",
        "noVar": "warn",
        "useAsConstAssertion": "error",
        "useConst": "warn",
        "useDefaultParameterLast": "error",
        "useEnumInitializers": "error",
        "useExportType": "error",
        "useImportType": "error",
        "useShorthandFunctionType": "error",
        "useSingleVarDeclarator": "error",
        "useTemplate": "error"
      },
      "suspicious": {
        "noArrayIndexKey": "warn",
        "noAssignInExpressions": "error",
        "noAsyncPromiseExecutor": "error",
        "noCatchAssign": "error",
        "noClassAssign": "error",
        "noCommentText": "error",
        "noCompareNegZero": "error",
        "noConfusingLabels": "error",
        "noConfusingVoidType": "error",
        "noConstEnum": "error",
        "noControlCharactersInRegex": "error",
        "noDebugger": "warn",
        "noDoubleEquals": "warn",
        "noDuplicateCase": "error",
        "noDuplicateClassMembers": "error",
        "noDuplicateJsxProps": "error",
        "noDuplicateObjectKeys": "error",
        "noDuplicateParameters": "error",
        "noDuplicateTestHooks": "error",
        "noExplicitAny": "warn",
        "noExportsInTest": "error",
        "noExtraNonNullAssertion": "error",
        "noFallthroughSwitchClause": "error",
        "noFunctionAssign": "error",
        "noGlobalAssign": "error",
        "noGlobalIsFinite": "error",
        "noGlobalIsNan": "error",
        "noImportAssign": "error",
        "noMisleadingCharacterClass": "error",
        "noMisleadingInstantiator": "error",
        "noMisrefactoredShorthandAssign": "error",
        "noPrototypeBuiltins": "warn",
        "noRedeclare": "error",
        "noRedundantUseStrict": "off",
        "noSelfCompare": "error",
        "noShadowRestrictedNames": "error",
        "noSuspiciousSemicolonInJsx": "error",
        "noThenProperty": "error",
        "noUnsafeNegation": "error",
        "useDefaultSwitchClauseLast": "error",
        "useGetterReturn": "error",
        "useIsArray": "error",
        "useNamespaceKeyword": "error",
        "useValidTypeof": "error"
      },
      "nursery": {
        "useSortedClasses": {
          "level": "error",
          "fix": "unsafe",
          "options": {
            "attributes": ["className", "tw"],
            "functions": ["tw"]
          }
        }
      }
    }
  },
  "formatter": {
    "enabled": true,
    "formatWithErrors": true,
    "indentStyle": "space",
    "indentWidth": 2,
    "lineEnding": "lf",
    "lineWidth": 120,
    "attributePosition": "auto"
  },
  "organizeImports": {
    "enabled": true
  },
  "javascript": {
    "globals": [],
    "parser": {
      "unsafeParameterDecoratorsEnabled": true
    },
    "jsxRuntime": "reactClassic",
    "formatter": {
      "quoteStyle": "single",
      "jsxQuoteStyle": "double",
      "quoteProperties": "asNeeded",
      "trailingCommas": "all",
      "semicolons": "always",
      "arrowParentheses": "always",
      "bracketSpacing": true,
      "bracketSameLine": false,
      "attributePosition": "auto"
    }
  },
  "json": {
    "parser": {
      "allowComments": true
    },
    "formatter": {
      "trailingCommas": "none",
      "indentStyle": "space",
      "indentWidth": 2
    }
  },
  "overrides": [
    {
      "include": ["package.json", "_package.json"],
      "formatter": {
        "lineWidth": 1
      }
    }
  ]
}

Playground link

https://biomejs.dev/playground/?bracketSameLine=true&lintRules=all&code=aQBtAHAAbwByAHQAIABSAGUAYQBjAHQAIABmAHIAbwBtACAAJwByAGUAYQBjAHQAJwA7AAoACgBjAG8AbgBzAHQAIABBAHAAcAAgAD0AIAAoACkAIAA9AD4AIAB7AAoAIAAgAGMAbwBuAHMAdAAgAGwAaQBzAHQAVAB5AHAAZQAgAD0AIAAnAHAAaQBjAHQAdQByAGUALQBjAGEAcgBkACcAOwAKACAAIAByAGUAdAB1AHIAbgAgADwAZABpAHYAIABjAGwAYQBzAHMATgBhAG0AZQA9AHsAYABtAC0AdwBlAGIAcwBlAHMAcwBpAG8AbgAtAHUAcABsAG8AYQBkAGwAaQBzAHQAIABmAGkAcwBoAGQALQB1AHAAbABvAGEAZAAtAGwAaQBzAHQALQAkAHsAbABpAHMAdABUAHkAcABlAH0AYAB9AD4AYQBwAHAAPAAvAGQAaQB2AD4AOwAKAH0AOwAKAAoAZQB4AHAAbwByAHQAIABkAGUAZgBhAHUAbAB0ACAAQQBwAHAAOwAKAA%3D%3D

Code of Conduct

ematipico commented 3 months ago

Providing a fix for this use case will be extremely challenging, almost impossible.

What's the expected and correct fix?

hangaoke1 commented 3 months ago

Maybe

 <div className={`fishd-upload-list-${listType} m-websession-uploadlist`}>app</div>

But Now it break fishd-upload-list-${listType}

hangaoke1 commented 3 months ago

image

I think it maybe need handle JsTemplateElementList but unfortunately I am just start to learn

ematipico commented 3 months ago

The rule does handle template literals, however there's a limitation of what the rule can do and that's one of them.

hangaoke1 commented 3 months ago

The rule does handle template literals, however there's a limitation of what the rule can do and that's one of them.

Hello buddy, I think it is possible by dealing with JsTemplateExpression

1、Extract the template string content from JsTemplateExpression 2、Sort string content 3、Rebuild JsTemplateExpression with make::js_template_expression (Perhaps biome provides an easier way to generate expression nodes from string) 4、Replace node in rule action

ematipico commented 3 months ago

That's not what I'm talking about. Can the tailwind plugin fix your case?

hangaoke1 commented 3 months ago

That's not what I'm talking about. Can the tailwind plugin fix your case?

Test in prettier-plugin-tailwindcss

image

It has special logic in sortTemplateLiteraland fishd-upload-list- will be filter

https://github.com/tailwindlabs/prettier-plugin-tailwindcss/blob/main/src/index.ts#L538

damianobarbati commented 3 months ago

@ematipico sorry but I can't understand if tailwind sorting of classes is enabled and handled out of the box when using Biome. In my monorepo project I don't see it working, but from the convos here on github (like this one) I'm assuming yes.

I also tried moving tailwind.config.ts from the /package/webapp folder into /root folder but nothing.

Can you point me out to relevant documentation? I only found this https://biomejs.dev/linter/rules/use-sorted-classes/#important-notes.

ematipico commented 2 months ago

This rule has been flagged with an unsafe fix, hence it's only fixable via CLI using the --unsafe option, or using the "Quick fix" pop up that VSCode provides.

You're free to change the severity of the fix. The lint rule page provides useful links at the bottom of the page. One of them is this one: https://biomejs.dev/linter/#configure-the-rule-fix

I advise you to read more about how our linter works, it's different from ESLint

damianobarbati commented 2 months ago

@ematipico I managed to have the sort working, marking as "safe" and letting webstorm handling it on save as I had with eslint, grazie!

As reference for others:

{
  "$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
  "organizeImports": {
    "enabled": true
  },
  "formatter": {
    "indentStyle": "space",
    "indentWidth": 2,
    "lineWidth": 160,
    "lineEnding": "lf"
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true,
      "a11y": {
        "recommended": false
      },
      "suspicious": {
        "recommended": false
      },
      "nursery": {
        "useSortedClasses": {
          "level": "error",
          "fix": "safe",
          "options": {
            "attributes": ["className"],
            "functions": ["cx"]
          }
        }
      }
    }
  }
}