Skip to content
Merged
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ All notable changes to the Docker Language Server will be documented in this fil
- textDocument/publishDiagnostics
- introduce a setting to ignore certain diagnostics to not duplicate the ones from the Dockerfile Language Server

### Fixed

- Docker Bake
- textDocument/definition
- always return LocationLinks to help disambiguate word boundaries for clients ([#31](https://github.com/docker/docker-language-server/issues/31))

## 0.1.0 - 2025-03-31

### Added
Expand Down
214 changes: 142 additions & 72 deletions internal/bake/hcl/definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,21 +89,30 @@ func Definition(ctx context.Context, definitionLinkSupport bool, manager *docume

for _, attribute := range body.Attributes {
if isInsideRange(attribute.NameRange, position) {
return []protocol.Location{
{
Range: protocol.Range{
Start: protocol.Position{
Line: uint32(attribute.NameRange.Start.Line) - 1,
Character: uint32(attribute.NameRange.Start.Column) - 1,
},
End: protocol.Position{
Line: uint32(attribute.NameRange.End.Line) - 1,
Character: uint32(attribute.NameRange.End.Column) - 1,
},
return createDefinitionResult(
definitionLinkSupport,
protocol.Range{
Start: protocol.Position{
Line: uint32(attribute.NameRange.Start.Line) - 1,
Character: uint32(attribute.NameRange.Start.Column) - 1,
},
End: protocol.Position{
Line: uint32(attribute.NameRange.End.Line) - 1,
Character: uint32(attribute.NameRange.End.Column) - 1,
},
},
&protocol.Range{
Start: protocol.Position{
Line: uint32(attribute.NameRange.Start.Line) - 1,
Character: uint32(attribute.NameRange.Start.Column) - 1,
},
End: protocol.Position{
Line: uint32(attribute.NameRange.End.Line) - 1,
Character: uint32(attribute.NameRange.End.Column) - 1,
},
URI: string(documentURI),
},
}, nil
string(documentURI),
), nil
}

if isInsideRange(attribute.SrcRange, position) {
Expand All @@ -126,7 +135,18 @@ func ResolveAttributeValue(ctx context.Context, definitionLinkSupport bool, mana
(sourceBlock.Type == "group" && attribute.Name == "targets") {
value, _ := templateExpr.Value(&hcl.EvalContext{})
target := value.AsString()
return CalculateBlockLocation(input, body, documentURI, "target", target, false)
templateExprRange := templateExpr.Range()
sourceRange := hcl.Range{
Start: hcl.Pos{
Line: templateExprRange.Start.Line,
Column: templateExprRange.Start.Column + 1,
},
End: hcl.Pos{
Line: templateExprRange.End.Line,
Column: templateExprRange.End.Column - 1,
},
}
return CalculateBlockLocation(definitionLinkSupport, input, body, documentURI, sourceRange, "target", target, false)
}
}
}
Expand Down Expand Up @@ -158,32 +178,30 @@ func ResolveExpression(ctx context.Context, definitionLinkSupport bool, manager
for _, child := range nodes {
if strings.EqualFold(child.Value, "FROM") {
if child.Next != nil && child.Next.Next != nil && strings.EqualFold(child.Next.Next.Value, "AS") && child.Next.Next.Next != nil && child.Next.Next.Next.Value == target {
targetRange := protocol.Range{
Start: protocol.Position{Line: uint32(child.StartLine) - 1, Character: 0},
End: protocol.Position{Line: uint32(child.EndLine) - 1, Character: uint32(len(lines[child.EndLine-1]))},
}

linkURI := protocol.URI(fmt.Sprintf("file:///%v", strings.TrimPrefix(filepath.ToSlash(dockerfilePath), "/")))
if !definitionLinkSupport {
return []protocol.Location{
{
Range: targetRange,
URI: linkURI,
return createDefinitionResult(
definitionLinkSupport,
protocol.Range{
Start: protocol.Position{
Line: uint32(child.StartLine) - 1,
Character: 0,
},
}
}

return []protocol.LocationLink{
{
OriginSelectionRange: &protocol.Range{
Start: protocol.Position{Line: uint32(literalValueExpr.Range().Start.Line) - 1, Character: uint32(literalValueExpr.Range().Start.Column) - 1},
End: protocol.Position{Line: uint32(literalValueExpr.Range().End.Line) - 1, Character: uint32(uint32(literalValueExpr.Range().End.Column) - 1)},
End: protocol.Position{
Line: uint32(child.EndLine) - 1,
Character: uint32(len(lines[child.EndLine-1])),
},
TargetRange: targetRange,
TargetSelectionRange: targetRange,
TargetURI: linkURI,
},
}
&protocol.Range{
Start: protocol.Position{
Line: uint32(literalValueExpr.Range().Start.Line) - 1,
Character: uint32(literalValueExpr.Range().Start.Column) - 1,
},
End: protocol.Position{
Line: uint32(literalValueExpr.Range().End.Line) - 1,
Character: uint32(uint32(literalValueExpr.Range().End.Column) - 1),
},
},
protocol.URI(fmt.Sprintf("file:///%v", strings.TrimPrefix(filepath.ToSlash(dockerfilePath), "/"))),
)
}
}
}
Expand Down Expand Up @@ -225,15 +243,29 @@ func ResolveExpression(ctx context.Context, definitionLinkSupport bool, manager
}

if value == arg {
return []protocol.Location{
{
Range: protocol.Range{
Start: protocol.Position{Line: uint32(node.StartLine) - 1, Character: 0},
End: protocol.Position{Line: uint32(node.EndLine) - 1, Character: uint32(len(lines[node.EndLine-1]))},
},
URI: protocol.URI(fmt.Sprintf("file:///%v", strings.TrimPrefix(filepath.ToSlash(dockerfilePath), "/"))),
originSelectionRange := protocol.Range{
Start: protocol.Position{
Line: uint32(item.KeyExpr.Range().Start.Line) - 1,
Character: uint32(item.KeyExpr.Range().Start.Column) - 1,
},
End: protocol.Position{
Line: uint32(item.KeyExpr.Range().End.Line) - 1,
Character: uint32(item.KeyExpr.Range().End.Column) - 1,
},
}
if LiteralValue(item.KeyExpr) {
originSelectionRange.Start.Character = originSelectionRange.Start.Character + 1
originSelectionRange.End.Character = originSelectionRange.End.Character - 1
}
return createDefinitionResult(
definitionLinkSupport,
protocol.Range{
Start: protocol.Position{Line: uint32(node.StartLine) - 1, Character: 0},
End: protocol.Position{Line: uint32(node.EndLine) - 1, Character: uint32(len(lines[node.EndLine-1]))},
},
&originSelectionRange,
protocol.URI(fmt.Sprintf("file:///%v", strings.TrimPrefix(filepath.ToSlash(dockerfilePath), "/"))),
)
}
child = child.Next
}
Expand Down Expand Up @@ -278,7 +310,7 @@ func ResolveExpression(ctx context.Context, definitionLinkSupport bool, manager

if _, ok := expression.(*hclsyntax.ScopeTraversalExpr); ok {
name := string(input[expression.Range().Start.Byte:expression.Range().End.Byte])
return CalculateBlockLocation(input, body, documentURI, "variable", name, true)
return CalculateBlockLocation(definitionLinkSupport, input, body, documentURI, expression.Range(), "variable", name, true)
}

if templateWrapExpr, ok := expression.(*hclsyntax.TemplateWrapExpr); ok {
Expand All @@ -287,7 +319,7 @@ func ResolveExpression(ctx context.Context, definitionLinkSupport bool, manager

if functionCallExpr, ok := expression.(*hclsyntax.FunctionCallExpr); ok {
if isInsideRange(functionCallExpr.NameRange, position) {
return CalculateBlockLocation(input, body, documentURI, "function", functionCallExpr.Name, true)
return CalculateBlockLocation(definitionLinkSupport, input, body, documentURI, functionCallExpr.NameRange, "function", functionCallExpr.Name, true)
}

for _, arg := range functionCallExpr.Args {
Expand All @@ -303,7 +335,7 @@ func ResolveExpression(ctx context.Context, definitionLinkSupport bool, manager
// returns it. If variable is true then it will also look at the
// top-level attributes of the HCL file and resolve to those if the
// names match.
func CalculateBlockLocation(input []byte, body *hclsyntax.Body, documentURI uri.URI, blockName, name string, variable bool) any {
func CalculateBlockLocation(definitionLinkSupport bool, input []byte, body *hclsyntax.Body, documentURI uri.URI, sourceRange hcl.Range, blockName, name string, variable bool) any {
for _, b := range body.Blocks {
if b.Type == blockName && b.Labels[0] == name {
startCharacter := uint32(b.LabelRanges[0].Start.Column)
Expand All @@ -315,42 +347,80 @@ func CalculateBlockLocation(input []byte, body *hclsyntax.Body, documentURI uri.
startCharacter--
endCharacter--
}
return []protocol.Location{
{
Range: protocol.Range{
Start: protocol.Position{
Line: uint32(b.LabelRanges[0].Start.Line) - 1,
Character: startCharacter,
},
End: protocol.Position{
Line: uint32(b.LabelRanges[0].End.Line) - 1,
Character: endCharacter,
},
return createDefinitionResult(
definitionLinkSupport,
protocol.Range{
Start: protocol.Position{
Line: uint32(b.LabelRanges[0].Start.Line) - 1,
Character: startCharacter,
},
End: protocol.Position{
Line: uint32(b.LabelRanges[0].End.Line) - 1,
Character: endCharacter,
},
URI: string(documentURI),
},
}
&protocol.Range{
Start: protocol.Position{
Line: uint32(sourceRange.Start.Line) - 1,
Character: uint32(sourceRange.Start.Column) - 1,
},
End: protocol.Position{
Line: uint32(sourceRange.End.Line) - 1,
Character: uint32(sourceRange.End.Column) - 1,
},
},
string(documentURI),
)
}
}

if attribute, ok := body.Attributes[name]; ok && variable {
return createDefinitionResult(
definitionLinkSupport,
protocol.Range{
Start: protocol.Position{
Line: uint32(attribute.NameRange.Start.Line) - 1,
Character: uint32(attribute.NameRange.Start.Column) - 1,
},
End: protocol.Position{
Line: uint32(attribute.NameRange.End.Line) - 1,
Character: uint32(attribute.NameRange.End.Column) - 1,
},
},
&protocol.Range{
Start: protocol.Position{
Line: uint32(sourceRange.Start.Line) - 1,
Character: uint32(sourceRange.Start.Column) - 1,
},
End: protocol.Position{
Line: uint32(sourceRange.End.Line) - 1,
Character: uint32(sourceRange.End.Column) - 1,
},
},
string(documentURI),
)
}
return nil
}

func createDefinitionResult(definitionLinkSupport bool, targetRange protocol.Range, originSelectionRange *protocol.Range, linkURI protocol.URI) any {
if !definitionLinkSupport {
return []protocol.Location{
{
Range: protocol.Range{
Start: protocol.Position{
Line: uint32(attribute.NameRange.Start.Line) - 1,
Character: uint32(attribute.NameRange.Start.Column) - 1,
},
End: protocol.Position{
Line: uint32(attribute.NameRange.End.Line) - 1,
Character: uint32(attribute.NameRange.End.Column) - 1,
},
},
URI: string(documentURI),
Range: targetRange,
URI: linkURI,
},
}
}
return nil

return []protocol.LocationLink{
{
OriginSelectionRange: originSelectionRange,
TargetRange: targetRange,
TargetSelectionRange: targetRange,
TargetURI: linkURI,
},
}
}

func ParseDockerfile(dockerfilePath string) ([]byte, *parser.Result, error) {
Expand Down
Loading