Skip to content

Commit 532b1ff

Browse files
authored
Merge pull request #30 from docker/configure-overlapping-dockerfile-diagnostics
Add initialization option to suppress some Dockerfile diagnostics
2 parents 25ca943 + 2c8e7c7 commit 532b1ff

File tree

6 files changed

+267
-40
lines changed

6 files changed

+267
-40
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
All notable changes to the Docker Language Server will be documented in this file.
44

5+
## Unreleased
6+
7+
### Added
8+
9+
- Dockerfile
10+
- textDocument/publishDiagnostics
11+
- introduce a setting to ignore certain diagnostics to not duplicate the ones from the Dockerfile Language Server
12+
513
## 0.1.0 - 2025-03-31
614

715
### Added

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,20 @@ Use "docker-language-server [command] --help" for more information about a comma
6969

7070
## Language Server Configuration
7171

72+
### Initialization Options
73+
74+
On startup, the client can include initizliation options on the initial `initialize` request. If the client is also using [rcjsuen/dockerfile-language-server](https://github.com/rcjsuen/dockerfile-language-server), then some results in `textDocument/publishDiagnostics` will be duplicated across the two language servers. By setting the _experimental_ `dockerfileExperimental.removeOverlappingIssues` to `true`, the Docker Language Server will suppress the duplicated results. Note that this setting may be renamed or removed at any time.
75+
76+
```JSONC
77+
{
78+
"initializationOptions": {
79+
"dockerfileExperimental": {
80+
"removeOverlappingIssues:": true | false
81+
}
82+
}
83+
}
84+
```
85+
7286
### Experimental Capabilities
7387

7488
To support `textDocument/codeLens`, the client must provide a command with the id `dockerLspClient.bake.build` for executing the build. If this is supported, the client can define its experimental capabilities as follows. The server will then respond that it supports code lens requests and return results for `textDocument/codeLens` requests for Bake HCL files.

e2e-tests/publishDiagnostics_test.go

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"testing"
1010

1111
"github.com/docker/docker-language-server/internal/bake/hcl"
12+
"github.com/docker/docker-language-server/internal/pkg/buildkit"
1213
"github.com/docker/docker-language-server/internal/pkg/cli/metadata"
1314
"github.com/docker/docker-language-server/internal/tliron/glsp/protocol"
1415
"github.com/docker/docker-language-server/internal/types"
@@ -26,6 +27,8 @@ func (h *PublishDiagnosticsHandler) Handle(_ context.Context, conn *jsonrpc2.Con
2627
switch request.Method {
2728
case protocol.ServerTextDocumentPublishDiagnostics:
2829
if request.Notif && request.Params != nil {
30+
// always deserialize to a completely new struct
31+
h.diagnostics = protocol.PublishDiagnosticsParams{}
2932
require.NoError(h.t, json.Unmarshal(*request.Params, &h.diagnostics))
3033
h.responseChannel <- nil
3134
}
@@ -40,6 +43,13 @@ func TestPublishDiagnostics(t *testing.T) {
4043
// ensure the language server works without any workspace folders
4144
testPublishDiagnostics(t, protocol.InitializeParams{})
4245

46+
// ensure the language server works without any workspace folders
47+
testPublishDiagnostics(t, protocol.InitializeParams{
48+
InitializationOptions: map[string]any{
49+
"dockerfileExperimental": map[string]bool{"removeOverlappingIssues": true},
50+
},
51+
})
52+
4353
homedir, err := os.UserHomeDir()
4454
require.NoError(t, err)
4555

@@ -106,27 +116,33 @@ func testPublishDiagnostics(t *testing.T, initializeParams protocol.InitializePa
106116
clientStream,
107117
handler,
108118
)
119+
defer func() {
120+
buildkit.RemoveOverlappingIssues = false
121+
}()
109122
initialize(t, conn, initializeParams)
110123

111124
homedir, err := os.UserHomeDir()
112125
require.NoError(t, err)
113126

114127
testCases := []struct {
115-
name string
116-
content string
117-
included []bool
118-
diagnostics []protocol.Diagnostic
128+
name string
129+
content string
130+
included []bool
131+
overlappingIssue bool
132+
diagnostics []protocol.Diagnostic
119133
}{
120134
{
121-
name: "no diagnostics",
122-
content: "FROM scratch",
123-
included: []bool{},
124-
diagnostics: []protocol.Diagnostic{},
135+
name: "no diagnostics",
136+
content: "FROM scratch",
137+
included: []bool{},
138+
overlappingIssue: false,
139+
diagnostics: []protocol.Diagnostic{},
125140
},
126141
{
127-
name: "MAINTAINER is deprecated",
128-
content: "FROM alpine:3.16.1\nMAINTAINER x",
129-
included: []bool{true, false, false, false},
142+
name: "MAINTAINER is deprecated",
143+
content: "FROM scratch\nMAINTAINER x",
144+
included: []bool{true},
145+
overlappingIssue: true,
130146
diagnostics: []protocol.Diagnostic{
131147
{
132148
Message: "The MAINTAINER instruction is deprecated, use a label instead to define an image author (Maintainer instruction is deprecated in favor of using label)",
@@ -148,6 +164,27 @@ func testPublishDiagnostics(t *testing.T, initializeParams protocol.InitializePa
148164
},
149165
},
150166
},
167+
},
168+
},
169+
{
170+
name: "JSON args",
171+
content: "FROM alpine:3.16.1\nCMD ls",
172+
included: []bool{true, false, false, false},
173+
overlappingIssue: false,
174+
diagnostics: []protocol.Diagnostic{
175+
{
176+
Message: "JSON arguments recommended for ENTRYPOINT/CMD to prevent unintended behavior related to OS signals (JSON arguments recommended for CMD to prevent unintended behavior related to OS signals)",
177+
Source: types.CreateStringPointer("docker-language-server"),
178+
Severity: types.CreateDiagnosticSeverityPointer(protocol.DiagnosticSeverityWarning),
179+
Code: &protocol.IntegerOrString{Value: "JSONArgsRecommended"},
180+
CodeDescription: &protocol.CodeDescription{
181+
HRef: "https://docs.docker.com/go/dockerfile/rule/json-args-recommended/",
182+
},
183+
Range: protocol.Range{
184+
Start: protocol.Position{Line: 1, Character: 0},
185+
End: protocol.Position{Line: 1, Character: 6},
186+
},
187+
},
151188
{
152189
Message: "The image can be pinned to a digest",
153190
Source: types.CreateStringPointer("docker-language-server"),
@@ -205,8 +242,17 @@ func testPublishDiagnostics(t *testing.T, initializeParams protocol.InitializePa
205242
},
206243
}
207244

245+
removeOverlappingIssues := false
246+
if options, ok := initializeParams.InitializationOptions.(map[string]any); ok {
247+
if settings, ok := options["dockerfileExperimental"].(map[string]bool); ok {
248+
if value, ok := settings["removeOverlappingIssues"]; ok {
249+
removeOverlappingIssues = value
250+
}
251+
}
252+
}
253+
208254
for _, tc := range testCases {
209-
t.Run(fmt.Sprintf("%v (len(workspaceFolders) == %v)", tc.name, len(initializeParams.WorkspaceFolders)), func(t *testing.T) {
255+
t.Run(fmt.Sprintf("%v (len(workspaceFolders) == %v, removeOverlappingIssues=%v)", tc.name, len(initializeParams.WorkspaceFolders), removeOverlappingIssues), func(t *testing.T) {
210256
didOpen := createDidOpenTextDocumentParams(homedir, t.Name(), tc.content, "dockerfile")
211257
err := conn.Notify(context.Background(), protocol.MethodTextDocumentDidOpen, didOpen)
212258
require.NoError(t, err)
@@ -224,6 +270,12 @@ func testPublishDiagnostics(t *testing.T, initializeParams protocol.InitializePa
224270
} else {
225271
filteredDiagnostics = tc.diagnostics
226272
}
273+
274+
if removeOverlappingIssues && tc.overlappingIssue {
275+
filteredDiagnostics = []protocol.Diagnostic{}
276+
}
277+
278+
require.Equal(t, didOpen.TextDocument.URI, params.URI)
227279
require.Equal(t, filteredDiagnostics, params.Diagnostics)
228280
require.Equal(t, int32(1), *params.Version)
229281
})

internal/pkg/buildkit/service.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ type BuildOutput struct {
2828
type BuildKitDiagnosticsCollector struct {
2929
}
3030

31+
// RemoveOverlappingIssues can be used to decide if diagnostics that
32+
// overlap with what dockerfile-utils generates should be removed or
33+
// not.
34+
var RemoveOverlappingIssues = false
35+
3136
var unknownFlagRegexp *regexp.Regexp
3237
var commandMajorityFlagRegexp *regexp.Regexp
3338

@@ -256,8 +261,42 @@ func parse(contextPath, source string, doc document.DockerfileDocument, content
256261
return lintWithBuildKitBinary(escapedPath, source, doc, content)
257262
}
258263

264+
func shouldIgnore(diagnostic protocol.Diagnostic) bool {
265+
if *diagnostic.Severity == protocol.DiagnosticSeverityError {
266+
return true
267+
}
268+
if diagnostic.Code != nil {
269+
if value, ok := diagnostic.Code.Value.(string); ok {
270+
switch value {
271+
case "ConsistentInstructionCasing":
272+
return true
273+
case "DuplicateStageName":
274+
return true
275+
case "MaintainerDeprecated":
276+
return true
277+
case "MultipleInstructionsDisallowed":
278+
return true
279+
case "NoEmptyContinuation":
280+
return true
281+
case "WorkdirRelativePath":
282+
return true
283+
}
284+
}
285+
}
286+
return false
287+
}
288+
259289
func (c *BuildKitDiagnosticsCollector) CollectDiagnostics(source, workspaceFolder string, doc document.Document, text string) []protocol.Diagnostic {
260290
diagnostics, _ := parse(workspaceFolder, source, doc.(document.DockerfileDocument), text)
291+
if RemoveOverlappingIssues {
292+
filtered := []protocol.Diagnostic{}
293+
for i := range diagnostics {
294+
if !shouldIgnore(diagnostics[i]) {
295+
filtered = append(filtered, diagnostics[i])
296+
}
297+
}
298+
return filtered
299+
}
261300
return diagnostics
262301
}
263302

0 commit comments

Comments
 (0)