Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add editorconfig validation #896

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
207 changes: 207 additions & 0 deletions cmd/editor_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
package cmd

import (
"fmt"
"os"

"github.com/cloudposse/atmos/pkg/schema"
u "github.com/cloudposse/atmos/pkg/utils"

"github.com/editorconfig-checker/editorconfig-checker/v3/pkg/config"
er "github.com/editorconfig-checker/editorconfig-checker/v3/pkg/error"
"github.com/editorconfig-checker/editorconfig-checker/v3/pkg/files"
"github.com/editorconfig-checker/editorconfig-checker/v3/pkg/utils"
"github.com/editorconfig-checker/editorconfig-checker/v3/pkg/validation"
"github.com/spf13/cobra"
)

var (
editorConfigVersion = "v3.0.3"
samtholiya marked this conversation as resolved.
Show resolved Hide resolved
defaultConfigFilePath = ".editorconfig"
initEditorConfig bool
currentConfig *config.Config
cliConfig config.Config
configFilePath string
tmpExclude string
)

var editorConfigCmd *cobra.Command = &cobra.Command{
Use: "editorconfig",
Short: "Validate all files against the EditorConfig",
Long: "Validate all files against the project's EditorConfig rules",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
initializeConfig()
},
Run: func(cmd *cobra.Command, args []string) {
if cliConfig.Help {
cmd.Help()
os.Exit(0)
}
runMainLogic()
},
}

// initializeConfig breaks the initialization cycle by separating the config setup
func initializeConfig() {
replaceAtmosConfigInConfig(atmosConfig)

u.LogInfo(atmosConfig, fmt.Sprintf("EditorConfig Checker CLI Version: %s", editorConfigVersion))
samtholiya marked this conversation as resolved.
Show resolved Hide resolved
if configFilePath == "" {
configFilePath = defaultConfigFilePath
}

var err error
currentConfig, err = config.NewConfig(configFilePath)
if err != nil {
u.LogErrorAndExit(atmosConfig, err)
}

if initEditorConfig {
err := currentConfig.Save(editorConfigVersion)
if err != nil {
u.LogErrorAndExit(atmosConfig, err)
}
}

_ = currentConfig.Parse()
samtholiya marked this conversation as resolved.
Show resolved Hide resolved

if tmpExclude != "" {
currentConfig.Exclude = append(currentConfig.Exclude, tmpExclude)
}

currentConfig.Merge(cliConfig)
}

func replaceAtmosConfigInConfig(atmosConfig schema.AtmosConfiguration) {
if atmosConfig.Validate.EditorConfig.ConfigFilePath != "" {
configFilePath = atmosConfig.Validate.EditorConfig.ConfigFilePath
}
if atmosConfig.Validate.EditorConfig.Exclude != "" {
tmpExclude = atmosConfig.Validate.EditorConfig.Exclude
}
if atmosConfig.Validate.EditorConfig.Init {
initEditorConfig = atmosConfig.Validate.EditorConfig.Init
}
if atmosConfig.Validate.EditorConfig.IgnoreDefaults {
cliConfig.IgnoreDefaults = atmosConfig.Validate.EditorConfig.IgnoreDefaults
}
if atmosConfig.Validate.EditorConfig.DryRun {
cliConfig.DryRun = atmosConfig.Validate.EditorConfig.DryRun
}
if atmosConfig.Validate.EditorConfig.Format != "" {
cliConfig.Format = atmosConfig.Validate.EditorConfig.Format
}
if atmosConfig.Logs.Level == "trace" {
cliConfig.Verbose = true
}
if atmosConfig.Validate.EditorConfig.NoColor {
cliConfig.NoColor = atmosConfig.Validate.EditorConfig.NoColor
}
if atmosConfig.Validate.EditorConfig.Disable.TrimTrailingWhitespace {
cliConfig.Disable.TrimTrailingWhitespace = atmosConfig.Validate.EditorConfig.Disable.TrimTrailingWhitespace
}
if atmosConfig.Validate.EditorConfig.Disable.EndOfLine {
cliConfig.Disable.EndOfLine = atmosConfig.Validate.EditorConfig.Disable.EndOfLine
}
if atmosConfig.Validate.EditorConfig.Disable.InsertFinalNewline {
cliConfig.Disable.InsertFinalNewline = atmosConfig.Validate.EditorConfig.Disable.InsertFinalNewline
}
if atmosConfig.Validate.EditorConfig.Disable.Indentation {
cliConfig.Disable.Indentation = atmosConfig.Validate.EditorConfig.Disable.Indentation
}
if atmosConfig.Validate.EditorConfig.Disable.IndentSize {
cliConfig.Disable.IndentSize = atmosConfig.Validate.EditorConfig.Disable.IndentSize
}
if atmosConfig.Validate.EditorConfig.Disable.MaxLineLength {
cliConfig.Disable.MaxLineLength = atmosConfig.Validate.EditorConfig.Disable.MaxLineLength
}
}

// runMainLogic contains the main logic
func runMainLogic() {
config := *currentConfig
u.LogDebug(atmosConfig, config.GetAsString())
u.LogTrace(atmosConfig, fmt.Sprintf("Exclude Regexp: %s", config.GetExcludesAsRegularExpression()))

if err := checkVersion(config); err != nil {
u.LogErrorAndExit(atmosConfig, err)
}

if handleReturnableFlags(config) {
return
}

filePaths, err := files.GetFiles(config)
if err != nil {
u.LogErrorAndExit(atmosConfig, err)
}

if config.DryRun {
for _, file := range filePaths {
u.LogInfo(atmosConfig, file)
}
os.Exit(0)
}

errors := validation.ProcessValidation(filePaths, config)
errorCount := er.GetErrorCount(errors)

if errorCount != 0 {
er.PrintErrors(errors, config)
u.LogErrorAndExit(atmosConfig, fmt.Errorf("\n%d errors found", errorCount))
}

u.LogDebug(atmosConfig, fmt.Sprintf("%d files checked", len(filePaths)))
u.LogInfo(atmosConfig, "No errors found")
}

func checkVersion(config config.Config) error {
if !utils.FileExists(config.Path) || config.Version == "" {
return nil
}
if config.Version != editorConfigVersion {
return fmt.Errorf("version mismatch: binary=%s, config=%s",
editorConfigVersion, config.Version)
}

return nil
}

// handleReturnableFlags handles early termination flags
func handleReturnableFlags(config config.Config) bool {
if config.ShowVersion {
config.Logger.Output(editorConfigVersion)
return true
}
if config.Help {
config.Logger.Output("USAGE:")
return true
}
return false
}

// addPersistentFlags adds flags to the root command
func addPersistentFlags(cmd *cobra.Command) {
samtholiya marked this conversation as resolved.
Show resolved Hide resolved
cmd.PersistentFlags().StringVar(&configFilePath, "config", "", "Path to the configuration file")
cmd.PersistentFlags().StringVar(&tmpExclude, "exclude", "", "Regex to exclude files from checking")
cmd.PersistentFlags().BoolVar(&initEditorConfig, "init", false, "creates an initial configuration")

cmd.PersistentFlags().BoolVar(&cliConfig.IgnoreDefaults, "ignore-defaults", false, "Ignore default excludes")
cmd.PersistentFlags().BoolVar(&cliConfig.DryRun, "dry-run", false, "Show which files would be checked")
cmd.PersistentFlags().BoolVar(&cliConfig.ShowVersion, "version", false, "Print the version number")
samtholiya marked this conversation as resolved.
Show resolved Hide resolved
cmd.PersistentFlags().StringVar(&cliConfig.Format, "format", "default", "Specify the output format: default, gcc")
cmd.PersistentFlags().BoolVar(&cliConfig.NoColor, "no-color", false, "Don't print colors")
cmd.PersistentFlags().BoolVar(&cliConfig.Disable.TrimTrailingWhitespace, "disable-trim-trailing-whitespace", false, "Disable trailing whitespace check")
cmd.PersistentFlags().BoolVar(&cliConfig.Disable.EndOfLine, "disable-end-of-line", false, "Disable end-of-line check")
cmd.PersistentFlags().BoolVar(&cliConfig.Disable.InsertFinalNewline, "disable-insert-final-newline", false, "Disable final newline check")
cmd.PersistentFlags().BoolVar(&cliConfig.Disable.Indentation, "disable-indentation", false, "Disable indentation check")
cmd.PersistentFlags().BoolVar(&cliConfig.Disable.IndentSize, "disable-indent-size", false, "Disable indent size check")
cmd.PersistentFlags().BoolVar(&cliConfig.Disable.MaxLineLength, "disable-max-line-length", false, "Disable max line length check")
}

func init() {
// Add flags
addPersistentFlags(editorConfigCmd)
// Add command
validateCmd.AddCommand(editorConfigCmd)
}
38 changes: 38 additions & 0 deletions examples/quick-start-simple/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# EditorConfig for Terraform repository

root = true

# Terraform files
[*.tf]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

# HCL files (for Terraform configurations)
[*.hcl]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

# Atmos configurations (if they exist as YAML or TOML)
[*.yml]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.toml]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ require (
github.com/charmbracelet/huh v0.6.0
github.com/charmbracelet/lipgloss v1.0.0
github.com/charmbracelet/log v0.4.0
github.com/editorconfig-checker/editorconfig-checker/v3 v3.0.3
github.com/elewis787/boa v0.1.2
github.com/fatih/color v1.18.0
github.com/go-git/go-git/v5 v5.12.0
Expand Down Expand Up @@ -95,6 +96,7 @@ require (
github.com/aws/smithy-go v1.22.1 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/baulk/chardet v0.1.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 // indirect
Expand Down Expand Up @@ -122,10 +124,12 @@ require (
github.com/docker/libkv v0.2.2-0.20180912205406-458977154600 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad // indirect
github.com/editorconfig/editorconfig-core-go/v2 v2.6.2 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.4 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.5.0 // indirect
github.com/go-ini/ini v1.67.0 // indirect
Expand Down
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,8 @@ github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWp
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/baulk/chardet v0.1.0 h1:6/r5nPMikB9OG1Njs10VfVHZTDMFH6BdybHPISpfUVA=
github.com/baulk/chardet v0.1.0/go.mod h1:0ibN6068qswel5Hv54U7GNJUU57njfzPJrLIq7Y8xas=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
Expand Down Expand Up @@ -513,6 +515,10 @@ github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+m
github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad h1:Qk76DOWdOp+GlyDKBAG3Klr9cn7N+LcYc82AZ2S7+cA=
github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad/go.mod h1:mPKfmRa823oBIgl2r20LeMSpTAteW5j7FLkc0vjmzyQ=
github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
github.com/editorconfig-checker/editorconfig-checker/v3 v3.0.3 h1:WO9Yd7/KjfGDYUeSBsGKh+Uj+K+/oTnJ3elDQ7Oq7yU=
github.com/editorconfig-checker/editorconfig-checker/v3 v3.0.3/go.mod h1:9mvpU+I3xMoU+l0vNtR98SUX/AsoAhBCntntRtNIu3Y=
github.com/editorconfig/editorconfig-core-go/v2 v2.6.2 h1:dKG8sc7n321deIVRcQtwlMNoBEra7j0qQ8RwxO8RN0w=
github.com/editorconfig/editorconfig-core-go/v2 v2.6.2/go.mod h1:7dvD3GCm7eBw53xZ/lsiq72LqobdMg3ITbMBxnmJmqY=
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/elewis787/boa v0.1.2 h1:xNKWJ9X2MWbLSLLOA31N4l1Jdec9FZSkbTvXy3C8rw4=
Expand Down Expand Up @@ -553,6 +559,8 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I=
github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
Expand Down
32 changes: 30 additions & 2 deletions pkg/schema/schema.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package schema

import "github.com/cloudposse/atmos/pkg/store"
import (
"github.com/cloudposse/atmos/pkg/store"
)

type AtmosSectionMapType = map[string]any

Expand Down Expand Up @@ -30,13 +32,39 @@ type AtmosConfiguration struct {
StackType string `yaml:"stackType,omitempty" json:"StackType,omitempty" mapstructure:"stackType"`
Default bool `yaml:"default" json:"default" mapstructure:"default"`
Version Version `yaml:"version,omitempty" json:"version,omitempty" mapstructure:"version"`

Validate Validate `yaml:"validate,omitempty" json:"validate,omitempty" mapstructure:"validate"`
// Stores is never read from yaml, it is populated in processStoreConfig and it's used to pass to the populated store
// registry through to the yaml parsing functions when !store is run and to pass the registry to the hooks
// functions to be able to call stores from within hooks.
Stores store.StoreRegistry `yaml:"stores_registry,omitempty" json:"stores_registry,omitempty" mapstructure:"stores_registry"`
}

type Validate struct {
EditorConfig EditorConfig `yaml:"editorconfig,omitempty" json:"editorconfig,omitempty" mapstructure:"editorconfig"`
}

type EditorConfig struct {
IgnoreDefaults bool `yaml:"ignore_defaults,omitempty" json:"ignore_defaults,omitempty" mapstructure:"ignore_defaults"`
DryRun bool `yaml:"dry_run,omitempty" json:"dry_run,omitempty" mapstructure:"dry_run"`
Format string `yaml:"format,omitempty" json:"format,omitempty" mapstructure:"format"`
NoColor bool `yaml:"no_color,omitempty" json:"no_color,omitempty" mapstructure:"no_color"`
Disable DisabledChecks `yaml:"disable,omitempty" json:"disable,omitempty" mapstructure:"disable"`

ConfigFilePath string `yaml:"config_file_path,omitempty" json:"config_file_path,omitempty" mapstructure:"config_file_path"`
Exclude string `yaml:"exclude,omitempty" json:"exclude,omitempty" mapstructure:"exclude"`
Init bool `yaml:"init,omitempty" json:"init,omitempty" mapstructure:"init"`
}

// DisabledChecks is a Struct which represents disabled checks
type DisabledChecks struct {
EndOfLine bool `yaml:"end_of_line,omitempty" json:"end_of_line,omitempty" mapstructure:"end_of_line"`
InsertFinalNewline bool `yaml:"insert_final_newline,omitempty" json:"insert_final_newline,omitempty" mapstructure:"insert_final_newline"`
Indentation bool `yaml:"indentation,omitempty" json:"indentation,omitempty" mapstructure:"indentation"`
IndentSize bool `yaml:"indent_size,omitempty" json:"indent_size,omitempty" mapstructure:"indent_size"`
MaxLineLength bool `yaml:"max_line_length,omitempty" json:"max_line_length,omitempty" mapstructure:"max_line_length"`
TrimTrailingWhitespace bool `yaml:"trim_trailing_whitespace,omitempty" json:"trim_trailing_whitespace,omitempty" mapstructure:"trim_trailing_whitespace"`
}

type AtmosSettings struct {
ListMergeStrategy string `yaml:"list_merge_strategy" json:"list_merge_strategy" mapstructure:"list_merge_strategy"`
Docs Docs `yaml:"docs,omitempty" json:"docs,omitempty" mapstructure:"docs"`
Expand Down
16 changes: 15 additions & 1 deletion tests/test_cases.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,21 @@ tests:
stderr:
- 'unknown command "non-existent" for "atmos"'
exit_code: 1

- name: atmos validate editorconfig-check
samtholiya marked this conversation as resolved.
Show resolved Hide resolved
enabled: true
description: "Ensure atmos CLI validates based on the .ecrc file."
workdir: "../examples/quick-start-simple/"
command: "atmos"
args:
- "validate"
- "editorconfig"
expect:
stdout:
- "EditorConfig Checker CLI Version: v3.0.3"
- "No errors found"
stderr:
- "^$"
exit_code: 0
- name: atmos describe config -f yaml
enabled: true
description: "Ensure atmos CLI outputs the Atmos configuration in YAML."
Expand Down
Loading