mirror of
https://github.com/kubescape/kubescape.git
synced 2026-03-09 13:12:07 +00:00
This change makes the autofix handler use the newline separator defined in the fixed file for writing its changes.
287 lines
9.0 KiB
Go
287 lines
9.0 KiB
Go
package fixhandler
|
|
|
|
import (
|
|
"container/list"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
|
|
"github.com/mikefarah/yq/v4/pkg/yqlib"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// decodeDocumentRoots decodes all YAML documents stored in a given `filepath` and returns a slice of their root nodes
|
|
func decodeDocumentRoots(yamlAsString string) ([]yaml.Node, error) {
|
|
fileReader := strings.NewReader(yamlAsString)
|
|
dec := yaml.NewDecoder(fileReader)
|
|
|
|
nodes := make([]yaml.Node, 0)
|
|
for {
|
|
var node yaml.Node
|
|
err := dec.Decode(&node)
|
|
|
|
nodes = append(nodes, node)
|
|
|
|
if errors.Is(err, io.EOF) {
|
|
break
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Cannot Decode File as YAML")
|
|
|
|
}
|
|
}
|
|
|
|
return nodes, nil
|
|
}
|
|
|
|
func getFixedNodes(yamlAsString, yamlExpression string) ([]yaml.Node, error) {
|
|
preferences := yqlib.ConfiguredYamlPreferences
|
|
preferences.EvaluateTogether = true
|
|
decoder := yqlib.NewYamlDecoder(preferences)
|
|
|
|
var allDocuments = list.New()
|
|
reader := strings.NewReader(yamlAsString)
|
|
|
|
fileDocuments, err := readDocuments(reader, decoder)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
allDocuments.PushBackList(fileDocuments)
|
|
|
|
allAtOnceEvaluator := yqlib.NewAllAtOnceEvaluator()
|
|
|
|
fixedCandidateNodes, err := allAtOnceEvaluator.EvaluateCandidateNodes(yamlExpression, allDocuments)
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error fixing YAML, %w", err)
|
|
}
|
|
|
|
fixedNodes := make([]yaml.Node, 0)
|
|
var fixedNode *yaml.Node
|
|
for fixedCandidateNode := fixedCandidateNodes.Front(); fixedCandidateNode != nil; fixedCandidateNode = fixedCandidateNode.Next() {
|
|
fixedNode = fixedCandidateNode.Value.(*yqlib.CandidateNode).Node
|
|
fixedNodes = append(fixedNodes, *fixedNode)
|
|
}
|
|
|
|
return fixedNodes, nil
|
|
}
|
|
|
|
func flattenWithDFS(node *yaml.Node) *[]nodeInfo {
|
|
dfsOrder := make([]nodeInfo, 0)
|
|
flattenWithDFSHelper(node, nil, &dfsOrder, 0)
|
|
return &dfsOrder
|
|
}
|
|
|
|
func flattenWithDFSHelper(node *yaml.Node, parent *yaml.Node, dfsOrder *[]nodeInfo, index int) {
|
|
dfsNode := nodeInfo{
|
|
node: node,
|
|
parent: parent,
|
|
index: index,
|
|
}
|
|
*dfsOrder = append(*dfsOrder, dfsNode)
|
|
|
|
for idx, child := range node.Content {
|
|
flattenWithDFSHelper(child, node, dfsOrder, idx)
|
|
}
|
|
}
|
|
|
|
func getFixInfo(originalRootNodes, fixedRootNodes []yaml.Node) fileFixInfo {
|
|
contentToAdd := make([]contentToAdd, 0)
|
|
linesToRemove := make([]linesToRemove, 0)
|
|
|
|
for idx := 0; idx < len(fixedRootNodes); idx++ {
|
|
originalList := flattenWithDFS(&originalRootNodes[idx])
|
|
fixedList := flattenWithDFS(&fixedRootNodes[idx])
|
|
nodeContentToAdd, nodeLinesToRemove := getFixInfoHelper(*originalList, *fixedList)
|
|
contentToAdd = append(contentToAdd, nodeContentToAdd...)
|
|
linesToRemove = append(linesToRemove, nodeLinesToRemove...)
|
|
}
|
|
|
|
return fileFixInfo{
|
|
contentsToAdd: &contentToAdd,
|
|
linesToRemove: &linesToRemove,
|
|
}
|
|
}
|
|
|
|
func getFixInfoHelper(originalList, fixedList []nodeInfo) ([]contentToAdd, []linesToRemove) {
|
|
|
|
// While obtaining fixedYamlNode, comments and empty lines at the top are ignored.
|
|
// This causes a difference in Line numbers across the tree structure. In order to
|
|
// counter this, line numbers are adjusted in fixed list.
|
|
adjustFixedListLines(&originalList, &fixedList)
|
|
|
|
contentToAdd := make([]contentToAdd, 0)
|
|
linesToRemove := make([]linesToRemove, 0)
|
|
|
|
originalListTracker, fixedListTracker := 0, 0
|
|
|
|
fixInfoMetadata := &fixInfoMetadata{
|
|
originalList: &originalList,
|
|
fixedList: &fixedList,
|
|
originalListTracker: originalListTracker,
|
|
fixedListTracker: fixedListTracker,
|
|
contentToAdd: &contentToAdd,
|
|
linesToRemove: &linesToRemove,
|
|
}
|
|
|
|
for originalListTracker < len(originalList) && fixedListTracker < len(fixedList) {
|
|
matchNodeResult := matchNodes(originalList[originalListTracker].node, fixedList[fixedListTracker].node)
|
|
|
|
fixInfoMetadata.originalListTracker = originalListTracker
|
|
fixInfoMetadata.fixedListTracker = fixedListTracker
|
|
|
|
switch matchNodeResult {
|
|
case sameNodes:
|
|
originalListTracker += 1
|
|
fixedListTracker += 1
|
|
|
|
case removedNode:
|
|
originalListTracker, fixedListTracker = addLinesToRemove(fixInfoMetadata)
|
|
|
|
case insertedNode:
|
|
originalListTracker, fixedListTracker = addLinesToInsert(fixInfoMetadata)
|
|
|
|
case replacedNode:
|
|
originalListTracker, fixedListTracker = updateLinesToReplace(fixInfoMetadata)
|
|
}
|
|
}
|
|
|
|
// Some nodes are still not visited if they are removed at the end of the list
|
|
for originalListTracker < len(originalList) {
|
|
fixInfoMetadata.originalListTracker = originalListTracker
|
|
originalListTracker, _ = addLinesToRemove(fixInfoMetadata)
|
|
}
|
|
|
|
// Some nodes are still not visited if they are inserted at the end of the list
|
|
for fixedListTracker < len(fixedList) {
|
|
// Use negative index of last node in original list as a placeholder to determine the last line number later
|
|
fixInfoMetadata.originalListTracker = -(len(originalList) - 1)
|
|
fixInfoMetadata.fixedListTracker = fixedListTracker
|
|
_, fixedListTracker = addLinesToInsert(fixInfoMetadata)
|
|
}
|
|
|
|
return contentToAdd, linesToRemove
|
|
|
|
}
|
|
|
|
// Adds the lines to remove and returns the updated originalListTracker
|
|
func addLinesToRemove(fixInfoMetadata *fixInfoMetadata) (int, int) {
|
|
isOneLine, line := isOneLineSequenceNode(fixInfoMetadata.originalList, fixInfoMetadata.originalListTracker)
|
|
|
|
if isOneLine {
|
|
// Remove the entire line and replace it with the sequence node in fixed info. This way,
|
|
// the original formatting is not lost.
|
|
return replaceSingleLineSequence(fixInfoMetadata, line)
|
|
}
|
|
|
|
currentDFSNode := (*fixInfoMetadata.originalList)[fixInfoMetadata.originalListTracker]
|
|
|
|
newOriginalListTracker := updateTracker(fixInfoMetadata.originalList, fixInfoMetadata.originalListTracker)
|
|
*fixInfoMetadata.linesToRemove = append(*fixInfoMetadata.linesToRemove, linesToRemove{
|
|
startLine: currentDFSNode.node.Line,
|
|
endLine: getNodeLine(fixInfoMetadata.originalList, newOriginalListTracker),
|
|
})
|
|
|
|
return newOriginalListTracker, fixInfoMetadata.fixedListTracker
|
|
}
|
|
|
|
// Adds the lines to insert and returns the updated fixedListTracker
|
|
func addLinesToInsert(fixInfoMetadata *fixInfoMetadata) (int, int) {
|
|
|
|
isOneLine, line := isOneLineSequenceNode(fixInfoMetadata.fixedList, fixInfoMetadata.fixedListTracker)
|
|
|
|
if isOneLine {
|
|
return replaceSingleLineSequence(fixInfoMetadata, line)
|
|
}
|
|
|
|
currentDFSNode := (*fixInfoMetadata.fixedList)[fixInfoMetadata.fixedListTracker]
|
|
|
|
lineToInsert := getLineToInsert(fixInfoMetadata)
|
|
contentToInsert := getContent(currentDFSNode.parent, fixInfoMetadata.fixedList, fixInfoMetadata.fixedListTracker)
|
|
|
|
newFixedTracker := updateTracker(fixInfoMetadata.fixedList, fixInfoMetadata.fixedListTracker)
|
|
|
|
*fixInfoMetadata.contentToAdd = append(*fixInfoMetadata.contentToAdd, contentToAdd{
|
|
line: lineToInsert,
|
|
content: contentToInsert,
|
|
})
|
|
|
|
return fixInfoMetadata.originalListTracker, newFixedTracker
|
|
}
|
|
|
|
// Adds the lines to remove and insert and updates the fixedListTracker and originalListTracker
|
|
func updateLinesToReplace(fixInfoMetadata *fixInfoMetadata) (int, int) {
|
|
|
|
isOneLine, line := isOneLineSequenceNode(fixInfoMetadata.fixedList, fixInfoMetadata.fixedListTracker)
|
|
|
|
if isOneLine {
|
|
return replaceSingleLineSequence(fixInfoMetadata, line)
|
|
}
|
|
|
|
currentDFSNode := (*fixInfoMetadata.fixedList)[fixInfoMetadata.fixedListTracker]
|
|
|
|
// If only the value node is changed, entire "key-value" pair is replaced
|
|
if isValueNodeinMapping(¤tDFSNode) {
|
|
fixInfoMetadata.originalListTracker -= 1
|
|
fixInfoMetadata.fixedListTracker -= 1
|
|
}
|
|
|
|
addLinesToRemove(fixInfoMetadata)
|
|
updatedOriginalTracker, updatedFixedTracker := addLinesToInsert(fixInfoMetadata)
|
|
|
|
return updatedOriginalTracker, updatedFixedTracker
|
|
}
|
|
|
|
func removeNewLinesAtTheEnd(yamlLines []string) []string {
|
|
for idx := 1; idx < len(yamlLines); idx++ {
|
|
if yamlLines[len(yamlLines)-idx] != "\n" {
|
|
yamlLines = yamlLines[:len(yamlLines)-idx+1]
|
|
break
|
|
}
|
|
}
|
|
return yamlLines
|
|
}
|
|
|
|
func getFixedYamlLines(yamlLines []string, fileFixInfo fileFixInfo, newline string) (fixedYamlLines []string) {
|
|
|
|
// Determining last line requires original yaml lines slice. The placeholder for last line is replaced with the real last line
|
|
assignLastLine(fileFixInfo.contentsToAdd, fileFixInfo.linesToRemove, &yamlLines)
|
|
|
|
removeLines(fileFixInfo.linesToRemove, &yamlLines)
|
|
|
|
fixedYamlLines = make([]string, 0)
|
|
lineIdx, lineToAddIdx := 1, 0
|
|
|
|
// Ideally, new node is inserted at line before the next node in DFS order. But, when the previous line contains a
|
|
// comment or empty line, we need to insert new nodes before them.
|
|
adjustContentLines(fileFixInfo.contentsToAdd, &yamlLines)
|
|
|
|
for lineToAddIdx < len(*fileFixInfo.contentsToAdd) {
|
|
for lineIdx <= (*fileFixInfo.contentsToAdd)[lineToAddIdx].line {
|
|
// Check if the current line is not removed
|
|
if yamlLines[lineIdx-1] != "*" {
|
|
fixedYamlLines = append(fixedYamlLines, yamlLines[lineIdx-1])
|
|
}
|
|
lineIdx += 1
|
|
}
|
|
|
|
content := (*fileFixInfo.contentsToAdd)[lineToAddIdx].Content(newline)
|
|
fixedYamlLines = append(fixedYamlLines, content)
|
|
|
|
lineToAddIdx += 1
|
|
}
|
|
|
|
for lineIdx <= len(yamlLines) {
|
|
if yamlLines[lineIdx-1] != "*" {
|
|
fixedYamlLines = append(fixedYamlLines, yamlLines[lineIdx-1])
|
|
}
|
|
lineIdx += 1
|
|
}
|
|
|
|
fixedYamlLines = removeNewLinesAtTheEnd(fixedYamlLines)
|
|
|
|
return fixedYamlLines
|
|
}
|