jsx_closing_bracket_location

package
v0.5.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 14, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var JsxClosingBracketLocationRule = rule.Rule{
	Name: "react/jsx-closing-bracket-location",
	Run: func(ctx rule.RuleContext, options any) rule.RuleListeners {
		opts := parseOptions(options)

		check := func(node *ast.Node) {
			var tagName *ast.Node
			var attrs []*ast.Node
			var selfClosing bool

			switch node.Kind {
			case ast.KindJsxOpeningElement:
				opening := node.AsJsxOpeningElement()
				tagName = opening.TagName
				if opening.Attributes != nil {
					jsxAttrs := opening.Attributes.AsJsxAttributes()
					if jsxAttrs.Properties != nil {
						attrs = jsxAttrs.Properties.Nodes
					}
				}
				selfClosing = false
			case ast.KindJsxSelfClosingElement:
				self := node.AsJsxSelfClosingElement()
				tagName = self.TagName
				if self.Attributes != nil {
					jsxAttrs := self.Attributes.AsJsxAttributes()
					if jsxAttrs.Properties != nil {
						attrs = jsxAttrs.Properties.Nodes
					}
				}
				selfClosing = true
			default:
				return
			}

			text := ctx.SourceFile.Text()
			lineStarts := ctx.SourceFile.ECMALineMap()

			elemTrimmed := utils.TrimNodeTextRange(ctx.SourceFile, node)
			openingPos := elemTrimmed.Pos()
			elemEnd := elemTrimmed.End()

			gtPos := elemEnd - 1
			for gtPos > openingPos && gtPos < len(text) && text[gtPos] != '>' {
				gtPos--
			}
			if gtPos < 0 || gtPos >= len(text) || text[gtPos] != '>' {
				return
			}

			closingPos := gtPos
			if selfClosing {
				slash := gtPos - 1
				for slash > openingPos && (text[slash] == ' ' || text[slash] == '\t' || text[slash] == '\n' || text[slash] == '\r') {
					slash--
				}
				if slash <= openingPos || text[slash] != '/' {
					return
				}
				closingPos = slash
			}

			openingLine := scanner.ComputeLineOfPosition(lineStarts, openingPos)
			openingLineStart := int(lineStarts[openingLine])
			openingColumn := reactutil.UTF16Length(text[openingLineStart:openingPos])

			closingLine := scanner.ComputeLineOfPosition(lineStarts, closingPos)
			closingLineStart := int(lineStarts[closingLine])
			closingColumn := reactutil.UTF16Length(text[closingLineStart:closingPos])

			tagTrimmed := utils.TrimNodeTextRange(ctx.SourceFile, tagName)
			tagLine := scanner.ComputeLineOfPosition(lineStarts, tagTrimmed.Pos())

			openingStartIndent := leadingWhitespace(text, openingLineStart)

			info := tokenInfo{
				openingPos:      openingPos,
				openingLine:     openingLine,
				openingColumn:   openingColumn,
				closingPos:      closingPos,
				closingLine:     closingLine,
				closingColumn:   closingColumn,
				tagLine:         tagLine,
				openingStartCol: reactutil.UTF16Length(openingStartIndent),
				openTab:         openingLineStart < len(text) && text[openingLineStart] == '\t',
				closeTab:        closingLineStart < len(text) && text[closingLineStart] == '\t',
				selfClosing:     selfClosing,
			}

			if len(attrs) > 0 {
				lastProp := attrs[len(attrs)-1]
				lpTrimmed := utils.TrimNodeTextRange(ctx.SourceFile, lastProp)
				lpStart := lpTrimmed.Pos()
				lpEnd := lpTrimmed.End()
				lpFirstLine := scanner.ComputeLineOfPosition(lineStarts, lpStart)
				lpLastLine := scanner.ComputeLineOfPosition(lineStarts, lpEnd-1)
				lpLineStart := int(lineStarts[lpFirstLine])
				lpColumn := reactutil.UTF16Length(text[lpLineStart:lpStart])
				info.hasLastProp = true
				info.lastPropEnd = lpEnd
				info.lastPropFirstLine = lpFirstLine
				info.lastPropLastLine = lpLastLine
				info.lastPropColumn = lpColumn
			}

			expectedLocation, disabled := getExpectedLocation(info, opts)
			if disabled {
				return
			}

			usingSameIndentation := true
			if expectedLocation == "tag-aligned" {
				usingSameIndentation = info.openTab == info.closeTab
			}

			if hasCorrectLocation(info, expectedLocation) && usingSameIndentation {
				return
			}

			locationDesc, ok := locationMessages[expectedLocation]
			if !ok {
				return
			}

			details := ""
			expectedNextLine := info.hasLastProp && info.lastPropLastLine == info.closingLine
			correctColumn, hasCorrectColumn := getCorrectColumn(info, expectedLocation)
			if hasCorrectColumn {
				if expectedNextLine {
					details = fmt.Sprintf(" (expected column %d on the next line)", correctColumn+1)
				} else {
					details = fmt.Sprintf(" (expected column %d)", correctColumn+1)
				}
			}

			fix := buildFix(text, info, expectedLocation, expectedNextLine, correctColumn, lineStarts, elemEnd, tagTrimmed.End(), selfClosing)

			msg := rule.RuleMessage{
				Id:          "bracketLocation",
				Description: fmt.Sprintf("The closing bracket must be %s%s", locationDesc, details),
			}

			closingEnd := closingPos + 1
			if closingEnd > len(text) {
				closingEnd = len(text)
			}
			reportRange := core.NewTextRange(closingPos, closingEnd)

			if fix != nil {
				ctx.ReportRangeWithFixes(reportRange, msg, *fix)
			} else {
				ctx.ReportRange(reportRange, msg)
			}
		}

		return rule.RuleListeners{
			ast.KindJsxOpeningElement:     check,
			ast.KindJsxSelfClosingElement: check,
		}
	},
}

JsxClosingBracketLocationRule enforces the closing bracket location for JSX multiline elements.

Functions

This section is empty.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL