Skip to content

Commit

Permalink
Support disallowing YAML anchors and aliases (#54)
Browse files Browse the repository at this point in the history
* WIP: support disallowing YAML anchors and aliases

* review feedback: move yamlisms into basic formatter

* remove unused during action scaffolding

* remove unused field, update basic formatter README
  • Loading branch information
imjasonh committed Oct 12, 2022
1 parent 49e6d52 commit c31ff16
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 6 deletions.
1 change: 1 addition & 0 deletions formatters/basic/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ The basic formatter is a barebones formatter that simply takes the data provided
| `line_ending` | `lf` or `crlf` | `crlf` on Windows, `lf` otherwise | Parse and write the file with "lf" or "crlf" line endings. This setting will be overwritten by the global `line_ending`. |
| `emoji_support` | bool | false | Support encoding utf-8 emojis |
| `retain_line_breaks` | bool | false | Retain line breaks in formatted yaml |
| `disallow_anchors` | bool | false | If true, reject any YAML anchors or aliases found in the document. |
1 change: 1 addition & 0 deletions formatters/basic/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ type Config struct {
EmojiSupport bool `mapstructure:"emoji_support"`
LineEnding yamlfmt.LineBreakStyle `mapstructure:"line_ending"`
RetainLineBreaks bool `mapstructure:"retain_line_breaks"`
DisallowAnchors bool `mapstructure:"disallow_anchors"`
}

func DefaultConfig() *Config {
Expand Down
5 changes: 3 additions & 2 deletions formatters/basic/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ func (f *BasicFormatterFactory) NewFormatter(configData map[string]interface{})

func newFormatter(config *Config) yamlfmt.Formatter {
return &BasicFormatter{
Config: config,
Features: ConfigureFeaturesFromConfig(config),
Config: config,
Features: ConfigureFeaturesFromConfig(config),
YAMLFeatures: ConfigureYAMLFeaturesFromConfig(config),
}
}
9 changes: 9 additions & 0 deletions formatters/basic/features.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package basic

import (
"github.com/google/yamlfmt"
"github.com/google/yamlfmt/internal/anchors"
"github.com/google/yamlfmt/internal/hotfix"
)

Expand Down Expand Up @@ -59,3 +60,11 @@ func ConfigureFeaturesFromConfig(config *Config) yamlfmt.FeatureList {
}
return features
}

func ConfigureYAMLFeaturesFromConfig(config *Config) YAMLFeatureList {
var features YAMLFeatureList
if config.DisallowAnchors {
features = append(features, anchors.Check)
}
return features
}
25 changes: 23 additions & 2 deletions formatters/basic/formatter.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ import (
const BasicFormatterType string = "basic"

type BasicFormatter struct {
Config *Config
Features yamlfmt.FeatureList
Config *Config
Features yamlfmt.FeatureList
YAMLFeatures YAMLFeatureList
}

// yamlfmt.Formatter interface
Expand Down Expand Up @@ -59,6 +60,13 @@ func (f *BasicFormatter) Format(input []byte) ([]byte, error) {
documents = append(documents, docNode)
}

// Run all YAML features.
for _, d := range documents {
if err := f.YAMLFeatures.ApplyFeatures(d); err != nil {
return nil, err
}
}

var b bytes.Buffer
e := yaml.NewEncoder(&b)
e.SetIndent(f.Config.Indent)
Expand All @@ -77,3 +85,16 @@ func (f *BasicFormatter) Format(input []byte) ([]byte, error) {

return resultYaml, nil
}

type YAMLFeatureList []YAMLFeatureFunc

func (fl YAMLFeatureList) ApplyFeatures(node yaml.Node) error {
for _, f := range fl {
if err := f(node); err != nil {
return err
}
}
return nil
}

type YAMLFeatureFunc func(yaml.Node) error
37 changes: 37 additions & 0 deletions internal/anchors/check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package anchors

import (
"errors"
"fmt"

"gopkg.in/yaml.v3"
)

func Check(n yaml.Node) error {
if n.Kind == yaml.AliasNode {
return errors.New("alias node found")
}
if n.Anchor != "" {
return fmt.Errorf("node references anchor %q", n.Anchor)
}
for _, c := range n.Content {
if err := Check(*c); err != nil {
return err
}
}
return nil
}
66 changes: 66 additions & 0 deletions internal/anchors/check_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package anchors_test

import (
"strings"
"testing"

"github.com/google/yamlfmt/internal/anchors"
"gopkg.in/yaml.v3"
)

func TestCheck(t *testing.T) {
for _, c := range []struct {
desc string
in string
wantErr bool
}{{
desc: "no anchors",
in: "foo: bar",
wantErr: false,
}, {
desc: "anchor",
// From https://support.atlassian.com/bitbucket-cloud/docs/yaml-anchors/
in: `
definitions:
steps:
- step: &build-test
name: Build and test
script:
- mvn package
artifacts:
- target/**
pipelines:
branches:
develop:
- step: *build-test
main:
- step: *build-test
`,
wantErr: true,
}} {
t.Run(c.desc, func(t *testing.T) {
var docNode yaml.Node
if err := yaml.NewDecoder(strings.NewReader(c.in)).Decode(&docNode); err != nil {
t.Fatalf("parse error: %v", err)
}
if err := anchors.Check(docNode); (err != nil) != c.wantErr {
t.Errorf("Check() error = %v, wantErr %v", err, c.wantErr)
}
})
}
}
7 changes: 5 additions & 2 deletions yamlfmt.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,11 @@ type FeatureApplyError struct {
}

func (e *FeatureApplyError) Error() string {
action := "Before"
if e.mode == FeatureApplyAfter {
var action string
switch e.mode {
case FeatureApplyBefore:
action = "Before"
case FeatureApplyAfter:
action = "After"
}
return fmt.Sprintf("Feature %s %sAction failed with error: %v", e.featureName, action, e.err)
Expand Down

0 comments on commit c31ff16

Please sign in to comment.