java

package
v0.0.0-...-22a4c2b Latest Latest
Warning

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

Go to latest
Published: Apr 1, 2024 License: MIT Imports: 13 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var AlreadyDefinedError = lib.ErrorTemplate{
	Name:              "AlreadyDefinedError",
	Pattern:           comptimeErrorPattern(`variable (?P<variable>\S+) is already defined in method (?P<symbolSignature>.+)`),
	StackTracePattern: comptimeStackTracePattern,
	OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) {
		aCtx := alreadyDefinedErrorCtx{}
		rootNode := m.Document.RootNode()
		rawQuery := parseSymbolSignature(cd.Variables["symbolSignature"])
		pos := m.ErrorNode.StartPos

		for q := rootNode.Query("(class_declaration) @class"); q.Next(); {
			classNode := q.CurrentNode()
			pointA := classNode.StartPoint()
			pointB := classNode.EndPoint()
			if uint32(pos.Line) >= pointA.Row+1 && uint32(pos.Line) <= pointB.Row+1 {
				aCtx.NearestClass = classNode
				break
			}
		}

		for q := aCtx.NearestClass.Query(rawQuery); q.Next(); {
			aCtx.NearestMethod = q.CurrentNode()
			break
		}

		m.Context = aCtx
	},
	OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) {
		gen.Add("This error occurs when you try to declare a variable with a name that is already in use within the same scope.")
	},
	OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) {
		gen.Add("Remove redeclaration", func(s *lib.BugFixSuggestion) {
			s.AddStep("To resolve the already defined error, remove the attempt to redeclare the variable '%s'.", cd.Variables["variable"]).
				AddFix(lib.FixSuggestion{
					NewText:       "",
					StartPosition: lib.Position{Line: cd.MainError.Nearest.StartPosition().Line, Column: 0},
					EndPosition:   cd.MainError.Nearest.EndPosition(),
					Description:   fmt.Sprintf("Since '%s' is already declared earlier in the method, you don't need to declare it again.", cd.Variables["variable"]),
				})
		})

		gen.Add("Assign a new value", func(s *lib.BugFixSuggestion) {
			dupeVarType := cd.MainError.Nearest.ChildByFieldName("type")
			dupeVarDeclarator := cd.MainError.Nearest.ChildByFieldName("declarator")

			s.AddStep("If you intended to change the value of '%s', you can simply assign a new value to the existing variable.", cd.Variables["variable"]).
				AddFix(lib.FixSuggestion{
					NewText:       "",
					StartPosition: dupeVarType.StartPosition(),
					EndPosition:   dupeVarDeclarator.StartPosition(),
					Description:   fmt.Sprintf("This way, you update the value of '%s' without redeclaring it.", cd.Variables["variable"]),
				})
		})
	},
}
View Source
var ArithmeticException = lib.ErrorTemplate{
	Name:    "ArithmeticException",
	Pattern: runtimeErrorPattern("java.lang.ArithmeticException", "(?P<reason>.+)"),
	OnAnalyzeErrorFn: func(cd *lib.ContextData, err *lib.MainError) {
		ctx := arithExceptionCtx{}
		reason := cd.Variables["reason"]
		query := ""

		switch reason {
		case "/ by zero":
			ctx.kind = dividedByZero
			query = "(_) \"/\" ((decimal_integer_literal) @literal (#eq? @literal \"0\"))"
		case "Non-terminating decimal expansion; no exact representable decimal result.":
			ctx.kind = nonTerminatingDecimal
			query = "((method_invocation) @methodCall (#eq? @methodCall \".divide\"))"
		default:
			ctx.kind = unknown
		}

		if len(query) != 0 {
			for q := err.Nearest.Query(query); q.Next(); {
				err.Nearest = q.CurrentNode()
				break
			}
		}

		err.Context = ctx
	},
	OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) {
		ctx := cd.MainError.Context.(arithExceptionCtx)
		switch ctx.kind {
		case dividedByZero:
			gen.Add("This error is raised when you try to perform arithmetic operations that are not mathematically possible, such as division by zero.")
		case nonTerminatingDecimal:
			gen.Add("This error is raised when dividing two `BigDecimal` numbers, and the division operation results in a non-terminating decimal expansion, meaning the division produces a non-repeating and non-terminating decimal.")
		case unknown:
			gen.Add("You just encountered an unknown `ArithmeticException` error of which we cannot explain to you properly.")
		}
	},
	OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) {
		ctx := cd.MainError.Context.(arithExceptionCtx)
		switch ctx.kind {
		case dividedByZero:
			gen.Add("Avoid dividing by zero.", func(s *lib.BugFixSuggestion) {
				s.AddStep("To fix the 'ArithmeticException: / by zero', you need to ensure you are not dividing by zero, which is mathematically undefined.").
					AddFix(lib.FixSuggestion{
						NewText:       "1",
						Description:   "This adjustment replaces the division by zero with a value that is not zero, ensuring the operation is valid. Division by zero is mathematically undefined, causing an 'ArithmeticException'. By changing the denominator to a non-zero value, you prevent the error.",
						StartPosition: cd.MainError.Nearest.StartPosition(),
						EndPosition:   cd.MainError.Nearest.EndPosition(),
					})
			})
		case nonTerminatingDecimal:
			gen.Add("Ensure precise division", func(s *lib.BugFixSuggestion) {
				s.AddStep("To fix the 'ArithmeticException: Non-terminating decimal expansion', you need to ensure the division operation is precise.").
					AddFix(lib.FixSuggestion{
						NewText:       ", RoundingMode.HALF_UP)",
						StartPosition: cd.MainError.Nearest.EndPosition(),
						EndPosition:   cd.MainError.Nearest.EndPosition(),
					})
			})

			if parent := cd.MainError.Nearest.Parent(); parent.Type() == "block" {
				gen.Add("Catch ArithmeticException", func(s *lib.BugFixSuggestion) {
					firstChild := parent.FirstNamedChild()
					lastChild := parent.LastNamedChild()

					s.AddStep("Handle the ArithmeticException by wrapping the division operation in a try-catch block to manage the potential exception and inform the user about the non-terminating result.").
						AddFix(lib.FixSuggestion{
							NewText:       "try {",
							StartPosition: firstChild.StartPosition(),
							EndPosition:   firstChild.StartPosition(),
						}).
						AddFix(lib.FixSuggestion{
							NewText:       "} catch (ArithmeticException e) {\n\tSystem.out.println(\"Non-terminating result: \" + e.getMessage());\n}",
							StartPosition: lastChild.StartPosition(),
							EndPosition:   lastChild.StartPosition(),
						})
				})
			}
		}
	},
}
View Source
var ArrayIndexOutOfBoundsException = lib.ErrorTemplate{
	Name:    "ArrayIndexOutOfBoundsException",
	Pattern: runtimeErrorPattern("java.lang.ArrayIndexOutOfBoundsException", `Index (?P<index>\d+) out of bounds for length (?P<length>\d+)`),
	OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) {
		for q := m.Nearest.Query(`(array_access index: (_) @index)`); q.Next(); {
			node := q.CurrentNode()
			m.Nearest = node
			break
		}
	},
	OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) {
		gen.Add("This error occurs because the code is trying to access index %s that is beyond the bounds of the array which only has %s items.", cd.Variables["index"], cd.Variables["length"])
	},
	OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) {
		arrayLen, _ := strconv.Atoi(cd.Variables["length"])

		gen.Add("Accessing Array Index Within Bounds", func(s *lib.BugFixSuggestion) {
			sampleIndex := max(0, arrayLen-2)

			s.AddStep("The error is caused by trying to access an index that does not exist within the array. Instead of accessing index %s, which is beyond the array's length, change it to a valid index within the array bounds, for example, `nums[%d]`.", cd.Variables["index"], sampleIndex).
				AddFix(lib.FixSuggestion{
					NewText:       fmt.Sprintf("%d", sampleIndex),
					StartPosition: cd.MainError.Nearest.StartPosition(),
					EndPosition:   cd.MainError.Nearest.EndPosition(),
					Description:   "This adjustment ensures that you're accessing an index that exists within the array bounds, preventing the `ArrayIndexOutOfBoundsException`.",
				})
		})
	},
}
View Source
var ArrayRequiredTypeError = lib.ErrorTemplate{
	Name:              "ArrayRequiredTypeError",
	Pattern:           comptimeErrorPattern(`array required, but (?P<foundType>\S+) found`),
	StackTracePattern: comptimeStackTracePattern,
	OnAnalyzeErrorFn: func(cd *lib.ContextData, err *lib.MainError) {
		for q := cd.MainError.Nearest.Query("(array_access array: (identifier) index: ((_) @index (#eq? @index \"0\")))"); q.Next(); {
			err.Nearest = q.CurrentNode()
			break
		}
	},
	OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) {
		parent := cd.MainError.Nearest.Parent()
		varNode := parent.ChildByFieldName("array")
		indexNode := cd.MainError.Nearest

		gen.Add(
			"This error occurs because the variable `%s` is declared as an `%s` rather than an array. You're attempting to access an index (`%s`) on a variable that's not an array.",
			varNode.Text(),
			cd.Variables["foundType"],
			indexNode.Text(),
		)
	},
	OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) {
		parent := cd.MainError.Nearest.Parent()
		varNode := parent.ChildByFieldName("array")

		tree := cd.InitOrGetSymbolTree(cd.MainDocumentPath())

		gen.Add("Convert variable to an array", func(s *lib.BugFixSuggestion) {
			declSym := tree.GetSymbolByNode(getIdentifierNode(varNode))
			declNode := cd.MainError.Document.RootNode().NamedDescendantForPointRange(
				lib.Location{
					StartPos: declSym.Location().StartPos,
					EndPos:   declSym.Location().StartPos,
				},
			).Parent()

			valueNode := declNode.ChildByFieldName("value")
			declNode = declNode.Parent()

			s.AddStep("Declare the variable `%s` as an array of `%s`.", varNode.Text(), cd.Variables["foundType"]).
				AddFix(lib.FixSuggestion{
					NewText:       fmt.Sprintf("%s[] %s = {%s};", cd.Variables["foundType"], varNode.Text(), valueNode.Text()),
					StartPosition: declNode.StartPosition(),
					EndPosition:   declNode.EndPosition(),
				})
		})
	},
}
View Source
var CannotBeAppliedError = lib.ErrorTemplate{
	Name: "CannotBeAppliedError",
	Pattern: comptimeErrorPattern(
		`method (?P<method>\S+) in class (?P<className>\S+) cannot be applied to given types;`,
		`required:\s+(?P<requiredTypes>.+)\s+found:\s+(?P<foundTypes>.+)\s+reason:\s+(?P<reason>.+)`,
	),
	StackTracePattern: comptimeStackTracePattern,
	OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) {
		cCtx := cannotBeAppliedErrorCtx{
			rawRequiredTypes: strings.Split(cd.Variables["requiredTypes"], ","),
			rawFoundTypes:    strings.Split(cd.Variables["foundTypes"], ","),
			requiredTypes:    []lib.Symbol{},
			foundTypes:       []lib.Symbol{},
		}

		for _, rawType := range cCtx.rawRequiredTypes {
			sym := cd.FindSymbol(rawType, 0)
			cCtx.requiredTypes = append(cCtx.requiredTypes, sym)
		}

		for _, rawType := range cCtx.rawFoundTypes {
			sym := cd.FindSymbol(rawType, 0)
			cCtx.foundTypes = append(cCtx.foundTypes, sym)
		}

		if len(cCtx.rawFoundTypes) > len(cCtx.rawRequiredTypes) {
			cCtx.kind = cannotBeAppliedMismatchedArgCount
			cCtx.invalidIdx = len(cCtx.rawFoundTypes) - 1
		} else {
			cCtx.kind = cannotBeAppliedMismatchedArgType

			for i := 0; i < len(cCtx.requiredTypes); i++ {
				if i >= len(cCtx.foundTypes) {
					break
				}

				if cCtx.requiredTypes[i] != cCtx.foundTypes[i] {
					cCtx.invalidIdx = i
					break
				}
			}
		}

		argumentNodeTypesToLook := ""
		for _, sym := range cCtx.foundTypes {
			valueNodeTypes := symbolToValueNodeType(sym)
			nTypesStr := "[(identifier) (field_access)"

			for _, nType := range valueNodeTypes {
				nTypesStr += fmt.Sprintf(" (%s)", nType)
			}

			nTypesStr += "]"
			argumentNodeTypesToLook += nTypesStr
		}

		for q := cd.MainError.Nearest.Query(
			`((method_invocation name: (identifier) @name arguments: (argument_list %s)) @call (#eq? @name "%s"))`,
			argumentNodeTypesToLook, cd.Variables["method"],
		); q.Next(); {
			node := q.CurrentNode()
			cCtx.callExprNode = node
			m.Nearest = node.ChildByFieldName("arguments").NamedChild(cCtx.invalidIdx)
			break
		}

		m.Context = cCtx
	},
	OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) {
		ctx := cd.MainError.Context.(cannotBeAppliedErrorCtx)

		switch ctx.kind {
		case cannotBeAppliedMismatchedArgCount:
			gen.Add("This error occurs when there is an attempt to apply a method with an incorrect number of arguments.")
		case cannotBeAppliedMismatchedArgType:
			gen.Add("This error occurs when there is an attempt to apply a method with arguments that do not match the method signature.")
		default:
			gen.Add("unable to determine.")
		}
	},
	OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) {
		ctx := cd.MainError.Context.(cannotBeAppliedErrorCtx)

		switch ctx.kind {
		case cannotBeAppliedMismatchedArgCount:
			gen.Add("Use the correct number of arguments", func(s *lib.BugFixSuggestion) {
				s.AddStep("Modify the `%s` method call to use only %s argument.", cd.Variables["method"], numbers.ToWords(len(ctx.rawRequiredTypes))).
					AddFix(lib.FixSuggestion{
						NewText:       "",
						StartPosition: cd.MainError.Nearest.PrevSibling().StartPosition(),
						EndPosition:   cd.MainError.Nearest.EndPosition(),
					})
			})
		case cannotBeAppliedMismatchedArgType:
			gen.Add("Use the correct argument types", func(s *lib.BugFixSuggestion) {
				s.AddStep("Provide the correct argument types when calling the `%s` method", cd.Variables["method"]).
					AddFix(lib.FixSuggestion{
						NewText:       castValueNode(cd.MainError.Nearest, ctx.requiredTypes[ctx.invalidIdx]),
						StartPosition: cd.MainError.Nearest.StartPosition(),
						EndPosition:   cd.MainError.Nearest.EndPosition(),
					})
			})
		}

	},
}
View Source
var CharacterExpectedError = lib.ErrorTemplate{
	Name:              "CharacterExpectedError",
	Pattern:           comptimeErrorPattern(`'(?P<character>\S+)'(?: or '(?P<altCharacter>\S+)')? expected`),
	StackTracePattern: comptimeStackTracePattern,
	OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) {
		iCtx := characterExpectedErrorCtx{
			direction: characterInsertDirectionLeft,
		}

		rootNode := m.Document.Tree.RootNode()
		cursor := sitter.NewTreeCursor(rootNode)
		rawNearestMissingNode := nearestMissingNodeFromPos(cursor, m.ErrorNode.StartPos)
		if rawNearestMissingNode == nil {
			if rawNearestMissingNode2 := nearestNodeFromPos2(cursor, m.ErrorNode.StartPos); rawNearestMissingNode2 != nil {
				rawNearestMissingNode = rawNearestMissingNode2
			}
		}

		if rawNearestMissingNode != nil {
			if rawNearestMissingNode.IsExtra() {

				rawNearestMissingNode = rawNearestMissingNode.PrevSibling()
			}

			if rawNearestMissingNode.IsNamed() {
				iCtx.direction = characterInsertDirectionRight
			}

			nearestMissingNode := lib.WrapNode(m.Document, rawNearestMissingNode)
			m.Nearest = nearestMissingNode
		}

		m.Context = iCtx
	},
	OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) {
		gen.Add("This error occurs when there is an unexpected character in the code, and '%s' is expected.", cd.Variables["character"])

	},
	OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) {
		ctx := cd.MainError.Context.(characterExpectedErrorCtx)

		gen.Add("Add the missing character", func(s *lib.BugFixSuggestion) {
			step := s.AddStep("Ensure that the array declaration has the correct syntax by adding the missing `%s`.", cd.Variables["character"])
			if ctx.direction == characterInsertDirectionLeft {
				step.AddFix(lib.FixSuggestion{
					NewText:       cd.Variables["character"],
					StartPosition: cd.MainError.Nearest.StartPosition(),
					EndPosition:   cd.MainError.Nearest.StartPosition(),
				})
			} else if ctx.direction == characterInsertDirectionRight {
				step.AddFix(lib.FixSuggestion{
					NewText:       cd.Variables["character"],
					StartPosition: cd.MainError.Nearest.EndPosition(),
					EndPosition:   cd.MainError.Nearest.EndPosition(),
				})
			}
		})

	},
}
View Source
var IdentifierExpectedError = lib.ErrorTemplate{
	Name:              "IdentifierExpectedError",
	Pattern:           comptimeErrorPattern(`(?P<reason>\<identifier\>|class, interface, or enum) expected`),
	StackTracePattern: comptimeStackTracePattern,
	OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) {
		iCtx := identifierExpectedErrorCtx{}

		switch cd.Variables["reason"] {
		case "class, interface, or enum":
			iCtx.reasonKind = identifierExpectedReasonClassInterfaceEnum
		default:
			iCtx.reasonKind = identifierExpectedReasonUnknown
		}

		if iCtx.reasonKind == identifierExpectedReasonClassInterfaceEnum {
			accessTokens := []string{"public"}
			statementTokens := []string{"class", "interface", "enum"}

			tokens := append(accessTokens, statementTokens...)

			nearestWord := ""
			wordToReplace := ""

			line := m.Document.LineAt(m.Nearest.StartPosition().Line)
			lineTokens := strings.Split(line, " ")

			nearestCol := 0

			for _, token := range tokens {
				for ltIdx, lineToken := range lineTokens {
					distance := levenshtein.ComputeDistance(token, lineToken)
					if distance >= 1 && distance <= 3 {
						wordToReplace = lineToken
						nearestWord = token

						for i := 0; i < ltIdx; i++ {
							nearestCol += len(lineTokens[i]) + 1
						}

						nearestCol++
						break
					}
				}
			}

			if nearestWord != "" {
				iCtx.wordForTypo = nearestWord
				iCtx.typoWord = wordToReplace
				iCtx.fixKind = identifierExpectedCorrectTypo

				targetPos := lib.Position{
					Line:   m.Nearest.StartPosition().Line,
					Column: nearestCol,
				}

				initialNearest := m.Document.RootNode().NamedDescendantForPointRange(lib.Location{
					StartPos: targetPos,
					EndPos:   targetPos,
				})

				rawNearestNode := nearestNodeFromPos2(initialNearest.TreeCursor(), targetPos)
				if rawNearestNode != nil {
					m.Nearest = lib.WrapNode(m.Document, rawNearestNode)
				} else {
					m.Nearest = initialNearest
				}

				if !slice.ContainsString(statementTokens, nearestWord) {
					iCtx.reasonKind = identifierExpectedReasonTypo
				}
			}
		} else if tree, err := sitter.ParseCtx(
			context.Background(),
			[]byte(m.Nearest.Text()),
			m.Document.Language.SitterLanguage,
		); err == nil && !tree.IsError() {
			iCtx.fixKind = identifierExpectedFixWrapFunction
		}

		m.Context = iCtx
	},
	OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) {
		iCtx := cd.MainError.Context.(identifierExpectedErrorCtx)

		switch iCtx.reasonKind {
		case identifierExpectedReasonClassInterfaceEnum:
			gen.Add("This error occurs when there's a typo or the keyword `class`, `interface`, or `enum` is missing.")
		case identifierExpectedReasonTypo:
			gen.Add("This error indicates there's a typo or misspelled word in your code.")
		default:
			gen.Add("This error occurs when an identifier is expected, but an expression is found in a location where a statement or declaration is expected.")
		}

	},
	OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) {
		ctx := cd.MainError.Context.(identifierExpectedErrorCtx)

		switch ctx.fixKind {
		case identifierExpectedFixWrapFunction:
			gen.Add("Use the correct syntax", func(s *lib.BugFixSuggestion) {
				startPos := cd.MainError.Nearest.StartPosition()
				space := getSpaceFromBeginning(cd.MainError.Document, startPos.Line, startPos.Column)

				s.AddStep("Use a valid statement or expression within a method or block.").
					AddFix(lib.FixSuggestion{
						NewText: space + "public void someMethod() {\n" + space,
						StartPosition: lib.Position{
							Line: cd.MainError.Nearest.StartPosition().Line,
						},
						EndPosition: lib.Position{
							Line: cd.MainError.Nearest.StartPosition().Line,
						},
					}).
					AddFix(lib.FixSuggestion{
						NewText:       "\n" + space + "}",
						StartPosition: cd.MainError.Nearest.EndPosition(),
						EndPosition:   cd.MainError.Nearest.EndPosition(),
					})
			})
		case identifierExpectedCorrectTypo:
			gen.Add("Correct the typo", func(s *lib.BugFixSuggestion) {
				s.AddStep("Change `%s` to `%s`.", ctx.typoWord, ctx.wordForTypo).
					AddFix(lib.FixSuggestion{
						NewText:       ctx.wordForTypo,
						StartPosition: cd.MainError.Nearest.StartPosition(),
						EndPosition:   cd.MainError.Nearest.EndPosition(),
					})
			})
		}
	},
}
View Source
var IllegalCharacterError = lib.ErrorTemplate{
	Name:              "IllegalCharacterError",
	Pattern:           comptimeErrorPattern(`illegal character: '(?P<character>\S+)'`),
	StackTracePattern: comptimeStackTracePattern,
	OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) {
		for q := m.Nearest.Query("(ERROR) @error"); q.Next(); {
			node := q.CurrentNode()
			m.Nearest = node.NextSibling()
			break
		}
	},
	OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) {
		gen.Add("This error occurs when there is an attempt to use an illegal character in the code.")
	},
	OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) {
		parent := cd.MainError.Nearest.Parent()

		if parent.Type() == "binary_expression" {
			gen.Add("Remove the illegal character", func(s *lib.BugFixSuggestion) {
				left := parent.ChildByFieldName("left")

				s.AddStep("Remove the illegal character `%s` from the code", cd.Variables["character"]).
					AddFix(lib.FixSuggestion{
						NewText:       left.Text(),
						StartPosition: parent.StartPosition(),
						EndPosition:   parent.EndPosition(),
					})
			})
		}
	},
}
View Source
var IllegalExpressionStartError = lib.ErrorTemplate{
	Name:              "IllegalExpressionStartError",
	Pattern:           comptimeErrorPattern(`illegal start of expression`),
	StackTracePattern: comptimeStackTracePattern,
	OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) {
		for nearest := m.Nearest; !nearest.IsNull(); nearest = nearest.Parent() {
			found := false

			for q := nearest.Query("(ERROR) @error"); q.Next(); {
				node := q.CurrentNode()
				m.Nearest = node
				found = true

				break
			}

			if found {
				break
			}
		}
	},
	OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) {
		gen.Add("This error occurs when the compiler encounters an expression that is not valid.")
	},
	OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) {
		parent := cd.MainError.Nearest.Parent()

		if parent.Type() == "unary_expression" {
			firstChild := parent.Child(0)
			operand := parent.ChildByFieldName("operand")

			gen.Add("Correct the expression", func(s *lib.BugFixSuggestion) {
				s.AddStep("Ensure a valid expression by fixing the incorrect use of operators").
					AddFix(lib.FixSuggestion{
						NewText:       operand.Text(),
						StartPosition: firstChild.StartPosition(),
						EndPosition:   firstChild.EndPosition(),
					}).
					AddFix(lib.FixSuggestion{
						NewText:       "(",
						StartPosition: parent.StartPosition(),
						EndPosition:   parent.StartPosition(),
					}).
					AddFix(lib.FixSuggestion{
						NewText:       ")",
						StartPosition: parent.EndPosition(),
						EndPosition:   parent.EndPosition(),
					})
			})
		} else if errNodeText := cd.MainError.Nearest.Text(); !strings.HasPrefix(errNodeText, "}") && strings.HasSuffix(errNodeText, "else") {
			gen.Add("Use the right closing bracket", func(s *lib.BugFixSuggestion) {
				s.AddStep("Ensure that the right closing bracket for the else branch of your if statement is used").
					AddFix(lib.FixSuggestion{
						NewText:       "} else",
						StartPosition: cd.MainError.Nearest.StartPosition(),
						EndPosition:   cd.MainError.Nearest.EndPosition(),
					})
			})
		}
	},
}
View Source
var IncompatibleTypesError = lib.ErrorTemplate{
	Name:              "IncompatibleTypesError",
	Pattern:           comptimeErrorPattern(`incompatible types: (?P<leftType>\S+) cannot be converted to (?P<rightType>\S+)`),
	StackTracePattern: comptimeStackTracePattern,
	OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) {
		iCtx := incompatibleTypesErrorCtx{}

		if m.Nearest.Type() == "expression_statement" {
			m.Nearest = m.Nearest.NamedChild(0)
		}

		if m.Nearest.Type() == "assignment_expression" {
			iCtx.Parent = m.Nearest
			m.Nearest = m.Nearest.ChildByFieldName("right")
		}

		m.Context = iCtx
	},
	OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) {
		gen.Add("This error occurs when you attempt to assign a value of one data type to a variable of a different, incompatible data type.")
	},
	OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) {
		leftType := cd.Variables["leftType"]
		rightType := cd.Variables["rightType"]
		ctx := cd.MainError.Context.(incompatibleTypesErrorCtx)

		gen.Add(fmt.Sprintf("Convert %s to %s", leftType, rightType), func(s *lib.BugFixSuggestion) {
			s.AddStep("To resolve the incompatible types error, you need to explicitly convert the `%s` to a `%s`.", leftType, rightType).
				AddFix(lib.FixSuggestion{
					NewText:       fmt.Sprintf("%s.valueOf(%s)", rightType, cd.MainError.Nearest.Text()),
					StartPosition: cd.MainError.Nearest.StartPosition(),
					EndPosition:   cd.MainError.Nearest.EndPosition(),
					Description:   fmt.Sprintf("The `%s.valueOf()` method converts the `%s` to its string representation.", rightType, leftType),
				})
		})

		gen.Add(fmt.Sprintf("Concatenate %s with %s", leftType, rightType), func(s *lib.BugFixSuggestion) {
			leftVariable := ctx.Parent.ChildByFieldName("left").Text()

			s.AddStep("Alternatively, you can concatenate the `%s` with the existing `%s`.", leftType, rightType).
				AddFix(lib.FixSuggestion{
					NewText:       fmt.Sprintf("%s + ", leftVariable),
					StartPosition: cd.MainError.Nearest.StartPosition(),
					EndPosition:   cd.MainError.Nearest.StartPosition(),
					Description:   fmt.Sprintf("This converts the `%s` to a `%s` and concatenates it with the existing `%s`.", leftType, rightType, rightType),
				})
		})
	},
}
View Source
var InputMismatchException = lib.ErrorTemplate{
	Name:    "InputMismatchException",
	Pattern: runtimeErrorPattern("java.util.InputMismatchException", ""),
	OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) {
		query := "(method_invocation object: (_) name: (identifier) @fn-name arguments: (argument_list) (#eq? @fn-name \"nextInt\"))"
		for q := m.Nearest.Query(query); q.Next(); {
			if q.CurrentTagName() != "fn-name" {
				continue
			}

			node := q.CurrentNode()
			m.Nearest = node
		}
	},
	OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) {
		methodName := cd.MainError.Nearest.Text()
		expectedType := scannerMethodsToTypes[methodName]
		gen.Add(
			"This error occurs when a non-%s input is passed to the `%s()` method of the `Scanner` class.",
			expectedType,
			methodName)
	},
	OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) {

		cursor := sitter.NewTreeCursor(cd.MainError.Document.RootNode().Node)
		rawNearestBlock := nearestNodeFromPosByType(cursor, "block", cd.MainError.Nearest.StartPosition())

		if rawNearestBlock != nil && !rawNearestBlock.IsNull() {
			nearestBlock := lib.WrapNode(cd.MainError.Document, rawNearestBlock)

			gen.Add("Add a try-catch for error handling", func(s *lib.BugFixSuggestion) {
				step := s.AddStep("Implement error handling to account for input mismatches and prompt the user for valid input.")

				wrapStatement(
					step,
					"try {",
					"} catch (InputMismatchException e) {\n\t<i>System.out.println(\"Invalid input. Please try again.\");\n\t}",
					lib.Location{
						StartPos: lib.Position{
							Line: nearestBlock.FirstNamedChild().StartPosition().Line,
						},
						EndPos: nearestBlock.LastNamedChild().EndPosition(),
					},
					true,
				)
			})
		}
	},
}
View Source
var InvalidMethodDeclarationError = lib.ErrorTemplate{
	Name:              "InvalidMethodDeclarationError",
	Pattern:           comptimeErrorPattern("invalid method declaration; return type required"),
	StackTracePattern: comptimeStackTracePattern,
	OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) {
		iCtx := invalidMethodDeclarationErrorCtx{}
		pos := m.ErrorNode.StartPos

		for q := m.Document.RootNode().Query("(constructor_declaration) @method"); q.Next(); {
			node := q.CurrentNode()
			pointA := node.StartPoint()
			pointB := node.EndPoint()
			if uint32(pos.Line) >= pointA.Row+1 && uint32(pos.Line) <= pointB.Row+1 {
				iCtx.declNode = node
				m.Nearest = node.ChildByFieldName("name")
				break
			}
		}

		iCtx.returnTypeToAdd = lib.UnwrapReturnType(cd.FindSymbol(m.Nearest.Text(), m.Nearest.StartPosition().Index))
		m.Context = iCtx
	},
	OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) {
		gen.Add("This error occurs when there is an invalid method declaration, and a return type is missing.")
	},
	OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) {
		ctx := cd.MainError.Context.(invalidMethodDeclarationErrorCtx)

		gen.Add("Add the return type to the method declaration", func(s *lib.BugFixSuggestion) {
			s.AddStep("Specify the return type of the `%s` method", cd.MainError.Nearest.Text()).
				AddFix(lib.FixSuggestion{
					NewText:       fmt.Sprintf("%s ", ctx.returnTypeToAdd.Name()),
					StartPosition: cd.MainError.Nearest.StartPosition(),
					EndPosition:   cd.MainError.Nearest.StartPosition(),
				})
		})
	},
}
View Source
var MissingReturnError = lib.ErrorTemplate{
	Name:              "MissingReturnError",
	Pattern:           comptimeErrorPattern(`missing return statement`),
	StackTracePattern: comptimeStackTracePattern,
	OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) {

		mCtx := missingReturnErrorCtx{}
		rootNode := m.Document.RootNode()
		pos := m.ErrorNode.StartPos

		for q := rootNode.Query("(method_declaration) @method"); q.Next(); {
			node := q.CurrentNode()
			pointA := node.StartPoint()
			pointB := node.EndPoint()
			if uint32(pos.Line) >= pointA.Row+1 && uint32(pos.Line) <= pointB.Row+1 {
				mCtx.NearestMethod = node
				break
			}
		}

		m.Context = mCtx
	},
	OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) {
		gen.Add("This error occurs when a method is declared to return a value, but there is no return statement within the method.")
	},
	OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) {
		ctx := cd.MainError.Context.(missingReturnErrorCtx)

		gen.Add("Provide a return statement", func(s *lib.BugFixSuggestion) {
			bodyNode := ctx.NearestMethod.ChildByFieldName("body")
			lastStartPosInBlock := bodyNode.EndPosition()
			lastEndPosInBlock := bodyNode.EndPosition()
			if bodyNode.NamedChildCount() > 0 {
				lastStartPosInBlock = bodyNode.LastNamedChild().StartPosition()
				lastEndPosInBlock = bodyNode.LastNamedChild().EndPosition()
			}

			expectedTypeNode := ctx.NearestMethod.ChildByFieldName("type")
			expectedTypeSym := cd.Analyzer.AnalyzeNode(context.Background(), expectedTypeNode)
			nearestScope := cd.InitOrGetSymbolTree(cd.MainDocumentPath()).GetNearestScopedTree(lastEndPosInBlock.Index)
			symbolsForReturn := nearestScope.FindSymbolsByClause(func(sym lib.Symbol) bool {
				if sym, ok := sym.(lib.IReturnableSymbol); ok {
					return sym.ReturnType() == expectedTypeSym
				}
				return false
			})

			valueToReturn := getDefaultValueForType(expectedTypeSym)
			if len(symbolsForReturn) != 0 {
				nearestSym := symbolsForReturn[0]
				valueToReturn = nearestSym.Name()
			}

			s.AddStep(
				"Since the `%s` method is declared to return an `%s`, you need to provide a return statement with the result.",
				ctx.NearestMethod.ChildByFieldName("name").Text(),
				ctx.NearestMethod.ChildByFieldName("type").Text(),
			).AddFix(lib.FixSuggestion{
				NewText:       "\n" + getSpaceFromBeginning(cd.MainError.Document, lastStartPosInBlock.Line, lastStartPosInBlock.Column) + fmt.Sprintf("return %s;", valueToReturn),
				StartPosition: lastEndPosInBlock,
				EndPosition:   lastEndPosInBlock,
				Description:   "This ensures that the method returns the sum of the two input numbers.",
			})
		})

		gen.Add("Set the method return type to void", func(s *lib.BugFixSuggestion) {
			s.AddStep(
				"If you don't intend to return a value from the `%s` method, you can change its return type to `void`.",
				ctx.NearestMethod.ChildByFieldName("name").Text(),
			).AddFix(lib.FixSuggestion{
				NewText:       "void",
				StartPosition: ctx.NearestMethod.ChildByFieldName("type").StartPosition(),
				EndPosition:   ctx.NearestMethod.ChildByFieldName("type").EndPosition(),
				Description:   "This is appropriate if you're using the method for side effects rather than returning a value.",
			})
		})
	},
}
View Source
var NegativeArraySizeException = lib.ErrorTemplate{
	Name:    "NegativeArraySizeException",
	Pattern: runtimeErrorPattern("java.lang.NegativeArraySizeException", "(?P<index>.+)"),
	OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) {
		nCtx := negativeArraySizeExceptionCtx{}
		query := "(array_creation_expression dimensions: (dimensions_expr (unary_expression operand: (decimal_integer_literal)))) @array"
		for q := m.Nearest.Query(query); q.Next(); {
			node := q.CurrentNode()
			nCtx.ArrayExprNode = node
			m.Nearest = node.ChildByFieldName("dimensions").NamedChild(0)
			break
		}

		m.Context = nCtx
	},
	OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) {
		gen.Add("This error occurs when you try to create an array with a negative size.")
	},
	OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) {
		gen.Add("Ensure a non-negative array size", func(s *lib.BugFixSuggestion) {
			s.AddStep("Make sure the array size is non-negative").
				AddFix(lib.FixSuggestion{
					NewText:       cd.MainError.Nearest.NamedChild(0).Text(),
					StartPosition: cd.MainError.Nearest.StartPosition(),
					EndPosition:   cd.MainError.Nearest.EndPosition(),
				})
		})
	},
}
View Source
var NoSuchElementException = lib.ErrorTemplate{
	Name:    "NoSuchElementException",
	Pattern: runtimeErrorPattern("java.util.NoSuchElementException", ""),
	OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) {
		ctx := noSuchElementExceptionCtx{}
		query := `(method_invocation object: (_) name: (identifier) @fn-name arguments: (argument_list) (#eq? @fn-name "next")) @call`
		for q := m.Nearest.Query(query); q.Next(); {
			if q.CurrentTagName() != "fn-name" {
				continue
			}

			node := q.CurrentNode()

			m.Nearest = node
		}

		ctx.parentNode = m.Nearest.Parent()

		ctx.grandParentNode = ctx.parentNode.Parent()
		if !ctx.grandParentNode.IsNull() {
			if ctx.grandParentNode.Type() == "expression_statement" || ctx.grandParentNode.Type() == "variable_declarator" {
				ctx.grandParentNode = ctx.grandParentNode.Parent()
			}
		}

		m.Context = ctx
	},
	OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) {
		gen.Add("This error occurs when attempting to retrieve an element from an empty collection using an iterator.")
	},
	OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) {
		ctx := cd.MainError.Context.(noSuchElementExceptionCtx)

		gen.Add("Check if the iterator has next elements before calling `next()`", func(s *lib.BugFixSuggestion) {
			step := s.AddStep("Ensure that the iterator has elements before attempting to retrieve the next one.")
			gpLocation := ctx.grandParentNode.Location()
			parentName := ctx.parentNode.ChildByFieldName("object").Text()

			wrapStatement(
				step,
				fmt.Sprintf("if (%s.hasNext()) {", parentName),
				"\t} else {\n\t<i>System.out.println(\"No elements in the list.\");\n\t}",
				lib.Location{
					StartPos: lib.Position{
						Line: gpLocation.StartPos.Line,
					},
					EndPos: gpLocation.EndPos,
				},
				true,
			)
		})
	},
}
View Source
var NonStaticMethodAccessError = lib.ErrorTemplate{
	Name:              "NonStaticMethodAccessError",
	Pattern:           comptimeErrorPattern(`non-static method (?P<method>\S+)\(\) cannot be referenced from a static context`),
	StackTracePattern: comptimeStackTracePattern,
	OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) {
		nCtx := nonStaticMethodAccessErrorCtx{parent: m.Nearest}

		symbols := cd.Symbols[cd.MainDocumentPath()]
		for _, sym := range symbols.Symbols {
			if sym.Kind() == lib.SymbolKindClass && m.Nearest.Location().IsWithin(sym.Location()) {
				nCtx.class = sym.Name()
				break
			}
		}

		for q := m.Nearest.Query(`(method_invocation name: (identifier) @method arguments: (argument_list))`); q.Next(); {
			node := q.CurrentNode()
			m.Nearest = node
			nCtx.method = node.Text()
			break
		}

		m.Context = nCtx
	},
	OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) {
		gen.Add("This error occurs when trying to access a non-static method from a static context. In Java, a non-static method belongs to an instance of the class and needs an object to be called upon.")
	},
	OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) {
		ctx := cd.MainError.Context.(nonStaticMethodAccessErrorCtx)
		startPos := ctx.parent.StartPosition()
		spacing := getSpaceFromBeginning(cd.MainError.Document, startPos.Line, startPos.Column)

		gen.Add("Instantiate and call the method", func(s *lib.BugFixSuggestion) {
			s.AddStep("Create an instance of the class to access the non-static method").
				AddFix(lib.FixSuggestion{
					NewText: fmt.Sprintf("%s obj = new %s();\n"+spacing, ctx.class, ctx.class),
					StartPosition: lib.Position{
						Line:   startPos.Line,
						Column: startPos.Column,
					},
					EndPosition: lib.Position{
						Line:   ctx.parent.EndPosition().Line,
						Column: 0,
					},
				}).
				AddFix(lib.FixSuggestion{
					NewText:       "obj.",
					StartPosition: ctx.parent.StartPosition(),
					EndPosition:   ctx.parent.StartPosition(),
				})
		})
	},
}
View Source
var NotAStatementError = lib.ErrorTemplate{
	Name:              "NotAStatementError",
	Pattern:           comptimeErrorPattern(`not a statement`),
	StackTracePattern: comptimeStackTracePattern,
	OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) {
		if m.Nearest.Type() == "expression_statement" {
			m.Nearest = m.Nearest.NamedChild(0)
		}
	},
	OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) {
		gen.Add("This error occurs when a line of code is written that is not a valid statement.")
	},
	OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) {
		nodeType := cd.Analyzer.AnalyzeNode(context.Background(), cd.MainError.Nearest)

		gen.Add(fmt.Sprintf("Convert the `%s` to a statement", nodeType.Name()), func(s *lib.BugFixSuggestion) {
			s.AddStep(
				"If you intended to use the `%s` as a statement, you can print it or use it in a valid statement.",
				nodeType.Name(),
			).AddFix(lib.FixSuggestion{
				NewText:       fmt.Sprintf("System.out.println(%s)", cd.MainError.Nearest.Text()),
				StartPosition: cd.MainError.Nearest.StartPosition(),
				EndPosition:   cd.MainError.Nearest.EndPosition(),
				Description:   "This change makes the string part of a valid statement by printing it to the console.",
			})
		})

		gen.Add(fmt.Sprintf("Assign the `%s` to a variable", nodeType.Name()), func(s *lib.BugFixSuggestion) {
			s.AddStep("Alternatively, you can assign the `%s` to a variable to make it a valid statement.", nodeType.Name()).
				AddFix(lib.FixSuggestion{
					NewText:       fmt.Sprintf("%s %s = %s", nodeType.Name(), generateVarName(cd.MainError.Nearest.Text()), cd.MainError.Nearest.Text()),
					StartPosition: cd.MainError.Nearest.StartPosition(),
					EndPosition:   cd.MainError.Nearest.EndPosition(),
					Description:   "This way, the string is now part of a statement and can be used later in your code.",
				})
		})
	},
}
View Source
var NullPointerException = lib.ErrorTemplate{
	Name:    "NullPointerException",
	Pattern: runtimeErrorPattern("java.lang.NullPointerException", ""),
	OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) {
		ctx := nullPointerExceptionCtx{}

		for q := m.Nearest.Query(`([
			(field_access object: (_) @ident field: (identifier))
			(method_invocation object: [
				(identifier) @ident
				(field_access object: (_) @obj field: (identifier)) @ident
			]) @call
			(method_invocation arguments: (argument_list (identifier) @ident))
			(array_access) @access
		]
			(#any-match? @obj "^[a-z0-9_]+")
			(#not-match? @ident "^[A-Z][a-z0-9_]$"))`); q.Next(); {
			tagName := q.CurrentTagName()
			node := q.CurrentNode()

			if tagName == "call" {
				if strings.HasPrefix(node.Text(), "System.out.") {
					ctx.kind = fromSystemOut
				} else {

					continue
				}
			} else if tagName == "access" {
				cd.MainError.Nearest = node
				ctx.kind = fromArrayAccess
			} else if tagName == "ident" {
				retType := lib.UnwrapActualReturnType(cd.FindSymbol(node.Text(), node.StartPosition().Index))
				if retType != java.BuiltinTypes.NullSymbol {
					continue
				}

				ctx.origin = node.Text()
				parent := node.Parent()
				switch parent.Type() {
				case "field_access":
					ctx.kind = fromExpression
				case "argument_list":

					ctx.kind = fromFunctionArgument
				case "method_invocation":
					ctx.kind = fromMethodInvocation
					ctx.methodName = parent.ChildByFieldName("name").Text()
					ctx.origin = parent.ChildByFieldName("object").Text()
				case "assignment_expression":
					ctx.kind = fromAssignment
				}

				ctx.parent = parent
				m.Nearest = node
				break
			}
		}

		m.Context = ctx
	},
	OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) {

		ctx := cd.MainError.Context.(nullPointerExceptionCtx)

		if ctx.kind == fromSystemOut {
			gen.Add("The error occurs due to your program tried to print the value of ")
			if len(ctx.methodName) != 0 {
				gen.Add("\"%s\" method from ", ctx.methodName)
			}
			gen.Add("\"%s\" which is a null.", ctx.origin)
			return
		} else if len(ctx.methodName) != 0 {

			gen.Add("The error occurs due to your program tried to execute the \"%s\" method from \"%s\" which is a null.", ctx.methodName, ctx.origin)

			return
		}

		gen.Add("Your program try to access or manipulate an object reference that is currently pointing to `null`, meaning it doesn't refer to any actual object in memory. This typically happens when you forget to initialize an object before using it, or when you try to access an object that hasn't been properly assigned a value. ")
	},
	OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) {
		ctx := cd.MainError.Context.(nullPointerExceptionCtx)
		parent := ctx.parent
		for parent.Type() != "expression_statement" {
			if parent.Parent().IsNull() {
				break
			}
			parent = parent.Parent()
		}

		if parent.Type() == "expression_statement" {
			gen.Add("Wrap with an if statement", func(s *lib.BugFixSuggestion) {
				wrapStatement(
					s.AddStep("Check for the variable that is being used as `null`."),
					fmt.Sprintf("if (%s != null) {", ctx.origin),
					"\t}",
					lib.Location{
						StartPos: lib.Position{
							Line: parent.StartPosition().Line,
						},
						EndPos: parent.EndPosition(),
					},
					true,
				)
			})
		}

		gen.Add("Initialize the variable", func(s *lib.BugFixSuggestion) {

			symbolTree := cd.InitOrGetSymbolTree(cd.MainDocumentPath())
			varSym := symbolTree.GetSymbolByNode(cd.MainError.Nearest)

			loc := varSym.Location()
			varDeclNode := cd.MainError.Document.RootNode().NamedDescendantForPointRange(loc)
			if varDeclNode.Type() == "variable_declarator" {
				loc = varDeclNode.ChildByFieldName("value").Location()
			}

			s.AddStep("An alternative fix is to initialize the `%s` variable with a non-null value before calling the method.", ctx.origin).
				AddFix(lib.FixSuggestion{
					NewText:       getDefaultValueForType(lib.UnwrapReturnType(varSym)),
					StartPosition: loc.StartPos,
					EndPosition:   loc.EndPos,
				})
		})
	},
}

TODO: unit testing

View Source
var NumberFormatException = lib.ErrorTemplate{
	Name:    "NumberFormatException",
	Pattern: runtimeErrorPattern("java.lang.NumberFormatException", "For input string: \"(?P<string>.+)\""),
	OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) {
		for q := m.Document.RootNode().Query(`(method_invocation name: (identifier) @name arguments: (argument_list (_) @arg) (#eq? @name "parseInt") (#any-eq? @arg "%s"))`, cd.Variables["string"]); q.Next(); {
			if q.CurrentTagName() != "arg" {
				continue
			}
			node := q.CurrentNode()
			m.Nearest = node
			break
		}
	},
	OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) {
		gen.Add("This error occurs when there is an attempt to convert a string to a numeric type, but the string does not represent a valid number.")
	},
	OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) {
		gen.Add("Ensure valid input for parsing", func(s *lib.BugFixSuggestion) {
			if cd.MainError.Nearest.Type() == "identifier" {
				variableSym := cd.Analyzer.AnalyzeNode(context.TODO(), cd.MainError.Nearest)

				if variableSym != nil {
					varNode := cd.MainError.Document.RootNode().NamedDescendantForPointRange(
						variableSym.Location(),
					)

					if varNode.Type() != "variable_declarator" {
						return
					}

					valueNode := varNode.ChildByFieldName("value")
					if valueNode.Type() != "string_literal" {
						return
					}

					s.AddStep("Make sure the string contains a valid numeric representation before attempting to parse it.").
						AddFix(lib.FixSuggestion{
							NewText:       "123",
							StartPosition: varNode.ChildByFieldName("value").StartPosition().Add(lib.Position{Column: 1}),
							EndPosition:   varNode.ChildByFieldName("value").EndPosition().Add(lib.Position{Column: -1}),
						})
				}
			}
		})
	},
}
View Source
var OperatorCannotBeAppliedError = lib.ErrorTemplate{
	Name:              "OperatorCannotBeAppliedError",
	Pattern:           comptimeErrorPattern("bad operand types for binary operator '(?P<operator>.)'", `first type\:\s+(?P<firstType>\S+)\s+second type\:\s+(?P<secondType>\S+)`),
	StackTracePattern: comptimeStackTracePattern,
	OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) {
		oCtx := opCannotBeAppliedCtx{}
		operator := cd.Variables["operator"]
		for q := m.Nearest.Query(`((binary_expression) @binary_expr (#eq @binary_expr "%s"))`, operator); q.Next(); {
			node := q.CurrentNode()
			oCtx.Parent = node
			m.Nearest = node.Child(1)
			break
		}

		m.Context = oCtx
	},
	OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) {
		gen.Add(
			"This error occurs when you try to apply a binary operator to incompatible operand types, such as trying to use the '%s' operator between a %s and an %s.",
			cd.Variables["operator"],
			cd.Variables["firstType"],
			cd.Variables["secondType"],
		)
	},
	OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) {
		ctx := cd.MainError.Context.(opCannotBeAppliedCtx)
		left := ctx.Parent.ChildByFieldName("left")
		right := ctx.Parent.ChildByFieldName("right")

		gen.Add(fmt.Sprintf("Use %s's compareTo method", cd.Variables["firstType"]), func(s *lib.BugFixSuggestion) {
			s.AddStep(
				"Since you are comparing a %s and an %s, you need to use the `compareTo` method to compare their values.",
				cd.Variables["firstType"],
				cd.Variables["secondType"],
			).AddFix(lib.FixSuggestion{
				NewText:       ".compareTo(String.valueOf(" + right.Text() + "))",
				StartPosition: left.EndPosition(),
				EndPosition:   left.EndPosition(),
				Description:   "The `compareTo` method returns a negative integer if the calling string is lexicographically less than the argument string.",
			})
		})

		gen.Add(fmt.Sprintf("Convert %s to %s for direct comparison", cd.Variables["secondType"], cd.Variables["firstType"]), func(s *lib.BugFixSuggestion) {
			s.AddStep(
				"If you want to compare them directly, convert the %s to %s using `%s.valueOf()`.",
				cd.Variables["secondType"],
				cd.Variables["firstType"],
				cd.Variables["firstType"],
			).AddFix(lib.FixSuggestion{
				Description:   "This ensures both operands are of the same type for comparison.",
				NewText:       ".equals(" + cd.Variables["firstType"] + ".valueOf(" + right.Text() + "))",
				StartPosition: left.EndPosition(),
				EndPosition:   ctx.Parent.EndPosition(),
			})
		})
	},
}
View Source
var ParseEndOfFileError = lib.ErrorTemplate{
	Name:              "ParseEndOfFileError",
	Pattern:           comptimeErrorPattern("reached end of file while parsing"),
	StackTracePattern: comptimeStackTracePattern,
	OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) {
		ctx := parseEofErrorCtx{}

		rootNode := m.Document.Tree.RootNode()
		cursor := sitter.NewTreeCursor(rootNode)
		rawNearestMissingNode := nearestMissingNodeFromPos(cursor, m.ErrorNode.StartPos)
		nearestMissingNode := lib.WrapNode(m.Document, rawNearestMissingNode)
		m.Nearest = nearestMissingNode
		nearestStr := m.Nearest.String()
		prefix := "(MISSING \""
		ctx.missingCharacter = nearestStr[len(prefix) : len(prefix)+1]
		m.Context = ctx
	},
	OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) {
		gen.Add("This error occurs when the compiler expects more code but encounters the end of the file.")
	},
	OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) {
		ctx := cd.MainError.Context.(parseEofErrorCtx)

		gen.Add("Complete the code", func(s *lib.BugFixSuggestion) {
			endPos := cd.MainError.Nearest.EndPosition()

			s.AddStep("Add the missing `%s` in line %d", ctx.missingCharacter, endPos.Line+1).
				AddFix(lib.FixSuggestion{
					NewText:       "\n" + ctx.missingCharacter,
					StartPosition: endPos,
					EndPosition:   endPos,
				})
		})
	},
}
View Source
var PrecisionLossError = lib.ErrorTemplate{
	Name:              "PrecisionLossError",
	Pattern:           comptimeErrorPattern(`incompatible types: possible lossy conversion from (?P<currentType>\S+) to (?P<targetType>\S+)`),
	StackTracePattern: comptimeStackTracePattern,
	OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) {
		pCtx := precisionLossCtx{}
		targetType := cd.Variables["targetType"]
		for q := m.Nearest.Query(`((local_variable_declaration type: (_) @target-type) (#eq? @target-type "%s"))`, targetType); q.Next(); {
			node := q.CurrentNode()
			pCtx.Parent = node.Parent()
			m.Nearest = pCtx.Parent.ChildByFieldName("declarator").ChildByFieldName("value")
			break
		}

		m.Context = pCtx
	},
	OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) {
		gen.Add(
			"This error occurs when you try to assign a value from a data type with higher precision (%s) to a data type with lower precision (%s), which may result in a loss of precision.",
			cd.Variables["currentType"],
			cd.Variables["targetType"],
		)
	},
	OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) {

		variableInvolved := cd.MainError.Nearest
		gen.Add(fmt.Sprintf("Explicitly cast to %s", cd.Variables["targetType"]), func(s *lib.BugFixSuggestion) {
			s.AddStep("To resolve the precision loss, explicitly cast the `%s` to %s.", variableInvolved.Text(), cd.Variables["targetType"]).AddFix(lib.FixSuggestion{
				NewText:       fmt.Sprintf("(%s) ", cd.Variables["targetType"]),
				StartPosition: variableInvolved.StartPosition(),
				EndPosition:   variableInvolved.StartPosition(),
				Description:   "This casting informs the compiler about the potential loss of precision and allows the assignment.",
			})
		})

		if cd.Variables["targetType"] != "int" {
			gen.Add(fmt.Sprintf("Use an 'f' suffix for the %s literal", cd.Variables["targetType"]), func(s *lib.BugFixSuggestion) {
				nearestTree := cd.InitOrGetSymbolTree(cd.MainError.DocumentPath()).GetNearestScopedTree(variableInvolved.StartPosition().Index)

				involvedVariable := nearestTree.GetSymbolByNode(variableInvolved)
				if involvedVariable == nil {

					return
				}

				node := cd.MainError.Document.RootNode().
					NamedDescendantForPointRange(involvedVariable.Location())

				involvedVariableValueNode := node.ChildByFieldName("value")

				s.AddStep(
					"Alternatively, you can use the 'f' suffix to specify that the literal is of type %s.",
					cd.Variables["targetType"]).AddFix(lib.FixSuggestion{
					NewText:       involvedVariableValueNode.Text() + "f",
					StartPosition: variableInvolved.StartPosition(),
					EndPosition:   variableInvolved.EndPosition(),
					Description:   "This way, you directly define the float variable without the need for casting.",
				})
			})
		}
	},
}
View Source
var PrivateAccessError = lib.ErrorTemplate{
	Name:              "PrivateAccessError",
	Pattern:           comptimeErrorPattern(`(?P<field>\S+) has private access in (?P<class>\S+)`),
	StackTracePattern: comptimeStackTracePattern,
	OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) {
		pCtx := privateAccessErrorCtx{}
		className := cd.Variables["class"]
		rootNode := m.Nearest.Doc.RootNode()

		for q := m.Nearest.Query(`((field_access (identifier) . (identifier) @field-name) @field (#eq? @field-name "%s"))`, cd.Variables["field"]); q.Next(); {
			node := q.CurrentNode()
			m.Nearest = node
			break
		}

		for q := rootNode.Query(`(class_declaration name: (identifier) @class-name (#eq? @class-name "%s")) @class`, className); q.Next(); {
			node := q.CurrentNode()
			pCtx.ClassDeclarationNode = node
			break
		}

		m.Context = pCtx
	},
	OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) {
		gen.Add("This error occurs when you try to access a private variable from another class, which is not allowed.")
	},
	OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) {
		ctx := cd.MainError.Context.(privateAccessErrorCtx)

		classDeclScope := cd.InitOrGetSymbolTree(cd.MainDocumentPath()).GetNearestScopedTree(ctx.ClassDeclarationNode.EndPosition().Index)

		gen.Add("Use a public accessor method", func(s *lib.BugFixSuggestion) {
			methodCreatorSb := &strings.Builder{}
			bodyNode := ctx.ClassDeclarationNode.ChildByFieldName("body")

			privateVarRetType := cd.Analyzer.FallbackSymbol()
			if gotSym := classDeclScope.Find(cd.Variables["field"]); gotSym != nil {
				if gotSym, ok := gotSym.(lib.IReturnableSymbol); ok {
					privateVarRetType = gotSym.ReturnType()
				}
			}

			space := ""
			lastNamedChild := bodyNode.LastNamedChild()
			if !lastNamedChild.IsNull() {
				targetPos := lastNamedChild.StartPosition()

				space = getSpaceFromBeginning(cd.MainError.Document, targetPos.Line, targetPos.Column)
				methodCreatorSb.WriteString("\n\n" + space)
			}

			accessorMethodName := "get" + strings.ToUpper(string(cd.Variables["field"][0])) + cd.Variables["field"][1:]
			methodCreatorSb.WriteString(fmt.Sprintf("public %s %s() {\n", privateVarRetType.Name(), accessorMethodName))
			methodCreatorSb.WriteString(strings.Repeat(space, 2) + fmt.Sprintf("return this.%s;\n", cd.Variables["field"]))
			methodCreatorSb.WriteString(space + "}\n")

			targetPos := lastNamedChild.EndPosition()
			s.AddStep("To access a private variable from another class, create a public accessor method in `%s`.", cd.Variables["class"]).
				AddFix(lib.FixSuggestion{
					NewText:       methodCreatorSb.String(),
					StartPosition: lib.Position{Line: targetPos.Line, Column: targetPos.Column},
					EndPosition:   lib.Position{Line: targetPos.Line, Column: targetPos.Column},
				})

			fieldNode := cd.MainError.Nearest.ChildByFieldName("field")

			s.AddStep("Then, use this method to get the value.").
				AddFix(lib.FixSuggestion{
					NewText:       accessorMethodName + "()",
					StartPosition: fieldNode.StartPosition(),
					EndPosition:   fieldNode.EndPosition(),
					Description:   "This way, you respect encapsulation by using a method to access the private variable.",
				})
		})

		gen.Add("Make the variable public (not recommended)", func(s *lib.BugFixSuggestion) {
			targetLoc := lib.Location{}
			if gotSym := classDeclScope.Find(cd.Variables["field"]); gotSym != nil {

				rawDescendantNode := cd.MainError.Document.RootNode().NamedDescendantForPointRange(
					gotSym.Location(),
				)

				if rawDescendantNode.Type() == "variable_declarator" {
					rawDescendantNode = rawDescendantNode.Parent()
				}

				if firstChild := rawDescendantNode.NamedChild(0); firstChild.Type() == "modifiers" {
					targetLoc = firstChild.Location()
				} else {
					targetLoc.StartPos = firstChild.StartPosition()
					targetLoc.EndPos = targetLoc.StartPos
				}
			}

			newText := "public"
			if targetLoc.StartPos.Eq(targetLoc.EndPos) {
				newText += " "
			}

			s.AddStep("If you must access the variable directly, you can make it public, but this is generally not recommended for maintaining encapsulation.").
				AddFix(lib.FixSuggestion{
					NewText:       newText,
					StartPosition: targetLoc.StartPos,
					EndPosition:   targetLoc.EndPos,
				})
		})
	},
}
View Source
var PublicClassFilenameMismatchError = lib.ErrorTemplate{
	Name:              "PublicClassFilenameMismatchError",
	Pattern:           comptimeErrorPattern(`class (?P<className>\S+) is public, should be declared in a file named (?P<classFileName>\S+\.java)`),
	StackTracePattern: comptimeStackTracePattern,
	OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) {
		className := cd.Variables["className"]
		actualClassFilename := filepath.Base(cd.MainError.ErrorNode.DocumentPath)
		m.Context = publicClassFilenameMismatchErrorCtx{
			className:             className,
			actualClassFileName:   actualClassFilename,
			expectedClassName:     strings.Replace(actualClassFilename, ".java", "", 1),
			expectedClassFilename: className + ".java",
		}

		for q := m.Nearest.Doc.RootNode().Query(`(class_declaration name: (identifier) @class-name (#eq? @class-name "%s"))`, className); q.Next(); {
			node := q.CurrentNode()
			m.Nearest = node
			break
		}
	},
	OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) {
		gen.Add(`This error occurs because the name of the Java file does not match the name of the public class within it.`)
	},
	OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) {
		ctx := cd.MainError.Context.(publicClassFilenameMismatchErrorCtx)

		gen.Add("Rename your file", func(s *lib.BugFixSuggestion) {
			s.AddStep("Rename the file to \"%s\" to match the class", ctx.expectedClassFilename)
		})

		gen.Add("Rename the public class", func(s *lib.BugFixSuggestion) {
			s.AddStep("The filename should match the name of the public class in the file. To resolve this, change the class name to match the filename.").
				AddFix(lib.FixSuggestion{
					NewText:       ctx.expectedClassName,
					StartPosition: cd.MainError.Nearest.StartPosition(),
					EndPosition:   cd.MainError.Nearest.EndPosition(),
				})
		})
	},
}
View Source
var StringIndexOutOfBoundsException = lib.ErrorTemplate{
	Name:    "StringIndexOutOfBoundsException",
	Pattern: runtimeErrorPattern("java.lang.StringIndexOutOfBoundsException", `String index out of range: (?P<index>\d+)`),
	OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) {
		ctx := stringIndexOutOfBoundsExceptionCtx{}

		for q := m.Nearest.Query(`(method_invocation name: (identifier) @name arguments: (argument_list (_) @arg) (#eq? @name "charAt") (#any-eq? @arg "%s"))`, cd.Variables["index"]); q.Next(); {
			if q.CurrentTagName() != "arg" {
				continue
			}
			node := q.CurrentNode()
			m.Nearest = node
			ctx.parentNode = node.Parent().Parent()

			ctx.grandParentNode = ctx.parentNode.Parent()
			if !ctx.grandParentNode.IsNull() {
				if ctx.grandParentNode.Type() == "expression_statement" || ctx.grandParentNode.Type() == "variable_declarator" {
					ctx.grandParentNode = ctx.grandParentNode.Parent()
				}
			}
			break
		}

		m.Context = ctx
	},
	OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) {
		gen.Add("This error occurs because the code is trying to access index %s that is beyond the length of the string.", cd.Variables["index"])
	},
	OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) {
		ctx := cd.MainError.Context.(stringIndexOutOfBoundsExceptionCtx)

		index, _ := strconv.Atoi(cd.Variables["index"])

		gen.Add("Ensure the index is within the string length", func(s *lib.BugFixSuggestion) {
			obj := ctx.parentNode.ChildByFieldName("object")
			step := s.AddStep("Check that the index used for accessing the character is within the valid range of the string length.")
			gpLocation := ctx.grandParentNode.Location()

			wrapStatement(
				step,
				fmt.Sprintf("if (%d < %s.length()) {", index, obj.Text()),
				"\t} else {\n\t<i>System.out.println(\"Index out of range.\");\n\t}",
				lib.Location{
					StartPos: lib.Position{
						Line: gpLocation.StartPos.Line,
					},
					EndPos: gpLocation.EndPos,
				},
				true,
			)
		})
	},
}
View Source
var SymbolNotFoundError = lib.ErrorTemplate{
	Name:              "SymbolNotFoundError",
	Pattern:           comptimeErrorPattern("cannot find symbol", `symbol:\s+(?P<symbolType>variable|method|class) (?P<symbolName>\S+)\s+location\:\s+(?:(?:class (?P<locationClass>\S+))|(?:variable (?P<locationVariable>\S+) of type (?P<locationVarType>\S+)))`),
	StackTracePattern: comptimeStackTracePattern,
	OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) {
		symbolName := cd.Variables["symbolName"]
		errorCtx := symbolNotFoundErrorCtx{
			symbolType:       cd.Variables["symbolType"],
			locationClass:    cd.Variables["locationClass"],
			locationVariable: cd.Variables["locationVariable"],
			locationVarType:  cd.Variables["locationVarType"],
			symbolName:       symbolName,
			variableType:     lib.UnresolvedSymbol,
			rootNode:         m.Nearest,
		}

		nodeTypeToFind := "identifier"
		if errorCtx.symbolType == "class" {
			nodeTypeToFind = "type_identifier"
		}

		for q := m.Nearest.Query("((%s) @symbol (#eq? @symbol \"%s\"))", nodeTypeToFind, symbolName); q.Next(); {
			node := q.CurrentNode()
			errorCtx.rootNode = m.Nearest
			errorCtx.parentNode = node.Parent()
			m.Nearest = node
			break
		}

		if len(errorCtx.locationClass) > 0 {
			rootNode := m.Nearest.Doc.RootNode()
			for q := rootNode.Query(`(class_declaration name: (identifier) @class-name (#eq? @class-name "%s"))`, errorCtx.locationClass); q.Next(); {
				node := q.CurrentNode().Parent()
				errorCtx.locationNode = node
				break
			}
		} else if len(errorCtx.locationVariable) > 0 && len(errorCtx.locationVarType) > 0 {
			rootNode := m.Nearest.Doc.RootNode()
			for q := rootNode.Query(`
				(local_variable_declaration
					type: (_) @var-type
					declarator: (variable_declarator
						name: (identifier) @var-name
						(#eq? @var-name "%s"))
					(#eq? @var-type "%s"))`,
				errorCtx.locationVariable,
				errorCtx.locationVarType); q.Next(); q.Next() {
				node := q.CurrentNode().Parent()
				errorCtx.locationNode = node
				break
			}
		} else if len(errorCtx.locationVariable) > 0 {
			rootNode := m.Nearest.Doc.RootNode()
			for q := rootNode.Query(`(variable_declarator name: (identifier) @var-name (#eq? @var-name "%s"))`, errorCtx.locationVariable); q.Next(); {
				node := q.CurrentNode().Parent()
				errorCtx.locationNode = node
				break
			}

			declNode := errorCtx.locationNode.Parent()
			typeNode := declNode.ChildByFieldName("type")

			errorCtx.locationVarType = typeNode.Text()
		}

		if len(errorCtx.locationVarType) != 0 {
			errorCtx.locationClass = errorCtx.locationVarType
			foundVariableType := cd.FindSymbol(errorCtx.locationVarType, -1)
			if foundVariableType != nil {
				errorCtx.variableType = foundVariableType

				if topLevelSym, ok := foundVariableType.(*lib.TopLevelSymbol); ok {
					errorCtx.classLocation = topLevelSym.Location()
				}
			} else {
				errorCtx.variableType = lib.UnresolvedSymbol
			}
		}

		if !errorCtx.locationNode.IsNull() && errorCtx.locationNode.Type() != "class_declaration" {
			if errorCtx.classLocation.DocumentPath != "" {

				doc := cd.Store.Documents[errorCtx.classLocation.DocumentPath]
				foundNode := doc.RootNode().NamedDescendantForPointRange(errorCtx.classLocation)

				if !foundNode.IsNull() {
					errorCtx.locationNode = foundNode
				}
			} else {

				for !errorCtx.locationNode.IsNull() && errorCtx.locationNode.Type() != "class_declaration" {
					errorCtx.locationNode = errorCtx.locationNode.Parent()
				}
			}
		}

		m.Context = errorCtx
	},
	OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) {
		ctx := cd.MainError.Context.(symbolNotFoundErrorCtx)
		switch ctx.symbolType {
		case "variable":
			gen.Add(`The error indicates that the compiler cannot find variable "%s"`, ctx.symbolName)
		case "method":
			gen.Add("The error indicates that the compiler cannot find the method `%s` in the `%s` class.", ctx.symbolName, ctx.locationClass)
		case "class":
			gen.Add("The error indicates that the compiler cannot find the class `%s` when attempting to create an instance of it in the `%s` class.", ctx.symbolName, ctx.locationClass)
		}
	},
	OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) {
		ctx := cd.MainError.Context.(symbolNotFoundErrorCtx)
		switch ctx.symbolType {
		case "variable":
			affectedStatementPosition := ctx.rootNode.StartPosition()
			space := getSpaceFromBeginning(cd.MainError.Document, affectedStatementPosition.Line, affectedStatementPosition.Column)

			gen.Add("Create a variable.", func(s *lib.BugFixSuggestion) {
				s.AddStep("Create a variable named \"%s\". For example:", ctx.symbolName).
					AddFix(lib.FixSuggestion{
						NewText:       space + fmt.Sprintf("String %s = \"\";\n", ctx.symbolName),
						StartPosition: lib.Position{Line: affectedStatementPosition.Line, Column: 0},
						EndPosition:   lib.Position{Line: affectedStatementPosition.Line, Column: 0},
					})
			})
		case "method":

			if ctx.classLocation.DocumentPath != "" {
				gen.Document = cd.Store.Documents[ctx.classLocation.DocumentPath]
			}

			gen.Add("Define the missing method.", func(s *lib.BugFixSuggestion) {
				bodyNode := ctx.locationNode.ChildByFieldName("body")
				lastMethodNode := bodyNode.LastNamedChild()

				methodName, parameterTypes := parseMethodSignature(ctx.symbolName)
				parameters := make([]string, len(parameterTypes))
				for i, paramType := range parameterTypes {
					parameters[i] = fmt.Sprintf("%s %c", paramType, 'a'+i)
				}

				prefix := "Add"
				if ctx.classLocation.DocumentPath != "" {
					prefix = fmt.Sprintf("In `%s`, add", ctx.classLocation.DocumentPath)
				}

				s.AddStep("%s the missing method `%s` to the `%s` class", prefix, methodName, ctx.locationClass).
					AddFix(lib.FixSuggestion{
						NewText:       fmt.Sprintf("\n\n\tprivate static void %s(%s) {\n\t\t// Add code here\n\t}\n", methodName, strings.Join(parameters, ", ")),
						StartPosition: lastMethodNode.EndPosition().Add(lib.Position{Column: 1}),
						EndPosition:   lastMethodNode.EndPosition().Add(lib.Position{Column: 1}),
					})
			})
		case "class":
			gen.Add("Create the missing class", func(s *lib.BugFixSuggestion) {
				s.AddStep("Create a new class named `%s` to resolve the \"cannot find symbol\" error.", ctx.symbolName).
					AddFix(lib.FixSuggestion{
						NewText: fmt.Sprintf("class %s {\n\t// Add any necessary code for %s class\n}\n\n", ctx.symbolName, ctx.symbolName),
						StartPosition: lib.Position{
							Line:   ctx.locationNode.StartPosition().Line,
							Column: 0,
						},
						EndPosition: lib.Position{
							Line:   ctx.locationNode.StartPosition().Line,
							Column: 0,
						},
					})
			})
		}
	},
}
View Source
var UnclosedCharacterLiteralError = lib.ErrorTemplate{
	Name:              "UnclosedCharacterLiteralError",
	Pattern:           comptimeErrorPattern(`unclosed character literal`),
	StackTracePattern: comptimeStackTracePattern,
	OnAnalyzeErrorFn: func(cd *lib.ContextData, err *lib.MainError) {
		uContext := unclosedCharacterLiteralErrorCtx{
			parent: err.Nearest,
		}

		if err.Nearest.Type() != "character_literal" {
			for q := err.Nearest.Query(`(character_literal) @literal`); q.Next(); {
				node := q.CurrentNode()
				err.Nearest = node
				uContext.parent = node.Parent()
				break
			}
		}

		err.Context = uContext
	},
	OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) {
		gen.Add("This error occurs when there's an attempt to define a character literal with more than one character, or if the character literal is not closed properly.")
	},
	OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) {
		ctx := cd.MainError.Context.(unclosedCharacterLiteralErrorCtx)
		valueNode := cd.MainError.Nearest

		if ctx.parent.Type() == "local_variable_declaration" {
			if isString := len(valueNode.Text()) > 1; isString {
				valueStartPos := valueNode.StartPosition()
				valueEndPos := valueNode.EndPosition()

				gen.Add("Store as a String", func(s *lib.BugFixSuggestion) {
					typeNode := ctx.parent.ChildByFieldName("type")

					s.AddStep("The character literal should contain only one character. If you intend to store a string, use double quotes (`\"`).").
						AddFix(lib.FixSuggestion{
							NewText:       "String",
							StartPosition: typeNode.StartPosition(),
							EndPosition:   typeNode.EndPosition(),
						}).
						AddFix(lib.FixSuggestion{
							NewText: "\"",
							StartPosition: lib.Position{
								Line:   valueStartPos.Line,
								Column: valueStartPos.Column,
							},
							EndPosition: lib.Position{
								Line:   valueEndPos.Line,
								Column: valueStartPos.Column + 1,
							},
						}).
						AddFix(lib.FixSuggestion{
							NewText: "\"",
							StartPosition: lib.Position{
								Line:   valueStartPos.Line,
								Column: valueEndPos.Column - 1,
							},
							EndPosition: lib.Position{
								Line:   valueEndPos.Line,
								Column: valueEndPos.Column,
							},
						})
				})

				gen.Add("Use single quotes for characters", func(s *lib.BugFixSuggestion) {
					s.AddStep("If you want to store a single character, ensure that you use single quotes (`'`).").
						AddFix(lib.FixSuggestion{
							NewText:       fmt.Sprintf("'%c'", valueNode.Text()[1]),
							StartPosition: valueStartPos,
							EndPosition:   valueEndPos,
						})
				})
			}
		} else if isString := len(valueNode.Text()) > 1; isString {
			valueStartPos := valueNode.StartPosition()
			valueEndPos := valueNode.EndPosition()

			gen.Add("Convert string text to double quotes", func(s *lib.BugFixSuggestion) {
				s.AddStep("The character literal should contain only one character. If you intend to create a string, use double quotes (`\"`).").
					AddFix(lib.FixSuggestion{
						NewText: "\"",
						StartPosition: lib.Position{
							Line:   valueStartPos.Line,
							Column: valueStartPos.Column,
						},
						EndPosition: lib.Position{
							Line:   valueStartPos.Line,
							Column: valueStartPos.Column + 1,
						},
					}).
					AddFix(lib.FixSuggestion{
						NewText: "\"",
						StartPosition: lib.Position{
							Line:   valueEndPos.Line,
							Column: valueEndPos.Column - 2,
						},
						EndPosition: lib.Position{
							Line:   valueEndPos.Line,
							Column: valueEndPos.Column - 1,
						},
					})
			})

			gen.Add("Use single quotes for characters", func(s *lib.BugFixSuggestion) {
				s.AddStep("If your intention is to only use a single character, ensure that you use single quotes (`'`) and it only contains one character only.").
					AddFix(lib.FixSuggestion{
						NewText:       fmt.Sprintf("'%c'", valueNode.Text()[1]),
						StartPosition: valueStartPos,
						EndPosition:   valueEndPos,
					})
			})
		}
	},
}
View Source
var UnclosedStringLiteralError = lib.ErrorTemplate{
	Name:              "UnclosedStringLiteralError",
	Pattern:           comptimeErrorPattern(`unclosed string literal`),
	StackTracePattern: comptimeStackTracePattern,
	OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) {

		if !m.Nearest.IsError() {
			m.Nearest = m.Nearest.Parent()
		}

		for q := m.Nearest.Query(`(ERROR) @error`); q.Next(); {
			node := q.CurrentNode()
			m.Nearest = node

			break
		}
	},
	OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) {
		gen.Add("This error occurs when there is an unclosed string literal in the code.")
	},
	OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) {
		parent := cd.MainError.Nearest.Parent()

		if parent.Type() == "variable_declarator" {
			gen.Add("Close the string literal", func(s *lib.BugFixSuggestion) {
				s.AddStep("Ensure that the string literal is properly closed with a double-quote.").
					AddFix(lib.FixSuggestion{
						NewText:       "\"",
						StartPosition: cd.MainError.Nearest.EndPosition().Add(lib.Position{Column: -1}),
						EndPosition:   cd.MainError.Nearest.EndPosition().Add(lib.Position{Column: -1}),
					})
			})
		}
	},
}
View Source
var UninitializedVariableError = lib.ErrorTemplate{
	Name:              "UninitializedVariableError",
	Pattern:           comptimeErrorPattern(`variable (?P<variable>\S+) might not have been initialized`),
	StackTracePattern: comptimeStackTracePattern,
	OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) {
		uCtx := uninitializedVariableErrCtx{}
		q := m.Nearest.Query(`((identifier) @variable (#eq? @variable "%s"))`, cd.Variables["variable"])
		for q.Next() {
			m.Nearest = q.CurrentNode()
			break
		}

		root := m.Document.RootNode()
		nearestTree := cd.InitOrGetSymbolTree(cd.MainDocumentPath()).GetNearestScopedTree(m.Nearest.StartPosition().Index)
		declaredVariableSym := nearestTree.GetSymbolByNode(m.Nearest)
		declNode := root.NamedDescendantForPointRange(declaredVariableSym.Location())

		uCtx.DeclarationSym = declaredVariableSym

		if !declNode.IsNull() && !declNode.Parent().IsNull() && declNode.Parent().Type() == "variable_declarator" {
			uCtx.DeclarationNode = declNode.Parent().Parent()
		}

		m.Context = uCtx
	},
	OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) {
		gen.Add("This error occurs when you try to use a variable that has not been initialized with a value.")
	},
	OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) {
		ctx := cd.MainError.Context.(uninitializedVariableErrCtx)
		gen.Add("Initialize the variable", func(s *lib.BugFixSuggestion) {
			nameNode := ctx.DeclarationNode.ChildByFieldName("declarator").ChildByFieldName("name")

			s.AddStep("To resolve the uninitialized variable error, you need to initialize the `%s` variable with a value.", cd.Variables["variable"]).
				AddFix(lib.FixSuggestion{
					NewText:       fmt.Sprintf(" = %s", getDefaultValueForType(ctx.DeclarationSym.(*lib.VariableSymbol).ReturnType())),
					StartPosition: nameNode.EndPosition(),
					EndPosition:   nameNode.EndPosition(),
					Description:   "This ensures that the variable has a valid initial value before it's used.",
				})
		})

		gen.Add("Assign a value before using", func(s *lib.BugFixSuggestion) {
			startPos := ctx.DeclarationNode.StartPosition()
			spaces := getSpaceFromBeginning(cd.MainError.Document, startPos.Line, startPos.Column)

			s.AddStep("Alternatively, you can assign a value to the variable before using it.").AddFix(lib.FixSuggestion{
				NewText:       "\n" + spaces + fmt.Sprintf("%s = %s; // or any other valid value", cd.Variables["variable"], getDefaultValueForType(ctx.DeclarationSym.(*lib.VariableSymbol).ReturnType())),
				StartPosition: ctx.DeclarationNode.EndPosition(),
				EndPosition:   ctx.DeclarationNode.EndPosition(),
				Description:   "This way, the variable is initialized with a value before it's used in the statement.",
			})
		})
	},
}
View Source
var UnreachableStatementError = lib.ErrorTemplate{
	Name:              "UnreachableStatementError",
	Pattern:           comptimeErrorPattern("unreachable statement"),
	StackTracePattern: comptimeStackTracePattern,
	OnAnalyzeErrorFn: func(cd *lib.ContextData, m *lib.MainError) {

	},
	OnGenExplainFn: func(cd *lib.ContextData, gen *lib.ExplainGenerator) {
		gen.Add("This error occurs because there's code after a return statement, which can never be reached as the function has already exited.")
	},
	OnGenBugFixFn: func(cd *lib.ContextData, gen *lib.BugFixGenerator) {
		gen.Add("Remove unreachable code", func(s *lib.BugFixSuggestion) {
			startPos := cd.MainError.Nearest.StartPosition()
			endPos := cd.MainError.Nearest.Parent().LastNamedChild().EndPosition()

			startPos = startPos.Add(lib.Position{Column: -startPos.Column})

			s.AddStep(
				"Since the `return` statement is encountered before `%s`, the latter statement is unreachable. Remove the unreachable statement/s.",
				cd.MainError.Nearest.Text(),
			).AddFix(lib.FixSuggestion{
				NewText:       "",
				StartPosition: startPos,
				EndPosition:   endPos,
			})
		})
	},
}

Functions

func GetSpaceBoundary

func GetSpaceBoundary(line string, from int, to int) (int, int)

func LoadErrorTemplates

func LoadErrorTemplates(errorTemplates *lib.ErrorTemplates)

Types

This section is empty.

Jump to

Keyboard shortcuts

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