eslint-plugin-react icon indicating copy to clipboard operation
eslint-plugin-react copied to clipboard

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

Open CzarOfScripts opened this issue 2 years ago • 9 comments

Is there an existing issue for this?

  • [X] I have searched the existing issues and my issue is unique
  • [X] My issue appears in the command-line and not only in the text editor

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

CzarOfScripts avatar Nov 20 '23 02:11 CzarOfScripts

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.

ljharb avatar Nov 20 '23 13:11 ljharb

@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.

CzarOfScripts avatar Nov 20 '23 20:11 CzarOfScripts

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.

ljharb avatar Nov 20 '23 20:11 ljharb

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.

CzarOfScripts avatar Nov 20 '23 21:11 CzarOfScripts

Compactness can often hurt readability.

ljharb avatar Nov 20 '23 22:11 ljharb

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>

CzarOfScripts avatar Jan 10 '24 01:01 CzarOfScripts

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?

ljharb avatar Jan 10 '24 01:01 ljharb

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 } }
			}
		]
	}
}

CzarOfScripts avatar Jan 10 '24 15:01 CzarOfScripts

@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?

ljharb avatar May 31 '24 23:05 ljharb