Repository: goccy/go-yaml
Pull request #470 (2024-07-29 15:00:50)

Refactor overflowError to be pretty-printable
When trying to yaml.Unmarshal a negative number into an uint, the resulting error indicates an overflow with type information, but without the position. For example:

```
cannot unmarshal -23 into Go value of type uint64 ( overflow )
```

From an end user's perspective reading an error for an invalid YAML, this is quite unintuitive. Especially compared to the error message when trying to unmarshal a string into an uint.

```
[1:4] cannot unmarshal string into Go struct field Foo.A of type uint64
    >  1 | a: 'foo'
              ^
```

This change has moved overflowError to internal.errors.overflowError, implementing both the PrettyPrinter and Formatter. Its implementation is uniform with those of the syntaxError and TypeError.
Thus, the error from above now looks like:

```
[1:4] cannot unmarshal -23 into Go value of type uint64 ( overflow )
    >  1 | a: -23
              ^
```

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/470?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Masaaki+Goshima) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 78.80%. 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 [(`525e201`)](https://app.codecov.io/gh/goccy/go-yaml/commit/525e201a2fc9795a0ac87d6de92158a9339401f3?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     #470      +/-   ##
==========================================
+ Coverage   78.77%   78.80%   +0.02%     
==========================================
  Files          13       13              
  Lines        4004     4000       -4     
==========================================
- Hits         3154     3152       -2     
+ Misses        591      589       -2     
  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 04b94bc..b159e49 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) {
@@ -524,19 +515,6 @@ func (d *Decoder) convertValue(v reflect.Value, typ reflect.Type, src ast.Node)
 	return v.Convert(typ), nil
 }
 
-type overflowError struct {
-	dstType reflect.Type
-	srcNum  string
-}
-
-func (e *overflowError) Error() string {
-	return fmt.Sprintf("cannot unmarshal %s into Go value of type %s ( overflow )", e.srcNum, e.dstType)
-}
-
-func errOverflow(dstType reflect.Type, num string) *overflowError {
-	return &overflowError{dstType: dstType, srcNum: num}
-}
-
 func errTypeMismatch(dstType, srcType reflect.Type, token *token.Token) *errors.TypeError {
 	return &errors.TypeError{DstType: dstType, SrcType: srcType, Token: token}
 }
@@ -590,7 +568,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)
 		}
@@ -904,7 +882,7 @@ func (d *Decoder) decodeValue(ctx context.Context, dst reflect.Value, src ast.No
 		default:
 			return errTypeMismatch(valueType, reflect.TypeOf(v), src.GetToken())
 		}
-		return errOverflow(valueType, fmt.Sprint(v))
+		return errors.ErrOverflow(valueType, fmt.Sprint(v), src.GetToken())
 	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
 		v := d.nodeToValue(src)
 		switch vv := v.(type) {
@@ -936,7 +914,7 @@ func (d *Decoder) decodeValue(ctx context.Context, dst reflect.Value, src ast.No
 		default:
 			return errTypeMismatch(valueType, reflect.TypeOf(v), src.GetToken())
 		}
-		return errOverflow(valueType, fmt.Sprint(v))
+		return errors.ErrOverflow(valueType, fmt.Sprint(v), src.GetToken())
 	}
 	v := reflect.ValueOf(d.nodeToValue(src))
 	if v.IsValid() {
@@ -1275,7 +1253,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
 		}
@@ -1623,13 +1601,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..c654bd5 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 (
@@ -39,6 +40,11 @@ func ErrSyntax(msg string, tk *token.Token) *syntaxError {
 	}
 }
 
+// ErrOverflow creates an overflow error instance with message and a token.
+func ErrOverflow(dstType reflect.Type, num string, tk *token.Token) *overflowError {
+	return &overflowError{dstType: dstType, srcNum: num, token: tk}
+}
+
 type baseError struct {
 	state fmt.State
 	verb  rune
@@ -119,7 +125,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 +149,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.
@@ -164,6 +171,39 @@ func (e *wrapError) Error() string {
 	return buf.String()
 }
 
+type overflowError struct {
+	dstType reflect.Type
+	srcNum  string
+	token   *token.Token
+}
+
+func (e *overflowError) Error() string {
+	return fmt.Sprintf("cannot unmarshal %s into Go value of type %s ( overflow )", e.srcNum, e.dstType)
+}
+
+func (e *overflowError) PrettyPrint(p xerrors.Printer, colored, inclSource bool) error {
+	return e.FormatError(&FormatErrorPrinter{Printer: p, Colored: colored, InclSource: inclSource})
+}
+
+func (e *overflowError) FormatError(p xerrors.Printer) error {
+	var pp printer.Printer
+
+	var colored, inclSource bool
+	if fep, ok := p.(*FormatErrorPrinter); ok {
+		colored = fep.Colored
+		inclSource = fep.InclSource
+	}
+
+	pos := fmt.Sprintf("[%d:%d] ", e.token.Position.Line, e.token.Position.Column)
+	msg := pp.PrintErrorMessage(fmt.Sprintf("%s%s", pos, e.Error()), colored)
+	if inclSource {
+		msg += "\n" + pp.PrintErrorToken(e.token, colored)
+	}
+	p.Print(msg)
+
+	return nil
+}
+
 type syntaxError struct {
 	*baseError
 	msg   string
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..f638a99 100644
--- a/scanner/scanner.go
+++ b/scanner/scanner.go
@@ -26,18 +26,24 @@ const (
 // Scanner holds the scanner's internal state while processing a given text.
 // It can be allocated as part of another data structure but must be initialized via Init before use.
 type Scanner struct {
-	source                 []rune
-	sourcePos              int
-	sourceSize             int
-	line                   int
-	column                 int
-	offset                 int
-	prevIndentLevel        int
-	prevIndentNum          int
-	prevIndentColumn       int
-	docStartColumn         int
+	source     []rune
+	sourcePos  int
+	sourceSize int
+	// line number. This number starts from 1.
+	line int
+	// column number. This number starts from 1.
+	column int
+	// offset represents the offset from the beginning of the source.
+	offset int
+	// lastDelimColumn is the last column needed to compare indent is retained.
+	lastDelimColumn int
+	// indentNum indicates the number of spaces used for indentation.
+	indentNum int
+	// prevLineIndentNum indicates the number of spaces used for indentation at previous line.
+	prevLineIndentNum int
+	// indentLevel indicates the level of indent depth. This value does not match the column value.
 	indentLevel            int
-	indentNum              int
+	docStartColumn         int
 	isFirstCharAtLine      bool
 	isAnchor               bool
 	startedFlowSequenceNum int
@@ -91,6 +97,7 @@ func (s *Scanner) progressColumn(ctx *Context, num int) {
 }
 
 func (s *Scanner) progressLine(ctx *Context) {
+	s.prevLineIndentNum = s.indentNum
 	s.column = 1
 	s.line++
 	s.offset++
@@ -100,19 +107,6 @@ func (s *Scanner) progressLine(ctx *Context) {
 	ctx.progress(1)
 }
 
-func (s *Scanner) isNeededKeepPreviousIndentNum(ctx *Context, c rune) bool {
-	if !s.isChangedToIndentStateUp() {
-		return false
-	}
-	if ctx.isDocument() {
-		return true
-	}
-	if c == '-' && ctx.existsBuffer() {
-		return true
-	}
-	return false
-}
-
 func (s *Scanner) isNewLineChar(c rune) bool {
 	if c == '\n' {
 		return true
@@ -141,30 +135,39 @@ func (s *Scanner) newLineCount(src []rune) int {
 	return cnt
 }
 
-func (s *Scanner) updateIndentState(ctx *Context) {
-	indentNumBasedIndentState := s.indentState
-	if s.prevIndentNum < s.indentNum {
-		s.indentLevel = s.prevIndentLevel + 1
-		indentNumBasedIndentState = IndentStateUp
-	} else if s.prevIndentNum == s.indentNum {
-		s.indentLevel = s.prevIndentLevel
-		indentNumBasedIndentState = IndentStateEqual
-	} else {
-		indentNumBasedIndentState = IndentStateDown
-		if s.prevIndentLevel > 0 {
-			s.indentLevel = s.prevIndentLevel - 1
+func (s *Scanner) updateIndentLevel() {
+	if s.prevLineIndentNum < s.indentNum {
+		s.indentLevel++
+	} else if s.prevLineIndentNum > s.indentNum {
+		if s.indentLevel > 0 {
+			s.indentLevel--
 		}
 	}
+}
+
+func (s *Scanner) indentStateFromIndentNumDifference() IndentState {
+	switch {
+	case s.prevLineIndentNum < s.indentNum:
+		return IndentStateUp
+	case s.prevLineIndentNum == s.indentNum:
+		return IndentStateEqual
+	default:
+		return IndentStateDown
+	}
+}
 
-	if s.prevIndentColumn > 0 {
-		if s.prevIndentColumn < s.column {
+func (s *Scanner) updateIndentState(ctx *Context) {
+	s.updateIndentLevel()
+
+	if s.lastDelimColumn > 0 {
+		if s.lastDelimColumn < s.column {
 			s.indentState = IndentStateUp
-		} else if s.prevIndentColumn != s.column || indentNumBasedIndentState != IndentStateEqual {
+		} else if s.lastDelimColumn != s.column || s.prevLineIndentNum != s.indentNum {
 			// The following case ( current position is 'd' ), some variables becomes like here
-			// - prevIndentColumn: 1 of 'a'
+			// - lastDelimColumn: 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.
+			// Therefore, s.lastDelimColumn(1) == s.column(1) is true, but we want to treat this as IndentStateDown.
+			// So, we look also current indentState value by the above prevLineIndentNum based logic, and determines finally indentState.
 			// ---
 			// a:
 			//   b
@@ -176,7 +179,7 @@ func (s *Scanner) updateIndentState(ctx *Context) {
 			s.indentState = IndentStateEqual
 		}
 	} else {
-		s.indentState = indentNumBasedIndentState
+		s.indentState = s.indentStateFromIndentNumDifference()
 	}
 }
 
@@ -194,14 +197,6 @@ func (s *Scanner) updateIndent(ctx *Context, c rune) {
 	}
 	s.updateIndentState(ctx)
 	s.isFirstCharAtLine = false
-	if s.isNeededKeepPreviousIndentNum(ctx, c) {
-		return
-	}
-	if s.indentState != IndentStateUp {
-		s.prevIndentColumn = 0
-	}
-	s.prevIndentNum = s.indentNum
-	s.prevIndentLevel = s.indentLevel
 }
 
 func (s *Scanner) isChangedToIndentStateDown() bool {
@@ -470,7 +465,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 +582,6 @@ func (s *Scanner) scanLiteralHeader(ctx *Context) (pos int, err error) {
 				ctx.literalOpt = opt
 				return
 			}
-			break
 		}
 	}
 	err = xerrors.New("invalid literal header")
@@ -642,7 +635,7 @@ func (s *Scanner) scan(ctx *Context) (pos int) {
 		c := ctx.currentChar()
 		s.updateIndent(ctx, c)
 		if ctx.isDocument() {
-			if s.isChangedToIndentStateEqual() ||
+			if (s.indentNum == 0 && s.isChangedToIndentStateEqual()) ||
 				s.isChangedToIndentStateDown() {
 				s.addBufferedTokenIfExists(ctx)
 				s.breakLiteral(ctx)
@@ -686,7 +679,7 @@ func (s *Scanner) scan(ctx *Context) (pos int) {
 			}
 		case '<':
 			if s.isMergeKey(ctx) {
-				s.prevIndentColumn = s.column
+				s.lastDelimColumn = s.column
 				ctx.addToken(token.MergeKey(string(ctx.obuf)+"<<", s.pos()))
 				s.progressColumn(ctx, 1)
 				pos++
@@ -720,7 +713,7 @@ func (s *Scanner) scan(ctx *Context) (pos int) {
 				s.addBufferedTokenIfExists(ctx)
 				ctx.addOriginBuf(c)
 				tk := token.SequenceEntry(string(ctx.obuf), s.pos())
-				s.prevIndentColumn = tk.Position.Column
+				s.lastDelimColumn = tk.Position.Column
 				ctx.addToken(tk)
 				s.progressColumn(ctx, 1)
 				return
@@ -756,13 +749,13 @@ func (s *Scanner) scan(ctx *Context) (pos int) {
 				// mapping value
 				tk := s.bufferedToken(ctx)
 				if tk != nil {
-					s.prevIndentColumn = tk.Position.Column
+					s.lastDelimColumn = tk.Position.Column
 					ctx.addToken(tk)
 				} else if tk := ctx.lastToken(); tk != nil {
 					// If the map key is quote, the buffer does not exist because it has already been cut into tokens.
 					// Therefore, we need to check the last token.
 					if tk.Indicator == token.QuotedScalarIndicator {
-						s.prevIndentColumn = tk.Position.Column
+						s.lastDelimColumn = tk.Position.Column
 					}
 				}
 				ctx.addToken(token.MappingValue(s.pos()))
@@ -880,9 +873,8 @@ func (s *Scanner) Init(text string) {
 	s.line = 1
 	s.column = 1
 	s.offset = 1
-	s.prevIndentLevel = 0
-	s.prevIndentNum = 0
-	s.prevIndentColumn = 0
+	s.prevLineIndentNum = 0
+	s.lastDelimColumn = 0
 	s.indentLevel = 0
 	s.indentNum = 0
 	s.isFirstCharAtLine = true
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 0d47a29..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",
 		},
 	}
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)
 	}