Repository: goccy/go-yaml
Pull request #469 (2024-07-29 06:29:44)

feat(printer): Add support for formatting comment nodes
Hi!

I use `printer` to colorize YAML output to a terminal [here](https://github.com/clevyr/yampl/blob/ef3493e72b79551428337c81afbbf520a2fa6323/internal/colorize/colorize.go#L40-L89) (see also: [`yq` color printer](https://github.com/mikefarah/yq/blob/b80e1cb35e7f4728608d71df002870c379a76e60/pkg/yqlib/color_print.go#L20-L61)), and would like to print comments in gray. This PR adds support for formatting comment nodes.

codecov-commenter: :warning: Please install the !['codecov app svg image'](https://github.com/codecov/engineering-team/assets/152432831/e90313f4-9d3a-4b63-8b54-cfe14e7ec20d) to ensure uploads and comments are reliably processed by Codecov.

## [Codecov](https://app.codecov.io/gh/goccy/go-yaml/pull/469?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Masaaki+Goshima) Report
Attention: Patch coverage is `0%` with `4 lines` in your changes missing coverage. Please review.
> Project coverage is 78.69%. Comparing base [(`237df0e`)](https://app.codecov.io/gh/goccy/go-yaml/commit/237df0ef1c53d7c1732e6afb1fcfd85dd1745637?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Masaaki+Goshima) to head [(`d30e09a`)](https://app.codecov.io/gh/goccy/go-yaml/commit/d30e09a9af0f92bd6f5754ac77fbe70c1f82e777?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Masaaki+Goshima).

:exclamation: Your organization needs to install the [Codecov GitHub app](https://github.com/apps/codecov/installations/select_target) to enable full functionality.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master     #469      +/-   ##
==========================================
- Coverage   78.77%   78.69%   -0.08%     
==========================================
  Files          13       13              
  Lines        4004     4008       +4     
==========================================
  Hits         3154     3154              
- Misses        591      595       +4     
  Partials      259      259              
```

</details>

Patch:
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 74140f3..27337dd 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -5,12 +5,26 @@ on:
       - master
   pull_request:
 jobs:
+  lint:
+    name: lint
+    runs-on: ubuntu-latest
+    steps:
+      - name: checkout
+        uses: actions/checkout@v4
+      - name: setup Go
+        uses: actions/setup-go@v4
+        with:
+          go-version: "1.23"
+      - name: run linters
+        run: |
+          make lint
+
   race-test:
     name: Test with -race
     strategy:
       matrix:
         os: [ "ubuntu-latest", "macos-latest", "windows-latest" ]
-        go-version: [ "1.19", "1.20", "1.21" ]
+        go-version: [ "1.21", "1.22", "1.23" ]
     runs-on: ${{ matrix.os }}
     steps:
     - name: checkout
@@ -28,7 +42,7 @@ jobs:
     strategy:
       matrix:
         os: [ "ubuntu-latest", "windows-latest" ]
-        go-version: [ "1.19", "1.20", "1.21" ]
+        go-version: [ "1.21", "1.22", "1.23" ]
     runs-on: ${{ matrix.os }}
     env:
       GOARCH: "386"
@@ -52,7 +66,7 @@ jobs:
       - name: setup Go
         uses: actions/setup-go@v4
         with:
-          go-version: "1.21"
+          go-version: "1.23"
       - name: build
         run: |
           make ycat/build
@@ -69,7 +83,7 @@ jobs:
       - name: setup Go
         uses: actions/setup-go@v4
         with:
-          go-version: "1.21"
+          go-version: "1.23"
       - name: measure coverage
         run: |
           make cover
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e660fd9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,1 @@
+bin/
diff --git a/.golangci.yml b/.golangci.yml
new file mode 100644
index 0000000..ec8d731
--- /dev/null
+++ b/.golangci.yml
@@ -0,0 +1,42 @@
+run:
+  timeout: 5m
+
+linters-settings:
+  errcheck:
+    check-type-assertions: true
+  gci:
+    sections:
+      - "standard"
+      - "default"
+      - "prefix(github.com/goccy/go-yaml)"
+      - "blank"
+      - "dot"
+  gofmt:
+    simplify: true
+  govet:
+    disable:
+      - tests
+  misspell:
+    locale: US
+  staticcheck:
+    checks: ["all", "-ST1000", "-ST1005"]
+
+linters:
+  disable-all: true
+  enable:
+    - errcheck
+    - gci
+    - gofmt
+    - gosimple
+    - govet
+    - ineffassign
+    - misspell
+    - staticcheck
+    - typecheck
+    - unused
+
+issues:
+  exclude-rules:
+    - path: _test\.go
+      linters:
+        - staticcheck
diff --git a/Makefile b/Makefile
index 1b1d923..02a38dd 100644
--- a/Makefile
+++ b/Makefile
@@ -17,3 +17,31 @@ cover-html: cover
 .PHONY: ycat/build
 ycat/build:
 	go build -o ycat ./cmd/ycat
+
+.PHONY: lint
+lint: golangci-lint ## Run golangci-lint
+	@$(GOLANGCI_LINT) run
+
+.PHONY: fmt
+fmt: golangci-lint ## Ensure consistent code style
+	@go mod tidy
+	@go fmt ./...
+	@$(GOLANGCI_LINT) run --fix
+
+## Location to install dependencies to
+LOCALBIN ?= $(shell pwd)/bin
+$(LOCALBIN):
+	mkdir -p $(LOCALBIN)
+
+## Tool Binaries
+GOLANGCI_LINT ?= $(LOCALBIN)/golangci-lint
+
+## Tool Versions
+GOLANGCI_VERSION := 1.61.0
+
+.PHONY: golangci-lint
+.PHONY: $(GOLANGCI_LINT)
+golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.
+$(GOLANGCI_LINT): $(LOCALBIN)
+	@test -s $(LOCALBIN)/golangci-lint && $(LOCALBIN)/golangci-lint version --format short | grep -q $(GOLANGCI_VERSION) || \
+	curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(LOCALBIN) v$(GOLANGCI_VERSION)
diff --git a/ast/ast.go b/ast/ast.go
index b4d5ec4..66ddfd9 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -7,8 +7,9 @@ import (
 	"strconv"
 	"strings"
 
-	"github.com/goccy/go-yaml/token"
 	"golang.org/x/xerrors"
+
+	"github.com/goccy/go-yaml/token"
 )
 
 var (
@@ -2092,10 +2093,10 @@ func Merge(dst Node, src Node) error {
 	err := &ErrInvalidMergeType{dst: dst, src: src}
 	switch dst.Type() {
 	case DocumentType:
-		node := dst.(*DocumentNode)
+		node, _ := dst.(*DocumentNode)
 		return Merge(node.Body, src)
 	case MappingType:
-		node := dst.(*MappingNode)
+		node, _ := dst.(*MappingNode)
 		target, ok := src.(*MappingNode)
 		if !ok {
 			return err
@@ -2103,7 +2104,7 @@ func Merge(dst Node, src Node) error {
 		node.Merge(target)
 		return nil
 	case SequenceType:
-		node := dst.(*SequenceNode)
+		node, _ := dst.(*SequenceNode)
 		target, ok := src.(*SequenceNode)
 		if !ok {
 			return err
diff --git a/cmd/ycat/ycat.go b/cmd/ycat/ycat.go
index dbac4df..b810b6f 100644
--- a/cmd/ycat/ycat.go
+++ b/cmd/ycat/ycat.go
@@ -6,10 +6,11 @@ import (
 	"os"
 
 	"github.com/fatih/color"
+	"github.com/mattn/go-colorable"
+
 	"github.com/goccy/go-yaml"
 	"github.com/goccy/go-yaml/lexer"
 	"github.com/goccy/go-yaml/printer"
-	"github.com/mattn/go-colorable"
 )
 
 const escape = "\x1b"
@@ -71,7 +72,7 @@ func _main(args []string) error {
 		}
 	}
 	writer := colorable.NewColorableStdout()
-	writer.Write([]byte(p.PrintTokens(tokens) + "\n"))
+	_, _ = writer.Write([]byte(p.PrintTokens(tokens) + "\n"))
 	return nil
 }
 
diff --git a/decode.go b/decode.go
index 72af5e2..ce2c751 100644
--- a/decode.go
+++ b/decode.go
@@ -100,7 +100,7 @@ func (d *Decoder) castToFloat(v interface{}) interface{} {
 
 func (d *Decoder) mergeValueNode(value ast.Node) ast.Node {
 	if value.Type() == ast.AliasType {
-		aliasNode := value.(*ast.AliasNode)
+		aliasNode, _ := value.(*ast.AliasNode)
 		aliasName := aliasNode.Value.GetToken().Value
 		return d.anchorNodeMap[aliasName]
 	}
@@ -360,7 +360,7 @@ func (d *Decoder) resolveAlias(node ast.Node) (ast.Node, error) {
 			if err != nil {
 				return nil, err
 			}
-			n.Values[idx] = value.(*ast.MappingValueNode)
+			n.Values[idx], _ = value.(*ast.MappingValueNode)
 		}
 	case *ast.TagNode:
 		value, err := d.resolveAlias(n.Value)
@@ -389,7 +389,7 @@ func (d *Decoder) resolveAlias(node ast.Node) (ast.Node, error) {
 			if err != nil {
 				return nil, err
 			}
-			n.Key = key.(ast.MapKeyNode)
+			n.Key, _ = key.(ast.MapKeyNode)
 			value, err := d.resolveAlias(n.Value)
 			if err != nil {
 				return nil, err
@@ -476,15 +476,6 @@ func (d *Decoder) getArrayNode(node ast.Node) (ast.ArrayNode, error) {
 	return arrayNode, nil
 }
 
-func (d *Decoder) fileToNode(f *ast.File) ast.Node {
-	for _, doc := range f.Docs {
-		if v := d.nodeToValue(doc.Body); v != nil {
-			return doc.Body
-		}
-	}
-	return nil
-}
-
 func (d *Decoder) convertValue(v reflect.Value, typ reflect.Type, src ast.Node) (reflect.Value, error) {
 	if typ.Kind() != reflect.String {
 		if !v.Type().ConvertibleTo(typ) {
@@ -590,7 +581,7 @@ func (d *Decoder) deleteStructKeys(structType reflect.Type, unknownFields map[st
 		}
 
 		if structField.IsInline {
-			d.deleteStructKeys(field.Type, unknownFields)
+			_ = d.deleteStructKeys(field.Type, unknownFields)
 		} else {
 			delete(unknownFields, structField.RenderName)
 		}
@@ -989,18 +980,22 @@ func (d *Decoder) createDecodedNewValue(
 			return newValue, nil
 		}
 	}
+	var newValue reflect.Value
 	if node.Type() == ast.NullType {
-		return reflect.Zero(typ), nil
+		newValue = reflect.New(typ).Elem()
+	} else {
+		newValue = d.createDecodableValue(typ)
 	}
-	newValue := d.createDecodableValue(typ)
 	for defaultVal.Kind() == reflect.Ptr {
 		defaultVal = defaultVal.Elem()
 	}
 	if defaultVal.IsValid() && defaultVal.Type().AssignableTo(newValue.Type()) {
 		newValue.Set(defaultVal)
 	}
-	if err := d.decodeValue(ctx, newValue, node); err != nil {
-		return newValue, errors.Wrapf(err, "failed to decode value")
+	if node.Type() != ast.NullType {
+		if err := d.decodeValue(ctx, newValue, node); err != nil {
+			return newValue, errors.Wrapf(err, "failed to decode value")
+		}
 	}
 	return newValue, nil
 }
@@ -1271,7 +1266,7 @@ func (d *Decoder) decodeStruct(ctx context.Context, dst reflect.Value, src ast.N
 				}
 				continue
 			}
-			d.setDefaultValueIfConflicted(newFieldValue, structFieldMap)
+			_ = d.setDefaultValueIfConflicted(newFieldValue, structFieldMap)
 			fieldValue.Set(d.castToAssignableValue(newFieldValue, fieldValue.Type()))
 			continue
 		}
@@ -1619,13 +1614,13 @@ func (d *Decoder) readersUnderDir(dir string) ([]io.Reader, error) {
 
 func (d *Decoder) readersUnderDirRecursive(dir string) ([]io.Reader, error) {
 	readers := []io.Reader{}
-	if err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
+	if err := filepath.Walk(dir, func(path string, info os.FileInfo, _ error) error {
 		if !d.isYAMLFile(path) {
 			return nil
 		}
-		reader, err := d.fileToReader(path)
-		if err != nil {
-			return errors.Wrapf(err, "failed to get reader")
+		reader, readerErr := d.fileToReader(path)
+		if readerErr != nil {
+			return errors.Wrapf(readerErr, "failed to get reader")
 		}
 		readers = append(readers, reader)
 		return nil
diff --git a/encode.go b/encode.go
index 3b9b298..f54040f 100644
--- a/encode.go
+++ b/encode.go
@@ -12,12 +12,13 @@ import (
 	"strings"
 	"time"
 
+	"golang.org/x/xerrors"
+
 	"github.com/goccy/go-yaml/ast"
 	"github.com/goccy/go-yaml/internal/errors"
 	"github.com/goccy/go-yaml/parser"
 	"github.com/goccy/go-yaml/printer"
 	"github.com/goccy/go-yaml/token"
-	"golang.org/x/xerrors"
 )
 
 const (
@@ -93,10 +94,10 @@ func (e *Encoder) EncodeContext(ctx context.Context, v interface{}) error {
 		e.written = true
 	} else {
 		// write document separator
-		e.writer.Write([]byte("---\n"))
+		_, _ = e.writer.Write([]byte("---\n"))
 	}
 	var p printer.Printer
-	e.writer.Write(p.PrintNode(node))
+	_, _ = e.writer.Write(p.PrintNode(node))
 	return nil
 }
 
diff --git a/error.go b/error.go
index 163dcc5..26935eb 100644
--- a/error.go
+++ b/error.go
@@ -1,8 +1,9 @@
 package yaml
 
 import (
-	"github.com/goccy/go-yaml/ast"
 	"golang.org/x/xerrors"
+
+	"github.com/goccy/go-yaml/ast"
 )
 
 var (
diff --git a/internal/errors/error.go b/internal/errors/error.go
index 7f1ea9a..1a137c8 100644
--- a/internal/errors/error.go
+++ b/internal/errors/error.go
@@ -5,9 +5,10 @@ import (
 	"fmt"
 	"reflect"
 
+	"golang.org/x/xerrors"
+
 	"github.com/goccy/go-yaml/printer"
 	"github.com/goccy/go-yaml/token"
-	"golang.org/x/xerrors"
 )
 
 const (
@@ -119,7 +120,7 @@ func (e *wrapError) FormatError(p xerrors.Printer) error {
 	}
 	e.chainStateAndVerb(err)
 	if fmtErr, ok := err.(xerrors.Formatter); ok {
-		fmtErr.FormatError(p)
+		_ = fmtErr.FormatError(p)
 	} else {
 		p.Print(err)
 	}
@@ -143,6 +144,7 @@ func (s *wrapState) Precision() (prec int, ok bool) {
 }
 
 func (s *wrapState) Flag(c int) bool {
+	//nolint:gosimple
 	// set true to 'printDetail' forced because when p.Detail() is false, xerrors.Printer no output any text
 	if c == '#' {
 		// ignore '#' keyword because xerrors.FormatError doesn't set true to printDetail.
diff --git a/parser/context.go b/parser/context.go
index 42cc4f8..c4752f9 100644
--- a/parser/context.go
+++ b/parser/context.go
@@ -110,13 +110,6 @@ func (c *context) nextToken() *token.Token {
 	return c.tokens[c.idx+1]
 }
 
-func (c *context) afterNextToken() *token.Token {
-	if c.idx+2 >= c.size {
-		return nil
-	}
-	return c.tokens[c.idx+2]
-}
-
 func (c *context) nextNotCommentToken() *token.Token {
 	for i := c.idx + 1; i < c.size; i++ {
 		tk := c.tokens[i]
diff --git a/parser/parser.go b/parser/parser.go
index 2bec5fe..09b3885 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -5,11 +5,12 @@ import (
 	"os"
 	"strings"
 
+	"golang.org/x/xerrors"
+
 	"github.com/goccy/go-yaml/ast"
 	"github.com/goccy/go-yaml/internal/errors"
 	"github.com/goccy/go-yaml/lexer"
 	"github.com/goccy/go-yaml/token"
-	"golang.org/x/xerrors"
 )
 
 type parser struct{}
@@ -175,7 +176,7 @@ func (p *parser) createMapValueNode(ctx *context, key ast.MapKeyNode, colonToken
 		nullNode := ast.Null(nullToken)
 
 		if comment != nil {
-			nullNode.SetComment(comment)
+			_ = nullNode.SetComment(comment)
 		} else {
 			// If there is a comment, it is already bound to the key node,
 			// so remove the comment from the key to bind it to the null value.
@@ -184,7 +185,7 @@ func (p *parser) createMapValueNode(ctx *context, key ast.MapKeyNode, colonToken
 				if err := key.SetComment(nil); err != nil {
 					return nil, err
 				}
-				nullNode.SetComment(keyComment)
+				_ = nullNode.SetComment(keyComment)
 			}
 		}
 		return nullNode, nil
@@ -199,7 +200,7 @@ func (p *parser) createMapValueNode(ctx *context, key ast.MapKeyNode, colonToken
 		ctx.insertToken(ctx.idx, nullToken)
 		nullNode := ast.Null(nullToken)
 		if comment != nil {
-			nullNode.SetComment(comment)
+			_ = nullNode.SetComment(comment)
 		}
 		return nullNode, nil
 	}
@@ -209,7 +210,7 @@ func (p *parser) createMapValueNode(ctx *context, key ast.MapKeyNode, colonToken
 		return nil, errors.Wrapf(err, "failed to parse mapping 'value' node")
 	}
 	if comment != nil {
-		value.SetComment(comment)
+		_ = value.SetComment(comment)
 	}
 	return value, nil
 }
@@ -279,7 +280,7 @@ func (p *parser) parseMappingValue(ctx *context) (ast.Node, error) {
 		}
 		switch value.Type() {
 		case ast.MappingType:
-			c := value.(*ast.MappingNode)
+			c, _ := value.(*ast.MappingNode)
 			comment := c.GetComment()
 			for idx, v := range c.Values {
 				if idx == 0 && comment != nil {
diff --git a/path.go b/path.go
index b79c666..010cc12 100644
--- a/path.go
+++ b/path.go
@@ -534,7 +534,7 @@ func (n *selectorNode) filter(node ast.Node) (ast.Node, error) {
 			}
 		}
 	case ast.MappingValueType:
-		value := node.(*ast.MappingValueNode)
+		value, _ := node.(*ast.MappingValueNode)
 		key := value.Key.GetToken().Value
 		if key == selector {
 			if n.child == nil {
@@ -578,7 +578,7 @@ func (n *selectorNode) replace(node ast.Node, target ast.Node) error {
 			}
 		}
 	case ast.MappingValueType:
-		value := node.(*ast.MappingValueNode)
+		value, _ := node.(*ast.MappingValueNode)
 		if err := n.replaceMapValue(value, target); err != nil {
 			return errors.Wrapf(err, "failed to replace map value")
 		}
@@ -614,7 +614,7 @@ func (n *indexNode) filter(node ast.Node) (ast.Node, error) {
 	if node.Type() != ast.SequenceType {
 		return nil, errors.Wrapf(ErrInvalidQuery, "expected sequence type node. but got %s", node.Type())
 	}
-	sequence := node.(*ast.SequenceNode)
+	sequence, _ := node.(*ast.SequenceNode)
 	if n.selector >= uint(len(sequence.Values)) {
 		return nil, errors.Wrapf(ErrInvalidQuery, "expected index is %d. but got sequences has %d items", n.selector, sequence.Values)
 	}
@@ -633,7 +633,7 @@ func (n *indexNode) replace(node ast.Node, target ast.Node) error {
 	if node.Type() != ast.SequenceType {
 		return errors.Wrapf(ErrInvalidQuery, "expected sequence type node. but got %s", node.Type())
 	}
-	sequence := node.(*ast.SequenceNode)
+	sequence, _ := node.(*ast.SequenceNode)
 	if n.selector >= uint(len(sequence.Values)) {
 		return errors.Wrapf(ErrInvalidQuery, "expected index is %d. but got sequences has %d items", n.selector, sequence.Values)
 	}
@@ -679,7 +679,7 @@ func (n *indexAllNode) filter(node ast.Node) (ast.Node, error) {
 	if node.Type() != ast.SequenceType {
 		return nil, errors.Wrapf(ErrInvalidQuery, "expected sequence type node. but got %s", node.Type())
 	}
-	sequence := node.(*ast.SequenceNode)
+	sequence, _ := node.(*ast.SequenceNode)
 	if n.child == nil {
 		return sequence, nil
 	}
@@ -699,7 +699,7 @@ func (n *indexAllNode) replace(node ast.Node, target ast.Node) error {
 	if node.Type() != ast.SequenceType {
 		return errors.Wrapf(ErrInvalidQuery, "expected sequence type node. but got %s", node.Type())
 	}
-	sequence := node.(*ast.SequenceNode)
+	sequence, _ := node.(*ast.SequenceNode)
 	if n.child == nil {
 		for idx := range sequence.Values {
 			if err := sequence.Replace(idx, target); err != nil {
diff --git a/printer/printer.go b/printer/printer.go
index d5e25dc..c714812 100644
--- a/printer/printer.go
+++ b/printer/printer.go
@@ -6,6 +6,7 @@ import (
 	"strings"
 
 	"github.com/fatih/color"
+
 	"github.com/goccy/go-yaml/ast"
 	"github.com/goccy/go-yaml/token"
 )
@@ -29,6 +30,7 @@ type Printer struct {
 	Bool             PrintFunc
 	String           PrintFunc
 	Number           PrintFunc
+	Comment          PrintFunc
 }
 
 func defaultLineNumberFormat(num int) string {
@@ -82,6 +84,11 @@ func (p *Printer) property(tk *token.Token) *Property {
 			return p.Number()
 		}
 		return prop
+	case token.CommentType:
+		if p.Comment != nil {
+			return p.Comment()
+		}
+		return prop
 	default:
 	}
 	return prop
diff --git a/scanner/context.go b/scanner/context.go
index 3aaec56..5f92a6e 100644
--- a/scanner/context.go
+++ b/scanner/context.go
@@ -42,7 +42,7 @@ func createContext() *Context {
 }
 
 func newContext(src []rune) *Context {
-	ctx := ctxPool.Get().(*Context)
+	ctx, _ := ctxPool.Get().(*Context)
 	ctx.reset(src)
 	return ctx
 }
diff --git a/scanner/scanner.go b/scanner/scanner.go
index 77acb41..0c7596a 100644
--- a/scanner/scanner.go
+++ b/scanner/scanner.go
@@ -142,7 +142,7 @@ func (s *Scanner) newLineCount(src []rune) int {
 }
 
 func (s *Scanner) updateIndentState(ctx *Context) {
-	indentNumBasedIndentState := s.indentState
+	var indentNumBasedIndentState IndentState
 	if s.prevIndentNum < s.indentNum {
 		s.indentLevel = s.prevIndentLevel + 1
 		indentNumBasedIndentState = IndentStateUp
@@ -164,7 +164,7 @@ func (s *Scanner) updateIndentState(ctx *Context) {
 			// - prevIndentColumn: 1 of 'a'
 			// - indentNumBasedIndentState: IndentStateDown because d's indentNum(1) is less than c's indentNum(3).
 			// Therefore, s.prevIndentColumn(1) == s.column(1) is true, but we want to treat this as IndentStateDown.
-			// So, we look also current indentState value by the above prevIndentNum based logic, and determins finally indentState.
+			// So, we look also current indentState value by the above prevIndentNum based logic, and determines finally indentState.
 			// ---
 			// a:
 			//   b
@@ -470,7 +470,6 @@ func (s *Scanner) scanComment(ctx *Context) (tk *token.Token, pos int) {
 	ctx.addOriginBuf('#')
 	ctx.progress(1) // skip '#' character
 	for idx, c := range ctx.src[ctx.idx:] {
-		pos = idx + 1
 		ctx.addOriginBuf(c)
 		switch c {
 		case '\n', '\r':
@@ -588,7 +587,6 @@ func (s *Scanner) scanLiteralHeader(ctx *Context) (pos int, err error) {
 				ctx.literalOpt = opt
 				return
 			}
-			break
 		}
 	}
 	err = xerrors.New("invalid literal header")
diff --git a/struct.go b/struct.go
index a3da8dd..9d251f3 100644
--- a/struct.go
+++ b/struct.go
@@ -84,11 +84,7 @@ func isIgnoredStructField(field reflect.StructField) bool {
 		// private field
 		return true
 	}
-	tag := getTag(field)
-	if tag == "-" {
-		return true
-	}
-	return false
+	return getTag(field) == "-"
 }
 
 type StructFieldMap map[string]*StructField
diff --git a/token/token.go b/token/token.go
index 14d7622..bc8b531 100644
--- a/token/token.go
+++ b/token/token.go
@@ -339,9 +339,12 @@ func reservedKeywordToken(typ Type, value, org string, pos *Position) *Token {
 
 func init() {
 	for _, keyword := range reservedNullKeywords {
-		reservedKeywordMap[keyword] = func(value, org string, pos *Position) *Token {
+		f := func(value, org string, pos *Position) *Token {
 			return reservedKeywordToken(NullType, value, org, pos)
 		}
+
+		reservedKeywordMap[keyword] = f
+		reservedEncKeywordMap[keyword] = f
 	}
 	for _, keyword := range reservedBoolKeywords {
 		f := func(value, org string, pos *Position) *Token {
diff --git a/yaml.go b/yaml.go
index 25b1056..38214a8 100644
--- a/yaml.go
+++ b/yaml.go
@@ -7,9 +7,10 @@ import (
 	"reflect"
 	"sync"
 
+	"golang.org/x/xerrors"
+
 	"github.com/goccy/go-yaml/ast"
 	"github.com/goccy/go-yaml/internal/errors"
-	"golang.org/x/xerrors"
 )
 
 // BytesMarshaler interface may be implemented by types to customize their
@@ -80,11 +81,11 @@ func (s MapSlice) ToMap() map[interface{}]interface{} {
 // of the generated document will reflect the structure of the value itself.
 // Maps and pointers (to struct, string, int, etc) are accepted as the in value.
 //
-// Struct fields are only marshalled if they are exported (have an upper case
-// first letter), and are marshalled using the field name lowercased as the
+// Struct fields are only marshaled if they are exported (have an upper case
+// first letter), and are marshaled using the field name lowercased as the
 // default key. Custom keys may be defined via the "yaml" name in the field
 // tag: the content preceding the first comma is used as the key, and the
-// following comma-separated options are used to tweak the marshalling process.
+// following comma-separated options are used to tweak the marshaling process.
 // Conflicting names result in a runtime error.
 //
 // The field tag format accepted is:
@@ -161,7 +162,7 @@ func ValueToNode(v interface{}, opts ...EncodeOption) (ast.Node, error) {
 // lowercased as the default key. Custom keys may be defined via the
 // "yaml" name in the field tag: the content preceding the first comma
 // is used as the key, and the following comma-separated options are
-// used to tweak the marshalling process (see Marshal).
+// used to tweak the marshaling process (see Marshal).
 // Conflicting names result in a runtime error.
 //
 // For example:
@@ -216,7 +217,7 @@ func FormatError(e error, colored, inclSource bool) string {
 	var pp errors.PrettyPrinter
 	if xerrors.As(e, &pp) {
 		var buf bytes.Buffer
-		pp.PrettyPrint(&errors.Sink{&buf}, colored, inclSource)
+		pp.PrettyPrint(&errors.Sink{Buffer: &buf}, colored, inclSource)
 		return buf.String()
 	}
 


Test patch:
diff --git a/decode_test.go b/decode_test.go
index f0b0c08..cff8640 100644
--- a/decode_test.go
+++ b/decode_test.go
@@ -592,11 +592,11 @@ func TestDecoder(t *testing.T) {
 		},
 		{
 			"v: [A,B,C,]",
-			map[string][]string{"v": []string{"A", "B", "C"}},
+			map[string][]string{"v": {"A", "B", "C"}},
 		},
 		{
 			"v: [A,1,C]",
-			map[string][]string{"v": []string{"A", "1", "C"}},
+			map[string][]string{"v": {"A", "1", "C"}},
 		},
 		{
 			"v: [A,1,C]",
@@ -610,11 +610,11 @@ func TestDecoder(t *testing.T) {
 		},
 		{
 			"v:\n - A\n - B\n - C",
-			map[string][]string{"v": []string{"A", "B", "C"}},
+			map[string][]string{"v": {"A", "B", "C"}},
 		},
 		{
 			"v:\n - A\n - 1\n - C",
-			map[string][]string{"v": []string{"A", "1", "C"}},
+			map[string][]string{"v": {"A", "1", "C"}},
 		},
 		{
 			"v:\n - A\n - 1\n - C",
@@ -1470,7 +1470,7 @@ items:
 		if err := dec.Decode(&v); err != nil {
 			t.Fatalf("%+v", err)
 		}
-		items := v.(map[string]interface{})["items"].([]interface{})
+		items, _ := v.(map[string]interface{})["items"].([]interface{})
 		if len(items) != 2 {
 			t.Fatal("failed to decode with merge key")
 		}
@@ -2152,16 +2152,16 @@ func Example_DisallowUnknownField() {
 
 	const src = `---
 simple: string
-complecated: string
+unknown: string
 `
 	err := yaml.NewDecoder(strings.NewReader(src), yaml.DisallowUnknownField()).Decode(&v)
 	fmt.Printf("%v\n", err)
 
 	// OUTPUT:
-	// [3:1] unknown field "complecated"
+	// [3:1] unknown field "unknown"
 	//        1 | ---
 	//        2 | simple: string
-	//     >  3 | complecated: string
+	//     >  3 | unknown: string
 	//            ^
 }
 
@@ -2712,22 +2712,22 @@ func TestDecoder_LiteralWithNewLine(t *testing.T) {
 		LastNode string `yaml:"last"`
 	}
 	tests := []A{
-		A{
+		{
 			Node: "hello\nworld",
 		},
-		A{
+		{
 			Node: "hello\nworld\n",
 		},
-		A{
+		{
 			Node: "hello\nworld\n\n",
 		},
-		A{
+		{
 			LastNode: "hello\nworld",
 		},
-		A{
+		{
 			LastNode: "hello\nworld\n",
 		},
-		A{
+		{
 			LastNode: "hello\nworld\n\n",
 		},
 	}
@@ -3027,3 +3027,28 @@ func TestMapKeyCustomUnmarshaler(t *testing.T) {
 		t.Fatalf("expected to have value \"value\", but got %q", val)
 	}
 }
+
+func TestDecoderPreservesDefaultValues(t *testing.T) {
+	type nested struct {
+		Val string `yaml:"val"`
+	}
+
+	type test struct {
+		First   string `yaml:"first"`
+		Default nested `yaml:"nested"`
+	}
+
+	yml := `
+first: "Test"
+nested:
+  # Just some comment here
+#  val: "default"
+`
+	v := test{Default: nested{Val: "default"}}
+	if err := yaml.Unmarshal([]byte(yml), &v); err != nil {
+		t.Fatal(err)
+	}
+	if v.Default.Val != "default" {
+		t.Fatal("decoder doesn't preserve struct defaults")
+	}
+}
diff --git a/encode_test.go b/encode_test.go
index 74388b9..7c55269 100644
--- a/encode_test.go
+++ b/encode_test.go
@@ -10,10 +10,9 @@ import (
 	"testing"
 	"time"
 
-	"github.com/goccy/go-yaml/parser"
-
 	"github.com/goccy/go-yaml"
 	"github.com/goccy/go-yaml/ast"
+	"github.com/goccy/go-yaml/parser"
 )
 
 var zero = 0
@@ -683,12 +682,12 @@ func TestEncoder(t *testing.T) {
 		// time value
 		{
 			"v: 0001-01-01T00:00:00Z\n",
-			map[string]time.Time{"v": time.Time{}},
+			map[string]time.Time{"v": {}},
 			nil,
 		},
 		{
 			"v: 0001-01-01T00:00:00Z\n",
-			map[string]*time.Time{"v": &time.Time{}},
+			map[string]*time.Time{"v": {}},
 			nil,
 		},
 		{
@@ -1078,7 +1077,7 @@ func TestEncoder_FlowRecursive(t *testing.T) {
 		M map[string][]int `yaml:",flow"`
 	}
 	v.M = map[string][]int{
-		"test": []int{1, 2, 3},
+		"test": {1, 2, 3},
 	}
 	var buf bytes.Buffer
 	if err := yaml.NewEncoder(&buf).Encode(v); err != nil {
@@ -1188,7 +1187,7 @@ func TestEncoder_MarshalAnchor(t *testing.T) {
 	hostIdx := 1
 	opt := yaml.MarshalAnchor(func(anchor *ast.AnchorNode, value interface{}) error {
 		if _, ok := value.(*Host); ok {
-			nameNode := anchor.Name.(*ast.StringNode)
+			nameNode, _ := anchor.Name.(*ast.StringNode)
 			nameNode.Value = fmt.Sprintf("host%d", hostIdx)
 			hostIdx++
 		}
diff --git a/token/token_test.go b/token/token_test.go
index dc1b4df..27565fb 100644
--- a/token/token_test.go
+++ b/token/token_test.go
@@ -121,6 +121,10 @@ func TestIsNeedQuoted(t *testing.T) {
 		" a",
 		" a ",
 		"a ",
+		"null",
+		"Null",
+		"NULL",
+		"~",
 	}
 	for i, test := range needQuotedTests {
 		if !token.IsNeedQuoted(test) {
diff --git a/validate_test.go b/validate_test.go
index 265deb8..d73ee3a 100644
--- a/validate_test.go
+++ b/validate_test.go
@@ -5,6 +5,7 @@ import (
 	"testing"
 
 	"github.com/go-playground/validator/v10"
+
 	"github.com/goccy/go-yaml"
 )
 
diff --git a/yaml_test.go b/yaml_test.go
index 4446d31..61f5327 100644
--- a/yaml_test.go
+++ b/yaml_test.go
@@ -325,7 +325,7 @@ b: *a
 	if len(anchors) != 1 {
 		t.Fatal("failed to filter node")
 	}
-	anchor := anchors[0].(*ast.AnchorNode)
+	anchor, _ := anchors[0].(*ast.AnchorNode)
 	if err := anchor.SetName("b"); err != nil {
 		t.Fatal(err)
 	}
@@ -333,7 +333,7 @@ b: *a
 	if len(anchors) != 1 {
 		t.Fatal("failed to filter node")
 	}
-	alias := aliases[0].(*ast.AliasNode)
+	alias, _ := aliases[0].(*ast.AliasNode)
 	if err := alias.SetName("b"); err != nil {
 		t.Fatal(err)
 	}