Skip to content

Commit

Permalink
Add editorconfig validation (#896)
Browse files Browse the repository at this point in the history
* Add editorconfig-checker to atmos cli

issue: https://linear.app/cloudposse/issue/DEV-2836/implement-atmos-validate-editorconfig

* Add positive test case for editorconfig-checker

* Update logger to atmos logger

* refactored code

* Temp atmos config fix to fix unit test

* Fix logging in editor_config

* added EditorConfig Checker in Atmos config

* Removed editor config checker version info

* Added atmos.yaml schema help

* Update tests/test_cases.yaml

Co-authored-by: Erik Osterman (CEO @ Cloud Posse) <[email protected]>

* try to fix windows workflow

* Add reason for git config before checkout step

* Change to print message based on existing working

* Added negative test case for editorconfig

* changed negative testcase

* update editorconfig schema

* Update the test cases based on new fixtures

* fix verbose log level

* Add editor config documentation

* Update website/docs/core-concepts/validate/editorconfig.mdx

Co-authored-by: Erik Osterman (CEO @ Cloud Posse) <[email protected]>

* Added editorconfig help page

* Set git attributes

* validate.mdx to remove unwanted diff

* Update website/docs/cli/commands/validate/validate-editorconfig.mdx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update website/docs/cli/commands/validate/validate-editorconfig.mdx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* editorconfig test cases

* refactor file name for editorconfig

* Update .github/workflows/test.yml

Co-authored-by: Erik Osterman (CEO @ Cloud Posse) <[email protected]>

* Update .gitattributes

Co-authored-by: Erik Osterman (CEO @ Cloud Posse) <[email protected]>

* changed editorconfig fixture directory

* Removed files to minimal

* Update tests/fixtures/scenarios/editorconfig/README.md

Co-authored-by: Erik Osterman (CEO @ Cloud Posse) <[email protected]>

* fix go.mod

* updated golden snapshots

* updated the description

---------

Co-authored-by: Erik Osterman (CEO @ Cloud Posse) <[email protected]>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Jan 18, 2025
1 parent e98c3aa commit 14bea36
Show file tree
Hide file tree
Showing 24 changed files with 1,700 additions and 10 deletions.
37 changes: 37 additions & 0 deletions atmos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,43 @@ logs:
# Can also be set using 'ATMOS_LOGS_LEVEL' ENV var, or '--logs-level' command-line argument
level: Info

validate:
# The configuration settings for the editorconfig-checker.

editorconfig:
# A list of file paths or patterns to exclude from checks.
exclude: []

# If set to true, the default ignore patterns (like .git/*) will not be applied.
ignore_defaults: false

# Runs the checker without making any changes or producing output, useful for testing configuration.
dry_run: false

# Specifies the output format. Options: "default", "json".
format: "default"

# Enables/Disables colored output in the terminal if .
color: true

# Disables checking for trailing whitespace at the end of lines.
disable_trim_trailing_whitespace: false

# Disables checking for consistent line endings (e.g., LF vs. CRLF).
disable_end_of_line: false

# Disables checking for the presence of a newline at the end of files.
disable_insert_final_newline: false

# Disables checking for consistent indentation style (e.g., tabs or spaces).
disable_indentation: false

# Disables checking for consistent indentation size (e.g., 2 spaces or 4 spaces).
disable_indent_size: false

# Disables checking for lines exceeding a maximum length.
disable_max_line_length: false

# Custom CLI commands
commands:
- name: tf
Expand Down
194 changes: 194 additions & 0 deletions cmd/validate_editorconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package cmd

import (
"fmt"
"os"
"strings"

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

"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 (
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(cmd)
},
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(cmd *cobra.Command) {
replaceAtmosConfigInConfig(cmd, atmosConfig)

if configFilePath == "" {
configFilePath = defaultConfigFilePath
}

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

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

_ = currentConfig.Parse()

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

currentConfig.Merge(cliConfig)
}

func replaceAtmosConfigInConfig(cmd *cobra.Command, atmosConfig schema.AtmosConfiguration) {
if !cmd.Flags().Changed("config") && atmosConfig.Validate.EditorConfig.ConfigFilePath != "" {
configFilePath = atmosConfig.Validate.EditorConfig.ConfigFilePath
}
if !cmd.Flags().Changed("exclude") && len(atmosConfig.Validate.EditorConfig.Exclude) > 0 {
tmpExclude = strings.Join(atmosConfig.Validate.EditorConfig.Exclude, ",")
}
if !cmd.Flags().Changed("init") && atmosConfig.Validate.EditorConfig.Init {
initEditorConfig = atmosConfig.Validate.EditorConfig.Init
}
if !cmd.Flags().Changed("ignore-defaults") && atmosConfig.Validate.EditorConfig.IgnoreDefaults {
cliConfig.IgnoreDefaults = atmosConfig.Validate.EditorConfig.IgnoreDefaults
}
if !cmd.Flags().Changed("dry-run") && atmosConfig.Validate.EditorConfig.DryRun {
cliConfig.DryRun = atmosConfig.Validate.EditorConfig.DryRun
}
if !cmd.Flags().Changed("format") && atmosConfig.Validate.EditorConfig.Format != "" {
cliConfig.Format = atmosConfig.Validate.EditorConfig.Format
}
if !cmd.Flags().Changed("logs-level") && atmosConfig.Logs.Level == "trace" {
cliConfig.Verbose = true
} else if cmd.Flags().Changed("logs-level") {
if v, err := cmd.Flags().GetString("logs-level"); err == nil && v == "trace" {
cliConfig.Verbose = true
}
}
if !cmd.Flags().Changed("no-color") && !atmosConfig.Validate.EditorConfig.Color {
cliConfig.NoColor = !atmosConfig.Validate.EditorConfig.Color
}
if !cmd.Flags().Changed("disable-trim-trailing-whitespace") && atmosConfig.Validate.EditorConfig.DisableTrimTrailingWhitespace {
cliConfig.Disable.TrimTrailingWhitespace = atmosConfig.Validate.EditorConfig.DisableTrimTrailingWhitespace
}
if !cmd.Flags().Changed("disable-end-of-line") && atmosConfig.Validate.EditorConfig.DisableEndOfLine {
cliConfig.Disable.EndOfLine = atmosConfig.Validate.EditorConfig.DisableEndOfLine
}
if !cmd.Flags().Changed("disable-insert-final-newline") && atmosConfig.Validate.EditorConfig.DisableInsertFinalNewline {
cliConfig.Disable.InsertFinalNewline = atmosConfig.Validate.EditorConfig.DisableInsertFinalNewline
}
if !cmd.Flags().Changed("disable-indentation") && atmosConfig.Validate.EditorConfig.DisableIndentation {
cliConfig.Disable.Indentation = atmosConfig.Validate.EditorConfig.DisableIndentation
}
if !cmd.Flags().Changed("disable-indent-size") && atmosConfig.Validate.EditorConfig.DisableIndentSize {
cliConfig.Disable.IndentSize = atmosConfig.Validate.EditorConfig.DisableIndentSize
}
if !cmd.Flags().Changed("disable-max-line-length") && atmosConfig.Validate.EditorConfig.DisableMaxLineLength {
cliConfig.Disable.MaxLineLength = atmosConfig.Validate.EditorConfig.DisableMaxLineLength
}
}

// 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)
}

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.PrintMessage("No errors found")
}

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

return nil
}

// addPersistentFlags adds flags to the root command
func addPersistentFlags(cmd *cobra.Command) {
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")
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
2 changes: 1 addition & 1 deletion examples/quick-start-simple/atmos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ stacks:
name_pattern: "{stage}"

logs:
file: "/dev/stderr"
level: Info
file: "/dev/stderr"
4 changes: 4 additions & 0 deletions go.mod

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 8 additions & 2 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,8 @@ func InitCliConfig(configAndStacksInfo schema.ConfigAndStacksInfo, processStacks
return atmosConfig, err
}
}

// We want the editorconfig color by default to be true
atmosConfig.Validate.EditorConfig.Color = true
// https://gist.github.com/chazcheadle/45bf85b793dea2b71bd05ebaa3c28644
// https://sagikazarmark.hu/blog/decoding-custom-formats-with-viper/
err = v.Unmarshal(&atmosConfig)
Expand Down
Loading

0 comments on commit 14bea36

Please sign in to comment.