object_shorthand

package
v0.5.2 Latest Latest
Warning

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

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

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ObjectShorthandRule = rule.Rule{
	Name: "object-shorthand",
	Run: func(ctx rule.RuleContext, optionsAny any) rule.RuleListeners {
		opts := parseOptions(optionsAny)
		sourceText := ctx.SourceFile.Text()

		applyToMethods := opts.apply == modeMethods || opts.apply == modeAlways
		applyToProps := opts.apply == modeProperties || opts.apply == modeAlways
		applyNever := opts.apply == modeNever
		applyConsistent := opts.apply == modeConsistent
		applyConsistentAsNeeded := opts.apply == modeConsistentAsNeeded

		lexicalScopeStack := []map[*ast.Node]bool{{}}
		arrowsWithLexicalIdentifiers := map[*ast.Node]bool{}

		enterScope := func() {
			lexicalScopeStack = append(lexicalScopeStack, map[*ast.Node]bool{})
		}
		exitScope := func() {
			if len(lexicalScopeStack) > 0 {
				lexicalScopeStack = lexicalScopeStack[:len(lexicalScopeStack)-1]
			}
		}
		markCurrentLexical := func() {
			if len(lexicalScopeStack) == 0 {
				return
			}
			for arrow := range lexicalScopeStack[len(lexicalScopeStack)-1] {
				arrowsWithLexicalIdentifiers[arrow] = true
			}
		}

		msgExpectedPropertyShorthand := rule.RuleMessage{
			Id:          "expectedPropertyShorthand",
			Description: "Expected property shorthand.",
		}
		msgExpectedMethodShorthand := rule.RuleMessage{
			Id:          "expectedMethodShorthand",
			Description: "Expected method shorthand.",
		}
		msgExpectedPropertyLongform := rule.RuleMessage{
			Id:          "expectedPropertyLongform",
			Description: "Expected longform property syntax.",
		}
		msgExpectedMethodLongform := rule.RuleMessage{
			Id:          "expectedMethodLongform",
			Description: "Expected longform method syntax.",
		}
		msgExpectedLiteralMethodLongform := rule.RuleMessage{
			Id:          "expectedLiteralMethodLongform",
			Description: "Expected longform method syntax for string literal keys.",
		}
		msgExpectedAllPropertiesShorthanded := rule.RuleMessage{
			Id:          "expectedAllPropertiesShorthanded",
			Description: "Expected shorthand for all properties.",
		}
		msgUnexpectedMix := rule.RuleMessage{
			Id:          "unexpectedMix",
			Description: "Unexpected mix of shorthand and non-shorthand properties.",
		}

		keyText := func(nameNode *ast.Node) string {
			if nameNode == nil {
				return ""
			}
			r := utils.TrimNodeTextRange(ctx.SourceFile, nameNode)
			return sourceText[r.Pos():r.End()]
		}

		fixShorthandProperty := func(node *ast.Node, valueName string) []rule.RuleFix {
			if hasCommentsInsideText(sourceText, node.Pos(), node.End()) {
				return nil
			}
			return []rule.RuleFix{rule.RuleFixReplace(ctx.SourceFile, node, valueName)}
		}

		fixPropertyToLongform := func(node *ast.Node) []rule.RuleFix {
			sp := node.AsShorthandPropertyAssignment()
			if sp == nil {
				return nil
			}
			name := sp.Name()
			if name == nil || name.Kind != ast.KindIdentifier {
				return nil
			}
			ident := name.AsIdentifier().Text
			return []rule.RuleFix{rule.RuleFixInsertAfter(name, ": "+ident)}
		}

		fixMethodToLongform := func(node *ast.Node) []rule.RuleFix {
			method := node.AsMethodDeclaration()
			if method == nil || method.Body == nil {
				return nil
			}

			isAsync := ast.HasSyntacticModifier(node, ast.ModifierFlagsAsync)
			isGenerator := method.AsteriskToken != nil

			nameNode := method.Name()
			if nameNode == nil {
				return nil
			}

			nodeRange := utils.TrimNodeTextRange(ctx.SourceFile, node)
			nameRange := utils.TrimNodeTextRange(ctx.SourceFile, nameNode)

			keyStr := keyText(nameNode)
			header := "function"
			if isAsync {
				header = "async function"
			}
			if isGenerator {
				header += "*"
			}

			replaceRange := core.NewTextRange(nodeRange.Pos(), nameRange.End())
			return []rule.RuleFix{rule.RuleFixReplaceRange(replaceRange, keyStr+": "+header)}
		}

		fixFunctionToMethod := func(node *ast.Node) []rule.RuleFix {
			pa := node.AsPropertyAssignment()
			fn := propertyValue(pa)
			if fn == nil || fn.Kind != ast.KindFunctionExpression {
				return nil
			}
			nameNode := pa.Name()
			if nameNode == nil {
				return nil
			}

			keyRange := utils.TrimNodeTextRange(ctx.SourceFile, nameNode)
			fnRange := utils.TrimNodeTextRange(ctx.SourceFile, fn)
			if utils.HasCommentsInRange(ctx.SourceFile, core.NewTextRange(keyRange.End(), fnRange.Pos())) {
				return nil
			}

			fe := fn.AsFunctionExpression()
			if fe == nil || fe.Body == nil {
				return nil
			}
			isAsync := ast.HasSyntacticModifier(fn, ast.ModifierFlagsAsync)
			isGenerator := fe.AsteriskToken != nil

			prefix := ""
			if isAsync {
				prefix += "async "
			}
			if isGenerator {
				prefix += "*"
			}

			// The tail we want to keep is everything after `function` (and
			// `*` when generator). For generators, AsteriskToken gives us a
			// precise end position; otherwise we scan for the `function`
			// keyword past any leading modifiers (e.g. `async`).
			var headerEnd int
			if isGenerator {
				headerEnd = fe.AsteriskToken.End()
			} else {
				kwEnd, ok := positionAfterFunctionKeyword(ctx.SourceFile, fn)
				if !ok {
					return nil
				}
				headerEnd = kwEnd
			}

			nodeRange := utils.TrimNodeTextRange(ctx.SourceFile, node)
			keyStr := keyText(nameNode)
			tail := sourceText[headerEnd:fnRange.End()]

			return []rule.RuleFix{rule.RuleFixReplaceRange(
				core.NewTextRange(nodeRange.Pos(), nodeRange.End()),
				prefix+keyStr+tail,
			)}
		}

		fixArrowToMethod := func(node *ast.Node) []rule.RuleFix {
			pa := node.AsPropertyAssignment()
			fn := propertyValue(pa)
			if fn == nil || fn.Kind != ast.KindArrowFunction {
				return nil
			}
			arrow := fn.AsArrowFunction()
			if arrow == nil || arrow.Body == nil {
				return nil
			}
			if arrow.Body.Kind != ast.KindBlock {
				return nil
			}

			nameNode := pa.Name()
			if nameNode == nil {
				return nil
			}
			keyRange := utils.TrimNodeTextRange(ctx.SourceFile, nameNode)
			fnRange := utils.TrimNodeTextRange(ctx.SourceFile, fn)
			if utils.HasCommentsInRange(ctx.SourceFile, core.NewTextRange(keyRange.End(), fnRange.Pos())) {
				return nil
			}

			isAsync := ast.HasSyntacticModifier(fn, ast.ModifierFlagsAsync)
			if arrow.EqualsGreaterThanToken == nil {
				return nil
			}

			paramsStart := fnRange.Pos()
			if mods := arrow.Modifiers(); isAsync && mods != nil && len(mods.Nodes) > 0 {

				paramsStart = mods.End()
			}

			arrowTokenPos := arrow.EqualsGreaterThanToken.Pos()
			arrowTokenEnd := arrow.EqualsGreaterThanToken.End()

			paramsText := strings.TrimSpace(sourceText[paramsStart:arrowTokenPos])
			bodyText := strings.TrimLeft(sourceText[arrowTokenEnd:fnRange.End()], " \t")

			if len(paramsText) == 0 || paramsText[0] != '(' {
				paramsText = "(" + paramsText + ")"
			}

			prefix := ""
			if isAsync {
				prefix = "async "
			}

			nodeRange := utils.TrimNodeTextRange(ctx.SourceFile, node)
			keyStr := keyText(nameNode)
			replacement := prefix + keyStr + paramsText + " " + bodyText

			return []rule.RuleFix{rule.RuleFixReplaceRange(
				core.NewTextRange(nodeRange.Pos(), nodeRange.End()),
				replacement,
			)}
		}

		reportMix := func(obj *ast.Node) {
			ctx.ReportNode(obj, msgUnexpectedMix)
		}
		reportAllShorthand := func(obj *ast.Node) {
			ctx.ReportNode(obj, msgExpectedAllPropertiesShorthanded)
		}

		checkConsistency := func(obj *ast.Node, checkRedundancy bool) {
			ol := obj.AsObjectLiteralExpression()
			if ol == nil || ol.Properties == nil {
				return
			}
			var considered []*ast.Node
			for _, p := range ol.Properties.Nodes {
				if canHaveShorthand(p) {
					considered = append(considered, p)
				}
			}
			if len(considered) == 0 {
				return
			}
			shorthandCount := 0
			for _, p := range considered {
				if isShorthandKind(classify(p)) {
					shorthandCount++
				}
			}
			if shorthandCount == len(considered) {
				return
			}
			if shorthandCount > 0 {
				reportMix(obj)
				return
			}
			if !checkRedundancy {
				return
			}

			allRedundant := true
			for _, p := range considered {
				if !isRedundantLongform(p) {
					allRedundant = false
					break
				}
			}
			if allRedundant {
				reportAllShorthand(obj)
			}
		}

		handleProperty := func(node *ast.Node) {

			if node.Parent == nil || node.Parent.Kind != ast.KindObjectLiteralExpression {
				return
			}

			kind := classify(node)
			if kind == propKindOther {
				return
			}

			isConcise := kind == propKindShorthandProp || kind == propKindShorthandMethod

			if node.Kind == ast.KindPropertyAssignment {
				pa := node.AsPropertyAssignment()
				if pa != nil && pa.Name() != nil && pa.Name().Kind == ast.KindComputedPropertyName {
					if pa.Initializer != nil &&
						pa.Initializer.Kind != ast.KindFunctionExpression &&
						pa.Initializer.Kind != ast.KindArrowFunction {
						return
					}
				}
			}

			if isConcise {

				if kind == propKindShorthandMethod {
					method := node.AsMethodDeclaration()
					var keyNode *ast.Node
					if method != nil {
						keyNode = method.Name()
					}
					if applyNever || (opts.avoidQuotes && isStringLiteralKey(keyNode)) {
						msg := msgExpectedMethodLongform
						if !applyNever && opts.avoidQuotes {
							msg = msgExpectedLiteralMethodLongform
						}
						if fixes := fixMethodToLongform(node); fixes != nil {
							ctx.ReportNodeWithFixes(node, msg, fixes...)
						} else {
							ctx.ReportNode(node, msg)
						}
					}
				} else if applyNever {

					if fixes := fixPropertyToLongform(node); fixes != nil {
						ctx.ReportNodeWithFixes(node, msgExpectedPropertyLongform, fixes...)
					} else {
						ctx.ReportNode(node, msgExpectedPropertyLongform)
					}
				}
				return
			}

			pa := node.AsPropertyAssignment()
			value := propertyValue(pa)
			if value == nil {
				return
			}
			valueKind := value.Kind

			if applyToMethods && (valueKind == ast.KindFunctionExpression || valueKind == ast.KindArrowFunction) {
				if valueKind == ast.KindFunctionExpression {

					if value.AsFunctionExpression().Name() != nil {
						return
					}
				}

				nameNode := pa.Name()
				if opts.ignoreConstructors && nameNode != nil && nameNode.Kind == ast.KindIdentifier {
					if utils.IsConstructorName(nameNode.AsIdentifier().Text) {
						return
					}
				}
				if shouldIgnoreMethodName(&opts, nameNode) {
					return
				}
				if opts.avoidQuotes && isStringLiteralKey(nameNode) {
					return
				}

				if valueKind == ast.KindFunctionExpression {
					if fixes := fixFunctionToMethod(node); fixes != nil {
						ctx.ReportNodeWithFixes(node, msgExpectedMethodShorthand, fixes...)
					} else {
						ctx.ReportNode(node, msgExpectedMethodShorthand)
					}
					return
				}

				arrow := value.AsArrowFunction()
				if arrow == nil || arrow.Body == nil || arrow.Body.Kind != ast.KindBlock {
					return
				}
				if !opts.avoidExplicitReturnArrows {
					return
				}
				if arrowsWithLexicalIdentifiers[value] {
					return
				}
				if fixes := fixArrowToMethod(node); fixes != nil {
					ctx.ReportNodeWithFixes(node, msgExpectedMethodShorthand, fixes...)
				} else {
					ctx.ReportNode(node, msgExpectedMethodShorthand)
				}
				return
			}

			if !applyToProps {
				return
			}
			if valueKind != ast.KindIdentifier {
				return
			}
			valueIdent := value.AsIdentifier()
			if valueIdent == nil {
				return
			}
			nameNode := pa.Name()
			if nameNode == nil {
				return
			}

			switch nameNode.Kind {
			case ast.KindIdentifier:
				if nameNode.AsIdentifier().Text != valueIdent.Text {
					return
				}

				if hasJSDocTypeAnnotationInside(sourceText, node, false) {
					return
				}
				if fixes := fixShorthandProperty(node, valueIdent.Text); fixes != nil {
					ctx.ReportNodeWithFixes(node, msgExpectedPropertyShorthand, fixes...)
				} else {
					ctx.ReportNode(node, msgExpectedPropertyShorthand)
				}
			case ast.KindStringLiteral:
				if nameNode.AsStringLiteral().Text != valueIdent.Text {
					return
				}
				if opts.avoidQuotes {
					return
				}

				if hasJSDocTypeAnnotationInside(sourceText, node, true) {
					return
				}
				if fixes := fixShorthandProperty(node, valueIdent.Text); fixes != nil {
					ctx.ReportNodeWithFixes(node, msgExpectedPropertyShorthand, fixes...)
				} else {
					ctx.ReportNode(node, msgExpectedPropertyShorthand)
				}
			}
		}

		listeners := rule.RuleListeners{
			ast.KindFunctionDeclaration:                      func(n *ast.Node) { enterScope() },
			rule.ListenerOnExit(ast.KindFunctionDeclaration): func(n *ast.Node) { exitScope() },
			ast.KindFunctionExpression:                       func(n *ast.Node) { enterScope() },
			rule.ListenerOnExit(ast.KindFunctionExpression):  func(n *ast.Node) { exitScope() },
			ast.KindMethodDeclaration: func(n *ast.Node) {
				enterScope()

				handleProperty(n)
			},
			rule.ListenerOnExit(ast.KindMethodDeclaration): func(n *ast.Node) { exitScope() },
			ast.KindGetAccessor:                            func(n *ast.Node) { enterScope() },
			rule.ListenerOnExit(ast.KindGetAccessor):       func(n *ast.Node) { exitScope() },
			ast.KindSetAccessor:                            func(n *ast.Node) { enterScope() },
			rule.ListenerOnExit(ast.KindSetAccessor):       func(n *ast.Node) { exitScope() },
			ast.KindConstructor:                            func(n *ast.Node) { enterScope() },
			rule.ListenerOnExit(ast.KindConstructor):       func(n *ast.Node) { exitScope() },

			ast.KindArrowFunction: func(n *ast.Node) {
				if len(lexicalScopeStack) > 0 {
					lexicalScopeStack[len(lexicalScopeStack)-1][n] = true
				}
			},
			rule.ListenerOnExit(ast.KindArrowFunction): func(n *ast.Node) {
				if len(lexicalScopeStack) > 0 {
					delete(lexicalScopeStack[len(lexicalScopeStack)-1], n)
				}
			},

			ast.KindThisKeyword:  func(n *ast.Node) { markCurrentLexical() },
			ast.KindSuperKeyword: func(n *ast.Node) { markCurrentLexical() },
			ast.KindMetaProperty: func(n *ast.Node) {

				mp := n.AsMetaProperty()
				if mp != nil && mp.KeywordToken == ast.KindNewKeyword {
					markCurrentLexical()
				}
			},
			ast.KindIdentifier: func(n *ast.Node) {
				if !isArgumentsIdentifier(n) {
					return
				}

				if len(lexicalScopeStack) <= 1 {
					return
				}

				if isArgumentsShadowedInBlockScope(n) {
					return
				}
				markCurrentLexical()
			},

			rule.ListenerOnExit(ast.KindPropertyAssignment): func(n *ast.Node) {
				handleProperty(n)
			},
			ast.KindShorthandPropertyAssignment: func(n *ast.Node) {

				if n.Parent != nil && n.Parent.Kind != ast.KindObjectLiteralExpression {
					return
				}
				handleProperty(n)
			},

			ast.KindObjectLiteralExpression: func(n *ast.Node) {
				if applyConsistent {
					checkConsistency(n, false)
				} else if applyConsistentAsNeeded {
					checkConsistency(n, true)
				}
			},
		}

		return listeners
	},
}

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