2026-09-12 15:58:06 +00:00
package engine
import (
"path"
"strings"
"gitea.maximumdirect.net/eric/ConfigKit/internal/document"
)
const directiveKey = "configkit"
type importDeclaration struct {
2026-10-14 17:21:07 +00:00
path string
glob string
destination Path
optional bool
location Location
2026-09-12 15:58:06 +00:00
}
func splitImportDirectives ( root * document . Node ) ( * document . Node , [ ] importDeclaration , [ ] Diagnostic ) {
body := document . Clone ( root )
var declarations [ ] importDeclaration
directiveIndex := - 1
for index , entry := range body . Mapping {
if entry . Key != directiveKey {
continue
}
directiveIndex = index
parsed , diagnostics := parseImportDirective ( entry . Value , Keys ( directiveKey ) )
if len ( diagnostics ) != 0 {
return nil , nil , diagnostics
}
declarations = parsed
break
}
if directiveIndex >= 0 {
body . Mapping = append ( body . Mapping [ : directiveIndex ] , body . Mapping [ directiveIndex + 1 : ] ... )
}
if diagnostics := nestedDirectiveDiagnostics ( body , Path { } ) ; len ( diagnostics ) != 0 {
return nil , nil , diagnostics
}
return body , declarations , nil
}
func parseImportDirective ( node * document . Node , directivePath Path ) ( [ ] importDeclaration , [ ] Diagnostic ) {
if node . Kind != document . Mapping {
return nil , [ ] Diagnostic { nodeImportDiagnostic ( CodeImportInvalid , directivePath , "configuration directive must be a mapping" , node ) }
}
var declarations [ ] importDeclaration
for _ , entry := range node . Mapping {
fieldPath := directivePath . Key ( entry . Key )
if entry . Key != "imports" {
return nil , [ ] Diagnostic { nodeImportDiagnostic ( CodeImportInvalid , fieldPath , "configuration directive field is not recognized" , entry . Value ) }
}
if entry . Value . Kind != document . Sequence {
return nil , [ ] Diagnostic { nodeImportDiagnostic ( CodeImportInvalid , fieldPath , "imports must be a sequence" , entry . Value ) }
}
for index , item := range entry . Value . Sequence {
declaration , diagnostics := parseImportDeclaration ( item , fieldPath . Index ( index ) )
if len ( diagnostics ) != 0 {
return nil , diagnostics
}
declarations = append ( declarations , declaration )
}
}
return declarations , nil
}
func parseImportDeclaration ( node * document . Node , declarationPath Path ) ( importDeclaration , [ ] Diagnostic ) {
if node . Kind != document . Mapping {
return importDeclaration { } , [ ] Diagnostic { nodeImportDiagnostic ( CodeImportInvalid , declarationPath , "import declaration must be a mapping" , node ) }
}
declaration := importDeclaration { location : publicLocation ( node . Location ) }
seenTarget := false
for _ , entry := range node . Mapping {
fieldPath := declarationPath . Key ( entry . Key )
switch entry . Key {
case "path" , "glob" :
if seenTarget || entry . Value . Kind != document . String || entry . Value . Text == "" || strings . TrimSpace ( entry . Value . Text ) != entry . Value . Text {
return importDeclaration { } , [ ] Diagnostic { nodeImportDiagnostic ( CodeImportInvalid , fieldPath , "import declaration requires exactly one nonempty path or glob string" , entry . Value ) }
}
seenTarget = true
declaration . location = publicLocation ( entry . Value . Location )
if entry . Key == "path" {
declaration . path = entry . Value . Text
} else {
declaration . glob = entry . Value . Text
}
case "optional" :
if entry . Value . Kind != document . Boolean {
return importDeclaration { } , [ ] Diagnostic { nodeImportDiagnostic ( CodeImportInvalid , fieldPath , "optional must be a boolean" , entry . Value ) }
}
declaration . optional = entry . Value . Text == "true"
2026-10-14 17:24:57 +00:00
case "destination" :
if entry . Value . Kind != document . Sequence {
return importDeclaration { } , [ ] Diagnostic { nodeImportDiagnostic ( CodeImportInvalid , fieldPath , "destination must be a sequence of strings" , entry . Value ) }
}
relative := Path { segments : make ( [ ] pathSegment , 0 , len ( entry . Value . Sequence ) ) }
for index , item := range entry . Value . Sequence {
memberPath := fieldPath . Index ( index )
if item . Kind != document . String {
return importDeclaration { } , [ ] Diagnostic { nodeImportDiagnostic ( CodeImportInvalid , memberPath , "destination members must be strings" , item ) }
}
member := Path { segments : [ ] pathSegment { { kind : pathSegmentKey , key : item . Text } } }
if _ , err := normalizeDestination ( member ) ; err != nil {
return importDeclaration { } , [ ] Diagnostic { nodeImportDiagnostic ( CodeImportInvalid , memberPath , "destination members must be non-reserved mapping keys" , item ) }
}
relative . segments = append ( relative . segments , pathSegment { kind : pathSegmentKey , key : item . Text } )
}
normalized , err := normalizeDestination ( relative )
if err != nil {
return importDeclaration { } , [ ] Diagnostic { nodeImportDiagnostic ( CodeImportInvalid , fieldPath , "destination must contain only non-reserved mapping keys" , entry . Value ) }
}
declaration . destination = normalized
2026-09-12 15:58:06 +00:00
default :
return importDeclaration { } , [ ] Diagnostic { nodeImportDiagnostic ( CodeImportInvalid , fieldPath , "import declaration field is not recognized" , entry . Value ) }
}
}
if ! seenTarget {
return importDeclaration { } , [ ] Diagnostic { nodeImportDiagnostic ( CodeImportInvalid , declarationPath , "import declaration requires exactly one path or glob" , node ) }
}
return declaration , nil
}
func nestedDirectiveDiagnostics ( node * document . Node , current Path ) [ ] Diagnostic {
if node . Kind == document . Mapping {
for _ , entry := range node . Mapping {
childPath := current . Key ( entry . Key )
if entry . Key == directiveKey {
location := publicLocation ( entry . KeyLocation )
return [ ] Diagnostic { { Code : CodeImportInvalid , Severity : SeverityError , Path : childPath , Message : "configuration directives are permitted only at the document root" , Primary : & location } }
}
if diagnostics := nestedDirectiveDiagnostics ( entry . Value , childPath ) ; len ( diagnostics ) != 0 {
return diagnostics
}
}
} else if node . Kind == document . Sequence {
for index , child := range node . Sequence {
if diagnostics := nestedDirectiveDiagnostics ( child , current . Index ( index ) ) ; len ( diagnostics ) != 0 {
return diagnostics
}
}
}
return nil
}
func fragmentVersionClaim ( root * document . Node , rule * VersionRule ) * document . Node {
if rule == nil {
return nil
}
current := root
if len ( rule . Path . segments ) == 0 {
return current
}
for _ , segment := range rule . Path . segments {
if current . Kind != document . Mapping {
return current
}
if segment . kind == pathSegmentIndex {
return nil
}
key := segment . key
index := documentEntryIndex ( current . Mapping , key )
if index < 0 {
return nil
}
current = current . Mapping [ index ] . Value
}
return current
}
func validImportPath ( value string ) bool {
if value == "" || strings . TrimSpace ( value ) != value || path . IsAbs ( value ) || strings . HasPrefix ( value , "//" ) {
return false
}
return ! hasImportScheme ( value )
}
func hasImportScheme ( value string ) bool {
colon := strings . IndexByte ( value , ':' )
slash := strings . IndexByte ( value , '/' )
if colon <= 0 || ( slash >= 0 && slash < colon ) {
return false
}
for index := 0 ; index < colon ; index ++ {
character := value [ index ]
if index == 0 {
if ( character < 'a' || character > 'z' ) && ( character < 'A' || character > 'Z' ) {
return false
}
continue
}
if ( character < 'a' || character > 'z' ) && ( character < 'A' || character > 'Z' ) &&
( character < '0' || character > '9' ) && character != '+' && character != '-' && character != '.' {
return false
}
}
return true
}
func nodeImportDiagnostic ( code Code , path Path , message string , node * document . Node ) Diagnostic {
location := publicLocation ( node . Location )
return Diagnostic { Code : code , Severity : SeverityError , Path : path , Message : message , Primary : & location }
}
2026-09-23 18:27:29 +00:00
func overlayDirectiveDiagnostics ( node * document . Node , current Path ) [ ] Diagnostic {
2026-09-12 15:58:06 +00:00
if node . Kind == document . Mapping {
for _ , entry := range node . Mapping {
childPath := current . Key ( entry . Key )
if entry . Key == directiveKey {
location := publicLocation ( entry . KeyLocation )
return [ ] Diagnostic { {
2026-09-23 18:27:29 +00:00
Code : CodeOverlayDirective , Severity : SeverityError , Path : childPath ,
Message : "overlays cannot contain configuration directives" , Primary : & location ,
2026-09-12 15:58:06 +00:00
} }
}
2026-09-23 18:27:29 +00:00
if diagnostics := overlayDirectiveDiagnostics ( entry . Value , childPath ) ; len ( diagnostics ) != 0 {
2026-09-12 15:58:06 +00:00
return diagnostics
}
}
} else if node . Kind == document . Sequence {
for index , child := range node . Sequence {
2026-09-23 18:27:29 +00:00
if diagnostics := overlayDirectiveDiagnostics ( child , current . Index ( index ) ) ; len ( diagnostics ) != 0 {
2026-09-12 15:58:06 +00:00
return diagnostics
}
}
}
return nil
}
func disabledDirectiveDiagnostics ( node * document . Node , path Path ) [ ] Diagnostic {
var diagnostics [ ] Diagnostic
if node . Kind == document . Mapping {
for _ , entry := range node . Mapping {
childPath := path . Key ( entry . Key )
if entry . Key == directiveKey {
location := Location { SourceID : entry . KeyLocation . SourceID , Line : entry . KeyLocation . Line , Column : entry . KeyLocation . Column }
diagnostics = append ( diagnostics , Diagnostic {
Code : CodeImportDisabled , Severity : SeverityError , Path : childPath ,
Message : "configuration directives require imports to be enabled" , Primary : & location ,
} )
}
diagnostics = append ( diagnostics , disabledDirectiveDiagnostics ( entry . Value , childPath ) ... )
}
} else if node . Kind == document . Sequence {
for index , child := range node . Sequence {
diagnostics = append ( diagnostics , disabledDirectiveDiagnostics ( child , path . Index ( index ) ) ... )
}
}
return diagnostics
}