Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 23 additions & 18 deletions internal/handler/yaml_handler/diagnostics.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"regexp"
"strconv"

helmlint "github.com/mrjosh/helm-ls/internal/helm_lint"
"go.lsp.dev/protocol"
"go.lsp.dev/uri"
)
Expand All @@ -15,16 +16,22 @@ var lineNumberRegex = regexp.MustCompile("line ([0-9]+): (.*)")
// GetDiagnostics implements handler.LangHandler.
func (h *YamlHandler) GetDiagnostics(uri uri.URI) []protocol.PublishDiagnosticsParams {
doc, ok := h.documents.GetYamlDoc(uri)
chart, err := h.chartStore.GetChartForDoc(uri)
diagnostics := []protocol.Diagnostic{}

if !ok {
return nil
}

if err == nil {
diagnostics = append(diagnostics, helmlint.LintUnusedValues(chart, doc, h.documents.GetAllTemplateDocs())...)
}

if doc.ParseErr == nil {
logger.Debug("YamlHandler: No parse error")
return []protocol.PublishDiagnosticsParams{{
URI: uri,
Diagnostics: []protocol.Diagnostic{},
Diagnostics: diagnostics,
}}
}

Expand Down Expand Up @@ -55,25 +62,23 @@ func (h *YamlHandler) GetDiagnostics(uri uri.URI) []protocol.PublishDiagnosticsP
return []protocol.PublishDiagnosticsParams{
{
URI: uri,
Diagnostics: []protocol.Diagnostic{
{
Range: protocol.Range{
Start: protocol.Position{
Line: lineUint,
Character: 0,
},
End: protocol.Position{
Line: lineUint + 1,
Character: 0,
},
Diagnostics: append(diagnostics, protocol.Diagnostic{
Range: protocol.Range{
Start: protocol.Position{
Line: lineUint,
Character: 0,
},
End: protocol.Position{
Line: lineUint + 1,
Character: 0,
},
Source: "Helm-ls YamlHandler",
Message: matches[2],
Tags: []protocol.DiagnosticTag{},
RelatedInformation: []protocol.DiagnosticRelatedInformation{},
Data: nil,
},
},
Source: "Helm-ls YamlHandler",
Message: matches[2],
Tags: []protocol.DiagnosticTag{},
RelatedInformation: []protocol.DiagnosticRelatedInformation{},
Data: nil,
}),
},
}
}
86 changes: 86 additions & 0 deletions internal/helm_lint/values_lint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package helmlint

import (
"fmt"

"github.com/goccy/go-yaml/ast"
"github.com/mrjosh/helm-ls/internal/charts"
"github.com/mrjosh/helm-ls/internal/lsp/document"
"github.com/mrjosh/helm-ls/internal/lsp/symboltable"
"github.com/mrjosh/helm-ls/internal/util"
lsp "go.lsp.dev/protocol"
)

func LintUnusedValues(chart *charts.Chart, doc *document.YamlDocument, templateDocs []*document.TemplateDocument) []lsp.Diagnostic {
if len(templateDocs) == 0 {
// TODO: delay linting until the template docs are loaded
return []lsp.Diagnostic{}
}

result := []lsp.Diagnostic{}
for _, node := range FindNodes(doc.GoccyYamlNode) {

templateContext := symboltable.TemplateContextFromYAMLPath(node.GetPath())
found := false

for _, templateDoc := range templateDocs {
// TODO(dependecy-charts): template context would need to be adjusted for dependency charts
// see https://github.com/mrjosh/helm-ls/issues/152
referenceRanges := templateDoc.SymbolTable.GetTemplateContextRanges(append([]string{"Values"}, templateContext...))

logger.Println(fmt.Sprintf("LintUnusedValues: checking template context %v in template doc %s", templateContext, templateDoc.GetPath()))

if len(referenceRanges) > 0 {
found = true
break
}
}

if found {
continue
}

result = append(result, lsp.Diagnostic{
Range: util.TokenToRange(node.GetToken()),
Severity: lsp.DiagnosticSeverityHint,
Code: nil,
CodeDescription: &lsp.CodeDescription{},
Source: "helm-ls unused values",
Message: fmt.Sprintf("Unused value: %s of type %s", node.GetPath(), node.Type()),
Tags: []lsp.DiagnosticTag{
lsp.DiagnosticTagUnnecessary,
},
RelatedInformation: []lsp.DiagnosticRelatedInformation{},
Data: nil,
})
}

return result
}

func FindNodes(node ast.Node) []ast.Node {
visitor := &LeafMappingsFinderVisitor{}
ast.Walk(visitor, node)
return visitor.result
}

// PositionFinderVisitor is a visitor that collects positions.
type LeafMappingsFinderVisitor struct {
result []ast.Node
}

func (v *LeafMappingsFinderVisitor) Visit(node ast.Node) ast.Visitor {
if IsLeafMapping(node) {
v.result = append(v.result, node)
}
return v
}

func IsLeafMapping(node ast.Node) bool {
switch node.Type() {
case ast.MappingKeyType, ast.MappingValueType, ast.MappingType, ast.SequenceType:
return false
default:
return true
}
}
13 changes: 13 additions & 0 deletions internal/util/yaml_goccy.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import (

"github.com/goccy/go-yaml"
"github.com/goccy/go-yaml/ast"
"github.com/goccy/go-yaml/token"
"go.lsp.dev/protocol"
lsp "go.lsp.dev/protocol"
)

func GetNodeForPosition(node ast.Node, position protocol.Position) ast.Node {
Expand Down Expand Up @@ -83,3 +85,14 @@ func ReadYamlToGoccyNode(data []byte) (node ast.Node, err error) {
err = yaml.Unmarshal(normalizedData, &node)
return node, err
}

func TokenToRange(token *token.Token) lsp.Range {
if token == nil {
return lsp.Range{}
}

return lsp.Range{
Start: lsp.Position{Line: uint32(token.Position.Line - 1), Character: uint32(token.Position.Column - 1)},
End: lsp.Position{Line: uint32(token.Position.Line - 1), Character: uint32(token.Position.Column+len(token.Value)) + 1},
}
}
Loading