jsx-eslint / eslint-plugin-react

React-specific linting rules for ESLint
MIT License
8.87k stars 2.76k forks source link

[Bug]: Wrong definition. jsx-closing-bracket-location #3655

Open CzarOfScripts opened 7 months ago

CzarOfScripts commented 7 months ago

Is there an existing issue for this?

Description Overview

I want to use the line-aligned value, but I don't think it works correctly if I have a props that has a hyphenated object.

Here's an example where I don't expect to see any errors:

<Box sx={{
    position: "relative",
    maxWidth: "720px",
    width: "100%",
    backgroundColor: "#26216d"
}}>
    {/* Content */}
</Box>

or

<Box sx={{
    position: "relative",
    maxWidth: "720px",
    width: "100%",
    backgroundColor: "#26216d"
}} />

I think it's because of multiline in props that I'm getting the error.

"react/jsx-closing-bracket-location": [ "error", { "nonEmpty": "line-aligned", "selfClosing": "line-aligned" } ]

Expected Behavior

I would like the code I have provided to work perfectly, for I believe this is the right way to go.

<Box sx={{
    position: "relative",
    maxWidth: "720px",
    width: "100%",
    backgroundColor: "#26216d"
}}>
    {/* Content */}
</Box>

or

<Box sx={{
    position: "relative",
    maxWidth: "720px",
    width: "100%",
    backgroundColor: "#26216d"
}} />

eslint-plugin-react version

^7.33.2

eslint version

^8.54.0

node version

v18.12.1

ljharb commented 7 months ago

The rule is expecting this, i suspect:

<Box
  sx={{
  }}
/>

the tag name and prop name shouldn’t be on the same line, if the prop is spread across multiple lines.

CzarOfScripts commented 7 months ago

@ljharb, I understand what it expects, but I don't think it's the right option. Why should I carry over if I only have one attribute, even though it's multi-line? I think there should be some kind of customization.

ljharb commented 7 months ago

Because you have more than zero attributes. The premise (also supported by the Airbnb styleguide) is that if an entire thing - in this case, a jsx element - isn’t contained within a single line, then each conceptual thing should be on its own line by itself.

We could allow the customization you want via an option, but that’s a lot of extra complexity for something that I’m convinced makes for less readable and deterministic code.

CzarOfScripts commented 7 months ago

My personal opinion: this variant looks much worse, because it takes up a lot of space. My variant is more compact and readable. If there is no such variant, it is very sad and it looks like I will have to give up this rule altogether, which I would not like.

ljharb commented 7 months ago

Compactness can often hurt readability.

CzarOfScripts commented 6 months ago

Ok, I've decided to try to go with the current format after all. But then how do I automatically move the props to a new line? Right now I get the following:

<Box sx={{
  minHeight: "100vh",
  background: `url(${ IMGBackground }) center center/cover`,
  display: "flex",
  flexDirection: "column",

  "@supports (min-height: 100dvh)":
  {
      minHeight: "100dvh"
  }
}}
>
  <Component />
</Box>
ljharb commented 6 months ago

What I'd expect is this:

<Box
  sx={{
    minHeight: "100vh",
    background: `url(${ IMGBackground }) center center/cover`,
    display: "flex",
    flexDirection: "column",

    "@supports (min-height: 100dvh)": {
      minHeight: "100dvh"
    }
  }}
>
  <Component />
</Box>

If you start with the airbnb config, it should be roughly corrected to this?

CzarOfScripts commented 6 months ago

Well, if you're judging by jsx, yes. I can provide my config.

{
    "plugins": [ "react", "@typescript-eslint", "react-hooks" ],
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "project": "./tsconfig.json"
    },
    "settings": {
        "react": { "version": "detect" }
    },
    "rules": {
        "no-restricted-imports": [
            "error",
            {
                "paths": [
                    {
                        "name": "react-i18next",
                        "importNames": [
                            "useTranslation"
                        ],
                        "message": "Please use useTranslation from next-i18next instead."
                    }
                ]
            }
        ],
        "array-bracket-newline": [ "error", "consistent" ],
        "array-bracket-spacing": [ "error", "always" ],
        "array-element-newline": [ "error", "consistent" ],
        "arrow-spacing": [ "error", { "before": true, "after": true } ],
        "block-spacing": [ "error", "always" ],
        "brace-style": [ "error", "allman", { "allowSingleLine": true } ],
        "comma-dangle": [ "error", "never" ],
        "comma-spacing": [ "error", { "before": false, "after": true } ],
        "comma-style": [ "error", "last" ],
        "computed-property-spacing": [ "error", "always" ],
        "keyword-spacing": [ "error", { "before": true, "after": true } ],
        "no-extra-semi": "error",
        "object-curly-spacing": [ "error", "always" ],
        "object-curly-newline": [ "error", { "consistent": true, "multiline": true } ],
        "object-property-newline": [ "error", { "allowAllPropertiesOnSameLine": true } ],
        "quotes": [ "error", "double", { "avoidEscape": true } ],
        "semi": "error",
        "semi-spacing": [ "error", { "before": false, "after": true } ],
        "space-before-blocks": "error",
        "space-before-function-paren": [ "error", { "asyncArrow": "always", "anonymous": "always", "named": "never" } ],
        "template-curly-spacing": [ "error", "always" ],
        "template-tag-spacing": [ "error", "never" ],
        "space-in-parens": [ "error", "never" ],
        "space-infix-ops": [ "error", { "int32Hint": false } ],
        "space-unary-ops": [ "error", { "words": true, "nonwords": false } ],
        "switch-colon-spacing": [ "error", { "after": true, "before": false } ],
        "dot-location": [ "error", "property" ],
        "dot-notation": "off",
        "eol-last": [ "error", "always" ],
        "no-multiple-empty-lines": [ "error", { "max": 1, "maxEOF": 0, "maxBOF": 0 } ],
        "no-trailing-spaces": "error",
        "func-call-spacing": [ "error", "never" ],
        "function-call-argument-newline": [ "error", "consistent" ],
        "function-paren-newline": [ "error", "multiline-arguments" ],
        "implicit-arrow-linebreak": [ "error", "beside" ],
        "indent": [ "error", "tab", { "SwitchCase": 1, "MemberExpression": 1 } ],
        "jsx-quotes": [ "error", "prefer-double" ],
        "key-spacing": [ "error", { "beforeColon": false, "afterColon": true } ],
        "padding-line-between-statements": [
            "error",
            { "blankLine": "always", "prev": [ "import", "cjs-import", "function", "block-like", "multiline-const", "multiline-let", "multiline-var" ], "next": "*" },
            { "blankLine": "any", "prev": [ "import", "cjs-import" ], "next": [ "import", "cjs-import" ] },
            { "blankLine": "always", "prev": [ "const", "let", "var" ], "next": "*" },
            { "blankLine": "any", "prev": [ "const", "let", "var" ], "next": [ "const", "let", "var", "if" ] },
            { "blankLine": "always", "prev": "*", "next": "return" }
        ],
        "max-len": [
            "error",
            {
                "code": 140,
                "tabWidth": 3,
                "ignoreUrls": true,
                "ignoreStrings": true,
                "ignoreTemplateLiterals": true,
                "ignoreRegExpLiterals": true,
                "ignoreComments": true,
                "ignoreTrailingComments": true
            }
        ],
        "multiline-ternary": [ "error", "always-multiline" ],
        "new-parens": "error",
        "newline-per-chained-call": [ "error", { "ignoreChainWithDepth": 3 } ],
        "no-mixed-spaces-and-tabs": [ "error", "smart-tabs" ],
        "no-multi-spaces": "error",
        "no-whitespace-before-property": "error",
        "quote-props": [ "error", "as-needed" ],
        "rest-spread-spacing": [ "error", "never" ],
        "vars-on-top": "error",
        "no-unneeded-ternary": "error",
        "no-console": [ "error", { "allow": [ "warn", "error", "debug", "info" ] } ],
        "no-else-return": "error",
        "func-style": [ "error", "declaration" ],
        "default-param-last": "off",
        "use-isnan": "error",
        // React
        "react/destructuring-assignment": [ "error", "always" ],
        "react/function-component-definition": [
            "error",
            { "namedComponents": "function-declaration", "unnamedComponents": "arrow-function" }
        ],
        "react/hook-use-state": [ "off", { "allowDestructuredState": true } ],
        "react/jsx-boolean-value": [ "error", "never" ],
        "react/jsx-closing-bracket-location": [ "error", { "nonEmpty": "line-aligned", "selfClosing": "line-aligned" } ],
        "react/jsx-closing-tag-location": [ "error" ],
        "react/jsx-curly-brace-presence": [ "error", { "props": "never", "children": "never", "propElementValues": "always" } ],
        "react/jsx-equals-spacing": [ "error", "never" ],
        "react/jsx-filename-extension": [ "error", { "extensions": [ ".jsx", ".tsx" ] } ],
        "react/jsx-fragments": [ "error", "syntax" ],
        "react/jsx-first-prop-new-line": [ "error", "multiline-multiprop" ],
        "react/jsx-indent": [ "error", "tab", { "indentLogicalExpressions": true, "checkAttributes": true } ],
        "react/jsx-indent-props": [ "error", "tab" ],
        "react/jsx-key": [ "off" ],
        "react/jsx-max-props-per-line": [ "error", { "maximum": { "single": 5, "multi": 1 } } ],
        "react/jsx-no-constructed-context-values": [ "error" ],
        "react/jsx-no-duplicate-props": [ "error" ],
        "react/jsx-no-leaked-render": [ "error" ],
        "react/jsx-no-useless-fragment": [ "error", { "allowExpressions": true } ],
        "react/jsx-pascal-case": [ "error", { "allowNamespace": true } ],
        "react/jsx-props-no-multi-spaces": [ "error" ],
        "react/jsx-tag-spacing": [
            "error",
            {
                "closingSlash": "never",
                "beforeSelfClosing": "always",
                "afterOpening": "never",
                "beforeClosing": "never"
            }
        ],
        "react/jsx-wrap-multilines": [
            "error",
            {
                "declaration": "parens",
                "assignment": "parens",
                "return": "parens",
                "arrow": "parens-new-line",
                "condition": "ignore",
                "logical": "ignore",
                "prop": "ignore"
            }
        ],
        "react/self-closing-comp": [
            "error",
            {
                "component": true,
                "html": true
            }
        ],
        "react/style-prop-object": [ "error" ],
        // Typescript
        "@typescript-eslint/adjacent-overload-signatures": "error",
        "@typescript-eslint/await-thenable": "error",
        "@typescript-eslint/consistent-generic-constructors": [ "error", "constructor" ],
        "@typescript-eslint/consistent-indexed-object-style": [ "error", "record" ],
        "@typescript-eslint/default-param-last": "error",
        "@typescript-eslint/dot-notation": "error",
        "@typescript-eslint/explicit-member-accessibility": [ "error", { "accessibility": "no-public" } ],
        "@typescript-eslint/member-delimiter-style": [ "error", { "singleline": { "requireLast": true } } ],
        "@typescript-eslint/method-signature-style": [ "error", "method" ],
        "@typescript-eslint/no-base-to-string": "error",
        "@typescript-eslint/no-confusing-non-null-assertion": "error",
        "@typescript-eslint/no-duplicate-enum-values": "error",
        "@typescript-eslint/no-duplicate-type-constituents": "error",
        "@typescript-eslint/no-empty-function": "error",
        "@typescript-eslint/no-empty-interface": [ "off", { "allowSingleExtends": true } ],
        "@typescript-eslint/no-for-in-array": "error",
        "@typescript-eslint/prefer-for-of": "error",
        "@typescript-eslint/prefer-optional-chain": "error",
        "@typescript-eslint/prefer-reduce-type-parameter": "error",
        "@typescript-eslint/prefer-string-starts-ends-with": "error",
        "@typescript-eslint/require-array-sort-compare": "error",
        "@typescript-eslint/type-annotation-spacing": [
            "error",
            {
                "before": false,
                "after": true,
                "overrides": { "arrow": { "before": true, "after": true } }
            }
        ]
    }
}
ljharb commented 1 month ago

@CzarOfScripts sorry for the long delay. if you autofix with the full airbnb config, what code are you left with that still requires manual fixing, and what are the errors?