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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ All notable changes to the Docker Language Server will be documented in this fil

### Added

- Dockerfile
- textDocument/publishDiagnostics
- provide code actions to easily ignore build checks ([#320](https://github.com/docker/docker-language-server/issues/320))
- Compose
- textDocument/completion
- add support for suggesting `include` properties ([#316](https://github.com/docker/docker-language-server/issues/316))
Expand Down
28 changes: 28 additions & 0 deletions e2e-tests/publishDiagnostics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,14 @@ func testPublishDiagnostics(t *testing.T, initializeParams protocol.InitializePa
"edit": "LABEL org.opencontainers.image.authors=\"x\"",
"title": "Convert MAINTAINER to a org.opencontainers.image.authors LABEL",
},
map[string]any{
"edit": "# check=skip=MaintainerDeprecated\n",
"title": "Ignore this type of error with check=skip=MaintainerDeprecated",
"range": map[string]any{
"start": map[string]any{"line": float64(0), "character": float64(0)},
"end": map[string]any{"line": float64(0), "character": float64(0)},
},
},
},
},
},
Expand All @@ -150,6 +158,16 @@ func testPublishDiagnostics(t *testing.T, initializeParams protocol.InitializePa
Start: protocol.Position{Line: 1, Character: 0},
End: protocol.Position{Line: 1, Character: 6},
},
Data: []any{
map[string]any{
"edit": "# check=skip=JSONArgsRecommended\n",
"title": "Ignore this type of error with check=skip=JSONArgsRecommended",
"range": map[string]any{
"start": map[string]any{"line": float64(0), "character": float64(0)},
"end": map[string]any{"line": float64(0), "character": float64(0)},
},
},
},
},
{
Message: "The image contains 1 critical and 3 high vulnerabilities",
Expand Down Expand Up @@ -242,6 +260,16 @@ func testPublishDiagnostics(t *testing.T, initializeParams protocol.InitializePa
Start: protocol.Position{Line: 0, Character: 0},
End: protocol.Position{Line: 0, Character: 35},
},
Data: []any{
map[string]any{
"edit": "# check=skip=FromPlatformFlagConstDisallowed\n",
"title": "Ignore this type of error with check=skip=FromPlatformFlagConstDisallowed",
"range": map[string]any{
"start": map[string]any{"line": float64(0), "character": float64(0)},
"end": map[string]any{"line": float64(0), "character": float64(0)},
},
},
},
},
},
}, params)
Expand Down
98 changes: 75 additions & 23 deletions internal/pkg/buildkit/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,39 +93,33 @@ func encloseWithQuotes(s string) string {
return fmt.Sprintf(`"%v"`, s)
}

func setDiagnosticData(diagnostic *protocol.Diagnostic, instruction *parser.Node, warning lint.Warning) {
func createResolutionEdit(instruction *parser.Node, warning lint.Warning) *types.NamedEdit {
if instruction != nil && instruction.StartLine == int(warning.Location.Ranges[0].Start.Line) && instruction.Next != nil {
if warning.RuleName == "MaintainerDeprecated" {
diagnostic.Data = []types.NamedEdit{
{
Title: "Convert MAINTAINER to a org.opencontainers.image.authors LABEL",
Edit: fmt.Sprintf(`LABEL org.opencontainers.image.authors=%v`, encloseWithQuotes(instruction.Next.Value)),
},
return &types.NamedEdit{
Title: "Convert MAINTAINER to a org.opencontainers.image.authors LABEL",
Edit: fmt.Sprintf(`LABEL org.opencontainers.image.authors=%v`, encloseWithQuotes(instruction.Next.Value)),
}
} else if warning.RuleName == "StageNameCasing" {
stageName := instruction.Next.Next.Next.Value
lowercase := strings.ToLower(stageName)
words := []string{instruction.Value, instruction.Next.Value, instruction.Next.Next.Value, lowercase}
diagnostic.Data = []types.NamedEdit{
{
Title: fmt.Sprintf("Convert stage name (%v) to lowercase (%v)", stageName, lowercase),
Edit: strings.Join(words, " "),
},
return &types.NamedEdit{
Title: fmt.Sprintf("Convert stage name (%v) to lowercase (%v)", stageName, lowercase),
Edit: strings.Join(words, " "),
}
} else if warning.RuleName == "RedundantTargetPlatform" {
words := getWords(instruction)
for i := range words {
if words[i] == "--platform=$TARGETPLATFORM" {
words = slices.Delete(words, i, i+1)
diagnostic.Data = []types.NamedEdit{
{
Title: "Remove unnecessary --platform flag",
Edit: strings.Join(words, " "),
},
}
break
}
}
return &types.NamedEdit{
Title: "Remove unnecessary --platform flag",
Edit: strings.Join(words, " "),
}
} else if warning.RuleName == "ConsistentInstructionCasing" {
words := getWords(instruction)
suggestion := strings.ToUpper(instruction.Value)
Expand All @@ -134,14 +128,13 @@ func setDiagnosticData(diagnostic *protocol.Diagnostic, instruction *parser.Node
suggestion = strings.ToLower(suggestion)
}
words[0] = suggestion
diagnostic.Data = []types.NamedEdit{
{
Title: fmt.Sprintf("Convert to %v", caseSuggestion),
Edit: strings.Join(words, " "),
},
return &types.NamedEdit{
Title: fmt.Sprintf("Convert to %v", caseSuggestion),
Edit: strings.Join(words, " "),
}
}
}
return nil
}

func convertToDiagnostics(source string, doc document.DockerfileDocument, lines []string, warnings []lint.Warning) []protocol.Diagnostic {
Expand All @@ -168,12 +161,71 @@ func convertToDiagnostics(source string, doc document.DockerfileDocument, lines
diagnostic.Tags = []protocol.DiagnosticTag{protocol.DiagnosticTagDeprecated}
}
instruction := doc.Instruction(protocol.Position{Line: uint32(warning.Location.Ranges[0].Start.Line) - 1})
setDiagnosticData(diagnostic, instruction, warning)
ignoreEdit := createIgnoreEdit(warning.RuleName)
resolutionEdit := createResolutionEdit(instruction, warning)
if resolutionEdit == nil {
if ignoreEdit != nil {
diagnostic.Data = []types.NamedEdit{*ignoreEdit}
}
} else {
diagnostic.Data = []types.NamedEdit{*resolutionEdit, *ignoreEdit}
}
diagnostics = append(diagnostics, *diagnostic)
}
return diagnostics
}

func createIgnoreEdit(ruleName string) *types.NamedEdit {
switch ruleName {
case "ConsistentInstructionCasing":
fallthrough
case "CopyIgnoredFile":
fallthrough
case "DuplicateStageName":
fallthrough
case "FromAsCasing":
fallthrough
case "FromPlatformFlagConstDisallowed":
fallthrough
case "InvalidDefaultArgInFrom":
fallthrough
case "InvalidDefinitionDescription":
fallthrough
case "JSONArgsRecommended":
fallthrough
case "LegacyKeyValueFormat":
fallthrough
case "MaintainerDeprecated":
fallthrough
case "MultipleInstructionsDisallowed":
fallthrough
case "NoEmptyContinuation":
fallthrough
case "RedundantTargetPlatform":
fallthrough
case "ReservedStageName":
fallthrough
case "SecretsUsedInArgOrEnv":
fallthrough
case "StageNameCasing":
fallthrough
case "UndefinedArgInFrom":
fallthrough
case "UndefinedVar":
fallthrough
case "WorkdirRelativePath":
return &types.NamedEdit{
Title: fmt.Sprintf("Ignore this type of error with check=skip=%v", ruleName),
Edit: fmt.Sprintf("# check=skip=%v\n", ruleName),
Range: &protocol.Range{
Start: protocol.Position{Line: 0, Character: 0},
End: protocol.Position{Line: 0, Character: 0},
},
}
}
return nil
}

func lintWithBuildKitBinary(contextPath, source string, doc document.DockerfileDocument, content string) ([]protocol.Diagnostic, error) {
var buf bytes.Buffer
cmd := exec.Command("docker", "buildx", "build", "--call=check,format=json", "-f-", contextPath)
Expand Down
Loading
Loading