More errors, some fixes

main
andrew 3 weeks ago
parent 3032626095
commit d7ae9bea8a

@ -4,67 +4,74 @@ package errors
type ErrorCode string type ErrorCode string
const ( const (
ErrEmptyFile ErrorCode = "EMPTY_FILE" // файл пуст или отсутствует // General Errors
ErrYamlSyntax ErrorCode = "YAML_SYNTAX_ERROR" // ошибка синтаксического анализа YAML ErrEmptyFile ErrorCode = "FileIsEmpty" // The file is empty or missing
ErrYamlSyntax ErrorCode = "YAMLSyntaxError" // YAML syntax error
ErrJobNameBlank ErrorCode = "JOB_NAME_BLANK" // имя задачи пустое // Job-related Errors
ErrJobNameTooLong ErrorCode = "JOB_NAME_TOO_LONG" ErrJobNameBlank ErrorCode = "JobNameBlank" // The job name is blank
ErrMissingScript ErrorCode = "MISSING_SCRIPT" // отсутствует обязательное поле script (или run) ErrJobNameTooLong ErrorCode = "JobNameTooLong" // The job name exceeds the maximum length
ErrBothScriptAndRun ErrorCode = "BOTH_SCRIPT_RUN" // одновременно присутствуют script и run ErrMissingScript ErrorCode = "MissingScript" // The required field script (or run) is missing
ErrJobStageNotExist ErrorCode = "JOB_STAGE_NOT_EXIST" // указан stage, которого нет в списке разрешённых ErrBothScriptAndRun ErrorCode = "BothScriptAndRun" // Both script and run are specified simultaneously
ErrUndefinedDependency ErrorCode = "UNDEFINED_DEPENDENCY" // зависимость не определена в списке job'ов ErrJobStageNotExist ErrorCode = "JobStageNotExist" // The specified stage does not exist in the allowed list
ErrInvalidStageOrder ErrorCode = "INVALID_STAGE_ORDER" // зависимость имеет stage, который идёт позже, чем у задачи ErrUndefinedDependency ErrorCode = "UndefinedDependency" // A dependency is not defined among the jobs
ErrDuplicateNeeds ErrorCode = "DUPLICATE_NEEDS" // дублирующиеся записи в needs ErrInvalidStageOrder ErrorCode = "InvalidStageOrder" // A dependency has a stage that occurs later than the job's stage
ErrUndefinedNeed ErrorCode = "UNDEFINED_NEED" // need ссылается на несуществующую задачу ErrDuplicateNeeds ErrorCode = "DuplicateNeeds" // Duplicate entries found in needs
ErrNeedNameTooLong ErrorCode = "NEED_NAME_TOO_LONG" // имя need превышает допустимую длину ErrUndefinedNeed ErrorCode = "UndefinedNeed" // A need refers to a non-existent job
ErrNoVisibleJob ErrorCode = "NO_VISIBLE_JOB" // нет ни одной видимой задачи ErrNeedNameTooLong ErrorCode = "NeedNameTooLong" // The need name exceeds the allowed length
ErrMissingJobs ErrorCode = "MISSING_JOBS" ErrNoVisibleJob ErrorCode = "NoVisibleJob" // There are no visible jobs
ErrArtifactsPathsBlank ErrorCode = "ARTIFACTS_PATHS_BLANK" // отсутствует или пустой блок paths в artifacts ErrMissingJobs ErrorCode = "MissingJobs" // No jobs defined
ErrUnknownRootKey ErrorCode = "UNKNOWN_ROOT_KEY" ErrArtifactsPathsBlank ErrorCode = "ArtifactsPathsBlank" // The artifacts paths block is missing or empty
ErrInvalidWhen ErrorCode = "INVALID_WHEN" ErrUnknownRootKey ErrorCode = "UnknownRootKey" // An unknown key was found at the root level
ErrInvalidOnly ErrorCode = "INVALID_ONLY" ErrInvalidWhen ErrorCode = "InvalidWhen" // The value of 'when' is invalid
ErrUnknownKey ErrorCode = "UNKNOWN_KEY" ErrInvalidOnly ErrorCode = "InvalidOnly" // The value of 'only' is invalid
ErrMissingStage ErrorCode = "MISSING_STAGE" ErrUnknownKey ErrorCode = "UnknownKey" // An unknown key was found
ErrInvalidChanges ErrorCode = "INVALID_CHANGES" ErrMissingStage ErrorCode = "MissingStage" // The stage is missing
ErrInvalidRulesFormat ErrorCode = "INVALID_RULES_FORMAT" ErrInvalidChanges ErrorCode = "InvalidChanges" // The changes configuration is invalid
ErrInvalidRulesFormat ErrorCode = "InvalidRulesFormat" // The rules format is invalid
// Changes // Changes-related Errors
ErrChangesNotArrayOfStrings ErrorCode = "CHANGES_NOT_ARRAY_OF_STRINGS" ErrChangesNotArrayOfStrings ErrorCode = "ChangesNotArrayOfStrings" // Changes config should be an array of strings
ErrChangesInvalidType ErrorCode = "CHANGES_INVALID_TYPE" ErrChangesInvalidType ErrorCode = "ChangesInvalidType" // Changes config is of an invalid type
ErrChangesTooManyEntries ErrorCode = "CHANGES_TOO_MANY_ENTRIES" ErrChangesTooManyEntries ErrorCode = "ChangesTooManyEntries" // Changes config has too many entries (maximum 50)
ErrChangesMissingPaths ErrorCode = "CHANGES_MISSING_PATHS" ErrChangesMissingPaths ErrorCode = "ChangesMissingPaths" // The changes config hash must contain the key 'paths'
// Paths // Paths-related Errors
ErrPathsNotArrayOfStrings ErrorCode = "PATHS_NOT_ARRAY_OF_STRINGS" ErrPathsNotArrayOfStrings ErrorCode = "PathsNotArrayOfStrings" // Paths config should be an array of strings
// Job delayed parameters // Job Delayed Parameters
ErrStartInMissing ErrorCode = "START_IN_MISSING" ErrStartInMissing ErrorCode = "StartInMissing" // For delayed jobs, start_in is missing
ErrStartInInvalid ErrorCode = "START_IN_INVALID" ErrStartInInvalid ErrorCode = "StartInInvalid" // start_in is not a valid duration
ErrStartInTooLong ErrorCode = "START_IN_TOO_LONG" ErrStartInTooLong ErrorCode = "StartInTooLong" // start_in exceeds the allowed limit
ErrStartInMustBeBlank ErrorCode = "START_IN_MUST_BE_BLANK" ErrStartInMustBeBlank ErrorCode = "StartInMustBeBlank" // For non-delayed jobs, start_in must be blank
// Dependencies / Needs consistency // Dependencies / Needs Consistency
ErrDependencyNotInNeeds ErrorCode = "DEPENDENCY_NOT_IN_NEEDS" ErrDependencyNotInNeeds ErrorCode = "DependencyNotInNeeds" // A dependency is not included in needs
// Rules validation // Rules Validation
ErrRulesOnlyExcept ErrorCode = "RULES_ONLY_EXCEPT" ErrRulesOnlyExcept ErrorCode = "RulesOnlyExcept" // Only and except cannot be used with rules
ErrRulesOnly ErrorCode = "RULES_ONLY" ErrRulesOnly ErrorCode = "RulesOnly" // Only cannot be used with rules
ErrRulesExcept ErrorCode = "RULES_EXCEPT" ErrRulesExcept ErrorCode = "RulesExcept" // Except cannot be used with rules
ErrInvalidExpressionSyntax ErrorCode = "INVALID_EXPRESSION_SYNTAX" ErrInvalidExpressionSyntax ErrorCode = "InvalidExpressionSyntax" // The expression syntax is invalid
ErrUnknownRulesKey ErrorCode = "UNKNOWN_RULES_KEY" ErrUnknownRulesKey ErrorCode = "UnknownRulesKey" // An unknown key was found in rules
ErrBeforeScriptInvalid ErrorCode = "BEFORE_SCRIPT_INVALID" // Other Job-related Errors
ErrServiceInvalid ErrorCode = "SERVICE_INVALID" ErrBeforeScriptInvalid ErrorCode = "BeforeScriptInvalid" // The before_script configuration is invalid
ErrStageInvalid ErrorCode = "STAGE_INVALID" ErrServiceInvalid ErrorCode = "ServiceInvalid" // The service configuration is invalid
ErrVariableInvalid ErrorCode = "VARIABLE_INVALID" ErrStageInvalid ErrorCode = "StageInvalid" // The stage configuration is invalid
ErrVariableNameTooLong ErrorCode = "VARIABLE_NAME_TOO_LONG" ErrVariableInvalid ErrorCode = "VariableInvalid" // The variable is invalid
ErrInvalidStagesOrder ErrorCode = "INVALID_STAGES_ORDER" ErrVariableNameTooLong ErrorCode = "VariableNameTooLong" // The variable name exceeds the maximum length
ErrVariablesInvalid ErrorCode = "VARIABLES_INVALID" ErrInvalidStagesOrder ErrorCode = "InvalidStagesOrder" // The stage order is invalid
ErrVariablesInvalidKey ErrorCode = "VARIABLES_INVALID_KEY" ErrVariablesInvalid ErrorCode = "VariablesInvalid" // The variables configuration is invalid
ErrVariablesInvalidKey ErrorCode = "VariablesInvalidKey" // The variable uses invalid keys
ErrCyclicDependency ErrorCode = "CYCLIC_DEPENDENCY" // обнаружена циклическая зависимость // Additional Errors
ErrBooleanValue ErrorCode = "BOOLEAN_VALUE" // значение должно быть булевым ErrCyclicDependency ErrorCode = "CyclicDependency" // A cyclic dependency was detected
ErrIncludeRulesInvalid ErrorCode = "INCLUDE_RULES_INVALID" // значение exists или changes не является строкой или массивом строк ErrBooleanValue ErrorCode = "BooleanValue" // The value must be boolean
ErrIncludeRulesInvalid ErrorCode = "IncludeRulesInvalid" // The 'exists' or 'changes' value is not a string or an array of strings
// Style Note (Informational/Note)
ErrMissingFinalNewline ErrorCode = "MissingFinalNewline" // The file is missing a final empty newline
) )
// ErrorSeverity maps each error code to a severity level. // ErrorSeverity maps each error code to a severity level.
@ -114,5 +121,6 @@ var ErrorSeverity = map[ErrorCode]SeverityLevel{
// Notes (if needed, add informational codes here) // Notes (if needed, add informational codes here)
ErrJobNameTooLong: Note, ErrJobNameTooLong: Note,
ErrVariableNameTooLong: Note, ErrVariableNameTooLong: Note,
ErrNeedNameTooLong: Critical, ErrNeedNameTooLong: Note,
ErrMissingFinalNewline: Note,
} }

@ -46,4 +46,4 @@ var ErrorMessages = map[ErrorCode]string{
ErrIncludeRulesInvalid: "include rule key '%s' should be a string or an array of strings", ErrIncludeRulesInvalid: "include rule key '%s' should be a string or an array of strings",
ErrVariableInvalid: "variable '%s' must be a scalar or a map", ErrVariableInvalid: "variable '%s' must be a scalar or a map",
ErrInvalidStagesOrder: "job '%s' (Line %d, Col %d) has need '%s' (Line %d, Col %d-%d) with a stage occurring later than '%s'", ErrInvalidStagesOrder: "job '%s' (Line %d, Col %d) has need '%s' (Line %d, Col %d-%d) with a stage occurring later than '%s'",
} ErrMissingFinalNewline: "File should end with an empty line for better style"}

@ -1,5 +1,7 @@
package gitlabcivalidator package gitlabcivalidator
import "gopkg.in/yaml.v3"
// ValidationOptions описывает настройки валидации. // ValidationOptions описывает настройки валидации.
type ValidationOptions struct { type ValidationOptions struct {
VerifyProjectSHA bool // нужно ли проверять SHA проекта VerifyProjectSHA bool // нужно ли проверять SHA проекта
@ -15,6 +17,7 @@ type GitLabCIConfig struct {
Services interface{} `yaml:"services,omitempty"` Services interface{} `yaml:"services,omitempty"`
AfterScript interface{} `yaml:"after_script,omitempty"` AfterScript interface{} `yaml:"after_script,omitempty"`
Variables interface{} `yaml:"variables,omitempty"` Variables interface{} `yaml:"variables,omitempty"`
VariablesNode *yaml.Node `yaml:"-"`
Stages []string `yaml:"stages,omitempty"` Stages []string `yaml:"stages,omitempty"`
Cache interface{} `yaml:"cache,omitempty"` Cache interface{} `yaml:"cache,omitempty"`
Workflow interface{} `yaml:"workflow,omitempty"` Workflow interface{} `yaml:"workflow,omitempty"`
@ -51,6 +54,9 @@ type Job struct {
OnlyColumn int `yaml:"-"` OnlyColumn int `yaml:"-"`
Except interface{} `yaml:"except,omitempty"` Except interface{} `yaml:"except,omitempty"`
Variables interface{} `yaml:"variables,omitempty"`
VariablesNode *yaml.Node `yaml:"-"`
Line int `yaml:"-"` // Строка в файле Line int `yaml:"-"` // Строка в файле
Column int `yaml:"-"` // Позиция в строке Column int `yaml:"-"` // Позиция в строке
DependencyLines map[string]struct { DependencyLines map[string]struct {

@ -56,6 +56,10 @@ func ParseGitLabCIConfig(data []byte, ve *errors.ValidationError) (*GitLabCIConf
for i := 0; i < len(mainNode.Content)-1; i += 2 { for i := 0; i < len(mainNode.Content)-1; i += 2 {
keyNode := mainNode.Content[i] keyNode := mainNode.Content[i]
valNode := mainNode.Content[i+1] valNode := mainNode.Content[i+1]
if keyNode.Value == "variables" {
config.VariablesNode = valNode
}
// If the key is not one of the allowed top-level keys, treat it as a job. // If the key is not one of the allowed top-level keys, treat it as a job.
if _, ok := allowedKeys[keyNode.Value]; !ok { if _, ok := allowedKeys[keyNode.Value]; !ok {
// Create a new Job. // Create a new Job.
@ -79,7 +83,9 @@ func ParseGitLabCIConfig(data []byte, ve *errors.ValidationError) (*GitLabCIConf
for d := 0; d < len(valNode.Content)-1; d += 2 { for d := 0; d < len(valNode.Content)-1; d += 2 {
depKey := valNode.Content[d] depKey := valNode.Content[d]
depVal := valNode.Content[d+1] depVal := valNode.Content[d+1]
if depKey.Value == "dependencies" && depVal.Kind == yaml.SequenceNode { switch depKey.Value {
case "dependencies":
if depVal.Kind == yaml.SequenceNode {
for k := 0; k < len(depVal.Content); k++ { for k := 0; k < len(depVal.Content); k++ {
depNode := depVal.Content[k] depNode := depVal.Content[k]
job.Dependencies = append(job.Dependencies, depNode.Value) job.Dependencies = append(job.Dependencies, depNode.Value)
@ -89,7 +95,8 @@ func ParseGitLabCIConfig(data []byte, ve *errors.ValidationError) (*GitLabCIConf
}{depNode.Line, depNode.Column} }{depNode.Line, depNode.Column}
} }
} }
if depKey.Value == "needs" && depVal.Kind == yaml.SequenceNode { case "needs":
if depVal.Kind == yaml.SequenceNode {
job.NeedLines = make(map[string]struct { job.NeedLines = make(map[string]struct {
Line int Line int
Column int Column int
@ -103,7 +110,7 @@ func ParseGitLabCIConfig(data []byte, ve *errors.ValidationError) (*GitLabCIConf
}{needNode.Line, needNode.Column} }{needNode.Line, needNode.Column}
} }
} }
if depKey.Value == "only" { case "only":
if depVal.Kind == yaml.ScalarNode { if depVal.Kind == yaml.ScalarNode {
job.Only = depVal.Value job.Only = depVal.Value
} else if depVal.Kind == yaml.SequenceNode { } else if depVal.Kind == yaml.SequenceNode {
@ -115,8 +122,15 @@ func ParseGitLabCIConfig(data []byte, ve *errors.ValidationError) (*GitLabCIConf
} }
job.OnlyLine = depKey.Line job.OnlyLine = depKey.Line
job.OnlyColumn = depKey.Column job.OnlyColumn = depKey.Column
case "variables":
// Заполняем поля переменных для job.
job.VariablesNode = depVal
var vars map[string]interface{}
if err := depVal.Decode(&vars); err != nil {
return nil, fmt.Errorf("failed to decode variables for job '%s': %w", job.Name, err)
}
job.Variables = vars
} }
// You can add additional manual processing here.
} }
} else { } else {
// If the job value is not a mapping, leave job fields as default. // If the job value is not a mapping, leave job fields as default.

@ -31,19 +31,19 @@ func ValidateGitLabCIFile(content string, opts ValidationOptions) error {
err := yaml.Unmarshal([]byte(content), &root) err := yaml.Unmarshal([]byte(content), &root)
if err != nil { if err != nil {
validationErr.AddDetailWithSpan(errors.ErrYamlSyntax, validationErr.AddDetailWithSpan(errors.ErrYamlSyntax,
fmt.Sprintf("ошибка синтаксического анализа YAML: %v", err), fmt.Sprintf("YAML parsing error: %v", err),
0, 0, 0, 0) 0, 0, 0, 0)
return validationErr return validationErr
} }
if len(root.Content) == 0 { if len(root.Content) == 0 {
validationErr.AddDetailWithSpan(errors.ErrYamlSyntax, validationErr.AddDetailWithSpan(errors.ErrYamlSyntax,
"файл .gitlab-ci.yml пуст или не содержит допустимых данных", ".gitflame-ci.yml file is empty or does not contain valid data",
0, 0, 0, 0) 0, 0, 0, 0)
return validationErr return validationErr
} }
if root.Kind != yaml.DocumentNode || len(root.Content) == 0 { if root.Kind != yaml.DocumentNode || len(root.Content) == 0 {
validationErr.AddDetailWithSpan(errors.ErrYamlSyntax, validationErr.AddDetailWithSpan(errors.ErrYamlSyntax,
"некорректный формат YAML: ожидался корневой объект", "invalid YAML format: a root object was expected",
0, 0, 0, 0) 0, 0, 0, 0)
return validationErr return validationErr
} }
@ -75,6 +75,7 @@ func ValidateGitLabCIFile(content string, opts ValidationOptions) error {
validateStages(cfg, validationErr) validateStages(cfg, validationErr)
validateVariables(cfg, validationErr) validateVariables(cfg, validationErr)
validateIncludeRules(cfg, validationErr) validateIncludeRules(cfg, validationErr)
checkFinalEmptyLine(content, validationErr)
if !validationErr.IsEmpty() { if !validationErr.IsEmpty() {
return validationErr return validationErr
@ -153,47 +154,124 @@ func validateStages(cfg *GitLabCIConfig, ve *errors.ValidationError) {
} }
} }
func validateVariables(cfg *GitLabCIConfig, ve *errors.ValidationError) { // validateVariablesMap проверяет карту переменных varsMap, используя данные из YAMLузла varsNode для получения позиций.
if cfg.Variables == nil { func validateVariablesMap(varsMap map[string]interface{}, varsNode *yaml.Node, ve *errors.ValidationError, posGetter func(varName string) (int, int)) {
return
}
varsMap, ok := cfg.Variables.(map[string]interface{})
if !ok {
ve.AddDetailWithSpan(errors.ErrVariablesInvalid, errors.ErrorMessages[errors.ErrVariablesInvalid], 0, 0, 0, 0)
return
}
allowedKeys := map[string]struct{}{ allowedKeys := map[string]struct{}{
"value": {}, "value": {},
"description": {}, "description": {},
"expand": {}, "expand": {},
"options": {}, "options": {},
} }
for varName, val := range varsMap {
if isScalar(val) { // Проходим по всем ключам, определённым в YAMLузле
for i := 0; i < len(varsNode.Content)-1; i += 2 {
keyNode := varsNode.Content[i]
varName := keyNode.Value
// Получаем позицию для этой переменной через posGetter.
line, col := posGetter(varName)
// Проверяем длину имени переменной
if len(varName) > MaxVariableNameLength { if len(varName) > MaxVariableNameLength {
ve.AddDetailWithSpan(errors.ErrVariableNameTooLong, fmt.Sprintf(errors.ErrorMessages[errors.ErrVariableNameTooLong], varName), ve.AddDetailWithSpan(
0, 0, 0, 0, errors.ErrVariableNameTooLong,
fmt.Sprintf(errors.ErrorMessages[errors.ErrVariableNameTooLong], varName),
line, col, line, col+len(varName),
)
}
// Проверяем, что значение переменной присутствует и корректно
if val, exists := varsMap[varName]; exists {
if !isScalar(val) {
ve.AddDetailWithSpan(
errors.ErrVariableInvalid,
fmt.Sprintf(errors.ErrorMessages[errors.ErrVariableInvalid], varName),
line, col, line, col+len(varName),
) )
} }
} else if nested, ok := val.(map[string]interface{}); ok { if nested, ok := val.(map[string]interface{}); ok {
invalidKeys := []string{} var invalidKeys []string
for k := range nested { for k := range nested {
if _, allowed := allowedKeys[k]; !allowed { if _, allowed := allowedKeys[k]; !allowed {
invalidKeys = append(invalidKeys, k) invalidKeys = append(invalidKeys, k)
} }
} }
if len(invalidKeys) > 0 { if len(invalidKeys) > 0 {
ve.AddDetailWithSpan(errors.ErrVariablesInvalidKey, ve.AddDetailWithSpan(
errors.ErrVariablesInvalidKey,
fmt.Sprintf(errors.ErrorMessages[errors.ErrVariablesInvalidKey], varName, strings.Join(invalidKeys, ", ")), fmt.Sprintf(errors.ErrorMessages[errors.ErrVariablesInvalidKey], varName, strings.Join(invalidKeys, ", ")),
0, 0, 0, 0) line, col, line, col+len(varName),
)
} }
} else {
ve.AddDetailWithSpan(errors.ErrVariableInvalid,
fmt.Sprintf(errors.ErrorMessages[errors.ErrVariableInvalid], varName),
0, 0, 0, 0)
} }
} }
} }
}
func validateJobVariables(job *Job, ve *errors.ValidationError) {
if job.VariablesNode == nil {
return
}
if job.VariablesNode.Kind != yaml.MappingNode {
ve.AddDetailWithSpan(
errors.ErrVariablesInvalid,
"job variables should be a map",
job.Line, job.Column, job.Line, job.Column+1,
)
return
}
varsMap, ok := job.Variables.(map[string]interface{})
if !ok {
ve.AddDetailWithSpan(
errors.ErrVariablesInvalid,
"job variables should be a map",
job.Line, job.Column, job.Line, job.Column+1,
)
return
}
validateVariablesMap(varsMap, job.VariablesNode, ve, func(varName string) (int, int) {
// Ищем переменную в узле job.VariablesNode
for i := 0; i < len(job.VariablesNode.Content)-1; i += 2 {
keyNode := job.VariablesNode.Content[i]
if keyNode.Value == varName {
return keyNode.Line, keyNode.Column
}
}
return job.Line, job.Column // если не найдено, используем позицию job
})
}
func validateVariables(cfg *GitLabCIConfig, ve *errors.ValidationError) {
if cfg.VariablesNode == nil {
return
}
if cfg.VariablesNode.Kind != yaml.MappingNode {
ve.AddDetailWithSpan(
errors.ErrVariablesInvalid,
errors.ErrorMessages[errors.ErrVariablesInvalid],
0, 0, 0, 0,
)
return
}
varsMap, ok := cfg.Variables.(map[string]interface{})
if !ok {
ve.AddDetailWithSpan(
errors.ErrVariablesInvalid,
errors.ErrorMessages[errors.ErrVariablesInvalid],
0, 0, 0, 0,
)
return
}
validateVariablesMap(varsMap, cfg.VariablesNode, ve, func(varName string) (int, int) {
// Ищем в YAML узле переменной с именем varName
for i := 0; i < len(cfg.VariablesNode.Content)-1; i += 2 {
keyNode := cfg.VariablesNode.Content[i]
if keyNode.Value == varName {
return keyNode.Line, keyNode.Column
}
}
return 0, 0
})
}
func isScalar(val interface{}) bool { func isScalar(val interface{}) bool {
switch val.(type) { switch val.(type) {
@ -335,6 +413,7 @@ func validateJobs(cfg *GitLabCIConfig, ve *errors.ValidationError) {
validateJobRules(job, ve) validateJobRules(job, ve)
validateJobWhen(job, ve) validateJobWhen(job, ve)
validateJobOnly(job, ve) validateJobOnly(job, ve)
validateJobVariables(job, ve)
} }
if visibleCount == 0 { if visibleCount == 0 {
@ -798,3 +877,20 @@ func validateIncludeRuleMap(m map[string]interface{}, ve *errors.ValidationError
} }
} }
} }
// checkFinalEmptyLine проверяет, что файл заканчивается пустой строкой.
func checkFinalEmptyLine(content string, ve *errors.ValidationError) {
// Если содержимое не заканчивается символом новой строки...
if !strings.HasSuffix(content, "\n") {
lines := strings.Split(content, "\n")
lastLineIndex := len(lines)
lastLineContent := lines[lastLineIndex-1]
lineNum := lastLineIndex
colNum := len(lastLineContent) + 1
ve.AddDetailWithSpan(
errors.ErrMissingFinalNewline,
errors.ErrorMessages[errors.ErrMissingFinalNewline],
lineNum, colNum, lineNum, colNum,
)
}
}

@ -1,71 +1,5 @@
package main package main
import (
"bufio"
"fmt"
"os"
"tea.gitpark.ru/Azaki/CI_VALIDATOR/gitlabcivalidator"
)
func main() { func main() {
// Корректный (упрощённый) YAML
// validYAML := `
//stages:
// - build
// - test
//jobs:
// build_job:
// stage: build
// script:
// - echo "Building..."
// test_job:
// stage: test
// dependencies: ["build_job"]
// script:
// - echo "Testing..."
//`
// Тип, ссылка и текст ошибки, показатели где нахоидтся ошибка...
// Некорректный YAML — отсутствует видимая задача, есть только скрытая
// invalidYAML := `
//stages:
// - build
// - test
//jobs:
// teaaa:
// build_job:
// validateJobMapping(jobValNode, ve):
// script:
// - c
// needs: ["test_job2"]
// test_job2:
// stage: test
//`
scanner := bufio.NewScanner(os.Stdin)
var inputYAML string
for scanner.Scan() {
inputYAML += scanner.Text() + "\n"
}
if err := scanner.Err(); err != nil {
fmt.Println("Ошибка чтения ввода:", err)
return
}
err := gitlabcivalidator.ValidateGitLabCIFile(inputYAML, gitlabcivalidator.ValidationOptions{})
if err != nil {
fmt.Println(err.Error())
} else {
fmt.Println("no errors")
}
//fmt.Println("\n=== Пример 2: Некорректный YAML ===")
//err2 := gitlabcivalidator.ValidateGitLabCIFile(invalidYAML, gitlabcivalidator.ValidationOptions{})
//if err2 != nil {
// fmt.Println("Ошибка валидации (ожидаемо для данного файла):")
// fmt.Println(err2.Error())
//} else {
// log.Fatal("Ожидали ошибку, но её не возникло")
//}
} }

Loading…
Cancel
Save