Skip to content
Open
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: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
Config File Validator is a cross-platform CLI tool that validates configuration files in your project. Catch syntax errors, schema violations, and misconfigurations across all your config files — with one tool.

- **Single binary, zero dependencies** — no runtimes, no package managers, just one executable
- **16 file formats** — JSON, JSONC, YAML, TOML, XML, HCL, INI, HOCON, ENV, CSV, Properties, EDITORCONFIG, Justfile, PList, SARIF, and TOON
- **17 file formats** — JSON, JSONC, YAML, TOML, XML, HCL, INI, HOCON, ENV, CSV, Properties, EDITORCONFIG, Justfile, KDL, PList, SARIF, and TOON
- **Syntax + schema validation** — validates structure with [JSON Schema](https://json-schema.org/) and XSD, with automatic [SchemaStore](https://www.schemastore.org/) integration
- **CI/CD ready** — JSON, JUnit, and SARIF output for GitHub Actions, GitLab CI, Jenkins, and more
- **Configurable** — project-level `.cfv.toml` config files, glob patterns, schema mappings, and environment variables
Expand Down Expand Up @@ -77,6 +77,7 @@ Config File Validator is a cross-platform CLI tool that validates configuration
| Justfile | ✅ | ❌ |
| JSON | ✅ | ✅ |
| JSONC | ✅ | ✅ |
| KDL | ✅ | ❌ |
| Properties | ✅ | ❌ |
| SARIF | ✅ | ✅ |
| TOML | ✅ | ✅ |
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ require (
howett.net/plist v1.0.1
)

require github.com/sblinch/kdl-go v0.0.0-20260121213736-8b7053306ca6

require (
github.com/Boeing/go-just v0.0.0
github.com/agext/levenshtein v1.2.1 // indirect
Expand All @@ -35,13 +37,11 @@ require (
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/lestrrat-go/helium v0.0.1
github.com/lestrrat-go/pdebug v0.0.0-20210111095411-35b07dbf089b // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/tailscale/hujson v0.0.0-20260302212456-ecc657c15afd
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/zclconf/go-cty v1.13.0 // indirect
Expand Down
14 changes: 2 additions & 12 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/sblinch/kdl-go v0.0.0-20260121213736-8b7053306ca6 h1:JsjzqC6ymELkN4XlTjZPSahSAem21GySugLbKz6uF5E=
github.com/sblinch/kdl-go v0.0.0-20260121213736-8b7053306ca6/go.mod h1:b3oNGuAKOQzhsCKmuLc/urEOPzgHj6fB8vl8bwTBh28=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
Expand Down Expand Up @@ -104,18 +106,6 @@ golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8=
golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k=
golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
Expand Down
11 changes: 11 additions & 0 deletions pkg/filetype/file_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,15 @@ var JustfileFileType = FileType{
Validator: validator.JustfileValidator{},
}

// Instance of the FileType object to
// represent a KDL (KDL Document Language) file.
// See https://kdl.dev/ for the spec.
var KdlFileType = FileType{
Name: "kdl",
Extensions: arrToMap("kdl"),
Validator: validator.KdlValidator{},
}

// extraKnownFiles contains manual entries not covered by Linguist.
var extraKnownFiles = map[string][]string{
"ini": {
Expand Down Expand Up @@ -187,6 +196,7 @@ var fileTypeRegistry = map[string]*FileType{
"toon": &ToonFileType,
"sarif": &SarifFileType,
"justfile": &JustfileFileType,
"kdl": &KdlFileType,
}

// excludeKnownFiles lists Linguist entries to skip because we have
Expand Down Expand Up @@ -258,6 +268,7 @@ func init() {
SarifFileType,
JSONCFileType,
JustfileFileType,
KdlFileType,
}
}

Expand Down
37 changes: 37 additions & 0 deletions pkg/validator/kdl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package validator

import (
"bytes"
"regexp"
"strconv"

kdl "github.com/sblinch/kdl-go"
)

// kdlLineRe matches the "at line N, column M" suffix the upstream parser
// appends to scan/parse errors so we can surface the position via
// ValidationError instead of leaving it buried in the message.
var kdlLineRe = regexp.MustCompile(`at line (\d+), column (\d+)`)

// KdlValidator validates KDL Document Language files via the sblinch/kdl-go
// parser. It implements ValidateSyntax only — KDL has no schema system, so
// it does not implement SchemaValidator.
//
// See https://kdl.dev/ for the KDL spec.
type KdlValidator struct{}

var _ Validator = KdlValidator{}

func (KdlValidator) ValidateSyntax(b []byte) (bool, error) {
if _, err := kdl.Parse(bytes.NewReader(b)); err != nil {
if m := kdlLineRe.FindStringSubmatch(err.Error()); m != nil {
line, lineErr := strconv.Atoi(m[1])
col, colErr := strconv.Atoi(m[2])
if lineErr == nil && colErr == nil {
return false, &ValidationError{Err: err, Line: line, Column: col}
}
}
return false, err
}
return true, nil
}
4 changes: 4 additions & 0 deletions pkg/validator/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ var testData = []struct {
{"validJsoncTrailingComma", []byte(`{"a": 1, "b": 2,}`), true, JSONCValidator{}},
{"invalidJsonc", []byte(`{"bad": }`), false, JSONCValidator{}},
{"validJsoncNoComments", []byte(`{"key": "value"}`), true, JSONCValidator{}},
{"validKdl", []byte("name \"Bob\"\nage 76\nactive true\n"), true, KdlValidator{}},
{"validKdlChildren", []byte("package {\n name \"foo\"\n version \"1.0\"\n}\n"), true, KdlValidator{}},
{"invalidKdlUnterminatedString", []byte("name \"Bob\n"), false, KdlValidator{}},
{"invalidKdlUnclosedChildren", []byte("package {\n name \"foo\"\n"), false, KdlValidator{}},
}

func Test_ValidationInput(t *testing.T) {
Expand Down
Loading